Skip to content

Commit 4c66cf6

Browse files
authored
Merge pull request swiftlang#157 from ahoppen/rewriter-refactoring
Refactor SyntaxRewriter
2 parents ca3d0e0 + 1d5126f commit 4c66cf6

File tree

2 files changed

+128
-26
lines changed

2 files changed

+128
-26
lines changed

Diff for: Sources/SwiftSyntax/SyntaxRewriter.swift.gyb

+127-25
Original file line numberDiff line numberDiff line change
@@ -27,8 +27,12 @@
2727

2828
open class SyntaxRewriter {
2929
public init() {}
30+
3031
% for node in SYNTAX_NODES:
3132
% if is_visitable(node):
33+
/// Visit a `${node.name}`.
34+
/// - Parameter node: the node that is being visited
35+
/// - Returns: the rewritten node
3236
open func visit(_ node: ${node.name}) -> ${node.base_type} {
3337
% cast = ('as! ' + node.base_type) if node.base_type != 'Syntax' else ''
3438
return visitChildren(node) ${cast}
@@ -37,9 +41,19 @@ open class SyntaxRewriter {
3741
% end
3842
% end
3943

44+
/// Visit a `TokenSyntax`.
45+
/// - Parameter node: the node that is being visited
46+
/// - Returns: the rewritten node
4047
open func visit(_ token: TokenSyntax) -> Syntax {
4148
return token
4249
}
50+
51+
/// Visit a `UnknownSyntax`.
52+
/// - Parameter node: the node that is being visited
53+
/// - Returns: the rewritten node or `nil`.
54+
open func visit(_ node: UnknownSyntax) -> Syntax {
55+
return visitChildren(node)
56+
}
4357

4458
/// The function called before visiting the node and its descendents.
4559
/// - node: the node we are about to visit.
@@ -49,7 +63,9 @@ open class SyntaxRewriter {
4963
/// specialized `visit(_:)` methods. Use this instead of those methods if
5064
/// you intend to dynamically dispatch rewriting behavior.
5165
/// - note: If this method returns a non-nil result, the specialized
52-
/// `visit(_:)` methods will not be called for this node.
66+
/// `visit(_:)` methods will not be called for this node and the
67+
/// visited node will be replaced by the returned node in the
68+
/// rewritten tree.
5369
open func visitAny(_ node: Syntax) -> Syntax? {
5470
return nil
5571
}
@@ -58,44 +74,130 @@ open class SyntaxRewriter {
5874
/// - node: the node we just finished visiting.
5975
open func visitPost(_ node: Syntax) {}
6076

77+
/// Visit any Syntax node.
78+
/// - Parameter node: the node that is being visited
79+
/// - Returns: the rewritten node
6180
public func visit(_ node: Syntax) -> Syntax {
62-
visitPre(node)
63-
defer { visitPost(node) }
64-
65-
// If the global visitor returned non-nil, skip specialized dispatch.
66-
if let newNode = visitAny(node) {
67-
return newNode
68-
}
81+
return visit(node.base.data)
82+
}
6983

70-
switch node.raw.kind {
71-
case .token: return visit(node as! TokenSyntax)
7284
% for node in SYNTAX_NODES:
73-
% if is_visitable(node):
74-
case .${node.swift_syntax_kind}: return visit(node as! ${node.name})
85+
/// Implementation detail of visit(_:). Do not call directly.
86+
private func visitImpl${node.name}(_ data: SyntaxData) -> Syntax {
87+
% if node.is_base():
88+
let node = Unknown${node.name}(data)
89+
visitPre(node)
90+
defer { visitPost(node) }
91+
if let newNode = visitAny(node) { return newNode }
92+
return visit(node)
93+
% else:
94+
let node = ${node.name}(data)
95+
visitPre(node)
96+
defer { visitPost(node) }
97+
if let newNode = visitAny(node) { return newNode }
98+
return visit(node)
7599
% end
100+
}
101+
76102
% end
77-
default: return visitChildren(node)
103+
104+
final func visit(_ data: SyntaxData) -> Syntax {
105+
// Create the node types directly instead of going through `makeSyntax()`
106+
// which has additional cost for casting back and forth from `_SyntaxBase`.
107+
switch data.raw.kind {
108+
case .token:
109+
let node = TokenSyntax(data)
110+
visitPre(node)
111+
defer { visitPost(node) }
112+
if let newNode = visitAny(node) { return newNode }
113+
return visit(node)
114+
case .unknown:
115+
let node = UnknownSyntax(data)
116+
visitPre(node)
117+
defer { visitPost(node) }
118+
if let newNode = visitAny(node) { return newNode }
119+
return visit(node)
120+
// The implementation of every generated case goes into its own function. This
121+
// circumvents an issue where the compiler allocates stack space for every
122+
// case statement next to each other in debug builds, causing it to allocate
123+
// ~50KB per call to this function. rdar://55929175
124+
% for node in SYNTAX_NODES:
125+
case .${node.swift_syntax_kind}:
126+
return visitImpl${node.name}(data)
127+
% end
78128
}
79129
}
80130

81-
func visitChildren(_ nodeS: Syntax) -> Syntax {
82-
// Visit all children of this node, returning `nil` if child is not
83-
// present. This will ensure that there are always the same number
84-
// of children after transforming.
131+
final func visitChildren(_ nodeS: Syntax) -> Syntax {
85132
let node = nodeS.base
86-
let newLayout = RawSyntaxChildren(node).map { (n: (RawSyntax?, AbsoluteSyntaxInfo)) -> RawSyntax? in
87-
let (raw, info) = n
88-
guard let child = raw else { return nil }
133+
134+
// Walk over all children of this node and rewrite them. Don't store any
135+
// rewritten nodes until the first non-`nil` value is encountered. When this
136+
// happens, retrieve all previous syntax nodes from the parent node to
137+
// initialize the new layout. Once we know that we have to rewrite the
138+
// layout, we need to collect all futher children, regardless of whether
139+
// they are rewritten or not.
140+
141+
// newLayout is nil until the first child node is rewritten and rewritten
142+
// nodes are being collected.
143+
var newLayout: ContiguousArray<RawSyntax?>?
144+
145+
for (i, (raw, info)) in RawSyntaxChildren(node).enumerated() {
146+
guard let child = raw else {
147+
// Node does not exist. If we are collecting rewritten nodes, we need to
148+
// collect this one as well, otherwise we can ignore it.
149+
if newLayout != nil {
150+
newLayout!.append(nil)
151+
}
152+
continue
153+
}
154+
155+
// Build the Syntax node to rewrite
89156
let absoluteRaw = AbsoluteRawSyntax(raw: child, info: info)
90157
let data = SyntaxData(absoluteRaw, parent: node)
91-
return visit(makeSyntax(data)).raw
158+
159+
let rewritten = visit(data)
160+
if rewritten.base.data.absoluteRaw.info.nodeId != info.nodeId {
161+
// The node was rewritten, let's handle it
162+
if newLayout == nil {
163+
// We have not yet collected any previous rewritten nodes. Initialize
164+
// the new layout with the previous nodes of the parent. This is
165+
// possible, since we know they were not rewritten.
166+
167+
// The below implementation is based on Collection.map but directly
168+
// reserves enough capacity for the entire layout.
169+
newLayout = ContiguousArray<RawSyntax?>()
170+
newLayout!.reserveCapacity(node.raw.numberOfChildren)
171+
for j in 0..<i {
172+
newLayout!.append(node.raw.child(at: j))
173+
}
174+
}
175+
176+
// Now that we know we have a new layout in which we collect rewritten
177+
// nodes, add it.
178+
newLayout!.append(rewritten.raw)
179+
} else {
180+
// The node was not changed by the rewriter. Only store it if a previous
181+
// node has been rewritten and we are collecting a rewritten layout.
182+
if newLayout != nil {
183+
newLayout!.append(raw)
184+
}
185+
}
92186
}
93187

94-
// Sanity check, ensure the new children are the same length.
95-
assert(newLayout.count == node.raw.numberOfChildren)
188+
if let newLayout = newLayout {
189+
// A child node was rewritten. Build the updated node.
190+
191+
// Sanity check, ensure the new children are the same length.
192+
assert(newLayout.count == node.raw.numberOfChildren)
193+
194+
let newRaw = node.raw.replacingLayout(Array(newLayout))
195+
return makeSyntax(.forRoot(newRaw))
196+
} else {
197+
// No child node was rewritten. So no need to change this node as well.
198+
return nodeS
199+
}
96200

97-
let newRaw = node.raw.replacingLayout(newLayout)
98-
return makeSyntax(.forRoot(newRaw))
99201
}
100202
}
101203

Diff for: Tests/SwiftSyntaxTest/AbsolutePosition.swift

+1-1
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@ import XCTest
22
import SwiftSyntax
33

44
fileprivate class FuncRenamer: SyntaxRewriter {
5-
override func visit(_ node: FunctionDeclSyntax) ->DeclSyntax {
5+
override func visit(_ node: FunctionDeclSyntax) -> DeclSyntax {
66
return (super.visit(node) as! FunctionDeclSyntax).withIdentifier(
77
SyntaxFactory.makeIdentifier("anotherName"))
88
}

0 commit comments

Comments
 (0)