Skip to content

Commit cd53600

Browse files
committed
Introduce DeclGroupHeaderSyntax
This is the base node for the headers we are about to extract. Unlike other base nodes, DeclGroupHeaderSyntax has children, which are treated as protocol requirements for derived nodes.
1 parent d20743c commit cd53600

32 files changed

+1300
-22
lines changed

CodeGeneration/Sources/SyntaxSupport/CommonNodes.swift

Lines changed: 85 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -180,13 +180,98 @@ public let COMMON_NODES: [Node] = [
180180
parserFunction: "parseDeclaration"
181181
),
182182

183+
Node(
184+
kind: .declGroupHeader,
185+
base: .syntax,
186+
nameForDiagnostics: "declaration group header",
187+
parserFunction: "parseDeclarationGroupHeader",
188+
traits: [
189+
"WithAttributes",
190+
"WithModifiers",
191+
],
192+
children: [
193+
Child(
194+
name: "attributes",
195+
kind: .collection(kind: .attributeList, collectionElementName: "Attribute", defaultsToEmpty: true),
196+
nameForDiagnostics: "attributes"
197+
),
198+
Child(
199+
name: "modifiers",
200+
kind: .collection(kind: .declModifierList, collectionElementName: "Modifier", defaultsToEmpty: true),
201+
nameForDiagnostics: "modifiers",
202+
documentation: "Modifiers like `public` that are attached to the actor declaration."
203+
),
204+
Child(
205+
name: "introducer",
206+
kind: .token(choices: [
207+
.keyword(.actor), .keyword(.class), .keyword(.enum), .keyword(.extension), .keyword(.protocol),
208+
.keyword(.struct),
209+
]),
210+
documentation: "The token that introduces this declaration, eg. `class` for a class declaration."
211+
),
212+
Child(name: "inheritanceClause", kind: .node(kind: .inheritanceClause), isOptional: true),
213+
Child(
214+
name: "genericWhereClause",
215+
kind: .node(kind: .genericWhereClause),
216+
documentation:
217+
"A `where` clause that places additional constraints on generic parameters like `where Element: Hashable`.",
218+
isOptional: true
219+
),
220+
]
221+
),
222+
183223
Node(
184224
kind: .expr,
185225
base: .syntax,
186226
nameForDiagnostics: "expression",
187227
parserFunction: "parseExpression"
188228
),
189229

