@@ -42,6 +42,13 @@ let syntaxRewriterFile = SourceFileSyntax(leadingTrivia: copyrightHeader) {
4242 """
4343 )
4444
45+ DeclSyntax (
46+ """
47+ /// 'Syntax' object factory recycling 'Syntax.Info' instances.
48+ private let nodeFactory: SyntaxNodeFactory = SyntaxNodeFactory()
49+ """
50+ )
51+
4552 DeclSyntax (
4653 """
4754 public init(viewMode: SyntaxTreeViewMode = .sourceAccurate) {
@@ -65,7 +72,8 @@ let syntaxRewriterFile = SourceFileSyntax(leadingTrivia: copyrightHeader) {
6572 """
6673 /// Rewrite `node`, keeping its parent unless `detach` is `true`.
6774 public func rewrite(_ node: some SyntaxProtocol, detach: Bool = false) -> Syntax {
68- let rewritten = self.dispatchVisit(Syntax(node))
75+ var rewritten = Syntax(node)
76+ self.dispatchVisit(&rewritten)
6977 if detach {
7078 return rewritten
7179 }
@@ -126,15 +134,19 @@ let syntaxRewriterFile = SourceFileSyntax(leadingTrivia: copyrightHeader) {
126134 /// - Returns: the rewritten node
127135 @available(*, deprecated, renamed: " rewrite(_:detach:) " )
128136 public func visit(_ node: Syntax) -> Syntax {
129- return dispatchVisit(node)
137+ var rewritten = node
138+ dispatchVisit(&rewritten)
139+ return rewritten
130140 }
131141 """
132142 )
133143
134144 DeclSyntax (
135145 """
136146 public func visit<T: SyntaxChildChoices>(_ node: T) -> T {
137- return dispatchVisit(Syntax(node)).cast(T.self)
147+ var rewritten = Syntax(node)
148+ dispatchVisit(&rewritten)
149+ return rewritten.cast(T.self)
138150 }
139151 """
140152 )
@@ -148,7 +160,7 @@ let syntaxRewriterFile = SourceFileSyntax(leadingTrivia: copyrightHeader) {
148160 /// - Returns: the rewritten node
149161 \( node. apiAttributes ( ) ) \
150162 open func visit(_ node: \( node. kind. syntaxType) ) -> \( node. kind. syntaxType) {
151- return visitChildren(node)
163+ return visitChildren(node._syntaxNode).cast( \( node . kind . syntaxType ) .self )
152164 }
153165 """
154166 )
@@ -160,7 +172,7 @@ let syntaxRewriterFile = SourceFileSyntax(leadingTrivia: copyrightHeader) {
160172 /// - Returns: the rewritten node
161173 \( node. apiAttributes ( ) ) \
162174 open func visit(_ node: \( node. kind. syntaxType) ) -> \( node. baseType. syntaxBaseName) {
163- return \( node. baseType. syntaxBaseName) (visitChildren(node))
175+ return \( node. baseType. syntaxBaseName) (visitChildren(node._syntaxNode).cast( \( node . kind . syntaxType ) .self ))
164176 }
165177 """
166178 )
@@ -177,7 +189,9 @@ let syntaxRewriterFile = SourceFileSyntax(leadingTrivia: copyrightHeader) {
177189 /// - Returns: the rewritten node
178190 \( baseNode. apiAttributes ( ) ) \
179191 public func visit(_ node: \( baseKind. syntaxType) ) -> \( baseKind. syntaxType) {
180- return dispatchVisit(Syntax(node)).cast( \( baseKind. syntaxType) .self)
192+ var node: Syntax = Syntax(node)
193+ dispatchVisit(&node)
194+ return node.cast( \( baseKind. syntaxType) .self)
181195 }
182196 """
183197 )
@@ -187,21 +201,16 @@ let syntaxRewriterFile = SourceFileSyntax(leadingTrivia: copyrightHeader) {
187201 """
188202 /// Interpret `node` as a node of type `nodeType`, visit it, calling
189203 /// the `visit` to transform the node.
204+ @inline(__always)
190205 private func visitImpl<NodeType: SyntaxProtocol>(
191- _ node: Syntax,
206+ _ node: inout Syntax,
192207 _ nodeType: NodeType.Type,
193208 _ visit: (NodeType) -> some SyntaxProtocol
194- ) -> Syntax {
195- let castedNode = node.cast(NodeType.self)
196- // Accessing _syntaxNode directly is faster than calling Syntax(node)
197- visitPre(node)
198- defer {
199- visitPost(node)
200- }
201- if let newNode = visitAny(node) {
202- return newNode
203- }
204- return Syntax(visit(castedNode))
209+ ) {
210+ let origNode = node
211+ visitPre(origNode)
212+ node = visitAny(origNode) ?? Syntax(visit(origNode.cast(NodeType.self)))
213+ visitPost(origNode)
205214 }
206215 """
207216 )
@@ -242,26 +251,26 @@ let syntaxRewriterFile = SourceFileSyntax(leadingTrivia: copyrightHeader) {
242251 /// that determines the correct visitation function will be popped of the
243252 /// stack before the function is being called, making the switch's stack
244253 /// space transient instead of having it linger in the call stack.
245- private func visitationFunc(for node: Syntax) -> ((Syntax) -> Syntax )
254+ private func visitationFunc(for node: Syntax) -> ((inout Syntax) -> Void )
246255 """
247256 ) {
248257 try SwitchExprSyntax ( " switch node.raw.kind " ) {
249258 SwitchCaseSyntax ( " case .token: " ) {
250- StmtSyntax ( " return { self.visitImpl($0, TokenSyntax.self, self.visit) } " )
259+ StmtSyntax ( " return { self.visitImpl(& $0, TokenSyntax.self, self.visit) } " )
251260 }
252261
253262 for node in NON_BASE_SYNTAX_NODES {
254263 SwitchCaseSyntax ( " case . \( node. varOrCaseName) : " ) {
255- StmtSyntax ( " return { self.visitImpl($0, \( node. kind. syntaxType) .self, self.visit) } " )
264+ StmtSyntax ( " return { self.visitImpl(& $0, \( node. kind. syntaxType) .self, self.visit) } " )
256265 }
257266 }
258267 }
259268 }
260269
261270 DeclSyntax (
262271 """
263- private func dispatchVisit(_ node: Syntax) -> Syntax {
264- return visitationFunc(for: node)(node)
272+ private func dispatchVisit(_ node: inout Syntax) {
273+ visitationFunc(for: node)(& node)
265274 }
266275 """
267276 )
@@ -272,15 +281,15 @@ let syntaxRewriterFile = SourceFileSyntax(leadingTrivia: copyrightHeader) {
272281 poundKeyword: . poundElseToken( ) ,
273282 elements: . statements(
274283 CodeBlockItemListSyntax {
275- try ! FunctionDeclSyntax ( " private func dispatchVisit(_ node: Syntax) -> Syntax " ) {
284+ try ! FunctionDeclSyntax ( " private func dispatchVisit(_ node: inout Syntax) " ) {
276285 try SwitchExprSyntax ( " switch node.raw.kind " ) {
277286 SwitchCaseSyntax ( " case .token: " ) {
278- StmtSyntax ( " return visitImpl(node, TokenSyntax.self, visit) " )
287+ StmtSyntax ( " return visitImpl(& node, TokenSyntax.self, visit) " )
279288 }
280289
281290 for node in NON_BASE_SYNTAX_NODES {
282291 SwitchCaseSyntax ( " case . \( node. varOrCaseName) : " ) {
283- StmtSyntax ( " return visitImpl(node, \( node. kind. syntaxType) .self, visit) " )
292+ StmtSyntax ( " return visitImpl(& node, \( node. kind. syntaxType) .self, visit) " )
284293 }
285294 }
286295 }
@@ -293,9 +302,7 @@ let syntaxRewriterFile = SourceFileSyntax(leadingTrivia: copyrightHeader) {
293302
294303 DeclSyntax (
295304 """
296- private func visitChildren<SyntaxType: SyntaxProtocol>(
297- _ node: SyntaxType
298- ) -> SyntaxType {
305+ private func visitChildren(_ node: Syntax) -> Syntax {
299306 // Walk over all children of this node and rewrite them. Don't store any
300307 // rewritten nodes until the first non-`nil` value is encountered. When this
301308 // happens, retrieve all previous syntax nodes from the parent node to
@@ -305,73 +312,48 @@ let syntaxRewriterFile = SourceFileSyntax(leadingTrivia: copyrightHeader) {
305312
306313 // newLayout is nil until the first child node is rewritten and rewritten
307314 // nodes are being collected.
308- var newLayout: ContiguousArray<RawSyntax?>?
309-
310- // Rewritten children just to keep their 'SyntaxArena' alive until they are
311- // wrapped with 'Syntax'
312- var rewrittens: ContiguousArray<Syntax> = []
315+ var newLayout: UnsafeMutableBufferPointer<RawSyntax?> = .init(start: nil, count: 0)
313316
314- let syntaxNode = node._syntaxNode
317+ // Keep 'SyntaxArena' of rewritten nodes alive until they are wrapped
318+ // with 'Syntax'
319+ var rewrittens: ContiguousArray<RetainedSyntaxArena> = []
315320
316- // Incrementing i manually is faster than using .enumerated()
317- var childIndex = 0
318- for (raw, info) in RawSyntaxChildren(syntaxNode) {
319- defer { childIndex += 1 }
320-
321- guard let child = raw, viewMode.shouldTraverse(node: child) else {
322- // Node does not exist or should not be visited. If we are collecting
323- // rewritten nodes, we need to collect this one as well, otherwise we
324- // can ignore it.
325- if newLayout != nil {
326- newLayout!.append(raw)
327- }
328- continue
329- }
321+ for case let (child?, info) in RawSyntaxChildren(node) where viewMode.shouldTraverse(node: child) {
330322
331323 // Build the Syntax node to rewrite
332- let absoluteRaw = AbsoluteRawSyntax( raw: child, info : info)
324+ var childNode = nodeFactory.create(parent: node, raw: child, absoluteInfo : info)
333325
334- let rewritten = dispatchVisit(Syntax(absoluteRaw, parent: syntaxNode) )
335- if rewritten. id != info.nodeId {
326+ dispatchVisit(&childNode )
327+ if childNode.raw. id != child.id {
336328 // The node was rewritten, let's handle it
337- if newLayout == nil {
329+
330+ if newLayout.baseAddress == nil {
338331 // We have not yet collected any previous rewritten nodes. Initialize
339- // the new layout with the previous nodes of the parent. This is
340- // possible, since we know they were not rewritten.
341-
342- // The below implementation is based on Collection.map but directly
343- // reserves enough capacity for the entire layout.
344- newLayout = ContiguousArray<RawSyntax?>()
345- newLayout!.reserveCapacity(node.raw.layoutView!.children.count)
346- for j in 0..<childIndex {
347- newLayout!.append(node.raw.layoutView!.children[j])
348- }
332+ // the new layout with the previous nodes of the parent.
333+ newLayout = .allocate(capacity: node.raw.layoutView!.children.count)
334+ _ = newLayout.initialize(fromContentsOf: node.raw.layoutView!.children)
349335 }
350336
351- // Now that we know we have a new layout in which we collect rewritten
352- // nodes, add it.
353- rewrittens.append(rewritten)
354- newLayout!.append(rewritten.raw)
355- } else {
356- // The node was not changed by the rewriter. Only store it if a previous
357- // node has been rewritten and we are collecting a rewritten layout.
358- if newLayout != nil {
359- newLayout!.append(raw)
360- }
337+ // Update the rewritten child.
338+ newLayout[Int(info.indexInParent)] = childNode.raw
339+ // Retain the syntax arena of the new node until it's wrapped with Syntax node.
340+ rewrittens.append(childNode.raw.arenaReference.retained)
361341 }
342+
343+ // Recycle 'childNode.info'
344+ nodeFactory.dispose(&childNode)
362345 }
363346
364- if let newLayout {
347+ if newLayout.baseAddress != nil {
365348 // A child node was rewritten. Build the updated node.
366349
367- // Sanity check, ensure the new children are the same length.
368- precondition(newLayout.count == node.raw.layoutView!.children.count)
369-
370350 let arena = self.arena ?? SyntaxArena()
371- let newRaw = node.raw.layoutView!.replacingLayout(with: Array(newLayout), arena: arena)
351+ let newRaw = node.raw.layoutView!.replacingLayout(with: newLayout, arena: arena)
352+ newLayout.deinitialize()
353+ newLayout.deallocate()
372354 // 'withExtendedLifetime' to keep 'SyntaxArena's of them alive until here.
373355 return withExtendedLifetime(rewrittens) {
374- Syntax(raw: newRaw, rawNodeArena: arena).cast(SyntaxType.self)
356+ Syntax(raw: newRaw, rawNodeArena: arena)
375357 }
376358 } else {
377359 // No child node was rewritten. So no need to change this node as well.
0 commit comments