Skip to content

Commit 3dbed15

Browse files
authored
Referencing (#26)
1 parent 2b87344 commit 3dbed15

File tree

7 files changed

+141
-117
lines changed

7 files changed

+141
-117
lines changed

Sources/StateStruct/CopyOnWrite.swift

Lines changed: 3 additions & 51 deletions
Original file line numberDiff line numberDiff line change
@@ -2,22 +2,9 @@
22
import os.lock
33

44
@dynamicMemberLookup
5-
public final class _BackingStorage<Value>: Sendable {
5+
public final class _BackingStorage<Value>: @unchecked Sendable {
66

7-
public var value: Value {
8-
get {
9-
_value.withCriticalRegion {
10-
$0
11-
}
12-
}
13-
set {
14-
_value.withCriticalRegion {
15-
$0 = newValue
16-
}
17-
}
18-
}
19-
20-
private let _value: ManagedCriticalState<Value>
7+
public var value: Value
218

229
public subscript <U>(dynamicMember keyPath: KeyPath<Value, U>) -> U {
2310
_read { yield value[keyPath: keyPath] }
@@ -38,7 +25,7 @@ public final class _BackingStorage<Value>: Sendable {
3825
}
3926

4027
public init(_ value: consuming Value) {
41-
self._value = .init(value)
28+
self.value = value
4229
}
4330

4431
public func copy(with newValue: consuming Value) -> _BackingStorage {
@@ -50,41 +37,6 @@ public final class _BackingStorage<Value>: Sendable {
5037
}
5138
}
5239

53-
private struct ManagedCriticalState<State>: ~Copyable, @unchecked Sendable {
54-
55-
typealias Lock = os_unfair_lock
56-
57-
private final class LockedBuffer: ManagedBuffer<State, Lock> {
58-
deinit {
59-
withUnsafeMutablePointerToElements {
60-
$0.deinitialize(count: 1)
61-
return
62-
}
63-
}
64-
}
65-
66-
private let buffer: ManagedBuffer<State, Lock>
67-
68-
init(_ initial: State) {
69-
buffer = LockedBuffer.create(minimumCapacity: 1) { buffer in
70-
buffer.withUnsafeMutablePointerToElements {
71-
$0.initialize(to: Lock())
72-
}
73-
return initial
74-
}
75-
}
76-
77-
func withCriticalRegion<R>(_ critical: (inout State) throws -> R) rethrows -> R {
78-
try buffer.withUnsafeMutablePointers { header, lock in
79-
os_unfair_lock_lock(lock)
80-
defer {
81-
os_unfair_lock_unlock(lock)
82-
}
83-
return try critical(&header.pointee)
84-
}
85-
}
86-
}
87-
8840
extension _BackingStorage: Equatable where Value: Equatable {
8941
public static func == (lhs: _BackingStorage<Value>, rhs: _BackingStorage<Value>) -> Bool {
9042
lhs === rhs || lhs.value == rhs.value

Sources/StateStruct/Referencing.swift

Lines changed: 82 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,82 @@
1+
/**
2+
Copy-on-Write property wrapper.
3+
Stores the value in reference type storage.
4+
*/
5+
@propertyWrapper
6+
@dynamicMemberLookup
7+
public struct Referencing<Value> {
8+
9+
public static func == (lhs: Self, rhs: Self) -> Bool {
10+
lhs.storage === rhs.storage
11+
}
12+
13+
public subscript <U>(dynamicMember keyPath: KeyPath<Value, U>) -> U {
14+
_read { yield wrappedValue[keyPath: keyPath] }
15+
}
16+
17+
public subscript <U>(dynamicMember keyPath: KeyPath<Value, U?>) -> U? {
18+
_read { yield wrappedValue[keyPath: keyPath] }
19+
}
20+
21+
public subscript <U>(dynamicMember keyPath: WritableKeyPath<Value, U>) -> U {
22+
_read { yield wrappedValue[keyPath: keyPath] }
23+
_modify { yield &wrappedValue[keyPath: keyPath] }
24+
}
25+
26+
public subscript <U>(dynamicMember keyPath: WritableKeyPath<Value, U?>) -> U? {
27+
_read { yield wrappedValue[keyPath: keyPath] }
28+
_modify { yield &wrappedValue[keyPath: keyPath] }
29+
}
30+
31+
public init(wrappedValue: consuming Value) {
32+
self.storage = .init(consume wrappedValue)
33+
}
34+
35+
public init(storage: _BackingStorage<Value>) {
36+
self.storage = storage
37+
}
38+
39+
private var storage: _BackingStorage<Value>
40+
41+
public var wrappedValue: Value {
42+
get {
43+
return storage.value
44+
}
45+
set {
46+
if isKnownUniquelyReferenced(&storage) {
47+
storage.value = newValue
48+
} else {
49+
storage = .init(newValue)
50+
}
51+
}
52+
_modify {
53+
if isKnownUniquelyReferenced(&storage) {
54+
yield &storage.value
55+
} else {
56+
var current = storage.value
57+
yield &current
58+
storage = .init(current)
59+
}
60+
}
61+
}
62+
63+
public var projectedValue: Self {
64+
get {
65+
self
66+
}
67+
mutating set {
68+
self = newValue
69+
}
70+
}
71+
72+
}
73+
74+
extension Referencing where Value : Equatable {
75+
public static func == (lhs: Self, rhs: Self) -> Bool {
76+
lhs.storage === rhs.storage || lhs.wrappedValue == rhs.wrappedValue
77+
}
78+
}
79+
80+
extension Referencing: Sendable where Value: Sendable {
81+
82+
}

Sources/StateStruct/Source.swift

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,7 @@ public macro TrackingIgnored() =
2323
accessor,
2424
names: named(init), named(_read), named(set), named(_modify)
2525
)
26-
@attached(peer, names: prefixed(`_backing_`))
26+
@attached(peer, names: prefixed(`_backing_`), prefixed(`$`))
2727
public macro COWTrackingProperty() =
2828
#externalMacro(module: "StateStructMacros", type: "COWTrackingPropertyMacro")
2929

Sources/StateStructMacros/COWTrackingPropertyMacro.swift

Lines changed: 29 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,11 @@ import SwiftSyntaxMacroExpansion
55
import SwiftSyntaxMacros
66

77
public struct COWTrackingPropertyMacro {
8-
8+
9+
public enum Error: Swift.Error {
10+
case needsTypeAnnotation
11+
}
12+
913
}
1014

1115
extension COWTrackingPropertyMacro: PeerMacro {
@@ -18,6 +22,11 @@ extension COWTrackingPropertyMacro: PeerMacro {
1822
guard let variableDecl = declaration.as(VariableDeclSyntax.self) else {
1923
return []
2024
}
25+
26+
guard variableDecl.typeSyntax != nil else {
27+
context.addDiagnostics(from: Error.needsTypeAnnotation, node: declaration)
28+
return []
29+
}
2130

2231
var newMembers: [DeclSyntax] = []
2332

@@ -88,6 +97,16 @@ extension COWTrackingPropertyMacro: PeerMacro {
8897
}
8998

9099
newMembers.append(DeclSyntax(_variableDecl))
100+
101+
do {
102+
let referencingAccessor = """
103+
public var $\(raw: variableDecl.name): Referencing<\(variableDecl.typeSyntax!.trimmed)> {
104+
Referencing(storage: _backing_\(raw: variableDecl.name))
105+
}
106+
""" as DeclSyntax
107+
108+
newMembers.append(referencingAccessor)
109+
}
91110

92111
return newMembers
93112
}
@@ -200,6 +219,11 @@ extension COWTrackingPropertyMacro: AccessorMacro {
200219
}
201220

202221
extension VariableDeclSyntax {
222+
223+
var name: String {
224+
return self.bindings.first?.pattern.as(IdentifierPatternSyntax.self)?.identifier.text ?? ""
225+
}
226+
203227
func renamingIdentifier(with newName: String) -> VariableDeclSyntax {
204228
let newBindings = self.bindings.map { binding -> PatternBindingSyntax in
205229

@@ -242,6 +266,10 @@ extension VariableDeclSyntax {
242266
})
243267

244268
}
269+
270+
var typeSyntax: TypeSyntax? {
271+
return self.bindings.first?.typeAnnotation?.type
272+
}
245273

246274
func modifyingTypeAnnotation(_ modifier: (TypeSyntax) -> TypeSyntax) -> VariableDeclSyntax {
247275
let newBindings = self.bindings.map { binding -> PatternBindingSyntax in

Tests/StateStructMacroTests/COWTrackingProperyMacroTests.swift

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -109,6 +109,10 @@ final class COWTrackingProperyMacroTests: XCTestCase {
109109
}
110110
var _backing_stored_0: _BackingStorage<Int> = _BackingStorage.init(18)
111111
112+
public var $stored_0: Referencing<Int> {
113+
Referencing(storage: _backing_stored_0)
114+
}
115+
112116
113117
var stored_1: Int {
114118
willSet {
@@ -150,6 +154,10 @@ final class COWTrackingProperyMacroTests: XCTestCase {
150154
}
151155
var _backing_stored_1: _BackingStorage<Int> = _BackingStorage.init(18)
152156
157+
public var $stored_1: Referencing<Int> {
158+
Referencing(storage: _backing_stored_1)
159+
}
160+
153161
154162
var stored_2: Int {
155163
willSet {
@@ -187,6 +195,10 @@ final class COWTrackingProperyMacroTests: XCTestCase {
187195
}
188196
var _backing_stored_2: _BackingStorage<Int> = _BackingStorage.init(18)
189197
198+
public var $stored_2: Referencing<Int> {
199+
Referencing(storage: _backing_stored_2)
200+
}
201+
190202
internal let _tracking_context: _TrackingContext = .init()
191203
192204
}
@@ -301,6 +313,10 @@ final class COWTrackingProperyMacroTests: XCTestCase {
301313
}
302314
}
303315
var _backing_stored_1: _BackingStorage<Int?> = _BackingStorage.init(nil)
316+
317+
public var $stored_1: Referencing<Int?> {
318+
Referencing(storage: _backing_stored_1)
319+
}
304320
305321
init() {
306322
}
@@ -376,6 +392,10 @@ final class COWTrackingProperyMacroTests: XCTestCase {
376392
}
377393
private var _backing_stored_0: _BackingStorage<Int> = _BackingStorage.init(18)
378394
395+
public var $stored_0: Referencing<Int> {
396+
Referencing(storage: _backing_stored_0)
397+
}
398+
379399
380400
public var stored_1: Int {
381401
_read {
@@ -422,6 +442,10 @@ final class COWTrackingProperyMacroTests: XCTestCase {
422442
}
423443
public var _backing_stored_1: _BackingStorage<Int> = _BackingStorage.init(18)
424444
445+
public var $stored_1: Referencing<Int> {
446+
Referencing(storage: _backing_stored_1)
447+
}
448+
425449
func compute() {
426450
}
427451
}

Tests/StateStructTests/BackingStorageTests.swift

Lines changed: 0 additions & 62 deletions
This file was deleted.

Tests/StateStructTests/MyState.swift

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -557,8 +557,8 @@ struct MyState {
557557
self.name = name
558558
}
559559

560-
var name = ""
561-
var age = 10
560+
var name: String = ""
561+
var age: Int = 10
562562
}
563563

564564
struct NestedAttached {

0 commit comments

Comments
 (0)