Skip to content

Commit 2165d02

Browse files
committed
[Experiment] Make Syntax nodes structs instead of classes
1 parent bf439cd commit 2165d02

14 files changed

+184
-91
lines changed

test/SwiftSyntax/LazyCaching.swift

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,8 @@ LazyCaching.test("Pathological") {
1515
let tuple = SyntaxFactory.makeVoidTupleType()
1616

1717
DispatchQueue.concurrentPerform(iterations: 100) { _ in
18-
expectEqual(tuple.leftParen, tuple.leftParen)
18+
expectEqual(tuple.leftParen.uniqueIdentifier,
19+
tuple.leftParen.uniqueIdentifier)
1920
}
2021
}
2122

@@ -41,9 +42,9 @@ LazyCaching.test("TwoAccesses") {
4142

4243
expectNotNil(node1)
4344
expectNotNil(node2)
44-
expectEqual(node1, node2)
45-
expectEqual(node1, final)
46-
expectEqual(node2, final)
45+
expectEqual(node1?.uniqueIdentifier, node2?.uniqueIdentifier)
46+
expectEqual(node1?.uniqueIdentifier, final.uniqueIdentifier)
47+
expectEqual(node2?.uniqueIdentifier, final.uniqueIdentifier)
4748
}
4849

4950
runAllTests()

test/SwiftSyntax/SyntaxFactory.swift

Lines changed: 7 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -52,15 +52,18 @@ SyntaxFactoryAPI.test("Generated") {
5252
}
5353
""")
5454

55-
expectNotEqual(structDecl.members, renamed.members)
56-
expectEqual(structDecl, structDecl.root)
55+
expectNotEqual(structDecl.members.uniqueIdentifier,
56+
renamed.members.uniqueIdentifier)
57+
expectEqual(structDecl.uniqueIdentifier, structDecl.root.uniqueIdentifier)
5758
expectNil(structDecl.parent)
5859
expectNotNil(structDecl.members.parent)
59-
expectEqual(structDecl.members.parent, structDecl)
60+
expectEqual(structDecl.members.parent?.uniqueIdentifier,
61+
structDecl.uniqueIdentifier)
6062

6163
// Ensure that accessing children via named identifiers is exactly the
6264
// same as accessing them as their underlying data.
63-
expectEqual(structDecl.members, structDecl.child(at: 7))
65+
expectEqual(structDecl.members.uniqueIdentifier,
66+
structDecl.child(at: 7)?.uniqueIdentifier)
6467

6568
expectEqual("\(structDecl.members.rightBrace)",
6669
"""

test/SwiftSyntax/VisitorTest.swift

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -31,10 +31,10 @@ VisitorTests.test("Basic") {
3131
expectDoesNotThrow({
3232
let parsed = try Syntax.parse(getInput("visitor.swift"))
3333
let counter = FuncCounter()
34-
let hashBefore = parsed.hashValue
34+
let idBefore = parsed.uniqueIdentifier
3535
counter.visit(parsed)
3636
expectEqual(counter.funcCount, 3)
37-
expectEqual(hashBefore, parsed.hashValue)
37+
expectEqual(idBefore, parsed.uniqueIdentifier)
3838
})
3939
}
4040

