From 96439c4fa505d583f95417e961dd648aba73900e Mon Sep 17 00:00:00 2001 From: Alex Hoppen Date: Sun, 12 Feb 2023 13:50:55 +0100 Subject: [PATCH 1/7] Fix parsing error if `@objc` selector contains an underscore --- Sources/SwiftParser/Attributes.swift | 10 +++++----- Tests/SwiftParserTest/AttributeTests.swift | 7 +++++++ 2 files changed, 12 insertions(+), 5 deletions(-) diff --git a/Sources/SwiftParser/Attributes.swift b/Sources/SwiftParser/Attributes.swift index 2b22e6656e2..4e9a45703e7 100644 --- a/Sources/SwiftParser/Attributes.swift +++ b/Sources/SwiftParser/Attributes.swift @@ -607,7 +607,7 @@ extension Parser { mutating func parseObjectiveCSelector() -> RawObjCSelectorSyntax { var elements = [RawObjCSelectorPieceSyntax]() var loopProgress = LoopProgressCondition() - while !self.at(.eof, .rightParen) && loopProgress.evaluate(currentToken) { + while loopProgress.evaluate(currentToken) { // Empty selector piece. if let colon = self.consume(if: .colon) { elements.append( @@ -618,10 +618,8 @@ extension Parser { ) ) continue - } - - if self.at(.identifier) || self.currentToken.isLexerClassifiedKeyword { - let name = self.consumeAnyToken() + } else if self.at(.identifier, .wildcard) || self.currentToken.isLexerClassifiedKeyword { + let name = self.consumeAnyToken(remapping: .identifier) // If we hit a ')' we may have a zero-argument selector. if self.at(.rightParen) && elements.isEmpty { @@ -644,6 +642,8 @@ extension Parser { arena: self.arena ) ) + } else { + break } } return RawObjCSelectorSyntax(elements: elements, arena: self.arena) diff --git a/Tests/SwiftParserTest/AttributeTests.swift b/Tests/SwiftParserTest/AttributeTests.swift index 64e887ec564..9f1702850b0 100644 --- a/Tests/SwiftParserTest/AttributeTests.swift +++ b/Tests/SwiftParserTest/AttributeTests.swift @@ -90,6 +90,13 @@ final class AttributeTests: XCTestCase { func f(_: Int, _: Int, _: Int, _: Int, _: Int) { } """ ) + + AssertParse( + """ + @objc(_:) + func f(_: Int) + """ + ) } func testRethrowsAttribute() { From e5ff0fdeb1cc70dd87b5e8a736a8392c09165905 Mon Sep 17 00:00:00 2001 From: Alex Hoppen Date: Sun, 12 Feb 2023 13:55:54 +0100 Subject: [PATCH 2/7] Fix parsing bug when a postfix expression is followed by an empty `#if` clause --- Sources/SwiftParser/Attributes.swift | 2 +- Sources/SwiftParser/Declarations.swift | 2 +- Sources/SwiftParser/Directives.swift | 11 ++++------- Sources/SwiftParser/Expressions.swift | 7 +++++-- Sources/SwiftParser/TopLevel.swift | 4 ++-- Tests/SwiftParserTest/DirectiveTests.swift | 16 ++++++++++++++++ 6 files changed, 29 insertions(+), 13 deletions(-) diff --git a/Sources/SwiftParser/Attributes.swift b/Sources/SwiftParser/Attributes.swift index 4e9a45703e7..b6106161dab 100644 --- a/Sources/SwiftParser/Attributes.swift +++ b/Sources/SwiftParser/Attributes.swift @@ -212,7 +212,7 @@ extension Parser { mutating func parseAttribute() -> RawAttributeListSyntax.Element { if self.at(.poundIfKeyword) { return .ifConfigDecl( - self.parsePoundIfDirective { parser -> RawAttributeListSyntax.Element in + self.parsePoundIfDirective { (parser, _) -> RawAttributeListSyntax.Element in return parser.parseAttribute() } syntax: { parser, attributes in return .attributes(RawAttributeListSyntax(elements: attributes, arena: parser.arena)) diff --git a/Sources/SwiftParser/Declarations.swift b/Sources/SwiftParser/Declarations.swift index b6377692d73..7c626c34a5e 100644 --- a/Sources/SwiftParser/Declarations.swift +++ b/Sources/SwiftParser/Declarations.swift @@ -176,7 +176,7 @@ extension Parser { // parsed when we're parsing the attributes. break } - let directive = self.parsePoundIfDirective { parser in + let directive = self.parsePoundIfDirective { (parser, _) in let parsedDecl = parser.parseDeclaration() let semicolon = parser.consume(if: .semicolon) return RawMemberDeclListItemSyntax( diff --git a/Sources/SwiftParser/Directives.swift b/Sources/SwiftParser/Directives.swift index e518b0c5936..a71085a2c79 100644 --- a/Sources/SwiftParser/Directives.swift +++ b/Sources/SwiftParser/Directives.swift @@ -13,14 +13,12 @@ @_spi(RawSyntax) import SwiftSyntax extension Parser { - private enum IfConfigClauseStartKeyword: TokenSpecSet { - case poundIfKeyword + private enum IfConfigContinuationClauseStartKeyword: TokenSpecSet { case poundElseifKeyword case poundElseKeyword var spec: TokenSpec { switch self { - case .poundIfKeyword: return .poundIfKeyword case .poundElseifKeyword: return .poundElseifKeyword case .poundElseKeyword: return .poundElseKeyword } @@ -28,7 +26,6 @@ extension Parser { init?(lexeme: Lexer.Lexeme) { switch PrepareForKeywordMatch(lexeme) { - case TokenSpec(.poundIfKeyword): self = .poundIfKeyword case TokenSpec(.poundElseifKeyword): self = .poundElseifKeyword case TokenSpec(.poundElseKeyword): self = .poundElseKeyword default: return nil @@ -89,7 +86,7 @@ extension Parser { /// into a syntax collection. @_spi(RawSyntax) public mutating func parsePoundIfDirective( - _ parseElement: (inout Parser) -> Element?, + _ parseElement: (_ parser: inout Parser, _ isFirstElement: Bool) -> Element?, addSemicolonIfNeeded: (_ lastElement: Element, _ newItemAtStartOfLine: Bool, _ parser: inout Parser) -> Element? = { _, _, _ in nil }, syntax: (inout Parser, [Element]) -> RawIfConfigClauseSyntax.Elements? ) -> RawIfConfigDeclSyntax { @@ -106,7 +103,7 @@ extension Parser { do { var firstIteration = true var loopProgress = LoopProgressCondition() - while let poundIfHandle = firstIteration ? self.canRecoverTo(.poundIfKeyword) : self.canRecoverTo(anyIn: IfConfigClauseStartKeyword.self)?.handle, + while let poundIfHandle = firstIteration ? self.canRecoverTo(.poundIfKeyword) : self.canRecoverTo(anyIn: IfConfigContinuationClauseStartKeyword.self)?.handle, loopProgress.evaluate(self.currentToken) { let (unexpectedBeforePoundIf, poundIf) = self.eat(poundIfHandle) @@ -127,7 +124,7 @@ extension Parser { var elementsProgress = LoopProgressCondition() while !self.at(.eof) && !self.at(.poundElseKeyword, .poundElseifKeyword, .poundEndifKeyword) && elementsProgress.evaluate(currentToken) { let newItemAtStartOfLine = self.currentToken.isAtStartOfLine - guard let element = parseElement(&self), !element.isEmpty else { + guard let element = parseElement(&self, elements.isEmpty), !element.isEmpty else { break } if let lastElement = elements.last, let fixedUpLastItem = addSemicolonIfNeeded(lastElement, newItemAtStartOfLine, &self) { diff --git a/Sources/SwiftParser/Expressions.swift b/Sources/SwiftParser/Expressions.swift index 4a541478cec..2b875eb9b92 100644 --- a/Sources/SwiftParser/Expressions.swift +++ b/Sources/SwiftParser/Expressions.swift @@ -659,7 +659,10 @@ extension Parser { ) -> RawExprSyntax { assert(self.at(.poundIfKeyword)) - let config = self.parsePoundIfDirective { parser -> RawExprSyntax? in + let config = self.parsePoundIfDirective { (parser, isFirstElement) -> RawExprSyntax? in + if !isFirstElement { + return nil + } let head: RawExprSyntax if parser.at(.period) { head = parser.parseDottedExpressionSuffix(nil) @@ -2236,7 +2239,7 @@ extension Parser { elements.append( .ifConfigDecl( self.parsePoundIfDirective( - { $0.parseSwitchCases(allowStandaloneStmtRecovery: allowStandaloneStmtRecovery) }, + { (parser, _) in parser.parseSwitchCases(allowStandaloneStmtRecovery: allowStandaloneStmtRecovery) }, syntax: { parser, cases in guard cases.count == 1, let firstCase = cases.first else { assert(cases.isEmpty) diff --git a/Sources/SwiftParser/TopLevel.swift b/Sources/SwiftParser/TopLevel.swift index 00e2c8de69f..2278e7617d5 100644 --- a/Sources/SwiftParser/TopLevel.swift +++ b/Sources/SwiftParser/TopLevel.swift @@ -228,8 +228,8 @@ extension Parser { if self.at(.poundIfKeyword) && !self.withLookahead({ $0.consumeIfConfigOfAttributes() }) { // If config of attributes is parsed as part of declaration parsing as it // doesn't constitute its own code block item. - let directive = self.parsePoundIfDirective { - $0.parseCodeBlockItem() + let directive = self.parsePoundIfDirective { (parser, _) in + parser.parseCodeBlockItem() } addSemicolonIfNeeded: { lastElement, newItemAtStartOfLine, parser in if lastElement.semicolon == nil && !newItemAtStartOfLine { return RawCodeBlockItemSyntax( diff --git a/Tests/SwiftParserTest/DirectiveTests.swift b/Tests/SwiftParserTest/DirectiveTests.swift index a34f0a99f25..8df392d09db 100644 --- a/Tests/SwiftParserTest/DirectiveTests.swift +++ b/Tests/SwiftParserTest/DirectiveTests.swift @@ -69,6 +69,22 @@ final class DirectiveTests: XCTestCase { ) } + func testPostfixIfConfigExpressionContainsPoundIf() { + AssertParse( + """ + b + #if true + .a + 1️⃣#if true + #endif + #endif + """, + diagnostics: [ + DiagnosticSpec(message: "unexpected code in conditional compilation block") + ] + ) + } + func testSourceLocation() { AssertParse( """ From 243a1d35fe2829307f76ff63c53279e2d6166db6 Mon Sep 17 00:00:00 2001 From: Alex Hoppen Date: Sun, 12 Feb 2023 13:58:19 +0100 Subject: [PATCH 3/7] =?UTF-8?q?Don=E2=80=99t=20consider=20MattchingPattern?= =?UTF-8?q?=20start=20the=20start=20of=20an=20expression?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- Sources/SwiftParser/TokenSpecSet.swift | 5 ----- Tests/SwiftParserTest/ExpressionTests.swift | 14 ++++++++++++++ 2 files changed, 14 insertions(+), 5 deletions(-) diff --git a/Sources/SwiftParser/TokenSpecSet.swift b/Sources/SwiftParser/TokenSpecSet.swift index ff0a84990db..72c5bc512b9 100644 --- a/Sources/SwiftParser/TokenSpecSet.swift +++ b/Sources/SwiftParser/TokenSpecSet.swift @@ -706,7 +706,6 @@ enum PrimaryExpressionStart: TokenSpecSet { enum ExpressionStart: TokenSpecSet { case awaitTryMove(AwaitTryMove) case expressionPrefixOperator(ExpressionPrefixOperator) - case matchingPatternStart(MatchingPatternStart) case primaryExpressionStart(PrimaryExpressionStart) case ifOrSwitch(IfOrSwitch) @@ -715,8 +714,6 @@ enum ExpressionStart: TokenSpecSet { self = .awaitTryMove(subset) } else if let subset = ExpressionPrefixOperator(lexeme: lexeme) { self = .expressionPrefixOperator(subset) - } else if let subset = MatchingPatternStart(lexeme: lexeme) { - self = .matchingPatternStart(subset) } else if let subset = PrimaryExpressionStart(lexeme: lexeme) { self = .primaryExpressionStart(subset) } else if let subset = IfOrSwitch(lexeme: lexeme) { @@ -729,7 +726,6 @@ enum ExpressionStart: TokenSpecSet { static var allCases: [ExpressionStart] { return AwaitTryMove.allCases.map(Self.awaitTryMove) + ExpressionPrefixOperator.allCases.map(Self.expressionPrefixOperator) - + MatchingPatternStart.allCases.map(Self.matchingPatternStart) + PrimaryExpressionStart.allCases.map(Self.primaryExpressionStart) + IfOrSwitch.allCases.map(Self.ifOrSwitch) } @@ -738,7 +734,6 @@ enum ExpressionStart: TokenSpecSet { switch self { case .awaitTryMove(let underlyingKind): return underlyingKind.spec case .expressionPrefixOperator(let underlyingKind): return underlyingKind.spec - case .matchingPatternStart(let underlyingKind): return underlyingKind.spec case .primaryExpressionStart(let underlyingKind): return underlyingKind.spec case .ifOrSwitch(let underlyingKind): return underlyingKind.spec } diff --git a/Tests/SwiftParserTest/ExpressionTests.swift b/Tests/SwiftParserTest/ExpressionTests.swift index da30b7ff3e5..5038687edb3 100644 --- a/Tests/SwiftParserTest/ExpressionTests.swift +++ b/Tests/SwiftParserTest/ExpressionTests.swift @@ -1627,4 +1627,18 @@ final class StatementExpressionTests: XCTestCase { ) ) } + + func testPatternExprInSwitchCaseItem() { + AssertParse( + """ + switch x { + case a: + 1️⃣is + } + """, + diagnostics: [ + DiagnosticSpec(message: "unexpected 'is' keyword in 'switch' statement") + ] + ) + } } From 7db61b4f9b5f340d66bd64c410feef5eac199cd4 Mon Sep 17 00:00:00 2001 From: Alex Hoppen Date: Sun, 12 Feb 2023 13:59:10 +0100 Subject: [PATCH 4/7] Fix a bug that cause the parser to not terminate if we recoverd to a `case` item --- Sources/SwiftParser/Expressions.swift | 3 +++ Tests/SwiftParserTest/ExpressionTests.swift | 11 +++++++++++ 2 files changed, 14 insertions(+) diff --git a/Sources/SwiftParser/Expressions.swift b/Sources/SwiftParser/Expressions.swift index 2b875eb9b92..e192acf9ee0 100644 --- a/Sources/SwiftParser/Expressions.swift +++ b/Sources/SwiftParser/Expressions.swift @@ -2253,6 +2253,9 @@ extension Parser { } else if allowStandaloneStmtRecovery && (self.atStartOfExpression() || self.atStartOfStatement() || self.atStartOfDeclaration()) { // Synthesize a label for the stamenent or declaration that isn't coverd by a case right now. let statements = parseSwitchCaseBody() + if statements.isEmpty { + break + } elements.append( .switchCase( RawSwitchCaseSyntax( diff --git a/Tests/SwiftParserTest/ExpressionTests.swift b/Tests/SwiftParserTest/ExpressionTests.swift index 5038687edb3..2f2596bb09c 100644 --- a/Tests/SwiftParserTest/ExpressionTests.swift +++ b/Tests/SwiftParserTest/ExpressionTests.swift @@ -1641,4 +1641,15 @@ final class StatementExpressionTests: XCTestCase { ] ) } + + func testStandaloneAtCaseInSwitch() { + AssertParse( + """ + switch x { + 1️⃣@case + } + """, + diagnostics: [DiagnosticSpec(message: "unexpected code '@case' in 'switch' statement")] + ) + } } From 6143e9ded1abe2de526222490970d5056e603b0e Mon Sep 17 00:00:00 2001 From: Alex Hoppen Date: Sun, 12 Feb 2023 14:02:07 +0100 Subject: [PATCH 5/7] Remove assertion that the closing quote of a string literal must not have leading trivia --- Sources/SwiftParser/StringLiterals.swift | 3 +-- Tests/SwiftParserTest/ExpressionTests.swift | 14 ++++++++++++++ 2 files changed, 15 insertions(+), 2 deletions(-) diff --git a/Sources/SwiftParser/StringLiterals.swift b/Sources/SwiftParser/StringLiterals.swift index 04330b06b8f..cda5084f496 100644 --- a/Sources/SwiftParser/StringLiterals.swift +++ b/Sources/SwiftParser/StringLiterals.swift @@ -309,7 +309,6 @@ extension Parser { // ------------------------------------------------------------------------- // Precondition - assert(closeQuote.leadingTriviaByteLength == 0, "Closing quote produced by the lexer should not have leading trivia because we would drop it during post-processing") assert( allSegments.allSatisfy { if case .stringSegment(let segment) = $0 { @@ -349,7 +348,7 @@ extension Parser { middleSegments: &middleSegments ) - if !closeDelimiterOnNewLine { + if !closeDelimiterOnNewLine || closeQuote.leadingTriviaByteLength != 0 { unexpectedBeforeCloseQuote = [closeQuote] closeQuote = RawTokenSyntax(missing: closeQuote.tokenKind, leadingTriviaPieces: [.newlines(1)], arena: self.arena) diff --git a/Tests/SwiftParserTest/ExpressionTests.swift b/Tests/SwiftParserTest/ExpressionTests.swift index 2f2596bb09c..18f6cd567fa 100644 --- a/Tests/SwiftParserTest/ExpressionTests.swift +++ b/Tests/SwiftParserTest/ExpressionTests.swift @@ -1652,4 +1652,18 @@ final class StatementExpressionTests: XCTestCase { diagnostics: [DiagnosticSpec(message: "unexpected code '@case' in 'switch' statement")] ) } + + func testUnterminatedInterpolationAtEndOfMultilineStringLiteral() { + AssertParse( + #""" + """\({(1️⃣}) + 2️⃣"""3️⃣ + """#, + diagnostics: [ + DiagnosticSpec(locationMarker: "1️⃣", message: "expected value and ')' to end tuple"), + DiagnosticSpec(locationMarker: "2️⃣", message: #"unexpected code '"""' in string literal"#), + DiagnosticSpec(locationMarker: "3️⃣", message: #"expected '"""' to end string literal"#), + ] + ) + } } From 5d455f482ae0cee8244da22687270ae43c8c83ef Mon Sep 17 00:00:00 2001 From: Alex Hoppen Date: Sun, 12 Feb 2023 14:03:39 +0100 Subject: [PATCH 6/7] =?UTF-8?q?Fix=20a=20bug=20that=20caused=20the=20lexer?= =?UTF-8?q?=E2=80=99s=20cursor=20to=20end=20up=20in=20an=20incorrect=20sta?= =?UTF-8?q?te=20after=20`resetForSplit`?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- Sources/SwiftParser/Lexer/Cursor.swift | 10 ------ .../SwiftParser/Lexer/LexemeSequence.swift | 16 ++++----- Sources/SwiftParser/Lookahead.swift | 10 +++--- Sources/SwiftParser/Parser.swift | 34 +++---------------- Tests/SwiftParserTest/ExpressionTests.swift | 11 ++++++ 5 files changed, 25 insertions(+), 56 deletions(-) diff --git a/Sources/SwiftParser/Lexer/Cursor.swift b/Sources/SwiftParser/Lexer/Cursor.swift index 41661e434bb..a70f52ed208 100644 --- a/Sources/SwiftParser/Lexer/Cursor.swift +++ b/Sources/SwiftParser/Lexer/Cursor.swift @@ -695,16 +695,6 @@ extension Lexer.Cursor { mutating func advanceValidatingUTF8Character() -> Unicode.Scalar? { return Unicode.Scalar.lexing(advance: { self.advance() }, peek: { self.peek(at: 0) }) } - - /// Rever the lexer by `offset` bytes. This should only be used by `resetForSplit`. - /// This must not back up by more bytes than the last token because that would - /// require us to also update `previousTokenKind`, which we don't do in this - /// function - mutating func backUp(by offset: Int) { - assert(!self.isAtStartOfFile) - self.previous = self.input.baseAddress!.advanced(by: -(offset + 1)).pointee - self.input = UnsafeBufferPointer(start: self.input.baseAddress!.advanced(by: -offset), count: self.input.count + offset) - } } // MARK: - Boundness of operators diff --git a/Sources/SwiftParser/Lexer/LexemeSequence.swift b/Sources/SwiftParser/Lexer/LexemeSequence.swift index 83a9cf5d586..5e571079542 100644 --- a/Sources/SwiftParser/Lexer/LexemeSequence.swift +++ b/Sources/SwiftParser/Lexer/LexemeSequence.swift @@ -61,18 +61,14 @@ extension Lexer { return self.nextToken } + /// Reset the lexeme sequence to the state we were in when lexing `splitToken` + /// but after we consumed `consumedPrefix` bytes from `splitToken`. /// - Warning: Do not add more usages of this function. - mutating func resetForSplit(of bytes: Int) -> Lexer.Lexeme { - guard bytes > 0 else { - return self.advance() + mutating func resetForSplit(splitToken: Lexeme, consumedPrefix: Int) -> Lexer.Lexeme { + self.cursor = splitToken.cursor + for _ in 0..> ... -> > - // - // The current calculation is: - // - // <TOKEN> - // CURSOR ^ - // + trailing trivia length - // - // <TOKEN> - // CURSOR ^ - // + content length - // - // <TOKEN> - // CURSOR ^ - // - split point length - // - // <TOKEN> - // CURSOR ^ - let offset = - (self.currentToken.trailingTriviaByteLength - + tokenText.count - - prefix.count) - self.currentToken = self.lexemes.resetForSplit(of: offset) + self.currentToken = self.lexemes.resetForSplit( + splitToken: self.currentToken, + consumedPrefix: self.currentToken.leadingTriviaByteLength + prefix.count + ) return tok } } diff --git a/Tests/SwiftParserTest/ExpressionTests.swift b/Tests/SwiftParserTest/ExpressionTests.swift index 18f6cd567fa..828f5a9b2a0 100644 --- a/Tests/SwiftParserTest/ExpressionTests.swift +++ b/Tests/SwiftParserTest/ExpressionTests.swift @@ -1666,4 +1666,15 @@ final class StatementExpressionTests: XCTestCase { ] ) } + + func testStringLiteralAfterKeyPath() { + AssertParse( + #""" + \String.?1️⃣"" + """#, + diagnostics: [ + DiagnosticSpec(message: "consecutive statements on a line must be separated by ';'") + ] + ) + } } From 88b7ba6faa388dfd498d2581f628bcebadca9156 Mon Sep 17 00:00:00 2001 From: Alex Hoppen Date: Sun, 12 Feb 2023 14:04:18 +0100 Subject: [PATCH 7/7] Fix bug when recovering to an accessor introducer --- Sources/SwiftParser/Declarations.swift | 7 +++++-- Tests/SwiftParserTest/StatementTests.swift | 15 +++++++++++++++ 2 files changed, 20 insertions(+), 2 deletions(-) diff --git a/Sources/SwiftParser/Declarations.swift b/Sources/SwiftParser/Declarations.swift index 7c626c34a5e..9ce538da2f3 100644 --- a/Sources/SwiftParser/Declarations.swift +++ b/Sources/SwiftParser/Declarations.swift @@ -1581,6 +1581,7 @@ extension Parser { var attributes: RawAttributeListSyntax? var modifier: RawDeclModifierSyntax? var kind: AccessorKind + var unexpectedBeforeToken: RawUnexpectedNodesSyntax? var token: RawTokenSyntax } @@ -1591,7 +1592,7 @@ extension Parser { var look = self.lookahead() let _ = look.consumeAttributeList() let hasModifier = look.consume(if: .keyword(.mutating), .keyword(.nonmutating), .keyword(.__consuming)) != nil - guard let (kind, handle) = look.at(anyIn: AccessorKind.self) ?? forcedKind else { + guard let (kind, _) = look.at(anyIn: AccessorKind.self) ?? forcedKind else { return nil } @@ -1612,11 +1613,12 @@ extension Parser { modifier = nil } - let introducer = self.eat(handle) + let (unexpectedBeforeIntroducer, introducer) = self.expect(kind.spec) return AccessorIntroducer( attributes: attrs, modifier: modifier, kind: kind, + unexpectedBeforeToken: unexpectedBeforeIntroducer, token: introducer ) } @@ -1673,6 +1675,7 @@ extension Parser { return RawAccessorDeclSyntax( attributes: introducer.attributes, modifier: introducer.modifier, + introducer.unexpectedBeforeToken, accessorKind: introducer.token, parameter: parameter, effectSpecifiers: effectSpecifiers, diff --git a/Tests/SwiftParserTest/StatementTests.swift b/Tests/SwiftParserTest/StatementTests.swift index bf75740cf8d..6405489d42c 100644 --- a/Tests/SwiftParserTest/StatementTests.swift +++ b/Tests/SwiftParserTest/StatementTests.swift @@ -615,4 +615,19 @@ final class StatementTests: XCTestCase { ] ) } + + func testRecoveryInFrontOfAccessorIntroducer() { + AssertParse( + """ + subscript(1️⃣{@2️⃣self _modify3️⃣ + """, + diagnostics: [ + DiagnosticSpec(locationMarker: "1️⃣", message: "expected type and ')' to end parameter clause"), + DiagnosticSpec(locationMarker: "1️⃣", message: "expected '->' and return type in subscript"), + DiagnosticSpec(locationMarker: "2️⃣", message: "expected name in attribute"), + DiagnosticSpec(locationMarker: "2️⃣", message: "unexpected 'self' keyword in accessor"), + DiagnosticSpec(locationMarker: "3️⃣", message: "expected '}' to end subscript"), + ] + ) + } }