Skip to content

Commit 6eee38b

Browse files
authored
Merge pull request #542 from fwcd/syntax-buildable-wrappers-buildable-nodes
Migrate from strings to typed buildable nodes in `SyntaxBuildableWrappers`
2 parents 8875897 + 966a50c commit 6eee38b

File tree

7 files changed

+149
-93
lines changed

7 files changed

+149
-93
lines changed

Sources/SwiftSyntaxBuilder/generated/BuildableBaseProtocols.swift

Lines changed: 11 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -36,7 +36,7 @@ public extension DeclBuildable {
3636
/// - Parameter leadingTrivia: Replaces the last leading trivia if not nil.
3737
///
3838
/// Satisfies conformance to `DeclListBuildable`
39-
func buildDeclList(format: Format, leadingTrivia: Trivia? = nil) -> [DeclSyntax] {
39+
func buildDeclList(format: Format, leadingTrivia: Trivia? = nil ) -> [DeclSyntax] {
4040
return [buildDecl(format: format, leadingTrivia: leadingTrivia)]
4141
}
4242
/// Builds a `DeclSyntax`.
@@ -45,7 +45,7 @@ public extension DeclBuildable {
4545
/// - Returns: A new `Syntax` with the built `DeclSyntax`.
4646
///
4747
/// Satisfies conformance to `SyntaxBuildable`.
48-
func buildSyntax(format: Format, leadingTrivia: Trivia? = nil) -> Syntax {
48+
func buildSyntax(format: Format, leadingTrivia: Trivia? = nil ) -> Syntax {
4949
return Syntax(buildDecl(format: format, leadingTrivia: leadingTrivia))
5050
}
5151
}
@@ -71,7 +71,7 @@ public extension ExprBuildable {
7171
/// - Parameter leadingTrivia: Replaces the last leading trivia if not nil.
7272
///
7373
/// Satisfies conformance to `ExprListBuildable`
74-
func buildExprList(format: Format, leadingTrivia: Trivia? = nil) -> [ExprSyntax] {
74+
func buildExprList(format: Format, leadingTrivia: Trivia? = nil ) -> [ExprSyntax] {
7575
return [buildExpr(format: format, leadingTrivia: leadingTrivia)]
7676
}
7777
/// Builds a `ExprSyntax`.
@@ -80,7 +80,7 @@ public extension ExprBuildable {
8080
/// - Returns: A new `Syntax` with the built `ExprSyntax`.
8181
///
8282
/// Satisfies conformance to `SyntaxBuildable`.
83-
func buildSyntax(format: Format, leadingTrivia: Trivia? = nil) -> Syntax {
83+
func buildSyntax(format: Format, leadingTrivia: Trivia? = nil ) -> Syntax {
8484
return Syntax(buildExpr(format: format, leadingTrivia: leadingTrivia))
8585
}
8686
}
@@ -106,7 +106,7 @@ public extension PatternBuildable {
106106
/// - Parameter leadingTrivia: Replaces the last leading trivia if not nil.
107107
///
108108
/// Satisfies conformance to `PatternListBuildable`
109-
func buildPatternList(format: Format, leadingTrivia: Trivia? = nil) -> [PatternSyntax] {
109+
func buildPatternList(format: Format, leadingTrivia: Trivia? = nil ) -> [PatternSyntax] {
110110
return [buildPattern(format: format, leadingTrivia: leadingTrivia)]
111111
}
112112
/// Builds a `PatternSyntax`.
@@ -115,7 +115,7 @@ public extension PatternBuildable {
115115
/// - Returns: A new `Syntax` with the built `PatternSyntax`.
116116
///
117117
/// Satisfies conformance to `SyntaxBuildable`.
118-
func buildSyntax(format: Format, leadingTrivia: Trivia? = nil) -> Syntax {
118+
func buildSyntax(format: Format, leadingTrivia: Trivia? = nil ) -> Syntax {
119119
return Syntax(buildPattern(format: format, leadingTrivia: leadingTrivia))
120120
}
121121
}
@@ -141,7 +141,7 @@ public extension StmtBuildable {
141141
/// - Parameter leadingTrivia: Replaces the last leading trivia if not nil.
142142
///
143143
/// Satisfies conformance to `StmtListBuildable`
144-
func buildStmtList(format: Format, leadingTrivia: Trivia? = nil) -> [StmtSyntax] {
144+
func buildStmtList(format: Format, leadingTrivia: Trivia? = nil ) -> [StmtSyntax] {
145145
return [buildStmt(format: format, leadingTrivia: leadingTrivia)]
146146
}
147147
/// Builds a `StmtSyntax`.
@@ -150,7 +150,7 @@ public extension StmtBuildable {
150150
/// - Returns: A new `Syntax` with the built `StmtSyntax`.
151151
///
152152
/// Satisfies conformance to `SyntaxBuildable`.
153-
func buildSyntax(format: Format, leadingTrivia: Trivia? = nil) -> Syntax {
153+
func buildSyntax(format: Format, leadingTrivia: Trivia? = nil ) -> Syntax {
154154
return Syntax(buildStmt(format: format, leadingTrivia: leadingTrivia))
155155
}
156156
}
@@ -176,7 +176,7 @@ public extension SyntaxBuildable {
176176
/// - Parameter leadingTrivia: Replaces the last leading trivia if not nil.
177177
///
178178
/// Satisfies conformance to `SyntaxListBuildable`
179-
func buildSyntaxList(format: Format, leadingTrivia: Trivia? = nil) -> [Syntax] {
179+
func buildSyntaxList(format: Format, leadingTrivia: Trivia? = nil ) -> [Syntax] {
180180
return [buildSyntax(format: format, leadingTrivia: leadingTrivia)]
181181
}
182182
}
@@ -202,7 +202,7 @@ public extension TypeBuildable {
202202
/// - Parameter leadingTrivia: Replaces the last leading trivia if not nil.
203203
///
204204
/// Satisfies conformance to `TypeListBuildable`
205-
func buildTypeList(format: Format, leadingTrivia: Trivia? = nil) -> [TypeSyntax] {
205+
func buildTypeList(format: Format, leadingTrivia: Trivia? = nil ) -> [TypeSyntax] {
206206
return [buildType(format: format, leadingTrivia: leadingTrivia)]
207207
}
208208
/// Builds a `TypeSyntax`.
@@ -211,7 +211,7 @@ public extension TypeBuildable {
211211
/// - Returns: A new `Syntax` with the built `TypeSyntax`.
212212
///
213213
/// Satisfies conformance to `SyntaxBuildable`.
214-
func buildSyntax(format: Format, leadingTrivia: Trivia? = nil) -> Syntax {
214+
func buildSyntax(format: Format, leadingTrivia: Trivia? = nil ) -> Syntax {
215215
return Syntax(buildType(format: format, leadingTrivia: leadingTrivia))
216216
}
217217
}

Sources/SwiftSyntaxBuilderGeneration/SyntaxBuildableChild.swift

Lines changed: 28 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,9 @@
1010
//
1111
//===----------------------------------------------------------------------===//
1212

13+
import SwiftSyntax
14+
import SwiftSyntaxBuilder
15+
1316
/// Extension to the `Child` type to provide functionality specific to
1417
/// SwiftSyntaxBuilder.
1518
extension Child {
@@ -31,17 +34,37 @@ extension Child {
3134
/// Generate a Swift expression that creates a proper SwiftSyntax node of type
3235
/// `type.syntax` from a variable named `varName` of type `type.buildable` that
3336
/// represents this child node.
34-
func generateExprBuildSyntaxNode(varName: String, formatName: String) -> String {
37+
func generateExprBuildSyntaxNode(varName: String, formatName: String) -> ExpressibleAsExprBuildable {
3538
if type.isToken {
3639
if requiresLeadingNewline {
37-
return "\(varName).withLeadingTrivia(.newline + \(formatName)._makeIndent() + (\(varName).leadingTrivia ?? []))"
40+
return FunctionCallExpr(MemberAccessExpr(base: varName, name: "withLeadingTrivia")) {
41+
SequenceExpr {
42+
MemberAccessExpr(name: "newline")
43+
BinaryOperatorExpr("+")
44+
FunctionCallExpr(MemberAccessExpr(base: formatName, name: "_makeIndent"))
45+
BinaryOperatorExpr("+")
46+
TupleExpr {
47+
SequenceExpr {
48+
MemberAccessExpr(base: varName, name: "leadingTrivia")
49+
BinaryOperatorExpr("??")
50+
ArrayExpr(elements: [])
51+
}
52+
}
53+
}
54+
}
3855
} else {
3956
return varName
4057
}
4158
} else {
42-
let format = formatName + (isIndented ? "._indented()" : "")
43-
let expr = "\(varName)\(type.optionalQuestionMark)"
44-
return "\(expr).build\(type.baseName)(format: \(format), leadingTrivia: nil)"
59+
var format: ExpressibleAsExprBuildable = formatName
60+
if isIndented {
61+
format = FunctionCallExpr(MemberAccessExpr(base: format, name: "_indented"))
62+
}
63+
let expr = type.optionalChained(expr: varName)
64+
return FunctionCallExpr(MemberAccessExpr(base: expr, name: "build\(type.baseName)")) {
65+
TupleExprElement(label: "format", expression: format)
66+
TupleExprElement(label: "leadingTrivia", expression: NilLiteralExpr())
67+
}
4568
}
4669
}
4770

Sources/SwiftSyntaxBuilderGeneration/SyntaxBuildableNode.swift

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -45,7 +45,7 @@ extension Node {
4545

4646
/// Assuming this node has a single child without a default value, that child.
4747
var singleNonDefaultedChild: Child {
48-
let nonDefaultedParams = children.filter(\.type.defaultInitialization.isEmpty)
48+
let nonDefaultedParams = children.filter { $0.type.defaultInitialization == nil }
4949
assert(nonDefaultedParams.count == 1)
5050
return nonDefaultedParams[0]
5151
}

Sources/SwiftSyntaxBuilderGeneration/SyntaxBuildableType.swift

Lines changed: 78 additions & 45 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,9 @@
1010
//
1111
//===----------------------------------------------------------------------===//
1212

13+
import SwiftSyntax
14+
import SwiftSyntaxBuilder
15+
1316
/// Wrapper around the syntax kind strings to provide functionality specific to
1417
/// SwiftSyntaxBuilder. In particular, this includes the functionality to create
1518
/// the `*Buildable`, `ExpressibleAs*` and `*Syntax` Swift types from the syntax
@@ -19,11 +22,6 @@ struct SyntaxBuildableType: Hashable {
1922
let tokenKind: String?
2023
let isOptional: Bool
2124

22-
/// If optional `?`, otherwise the empty string.
23-
var optionalQuestionMark: String {
24-
isOptional ? "?" : ""
25-
}
26-
2725
/// Whether this is a token.
2826
var isToken: Bool {
2927
syntaxKind == "Token"
@@ -43,17 +41,17 @@ struct SyntaxBuildableType: Hashable {
4341
/// with fixed test), return an expression of the form ` = defaultValue`
4442
/// that can be used as the default value for a function parameter.
4543
/// Otherwise, return the empty string.
46-
var defaultInitialization: String {
44+
var defaultInitialization: ExpressibleAsExprBuildable? {
4745
if isOptional {
48-
return " = nil"
46+
return NilLiteralExpr()
4947
} else if isToken {
5048
if let token = token, token.text != nil {
51-
return " = TokenSyntax.`\(lowercaseFirstWord(name: token.name))`"
49+
return MemberAccessExpr(base: "TokenSyntax", name: lowercaseFirstWord(name: token.name))
5250
} else if tokenKind == "EOFToken" {
53-
return " = TokenSyntax.eof"
51+
return MemberAccessExpr(base: "TokenSyntax", name: "eof")
5452
}
5553
}
56-
return ""
54+
return nil
5755
}
5856

5957
/// Whether the type is a syntax collection.
@@ -68,81 +66,98 @@ struct SyntaxBuildableType: Hashable {
6866
syntaxKind
6967
}
7068

71-
/// Return the name of the `Buildable` type that is the main entry point for building
69+
/// Return the `Buildable` type that is the main entry point for building
7270
/// SwiftSyntax trees using `SwiftSyntaxBuilder`.
7371
///
7472
/// These names look as follows:
7573
/// - For nodes: The node name, e.g. `IdentifierExpr` (these are implemented as structs)
7674
/// - For base kinds: `<BaseKind>Buildable`, e.g. `ExprBuildable` (these are implemented as protocols)
7775
/// - For token: `TokenSyntax` (tokens don't have a dedicated type in SwiftSyntaxBuilder)
78-
/// If the type is optional, this terminates with a '?'.
79-
var buildable: String {
80-
if isToken {
81-
// Tokens don't have a dedicated buildable type.
82-
return "TokenSyntax\(optionalQuestionMark)"
83-
} else if SYNTAX_BASE_KINDS.contains(syntaxKind) {
84-
return "\(syntaxKind)Buildable\(optionalQuestionMark)"
85-
} else {
86-
return "\(syntaxKind)\(optionalQuestionMark)"
87-
}
76+
/// If the type is optional, the type is wrapped in an `OptionalType`.
77+
var buildable: ExpressibleAsTypeBuildable {
78+
optionalWrapped(type: buildableBaseName)
8879
}
8980

9081
/// Whether parameters of this type should be initializable by a result builder.
9182
/// Used for certain syntax collections and block-like structures (e.g. `CodeBlock`,
9283
/// `MemberDeclList`).
9384
var isBuilderInitializable: Bool {
94-
BUILDER_INITIALIZABLE_TYPES.keys.contains(nonOptional.buildable)
85+
BUILDER_INITIALIZABLE_TYPES.keys.contains(buildableBaseName)
9586
}
9687

9788
/// A type suitable for initializing this type through a result builder (e.g.
9889
/// returns `CodeBlockItemList` for `CodeBlock`) and otherwise itself.
9990
var builderInitializableType: Self {
100-
let buildable = nonOptional.buildable
101-
return Self(
102-
syntaxKind: BUILDER_INITIALIZABLE_TYPES[buildable].flatMap { $0 } ?? buildable,
91+
Self(
92+
syntaxKind: BUILDER_INITIALIZABLE_TYPES[buildableBaseName].flatMap { $0 } ?? buildableBaseName,
10393
isOptional: isOptional
10494
)
10595
}
10696

10797
/// The type from `buildable()` without any question marks attached.
10898
/// This is used for the `create*` methods defined in the `ExpressibleAs*` protocols.
10999
var buildableBaseName: String {
110-
nonOptional.buildable
100+
if isToken {
101+
// Tokens don't have a dedicated buildable type.
102+
return "TokenSyntax"
103+
} else if SYNTAX_BASE_KINDS.contains(syntaxKind) {
104+
return "\(syntaxKind)Buildable"
105+
} else {
106+
return syntaxKind
107+
}
111108
}
112109

113-
/// The `ExpressibleAs*` Swift type for this syntax kind. Tokens don't
114-
/// have an `ExpressibleAs*` type, so for those this method just returns
115-
/// `TokenSyntax`. If the type is optional, this terminates with a `?`.
116-
var expressibleAs: String {
110+
/// The `ExpressibleAs*` Swift type for this syntax kind without any
111+
/// question marks attached.
112+
var expressibleAsBaseName: String {
117113
if isToken {
118114
// Tokens don't have a dedicated ExpressibleAs type.
119-
return buildable
115+
return buildableBaseName
120116
} else {
121-
return "ExpressibleAs\(buildable)"
117+
return "ExpressibleAs\(buildableBaseName)"
122118
}
123119
}
124120

121+
/// The `ExpressibleAs*` Swift type for this syntax kind. Tokens don't
122+
/// have an `ExpressibleAs*` type, so for those this method just returns
123+
/// `TokenSyntax`. If the type is optional, this terminates with a `?`.
124+
var expressibleAs: ExpressibleAsTypeBuildable {
125+
optionalWrapped(type: expressibleAsBaseName)
126+
}
127+
125128
/// The corresponding `*Syntax` type defined in the `SwiftSyntax` module,
126-
/// which will eventually get built from `SwiftSyntaxBuilder`. If the type
127-
/// is optional, this terminates with a `?`.
128-
var syntax: String {
129+
/// without any question marks attached.
130+
var syntaxBaseName: String {
129131
if syntaxKind == "Syntax" {
130-
return "Syntax\(optionalQuestionMark)"
132+
return "Syntax"
131133
} else {
132-
return "\(syntaxKind)Syntax\(optionalQuestionMark)"
134+
return "\(syntaxKind)Syntax"
133135
}
134136
}
135137

136-
/// Assuming that this is a base kind, return the corresponding `*ListBuildable` type.
137-
var listBuildable: String {
138+
/// The corresponding `*Syntax` type defined in the `SwiftSyntax` module,
139+
/// which will eventually get built from `SwiftSyntaxBuilder`. If the type
140+
/// is optional, this terminates with a `?`.
141+
var syntax: ExpressibleAsTypeBuildable {
142+
optionalWrapped(type: syntaxBaseName)
143+
}
144+
145+
/// Assuming that this is a base kind, return the corresponding `*ListBuildable` type
146+
/// without any question marks attached.
147+
var listBuildableBaseName: String {
138148
assert(SYNTAX_BASE_KINDS.contains(syntaxKind), "ListBuildable types only exist for syntax base kinds")
139-
return "\(syntaxKind)ListBuildable\(optionalQuestionMark)"
149+
return "\(syntaxKind)ListBuildable"
150+
}
151+
152+
/// Assuming that this is a base kind, return the corresponding `*ListBuildable` type.
153+
var listBuildable: ExpressibleAsTypeBuildable {
154+
optionalWrapped(type: listBuildableBaseName)
140155
}
141156

142157
/// Assuming that this is a collection type, the type of the result builder
143158
/// that can be used to build the collection.
144-
var resultBuilder: String {
145-
"\(syntaxKind)Builder\(optionalQuestionMark)"
159+
var resultBuilder: ExpressibleAsTypeBuildable {
160+
optionalWrapped(type: "\(syntaxKind)Builder")
146161
}
147162

148163
/// The collection types in which this type occurs as an element.
@@ -164,7 +179,7 @@ struct SyntaxBuildableType: Hashable {
164179
/// make the `ExpressibleAs*` of this type conform to the `ExpressibleAs*`
165180
/// protocol of the convertible types.
166181
var convertibleToTypes: [Self] {
167-
(SYNTAX_BUILDABLE_EXPRESSIBLE_AS_CONFORMANCES[buildable] ?? [])
182+
(SYNTAX_BUILDABLE_EXPRESSIBLE_AS_CONFORMANCES[buildableBaseName] ?? [])
168183
.map { Self(syntaxKind: $0) }
169184
}
170185

@@ -217,13 +232,31 @@ struct SyntaxBuildableType: Hashable {
217232
}
218233
}
219234

235+
/// Wraps a type in an optional depending on whether `isOptional` is true.
236+
func optionalWrapped(type: ExpressibleAsTypeBuildable) -> ExpressibleAsTypeBuildable {
237+
if isOptional {
238+
return OptionalType(wrappedType: type)
239+
} else {
240+
return type
241+
}
242+
}
243+
244+
/// Wraps a type in an optional chaining depending on whether `isOptional` is true.
245+
func optionalChained(expr: ExpressibleAsExprBuildable) -> ExpressibleAsExprBuildable {
246+
if isOptional {
247+
return OptionalChainingExpr(expression: expr)
248+
} else {
249+
return expr
250+
}
251+
}
252+
220253
/// Generate an expression that converts a variable named `varName`
221254
/// which is of `expressibleAs` type to an object of type `buildable`.
222-
func generateExprConvertParamTypeToStorageType(varName: String) -> String {
255+
func generateExprConvertParamTypeToStorageType(varName: String) -> ExpressibleAsExprBuildable {
223256
if isToken {
224257
return varName
225258
} else {
226-
return "\(varName)\(optionalQuestionMark).create\(buildableBaseName)()"
259+
return FunctionCallExpr(MemberAccessExpr(base: optionalChained(expr: varName), name: "create\(buildableBaseName)"))
227260
}
228261
}
229262
}

0 commit comments

Comments
 (0)