tools/SwiftSyntax/SwiftSyntax.swift

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -44,7 +44,7 @@ extension Syntax {
4444
}
4545
let decoder = JSONDecoder()
4646
let raw = try decoder.decode(RawSyntax.self, from: result.stdoutData)
47-
guard let file = Syntax.fromRaw(raw) as? SourceFileSyntax else {
47+
guard let file = makeSyntax(raw) as? SourceFileSyntax else {
4848
throw ParserError.invalidFile
4949
}
5050
return file

tools/SwiftSyntax/Syntax.swift

Lines changed: 53 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -15,41 +15,58 @@ import Foundation
1515
/// A Syntax node represents a tree of nodes with tokens at the leaves.
1616
/// Each node has accessors for its known children, and allows efficient
1717
/// iteration over the children through its `children` property.
18-
public class Syntax: CustomStringConvertible {
18+
public protocol Syntax:
19+
CustomStringConvertible, TextOutputStreamable {}
20+
21+
internal protocol _SyntaxBase: Syntax {
1922
/// The type of sequence containing the indices of present children.
20-
internal typealias PresentChildIndicesSequence =
23+
typealias PresentChildIndicesSequence =
2124
LazyFilterSequence<CountableRange<Int>>
22-
25+
2326
/// The root of the tree this node is currently in.
24-
internal let _root: SyntaxData
27+
var _root: SyntaxData { get } // Must be of type SyntaxData
2528

2629
/// The data backing this node.
2730
/// - note: This is unowned, because the reference to the root data keeps it
2831
/// alive. This means there is an implicit relationship -- the data
2932
/// property must be a descendent of the root. This relationship must
3033
/// be preserved in all circumstances where Syntax nodes are created.
31-
internal unowned var data: SyntaxData
34+
var _data: SyntaxData { get }
3235

3336
#if DEBUG
34-
func validate() {
35-
// This is for subclasses to override to perform structural validation.
36-
}
37+
func validate()
3738
#endif
39+
}
40+
extension _SyntaxBase {
41+
public func validate() {
42+
// This is for implementers to override to perform structural validation.
43+
}
44+
}
3845

39-
/// Creates a Syntax node from the provided root and data.
40-
internal init(root: SyntaxData, data: SyntaxData) {
41-
self._root = root
42-
self.data = data
43-
#if DEBUG
44-
validate()
45-
#endif
46+
extension Syntax {
47+
var data: SyntaxData {
48+
guard let base = self as? _SyntaxBase else {
49+
fatalError("only first-class syntax nodes can conform to Syntax")
50+
}
51+
return base._data
52+
}
53+
54+
var _root: SyntaxData {
55+
guard let base = self as? _SyntaxBase else {
56+
fatalError("only first-class syntax nodes can conform to Syntax")
57+
}
58+
return base._root
4659
}
4760

4861
/// Access the raw syntax assuming the node is a Syntax.
4962
var raw: RawSyntax {
5063
return data.raw
5164
}
5265

66+
public var uniqueIdentifier: Int {
67+
return ObjectIdentifier(data).hashValue
68+
}
69+
5370
/// An iterator over children of this node.
5471
public var children: SyntaxChildren {
5572
return SyntaxChildren(node: self)
@@ -93,7 +110,7 @@ public class Syntax: CustomStringConvertible {
93110
/// The parent of this syntax node, or `nil` if this node is the root.
94111
public var parent: Syntax? {
95112
guard let parentData = data.parent else { return nil }
96-
return Syntax.make(root: _root, data: parentData)
113+
return makeSyntax(root: _root, data: parentData)
97114
}
98115

99116
/// The index of this node in the parent's children.
@@ -103,14 +120,14 @@ public class Syntax: CustomStringConvertible {
103120

104121
/// The root of the tree in which this node resides.
105122
public var root: Syntax {
106-
return Syntax.make(root: _root, data: _root)
123+
return makeSyntax(root: _root, data: _root)
107124
}
108125

109126
/// The sequence of indices that correspond to child nodes that are not
110127
/// missing.
111128
///
112129
/// This property is an implementation detail of `SyntaxChildren`.
113-
internal var presentChildIndices: PresentChildIndicesSequence {
130+
internal var presentChildIndices: _SyntaxBase.PresentChildIndicesSequence {
114131
return raw.layout.indices.lazy.filter { self.raw.layout[$0].isPresent }
115132
}
116133

@@ -121,7 +138,7 @@ public class Syntax: CustomStringConvertible {
121138
public func child(at index: Int) -> Syntax? {
122139
guard raw.layout.indices.contains(index) else { return nil }
123140
if raw.layout[index].isMissing { return nil }
124-
return Syntax.make(root: _root, data: data.cachedChild(at: index))
141+
return makeSyntax(root: _root, data: data.cachedChild(at: index))
125142
}
126143

127144
/// A source-accurate description of this node.
@@ -130,9 +147,7 @@ public class Syntax: CustomStringConvertible {
130147
self.write(to: &s)
131148
return s
132149
}
133-
}
134-
135-
extension Syntax: TextOutputStreamable {
150+
136151
/// Prints the raw value of this node to the provided stream.
137152
/// - Parameter stream: The stream to which to print the raw tree.
138153
public func write<Target>(to target: inout Target)
@@ -141,21 +156,27 @@ extension Syntax: TextOutputStreamable {
141156
}
142157
}
143158

144-
extension Syntax: Hashable {
145-
/// Determines if two nodes are equal to each other.
146-
public static func ==(lhs: Syntax, rhs: Syntax) -> Bool {
147-
return lhs.hashValue == rhs.hashValue
148-
}
149-
/// Use reference value as the hashValue for this Syntax
150-
public var hashValue: Int {
151-
return ObjectIdentifier(data).hashValue
152-
}
159+
/// Determines if two nodes are equal to each other.
160+
public func ==(lhs: Syntax, rhs: Syntax) -> Bool {
161+
return lhs.uniqueIdentifier == rhs.uniqueIdentifier
153162
}
154163

155164
/// MARK: - Nodes
156165

157166
/// A Syntax node representing a single token.
158-
public class TokenSyntax: Syntax {
167+
public struct TokenSyntax: _SyntaxBase {
168+
var _root: SyntaxData
169+
unowned var _data: SyntaxData
170+
171+
/// Creates a Syntax node from the provided root and data.
172+
internal init(root: SyntaxData, data: SyntaxData) {
173+
self._root = root
174+
self._data = data
175+
#if DEBUG
176+
validate()
177+
#endif
178+
}
179+
159180
/// The text of the token as written in the source code.
160181
public var text: String {
161182
return tokenKind.text

tools/SwiftSyntax/SyntaxBuilders.swift.gyb

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -71,7 +71,7 @@ extension ${node.name} {
7171
/// incrementally build the structure of the node.
7272
/// - Returns: A `${node.name}` with all the fields populated in the builder
7373
/// closure.
74-
public convenience init(_ build: (inout ${Builder}) -> Void) {
74+
public init(_ build: (inout ${Builder}) -> Void) {
7575
var builder = ${Builder}()
7676
build(&builder)
7777
let data = builder.buildData()

tools/SwiftSyntax/SyntaxChildren.swift

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@ import Foundation
1515
public struct SyntaxChildren: Sequence {
1616
public struct Iterator: IteratorProtocol {
1717
let node: Syntax
18-
var indexIterator: Syntax.PresentChildIndicesSequence.Iterator
18+
var indexIterator: _SyntaxBase.PresentChildIndicesSequence.Iterator
1919

2020
init(node: Syntax) {
2121
self.indexIterator = node.presentChildIndices.makeIterator()

tools/SwiftSyntax/SyntaxCollection.swift

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,19 @@ import Foundation
1515
/// Represents a collection of Syntax nodes of a specific type. SyntaxCollection
1616
/// behaves as a regular Swift collection, and has accessors that return new
1717
/// versions of the collection with different children.
18-
public class SyntaxCollection<SyntaxElement: Syntax>: Syntax {
18+
public class SyntaxCollection<SyntaxElement: Syntax>: _SyntaxBase {
19+
var _root: SyntaxData
20+
unowned var _data: SyntaxData
21+
22+
/// Creates a Syntax node from the provided root and data.
23+
internal init(root: SyntaxData, data: SyntaxData) {
24+
self._root = root
25+
self._data = data
26+
#if DEBUG
27+
validate()
28+
#endif
29+
}
30+
1931
/// Creates a new SyntaxCollection by replacing the underlying layout with
2032
/// a different set of raw syntax nodes.
2133
///

tools/SwiftSyntax/SyntaxFactory.swift.gyb

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -40,7 +40,7 @@ public enum SyntaxFactory {
4040
let data = SyntaxData(raw: .node(.unknown,
4141
tokens.map { $0.data.raw },
4242
.present))
43-
return Syntax(root: data, data: data)
43+
return AnySyntax(root: data, data: data)
4444
}
4545

4646
/// MARK: Syntax Node Creation APIs
@@ -64,14 +64,14 @@ public enum SyntaxFactory {
6464
% end
6565
% end
6666
], .present))
67-
return ${node.name}(root: data, data: data)
67+
return ${node.swift_type_name}(root: data, data: data)
6868
}
6969
% elif node.is_syntax_collection():
7070
public static func make${node.syntax_kind}(
7171
_ elements: [${node.collection_element_type}]) -> ${node.name} {
7272
let data = SyntaxData(raw: .node(.${node.swift_syntax_kind},
7373
elements.map { $0.data.raw }, .present))
74-
return ${node.name}(root: data, data: data)
74+
return ${node.swift_type_name}(root: data, data: data)
7575
}
7676
% end
7777

@@ -81,7 +81,7 @@ public enum SyntaxFactory {
8181
${make_missing_swift_child(child)},
8282
% end
8383
], .present))
84-
return ${node.name}(root: data, data: data)
84+
return ${node.swift_type_name}(root: data, data: data)
8585
}
8686
% end
8787

tools/SwiftSyntax/SyntaxKind.swift.gyb

Lines changed: 21 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -52,30 +52,29 @@ internal enum SyntaxKind: String, Codable {
5252
}
5353
}
5454

55-
extension Syntax {
56-
/// Creates a Syntax node from the provided RawSyntax using the appropriate
57-
/// Syntax type, as specified by its kind.
58-
/// - Parameters:
59-
/// - raw: The raw syntax with which to create this node.
60-
/// - root: The root of this tree, or `nil` if the new node is the root.
61-
static func fromRaw(_ raw: RawSyntax) -> Syntax {
62-
let data = SyntaxData(raw: raw)
63-
return make(root: nil, data: data)
64-
}
55+
/// Creates a Syntax node from the provided RawSyntax using the appropriate
56+
/// Syntax type, as specified by its kind.
57+
/// - Parameters:
58+
/// - raw: The raw syntax with which to create this node.
59+
/// - root: The root of this tree, or `nil` if the new node is the root.
60+
internal func makeSyntax(_ raw: RawSyntax) -> Syntax {
61+
let data = SyntaxData(raw: raw)
62+
return makeSyntax(root: nil, data: data)
63+
}
6564

66-
/// Creates a Syntax node from the provided SyntaxData using the appropriate
67-
/// Syntax type, as specified by its kind.
68-
/// - Parameters:
69-
/// - root: The root of this tree, or `nil` if the new node is the root.
70-
/// - data: The data for this new node.
71-
static func make(root: SyntaxData?, data: SyntaxData) -> Syntax {
72-
let root = root ?? data
73-
switch data.raw.kind {
74-
case .token: return TokenSyntax(root: root, data: data)
75-
case .unknown: return Syntax(root: root, data: data)
65+
/// Creates a Syntax node from the provided SyntaxData using the appropriate
66+
/// Syntax type, as specified by its kind.
67+
/// - Parameters:
68+
/// - root: The root of this tree, or `nil` if the new node is the root.
69+
/// - data: The data for this new node.
70+
internal func makeSyntax(root: SyntaxData?, data: SyntaxData) -> Syntax {
71+
let root = root ?? data
72+
switch data.raw.kind {
73+
case .token: return TokenSyntax(root: root, data: data)
74+
case .unknown: return AnySyntax(root: root, data: data)
7675
% for node in SYNTAX_NODES:
77-
case .${node.swift_syntax_kind}: return ${node.name}(root: root, data: data)
76+
case .${node.swift_syntax_kind}:
77+
return ${node.swift_type_name}(root: root, data: data)
7878
% end
79-
}
8079
}
8180
}

0 commit comments

Comments
 (0)