@@ -137,7 +137,7 @@ extension Parser {
137
137
/// Parse a guard statement.
138
138
mutating func parseGuardStatement( guardHandle: RecoveryConsumptionHandle ) -> RawGuardStmtSyntax {
139
139
let ( unexpectedBeforeGuardKeyword, guardKeyword) = self . eat ( guardHandle)
140
- let conditions = self . parseConditionList ( )
140
+ let conditions = self . parseConditionList ( isGuardStatement : true )
141
141
let ( unexpectedBeforeElseKeyword, elseKeyword) = self . expect ( . keyword( . else) )
142
142
let body = self . parseCodeBlock ( introducer: guardKeyword)
143
143
return RawGuardStmtSyntax (
@@ -154,7 +154,7 @@ extension Parser {
154
154
155
155
extension Parser {
156
156
/// Parse a list of condition elements.
157
- mutating func parseConditionList( ) -> RawConditionElementListSyntax {
157
+ mutating func parseConditionList( isGuardStatement : Bool ) -> RawConditionElementListSyntax {
158
158
// We have a simple comma separated list of clauses, but also need to handle
159
159
// a variety of common errors situations (including migrating from Swift 2
160
160
// syntax).
@@ -183,11 +183,25 @@ extension Parser {
183
183
arena: self . arena
184
184
)
185
185
)
186
- } while keepGoing != nil && self . hasProgressed ( & loopProgress)
186
+ } while keepGoing != nil && !atConditionListTerminator( isGuardStatement: isGuardStatement)
187
+ && self . hasProgressed ( & loopProgress)
187
188
188
189
return RawConditionElementListSyntax ( elements: elements, arena: self . arena)
189
190
}
190
191
192
+ mutating func atConditionListTerminator( isGuardStatement: Bool ) -> Bool {
193
+ guard experimentalFeatures. contains ( . trailingComma) else {
194
+ return false
195
+ }
196
+ // Condition terminator is `else` for `guard` statements.
197
+ if isGuardStatement, self . at ( . keyword( . else) ) {
198
+ return true
199
+ }
200
+ // Condition terminator is start of statement body for `if` or `while` statements.
201
+ // Missing `else` is a common mistake for `guard` statements so we fall back to lookahead for a body.
202
+ return self . at ( . leftBrace) && withLookahead ( { $0. atStartOfConditionalStatementBody ( ) } )
203
+ }
204
+
191
205
/// Parse a condition element.
192
206
///
193
207
/// `lastBindingKind` will be used to get a correct fall back, when there is missing `var` or `let` in a `if` statement etc.
@@ -498,7 +512,6 @@ extension Parser {
498
512
mutating func parseWhileStatement( whileHandle: RecoveryConsumptionHandle ) -> RawWhileStmtSyntax {
499
513
let ( unexpectedBeforeWhileKeyword, whileKeyword) = self . eat ( whileHandle)
500
514
let conditions : RawConditionElementListSyntax
501
-
502
515
if self . at ( . leftBrace) {
503
516
conditions = RawConditionElementListSyntax (
504
517
elements: [
@@ -511,9 +524,11 @@ extension Parser {
511
524
arena: self . arena
512
525
)
513
526
} else {
514
- conditions = self . parseConditionList ( )
527
+ conditions = self . parseConditionList ( isGuardStatement : false )
515
528
}
529
+
516
530
let body = self . parseCodeBlock ( introducer: whileKeyword)
531
+
517
532
return RawWhileStmtSyntax (
518
533
unexpectedBeforeWhileKeyword,
519
534
whileKeyword: whileKeyword,
@@ -1053,4 +1068,54 @@ extension Parser.Lookahead {
1053
1068
} while lookahead. at ( . poundIf, . poundElseif, . poundElse) && lookahead. hasProgressed ( & loopProgress)
1054
1069
return lookahead. atStartOfSwitchCase ( )
1055
1070
}
1071
+
1072
+ /// Returns `true` if the current token represents the start of an `if` or `while` statement body.
1073
+ mutating func atStartOfConditionalStatementBody( ) -> Bool {
1074
+ guard at ( . leftBrace) else {
1075
+ // Statement bodies always start with a '{'. If there is no '{', we can't be at the statement body.
1076
+ return false
1077
+ }
1078
+ skipSingle ( )
1079
+ if self . at ( . endOfFile) {
1080
+ // There's nothing else in the source file that could be the statement body, so this must be it.
1081
+ return true
1082
+ }
1083
+ if self . at ( . semicolon) {
1084
+ // We can't have a semicolon between the condition and the statement body, so this must be the statement body.
1085
+ return true
1086
+ }
1087
+ if self . at ( . keyword( . else) ) {
1088
+ // If the current token is an `else` keyword, this must be the statement body of an `if` statement since conditions can't be followed by `else`.
1089
+ return true
1090
+ }
1091
+ if self . at ( . rightBrace, . rightParen) {
1092
+ // A right brace or parenthesis cannot start a statement body, nor can the condition list continue afterwards. So, this must be the statement body.
1093
+ // This covers cases like `if true, { if true, { } }` or `( if true, { print(0) } )`. While the latter is not valid code, it improves diagnostics.
1094
+ return true
1095
+ }
1096
+ if self . atStartOfLine {
1097
+ // If the current token is at the start of a line, it is most likely a statement body. The only exceptions are:
1098
+ if self . at ( . comma) {
1099
+ // If newline begins with ',' it must be a condition trailing comma, so this can't be the statement body, e.g.
1100
+ // if true, { true }
1101
+ // , true { print("body") }
1102
+ return false
1103
+ }
1104
+ if self . at ( . binaryOperator) {
1105
+ // If current token is a binary operator this can't be the statement body since an `if` expression can't be the left-hand side of an operator, e.g.
1106
+ // if true, { true }
1107
+ // != nil
1108
+ // {
1109
+ // print("body")
1110
+ // }
1111
+ return false
1112
+ }
1113
+ // Excluded the above exceptions, this must be the statement body.
1114
+ return true
1115
+ } else {
1116
+ // If the current token isn't at the start of a line and isn't `EOF`, `;`, `else`, `)` or `}` this can't be the statement body.
1117
+ return false
1118
+ }
1119
+ }
1120
+
1056
1121
}
0 commit comments