@@ -101,6 +101,10 @@ public struct ObservableMacro {
101101 """
102102 }
103103
104+ static func canCacheKeyPaths( _ lexicalContext: [ Syntax ] ) -> Bool {
105+ lexicalContext. allSatisfy { $0. isNonGeneric }
106+ }
107+
104108 static var ignoredAttribute : AttributeSyntax {
105109 AttributeSyntax (
106110 leadingTrivia: . space,
@@ -353,47 +357,88 @@ public struct ObservationTrackedMacro: AccessorMacro {
353357 _ \( identifier) = initialValue
354358 }
355359 """
360+ if ObservableMacro . canCacheKeyPaths ( context. lexicalContext) {
361+ let getAccessor : AccessorDeclSyntax =
362+ """
363+ get {
364+ access(keyPath: \( container. trimmed. name) ._cachedKeypath_ \( identifier) )
365+ return _ \( identifier)
366+ }
367+ """
356368
357- let getAccessor : AccessorDeclSyntax =
358- """
359- get {
360- access(keyPath: \( container. trimmed. name) ._cachedKeypath_ \( identifier) )
361- return _ \( identifier)
362- }
363- """
369+ let setAccessor : AccessorDeclSyntax =
370+ """
371+ set {
372+ guard shouldNotifyObservers(_ \( identifier) , newValue) else {
373+ return
374+ }
375+ withMutation(keyPath: \( container. trimmed. name) ._cachedKeypath_ \( identifier) ) {
376+ _ \( identifier) = newValue
377+ }
378+ }
379+ """
380+
381+ // Note: this accessor cannot test the equality since it would incur
382+ // additional CoW's on structural types. Most mutations in-place do
383+ // not leave the value equal so this is "fine"-ish.
384+ // Warning to future maintence: adding equality checks here can make
385+ // container mutation O(N) instead of O(1).
386+ // e.g. observable.array.append(element) should just emit a change
387+ // to the new array, and NOT cause a copy of each element of the
388+ // array to an entirely new array.
389+ let modifyAccessor : AccessorDeclSyntax =
390+ """
391+ _modify {
392+ let keyPath = \( container. trimmed. name) ._cachedKeypath_ \( identifier)
393+ access(keyPath: keyPath)
394+ \( raw: ObservableMacro . registrarVariableName) .willSet(self, keyPath: keyPath)
395+ defer { \( raw: ObservableMacro . registrarVariableName) .didSet(self, keyPath: keyPath) }
396+ yield &_ \( identifier)
397+ }
398+ """
364399
365- let setAccessor : AccessorDeclSyntax =
366- """
367- set {
368- guard shouldNotifyObservers(_ \( identifier) , newValue) else {
369- return
400+ return [ initAccessor, getAccessor, setAccessor, modifyAccessor]
401+ } else {
402+ let getAccessor : AccessorDeclSyntax =
403+ """
404+ get {
405+ access(keyPath: \\ . \( identifier) )
406+ return _ \( identifier)
407+ }
408+ """
409+
410+ let setAccessor : AccessorDeclSyntax =
411+ """
412+ set {
413+ guard shouldNotifyObservers(_ \( identifier) , newValue) else {
414+ return
415+ }
416+ withMutation(keyPath: \\ . \( identifier) ) {
417+ _ \( identifier) = newValue
418+ }
370419 }
371- withMutation(keyPath: \( container. trimmed. name) ._cachedKeypath_ \( identifier) ) {
372- _ \( identifier) = newValue
420+ """
421+
422+ // Note: this accessor cannot test the equality since it would incur
423+ // additional CoW's on structural types. Most mutations in-place do
424+ // not leave the value equal so this is "fine"-ish.
425+ // Warning to future maintence: adding equality checks here can make
426+ // container mutation O(N) instead of O(1).
427+ // e.g. observable.array.append(element) should just emit a change
428+ // to the new array, and NOT cause a copy of each element of the
429+ // array to an entirely new array.
430+ let modifyAccessor : AccessorDeclSyntax =
431+ """
432+ _modify {
433+ access(keyPath: \\ . \( identifier) )
434+ \( raw: ObservableMacro . registrarVariableName) .willSet(self, keyPath: \\ . \( identifier) )
435+ defer { \( raw: ObservableMacro . registrarVariableName) .didSet(self, keyPath: \\ . \( identifier) ) }
436+ yield &_ \( identifier)
373437 }
374- }
375- """
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.
385- let modifyAccessor : AccessorDeclSyntax =
386- """
387- _modify {
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)
393- }
394- """
438+ """
395439
396- return [ initAccessor, getAccessor, setAccessor, modifyAccessor]
440+ return [ initAccessor, getAccessor, setAccessor, modifyAccessor]
441+ }
397442 }
398443}
399444
@@ -422,11 +467,15 @@ extension ObservationTrackedMacro: PeerMacro {
422467 }
423468
424469 let storage = DeclSyntax ( property. privatePrefixed ( " _ " , addingAttribute: ObservableMacro . ignoredAttribute) )
425- let cachedKeypath : DeclSyntax =
426- """
427- private static let _cachedKeypath_ \( identifier) = \\ \( container. name) . \( identifier)
428- """
429- return [ storage, cachedKeypath]
470+ if ObservableMacro . canCacheKeyPaths ( context. lexicalContext) {
471+ let cachedKeypath : DeclSyntax =
472+ """
473+ private static let _cachedKeypath_ \( identifier) = \\ \( container. name) . \( identifier)
474+ """
475+ return [ storage, cachedKeypath]
476+ } else {
477+ return [ storage]
478+ }
430479 }
431480}
432481
0 commit comments