Skip to content

Commit e8e50f8

Browse files
authored
Merge pull request #2209 from kimdv/kimdv/handle-missing-func-keyword
Handle missing `func` keyword
2 parents b9e9b82 + c77ab06 commit e8e50f8

File tree

5 files changed

+85
-57
lines changed

5 files changed

+85
-57
lines changed

Sources/SwiftParser/Declarations.swift

Lines changed: 37 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -206,11 +206,24 @@ extension Parser {
206206
modifiers: self.parseDeclModifierList()
207207
)
208208

209-
// If we are inside a memberDecl list, we don't want to eat closing braces (which most likely close the outer context)
210-
// while recovering to the declaration start.
211-
let recoveryPrecedence = inMemberDeclList ? TokenPrecedence.closingBrace : nil
209+
let recoveryResult: (match: DeclarationKeyword, handle: RecoveryConsumptionHandle)?
210+
if let atResult = self.at(anyIn: DeclarationKeyword.self) {
211+
// We are at a keyword that starts a declaration. Parse that declaration.
212+
recoveryResult = (atResult.spec, .noRecovery(atResult.handle))
213+
} else if atFunctionDeclarationWithoutFuncKeyword() {
214+
// We aren't at a declaration keyword and it looks like we are at a function
215+
// declaration. Parse a function declaration.
216+
recoveryResult = (.lhs(.func), .missing(.keyword(.func)))
217+
} else {
218+
// In all other cases, use standard token recovery to find the declaration
219+
// to parse.
220+
// If we are inside a memberDecl list, we don't want to eat closing braces (which most likely close the outer context)
221+
// while recovering to the declaration start.
222+
let recoveryPrecedence = inMemberDeclList ? TokenPrecedence.closingBrace : nil
223+
recoveryResult = self.canRecoverTo(anyIn: DeclarationKeyword.self, overrideRecoveryPrecedence: recoveryPrecedence)
224+
}
212225

213-
switch self.canRecoverTo(anyIn: DeclarationKeyword.self, overrideRecoveryPrecedence: recoveryPrecedence) {
226+
switch recoveryResult {
214227
case (.lhs(.import), let handle)?:
215228
return RawDeclSyntax(self.parseImportDeclaration(attrs, handle))
216229
case (.lhs(.class), let handle)?:
@@ -273,10 +286,7 @@ extension Parser {
273286
)
274287
}
275288

276-
let isPossibleFuncIdentifier = self.at(.identifier, .wildcard)
277-
let isPossibleFuncParen = self.peek(isAt: .leftParen, .binaryOperator)
278-
// Treat operators specially because they're likely to be functions.
279-
if (isPossibleFuncIdentifier && isPossibleFuncParen) || self.at(anyIn: Operator.self) != nil {
289+
if atFunctionDeclarationWithoutFuncKeyword() {
280290
return RawDeclSyntax(self.parseFuncDeclaration(attrs, .missing(.keyword(.func))))
281291
}
282292
}
@@ -288,6 +298,24 @@ extension Parser {
288298
)
289299
)
290300
}
301+
302+
/// Returns `true` if it looks like the parser is positioned at a function declaration that’s missing the `func` keyword.
303+
fileprivate mutating func atFunctionDeclarationWithoutFuncKeyword() -> Bool {
304+
var nextTokenIsLeftParenOrLeftAngle: Bool {
305+
self.peek(isAt: .leftParen) || self.peek().tokenText.hasPrefix("<")
306+
}
307+
308+
if self.at(.identifier) {
309+
return nextTokenIsLeftParenOrLeftAngle
310+
} else if self.at(anyIn: Operator.self) != nil {
311+
if self.currentToken.tokenText.hasSuffix("<") && self.peek(isAt: .identifier) {
312+
return true
313+
}
314+
return nextTokenIsLeftParenOrLeftAngle
315+
} else {
316+
return false
317+
}
318+
}
291319
}
292320

