Skip to content

Commit 5355c79

Browse files
authored
Merge pull request swiftlang#85 from akyrtzi/parse-transition-simplify
[IncrementalParseTransition] Simplify implementation of IncrementalParseTransition
2 parents a4ecc17 + 373ae3c commit 5355c79

File tree

4 files changed

+63
-35
lines changed

4 files changed

+63
-35
lines changed

Sources/SwiftSyntax/IncrementalParseTransition.swift

Lines changed: 24 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -104,48 +104,35 @@ public final class IncrementalParseTransition {
104104
guard let prevOffset = translateToPreEditOffset(newOffset) else {
105105
return nil
106106
}
107-
let (nodeOffset, node) = lookUpFrom(previousTree, nodeOffset: 0,
108-
prevOffset: prevOffset, kind: kind)
107+
let prevPosition = AbsolutePosition(utf8Offset: prevOffset)
108+
let node = lookUpFrom(previousTree, prevPosition: prevPosition, kind: kind)
109109
if let delegate = reusedDelegate, let node = node {
110110
delegate.parserReusedNode(
111-
range: ByteSourceRange(offset: nodeOffset, length: node.byteSize),
111+
range: ByteSourceRange(offset: newOffset, length: node.byteSize),
112112
previousNode: node)
113113
}
114114
return node
115115
}
116116

