@@ -101,6 +101,10 @@ public struct ObservableMacro {
101
101
"""
102
102
}
103
103
104
+ static func canCacheKeyPaths( _ lexicalContext: [ Syntax ] ) -> Bool {
105
+ lexicalContext. allSatisfy { $0. isNonGeneric }
106
+ }
107
+
104
108
static var ignoredAttribute : AttributeSyntax {
105
109
AttributeSyntax (
106
110
leadingTrivia: . space,
@@ -353,47 +357,88 @@ public struct ObservationTrackedMacro: AccessorMacro {
353
357
_ \( identifier) = initialValue
354
358
}
355
359
"""
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
+ """
356
368
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
+ """
364
399
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
+ }
370
419
}
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)
373
437
}
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
+ """
395
439
396
- return [ initAccessor, getAccessor, setAccessor, modifyAccessor]
440
+ return [ initAccessor, getAccessor, setAccessor, modifyAccessor]
441
+ }
397
442
}
398
443
}
399
444
@@ -422,11 +467,15 @@ extension ObservationTrackedMacro: PeerMacro {
422
467
}
423
468
424
469
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
+ }
430
479
}
431
480
}
432
481
0 commit comments