From 3bd7e503bfab26e5f40ee33b084610b2bb60410d Mon Sep 17 00:00:00 2001 From: Brandon Williams <135203+mbrandonw@users.noreply.github.com> Date: Wed, 26 Jun 2024 11:32:49 -0400 Subject: [PATCH] Add _modify to @Perceptible (#91) * Add _modify to @Perceptible * Modernize syntax. * Added CoW test --- Sources/Perception/Macros.swift | 2 +- Sources/PerceptionMacros/Extensions.swift | 5 +--- .../PerceptionMacros/PerceptibleMacro.swift | 12 +++++++- .../PerceptionMacrosTests.swift | 8 +++++ Tests/PerceptionTests/ModifyTests.swift | 30 +++++++++++++++++++ 5 files changed, 51 insertions(+), 6 deletions(-) create mode 100644 Tests/PerceptionTests/ModifyTests.swift diff --git a/Sources/Perception/Macros.swift b/Sources/Perception/Macros.swift index fa12c87..c224b3e 100644 --- a/Sources/Perception/Macros.swift +++ b/Sources/Perception/Macros.swift @@ -16,7 +16,7 @@ public macro Perceptible() = #externalMacro(module: "PerceptionMacros", type: "PerceptibleMacro") -@attached(accessor, names: named(init), named(get), named(set)) +@attached(accessor, names: named(init), named(get), named(set), named(_modify)) @attached(peer, names: prefixed(_)) public macro PerceptionTracked() = #externalMacro(module: "PerceptionMacros", type: "PerceptionTrackedMacro") diff --git a/Sources/PerceptionMacros/Extensions.swift b/Sources/PerceptionMacros/Extensions.swift index 6e8e72d..ba83df5 100644 --- a/Sources/PerceptionMacros/Extensions.swift +++ b/Sources/PerceptionMacros/Extensions.swift @@ -40,10 +40,7 @@ extension VariableDeclSyntax { } func accessorsMatching(_ predicate: (TokenKind) -> Bool) -> [AccessorDeclSyntax] { - let patternBindings = bindings.compactMap { binding in - binding - } - let accessors: [AccessorDeclListSyntax.Element] = patternBindings.compactMap { patternBinding in + let accessors: [AccessorDeclListSyntax.Element] = bindings.compactMap { patternBinding in switch patternBinding.accessorBlock?.accessors { case .accessors(let accessors): return accessors diff --git a/Sources/PerceptionMacros/PerceptibleMacro.swift b/Sources/PerceptionMacros/PerceptibleMacro.swift index 35edb52..efce422 100644 --- a/Sources/PerceptionMacros/PerceptibleMacro.swift +++ b/Sources/PerceptionMacros/PerceptibleMacro.swift @@ -367,7 +367,17 @@ public struct PerceptionTrackedMacro: AccessorMacro { } """ - return [initAccessor, getAccessor, setAccessor] + let modifyAccessor: AccessorDeclSyntax = + """ + _modify { + access(keyPath: \\.\(identifier)) + \(raw: PerceptibleMacro.registrarVariableName).willSet(self, keyPath: \\.\(identifier)) + defer { \(raw: PerceptibleMacro.registrarVariableName).didSet(self, keyPath: \\.\(identifier)) } + yield &_\(identifier) + } + """ + + return [initAccessor, getAccessor, setAccessor, modifyAccessor] } } diff --git a/Tests/PerceptionMacrosTests/PerceptionMacrosTests.swift b/Tests/PerceptionMacrosTests/PerceptionMacrosTests.swift index 8e6a483..e6abb37 100644 --- a/Tests/PerceptionMacrosTests/PerceptionMacrosTests.swift +++ b/Tests/PerceptionMacrosTests/PerceptionMacrosTests.swift @@ -42,6 +42,14 @@ _count = newValue } } + _modify { + access(keyPath: \.count) + _$perceptionRegistrar.willSet(self, keyPath: \.count) + defer { + _$perceptionRegistrar.didSet(self, keyPath: \.count) + } + yield &_count + } } private let _$perceptionRegistrar = Perception.PerceptionRegistrar() diff --git a/Tests/PerceptionTests/ModifyTests.swift b/Tests/PerceptionTests/ModifyTests.swift new file mode 100644 index 0000000..6a049eb --- /dev/null +++ b/Tests/PerceptionTests/ModifyTests.swift @@ -0,0 +1,30 @@ +import Perception +import XCTest + +final class ModifyTests: XCTestCase { + func testCOW() { + let subject = CowTest() + let startId = subject.container.id + XCTAssertEqual(subject.container.id, startId) + subject.container.mutate() + XCTAssertEqual(subject.container.id, startId) + } +} + +struct CowContainer { + final class Contents { } + var contents = Contents() + mutating func mutate() { + if !isKnownUniquelyReferenced(&contents) { + contents = Contents() + } + } + var id: ObjectIdentifier { + ObjectIdentifier(contents) + } +} + +@Perceptible +final class CowTest { + var container = CowContainer() +}