Skip to content

Commit 614eabe

Browse files
committed
Update
1 parent 6f060c0 commit 614eabe

File tree

12 files changed

+322
-628
lines changed

12 files changed

+322
-628
lines changed
Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
2+
final class ReferenceEdgeStorage<Value>: @unchecked Sendable {
3+
4+
var value: Value
5+
6+
init(_ value: consuming Value) {
7+
self.value = value
8+
}
9+
10+
func read<T>(_ thunk: (borrowing Value) -> T) -> T {
11+
thunk(value)
12+
}
13+
14+
}

Sources/StructTransaction/Source.swift

Lines changed: 62 additions & 40 deletions
Original file line numberDiff line numberDiff line change
@@ -3,48 +3,16 @@
33
Available only for structs
44
*/
55
@attached(
6-
extension,
7-
conformances: DetectingType,
8-
names: named(Accessing),
9-
named(modify(source:modifier:)),
10-
named(read(source:reader:)),
11-
named(AccessingTarget)
6+
memberAttribute
127
)
13-
public macro Detecting() = #externalMacro(module: "StructTransactionMacros", type: "WriterMacro")
8+
public macro Tracking() = #externalMacro(module: "StructTransactionMacros", type: "TrackingMacro")
149

15-
/**
16-
Available only for member functions.
17-
Marker Macro that indicates if the function will be exported into Accessing struct.
18-
*/
19-
@attached(peer)
20-
public macro Exporting() = #externalMacro(module: "StructTransactionMacros", type: "MarkerMacro")
21-
22-
/**
23-
Use ``Detecting()`` macro to adapt struct
24-
*/
25-
public protocol DetectingType {
26-
27-
associatedtype Accessing
28-
29-
@discardableResult
30-
static func modify(source: inout Self, modifier: (inout Accessing) throws -> Void) rethrows -> AccessingResult
31-
32-
@discardableResult
33-
static func read(source: Self, reader: (inout Accessing) throws -> Void) rethrows -> AccessingResult
34-
}
35-
36-
extension DetectingType {
37-
38-
@discardableResult
39-
public mutating func modify(modifier: (inout Accessing) throws -> Void) rethrows -> AccessingResult {
40-
try Self.modify(source: &self, modifier: modifier)
41-
}
42-
43-
@discardableResult
44-
public borrowing func read(reader: (inout Accessing) throws -> Void) rethrows -> AccessingResult {
45-
try Self.read(source: self, reader: reader)
46-
}
47-
}
10+
@attached(
11+
accessor,
12+
names: named(init), named(get), named(set), named(_modify)
13+
)
14+
@attached(peer, names: prefixed(`_backing_`))
15+
public macro TrackingProperty() = #externalMacro(module: "StructTransactionMacros", type: "TrackingPropertyMacro")
4816

4917
public struct AccessingResult {
5018

@@ -60,3 +28,57 @@ public struct AccessingResult {
6028
}
6129
}
6230

31+
32+
33+
@Tracking
34+
struct MyState {
35+
36+
init() {
37+
stored_2 = 0
38+
}
39+
40+
var stored_1: Int = 18
41+
42+
var stored_2: Int
43+
44+
var computed_1: Int {
45+
stored_1
46+
}
47+
48+
var subState: MySubState = .init()
49+
50+
}
51+
52+
53+
@Tracking
54+
struct MySubState {
55+
56+
var stored_1: Int = 18
57+
58+
var computed_1: Int {
59+
stored_1
60+
}
61+
62+
init() {
63+
64+
}
65+
66+
}
67+
68+
#if canImport(Observation)
69+
import Observation
70+
71+
@available(macOS 14.0, iOS 17.0, tvOS 15.0, watchOS 8.0, *)
72+
@Observable
73+
class Hoge {
74+
75+
let stored: Int
76+
77+
var stored_2: Int
78+
79+
init(stored: Int) {
80+
self.stored = stored
81+
self.stored_2 = stored
82+
}
83+
}
84+
#endif

Sources/StructTransactionMacros/Plugin.swift

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ import SwiftSyntaxMacros
66
@main
77
struct Plugin: CompilerPlugin {
88
let providingMacros: [Macro.Type] = [
9-
WriterMacro.self,
10-
MarkerMacro.self,
9+
TrackingMacro.self,
10+
TrackingPropertyMacro.self,
1111
]
1212
}

Sources/StructTransactionMacros/TracingMacro.swift

