2727
2828open 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
0 commit comments