Skip to content

Commit 42b325f

Browse files
committed
Performance improvements for SyntaxRewriter
Improve the performance of SyntaxRewriter by avoiding unnecessary casts, reusing the SyntaxBox and eliminating .enumerated()
1 parent fbdf5d5 commit 42b325f

File tree

2 files changed

+38
-19
lines changed

2 files changed

+38
-19
lines changed

Sources/SwiftSyntax/SyntaxData.swift

Lines changed: 14 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -162,7 +162,7 @@ struct AbsoluteRawSyntax {
162162

163163
/// Indirect wrapper for a `Syntax` node to avoid cyclic inclusion of the
164164
/// `Syntax` struct in `SyntaxData`
165-
fileprivate class SyntaxBox: CustomStringConvertible,
165+
class SyntaxBox: CustomStringConvertible,
166166
CustomDebugStringConvertible, TextOutputStreamable {
167167
let value: Syntax
168168

@@ -173,18 +173,18 @@ fileprivate class SyntaxBox: CustomStringConvertible,
173173
// SyntaxBox should be transparent in all descriptions
174174

175175
/// A source-accurate description of this node.
176-
public var description: String {
176+
var description: String {
177177
return value.description
178178
}
179179

180180
/// Returns a description used by dump.
181-
public var debugDescription: String {
181+
var debugDescription: String {
182182
return value.debugDescription
183183
}
184184

185185
/// Prints the raw value of this node to the provided stream.
186186
/// - Parameter stream: The stream to which to print the raw tree.
187-
public func write<Target>(to target: inout Target)
187+
func write<Target>(to target: inout Target)
188188
where Target: TextOutputStream {
189189
return value.write(to: &target)
190190
}
@@ -236,6 +236,16 @@ struct SyntaxData {
236236
self.parentBox = parent.map(SyntaxBox.init)
237237
}
238238

239+
/// Creates a `SyntaxData` with the provided raw syntax and parent.
240+
/// - Parameters:
241+
/// - absoluteRaw: The underlying `AbsoluteRawSyntax` of this node.
242+
/// - parentBox: The boxed parent of this node, or `nil` if this node is the
243+
/// root.
244+
init(_ absoluteRaw: AbsoluteRawSyntax, parentBox: SyntaxBox?) {
245+
self.absoluteRaw = absoluteRaw
246+
self.parentBox = parentBox
247+
}
248+
239249
/// Creates a `SyntaxData` for a root raw node.
240250
static func forRoot(_ raw: RawSyntax) -> SyntaxData {
241251
return SyntaxData(.forRoot(raw), parent: nil)

Sources/SwiftSyntax/SyntaxRewriter.swift.gyb

Lines changed: 24 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -85,15 +85,17 @@ open class SyntaxRewriter {
8585
private func visitImpl${node.name}(_ data: SyntaxData) -> Syntax {
8686
% if node.is_base():
8787
let node = Unknown${node.name}(data)
88-
visitPre(Syntax(node))
89-
defer { visitPost(Syntax(node)) }
90-
if let newNode = visitAny(Syntax(node)) { return newNode }
88+
// Accessing _syntaxNode directly is faster than calling Syntax(node)
89+
visitPre(node._syntaxNode)
90+
defer { visitPost(node._syntaxNode) }
91+
if let newNode = visitAny(node._syntaxNode) { return newNode }
9192
return Syntax(visit(node))
9293
% else:
9394
let node = ${node.name}(data)
94-
visitPre(Syntax(node))
95-
defer { visitPost(Syntax(node)) }
96-
if let newNode = visitAny(Syntax(node)) { return newNode }
95+
// Accessing _syntaxNode directly is faster than calling Syntax(node)
96+
visitPre(node._syntaxNode)
97+
defer { visitPost(node._syntaxNode) }
98+
if let newNode = visitAny(node._syntaxNode) { return newNode }
9799
% if node.base_type == 'Syntax':
98100
return visit(node)
99101
% else:
@@ -108,15 +110,17 @@ open class SyntaxRewriter {
108110
switch data.raw.kind {
109111
case .token:
110112
let node = TokenSyntax(data)
111-
visitPre(Syntax(node))
112-
defer { visitPost(Syntax(node)) }
113-
if let newNode = visitAny(Syntax(node)) { return newNode }
113+
// Accessing _syntaxNode directly is faster than calling Syntax(node)
114+
visitPre(node._syntaxNode)
115+
defer { visitPost(node._syntaxNode) }
116+
if let newNode = visitAny(node._syntaxNode) { return newNode }
114117
return visit(node)
115118
case .unknown:
116119
let node = UnknownSyntax(data)
117-
visitPre(Syntax(node))
118-
defer { visitPost(Syntax(node)) }
119-
if let newNode = visitAny(Syntax(node)) { return newNode }
120+
// Accessing _syntaxNode directly is faster than calling Syntax(node)
121+
visitPre(node._syntaxNode)
122+
defer { visitPost(node._syntaxNode) }
123+
if let newNode = visitAny(node._syntaxNode) { return newNode }
120124
return visit(node)
121125
// The implementation of every generated case goes into its own function. This
122126
// circumvents an issue where the compiler allocates stack space for every
@@ -142,7 +146,12 @@ open class SyntaxRewriter {
142146
// nodes are being collected.
143147
var newLayout: ContiguousArray<RawSyntax?>?
144148

145-
for (i, (raw, info)) in RawSyntaxChildren(Syntax(node)).enumerated() {
149+
let parentBox = SyntaxBox(Syntax(node))
150+
151+
// Incrementing i manually is faster than using .enumerated()
152+
var childIndex = 0
153+
for (raw, info) in RawSyntaxChildren(Syntax(node)) {
154+
defer { childIndex += 1 }
146155
guard let child = raw else {
147156
// Node does not exist. If we are collecting rewritten nodes, we need to
148157
// collect this one as well, otherwise we can ignore it.
@@ -154,7 +163,7 @@ open class SyntaxRewriter {
154163

155164
// Build the Syntax node to rewrite
156165
let absoluteRaw = AbsoluteRawSyntax(raw: child, info: info)
157-
let data = SyntaxData(absoluteRaw, parent: Syntax(node))
166+
let data = SyntaxData(absoluteRaw, parentBox: parentBox)
158167

159168
let rewritten = visit(data)
160169
if rewritten.data.nodeId != info.nodeId {
@@ -168,7 +177,7 @@ open class SyntaxRewriter {
168177
// reserves enough capacity for the entire layout.
169178
newLayout = ContiguousArray<RawSyntax?>()
170179
newLayout!.reserveCapacity(node.raw.numberOfChildren)
171-
for j in 0..<i {
180+
for j in 0..<childIndex {
172181
newLayout!.append(node.raw.child(at: j))
173182
}
174183
}

0 commit comments

Comments
 (0)