Skip to content

Commit 4c83462

Browse files
authored
[support] weak var (#28)
```swift @test func weakRef() { let state = MyState.init() let result = state.tracking { _ = state.weak_ref } #expect( result.graph.prettyPrint() == """ StateStructTests.MyState { weak_ref-(1) } """ ) } ```
1 parent 213515d commit 4c83462

File tree

7 files changed

+258
-35
lines changed

7 files changed

+258
-35
lines changed

Sources/StateStruct/Source.swift

Lines changed: 23 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -23,10 +23,18 @@ public macro TrackingIgnored() =
2323
accessor,
2424
names: named(init), named(_read), named(set), named(_modify)
2525
)
26-
@attached(peer, names: prefixed(`_backing_`), prefixed(`$`))
26+
@attached(peer, names: prefixed(`_backing_`), prefixed(`$`))
2727
public macro COWTrackingProperty() =
2828
#externalMacro(module: "StateStructMacros", type: "COWTrackingPropertyMacro")
2929

30+
@attached(
31+
accessor,
32+
names: named(init), named(_read), named(set), named(_modify)
33+
)
34+
@attached(peer, names: prefixed(`_backing_`))
35+
public macro WeakTrackingProperty() =
36+
#externalMacro(module: "StateStructMacros", type: "WeakTrackingPropertyMacro")
37+
3038
#if DEBUG
3139

3240
@Tracking
@@ -57,10 +65,10 @@ public macro COWTrackingProperty() =
5765
var count: Int = 0
5866
}
5967

60-
@Tracking
61-
struct HashableState: Hashable {
62-
var count: Int = 0
63-
}
68+
@Tracking
69+
struct HashableState: Hashable {
70+
var count: Int = 0
71+
}
6472

