Skip to content

Commit 05a90b3

Browse files
authored
Merge pull request #1914 from ahoppen/ahoppen/child-improvements
Improve modeling of `Child` in CodeGeneration
2 parents 680942e + 9adf2df commit 05a90b3

18 files changed

+125
-81
lines changed

CodeGeneration/Sources/SyntaxSupport/Child.swift

Lines changed: 30 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,8 @@
1010
//
1111
//===----------------------------------------------------------------------===//
1212

13+
import SwiftSyntax
14+
1315
/// The kind of token a node can contain. Either a token of a specific kind or a
1416
/// keyword with the given text.
1517
public enum TokenChoice: Equatable {
@@ -56,13 +58,33 @@ public enum ChildKind {
5658
/// A child of a node, that may be declared optional or a token with a
5759
/// restricted subset of acceptable kinds or texts.
5860
public class Child {
61+
/// The name of the child.
62+
///
63+
/// The first character of the name is always uppercase.
5964
public let name: String
65+
66+
/// If the child has been renamed, its old, now deprecated, name.
67+
///
68+
/// This is used to generate deprecated compatibility layers.
6069
public let deprecatedName: String?
70+
71+
/// The kind of the child (node, token, collection, ...)
6172
public let kind: ChildKind
62-
public let nameForDiagnostics: String?
63-
public let documentation: String?
73+
74+
/// Whether this child is optional and can be `nil`.
6475
public let isOptional: Bool
6576

77+
/// A name of this child that can be shown in diagnostics.
78+
///
79+
/// This is used to e.g. describe the child if all of its tokens are missing in the source file.
80+
public let nameForDiagnostics: String?
81+
82+
/// A doc comment describing the child.
83+
public let documentation: SwiftSyntax.Trivia
84+
85+
/// The first line of the child's documentation
86+
public let documentationAbstract: String
87+
6688
public var syntaxNodeKind: SyntaxNodeKind {
6789
switch kind {
6890
case .node(kind: let kind):
@@ -77,16 +99,16 @@ public class Child {
7799
}
78100

79101
/// A name of this child that's suitable to be used for variable or enum case names.
80-
public var varName: String {
81-
return lowercaseFirstWord(name: name)
102+
public var varOrCaseName: TokenSyntax {
103+
return .identifier(lowercaseFirstWord(name: name))
82104
}
83105

84106
/// The deprecated name of this child that's suitable to be used for variable or enum case names.
85-
public var deprecatedVarName: String? {
107+
public var deprecatedVarName: TokenSyntax? {
86108
guard let deprecatedName = deprecatedName else {
87109
return nil
88110
}
89-
return lowercaseFirstWord(name: deprecatedName)
111+
return .identifier(lowercaseFirstWord(name: deprecatedName))
90112
}
91113

92114
/// If the child ends with "token" in the kind, it's considered a token node.
@@ -146,12 +168,6 @@ public class Child {
146168
}
147169
}
148170

149-
/// Returns `true` if this child's type is one of the base syntax kinds and
150-
/// it's optional.
151-
public var hasOptionalBaseType: Bool {
152-
return hasBaseType && isOptional
153-
}
154-
155171
/// If a classification is passed, it specifies the color identifiers in
156172
/// that subtree should inherit for syntax coloring. Must be a member of
157173
/// ``SyntaxClassification``.
@@ -172,7 +188,8 @@ public class Child {
172188
self.deprecatedName = deprecatedName
173189
self.kind = kind
174190
self.nameForDiagnostics = nameForDiagnostics
175-
self.documentation = documentation
191+
self.documentation = docCommentTrivia(from: documentation)
192+
self.documentationAbstract = String(documentation?.split(whereSeparator: \.isNewline).first ?? "")
176193
self.isOptional = isOptional
177194
}
178195
}

CodeGeneration/Sources/SyntaxSupport/CommonNodes.swift

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -181,6 +181,7 @@ public let COMMON_NODES: [Node] = [
181181
kind: .token(choices: [.token(tokenKind: "IdentifierToken")], requiresLeadingSpace: false, requiresTrailingSpace: false),
182182
documentation: """
183183
A placeholder, i.e. `<#decl#>`, that can be inserted into the source code to represent the missing declaration.
184+
184185
This token should always have `presence = .missing`.
185186
"""
186187
),
@@ -201,6 +202,7 @@ public let COMMON_NODES: [Node] = [
201202
kind: .token(choices: [.token(tokenKind: "IdentifierToken")], requiresLeadingSpace: false, requiresTrailingSpace: false),
202203
documentation: """
203204
A placeholder, i.e. `<#expression#>`, that can be inserted into the source code to represent the missing expression.
205+
204206
This token should always have `presence = .missing`.
205207
"""
206208
)
@@ -221,6 +223,7 @@ public let COMMON_NODES: [Node] = [
221223
kind: .token(choices: [.token(tokenKind: "IdentifierToken")], requiresLeadingSpace: false, requiresTrailingSpace: false),
222224
documentation: """
223225
A placeholder, i.e. `<#pattern#>`, that can be inserted into the source code to represent the missing pattern.
226+
224227
This token should always have `presence = .missing`.
225228
"""
226229
)
@@ -241,6 +244,7 @@ public let COMMON_NODES: [Node] = [
241244
kind: .token(choices: [.token(tokenKind: "IdentifierToken")], requiresLeadingSpace: false, requiresTrailingSpace: false),
242245
documentation: """
243246
A placeholder, i.e. `<#statement#>`, that can be inserted into the source code to represent the missing pattern.
247+
244248
This token should always have `presence = .missing`.
245249
"""
246250
)
@@ -261,6 +265,7 @@ public let COMMON_NODES: [Node] = [
261265
kind: .token(choices: [.token(tokenKind: "IdentifierToken")], requiresLeadingSpace: false, requiresTrailingSpace: false),
262266
documentation: """
263267
A placeholder, i.e. `<#syntax#>`, that can be inserted into the source code to represent the missing pattern.
268+
264269
This token should always have `presence = .missing`
265270
"""
266271
)
@@ -280,7 +285,9 @@ public let COMMON_NODES: [Node] = [
280285
name: "Placeholder",
281286
kind: .token(choices: [.token(tokenKind: "IdentifierToken")], requiresLeadingSpace: false, requiresTrailingSpace: false),
282287
documentation: """
283-
A placeholder, i.e. `<#type#>`, that can be inserted into the source code to represent the missing type. This token should always have `presence = .missing`.
288+
A placeholder, i.e. `<#type#>`, that can be inserted into the source code to represent the missing type.
289+
290+
This token should always have `presence = .missing`.
284291
"""
285292
)
286293
]

CodeGeneration/Sources/SyntaxSupport/GrammarGenerator.swift

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -57,7 +57,7 @@ struct GrammarGenerator {
5757
return
5858
children
5959
.filter { !$0.isUnexpectedNodes }
60-
.map { " - `\($0.varName)`: \(generator.grammar(for: $0))" }
60+
.map { " - `\($0.varOrCaseName)`: \(generator.grammar(for: $0))" }
6161
.joined(separator: "\n")
6262
}
6363
}

CodeGeneration/Sources/SyntaxSupport/Utils.swift

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -65,3 +65,15 @@ public extension Collection {
6565
}
6666
}
6767
}
68+
69+
public extension TokenSyntax {
70+
var backtickedIfNeeded: TokenSyntax {
71+
if KEYWORDS.contains(where: {
72+
$0.name == self.description && $0.isLexerClassified
73+
}) {
74+
return "`\(self)`"
75+
} else {
76+
return self
77+
}
78+
}
79+
}

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

Lines changed: 21 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -39,18 +39,18 @@ extension LayoutNode {
3939
}
4040
}
4141

