diff --git a/include/swift/AST/Decl.h b/include/swift/AST/Decl.h index 83d4d3a89d46c..0141a896c8ca2 100644 --- a/include/swift/AST/Decl.h +++ b/include/swift/AST/Decl.h @@ -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 && diff --git a/lib/AST/Decl.cpp b/lib/AST/Decl.cpp index 0041138456f8b..2573469d17db6 100644 --- a/lib/AST/Decl.cpp +++ b/lib/AST/Decl.cpp @@ -4443,6 +4443,14 @@ bool AbstractStorageDecl::hasPrivateAccessor() const { return false; } +bool AbstractStorageDecl::hasDidSetOrWillSetDynamicReplacement() const { + if (auto *func = getDidSetFunc()) + return func->getAttrs().hasAttribute(); + if (auto *func = getWillSetFunc()) + return func->getAttrs().hasAttribute(); + return false; +} + void AbstractStorageDecl::setAccessors(StorageImplInfo implInfo, SourceLoc lbraceLoc, ArrayRef accessors, diff --git a/lib/SILGen/SILGenType.cpp b/lib/SILGen/SILGenType.cpp index d81ad265d96e0..7e2886d6f3c55 100644 --- a/lib/SILGen/SILGenType.cpp +++ b/lib/SILGen/SILGenType.cpp @@ -1094,6 +1094,28 @@ class SILGenExtension : public TypeMemberVisitor { 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(fd)) { + auto *storage = accessor->getStorage(); + bool hasDidSetOrWillSetDynamicReplacement = + storage->hasDidSetOrWillSetDynamicReplacement(); + + if (hasDidSetOrWillSetDynamicReplacement && + isa(storage->getDeclContext()) && + fd != storage->getDidSetFunc() && fd != storage->getWillSetFunc()) + return; + } SGM.emitFunction(fd); if (SGM.requiresObjCMethodEntryPoint(fd)) SGM.emitObjCMethodThunk(fd); @@ -1119,8 +1141,12 @@ class SILGenExtension : public TypeMemberVisitor { 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); } diff --git a/lib/Sema/TypeCheckAttr.cpp b/lib/Sema/TypeCheckAttr.cpp index 11e3440a33b61..5d45cb37b0851 100644 --- a/lib/Sema/TypeCheckAttr.cpp +++ b/lib/Sema/TypeCheckAttr.cpp @@ -2623,7 +2623,8 @@ void TypeChecker::addImplicitDynamicAttribute(Decl *D) { if (auto *VD = dyn_cast(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()) diff --git a/lib/Sema/TypeCheckDecl.cpp b/lib/Sema/TypeCheckDecl.cpp index 685ed8cbce1dd..c44f840fd73c7 100644 --- a/lib/Sema/TypeCheckDecl.cpp +++ b/lib/Sema/TypeCheckDecl.cpp @@ -2161,7 +2161,8 @@ class DeclChecker : public DeclVisitor { TC.diagnose(VD->getLoc(), diag::enum_stored_property); VD->markInvalid(); } else if (isa(VD->getDeclContext()) && - !VD->isStatic()) { + !VD->isStatic() && + !VD->getAttrs().getAttribute()) { TC.diagnose(VD->getLoc(), diag::extension_stored_property); VD->markInvalid(); } diff --git a/test/Interpreter/Inputs/dynamic_replacement_property_observer_orig.swift b/test/Interpreter/Inputs/dynamic_replacement_property_observer_orig.swift new file mode 100644 index 0000000000000..21f6364b5263f --- /dev/null +++ b/test/Interpreter/Inputs/dynamic_replacement_property_observer_orig.swift @@ -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") + } + } +} diff --git a/test/Interpreter/Inputs/dynamic_replacement_property_observer_repl.swift b/test/Interpreter/Inputs/dynamic_replacement_property_observer_repl.swift new file mode 100644 index 0000000000000..5b9ebafb4495c --- /dev/null +++ b/test/Interpreter/Inputs/dynamic_replacement_property_observer_repl.swift @@ -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") + } + } +} diff --git a/test/Interpreter/dynamicReplacement_property_observer.swift b/test/Interpreter/dynamicReplacement_property_observer.swift new file mode 100644 index 0000000000000..f042bc65a4ec9 --- /dev/null +++ b/test/Interpreter/dynamicReplacement_property_observer.swift @@ -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 diff --git a/test/SILGen/dynamically_replaceable.swift b/test/SILGen/dynamically_replaceable.swift index 2d4fa3b6c7774..c234e3fe1b23e 100644 --- a/test/SILGen/dynamically_replaceable.swift +++ b/test/SILGen/dynamically_replaceable.swift @@ -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) { } @@ -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) { } @@ -72,6 +84,13 @@ class Klass { set { } } + + dynamic var property_with_observer : Int { + didSet { + } + willSet { + } + } } class SubKlass : Klass { @@ -253,6 +272,16 @@ struct GenericS { 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 { @@ -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