Lines changed: 0 additions & 48 deletions
This file was deleted.
Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,48 @@
1+
import SwiftCompilerPlugin
2+
import SwiftSyntax
3+
import SwiftSyntaxBuilder
4+
import SwiftSyntaxMacros
5+
6+
public struct TrackingMacro: Macro {
7+
8+
public enum Error: Swift.Error {
9+
case needsTypeAnnotation
10+
case notFoundPropertyName
11+
}
12+
13+
public static var formatMode: FormatMode = .disabled
14+
15+
}
16+
17+
extension TrackingMacro: MemberAttributeMacro {
18+
19+
public static func expansion(
20+
of node: AttributeSyntax,
21+
attachedTo declaration: some DeclGroupSyntax,
22+
providingAttributesFor member: some DeclSyntaxProtocol,
23+
in context: some MacroExpansionContext
24+
) throws -> [AttributeSyntax] {
25+
26+
guard let variableDecl = member.as(VariableDeclSyntax.self) else {
27+
return []
28+
}
29+
30+
// to ignore computed properties
31+
for binding in variableDecl.bindings {
32+
if binding.accessorBlock != nil {
33+
return []
34+
}
35+
}
36+
37+
if variableDecl.bindingSpecifier.tokenKind == .keyword(.let) ||
38+
variableDecl.bindingSpecifier.tokenKind == .keyword(.var) {
39+
let macroAttribute = "@TrackingProperty"
40+
let attributeSyntax = AttributeSyntax.init(stringLiteral: macroAttribute)
41+
42+
return [attributeSyntax]
43+
}
44+
45+
return []
46+
}
47+
48+
}
Lines changed: 150 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,150 @@
1+
2+
import SwiftCompilerPlugin
3+
import SwiftSyntax
4+
import SwiftSyntaxBuilder
5+
import SwiftSyntaxMacros
6+
7+
public struct TrackingPropertyMacro {
8+
9+
public enum Error: Swift.Error {
10+
11+
}
12+
}
13+
14+
extension TrackingPropertyMacro: PeerMacro {
15+
public static func expansion(
16+
of node: AttributeSyntax,
17+
providingPeersOf declaration: some DeclSyntaxProtocol,
18+
in context: some MacroExpansionContext
19+
) throws -> [DeclSyntax] {
20+
21+
22+
guard let variableDecl = declaration.as(VariableDeclSyntax.self) else {
23+
return []
24+
}
25+
26+
let renameRewriter = RenameIdentifierRewriter()
27+
let modifierRewriter = MakePrivateRewriter()
28+
29+
var newMembers: [DeclSyntax] = []
30+
31+
for binding in variableDecl.bindings {
32+
if binding.accessorBlock != nil {
33+
// skip computed properties
34+
continue
35+
}
36+
37+
let backingStorageDecl = modifierRewriter.visit(
38+
renameRewriter.visit(variableDecl.trimmed)
39+
)
40+
41+
newMembers.append(backingStorageDecl)
42+
}
43+
44+
return newMembers
45+
}
46+
}
47+
48+
extension TrackingPropertyMacro: AccessorMacro {
49+
public static func expansion(
50+
of node: SwiftSyntax.AttributeSyntax,
51+
providingAccessorsOf declaration: some SwiftSyntax.DeclSyntaxProtocol,
52+
in context: some SwiftSyntaxMacros.MacroExpansionContext
53+
) throws -> [SwiftSyntax.AccessorDeclSyntax] {
54+
55+
guard let variableDecl = declaration.as(VariableDeclSyntax.self) else {
56+
return []
57+
}
58+
59+
// `@TrackingProperty` は単一のプロパティにのみ適用可能
60+
guard let binding = variableDecl.bindings.first,
61+
let identifierPattern = binding.pattern.as(IdentifierPatternSyntax.self) else {
62+
return []
63+
}
64+
65+
let propertyName = identifierPattern.identifier.text
66+
let backingName = "_backing_" + propertyName
67+
68+
let initAccessor = AccessorDeclSyntax(
69+
"""
70+
@storageRestrictions(initializes: \(raw: backingName))
71+
init(initialValue) {
72+
\(raw: backingName) = initialValue
73+
}
74+
"""
75+
)
76+
77+
let getAccessor = AccessorDeclSyntax(
78+
"""
79+
get { \(raw: backingName) }
80+
"""
81+
)
82+
83+
let setAccessor = AccessorDeclSyntax(
84+
"""
85+
set { \(raw: backingName) = newValue }
86+
"""
87+
)
88+
89+
let modifyAccessor = AccessorDeclSyntax(
90+
"""
91+
_modify {
92+
yield &\(raw: backingName)
93+
}
94+
"""
95+
)
96+
97+
if binding.initializer == nil {
98+
return [
99+
initAccessor,
100+
getAccessor,
101+
setAccessor,
102+
modifyAccessor
103+
]
104+
} else {
105+
return [
106+
getAccessor,
107+
setAccessor,
108+
modifyAccessor
109+
]
110+
}
111+
112+
}
113+
114+
115+
}
116+
117+
118+
final class RenameIdentifierRewriter: SyntaxRewriter {
119+
120+
init() {}
121+
122+
override func visit(_ node: IdentifierPatternSyntax) -> PatternSyntax {
123+
124+
let propertyName = node.identifier.text
125+
let newIdentifier = IdentifierPatternSyntax.init(identifier: "_backing_\(raw: propertyName)")
126+
127+
return super.visit(newIdentifier)
128+
129+
}
130+
}
131+
132+
final class MakePrivateRewriter: SyntaxRewriter {
133+
134+
init() {}
135+
136+
// override func visit(_ node: VariableDeclSyntax) -> DeclSyntax {
137+
// return super.visit(node.trimmed(matching: { $0.isNewline }))
138+
// }
139+
140+
override func visit(_ node: DeclModifierListSyntax) -> DeclModifierListSyntax {
141+
if node.contains(where: { $0.name.tokenKind == .keyword(.private) }) {
142+
return super.visit(node)
143+
}
144+
145+
var modified = node
146+
modified.append(.init(name: .keyword(.private), trailingTrivia: .spaces(1)))
147+
148+
return super.visit(modified)
149+
}
150+
}

0 commit comments

Comments
 (0)