Skip to content

Commit 2f904b1

Browse files
committed
Refactor Syntax initializer
Use generics rather than existentials, add `nil` defaults for *all* optional nodes, and add a default for tokens where there's only a single choice. Allows removing the default initializer in SwiftSyntaxBuilder, since the regular initializer covers it entirely.
1 parent a820410 commit 2f904b1

File tree

18 files changed

+2719
-5503
lines changed

18 files changed

+2719
-5503
lines changed

CodeGeneration/Sources/generate-swiftsyntaxbuilder/BuildableNodesFile.swift

Lines changed: 21 additions & 74 deletions
Original file line numberDiff line numberDiff line change
@@ -27,34 +27,36 @@ let buildableNodesFile = SourceFile {
2727
let hasTrailingComma = node.traits.contains("WithTrailingComma")
2828

2929
let docComment: SwiftSyntax.Trivia = node.documentation.isEmpty ? [] : .docLineComment("/// \(node.documentation)") + .newline
30-
ExtensionDecl(
31-
leadingTrivia: docComment,
32-
extendedType: Type(type.shorthandName),
33-
inheritanceClause: hasTrailingComma ? TypeInheritanceClause { InheritedType(typeName: Type("HasTrailingComma")) } : nil
34-
) {
35-
// Generate initializers
36-
createDefaultInitializer(node: node)
37-
if let convenienceInit = createConvenienceInitializer(node: node) {
38-
convenienceInit
39-
}
30+
let convenienceInit = createConvenienceInitializer(node: node)
31+
32+
if convenienceInit != nil || hasTrailingComma {
33+
ExtensionDecl(
34+
leadingTrivia: docComment,
35+
extendedType: Type(type.shorthandName),
36+
inheritanceClause: hasTrailingComma ? TypeInheritanceClause { InheritedType(typeName: Type("HasTrailingComma")) } : nil
37+
) {
38+
if let convenienceInit = convenienceInit {
39+
convenienceInit
40+
}
4041

41-
if hasTrailingComma {
42-
VariableDecl(
42+
if hasTrailingComma {
43+
VariableDecl(
4344
"""
4445
var hasTrailingComma: Bool {
4546
return trailingComma != nil
4647
}
4748
"""
48-
)
49+
)
4950

50-
FunctionDecl(
51+
FunctionDecl(
5152
"""
5253
/// Conformance to `HasTrailingComma`.
5354
public func withTrailingComma(_ withComma: Bool) -> Self {
5455
return withTrailingComma(withComma ? .commaToken() : nil)
5556
}
5657
"""
57-
)
58+
)
59+
}
5860
}
5961
}
6062
}
@@ -68,57 +70,6 @@ private func convertFromSyntaxProtocolToSyntaxType(child: Child) -> Expr {
6870
}
6971
}
7072

71-
/// Create the default initializer for the given node.
72-
private func createDefaultInitializer(node: Node) -> InitializerDecl {
73-
let type = node.type
74-
return InitializerDecl(
75-
leadingTrivia: ([
76-
"/// Creates a `\(type.shorthandName)` using the provided parameters.",
77-
"/// - Parameters:",
78-
] + node.children.map { child in
79-
"/// - \(child.swiftName): \(child.documentation)"
80-
}).map { .docLineComment($0) + .newline }.reduce([], +),
81-
// FIXME: If all parameters are specified, the SwiftSyntaxBuilder initializer is ambigious
82-
// with the memberwise initializer in SwiftSyntax.
83-
// Hot-fix this by preferring the overload in SwiftSyntax. In the long term, consider sinking
84-
// this initializer to SwiftSyntax.
85-
attributes: AttributeList { CustomAttribute("_disfavoredOverload").withTrailingTrivia(.space) },
86-
modifiers: [DeclModifier(name: .public)],
87-
signature: FunctionSignature(
88-
input: ParameterClause {
89-
for trivia in ["leadingTrivia", "trailingTrivia"] {
90-
FunctionParameter("\(trivia): Trivia = []", for: .functionParameters)
91-
}
92-
for child in node.children {
93-
FunctionParameter(
94-
firstName: .identifier(child.swiftName),
95-
colon: .colon,
96-
type: child.parameterType,
97-
defaultArgument: child.type.defaultInitialization.map { InitializerClause(value: $0) }
98-
)
99-
}
100-
}
101-
)
102-
) {
103-
for child in node.children {
104-
if let assertStmt = child.generateAssertStmtTextChoices(varName: child.swiftName) {
105-
assertStmt
106-
}
107-
}
108-
let nodeConstructorCall = FunctionCallExpr(callee: type.syntaxBaseName) {
109-
for child in node.children {
110-
TupleExprElement(
111-
label: child.isUnexpectedNodes ? nil : child.swiftName,
112-
expression: convertFromSyntaxProtocolToSyntaxType(child: child)
113-
)
114-
}
115-
}
116-
SequenceExpr("self = \(nodeConstructorCall)")
117-
SequenceExpr("self.leadingTrivia = leadingTrivia + (self.leadingTrivia ?? [])")
118-
SequenceExpr("self.trailingTrivia = trailingTrivia + (self.trailingTrivia ?? [])")
119-
}
120-
}
121-
12273
/// Create a builder-based convenience initializer, if needed.
12374
private func createConvenienceInitializer(node: Node) -> InitializerDecl? {
12475
// Only create the convenience initializer if at least one parameter
@@ -193,28 +144,24 @@ private func createConvenienceInitializer(node: Node) -> InitializerDecl? {
193144
"/// - Initializing syntax collections using result builders",
194145
"/// - Initializing tokens without default text using strings",
195146
].map { .docLineComment($0) + .newline }.reduce([], +),
196-
// FIXME: If all parameters are specified, the SwiftSyntaxBuilder initializer is ambigious
197-
// with the memberwise initializer in SwiftSyntax.
198-
// Hot-fix this by preferring the overload in SwiftSyntax. In the long term, consider sinking
199-
// this initializer to SwiftSyntax.
200-
attributes: AttributeList { CustomAttribute("_disfavoredOverload").withTrailingTrivia(.space) },
201147
modifiers: [DeclModifier(name: .public)],
202148
signature: FunctionSignature(
203149
input: ParameterClause {
204-
FunctionParameter("leadingTrivia: Trivia = []", for: .functionParameters)
150+
FunctionParameter("leadingTrivia: Trivia? = nil", for: .functionParameters)
205151
for param in normalParameters + builderParameters {
206152
param
207153
}
154+
FunctionParameter("trailingTrivia: Trivia? = nil", for: .functionParameters)
208155
}
209156
)
210157
) {
211158
FunctionCallExpr(callee: "self.init") {
159+
TupleExprElement(label: "leadingTrivia", expression: "leadingTrivia")
212160
for arg in delegatedInitArgs {
213161
arg
214162
}
163+
TupleExprElement(label: "trailingTrivia", expression: "trailingTrivia")
215164
}
216-
217-
SequenceExpr("self.leadingTrivia = leadingTrivia + (self.leadingTrivia ?? [])")
218165
}
219166
}
220167

