@@ -36,35 +36,70 @@ public struct ObservableMacro {
3636
3737 static let registrarVariableName = " _$observationRegistrar "
3838
39- static func registrarVariable( _ observableType: TokenSyntax ) -> DeclSyntax {
39+ static func registrarVariable( _ observableType: TokenSyntax , context : some MacroExpansionContext ) -> DeclSyntax {
4040 return
4141 """
4242 @ \( raw: ignoredMacroName) private let \( raw: registrarVariableName) = \( raw: qualifiedRegistrarTypeName) ()
4343 """
4444 }
4545
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
4849 """
49- internal nonisolated func access<Member >(
50- keyPath: KeyPath< \( observableType) , Member >
50+ internal nonisolated func access< \( memberGeneric ) >(
51+ keyPath: KeyPath< \( observableType) , \( memberGeneric ) >
5152 ) {
52- \( raw: registrarVariableName) .access(self, keyPath: keyPath)
53+ \( raw: registrarVariableName) .access(self, keyPath: keyPath)
5354 }
5455 """
5556 }
5657
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
5962 """
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)
6568 }
6669 """
6770 }
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+ }
68103
69104 static var ignoredAttribute : AttributeSyntax {
70105 AttributeSyntax (
@@ -220,9 +255,13 @@ extension ObservableMacro: MemberMacro {
220255
221256 var declarations = [ DeclSyntax] ( )
222257
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)
226265
227266 return declarations
228267 }
@@ -298,6 +337,10 @@ public struct ObservationTrackedMacro: AccessorMacro {
298337 let identifier = property. identifier? . trimmed else {
299338 return [ ]
300339 }
340+
341+ guard let container = context. lexicalContext [ 0 ] . as ( ClassDeclSyntax . self) else {
342+ return [ ]
343+ }
301344
302345 if property. hasMacroApplication ( ObservableMacro . ignoredMacroName) {
303346 return [ ]
@@ -307,34 +350,46 @@ public struct ObservationTrackedMacro: AccessorMacro {
307350 """
308351 @storageRestrictions(initializes: _ \( identifier) )
309352 init(initialValue) {
310- _ \( identifier) = initialValue
353+ _ \( identifier) = initialValue
311354 }
312355 """
313356
314357 let getAccessor : AccessorDeclSyntax =
315358 """
316359 get {
317- access(keyPath: \\ . \( identifier) )
318- return _ \( identifier)
360+ access(keyPath: \( container . trimmed . name ) ._cachedKeypath_ \( identifier) )
361+ return _ \( identifier)
319362 }
320363 """
321364
322365 let setAccessor : AccessorDeclSyntax =
323366 """
324367 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+ }
328374 }
329375 """
330376
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.
331385 let modifyAccessor : AccessorDeclSyntax =
332386 """
333387 _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)
338393 }
339394 """
340395
@@ -352,7 +407,12 @@ extension ObservationTrackedMacro: PeerMacro {
352407 in context: Context
353408 ) throws -> [ DeclSyntax ] {
354409 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 {
356416 return [ ]
357417 }
358418
@@ -362,7 +422,11 @@ extension ObservationTrackedMacro: PeerMacro {
362422 }
363423
364424 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]
366430 }
367431}
368432
0 commit comments