Skip to content

Commit d3471fd

Browse files
ahoppenrintaro
andcommitted
If a node’s child has node_choices or a collection has element_choices use an enum to ensure there are only matching child nodes
Previously, we relied on runtime checks to make sure only supported node types were used for a child with `node_choices`. If we explicitly model the set of supported nodes as an enum, we get compile-time safety. rdar://101200491 Co-authored-by: Rintaro Ishizaki <[email protected]>
1 parent e621e8a commit d3471fd

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

42 files changed

+2441
-1443
lines changed

CodeGeneration/Sources/Utils/SyntaxBuildableChild.swift

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,18 @@ public extension Child {
2626
)
2727
}
2828

29+
var parameterBaseType: String {
30+
if !self.nodeChoices.isEmpty {
31+
return self.name
32+
} else {
33+
return type.parameterBaseType
34+
}
35+
}
36+
37+
var parameterType: Type {
38+
return self.type.optionalWrapped(type: Type(parameterBaseType))
39+
}
40+
2941
/// If the child node has documentation associated with it, return it as single
3042
/// line string. Otherwise return an empty string.
3143
var documentation: String {

CodeGeneration/Sources/generate-swiftsyntaxbuilder/BuildableCollectionNodesFile.swift

Lines changed: 17 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -35,25 +35,31 @@ let buildableCollectionNodesFile = SourceFile {
3535
inheritanceClause: TypeInheritanceClause { InheritedType(typeName: Type("ExpressibleByArrayLiteral")) }
3636
) {
3737
// Generate initializers
38-
if elementType.isBaseType {
38+
if elementType.isBaseType && node.collectionElementChoices?.isEmpty ?? true {
3939
InitializerDecl(
4040
"""
41-
/// Creates a `\(node.type.shorthandName)` with the provided list of elements.
42-
/// - Parameters:
43-
/// - elements: A list of `\(elementType.parameterType)`
4441
public init(_ elements: \(ArrayType(elementType: elementType.parameterType))) {
4542
self = \(node.type.syntaxBaseName)(elements.map { \(elementType.syntax)(fromProtocol: $0) })
4643
}
4744
"""
4845
)
46+
47+
InitializerDecl(
48+
"""
49+
public init(arrayLiteral elements: \(elementType.parameterType)...) {
50+
self.init(elements)
51+
}
52+
"""
53+
)
54+
} else {
55+
InitializerDecl(
56+
"""
57+
public init(arrayLiteral elements: Element...) {
58+
self.init(elements)
59+
}
60+
"""
61+
)
4962
}
50-
InitializerDecl(
51-
"""
52-
public init(arrayLiteral elements: \(elementType.parameterType)...) {
53-
self.init(elements)
54-
}
55-
"""
56-
)
5763
}
5864
}
5965
}

CodeGeneration/Sources/generate-swiftsyntaxbuilder/BuildableNodesFile.swift

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -63,7 +63,7 @@ let buildableNodesFile = SourceFile {
6363
}
6464

