Skip to content

Commit 0381d8f

Browse files
committed
Introduce type naming for Child nodes in CodeGeneration
1 parent 777520e commit 0381d8f

File tree

13 files changed

+179
-145
lines changed

13 files changed

+179
-145
lines changed

CodeGeneration/Sources/SyntaxSupport/Child.swift

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,14 @@ public enum ChildKind {
4646
}
4747
}
4848

49+
public var isToken: Bool {
50+
if case .token = self {
51+
return true
52+
} else {
53+
return false
54+
}
55+
}
56+
4957
public var isNodeChoicesEmpty: Bool {
5058
if case .nodeChoices(let nodeChoices) = self {
5159
return nodeChoices.isEmpty
@@ -103,6 +111,22 @@ public class Child {
103111
return .identifier(lowercaseFirstWord(name: name))
104112
}
105113

114+
/// If this child has node choices, the type that the nested `SyntaxChildChoices` type should get.
115+
///
116+
/// For any other kind of child nodes, accessing this property crashes.
117+
public var syntaxChoicesType: TypeSyntax {
118+
precondition(kind.isNodeChoices, "Cannot get `syntaxChoicesType` for node that doesn’t have nodeChoices")
119+
return "\(raw: name.withFirstCharacterUppercased)"
120+
}
121+
122+
/// If this child only has tokens, the type that the generated `TokenSpecSet` should get.
123+
///
124+
/// For any other kind of child nodes, accessing this property crashes.
125+
public var tokenSpecSetType: TypeSyntax {
126+
precondition(kind.isToken, "Cannot get `tokenSpecSetType` for node that isn’t a token")
127+
return "\(raw: name.withFirstCharacterUppercased)Options"
128+
}
129+
106130
/// The deprecated name of this child that's suitable to be used for variable or enum case names.
107131
public var deprecatedVarName: TokenSyntax? {
108132
guard let deprecatedName = deprecatedName else {
@@ -111,6 +135,11 @@ public class Child {
111135
return .identifier(lowercaseFirstWord(name: deprecatedName))
112136
}
113137

138+
/// Determines if this child has a deprecated name
139+
public var hasDeprecatedName: Bool {
140+
return deprecatedName != nil
141+
}
142+
114143
/// If the child ends with "token" in the kind, it's considered a token node.
115144
/// Grab the existing reference to that token from the global list.
116145
public var tokenKind: Token? {

CodeGeneration/Sources/SyntaxSupport/Node.swift

Lines changed: 12 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -114,18 +114,20 @@ public class Node {
114114
// any two defined children
115115
childrenWithUnexpected =
116116
children.enumerated().flatMap { (i, child) -> [Child] in
117+
let childName = child.name.withFirstCharacterUppercased
118+
117119
let unexpectedName: String
118120
let unexpectedDeprecatedName: String?
119121

120122
if i == 0 {
121-
unexpectedName = "UnexpectedBefore\(child.name)"
122-
unexpectedDeprecatedName = child.deprecatedName.map { "UnexpectedBefore\($0)" }
123+
unexpectedName = "UnexpectedBefore\(childName)"
124+
unexpectedDeprecatedName = child.deprecatedName.map { "UnexpectedBefore\($0.withFirstCharacterUppercased)" }
123125
} else {
124-
unexpectedName = "UnexpectedBetween\(children[i - 1].name)And\(child.name)"
125-
if let deprecatedName = children[i - 1].deprecatedName {
126-
unexpectedDeprecatedName = "UnexpectedBetween\(deprecatedName)And\(child.deprecatedName ?? child.name)"
127-
} else if let deprecatedName = child.deprecatedName {
128-
unexpectedDeprecatedName = "UnexpectedBetween\(children[i - 1].name)And\(deprecatedName)"
126+
unexpectedName = "UnexpectedBetween\(children[i - 1].name.withFirstCharacterUppercased)And\(childName)"
127+
if let deprecatedName = children[i - 1].deprecatedName?.withFirstCharacterUppercased {
128+
unexpectedDeprecatedName = "UnexpectedBetween\(deprecatedName)And\(child.deprecatedName?.withFirstCharacterUppercased ?? childName)"
129+
} else if let deprecatedName = child.deprecatedName?.withFirstCharacterUppercased {
130+
unexpectedDeprecatedName = "UnexpectedBetween\(children[i - 1].name.withFirstCharacterUppercased)And\(deprecatedName)"
129131
} else {
130132
unexpectedDeprecatedName = nil
131133
}
@@ -139,9 +141,9 @@ public class Node {
139141
return [unexpectedBefore, child]
140142
} + [
141143
Child(
142-
name: "UnexpectedAfter\(children.last!.name)",
143-
deprecatedName: children.last!.deprecatedName.map { "UnexpectedAfter\($0)" },
144-
kind: .collection(kind: .unexpectedNodes, collectionElementName: "UnexpectedAfter\(children.last!.name)"),
144+
name: "UnexpectedAfter\(children.last!.name.withFirstCharacterUppercased)",
145+
deprecatedName: children.last!.deprecatedName.map { "UnexpectedAfter\($0.withFirstCharacterUppercased)" },
146+
kind: .collection(kind: .unexpectedNodes, collectionElementName: "UnexpectedAfter\(children.last!.name.withFirstCharacterUppercased)"),
145147
isOptional: true
146148
)
147149
]

CodeGeneration/Sources/Utils/SyntaxBuildableChild.swift

Lines changed: 10 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,7 @@ public enum SyntaxOrTokenNodeKind: Hashable {
2626
public extension Child {
2727
/// The type of this child, represented by a ``SyntaxBuildableType``, which can
2828
/// be used to create the corresponding `Buildable` and `ExpressibleAs` types.
29-
var type: SyntaxBuildableType {
29+
var buildableType: SyntaxBuildableType {
3030
let buildableKind: SyntaxOrTokenNodeKind
3131
switch kind {
3232
case .node(kind: let kind):
@@ -44,29 +44,29 @@ public extension Child {
4444
)
4545
}
4646

47-
var parameterBaseType: String {
47+
var parameterBaseType: TypeSyntax {
4848
switch kind {
4949
case .nodeChoices:
50-
return self.name
50+
return self.syntaxChoicesType
5151
default:
52-
return type.parameterBaseType
52+
return buildableType.parameterBaseType
5353
}
5454
}
5555

5656
var parameterType: TypeSyntax {
57-
return self.type.optionalWrapped(type: IdentifierTypeSyntax(name: .identifier(parameterBaseType)))
57+
return self.buildableType.optionalWrapped(type: parameterBaseType)
5858
}
5959

6060
var defaultValue: ExprSyntax? {
6161
if isOptional || isUnexpectedNodes {
62-
if type.isBaseType && kind.isNodeChoicesEmpty {
63-
return ExprSyntax("\(type.buildable).none")
62+
if buildableType.isBaseType && kind.isNodeChoicesEmpty {
63+
return ExprSyntax("\(buildableType.buildable).none")
6464
} else {
6565
return ExprSyntax("nil")
6666
}
6767
}
6868
guard let token = token, isToken else {
69-
return type.defaultValue
69+
return buildableType.defaultValue
7070
}
7171
if token.text != nil {
7272
return ExprSyntax(".\(token.varOrCaseName)Token()")
@@ -128,7 +128,7 @@ public extension Child {
128128
}
129129

130130
var preconditionChoices: [ExprSyntax] = []
131-
if type.isOptional {
131+
if buildableType.isOptional {
132132
preconditionChoices.append(
133133
ExprSyntax(
134134
SequenceExprSyntax {
@@ -143,7 +143,7 @@ public extension Child {
143143
preconditionChoices.append(
144144
ExprSyntax(
145145
SequenceExprSyntax {
146-
MemberAccessExprSyntax(base: type.forceUnwrappedIfNeeded(expr: DeclReferenceExprSyntax(baseName: .identifier(varName))), name: "text")
146+
MemberAccessExprSyntax(base: buildableType.forceUnwrappedIfNeeded(expr: DeclReferenceExprSyntax(baseName: .identifier(varName))), name: "text")
147147
BinaryOperatorExprSyntax(text: "==")
148148
StringLiteralExprSyntax(content: textChoice)
149149
}

CodeGeneration/Sources/Utils/SyntaxBuildableType.swift

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -85,7 +85,7 @@ public struct SyntaxBuildableType: Hashable {
8585
/// - For token: ``TokenSyntax`` (tokens don't have a dedicated type in SwiftSyntaxBuilder)
8686
/// If the type is optional, the type is wrapped in an `OptionalType`.
8787
public var buildable: TypeSyntax {
88-
optionalWrapped(type: IdentifierTypeSyntax(name: .identifier(syntaxBaseName)))
88+
optionalWrapped(type: syntaxBaseName)
8989
}
9090

9191
/// Whether parameters of this type should be initializable by a result builder.
@@ -120,10 +120,10 @@ public struct SyntaxBuildableType: Hashable {
120120

121121
/// The corresponding `*Syntax` type defined in the `SwiftSyntax` module,
122122
/// without any question marks attached.
123-
public var syntaxBaseName: String {
123+
public var syntaxBaseName: TypeSyntax {
124124
switch kind {
125125
case .node(kind: let kind):
126-
return "\(kind.syntaxType)"
126+
return kind.syntaxType
127127
case .token:
128128
return "TokenSyntax"
129129
}
@@ -133,12 +133,12 @@ public struct SyntaxBuildableType: Hashable {
133133
/// which will eventually get built from `SwiftSyntaxBuilder`. If the type
134134
/// is optional, this terminates with a `?`.
135135
public var syntax: TypeSyntax {
136-
return optionalWrapped(type: IdentifierTypeSyntax(name: .identifier(syntaxBaseName)))
136+
return optionalWrapped(type: syntaxBaseName)
137137
}
138138

139139
/// The type that is used for parameters in SwiftSyntaxBuilder that take this
140140
/// type of syntax node.
141-
public var parameterBaseType: String {
141+
public var parameterBaseType: TypeSyntax {
142142
if isBaseType {
143143
return "\(syntaxBaseName)Protocol"
144144
} else {
@@ -147,7 +147,7 @@ public struct SyntaxBuildableType: Hashable {
147147
}
148148

149149
public var parameterType: TypeSyntax {
150-
return optionalWrapped(type: IdentifierTypeSyntax(name: .identifier(parameterBaseType)))
150+
return optionalWrapped(type: parameterBaseType)
151151
}
152152

153153
/// Assuming that this is a collection type, the non-optional type of the result builder

CodeGeneration/Sources/generate-swiftsyntax/LayoutNode+Extensions.swift

Lines changed: 9 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,7 @@ extension LayoutNode {
2424
func createFunctionParameterSyntax(for child: Child) -> FunctionParameterSyntax {
2525
var paramType: TypeSyntax
2626
if !child.kind.isNodeChoicesEmpty {
27-
paramType = "\(raw: child.name)"
27+
paramType = "\(child.syntaxChoicesType)"
2828
} else if child.hasBaseType {
2929
paramType = "some \(raw: child.syntaxNodeKind.protocolType)"
3030
} else {
@@ -119,16 +119,16 @@ extension LayoutNode {
119119
childName = child.varOrCaseName
120120
}
121121

122-
if child.type.isBuilderInitializable {
122+
if child.buildableType.isBuilderInitializable {
123123
// Allow initializing certain syntax collections with result builders
124124
shouldCreateInitializer = true
125-
let builderInitializableType = child.type.builderInitializableType
126-
if child.type.builderInitializableType != child.type {
127-
let param = Node.from(type: child.type).layoutNode!.singleNonDefaultedChild
125+
let builderInitializableType = child.buildableType.builderInitializableType
126+
if child.buildableType.builderInitializableType != child.buildableType {
127+
let param = Node.from(type: child.buildableType).layoutNode!.singleNonDefaultedChild
128128
if child.isOptional {
129-
produceExpr = ExprSyntax("\(childName)Builder().map { \(raw: child.type.syntaxBaseName)(\(param.varOrCaseName): $0) }")
129+
produceExpr = ExprSyntax("\(childName)Builder().map { \(raw: child.buildableType.syntaxBaseName)(\(param.varOrCaseName): $0) }")
130130
} else {
131-
produceExpr = ExprSyntax("\(raw: child.type.syntaxBaseName)(\(param.varOrCaseName): \(childName)Builder())")
131+
produceExpr = ExprSyntax("\(raw: child.buildableType.syntaxBaseName)(\(param.varOrCaseName): \(childName)Builder())")
132132
}
133133
} else {
134134
produceExpr = ExprSyntax("\(childName)Builder()")
@@ -195,8 +195,8 @@ fileprivate func convertFromSyntaxProtocolToSyntaxType(child: Child, useDeprecat
195195
childName = child.varOrCaseName
196196
}
197197

198-
if child.type.isBaseType && !child.kind.isNodeChoices {
199-
return ExprSyntax("\(raw: child.type.syntaxBaseName)(fromProtocol: \(childName.backtickedIfNeeded))")
198+
if child.buildableType.isBaseType && !child.kind.isNodeChoices {
199+
return ExprSyntax("\(raw: child.buildableType.syntaxBaseName)(fromProtocol: \(childName.backtickedIfNeeded))")
200200
}
201201
return ExprSyntax("\(raw: childName.backtickedIfNeeded)")
202202
}

CodeGeneration/Sources/generate-swiftsyntax/templates/Array+Child.swift

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,6 @@ import SyntaxSupport
1414

1515
extension Array where Element == Child {
1616
var hasDeprecatedChild: Bool {
17-
return self.contains(where: { $0.deprecatedName != nil })
17+
return self.contains(where: { $0.hasDeprecatedName })
1818
}
1919
}

CodeGeneration/Sources/generate-swiftsyntax/templates/swiftparser/ParserTokenSpecSetFile.swift

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,7 @@ let parserTokenSpecSetFile = SourceFileSyntax(leadingTrivia: copyrightHeader) {
2525
try EnumDeclSyntax(
2626
"""
2727
@_spi(Diagnostics)
28-
public enum \(raw: child.name)Options: TokenSpecSet
28+
public enum \(child.tokenSpecSetType): TokenSpecSet
2929
"""
3030
) {
3131
for choice in choices {

CodeGeneration/Sources/generate-swiftsyntax/templates/swiftsyntax/RawSyntaxNodesFile.swift

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -16,13 +16,13 @@ import SyntaxSupport
1616
import Utils
1717

1818
fileprivate extension Node {
19-
var childrenChoicesEnums: [(name: TokenSyntax, choices: [(caseName: TokenSyntax, kind: SyntaxNodeKind)])] {
19+
var childrenChoicesEnums: [(name: TypeSyntax, choices: [(caseName: TokenSyntax, kind: SyntaxNodeKind)])] {
2020
let node = self
2121
if let node = node.layoutNode {
22-
return node.children.compactMap { child -> (name: TokenSyntax, choices: [(caseName: TokenSyntax, kind: SyntaxNodeKind)])? in
22+
return node.children.compactMap { child -> (name: TypeSyntax, choices: [(caseName: TokenSyntax, kind: SyntaxNodeKind)])? in
2323
switch child.kind {
2424
case .nodeChoices(let choices):
25-
return (.identifier(child.name), choices.map { ($0.varOrCaseName, $0.syntaxNodeKind) })
25+
return (child.syntaxChoicesType, choices.map { ($0.varOrCaseName, $0.syntaxNodeKind) })
2626
default:
2727
return nil
2828
}
@@ -31,7 +31,7 @@ fileprivate extension Node {
3131
let choices = node.elementChoices.map { choice -> (TokenSyntax, SyntaxNodeKind) in
3232
(SYNTAX_NODE_MAP[choice]!.varOrCaseName, SYNTAX_NODE_MAP[choice]!.kind)
3333
}
34-
return [(.identifier("Element"), choices)]
34+
return [("Element", choices)]
3535
} else {
3636
return []
3737
}
@@ -238,7 +238,7 @@ let rawSyntaxNodesFile = SourceFileSyntax(leadingTrivia: copyrightHeader) {
238238
}
239239

240240
for (index, child) in node.children.enumerated() {
241-
try VariableDeclSyntax("public var \(child.varOrCaseName.backtickedIfNeeded): Raw\(raw: child.type.buildable)") {
241+
try VariableDeclSyntax("public var \(child.varOrCaseName.backtickedIfNeeded): Raw\(raw: child.buildableType.buildable)") {
242242
let iuoMark = child.isOptional ? "" : "!"
243243

244244
if child.syntaxNodeKind == .syntax {
@@ -257,11 +257,11 @@ fileprivate extension Child {
257257
var rawParameterType: TypeSyntax {
258258
let paramType: TypeSyntax
259259
if case ChildKind.nodeChoices = kind {
260-
paramType = "\(raw: name)"
260+
paramType = syntaxChoicesType
261261
} else {
262262
paramType = syntaxNodeKind.rawType
263263
}
264264

265-
return type.optionalWrapped(type: paramType)
265+
return buildableType.optionalWrapped(type: paramType)
266266
}
267267
}

CodeGeneration/Sources/generate-swiftsyntax/templates/swiftsyntax/RawSyntaxValidationFile.swift

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -204,7 +204,7 @@ let rawSyntaxValidationFile = try! SourceFileSyntax(leadingTrivia: copyrightHead
204204
let verifiedChoices = ArrayExprSyntax {
205205
ArrayElementSyntax(
206206
leadingTrivia: .newline,
207-
expression: ExprSyntax("verify(layout[\(raw: index)], as: Raw\(raw: child.type.buildable).self)")
207+
expression: ExprSyntax("verify(layout[\(raw: index)], as: Raw\(raw: child.buildableType.buildable).self)")
208208
)
209209
}
210210

@@ -220,10 +220,12 @@ let rawSyntaxValidationFile = try! SourceFileSyntax(leadingTrivia: copyrightHead
220220
}
221221
}
222222
}
223-
let verifyCall = ExprSyntax("verify(layout[\(raw: index)], as: Raw\(raw: child.type.buildable).self, tokenChoices: \(choices))")
223+
let verifyCall = ExprSyntax(
224+
"verify(layout[\(raw: index)], as: Raw\(raw: child.buildableType.buildable).self, tokenChoices: \(choices))"
225+
)
224226
ExprSyntax("assertNoError(kind, \(raw: index), \(verifyCall))")
225227
default:
226-
ExprSyntax("assertNoError(kind, \(raw: index), verify(layout[\(raw: index)], as: Raw\(raw: child.type.buildable).self))")
228+
ExprSyntax("assertNoError(kind, \(raw: index), verify(layout[\(raw: index)], as: Raw\(raw: child.buildableType.buildable).self))")
227229
}
228230
}
229231
} else if let node = node.collectionNode {

CodeGeneration/Sources/generate-swiftsyntax/templates/swiftsyntax/RenamedChildrenCompatibilityFile.swift

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -20,8 +20,8 @@ let renamedChildrenCompatibilityFile = try! SourceFileSyntax(leadingTrivia: copy
2020
try ExtensionDeclSyntax("extension \(raw: layoutNode.type.syntaxBaseName)") {
2121
for child in layoutNode.children {
2222
if let deprecatedVarName = child.deprecatedVarName {
23-
let childType: TypeSyntax = child.kind.isNodeChoicesEmpty ? child.syntaxNodeKind.syntaxType : "\(raw: child.name)"
24-
let type = child.isOptional ? TypeSyntax("\(raw: childType)?") : TypeSyntax("\(raw: childType)")
23+
let childType: TypeSyntax = child.kind.isNodeChoicesEmpty ? child.syntaxNodeKind.syntaxType : child.syntaxChoicesType
24+
let type = child.isOptional ? TypeSyntax("\(childType)?") : childType
2525

2626
DeclSyntax(
2727
"""
@@ -56,7 +56,7 @@ let renamedChildrenCompatibilityFile = try! SourceFileSyntax(leadingTrivia: copy
5656
}
5757

5858
let deprecatedNames = layoutNode.children
59-
.filter { !$0.isUnexpectedNodes && $0.deprecatedName != nil }
59+
.filter { !$0.isUnexpectedNodes && $0.hasDeprecatedName }
6060
.map { $0.varOrCaseName.description }
6161
.joined(separator: ", ")
6262

CodeGeneration/Sources/generate-swiftsyntax/templates/swiftsyntax/SyntaxNodesFile.swift

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -103,7 +103,7 @@ func syntaxNode(emitKind: SyntaxNodeKind) -> SourceFileSyntax {
103103
for child in node.children {
104104
ArrayElementSyntax(
105105
expression: MemberAccessExprSyntax(
106-
base: child.type.optionalChained(expr: ExprSyntax("\(child.varOrCaseName.backtickedIfNeeded)")),
106+
base: child.buildableType.optionalChained(expr: ExprSyntax("\(child.varOrCaseName.backtickedIfNeeded)")),
107107
period: .periodToken(),
108108
name: "raw"
109109
)
@@ -159,7 +159,7 @@ func syntaxNode(emitKind: SyntaxNodeKind) -> SourceFileSyntax {
159159
// Children properties
160160
// ===================
161161

162-
let childType: TypeSyntax = child.kind.isNodeChoicesEmpty ? child.syntaxNodeKind.syntaxType : "\(raw: child.name)"
162+
let childType: TypeSyntax = child.kind.isNodeChoicesEmpty ? child.syntaxNodeKind.syntaxType : child.syntaxChoicesType
163163
let type = child.isOptional ? TypeSyntax("\(raw: childType)?") : TypeSyntax("\(raw: childType)")
164164

165165
try! VariableDeclSyntax(
@@ -244,7 +244,7 @@ private func generateSyntaxChildChoices(for child: Child) throws -> EnumDeclSynt
244244
return nil
245245
}
246246

247-
return try! EnumDeclSyntax("public enum \(raw: child.name): SyntaxChildChoices, SyntaxHashable") {
247+
return try! EnumDeclSyntax("public enum \(child.syntaxChoicesType): SyntaxChildChoices, SyntaxHashable") {
248248
for choice in choices {
249249
DeclSyntax("case `\(choice.varOrCaseName)`(\(raw: choice.syntaxNodeKind.syntaxType))")
250250
}

CodeGeneration/Sources/generate-swiftsyntax/templates/swiftsyntaxbuilder/RenamedChildrenBuilderCompatibilityFile.swift

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@ let renamedChildrenBuilderCompatibilityFile = try! SourceFileSyntax(leadingTrivi
2121
for layoutNode in SYNTAX_NODES.compactMap(\.layoutNode).filter({ $0.children.hasDeprecatedChild }) {
2222
if let convenienceInit = try layoutNode.createConvenienceBuilderInitializer(useDeprecatedChildName: true) {
2323
let deprecatedNames = layoutNode.children
24-
.filter { !$0.isUnexpectedNodes && $0.deprecatedName != nil }
24+
.filter { !$0.isUnexpectedNodes && $0.hasDeprecatedName }
2525
.compactMap { $0.varOrCaseName.description }
2626
.joined(separator: ", ")
2727

0 commit comments

Comments
 (0)