Skip to content

Commit 3b274ef

Browse files
committed
Implement tracking of syntax nodes if the syntax tree is edited
1 parent dd37387 commit 3b274ef

File tree

15 files changed

+2804
-6
lines changed

15 files changed

+2804
-6
lines changed

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

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -152,6 +152,23 @@ func syntaxNode(nodesStartingWith: [Character]) -> SourceFileSyntax {
152152
initializer: InitializerClauseSyntax(value: initializer)
153153
)
154154

155+
VariableDeclSyntax(
156+
.let,
157+
name: "nodes",
158+
initializer: InitializerClauseSyntax(
159+
value: ArrayExprSyntax {
160+
for child in node.children {
161+
if child.isOptional {
162+
ArrayElementSyntax(expression: ExprSyntax("\(child.varOrCaseName.backtickedIfNeeded)?.data"))
163+
} else {
164+
ArrayElementSyntax(expression: ExprSyntax("\(child.varOrCaseName.backtickedIfNeeded).data"))
165+
}
166+
}
167+
}
168+
)
169+
)
170+
ExprSyntax("data.setSyntaxTrackingOfTree(SyntaxTracking(tracking: nodes))")
171+
155172
ExprSyntax("self.init(data)")
156173
}
157174

Sources/SwiftSyntax/Syntax.swift

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -235,7 +235,9 @@ public extension SyntaxProtocol {
235235
// Make sure `self` (and thus the arena of `self.raw`) can’t get deallocated
236236
// before the detached node can be created.
237237
return withExtendedLifetime(self) {
238-
return Syntax(raw: self.raw, rawNodeArena: self.raw.arena).cast(Self.self)
238+
let result = Syntax(raw: self.raw, rawNodeArena: self.raw.arena).cast(Self.self)
239+
result.data.setSyntaxTrackingOfTree(SyntaxTracking(trackingRoot: self.data))
240+
return result
239241
}
240242
}
241243
}

Sources/SwiftSyntax/SyntaxCollection.swift

Lines changed: 24 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -56,7 +56,9 @@ extension SyntaxCollection {
5656
arena: arena
5757
)
5858
}
59-
self.init(SyntaxData.forRoot(raw, rawNodeArena: arena))
59+
let data = SyntaxData.forRoot(raw, rawNodeArena: arena)
60+
data.setSyntaxTrackingOfTree(SyntaxTracking(tracking: children.map { $0.data }))
61+
self.init(data)
6062
}
6163

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

Sources/SwiftSyntax/SyntaxData.swift

Lines changed: 24 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -194,9 +194,11 @@ struct SyntaxData {
194194
// For root node.
195195
class Root {
196196
var arena: SyntaxArena
197+
var syntaxTracking: SyntaxTracking?
197198

198-
init(arena: SyntaxArena) {
199+
init(arena: SyntaxArena, syntaxTracking: SyntaxTracking?) {
199200
self.arena = arena
201+
self.syntaxTracking = syntaxTracking
200202
}
201203
}
202204

@@ -258,6 +260,18 @@ struct SyntaxData {
258260
absoluteInfo.nodeId
259261
}
260262

263+
var syntaxTracking: SyntaxTracking? {
264+
rootInfo.syntaxTracking
265+
}
266+
267+
/// Set the translation ranges of the entire tree.
268+
///
269+
/// Must only be called once for every tree.
270+
func setSyntaxTrackingOfTree(_ syntaxTracking: SyntaxTracking?) {
271+
precondition(rootInfo.syntaxTracking == nil)
272+
rootInfo.syntaxTracking = syntaxTracking
273+
}
274+
261275
/// The position of the start of this node's leading trivia
262276
var position: AbsolutePosition {
263277
AbsolutePosition(utf8Offset: Int(absoluteInfo.offset))
@@ -305,7 +319,7 @@ struct SyntaxData {
305319
/// has a chance to retain it.
306320
static func forRoot(_ raw: RawSyntax, rawNodeArena: SyntaxArena) -> SyntaxData {
307321
precondition(rawNodeArena === raw.arena)
308-
return SyntaxData(raw, info: .root(.init(arena: rawNodeArena)))
322+
return SyntaxData(raw, info: .root(.init(arena: rawNodeArena, syntaxTracking: nil)))
309323
}
310324

311325
/// Returns the child data at the provided index in this data's layout.
@@ -377,7 +391,14 @@ struct SyntaxData {
377391
/// `newChild` has been addded to the result.
378392
func replacingChild(at index: Int, with newChild: SyntaxData?, arena: SyntaxArena) -> SyntaxData {
379393
return withExtendedLifetime(newChild) {
380-
return replacingChild(at: index, with: newChild?.raw, rawNodeArena: newChild?.raw.arena, allocationArena: arena)
394+
let result = replacingChild(at: index, with: newChild?.raw, rawNodeArena: newChild?.raw.arena, allocationArena: arena)
395+
if trackedTree != nil {
396+
var iter = RawSyntaxChildren(absoluteRaw).makeIterator()
397+
for _ in 0..<index { _ = iter.next() }
398+
let (raw, info) = iter.next()!
399+
result.rootInfo.syntaxTracking = syntaxTracking?.replacing(oldIndexInTree: info.nodeId.indexInTree, oldTotalNodes: raw?.totalNodes ?? 0, by: newChild)
400+
}
401+
return result
381402
}
382403
}
383404

0 commit comments

Comments
 (0)