Skip to content

Commit 6ac5f4b

Browse files
committed
Add diagnostic for missing condition binding
1 parent 9ad7a48 commit 6ac5f4b

File tree

2 files changed

+26
-11
lines changed

2 files changed

+26
-11
lines changed

Sources/SwiftParser/Statements.swift

Lines changed: 18 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -186,7 +186,7 @@ extension Parser {
186186
var keepGoing: RawTokenSyntax? = nil
187187
var loopProgress = LoopProgressCondition()
188188
repeat {
189-
let condition = self.parseConditionElement()
189+
let condition = self.parseConditionElement(lastBindingKind: elements.last?.condition.as(RawOptionalBindingConditionSyntax.self)?.bindingKeyword)
190190
let unexpectedBeforeKeepGoing: RawUnexpectedNodesSyntax?
191191
keepGoing = self.consume(if: .comma)
192192
if keepGoing == nil, let andOperator = self.consumeIfContextualPunctuator("&&") {
@@ -219,15 +219,16 @@ extension Parser {
219219
/// optional-binding-condition → 'let' pattern initializer? | 'var' pattern initializer? |
220220
/// 'inout' pattern initializer?
221221
@_spi(RawSyntax)
222-
public mutating func parseConditionElement() -> RawConditionElementSyntax.Condition {
222+
/// `lastBindingKind` will be used to get a correct fall back, when there is missing `var` or `let` in a `if` statement etc.
223+
public mutating func parseConditionElement(lastBindingKind: RawTokenSyntax?) -> RawConditionElementSyntax.Condition {
223224
// Parse a leading #available/#unavailable condition if present.
224225
if self.at(.poundAvailableKeyword, .poundUnavailableKeyword) {
225226
return self.parsePoundAvailableConditionElement()
226227
}
227228

228229
// Parse the basic expression case. If we have a leading let, var, inout,
229230
// borrow, case keyword or an assignment, then we know this is a binding.
230-
guard self.at(.keyword(.let), .keyword(.var), .keyword(.case)) || self.at(.keyword(.inout)) else {
231+
guard self.at(.keyword(.let), .keyword(.var), .keyword(.case)) || self.at(.keyword(.inout)) || (lastBindingKind != nil && self.peek().rawTokenKind == .equal) else {
231232
// If we lack it, then this is theoretically a boolean condition.
232233
// However, we also need to handle migrating from Swift 2 syntax, in
233234
// which a comma followed by an expression could actually be a pattern
@@ -244,20 +245,28 @@ extension Parser {
244245
}
245246

246247
// We're parsing a conditional binding.
247-
precondition(self.at(.keyword(.let), .keyword(.var)) || self.at(.keyword(.inout), .keyword(.case)))
248248
enum BindingKind {
249249
case pattern(RawTokenSyntax, RawPatternSyntax)
250-
case optional(RawTokenSyntax, RawPatternSyntax)
250+
case optional(RawUnexpectedNodesSyntax?, RawTokenSyntax, RawPatternSyntax)
251251
}
252252

253253
let kind: BindingKind
254254
if let caseKeyword = self.consume(if: .keyword(.case)) {
255255
let pattern = self.parseMatchingPattern(context: .matching)
256256
kind = .pattern(caseKeyword, pattern)
257257
} else {
258-
let letOrVar = self.consumeAnyToken()
258+
let unexpectedBeforeBindingKeyword: RawUnexpectedNodesSyntax?
259+
let letOrVar: RawTokenSyntax
260+
261+
if self.at(.identifier), let lastBindingKind = lastBindingKind {
262+
(unexpectedBeforeBindingKeyword, letOrVar) = self.expect(.keyword(.let), .keyword(.var), default: .keyword(Keyword(lastBindingKind.tokenText) ?? .let))
263+
} else {
264+
letOrVar = self.consume(if: TokenSpec.keyword(.let), .keyword(.var)) ?? self.missingToken(.let)
265+
unexpectedBeforeBindingKeyword = nil
266+
}
267+
259268
let pattern = self.parseMatchingPattern(context: .bindingIntroducer)
260-
kind = .optional(letOrVar, pattern)
269+
kind = .optional(unexpectedBeforeBindingKeyword, letOrVar, pattern)
261270
}
262271

263272
// Now parse an optional type annotation.
@@ -289,9 +298,10 @@ extension Parser {
289298
}
290299

291300
switch kind {
292-
case let .optional(bindingKeyword, pattern):
301+
case let .optional(unexpectedBeforeBindingKeyword, bindingKeyword, pattern):
293302
return .optionalBinding(
294303
RawOptionalBindingConditionSyntax(
304+
unexpectedBeforeBindingKeyword,
295305
bindingKeyword: bindingKeyword,
296306
pattern: pattern,
297307
typeAnnotation: annotation,

Tests/SwiftParserTest/translated/RecoveryTests.swift

Lines changed: 8 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1839,13 +1839,18 @@ final class RecoveryTests: XCTestCase {
18391839
func testRecovery153() {
18401840
assertParse(
18411841
"""
1842-
if var y = x, z = x {
1842+
if var y = x, 1️⃣z = x {
18431843
z = y; y = z
18441844
}
18451845
""",
18461846
diagnostics: [
1847-
// TODO: Old parser expected error on line 1: expected 'var' in conditional, Fix-It replacements: 17 - 17 = 'var '
1848-
]
1847+
DiagnosticSpec(message: "expected 'var' in optional binding", fixIts: ["insert 'var'"])
1848+
],
1849+
fixedSource: """
1850+
if var y = x, var z = x {
1851+
z = y; y = z
1852+
}
1853+
"""
18491854
)
18501855
}
18511856

0 commit comments

Comments
 (0)