diff --git a/Sources/SwiftParser/Types.swift b/Sources/SwiftParser/Types.swift index 21c99f542d0..94827578652 100644 --- a/Sources/SwiftParser/Types.swift +++ b/Sources/SwiftParser/Types.swift @@ -1064,7 +1064,37 @@ extension Parser { ) ) } else { - return self.parseType() + var result = self.parseType() + + guard !result.hasError else { + return result + } + + if self.at(.rightSquareBracket) { + let (unexpectedBeforeRSquareBracket, rightSquareBracket) = self.expect(.rightSquareBracket) + result = RawTypeSyntax( + RawArrayTypeSyntax( + leftSquareBracket: missingToken(.leftSquareBracket), + elementType: result, + unexpectedBeforeRSquareBracket, + rightSquareBracket: rightSquareBracket, + arena: self.arena + ) + ) + } + + var loopProgress = LoopProgressCondition() + while loopProgress.evaluate(currentToken) { + if self.at(TokenSpec(.postfixQuestionMark, allowAtStartOfLine: false)) { + result = RawTypeSyntax(self.parseOptionalType(result)) + } else if self.at(TokenSpec(.exclamationMark, allowAtStartOfLine: false)) { + result = RawTypeSyntax(self.parseImplicitlyUnwrappedOptionalType(result)) + } else { + break + } + } + + return result } } } diff --git a/Sources/SwiftParserDiagnostics/ParseDiagnosticsGenerator.swift b/Sources/SwiftParserDiagnostics/ParseDiagnosticsGenerator.swift index 1db5b02763d..1447997c4f3 100644 --- a/Sources/SwiftParserDiagnostics/ParseDiagnosticsGenerator.swift +++ b/Sources/SwiftParserDiagnostics/ParseDiagnosticsGenerator.swift @@ -410,6 +410,23 @@ public class ParseDiagnosticsGenerator: SyntaxAnyVisitor { return .visitChildren } + public override func visit(_ node: ArrayTypeSyntax) -> SyntaxVisitorContinueKind { + if shouldSkip(node) { + return .skipChildren + } + + if node.leftSquareBracket.presence == .missing && node.rightSquareBracket.presence == .present { + addDiagnostic( + node.rightSquareBracket, + .extraRightBracket, + fixIts: [.init(message: InsertFixIt(tokenToBeInserted: node.leftSquareBracket), changes: .makePresent(node.leftSquareBracket))], + handledNodes: [node.leftSquareBracket.id] + ) + } + + return .visitChildren + } + public override func visit(_ node: AttributeSyntax) -> SyntaxVisitorContinueKind { if shouldSkip(node) { return .skipChildren diff --git a/Sources/SwiftParserDiagnostics/ParserDiagnosticMessages.swift b/Sources/SwiftParserDiagnostics/ParserDiagnosticMessages.swift index d73d2ee5b1a..a8c860a2707 100644 --- a/Sources/SwiftParserDiagnostics/ParserDiagnosticMessages.swift +++ b/Sources/SwiftParserDiagnostics/ParserDiagnosticMessages.swift @@ -140,6 +140,9 @@ extension DiagnosticMessage where Self == StaticParserError { public static var expectedSequenceExpressionInForEachLoop: Self { .init("expected Sequence expression for for-each loop") } + public static var extraRightBracket: Self { + .init("unexpected ']' in type; did you mean to write an array type?") + } public static var initializerInPattern: Self { .init("unexpected initializer in pattern; did you mean to use '='?") } @@ -545,6 +548,14 @@ extension FixItMessage where Self == StaticParserFixIt { } } +public struct InsertFixIt: ParserFixIt { + public let tokenToBeInserted: TokenSyntax + + public var message: String { + "insert '\(tokenToBeInserted.text)'" + } +} + public struct MoveTokensAfterFixIt: ParserFixIt { /// The token that should be moved public let movedTokens: [TokenSyntax] diff --git a/Sources/SwiftSyntax/Raw/RawSyntaxNodeProtocol.swift b/Sources/SwiftSyntax/Raw/RawSyntaxNodeProtocol.swift index 51fc7235e89..78c4f833105 100644 --- a/Sources/SwiftSyntax/Raw/RawSyntaxNodeProtocol.swift +++ b/Sources/SwiftSyntax/Raw/RawSyntaxNodeProtocol.swift @@ -46,6 +46,14 @@ public extension RawSyntaxNodeProtocol { var isEmpty: Bool { return raw.byteLength == 0 } + + /// Whether the tree contained by this layout has any + /// - missing nodes or + /// - unexpected nodes or + /// - tokens with a `TokenDiagnostic` of severity `error` + var hasError: Bool { + return raw.recursiveFlags.contains(.hasError) + } } /// `RawSyntax` itself conforms to `RawSyntaxNodeProtocol`. diff --git a/Sources/SwiftSyntax/Syntax.swift b/Sources/SwiftSyntax/Syntax.swift index 9c202968a56..3f91ad1d84d 100644 --- a/Sources/SwiftSyntax/Syntax.swift +++ b/Sources/SwiftSyntax/Syntax.swift @@ -238,9 +238,9 @@ public extension SyntaxProtocol { /// Whether the tree contained by this layout has any /// - missing nodes or /// - unexpected nodes or - /// - tokens with a `TokenDiagnostic` of severity `error` + /// - tokens with a ``TokenDiagnostic`` of severity ``TokenDiagnostic/Severity-swift.enum/error``. var hasError: Bool { - return raw.recursiveFlags.contains(.hasError) + return raw.hasError } /// Whether the tree contained by this layout has any tokens with a diff --git a/Tests/SwiftParserTest/translated/RecoveryTests.swift b/Tests/SwiftParserTest/translated/RecoveryTests.swift index d768064ee74..ef10ddb2ced 100644 --- a/Tests/SwiftParserTest/translated/RecoveryTests.swift +++ b/Tests/SwiftParserTest/translated/RecoveryTests.swift @@ -1119,15 +1119,27 @@ final class RecoveryTests: XCTestCase { ) } - func testRecovery98a() { - assertParse( - """ - let a1: Swift.Int1️⃣] - """, - diagnostics: [ - DiagnosticSpec(message: "extraneous code ']' at top level") - ] - ) + func testRecovery98() { + let testCases: [UInt: (testCase: String, fixedSource: String)] = [ + #line: ("let a1: Swift.Int1️⃣]", "let a1: [Swift.Int]"), + #line: ("let a3: Set1️⃣]", "let a3: [Set]"), + #line: ("let a4: Int1️⃣]?", "let a4: [Int]?"), + #line: ("let a5: Int?1️⃣]", "let a5: [Int?]"), + #line: ("let a6: [Int]1️⃣]", "let a6: [[Int]]"), + #line: ("let a7: [String: Int]1️⃣]", "let a7: [[String: Int]]"), + #line: ("func foo() -> Int1️⃣]??", "func foo() -> [Int]??"), + ] + + for (line, testCase) in testCases { + assertParse( + testCase.testCase, + diagnostics: [ + DiagnosticSpec(message: "unexpected ']' in type; did you mean to write an array type?", fixIts: ["insert '['"], line: line) + ], + fixedSource: testCase.fixedSource, + line: line + ) + } } func testRecovery98b() { @@ -1142,66 +1154,6 @@ final class RecoveryTests: XCTestCase { ) } - func testRecovery98c() { - assertParse( - """ - let a3: Set1️⃣] - """, - diagnostics: [ - // TODO: Old parser expected error on line 4: unexpected ']' in type; did you mean to write an array type?, Fix-It replacements: 11 - 11 = '[' - DiagnosticSpec(message: "extraneous code ']' at top level") - ] - ) - } - - func testRecovery98d() { - assertParse( - """ - let a4: Int1️⃣]? - """, - diagnostics: [ - // TODO: Old parser expected error on line 5: unexpected ']' in type; did you mean to write an array type?, Fix-It replacements: 11 - 11 = '[' - DiagnosticSpec(message: "extraneous code ']?' at top level") - ] - ) - } - - func testRecovery98e() { - assertParse( - """ - let a5: Int?1️⃣] - """, - diagnostics: [ - // TODO: Old parser expected error on line 6: unexpected ']' in type; did you mean to write an array type?, Fix-It replacements: 11 - 11 = '[' - DiagnosticSpec(message: "extraneous code ']' at top level") - ] - ) - } - - func testRecovery98f() { - assertParse( - """ - let a6: [Int]1️⃣] - """, - diagnostics: [ - // TODO: Old parser expected error on line 7: unexpected ']' in type; did you mean to write an array type?, Fix-It replacements: 11 - 11 = '[' - DiagnosticSpec(message: "extraneous code ']' at top level") - ] - ) - } - - func testRecovery98g() { - assertParse( - """ - let a7: [String: Int]1️⃣] - """, - diagnostics: [ - // TODO: Old parser expected error on line 8: unexpected ']' in type; did you mean to write an array type?, Fix-It replacements: 11 - 11 = '[' - DiagnosticSpec(message: "extraneous code ']' at top level") - ] - ) - } - func testRecovery99() { assertParse( """ @@ -1239,10 +1191,9 @@ final class RecoveryTests: XCTestCase { 4️⃣} """, diagnostics: [ - DiagnosticSpec(locationMarker: "1️⃣", message: "expected '}' to end struct"), - DiagnosticSpec(locationMarker: "2️⃣", message: "expected ']' to end array"), - // TODO: Old parser expected error on line 5: unexpected ']' in type; did you mean to write an array type?, Fix-It replacements: 17 - 17 = '[' - DiagnosticSpec(locationMarker: "3️⃣", message: "unexpected code ']' in function"), + DiagnosticSpec(locationMarker: "1️⃣", message: "expected '}' to end struct", fixIts: ["insert '}'"]), + DiagnosticSpec(locationMarker: "2️⃣", message: "expected ']' to end array", fixIts: ["insert ']'"]), + DiagnosticSpec(locationMarker: "3️⃣", message: "unexpected ']' in type; did you mean to write an array type?", fixIts: ["insert '['"]), DiagnosticSpec(locationMarker: "4️⃣", message: "extraneous brace at top level"), ] )