Skip to content

Commit fd29f74

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

File tree

2 files changed

+25
-11
lines changed

2 files changed

+25
-11
lines changed

Sources/SwiftParser/Statements.swift

Lines changed: 17 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,15 @@ 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+
public mutating func parseConditionElement(lastBindingKind: RawTokenSyntax?) -> RawConditionElementSyntax.Condition {
223223
// Parse a leading #available/#unavailable condition if present.
224224
if self.at(.poundAvailableKeyword, .poundUnavailableKeyword) {
225225
return self.parsePoundAvailableConditionElement()
226226
}
227227

228228
// Parse the basic expression case. If we have a leading let, var, inout,
229229
// 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 {
230+
guard self.at(.keyword(.let), .keyword(.var), .keyword(.case)) || self.at(.keyword(.inout)) || (lastBindingKind != nil && self.peek().rawTokenKind == .equal) else {
231231
// If we lack it, then this is theoretically a boolean condition.
232232
// However, we also need to handle migrating from Swift 2 syntax, in
233233
// which a comma followed by an expression could actually be a pattern
@@ -244,20 +244,28 @@ extension Parser {
244244
}
245245

246246
// We're parsing a conditional binding.
247-
precondition(self.at(.keyword(.let), .keyword(.var)) || self.at(.keyword(.inout), .keyword(.case)))
248247
enum BindingKind {
249248
case pattern(RawTokenSyntax, RawPatternSyntax)
250-
case optional(RawTokenSyntax, RawPatternSyntax)
249+
case optional(RawUnexpectedNodesSyntax?, RawTokenSyntax, RawPatternSyntax)
251250
}
252251

253252
let kind: BindingKind
254253
if let caseKeyword = self.consume(if: .keyword(.case)) {
255254
let pattern = self.parseMatchingPattern(context: .matching)
256255
kind = .pattern(caseKeyword, pattern)
257256
} else {
258-
let letOrVar = self.consumeAnyToken()
257+
let unexpectedBeforeBindingKeyword: RawUnexpectedNodesSyntax?
258+
let letOrVar: RawTokenSyntax
259+
260+
if self.at(.identifier), let lastBindingKind = lastBindingKind {
261+
(unexpectedBeforeBindingKeyword, letOrVar) = self.expect(.keyword(.let), .keyword(.var), default: .keyword(Keyword(lastBindingKind.tokenText) ?? .let))
262+
} else {
263+
letOrVar = self.consume(if: TokenSpec.keyword(.let), .keyword(.var)) ?? self.missingToken(.let)
264+
unexpectedBeforeBindingKeyword = nil
265+
}
266+
259267
let pattern = self.parseMatchingPattern(context: .bindingIntroducer)
260-
kind = .optional(letOrVar, pattern)
268+
kind = .optional(unexpectedBeforeBindingKeyword, letOrVar, pattern)
261269
}
262270

263271
// Now parse an optional type annotation.
@@ -289,9 +297,10 @@ extension Parser {
289297
}
290298

291299
switch kind {
292-
case let .optional(bindingKeyword, pattern):
300+
case let .optional(unexpectedBeforeBindingKeyword, bindingKeyword, pattern):
293301
return .optionalBinding(
294302
RawOptionalBindingConditionSyntax(
303+
unexpectedBeforeBindingKeyword,
295304
bindingKeyword: bindingKeyword,
296305
pattern: pattern,
297306
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)