Skip to content

Remove recover methods from the parser #655

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 3 commits into from
Aug 31, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion Sources/SwiftParser/Attributes.swift
Original file line number Diff line number Diff line change
Expand Up @@ -340,7 +340,7 @@ extension Parser {
guard self.at(.leftParen) else {
// If no opening '(' for parameter list, parse a single parameter.
let param = self.parseDifferentiabilityParameter().map(RawSyntax.init(_:))
?? RawSyntax(RawTokenListSyntax(elements: self.recover(), arena: self.arena))
?? RawSyntax(RawMissingSyntax(arena: self.arena))
return RawDifferentiabilityParamsClauseSyntax(
wrtLabel: wrt,
unexpectedBeforeColon,
Expand Down
46 changes: 4 additions & 42 deletions Sources/SwiftParser/Declarations.swift
Original file line number Diff line number Diff line change
Expand Up @@ -52,20 +52,6 @@ extension Parser {
if self.at(.poundIfKeyword) {
return RawDeclSyntax(self.parsePoundIfDirective { parser in
var parsedDecl = parser.parseDeclaration()
if parsedDecl.is(RawMissingDeclSyntax.self) {
// Try to recover from a bogus decl.
var tokenList = [RawTokenSyntax]()
while !parser.at(.eof) && !parser.at(.poundElseKeyword) &&
!parser.at(.poundElseifKeyword) && !parser.at(.poundEndifKeyword) {
let tokens = parser.recover()
guard !tokens.isEmpty else {
break
}
tokenList.append(contentsOf: tokens)
}
let unexpected = RawUnexpectedNodesSyntax(elements: tokenList.map(RawSyntax.init), arena: parser.arena)
parsedDecl = RawDeclSyntax(RawMissingDeclSyntax(unexpected, attributes: nil, modifiers: nil, arena: parser.arena))
}
let semicolon = parser.consume(if: .semicolon)
return RawMemberDeclListItemSyntax(
decl: parsedDecl,
Expand Down Expand Up @@ -1504,22 +1490,6 @@ extension Parser {
// There can only be an implicit getter if no other accessors were
// seen before this one.
guard elements.isEmpty else {
// Recover until the matching right brace. It's a little
// presumptuous of us to assume everything between here and there
// is an accessor, but we cannot stick unexpected anywhere for the
// moment...
while !self.at(.eof) && !self.at(.rightBrace) {
for token in self.recover() {
elements.append(RawAccessorDeclSyntax(
attributes: nil, modifier: nil,
accessorKind: token,
parameter: nil,
asyncKeyword: nil, throwsKeyword: nil,
body: nil,
arena: self.arena))
}
}

let (unexpectedBeforeRBrace, rbrace) = self.expect(.rightBrace)
return RawSyntax(RawAccessorBlockSyntax(
leftBrace: lbrace,
Expand All @@ -1530,8 +1500,8 @@ extension Parser {
}

var body = [RawCodeBlockItemSyntax]()
while !self.at(.eof) && !self.at(.rightBrace) {
body.append(self.parseCodeBlockItem())
while !self.at(.rightBrace), let newItem = self.parseCodeBlockItem() {
body.append(newItem)
}
let (unexpectedBeforeRBrace, rbrace) = self.expect(.rightBrace)
return RawSyntax(RawCodeBlockSyntax(
Expand Down Expand Up @@ -1719,7 +1689,7 @@ extension Parser {
let (unexpectedBeforeLBrace, lbrace) = self.expect(.leftBrace)
var elements = [RawSyntax]()
do {
while !self.at(.eof) && !self.at(.rightBrace) {
LOOP: while !self.at(.eof) && !self.at(.rightBrace) {
switch self.currentToken.tokenText {
case "associativity":
let associativity = self.consumeIdentifier()
Expand Down Expand Up @@ -1772,15 +1742,7 @@ extension Parser {
otherNames: RawPrecedenceGroupNameListSyntax(elements: names, arena: self.arena),
arena: self.arena)))
default:
var tokenList = [RawTokenSyntax]()
while !self.at(.eof) && !self.at(.rightBrace) {
let tokens = self.recover()
guard !tokens.isEmpty else {
break
}
tokenList.append(contentsOf: tokens)
}
elements.append(RawSyntax(RawTokenListSyntax(elements: tokenList, arena: self.arena)))
break LOOP
}
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@
//===----------------------------------------------------------------------===//

import SwiftDiagnostics
import SwiftSyntax
@_spi(RawSyntax) import SwiftSyntax

extension UnexpectedNodesSyntax {
func tokens(satisfying isIncluded: (TokenSyntax) -> Bool) -> [TokenSyntax] {
Expand Down Expand Up @@ -58,6 +58,9 @@ public class ParseDiagnosticsGenerator: SyntaxAnyVisitor {
) -> [Diagnostic] {
let diagProducer = ParseDiagnosticsGenerator()
diagProducer.walk(tree)
diagProducer.diagnostics.sort {
return $0.node.id.indexInTree < $1.node.id.indexInTree
}
return diagProducer.diagnostics
}

Expand Down Expand Up @@ -172,6 +175,17 @@ public class ParseDiagnosticsGenerator: SyntaxAnyVisitor {
return .visitChildren
}

public override func visit(_ node: SourceFileSyntax) -> SyntaxVisitorContinueKind {
if shouldSkip(node) {
return .skipChildren
}
if let extraneous = node.unexpectedBetweenStatementsAndEOFToken, !extraneous.isEmpty {
addDiagnostic(extraneous, ExtaneousCodeAtTopLevel(extraneousCode: extraneous))
markNodesAsHandled(extraneous.id)
}
return .visitChildren
}

public override func visit(_ node: UnresolvedTernaryExprSyntax) -> SyntaxVisitorContinueKind {
if shouldSkip(node) {
return .skipChildren
Expand Down
43 changes: 37 additions & 6 deletions Sources/SwiftParser/Diagnostics/ParserDiagnosticMessages.swift
Original file line number Diff line number Diff line change
Expand Up @@ -15,16 +15,28 @@ import SwiftSyntax

let diagnosticDomain: String = "SwiftParser"

extension Syntax {
extension SyntaxProtocol {
var nodeTypeNameForDiagnostics: String? {
if let name = self.as(SyntaxEnum.self).nameForDiagnostics {
if let name = Syntax(self).as(SyntaxEnum.self).nameForDiagnostics {
return name
}
if let parent = self.parent {
return parent.nodeTypeNameForDiagnostics
}
return nil
}

/// If the syntax node (excluding leading and trailing trivia) only spans a
/// single line and has less than 100 characters (and thus fits into a
/// diagnostic message), return that. Otherwise, return `nil`.
var contentForDiagnosticsIfShortSingleLine: String? {
let contentWithoutTrivia = self.withoutLeadingTrivia().withoutTrailingTrivia().description
if contentWithoutTrivia.contains("\n") || contentWithoutTrivia.count > 100 {
return nil
} else {
return contentWithoutTrivia
}
}
}

/// A error diagnostic whose ID is determined by the diagnostic's type.
Expand Down Expand Up @@ -90,6 +102,18 @@ public enum StaticParserFixIt: String, FixItMessage {

// MARK: - Diagnostics (please sort alphabetically)

public struct ExtaneousCodeAtTopLevel: ParserError {
public let extraneousCode: UnexpectedNodesSyntax

public var message: String {
if let shortContent = extraneousCode.contentForDiagnosticsIfShortSingleLine {
return "Extraneous '\(shortContent)' at top level"
} else {
return "Extraneous code at top level"
}
}
}

public struct MissingTokenError: ParserError {
public let missingToken: TokenSyntax

Expand Down Expand Up @@ -117,10 +141,17 @@ public struct UnexpectedNodesError: ParserError {
public let unexpectedNodes: UnexpectedNodesSyntax

public var message: String {
if let parentTypeName = unexpectedNodes.parent?.nodeTypeNameForDiagnostics {
return "Unexpected text '\(unexpectedNodes.description)' found in \(parentTypeName)"
} else {
return "Unexpected text '\(unexpectedNodes.description)'"
let parentTypeName = unexpectedNodes.parent?.nodeTypeNameForDiagnostics
let shortContent = unexpectedNodes.contentForDiagnosticsIfShortSingleLine
switch (parentTypeName, shortContent) {
case (let parentTypeName?, let shortContent?):
return "Unexpected text '\(shortContent)' found in \(parentTypeName)"
case (let parentTypeName?, nil):
return "Unexpected text found in \(parentTypeName)"
case (nil, let shortContent?):
return "Unexpected text '\(shortContent)'"
case (nil, nil):
return "Unexpected text"
}
}
}
24 changes: 6 additions & 18 deletions Sources/SwiftParser/Expressions.swift
Original file line number Diff line number Diff line change
Expand Up @@ -1547,8 +1547,8 @@ extension Parser {
// Parse the body.
var elements = [RawCodeBlockItemSyntax]()
do {
while !self.at(.eof) && !self.at(.rightBrace) {
elements.append(self.parseCodeBlockItem())
while !self.at(.rightBrace), let newItem = self.parseCodeBlockItem() {
elements.append(newItem)
}
}

Expand Down Expand Up @@ -1714,7 +1714,7 @@ extension Parser {
}

@_spi(RawSyntax)
public mutating func parseClosureCaptureSpecifiers() -> RawTokenListSyntax {
public mutating func parseClosureCaptureSpecifiers() -> RawTokenListSyntax? {
var specifiers = [RawTokenSyntax]()
do {
// Check for the strength specifier: "weak", "unowned", or
Expand All @@ -1734,26 +1734,14 @@ extension Parser {
guard next.tokenKind == .equal || next.tokenKind == .comma
|| next.tokenKind == .rightSquareBracket || next.tokenKind == .period
else {
// Recover from unexpected in the capture specifiers.
//
// FIXME: This is quite poor modeling in SwiftSyntax.
specifiers.append(contentsOf: self.recover())
return RawTokenListSyntax(elements: specifiers, arena: self.arena)
return nil
}
} else {
// Recover from unexpected in the capture specifiers.
//
// FIXME: This is quite poor modeling in SwiftSyntax.
specifiers.append(contentsOf: self.recover())
return RawTokenListSyntax(elements: specifiers, arena: self.arena)
return nil
}

guard self.currentToken.isIdentifier || self.at(.selfKeyword) else {
// Recover from unexpected in the capture specifiers.
//
// FIXME: This is quite poor modeling in SwiftSyntax.
specifiers.append(contentsOf: self.recover())
return RawTokenListSyntax(elements: specifiers, arena: self.arena)
return nil
}
}
// Squash all tokens, if any, as the specifier of the captured item.
Expand Down
Loading