diff --git a/Sources/SwiftParser/Attributes.swift b/Sources/SwiftParser/Attributes.swift index 1c15f12c5d8..01b3757be4c 100644 --- a/Sources/SwiftParser/Attributes.swift +++ b/Sources/SwiftParser/Attributes.swift @@ -32,7 +32,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)) @@ -494,7 +494,7 @@ extension Parser { mutating func parseObjectiveCSelector() -> RawObjCSelectorSyntax { var elements = [RawObjCSelectorPieceSyntax]() var loopProgress = LoopProgressCondition() - while !self.at(any: [.eof, .rightParen]) && loopProgress.evaluate(currentToken) { + while loopProgress.evaluate(currentToken) { // Empty selector piece. if let colon = self.consume(if: .colon) { elements.append( @@ -507,7 +507,7 @@ extension Parser { continue } - if self.at(.identifier) || self.currentToken.isKeyword { + if self.at(any: [.identifier, .wildcardKeyword]) || self.currentToken.isKeyword { let name = self.consumeAnyToken() // If we hit a ')' we may have a zero-argument selector. @@ -531,6 +531,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 9e5ea361b87..cfcc439227f 100644 --- a/Sources/SwiftParser/Declarations.swift +++ b/Sources/SwiftParser/Declarations.swift @@ -165,7 +165,7 @@ extension Parser { public mutating func parseDeclaration(inMemberDeclList: Bool = false) -> RawDeclSyntax { switch self.at(anyIn: PoundDeclarationStart.self) { case (.poundIfKeyword, _)?: - 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 bf1557ae832..19691c97f03 100644 --- a/Sources/SwiftParser/Directives.swift +++ b/Sources/SwiftParser/Directives.swift @@ -66,7 +66,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 { @@ -83,7 +83,7 @@ extension Parser { do { var firstIteration = true var loopProgress = LoopProgressCondition() - while let poundIfHandle = self.canRecoverTo(any: firstIteration ? [.poundIfKeyword] : [.poundIfKeyword, .poundElseifKeyword, .poundElseKeyword]), + while let poundIfHandle = self.canRecoverTo(any: firstIteration ? [.poundIfKeyword] : [.poundElseifKeyword, .poundElseKeyword]), loopProgress.evaluate(self.currentToken) { let (unexpectedBeforePoundIf, poundIf) = self.eat(poundIfHandle) @@ -104,7 +104,7 @@ extension Parser { var elementsProgress = LoopProgressCondition() while !self.at(any: [.eof, .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 01a0179e0d3..6a6fbcc9e52 100644 --- a/Sources/SwiftParser/Expressions.swift +++ b/Sources/SwiftParser/Expressions.swift @@ -624,7 +624,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(any: [.period, .prefixPeriod]) { head = parser.parseDottedExpressionSuffix(nil) diff --git a/Sources/SwiftParser/RawTokenKindSubset.swift b/Sources/SwiftParser/RawTokenKindSubset.swift index b430554ec5e..a5f2f13d1ec 100644 --- a/Sources/SwiftParser/RawTokenKindSubset.swift +++ b/Sources/SwiftParser/RawTokenKindSubset.swift @@ -773,7 +773,6 @@ enum PrimaryExpressionStart: RawTokenKindSubset { enum ExpressionStart: RawTokenKindSubset { case awaitTryMove(AwaitTryMove) case expressionPrefixOperator(ExpressionPrefixOperator) - case matchingPatternStart(MatchingPatternStart) case primaryExpressionStart(PrimaryExpressionStart) init?(lexeme: Lexer.Lexeme) { @@ -781,8 +780,6 @@ enum ExpressionStart: RawTokenKindSubset { 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 { @@ -793,7 +790,6 @@ enum ExpressionStart: RawTokenKindSubset { 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) } @@ -801,7 +797,6 @@ enum ExpressionStart: RawTokenKindSubset { switch self { case .awaitTryMove(let underlyingKind): return underlyingKind.rawTokenKind case .expressionPrefixOperator(let underlyingKind): return underlyingKind.rawTokenKind - case .matchingPatternStart(let underlyingKind): return underlyingKind.rawTokenKind case .primaryExpressionStart(let underlyingKind): return underlyingKind.rawTokenKind } } @@ -810,7 +805,6 @@ enum ExpressionStart: RawTokenKindSubset { switch self { case .awaitTryMove(let underlyingKind): return underlyingKind.contextualKeyword case .expressionPrefixOperator(let underlyingKind): return underlyingKind.contextualKeyword - case .matchingPatternStart(let underlyingKind): return underlyingKind.contextualKeyword case .primaryExpressionStart(let underlyingKind): return underlyingKind.contextualKeyword } } diff --git a/Sources/SwiftParser/Statements.swift b/Sources/SwiftParser/Statements.swift index c7115158f08..5ae8a921345 100644 --- a/Sources/SwiftParser/Statements.swift +++ b/Sources/SwiftParser/Statements.swift @@ -752,7 +752,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) @@ -766,6 +766,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/TopLevel.swift b/Sources/SwiftParser/TopLevel.swift index e09154eaa18..4f86a684972 100644 --- a/Sources/SwiftParser/TopLevel.swift +++ b/Sources/SwiftParser/TopLevel.swift @@ -201,8 +201,8 @@ extension Parser { /// wrapping declaration instead of being consumed by lookeahead. private mutating func parseItem(isAtTopLevel: Bool = false, allowInitDecl: Bool = true) -> RawCodeBlockItemSyntax.Item { if self.at(.poundIfKeyword) { - 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 3c91fa50612..920813d4014 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 8859b1fd1c2..9cee079ef9c 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/StatementTests.swift b/Tests/SwiftParserTest/StatementTests.swift index aa9878b04a6..43b1a3da45c 100644 --- a/Tests/SwiftParserTest/StatementTests.swift +++ b/Tests/SwiftParserTest/StatementTests.swift @@ -603,4 +603,29 @@ final class StatementTests: 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")] + ) + } }