Skip to content

Commit fa1fc74

Browse files
committed
Implement tracking of syntax nodes if the syntax tree is edited
1 parent 9faf476 commit fa1fc74

18 files changed

+2921
-8
lines changed

CodeGeneration/Sources/generate-swift-syntax/templates/swiftsyntax/SyntaxNodesFile.swift

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -128,6 +128,20 @@ func syntaxNode(nodesStartingWith: [Character]) -> SourceFileSyntax {
128128
operator: ExprSyntax(AssignmentExprSyntax()),
129129
rightOperand: initializer
130130
)
131+
132+
VariableDeclSyntax(
133+
.let,
134+
name: "nodes",
135+
type: TypeAnnotationSyntax(type: TypeSyntax("[Syntax?]")),
136+
initializer: InitializerClauseSyntax(
137+
value: ArrayExprSyntax {
138+
for child in node.children {
139+
ArrayElementSyntax(expression: ExprSyntax("Syntax(\(child.varOrCaseName.backtickedIfNeeded))"))
140+
}
141+
}
142+
)
143+
)
144+
ExprSyntax("Syntax(self).setSyntaxTrackingOfTree(SyntaxTracking(tracking: nodes))")
131145
}
132146

133147
for (index, child) in node.children.enumerated() {

Sources/SwiftSyntax/CMakeLists.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,7 @@ add_swift_syntax_library(SwiftSyntax
3434
SyntaxNodeStructure.swift
3535
SyntaxProtocol.swift
3636
SyntaxText.swift
37+
SyntaxTracking.swift
3738
SyntaxTreeViewMode.swift
3839
TokenDiagnostic.swift
3940
TokenSequence.swift

Sources/SwiftSyntax/Syntax.swift

Lines changed: 63 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -24,9 +24,11 @@ public struct Syntax: SyntaxProtocol, SyntaxHashable {
2424
// For root node.
2525
struct Root: Sendable {
2626
private let arena: RetainedSyntaxArena
27+
var syntaxTracking: SyntaxTracking?
2728

28-
init(arena: RetainedSyntaxArena) {
29+
init(arena: RetainedSyntaxArena, syntaxTracking: SyntaxTracking?) {
2930
self.arena = arena
31+
self.syntaxTracking = syntaxTracking
3032
}
3133
}
3234

@@ -52,6 +54,17 @@ public struct Syntax: SyntaxProtocol, SyntaxHashable {
5254
fileprivate var pointer: UnsafePointer<Root> {
5355
return withUnsafePointer(to: &value) { $0 }
5456
}
57+
58+
/// Get a reference to the root info which can be mutated.
59+
///
60+
/// - Warning: This must only be used if the caller is guaranteed to have exclusive access to the tree and no
61+
/// concurrent accesses can happen. Effectively, this can only be guaranteed if the tree has just been created,
62+
/// and it hasn't been returned to any function which might concurrently access it.
63+
/// In practice, this should only be used to set the `SyntaxTracking` on the root after a new tree has been
64+
/// created (eg. by replacing a child in an existing tree) but before that new tree is returned to the client.
65+
fileprivate var mutablePointer: UnsafeMutablePointer<Root> {
66+
return withUnsafeMutablePointer(to: &value) { $0 }
67+
}
5568
}
5669

5770
// For non-root nodes.
@@ -111,6 +124,20 @@ public struct Syntax: SyntaxProtocol, SyntaxHashable {
111124
}
112125
}
113126

127+
/// Get a reference to the root info which can be mutated.
128+
///
129+
/// - Warning: This must only be used if the caller is guaranteed to have exclusive access to the tree and no
130+
/// concurrent accesses can happen. Effectively, this can only be guaranteed if the tree has just been created,
131+
/// and it hasn't been returned to any function which might concurrently access it.
132+
/// In practice, this should only be used to set the `SyntaxTracking` on the root after a new tree has been
133+
/// created (eg. by replacing a child in an existing tree) but before that new tree is returned to the client.
134+
var mutableRootInfo: UnsafeMutablePointer<Info.Root> {
135+
switch info.info! {
136+
case .root(let info): return info.mutablePointer
137+
case .nonRoot(let info): return info.parent.mutableRootInfo
138+
}
139+
}
140+
114141
private var nonRootInfo: Info.NonRoot? {
115142
switch info.info! {
116143
case .root(_): return nil
@@ -145,6 +172,20 @@ public struct Syntax: SyntaxProtocol, SyntaxHashable {
145172
absoluteInfo.nodeId
146173
}
147174

175+
var syntaxTracking: SyntaxTracking? {
176+
rootInfo.pointee.syntaxTracking
177+
}
178+
179+
/// Set the translation ranges of the entire tree.
180+
///
181+
/// - Warning: This must only be used if the caller is guaranteed to have exclusive access to the tree and no
182+
/// concurrent accesses can happen. Effectively, this can only be guaranteed if the tree has just been created,
183+
/// and it hasn't been returned to any function which might concurrently access it.
184+
func setSyntaxTrackingOfTree(_ syntaxTracking: SyntaxTracking?) {
185+
precondition(rootInfo.pointee.syntaxTracking == nil)
186+
mutableRootInfo.pointee.syntaxTracking = syntaxTracking
187+
}
188+
148189
/// The position of the start of this node's leading trivia
149190
public var position: AbsolutePosition {
150191
AbsolutePosition(utf8Offset: Int(absoluteInfo.offset))
@@ -192,14 +233,21 @@ public struct Syntax: SyntaxProtocol, SyntaxHashable {
192233
/// has a chance to retain it.
193234
static func forRoot(_ raw: RawSyntax, rawNodeArena: RetainedSyntaxArena) -> Syntax {
194235
precondition(rawNodeArena == raw.arenaReference)
195-
return Syntax(raw, info: Info(.root(Syntax.Info.RefCountedRoot(Syntax.Info.Root(arena: rawNodeArena)))))
236+
return Syntax(
237+
raw,
238+
info: Info(.root(Syntax.Info.RefCountedRoot(Syntax.Info.Root(arena: rawNodeArena, syntaxTracking: nil))))
239+
)
196240
}
197241

198242
static func forRoot(_ raw: RawSyntax, rawNodeArena: SyntaxArena) -> Syntax {
199243
precondition(rawNodeArena == raw.arenaReference)
200244
return Syntax(
201245
raw,
202-
info: Info(.root(Syntax.Info.RefCountedRoot(Syntax.Info.Root(arena: RetainedSyntaxArena(rawNodeArena)))))
246+
info: Info(
247+
.root(
248+
Syntax.Info.RefCountedRoot(Syntax.Info.Root(arena: RetainedSyntaxArena(rawNodeArena), syntaxTracking: nil))
249+
)
250+
)
203251
)
204252
}
205253

@@ -296,12 +344,23 @@ public struct Syntax: SyntaxProtocol, SyntaxHashable {
296344
/// `newChild` has been addded to the result.
297345
func replacingChild(at index: Int, with newChild: Syntax?, arena: SyntaxArena) -> Syntax {
298346
return withExtendedLifetime(newChild) {
299-
return replacingChild(
347+
let result = replacingChild(
300348
at: index,
301349
with: newChild?.raw,
302350
rawNodeArena: newChild?.raw.arenaReference.retained,
303351
allocationArena: arena
304352
)
353+
if trackedTree != nil {
354+
var iter = RawSyntaxChildren(absoluteRaw).makeIterator()
355+
for _ in 0..<index { _ = iter.next() }
356+
let (raw, info) = iter.next()!
357+
result.mutableRootInfo.pointee.syntaxTracking = syntaxTracking?.replacing(
358+
oldIndexInTree: info.nodeId.indexInTree,
359+
oldTotalNodes: raw?.totalNodes ?? 0,
360+
by: newChild
361+
)
362+
}
363+
return result
305364
}
306365
}
307366

Sources/SwiftSyntax/SyntaxCollection.swift

Lines changed: 24 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -50,6 +50,7 @@ extension SyntaxCollection {
5050
)
5151
}
5252
self = Syntax.forRoot(raw, rawNodeArena: RetainedSyntaxArena(arena)).cast(Self.self)
53+
Syntax(self).setSyntaxTrackingOfTree(SyntaxTracking(tracking: children.map { Syntax($0) }))
5354
}
5455

5556
public init(arrayLiteral elements: Element...) {
@@ -266,7 +267,29 @@ extension SyntaxCollection {
266267
let layoutRangeLowerBound = (subrange.lowerBound.data?.indexInParent).map(Int.init) ?? newLayout.endIndex
267268
let layoutRangeUpperBound = (subrange.upperBound.data?.indexInParent).map(Int.init) ?? newLayout.endIndex
268269
newLayout.replaceSubrange(layoutRangeLowerBound..<layoutRangeUpperBound, with: newElements.map { $0.raw })
269-
self = replacingLayout(newLayout)
270+
if var newSyntaxTracking = Syntax(self).syntaxTracking {
271+
// Perform the tracking transformation in two steps.
272+
// 1. Remove all old elements
273+
newSyntaxTracking = newSyntaxTracking.replacing(
274+
oldIndexInTree: subrange.lowerBound.data!.indexInTree,
275+
oldTotalNodes: Int(
276+
subrange.upperBound.data!.indexInTree.indexInTree - subrange.lowerBound.data!.indexInTree.indexInTree
277+
),
278+
by: nil
279+
)
280+
// 2. Now insert all the new elements.
281+
for element in newElements.reversed() {
282+
newSyntaxTracking = newSyntaxTracking.replacing(
283+
oldIndexInTree: subrange.lowerBound.data!.indexInTree,
284+
oldTotalNodes: 0,
285+
by: Syntax(element)
286+
)
287+
}
288+
self = replacingLayout(newLayout)
289+
Syntax(self).setSyntaxTrackingOfTree(newSyntaxTracking)
290+
} else {
291+
self = replacingLayout(newLayout)
292+
}
270293
}
271294
}
272295

Sources/SwiftSyntax/SyntaxIdentifier.swift

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -39,7 +39,7 @@ public struct SyntaxIdentifier: Comparable, Hashable, Sendable {
3939
/// This is similar to ``SyntaxIdentifier`` but does not store the root ID of the tree.
4040
/// It can thus be transferred across trees that are structurally equivalent, for example two copies of the same tree
4141
/// that live in different processes.
42-
public struct SyntaxIndexInTree: Hashable, Sendable {
42+
public struct SyntaxIndexInTree: Comparable, Hashable, Sendable {
4343
/// When traversing the syntax tree using a depth-first traversal, the index at which the node will be visited.
4444
let indexInTree: UInt32
4545

@@ -78,6 +78,11 @@ public struct SyntaxIdentifier: Comparable, Hashable, Sendable {
7878
public init(fromOpaque opaque: UInt64) {
7979
self.indexInTree = UInt32(opaque)
8080
}
81+
82+
/// Returns `true` if `lhs` occurs before `rhs` in the tree.
83+
public static func < (lhs: SyntaxIndexInTree, rhs: SyntaxIndexInTree) -> Bool {
84+
return lhs.indexInTree < rhs.indexInTree
85+
}
8186
}
8287

8388
/// Unique value for the root node.

Sources/SwiftSyntax/SyntaxProtocol.swift

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -159,7 +159,9 @@ extension SyntaxProtocol {
159159
// Make sure `self` (and thus the arena of `self.raw`) can’t get deallocated
160160
// before the detached node can be created.
161161
return withExtendedLifetime(self) {
162-
return Syntax(raw: self.raw, rawNodeArena: self.raw.arenaReference.retained).cast(Self.self)
162+
let result = Syntax(raw: self.raw, rawNodeArena: self.raw.arenaReference.retained)
163+
result.setSyntaxTrackingOfTree(SyntaxTracking(trackingRoot: Syntax(self)))
164+
return result.cast(Self.self)
163165
}
164166
}
165167
}

0 commit comments

Comments
 (0)