Skip to content

Commit eec600c

Browse files
committed
Don’t eat closing braces while trying to recover to a declaration start keyword
1 parent 947f80f commit eec600c

File tree

3 files changed

+89
-3
lines changed

3 files changed

+89
-3
lines changed

Sources/SwiftParser/Declarations.swift

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -190,7 +190,12 @@ extension Parser {
190190
let attrs = DeclAttributes(
191191
attributes: self.parseAttributeList(),
192192
modifiers: self.parseModifierList())
193-
switch self.canRecoverTo(anyIn: DeclarationStart.self) {
193+
194+
// If we are inside a memberDecl list, we don't want to eat closing braces (which most likely close the outer context)
195+
// while recoverying to the declaration start.
196+
let recoveryPrecedence = inMemberDeclList ? TokenPrecedence.closingBrace : nil
197+
198+
switch self.canRecoverTo(anyIn: DeclarationStart.self, recoveryPrecedence: recoveryPrecedence) {
194199
case (.importKeyword, let handle)?:
195200
return RawDeclSyntax(self.parseImportDeclaration(attrs, handle))
196201
case (.classKeyword, let handle)?:

Sources/SwiftParser/Parser.swift

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -263,13 +263,14 @@ extension Parser {
263263
/// If so, return the token that we can recover to and a handle that can be
264264
/// used to consume the unexpected tokens and the token we recovered to.
265265
func canRecoverTo<Subset: RawTokenKindSubset>(
266-
anyIn subset: Subset.Type
266+
anyIn subset: Subset.Type,
267+
recoveryPrecedence: TokenPrecedence? = nil
267268
) -> (Subset, RecoveryConsumptionHandle)? {
268269
if let (kind, handle) = self.at(anyIn: subset) {
269270
return (kind, RecoveryConsumptionHandle(unexpectedTokens: 0, tokenConsumptionHandle: handle))
270271
}
271272
var lookahead = self.lookahead()
272-
return lookahead.canRecoverTo(anyIn: subset)
273+
return lookahead.canRecoverTo(anyIn: subset, recoveryPrecedence: recoveryPrecedence)
273274
}
274275

275276
/// Eat a token that we know we are currently positioned at, based on `canRecoverTo(anyIn:)`.

Tests/SwiftParserTest/DeclarationTests.swift

Lines changed: 80 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1215,6 +1215,86 @@ final class DeclarationTests: XCTestCase {
12151215
// `actor` cannot recover from a missing identifier since it's contextual
12161216
// based on the presence of the identifier.
12171217
}
1218+
1219+
func testDontNestClassesIfTheyContainUnexpectedTokens() {
1220+
// There used to be a bug where `class B` was parsed as a nested class of A
1221+
// because recovery to the `class` keyword of B consumed the closing brace.
1222+
AssertParse(
1223+
"""
1224+
class A {
1225+
1️⃣^2️⃣
1226+
}
1227+
class B {
1228+
}
1229+
""",
1230+
substructure: Syntax(CodeBlockItemListSyntax([
1231+
CodeBlockItemSyntax(
1232+
item: .init(ClassDeclSyntax(
1233+
attributes: nil,
1234+
modifiers: nil,
1235+
classKeyword: .classKeyword(),
1236+
identifier: .identifier("A"),
1237+
genericParameterClause: nil,
1238+
inheritanceClause: nil,
1239+
genericWhereClause: nil,
1240+
members: MemberDeclBlockSyntax(
1241+
leftBrace: .leftBraceToken(),
1242+
members: MemberDeclListSyntax([
1243+
MemberDeclListItemSyntax(
1244+
decl: Decl(FunctionDeclSyntax(
1245+
attributes: nil,
1246+
modifiers: nil,
1247+
funcKeyword: .funcKeyword(presence: .missing),
1248+
identifier: .spacedBinaryOperator("^"),
1249+
genericParameterClause: nil,
1250+
signature: FunctionSignatureSyntax(
1251+
input: ParameterClauseSyntax(
1252+
leftParen: .leftParenToken(presence: .missing),
1253+
parameterList: FunctionParameterListSyntax([]),
1254+
rightParen: .rightParenToken(presence: .missing)
1255+
),
1256+
asyncOrReasyncKeyword: nil,
1257+
throwsOrRethrowsKeyword: nil,
1258+
output: nil
1259+
),
1260+
genericWhereClause: nil,
1261+
body: nil
1262+
)),
1263+
semicolon: nil
1264+
)
1265+
]),
1266+
rightBrace: .rightBraceToken()
1267+
)
1268+
)),
1269+
semicolon: nil,
1270+
errorTokens: nil
1271+
),
1272+
CodeBlockItemSyntax(
1273+
item: .init(ClassDeclSyntax(
1274+
attributes: nil,
1275+
modifiers: nil,
1276+
classKeyword: .classKeyword(),
1277+
identifier: .identifier("B"),
1278+
genericParameterClause: nil,
1279+
inheritanceClause: nil,
1280+
genericWhereClause: nil,
1281+
members: MemberDeclBlockSyntax(
1282+
leftBrace: .leftBraceToken(),
1283+
members: MemberDeclListSyntax([]),
1284+
rightBrace: .rightBraceToken()
1285+
)
1286+
)),
1287+
semicolon: nil,
1288+
errorTokens: nil
1289+
)
1290+
])
1291+
),
1292+
diagnostics: [
1293+
DiagnosticSpec(locationMarker: "1️⃣", message: "expected 'func' in function"),
1294+
DiagnosticSpec(locationMarker: "2️⃣", message: "expected parameter clause in function signature"),
1295+
]
1296+
)
1297+
}
12181298
}
12191299

12201300
extension Parser.DeclAttributes {

0 commit comments

Comments
 (0)