Skip to content

Commit b89ef8e

Browse files
committed
Implement tracking of syntax nodes if the syntax tree is edited
1 parent 79d12fe commit b89ef8e

File tree

16 files changed

+2817
-6
lines changed

16 files changed

+2817
-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/CMakeLists.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@ add_swift_host_library(SwiftSyntax
2525
SyntaxCollection.swift
2626
SyntaxData.swift
2727
SyntaxText.swift
28+
SyntaxTracking.swift
2829
SyntaxTreeViewMode.swift
2930
TokenDiagnostic.swift
3031
TokenSyntax.swift

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: 28 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -206,9 +206,11 @@ struct SyntaxData {
206206
// For root node.
207207
struct Root {
208208
var arena: SyntaxArena
209+
var syntaxTracking: SyntaxTracking?
209210

210-
init(arena: SyntaxArena) {
211+
init(arena: SyntaxArena, syntaxTracking: SyntaxTracking?) {
211212
self.arena = arena
213+
self.syntaxTracking = syntaxTracking
212214
}
213215
}
214216

@@ -270,6 +272,18 @@ struct SyntaxData {
270272
absoluteInfo.nodeId
271273
}
272274

275+
var syntaxTracking: SyntaxTracking? {
276+
rootInfo.pointee.syntaxTracking
277+
}
278+
279+
/// Set the translation ranges of the entire tree.
280+
///
281+
/// Must only be called once for every tree.
282+
func setSyntaxTrackingOfTree(_ syntaxTracking: SyntaxTracking?) {
283+
precondition(rootInfo.pointee.syntaxTracking == nil)
284+
rootInfo.pointee.syntaxTracking = syntaxTracking
285+
}
286+
273287
/// The position of the start of this node's leading trivia
274288
var position: AbsolutePosition {
275289
AbsolutePosition(utf8Offset: Int(absoluteInfo.offset))
@@ -317,7 +331,7 @@ struct SyntaxData {
317331
/// has a chance to retain it.
318332
static func forRoot(_ raw: RawSyntax, rawNodeArena: SyntaxArena) -> SyntaxData {
319333
precondition(rawNodeArena === raw.arena)
320-
return SyntaxData(raw, info: .root(Wrapper(Info.Root(arena: rawNodeArena))))
334+
return SyntaxData(raw, info: .root(Wrapper(Info.Root(arena: rawNodeArena, syntaxTracking: nil))))
321335
}
322336

323337
/// Returns the child data at the provided index in this data's layout.
@@ -387,7 +401,18 @@ struct SyntaxData {
387401
/// `newChild` has been addded to the result.
388402
func replacingChild(at index: Int, with newChild: SyntaxData?, arena: SyntaxArena) -> SyntaxData {
389403
return withExtendedLifetime(newChild) {
390-
return replacingChild(at: index, with: newChild?.raw, rawNodeArena: newChild?.raw.arena, allocationArena: arena)
404+
let result = replacingChild(at: index, with: newChild?.raw, rawNodeArena: newChild?.raw.arena, allocationArena: arena)
405+
if trackedTree != nil {
406+
var iter = RawSyntaxChildren(absoluteRaw).makeIterator()
407+
for _ in 0..<index { _ = iter.next() }
408+
let (raw, info) = iter.next()!
409+
result.rootInfo.pointee.syntaxTracking = syntaxTracking?.replacing(
410+
oldIndexInTree: info.nodeId.indexInTree,
411+
oldTotalNodes: raw?.totalNodes ?? 0,
412+
by: newChild
413+
)
414+
}
415+
return result
391416
}
392417
}
393418

0 commit comments

Comments
 (0)