@@ -42,6 +42,13 @@ let syntaxRewriterFile = SourceFileSyntax(leadingTrivia: copyrightHeader) {
42
42
"""
43
43
)
44
44
45
+ DeclSyntax (
46
+ """
47
+ /// 'Syntax' object factory recycling 'Syntax.Info' instances.
48
+ private let nodeFactory: SyntaxNodeFactory = SyntaxNodeFactory()
49
+ """
50
+ )
51
+
45
52
DeclSyntax (
46
53
"""
47
54
public init(viewMode: SyntaxTreeViewMode = .sourceAccurate) {
@@ -65,7 +72,8 @@ let syntaxRewriterFile = SourceFileSyntax(leadingTrivia: copyrightHeader) {
65
72
"""
66
73
/// Rewrite `node`, keeping its parent unless `detach` is `true`.
67
74
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)
69
77
if detach {
70
78
return rewritten
71
79
}
@@ -126,15 +134,19 @@ let syntaxRewriterFile = SourceFileSyntax(leadingTrivia: copyrightHeader) {
126
134
/// - Returns: the rewritten node
127
135
@available(*, deprecated, renamed: " rewrite(_:detach:) " )
128
136
public func visit(_ node: Syntax) -> Syntax {
129
- return dispatchVisit(node)
137
+ var rewritten = node
138
+ dispatchVisit(&rewritten)
139
+ return rewritten
130
140
}
131
141
"""
132
142
)
133
143
134
144
DeclSyntax (
135
145
"""
136
146
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)
138
150
}
139
151
"""
140
152
)
@@ -148,7 +160,7 @@ let syntaxRewriterFile = SourceFileSyntax(leadingTrivia: copyrightHeader) {
148
160
/// - Returns: the rewritten node
149
161
\( node. apiAttributes ( ) ) \
150
162
open func visit(_ node: \( node. kind. syntaxType) ) -> \( node. kind. syntaxType) {
151
- return visitChildren(node)
163
+ return visitChildren(node._syntaxNode).cast( \( node . kind . syntaxType ) .self )
152
164
}
153
165
"""
154
166
)
@@ -160,7 +172,7 @@ let syntaxRewriterFile = SourceFileSyntax(leadingTrivia: copyrightHeader) {
160
172
/// - Returns: the rewritten node
161
173
\( node. apiAttributes ( ) ) \
162
174
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 ))
164
176
}
165
177
"""
166
178
)
@@ -177,7 +189,9 @@ let syntaxRewriterFile = SourceFileSyntax(leadingTrivia: copyrightHeader) {
177
189
/// - Returns: the rewritten node
178
190
\( baseNode. apiAttributes ( ) ) \
179
191
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)
181
195
}
182
196
"""
183
197
)
@@ -187,21 +201,16 @@ let syntaxRewriterFile = SourceFileSyntax(leadingTrivia: copyrightHeader) {
187
201
"""
188
202
/// Interpret `node` as a node of type `nodeType`, visit it, calling
189
203
/// the `visit` to transform the node.
204
+ @inline(__always)
190
205
private func visitImpl<NodeType: SyntaxProtocol>(
191
- _ node: Syntax,
206
+ _ node: inout Syntax,
192
207
_ nodeType: NodeType.Type,
193
208
_ 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)
205
214
}
206
215
"""
207
216
)
@@ -242,26 +251,26 @@ let syntaxRewriterFile = SourceFileSyntax(leadingTrivia: copyrightHeader) {
242
251
/// that determines the correct visitation function will be popped of the
243
252
/// stack before the function is being called, making the switch's stack
244
253
/// 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 )
246
255
"""
247
256
) {
248
257
try SwitchExprSyntax ( " switch node.raw.kind " ) {
249
258
SwitchCaseSyntax ( " case .token: " ) {
250
- StmtSyntax ( " return { self.visitImpl($0, TokenSyntax.self, self.visit) } " )
259
+ StmtSyntax ( " return { self.visitImpl(& $0, TokenSyntax.self, self.visit) } " )
251
260
}
252
261
253
262
for node in NON_BASE_SYNTAX_NODES {
254
263
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) } " )
256
265
}
257
266
}
258
267
}
259
268
}
260
269
261
270
DeclSyntax (
262
271
"""
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)
265
274
}
266
275
"""
267
276
)
@@ -272,15 +281,15 @@ let syntaxRewriterFile = SourceFileSyntax(leadingTrivia: copyrightHeader) {
272
281
poundKeyword: . poundElseToken( ) ,
273
282
elements: . statements(
274
283
CodeBlockItemListSyntax {
275
- try ! FunctionDeclSyntax ( " private func dispatchVisit(_ node: Syntax) -> Syntax " ) {
284
+ try ! FunctionDeclSyntax ( " private func dispatchVisit(_ node: inout Syntax) " ) {
276
285
try SwitchExprSyntax ( " switch node.raw.kind " ) {
277
286
SwitchCaseSyntax ( " case .token: " ) {
278
- StmtSyntax ( " return visitImpl(node, TokenSyntax.self, visit) " )
287
+ StmtSyntax ( " return visitImpl(& node, TokenSyntax.self, visit) " )
279
288
}
280
289
281
290
for node in NON_BASE_SYNTAX_NODES {
282
291
SwitchCaseSyntax ( " case . \( node. varOrCaseName) : " ) {
283
- StmtSyntax ( " return visitImpl(node, \( node. kind. syntaxType) .self, visit) " )
292
+ StmtSyntax ( " return visitImpl(& node, \( node. kind. syntaxType) .self, visit) " )
284
293
}
285
294
}
286
295
}
@@ -293,9 +302,7 @@ let syntaxRewriterFile = SourceFileSyntax(leadingTrivia: copyrightHeader) {
293
302
294
303
DeclSyntax (
295
304
"""
296
- private func visitChildren<SyntaxType: SyntaxProtocol>(
297
- _ node: SyntaxType
298
- ) -> SyntaxType {
305
+ private func visitChildren(_ node: Syntax) -> Syntax {
299
306
// Walk over all children of this node and rewrite them. Don't store any
300
307
// rewritten nodes until the first non-`nil` value is encountered. When this
301
308
// happens, retrieve all previous syntax nodes from the parent node to
@@ -305,73 +312,48 @@ let syntaxRewriterFile = SourceFileSyntax(leadingTrivia: copyrightHeader) {
305
312
306
313
// newLayout is nil until the first child node is rewritten and rewritten
307
314
// 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)
313
316
314
- let syntaxNode = node._syntaxNode
317
+ // Keep 'SyntaxArena' of rewritten nodes alive until they are wrapped
318
+ // with 'Syntax'
319
+ var rewrittens: ContiguousArray<RetainedSyntaxArena> = []
315
320
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) {
330
322
331
323
// 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)
333
325
334
- let rewritten = dispatchVisit(Syntax(absoluteRaw, parent: syntaxNode) )
335
- if rewritten. id != info.nodeId {
326
+ dispatchVisit(&childNode )
327
+ if childNode.raw. id != child.id {
336
328
// The node was rewritten, let's handle it
337
- if newLayout == nil {
329
+
330
+ if newLayout.baseAddress == nil {
338
331
// 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)
349
335
}
350
336
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)
361
341
}
342
+
343
+ // Recycle 'childNode.info'
344
+ nodeFactory.dispose(&childNode)
362
345
}
363
346
364
- if let newLayout {
347
+ if newLayout.baseAddress != nil {
365
348
// A child node was rewritten. Build the updated node.
366
349
367
- // Sanity check, ensure the new children are the same length.
368
- precondition(newLayout.count == node.raw.layoutView!.children.count)
369
-
370
350
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()
372
354
// 'withExtendedLifetime' to keep 'SyntaxArena's of them alive until here.
373
355
return withExtendedLifetime(rewrittens) {
374
- Syntax(raw: newRaw, rawNodeArena: arena).cast(SyntaxType.self)
356
+ Syntax(raw: newRaw, rawNodeArena: arena)
375
357
}
376
358
} else {
377
359
// No child node was rewritten. So no need to change this node as well.
0 commit comments