42-
let parameterName: String
42+
let parameterName: TokenSyntax
4343

4444
if useDeprecatedChildName, let deprecatedVarName = child.deprecatedVarName {
4545
parameterName = deprecatedVarName
4646
} else {
47-
parameterName = child.varName
47+
parameterName = child.varOrCaseName
4848
}
4949

5050
return FunctionParameterSyntax(
5151
leadingTrivia: .newline,
52-
firstName: child.isUnexpectedNodes ? .wildcardToken(trailingTrivia: .space) : .identifier(parameterName),
53-
secondName: child.isUnexpectedNodes ? .identifier(parameterName) : nil,
52+
firstName: child.isUnexpectedNodes ? .wildcardToken(trailingTrivia: .space) : parameterName,
53+
secondName: child.isUnexpectedNodes ? parameterName : nil,
5454
colon: .colonToken(),
5555
type: paramType,
5656
defaultArgument: child.defaultInitialization
@@ -77,13 +77,10 @@ extension LayoutNode {
7777

7878
func generateInitializerDocComment() -> SwiftSyntax.Trivia {
7979
func generateParamDocComment(for child: Child) -> String? {
80-
guard let documentation = child.documentation,
81-
let firstLine = documentation.split(whereSeparator: \.isNewline).first
82-
else {
80+
if child.documentationAbstract.isEmpty {
8381
return nil
8482
}
85-
86-
return " - \(child.varName): \(firstLine)"
83+
return " - \(child.varOrCaseName): \(child.documentationAbstract)"
8784
}
8885

8986
let formattedParams = """
@@ -114,12 +111,12 @@ extension LayoutNode {
114111
for child in children {
115112
/// The expression that is used to call the default initializer defined above.
116113
let produceExpr: ExprSyntax
117-
let childName: String
114+
let childName: TokenSyntax
118115

119116
if useDeprecatedChildName, let deprecatedVarName = child.deprecatedVarName {
120117
childName = deprecatedVarName
121118
} else {
122-
childName = child.varName
119+
childName = child.varOrCaseName
123120
}
124121

125122
if child.type.isBuilderInitializable {
@@ -129,12 +126,12 @@ extension LayoutNode {
129126
if child.type.builderInitializableType != child.type {
130127
let param = Node.from(type: child.type).layoutNode!.singleNonDefaultedChild
131128
if child.isOptional {
132-
produceExpr = ExprSyntax("\(raw: childName)Builder().map { \(raw: child.type.syntaxBaseName)(\(raw: param.varName): $0) }")
129+
produceExpr = ExprSyntax("\(childName)Builder().map { \(raw: child.type.syntaxBaseName)(\(param.varOrCaseName): $0) }")
133130
} else {
134-
produceExpr = ExprSyntax("\(raw: child.type.syntaxBaseName)(\(raw: param.varName): \(raw: childName)Builder())")
131+
produceExpr = ExprSyntax("\(raw: child.type.syntaxBaseName)(\(param.varOrCaseName): \(childName)Builder())")
135132
}
136133
} else {
137-
produceExpr = ExprSyntax("\(raw: childName)Builder()")
134+
produceExpr = ExprSyntax("\(childName)Builder()")
138135
}
139136
builderParameters.append(
140137
FunctionParameterSyntax(
@@ -145,14 +142,20 @@ extension LayoutNode {
145142
produceExpr = convertFromSyntaxProtocolToSyntaxType(child: child, useDeprecatedChildName: useDeprecatedChildName)
146143
normalParameters.append(
147144
FunctionParameterSyntax(
148-
firstName: .identifier(childName),
145+
firstName: childName,
149146
colon: .colonToken(),
150147
type: child.parameterType,
151148
defaultArgument: child.defaultInitialization
152149
)
153150
)
154151
}
155-
delegatedInitArgs.append(TupleExprElementSyntax(label: child.isUnexpectedNodes ? nil : child.varName, expression: produceExpr))
152+
delegatedInitArgs.append(
153+
TupleExprElementSyntax(
154+
label: child.isUnexpectedNodes ? nil : child.varOrCaseName,
155+
colon: child.isUnexpectedNodes ? nil : .colonToken(),
156+
expression: produceExpr
157+
)
158+
)
156159
}
157160

158161
guard shouldCreateInitializer else {
@@ -185,11 +188,11 @@ extension LayoutNode {
185188
}
186189

187190
fileprivate func convertFromSyntaxProtocolToSyntaxType(child: Child, useDeprecatedChildName: Bool = false) -> ExprSyntax {
188-
let childName: String
191+
let childName: TokenSyntax
189192
if useDeprecatedChildName, let deprecatedVarName = child.deprecatedVarName {
190193
childName = deprecatedVarName
191194
} else {
192-
childName = child.varName
195+
childName = child.varOrCaseName
193196
}
194197

195198
if child.type.isBaseType && !child.kind.isNodeChoices {

CodeGeneration/Sources/generate-swiftsyntax/templates/swiftparserdiagnostics/ChildNameForDiagnosticsFile.swift

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,7 @@ let childNameForDiagnosticFile = SourceFileSyntax(leadingTrivia: copyrightHeader
2525
for node in NON_BASE_SYNTAX_NODES.compactMap(\.layoutNode) {
2626
for child in node.children {
2727
if let nameForDiagnostics = child.nameForDiagnostics {
28-
SwitchCaseSyntax("case \\\(raw: node.type.syntaxBaseName).\(raw: child.varName):") {
28+
SwitchCaseSyntax("case \\\(raw: node.type.syntaxBaseName).\(child.varOrCaseName):") {
2929
StmtSyntax(#"return "\#(raw: nameForDiagnostics)""#)
3030
}
3131
}

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

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -29,8 +29,8 @@ let childNameForKeyPathFile = SourceFileSyntax(leadingTrivia: copyrightHeader) {
2929
for child in node.children {
3030
SwitchCaseSyntax(
3131
"""
32-
case \\\(raw: node.type.syntaxBaseName).\(raw: child.varName):
33-
return \(literal: child.varName)
32+
case \\\(raw: node.type.syntaxBaseName).\(child.varOrCaseName):
33+
return \(literal: child.varOrCaseName.description)
3434
"""
3535
)
3636
}

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

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,7 @@ fileprivate extension Node {
2222
return node.children.compactMap { child -> (name: TokenSyntax, choices: [(caseName: TokenSyntax, kind: SyntaxNodeKind)])? in
2323
switch child.kind {
2424
case .nodeChoices(let choices):
25-
return (.identifier(child.name), choices.map { (.identifier($0.varName), $0.syntaxNodeKind) })
25+
return (.identifier(child.name), choices.map { ($0.varOrCaseName, $0.syntaxNodeKind) })
2626
default:
2727
return nil
2828
}
@@ -202,8 +202,8 @@ let rawSyntaxNodesFile = SourceFileSyntax(leadingTrivia: copyrightHeader) {
202202
let params = FunctionParameterListSyntax {
203203
for child in node.children {
204204
FunctionParameterSyntax(
205-
firstName: child.isUnexpectedNodes ? .wildcardToken(trailingTrivia: .space) : .identifier(child.varName),
206-
secondName: child.isUnexpectedNodes ? .identifier(child.varName) : nil,
205+
firstName: child.isUnexpectedNodes ? .wildcardToken(trailingTrivia: .space) : child.varOrCaseName,
206+
secondName: child.isUnexpectedNodes ? child.varOrCaseName : nil,
207207
colon: .colonToken(),
208208
type: child.rawParameterType,
209209
defaultArgument: child.isUnexpectedNodes ? child.defaultInitialization : nil
@@ -218,7 +218,7 @@ let rawSyntaxNodesFile = SourceFileSyntax(leadingTrivia: copyrightHeader) {
218218
ExprSyntax("layout.initialize(repeating: nil)")
219219
for (index, child) in node.children.enumerated() {
220220
let optionalMark = child.isOptional ? "?" : ""
221-
ExprSyntax("layout[\(raw: index)] = \(raw: child.varName.backtickedIfNeeded)\(raw: optionalMark).raw")
221+
ExprSyntax("layout[\(raw: index)] = \(child.varOrCaseName.backtickedIfNeeded)\(raw: optionalMark).raw")
222222
.with(\.leadingTrivia, .newline)
223223
}
224224
}
@@ -238,7 +238,7 @@ let rawSyntaxNodesFile = SourceFileSyntax(leadingTrivia: copyrightHeader) {
238238
}
239239

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

244244
if child.syntaxNodeKind == .syntax {

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

Lines changed: 10 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -25,13 +25,13 @@ let renamedChildrenCompatibilityFile = try! SourceFileSyntax(leadingTrivia: copy
2525

2626
DeclSyntax(
2727
"""
28-
@available(*, deprecated, renamed: "\(raw: child.varName)")
28+
@available(*, deprecated, renamed: "\(child.varOrCaseName)")
2929
public var \(raw: deprecatedVarName): \(raw: type) {
3030
get {
31-
return \(raw: child.varName.backtickedIfNeeded)
31+
return \(child.varOrCaseName.backtickedIfNeeded)
3232
}
3333
set {
34-
\(raw: child.varName.backtickedIfNeeded) = newValue
34+
\(child.varOrCaseName.backtickedIfNeeded) = newValue
3535
}
3636
}
3737
"""
@@ -57,7 +57,7 @@ let renamedChildrenCompatibilityFile = try! SourceFileSyntax(leadingTrivia: copy
5757

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

6363
try! InitializerDeclSyntax(
@@ -71,9 +71,13 @@ let renamedChildrenCompatibilityFile = try! SourceFileSyntax(leadingTrivia: copy
7171
TupleExprElementSyntax(label: "leadingTrivia", expression: ExprSyntax("leadingTrivia"))
7272
for child in layoutNode.children {
7373
if child.isUnexpectedNodes {
74-
TupleExprElementSyntax(expression: ExprSyntax("\(raw: child.deprecatedVarName ?? child.varName)"))
74+
TupleExprElementSyntax(expression: ExprSyntax("\(raw: child.deprecatedVarName ?? child.varOrCaseName)"))
7575
} else {
76-
TupleExprElementSyntax(label: "\(child.varName)", expression: ExprSyntax("\(raw: child.deprecatedVarName ?? child.varName)"))
76+
TupleExprElementSyntax(
77+
label: child.varOrCaseName,
78+
colon: .colonToken(),
79+
expression: IdentifierExprSyntax(identifier: child.deprecatedVarName ?? child.varOrCaseName)
80+
)
7781
}
7882
}
7983
TupleExprElementSyntax(label: "trailingTrivia", expression: ExprSyntax("trailingTrivia"))

0 commit comments

Comments
 (0)