Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions include/swift/AST/Decl.h
Original file line number Diff line number Diff line change
Expand Up @@ -4624,6 +4624,8 @@ class AbstractStorageDecl : public ValueDecl {
/// True if any of the accessors to the storage is private or fileprivate.
bool hasPrivateAccessor() const;

bool hasDidSetOrWillSetDynamicReplacement() const;

// Implement isa/cast/dyncast/etc.
static bool classof(const Decl *D) {
return D->getKind() >= DeclKind::First_AbstractStorageDecl &&
Expand Down
8 changes: 8 additions & 0 deletions lib/AST/Decl.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -4443,6 +4443,14 @@ bool AbstractStorageDecl::hasPrivateAccessor() const {
return false;
}

bool AbstractStorageDecl::hasDidSetOrWillSetDynamicReplacement() const {
if (auto *func = getDidSetFunc())
return func->getAttrs().hasAttribute<DynamicReplacementAttr>();
if (auto *func = getWillSetFunc())
return func->getAttrs().hasAttribute<DynamicReplacementAttr>();
return false;
}

void AbstractStorageDecl::setAccessors(StorageImplInfo implInfo,
SourceLoc lbraceLoc,
ArrayRef<AccessorDecl *> accessors,
Expand Down
30 changes: 28 additions & 2 deletions lib/SILGen/SILGenType.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1094,6 +1094,28 @@ class SILGenExtension : public TypeMemberVisitor<SILGenExtension> {
SILGenType(SGM, ntd).emitType();
}
void visitFuncDecl(FuncDecl *fd) {
// Don't emit other accessors for a dynamic replacement of didSet inside of
// an extension. We only allow such a construct to allow definition of a
// didSet/willSet dynamic replacement. Emitting other accessors is
// problematic because there is no storage.
//
// extension SomeStruct {
// @_dynamicReplacement(for: someProperty)
// var replacement : Int {
// didSet {
// }
// }
// }
if (auto *accessor = dyn_cast<AccessorDecl>(fd)) {
auto *storage = accessor->getStorage();
bool hasDidSetOrWillSetDynamicReplacement =
storage->hasDidSetOrWillSetDynamicReplacement();

if (hasDidSetOrWillSetDynamicReplacement &&
isa<ExtensionDecl>(storage->getDeclContext()) &&
fd != storage->getDidSetFunc() && fd != storage->getWillSetFunc())
return;
}
SGM.emitFunction(fd);
if (SGM.requiresObjCMethodEntryPoint(fd))
SGM.emitObjCMethodThunk(fd);
Expand All @@ -1119,8 +1141,12 @@ class SILGenExtension : public TypeMemberVisitor<SILGenExtension> {

void visitVarDecl(VarDecl *vd) {
if (vd->hasStorage()) {
assert(vd->isStatic() && "stored property in extension?!");
return emitTypeMemberGlobalVariable(SGM, vd);
bool hasDidSetOrWillSetDynamicReplacement =
vd->hasDidSetOrWillSetDynamicReplacement();
assert((vd->isStatic() || hasDidSetOrWillSetDynamicReplacement) &&
"stored property in extension?!");
if (!hasDidSetOrWillSetDynamicReplacement)
return emitTypeMemberGlobalVariable(SGM, vd);
}
visitAbstractStorageDecl(vd);
}
Expand Down
3 changes: 2 additions & 1 deletion lib/Sema/TypeCheckAttr.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -2623,7 +2623,8 @@ void TypeChecker::addImplicitDynamicAttribute(Decl *D) {
if (auto *VD = dyn_cast<VarDecl>(D)) {
// Don't turn stored into computed properties. This could conflict with
// exclusivity checking.
if (VD->hasStorage())
// If there is a didSet or willSet function we allow dynamic replacement.
if (VD->hasStorage() && !VD->getDidSetFunc() && !VD->getWillSetFunc())
return;
// Don't add dynamic to local variables.
if (VD->getDeclContext()->isLocalContext())
Expand Down
3 changes: 2 additions & 1 deletion lib/Sema/TypeCheckDecl.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -2161,7 +2161,8 @@ class DeclChecker : public DeclVisitor<DeclChecker> {
TC.diagnose(VD->getLoc(), diag::enum_stored_property);
VD->markInvalid();
} else if (isa<ExtensionDecl>(VD->getDeclContext()) &&
!VD->isStatic()) {
!VD->isStatic() &&
!VD->getAttrs().getAttribute<DynamicReplacementAttr>()) {
TC.diagnose(VD->getLoc(), diag::extension_stored_property);
VD->markInvalid();
}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
struct Stored {
var i: Int {
didSet {
print("Stored.i.didSet from \(oldValue) to \(i) original")
}
}

var y: Int {
willSet {
print("Stored.y.willSet from \(y) to \(newValue) original")
}
}

var z: Int {
willSet {
print("Stored.z.willSet from \(z) to \(newValue) original")
}
didSet {
print("Stored.z.didSet from \(oldValue) to \(z) original")
}
}
}

var myglobal : Int = 1 {
didSet {
print("myglobal.didSet from \(oldValue) to \(myglobal) original")
}
}

var myglobal2 : Int = 1 {
willSet {
print("myglobal2.willSet from \(myglobal2) to \(newValue) original")
}
}

var myglobal3 : Int = 1 {
willSet {
print("myglobal3.willSet from \(myglobal3) to \(newValue) original")
}
didSet {
print("myglobal3.didSet from \(oldValue) to \(myglobal3) original")
}
}

class HeapStored {
var z: Int = 5{
willSet {
print("HeapStored.z.willSet from \(z) to \(newValue) original")
}
didSet {
print("HeapStored.z.didSet from \(oldValue) to \(z) original")
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
@_private(sourceFile: "dynamic_replacement_property_observer_orig.swift") import TestDidWillSet

extension Stored {
@_dynamicReplacement(for: i)
var _replacement_i: Int {
didSet {
print("Stored.i.didSet from \(oldValue) to \(i) replacement")
}
}
@_dynamicReplacement(for: y)
var _replacement_y: Int {
willSet {
print("Stored.y.willSet from \(y) to \(newValue) replacement")
}
}

@_dynamicReplacement(for: z)
var _replacement_z: Int {
willSet {
print("Stored.z.willSet from \(z) to \(newValue) replacement")
}
didSet {
print("Stored.z.didSet from \(oldValue) to \(z) replacement")
}
}
}

@_dynamicReplacement(for: myglobal)
public var _replacement_myglobal : Int = 1 {
didSet {
print("myglobal.didSet from \(oldValue) to \(myglobal) replacement")
}
}

@_dynamicReplacement(for: myglobal2)
var _replacement_myglobal2 : Int = 1 {
willSet {
print("myglobal2.willSet from \(myglobal2) to \(newValue) replacement")
}
}

@_dynamicReplacement(for: myglobal3)
var _replacement_myglobal3 : Int = 1 {
willSet {
print("myglobal3.willSet from \(myglobal3) to \(newValue) replacement")
}
didSet {
print("myglobal3.didSet from \(oldValue) to \(myglobal3) replacement")
}
}

extension HeapStored {
@_dynamicReplacement(for: z)
var _replacement_z: Int {
willSet {
print("HeapStored.z.willSet from \(z) to \(newValue) replacement")
}
didSet {
print("HeapStored.z.didSet from \(oldValue) to \(z) replacement")
}
}
}
87 changes: 87 additions & 0 deletions test/Interpreter/dynamicReplacement_property_observer.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,87 @@
// RUN: %empty-directory(%t)
// RUN: %target-build-swift-dylib(%t/%target-library-name(TestDidWillSet)) -module-name TestDidWillSet -emit-module -emit-module-path %t/TestDidWillSet.swiftmodule -swift-version 5 %S/Inputs/dynamic_replacement_property_observer_orig.swift -Xfrontend -enable-private-imports -Xfrontend -enable-implicit-dynamic
// RUN: %target-build-swift-dylib(%t/%target-library-name(TestDidWillSet2)) -I%t -L%t -lTestDidWillSet %target-rpath(%t) -module-name TestDidWillSet2 -swift-version 5 %S/Inputs/dynamic_replacement_property_observer_repl.swift
// RUN: %target-build-swift -I%t -L%t -lTestDidWillSet -o %t/main %target-rpath(%t) %s -swift-version 5
// RUN: %target-codesign %t/main %t/%target-library-name(TestDidWillSet) %t/%target-library-name(TestDidWillSet2)
// RUN: %target-run %t/main %t/%target-library-name(TestDidWillSet) %t/%target-library-name(TestDidWillSet)

// REQUIRES: executable_test

@_private(sourceFile: "dynamic_replacement_property_observer_orig.swift") import TestDidWillSet

#if os(macOS) || os(iOS) || os(tvOS) || os(watchOS)
import Darwin
#elseif os(Linux) || os(FreeBSD) || os(PS4) || os(Android) || os(Cygwin) || os(Haiku)
import Glibc
#elseif os(Windows)
import MSVCRT
import WinSDK
#else
#error("Unsupported platform")
#endif

private func target_library_name(_ name: String) -> String {
#if os(iOS) || os(macOS) || os(tvOS) || os(watchOS)
return "lib\(name).dylib"
#elseif os(Windows)
return "\(name).dll"
#else
return "lib\(name).so"
#endif
}

var s = Stored(i: 5, y: 5, z: 5)
var h = HeapStored()

// CHECK: Stored.i.didSet from 5 to 10 original
s.i = 10
// CHECK: Stored.y.willSet from 5 to 11 original
s.y = 11
// CHECK: Stored.z.willSet from 5 to 12 original
// CHECK: Stored.z.didSet from 5 to 12 original
s.z = 12

// CHECK: HeapStored.z.willSet from 5 to 16 original
// CHECK: HeapStored.z.didSet from 5 to 16 original
h.z = 16

// CHECK: myglobal.didSet from 1 to 13 original
myglobal = 13
// CHECK: myglobal2.willSet from 1 to 14 original
myglobal2 = 14
// CHECK: myglobal3.willSet from 1 to 15 original
// CHECK: myglobal3.didSet from 1 to 15 original
myglobal3 = 15

var executablePath = CommandLine.arguments[0]
executablePath.removeLast(4)

// Now, test with the module containing the replacements.

#if os(Linux)
_ = dlopen(target_library_name("Module2"), RTLD_NOW)
#elseif os(Windows)
_ = LoadLibraryA(target_library_name("Module2"))
#else
_ = dlopen(executablePath+target_library_name("Module2"), RTLD_NOW)
#endif

// CHECK: Stored.i.didSet from 5 to 10 replacement
s.i = 10
// CHECK: Stored.y.willSet from 5 to 11 replacement
s.y = 11
// CHECK: Stored.z.willSet from 5 to 12 replacement
// CHECK: Stored.z.didSet from 5 to 12 replacement
s.z = 12

// CHECK: HeapStored.z.willSet from 5 to 16 replacement
// CHECK: HeapStored.z.didSet from 5 to 16 replacement
h.z = 16

// CHECK: myglobal.didSet from 1 to 13 replacement
myglobal = 13
// CHECK: myglobal2.willSet from 1 to 14 replacement
myglobal2 = 14
// CHECK: myglobal3.willSet from 1 to 15 replacement
// CHECK: myglobal3.didSet from 1 to 15 replacement
myglobal3 = 15
41 changes: 40 additions & 1 deletion test/SILGen/dynamically_replaceable.swift
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,8 @@ dynamic func dynamic_replaceable() {
// CHECK-LABEL: sil hidden [dynamically_replacable] [ossa] @$s23dynamically_replaceable6StruktV08dynamic_B4_varSivs
// CHECK-LABEL: sil hidden [dynamically_replacable] [ossa] @$s23dynamically_replaceable6StruktVyS2icig : $@convention(method) (Int, Strukt) -> Int
// CHECK-LABEL: sil hidden [dynamically_replacable] [ossa] @$s23dynamically_replaceable6StruktVyS2icis : $@convention(method) (Int, Int, @inout Strukt) -> ()
// CHECK-LABEL: sil private [dynamically_replacable] [ossa] @$s23dynamically_replaceable6StruktV22property_with_observerSivW
// CHECK-LABEL: sil private [dynamically_replacable] [ossa] @$s23dynamically_replaceable6StruktV22property_with_observerSivw
struct Strukt {
dynamic init(x: Int) {
}
Expand All @@ -37,13 +39,23 @@ struct Strukt {
set {
}
}

dynamic var property_with_observer : Int {
didSet {
}
willSet {
}
}
}

// CHECK-LABEL: sil hidden [dynamically_replacable] [ossa] @$s23dynamically_replaceable5KlassC1xACSi_tcfc : $@convention(method) (Int, @owned Klass) -> @owned Klass
// CHECK-LABEL: sil hidden [dynamically_replacable] [ossa] @$s23dynamically_replaceable5KlassC08dynamic_B0yyF : $@convention(method) (@guaranteed Klass) -> () {
// CHECK-LABEL: sil hidden [dynamically_replacable] [ossa] @$s23dynamically_replaceable5KlassC08dynamic_B4_varSivg
// CHECK-LABEL: sil hidden [dynamically_replacable] [ossa] @$s23dynamically_replaceable5KlassC08dynamic_B4_varSivs
// CHECK-LABEL: sil hidden [dynamically_replacable] [ossa] @$s23dynamically_replaceable5KlassCyS2icig : $@convention(method) (Int, @guaranteed Klass) -> Int
// CHECK_LABEL: sil hidden [dynamically_replacable] [ossa] @$s23dynamically_replaceable5KlassCyS2icis : $@convention(method) (Int, Int, @guaranteed Klass) -> ()
// CHECK-LABEL: sil hidden [dynamically_replacable] [ossa] @$s23dynamically_replaceable5KlassCyS2icis : $@convention(method) (Int, Int, @guaranteed Klass) -> ()
// CHECK-LABEL: sil private [dynamically_replacable] [ossa] @$s23dynamically_replaceable5KlassC22property_with_observerSivW
// CHECK-LABEL: sil private [dynamically_replacable] [ossa] @$s23dynamically_replaceable5KlassC22property_with_observerSivw
class Klass {
dynamic init(x: Int) {
}
Expand Down Expand Up @@ -72,6 +84,13 @@ class Klass {
set {
}
}

dynamic var property_with_observer : Int {
didSet {
}
willSet {
}
}
}

class SubKlass : Klass {
Expand Down Expand Up @@ -253,6 +272,16 @@ struct GenericS<T> {
set {
}
}

// CHECK-LABEL: sil private [dynamically_replacable] [ossa] @$s23dynamically_replaceable8GenericSV22property_with_observerSivW
// CHECK-LABEL: sil private [dynamically_replacable] [ossa] @$s23dynamically_replaceable8GenericSV22property_with_observerSivw
dynamic var property_with_observer : Int {
didSet {
}
willSet {
}
}

}

extension GenericS {
Expand Down Expand Up @@ -299,6 +328,16 @@ extension GenericS {
self[y] = newValue
}
}

// CHECK-LABEL: sil private [dynamic_replacement_for "$s23dynamically_replaceable8GenericSV22property_with_observerSivW"] [ossa] @$s23dynamically_replaceable8GenericSV34replacement_property_with_observerSivW
// CHECK-LABEL: sil private [dynamic_replacement_for "$s23dynamically_replaceable8GenericSV22property_with_observerSivw"] [ossa] @$s23dynamically_replaceable8GenericSV34replacement_property_with_observerSivw
@_dynamicReplacement(for: property_with_observer)
var replacement_property_with_observer : Int {
didSet {
}
willSet {
}
}
}

dynamic var globalX = 0
Expand Down