Skip to content

Commit 97ecfb1

Browse files
committed
foundation for the recovery facility of misspelled keywords
fixes swiftlang#2198 swiftlang#2180
1 parent d992e0d commit 97ecfb1

File tree

8 files changed

+439
-11
lines changed

8 files changed

+439
-11
lines changed

Sources/SwiftParser/Declarations.swift

+22-6
Original file line numberDiff line numberDiff line change
@@ -209,9 +209,16 @@ extension Parser {
209209
)
210210

211211
let recoveryResult: (match: DeclarationKeyword, handle: RecoveryConsumptionHandle)?
212-
if let atResult = self.at(anyIn: DeclarationKeyword.self) {
212+
if let atResult = self.at(anyIn: EitherTokenSpecSet<DeclarationKeyword, MisspelledPureDeclarationKeyword>.self) {
213213
// We are at a keyword that starts a declaration. Parse that declaration.
214-
recoveryResult = (atResult.spec, .noRecovery(atResult.handle))
214+
let spec: DeclarationKeyword
215+
switch atResult.spec {
216+
case .lhs(let declarationKeyword):
217+
spec = declarationKeyword
218+
case .rhs(let mispelledDeclarationKeyword):
219+
spec = .lhs(mispelledDeclarationKeyword.correctSpecSet)
220+
}
221+
recoveryResult = (spec, .noRecovery(atResult.handle))
215222
} else if atFunctionDeclarationWithoutFuncKeyword() {
216223
// We aren't at a declaration keyword and it looks like we are at a function
217224
// declaration. Parse a function declaration.
@@ -885,7 +892,10 @@ extension Parser {
885892
_ attrs: DeclAttributes,
886893
_ handle: RecoveryConsumptionHandle
887894
) -> RawAssociatedTypeDeclSyntax {
888-
let (unexpectedBeforeAssocKeyword, assocKeyword) = self.eat(handle)
895+
let (unexpectedBeforeAssocKeyword, assocKeyword) = self.expect(
896+
keyword: .associatedtype,
897+
handle: handle
898+
)
889899

890900
// Detect an attempt to use a type parameter pack.
891901
let eachKeyword = self.consume(if: .keyword(.each))
@@ -1018,7 +1028,10 @@ extension Parser {
10181028
_ attrs: DeclAttributes,
10191029
_ handle: RecoveryConsumptionHandle
10201030
) -> RawDeinitializerDeclSyntax {
1021-
let (unexpectedBeforeDeinitKeyword, deinitKeyword) = self.eat(handle)
1031+
let (unexpectedBeforeDeinitKeyword, deinitKeyword) = self.expect(
1032+
keyword: .deinit,
1033+
handle: handle
1034+
)
10221035

10231036
var unexpectedNameAndSignature: [RawSyntax?] = []
10241037

@@ -1406,7 +1419,7 @@ extension Parser {
14061419
// Check there is an identifier before consuming
14071420
var look = self.lookahead()
14081421
let _ = look.consumeAttributeList()
1409-
let hasModifier = look.consume(ifAnyIn: AccessorModifier.self) != nil
1422+
let hasModifier = look.consume(ifAnyIn: MisspelledAccessorModifier.FuzzyMatchSpecSet.self) != nil
14101423
guard let (kind, _) = look.at(anyIn: AccessorDeclSyntax.AccessorSpecifierOptions.self) ?? forcedKind else {
14111424
return nil
14121425
}
@@ -1417,7 +1430,10 @@ extension Parser {
14171430
// get and set.
14181431
let modifier: RawDeclModifierSyntax?
14191432
if hasModifier {
1420-
let (unexpectedBeforeName, name) = self.expect(anyIn: AccessorModifier.self, default: .mutating)
1433+
let (unexpectedBeforeName, name) = self.expectPossibleMisspelling(
1434+
anyIn: MisspelledAccessorModifier.self,
1435+
default: .mutating
1436+
)
14211437
modifier = RawDeclModifierSyntax(
14221438
unexpectedBeforeName,
14231439
name: name,

Sources/SwiftParser/Parser.swift

+50
Original file line numberDiff line numberDiff line change
@@ -773,6 +773,56 @@ extension Parser {
773773
return self.eat(recoveryHandle)
774774
}
775775
}
776+
777+
mutating func expect(
778+
spec: TokenSpec,
779+
handle: RecoveryConsumptionHandle
780+
) -> (unexpectedBeforeKeyword: RawUnexpectedNodesSyntax?, keywordToken: RawTokenSyntax) {
781+
let tokenKind = spec.synthesizedTokenKind
782+
if case .keyword(let keyword) = spec.synthesizedTokenKind {
783+
return self.expect(keyword: keyword, handle: handle)
784+
} else {
785+
return (nil, missingToken(tokenKind.decomposeToRaw().rawKind, text: tokenKind.defaultText))
786+
}
787+
}
788+
789+
mutating func expect(
790+
keyword: Keyword,
791+
handle: RecoveryConsumptionHandle
792+
) -> (unexpectedBeforeKeyword: RawUnexpectedNodesSyntax?, keywordToken: RawTokenSyntax) {
793+
var (unexpectedBeforeKeyword, keywordToken) = self.eat(handle)
794+
795+
if keywordToken.tokenText != keyword.defaultText {
796+
if let _ = unexpectedBeforeKeyword {
797+
unexpectedBeforeKeyword = RawUnexpectedNodesSyntax(
798+
combining: unexpectedBeforeKeyword,
799+
keywordToken,
800+
arena: self.arena
801+
)
802+
} else {
803+
unexpectedBeforeKeyword = RawUnexpectedNodesSyntax([keywordToken], arena: self.arena)
804+
}
805+
keywordToken = missingToken(keyword)
806+
}
807+
808+
return (unexpectedBeforeKeyword, keywordToken)
809+
}
810+
811+
mutating func expectPossibleMisspelling<T: MisspelledTokenSpecSet>(
812+
anyIn: T.Type,
813+
default defaultKind: T.CorrectSpecSet
814+
) -> (RawUnexpectedNodesSyntax?, RawTokenSyntax) {
815+
if let (spec, handle) = self.at(anyIn: T.FuzzyMatchSpecSet.self) {
816+
switch spec {
817+
case .lhs(let misspelled):
818+
return self.expect(spec: misspelled.correctSpecSet.spec, handle: .noRecovery(handle))
819+
case .rhs:
820+
return self.eat(.noRecovery(handle))
821+
}
822+
} else {
823+
return (nil, missingToken(defaultKind.spec))
824+
}
825+
}
776826
}
777827

778828
// MARK: Splitting Tokens

Sources/SwiftParser/Statements.swift

+9-4
Original file line numberDiff line numberDiff line change
@@ -73,7 +73,10 @@ extension Parser {
7373
}
7474

7575
let optLabel = self.parseOptionalStatementLabel()
76-
switch self.canRecoverTo(anyIn: CanBeStatementStart.self) {
76+
let recovery = self.canRecoverTo(anyIn: MisspelledCanBeStatementStart.FuzzyMatchSpecSet.self).map {
77+
($0.match.correctSpecSet, $0.handle)
78+
}
79+
switch recovery {
7780
case (.for, let handle)?:
7881
return label(self.parseForStatement(forHandle: handle), with: optLabel)
7982
case (.while, let handle)?:
@@ -140,7 +143,7 @@ extension Parser {
140143
extension Parser {
141144
/// Parse a guard statement.
142145
mutating func parseGuardStatement(guardHandle: RecoveryConsumptionHandle) -> RawGuardStmtSyntax {
143-
let (unexpectedBeforeGuardKeyword, guardKeyword) = self.eat(guardHandle)
146+
let (unexpectedBeforeGuardKeyword, guardKeyword) = self.expect(keyword: .guard, handle: guardHandle)
144147
let conditions = self.parseConditionList(isGuardStatement: true)
145148
let (unexpectedBeforeElseKeyword, elseKeyword) = self.expect(.keyword(.else))
146149
let body = self.parseCodeBlock(introducer: guardKeyword)
@@ -915,10 +918,12 @@ extension Parser.Lookahead {
915918
_ = self.consume(if: .identifier, followedBy: .colon)
916919
let switchSubject: CanBeStatementStart?
917920
if allowRecovery {
918-
switchSubject = self.canRecoverTo(anyIn: CanBeStatementStart.self)?.0
921+
switchSubject =
922+
self.canRecoverTo(anyIn: MisspelledCanBeStatementStart.FuzzyMatchSpecSet.self)?.match.correctSpecSet
919923
} else {
920-
switchSubject = self.at(anyIn: CanBeStatementStart.self)?.0
924+
switchSubject = self.at(anyIn: MisspelledCanBeStatementStart.FuzzyMatchSpecSet.self)?.spec.correctSpecSet
921925
}
926+
922927
switch switchSubject {
923928
case .return?,
924929
.throw?,

Sources/SwiftParser/TokenSpecSet.swift

+164-1
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,20 @@ protocol TokenSpecSet: CaseIterable {
2626
init?(lexeme: Lexer.Lexeme, experimentalFeatures: Parser.ExperimentalFeatures)
2727
}
2828

29+
protocol MisspelledTokenSpecSet: TokenSpecSet {
30+
associatedtype CorrectSpecSet: TokenSpecSet
31+
32+
typealias FuzzyMatchSpecSet = EitherTokenSpecSet<Self, CorrectSpecSet>
33+
34+
var correctSpecSet: CorrectSpecSet { get }
35+
}
36+
37+
extension MisspelledTokenSpecSet {
38+
var spec: TokenSpec {
39+
TokenSpec(.identifier, recoveryPrecedence: correctSpecSet.spec.recoveryPrecedence)
40+
}
41+
}
42+
2943
/// A way to combine two token spec sets into an aggregate token spec set.
3044
enum EitherTokenSpecSet<LHS: TokenSpecSet, RHS: TokenSpecSet>: TokenSpecSet {
3145
case lhs(LHS)
@@ -58,6 +72,17 @@ enum EitherTokenSpecSet<LHS: TokenSpecSet, RHS: TokenSpecSet>: TokenSpecSet {
5872
}
5973
}
6074

75+
extension EitherTokenSpecSet where LHS: MisspelledTokenSpecSet, RHS == LHS.CorrectSpecSet {
76+
var correctSpecSet: RHS {
77+
switch self {
78+
case .lhs(let misspelled):
79+
return misspelled.correctSpecSet
80+
case .rhs(let correct):
81+
return correct
82+
}
83+
}
84+
}
85+
6186
// MARK: - Subsets
6287

6388
enum AccessorModifier: TokenSpecSet {
@@ -89,6 +114,42 @@ enum AccessorModifier: TokenSpecSet {
89114
}
90115
}
91116

117+
enum MisspelledAccessorModifier: MisspelledTokenSpecSet {
118+
case consuming
119+
case borrowing
120+
case nonmutating
121+
122+
var correctSpecSet: AccessorModifier {
123+
switch self {
124+
case .consuming: return .consuming
125+
case .borrowing: return .borrowing
126+
case .nonmutating: return .nonmutating
127+
}
128+
}
129+
130+
init?(lexeme: Lexer.Lexeme, experimentalFeatures: Parser.ExperimentalFeatures) {
131+
let text = lexeme.tokenText
132+
switch text.count {
133+
case 6:
134+
switch text {
135+
case "borrow": self = .borrowing
136+
default: return nil
137+
}
138+
case 7:
139+
switch text {
140+
case "consume": self = .consuming
141+
default: return nil
142+
}
143+
case 11:
144+
switch text {
145+
case "nonMutating": self = .nonmutating
146+
default: return nil
147+
}
148+
default: return nil
149+
}
150+
}
151+
}
152+
92153
enum CanBeStatementStart: TokenSpecSet {
93154
case `break`
94155
case `continue`
@@ -151,6 +212,35 @@ enum CanBeStatementStart: TokenSpecSet {
151212
}
152213
}
153214

215+
enum MisspelledCanBeStatementStart: MisspelledTokenSpecSet {
216+
case `guard`
217+
case `switch`
218+
219+
var correctSpecSet: CanBeStatementStart {
220+
switch self {
221+
case .guard: return .guard
222+
case .switch: return .switch
223+
}
224+
}
225+
226+
init?(lexeme: Lexer.Lexeme, experimentalFeatures: Parser.ExperimentalFeatures) {
227+
let text = lexeme.tokenText
228+
switch text.count {
229+
case 5:
230+
switch text {
231+
case "gaurd": self = .guard
232+
default: return nil
233+
}
234+
case 6:
235+
switch text {
236+
case "siwtch": self = .switch
237+
default: return nil
238+
}
239+
default: return nil
240+
}
241+
}
242+
}
243+
154244
enum CompilationCondition: TokenSpecSet {
155245
case swift
156246
case compiler
@@ -266,7 +356,6 @@ enum ContextualDeclKeyword: TokenSpecSet {
266356
}
267357
}
268358
}
269-
270359
/// A `DeclarationKeyword` that is not a `ValueBindingPatternSyntax.BindingSpecifierOptions`.
271360
///
272361
/// `ValueBindingPatternSyntax.BindingSpecifierOptions` are injected into
@@ -339,6 +428,80 @@ enum PureDeclarationKeyword: TokenSpecSet {
339428
}
340429
}
341430

431+
enum MisspelledPureDeclarationKeyword: MisspelledTokenSpecSet {
432+
case `associatedtype`
433+
case `class`
434+
case `deinit`
435+
case `func`
436+
case `init`
437+
case `precedencegroup`
438+
case `protocol`
439+
case `typealias`
440+
441+
var correctSpecSet: PureDeclarationKeyword {
442+
switch self {
443+
case .associatedtype: return .associatedtype
444+
case .class: return .class
445+
case .deinit: return .deinit
446+
case .func: return .func
447+
case .`init`: return .`init`
448+
case .precedencegroup: return .precedencegroup
449+
case .protocol: return .protocol
450+
case .typealias: return .typealias
451+
}
452+
}
453+
454+
init?(lexeme: Lexer.Lexeme, experimentalFeatures: Parser.ExperimentalFeatures) {
455+
let text = lexeme.tokenText
456+
switch text.count {
457+
case 3:
458+
switch text {
459+
case "def": self = .func
460+
case "fun": self = .func
461+
default: return nil
462+
}
463+
case 6:
464+
switch text {
465+
case "deInit": self = .deinit
466+
case "object": self = .class
467+
default: return nil
468+
}
469+
case 8:
470+
switch text {
471+
case "function": self = .func
472+
default: return nil
473+
}
474+
case 9:
475+
switch text {
476+
case "interface": self = .protocol
477+
case "typeAlias": self = .typealias
478+
default: return nil
479+
}
480+
case 11:
481+
switch text {
482+
case "constructor": self = .`init`
483+
default: return nil
484+
}
485+
case 13:
486+
switch text {
487+
case "associatetype", "associateType": self = .associatedtype
488+
default: return nil
489+
}
490+
case 14:
491+
switch text {
492+
case "associatedType": self = .associatedtype
493+
default: return nil
494+
}
495+
case 15:
496+
switch text {
497+
case "precedenceGroup": self = .precedencegroup
498+
default: return nil
499+
}
500+
default: return nil
501+
}
502+
}
503+
}
504+
342505
typealias DeclarationKeyword = EitherTokenSpecSet<
343506
PureDeclarationKeyword,
344507
ValueBindingPatternSyntax.BindingSpecifierOptions

0 commit comments

Comments
 (0)