Skip to content

Commit ab059e9

Browse files
committed
Replace optionalQuestionMark with SwiftSyntaxBuilder nodes
1 parent 103a9f5 commit ab059e9

File tree

5 files changed

+128
-75
lines changed

5 files changed

+128
-75
lines changed

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: "nil")
67+
}
4568
}
4669
}
4770

Sources/SwiftSyntaxBuilderGeneration/SyntaxBuildableType.swift

Lines changed: 70 additions & 40 deletions
Original file line numberDiff line numberDiff line change
@@ -22,11 +22,6 @@ struct SyntaxBuildableType: Hashable {
2222
let tokenKind: String?
2323
let isOptional: Bool
2424

25-
/// If optional `?`, otherwise the empty string.
26-
var optionalQuestionMark: String {
27-
isOptional ? "?" : ""
28-
}
29-
3025
/// Whether this is a token.
3126
var isToken: Bool {
3227
syntaxKind == "Token"
@@ -71,81 +66,98 @@ struct SyntaxBuildableType: Hashable {
7166
syntaxKind
7267
}
7368

74-
/// 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
7570
/// SwiftSyntax trees using `SwiftSyntaxBuilder`.
7671
///
7772
/// These names look as follows:
7873
/// - For nodes: The node name, e.g. `IdentifierExpr` (these are implemented as structs)
7974
/// - For base kinds: `<BaseKind>Buildable`, e.g. `ExprBuildable` (these are implemented as protocols)
8075
/// - For token: `TokenSyntax` (tokens don't have a dedicated type in SwiftSyntaxBuilder)
81-
/// If the type is optional, this terminates with a '?'.
82-
var buildable: String {
83-
if isToken {
84-
// Tokens don't have a dedicated buildable type.
85-
return "TokenSyntax\(optionalQuestionMark)"
86-
} else if SYNTAX_BASE_KINDS.contains(syntaxKind) {
87-
return "\(syntaxKind)Buildable\(optionalQuestionMark)"
88-
} else {
89-
return "\(syntaxKind)\(optionalQuestionMark)"
90-
}
76+
/// If the type is optional, the type is wrapped in an `OptionalType`.
77+
var buildable: ExpressibleAsTypeBuildable {
78+
optionalWrapped(type: buildableBaseName)
9179
}
9280

9381
/// Whether parameters of this type should be initializable by a result builder.
9482
/// Used for certain syntax collections and block-like structures (e.g. `CodeBlock`,
9583
/// `MemberDeclList`).
9684
var isBuilderInitializable: Bool {
97-
BUILDER_INITIALIZABLE_TYPES.keys.contains(nonOptional.buildable)
85+
BUILDER_INITIALIZABLE_TYPES.keys.contains(buildableBaseName)
9886
}
9987

10088
/// A type suitable for initializing this type through a result builder (e.g.
10189
/// returns `CodeBlockItemList` for `CodeBlock`) and otherwise itself.
10290
var builderInitializableType: Self {
103-
let buildable = nonOptional.buildable
104-
return Self(
105-
syntaxKind: BUILDER_INITIALIZABLE_TYPES[buildable].flatMap { $0 } ?? buildable,
91+
Self(
92+
syntaxKind: BUILDER_INITIALIZABLE_TYPES[buildableBaseName].flatMap { $0 } ?? buildableBaseName,
10693
isOptional: isOptional
10794
)
10895
}
10996

11097
/// The type from `buildable()` without any question marks attached.
11198
/// This is used for the `create*` methods defined in the `ExpressibleAs*` protocols.
11299
var buildableBaseName: String {
113-
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+
}
114108
}
115109

116-
/// The `ExpressibleAs*` Swift type for this syntax kind. Tokens don't
117-
/// have an `ExpressibleAs*` type, so for those this method just returns
118-
/// `TokenSyntax`. If the type is optional, this terminates with a `?`.
119-
var expressibleAs: String {
110+
/// The `ExpressibleAs*` Swift type for this syntax kind without any
111+
/// question marks attached.
112+
var expressibleAsBaseName: String {
120113
if isToken {
121114
// Tokens don't have a dedicated ExpressibleAs type.
122-
return buildable
115+
return buildableBaseName
123116
} else {
124-
return "ExpressibleAs\(buildable)"
117+
return "ExpressibleAs\(buildableBaseName)"
125118
}
126119
}
127120

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+
128128
/// The corresponding `*Syntax` type defined in the `SwiftSyntax` module,
129-
/// which will eventually get built from `SwiftSyntaxBuilder`. If the type
130-
/// is optional, this terminates with a `?`.
131-
var syntax: String {
129+
/// without any question marks attached.
130+
var syntaxBaseName: String {
132131
if syntaxKind == "Syntax" {
133-
return "Syntax\(optionalQuestionMark)"
132+
return "Syntax"
134133
} else {
135-
return "\(syntaxKind)Syntax\(optionalQuestionMark)"
134+
return "\(syntaxKind)Syntax"
136135
}
137136
}
138137

139-
/// Assuming that this is a base kind, return the corresponding `*ListBuildable` type.
140-
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 {
141148
assert(SYNTAX_BASE_KINDS.contains(syntaxKind), "ListBuildable types only exist for syntax base kinds")
142-
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)
143155
}
144156

145157
/// Assuming that this is a collection type, the type of the result builder
146158
/// that can be used to build the collection.
147-
var resultBuilder: String {
148-
"\(syntaxKind)Builder\(optionalQuestionMark)"
159+
var resultBuilder: ExpressibleAsTypeBuildable {
160+
optionalWrapped(type: "\(syntaxKind)Builder")
149161
}
150162

151163
/// The collection types in which this type occurs as an element.
@@ -167,7 +179,7 @@ struct SyntaxBuildableType: Hashable {
167179
/// make the `ExpressibleAs*` of this type conform to the `ExpressibleAs*`
168180
/// protocol of the convertible types.
169181
var convertibleToTypes: [Self] {
170-
(SYNTAX_BUILDABLE_EXPRESSIBLE_AS_CONFORMANCES[buildable] ?? [])
182+
(SYNTAX_BUILDABLE_EXPRESSIBLE_AS_CONFORMANCES[buildableBaseName] ?? [])
171183
.map { Self(syntaxKind: $0) }
172184
}
173185

@@ -220,13 +232,31 @@ struct SyntaxBuildableType: Hashable {
220232
}
221233
}
222234

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+
223253
/// Generate an expression that converts a variable named `varName`
224254
/// which is of `expressibleAs` type to an object of type `buildable`.
225-
func generateExprConvertParamTypeToStorageType(varName: String) -> String {
255+
func generateExprConvertParamTypeToStorageType(varName: String) -> ExpressibleAsExprBuildable {
226256
if isToken {
227257
return varName
228258
} else {
229-
return "\(varName)\(optionalQuestionMark).create\(buildableBaseName)()"
259+
return FunctionCallExpr(MemberAccessExpr(base: optionalChained(expr: varName), name: "create\(buildableBaseName)"))
230260
}
231261
}
232262
}

Sources/SwiftSyntaxBuilderGeneration/Templates/BuildableBaseProtocolsFile.swift

Lines changed: 15 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -30,67 +30,67 @@ let buildableBaseProtocolsFile = SourceFile {
3030
let syntaxType = SyntaxBuildableType(syntaxKind: "Syntax")
3131
let isSyntax = type == syntaxType
3232
// Types that the `*Buildable` conforms to
33-
let buildableConformances: [String] = [type.expressibleAs, type.listBuildable] + (isSyntax ? [] : [syntaxType.buildable])
34-
let listConformances: [String] = isSyntax ? [] : [syntaxType.listBuildable]
33+
let buildableConformances: [String] = [type.expressibleAsBaseName, type.listBuildableBaseName] + (isSyntax ? [] : [syntaxType.buildableBaseName])
34+
let listConformances: [String] = isSyntax ? [] : [syntaxType.listBuildableBaseName]
3535

3636
ProtocolDecl(
3737
modifiers: [TokenSyntax.public],
38-
identifier: type.listBuildable,
38+
identifier: type.listBuildableBaseName,
3939
inheritanceClause: createTypeInheritanceClause(conformances: listConformances)
4040
) {
4141
FunctionDecl(
4242
leadingTrivia: [
43-
"/// Builds list of `\(type.syntax)`s.",
43+
"/// Builds list of `\(type.syntaxBaseName)`s.",
4444
"/// - Parameter format: The `Format` to use.",
4545
"/// - Parameter leadingTrivia: Replaces the last leading trivia if not nil.",
4646
].map { .docLineComment($0) + .newline }.reduce([], +),
4747
identifier: .identifier("build\(type.baseName)List"),
4848
signature: FunctionSignature(
4949
input: formatLeadingTriviaParameters(),
50-
output: ArrayType(elementType: type.syntax)
50+
output: ArrayType(elementType: type.syntaxBaseName)
5151
),
5252
body: nil
5353
)
5454
}
5555

5656
ProtocolDecl(
5757
modifiers: [TokenSyntax.public],
58-
identifier: type.buildable,
58+
identifier: type.buildableBaseName,
5959
inheritanceClause: createTypeInheritanceClause(conformances: buildableConformances)
6060
) {
6161
FunctionDecl(
6262
leadingTrivia: [
63-
"/// Builds list of `\(type.syntax)`s.",
63+
"/// Builds list of `\(type.syntaxBaseName)`s.",
6464
"/// - Parameter format: The `Format` to use.",
6565
"/// - Parameter leadingTrivia: Replaces the last leading trivia if not nil.",
6666
].map { .docLineComment($0) + .newline }.reduce([], +),
6767
identifier: .identifier("build\(type.baseName)"),
6868
signature: FunctionSignature(
6969
input: formatLeadingTriviaParameters(),
70-
output: type.syntax
70+
output: type.syntaxBaseName
7171
),
7272
body: nil
7373
)
7474
}
7575

7676
ExtensionDecl(
7777
modifiers: [TokenSyntax.public],
78-
extendedType: type.buildable
78+
extendedType: type.buildableBaseName
7979
) {
8080
FunctionDecl(
81-
leadingTrivia: .docLineComment("/// Satisfies conformance to `\(type.expressibleAs)`.") + .newline,
81+
leadingTrivia: .docLineComment("/// Satisfies conformance to `\(type.expressibleAsBaseName)`.") + .newline,
8282
identifier: .identifier("create\(type.buildableBaseName)"),
8383
signature: FunctionSignature(
8484
input: ParameterClause(),
85-
output: type.buildable
85+
output: type.buildableBaseName
8686
)
8787
) {
8888
ReturnStmt(expression: "self")
8989
}
9090

9191
FunctionDecl(
9292
leadingTrivia: [
93-
"/// Builds list of `\(type.syntax)`s.",
93+
"/// Builds list of `\(type.syntaxBaseName)`s.",
9494
"/// - Parameter format: The `Format` to use.",
9595
"/// - Parameter leadingTrivia: Replaces the last leading trivia if not nil.",
9696
"///",
@@ -99,7 +99,7 @@ let buildableBaseProtocolsFile = SourceFile {
9999
identifier: .identifier("build\(type.baseName)List"),
100100
signature: FunctionSignature(
101101
input: formatLeadingTriviaParameters(withDefaultTrivia: true),
102-
output: ArrayType(elementType: type.syntax)
102+
output: ArrayType(elementType: type.syntaxBaseName)
103103
)
104104
) {
105105
ReturnStmt(expression: ArrayExpr {
@@ -113,10 +113,10 @@ let buildableBaseProtocolsFile = SourceFile {
113113
if !isSyntax {
114114
FunctionDecl(
115115
leadingTrivia: [
116-
"/// Builds a `\(type.syntax)`.",
116+
"/// Builds a `\(type.syntaxBaseName)`.",
117117
"/// - Parameter format: The `Format` to use.",
118118
"/// - Parameter leadingTrivia: Replaces the last leading trivia if not nil.",
119-
"/// - Returns: A new `Syntax` with the built `\(type.syntax)`.",
119+
"/// - Returns: A new `Syntax` with the built `\(type.syntaxBaseName)`.",
120120
"///",
121121
"/// Satisfies conformance to `SyntaxBuildable`.",
122122
].map { .docLineComment($0) + .newline }.reduce([], +),

Sources/SwiftSyntaxBuilderGeneration/Templates/ExpressibleAsProtocolsFile.swift

Lines changed: 8 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -39,8 +39,8 @@ let expressibleAsProtocolsFile = SourceFile {
3939

4040
ProtocolDecl(
4141
modifiers: [TokenSyntax.public],
42-
identifier: type.expressibleAs,
43-
inheritanceClause: createTypeInheritanceClause(conformances: declaredConformances.map(\.expressibleAs))
42+
identifier: type.expressibleAsBaseName,
43+
inheritanceClause: createTypeInheritanceClause(conformances: declaredConformances.map(\.expressibleAsBaseName))
4444
) {
4545
FunctionDecl(
4646
identifier: .identifier("create\(type.buildableBaseName)"),
@@ -55,18 +55,18 @@ let expressibleAsProtocolsFile = SourceFile {
5555
if !conformances.isEmpty {
5656
ExtensionDecl(
5757
modifiers: [TokenSyntax.public],
58-
extendedType: type.expressibleAs
58+
extendedType: type.expressibleAsBaseName
5959
) {
6060
for conformance in type.elementInCollections {
6161
FunctionDecl(
62-
leadingTrivia: .docLineComment("/// Conformance to `\(conformance.expressibleAs)`") + .newline,
62+
leadingTrivia: .docLineComment("/// Conformance to `\(conformance.expressibleAsBaseName)`") + .newline,
6363
identifier: .identifier("create\(conformance.buildableBaseName)"),
6464
signature: FunctionSignature(
6565
input: ParameterClause(),
6666
output: conformance.buildable
6767
)
6868
) {
69-
ReturnStmt(expression: FunctionCallExpr(conformance.buildable) {
69+
ReturnStmt(expression: FunctionCallExpr(conformance.buildableBaseName) {
7070
TupleExprElement(expression: ArrayExpr {
7171
ArrayElement(expression: "self")
7272
})
@@ -76,14 +76,14 @@ let expressibleAsProtocolsFile = SourceFile {
7676
for conformance in type.convertibleToTypes {
7777
let param = Node.from(type: conformance).singleNonDefaultedChild
7878
FunctionDecl(
79-
leadingTrivia: .docLineComment("/// Conformance to \(conformance.expressibleAs)") + .newline,
79+
leadingTrivia: .docLineComment("/// Conformance to \(conformance.expressibleAsBaseName)") + .newline,
8080
identifier: .identifier("create\(conformance.buildableBaseName)"),
8181
signature: FunctionSignature(
8282
input: ParameterClause(),
83-
output: conformance.buildable
83+
output: conformance.buildableBaseName
8484
)
8585
) {
86-
ReturnStmt(expression: FunctionCallExpr(conformance.buildable) {
86+
ReturnStmt(expression: FunctionCallExpr(conformance.buildableBaseName) {
8787
TupleExprElement(label: param.swiftName, expression: "self")
8888
})
8989
}

0 commit comments

Comments
 (0)