Skip to content

Commit 4180bb0

Browse files
author
Nathan Hawes
authored
Merge pull request swiftlang#17 from nathawes/r44877860-hold-on-to-omitted-syntax-nodes-across-multiple-incremental-updates
SyntaxTreeDeserializer: Hold on to omitted nodes across multiple updates
2 parents a997013 + c306a90 commit 4180bb0

7 files changed

+185
-10
lines changed

Sources/SwiftSyntax/RawSyntax.swift

+12-9
Original file line numberDiff line numberDiff line change
@@ -124,13 +124,6 @@ final class RawSyntax {
124124
}
125125
}).value
126126
}
127-
128-
/// Creates a copy of `other`.
129-
init(_ other: RawSyntax) {
130-
self.data = other.data
131-
self.presence = other.presence
132-
self.id = other.id
133-
}
134127

135128
init(kind: SyntaxKind, layout: [RawSyntax?], presence: SourcePresence,
136129
id: SyntaxNodeId? = nil) {
@@ -341,7 +334,17 @@ extension RawSyntax {
341334
}
342335
}
343336

344-
extension RawSyntax: Codable {
337+
// This is needed purely to have a self assignment initializer for
338+
// RawSyntax.init(from:Decoder) so we can reuse omitted nodes, instead of
339+
// copying them.
340+
private protocol _RawSyntaxFactory {}
341+
extension _RawSyntaxFactory {
342+
init(_instance: Self) {
343+
self = _instance
344+
}
345+
}
346+
347+
extension RawSyntax: Codable, _RawSyntaxFactory {
345348
/// Creates a RawSyntax from the provided Foundation Decoder.
346349
convenience init(from decoder: Decoder) throws {
347350
let container = try decoder.container(keyedBy: CodingKeys.self)
@@ -359,7 +362,7 @@ extension RawSyntax: Codable {
359362
guard let lookupNode = lookupFunc(id) else {
360363
throw IncrementalDecodingError.nodeLookupFailed(id)
361364
}
362-
self.init(lookupNode)
365+
self.init(_instance: lookupNode)
363366
return
364367
}
365368

Sources/SwiftSyntax/WeakLookupTable.swift

+3-1
Original file line numberDiff line numberDiff line change
@@ -188,7 +188,9 @@ class WeakLookupTable<Element: Identifiable & AnyObject> {
188188
return reserveCapacity(estimatedCount &+ 1)
189189
}
190190

191-
/// Inserts the given object into the table.
191+
/// Inserts the given object into the table if the table doesn't already
192+
/// contain an object with the same identifier.
193+
/// - returns: true if the object was inserted
192194
@discardableResult
193195
func insert(_ obj: Element) -> Bool {
194196
var (pos, alreadyExists) = _findHole(obj.id)

Tests/SwiftSyntaxTest/DeserializeFile.swift

+14
Original file line numberDiff line numberDiff line change
@@ -15,4 +15,18 @@ public class DecodeSyntaxTestCase: XCTestCase {
1515
XCTAssertEqual("\(parsed)", source)
1616
}())
1717
}
18+
19+
public func testIncrementalDeserialize() {
20+
XCTAssertNoThrow(try {
21+
let deserializer = SyntaxTreeDeserializer()
22+
var tree: SourceFileSyntax? = nil
23+
let responses = try (1...3).map {
24+
try Data(contentsOf: getInput("incremental-deserialize-\($0).json"))
25+
}
26+
for data in responses {
27+
tree = try deserializer.deserialize(data, serializationFormat: .json)
28+
}
29+
XCTAssertEqual(tree?.description, "x\ny")
30+
}())
31+
}
1832
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,51 @@
1+
{
2+
"id":39,
3+
"kind":"SourceFile",
4+
"layout":[
5+
{
6+
"id":38,
7+
"kind":"CodeBlockItemList",
8+
"layout":[
9+
{
10+
"id":35,
11+
"kind":"CodeBlockItem",
12+
"layout":[
13+
{
14+
"id":34,
15+
"kind":"IdentifierExpr",
16+
"layout":[
17+
{
18+
"id":33,
19+
"tokenKind":{
20+
"kind":"identifier",
21+
"text":"x"
22+
},
23+
"leadingTrivia":[],
24+
"trailingTrivia":[],
25+
"presence":"Present"
26+
},
27+
null
28+
],
29+
"presence":"Present"
30+
},
31+
null,
32+
null
33+
],
34+
"presence":"Present"
35+
}
36+
],
37+
"presence":"Present"
38+
},
39+
{
40+
"id":37,
41+
"tokenKind":{
42+
"kind":"eof",
43+
"text":""
44+
},
45+
"leadingTrivia":[],
46+
"trailingTrivia":[],
47+
"presence":"Present"
48+
}
49+
],
50+
"presence":"Present"
51+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
{
2+
"id":45,
3+
"kind":"SourceFile",
4+
"layout":[
5+
{
6+
"id":44,
7+
"kind":"CodeBlockItemList",
8+
"layout":[
9+
{
10+
"id":35,
11+
"omitted":true
12+
}
13+
],
14+
"presence":"Present"
15+
},
16+
{
17+
"id":43,
18+
"tokenKind":{
19+
"kind":"eof",
20+
"text":""
21+
},
22+
"leadingTrivia":[
23+
{
24+
"kind":"Newline",
25+
"value":1
26+
}
27+
],
28+
"trailingTrivia":[],
29+
"presence":"Present"
30+
}
31+
],
32+
"presence":"Present"
33+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,60 @@
1+
{
2+
"id":54,
3+
"kind":"SourceFile",
4+
"layout":[
5+
{
6+
"id":53,
7+
"kind":"CodeBlockItemList",
8+
"layout":[
9+
{
10+
"id":35,
11+
"omitted":true
12+
},
13+
{
14+
"id":50,
15+
"kind":"CodeBlockItem",
16+
"layout":[
17+
{
18+
"id":49,
19+
"kind":"IdentifierExpr",
20+
"layout":[
21+
{
22+
"id":48,
23+
"tokenKind":{
24+
"kind":"identifier",
25+
"text":"y"
26+
},
27+
"leadingTrivia":[
28+
{
29+
"kind":"Newline",
30+
"value":1
31+
}
32+
],
33+
"trailingTrivia":[],
34+
"presence":"Present"
35+
},
36+
null
37+
],
38+
"presence":"Present"
39+
},
40+
null,
41+
null
42+
],
43+
"presence":"Present"
44+
}
45+
],
46+
"presence":"Present"
47+
},
48+
{
49+
"id":52,
50+
"tokenKind":{
51+
"kind":"eof",
52+
"text":""
53+
},
54+
"leadingTrivia":[],
55+
"trailingTrivia":[],
56+
"presence":"Present"
57+
}
58+
],
59+
"presence":"Present"
60+
}

Tests/SwiftSyntaxTest/WeakLookupTableTest.swift

+12
Original file line numberDiff line numberDiff line change
@@ -108,6 +108,18 @@ public class WeakLookupTableTestCase: XCTestCase {
108108

109109
_ = tree // Prevents 'never read' warning
110110
}
111+
112+
func testSameIdentifierDifferentObject() {
113+
let table = WeakLookupTable<MinimalNode>()
114+
115+
let object1 = MinimalNode(id: "1", children: [])
116+
let object2 = MinimalNode(id: "1", children: [])
117+
118+
XCTAssertTrue(table.insert(object1))
119+
XCTAssert(table["1"] === object1)
120+
XCTAssertFalse(table.insert(object2))
121+
XCTAssert(table["1"] === object1)
122+
}
111123
}
112124

113125
#endif // DEBUG

0 commit comments

Comments
 (0)