diff --git a/Sources/SwiftParser/Attributes.swift b/Sources/SwiftParser/Attributes.swift index 2b22e6656e2..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)) @@ -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/Sources/SwiftParser/Declarations.swift b/Sources/SwiftParser/Declarations.swift index b6377692d73..9ce538da2f3 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( @@ -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/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..e192acf9ee0 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) @@ -2250,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/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/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/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/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/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() { 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( """ diff --git a/Tests/SwiftParserTest/ExpressionTests.swift b/Tests/SwiftParserTest/ExpressionTests.swift index da30b7ff3e5..828f5a9b2a0 100644 --- a/Tests/SwiftParserTest/ExpressionTests.swift +++ b/Tests/SwiftParserTest/ExpressionTests.swift @@ -1627,4 +1627,54 @@ final class StatementExpressionTests: XCTestCase { ) ) } + + func testPatternExprInSwitchCaseItem() { + AssertParse( + """ + switch x { + case a: + 1️⃣is + } + """, + diagnostics: [ + DiagnosticSpec(message: "unexpected 'is' keyword in 'switch' statement") + ] + ) + } + + func testStandaloneAtCaseInSwitch() { + AssertParse( + """ + switch x { + 1️⃣@case + } + """, + 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"#), + ] + ) + } + + func testStringLiteralAfterKeyPath() { + AssertParse( + #""" + \String.?1️⃣"" + """#, + diagnostics: [ + DiagnosticSpec(message: "consecutive statements on a line must be separated by ';'") + ] + ) + } } 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"), + ] + ) + } }