Skip to content

Commit aeec1cc

Browse files
committed
Add diagnostic for missing sequence expr in for-loop
1 parent debb28d commit aeec1cc

File tree

4 files changed

+46
-34
lines changed

4 files changed

+46
-34
lines changed

Sources/SwiftParser/Statements.swift

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -616,7 +616,15 @@ extension Parser {
616616

617617
let (unexpectedBeforeInKeyword, inKeyword) = self.expect(.keyword(.in))
618618

619-
let expr = self.parseExpression(.basic)
619+
// If there is no expression, like `switch { default: return false }` then left brace would parsed as
620+
// a `RawClosureExprSyntax` in the condition, which is most likely not what the user meant.
621+
// Create a missing condition instead and use the `{` for the start of the body.
622+
let expr: RawExprSyntax
623+
if self.at(.leftBrace) {
624+
expr = RawExprSyntax(RawMissingExprSyntax(arena: self.arena))
625+
} else {
626+
expr = self.parseExpression(.basic)
627+
}
620628

621629
// Parse the 'where' expression if present.
622630
let whereClause: RawWhereClauseSyntax?

Sources/SwiftParserDiagnostics/ParseDiagnosticsGenerator.swift

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -601,7 +601,12 @@ public class ParseDiagnosticsGenerator: SyntaxAnyVisitor {
601601
] as [Syntax?]).compactMap({ $0 }),
602602
handledNodes: [node.inKeyword.id, node.sequenceExpr.id, unexpectedCondition.id]
603603
)
604+
} else { // If it's not a C-style for loop
605+
if node.sequenceExpr.is(MissingExprSyntax.self) {
606+
addDiagnostic(node.sequenceExpr, .expectedSequenceExpressionInForEachLoop, handledNodes: [node.sequenceExpr.id])
607+
}
604608
}
609+
605610
return .visitChildren
606611
}
607612

@@ -1049,7 +1054,7 @@ public class ParseDiagnosticsGenerator: SyntaxAnyVisitor {
10491054
}
10501055

10511056
if node.expression.is(MissingExprSyntax.self) && !node.cases.isEmpty {
1052-
addDiagnostic(node.expression, .missingExpressionInSwitchStatement, handledNodes: [node.expression.id])
1057+
addDiagnostic(node.expression, MissingExpressionInStatement(node: node), handledNodes: [node.expression.id])
10531058
}
10541059

10551060
return .visitChildren

Sources/SwiftParserDiagnostics/ParserDiagnosticMessages.swift

