Skip to content

Commit 7cef632

Browse files
Added recovery logic for parsing output clause in deinit
1 parent c96e457 commit 7cef632

File tree

5 files changed

+314
-1
lines changed

5 files changed

+314
-1
lines changed

Sources/SwiftParser/Declarations.swift

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1141,6 +1141,15 @@ extension Parser {
11411141

11421142
effectSpecifiers = parseDeinitEffectSpecifiers()
11431143
}
1144+
1145+
var unexpectedAfterAsync: [RawSyntax?] = []
1146+
/// Only allow recovery to the arrow with exprKeyword precedence so we only
1147+
/// skip over misplaced identifiers and don't e.g. recover to an arrow in a 'where' clause.
1148+
if self.at(.arrow) || self.canRecoverTo(TokenSpec(.arrow, recoveryPrecedence: .exprKeyword)) != nil {
1149+
let output = self.parseFunctionReturnClause(effectSpecifiers: &effectSpecifiers, allowNamedOpaqueResultType: true)
1150+
unexpectedAfterAsync.append(RawSyntax(output))
1151+
}
1152+
11441153
let items = self.parseOptionalCodeBlock()
11451154
return RawDeinitializerDeclSyntax(
11461155
attributes: attrs.attributes,
@@ -1149,7 +1158,7 @@ extension Parser {
11491158
deinitKeyword: deinitKeyword,
11501159
RawUnexpectedNodesSyntax(unexpectedNameAndSignature, arena: self.arena),
11511160
effectSpecifiers: effectSpecifiers,
1152-
nil,
1161+
RawUnexpectedNodesSyntax(unexpectedAfterAsync, arena: arena),
11531162
body: items,
11541163
arena: self.arena
11551164
)

Sources/SwiftParser/Specifiers.swift

Lines changed: 102 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -417,6 +417,108 @@ extension RawAccessorEffectSpecifiersSyntax: RawEffectSpecifiersTrait {
417417
}
418418
}
419419

420+
// This conformance exists solely to parse misplaced specifiers in parseFunctionReturnClause()
421+
extension RawDeinitEffectSpecifiersSyntax: RawEffectSpecifiersTrait {
422+
enum MisspelledAsyncTokenKinds: TokenSpecSet {
423+
case await
424+
case reasync
425+
426+
init?(lexeme: Lexer.Lexeme) {
427+
switch PrepareForKeywordMatch(lexeme) {
428+
case TokenSpec(.await, allowAtStartOfLine: false): self = .await
429+
case TokenSpec(.reasync): self = .reasync
430+
default: return nil
431+
}
432+
}
433+
434+
var spec: TokenSpec {
435+
switch self {
436+
case .await: return TokenSpec(.await, allowAtStartOfLine: false)
437+
case .reasync: return .keyword(.reasync)
438+
}
439+
}
440+
}
441+
442+
enum CorrectAsyncTokenKinds: TokenSpecSet {
443+
case async
444+
445+
init?(lexeme: Lexer.Lexeme) {
446+
switch PrepareForKeywordMatch(lexeme) {
447+
case TokenSpec(.async): self = .async
448+
default: return nil
449+
}
450+
}
451+
452+
var spec: TokenSpec {
453+
switch self {
454+
case .async: return .keyword(.async)
455+
}
456+
}
457+
}
458+
459+
enum MisspelledThrowsTokenKinds: TokenSpecSet {
460+
case `rethrows`
461+
case `try`
462+
case `throw`
463+
case `throws`
464+
465+
init?(lexeme: Lexer.Lexeme) {
466+
switch PrepareForKeywordMatch(lexeme) {
467+
case TokenSpec(.rethrows): self = .rethrows
468+
case TokenSpec(.try, allowAtStartOfLine: false): self = .try
469+
case TokenSpec(.throw, allowAtStartOfLine: false): self = .throw
470+
case TokenSpec(.throws): self = .throws
471+
default: return nil
472+
}
473+
}
474+
475+
var spec: TokenSpec {
476+
switch self {
477+
case .rethrows: return .keyword(.rethrows)
478+
case .try: return TokenSpec(.try, allowAtStartOfLine: false)
479+
case .throw: return TokenSpec(.throw, allowAtStartOfLine: false)
480+
case .throws: return .keyword(.throws)
481+
}
482+
}
483+
}
484+
485+
enum CorrectThrowsTokenKinds: TokenSpecSet {
486+
// Uninhabited
487+
488+
init?(lexeme: Lexer.Lexeme) {
489+
return nil
490+
}
491+
492+
var spec: TokenSpec {
493+
switch self {
494+
}
495+
}
496+
}
497+
498+
var unexpectedBetweenAsyncSpecifierAndThrowsSpecifier: RawUnexpectedNodesSyntax? { unexpectedAfterAsyncSpecifier }
499+
var throwsSpecifier: RawTokenSyntax? { nil }
500+
var unexpectedAfterThrowsSpecifier: RawUnexpectedNodesSyntax? { nil }
501+
502+
init(
503+
_ unexpectedBeforeAsyncSpecifier: RawUnexpectedNodesSyntax?,
504+
asyncSpecifier: RawTokenSyntax?,
505+
_ unexpectedBetweenAsyncSpecifierAndThrowsSpecifier: RawUnexpectedNodesSyntax?,
506+
throwsSpecifier: RawTokenSyntax?,
507+
_ unexpectedAfterThrowsSpecifier: RawUnexpectedNodesSyntax?,
508+
arena: __shared SwiftSyntax.SyntaxArena
509+
) {
510+
// Inserted missing throws is discarded
511+
assert(throwsSpecifier?.isMissing ?? true)
512+
assert(unexpectedAfterThrowsSpecifier == nil)
513+
self.init(
514+
unexpectedBeforeAsyncSpecifier,
515+
asyncSpecifier: asyncSpecifier,
516+
unexpectedBetweenAsyncSpecifierAndThrowsSpecifier,
517+
arena: arena
518+
)
519+
}
520+
}
521+
420522
extension TokenConsumer {
421523
mutating func at<SpecSet1: TokenSpecSet, SpecSet2: TokenSpecSet>(
422524
anyIn specSet1: SpecSet1.Type,

Sources/SwiftParserDiagnostics/ParseDiagnosticsGenerator.swift

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -837,6 +837,18 @@ public class ParseDiagnosticsGenerator: SyntaxAnyVisitor {
837837
handledNodes: [params.id]
838838
)
839839
}
840+
if let unexpected = node.unexpectedBetweenEffectSpecifiersAndBody,
841+
let returnType = unexpected.compactMap({ $0.as(ReturnClauseSyntax.self) }).only
842+
{
843+
addDiagnostic(
844+
returnType,
845+
.deinitCannotHaveReturnType,
846+
fixIts: [
847+
FixIt(message: RemoveNodesFixIt(returnType), changes: .makeMissing(returnType))
848+
],
849+
handledNodes: [returnType.id]
850+
)
851+
}
840852

841853
return .visitChildren
842854
}