230+
Node(
231+
kind: .missingDeclHeader,
232+
base: .declGroupHeader,
233+
nameForDiagnostics: "declaration group header",
234+
documentation:
235+
"In case the source code is missing a declaration group header, this node stands in place of the missing header.",
236+
traits: [
237+
"MissingNode",
238+
"WithAttributes",
239+
"WithModifiers",
240+
],
241+
children: [
242+
Child(
243+
name: "attributes",
244+
kind: .collection(kind: .attributeList, collectionElementName: "Attribute", defaultsToEmpty: true),
245+
documentation:
246+
"If there were standalone attributes without a declaration to attach them to, the ``MissingDeclSyntax`` will contain these."
247+
),
248+
Child(
249+
name: "modifiers",
250+
kind: .collection(kind: .declModifierList, collectionElementName: "Modifier", defaultsToEmpty: true),
251+
documentation:
252+
"If there were standalone modifiers without a declaration to attach them to, the ``MissingDeclSyntax`` will contain these."
253+
),
254+
Child(
255+
name: "placeholder",
256+
kind: .token(choices: [.token(.identifier)], requiresLeadingSpace: false, requiresTrailingSpace: false),
257+
documentation: """
258+
A placeholder, i.e. `<#decl#>`, that can be inserted into the source code to represent the missing declaration.
259+
260+
This token should always have `presence = .missing`.
261+
"""
262+
),
263+
Child(name: "inheritanceClause", kind: .node(kind: .inheritanceClause), isOptional: true),
264+
Child(
265+
name: "genericWhereClause",
266+
kind: .node(kind: .genericWhereClause),
267+
documentation:
268+
"A `where` clause that places additional constraints on generic parameters like `where Element: Hashable`.",
269+
isOptional: true
270+
),
271+
272+
]
273+
),
274+
190275
Node(
191276
kind: .missingDecl,
192277
base: .decl,

CodeGeneration/Sources/SyntaxSupport/CompatibilityLayers.swift

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -68,7 +68,7 @@ public struct CompatibilityLayers /*: Sendable*/ {
6868
typeName: layoutNode.kind.rawValue,
6969
initialChildren: layoutNode.children,
7070
history: layoutNode.childHistory,
71-
areRequirements: false
71+
areRequirements: layoutNode.kind.isBase
7272
)
7373

7474
deprecatedVarsByNode[node.syntaxNodeKind] = result.vars

CodeGeneration/Sources/SyntaxSupport/Node.swift

Lines changed: 11 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -69,11 +69,7 @@ public class Node: NodeChoiceConvertible {
6969
public var layoutNode: LayoutNode? {
7070
switch data {
7171
case .layout:
72-
if kind.isBase {
73-
return nil
74-
} else {
75-
return LayoutNode(node: self)
76-
}
72+
return LayoutNode(node: self)
7773
default:
7874
return nil
7975
}
@@ -136,11 +132,7 @@ public class Node: NodeChoiceConvertible {
136132
self.documentation = SwiftSyntax.Trivia.docCommentTrivia(from: documentation)
137133
self.parserFunction = parserFunction
138134

139-
let liftedChildren = children.lazy.map(Optional.some)
140-
let pairedChildren = zip([nil] + liftedChildren, liftedChildren + [nil])
141-
let childrenWithUnexpected = pairedChildren.flatMap { earlier, later in
142-
[earlier, Child(forUnexpectedBetween: earlier, and: later)].compactMap { $0 }
143-
}
135+
let childrenWithUnexpected = kind.isBase ? children : interleaveUnexpectedChildren(children)
144136

145137
self.data = .layout(children: childrenWithUnexpected, childHistory: childHistory, traits: traits)
146138
}
@@ -389,3 +381,12 @@ fileprivate extension Child {
389381
}
390382
}
391383
}
384+
385+
fileprivate func interleaveUnexpectedChildren(_ children: [Child]) -> [Child] {
386+
let liftedChildren = children.lazy.map(Optional.some)
387+
let pairedChildren = zip([nil] + liftedChildren, liftedChildren + [nil])
388+
389+
return pairedChildren.flatMap { earlier, later in
390+
[earlier, Child(forUnexpectedBetween: earlier, and: later)].compactMap { $0 }
391+
}
392+
}

CodeGeneration/Sources/SyntaxSupport/SyntaxNodeKind.swift

Lines changed: 13 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -81,6 +81,7 @@ public enum SyntaxNodeKind: String, CaseIterable, IdentifierConvertible, TypeCon
8181
case conventionWitnessMethodAttributeArguments
8282
case copyExpr
8383
case decl
84+
case declGroupHeader
8485
case declModifier
8586
case declModifierDetail
8687
case declModifierList
@@ -197,6 +198,7 @@ public enum SyntaxNodeKind: String, CaseIterable, IdentifierConvertible, TypeCon
197198
case metatypeType
198199
case missing
199200
case missingDecl
201+
case missingDeclHeader
200202
case missingExpr
201203
case missingPattern
202204
case missingStmt
@@ -320,7 +322,7 @@ public enum SyntaxNodeKind: String, CaseIterable, IdentifierConvertible, TypeCon
320322
/// `true` if this is one of the `missing*` cases.
321323
public var isMissing: Bool {
322324
switch self {
323-
case .missingDecl, .missingExpr, .missingPattern, .missingStmt, .missing, .missingType:
325+
case .missingDecl, .missingDeclHeader, .missingExpr, .missingPattern, .missingStmt, .missing, .missingType:
324326
return true
325327
default:
326328
return false
@@ -329,7 +331,7 @@ public enum SyntaxNodeKind: String, CaseIterable, IdentifierConvertible, TypeCon
329331

330332
public var isBase: Bool {
331333
switch self {
332-
case .decl, .expr, .pattern, .stmt, .syntax, .syntaxCollection, .type:
334+
case .decl, .declGroupHeader, .expr, .pattern, .stmt, .syntax, .syntaxCollection, .type:
333335
return true
334336
default:
335337
return false
@@ -351,6 +353,15 @@ public enum SyntaxNodeKind: String, CaseIterable, IdentifierConvertible, TypeCon
351353
}
352354
}
353355

356+
public var baseTypeSuffix: String? {
357+
switch self {
358+
case .declGroupHeader:
359+
return "DeclHeaderSyntax"
360+
default:
361+
return isBase ? syntaxType.description : nil
362+
}
363+
}
364+
354365
public var isAvailableInDocc: Bool {
355366
if let node = SYNTAX_NODE_MAP[self], node.isExperimental {
356367
return false

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

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -142,7 +142,7 @@ func rawSyntaxNodesFile(nodesStartingWith: [Character]) -> SourceFileSyntax {
142142
)
143143
}
144144

145-
if let node = node.layoutNode {
145+
if !node.kind.isBase, let node = node.layoutNode {
146146
let params = FunctionParameterListSyntax {
147147
for child in node.children {
148148
FunctionParameterSyntax(

CodeGeneration/Sources/generate-swift-syntax/templates/swiftsyntax/SyntaxBaseNodesFile.swift

Lines changed: 55 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@ let syntaxBaseNodesFile = SourceFileSyntax(leadingTrivia: copyrightHeader) {
2121
node.documentation,
2222
node.subtypes,
2323
])
24-
DeclSyntax(
24+
try! ProtocolDeclSyntax(
2525
"""
2626
// MARK: - \(node.kind.syntaxType)
2727
@@ -31,9 +31,26 @@ let syntaxBaseNodesFile = SourceFileSyntax(leadingTrivia: copyrightHeader) {
3131
///
3232
/// - Warning: Do not conform to this protocol yourself.
3333
\(node.apiAttributes())\
34-
public protocol \(node.kind.protocolType): \(node.base.protocolType) {}
34+
public protocol \(node.kind.protocolType): \(node.base.protocolType)
3535
"""
36-
)
36+
) {
37+
for child in node.layoutNode?.children ?? [] {
38+
// ==================================
39+
// Children properties (requirements)
40+
// ==================================
41+
42+
let childType: TypeSyntax =
43+
child.kind.isNodeChoicesEmpty ? child.syntaxNodeKind.syntaxType : child.syntaxChoicesType
44+
let type = child.isOptional ? TypeSyntax("\(childType)?") : TypeSyntax("\(childType)")
45+
46+
DeclSyntax(
47+
"""
48+
\(child.documentation)\
49+
\(child.apiAttributes) var \(child.varDeclName): \(type) { get set }
50+
"""
51+
)
52+
}
53+
}
3754

3855
DeclSyntax(
3956
#"""
@@ -285,6 +302,41 @@ let syntaxBaseNodesFile = SourceFileSyntax(leadingTrivia: copyrightHeader) {
285302

286303
StmtSyntax("return .choices(\(choices))")
287304
}
305+
306+
for child in node.layoutNode?.children ?? [] {
307+
// =================================================
308+
// Children properties (type-erased implementations)
309+
// =================================================
310+
311+
let childType: TypeSyntax =
312+
child.kind.isNodeChoicesEmpty ? child.syntaxNodeKind.syntaxType : child.syntaxChoicesType
313+
let type = child.isOptional ? TypeSyntax("\(childType)?") : TypeSyntax("\(childType)")
314+
315+
try! VariableDeclSyntax(
316+
"""
317+
\(child.documentation)\
318+
\(child.apiAttributes)public var \(child.varDeclName): \(type)
319+
"""
320+
) {
321+
AccessorDeclSyntax(
322+
"""
323+
get {
324+
return self.asProtocol(\(node.kind.protocolType).self).\(child.baseCallName)
325+
}
326+
"""
327+
)
328+
329+
AccessorDeclSyntax(
330+
"""
331+
set(value) {
332+
var existentialCopy = self.asProtocol(\(node.kind.protocolType).self)
333+
existentialCopy.\(child.baseCallName) = value
334+
self = \(node.kind.syntaxType)(fromProtocol: existentialCopy)
335+
}
336+
"""
337+
)
338+
}
339+
}
288340
}
289341

290342
leafProtocolDecl(type: node.kind.leafProtocolType, inheritedType: node.kind.protocolType)

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

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@ import Utils
2121
/// It then only emits those syntax nodes whose base kind are that specified kind.
2222
func syntaxNode(nodesStartingWith: [Character]) -> SourceFileSyntax {
2323
SourceFileSyntax(leadingTrivia: copyrightHeader) {
24-
for node in SYNTAX_NODES.compactMap(\.layoutNode)
24+
for node in NON_BASE_SYNTAX_NODES.compactMap(\.layoutNode)
2525
where nodesStartingWith.contains(node.kind.syntaxType.description.droppingLeadingUnderscores.first!) {
2626
// We are actually handling this node now
2727
try! StructDeclSyntax(

CodeGeneration/Sources/generate-swift-syntax/templates/swiftsyntaxbuilder/BuildableNodesFile.swift

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,7 @@ let buildableNodesFile = SourceFileSyntax(leadingTrivia: copyrightHeader) {
2626
"""
2727
)
2828

29-
for node in SYNTAX_NODES.compactMap(\.layoutNode) {
29+
for node in NON_BASE_SYNTAX_NODES.compactMap(\.layoutNode) {
3030
let type = node.type
3131

3232
if let convenienceInit = try! InitSignature(node).createConvenienceBuilderInitializer() {

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

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,7 @@ let renamedChildrenBuilderCompatibilityFile = try! SourceFileSyntax(leadingTrivi
2626
"""
2727
)
2828

29-
for layoutNode in SYNTAX_NODES.compactMap(\.layoutNode).filter({ !$0.childHistory.isEmpty }) {
29+
for layoutNode in NON_BASE_SYNTAX_NODES.compactMap(\.layoutNode).filter({ !$0.childHistory.isEmpty }) {
3030
let initSignatures = SYNTAX_COMPATIBILITY_LAYERS.deprecatedInitSignaturesByNode[layoutNode.syntaxNodeKind] ?? []
3131

3232
for signature in initSignatures {

CodeGeneration/Tests/ValidateSyntaxNodes/ValidateSyntaxNodes.swift

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -145,12 +145,12 @@ class ValidateSyntaxNodes: XCTestCase {
145145
func testBaseKindSuffix() {
146146
var failures: [ValidationFailure] = []
147147
for node in SYNTAX_NODES where node.base != .syntaxCollection {
148-
if !node.kind.syntaxType.description.hasSuffix(node.base.syntaxType.description) {
148+
if !node.kind.syntaxType.description.hasSuffix(node.base.baseTypeSuffix!) {
149149
failures.append(
150150
ValidationFailure(
151151
node: node.kind,
152152
message:
153-
"has base kind '\(node.base.syntaxType)' but type name doesn’t have '\(node.base.syntaxType)' suffix"
153+
"has base kind '\(node.base.syntaxType)' but type name doesn’t have '\(node.base.baseTypeSuffix!)' suffix"
154154
)
155155
)
156156
}
@@ -461,6 +461,11 @@ class ValidateSyntaxNodes: XCTestCase {
461461
node: .declModifier,
462462
message: "child 'name' only has keywords as its token choices and should thus end with 'Specifier'"
463463
),
464+
// An extension member for '*DeclSyntax.introducer' already existed; we're just formalizing it
465+
ValidationFailure(
466+
node: .declGroupHeader,
467+
message: "child 'introducer' only has keywords as its token choices and should thus end with 'Specifier'"
468+
),
464469
]
465470
)
466471
}

0 commit comments

Comments
 (0)