@@ -36,35 +36,70 @@ public struct ObservableMacro {
36
36
37
37
static let registrarVariableName = " _$observationRegistrar "
38
38
39
- static func registrarVariable( _ observableType: TokenSyntax ) -> DeclSyntax {
39
+ static func registrarVariable( _ observableType: TokenSyntax , context : some MacroExpansionContext ) -> DeclSyntax {
40
40
return
41
41
"""
42
42
@ \( raw: ignoredMacroName) private let \( raw: registrarVariableName) = \( raw: qualifiedRegistrarTypeName) ()
43
43
"""
44
44
}
45
45
46
- static func accessFunction( _ observableType: TokenSyntax ) -> DeclSyntax {
47
- return
46
+ static func accessFunction( _ observableType: TokenSyntax , context: some MacroExpansionContext ) -> DeclSyntax {
47
+ let memberGeneric = context. makeUniqueName ( " Member " )
48
+ return
48
49
"""
49
- internal nonisolated func access<Member >(
50
- keyPath: KeyPath< \( observableType) , Member >
50
+ internal nonisolated func access< \( memberGeneric ) >(
51
+ keyPath: KeyPath< \( observableType) , \( memberGeneric ) >
51
52
) {
52
- \( raw: registrarVariableName) .access(self, keyPath: keyPath)
53
+ \( raw: registrarVariableName) .access(self, keyPath: keyPath)
53
54
}
54
55
"""
55
56
}
56
57
57
- static func withMutationFunction( _ observableType: TokenSyntax ) -> DeclSyntax {
58
- return
58
+ static func withMutationFunction( _ observableType: TokenSyntax , context: some MacroExpansionContext ) -> DeclSyntax {
59
+ let memberGeneric = context. makeUniqueName ( " Member " )
60
+ let mutationGeneric = context. makeUniqueName ( " MutationResult " )
61
+ return
59
62
"""
60
- internal nonisolated func withMutation<Member, MutationResult >(
61
- keyPath: KeyPath< \( observableType) , Member >,
62
- _ mutation: () throws -> MutationResult
63
- ) rethrows -> MutationResult {
64
- try \( raw: registrarVariableName) .withMutation(of: self, keyPath: keyPath, mutation)
63
+ internal nonisolated func withMutation< \( memberGeneric ) , \( mutationGeneric ) >(
64
+ keyPath: KeyPath< \( observableType) , \( memberGeneric ) >,
65
+ _ mutation: () throws -> \( mutationGeneric )
66
+ ) rethrows -> \( mutationGeneric ) {
67
+ try \( raw: registrarVariableName) .withMutation(of: self, keyPath: keyPath, mutation)
65
68
}
66
69
"""
67
70
}
71
+
72
+ static func shouldNotifyObserversNonEquatableFunction( _ observableType: TokenSyntax , context: some MacroExpansionContext ) -> DeclSyntax {
73
+ let memberGeneric = context. makeUniqueName ( " Member " )
74
+ return
75
+ """
76
+ private nonisolated func shouldNotifyObservers< \( memberGeneric) >(_ lhs: \( memberGeneric) , _ rhs: \( memberGeneric) ) -> Bool { true }
77
+ """
78
+ }
79
+
80
+ static func shouldNotifyObserversEquatableFunction( _ observableType: TokenSyntax , context: some MacroExpansionContext ) -> DeclSyntax {
81
+ let memberGeneric = context. makeUniqueName ( " Member " )
82
+ return
83
+ """
84
+ private nonisolated func shouldNotifyObservers< \( memberGeneric) : Equatable>(_ lhs: \( memberGeneric) , _ rhs: \( memberGeneric) ) -> Bool { lhs != rhs }
85
+ """
86
+ }
87
+
88
+ static func shouldNotifyObserversNonEquatableObjectFunction( _ observableType: TokenSyntax , context: some MacroExpansionContext ) -> DeclSyntax {
89
+ let memberGeneric = context. makeUniqueName ( " Member " )
90
+ return
91
+ """
92
+ private nonisolated func shouldNotifyObservers< \( memberGeneric) : AnyObject>(_ lhs: \( memberGeneric) , _ rhs: \( memberGeneric) ) -> Bool { lhs !== rhs }
93
+ """
94
+ }
95
+
96
+ static func shouldNotifyObserversEquatableObjectFunction( _ observableType: TokenSyntax , context: some MacroExpansionContext ) -> DeclSyntax {
97
+ let memberGeneric = context. makeUniqueName ( " Member " )
98
+ return
99
+ """
100
+ private nonisolated func shouldNotifyObservers< \( memberGeneric) : Equatable & AnyObject>(_ lhs: \( memberGeneric) , _ rhs: \( memberGeneric) ) -> Bool { lhs != rhs }
101
+ """
102
+ }
68
103
69
104
static var ignoredAttribute : AttributeSyntax {
70
105
AttributeSyntax (
@@ -220,9 +255,13 @@ extension ObservableMacro: MemberMacro {
220
255
221
256
var declarations = [ DeclSyntax] ( )
222
257
223
- declaration. addIfNeeded ( ObservableMacro . registrarVariable ( observableType) , to: & declarations)
224
- declaration. addIfNeeded ( ObservableMacro . accessFunction ( observableType) , to: & declarations)
225
- declaration. addIfNeeded ( ObservableMacro . withMutationFunction ( observableType) , to: & declarations)
258
+ declaration. addIfNeeded ( ObservableMacro . registrarVariable ( observableType, context: context) , to: & declarations)
259
+ declaration. addIfNeeded ( ObservableMacro . accessFunction ( observableType, context: context) , to: & declarations)
260
+ declaration. addIfNeeded ( ObservableMacro . withMutationFunction ( observableType, context: context) , to: & declarations)
261
+ declaration. addIfNeeded ( ObservableMacro . shouldNotifyObserversNonEquatableFunction ( observableType, context: context) , to: & declarations)
262
+ declaration. addIfNeeded ( ObservableMacro . shouldNotifyObserversEquatableFunction ( observableType, context: context) , to: & declarations)
263
+ declaration. addIfNeeded ( ObservableMacro . shouldNotifyObserversNonEquatableObjectFunction ( observableType, context: context) , to: & declarations)
264
+ declaration. addIfNeeded ( ObservableMacro . shouldNotifyObserversEquatableObjectFunction ( observableType, context: context) , to: & declarations)
226
265
227
266
return declarations
228
267
}
@@ -298,6 +337,10 @@ public struct ObservationTrackedMacro: AccessorMacro {
298
337
let identifier = property. identifier? . trimmed else {
299
338
return [ ]
300
339
}
340
+
341
+ guard let container = context. lexicalContext [ 0 ] . as ( ClassDeclSyntax . self) else {
342
+ return [ ]
343
+ }
301
344
302
345
if property. hasMacroApplication ( ObservableMacro . ignoredMacroName) {
303
346
return [ ]
@@ -307,34 +350,46 @@ public struct ObservationTrackedMacro: AccessorMacro {
307
350
"""
308
351
@storageRestrictions(initializes: _ \( identifier) )
309
352
init(initialValue) {
310
- _ \( identifier) = initialValue
353
+ _ \( identifier) = initialValue
311
354
}
312
355
"""
313
356
314
357
let getAccessor : AccessorDeclSyntax =
315
358
"""
316
359
get {
317
- access(keyPath: \\ . \( identifier) )
318
- return _ \( identifier)
360
+ access(keyPath: \( container . trimmed . name ) ._cachedKeypath_ \( identifier) )
361
+ return _ \( identifier)
319
362
}
320
363
"""
321
364
322
365
let setAccessor : AccessorDeclSyntax =
323
366
"""
324
367
set {
325
- withMutation(keyPath: \\ . \( identifier) ) {
326
- _ \( identifier) = newValue
327
- }
368
+ guard shouldNotifyObservers(_ \( identifier) , newValue) else {
369
+ return
370
+ }
371
+ withMutation(keyPath: \( container. trimmed. name) ._cachedKeypath_ \( identifier) ) {
372
+ _ \( identifier) = newValue
373
+ }
328
374
}
329
375
"""
330
376
377
+ // Note: this accessor cannot test the equality since it would incur
378
+ // additional CoW's on structural types. Most mutations in-place do
379
+ // not leave the value equal so this is "fine"-ish.
380
+ // Warning to future maintence: adding equality checks here can make
381
+ // container mutation O(N) instead of O(1).
382
+ // e.g. observable.array.append(element) should just emit a change
383
+ // to the new array, and NOT cause a copy of each element of the
384
+ // array to an entirely new array.
331
385
let modifyAccessor : AccessorDeclSyntax =
332
386
"""
333
387
_modify {
334
- access(keyPath: \\ . \( identifier) )
335
- \( raw: ObservableMacro . registrarVariableName) .willSet(self, keyPath: \\ . \( identifier) )
336
- defer { \( raw: ObservableMacro . registrarVariableName) .didSet(self, keyPath: \\ . \( identifier) ) }
337
- yield &_ \( identifier)
388
+ let keyPath = \( container. trimmed. name) ._cachedKeypath_ \( identifier)
389
+ access(keyPath: keyPath)
390
+ \( raw: ObservableMacro . registrarVariableName) .willSet(self, keyPath: keyPath)
391
+ defer { \( raw: ObservableMacro . registrarVariableName) .didSet(self, keyPath: keyPath) }
392
+ yield &_ \( identifier)
338
393
}
339
394
"""
340
395
@@ -352,7 +407,12 @@ extension ObservationTrackedMacro: PeerMacro {
352
407
in context: Context
353
408
) throws -> [ DeclSyntax ] {
354
409
guard let property = declaration. as ( VariableDeclSyntax . self) ,
355
- property. isValidForObservation else {
410
+ property. isValidForObservation,
411
+ let identifier = property. identifier? . trimmed else {
412
+ return [ ]
413
+ }
414
+
415
+ guard let container = context. lexicalContext [ 0 ] . as ( ClassDeclSyntax . self) else {
356
416
return [ ]
357
417
}
358
418
@@ -362,7 +422,11 @@ extension ObservationTrackedMacro: PeerMacro {
362
422
}
363
423
364
424
let storage = DeclSyntax ( property. privatePrefixed ( " _ " , addingAttribute: ObservableMacro . ignoredAttribute) )
365
- return [ storage]
425
+ let cachedKeypath : DeclSyntax =
426
+ """
427
+ private static let _cachedKeypath_ \( identifier) = \\ \( container. name) . \( identifier)
428
+ """
429
+ return [ storage, cachedKeypath]
366
430
}
367
431
}
368
432
0 commit comments