117-
fileprivate func lookUpFrom(_ node: _SyntaxBase, nodeOffset: Int, prevOffset: Int,
118-
kind: SyntaxKind) -> (Int, _SyntaxBase?) {
119-
if nodeCanBeReused(node, nodeOffset: nodeOffset, prevOffset: prevOffset,
120-
kind: kind) {
121-
return (nodeOffset, node)
117+
fileprivate func lookUpFrom(
118+
_ node: _SyntaxBase, prevPosition: AbsolutePosition, kind: SyntaxKind
119+
) -> _SyntaxBase? {
120+
if nodeCanBeReused(node, prevPosition: prevPosition, kind: kind) {
121+
return node
122122
}
123123

124-
// Compute the child's position on the fly
125-
var childOffset = nodeOffset
126124
for child in node.children {
127-
if child.isMissing {
128-
continue
125+
if child.position <= prevPosition && prevPosition < child.endPosition {
126+
return lookUpFrom(child, prevPosition: prevPosition, kind: kind)
129127
}
130-
let childEnd = childOffset + child.byteSize
131-
if childOffset <= prevOffset && prevOffset < childEnd {
132-
return lookUpFrom(child, nodeOffset: childOffset,
133-
prevOffset: prevOffset, kind: kind)
134-
}
135-
// The next child starts where the previous child ended
136-
childOffset = childEnd
137128
}
138-
return (0, nil)
129+
return nil
139130
}
140131

141-
fileprivate func nodeCanBeReused(_ node: _SyntaxBase, nodeOffset: Int,
142-
prevOffset: Int, kind: SyntaxKind) -> Bool {
143-
// Computing the value of NodeStart on the fly is faster than determining a
144-
// node's absolute position, but make sure the values match in an assertion
145-
// build
146-
assert(nodeOffset == node.position.utf8Offset);
147-
148-
if nodeOffset != prevOffset {
132+
fileprivate func nodeCanBeReused(
133+
_ node: _SyntaxBase, prevPosition: AbsolutePosition, kind: SyntaxKind
134+
) -> Bool {
135+
if node.position != prevPosition {
149136
return false
150137
}
151138
if node.raw.kind != kind {
@@ -158,17 +145,20 @@ public final class IncrementalParseTransition {
158145
// CodeBlockItems one for `private` and one for `struc Foo {}`
159146
var nextLeafNodeLength = 0
160147
if let nextToken = node.nextToken {
161-
let nextRawNode = nextToken.raw
162-
assert(nextRawNode.isPresent)
163-
nextLeafNodeLength += nextRawNode.contentLength.utf8Length
164-
nextLeafNodeLength += nextRawNode.leadingTriviaLength.utf8Length
148+
assert(nextToken.isPresent)
149+
nextLeafNodeLength = nextToken.byteSize - nextToken.trailingTriviaLength.utf8Length
165150
}
166151

167152
for edit in edits {
168153
// Check if this node or the trivia of the next node has been edited. If
169154
// it has, we cannot reuse it.
170-
if edit.intersectsOrTouchesRange(ByteSourceRange(offset: nodeOffset,
171-
length: node.byteSize + nextLeafNodeLength)) {
155+
let nodeAffectRange = ByteSourceRange(offset: node.position.utf8Offset,
156+
length: node.byteSize + nextLeafNodeLength)
157+
if edit.range.offset > nodeAffectRange.endOffset {
158+
// Remaining edits don't affect the node. (Edits are sorted)
159+
break
160+
}
161+
if edit.intersectsOrTouchesRange(nodeAffectRange) {
172162
return false
173163
}
174164
}

Sources/SwiftSyntax/Syntax.swift

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -185,6 +185,11 @@ extension _SyntaxBase {
185185
return totalLength.utf8Length
186186
}
187187

188+
/// The byte source range of this node including leading and trailing trivia.
189+
var byteRange: ByteSourceRange {
190+
return ByteSourceRange(offset: position.utf8Offset, length: byteSize)
191+
}
192+
188193
/// The length this node takes up spelled out in the source, excluding its
189194
/// leading or trailing trivia.
190195
var contentLength: SourceLength {
@@ -398,6 +403,11 @@ extension Syntax {
398403
return base.byteSize
399404
}
400405

406+
/// The byte source range of this node including leading and trailing trivia.
407+
public var byteRange: ByteSourceRange {
408+
return base.byteRange
409+
}
410+
401411
/// The length this node takes up spelled out in the source, excluding its
402412
/// leading or trailing trivia.
403413
public var contentLength: SourceLength {

Sources/SwiftSyntax/Utils.swift

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@
1010
//
1111
//===----------------------------------------------------------------------===//
1212

13-
public struct ByteSourceRange {
13+
public struct ByteSourceRange: Equatable {
1414
public let offset: Int
1515
public let length: Int
1616

Tests/SwiftSyntaxTest/IncrementalParsingTests.swift

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ public class IncrementalParsingTestCase: XCTestCase {
55

66
public static let allTests = [
77
("testIncrementalInvalid", testIncrementalInvalid),
8+
("testReusedNode", testReusedNode),
89
]
910

1011
public func testIncrementalInvalid() {
@@ -18,4 +19,31 @@ public class IncrementalParsingTestCase: XCTestCase {
1819
tree = try! SyntaxParser.parse(source: step.0, parseTransition: lookup)
1920
XCTAssertEqual("\(tree)", step.0)
2021
}
22+
23+
public func testReusedNode() {
24+
let original = "struct A {}\nstruct B {}\n"
25+
let step: (String, (Int, Int, String)) =
26+
("struct AA {}\nstruct B {}\n", (8, 0, "A"))
27+
28+
let origTree = try! SyntaxParser.parse(source: original)
29+
let sourceEdit = SourceEdit(range: ByteSourceRange(offset: step.1.0, length: step.1.1), replacementLength: step.1.2.utf8.count)
30+
let reusedNodeCollector = IncrementalParseReusedNodeCollector()
31+
let transition = IncrementalParseTransition(previousTree: origTree, edits: [sourceEdit], reusedNodeDelegate: reusedNodeCollector)
32+
let newTree = try! SyntaxParser.parse(source: step.0, parseTransition: transition)
33+
XCTAssertEqual("\(newTree)", step.0)
34+
35+
let origStructB = origTree.statements[1] as! CodeBlockItemSyntax
36+
let newStructB = newTree.statements[1] as! CodeBlockItemSyntax
37+
XCTAssertEqual("\(origStructB)", "\nstruct B {}")
38+
XCTAssertEqual("\(newStructB)", "\nstruct B {}")
39+
XCTAssertNotEqual(origStructB, newStructB)
40+
41+
XCTAssertEqual(reusedNodeCollector.rangeAndNodes.count, 1)
42+
if reusedNodeCollector.rangeAndNodes.count != 1 { return }
43+
let rangeAndNode = reusedNodeCollector.rangeAndNodes[0]
44+
XCTAssertEqual("\(rangeAndNode.1)", "\nstruct B {}")
45+
46+
XCTAssertEqual(newStructB.byteRange, rangeAndNode.0)
47+
XCTAssertEqual(origStructB, rangeAndNode.1 as! CodeBlockItemSyntax)
48+
}
2149
}

0 commit comments

Comments
 (0)