Skip to content

Commit bae4b34

Browse files
committed
[SwiftParser] Improve statement level recovery
1 parent 6673b05 commit bae4b34

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

44 files changed

+831
-230
lines changed

CodeGeneration/Sources/SyntaxSupport/CommonNodes.swift

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -401,4 +401,16 @@ public let COMMON_NODES: [Node] = [
401401
elementChoices: [.syntax]
402402
),
403403

404+
Node(
405+
kind: .unexpectedCodeDecl,
406+
base: .decl,
407+
nameForDiagnostics: nil,
408+
documentation: "Unexpected code at declaration position",
409+
children: [
410+
Child(
411+
name: "unexpectedCode",
412+
kind: .node(kind: .unexpectedNodes)
413+
)
414+
]
415+
),
404416
]

CodeGeneration/Sources/SyntaxSupport/SyntaxNodeKind.swift

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -301,6 +301,7 @@ public enum SyntaxNodeKind: String, CaseIterable, IdentifierConvertible, TypeCon
301301
case typeSpecifier
302302
case lifetimeSpecifierArguments
303303
case typeSpecifierList
304+
case unexpectedCodeDecl
304305
case unexpectedNodes
305306
case unresolvedAsExpr
306307
case unresolvedIsExpr

Sources/SwiftParser/Declarations.swift