Sources/SwiftParserDiagnostics/ParserDiagnosticMessages.swift

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -129,6 +129,9 @@ extension DiagnosticMessage where Self == StaticParserError {
129129
public static var deinitCannotThrow: Self {
130130
.init("deinitializers cannot throw")
131131
}
132+
public static var deinitCannotHaveReturnType: Self {
133+
.init("deinitializers cannot have return type")
134+
}
132135
public static var editorPlaceholderInSourceFile: Self {
133136
.init("editor placeholder in source file")
134137
}

Tests/SwiftParserTest/translated/InitDeinitTests.swift

Lines changed: 187 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -785,6 +785,193 @@ final class InitDeinitTests: XCTestCase {
785785
)
786786
}
787787

788+
func testDeinitOutput() {
789+
assertParse(
790+
"""
791+
class FooClassDeinitializerA {
792+
deinit 1️⃣-> Void {}
793+
}
794+
""",
795+
diagnostics: [
796+
DiagnosticSpec(locationMarker: "1️⃣", message: "deinitializers cannot have return type", fixIts: ["remove '->' and return type"])
797+
],
798+
fixedSource: """
799+
class FooClassDeinitializerA {
800+
deinit {}
801+
}
802+
"""
803+
)
804+
}
805+
806+
func testDeinitNameOutput() {
807+
assertParse(
808+
"""
809+
class FooClassDeinitializerA {
810+
deinit 1️⃣x 2️⃣-> Void {}
811+
}
812+
""",
813+
diagnostics: [
814+
DiagnosticSpec(locationMarker: "1️⃣", message: "deinitializers cannot have a name", fixIts: ["remove 'x'"]),
815+
DiagnosticSpec(locationMarker: "2️⃣", message: "deinitializers cannot have return type", fixIts: ["remove '->' and return type"]),
816+
],
817+
fixedSource: """
818+
class FooClassDeinitializerA {
819+
deinit {}
820+
}
821+
"""
822+
)
823+
}
824+
825+
func testDeinitParamsOutput() {
826+
assertParse(
827+
"""
828+
class FooClassDeinitializerA {
829+
deinit1️⃣() 2️⃣-> Void {}
830+
}
831+
""",
832+
diagnostics: [
833+
DiagnosticSpec(locationMarker: "1️⃣", message: "deinitializers cannot have parameters", fixIts: ["remove parameter clause"]),
834+
DiagnosticSpec(locationMarker: "2️⃣", message: "deinitializers cannot have return type", fixIts: ["remove '->' and return type"]),
835+
],
836+
fixedSource: """
837+
class FooClassDeinitializerA {
838+
deinit {}
839+
}
840+
"""
841+
)
842+
}
843+
844+
func testDeinitNameParamsOutput() {
845+
assertParse(
846+
"""
847+
class FooClassDeinitializerA {
848+
deinit 1️⃣x2️⃣() 3️⃣-> Void {}
849+
}
850+
""",
851+
diagnostics: [
852+
DiagnosticSpec(locationMarker: "1️⃣", message: "deinitializers cannot have a name", fixIts: ["remove 'x'"]),
853+
DiagnosticSpec(locationMarker: "2️⃣", message: "deinitializers cannot have parameters", fixIts: ["remove parameter clause"]),
854+
DiagnosticSpec(locationMarker: "3️⃣", message: "deinitializers cannot have return type", fixIts: ["remove '->' and return type"]),
855+
],
856+
fixedSource: """
857+
class FooClassDeinitializerA {
858+
deinit {}
859+
}
860+
"""
861+
)
862+
}
863+
864+
func testDeinitOutputAsync() {
865+
assertParse(
866+
"""
867+
class FooClassDeinitializerA {
868+
deinit 1️⃣-> async Void {}
869+
}
870+
""",
871+
diagnostics: [
872+
DiagnosticSpec(locationMarker: "1️⃣", message: "expected 'async' in effect specifiers", fixIts: ["insert 'async'"]),
873+
DiagnosticSpec(locationMarker: "1️⃣", message: "deinitializers cannot have return type", fixIts: ["remove '->', 'async', and return type"]),
874+
],
875+
fixedSource: """
876+
class FooClassDeinitializerA {
877+
deinit async {}
878+
}
879+
"""
880+
)
881+
}
882+
883+
func testDeinitAsyncOutputAsync() {
884+
assertParse(
885+
"""
886+
class FooClassDeinitializerA {
887+
deinit async 1️⃣-> async Void {}
888+
}
889+
""",
890+
diagnostics: [
891+
DiagnosticSpec(locationMarker: "1️⃣", message: "deinitializers cannot have return type", fixIts: ["remove '->', 'async', and return type"])
892+
],
893+
fixedSource: """
894+
class FooClassDeinitializerA {
895+
deinit async {}
896+
}
897+
"""
898+
)
899+
}
900+
901+
func testDeinitAwaitOutputAsync() {
902+
assertParse(
903+
"""
904+
class FooClassDeinitializerA {
905+
deinit 1️⃣await 2️⃣-> async Void {}
906+
}
907+
""",
908+
diagnostics: [
909+
DiagnosticSpec(locationMarker: "1️⃣", message: "expected async specifier; did you mean 'async'?", fixIts: ["replace 'await' with 'async'"]),
910+
DiagnosticSpec(locationMarker: "2️⃣", message: "deinitializers cannot have return type", fixIts: ["remove '->', 'async', and return type"]),
911+
],
912+
fixedSource: """
913+
class FooClassDeinitializerA {
914+
deinit async {}
915+
}
916+
"""
917+
)
918+
}
919+
920+
func testDeinitAsyncOutputAwait() {
921+
assertParse(
922+
"""
923+
class FooClassDeinitializerA {
924+
deinit async 1️⃣-> await Void {}
925+
}
926+
""",
927+
diagnostics: [
928+
DiagnosticSpec(locationMarker: "1️⃣", message: "deinitializers cannot have return type", fixIts: ["remove '->', 'await', and return type"])
929+
],
930+
fixedSource: """
931+
class FooClassDeinitializerA {
932+
deinit async {}
933+
}
934+
"""
935+
)
936+
}
937+
938+
func testDeinitOutputThrows() {
939+
assertParse(
940+
"""
941+
class FooClassDeinitializerA {
942+
deinit 1️⃣-> throws Void {}
943+
}
944+
""",
945+
diagnostics: [
946+
DiagnosticSpec(locationMarker: "1️⃣", message: "deinitializers cannot have return type", fixIts: ["remove '->', 'throws', and return type"])
947+
],
948+
fixedSource: """
949+
class FooClassDeinitializerA {
950+
deinit {}
951+
}
952+
"""
953+
)
954+
}
955+
956+
func testDeinitThrowsOutputThrows() {
957+
assertParse(
958+
"""
959+
class FooClassDeinitializerA {
960+
deinit 1️⃣throws 2️⃣-> throws Void {}
961+
}
962+
""",
963+
diagnostics: [
964+
DiagnosticSpec(locationMarker: "1️⃣", message: "deinitializers cannot throw", fixIts: ["remove 'throws'"]),
965+
DiagnosticSpec(locationMarker: "2️⃣", message: "deinitializers cannot have return type", fixIts: ["remove '->', 'throws', and return type"]),
966+
],
967+
fixedSource: """
968+
class FooClassDeinitializerA {
969+
deinit {}
970+
}
971+
"""
972+
)
973+
}
974+
788975
func testAsyncDeinit() {
789976
// This is expected for now.
790977
// `async` is parsed as a modifier like `public` because you can have an `async var x: Int`.

0 commit comments

Comments
 (0)