Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
22 changes: 22 additions & 0 deletions .vscode/launch.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
{
"configurations": [
{
"type": "lldb",
"request": "launch",
"args": [],
"cwd": "${workspaceFolder:swift-macro-struct-transaction}",
"name": "Debug StructTransactionClient",
"program": "${workspaceFolder:swift-macro-struct-transaction}/.build/debug/StructTransactionClient",
"preLaunchTask": "swift: Build Debug StructTransactionClient"
},
{
"type": "lldb",
"request": "launch",
"args": [],
"cwd": "${workspaceFolder:swift-macro-struct-transaction}",
"name": "Release StructTransactionClient",
"program": "${workspaceFolder:swift-macro-struct-transaction}/.build/release/StructTransactionClient",
"preLaunchTask": "swift: Build Release StructTransactionClient"
}
]
}
1 change: 1 addition & 0 deletions .vscode/settings.json
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
{}
47 changes: 14 additions & 33 deletions Package.swift
Original file line number Diff line number Diff line change
@@ -1,67 +1,48 @@
// swift-tools-version: 5.9
// swift-tools-version: 6.0
// The swift-tools-version declares the minimum version of Swift required to build this package.

import CompilerPluginSupport
import PackageDescription

let package = Package(
name: "swift-macro-struct-transaction",
platforms: [.macOS(.v10_15), .iOS(.v13), .tvOS(.v13), .watchOS(.v6), .macCatalyst(.v13)],
name: "StateStruct",
platforms: [.macOS(.v13), .iOS(.v16), .tvOS(.v13), .watchOS(.v6), .macCatalyst(.v13)],
products: [
// Products define the executables and libraries a package produces, making them visible to other packages.
.library(
name: "StructTransaction",
targets: ["StructTransaction"]
),
.executable(
name: "StructTransactionClient",
targets: ["StructTransactionClient"]
name: "StateStruct",
targets: ["StateStruct"]
),
],
dependencies: [
// Depend on the Swift 5.9 release of SwiftSyntax
.package(url: "https://github.com/apple/swift-syntax.git", from: "509.0.0"),
.package(url: "https://github.com/pointfreeco/swift-macro-testing.git", from: "0.2.1")
],
targets: [
// Targets are the basic building blocks of a package, defining a module or a test suite.
// Targets can depend on other targets in this package and products from dependencies.
// Macro implementation that performs the source transformation of a macro.
.macro(
name: "StructTransactionMacros",
name: "StateStructMacros",
dependencies: [
.product(name: "SwiftSyntaxMacros", package: "swift-syntax"),
.product(name: "SwiftCompilerPlugin", package: "swift-syntax"),
]
),

// Library that exposes a macro as part of its API, which is used in client programs.
.target(
name: "StructTransaction",
dependencies: ["StructTransactionMacros"]
),

// A client of the library, which is able to use the macro in its own code.
.executableTarget(
name: "StructTransactionClient",
dependencies: ["StructTransaction"]
name: "StateStruct",
dependencies: ["StateStructMacros"]
),

// A test target used to develop the macro implementation.
.testTarget(
name: "StructTransactionMacroTests",
name: "StateStructMacroTests",
dependencies: [
"StructTransactionMacros",
"StateStructMacros",
.product(name: "MacroTesting", package: "swift-macro-testing"),
.product(name: "SwiftSyntaxMacrosTestSupport", package: "swift-syntax"),
]
),

.testTarget(
name: "StructTransactionTests",
name: "StateStructTests",
dependencies: [
"StructTransaction"
"StateStruct"
]
),
]
],
swiftLanguageModes: [.v6]
)
44 changes: 44 additions & 0 deletions Sources/StateStruct/CopyOnWrite.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@

public final class _BackingStorage<Value>: @unchecked Sendable {

public var value: Value

public init(_ value: consuming Value) {
self.value = value
}

}

#if DEBUG
private struct Before {

var value: Int

}

private struct After {

var value: Int {
get {
_cow_value.value
}
set {
if !isKnownUniquelyReferenced(&_cow_value) {
_cow_value = .init(_cow_value.value)
} else {
_cow_value.value = newValue
}
}
_modify {
if !isKnownUniquelyReferenced(&_cow_value) {
_cow_value = .init(_cow_value.value)
}
yield &_cow_value.value
}
}

private var _cow_value: _BackingStorage<Int>

}

#endif
39 changes: 39 additions & 0 deletions Sources/StateStruct/Documentation.docc/TestingSpec.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
# Dependency Tracking & Change Detection Test Documentation

This document outlines the detailed behavior observed from our test cases regarding dependency tracking and change detection in the state management system.

## 1. Dependency Tracking

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.

**Examples:**

- **Test 1:**
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.

- **Test 2:**
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.

- **Additional Considerations:**
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.

## 2. Change Detection Logic

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.

This function compares the nodes between the two graphs:
- 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`).
- If the modifications are outside of the read dependency, then no change is flagged (returns `false`).

**Examples:**

- **Test 1:**
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`.

- **Test 2:**
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`.

- **Test 3:**
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`.

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.
2 changes: 2 additions & 0 deletions Sources/StateStruct/Documentation.docc/Trackable.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
# ``Trackable``

Loading