Skip to content

Commit a5ecb62

Browse files
authored
Merge pull request #2011 from Matejkob/introduce-typeName-on-Child
Introduce `tokenSpecSetType` and `syntaxChoicesType` for child nodes in CodeGeneration
2 parents 91b4481 + 0381d8f commit a5ecb62

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
@@ -134,18 +134,20 @@ public class Node {
134134
// any two defined children
135135
childrenWithUnexpected =
136136
children.enumerated().flatMap { (i, child) -> [Child] in
137+
let childName = child.name.withFirstCharacterUppercased
138+
137139
let unexpectedName: String
138140
let unexpectedDeprecatedName: String?
139141

140142
if i == 0 {
141-
unexpectedName = "UnexpectedBefore\(child.name)"
142-
unexpectedDeprecatedName = child.deprecatedName.map { "UnexpectedBefore\($0)" }
143+
unexpectedName = "UnexpectedBefore\(childName)"
144+
unexpectedDeprecatedName = child.deprecatedName.map { "UnexpectedBefore\($0.withFirstCharacterUppercased)" }
143145
} else {
144-
unexpectedName = "UnexpectedBetween\(children[i - 1].name)And\(child.name)"
145-
if let deprecatedName = children[i - 1].deprecatedName {
146-
unexpectedDeprecatedName = "UnexpectedBetween\(deprecatedName)And\(child.deprecatedName ?? child.name)"
147-
} else if let deprecatedName = child.deprecatedName {
148-
unexpectedDeprecatedName = "UnexpectedBetween\(children[i - 1].name)And\(deprecatedName)"
146+
unexpectedName = "UnexpectedBetween\(children[i - 1].name.withFirstCharacterUppercased)And\(childName)"
147+
if let deprecatedName = children[i - 1].deprecatedName?.withFirstCharacterUppercased {
148+
unexpectedDeprecatedName = "UnexpectedBetween\(deprecatedName)And\(child.deprecatedName?.withFirstCharacterUppercased ?? childName)"
149+
} else if let deprecatedName = child.deprecatedName?.withFirstCharacterUppercased {
150+
unexpectedDeprecatedName = "UnexpectedBetween\(children[i - 1].name.withFirstCharacterUppercased)And\(deprecatedName)"
149151
} else {
150152
unexpectedDeprecatedName = nil
151153
}
@@ -159,9 +161,9 @@ public class Node {
159161
return [unexpectedBefore, child]
160162
} + [
161163
Child(
162-
name: "UnexpectedAfter\(children.last!.name)",
163-
deprecatedName: children.last!.deprecatedName.map { "UnexpectedAfter\($0)" },
164-
kind: .collection(kind: .unexpectedNodes, collectionElementName: "UnexpectedAfter\(children.last!.name)"),
164+
name: "UnexpectedAfter\(children.last!.name.withFirstCharacterUppercased)",
165+
deprecatedName: children.last!.deprecatedName.map { "UnexpectedAfter\($0.withFirstCharacterUppercased)" },
166+
kind: .collection(kind: .unexpectedNodes, collectionElementName: "UnexpectedAfter\(children.last!.name.withFirstCharacterUppercased)"),
165167
isOptional: true
166168
)
167169
]

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,23 +44,23 @@ 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
}
@@ -69,7 +69,7 @@ public extension Child {
6969
return ExprSyntax("[]")
7070
}
7171
guard let token = token, isToken else {
72-
return type.defaultValue
72+
return buildableType.defaultValue
7373
}
7474
if token.text != nil {
7575
return ExprSyntax(".\(token.varOrCaseName)Token()")
@@ -131,7 +131,7 @@ public extension Child {
131131
}
132132

133133
var preconditionChoices: [ExprSyntax] = []
134-
if type.isOptional {
134+
if buildableType.isOptional {
135135
preconditionChoices.append(
136136
ExprSyntax(
137137
SequenceExprSyntax {
@@ -146,7 +146,7 @@ public extension Child {
146146
preconditionChoices.append(
147147
ExprSyntax(
148148
SequenceExprSyntax {
149-
MemberAccessExprSyntax(base: type.forceUnwrappedIfNeeded(expr: DeclReferenceExprSyntax(baseName: .identifier(varName))), name: "text")
149+
MemberAccessExprSyntax(base: buildableType.forceUnwrappedIfNeeded(expr: DeclReferenceExprSyntax(baseName: .identifier(varName))), name: "text")
150150
BinaryOperatorExprSyntax(text: "==")
151151
StringLiteralExprSyntax(content: textChoice)
152152
}

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
"""
@@ -57,7 +57,7 @@ let renamedChildrenCompatibilityFile = try! SourceFileSyntax(leadingTrivia: copy
5757
}
5858

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

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

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

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

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

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

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)