Skip to content

Commit 4f29066

Browse files
committed
Implement tracking of syntax nodes if the syntax tree is edited
1 parent fcc986d commit 4f29066

17 files changed

+2823
-6
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
@@ -137,6 +137,20 @@ func syntaxNode(nodesStartingWith: [Character]) -> SourceFileSyntax {
137137
operator: ExprSyntax(AssignmentExprSyntax()),
138138
rightOperand: initializer
139139
)
140+
141+
VariableDeclSyntax(
142+
.let,
143+
name: "nodes",
144+
type: TypeAnnotationSyntax(type: TypeSyntax("[Syntax?]")),
145+
initializer: InitializerClauseSyntax(
146+
value: ArrayExprSyntax {
147+
for child in node.children {
148+
ArrayElementSyntax(expression: ExprSyntax("Syntax(\(child.varOrCaseName.backtickedIfNeeded))"))
149+
}
150+
}
151+
)
152+
)
153+
ExprSyntax("Syntax(self).setSyntaxTrackingOfTree(SyntaxTracking(tracking: nodes))")
140154
}
141155

142156
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
@@ -29,6 +29,7 @@ add_swift_host_library(SwiftSyntax
2929
SyntaxNodeStructure.swift
3030
SyntaxProtocol.swift
3131
SyntaxText.swift
32+
SyntaxTracking.swift
3233
SyntaxTreeViewMode.swift
3334
TokenDiagnostic.swift
3435
TokenSequence.swift

Sources/SwiftSyntax/Syntax.swift

Lines changed: 28 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -33,9 +33,11 @@ public struct Syntax: SyntaxProtocol, SyntaxHashable {
3333
// For root node.
3434
struct Root {
3535
var arena: SyntaxArena
36+
var syntaxTracking: SyntaxTracking?
3637

37-
init(arena: SyntaxArena) {
38+
init(arena: SyntaxArena, syntaxTracking: SyntaxTracking?) {
3839
self.arena = arena
40+
self.syntaxTracking = syntaxTracking
3941
}
4042
}
4143

@@ -97,6 +99,18 @@ public struct Syntax: SyntaxProtocol, SyntaxHashable {
9799
absoluteInfo.nodeId
98100
}
99101

102+
var syntaxTracking: SyntaxTracking? {
103+
rootInfo.pointee.syntaxTracking
104+
}
105+
106+
/// Set the translation ranges of the entire tree.
107+
///
108+
/// Must only be called once for every tree.
109+
func setSyntaxTrackingOfTree(_ syntaxTracking: SyntaxTracking?) {
110+
precondition(rootInfo.pointee.syntaxTracking == nil)
111+
rootInfo.pointee.syntaxTracking = syntaxTracking
112+
}
113+
100114
/// The position of the start of this node's leading trivia
101115
public var position: AbsolutePosition {
102116
AbsolutePosition(utf8Offset: Int(absoluteInfo.offset))
@@ -144,7 +158,7 @@ public struct Syntax: SyntaxProtocol, SyntaxHashable {
144158
/// has a chance to retain it.
145159
static func forRoot(_ raw: RawSyntax, rawNodeArena: SyntaxArena) -> Syntax {
146160
precondition(rawNodeArena === raw.arena)
147-
return Syntax(raw, info: .root(Wrapper(Info.Root(arena: rawNodeArena))))
161+
return Syntax(raw, info: .root(Wrapper(Info.Root(arena: rawNodeArena, syntaxTracking: nil))))
148162
}
149163

150164
/// Returns the child data at the provided index in this data's layout.
@@ -215,7 +229,18 @@ public struct Syntax: SyntaxProtocol, SyntaxHashable {
215229
/// `newChild` has been addded to the result.
216230
func replacingChild(at index: Int, with newChild: Syntax?, arena: SyntaxArena) -> Syntax {
217231
return withExtendedLifetime(newChild) {
218-
return replacingChild(at: index, with: newChild?.raw, rawNodeArena: newChild?.raw.arena, allocationArena: arena)
232+
let result = replacingChild(at: index, with: newChild?.raw, rawNodeArena: newChild?.raw.arena, allocationArena: arena)
233+
if trackedTree != nil {
234+
var iter = RawSyntaxChildren(absoluteRaw).makeIterator()
235+
for _ in 0..<index { _ = iter.next() }
236+
let (raw, info) = iter.next()!
237+
result.rootInfo.pointee.syntaxTracking = syntaxTracking?.replacing(
238+
oldIndexInTree: info.nodeId.indexInTree,
239+
oldTotalNodes: raw?.totalNodes ?? 0,
240+
by: newChild
241+
)
242+
}
243+
return result
219244
}
220245
}
221246

Sources/SwiftSyntax/SyntaxCollection.swift

Lines changed: 22 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: arena).cast(Self.self)
53+
Syntax(self).setSyntaxTrackingOfTree(SyntaxTracking(tracking: children.map { Syntax($0) }))
5354
}
5455

5556
public init(arrayLiteral elements: Element...) {
@@ -260,7 +261,27 @@ extension SyntaxCollection {
260261
let layoutRangeLowerBound = (subrange.lowerBound.data?.indexInParent).map(Int.init) ?? newLayout.endIndex
261262
let layoutRangeUpperBound = (subrange.upperBound.data?.indexInParent).map(Int.init) ?? newLayout.endIndex
262263
newLayout.replaceSubrange(layoutRangeLowerBound..<layoutRangeUpperBound, with: newElements.map { $0.raw })
263-
self = replacingLayout(newLayout)
264+
if var newSyntaxTracking = Syntax(self).syntaxTracking {
265+
// Perform the tracking transformation in two steps.
266+
// 1. Remove all old elements
267+
newSyntaxTracking = newSyntaxTracking.replacing(
268+
oldIndexInTree: subrange.lowerBound.data!.indexInTree,
269+
oldTotalNodes: Int(subrange.upperBound.data!.indexInTree.indexInTree - subrange.lowerBound.data!.indexInTree.indexInTree),
270+
by: nil
271+
)
272+
// 2. Now insert all the new elements.
273+
for element in newElements.reversed() {
274+
newSyntaxTracking = newSyntaxTracking.replacing(
275+
oldIndexInTree: subrange.lowerBound.data!.indexInTree,
276+
oldTotalNodes: 0,
277+
by: Syntax(element)
278+
)
279+
}
280+
self = replacingLayout(newLayout)
281+
Syntax(self).setSyntaxTrackingOfTree(newSyntaxTracking)
282+
} else {
283+
self = replacingLayout(newLayout)
284+
}
264285
}
265286
}
266287

Sources/SwiftSyntax/SyntaxProtocol.swift

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -159,7 +159,9 @@ public 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.arena).cast(Self.self)
162+
let result = Syntax(raw: self.raw, rawNodeArena: self.raw.arena)
163+
result.setSyntaxTrackingOfTree(SyntaxTracking(trackingRoot: Syntax(self)))
164+
return result.cast(Self.self)
163165
}
164166
}
165167
}

0 commit comments

Comments
 (0)