6565
private func convertFromSyntaxProtocolToSyntaxType(child: Child) -> Expr {
66-
if child.type.isBaseType {
66+
if child.type.isBaseType && child.nodeChoices.isEmpty {
6767
return Expr(FunctionCallExpr("\(child.type.syntaxBaseName)(fromProtocol: \(child.swiftName))"))
6868
} else {
6969
return Expr(IdentifierExpr(child.swiftName))
@@ -100,7 +100,7 @@ private func createDefaultInitializer(node: Node) -> InitializerDecl {
100100
FunctionParameter(
101101
firstName: .identifier(child.swiftName),
102102
colon: .colon,
103-
type: child.type.parameterType,
103+
type: child.parameterType,
104104
defaultArgument: child.type.defaultInitialization.map { InitializerClause(value: $0) }
105105
)
106106
}
@@ -192,7 +192,7 @@ private func createConvenienceInitializer(node: Node) -> InitializerDecl? {
192192
normalParameters.append(FunctionParameter(
193193
firstName: .identifier(child.swiftName),
194194
colon: .colon,
195-
type: child.type.parameterType,
195+
type: child.parameterType,
196196
defaultArgument: child.type.defaultInitialization.map { InitializerClause(value: $0) }
197197
))
198198
}

CodeGeneration/Sources/generate-swiftsyntaxbuilder/ResultBuildersFile.swift

Lines changed: 24 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -24,25 +24,26 @@ let resultBuildersFile = SourceFile {
2424
for node in SYNTAX_NODES where node.isSyntaxCollection {
2525
let type = SyntaxBuildableType(syntaxKind: node.syntaxKind)
2626
let elementType = node.collectionElementType
27+
let expressionType: Type = (node.collectionElementChoices?.isEmpty ?? true) ? elementType.parameterType : Type(MemberTypeIdentifier("\(type.buildable).Element"))
2728

2829
StructDecl(
29-
attributes: [CustomAttribute(leadingTrivia: .newline, trailingTrivia: .newline, attributeName: Type("resultBuilder"))],
30+
attributes: [CustomAttribute(trailingTrivia: .newline, attributeName: Type("resultBuilder"))],
3031
modifiers: [DeclModifier(name: .public)],
3132
identifier: "\(type.syntaxKind)Builder") {
3233

3334
TypealiasDecl(
3435
"""
3536
/// The type of individual statement expressions in the transformed function,
3637
/// which defaults to Component if buildExpression() is not provided.
37-
public typealias Expression = \(elementType.parameterType)
38+
public typealias Expression = \(expressionType)
3839
"""
3940
)
4041

4142
TypealiasDecl(
4243
"""
4344
/// The type of a partial result, which will be carried through all of the
4445
/// build methods.
45-
public typealias Component = [\(elementType.parameterType)]
46+
public typealias Component = [Expression]
4647
"""
4748
)
4849

@@ -58,7 +59,7 @@ let resultBuildersFile = SourceFile {
5859
"""
5960
/// Required by every result builder to build combined results from
6061
/// statement blocks.
61-
public static func buildBlock(_ components: Component...) -> Component {
62+
public static func buildBlock(_ components: Self.Component...) -> Self.Component {
6263
return components.flatMap { $0 }
6364
}
6465
"""
@@ -68,16 +69,28 @@ let resultBuildersFile = SourceFile {
6869
"""
6970
/// If declared, provides contextual type information for statement
7071
/// expressions to translate them into partial results.
71-
public static func buildExpression(_ expression: Expression) -> Component {
72+
public static func buildExpression(_ expression: Self.Expression) -> Self.Component {
7273
return [expression]
7374
}
7475
"""
7576
)
7677

78+
for elementChoice in node.collectionElementChoices ?? [] {
79+
FunctionDecl(
80+
"""
81+
/// If declared, provides contextual type information for statement
82+
/// expressions to translate them into partial results.
83+
public static func buildExpression(_ expression: \(elementChoice)) -> Self.Component {
84+
return buildExpression(.init(expression))
85+
}
86+
"""
87+
)
88+
}
89+
7790
FunctionDecl(
7891
"""
7992
/// Add all the elements of `expression` to this result builder, effectively flattening them.
80-
public static func buildExpression(_ expression: FinalResult) -> Component {
93+
public static func buildExpression(_ expression: Self.FinalResult) -> Self.Component {
8194
return expression.map { $0 }
8295
}
8396
"""
@@ -86,7 +99,7 @@ let resultBuildersFile = SourceFile {
8699
FunctionDecl(
87100
"""
88101
/// Enables support for `if` statements that do not have an `else`.
89-
public static func buildOptional(_ component: Component?) -> Component {
102+
public static func buildOptional(_ component: Self.Component?) -> Self.Component {
90103
return component ?? []
91104
}
92105
"""
@@ -96,7 +109,7 @@ let resultBuildersFile = SourceFile {
96109
"""
97110
/// With buildEither(second:), enables support for 'if-else' and 'switch'
98111
/// statements by folding conditional results into a single result.
99-
public static func buildEither(first component: Component) -> Component {
112+
public static func buildEither(first component: Self.Component) -> Self.Component {
100113
return component
101114
}
102115
"""
@@ -106,7 +119,7 @@ let resultBuildersFile = SourceFile {
106119
"""
107120
/// With buildEither(first:), enables support for 'if-else' and 'switch'
108121
/// statements by folding conditional results into a single result.
109-
public static func buildEither(second component: Component) -> Component {
122+
public static func buildEither(second component: Self.Component) -> Self.Component {
110123
return component
111124
}
112125
"""
@@ -116,7 +129,7 @@ let resultBuildersFile = SourceFile {
116129
"""
117130
/// Enables support for 'for..in' loops by combining the
118131
/// results of all iterations into a single result.
119-
public static func buildArray(_ components: [Component]) -> Component {
132+
public static func buildArray(_ components: [Self.Component]) -> Self.Component {
120133
return components.flatMap { $0 }
121134
}
122135
"""
@@ -127,7 +140,7 @@ let resultBuildersFile = SourceFile {
127140
/// If declared, this will be called on the partial result of an 'if'
128141
/// #available' block to allow the result builder to erase type
129142
/// information.
130-
public static func buildLimitedAvailability(_ component: Component) -> Component {
143+
public static func buildLimitedAvailability(_ component: Self.Component) -> Self.Component {
131144
return component
132145
}
133146
"""

Examples/MigrateToNewIfLetSyntax.swift

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -35,7 +35,7 @@ class MigrateToNewIfLetSyntax: SyntaxRewriter {
3535
if index != node.conditions.count - 1 {
3636
binding.pattern = binding.pattern.withoutTrailingTrivia()
3737
}
38-
conditionCopy.condition = Syntax(binding)
38+
conditionCopy.condition = .optionalBinding(binding)
3939
}
4040
return conditionCopy
4141
}

Sources/SwiftOperators/OperatorTable+Semantics.swift

Lines changed: 1 addition & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,7 @@ extension PrecedenceGroup {
2020
self.syntax = syntax
2121

2222
for attr in syntax.groupAttributes {
23-
switch attr.as(SyntaxEnum.self) {
23+
switch attr {
2424
// Relation (lowerThan, higherThan)
2525
case .precedenceGroupRelation(let relation):
2626
let isLowerThan = relation.higherThanOrLowerThan.text == "lowerThan"
@@ -52,9 +52,6 @@ extension PrecedenceGroup {
5252
default:
5353
break
5454
}
55-
56-
default:
57-
break
5855
}
5956
}
6057
}

Sources/SwiftOperators/SyntaxSynthesis.swift

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -74,12 +74,12 @@ extension PrecedenceGroup {
7474
TokenSyntax.identifier(name, leadingTrivia: .space)
7575
let leftBrace = TokenSyntax.leftBraceToken(leadingTrivia: .space)
7676

77-
var groupAttributes: [Syntax] = []
77+
var groupAttributes: [PrecedenceGroupAttributeListSyntax.Element] = []
7878

7979
switch associativity {
8080
case .left, .right:
8181
groupAttributes.append(
82-
Syntax(
82+
.init(
8383
PrecedenceGroupAssociativitySyntax(
8484
associativityKeyword:
8585
.identifier(
@@ -99,7 +99,7 @@ extension PrecedenceGroup {
9999

100100
if assignment {
101101
groupAttributes.append(
102-
Syntax(
102+
.init(
103103
PrecedenceGroupAssignmentSyntax(
104104
assignmentKeyword:
105105
.identifier(
@@ -115,7 +115,7 @@ extension PrecedenceGroup {
115115

116116
for relation in relations {
117117
groupAttributes.append(
118-
Syntax(
118+
.init(
119119
relation.synthesizedSyntax()
120120
)
121121
)

Sources/SwiftParserDiagnostics/ParseDiagnosticsGenerator.swift

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -589,7 +589,7 @@ public class ParseDiagnosticsGenerator: SyntaxAnyVisitor {
589589
}
590590
if node.unknownAttr?.isMissingAllTokens != false && node.label.isMissingAllTokens {
591591
addDiagnostic(node.statements, .allStatmentsInSwitchMustBeCoveredByCase, fixIts: [
592-
FixIt(message: InsertTokenFixIt(missingNodes: [node.label]), changes: .makePresent(node.label, leadingTrivia: .newline))
592+
FixIt(message: InsertTokenFixIt(missingNodes: [Syntax(node.label)]), changes: .makePresent(node.label, leadingTrivia: .newline))
593593
], handledNodes: [node.label.id])
594594
}
595595
return .visitChildren

Sources/SwiftSyntax/Syntax.swift

Lines changed: 12 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -59,13 +59,13 @@ public struct Syntax: SyntaxProtocol, SyntaxHashable {
5959
}
6060

6161
// Casting functions to specialized syntax nodes.
62-
extension Syntax {
62+
extension SyntaxProtocol {
6363
public func `is`<S: SyntaxProtocol>(_ syntaxType: S.Type) -> Bool {
6464
return self.as(syntaxType) != nil
6565
}
6666

6767
public func `as`<S: SyntaxProtocol>(_ syntaxType: S.Type) -> S? {
68-
return S.init(self)
68+
return S.init(self._syntaxNode)
6969
}
7070

7171
func cast<S: SyntaxProtocol>(_ syntaxType: S.Type) -> S {
@@ -625,6 +625,16 @@ public extension SyntaxProtocol {
625625
}
626626
}
627627

628+
/// Protocol for the enums nested inside `Syntax` nodes that enumerate all the
629+
/// possible types a child node might have.
630+
public protocol SyntaxChildChoices: SyntaxProtocol {}
631+
632+
public extension SyntaxChildChoices {
633+
func childNameForDiagnostics(_ index: SyntaxChildrenIndex) -> String? {
634+
return Syntax(self).childNameForDiagnostics(index)
635+
}
636+
}
637+
628638
/// Sequence of tokens that are part of the provided Syntax node.
629639
public struct TokenSequence: Sequence {
630640
public struct Iterator: IteratorProtocol {

0 commit comments

Comments
 (0)