Skip to content

Commit f6db3c5

Browse files
authored
Merge pull request #1023 from ahoppen/ahoppen/no-nested-class-recovery
Don’t eat closing braces while trying to recover to a declaration start keyword
2 parents 43f7095 + 2e7b363 commit f6db3c5

File tree

6 files changed

+98
-10
lines changed

6 files changed

+98
-10
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:)`.

Sources/_SwiftSyntaxTestSupport/SyntaxComparison.swift

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -45,8 +45,8 @@ extension TreeDifference: CustomDebugStringConvertible {
4545
public var debugDescription: String {
4646
let includeTrivia = reason == .trivia
4747

48-
let expectedConverter = SourceLocationConverter(file: "Baseline.swift", source: baseline.description)
49-
let actualConverter = SourceLocationConverter(file: "Actual.swift", source: node.description)
48+
let expectedConverter = SourceLocationConverter(file: "Baseline.swift", tree: baseline.root)
49+
let actualConverter = SourceLocationConverter(file: "Actual.swift", tree: node.root)
5050

5151
let expectedDesc = baseline.debugDescription(includeTrivia: includeTrivia, converter: expectedConverter)
5252
let actualDesc = node.debugDescription(includeTrivia: includeTrivia, converter: actualConverter)

Sources/_SwiftSyntaxTestSupport/SyntaxProtocol+Initializer.swift

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -62,11 +62,11 @@ private extension TriviaPiece {
6262
let (label, value) = Mirror(reflecting: self).children.first!
6363
switch value {
6464
case let value as String:
65-
return FunctionCallExpr(calledExpression: MemberAccessExpr(name: label!)) {
65+
return FunctionCallExpr(callee: ".\(label!)") {
6666
TupleExprElement(expression: StringLiteralExpr(content: value))
6767
}
6868
case let value as Int:
69-
return FunctionCallExpr(calledExpression: MemberAccessExpr(name: label!)) {
69+
return FunctionCallExpr(callee: ".\(label!)") {
7070
TupleExprElement(expression: IntegerLiteralExpr(value))
7171
}
7272
default:
@@ -106,7 +106,7 @@ extension SyntaxProtocol {
106106
private var debugInitCallExpr: ExprSyntaxProtocol {
107107
let mirror = Mirror(reflecting: self)
108108
if self.isCollection {
109-
return FunctionCallExpr(calledExpression: IdentifierExpr(String("\(type(of: self))"))) {
109+
return FunctionCallExpr(callee: "\(type(of: self))") {
110110
TupleExprElement(
111111
expression: ArrayExpr() {
112112
for child in mirror.children {
@@ -131,7 +131,7 @@ extension SyntaxProtocol {
131131
tokenInitializerName = String(tokenKindStr[..<tokenKindStr.firstIndex(of: "(")!])
132132
requiresExplicitText = true
133133
}
134-
return FunctionCallExpr(calledExpression: MemberAccessExpr(name: tokenInitializerName)) {
134+
return FunctionCallExpr(callee: ".\(tokenInitializerName)") {
135135
if requiresExplicitText {
136136
TupleExprElement(
137137
expression: StringLiteralExpr(content: token.text)
@@ -160,7 +160,7 @@ extension SyntaxProtocol {
160160
}
161161
}
162162
} else {
163-
return FunctionCallExpr(calledExpression: IdentifierExpr(String("\(type(of: self))"))) {
163+
return FunctionCallExpr(callee: "\(type(of: self))") {
164164
for child in mirror.children {
165165
let label = child.label!
166166
let value = child.value as! SyntaxProtocol?

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 {

Tests/SwiftParserTest/ParserTests.swift

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -160,6 +160,8 @@ public class ParserTests: XCTestCase {
160160
|| fileURL.absoluteString.contains("26773-swift-diagnosticengine-diagnose.swift")
161161
|| fileURL.absoluteString.contains("parser-cutoff.swift")
162162
|| fileURL.absoluteString.contains("26233-swift-iterabledeclcontext-getmembers.swift")
163+
|| fileURL.absoluteString.contains("26190-swift-iterabledeclcontext-getmembers.swift")
164+
|| fileURL.absoluteString.contains("26139-swift-abstractclosureexpr-setparams.swift")
163165
}
164166
)
165167
}

0 commit comments

Comments
 (0)