6573
@Tracking
6674
struct MyState {
@@ -72,7 +80,7 @@ struct HashableState: Hashable {
7280
var stored_1: Int = 18
7381

7482
var stored_2: Int
75-
83+
7684
var stored_3: Int = 10 {
7785
didSet {
7886
print("stored_3 did set")
@@ -95,13 +103,19 @@ struct HashableState: Hashable {
95103
var computed_1: Int {
96104
stored_1
97105
}
106+
107+
weak var weak_stored: Ref?
98108

99109
init() {
100110

101111
}
102112

103113
}
104114

115+
class Ref {
116+
117+
}
118+
105119
#if canImport(Observation)
106120
import Observation
107121

@@ -114,7 +128,9 @@ struct HashableState: Hashable {
114128
var stored_2: Int
115129

116130
var stored_3: Int?
117-
131+
132+
weak var weak_stored: Hoge?
133+
118134
var stored_4: Int = 10 {
119135
didSet {
120136
print("stored_4 did set")

Sources/StateStructMacros/Plugin.swift

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ struct Plugin: CompilerPlugin {
88
let providingMacros: [Macro.Type] = [
99
TrackingMacro.self,
1010
COWTrackingPropertyMacro.self,
11+
WeakTrackingPropertyMacro.self,
1112
TrackingIgnoredMacro.self,
1213
]
1314
}

Sources/StateStructMacros/TrackingMacro.swift

Lines changed: 13 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -88,15 +88,20 @@ extension TrackingMacro: MemberAttributeMacro {
8888
}
8989
}
9090
}
91-
92-
if variableDecl.bindingSpecifier.tokenKind == .keyword(.var) {
93-
let macroAttribute = "@COWTrackingProperty"
94-
let attributeSyntax = AttributeSyntax.init(stringLiteral: macroAttribute)
95-
96-
return [attributeSyntax]
91+
92+
guard variableDecl.bindingSpecifier.tokenKind == .keyword(.var) else {
93+
return []
94+
}
95+
96+
let isWeak = variableDecl.modifiers.contains { modifier in
97+
modifier.name.tokenKind == .keyword(.weak)
98+
}
99+
100+
if isWeak {
101+
return [AttributeSyntax(stringLiteral: "@WeakTrackingProperty")]
102+
} else {
103+
return [AttributeSyntax(stringLiteral: "@COWTrackingProperty")]
97104
}
98-
99-
return []
100105
}
101106

102107
}
Lines changed: 148 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,148 @@
1+
import SwiftCompilerPlugin
2+
import SwiftSyntax
3+
import SwiftSyntaxBuilder
4+
import SwiftSyntaxMacroExpansion
5+
import SwiftSyntaxMacros
6+
7+
public struct WeakTrackingPropertyMacro {
8+
9+
public enum Error: Swift.Error {
10+
case needsTypeAnnotation
11+
}
12+
13+
}
14+
15+
extension WeakTrackingPropertyMacro: PeerMacro {
16+
public static func expansion(
17+
of node: AttributeSyntax,
18+
providingPeersOf declaration: some DeclSyntaxProtocol,
19+
in context: some MacroExpansionContext
20+
) throws -> [DeclSyntax] {
21+
22+
guard let variableDecl = declaration.as(VariableDeclSyntax.self) else {
23+
return []
24+
}
25+
26+
guard variableDecl.typeSyntax != nil else {
27+
context.addDiagnostics(from: Error.needsTypeAnnotation, node: declaration)
28+
return []
29+
}
30+
31+
var newMembers: [DeclSyntax] = []
32+
33+
let ignoreMacroAttached = variableDecl.attributes.contains {
34+
switch $0 {
35+
case .attribute(let attribute):
36+
return attribute.attributeName.description == "TrackingIgnored"
37+
case .ifConfigDecl:
38+
return false
39+
}
40+
}
41+
42+
guard !ignoreMacroAttached else {
43+
return []
44+
}
45+
46+
for binding in variableDecl.bindings {
47+
if binding.accessorBlock != nil {
48+
// skip computed properties
49+
continue
50+
}
51+
}
52+
53+
var _variableDecl = variableDecl.trimmed
54+
_variableDecl.attributes = [.init(.init(stringLiteral: "@TrackingIgnored"))]
55+
56+
_variableDecl = _variableDecl.renamingIdentifier(with: "_backing_")
57+
58+
newMembers.append(DeclSyntax(_variableDecl))
59+
60+
return newMembers
61+
}
62+
}
63+
64+
extension WeakTrackingPropertyMacro: AccessorMacro {
65+
public static func expansion(
66+
of node: SwiftSyntax.AttributeSyntax,
67+
providingAccessorsOf declaration: some SwiftSyntax.DeclSyntaxProtocol,
68+
in context: some SwiftSyntaxMacros.MacroExpansionContext
69+
) throws -> [SwiftSyntax.AccessorDeclSyntax] {
70+
71+
guard let variableDecl = declaration.as(VariableDeclSyntax.self) else {
72+
return []
73+
}
74+
75+
guard let binding = variableDecl.bindings.first,
76+
let identifierPattern = binding.pattern.as(IdentifierPatternSyntax.self)
77+
else {
78+
return []
79+
}
80+
81+
let isConstant = variableDecl.bindingSpecifier.tokenKind == .keyword(.let)
82+
let propertyName = identifierPattern.identifier.text
83+
let backingName = "_backing_" + propertyName
84+
let hasWillSet = variableDecl.willSetBlock != nil
85+
86+
let initAccessor = AccessorDeclSyntax(
87+
"""
88+
@storageRestrictions(initializes: \(raw: backingName))
89+
init(initialValue) {
90+
\(raw: backingName) = initialValue
91+
}
92+
"""
93+
)
94+
95+
let readAccessor = AccessorDeclSyntax(
96+
"""
97+
_read {
98+
_Tracking._tracking_modifyStorage {
99+
$0.accessorRead(path: _tracking_context.path?.pushed(.init("\(raw: propertyName)")))
100+
}
101+
yield \(raw: backingName)
102+
}
103+
"""
104+
)
105+
106+
let setAccessor = AccessorDeclSyntax(
107+
"""
108+
set {
109+
_Tracking._tracking_modifyStorage {
110+
$0.accessorSet(path: _tracking_context.path?.pushed(.init("\(raw: propertyName)")))
111+
}
112+
\(raw: backingName) = newValue
113+
}
114+
"""
115+
)
116+
117+
let modifyAccessor = AccessorDeclSyntax(
118+
"""
119+
_modify {
120+
_Tracking._tracking_modifyStorage {
121+
$0.accessorModify(path: _tracking_context.path?.pushed(.init("\(raw: propertyName)")))
122+
}
123+
yield &\(raw: backingName)
124+
}
125+
"""
126+
)
127+
128+
var accessors: [AccessorDeclSyntax] = []
129+
130+
if binding.initializer == nil {
131+
accessors.append(initAccessor)
132+
}
133+
134+
accessors.append(readAccessor)
135+
136+
if !isConstant {
137+
accessors.append(setAccessor)
138+
139+
if hasWillSet == false {
140+
accessors.append(modifyAccessor)
141+
}
142+
}
143+
144+
return accessors
145+
146+
}
147+
148+
}

Tests/StateStructMacroTests/TrackingMacroTests.swift

Lines changed: 49 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -14,22 +14,22 @@ final class TrackingMacroTests: XCTestCase {
1414
super.invokeTest()
1515
}
1616
}
17-
17+
1818
func test_ignore_computed() {
19-
19+
2020
assertMacro {
2121
"""
2222
@Tracking
2323
struct MyState {
24-
24+
2525
var c_0: Int {
2626
0
2727
}
28-
28+
2929
var c_1: Int {
3030
get { 0 }
3131
}
32-
32+
3333
var c_2: Int {
3434
get { 0 }
3535
set { }
@@ -62,16 +62,16 @@ final class TrackingMacroTests: XCTestCase {
6262
}
6363
"""
6464
}
65-
65+
6666
}
67-
67+
6868
func test_stored_observer() {
69-
69+
7070
assertMacro {
7171
"""
7272
@Tracking
7373
struct MyState {
74-
74+
7575
var stored_0: Int = 18 {
7676
didSet {
7777
print("stored_0 did set")
@@ -99,30 +99,30 @@ final class TrackingMacroTests: XCTestCase {
9999
}
100100
"""
101101
}
102-
102+
103103
}
104-
104+
105105
func test_public() {
106106
assertMacro {
107107
"""
108108
@Tracking
109109
public struct MyState {
110-
110+
111111
private var stored_0: Int = 18
112-
112+
113113
var stored_1: String
114-
114+
115115
let stored_2: Int = 0
116-
116+
117117
var age: Int { 0 }
118-
118+
119119
var age2: Int {
120120
get { 0 }
121121
set { }
122122
}
123-
123+
124124
var height: Int
125-
125+
126126
func compute() {
127127
}
128128
}
@@ -167,11 +167,11 @@ final class TrackingMacroTests: XCTestCase {
167167
"""
168168
@Tracking
169169
struct MyState {
170-
170+
171171
private var stored_0: Int = 18
172172
173173
var stored_1: String
174-
174+
175175
let stored_2: Int = 0
176176
177177
var age: Int { 0 }
@@ -221,4 +221,33 @@ final class TrackingMacroTests: XCTestCase {
221221
}
222222

223223
}
224+
225+
func test_weak_property() {
226+
227+
assertMacro {
228+
"""
229+
@Tracking
230+
struct MyState {
231+
232+
weak var weak_stored: Ref?
233+
234+
}
235+
"""
236+
} expansion: {
237+
"""
238+
struct MyState {
239+
@WeakTrackingProperty
240+
241+
weak var weak_stored: Ref?
242+
243+
internal let _tracking_context: _TrackingContext = .init()
244+
245+
}
246+
247+
extension MyState: TrackingObject {
248+
}
249+
"""
250+
}
251+
252+
}
224253
}

0 commit comments

Comments
 (0)