Skip to content

Commit 71ab8cc

Browse files
authored
Merge pull request #575 from fwcd/improve-assert-stmt-generation
Improve assert statement generation and fix token choices
2 parents 36c3ae0 + ebfa2af commit 71ab8cc

File tree

13 files changed

+1344
-326
lines changed

13 files changed

+1344
-326
lines changed

Sources/SwiftSyntaxBuilderGeneration/Child.swift

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@ class Child {
2626
let classification: SyntaxClassification?
2727
/// A restricted set of token kinds that will be accepted for this child.
2828
let tokenChoices: [Token]
29+
let tokenCanContainArbitraryText: Bool
2930

3031
var swiftName: String {
3132
return lowercaseFirstWord(name: name)
@@ -95,14 +96,18 @@ class Child {
9596
self.requiresLeadingNewline = requiresLeadingNewline
9697
self.isOptional = isOptional
9798

99+
let isToken = syntaxKind.hasSuffix("Token")
98100
var mappedTokenChoices = [Token]()
99101

100-
if syntaxKind.hasSuffix("Token"), let token = SYNTAX_TOKEN_MAP[syntaxKind] {
102+
if isToken, let token = SYNTAX_TOKEN_MAP[syntaxKind] {
101103
mappedTokenChoices.append(token)
102104
}
103105

104-
mappedTokenChoices.append(contentsOf: tokenChoices.compactMap { SYNTAX_TOKEN_MAP[$0] })
106+
mappedTokenChoices.append(contentsOf: tokenChoices.compactMap { SYNTAX_TOKEN_MAP["\($0)Token"] })
105107
self.tokenChoices = mappedTokenChoices
108+
109+
// If mappedTokenChoices contains `nil`, the token can contain arbitrary text
110+
self.tokenCanContainArbitraryText = mappedTokenChoices.contains { $0.text == nil }
106111

107112
// A list of valid text for tokens, if specified.
108113
// This will force validation logic to check the text passed into the

Sources/SwiftSyntaxBuilderGeneration/SyntaxBuildableChild.swift

Lines changed: 20 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -71,34 +71,41 @@ extension Child {
7171
/// If this node is a token that can't contain arbitrary text, generate a Swift
7272
/// `assert` statement that verifies the variable with name var_name and of type
7373
/// `TokenSyntax` contains one of the supported text options. Otherwise return `nil`.
74-
func generateAssertStmtTextChoices(varName: String) -> String? {
74+
func generateAssertStmtTextChoices(varName: String) -> ExpressibleAsExprBuildable? {
7575
guard type.isToken else {
7676
return nil
7777
}
7878

7979
let choices: [String]
8080
if !textChoices.isEmpty {
8181
choices = textChoices
82+
} else if tokenCanContainArbitraryText {
83+
// Don't generate an assert statement if token can contain arbitrary text.
84+
return nil
8285
} else if !tokenChoices.isEmpty {
83-
let optionalChoices = tokenChoices.map { SYNTAX_TOKEN_MAP["\($0.name)Token"]?.text }
84-
choices = optionalChoices.compactMap { $0 }
85-
guard choices.count == optionalChoices.count else {
86-
// If `nil` is in the text choices, one of the token options can contain arbitrary
87-
// text. Don't generate an assert statement.
88-
return nil
89-
}
86+
choices = tokenChoices.compactMap(\.text)
9087
} else {
9188
return nil
9289
}
9390

94-
var assertChoices: [String] = []
91+
var assertChoices: [ExpressibleAsExprBuildable] = []
9592
if type.isOptional {
96-
assertChoices.append("\(varName) == nil")
93+
assertChoices.append(SequenceExpr {
94+
varName
95+
BinaryOperatorExpr("==")
96+
NilLiteralExpr()
97+
})
9798
}
98-
let unwrap = type.isOptional ? "!" : ""
9999
for textChoice in choices {
100-
assertChoices.append("\(varName)\(unwrap) == \"\(textChoice)\"")
100+
assertChoices.append(SequenceExpr {
101+
MemberAccessExpr(base: type.forceUnwrappedIfNeeded(expr: varName), name: "text")
102+
BinaryOperatorExpr("==")
103+
StringLiteralExpr(raw: textChoice)
104+
})
105+
}
106+
let disjunction = ExprList(assertChoices.flatMap { [$0, BinaryOperatorExpr("||")] }.dropLast())
107+
return FunctionCallExpr("assert") {
108+
SequenceExpr(elements: disjunction)
101109
}
102-
return "assert(\(assertChoices.joined(separator: " || ")))"
103110
}
104111
}

Sources/SwiftSyntaxBuilderGeneration/SyntaxBuildableType.swift

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -256,6 +256,15 @@ struct SyntaxBuildableType: Hashable {
256256
}
257257
}
258258

259+
/// Wraps a type in a force unwrap expression depending on whether `isOptional` is true.
260+
func forceUnwrappedIfNeeded(expr: ExpressibleAsExprBuildable) -> ExpressibleAsExprBuildable {
261+
if isOptional {
262+
return ForcedValueExpr(expression: expr)
263+
} else {
264+
return expr
265+
}
266+
}
267+
259268
/// Generate an expression that converts a variable named `varName`
260269
/// which is of `expressibleAs` type to an object of type `buildable`.
261270
func generateExprConvertParamTypeToStorageType(varName: String) -> ExpressibleAsExprBuildable {

0 commit comments

Comments
 (0)