Skip to content

Commit b0fd2cd

Browse files
committed
Remove old recover method
Recovery should be handled as unexpected nodes, which are usually produced by `expect`.
1 parent 4c34fad commit b0fd2cd

File tree

10 files changed

+67
-196
lines changed

10 files changed

+67
-196
lines changed

Sources/SwiftParser/Attributes.swift

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -340,7 +340,7 @@ extension Parser {
340340
guard self.at(.leftParen) else {
341341
// If no opening '(' for parameter list, parse a single parameter.
342342
let param = self.parseDifferentiabilityParameter().map(RawSyntax.init(_:))
343-
?? RawSyntax(RawTokenListSyntax(elements: self.recover(), arena: self.arena))
343+
?? RawSyntax(RawMissingSyntax(arena: self.arena))
344344
return RawDifferentiabilityParamsClauseSyntax(
345345
wrtLabel: wrt,
346346
unexpectedBeforeColon,

Sources/SwiftParser/Declarations.swift

Lines changed: 2 additions & 40 deletions
Original file line numberDiff line numberDiff line change
@@ -52,20 +52,6 @@ extension Parser {
5252
if self.at(.poundIfKeyword) {
5353
return RawDeclSyntax(self.parsePoundIfDirective { parser in
5454
var parsedDecl = parser.parseDeclaration()
55-
if parsedDecl.is(RawMissingDeclSyntax.self) {
56-
// Try to recover from a bogus decl.
57-
var tokenList = [RawTokenSyntax]()
58-
while !parser.at(.eof) && !parser.at(.poundElseKeyword) &&
59-
!parser.at(.poundElseifKeyword) && !parser.at(.poundEndifKeyword) {
60-
let tokens = parser.recover()
61-
guard !tokens.isEmpty else {
62-
break
63-
}
64-
tokenList.append(contentsOf: tokens)
65-
}
66-
let unexpected = RawUnexpectedNodesSyntax(elements: tokenList.map(RawSyntax.init), arena: parser.arena)
67-
parsedDecl = RawDeclSyntax(RawMissingDeclSyntax(unexpected, attributes: nil, modifiers: nil, arena: parser.arena))
68-
}
6955
let semicolon = parser.consume(if: .semicolon)
7056
return RawMemberDeclListItemSyntax(
7157
decl: parsedDecl,
@@ -1504,22 +1490,6 @@ extension Parser {
15041490
// There can only be an implicit getter if no other accessors were
15051491
// seen before this one.
15061492
guard elements.isEmpty else {
1507-
// Recover until the matching right brace. It's a little
1508-
// presumptuous of us to assume everything between here and there
1509-
// is an accessor, but we cannot stick unexpected anywhere for the
1510-
// moment...
1511-
while !self.at(.eof) && !self.at(.rightBrace) {
1512-
for token in self.recover() {
1513-
elements.append(RawAccessorDeclSyntax(
1514-
attributes: nil, modifier: nil,
1515-
accessorKind: token,
1516-
parameter: nil,
1517-
asyncKeyword: nil, throwsKeyword: nil,
1518-
body: nil,
1519-
arena: self.arena))
1520-
}
1521-
}
1522-
15231493
let (unexpectedBeforeRBrace, rbrace) = self.expect(.rightBrace)
15241494
return RawSyntax(RawAccessorBlockSyntax(
15251495
leftBrace: lbrace,
@@ -1723,7 +1693,7 @@ extension Parser {
17231693
let (unexpectedBeforeLBrace, lbrace) = self.expect(.leftBrace)
17241694
var elements = [RawSyntax]()
17251695
do {
1726-
while !self.at(.eof) && !self.at(.rightBrace) {
1696+
LOOP: while !self.at(.eof) && !self.at(.rightBrace) {
17271697
switch self.currentToken.tokenText {
17281698
case "associativity":
17291699
let associativity = self.consumeIdentifier()
@@ -1776,15 +1746,7 @@ extension Parser {
17761746
otherNames: RawPrecedenceGroupNameListSyntax(elements: names, arena: self.arena),
17771747
arena: self.arena)))
17781748
default:
1779-
var tokenList = [RawTokenSyntax]()
1780-
while !self.at(.eof) && !self.at(.rightBrace) {
1781-
let tokens = self.recover()
1782-
guard !tokens.isEmpty else {
1783-
break
1784-
}
1785-
tokenList.append(contentsOf: tokens)
1786-
}
1787-
elements.append(RawSyntax(RawTokenListSyntax(elements: tokenList, arena: self.arena)))
1749+
break LOOP
17881750
}
17891751
}
17901752
}

Sources/SwiftParser/Diagnostics/ParserDiagnosticMessages.swift

Lines changed: 28 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -15,16 +15,28 @@ import SwiftSyntax
1515

1616
let diagnosticDomain: String = "SwiftParser"
1717

18-
extension Syntax {
18+
extension SyntaxProtocol {
1919
var nodeTypeNameForDiagnostics: String? {
20-
if let name = self.as(SyntaxEnum.self).nameForDiagnostics {
20+
if let name = Syntax(self).as(SyntaxEnum.self).nameForDiagnostics {
2121
return name
2222
}
2323
if let parent = self.parent {
2424
return parent.nodeTypeNameForDiagnostics
2525
}
2626
return nil
2727
}
28+
29+
/// If the syntax node (excluding leading and trailing trivia) only spans a
30+
/// single line and has less than 100 characters (and thus fits into a
31+
/// diagnostic message), return that. Otherwise, return `nil`.
32+
var contentForDiagnosticsIfShortSingleLine: String? {
33+
let contentWithoutTrivia = self.withoutLeadingTrivia().withoutTrailingTrivia().description
34+
if contentWithoutTrivia.contains("\n") || contentWithoutTrivia.count > 100 {
35+
return nil
36+
} else {
37+
return contentWithoutTrivia
38+
}
39+
}
2840
}
2941

3042
/// A error diagnostic whose ID is determined by the diagnostic's type.
@@ -94,13 +106,10 @@ public struct ExtaneousCodeAtTopLevel: ParserError {
94106
public let extraneousCode: UnexpectedNodesSyntax
95107

96108
public var message: String {
97-
let extraneousCodeStr = extraneousCode.withoutLeadingTrivia().withoutTrailingTrivia().description
98-
// If the extraneous code is multi-line or long (100 is in arbitrarily chosen value),
99-
// it just spams the diagnostic. Just show a generic diagnostic in this case.
100-
if extraneousCodeStr.contains("\n") || extraneousCodeStr.count > 100 {
101-
return "Extraneous code at top level"
109+
if let shortContent = extraneousCode.contentForDiagnosticsIfShortSingleLine {
110+
return "Extraneous '\(shortContent)' at top level"
102111
} else {
103-
return "Extraneous '\(extraneousCodeStr)' at top level"
112+
return "Extraneous code at top level"
104113
}
105114
}
106115
}
@@ -132,10 +141,17 @@ public struct UnexpectedNodesError: ParserError {
132141
public let unexpectedNodes: UnexpectedNodesSyntax
133142

134143
public var message: String {
135-
if let parentTypeName = unexpectedNodes.parent?.nodeTypeNameForDiagnostics {
136-
return "Unexpected text '\(unexpectedNodes.description)' found in \(parentTypeName)"
137-
} else {
138-
return "Unexpected text '\(unexpectedNodes.description)'"
144+
let parentTypeName = unexpectedNodes.parent?.nodeTypeNameForDiagnostics
145+
let shortContent = unexpectedNodes.contentForDiagnosticsIfShortSingleLine
146+
switch (parentTypeName, shortContent) {
147+
case (let parentTypeName?, let shortContent?):
148+
return "Unexpected text '\(shortContent)' found in \(parentTypeName)"
149+
case (let parentTypeName?, nil):
150+
return "Unexpected text found in \(parentTypeName)"
151+
case (nil, let shortContent?):
152+
return "Unexpected text '\(shortContent)'"
153+
case (nil, nil):
154+
return "Unexpected text"
139155
}
140156
}
141157
}

Sources/SwiftParser/Expressions.swift

Lines changed: 4 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -1703,7 +1703,7 @@ extension Parser {
17031703
}
17041704

17051705
@_spi(RawSyntax)
1706-
public mutating func parseClosureCaptureSpecifiers() -> RawTokenListSyntax {
1706+
public mutating func parseClosureCaptureSpecifiers() -> RawTokenListSyntax? {
17071707
var specifiers = [RawTokenSyntax]()
17081708
do {
17091709
// Check for the strength specifier: "weak", "unowned", or
@@ -1723,26 +1723,14 @@ extension Parser {
17231723
guard next.tokenKind == .equal || next.tokenKind == .comma
17241724
|| next.tokenKind == .rightSquareBracket || next.tokenKind == .period
17251725
else {
1726-
// Recover from unexpected in the capture specifiers.
1727-
//
1728-
// FIXME: This is quite poor modeling in SwiftSyntax.
1729-
specifiers.append(contentsOf: self.recover())
1730-
return RawTokenListSyntax(elements: specifiers, arena: self.arena)
1726+
return nil
17311727
}
17321728
} else {
1733-
// Recover from unexpected in the capture specifiers.
1734-
//
1735-
// FIXME: This is quite poor modeling in SwiftSyntax.
1736-
specifiers.append(contentsOf: self.recover())
1737-
return RawTokenListSyntax(elements: specifiers, arena: self.arena)
1729+
return nil
17381730
}
17391731

17401732
guard self.currentToken.isIdentifier || self.at(.selfKeyword) else {
1741-
// Recover from unexpected in the capture specifiers.
1742-
//
1743-
// FIXME: This is quite poor modeling in SwiftSyntax.
1744-
specifiers.append(contentsOf: self.recover())
1745-
return RawTokenListSyntax(elements: specifiers, arena: self.arena)
1733+
return nil
17461734
}
17471735
}
17481736
// Squash all tokens, if any, as the specifier of the captured item.

Sources/SwiftParser/Recovery.swift

Lines changed: 0 additions & 95 deletions
Original file line numberDiff line numberDiff line change
@@ -12,101 +12,6 @@
1212

1313
@_spi(RawSyntax) import SwiftSyntax
1414

15-
extension Parser {
16-
/// Implements general-purpose, balanced delimiter parser recovery.
17-
///
18-
/// This function implements a very coarse recovery algorithm that tries to
19-
/// skip over as much balanced token structure as it can before yielding back
20-
/// to the parser. For example, this enables the parser to recover from
21-
///
22-
/// ```swift
23-
/// case: { ("Hello World") }
24-
/// ```
25-
///
26-
/// Item parsing runs recovery in a loop until it encounters a
27-
/// token that could be the start of a new item. After eating the erroneous
28-
/// `case` at the top level, the parser calls `recover()` to eat the colon,
29-
/// then calls it again to eat the braces. The final time around, this
30-
/// routine will eat not just the braces, but also the parenthesized
31-
/// `"Hello World"` string token contained therein and its paren tokens.
32-
///
33-
/// Using Recovery
34-
/// ==============
35-
///
36-
/// Parser recovery is a method of last resort. It indicates that the parser
37-
/// has encountered some part of the input that it believes to be so erroneous
38-
/// it cannot possibly assign it any structure. The resulting syntax tree
39-
/// thus contains a sequence of unparsed tokens. As such, this function should
40-
/// be considered only when the parser would otherwise be unable to make
41-
/// forward progress, such as when an entire missing syntax node is
42-
/// encountered. For simple cases like a missing token, it is more appropriate
43-
/// to use ``Parser/expect(_:)``, which will correctly handle looking ahead
44-
/// to try to reach the desired point in the token stream.
45-
///
46-
/// - Returns: A non-empty list of tokens that were eaten during the recovery process.
47-
mutating func recover() -> [RawTokenSyntax] {
48-
var tokens = [RawTokenSyntax]()
49-
switch self.currentToken.tokenKind {
50-
case .leftParen:
51-
tokens.append(self.consumeAnyToken())
52-
while !self.at(.eof) && !self.at(.rightParen)
53-
&& !self.at(.poundEndifKeyword) && !self.at(.poundElseKeyword) && !self.at(.poundElseifKeyword) {
54-
tokens.append(contentsOf: self.recover())
55-
}
56-
if let token = self.consume(if: .rightParen) {
57-
tokens.append(token)
58-
}
59-
return tokens
60-
case .leftBrace:
61-
tokens.append(self.consumeAnyToken())
62-
while !self.at(.eof) && !self.at(.rightBrace)
63-
&& !self.at(.poundEndifKeyword) && !self.at(.poundElseKeyword) && !self.at(.poundElseifKeyword) {
64-
tokens.append(contentsOf: self.recover())
65-
}
66-
if let token = self.consume(if: .rightBrace) {
67-
tokens.append(token)
68-
}
69-
return tokens
70-
case .leftSquareBracket:
71-
tokens.append(self.consumeAnyToken())
72-
while !self.at(.eof) && !self.at(.rightSquareBracket)
73-
&& !self.at(.poundEndifKeyword) && !self.at(.poundElseKeyword) && !self.at(.poundElseifKeyword) {
74-
tokens.append(contentsOf: self.recover())
75-
}
76-
if let token = self.consume(if: .rightSquareBracket) {
77-
tokens.append(token)
78-
}
79-
return tokens
80-
case .poundIfKeyword,
81-
.poundElseKeyword,
82-
.poundElseifKeyword:
83-
tokens.append(self.consumeAnyToken())
84-
// skipUntil also implicitly stops at tok::pound_endif.
85-
while !self.at(.eof) && !self.at(.poundElseKeyword) && !self.at(.poundElseifKeyword)
86-
&& !self.at(.poundEndifKeyword) {
87-
tokens.append(contentsOf: self.recover())
88-
}
89-
if let token = self.consume(if: .rightSquareBracket) {
90-
tokens.append(token)
91-
}
92-
93-
if self.at(.poundElseKeyword) || self.at(.poundElseifKeyword) {
94-
tokens.append(contentsOf: self.recover())
95-
return tokens
96-
} else {
97-
if let token = self.consume(if: .poundEndifKeyword) {
98-
tokens.append(token)
99-
}
100-
return tokens
101-
}
102-
103-
default:
104-
tokens.append(self.consumeAnyToken())
105-
return tokens
106-
}
107-
}
108-
}
109-
11015
// MARK: Lookahead
11116

11217
extension Parser.Lookahead {

Sources/SwiftParser/Statements.swift

Lines changed: 1 addition & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -618,18 +618,7 @@ extension Parser {
618618
return RawSyntax(firstCase)
619619
}))
620620
} else {
621-
var tokenList = [RawTokenSyntax]()
622-
while !self.at(.eof) && !self.at(.rightBrace)
623-
&& !self.at(.poundElseifKeyword)
624-
&& !self.at(.poundElseKeyword) && !self.at(.poundEndifKeyword)
625-
&& !self.lookahead().isStartOfConditionalSwitchCases() {
626-
let tokens = self.recover()
627-
guard !tokens.isEmpty else {
628-
break
629-
}
630-
tokenList.append(contentsOf: tokens)
631-
}
632-
elements.append(RawSyntax(RawNonEmptyTokenListSyntax(elements: tokenList, arena: self.arena)))
621+
break
633622
}
634623
}
635624
return RawSwitchCaseListSyntax(elements: elements, arena: self.arena)
@@ -654,10 +643,6 @@ extension Parser {
654643
while self.at(.atSign) {
655644
tokenList.append(self.eat(.atSign))
656645
tokenList.append(self.consumeIdentifier())
657-
658-
if self.at(.leftParen) {
659-
tokenList.append(contentsOf: self.recover())
660-
}
661646
}
662647

663648
unknownAttr = RawAttributeSyntax(

Tests/SwiftParserTest/Assertions.swift

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -135,6 +135,12 @@ func AssertDiagnostic<T: SyntaxProtocol>(
135135
if let message = spec.message {
136136
AssertStringsEqualWithDiff(diag.message, message, file: file, line: line)
137137
}
138+
if diag.message.contains("\n") {
139+
XCTFail("""
140+
Diagnostic message should only span a single line. Message was:
141+
\(diag.message)
142+
""")
143+
}
138144
if let highlight = spec.highlight {
139145
AssertStringsEqualWithDiff(diag.highlights.map(\.description).joined(), highlight, file: file, line: line)
140146
}

Tests/SwiftParserTest/Declarations.swift

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -507,25 +507,25 @@ final class DeclarationTests: XCTestCase {
507507
}
508508
""",
509509
diagnostics: [
510-
// FIXME: The diagnostic should not contain a newline.
511510
DiagnosticSpec(
512511
message: """
513-
Unexpected text '
514-
/ ###line 25 "line-directive.swift"' found in struct
512+
Unexpected text '/ ###line 25 "line-directive.swift"' found in struct
515513
"""
516514
)
517515
]
518516
)
519517
}
520518

521519
func testBogusProtocolRequirements() {
522-
// FIXME: This test case should produce a diagnostics
523520
AssertParse(
524521
"""
525522
protocol P {
526-
var prop : Int { get bogus rethrows set }
523+
var prop : Int { get #^DIAG^#bogus rethrows set }
527524
}
528-
"""
525+
""",
526+
diagnostics: [
527+
DiagnosticSpec(message: "Unexpected text 'bogus rethrows set' found in variable")
528+
]
529529
)
530530
}
531531

Tests/SwiftParserTest/Expressions.swift

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -193,7 +193,7 @@ final class ExpressionTests: XCTestCase {
193193
" >> \( abc #^DIAG^#} ) << "
194194
"""#,
195195
diagnostics: [
196-
DiagnosticSpec(message: "Unexpected text '} ' found in string literal")
196+
DiagnosticSpec(message: "Unexpected text '}' found in string literal")
197197
]
198198
)
199199

0 commit comments

Comments
 (0)