Skip to content

Commit 1bde29c

Browse files
committed
Handle missing func keyword
1 parent ad237ff commit 1bde29c

File tree

4 files changed

+79
-28
lines changed

4 files changed

+79
-28
lines changed

Sources/SwiftParser/Declarations.swift

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

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

215-
switch self.canRecoverTo(anyIn: DeclarationKeyword.self, overrideRecoveryPrecedence: recoveryPrecedence) {
228+
switch recoveryResult {
216229
case (.lhs(.import), let handle)?:
217230
return RawDeclSyntax(self.parseImportDeclaration(attrs, handle))
218231
case (.lhs(.class), let handle)?:
@@ -283,10 +296,7 @@ extension Parser {
283296
)
284297
}
285298

286-
let isPossibleFuncIdentifier = self.at(.identifier, .wildcard)
287-
let isPossibleFuncParen = self.peek(isAt: .leftParen, .binaryOperator)
288-
// Treat operators specially because they're likely to be functions.
289-
if (isPossibleFuncIdentifier && isPossibleFuncParen) || self.at(anyIn: Operator.self) != nil {
299+
if atFunctionDeclarationWithoutFuncKeyword() {
290300
return RawDeclSyntax(self.parseFuncDeclaration(attrs, .missing(.keyword(.func))))
291301
}
292302
}
@@ -298,6 +308,24 @@ extension Parser {
298308
)
299309
)
300310
}
311+
312+
/// Returns `true` if it looks like the parser is positioned at a function declaration that’s missing the `func` keyword.
313+
fileprivate mutating func atFunctionDeclarationWithoutFuncKeyword() -> Bool {
314+
var nextTokenIsLeftParenOrLeftAngle: Bool {
315+
self.peek(isAt: .leftParen) || self.peek().tokenText.hasPrefix("<")
316+
}
317+
318+
if self.at(.identifier) {
319+
return nextTokenIsLeftParenOrLeftAngle
320+
} else if self.at(anyIn: Operator.self) != nil {
321+
if self.currentToken.tokenText.hasSuffix("<") && self.peek(isAt: .identifier) {
322+
return true
323+
}
324+
return nextTokenIsLeftParenOrLeftAngle
325+
} else {
326+
return false
327+
}
328+
}
301329
}
302330

303331
extension Parser {
@@ -1053,7 +1081,7 @@ extension Parser {
10531081
let identifier: RawTokenSyntax
10541082
if self.at(anyIn: Operator.self) != nil || self.at(.exclamationMark, .prefixAmpersand) {
10551083
var name = self.currentToken.tokenText
1056-
if name.count > 1 && name.hasSuffix("<") && self.peek(isAt: .identifier) {
1084+
if name.hasSuffix("<") && self.peek(isAt: .identifier) {
10571085
name = SyntaxText(rebasing: name.dropLast())
10581086
}
10591087
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/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)