Skip to content

Commit 3882177

Browse files
committed
Move SyntaxRewriter from gyb to codegen
1 parent c550d0c commit 3882177

File tree

7 files changed

+315
-264
lines changed

7 files changed

+315
-264
lines changed

CodeGeneration/Sources/Utils/SyntaxBuildableNode.swift

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,12 @@ public extension Node {
2222

2323
/// The node's syntax kind as `SyntaxBuildableType`.
2424
var baseType: SyntaxBuildableType {
25-
SyntaxBuildableType(syntaxKind: baseKind)
25+
if baseKind == "SyntaxCollection" {
26+
return SyntaxBuildableType(syntaxKind: "Syntax")
27+
} else {
28+
return SyntaxBuildableType(syntaxKind: baseKind)
29+
}
30+
2631
}
2732

2833
/// If documentation exists for this node, return it as a single-line string.

CodeGeneration/Sources/generate-swiftsyntax/GenerateSwiftSyntax.swift

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,7 @@ struct GenerateSwiftSyntax: ParsableCommand {
3333
(syntaxEnumFile, "SyntaxEnum.swift"),
3434
(syntaxFactoryFile, "SyntaxFactory.swift"),
3535
(syntaxKindFile, "SyntaxKind.swift"),
36+
(syntaxRewriterFile, "SyntaxRewriter.swift"),
3637
(syntaxTraitsFile, "SyntaxTraits.swift"),
3738
(syntaxTransformFile, "SyntaxTransform.swift"),
3839
(syntaxVisitorFile, "SyntaxVisitor.swift"),
Lines changed: 303 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,303 @@
1+
//===----------------------------------------------------------------------===//
2+
//
3+
// This source file is part of the Swift.org open source project
4+
//
5+
// Copyright (c) 2014 - 2022 Apple Inc. and the Swift project authors
6+
// Licensed under Apache License v2.0 with Runtime Library Exception
7+
//
8+
// See https://swift.org/LICENSE.txt for license information
9+
// See https://swift.org/CONTRIBUTORS.txt for the list of Swift project authors
10+
//
11+
//===----------------------------------------------------------------------===//
12+
13+
import SwiftSyntax
14+
import SwiftSyntaxBuilder
15+
import SyntaxSupport
16+
import Utils
17+
18+
let syntaxRewriterFile = SourceFile(leadingTrivia: .docLineComment(generateCopyrightHeader(for: "generate-swiftsyntax"))) {
19+
ClassDecl("""
20+
//
21+
// This file defines the SyntaxRewriter, a class that performs a standard walk
22+
// and tree-rebuilding pattern.
23+
//
24+
// Subclassers of this class can override the walking behavior for any syntax
25+
// node and transform nodes however they like.
26+
//
27+
//===----------------------------------------------------------------------===//
28+
29+
open class SyntaxRewriter
30+
""") {
31+
InitializerDecl("public init() {}")
32+
33+
for node in SYNTAX_NODES where node.isVisitable {
34+
if node.baseType.baseName == "Syntax" && node.name != "MissingSyntax" {
35+
FunctionDecl("""
36+
/// Visit a `\(raw: node.name)`.
37+
/// - Parameter node: the node that is being visited
38+
/// - Returns: the rewritten node
39+
open func visit(_ node: \(raw: node.name)) -> \(raw: node.name) {
40+
return Syntax(visitChildren(node)).cast(\(raw: node.name).self)
41+
}
42+
""")
43+
} else {
44+
FunctionDecl("""
45+
/// Visit a `\(raw: node.name)`.
46+
/// - Parameter node: the node that is being visited
47+
/// - Returns: the rewritten node
48+
open func visit(_ node: \(raw: node.name)) -> \(raw: node.baseType.syntaxBaseName) {
49+
return \(raw: node.baseType.syntaxBaseName)(visitChildren(node))
50+
}
51+
""")
52+
}
53+
}
54+
55+
FunctionDecl("""
56+
/// Visit a `TokenSyntax`.
57+
/// - Parameter node: the node that is being visited
58+
/// - Returns: the rewritten node
59+
open func visit(_ token: TokenSyntax) -> TokenSyntax {
60+
return token
61+
}
62+
""")
63+
64+
FunctionDecl("""
65+
/// The function called before visiting the node and its descendents.
66+
/// - node: the node we are about to visit.
67+
open func visitPre(_ node: Syntax) {}
68+
""")
69+
70+
FunctionDecl("""
71+
/// Override point to choose custom visitation dispatch instead of the
72+
/// specialized `visit(_:)` methods. Use this instead of those methods if
73+
/// you intend to dynamically dispatch rewriting behavior.
74+
/// - note: If this method returns a non-nil result, the specialized
75+
/// `visit(_:)` methods will not be called for this node and the
76+
/// visited node will be replaced by the returned node in the
77+
/// rewritten tree.
78+
open func visitAny(_ node: Syntax) -> Syntax? {
79+
return nil
80+
}
81+
""")
82+
83+
FunctionDecl("""
84+
/// The function called after visiting the node and its descendents.
85+
/// - node: the node we just finished visiting.
86+
open func visitPost(_ node: Syntax) {}
87+
""")
88+
89+
FunctionDecl("""
90+
/// Visit any Syntax node.
91+
/// - Parameter node: the node that is being visited
92+
/// - Returns: the rewritten node
93+
public func visit(_ node: Syntax) -> Syntax {
94+
return visit(node.data)
95+
}
96+
""")
97+
98+
FunctionDecl("""
99+
public func visit<T: SyntaxChildChoices>(_ node: T) -> T {
100+
return visit(Syntax(node)).cast(T.self)
101+
}
102+
""")
103+
104+
for baseKind in SYNTAX_BASE_KINDS.filter { !["Syntax", "SyntaxCollection"].contains($0) } {
105+
FunctionDecl("""
106+
/// Visit any \(raw: baseKind)Syntax node.
107+
/// - Parameter node: the node that is being visited
108+
/// - Returns: the rewritten node
109+
public func visit(_ node: \(raw: baseKind)Syntax) -> \(raw: baseKind)Syntax {
110+
return visit(node.data).cast(\(raw: baseKind)Syntax.self)
111+
}
112+
""")
113+
}
114+
115+
for node in NON_BASE_SYNTAX_NODES {
116+
FunctionDecl("""
117+
/// Implementation detail of visit(_:). Do not call directly.
118+
private func visitImpl\(raw: node.name)(_ data: SyntaxData) -> Syntax {
119+
let node = \(raw: node.name)(data)
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 }
124+
return Syntax(visit(node))
125+
}
126+
""")
127+
}
128+
129+
FunctionDecl("""
130+
/// Implementation detail of visit(_:). Do not call directly.
131+
private func visitImplTokenSyntax(_ data: SyntaxData) -> Syntax {
132+
let node = TokenSyntax(data)
133+
// Accessing _syntaxNode directly is faster than calling Syntax(node)
134+
visitPre(node._syntaxNode)
135+
defer { visitPost(node._syntaxNode) }
136+
if let newNode = visitAny(node._syntaxNode) { return newNode }
137+
return Syntax(visit(node))
138+
}
139+
""")
140+
141+
IfConfigDecl(
142+
leadingTrivia: .blockComment("""
143+
// SwiftSyntax requires a lot of stack space in debug builds for syntax tree
144+
// rewriting. In scenarios with reduced stack space (in particular dispatch
145+
// queues), this easily results in a stack overflow. To work around this issue,
146+
// use a less performant but also less stack-hungry version of SwiftSyntax's
147+
// SyntaxRewriter in debug builds.
148+
""") + .newline,
149+
clauses: IfConfigClauseList {
150+
IfConfigClause(
151+
poundKeyword: .poundIfKeyword(),
152+
condition: Expr("DEBUG"),
153+
elements: .statements(CodeBlockItemList {
154+
FunctionDecl("""
155+
/// Implementation detail of visit(_:). Do not call directly.
156+
///
157+
/// Returns the function that shall be called to visit a specific syntax node.
158+
///
159+
/// To determine the correct specific visitation function for a syntax node,
160+
/// we need to switch through a huge switch statement that covers all syntax
161+
/// types. In debug builds, the cases of this switch statement do not share
162+
/// stack space (rdar://55929175). Because of this, the switch statement
163+
/// requires allocates about 15KB of stack space. In scenarios with reduced
164+
/// stack size (in particular dispatch queues), this often results in a stack
165+
/// overflow during syntax tree rewriting.
166+
///
167+
/// To circumvent this problem, make calling the specific visitation function
168+
/// a two-step process: First determine the function to call in this function
169+
/// and return a reference to it, then call it. This way, the stack frame
170+
/// that determines the correct visitiation function will be popped of the
171+
/// stack before the function is being called, making the switch's stack
172+
/// space transient instead of having it linger in the call stack.
173+
private func visitationFunc(for data: SyntaxData) -> ((SyntaxData) -> Syntax)
174+
""") {
175+
SwitchStmt(expression: Expr("data.raw.kind")) {
176+
SwitchCase("case .token:") {
177+
ReturnStmt("return visitImplTokenSyntax")
178+
}
179+
180+
for node in NON_BASE_SYNTAX_NODES {
181+
SwitchCase("case .\(raw: node.swiftSyntaxKind):") {
182+
ReturnStmt("return visitImpl\(raw: node.name)")
183+
}
184+
}
185+
}
186+
}
187+
188+
FunctionDecl("""
189+
private func visit(_ data: SyntaxData) -> Syntax {
190+
return visitationFunc(for: data)(data)
191+
}
192+
""")
193+
})
194+
)
195+
IfConfigClause(
196+
poundKeyword: .poundElseKeyword().withLeadingTrivia(.newline),
197+
elements: .statements(CodeBlockItemList {
198+
FunctionDecl("private func visit(_ data: SyntaxData) -> Syntax") {
199+
SwitchStmt(expression: Expr("data.raw.kind")) {
200+
SwitchCase("case .token:") {
201+
ReturnStmt("return visitImplTokenSyntax(data)")
202+
}
203+
204+
for node in NON_BASE_SYNTAX_NODES {
205+
SwitchCase("case .\(raw: node.swiftSyntaxKind):") {
206+
ReturnStmt("return visitImpl\(raw: node.name)(data)")
207+
}
208+
}
209+
}
210+
}
211+
})
212+
)
213+
}
214+
)
215+
216+
FunctionDecl("""
217+
private func visitChildren<SyntaxType: SyntaxProtocol>(
218+
_ node: SyntaxType
219+
) -> SyntaxType {
220+
// Walk over all children of this node and rewrite them. Don't store any
221+
// rewritten nodes until the first non-`nil` value is encountered. When this
222+
// happens, retrieve all previous syntax nodes from the parent node to
223+
// initialize the new layout. Once we know that we have to rewrite the
224+
// layout, we need to collect all futher children, regardless of whether
225+
// they are rewritten or not.
226+
227+
// newLayout is nil until the first child node is rewritten and rewritten
228+
// nodes are being collected.
229+
var newLayout: ContiguousArray<RawSyntax?>?
230+
231+
// Rewritten children just to keep their 'SyntaxArena' alive until they are
232+
// wrapped with 'Syntax'
233+
var rewrittens: ContiguousArray<Syntax> = []
234+
235+
let syntaxNode = node._syntaxNode
236+
237+
// Incrementing i manually is faster than using .enumerated()
238+
var childIndex = 0
239+
for (raw, info) in RawSyntaxChildren(syntaxNode) {
240+
defer { childIndex += 1 }
241+
guard let child = raw else {
242+
// Node does not exist. If we are collecting rewritten nodes, we need to
243+
// collect this one as well, otherwise we can ignore it.
244+
if newLayout != nil {
245+
newLayout!.append(nil)
246+
}
247+
continue
248+
}
249+
250+
// Build the Syntax node to rewrite
251+
let absoluteRaw = AbsoluteRawSyntax(raw: child, info: info)
252+
let data = SyntaxData(absoluteRaw, parent: syntaxNode)
253+
254+
let rewritten = visit(data)
255+
if rewritten.data.nodeId != info.nodeId {
256+
// The node was rewritten, let's handle it
257+
if newLayout == nil {
258+
// We have not yet collected any previous rewritten nodes. Initialize
259+
// the new layout with the previous nodes of the parent. This is
260+
// possible, since we know they were not rewritten.
261+
262+
// The below implementation is based on Collection.map but directly
263+
// reserves enough capacity for the entire layout.
264+
newLayout = ContiguousArray<RawSyntax?>()
265+
newLayout!.reserveCapacity(node.raw.layoutView!.children.count)
266+
for j in 0..<childIndex {
267+
newLayout!.append(node.raw.layoutView!.children[j])
268+
}
269+
}
270+
271+
// Now that we know we have a new layout in which we collect rewritten
272+
// nodes, add it.
273+
rewrittens.append(rewritten)
274+
newLayout!.append(rewritten.raw)
275+
} else {
276+
// The node was not changed by the rewriter. Only store it if a previous
277+
// node has been rewritten and we are collecting a rewritten layout.
278+
if newLayout != nil {
279+
newLayout!.append(raw)
280+
}
281+
}
282+
}
283+
284+
if let newLayout = newLayout {
285+
// A child node was rewritten. Build the updated node.
286+
287+
// Sanity check, ensure the new children are the same length.
288+
assert(newLayout.count == node.raw.layoutView!.children.count)
289+
290+
let arena = SyntaxArena()
291+
let newRaw = node.raw.layoutView!.replacingLayout(with: Array(newLayout), arena: arena)
292+
// 'withExtendedLifetime' to keep 'SyntaxArena's of them alive until here.
293+
return withExtendedLifetime((arena, rewrittens)) {
294+
Syntax(raw: newRaw).cast(SyntaxType.self)
295+
}
296+
} else {
297+
// No child node was rewritten. So no need to change this node as well.
298+
return node
299+
}
300+
}
301+
""")
302+
}
303+
}

Package.swift

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -66,7 +66,6 @@ let package = Package(
6666
"Raw/RawSyntaxNodes.swift.gyb",
6767
"Raw/RawSyntaxValidation.swift.gyb",
6868
"SyntaxNodes.swift.gyb.template",
69-
"SyntaxRewriter.swift.gyb",
7069
"Trivia.swift.gyb",
7170
],
7271
swiftSettings: swiftSyntaxSwiftSettings

Sources/SwiftSyntax/CMakeLists.txt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -38,7 +38,7 @@ add_swift_host_library(SwiftSyntax
3838
generated/SyntaxEnum.swift
3939
generated/SyntaxFactory.swift
4040
generated/SyntaxKind.swift
41-
gyb_generated/SyntaxRewriter.swift
41+
generated/SyntaxRewriter.swift
4242
generated/SyntaxTraits.swift
4343
generated/SyntaxTransform.swift
4444
generated/SyntaxVisitor.swift

0 commit comments

Comments
 (0)