Skip to content

Commit 40c6cc0

Browse files
authored
Tracking (#6)
1 parent 920205f commit 40c6cc0

30 files changed

+2350
-1006
lines changed

.vscode/launch.json

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
{
2+
"configurations": [
3+
{
4+
"type": "lldb",
5+
"request": "launch",
6+
"args": [],
7+
"cwd": "${workspaceFolder:swift-macro-struct-transaction}",
8+
"name": "Debug StructTransactionClient",
9+
"program": "${workspaceFolder:swift-macro-struct-transaction}/.build/debug/StructTransactionClient",
10+
"preLaunchTask": "swift: Build Debug StructTransactionClient"
11+
},
12+
{
13+
"type": "lldb",
14+
"request": "launch",
15+
"args": [],
16+
"cwd": "${workspaceFolder:swift-macro-struct-transaction}",
17+
"name": "Release StructTransactionClient",
18+
"program": "${workspaceFolder:swift-macro-struct-transaction}/.build/release/StructTransactionClient",
19+
"preLaunchTask": "swift: Build Release StructTransactionClient"
20+
}
21+
]
22+
}

.vscode/settings.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
{}

Package.swift

Lines changed: 14 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -1,67 +1,48 @@
1-
// swift-tools-version: 5.9
1+
// swift-tools-version: 6.0
22
// The swift-tools-version declares the minimum version of Swift required to build this package.
33

44
import CompilerPluginSupport
55
import PackageDescription
66

77
let package = Package(
8-
name: "swift-macro-struct-transaction",
9-
platforms: [.macOS(.v10_15), .iOS(.v13), .tvOS(.v13), .watchOS(.v6), .macCatalyst(.v13)],
8+
name: "StateStruct",
9+
platforms: [.macOS(.v13), .iOS(.v16), .tvOS(.v13), .watchOS(.v6), .macCatalyst(.v13)],
1010
products: [
11-
// Products define the executables and libraries a package produces, making them visible to other packages.
1211
.library(
13-
name: "StructTransaction",
14-
targets: ["StructTransaction"]
15-
),
16-
.executable(
17-
name: "StructTransactionClient",
18-
targets: ["StructTransactionClient"]
12+
name: "StateStruct",
13+
targets: ["StateStruct"]
1914
),
2015
],
2116
dependencies: [
22-
// Depend on the Swift 5.9 release of SwiftSyntax
2317
.package(url: "https://github.com/apple/swift-syntax.git", from: "509.0.0"),
2418
.package(url: "https://github.com/pointfreeco/swift-macro-testing.git", from: "0.2.1")
2519
],
2620
targets: [
27-
// Targets are the basic building blocks of a package, defining a module or a test suite.
28-
// Targets can depend on other targets in this package and products from dependencies.
29-
// Macro implementation that performs the source transformation of a macro.
3021
.macro(
31-
name: "StructTransactionMacros",
22+
name: "StateStructMacros",
3223
dependencies: [
3324
.product(name: "SwiftSyntaxMacros", package: "swift-syntax"),
3425
.product(name: "SwiftCompilerPlugin", package: "swift-syntax"),
3526
]
3627
),
37-
38-
// Library that exposes a macro as part of its API, which is used in client programs.
3928
.target(
40-
name: "StructTransaction",
41-
dependencies: ["StructTransactionMacros"]
42-
),
43-
44-
// A client of the library, which is able to use the macro in its own code.
45-
.executableTarget(
46-
name: "StructTransactionClient",
47-
dependencies: ["StructTransaction"]
29+
name: "StateStruct",
30+
dependencies: ["StateStructMacros"]
4831
),
49-
50-
// A test target used to develop the macro implementation.
5132
.testTarget(
52-
name: "StructTransactionMacroTests",
33+
name: "StateStructMacroTests",
5334
dependencies: [
54-
"StructTransactionMacros",
35+
"StateStructMacros",
5536
.product(name: "MacroTesting", package: "swift-macro-testing"),
5637
.product(name: "SwiftSyntaxMacrosTestSupport", package: "swift-syntax"),
5738
]
5839
),
59-
6040
.testTarget(
61-
name: "StructTransactionTests",
41+
name: "StateStructTests",
6242
dependencies: [
63-
"StructTransaction"
43+
"StateStruct"
6444
]
6545
),
66-
]
46+
],
47+
swiftLanguageModes: [.v6]
6748
)
Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
2+
public final class _BackingStorage<Value>: @unchecked Sendable {
3+
4+
public var value: Value
5+
6+
public init(_ value: consuming Value) {
7+
self.value = value
8+
}
9+
10+
}
11+
12+
#if DEBUG
13+
private struct Before {
14+
15+
var value: Int
16+
17+
}
18+
19+
private struct After {
20+
21+
var value: Int {
22+
get {
23+
_cow_value.value
24+
}
25+
set {
26+
if !isKnownUniquelyReferenced(&_cow_value) {
27+
_cow_value = .init(_cow_value.value)
28+
} else {
29+
_cow_value.value = newValue
30+
}
31+
}
32+
_modify {
33+
if !isKnownUniquelyReferenced(&_cow_value) {
34+
_cow_value = .init(_cow_value.value)
35+
}
36+
yield &_cow_value.value
37+
}
38+
}
39+
40+
private var _cow_value: _BackingStorage<Int>
41+
42+
}
43+
44+
#endif
Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
# Dependency Tracking & Change Detection Test Documentation
2+
3+
This document outlines the detailed behavior observed from our test cases regarding dependency tracking and change detection in the state management system.
4+
5+
## 1. Dependency Tracking
6+
7+
The system provides a transactional tracking feature that records property access paths in both reading and writing modes. Every time a tracking block is executed using `state.tracking { ... }`, all property accesses performed within that block are automatically recorded into a dependency (or access) graph. This graph is structured hierarchically to represent exactly which properties—and even nested properties—were involved during the execution.
8+
9+
**Examples:**
10+
11+
- **Test 1:**
12+
In the reading block, `state.height` is accessed, so the generated read graph includes a node for `height`. In the subsequent writing block, `state.height` is updated (set to 200), which produces a write graph that includes the same node. This precise tracking allows for a direct comparison between the two graphs.
13+
14+
- **Test 2:**
15+
In the reading block, only `state.nested.name` is accessed, so the read graph specifically records the dependency path `nested → name`. Later, in the writing block, the entire `state.nested` object is replaced with a new instance. Even though the reading block explicitly accessed only `name`, the replacement of the entire `nested` object impacts the whole dependency chain and is duly captured in the write graph.
16+
17+
- **Additional Considerations:**
18+
The tracking mechanism handles nested and optional property accesses effectively, ensuring that the entire access path is recorded accurately. This means that any modification affecting a part of a nested access chain can influence the overall dependency.
19+
20+
## 2. Change Detection Logic
21+
22+
After generating the read and write dependency graphs, the system uses the function `PropertyNode.hasChanges(writeGraph:readGraph:)` to determine whether any property that was read has been affected by a write operation.
23+
24+
This function compares the nodes between the two graphs:
25+
- If a property that was read is directly modified or replaced in the write graph, the function reports that a change has occurred (returns `true`).
26+
- If the modifications are outside of the read dependency, then no change is flagged (returns `false`).
27+
28+
**Examples:**
29+
30+
- **Test 1:**
31+
Since the read graph includes `state.height` and the write operation directly updates `state.height`, the change detection function finds a matching node and returns `true`.
32+
33+
- **Test 2:**
34+
Although the read graph only tracks `state.nested.name`, replacing the entire `state.nested` object in the writing block is sufficient to consider the dependency altered, and the function consequently returns `true`.
35+
36+
- **Test 3:**
37+
Here, the read graph records a dependency on `state.nested.name`, but if the write block only modifies `state.nested.age`, then the property `name` remains unaffected. As a result, the function returns `false`.
38+
39+
This change detection logic ensures that only relevant modifications—those directly impacting the properties that were read—trigger further updates in the system. It prevents unnecessary processing by ignoring changes to properties that were not part of the initial dependency graph.
Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
# ``Trackable``
2+

0 commit comments

Comments
 (0)