Skip to content

Commit cf2d4c6

Browse files
authored
Merge pull request #1429 from kimdv/kimdv/add-missing-switch-expression-error
2 parents e41072e + 76b4d04 commit cf2d4c6

File tree

5 files changed

+196
-99
lines changed

5 files changed

+196
-99
lines changed

Sources/SwiftParser/Expressions.swift

Lines changed: 28 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -2174,9 +2174,24 @@ extension Parser {
21742174
ifHandle: RecoveryConsumptionHandle
21752175
) -> RawIfExprSyntax {
21762176
let (unexpectedBeforeIfKeyword, ifKeyword) = self.eat(ifHandle)
2177-
// A scope encloses the condition and true branch for any variables bound
2178-
// by a conditional binding. The else branch does *not* see these variables.
2179-
let conditions = self.parseConditionList()
2177+
2178+
let conditions: RawConditionElementListSyntax
2179+
2180+
if self.at(.leftBrace) {
2181+
conditions = RawConditionElementListSyntax(
2182+
elements: [
2183+
RawConditionElementSyntax(
2184+
condition: .expression(RawExprSyntax(RawMissingExprSyntax(arena: self.arena))),
2185+
trailingComma: nil,
2186+
arena: self.arena
2187+
)
2188+
],
2189+
arena: self.arena
2190+
)
2191+
} else {
2192+
conditions = self.parseConditionList()
2193+
}
2194+
21802195
let body = self.parseCodeBlock(introducer: ifKeyword)
21812196

21822197
// The else branch, if any, is outside of the scope of the condition.
@@ -2222,7 +2237,16 @@ extension Parser {
22222237
) -> RawSwitchExprSyntax {
22232238
let (unexpectedBeforeSwitchKeyword, switchKeyword) = self.eat(switchHandle)
22242239

2225-
let subject = self.parseExpression(.basic)
2240+
// If there is no expression, like `switch { default: return false }` then left brace would parsed as
2241+
// a `RawClosureExprSyntax` in the condition, which is most likely not what the user meant.
2242+
// Create a missing condition instead and use the `{` for the start of the body.
2243+
let subject: RawExprSyntax
2244+
if self.at(.leftBrace) {
2245+
subject = RawExprSyntax(RawMissingExprSyntax(arena: self.arena))
2246+
} else {
2247+
subject = self.parseExpression(.basic)
2248+
}
2249+
22262250
let (unexpectedBeforeLBrace, lbrace) = self.expect(.leftBrace)
22272251

22282252
let cases = self.parseSwitchCases(allowStandaloneStmtRecovery: !lbrace.isMissing)

Sources/SwiftParser/Statements.swift

Lines changed: 25 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -539,7 +539,22 @@ extension Parser {
539539
@_spi(RawSyntax)
540540
public mutating func parseWhileStatement(whileHandle: RecoveryConsumptionHandle) -> RawWhileStmtSyntax {
541541
let (unexpectedBeforeWhileKeyword, whileKeyword) = self.eat(whileHandle)
542-
let conditions = self.parseConditionList()
542+
let conditions: RawConditionElementListSyntax
543+
544+
if self.at(.leftBrace) {
545+
conditions = RawConditionElementListSyntax(
546+
elements: [
547+
RawConditionElementSyntax(
548+
condition: .expression(RawExprSyntax(RawMissingExprSyntax(arena: self.arena))),
549+
trailingComma: nil,
550+
arena: self.arena
551+
)
552+
],
553+
arena: self.arena
554+
)
555+
} else {
556+
conditions = self.parseConditionList()
557+
}
543558
let body = self.parseCodeBlock(introducer: whileKeyword)
544559
return RawWhileStmtSyntax(
545560
unexpectedBeforeWhileKeyword,
@@ -616,7 +631,15 @@ extension Parser {
616631

617632
let (unexpectedBeforeInKeyword, inKeyword) = self.expect(.keyword(.in))
618633

619-
let expr = self.parseExpression(.basic)
634+
// If there is no expression, like `switch { default: return false }` then left brace would parsed as
635+
// a `RawClosureExprSyntax` in the condition, which is most likely not what the user meant.
636+
// Create a missing condition instead and use the `{` for the start of the body.
637+
let expr: RawExprSyntax
638+
if self.at(.leftBrace) {
639+
expr = RawExprSyntax(RawMissingExprSyntax(arena: self.arena))
640+
} else {
641+
expr = self.parseExpression(.basic)
642+
}
620643

621644
// Parse the 'where' expression if present.
622645
let whereClause: RawWhereClauseSyntax?

Sources/SwiftParserDiagnostics/ParseDiagnosticsGenerator.swift

Lines changed: 41 additions & 0 deletions
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

@@ -750,6 +755,18 @@ public class ParseDiagnosticsGenerator: SyntaxAnyVisitor {
750755
return .visitChildren
751756
}
752757

758+
public override func visit(_ node: IfExprSyntax) -> SyntaxVisitorContinueKind {
759+
if shouldSkip(node) {
760+
return .skipChildren
761+
}
762+
763+
if node.conditions.count == 1, node.conditions.first?.as(ConditionElementSyntax.self)?.condition.is(MissingExprSyntax.self) == true, !node.body.leftBrace.isMissingAllTokens {
764+
addDiagnostic(node.conditions, MissingConditionInStatement(node: node), handledNodes: [node.conditions.id])
765+
}
766+
767+
return .visitChildren
768+
}
769+
753770
public override func visit(_ node: InitializerClauseSyntax) -> SyntaxVisitorContinueKind {
754771
if shouldSkip(node) {
755772
return .skipChildren
@@ -1043,6 +1060,18 @@ public class ParseDiagnosticsGenerator: SyntaxAnyVisitor {
10431060
return .visitChildren
10441061
}
10451062

1063+
public override func visit(_ node: SwitchExprSyntax) -> SyntaxVisitorContinueKind {
1064+
if shouldSkip(node) {
1065+
return .skipChildren
1066+
}
1067+
1068+
if node.expression.is(MissingExprSyntax.self) && !node.cases.isEmpty {
1069+
addDiagnostic(node.expression, MissingExpressionInStatement(node: node), handledNodes: [node.expression.id])
1070+
}
1071+
1072+
return .visitChildren
1073+
}
1074+
10461075
public override func visit(_ node: SwitchCaseSyntax) -> SyntaxVisitorContinueKind {
10471076
if shouldSkip(node) {
10481077
return .skipChildren
@@ -1191,6 +1220,18 @@ public class ParseDiagnosticsGenerator: SyntaxAnyVisitor {
11911220
return .visitChildren
11921221
}
11931222

1223+
public override func visit(_ node: WhileStmtSyntax) -> SyntaxVisitorContinueKind {
1224+
if shouldSkip(node) {
1225+
return .skipChildren
1226+
}
1227+
1228+
if node.conditions.count == 1, node.conditions.first?.as(ConditionElementSyntax.self)?.condition.is(MissingExprSyntax.self) == true, !node.body.leftBrace.isMissingAllTokens {
1229+
addDiagnostic(node.conditions, MissingConditionInStatement(node: node), handledNodes: [node.conditions.id])
1230+
}
1231+
1232+
return .visitChildren
1233+
}
1234+
11941235
//==========================================================================//
11951236
// IMPORTANT: If you are tempted to add a `visit` method here, please //
11961237
// insert it in alphabetical order above //

Sources/SwiftParserDiagnostics/ParserDiagnosticMessages.swift

Lines changed: 27 additions & 0 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
}
@@ -330,6 +333,30 @@ public struct MissingAttributeArgument: ParserError {
330333
}
331334
}
332335

336+
public struct MissingConditionInStatement: ParserError {
337+
let node: SyntaxProtocol
338+
339+
public var message: String {
340+
if let name = node.nodeTypeNameForDiagnostics(allowBlockNames: false) {
341+
return "missing condition in \(name)"
342+
} else {
343+
return "missing condition in statement"
344+
}
345+
}
346+
}
347+
348+
public struct MissingExpressionInStatement: ParserError {
349+
let node: SyntaxProtocol
350+
351+
public var message: String {
352+
if let name = node.nodeTypeNameForDiagnostics(allowBlockNames: false) {
353+
return "expected expression in \(name)"
354+
} else {
355+
return "expected expression in statement"
356+
}
357+
}
358+
}
359+
333360
public struct NegatedAvailabilityCondition: ParserError {
334361
public let avaialabilityCondition: AvailabilityConditionSyntax
335362
public let negatedAvailabilityKeyword: TokenSyntax

0 commit comments

Comments
 (0)