Lines changed: 138 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -55,7 +55,8 @@ extension TokenConsumer {
5555
mutating func atStartOfDeclaration(
5656
isAtTopLevel: Bool = false,
5757
allowInitDecl: Bool = true,
58-
allowRecovery: Bool = false
58+
allowRecovery: Bool = false,
59+
requiresDecl: Bool = false,
5960
) -> Bool {
6061
if self.at(.poundIf) {
6162
return true
@@ -132,15 +133,15 @@ extension TokenConsumer {
132133
case .lhs(.case):
133134
// When 'case' appears inside a function, it's probably a switch
134135
// case, not an enum case declaration.
135-
return false
136+
return requiresDecl
136137
case .lhs(.`init`):
137138
return allowInitDecl
138139
case .lhs(.macro):
139140
// macro Foo ...
140141
return subparser.peek().rawTokenKind == .identifier
141142
case .lhs(.pound):
142143
// Force parsing '#<identifier>' after attributes as a macro expansion decl.
143-
if hasAttribute || hasModifier {
144+
if hasAttribute || hasModifier || requiresDecl {
144145
return true
145146
}
146147

@@ -181,8 +182,25 @@ extension TokenConsumer {
181182
allowRecovery: allowRecovery
182183
)
183184
}
184-
if allowRecovery && (hasAttribute || hasModifier) {
185+
if requiresDecl {
185186
// If we found any attributes or modifiers, consider it's a missing decl.
187+
if hasAttribute || hasModifier {
188+
return true
189+
}
190+
if subparser.atFunctionDeclarationWithoutFuncKeyword() {
191+
return true
192+
}
193+
if subparser.atBindingDeclarationWithoutVarKeyword() {
194+
return true
195+
}
196+
if subparser.currentToken.isEditorPlaceholder {
197+
return true
198+
}
199+
}
200+
// Special recovery 'try let/var'.
201+
if subparser.at(.keyword(.try)),
202+
subparser.peek(isAtAnyIn: VariableDeclSyntax.BindingSpecifierOptions.self) != nil
203+
{
186204
return true
187205
}
188206
return false
@@ -199,6 +217,10 @@ extension Parser {
199217
self.attributes = attributes
200218
self.modifiers = modifiers
201219
}
220+
221+
var isEmpty: Bool {
222+
attributes.isEmpty && modifiers.isEmpty
223+
}
202224
}
203225

204226
/// Describes the context around a declaration in order to modify how it is parsed.
@@ -267,8 +289,8 @@ extension Parser {
267289
semicolon: semicolon,
268290
arena: parser.arena
269291
)
270-
} addSemicolonIfNeeded: { lastElement, newItemAtStartOfLine, parser in
271-
if lastElement.semicolon == nil && !newItemAtStartOfLine {
292+
} addSemicolonIfNeeded: { lastElement, newItemAtStartOfLine, newItem, parser in
293+
if lastElement.semicolon == nil && !newItemAtStartOfLine && !newItem.decl.is(RawUnexpectedCodeDeclSyntax.self) {
272294
return RawMemberBlockItemSyntax(
273295
lastElement.unexpectedBeforeDecl,
274296
decl: lastElement.decl,
@@ -313,11 +335,11 @@ extension Parser {
313335
// We aren't at a declaration keyword and it looks like we are at a function
314336
// declaration. Parse a function declaration.
315337
recoveryResult = (.lhs(.func), .missing(.keyword(.func)))
338+
} else if atBindingDeclarationWithoutVarKeyword() {
339+
recoveryResult = (.rhs(.var), .missing(.keyword(.var)))
316340
} else {
317341
// In all other cases, use standard token recovery to find the declaration
318342
// to parse.
319-
// If we are inside a memberDecl list, we don't want to eat closing braces (which most likely close the outer context)
320-
// while recovering to the declaration start.
321343
recoveryResult = self.canRecoverTo(
322344
anyIn: DeclarationKeyword.self,
323345
overrideRecoveryPrecedence: context.recoveryPrecedence
@@ -380,13 +402,6 @@ extension Parser {
380402
}
381403

382404
if context.requiresDecl {
383-
let isProbablyVarDecl = self.at(.identifier, .wildcard) && self.peek(isAt: .colon, .equal, .comma)
384-
let isProbablyTupleDecl = self.at(.leftParen) && self.peek(isAt: .identifier, .wildcard)
385-
386-
if isProbablyVarDecl || isProbablyTupleDecl {
387-
return RawDeclSyntax(self.parseBindingDeclaration(attrs, .missing(.keyword(.var)), in: context))
388-
}
389-
390405
if self.currentToken.isEditorPlaceholder {
391406
let placeholder = self.parseAnyIdentifier()
392407
return RawDeclSyntax(
@@ -398,10 +413,6 @@ extension Parser {
398413
)
399414
)
400415
}
401-
402-
if atFunctionDeclarationWithoutFuncKeyword() {
403-
return RawDeclSyntax(self.parseFuncDeclaration(attrs, .missing(.keyword(.func))))
404-
}
405416
}
406417
return RawDeclSyntax(
407418
RawMissingDeclSyntax(
@@ -411,6 +422,27 @@ extension Parser {
411422
)
412423
)
413424
}
425+
}
426+
427+
extension TokenConsumer {
428+
/// Returns `true` if it looks like the parser is positioned at a variable declaration that’s missing the `var` keyword.
429+
fileprivate mutating func atBindingDeclarationWithoutVarKeyword() -> Bool {
430+
if self.at(.identifier, .wildcard),
431+
self.peek(isAt: .colon, .equal, .comma)
432+
{
433+
return true
434+
}
435+
if self.at(.leftParen),
436+
self.peek(isAt: .identifier, .wildcard),
437+
self.withLookahead({
438+
$0.skipSingle(); return $0.at(.colon, .equal)
439+
})
440+
{
441+
return true
442+
}
443+
444+
return false
445+
}
414446

415447
/// Returns `true` if it looks like the parser is positioned at a function declaration that’s missing the `func` keyword.
416448
fileprivate mutating func atFunctionDeclarationWithoutFuncKeyword() -> Bool {
@@ -983,20 +1015,35 @@ extension Parser {
9831015
let decl: RawDeclSyntax
9841016
if self.at(.poundSourceLocation) {
9851017
decl = RawDeclSyntax(self.parsePoundSourceLocationDirective())
986-
} else {
1018+
} else if self.atStartOfDeclaration(isAtTopLevel: false, allowInitDecl: true, requiresDecl: true) {
9871019
decl = self.parseDeclaration(in: .memberDeclList)
1020+
} else {
1021+
decl = RawDeclSyntax(
1022+
self.parseUnexpectedCodeDeclaration(
1023+
isAtTopLevel: false,
1024+
allowInitDecl: true,
1025+
requiresDecl: true,
1026+
skipToDeclOnly: true
1027+
)
1028+
)
9881029
}
9891030

990-
let semi = self.consume(if: .semicolon)
1031+
if decl.isEmpty && !self.at(.semicolon) {
1032+
return nil
1033+
}
1034+
1035+
let semi: RawTokenSyntax?
1036+
if !decl.isEmpty {
1037+
semi = self.consume(if: .semicolon)
1038+
} else {
1039+
// orphan ';' case. Put it to "unexpected" nodes.
1040+
semi = nil
1041+
}
9911042
var trailingSemis: [RawTokenSyntax] = []
9921043
while let trailingSemi = self.consume(if: .semicolon) {
9931044
trailingSemis.append(trailingSemi)
9941045
}
9951046

996-
if decl.isEmpty && semi == nil && trailingSemis.isEmpty {
997-
return nil
998-
}
999-
10001047
let result = RawMemberBlockItemSyntax(
10011048
decl: decl,
10021049
semicolon: semi,
@@ -1013,12 +1060,14 @@ extension Parser {
10131060
var elements = [RawMemberBlockItemSyntax]()
10141061
do {
10151062
var loopProgress = LoopProgressCondition()
1016-
while !self.at(.endOfFile, .rightBrace) && self.hasProgressed(&loopProgress) {
1063+
while !self.at(.endOfFile, .rightBrace, .poundEndif) && self.hasProgressed(&loopProgress) {
10171064
let newItemAtStartOfLine = self.atStartOfLine
10181065
guard let newElement = self.parseMemberBlockItem() else {
10191066
break
10201067
}
1021-
if let lastItem = elements.last, lastItem.semicolon == nil && !newItemAtStartOfLine {
1068+
if let lastItem = elements.last,
1069+
lastItem.semicolon == nil && !newItemAtStartOfLine && !newElement.decl.is(RawUnexpectedCodeDeclSyntax.self)
1070+
{
10221071
elements[elements.count - 1] = RawMemberBlockItemSyntax(
10231072
lastItem.unexpectedBeforeDecl,
10241073
decl: lastItem.decl,
@@ -1039,7 +1088,15 @@ extension Parser {
10391088
/// If the left brace is missing, its indentation will be used to judge whether a following `}` was
10401089
/// indented to close this code block or a surrounding context. See `expectRightBrace`.
10411090
mutating func parseMemberBlock(introducer: RawTokenSyntax? = nil) -> RawMemberBlockSyntax {
1042-
let (unexpectedBeforeLBrace, lbrace) = self.expect(.leftBrace)
1091+
guard let lBraceHandle = canRecoverTo(.leftBrace) else {
1092+
return RawMemberBlockSyntax(
1093+
leftBrace: self.missingToken(.leftBrace),
1094+
members: RawMemberBlockItemListSyntax(elements: [], arena: self.arena),
1095+
rightBrace: consume(if: TokenSpec(.rightBrace, allowAtStartOfLine: false)) ?? self.missingToken(.rightBrace),
1096+
arena: arena
1097+
)
1098+
}
1099+
let (unexpectedBeforeLBrace, lbrace) = self.eat(lBraceHandle)
10431100
let members = parseMemberDeclList()
10441101
let (unexpectedBeforeRBrace, rbrace) = self.expectRightBrace(leftBrace: lbrace, introducer: introducer)
10451102

@@ -2322,4 +2379,57 @@ extension Parser {
23222379
arena: self.arena
23232380
)
23242381
}
2382+
2383+
mutating func parseUnexpectedCodeDeclaration(
2384+
isAtTopLevel: Bool,
2385+
allowInitDecl: Bool,
2386+
requiresDecl: Bool,
2387+
skipToDeclOnly: Bool,
2388+
) -> RawUnexpectedCodeDeclSyntax {
2389+
let numTokensToSkip = withLookahead { (lookahead) -> Int in
2390+
while !lookahead.at(.endOfFile, .semicolon) {
2391+
if lookahead.at(.poundElse, .poundElseif, .poundEndif) {
2392+
break;
2393+
}
2394+
if !isAtTopLevel && lookahead.at(.rightBrace) {
2395+
break;
2396+
}
2397+
lookahead.skipSingle()
2398+
2399+
if lookahead.at(.poundIf) {
2400+
break
2401+
}
2402+
if lookahead.at(.poundSourceLocation) {
2403+
break
2404+
}
2405+
if lookahead.atStartOfDeclaration(
2406+
isAtTopLevel: isAtTopLevel,
2407+
allowInitDecl: allowInitDecl,
2408+
requiresDecl: requiresDecl
2409+
) {
2410+
break
2411+
}
2412+
2413+
if skipToDeclOnly {
2414+
continue
2415+
}
2416+
if lookahead.atStartOfStatement(preferExpr: false) {
2417+
break
2418+
}
2419+
// Recover to an expression only if it's on the next line.
2420+
if lookahead.currentToken.isAtStartOfLine && lookahead.atStartOfExpression() {
2421+
break
2422+
}
2423+
}
2424+
return lookahead.tokensConsumed
2425+
}
2426+
var unexpectedTokens = [RawSyntax]()
2427+
for _ in 0..<numTokensToSkip {
2428+
unexpectedTokens.append(RawSyntax(self.consumeAnyTokenWithoutAdjustingNestingLevel()))
2429+
}
2430+
return RawUnexpectedCodeDeclSyntax(
2431+
unexpectedCode: RawUnexpectedNodesSyntax(elements: unexpectedTokens, arena: self.arena),
2432+
arena: arena
2433+
)
2434+
}
23252435
}

Sources/SwiftParser/Directives.swift

Lines changed: 10 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -59,7 +59,12 @@ extension Parser {
5959
mutating func parsePoundIfDirective<Element: RawSyntaxNodeProtocol>(
6060
_ parseElement: (_ parser: inout Parser, _ isFirstElement: Bool) -> Element?,
6161
addSemicolonIfNeeded:
62-
(_ lastElement: Element, _ newItemAtStartOfLine: Bool, _ parser: inout Parser) -> Element? = { _, _, _ in nil },
62+
(_ lastElement: Element, _ newItemAtStartOfLine: Bool, _ newItem: Element, _ parser: inout Parser) -> Element? = {
63+
_,
64+
_,
65+
_,
66+
_ in nil
67+
},
6368
syntax: (inout Parser, [Element]) -> RawIfConfigClauseSyntax.Elements?
6469
) -> RawIfConfigDeclSyntax {
6570
if let remainingTokens = remainingTokensIfMaximumNestingLevelReached() {
@@ -185,7 +190,9 @@ extension Parser {
185190

186191
private mutating func parseIfConfigClauseElements<Element: RawSyntaxNodeProtocol>(
187192
_ parseElement: (_ parser: inout Parser, _ isFirstElement: Bool) -> Element?,
188-
addSemicolonIfNeeded: (_ lastElement: Element, _ newItemAtStartOfLine: Bool, _ parser: inout Parser) -> Element?
193+
addSemicolonIfNeeded: (
194+
_ lastElement: Element, _ newItemAtStartOfLine: Bool, _ newItem: Element, _ parser: inout Parser
195+
) -> Element?
189196
) -> [Element] {
190197
var elements = [Element]()
191198
var elementsProgress = LoopProgressCondition()
@@ -199,7 +206,7 @@ extension Parser {
199206
break
200207
}
201208
if let lastElement = elements.last,
202-
let fixedUpLastItem = addSemicolonIfNeeded(lastElement, newItemAtStartOfLine, &self)
209+
let fixedUpLastItem = addSemicolonIfNeeded(lastElement, newItemAtStartOfLine, element, &self)
203210
{
204211
elements[elements.count - 1] = fixedUpLastItem
205212
}

Sources/SwiftParser/Expressions.swift

Lines changed: 17 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2414,8 +2414,23 @@ extension Parser {
24142414

24152415
mutating func parseSwitchCaseBody() -> RawCodeBlockItemListSyntax {
24162416
parseCodeBlockItemList(until: {
2417-
$0.at(.rightBrace) || $0.at(.poundEndif, .poundElseif, .poundElse)
2418-
|| $0.withLookahead({ $0.atStartOfConditionalSwitchCases() })
2417+
if $0.at(.rightBrace) || $0.at(.poundEndif, .poundElseif, .poundElse) {
2418+
return true
2419+
}
2420+
if $0.at(.keyword(.case), .keyword(.default)) {
2421+
return true
2422+
}
2423+
if $0.at(.atSign)
2424+
&& $0.withLookahead({
2425+
$0.consumeAnyAttribute(); return $0.at(.keyword(.case), .keyword(.default))
2426+
})
2427+
{
2428+
return true
2429+
}
2430+
if $0.withLookahead({ $0.atStartOfConditionalSwitchCases() }) {
2431+
return true
2432+
}
2433+
return false
24192434
})
24202435
}
24212436

Sources/SwiftParser/Statements.swift

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -141,7 +141,9 @@ extension Parser {
141141
mutating func parseGuardStatement(guardHandle: RecoveryConsumptionHandle) -> RawGuardStmtSyntax {
142142
let (unexpectedBeforeGuardKeyword, guardKeyword) = self.eat(guardHandle)
143143
let conditions = self.parseConditionList(isGuardStatement: true)
144-
let (unexpectedBeforeElseKeyword, elseKeyword) = self.expect(.keyword(.else))
144+
let (unexpectedBeforeElseKeyword, elseKeyword) = self.expect(
145+
TokenSpec(.else, recoveryPrecedence: .openingBrace(closingDelimiter: .rightBrace))
146+
)
145147
let body = self.parseCodeBlock(introducer: guardKeyword)
146148
return RawGuardStmtSyntax(
147149
unexpectedBeforeGuardKeyword,
@@ -1087,6 +1089,13 @@ extension Parser.Lookahead {
10871089
)
10881090

10891091
case nil:
1092+
// Special recovery 'try return' etc..
1093+
if !preferExpr,
1094+
consume(if: .keyword(.try)) != nil,
1095+
self.at(anyIn: SingleValueStatementExpression.self) == nil
1096+
{
1097+
return atStartOfStatement(allowRecovery: allowRecovery, preferExpr: preferExpr)
1098+
}
10901099
return false
10911100
}
10921101
}

0 commit comments

Comments
 (0)