Sources/SwiftSyntax/Raw/RawSyntax.swift

Lines changed: 11 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -700,11 +700,20 @@ extension RawSyntax {
700700
static func makeLayout<C: Collection>(
701701
kind: SyntaxKind,
702702
from collection: C,
703-
arena: SyntaxArena
703+
arena: SyntaxArena,
704+
leadingTrivia: Trivia? = nil,
705+
trailingTrivia: Trivia? = nil
704706
) -> RawSyntax where C.Element == RawSyntax? {
705-
.makeLayout(kind: kind, uninitializedCount: collection.count, arena: arena) {
707+
var raw: RawSyntax = .makeLayout(kind: kind, uninitializedCount: collection.count, arena: arena) {
706708
_ = $0.initialize(from: collection)
707709
}
710+
if let leadingTrivia = leadingTrivia {
711+
raw = raw.withLeadingTrivia(leadingTrivia + (raw.formLeadingTrivia() ?? []), arena: arena) ?? raw
712+
}
713+
if let trailingTrivia = trailingTrivia {
714+
raw = raw.withTrailingTrivia((raw.formTrailingTrivia() ?? []) + trailingTrivia, arena: arena) ?? raw
715+
}
716+
return raw
708717
}
709718
}
710719

Sources/SwiftSyntax/SyntaxNodes.swift.gyb.template

Lines changed: 38 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -113,35 +113,54 @@ public struct ${node.name}: ${base_type}Protocol, SyntaxHashable {
113113
self._syntaxNode = Syntax(data)
114114
}
115115

116-
public init(
117-
% for (index, child) in enumerate(node.children):
118-
% comma = ',' if index != len(node.children) - 1 else ''
119-
% param_type = child.name if child.node_choices else child.type_name
120-
% if child.is_optional:
121-
% param_type = param_type + "?"
122-
% if child.is_unexpected_nodes():
123-
_ ${child.swift_name}: ${param_type} = nil${comma}
124-
% else:
125-
${child.swift_name}: ${param_type}${comma}
126-
% end
127-
% end
128-
) {
116+
${node.generate_initializer_decl(include_optional_base=True, current_indentation=" ")} {
117+
% if node.children:
129118
let layout: [RawSyntax?] = [
130-
% for child in node.children:
131-
% if child.is_optional:
119+
% for child in node.children:
120+
% if child.is_optional:
132121
${child.swift_name}?.raw,
133-
% else:
122+
% else:
134123
${child.swift_name}.raw,
124+
% end
135125
% end
136-
% end
137126
]
127+
% end
138128
let data: SyntaxData = withExtendedLifetime(SyntaxArena()) { arena in
139-
let raw = RawSyntax.makeLayout(kind: SyntaxKind.${node.swift_syntax_kind},
140-
from: layout, arena: arena)
129+
% if node.children:
130+
let raw = RawSyntax.makeLayout(
131+
kind: SyntaxKind.${node.swift_syntax_kind}, from: layout, arena: arena,
132+
leadingTrivia: leadingTrivia, trailingTrivia: trailingTrivia)
133+
% else:
134+
let raw = RawSyntax.makeEmptyLayout(kind: SyntaxKind.${node.swift_syntax_kind}, arena: arena)
135+
% end
141136
return SyntaxData.forRoot(raw)
142137
}
143138
self.init(data)
144139
}
140+
% if node.has_optional_base_type_child():
141+
142+
${node.generate_initializer_decl(include_optional_base=False, current_indentation=" ")} {
143+
self.init(
144+
leadingTrivia: leadingTrivia,
145+
% skip_next = False
146+
% for child in node.children:
147+
% if skip_next:
148+
% skip_next = False
149+
% else:
150+
% if child.has_optional_base_type():
151+
% skip_next = True
152+
${child.swift_name}: Optional<${child.type_name}>.none,
153+
% elif child.is_unexpected_nodes():
154+
${child.swift_name},
155+
% else:
156+
${child.swift_name}: ${child.swift_name},
157+
% end
158+
% end
159+
% end
160+
trailingTrivia: trailingTrivia
161+
)
162+
}
163+
% end
145164
% for (idx, child) in enumerate(node.children):
146165
% child_node = NODE_MAP.get(child.syntax_kind)
147166
%

0 commit comments

Comments
 (0)