Skip to content

Commit a2c6515

Browse files
Merge pull request #23337 from aschwaighofer/support_dynamic_replacement_of_stored_with_observer
Add support for dynamic replacement of didSet/willSet
2 parents 73d174d + 35ca0e3 commit a2c6515

9 files changed

+285
-5
lines changed

include/swift/AST/Decl.h

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4552,6 +4552,8 @@ class AbstractStorageDecl : public ValueDecl {
45524552
/// True if any of the accessors to the storage is private or fileprivate.
45534553
bool hasPrivateAccessor() const;
45544554

4555+
bool hasDidSetOrWillSetDynamicReplacement() const;
4556+
45554557
// Implement isa/cast/dyncast/etc.
45564558
static bool classof(const Decl *D) {
45574559
return D->getKind() >= DeclKind::First_AbstractStorageDecl &&

lib/AST/Decl.cpp

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4421,6 +4421,14 @@ bool AbstractStorageDecl::hasPrivateAccessor() const {
44214421
return false;
44224422
}
44234423

4424+
bool AbstractStorageDecl::hasDidSetOrWillSetDynamicReplacement() const {
4425+
if (auto *func = getDidSetFunc())
4426+
return func->getAttrs().hasAttribute<DynamicReplacementAttr>();
4427+
if (auto *func = getWillSetFunc())
4428+
return func->getAttrs().hasAttribute<DynamicReplacementAttr>();
4429+
return false;
4430+
}
4431+
44244432
void AbstractStorageDecl::setAccessors(StorageImplInfo implInfo,
44254433
SourceLoc lbraceLoc,
44264434
ArrayRef<AccessorDecl *> accessors,

lib/SILGen/SILGenType.cpp

Lines changed: 28 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1097,6 +1097,28 @@ class SILGenExtension : public TypeMemberVisitor<SILGenExtension> {
10971097
SILGenType(SGM, ntd).emitType();
10981098
}
10991099
void visitFuncDecl(FuncDecl *fd) {
1100+
// Don't emit other accessors for a dynamic replacement of didSet inside of
1101+
// an extension. We only allow such a construct to allow definition of a
1102+
// didSet/willSet dynamic replacement. Emitting other accessors is
1103+
// problematic because there is no storage.
1104+
//
1105+
// extension SomeStruct {
1106+
// @_dynamicReplacement(for: someProperty)
1107+
// var replacement : Int {
1108+
// didSet {
1109+
// }
1110+
// }
1111+
// }
1112+
if (auto *accessor = dyn_cast<AccessorDecl>(fd)) {
1113+
auto *storage = accessor->getStorage();
1114+
bool hasDidSetOrWillSetDynamicReplacement =
1115+
storage->hasDidSetOrWillSetDynamicReplacement();
1116+
1117+
if (hasDidSetOrWillSetDynamicReplacement &&
1118+
isa<ExtensionDecl>(storage->getDeclContext()) &&
1119+
fd != storage->getDidSetFunc() && fd != storage->getWillSetFunc())
1120+
return;
1121+
}
11001122
SGM.emitFunction(fd);
11011123
if (SGM.requiresObjCMethodEntryPoint(fd))
11021124
SGM.emitObjCMethodThunk(fd);
@@ -1122,8 +1144,12 @@ class SILGenExtension : public TypeMemberVisitor<SILGenExtension> {
11221144

11231145
void visitVarDecl(VarDecl *vd) {
11241146
if (vd->hasStorage()) {
1125-
assert(vd->isStatic() && "stored property in extension?!");
1126-
return emitTypeMemberGlobalVariable(SGM, vd);
1147+
bool hasDidSetOrWillSetDynamicReplacement =
1148+
vd->hasDidSetOrWillSetDynamicReplacement();
1149+
assert((vd->isStatic() || hasDidSetOrWillSetDynamicReplacement) &&
1150+
"stored property in extension?!");
1151+
if (!hasDidSetOrWillSetDynamicReplacement)
1152+
return emitTypeMemberGlobalVariable(SGM, vd);
11271153
}
11281154
visitAbstractStorageDecl(vd);
11291155
}

lib/Sema/TypeCheckAttr.cpp

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2569,7 +2569,8 @@ void TypeChecker::addImplicitDynamicAttribute(Decl *D) {
25692569
if (auto *VD = dyn_cast<VarDecl>(D)) {
25702570
// Don't turn stored into computed properties. This could conflict with
25712571
// exclusivity checking.
2572-
if (VD->hasStorage())
2572+
// If there is a didSet or willSet function we allow dynamic replacement.
2573+
if (VD->hasStorage() && !VD->getDidSetFunc() && !VD->getWillSetFunc())
25732574
return;
25742575
// Don't add dynamic to local variables.
25752576
if (VD->getDeclContext()->isLocalContext())

lib/Sema/TypeCheckDecl.cpp

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2152,7 +2152,8 @@ class DeclChecker : public DeclVisitor<DeclChecker> {
21522152
TC.diagnose(VD->getLoc(), diag::enum_stored_property);
21532153
VD->markInvalid();
21542154
} else if (isa<ExtensionDecl>(VD->getDeclContext()) &&
2155-
!VD->isStatic()) {
2155+
!VD->isStatic() &&
2156+
!VD->getAttrs().getAttribute<DynamicReplacementAttr>()) {
21562157
TC.diagnose(VD->getLoc(), diag::extension_stored_property);
21572158
VD->markInvalid();
21582159
}
Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,54 @@
1+
struct Stored {
2+
var i: Int {
3+
didSet {
4+
print("Stored.i.didSet from \(oldValue) to \(i) original")
5+
}
6+
}
7+
8+
var y: Int {
9+
willSet {
10+
print("Stored.y.willSet from \(y) to \(newValue) original")
11+
}
12+
}
13+
14+
var z: Int {
15+
willSet {
16+
print("Stored.z.willSet from \(z) to \(newValue) original")
17+
}
18+
didSet {
19+
print("Stored.z.didSet from \(oldValue) to \(z) original")
20+
}
21+
}
22+
}
23+
24+
var myglobal : Int = 1 {
25+
didSet {
26+
print("myglobal.didSet from \(oldValue) to \(myglobal) original")
27+
}
28+
}
29+
30+
var myglobal2 : Int = 1 {
31+
willSet {
32+
print("myglobal2.willSet from \(myglobal2) to \(newValue) original")
33+
}
34+
}
35+
36+
var myglobal3 : Int = 1 {
37+
willSet {
38+
print("myglobal3.willSet from \(myglobal3) to \(newValue) original")
39+
}
40+
didSet {
41+
print("myglobal3.didSet from \(oldValue) to \(myglobal3) original")
42+
}
43+
}
44+
45+
class HeapStored {
46+
var z: Int = 5{
47+
willSet {
48+
print("HeapStored.z.willSet from \(z) to \(newValue) original")
49+
}
50+
didSet {
51+
print("HeapStored.z.didSet from \(oldValue) to \(z) original")
52+
}
53+
}
54+
}
Lines changed: 62 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,62 @@
1+
@_private(sourceFile: "dynamic_replacement_property_observer_orig.swift") import TestDidWillSet
2+
3+
extension Stored {
4+
@_dynamicReplacement(for: i)
5+
var _replacement_i: Int {
6+
didSet {
7+
print("Stored.i.didSet from \(oldValue) to \(i) replacement")
8+
}
9+
}
10+
@_dynamicReplacement(for: y)
11+
var _replacement_y: Int {
12+
willSet {
13+
print("Stored.y.willSet from \(y) to \(newValue) replacement")
14+
}
15+
}
16+
17+
@_dynamicReplacement(for: z)
18+
var _replacement_z: Int {
19+
willSet {
20+
print("Stored.z.willSet from \(z) to \(newValue) replacement")
21+
}
22+
didSet {
23+
print("Stored.z.didSet from \(oldValue) to \(z) replacement")
24+
}
25+
}
26+
}
27+
28+
@_dynamicReplacement(for: myglobal)
29+
public var _replacement_myglobal : Int = 1 {
30+
didSet {
31+
print("myglobal.didSet from \(oldValue) to \(myglobal) replacement")
32+
}
33+
}
34+
35+
@_dynamicReplacement(for: myglobal2)
36+
var _replacement_myglobal2 : Int = 1 {
37+
willSet {
38+
print("myglobal2.willSet from \(myglobal2) to \(newValue) replacement")
39+
}
40+
}
41+
42+
@_dynamicReplacement(for: myglobal3)
43+
var _replacement_myglobal3 : Int = 1 {
44+
willSet {
45+
print("myglobal3.willSet from \(myglobal3) to \(newValue) replacement")
46+
}
47+
didSet {
48+
print("myglobal3.didSet from \(oldValue) to \(myglobal3) replacement")
49+
}
50+
}
51+
52+
extension HeapStored {
53+
@_dynamicReplacement(for: z)
54+
var _replacement_z: Int {
55+
willSet {
56+
print("HeapStored.z.willSet from \(z) to \(newValue) replacement")
57+
}
58+
didSet {
59+
print("HeapStored.z.didSet from \(oldValue) to \(z) replacement")
60+
}
61+
}
62+
}
Lines changed: 87 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,87 @@
1+
// RUN: %empty-directory(%t)
2+
// 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
3+
// 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
4+
// RUN: %target-build-swift -I%t -L%t -lTestDidWillSet -o %t/main %target-rpath(%t) %s -swift-version 5
5+
// RUN: %target-codesign %t/main %t/%target-library-name(TestDidWillSet) %t/%target-library-name(TestDidWillSet2)
6+
// RUN: %target-run %t/main %t/%target-library-name(TestDidWillSet) %t/%target-library-name(TestDidWillSet)
7+
8+
// REQUIRES: executable_test
9+
10+
@_private(sourceFile: "dynamic_replacement_property_observer_orig.swift") import TestDidWillSet
11+
12+
#if os(macOS) || os(iOS) || os(tvOS) || os(watchOS)
13+
import Darwin
14+
#elseif os(Linux) || os(FreeBSD) || os(PS4) || os(Android) || os(Cygwin) || os(Haiku)
15+
import Glibc
16+
#elseif os(Windows)
17+
import MSVCRT
18+
import WinSDK
19+
#else
20+
#error("Unsupported platform")
21+
#endif
22+
23+
private func target_library_name(_ name: String) -> String {
24+
#if os(iOS) || os(macOS) || os(tvOS) || os(watchOS)
25+
return "lib\(name).dylib"
26+
#elseif os(Windows)
27+
return "\(name).dll"
28+
#else
29+
return "lib\(name).so"
30+
#endif
31+
}
32+
33+
var s = Stored(i: 5, y: 5, z: 5)
34+
var h = HeapStored()
35+
36+
// CHECK: Stored.i.didSet from 5 to 10 original
37+
s.i = 10
38+
// CHECK: Stored.y.willSet from 5 to 11 original
39+
s.y = 11
40+
// CHECK: Stored.z.willSet from 5 to 12 original
41+
// CHECK: Stored.z.didSet from 5 to 12 original
42+
s.z = 12
43+
44+
// CHECK: HeapStored.z.willSet from 5 to 16 original
45+
// CHECK: HeapStored.z.didSet from 5 to 16 original
46+
h.z = 16
47+
48+
// CHECK: myglobal.didSet from 1 to 13 original
49+
myglobal = 13
50+
// CHECK: myglobal2.willSet from 1 to 14 original
51+
myglobal2 = 14
52+
// CHECK: myglobal3.willSet from 1 to 15 original
53+
// CHECK: myglobal3.didSet from 1 to 15 original
54+
myglobal3 = 15
55+
56+
var executablePath = CommandLine.arguments[0]
57+
executablePath.removeLast(4)
58+
59+
// Now, test with the module containing the replacements.
60+
61+
#if os(Linux)
62+
_ = dlopen(target_library_name("Module2"), RTLD_NOW)
63+
#elseif os(Windows)
64+
_ = LoadLibraryA(target_library_name("Module2"))
65+
#else
66+
_ = dlopen(executablePath+target_library_name("Module2"), RTLD_NOW)
67+
#endif
68+
69+
// CHECK: Stored.i.didSet from 5 to 10 replacement
70+
s.i = 10
71+
// CHECK: Stored.y.willSet from 5 to 11 replacement
72+
s.y = 11
73+
// CHECK: Stored.z.willSet from 5 to 12 replacement
74+
// CHECK: Stored.z.didSet from 5 to 12 replacement
75+
s.z = 12
76+
77+
// CHECK: HeapStored.z.willSet from 5 to 16 replacement
78+
// CHECK: HeapStored.z.didSet from 5 to 16 replacement
79+
h.z = 16
80+
81+
// CHECK: myglobal.didSet from 1 to 13 replacement
82+
myglobal = 13
83+
// CHECK: myglobal2.willSet from 1 to 14 replacement
84+
myglobal2 = 14
85+
// CHECK: myglobal3.willSet from 1 to 15 replacement
86+
// CHECK: myglobal3.didSet from 1 to 15 replacement
87+
myglobal3 = 15

test/SILGen/dynamically_replaceable.swift

Lines changed: 40 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,8 @@ dynamic func dynamic_replaceable() {
1616
// CHECK-LABEL: sil hidden [dynamically_replacable] [ossa] @$s23dynamically_replaceable6StruktV08dynamic_B4_varSivs
1717
// CHECK-LABEL: sil hidden [dynamically_replacable] [ossa] @$s23dynamically_replaceable6StruktVyS2icig : $@convention(method) (Int, Strukt) -> Int
1818
// CHECK-LABEL: sil hidden [dynamically_replacable] [ossa] @$s23dynamically_replaceable6StruktVyS2icis : $@convention(method) (Int, Int, @inout Strukt) -> ()
19+
// CHECK-LABEL: sil private [dynamically_replacable] [ossa] @$s23dynamically_replaceable6StruktV22property_with_observerSivW
20+
// CHECK-LABEL: sil private [dynamically_replacable] [ossa] @$s23dynamically_replaceable6StruktV22property_with_observerSivw
1921
struct Strukt {
2022
dynamic init(x: Int) {
2123
}
@@ -37,13 +39,23 @@ struct Strukt {
3739
set {
3840
}
3941
}
42+
43+
dynamic var property_with_observer : Int {
44+
didSet {
45+
}
46+
willSet {
47+
}
48+
}
4049
}
50+
4151
// CHECK-LABEL: sil hidden [dynamically_replacable] [ossa] @$s23dynamically_replaceable5KlassC1xACSi_tcfc : $@convention(method) (Int, @owned Klass) -> @owned Klass
4252
// CHECK-LABEL: sil hidden [dynamically_replacable] [ossa] @$s23dynamically_replaceable5KlassC08dynamic_B0yyF : $@convention(method) (@guaranteed Klass) -> () {
4353
// CHECK-LABEL: sil hidden [dynamically_replacable] [ossa] @$s23dynamically_replaceable5KlassC08dynamic_B4_varSivg
4454
// CHECK-LABEL: sil hidden [dynamically_replacable] [ossa] @$s23dynamically_replaceable5KlassC08dynamic_B4_varSivs
4555
// CHECK-LABEL: sil hidden [dynamically_replacable] [ossa] @$s23dynamically_replaceable5KlassCyS2icig : $@convention(method) (Int, @guaranteed Klass) -> Int
46-
// CHECK_LABEL: sil hidden [dynamically_replacable] [ossa] @$s23dynamically_replaceable5KlassCyS2icis : $@convention(method) (Int, Int, @guaranteed Klass) -> ()
56+
// CHECK-LABEL: sil hidden [dynamically_replacable] [ossa] @$s23dynamically_replaceable5KlassCyS2icis : $@convention(method) (Int, Int, @guaranteed Klass) -> ()
57+
// CHECK-LABEL: sil private [dynamically_replacable] [ossa] @$s23dynamically_replaceable5KlassC22property_with_observerSivW
58+
// CHECK-LABEL: sil private [dynamically_replacable] [ossa] @$s23dynamically_replaceable5KlassC22property_with_observerSivw
4759
class Klass {
4860
dynamic init(x: Int) {
4961
}
@@ -72,6 +84,13 @@ class Klass {
7284
set {
7385
}
7486
}
87+
88+
dynamic var property_with_observer : Int {
89+
didSet {
90+
}
91+
willSet {
92+
}
93+
}
7594
}
7695

7796
class SubKlass : Klass {
@@ -253,6 +272,16 @@ struct GenericS<T> {
253272
set {
254273
}
255274
}
275+
276+
// CHECK-LABEL: sil private [dynamically_replacable] [ossa] @$s23dynamically_replaceable8GenericSV22property_with_observerSivW
277+
// CHECK-LABEL: sil private [dynamically_replacable] [ossa] @$s23dynamically_replaceable8GenericSV22property_with_observerSivw
278+
dynamic var property_with_observer : Int {
279+
didSet {
280+
}
281+
willSet {
282+
}
283+
}
284+
256285
}
257286

258287
extension GenericS {
@@ -299,6 +328,16 @@ extension GenericS {
299328
self[y] = newValue
300329
}
301330
}
331+
332+
// CHECK-LABEL: sil private [dynamic_replacement_for "$s23dynamically_replaceable8GenericSV22property_with_observerSivW"] [ossa] @$s23dynamically_replaceable8GenericSV34replacement_property_with_observerSivW
333+
// CHECK-LABEL: sil private [dynamic_replacement_for "$s23dynamically_replaceable8GenericSV22property_with_observerSivw"] [ossa] @$s23dynamically_replaceable8GenericSV34replacement_property_with_observerSivw
334+
@_dynamicReplacement(for: property_with_observer)
335+
var replacement_property_with_observer : Int {
336+
didSet {
337+
}
338+
willSet {
339+
}
340+
}
302341
}
303342

304343
dynamic var globalX = 0

0 commit comments

Comments
 (0)