Lines changed: 15 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -128,6 +128,9 @@ extension DiagnosticMessage where Self == StaticParserError {
128128
public static var expectedExpressionAfterTry: Self {
129129
.init("expected expression after 'try'")
130130
}
131+
public static var expectedSequenceExpressionInForEachLoop: Self {
132+
.init("expected Sequence expression for for-each loop")
133+
}
131134
public static var initializerInPattern: Self {
132135
.init("unexpected initializer in pattern; did you mean to use '='?")
133136
}
@@ -164,9 +167,6 @@ extension DiagnosticMessage where Self == StaticParserError {
164167
public static var missingConformanceRequirement: Self {
165168
.init("expected ':' or '==' to indicate a conformance or same-type requirement")
166169
}
167-
public static var missingExpressionInSwitchStatement: Self {
168-
.init("expected expression in 'switch' statement")
169-
}
170170
public static var misspelledAsync: Self {
171171
.init("expected async specifier; did you mean 'async'?")
172172
}
@@ -333,6 +333,18 @@ public struct MissingAttributeArgument: ParserError {
333333
}
334334
}
335335

336+
public struct MissingExpressionInStatement: ParserError {
337+
let node: SyntaxProtocol
338+
339+
public var message: String {
340+
if let name = node.nodeTypeNameForDiagnostics(allowBlockNames: false) {
341+
return "expected expression in \(name)"
342+
} else {
343+
return "expected expression in statement"
344+
}
345+
}
346+
}
347+
336348
public struct NegatedAvailabilityCondition: ParserError {
337349
public let avaialabilityCondition: AvailabilityConditionSyntax
338350
public let negatedAvailabilityKeyword: TokenSyntax

Tests/SwiftParserTest/translated/RecoveryTests.swift

Lines changed: 16 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -459,13 +459,10 @@ final class RecoveryTests: XCTestCase {
459459
assertParse(
460460
"""
461461
for 1️⃣{
462-
}2️⃣
462+
}
463463
""",
464464
diagnostics: [
465-
// TODO: Old parser expected error on line 1: expected pattern
466-
// TODO: Old parser expected error on line 1: expected Sequence expression for for-each loop
467-
DiagnosticSpec(locationMarker: "1️⃣", message: "expected pattern and 'in' in 'for' statement"),
468-
DiagnosticSpec(locationMarker: "2️⃣", message: "expected code block in 'for' statement"),
465+
DiagnosticSpec(locationMarker: "1️⃣", message: "expected pattern, 'in', and expression in 'for' statement")
469466
]
470467
)
471468
}
@@ -475,12 +472,10 @@ final class RecoveryTests: XCTestCase {
475472
"""
476473
for1️⃣
477474
{
478-
}2️⃣
475+
}
479476
""",
480477
diagnostics: [
481-
// TODO: Old parser expected error on line 1: expected Sequence expression for for-each loop
482-
DiagnosticSpec(locationMarker: "1️⃣", message: "expected pattern and 'in' in 'for' statement"),
483-
DiagnosticSpec(locationMarker: "2️⃣", message: "expected code block in 'for' statement"),
478+
DiagnosticSpec(locationMarker: "1️⃣", message: "expected pattern, 'in', and expression in 'for' statement")
484479
]
485480
)
486481
}
@@ -489,12 +484,10 @@ final class RecoveryTests: XCTestCase {
489484
assertParse(
490485
"""
491486
for i 1️⃣{
492-
}2️⃣
487+
}
493488
""",
494489
diagnostics: [
495-
// TODO: Old parser expected error on line 1: expected Sequence expression for for-each loop
496-
DiagnosticSpec(locationMarker: "1️⃣", message: "expected 'in' in 'for' statement"),
497-
DiagnosticSpec(locationMarker: "2️⃣", message: "expected code block in 'for' statement"),
490+
DiagnosticSpec(locationMarker: "1️⃣", message: "expected 'in' and expression in 'for' statement")
498491
]
499492
)
500493
}
@@ -503,12 +496,10 @@ final class RecoveryTests: XCTestCase {
503496
assertParse(
504497
"""
505498
for var i 1️⃣{
506-
}2️⃣
499+
}
507500
""",
508501
diagnostics: [
509-
// TODO: Old parser expected error on line 1: expected Sequence expression for for-each loop
510-
DiagnosticSpec(locationMarker: "1️⃣", message: "expected 'in' in 'for' statement"),
511-
DiagnosticSpec(locationMarker: "2️⃣", message: "expected code block in 'for' statement"),
502+
DiagnosticSpec(locationMarker: "1️⃣", message: "expected 'in' and expression in 'for' statement")
512503
]
513504
)
514505
}
@@ -517,14 +508,11 @@ final class RecoveryTests: XCTestCase {
517508
assertParse(
518509
"""
519510
for 1️⃣in 2️⃣{
520-
}3️⃣
511+
}
521512
""",
522513
diagnostics: [
523-
// TODO: Old parser expected error on line 1: expected pattern
524-
// TODO: Old parser expected error on line 1: expected Sequence expression for for-each loop
525514
DiagnosticSpec(locationMarker: "1️⃣", message: "keyword 'in' cannot be used as an identifier here"),
526-
DiagnosticSpec(locationMarker: "2️⃣", message: "expected 'in' in 'for' statement"),
527-
DiagnosticSpec(locationMarker: "3️⃣", message: "expected code block in 'for' statement"),
515+
DiagnosticSpec(locationMarker: "2️⃣", message: "expected 'in' and expression in 'for' statement"),
528516
]
529517
)
530518
}
@@ -544,25 +532,24 @@ final class RecoveryTests: XCTestCase {
544532
func testRecovery41() {
545533
assertParse(
546534
"""
547-
for 1️⃣for in {
548-
}2️⃣
535+
for 1️⃣for in 2️⃣{
536+
}
549537
""",
550538
diagnostics: [
551539
DiagnosticSpec(locationMarker: "1️⃣", message: "keyword 'for' cannot be used as an identifier here"),
552-
DiagnosticSpec(locationMarker: "2️⃣", message: "expected code block in 'for' statement"),
540+
DiagnosticSpec(locationMarker: "2️⃣", message: "expected Sequence expression for for-each loop"),
553541
]
554542
)
555543
}
556544

557545
func testRecovery42() {
558546
assertParse(
559547
"""
560-
for i in {
561-
}1️⃣
548+
for i in 1️⃣{
549+
}
562550
""",
563551
diagnostics: [
564-
// TODO: Old parser expected error on line 1: expected Sequence expression for for-each loop
565-
DiagnosticSpec(message: "expected code block in 'for' statement")
552+
DiagnosticSpec(message: "expected Sequence expression for for-each loop")
566553
]
567554
)
568555
}

0 commit comments

Comments
 (0)