293321
extension Parser {
@@ -1044,7 +1072,7 @@ extension Parser {
10441072
let identifier: RawTokenSyntax
10451073
if self.at(anyIn: Operator.self) != nil || self.at(.exclamationMark, .prefixAmpersand) {
10461074
var name = self.currentToken.tokenText
1047-
if !currentToken.isEditorPlaceholder && name.count > 1 && name.hasSuffix("<") && self.peek(isAt: .identifier) {
1075+
if !currentToken.isEditorPlaceholder && name.hasSuffix("<") && self.peek(isAt: .identifier) {
10481076
name = SyntaxText(rebasing: name.dropLast())
10491077
}
10501078
unexpectedBeforeIdentifier = nil

Sources/SwiftParser/Parser.swift

Lines changed: 2 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -445,10 +445,7 @@ extension Parser {
445445
@inline(__always)
446446
mutating func canRecoverTo(_ spec: TokenSpec) -> RecoveryConsumptionHandle? {
447447
if self.at(spec) {
448-
return RecoveryConsumptionHandle(
449-
unexpectedTokens: 0,
450-
tokenConsumptionHandle: TokenConsumptionHandle(spec: spec)
451-
)
448+
return .constant(spec)
452449
}
453450
var lookahead = self.lookahead()
454451
return lookahead.canRecoverTo(spec)
@@ -465,7 +462,7 @@ extension Parser {
465462
overrideRecoveryPrecedence: TokenPrecedence? = nil
466463
) -> (match: SpecSet, handle: RecoveryConsumptionHandle)? {
467464
if let (kind, handle) = self.at(anyIn: specSet) {
468-
return (kind, RecoveryConsumptionHandle(unexpectedTokens: 0, tokenConsumptionHandle: handle))
465+
return (kind, .noRecovery(handle))
469466
}
470467
var lookahead = self.lookahead()
471468
return lookahead.canRecoverTo(anyIn: specSet, overrideRecoveryPrecedence: overrideRecoveryPrecedence)

Sources/SwiftParser/Recovery.swift

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,13 +21,29 @@ struct RecoveryConsumptionHandle {
2121
var unexpectedTokens: Int
2222
var tokenConsumptionHandle: TokenConsumptionHandle
2323

24+
fileprivate init(unexpectedTokens: Int, tokenConsumptionHandle: TokenConsumptionHandle) {
25+
self.unexpectedTokens = unexpectedTokens
26+
self.tokenConsumptionHandle = tokenConsumptionHandle
27+
}
28+
29+
/// A `RecoveryConsumptionHandle` that doesn't skip over any unexpected tokens
30+
/// and consumes a token matching `spec`.
2431
static func constant(_ spec: TokenSpec) -> RecoveryConsumptionHandle {
2532
return RecoveryConsumptionHandle(
2633
unexpectedTokens: 0,
2734
tokenConsumptionHandle: TokenConsumptionHandle(spec: spec)
2835
)
2936
}
3037

38+
/// A `RecoveryConsumptionHandle` that doesn't skip over any unexpected tokens
39+
/// and consumes `handle`.
40+
static func noRecovery(_ handle: TokenConsumptionHandle) -> RecoveryConsumptionHandle {
41+
return RecoveryConsumptionHandle(
42+
unexpectedTokens: 0,
43+
tokenConsumptionHandle: handle
44+
)
45+
}
46+
3147
/// A `RecoveryConsumptionHandle` that will not eat any tokens but instead
3248
/// synthesize a missing token of kind `token`.
3349
static func missing(_ spec: TokenSpec) -> RecoveryConsumptionHandle {

Tests/SwiftParserTest/DeclarationTests.swift

Lines changed: 6 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -2015,7 +2015,7 @@ final class DeclarationTests: ParserTestCase {
20152015
assertParse(
20162016
"""
20172017
class A {
2018-
1️⃣^2️⃣
2018+
1️⃣^
20192019
}
20202020
class B {
20212021
}
@@ -2028,23 +2028,8 @@ final class DeclarationTests: ParserTestCase {
20282028
name: .identifier("A"),
20292029
memberBlock: MemberBlockSyntax(
20302030
leftBrace: .leftBraceToken(),
2031-
members: MemberBlockItemListSyntax([
2032-
MemberBlockItemSyntax(
2033-
decl: DeclSyntax(
2034-
FunctionDeclSyntax(
2035-
funcKeyword: .keyword(.func, presence: .missing),
2036-
name: .binaryOperator("^"),
2037-
signature: FunctionSignatureSyntax(
2038-
parameterClause: FunctionParameterClauseSyntax(
2039-
leftParen: .leftParenToken(presence: .missing),
2040-
parameters: FunctionParameterListSyntax([]),
2041-
rightParen: .rightParenToken(presence: .missing)
2042-
)
2043-
)
2044-
)
2045-
)
2046-
)
2047-
]),
2031+
members: MemberBlockItemListSyntax(),
2032+
UnexpectedNodesSyntax([TokenSyntax.binaryOperator("^")]),
20482033
rightBrace: .rightBraceToken()
20492034
)
20502035
)
@@ -2057,7 +2042,7 @@ final class DeclarationTests: ParserTestCase {
20572042
name: .identifier("B"),
20582043
memberBlock: MemberBlockSyntax(
20592044
leftBrace: .leftBraceToken(),
2060-
members: MemberBlockItemListSyntax([]),
2045+
members: MemberBlockItemListSyntax(),
20612046
rightBrace: .rightBraceToken()
20622047
)
20632048
)
@@ -2066,16 +2051,8 @@ final class DeclarationTests: ParserTestCase {
20662051
]
20672052
),
20682053
diagnostics: [
2069-
DiagnosticSpec(locationMarker: "1️⃣", message: "expected 'func' in function", fixIts: ["insert 'func'"]),
2070-
DiagnosticSpec(locationMarker: "2️⃣", message: "expected parameter clause in function signature", fixIts: ["insert parameter clause"]),
2071-
],
2072-
fixedSource: """
2073-
class A {
2074-
func ^ ()
2075-
}
2076-
class B {
2077-
}
2078-
"""
2054+
DiagnosticSpec(locationMarker: "1️⃣", message: "unexpected code '^' in class")
2055+
]
20792056
)
20802057
}
20812058

Tests/SwiftParserTest/translated/RecoveryTests.swift

Lines changed: 24 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -2107,13 +2107,7 @@ final class RecoveryTests: ParserTestCase {
21072107
)
21082108
}
21092109

2110-
func testRecovery120() {
2111-
assertParse(
2112-
"""
2113-
//===--- Recovery for wrong decl introducer keyword.
2114-
"""
2115-
)
2116-
}
2110+
// MARK: - Recovery for wrong decl introducer keyword.
21172111

21182112
func testRecovery121() {
21192113
assertParse(
@@ -2125,20 +2119,36 @@ final class RecoveryTests: ParserTestCase {
21252119
}
21262120
""",
21272121
diagnostics: [
2128-
// TODO: Old parser expected error on line 2: expected 'func' keyword in instance method declaration
2129-
DiagnosticSpec(message: "unexpected code 'notAKeyword() {}' before function")
2130-
]
2122+
DiagnosticSpec(message: "expected 'func' in function", fixIts: ["insert 'func'"])
2123+
],
2124+
fixedSource: """
2125+
class WrongDeclIntroducerKeyword1 {
2126+
func notAKeyword() {}
2127+
func foo() {}
2128+
class func bar() {}
2129+
}
2130+
"""
21312131
)
2132-
}
21332132

2134-
func testRecovery122() {
21352133
assertParse(
21362134
"""
2137-
//===--- Recovery for wrong inheritance clause.
2138-
"""
2135+
class WrongDeclIntroducerKeyword1 {
2136+
1️⃣notAKeyword() {}
2137+
var x: Int
2138+
}
2139+
""",
2140+
diagnostics: [DiagnosticSpec(message: "expected 'func' in function", fixIts: ["insert 'func'"])],
2141+
fixedSource: """
2142+
class WrongDeclIntroducerKeyword1 {
2143+
func notAKeyword() {}
2144+
var x: Int
2145+
}
2146+
"""
21392147
)
21402148
}
21412149

2150+
// MARK: - Recovery for wrong inheritance clause.
2151+
21422152
func testRecovery123() {
21432153
assertParse(
21442154
"""

0 commit comments

Comments
 (0)