Skip to content

Commit

Permalink
Update
Browse files Browse the repository at this point in the history
  • Loading branch information
muukii committed Feb 4, 2025
1 parent 8e9c94b commit 2a46d70
Show file tree
Hide file tree
Showing 6 changed files with 208 additions and 30 deletions.
2 changes: 2 additions & 0 deletions Sources/StructTransaction/Source.swift
Original file line number Diff line number Diff line change
Expand Up @@ -89,6 +89,8 @@ class Hoge {

var stored_2: Int

var name = ""

init(stored: Int) {
self.stored = stored
self.stored_2 = stored
Expand Down
109 changes: 96 additions & 13 deletions Sources/StructTransaction/Tracking.swift
Original file line number Diff line number Diff line change
Expand Up @@ -3,10 +3,10 @@ import Foundation
public struct Storage: Equatable {

public struct Identifier: Hashable {
public let name: String
public let keyPath: AnyKeyPath

public init(name: String) {
self.name = name
public init(keyPath: AnyKeyPath) {
self.keyPath = keyPath
}
}

Expand All @@ -22,23 +22,106 @@ public struct Storage: Equatable {
}
}

public func _tracking_modifyStorage(_ modifier: (inout Storage) -> Void) {
guard Thread.current.threadDictionary["tracking"] != nil else {
return
private enum ThreadDictionaryKey {
case tracking
case currentKeyPathStack
}

extension NSMutableDictionary {
fileprivate var currentKeyPathStack: Tracking.KeyPathStack? {
get {
self[ThreadDictionaryKey.currentKeyPathStack] as? Tracking.KeyPathStack
}
set {
self[ThreadDictionaryKey.currentKeyPathStack] = newValue
}
}

fileprivate var tracking: Storage? {
get {
self[ThreadDictionaryKey.tracking] as? Storage
}
set {
self[ThreadDictionaryKey.tracking] = newValue
}
}
}

public enum Tracking {

public static func _pushKeyPath(_ keyPath: AnyKeyPath) {
if Thread.current.threadDictionary.currentKeyPathStack == nil {
Thread.current.threadDictionary.currentKeyPathStack = KeyPathStack()
}

Thread.current.threadDictionary.currentKeyPathStack?.push(
keyPath
)
}

public static func _currentKeyPath(_ keyPath: AnyKeyPath) -> AnyKeyPath? {
guard let current = Thread.current.threadDictionary.currentKeyPathStack else {
return keyPath
}
return current.currentKeyPath()?.appending(path: keyPath)
}

public static func _popKeyPath() {
Thread.current.threadDictionary.currentKeyPathStack?.pop()
}

fileprivate struct KeyPathStack: Equatable {

var stack: [AnyKeyPath]

init() {
stack = []
}

mutating func push(_ keyPath: AnyKeyPath) {
stack.append(keyPath)
}

mutating func pop() {
stack.removeLast()
}

func currentKeyPath() -> AnyKeyPath? {

guard var keyPath = stack.first else {
return nil
}

for component in stack.dropFirst() {
guard let appended = keyPath.appending(path: component) else {
return nil
}

keyPath = appended
}

return keyPath
}


}

public static func _tracking_modifyStorage(_ modifier: (inout Storage) -> Void) {
guard Thread.current.threadDictionary.tracking != nil else {
return
}
modifier(&Thread.current.threadDictionary.tracking!)
}
var storage = Thread.current.threadDictionary["tracking"] as! Storage
modifier(&storage)
Thread.current.threadDictionary["tracking"] = storage
}

public func withTracking(_ perform: () -> Void) -> Storage {
let current = Thread.current.threadDictionary["tracking"] as? Storage
let current = Thread.current.threadDictionary.tracking
defer {
Thread.current.threadDictionary["tracking"] = current
Thread.current.threadDictionary.tracking = current
}

Thread.current.threadDictionary["tracking"] = Storage()
Thread.current.threadDictionary.tracking = Storage()
perform()
let result = Thread.current.threadDictionary["tracking"] as! Storage
let result = Thread.current.threadDictionary.tracking!
return result
}
66 changes: 54 additions & 12 deletions Sources/StructTransactionMacros/COWTrackingPropertyMacro.swift
Original file line number Diff line number Diff line change
Expand Up @@ -2,12 +2,10 @@ import SwiftCompilerPlugin
import SwiftSyntax
import SwiftSyntaxBuilder
import SwiftSyntaxMacros
import SwiftSyntaxMacroExpansion

public struct COWTrackingPropertyMacro {

public enum Error: Swift.Error {

}
}

extension COWTrackingPropertyMacro: PeerMacro {
Expand All @@ -21,6 +19,13 @@ extension COWTrackingPropertyMacro: PeerMacro {
return []
}

// for binding in variableDecl.bindings {
// guard binding.typeAnnotation != nil else {
// context.diagnose(.init(node: node, message: MacroExpansionErrorMessage("Property needs type annotation")))
// return []
// }
// }

var newMembers: [DeclSyntax] = []

let ignoreMacroAttached = variableDecl.attributes.contains {
Expand Down Expand Up @@ -53,7 +58,7 @@ extension COWTrackingPropertyMacro: PeerMacro {
return "_Backing_COW_Storage<\(type.trimmed)>"
})
.modifyingInit({ initializer in
return .init(value: ".init(\(initializer.value))" as ExprSyntax)
return .init(value: "_Backing_COW_Storage.init(\(initializer.value))" as ExprSyntax)
})

_variableDecl.leadingTrivia = .spaces(2)
Expand Down Expand Up @@ -92,12 +97,35 @@ extension COWTrackingPropertyMacro: AccessorMacro {
}
"""
)

let readAccessor = AccessorDeclSyntax(
"""
_read {
let keyPath = \\Self.\(raw: propertyName)
let currentKeyPath = Tracking._currentKeyPath(keyPath) ?? keyPath
Tracking._pushKeyPath(keyPath)
Tracking._tracking_modifyStorage {
$0.read(identifier: .init(keyPath: currentKeyPath))
}
yield \(raw: backingName).value
Tracking._popKeyPath()
}
"""
)

let getAccessor = AccessorDeclSyntax(
"""
get {
_tracking_modifyStorage {
$0.read(identifier: .init(name: "\(raw: propertyName)"))
let keyPath = \\Self.\(raw: propertyName)
let currentKeyPath = Tracking._currentKeyPath(keyPath) ?? keyPath
Tracking._pushKeyPath(keyPath)
defer {
Tracking._popKeyPath()
}
Tracking._tracking_modifyStorage {
$0.read(identifier: .init(keyPath: currentKeyPath))
}
return \(raw: backingName).value
}
Expand All @@ -107,8 +135,14 @@ extension COWTrackingPropertyMacro: AccessorMacro {
let setAccessor = AccessorDeclSyntax(
"""
set {
_tracking_modifyStorage {
$0.write(identifier: .init(name: "\(raw: propertyName)"))
let keyPath = \\Self.\(raw: propertyName)
let currentKeyPath = Tracking._currentKeyPath(keyPath) ?? keyPath
Tracking._pushKeyPath(keyPath)
defer {
Tracking._popKeyPath()
}
Tracking._tracking_modifyStorage {
$0.write(identifier: .init(keyPath: currentKeyPath))
}
if !isKnownUniquelyReferenced(&\(raw: backingName)) {
\(raw: backingName) = .init(newValue)
Expand All @@ -122,8 +156,14 @@ extension COWTrackingPropertyMacro: AccessorMacro {
let modifyAccessor = AccessorDeclSyntax(
"""
_modify {
_tracking_modifyStorage {
$0.write(identifier: .init(name: "\(raw: propertyName)"))
let keyPath = \\Self.\(raw: propertyName)
let currentKeyPath = Tracking._currentKeyPath(keyPath) ?? keyPath
Tracking._pushKeyPath(keyPath)
defer {
Tracking._popKeyPath()
}
Tracking._tracking_modifyStorage {
$0.write(identifier: .init(keyPath: currentKeyPath))
}
if !isKnownUniquelyReferenced(&\(raw: backingName)) {
\(raw: backingName) = .init(\(raw: backingName).value)
Expand All @@ -136,13 +176,15 @@ extension COWTrackingPropertyMacro: AccessorMacro {
if binding.initializer == nil {
return [
initAccessor,
getAccessor,
readAccessor,
// getAccessor,
setAccessor,
modifyAccessor,
]
} else {
return [
getAccessor,
readAccessor,
// getAccessor,
setAccessor,
modifyAccessor,
]
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ final class COWTrackingProperyMacroTests: XCTestCase {

override func invokeTest() {
withMacroTesting(
isRecording: false,
isRecording: true,
macros: [
"COWTrackingProperty": COWTrackingPropertyMacro.self,
"TrackingIgnored": TrackingIgnoredMacro.self,
Expand Down Expand Up @@ -48,7 +48,7 @@ final class COWTrackingProperyMacroTests: XCTestCase {
$0.write(identifier: .init(name: "stored_0"))
}
if !isKnownUniquelyReferenced(&_backing_stored_0) {
_backing_stored_0 = .init(_backing_stored_0.value)
_backing_stored_0 = .init(newValue)
} else {
_backing_stored_0.value = newValue
}
Expand All @@ -63,7 +63,7 @@ final class COWTrackingProperyMacroTests: XCTestCase {
yield &_backing_stored_0.value
}
}
private var _backing_stored_0: _Backing_COW_Storage<Int> = .init(18)
private var _backing_stored_0: _Backing_COW_Storage<Int> = _Backing_COW_Storage.init(18)
func compute() {
}
Expand Down
6 changes: 6 additions & 0 deletions Tests/StructTransactionTests/MyState.swift
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,13 @@ struct MyState {
var nested: Nested = .init(name: "hello")
var nestedAttached: NestedAttached = .init(name: "")

@Tracking
struct Nested {

init(name: String) {
self.name = name
}

var name = ""
}

Expand Down
49 changes: 47 additions & 2 deletions Tests/StructTransactionTests/Tests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -13,9 +13,54 @@ struct Tests {
original.height = 100
}

#expect(result.writeIdentifiers.contains(.init(name: "height")))
#expect(result.writeIdentifiers.contains(.init(keyPath: \MyState.height)))

}

@Test
func tracking_write_nested_stored_property() {

var original = MyState.init()

let result = withTracking {
original.nested.name = "AAA"
}

#expect(result.writeIdentifiers.contains(.init(keyPath: \MyState.nested)))
#expect(result.writeIdentifiers.contains(.init(keyPath: \MyState.nested.name)))

}

@Test
func tracking_read_nested_stored_property() {

let original = MyState.init()

let result = withTracking {
_ = original.nested.name
}

#expect(result.readIdentifiers.contains(.init(keyPath: \MyState.nested)))
#expect(result.readIdentifiers.contains(.init(keyPath: \MyState.nested.name)))

}

@Test
func modify_endpoint() {

var original = MyState.init()

func update(_ value: inout String) {
value = "AAA"
}

let result = withTracking {
update(&original.nested.name)
}

#expect(result.writeIdentifiers.contains(.init(keyPath: \MyState.nested)))
#expect(result.writeIdentifiers.contains(.init(keyPath: \MyState.nested.name)))
}

@Test
func tracking_computed_property() {
Expand All @@ -26,7 +71,7 @@ struct Tests {
let _ = original.computedName
}

#expect(result.readIdentifiers.contains(.init(name: "name")))
#expect(result.readIdentifiers.contains(.init(keyPath: \MyState.name)))

}

Expand Down

0 comments on commit 2a46d70

Please sign in to comment.