diff --git a/CodeGeneration/Sources/SyntaxSupport/CommonNodes.swift b/CodeGeneration/Sources/SyntaxSupport/CommonNodes.swift index 5e1b4f0a0b9..d3875576887 100644 --- a/CodeGeneration/Sources/SyntaxSupport/CommonNodes.swift +++ b/CodeGeneration/Sources/SyntaxSupport/CommonNodes.swift @@ -177,7 +177,7 @@ public let COMMON_NODES: [Node] = [ kind: .decl, base: .syntax, nameForDiagnostics: "declaration", - parserFunction: "parseDeclaration" + parserFunction: "parseDeclarationOrIfConfig" ), Node( @@ -401,4 +401,18 @@ public let COMMON_NODES: [Node] = [ elementChoices: [.syntax] ), + Node( + kind: .unexpectedCodeDecl, + base: .decl, + nameForDiagnostics: nil, + documentation: "Unexpected code at declaration position", + children: [ + Child( + name: "unexpectedCode", + // NOTE: This is not .collection() on purpose. We don't need collection related functions for this. + kind: .node(kind: .unexpectedNodes) + ) + ], + noInterleaveUnexpected: true + ), ] diff --git a/CodeGeneration/Sources/SyntaxSupport/Node.swift b/CodeGeneration/Sources/SyntaxSupport/Node.swift index 7a4eb9631b3..73fa9404835 100644 --- a/CodeGeneration/Sources/SyntaxSupport/Node.swift +++ b/CodeGeneration/Sources/SyntaxSupport/Node.swift @@ -127,7 +127,8 @@ public class Node: NodeChoiceConvertible { parserFunction: TokenSyntax? = nil, traits: [String] = [], children: [Child] = [], - childHistory: Child.History = [] + childHistory: Child.History = [], + noInterleaveUnexpected: Bool = false ) { precondition(base != .syntaxCollection) precondition(base.isBase, "unknown base kind '\(base)' for node '\(kind)'") @@ -140,7 +141,8 @@ public class Node: NodeChoiceConvertible { self.documentation = SwiftSyntax.Trivia.docCommentTrivia(from: documentation) self.parserFunction = parserFunction - let childrenWithUnexpected = kind.isBase ? children : interleaveUnexpectedChildren(children) + let childrenWithUnexpected = + (kind.isBase || noInterleaveUnexpected) ? children : interleaveUnexpectedChildren(children) self.data = .layout(children: childrenWithUnexpected, childHistory: childHistory, traits: traits) } diff --git a/CodeGeneration/Sources/SyntaxSupport/SyntaxNodeKind.swift b/CodeGeneration/Sources/SyntaxSupport/SyntaxNodeKind.swift index 85e93c701f1..70eae620e70 100644 --- a/CodeGeneration/Sources/SyntaxSupport/SyntaxNodeKind.swift +++ b/CodeGeneration/Sources/SyntaxSupport/SyntaxNodeKind.swift @@ -301,6 +301,7 @@ public enum SyntaxNodeKind: String, CaseIterable, IdentifierConvertible, TypeCon case typeSpecifier case lifetimeSpecifierArguments case typeSpecifierList + case unexpectedCodeDecl case unexpectedNodes case unresolvedAsExpr case unresolvedIsExpr diff --git a/CodeGeneration/Sources/generate-swift-syntax/templates/swiftparser/LayoutNodesParsableFile.swift b/CodeGeneration/Sources/generate-swift-syntax/templates/swiftparser/LayoutNodesParsableFile.swift index bb6006711f6..4c383adeee7 100644 --- a/CodeGeneration/Sources/generate-swift-syntax/templates/swiftparser/LayoutNodesParsableFile.swift +++ b/CodeGeneration/Sources/generate-swift-syntax/templates/swiftparser/LayoutNodesParsableFile.swift @@ -68,7 +68,7 @@ let layoutNodesParsableFile = SourceFileSyntax(leadingTrivia: copyrightHeader) { DeclSyntax( """ mutating func parseNonOptionalCodeBlockItem() -> RawCodeBlockItemSyntax { - guard let node = self.parseCodeBlockItem(isAtTopLevel: false, allowInitDecl: true) else { + guard let node = self.parseCodeBlockItem(allowInitDecl: true, until: { _ in false }) else { // The missing item is not necessary to be a declaration, // which is just a placeholder here return RawCodeBlockItemSyntax( diff --git a/CodeGeneration/Tests/ValidateSyntaxNodes/ValidateSyntaxNodes.swift b/CodeGeneration/Tests/ValidateSyntaxNodes/ValidateSyntaxNodes.swift index b61533bad21..a686edb059d 100644 --- a/CodeGeneration/Tests/ValidateSyntaxNodes/ValidateSyntaxNodes.swift +++ b/CodeGeneration/Tests/ValidateSyntaxNodes/ValidateSyntaxNodes.swift @@ -641,7 +641,12 @@ class ValidateSyntaxNodes: XCTestCase { assertFailuresMatchXFails( failures, - expectedFailures: [] + expectedFailures: [ + ValidationFailure( + node: .unexpectedCodeDecl, + message: "child 'unexpectedCode' is a SyntaxCollection but child is not marked as a collection" + ) + ] ) } diff --git a/Sources/SwiftParser/Attributes.swift b/Sources/SwiftParser/Attributes.swift index 27cc1b80363..9add4a654ae 100644 --- a/Sources/SwiftParser/Attributes.swift +++ b/Sources/SwiftParser/Attributes.swift @@ -33,11 +33,7 @@ extension Parser { // // In such cases, the second `#if` is not `consumeIfConfigOfAttributes()`. return .ifConfigDecl( - self.parsePoundIfDirective { (parser, _) -> RawAttributeListSyntax.Element in - return parser.parseAttributeListElement() - } syntax: { parser, attributes in - return .attributes(RawAttributeListSyntax(elements: attributes, arena: parser.arena)) - } + self.parsePoundIfDirective({ .attributes($0.parseAttributeList()) }) ) } else { return .attribute(self.parseAttribute()) @@ -896,7 +892,32 @@ extension Parser { return makeMissingProviderArguments(unexpectedBefore: []) } - let decl = parseDeclaration(in: .argumentList) + let decl: RawDeclSyntax + if self.at(.poundIf) { + // '#if' is not accepted in '@abi' attribute, but for recovery, parse it + // and wrap the first decl in it with unexpected nodes. + let ifConfig = self.parsePoundIfDirective({ parser in + let decl = parser.parseDeclaration(in: .argumentList) + let member = RawMemberBlockItemSyntax(decl: decl, semicolon: nil, arena: parser.arena) + return .decls(RawMemberBlockItemListSyntax(elements: [member], arena: parser.arena)) + }) + decl = ifConfig.makeUnexpectedKeepingFirstNode( + of: RawDeclSyntax.self, + arena: self.arena, + where: { !$0.is(RawIfConfigDeclSyntax.self) }, + makeMissing: { + RawDeclSyntax( + RawMissingDeclSyntax( + attributes: self.emptyCollection(RawAttributeListSyntax.self), + modifiers: self.emptyCollection(RawDeclModifierListSyntax.self), + arena: self.arena + ) + ) + } + ) + } else { + decl = self.parseDeclaration(in: .argumentList) + } guard let provider = RawABIAttributeArgumentsSyntax.Provider(decl) else { return makeMissingProviderArguments(unexpectedBefore: [decl.raw]) diff --git a/Sources/SwiftParser/CollectionNodes+Parsable.swift b/Sources/SwiftParser/CollectionNodes+Parsable.swift index da31a9424ec..3424de77418 100644 --- a/Sources/SwiftParser/CollectionNodes+Parsable.swift +++ b/Sources/SwiftParser/CollectionNodes+Parsable.swift @@ -116,7 +116,7 @@ extension CodeBlockItemListSyntax: SyntaxParseable { extension MemberBlockItemListSyntax: SyntaxParseable { public static func parse(from parser: inout Parser) -> Self { return parse(from: &parser) { parser in - return parser.parseMemberDeclList() + return parser.parseMemberDeclList(until: { _ in false }) } makeMissing: { remainingTokens, arena in let missingDecl = RawMissingDeclSyntax( attributes: RawAttributeListSyntax(elements: [], arena: arena), diff --git a/Sources/SwiftParser/CompilerFiles.swift b/Sources/SwiftParser/CompilerFiles.swift index dbaea1a1902..2424c7964a6 100644 --- a/Sources/SwiftParser/CompilerFiles.swift +++ b/Sources/SwiftParser/CompilerFiles.swift @@ -89,7 +89,7 @@ extension Parser { /// Parse a declaration macro expansions in type contexts. mutating func parseMemberBlockItemListFile() -> RawMemberBlockItemListFileSyntax { - let members = self.parseMemberDeclList() + let members = self.parseMemberDeclList(until: { _ in false }) let (unexpectedBeforeEndOfFileToken, endOfFile) = self.expectEndOfFile() return RawMemberBlockItemListFileSyntax( diff --git a/Sources/SwiftParser/Declarations.swift b/Sources/SwiftParser/Declarations.swift index 97e2c3e2c8b..8a8b7481a43 100644 --- a/Sources/SwiftParser/Declarations.swift +++ b/Sources/SwiftParser/Declarations.swift @@ -52,15 +52,20 @@ extension TokenConsumer { } } + /// Returns `true` if the current token represents the start of a declaration + /// item. + /// + /// - Parameters + /// - allowInitDecl: whether to consider 'init' a declaration in the context. + /// Only initializer bodies should use `false` for this. + /// - requiresDecl: Whether only declarations are expected in the context. + /// For example, in member blocks. + /// + /// - Note: this returns `false` for `#if` unless it's an attribute list. mutating func atStartOfDeclaration( - isAtTopLevel: Bool = false, allowInitDecl: Bool = true, - allowRecovery: Bool = false + requiresDecl: Bool = false ) -> Bool { - if self.at(.poundIf) { - return true - } - var subparser = self.lookahead() var hasAttribute = false @@ -70,7 +75,6 @@ extension TokenConsumer { _ = subparser.consumeAttributeList() hasAttribute = true } else if subparser.at(.poundIf) && subparser.consumeIfConfigOfAttributes() { - subparser.skipSingle() hasAttribute = true } else { break @@ -105,17 +109,7 @@ extension TokenConsumer { } } - let declStartKeyword: DeclarationKeyword? - if allowRecovery { - declStartKeyword = - subparser.canRecoverTo( - anyIn: DeclarationKeyword.self, - overrideRecoveryPrecedence: isAtTopLevel ? nil : .closingBrace - )?.0 - } else { - declStartKeyword = subparser.at(anyIn: DeclarationKeyword.self)?.0 - } - switch declStartKeyword { + switch subparser.at(anyIn: DeclarationKeyword.self)?.0 { case .lhs(.actor): // actor Foo {} if subparser.peek().rawTokenKind == .identifier { @@ -127,12 +121,12 @@ extension TokenConsumer { var lookahead = subparser.lookahead() repeat { lookahead.consumeAnyToken() - } while lookahead.atStartOfDeclaration(isAtTopLevel: isAtTopLevel, allowInitDecl: allowInitDecl) + } while lookahead.atStartOfDeclaration(allowInitDecl: allowInitDecl, requiresDecl: requiresDecl) return lookahead.at(.identifier) case .lhs(.case): // When 'case' appears inside a function, it's probably a switch // case, not an enum case declaration. - return false + return requiresDecl case .lhs(.`init`): return allowInitDecl case .lhs(.macro): @@ -140,7 +134,7 @@ extension TokenConsumer { return subparser.peek().rawTokenKind == .identifier case .lhs(.pound): // Force parsing '#' after attributes as a macro expansion decl. - if hasAttribute || hasModifier { + if hasAttribute || hasModifier || requiresDecl { return true } @@ -176,13 +170,29 @@ extension TokenConsumer { if subparser.at(anyIn: ContextualDeclKeyword.self)?.0 != nil { subparser.consumeAnyToken() return subparser.atStartOfDeclaration( - isAtTopLevel: isAtTopLevel, allowInitDecl: allowInitDecl, - allowRecovery: allowRecovery + requiresDecl: requiresDecl ) } - if allowRecovery && (hasAttribute || hasModifier) { + if requiresDecl { // If we found any attributes or modifiers, consider it's a missing decl. + if hasAttribute || hasModifier { + return true + } + if subparser.atFunctionDeclarationWithoutFuncKeyword() { + return true + } + if subparser.atBindingDeclarationWithoutVarKeyword() { + return true + } + if subparser.currentToken.isEditorPlaceholder { + return true + } + } + // Special recovery for 'try let/var'. + if subparser.at(.keyword(.try)), + subparser.peek(isAtAnyIn: VariableDeclSyntax.BindingSpecifierOptions.self) != nil + { return true } return false @@ -256,50 +266,6 @@ extension Parser { /// - Parameter context: Describes the code around the declaration being parsed. This affects how the parser tries /// to recover from malformed syntax in the declaration. mutating func parseDeclaration(in context: DeclarationParseContext = .topLevelOrCodeBlock) -> RawDeclSyntax { - // If we are at a `#if` of attributes, the `#if` directive should be - // parsed when we're parsing the attributes. - if self.at(.poundIf) && !self.withLookahead({ $0.consumeIfConfigOfAttributes() }) { - let directive = self.parsePoundIfDirective { (parser, _) in - let parsedDecl = parser.parseDeclaration() - let semicolon = parser.consume(if: .semicolon) - return RawMemberBlockItemSyntax( - decl: parsedDecl, - semicolon: semicolon, - arena: parser.arena - ) - } addSemicolonIfNeeded: { lastElement, newItemAtStartOfLine, parser in - if lastElement.semicolon == nil && !newItemAtStartOfLine { - return RawMemberBlockItemSyntax( - lastElement.unexpectedBeforeDecl, - decl: lastElement.decl, - lastElement.unexpectedBetweenDeclAndSemicolon, - semicolon: parser.missingToken(.semicolon), - lastElement.unexpectedAfterSemicolon, - arena: parser.arena - ) - } else { - return nil - } - } syntax: { parser, elements in - return .decls(RawMemberBlockItemListSyntax(elements: elements, arena: parser.arena)) - } - if !context.allowsIfConfigDecl { - // Convert the IfConfig to unexpected syntax around the first decl inside it, if any. - return directive.makeUnexpectedKeepingFirstNode(of: RawDeclSyntax.self, arena: self.arena) { node in - return !node.is(RawIfConfigDeclSyntax.self) - } makeMissing: { - return RawDeclSyntax( - RawMissingDeclSyntax( - attributes: self.emptyCollection(RawAttributeListSyntax.self), - modifiers: self.emptyCollection(RawDeclModifierListSyntax.self), - arena: self.arena - ) - ) - } - } - return RawDeclSyntax(directive) - } - let attrs = DeclAttributes( attributes: self.parseAttributeList(), modifiers: self.parseDeclModifierList() @@ -313,11 +279,11 @@ extension Parser { // We aren't at a declaration keyword and it looks like we are at a function // declaration. Parse a function declaration. recoveryResult = (.lhs(.func), .missing(.keyword(.func))) + } else if atBindingDeclarationWithoutVarKeyword() { + recoveryResult = (.rhs(.var), .missing(.keyword(.var))) } else { // In all other cases, use standard token recovery to find the declaration // to parse. - // If we are inside a memberDecl list, we don't want to eat closing braces (which most likely close the outer context) - // while recovering to the declaration start. recoveryResult = self.canRecoverTo( anyIn: DeclarationKeyword.self, overrideRecoveryPrecedence: context.recoveryPrecedence @@ -380,13 +346,6 @@ extension Parser { } if context.requiresDecl { - let isProbablyVarDecl = self.at(.identifier, .wildcard) && self.peek(isAt: .colon, .equal, .comma) - let isProbablyTupleDecl = self.at(.leftParen) && self.peek(isAt: .identifier, .wildcard) - - if isProbablyVarDecl || isProbablyTupleDecl { - return RawDeclSyntax(self.parseBindingDeclaration(attrs, .missing(.keyword(.var)), in: context)) - } - if self.currentToken.isEditorPlaceholder { let placeholder = self.parseAnyIdentifier() return RawDeclSyntax( @@ -398,10 +357,6 @@ extension Parser { ) ) } - - if atFunctionDeclarationWithoutFuncKeyword() { - return RawDeclSyntax(self.parseFuncDeclaration(attrs, .missing(.keyword(.func)))) - } } return RawDeclSyntax( RawMissingDeclSyntax( @@ -411,6 +366,27 @@ extension Parser { ) ) } +} + +extension TokenConsumer { + /// Returns `true` if it looks like the parser is positioned at a variable declaration that’s missing the `var` keyword. + fileprivate mutating func atBindingDeclarationWithoutVarKeyword() -> Bool { + if self.at(.identifier, .wildcard), + self.peek(isAt: .colon, .equal, .comma) + { + return true + } + if self.at(.leftParen), + self.peek(isAt: .identifier, .wildcard), + self.withLookahead({ + $0.skipSingle(); return $0.at(.colon, .equal) + }) + { + return true + } + + return false + } /// Returns `true` if it looks like the parser is positioned at a function declaration that’s missing the `func` keyword. fileprivate mutating func atFunctionDeclarationWithoutFuncKeyword() -> Bool { @@ -960,7 +936,7 @@ extension Parser { } extension Parser { - mutating func parseMemberBlockItem() -> RawMemberBlockItemSyntax? { + mutating func parseMemberBlockItem(until stopCondition: (inout Parser) -> Bool) -> RawMemberBlockItemSyntax? { let startToken = self.currentToken if let syntax = self.loadCurrentSyntaxNodeFromCache(for: .memberBlockItem) { self.registerNodeForIncrementalParse(node: syntax.raw, startToken: startToken) @@ -981,16 +957,41 @@ extension Parser { } let decl: RawDeclSyntax + let attachSemi: Bool if self.at(.poundSourceLocation) { decl = RawDeclSyntax(self.parsePoundSourceLocationDirective()) - } else { + attachSemi = false + } else if self.at(.poundIf) && !self.withLookahead({ $0.consumeIfConfigOfAttributes() }) { + decl = RawDeclSyntax( + self.parsePoundIfDirective { parser in + return .decls(parser.parseMemberDeclList(until: { $0.atEndOfIfConfigClauseBody() })) + } + ) + attachSemi = false + } else if self.atStartOfDeclaration(allowInitDecl: true, requiresDecl: true) { decl = self.parseDeclaration(in: .memberDeclList) + attachSemi = true + } else { + // Otherwise, eat the unexpected tokens into an "decl". + decl = RawDeclSyntax( + self.parseUnexpectedCodeDeclaration(allowInitDecl: true, requiresDecl: true, until: stopCondition) + ) + attachSemi = true } - let semi = self.consume(if: .semicolon) + let semi: RawTokenSyntax? var trailingSemis: [RawTokenSyntax] = [] - while let trailingSemi = self.consume(if: .semicolon) { - trailingSemis.append(trailingSemi) + if attachSemi { + if !decl.isEmpty { + semi = self.consume(if: .semicolon) + } else { + semi = nil + } + while let trailingSemi = self.consume(if: .semicolon) { + trailingSemis.append(trailingSemi) + } + } else { + semi = nil } if decl.isEmpty && semi == nil && trailingSemis.isEmpty { @@ -1009,16 +1010,20 @@ extension Parser { return result } - mutating func parseMemberDeclList() -> RawMemberBlockItemListSyntax { + mutating func parseMemberDeclList( + until stopCondition: (inout Parser) -> Bool = { $0.at(.rightBrace) || $0.atEndOfIfConfigClauseBody() } + ) -> RawMemberBlockItemListSyntax { var elements = [RawMemberBlockItemSyntax]() do { var loopProgress = LoopProgressCondition() - while !self.at(.endOfFile, .rightBrace) && self.hasProgressed(&loopProgress) { + while !stopCondition(&self), !self.at(.endOfFile), self.hasProgressed(&loopProgress) { let newItemAtStartOfLine = self.atStartOfLine - guard let newElement = self.parseMemberBlockItem() else { + guard let newElement = self.parseMemberBlockItem(until: stopCondition) else { break } - if let lastItem = elements.last, lastItem.semicolon == nil && !newItemAtStartOfLine { + if let lastItem = elements.last, + lastItem.semicolon == nil && !newItemAtStartOfLine && !newElement.decl.is(RawUnexpectedCodeDeclSyntax.self) + { elements[elements.count - 1] = RawMemberBlockItemSyntax( lastItem.unexpectedBeforeDecl, decl: lastItem.decl, @@ -1762,7 +1767,7 @@ extension Parser { // There can only be an implicit getter if no other accessors were // seen before this one. guard let accessorList else { - let body = parseCodeBlockItemList(until: { $0.at(.rightBrace) }) + let body = parseCodeBlockItemList() let (unexpectedBeforeRBrace, rbrace) = self.expect(.rightBrace) return RawAccessorBlockSyntax( @@ -2322,4 +2327,64 @@ extension Parser { arena: self.arena ) } + + /// Eats tokens until a start of decl, statement, or expression. + /// Returns consumed tokens as a `RawUnexpectedCodeDeclSyntax` declaration. + mutating func parseUnexpectedCodeDeclaration( + allowInitDecl: Bool, + requiresDecl: Bool, + until stopCondition: (inout Parser) -> Bool + ) -> RawUnexpectedCodeDeclSyntax { + var unexpectedTokens = [RawSyntax]() + var loopProgress = LoopProgressCondition() + while !self.at(.endOfFile, .semicolon), !stopCondition(&self), self.hasProgressed(&loopProgress) { + let numTokensToSkip = self.withLookahead { + $0.skipSingle() + return $0.tokensConsumed + } + for _ in 0.. RawDeclSyntax { + if self.at(.poundIf) && !self.withLookahead({ $0.consumeIfConfigOfAttributes() }) { + return RawDeclSyntax( + self.parsePoundIfDirective({ + .decls($0.parseMemberDeclList(until: { $0.atEndOfIfConfigClauseBody() })) + }) + ) + } else { + return parseDeclaration(in: .memberDeclList) + } + } } diff --git a/Sources/SwiftParser/Directives.swift b/Sources/SwiftParser/Directives.swift index 7fe3c89a6c9..cd26429f3ca 100644 --- a/Sources/SwiftParser/Directives.swift +++ b/Sources/SwiftParser/Directives.swift @@ -17,6 +17,10 @@ #endif extension Parser { + mutating func atEndOfIfConfigClauseBody() -> Bool { + return self.at(.poundElseif, .poundElse, .poundEndif) || self.atElifTypo() + } + private enum IfConfigContinuationClauseStartKeyword: TokenSpecSet { case poundElseif case poundElse @@ -42,25 +46,10 @@ extension Parser { /// Parse a conditional compilation block. /// - /// This function should be used to parse conditional compilation statements, - /// declarations, and expressions. It is generic over the particular kind of - /// parse that must occur for these elements, and allows a context-specific - /// syntax kind to be emitted to collect the results. For example, declaration - /// parsing parses items and collects the items into a ``MemberDeclListSyntax`` - /// node. - /// /// - Parameters: - /// - parseElement: Parse an element of the conditional compilation block. - /// - addSemicolonIfNeeded: If elements need to be separated by a newline, this - /// allows the insertion of missing semicolons to the - /// previous element. - /// - syntax: A function that aggregates the parsed conditional elements - /// into a syntax collection. - mutating func parsePoundIfDirective( - _ parseElement: (_ parser: inout Parser, _ isFirstElement: Bool) -> Element?, - addSemicolonIfNeeded: - (_ lastElement: Element, _ newItemAtStartOfLine: Bool, _ parser: inout Parser) -> Element? = { _, _, _ in nil }, - syntax: (inout Parser, [Element]) -> RawIfConfigClauseSyntax.Elements? + /// - parseBody: Parse a body of single conditional compilation clause. + mutating func parsePoundIfDirective( + _ parseBody: (_ parser: inout Parser) -> RawIfConfigClauseSyntax.Elements? ) -> RawIfConfigDeclSyntax { if let remainingTokens = remainingTokensIfMaximumNestingLevelReached() { return RawIfConfigDeclSyntax( @@ -84,7 +73,7 @@ extension Parser { poundKeyword: poundIf, condition: condition, unexpectedBetweenConditionAndElements, - elements: syntax(&self, parseIfConfigClauseElements(parseElement, addSemicolonIfNeeded: addSemicolonIfNeeded)), + elements: parseBody(&self), arena: self.arena ) ) @@ -145,10 +134,7 @@ extension Parser { poundKeyword: pound, condition: condition, unexpectedBetweenConditionAndElements, - elements: syntax( - &self, - parseIfConfigClauseElements(parseElement, addSemicolonIfNeeded: addSemicolonIfNeeded) - ), + elements: parseBody(&self), arena: self.arena ) ) @@ -182,31 +168,6 @@ extension Parser { // `#elif` or `#elif(…)` could be macro invocations. return lookahead.at(TokenSpec(.identifier, allowAtStartOfLine: false)) } - - private mutating func parseIfConfigClauseElements( - _ parseElement: (_ parser: inout Parser, _ isFirstElement: Bool) -> Element?, - addSemicolonIfNeeded: (_ lastElement: Element, _ newItemAtStartOfLine: Bool, _ parser: inout Parser) -> Element? - ) -> [Element] { - var elements = [Element]() - var elementsProgress = LoopProgressCondition() - while !self.at(.endOfFile) - && !self.at(.poundElse, .poundElseif, .poundEndif) - && !self.atElifTypo() - && self.hasProgressed(&elementsProgress) - { - let newItemAtStartOfLine = self.atStartOfLine - guard let element = parseElement(&self, elements.isEmpty), !element.isEmpty else { - break - } - if let lastElement = elements.last, - let fixedUpLastItem = addSemicolonIfNeeded(lastElement, newItemAtStartOfLine, &self) - { - elements[elements.count - 1] = fixedUpLastItem - } - elements.append(element) - } - return elements - } } extension Parser { diff --git a/Sources/SwiftParser/Expressions.swift b/Sources/SwiftParser/Expressions.swift index 09a9b0e3ce6..b768e586462 100644 --- a/Sources/SwiftParser/Expressions.swift +++ b/Sources/SwiftParser/Expressions.swift @@ -720,10 +720,7 @@ extension Parser { ) -> RawExprSyntax { precondition(self.at(.poundIf)) - let config = self.parsePoundIfDirective { (parser, isFirstElement) -> RawExprSyntax? in - if !isFirstElement { - return nil - } + let config = self.parsePoundIfDirective { parser in let head: RawExprSyntax if parser.at(.period) { head = parser.parseDottedExpressionSuffix(nil) @@ -738,15 +735,7 @@ extension Parser { flavor: flavor, pattern: .none ) - - // TODO: diagnose and skip the remaining token in the current clause. - return result - } syntax: { (parser, elements) -> RawIfConfigClauseSyntax.Elements? in - switch elements.count { - case 0: return nil - case 1: return .postfixExpression(elements.first!) - default: fatalError("Postfix #if should only have one element") - } + return .postfixExpression(result) } return RawExprSyntax( @@ -1784,7 +1773,7 @@ extension Parser { let signature = self.parseClosureSignatureIfPresent() // Parse the body. - let elements = parseCodeBlockItemList(until: { $0.at(.rightBrace) }) + let elements = parseCodeBlockItemList() // Parse the closing '}'. let (unexpectedBeforeRBrace, rbrace) = self.expect(.rightBrace) @@ -2342,32 +2331,20 @@ extension Parser { mutating func parseSwitchCases(allowStandaloneStmtRecovery: Bool) -> RawSwitchCaseListSyntax { var elements = [RawSwitchCaseListSyntax.Element]() var elementsProgress = LoopProgressCondition() - while !self.at(.endOfFile, .rightBrace) && !self.at(.poundEndif, .poundElseif, .poundElse) - && self.hasProgressed(&elementsProgress) - { - if self.withLookahead({ $0.atStartOfSwitchCase(allowRecovery: false) }) { + while !self.at(.endOfFile, .rightBrace), !self.atEndOfIfConfigClauseBody(), self.hasProgressed(&elementsProgress) { + if self.withLookahead({ $0.atStartOfSwitchCase() }) { elements.append(.switchCase(self.parseSwitchCase())) } else if self.canRecoverTo(.poundIf) != nil { // '#if' in 'case' position can enclose zero or more 'case' or 'default' // clauses. elements.append( .ifConfigDecl( - self.parsePoundIfDirective( - { (parser, _) in parser.parseSwitchCases(allowStandaloneStmtRecovery: allowStandaloneStmtRecovery) }, - syntax: { parser, cases in - guard cases.count == 1, let firstCase = cases.first else { - precondition(cases.isEmpty) - return .switchCases(RawSwitchCaseListSyntax(elements: [], arena: parser.arena)) - } - return .switchCases(firstCase) - } - ) + self.parsePoundIfDirective({ parser in + .switchCases(parser.parseSwitchCases(allowStandaloneStmtRecovery: allowStandaloneStmtRecovery)) + }) ) ) - } else if allowStandaloneStmtRecovery - && (self.atStartOfExpression() || self.atStartOfStatement(preferExpr: false) - || self.atStartOfDeclaration()) - { + } else if allowStandaloneStmtRecovery { // Synthesize a label for the statement or declaration that isn't covered by a case right now. let statements = parseSwitchCaseBody() if statements.isEmpty { @@ -2403,8 +2380,6 @@ extension Parser { ) ) ) - } else if self.withLookahead({ $0.atStartOfSwitchCase(allowRecovery: true) }) { - elements.append(.switchCase(self.parseSwitchCase())) } else { break } @@ -2414,8 +2389,13 @@ extension Parser { mutating func parseSwitchCaseBody() -> RawCodeBlockItemListSyntax { parseCodeBlockItemList(until: { - $0.at(.rightBrace) || $0.at(.poundEndif, .poundElseif, .poundElse) - || $0.withLookahead({ $0.atStartOfConditionalSwitchCases() }) + if $0.at(.rightBrace, .keyword(.case), .keyword(.default)) || $0.atEndOfIfConfigClauseBody() { + return true + } + if $0.withLookahead({ $0.atStartOfConditionalSwitchCases() }) { + return true + } + return false }) } diff --git a/Sources/SwiftParser/Statements.swift b/Sources/SwiftParser/Statements.swift index 619794f19e7..53ee258ce81 100644 --- a/Sources/SwiftParser/Statements.swift +++ b/Sources/SwiftParser/Statements.swift @@ -21,31 +21,15 @@ extension TokenConsumer { /// item. /// /// - Parameters: - /// - allowRecovery: Whether to attempt to perform recovery. /// - preferExpr: If either an expression or statement could be /// parsed and this parameter is `true`, the function returns `false` /// such that an expression can be parsed. /// /// - Note: This function must be kept in sync with `parseStatement()`. /// - Seealso: ``Parser/parseStatement()`` - func atStartOfStatement(allowRecovery: Bool = false, preferExpr: Bool) -> Bool { + func atStartOfStatement(preferExpr: Bool) -> Bool { var lookahead = self.lookahead() - if allowRecovery { - // Attributes are not allowed on statements. But for recovery, skip over - // misplaced attributes. - _ = lookahead.consumeAttributeList() - } - return lookahead.atStartOfStatement(allowRecovery: allowRecovery, preferExpr: preferExpr) - } -} - -extension Parser.Lookahead { - mutating func atStartOfSwitchCaseItem() -> Bool { - while self.consume(if: .atSign) != nil { - self.consume(if: .identifier) - } - - return self.at(anyIn: SwitchCaseStart.self) != nil + return lookahead.atStartOfStatement(preferExpr: preferExpr) } } @@ -141,7 +125,9 @@ extension Parser { mutating func parseGuardStatement(guardHandle: RecoveryConsumptionHandle) -> RawGuardStmtSyntax { let (unexpectedBeforeGuardKeyword, guardKeyword) = self.eat(guardHandle) let conditions = self.parseConditionList(isGuardStatement: true) - let (unexpectedBeforeElseKeyword, elseKeyword) = self.expect(.keyword(.else)) + let (unexpectedBeforeElseKeyword, elseKeyword) = self.expect( + TokenSpec(.else, recoveryPrecedence: .openingBrace(closingDelimiter: .rightBrace)) + ) let body = self.parseCodeBlock(introducer: guardKeyword) return RawGuardStmtSyntax( unexpectedBeforeGuardKeyword, @@ -703,6 +689,9 @@ extension Parser { if self.atStartOfStatement(preferExpr: true) || self.atStartOfDeclaration() { return false } + if self.atStartOfLine && self.withLookahead({ $0.atStartOfSwitchCase() }) { + return false + } return true } @@ -1032,29 +1021,15 @@ extension Parser.Lookahead { /// item. /// /// - Parameters: - /// - allowRecovery: Whether to attempt to perform recovery. /// - preferExpr: If either an expression or statement could be /// parsed and this parameter is `true`, the function returns `false` /// such that an expression can be parsed. /// /// - Note: This function must be kept in sync with `parseStatement()`. /// - Seealso: ``Parser/parseStatement()`` - mutating func atStartOfStatement(allowRecovery: Bool = false, preferExpr: Bool) -> Bool { - if (self.at(anyIn: SwitchCaseStart.self) != nil || self.at(.atSign)) - && withLookahead({ $0.atStartOfSwitchCaseItem() }) - { - // We consider SwitchCaseItems statements so we don't parse the start of a new case item as trailing parts of an expression. - return true - } - + mutating func atStartOfStatement(preferExpr: Bool) -> Bool { _ = self.consume(if: .identifier, followedBy: .colon) - let switchSubject: CanBeStatementStart? - if allowRecovery { - switchSubject = self.canRecoverTo(anyIn: CanBeStatementStart.self)?.0 - } else { - switchSubject = self.at(anyIn: CanBeStatementStart.self)?.0 - } - switch switchSubject { + switch self.at(anyIn: CanBeStatementStart.self)?.0 { case .return?, .throw?, .defer?, @@ -1087,28 +1062,25 @@ extension Parser.Lookahead { ) case nil: + // Special recovery 'try return' etc.. + if !preferExpr, + consume(if: .keyword(.try)) != nil, + self.at(anyIn: SingleValueStatementExpression.self) == nil + { + return atStartOfStatement(preferExpr: preferExpr) + } return false } } /// Returns whether the parser's current position is the start of a switch case, /// given that we're in the middle of a switch already. - mutating func atStartOfSwitchCase(allowRecovery: Bool = false) -> Bool { + mutating func atStartOfSwitchCase() -> Bool { // Check for and consume attributes. The only valid attribute is `@unknown` // but that's a semantic restriction. var lookahead = self.lookahead() - var loopProgress = LoopProgressCondition() - var hasAttribute = false - while lookahead.at(.atSign) && lookahead.hasProgressed(&loopProgress) { - guard lookahead.peek().rawTokenKind == .identifier else { - return false - } - - lookahead.eat(.atSign) - lookahead.eat(.identifier) - hasAttribute = true - } + let hasAttribute = lookahead.consumeAttributeList() if hasAttribute && lookahead.at(.rightBrace) { // If we are at an attribute that's the last token in the SwitchCase, parse // that as an attribute to a missing 'case'. That way, if the developer writes @@ -1118,11 +1090,7 @@ extension Parser.Lookahead { return true } - if allowRecovery { - return lookahead.canRecoverTo(anyIn: SwitchCaseStart.self) != nil - } else { - return lookahead.at(anyIn: SwitchCaseStart.self) != nil - } + return lookahead.at(anyIn: SwitchCaseStart.self) != nil } mutating func atStartOfConditionalSwitchCases() -> Bool { diff --git a/Sources/SwiftParser/TokenPrecedence.swift b/Sources/SwiftParser/TokenPrecedence.swift index 55dd9ffb769..212d6dfbcfa 100644 --- a/Sources/SwiftParser/TokenPrecedence.swift +++ b/Sources/SwiftParser/TokenPrecedence.swift @@ -38,8 +38,6 @@ enum TokenPrecedence: Comparable { case mediumPunctuator /// The closing delimiter of `weakBracketed` case weakBracketClose - /// Keywords that start a new statement. - case stmtKeyword /// The '{' token because it typically marks the body of a declaration. /// `closingDelimiter` must have type `strongPunctuator` case openingBrace(closingDelimiter: RawTokenKind) @@ -47,7 +45,9 @@ enum TokenPrecedence: Comparable { case strongPunctuator /// The closing delimiter of `strongBracketed` case closingBrace - /// Tokens that start a new declaration + /// Keywords that start a new statement. + case stmtKeyword + /// Keywords that start a new declaration case declKeyword case openingPoundIf case closingPoundIf @@ -84,20 +84,18 @@ enum TokenPrecedence: Comparable { return 5 case .weakBracketClose: return 6 - case .stmtKeyword: - return 7 case .strongPunctuator: - return 8 + return 7 case .openingBrace: + return 8 + case .declKeyword, .stmtKeyword: return 9 case .closingBrace: return 10 - case .declKeyword: - return 11 case .openingPoundIf: - return 12 + return 11 case .closingPoundIf: - return 13 + return 12 } } diff --git a/Sources/SwiftParser/TopLevel.swift b/Sources/SwiftParser/TopLevel.swift index 5bdc10fddf1..9ceaeed59d0 100644 --- a/Sources/SwiftParser/TopLevel.swift +++ b/Sources/SwiftParser/TopLevel.swift @@ -67,18 +67,21 @@ extension Parser { extension Parser { mutating func parseCodeBlockItemList( - isAtTopLevel: Bool = false, allowInitDecl: Bool = true, - until stopCondition: (inout Parser) -> Bool + until stopCondition: (inout Parser) -> Bool = { $0.at(.rightBrace) || $0.atEndOfIfConfigClauseBody() } ) -> RawCodeBlockItemListSyntax { var elements = [RawCodeBlockItemSyntax]() var loopProgress = LoopProgressCondition() - while !stopCondition(&self), self.hasProgressed(&loopProgress) { + while !stopCondition(&self), !self.at(.endOfFile), self.hasProgressed(&loopProgress) { let newItemAtStartOfLine = self.atStartOfLine - guard let newElement = self.parseCodeBlockItem(isAtTopLevel: isAtTopLevel, allowInitDecl: allowInitDecl) else { + guard let newItem = self.parseCodeBlockItem(allowInitDecl: allowInitDecl, until: stopCondition) else { break } - if let lastItem = elements.last, lastItem.semicolon == nil && !newItemAtStartOfLine { + if let lastItem = elements.last, + lastItem.semicolon == nil, + !newItemAtStartOfLine, + !newItem.item.is(RawUnexpectedCodeDeclSyntax.self) + { elements[elements.count - 1] = RawCodeBlockItemSyntax( lastItem.unexpectedBeforeItem, item: .init(lastItem.item)!, @@ -88,14 +91,14 @@ extension Parser { arena: self.arena ) } - elements.append(newElement) + elements.append(newItem) } return .init(elements: elements, arena: self.arena) } /// Parse the top level items in a source file. mutating func parseTopLevelCodeBlockItems() -> RawCodeBlockItemListSyntax { - return parseCodeBlockItemList(isAtTopLevel: true, until: { _ in false }) + return parseCodeBlockItemList(until: { _ in false }) } /// The optional form of `parseCodeBlock` that checks to see if the parser has @@ -117,7 +120,7 @@ extension Parser { /// indented to close this code block or a surrounding context. See `expectRightBrace`. mutating func parseCodeBlock(introducer: RawTokenSyntax? = nil, allowInitDecl: Bool = true) -> RawCodeBlockSyntax { let (unexpectedBeforeLBrace, lbrace) = self.expect(.leftBrace) - let itemList = parseCodeBlockItemList(allowInitDecl: allowInitDecl, until: { $0.at(.rightBrace) }) + let itemList = parseCodeBlockItemList(allowInitDecl: allowInitDecl) let (unexpectedBeforeRBrace, rbrace) = self.expectRightBrace(leftBrace: lbrace, introducer: introducer) return .init( @@ -134,7 +137,10 @@ extension Parser { /// /// Returns `nil` if the parser did not consume any tokens while trying to /// parse the code block item. - mutating func parseCodeBlockItem(isAtTopLevel: Bool, allowInitDecl: Bool) -> RawCodeBlockItemSyntax? { + mutating func parseCodeBlockItem( + allowInitDecl: Bool, + until stopCondition: (inout Parser) -> Bool + ) -> RawCodeBlockItemSyntax? { let startToken = self.currentToken if let syntax = self.loadCurrentSyntaxNodeFromCache(for: .codeBlockItem) { self.registerNodeForIncrementalParse(node: syntax.raw, startToken: startToken) @@ -149,7 +155,34 @@ extension Parser { arena: self.arena ) } - if self.at(.keyword(.case), .keyword(.default)) { + + let item: RawCodeBlockItemSyntax.Item + let attachSemi: Bool + if self.at(.poundIf) && !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 { parser in + let items = parser.parseCodeBlockItemList( + allowInitDecl: allowInitDecl, + until: { $0.atEndOfIfConfigClauseBody() } + ) + return .statements(items) + } + item = .init(decl: directive) + attachSemi = false + } else if self.at(.poundSourceLocation) { + item = .init(decl: self.parsePoundSourceLocationDirective()) + attachSemi = false + } else if self.atStartOfDeclaration(allowInitDecl: allowInitDecl) { + item = .decl(self.parseDeclaration()) + attachSemi = true + } else if self.atStartOfStatement(preferExpr: false) { + item = self.parseStatementItem() + attachSemi = true + } else if self.atStartOfExpression() { + item = .expr(self.parseExpression(flavor: .basic, pattern: .none)) + attachSemi = true + } else if self.withLookahead({ $0.atStartOfSwitchCase() }) { // 'case' and 'default' are invalid in code block items. // Parse them and put them in their own CodeBlockItem but as an unexpected node. let switchCase = self.parseSwitchCase() @@ -159,16 +192,33 @@ extension Parser { semicolon: nil, arena: self.arena ) + } else if (self.at(.atSign) && peek(isAt: .identifier)) || self.at(anyIn: DeclarationModifier.self) != nil { + // Force parsing '@' as a declaration, as there's no valid + // expression or statement starting with an attribute. + item = .decl(self.parseDeclaration()) + attachSemi = true + } else { + // Otherwise, eat the unexpected tokens into an "decl". + item = .decl( + RawDeclSyntax( + self.parseUnexpectedCodeDeclaration(allowInitDecl: allowInitDecl, requiresDecl: false, until: stopCondition) + ) + ) + attachSemi = true } - let item = self.parseItem(isAtTopLevel: isAtTopLevel, allowInitDecl: allowInitDecl) - let semi = self.consume(if: .semicolon) + let semi: RawTokenSyntax? var trailingSemis: [RawTokenSyntax] = [] - while let trailingSemi = self.consume(if: .semicolon) { - trailingSemis.append(trailingSemi) + if attachSemi { + semi = self.consume(if: .semicolon) + while let trailingSemi = self.consume(if: .semicolon) { + trailingSemis.append(trailingSemi) + } + } else { + semi = nil } - if item.raw.isEmpty && semi == nil && trailingSemis.isEmpty { + if item.isEmpty && semi == nil && trailingSemis.isEmpty { return nil } @@ -180,7 +230,6 @@ extension Parser { ) self.registerNodeForIncrementalParse(node: result.raw, startToken: startToken) - return result } @@ -211,56 +260,4 @@ extension Parser { } return .stmt(stmt) } - - /// `isAtTopLevel` determines whether this is trying to parse an item that's at - /// the top level of the source file. If this is the case, we allow skipping - /// closing braces while trying to recover to the next item. - /// If we are not at the top level, such a closing brace should close the - /// wrapping declaration instead of being consumed by lookahead. - private mutating func parseItem( - isAtTopLevel: Bool = false, - allowInitDecl: Bool = true - ) -> RawCodeBlockItemSyntax.Item { - if self.at(.poundIf) && !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 { (parser, _) in - parser.parseCodeBlockItem(isAtTopLevel: isAtTopLevel, allowInitDecl: allowInitDecl) - } addSemicolonIfNeeded: { lastElement, newItemAtStartOfLine, parser in - if lastElement.semicolon == nil && !newItemAtStartOfLine { - return RawCodeBlockItemSyntax( - lastElement.unexpectedBeforeItem, - item: .init(lastElement.item)!, - lastElement.unexpectedBetweenItemAndSemicolon, - semicolon: parser.missingToken(.semicolon), - lastElement.unexpectedAfterSemicolon, - arena: parser.arena - ) - } else { - return nil - } - } syntax: { parser, items in - return .statements(RawCodeBlockItemListSyntax(elements: items, arena: parser.arena)) - } - return .init(decl: directive) - } else if self.at(.poundSourceLocation) { - return .init(decl: self.parsePoundSourceLocationDirective()) - } else if self.atStartOfDeclaration(isAtTopLevel: isAtTopLevel, allowInitDecl: allowInitDecl) { - return .decl(self.parseDeclaration()) - } else if self.atStartOfStatement(preferExpr: false) { - return self.parseStatementItem() - } else if self.atStartOfExpression() { - return .expr(self.parseExpression(flavor: .basic, pattern: .none)) - } else if self.atStartOfStatement(allowRecovery: true, preferExpr: false) { - return self.parseStatementItem() - } else if self.atStartOfDeclaration(isAtTopLevel: isAtTopLevel, allowInitDecl: allowInitDecl, allowRecovery: true) { - return .decl(self.parseDeclaration()) - } else if self.at(.atSign), peek(isAt: .identifier) { - // Force parsing '@' as a declaration, as there's no valid - // expression or statement starting with an attribute. - return .decl(self.parseDeclaration()) - } else { - return .init(expr: RawMissingExprSyntax(arena: self.arena)) - } - } } diff --git a/Sources/SwiftParser/generated/LayoutNodes+Parsable.swift b/Sources/SwiftParser/generated/LayoutNodes+Parsable.swift index c880b12f8e5..7dd4f5f89ab 100644 --- a/Sources/SwiftParser/generated/LayoutNodes+Parsable.swift +++ b/Sources/SwiftParser/generated/LayoutNodes+Parsable.swift @@ -135,7 +135,7 @@ extension CodeBlockSyntax: SyntaxParseable { extension DeclSyntax: SyntaxParseable { public static func parse(from parser: inout Parser) -> Self { parse(from: &parser) { - $0.parseDeclaration() + $0.parseDeclarationOrIfConfig() } } } @@ -238,7 +238,9 @@ extension VersionTupleSyntax: SyntaxParseable { fileprivate extension Parser { mutating func parseNonOptionalCodeBlockItem() -> RawCodeBlockItemSyntax { - guard let node = self.parseCodeBlockItem(isAtTopLevel: false, allowInitDecl: true) else { + guard let node = self.parseCodeBlockItem(allowInitDecl: true, until: { _ in + false + }) else { // The missing item is not necessary to be a declaration, // which is just a placeholder here return RawCodeBlockItemSyntax( diff --git a/Sources/SwiftSyntax/Documentation.docc/generated/SwiftSyntax.md b/Sources/SwiftSyntax/Documentation.docc/generated/SwiftSyntax.md index 843dbcfbbe0..d155f0bf117 100644 --- a/Sources/SwiftSyntax/Documentation.docc/generated/SwiftSyntax.md +++ b/Sources/SwiftSyntax/Documentation.docc/generated/SwiftSyntax.md @@ -79,6 +79,7 @@ allows Swift tools to parse, inspect, generate, and transform Swift source code. - - - +- - ### Expressions diff --git a/Sources/SwiftSyntax/generated/ChildNameForKeyPath.swift b/Sources/SwiftSyntax/generated/ChildNameForKeyPath.swift index 7f199475e8b..db537fb970b 100644 --- a/Sources/SwiftSyntax/generated/ChildNameForKeyPath.swift +++ b/Sources/SwiftSyntax/generated/ChildNameForKeyPath.swift @@ -3444,6 +3444,8 @@ public func childName(_ keyPath: AnyKeyPath) -> String? { return "value" case \TypeInitializerClauseSyntax.unexpectedAfterValue: return "unexpectedAfterValue" + case \UnexpectedCodeDeclSyntax.unexpectedCode: + return "unexpectedCode" case \UnresolvedAsExprSyntax.unexpectedBeforeAsKeyword: return "unexpectedBeforeAsKeyword" case \UnresolvedAsExprSyntax.asKeyword: diff --git a/Sources/SwiftSyntax/generated/SyntaxAnyVisitor.swift b/Sources/SwiftSyntax/generated/SyntaxAnyVisitor.swift index 093287a440e..1bff5115f67 100644 --- a/Sources/SwiftSyntax/generated/SyntaxAnyVisitor.swift +++ b/Sources/SwiftSyntax/generated/SyntaxAnyVisitor.swift @@ -2265,6 +2265,14 @@ open class SyntaxAnyVisitor: SyntaxVisitor { visitAnyPost(node._syntaxNode) } + override open func visit(_ node: UnexpectedCodeDeclSyntax) -> SyntaxVisitorContinueKind { + return visitAny(node._syntaxNode) + } + + override open func visitPost(_ node: UnexpectedCodeDeclSyntax) { + visitAnyPost(node._syntaxNode) + } + override open func visit(_ node: UnexpectedNodesSyntax) -> SyntaxVisitorContinueKind { return visitAny(node._syntaxNode) } diff --git a/Sources/SwiftSyntax/generated/SyntaxBaseNodes.swift b/Sources/SwiftSyntax/generated/SyntaxBaseNodes.swift index 4aede428e8c..971e335ecd5 100644 --- a/Sources/SwiftSyntax/generated/SyntaxBaseNodes.swift +++ b/Sources/SwiftSyntax/generated/SyntaxBaseNodes.swift @@ -179,6 +179,7 @@ extension Syntax { /// - ``StructDeclSyntax`` /// - ``SubscriptDeclSyntax`` /// - ``TypeAliasDeclSyntax`` +/// - ``UnexpectedCodeDeclSyntax`` /// - ``VariableDeclSyntax`` public struct DeclSyntax: DeclSyntaxProtocol, SyntaxHashable { public let _syntaxNode: Syntax @@ -214,7 +215,7 @@ public struct DeclSyntax: DeclSyntaxProtocol, SyntaxHashable { public init?(_ node: __shared some SyntaxProtocol) { switch node.raw.kind { - case .accessorDecl, .actorDecl, .associatedTypeDecl, .classDecl, .deinitializerDecl, .editorPlaceholderDecl, .enumCaseDecl, .enumDecl, .extensionDecl, .functionDecl, .ifConfigDecl, .importDecl, .initializerDecl, .macroDecl, .macroExpansionDecl, .missingDecl, .operatorDecl, .poundSourceLocation, .precedenceGroupDecl, .protocolDecl, .structDecl, .subscriptDecl, .typeAliasDecl, .usingDecl, .variableDecl: + case .accessorDecl, .actorDecl, .associatedTypeDecl, .classDecl, .deinitializerDecl, .editorPlaceholderDecl, .enumCaseDecl, .enumDecl, .extensionDecl, .functionDecl, .ifConfigDecl, .importDecl, .initializerDecl, .macroDecl, .macroExpansionDecl, .missingDecl, .operatorDecl, .poundSourceLocation, .precedenceGroupDecl, .protocolDecl, .structDecl, .subscriptDecl, .typeAliasDecl, .unexpectedCodeDecl, .usingDecl, .variableDecl: self._syntaxNode = node._syntaxNode default: return nil @@ -262,6 +263,7 @@ public struct DeclSyntax: DeclSyntaxProtocol, SyntaxHashable { .node(StructDeclSyntax.self), .node(SubscriptDeclSyntax.self), .node(TypeAliasDeclSyntax.self), + .node(UnexpectedCodeDeclSyntax.self), .node(UsingDeclSyntax.self), .node(VariableDeclSyntax.self) ]) @@ -1786,6 +1788,7 @@ extension Syntax { .node(TypeExprSyntax.self), .node(TypeInitializerClauseSyntax.self), .node(TypeSpecifierListSyntax.self), + .node(UnexpectedCodeDeclSyntax.self), .node(UnexpectedNodesSyntax.self), .node(UnresolvedAsExprSyntax.self), .node(UnresolvedIsExprSyntax.self), diff --git a/Sources/SwiftSyntax/generated/SyntaxEnum.swift b/Sources/SwiftSyntax/generated/SyntaxEnum.swift index 7dfaad0a54f..9c7a96068ff 100644 --- a/Sources/SwiftSyntax/generated/SyntaxEnum.swift +++ b/Sources/SwiftSyntax/generated/SyntaxEnum.swift @@ -301,6 +301,7 @@ public enum SyntaxEnum: Sendable { case typeExpr(TypeExprSyntax) case typeInitializerClause(TypeInitializerClauseSyntax) case typeSpecifierList(TypeSpecifierListSyntax) + case unexpectedCodeDecl(UnexpectedCodeDeclSyntax) case unexpectedNodes(UnexpectedNodesSyntax) case unresolvedAsExpr(UnresolvedAsExprSyntax) case unresolvedIsExpr(UnresolvedIsExprSyntax) @@ -874,6 +875,8 @@ extension Syntax { return .typeInitializerClause(TypeInitializerClauseSyntax(self)!) case .typeSpecifierList: return .typeSpecifierList(TypeSpecifierListSyntax(self)!) + case .unexpectedCodeDecl: + return .unexpectedCodeDecl(UnexpectedCodeDeclSyntax(self)!) case .unexpectedNodes: return .unexpectedNodes(UnexpectedNodesSyntax(self)!) case .unresolvedAsExpr: @@ -939,6 +942,7 @@ public enum DeclSyntaxEnum { case structDecl(StructDeclSyntax) case subscriptDecl(SubscriptDeclSyntax) case typeAliasDecl(TypeAliasDeclSyntax) + case unexpectedCodeDecl(UnexpectedCodeDeclSyntax) @_spi(ExperimentalLanguageFeatures) case usingDecl(UsingDeclSyntax) case variableDecl(VariableDeclSyntax) @@ -994,6 +998,8 @@ extension DeclSyntax { return .subscriptDecl(SubscriptDeclSyntax(self)!) case .typeAliasDecl: return .typeAliasDecl(TypeAliasDeclSyntax(self)!) + case .unexpectedCodeDecl: + return .unexpectedCodeDecl(UnexpectedCodeDeclSyntax(self)!) case .usingDecl: return .usingDecl(UsingDeclSyntax(self)!) case .variableDecl: diff --git a/Sources/SwiftSyntax/generated/SyntaxKind.swift b/Sources/SwiftSyntax/generated/SyntaxKind.swift index 48111e23c7c..b4bfaad0313 100644 --- a/Sources/SwiftSyntax/generated/SyntaxKind.swift +++ b/Sources/SwiftSyntax/generated/SyntaxKind.swift @@ -301,6 +301,7 @@ public enum SyntaxKind: Sendable { case typeExpr case typeInitializerClause case typeSpecifierList + case unexpectedCodeDecl case unexpectedNodes case unresolvedAsExpr case unresolvedIsExpr @@ -999,6 +1000,8 @@ public enum SyntaxKind: Sendable { return TypeInitializerClauseSyntax.self case .typeSpecifierList: return TypeSpecifierListSyntax.self + case .unexpectedCodeDecl: + return UnexpectedCodeDeclSyntax.self case .unexpectedNodes: return UnexpectedNodesSyntax.self case .unresolvedAsExpr: diff --git a/Sources/SwiftSyntax/generated/SyntaxRewriter.swift b/Sources/SwiftSyntax/generated/SyntaxRewriter.swift index 598f0786e17..4edf4a1a1f9 100644 --- a/Sources/SwiftSyntax/generated/SyntaxRewriter.swift +++ b/Sources/SwiftSyntax/generated/SyntaxRewriter.swift @@ -2022,6 +2022,13 @@ open class SyntaxRewriter { return TypeSpecifierListSyntax(unsafeCasting: visitChildren(node._syntaxNode)) } + /// Visit a ``UnexpectedCodeDeclSyntax``. + /// - Parameter node: the node that is being visited + /// - Returns: the rewritten node + open func visit(_ node: UnexpectedCodeDeclSyntax) -> DeclSyntax { + return DeclSyntax(UnexpectedCodeDeclSyntax(unsafeCasting: visitChildren(node._syntaxNode))) + } + /// Visit a ``UnexpectedNodesSyntax``. /// - Parameter node: the node that is being visited /// - Returns: the rewritten node @@ -3554,6 +3561,11 @@ open class SyntaxRewriter { Syntax(visit(TypeSpecifierListSyntax(unsafeCasting: node))) } + @inline(never) + private func visitUnexpectedCodeDeclSyntaxImpl(_ node: Syntax) -> Syntax { + Syntax(visit(UnexpectedCodeDeclSyntax(unsafeCasting: node))) + } + @inline(never) private func visitUnexpectedNodesSyntaxImpl(_ node: Syntax) -> Syntax { Syntax(visit(UnexpectedNodesSyntax(unsafeCasting: node))) @@ -4218,6 +4230,8 @@ open class SyntaxRewriter { return self.visitTypeInitializerClauseSyntaxImpl(_:) case .typeSpecifierList: return self.visitTypeSpecifierListSyntaxImpl(_:) + case .unexpectedCodeDecl: + return self.visitUnexpectedCodeDeclSyntaxImpl(_:) case .unexpectedNodes: return self.visitUnexpectedNodesSyntaxImpl(_:) case .unresolvedAsExpr: @@ -4810,6 +4824,8 @@ open class SyntaxRewriter { return visitTypeInitializerClauseSyntaxImpl(node) case .typeSpecifierList: return visitTypeSpecifierListSyntaxImpl(node) + case .unexpectedCodeDecl: + return visitUnexpectedCodeDeclSyntaxImpl(node) case .unexpectedNodes: return visitUnexpectedNodesSyntaxImpl(node) case .unresolvedAsExpr: diff --git a/Sources/SwiftSyntax/generated/SyntaxVisitor.swift b/Sources/SwiftSyntax/generated/SyntaxVisitor.swift index b6800a6db74..55d8361eeb8 100644 --- a/Sources/SwiftSyntax/generated/SyntaxVisitor.swift +++ b/Sources/SwiftSyntax/generated/SyntaxVisitor.swift @@ -3335,6 +3335,18 @@ open class SyntaxVisitor { open func visitPost(_ node: TypeSpecifierListSyntax) { } + /// Visiting ``UnexpectedCodeDeclSyntax`` specifically. + /// - Parameter node: the node we are visiting. + /// - Returns: how should we continue visiting. + open func visit(_ node: UnexpectedCodeDeclSyntax) -> SyntaxVisitorContinueKind { + return .visitChildren + } + + /// The function called after visiting ``UnexpectedCodeDeclSyntax`` and its descendants. + /// - node: the node we just finished visiting. + open func visitPost(_ node: UnexpectedCodeDeclSyntax) { + } + /// Visiting ``UnexpectedNodesSyntax`` specifically. /// - Parameter node: the node we are visiting. /// - Returns: how should we continue visiting. @@ -5756,6 +5768,14 @@ open class SyntaxVisitor { visitPost(TypeSpecifierListSyntax(unsafeCasting: node)) } + @inline(never) + private func visitUnexpectedCodeDeclSyntaxImpl(_ node: Syntax) { + if visit(UnexpectedCodeDeclSyntax(unsafeCasting: node)) == .visitChildren { + visitChildren(node) + } + visitPost(UnexpectedCodeDeclSyntax(unsafeCasting: node)) + } + @inline(never) private func visitUnexpectedNodesSyntaxImpl(_ node: Syntax) { if visit(UnexpectedNodesSyntax(unsafeCasting: node)) == .visitChildren { @@ -6474,6 +6494,8 @@ open class SyntaxVisitor { return self.visitTypeInitializerClauseSyntaxImpl(_:) case .typeSpecifierList: return self.visitTypeSpecifierListSyntaxImpl(_:) + case .unexpectedCodeDecl: + return self.visitUnexpectedCodeDeclSyntaxImpl(_:) case .unexpectedNodes: return self.visitUnexpectedNodesSyntaxImpl(_:) case .unresolvedAsExpr: @@ -7066,6 +7088,8 @@ open class SyntaxVisitor { self.visitTypeInitializerClauseSyntaxImpl(node) case .typeSpecifierList: self.visitTypeSpecifierListSyntaxImpl(node) + case .unexpectedCodeDecl: + self.visitUnexpectedCodeDeclSyntaxImpl(node) case .unexpectedNodes: self.visitUnexpectedNodesSyntaxImpl(node) case .unresolvedAsExpr: diff --git a/Sources/SwiftSyntax/generated/raw/RawSyntaxNodesD.swift b/Sources/SwiftSyntax/generated/raw/RawSyntaxNodesD.swift index 53e55832a34..f27b8eaff23 100644 --- a/Sources/SwiftSyntax/generated/raw/RawSyntaxNodesD.swift +++ b/Sources/SwiftSyntax/generated/raw/RawSyntaxNodesD.swift @@ -511,7 +511,7 @@ public struct RawDeclSyntax: RawDeclSyntaxNodeProtocol { public static func isKindOf(_ raw: RawSyntax) -> Bool { switch raw.kind { - case .accessorDecl, .actorDecl, .associatedTypeDecl, .classDecl, .deinitializerDecl, .editorPlaceholderDecl, .enumCaseDecl, .enumDecl, .extensionDecl, .functionDecl, .ifConfigDecl, .importDecl, .initializerDecl, .macroDecl, .macroExpansionDecl, .missingDecl, .operatorDecl, .poundSourceLocation, .precedenceGroupDecl, .protocolDecl, .structDecl, .subscriptDecl, .typeAliasDecl, .usingDecl, .variableDecl: + case .accessorDecl, .actorDecl, .associatedTypeDecl, .classDecl, .deinitializerDecl, .editorPlaceholderDecl, .enumCaseDecl, .enumDecl, .extensionDecl, .functionDecl, .ifConfigDecl, .importDecl, .initializerDecl, .macroDecl, .macroExpansionDecl, .missingDecl, .operatorDecl, .poundSourceLocation, .precedenceGroupDecl, .protocolDecl, .structDecl, .subscriptDecl, .typeAliasDecl, .unexpectedCodeDecl, .usingDecl, .variableDecl: return true default: return false diff --git a/Sources/SwiftSyntax/generated/raw/RawSyntaxNodesTUVWXYZ.swift b/Sources/SwiftSyntax/generated/raw/RawSyntaxNodesTUVWXYZ.swift index 704219505b9..b61e8335b43 100644 --- a/Sources/SwiftSyntax/generated/raw/RawSyntaxNodesTUVWXYZ.swift +++ b/Sources/SwiftSyntax/generated/raw/RawSyntaxNodesTUVWXYZ.swift @@ -1533,6 +1533,49 @@ public struct RawTypeSyntax: RawTypeSyntaxNodeProtocol { } } +@_spi(RawSyntax) +public struct RawUnexpectedCodeDeclSyntax: RawDeclSyntaxNodeProtocol { + @_spi(RawSyntax) + public var layoutView: RawSyntaxLayoutView { + return raw.layoutView! + } + + public static func isKindOf(_ raw: RawSyntax) -> Bool { + return raw.kind == .unexpectedCodeDecl + } + + public var raw: RawSyntax + + init(raw: RawSyntax) { + precondition(Self.isKindOf(raw)) + self.raw = raw + } + + private init(unchecked raw: RawSyntax) { + self.raw = raw + } + + public init?(_ other: some RawSyntaxNodeProtocol) { + guard Self.isKindOf(other.raw) else { + return nil + } + self.init(unchecked: other.raw) + } + + public init(unexpectedCode: RawUnexpectedNodesSyntax, arena: __shared RawSyntaxArena) { + let raw = RawSyntax.makeLayout( + kind: .unexpectedCodeDecl, uninitializedCount: 1, arena: arena) { layout in + layout.initialize(repeating: nil) + layout[0] = unexpectedCode.raw + } + self.init(unchecked: raw) + } + + public var unexpectedCode: RawUnexpectedNodesSyntax { + layoutView.children[0].map(RawUnexpectedNodesSyntax.init(raw:))! + } +} + @_spi(RawSyntax) public struct RawUnexpectedNodesSyntax: RawSyntaxNodeProtocol { @_spi(RawSyntax) diff --git a/Sources/SwiftSyntax/generated/raw/RawSyntaxValidation.swift b/Sources/SwiftSyntax/generated/raw/RawSyntaxValidation.swift index 437ec0f5d05..2592e549bbd 100644 --- a/Sources/SwiftSyntax/generated/raw/RawSyntaxValidation.swift +++ b/Sources/SwiftSyntax/generated/raw/RawSyntaxValidation.swift @@ -3006,6 +3006,10 @@ func validateLayout(layout: RawSyntaxBuffer, as kind: SyntaxKind) { verify(element, as: RawNonisolatedTypeSpecifierSyntax.self)]) } } + func validateUnexpectedCodeDeclSyntax(kind: SyntaxKind, layout: RawSyntaxBuffer) { + assert(layout.count == 1) + assertNoError(kind, 0, verify(layout[0], as: RawUnexpectedNodesSyntax.self)) + } func validateUnexpectedNodesSyntax(kind: SyntaxKind, layout: RawSyntaxBuffer) { for (index, element) in layout.enumerated() { assertNoError(kind, index, verify(element, as: RawSyntax.self)) @@ -3713,6 +3717,8 @@ func validateLayout(layout: RawSyntaxBuffer, as kind: SyntaxKind) { validateTypeInitializerClauseSyntax(kind: kind, layout: layout) case .typeSpecifierList: validateTypeSpecifierListSyntax(kind: kind, layout: layout) + case .unexpectedCodeDecl: + validateUnexpectedCodeDeclSyntax(kind: kind, layout: layout) case .unexpectedNodes: validateUnexpectedNodesSyntax(kind: kind, layout: layout) case .unresolvedAsExpr: diff --git a/Sources/SwiftSyntax/generated/syntaxNodes/SyntaxNodesTUVWXYZ.swift b/Sources/SwiftSyntax/generated/syntaxNodes/SyntaxNodesTUVWXYZ.swift index bf5973151ba..40a3bc8890c 100644 --- a/Sources/SwiftSyntax/generated/syntaxNodes/SyntaxNodesTUVWXYZ.swift +++ b/Sources/SwiftSyntax/generated/syntaxNodes/SyntaxNodesTUVWXYZ.swift @@ -2606,6 +2606,63 @@ public struct TypeInitializerClauseSyntax: SyntaxProtocol, SyntaxHashable, _Leaf ]) } +// MARK: - UnexpectedCodeDeclSyntax + +/// Unexpected code at declaration position +/// +/// ### Children +/// +/// - `unexpectedCode`: ``UnexpectedNodesSyntax`` +public struct UnexpectedCodeDeclSyntax: DeclSyntaxProtocol, SyntaxHashable, _LeafDeclSyntaxNodeProtocol { + public let _syntaxNode: Syntax + + public init?(_ node: __shared some SyntaxProtocol) { + guard node.raw.kind == .unexpectedCodeDecl else { + return nil + } + self._syntaxNode = node._syntaxNode + } + + @_transparent + init(unsafeCasting node: Syntax) { + self._syntaxNode = node + } + + /// - Parameters: + /// - leadingTrivia: Trivia to be prepended to the leading trivia of the node’s first token. If the node is empty, there is no token to attach the trivia to and the parameter is ignored. + /// - trailingTrivia: Trivia to be appended to the trailing trivia of the node’s last token. If the node is empty, there is no token to attach the trivia to and the parameter is ignored. + public init( + leadingTrivia: Trivia? = nil, + unexpectedCode: UnexpectedNodesSyntax, + trailingTrivia: Trivia? = nil + ) { + // Extend the lifetime of all parameters so their arenas don't get destroyed + // before they can be added as children of the new arena. + self = withExtendedLifetime((RawSyntaxArena(), (unexpectedCode))) { (arena, _) in + let layout: [RawSyntax?] = [unexpectedCode.raw] + let raw = RawSyntax.makeLayout( + kind: SyntaxKind.unexpectedCodeDecl, + from: layout, + arena: arena, + leadingTrivia: leadingTrivia, + trailingTrivia: trailingTrivia + ) + return Syntax.forRoot(raw, rawNodeArena: arena).cast(Self.self) + } + } + + public var unexpectedCode: UnexpectedNodesSyntax { + get { + return Syntax(self).child(at: 0)!.cast(UnexpectedNodesSyntax.self) + } + set(value) { + self = Syntax(self).replacingChild(at: 0, with: Syntax(value), rawAllocationArena: RawSyntaxArena()).cast(UnexpectedCodeDeclSyntax.self) + } + } + + public static let structure: SyntaxNodeStructure = .layout([\Self.unexpectedCode]) +} + // MARK: - UnresolvedAsExprSyntax /// The `as` keyword without any operands. diff --git a/Tests/SwiftDiagnosticsTest/ParserDiagnosticsFormatterIntegrationTests.swift b/Tests/SwiftDiagnosticsTest/ParserDiagnosticsFormatterIntegrationTests.swift index 5f717422309..d40353270c0 100644 --- a/Tests/SwiftDiagnosticsTest/ParserDiagnosticsFormatterIntegrationTests.swift +++ b/Tests/SwiftDiagnosticsTest/ParserDiagnosticsFormatterIntegrationTests.swift @@ -161,7 +161,7 @@ final class ParserDiagnosticsFormatterIntegrationTests: XCTestCase { let expectedOutput = """ 1 | func o() { 2 | }👨‍👩‍👧‍👦} - | |`- error: extraneous braces at top level + | |`- error: unexpected braces in source file | `- error: consecutive statements on a line must be separated by newline or ';' 3 | } diff --git a/Tests/SwiftParserTest/AttributeTests.swift b/Tests/SwiftParserTest/AttributeTests.swift index b5eb6ae1193..1a9f72712b4 100644 --- a/Tests/SwiftParserTest/AttributeTests.swift +++ b/Tests/SwiftParserTest/AttributeTests.swift @@ -895,7 +895,7 @@ final class AttributeTests: ParserTestCase { ), DiagnosticSpec( locationMarker: "2️⃣", - message: "unexpected code '))' before macro" + message: "unexpected code '))' in class" ), DiagnosticSpec( locationMarker: "3️⃣", @@ -1276,7 +1276,7 @@ final class AttributeTests: ParserTestCase { ), DiagnosticSpec( locationMarker: "7️⃣", - message: "unexpected code ')' before function" + message: "unexpected code ')' in source file" ), ], fixedSource: """ diff --git a/Tests/SwiftParserTest/DeclarationTests.swift b/Tests/SwiftParserTest/DeclarationTests.swift index dfce19135e3..5abae62a131 100644 --- a/Tests/SwiftParserTest/DeclarationTests.swift +++ b/Tests/SwiftParserTest/DeclarationTests.swift @@ -163,7 +163,7 @@ final class DeclarationTests: ParserTestCase { func foo() {} """, diagnostics: [ - DiagnosticSpec(message: "unexpected brace before function") + DiagnosticSpec(message: "unexpected brace in source file") ] ) } @@ -243,7 +243,7 @@ final class DeclarationTests: ParserTestCase { actor Foo {} """, diagnostics: [ - DiagnosticSpec(message: "unexpected brace before actor") + DiagnosticSpec(message: "unexpected brace in source file") ] ) } @@ -327,7 +327,7 @@ final class DeclarationTests: ParserTestCase { diagnostics: [ DiagnosticSpec( locationMarker: "1️⃣", - message: "unexpected code '{}' before enum case" + message: "unexpected code '{}' in protocol" ), DiagnosticSpec( locationMarker: "2️⃣", @@ -611,10 +611,10 @@ final class DeclarationTests: ParserTestCase { """, diagnostics: [ DiagnosticSpec(message: "expected 'set)' to end modifier", fixIts: ["insert 'set)'"]), - DiagnosticSpec(message: "unexpected code 'get, didSet' in variable"), + DiagnosticSpec(message: "expected 'var' in variable", fixIts: ["insert 'var'"]), ], fixedSource: """ - private(set) get, didSet var a = 0 + private(set) var get, didSet var a = 0 """ ) } @@ -1245,7 +1245,7 @@ final class DeclarationTests: ParserTestCase { 1️⃣} """, diagnostics: [ - DiagnosticSpec(message: "extraneous brace at top level") + DiagnosticSpec(message: "unexpected brace in source file") ] ) } @@ -1432,7 +1432,7 @@ final class DeclarationTests: ParserTestCase { message: "consecutive statements on a line must be separated by newline or ';'", fixIts: ["insert newline", "insert ';'"] ), - DiagnosticSpec(locationMarker: "5️⃣", message: "extraneous code ', consectetur adipiscing elit' at top level"), + DiagnosticSpec(locationMarker: "5️⃣", message: "unexpected code ', consectetur adipiscing elit' in source file"), ], applyFixIts: ["insert newline"], fixedSource: """ @@ -1471,7 +1471,7 @@ final class DeclarationTests: ParserTestCase { message: "consecutive statements on a line must be separated by newline or ';'", fixIts: ["insert newline", "insert ';'"] ), - DiagnosticSpec(locationMarker: "5️⃣", message: "extraneous code ', consectetur adipiscing elit' at top level"), + DiagnosticSpec(locationMarker: "5️⃣", message: "unexpected code ', consectetur adipiscing elit' in source file"), ], applyFixIts: ["insert ';'"], fixedSource: """ @@ -1649,7 +1649,11 @@ final class DeclarationTests: ParserTestCase { @3️⃣ """, diagnostics: [ - DiagnosticSpec(locationMarker: "1️⃣", message: "expected '{' in struct", fixIts: ["insert '{'"]), + DiagnosticSpec( + locationMarker: "1️⃣", + message: "expected '{' in struct", + fixIts: ["insert '{'"] + ), DiagnosticSpec( locationMarker: "2️⃣", message: "expected condition in conditional compilation clause", @@ -1666,7 +1670,12 @@ final class DeclarationTests: ParserTestCase { message: "expected '#endif' in conditional compilation block", fixIts: ["insert '#endif'"] ), - DiagnosticSpec(locationMarker: "3️⃣", message: "expected '}' to end struct", fixIts: ["insert '}'"]), + DiagnosticSpec( + locationMarker: "3️⃣", + message: "expected '}' to end struct", + fixIts: ["insert '}'"] + ), + ], fixedSource: """ struct n { @@ -1738,7 +1747,7 @@ final class DeclarationTests: ParserTestCase { assertParse( "1️⃣}class C2️⃣", diagnostics: [ - DiagnosticSpec(locationMarker: "1️⃣", message: "unexpected brace before class"), + DiagnosticSpec(locationMarker: "1️⃣", message: "unexpected brace in source file"), DiagnosticSpec(locationMarker: "2️⃣", message: "expected member block in class", fixIts: ["insert member block"]), ], fixedSource: """ @@ -1749,7 +1758,7 @@ final class DeclarationTests: ParserTestCase { assertParse( "1️⃣}enum C2️⃣", diagnostics: [ - DiagnosticSpec(locationMarker: "1️⃣", message: "unexpected brace before enum"), + DiagnosticSpec(locationMarker: "1️⃣", message: "unexpected brace in source file"), DiagnosticSpec(locationMarker: "2️⃣", message: "expected member block in enum", fixIts: ["insert member block"]), ], fixedSource: """ @@ -1760,7 +1769,7 @@ final class DeclarationTests: ParserTestCase { assertParse( "1️⃣}protocol C2️⃣", diagnostics: [ - DiagnosticSpec(locationMarker: "1️⃣", message: "unexpected brace before protocol"), + DiagnosticSpec(locationMarker: "1️⃣", message: "unexpected brace in source file"), DiagnosticSpec( locationMarker: "2️⃣", message: "expected member block in protocol", @@ -1775,7 +1784,7 @@ final class DeclarationTests: ParserTestCase { assertParse( "1️⃣}actor C2️⃣", diagnostics: [ - DiagnosticSpec(locationMarker: "1️⃣", message: "unexpected brace before actor"), + DiagnosticSpec(locationMarker: "1️⃣", message: "unexpected brace in source file"), DiagnosticSpec(locationMarker: "2️⃣", message: "expected member block in actor", fixIts: ["insert member block"]), ], fixedSource: """ @@ -1786,7 +1795,7 @@ final class DeclarationTests: ParserTestCase { assertParse( "1️⃣}struct C2️⃣", diagnostics: [ - DiagnosticSpec(locationMarker: "1️⃣", message: "unexpected brace before struct"), + DiagnosticSpec(locationMarker: "1️⃣", message: "unexpected brace in source file"), DiagnosticSpec( locationMarker: "2️⃣", message: "expected member block in struct", @@ -1801,7 +1810,7 @@ final class DeclarationTests: ParserTestCase { assertParse( "1️⃣}func C2️⃣", diagnostics: [ - DiagnosticSpec(locationMarker: "1️⃣", message: "unexpected brace before function"), + DiagnosticSpec(locationMarker: "1️⃣", message: "unexpected brace in source file"), DiagnosticSpec( locationMarker: "2️⃣", message: "expected parameter clause in function signature", @@ -1815,7 +1824,7 @@ final class DeclarationTests: ParserTestCase { assertParse( "1️⃣}init2️⃣", diagnostics: [ - DiagnosticSpec(locationMarker: "1️⃣", message: "unexpected brace before initializer"), + DiagnosticSpec(locationMarker: "1️⃣", message: "unexpected brace in source file"), DiagnosticSpec( locationMarker: "2️⃣", message: "expected parameter clause in function signature", @@ -1829,7 +1838,7 @@ final class DeclarationTests: ParserTestCase { assertParse( "1️⃣}subscript2️⃣", diagnostics: [ - DiagnosticSpec(locationMarker: "1️⃣", message: "unexpected brace before subscript"), + DiagnosticSpec(locationMarker: "1️⃣", message: "unexpected brace in source file"), DiagnosticSpec( locationMarker: "2️⃣", message: "expected parameter clause in subscript", @@ -2411,8 +2420,13 @@ final class DeclarationTests: ParserTestCase { name: .identifier("A"), memberBlock: MemberBlockSyntax( leftBrace: .leftBraceToken(), - members: MemberBlockItemListSyntax(), - UnexpectedNodesSyntax([TokenSyntax.binaryOperator("^")]), + members: MemberBlockItemListSyntax([ + MemberBlockItemSyntax( + decl: DeclSyntax( + UnexpectedCodeDeclSyntax(unexpectedCode: UnexpectedNodesSyntax([TokenSyntax.binaryOperator("^")])) + ) + ) + ]), rightBrace: .rightBraceToken() ) ) @@ -2467,13 +2481,13 @@ final class DeclarationTests: ParserTestCase { message: "expected parameter clause in function signature", fixIts: ["insert parameter clause"] ), - DiagnosticSpec(locationMarker: "1️⃣", message: "unexpected code ': Int = A.M1' before macro"), + DiagnosticSpec(locationMarker: "1️⃣", message: "unexpected code ': Int = A.M1' in source file"), DiagnosticSpec( locationMarker: "2️⃣", message: "expected parameter clause in function signature", fixIts: ["insert parameter clause"] ), - DiagnosticSpec(locationMarker: "2️⃣", message: "unexpected code ': T = A.M4 where T.Assoc: P' before macro"), + DiagnosticSpec(locationMarker: "2️⃣", message: "unexpected code ': T = A.M4 where T.Assoc: P' in source file"), ], fixedSource: """ macro m1(): Int = A.M1 @@ -2904,15 +2918,15 @@ final class DeclarationTests: ParserTestCase { class A ℹ️{ 1️⃣^ } - unowned 2️⃣B { + unowned2️⃣ B { } """, diagnostics: [ - DiagnosticSpec(locationMarker: "1️⃣", message: "unexpected code before modifier"), + DiagnosticSpec(locationMarker: "1️⃣", message: "unexpected code '^' in class"), DiagnosticSpec( locationMarker: "2️⃣", - message: "expected declaration and '}' after 'unowned' modifier", - fixIts: ["insert declaration and '}'"] + message: "consecutive statements on a line must be separated by newline or ';'", + fixIts: ["insert newline", "insert ';'"] ), ], fixedSource: @@ -2920,8 +2934,8 @@ final class DeclarationTests: ParserTestCase { class A { ^ } - unowned <#declaration#> - }B { + unowned + B { } """ ) diff --git a/Tests/SwiftParserTest/DirectiveTests.swift b/Tests/SwiftParserTest/DirectiveTests.swift index 60ee4cd239a..35848f2db25 100644 --- a/Tests/SwiftParserTest/DirectiveTests.swift +++ b/Tests/SwiftParserTest/DirectiveTests.swift @@ -151,8 +151,8 @@ final class DirectiveTests: ParserTestCase { #endif """, diagnostics: [ - DiagnosticSpec(locationMarker: "1️⃣", message: "unexpected brace before conditional compilation clause"), - DiagnosticSpec(locationMarker: "2️⃣", message: "unexpected brace in conditional compilation block"), + DiagnosticSpec(locationMarker: "1️⃣", message: "unexpected brace in conditional compilation clause"), + DiagnosticSpec(locationMarker: "2️⃣", message: "unexpected brace in conditional compilation clause"), ] ) } @@ -458,4 +458,156 @@ final class DirectiveTests: ParserTestCase { """ ) } + + func testOrphanEndifInMember() { + assertParse( + """ + struct S ℹ️{1️⃣ + 2️⃣#endif + } + """, + diagnostics: [ + DiagnosticSpec( + locationMarker: "1️⃣", + message: "expected '}' to end struct", + notes: [NoteSpec(message: "to match this opening '{'")], + fixIts: ["insert '}'"] + ), + DiagnosticSpec(locationMarker: "2️⃣", message: "unexpected code in source file"), + ], + fixedSource: """ + struct S { + } + #endif + } + """ + ) + } + + func testOrphanEndifInCodeBlock() { + assertParse( + """ + func foo() ℹ️{1️⃣ + 2️⃣#endif + } + """, + diagnostics: [ + DiagnosticSpec( + locationMarker: "1️⃣", + message: "expected '}' to end function", + notes: [NoteSpec(message: "to match this opening '{'")], + fixIts: ["insert '}'"] + ), + DiagnosticSpec(locationMarker: "2️⃣", message: "unexpected code in source file"), + ], + fixedSource: """ + func foo() { + } + #endif + } + """ + ) + } + + func testOrphanEndifInSwitch() { + assertParse( + """ + switch subject ℹ️{1️⃣ + 2️⃣#endif + } + """, + diagnostics: [ + DiagnosticSpec( + locationMarker: "1️⃣", + message: "expected '}' to end 'switch' statement", + notes: [NoteSpec(message: "to match this opening '{'")], + fixIts: ["insert '}'"] + ), + DiagnosticSpec(locationMarker: "2️⃣", message: "unexpected code in source file"), + ], + fixedSource: """ + switch subject { + } + #endif + } + """ + ) + } + + func testOrphanEndifInSwitchCase() { + assertParse( + """ + switch subject ℹ️{ + case foo: + print()1️⃣ + 2️⃣#endif + } + """, + diagnostics: [ + DiagnosticSpec( + locationMarker: "1️⃣", + message: "expected '}' to end 'switch' statement", + notes: [NoteSpec(message: "to match this opening '{'")], + fixIts: ["insert '}'"] + ), + DiagnosticSpec(locationMarker: "2️⃣", message: "unexpected code in source file"), + ], + fixedSource: """ + switch subject { + case foo: + print() + } + #endif + } + """ + ) + } + + func testRightBraceInIfConfig() { + assertParse( + """ + struct S { + #if true + 1️⃣} + #endif + } + func foo() { + #if true + 2️⃣} + #endif + } + """, + diagnostics: [ + DiagnosticSpec(locationMarker: "1️⃣", message: "unexpected brace in conditional compilation clause"), + DiagnosticSpec(locationMarker: "2️⃣", message: "unexpected brace in conditional compilation clause"), + ] + ) + } + + func testMismatchedPoundIfAndCodeBlock() { + assertParse( + """ + #if FOO + func foo() ℹ️{1️⃣ + #endif + 2️⃣} + """, + diagnostics: [ + DiagnosticSpec( + locationMarker: "1️⃣", + message: "expected '}' to end function", + notes: [NoteSpec(message: "to match this opening '{'")], + fixIts: ["insert '}'"] + ), + DiagnosticSpec(locationMarker: "2️⃣", message: "unexpected brace in source file"), + ], + fixedSource: """ + #if FOO + func foo() { + } + #endif + } + """ + ) + } } diff --git a/Tests/SwiftParserTest/DoExpressionTests.swift b/Tests/SwiftParserTest/DoExpressionTests.swift index 3dc5b4efd83..b34e85ba79e 100644 --- a/Tests/SwiftParserTest/DoExpressionTests.swift +++ b/Tests/SwiftParserTest/DoExpressionTests.swift @@ -332,7 +332,7 @@ final class DoExpressionTests: ParserTestCase { } ), diagnostics: [ - DiagnosticSpec(message: "extraneous code 'as Int' at top level") + DiagnosticSpec(message: "unexpected code 'as Int' in source file") ], experimentalFeatures: [] ) diff --git a/Tests/SwiftParserTest/ExpressionTests.swift b/Tests/SwiftParserTest/ExpressionTests.swift index 9533e6b4f56..41ac9978167 100644 --- a/Tests/SwiftParserTest/ExpressionTests.swift +++ b/Tests/SwiftParserTest/ExpressionTests.swift @@ -233,7 +233,7 @@ final class ExpressionTests: ParserTestCase { #""" \String?.!.count1️⃣.? """#, - diagnostics: [DiagnosticSpec(message: "extraneous code '.?' at top level")] + diagnostics: [DiagnosticSpec(message: "unexpected code '.?' in source file")] ) assertParse( @@ -246,7 +246,7 @@ final class ExpressionTests: ParserTestCase { #""" \Optional.?!?!?!?1️⃣.??! """#, - diagnostics: [DiagnosticSpec(message: "extraneous code '.??!' at top level")] + diagnostics: [DiagnosticSpec(message: "unexpected code '.??!' in source file")] ) assertParse( @@ -678,14 +678,14 @@ final class ExpressionTests: ParserTestCase { func testChainedOptionalUnwrapsWithDot() { assertParse( #"\T.?1️⃣.!"#, - diagnostics: [DiagnosticSpec(message: "extraneous code '.!' at top level")] + diagnostics: [DiagnosticSpec(message: "unexpected code '.!' in source file")] ) } func testChainedOptionalUnwrapsAfterSubscript() { assertParse( #"\T.abc[2]1️⃣.?"#, - diagnostics: [DiagnosticSpec(message: "extraneous code '.?' at top level")] + diagnostics: [DiagnosticSpec(message: "unexpected code '.?' in source file")] ) } @@ -1764,7 +1764,7 @@ final class ExpressionTests: ParserTestCase { ), DiagnosticSpec( locationMarker: "4️⃣", - message: #"extraneous code ')"' at top level"# + message: #"unexpected code ')"' in source file"# ), ], fixedSource: #""" @@ -2277,19 +2277,20 @@ final class ExpressionTests: ParserTestCase { func testSecondaryArgumentLabelDollarIdentifierInClosure() { assertParse( """ - ℹ️{ a1️⃣: (a $ + ℹ️{ a1️⃣: (a $2️⃣ """, diagnostics: [ + DiagnosticSpec(locationMarker: "1️⃣", message: "unexpected code ': (a $' in closure"), DiagnosticSpec( + locationMarker: "2️⃣", message: "expected '}' to end closure", notes: [NoteSpec(message: "to match this opening '{'")], fixIts: ["insert '}'"] ), - DiagnosticSpec(message: "extraneous code ': (a $' at top level"), ], fixedSource: """ - { a - }: (a $ + { a: (a $ + } """ ) } @@ -2699,7 +2700,7 @@ final class StatementExpressionTests: ParserTestCase { } """, diagnostics: [ - DiagnosticSpec(message: "unexpected 'is' keyword in 'switch' statement") + DiagnosticSpec(message: "unexpected 'is' keyword in switch case") ] ) } @@ -2708,10 +2709,28 @@ final class StatementExpressionTests: ParserTestCase { assertParse( """ switch x { - 1️⃣@case + @1️⃣case2️⃣ } """, - diagnostics: [DiagnosticSpec(message: "unexpected code '@case' in 'switch' statement")] + diagnostics: [ + DiagnosticSpec( + locationMarker: "1️⃣", + // FIXME: "expected attribute name after '@'". https://github.com/swiftlang/swift-syntax/issues/3159 + message: "expected type in attribute", + fixIts: ["insert type"] + ), + DiagnosticSpec( + locationMarker: "2️⃣", + // FIXME: "expected pattern and ':' in switch case". https://github.com/swiftlang/swift-syntax/issues/3158 + message: "expected expression and ':' in switch case", + fixIts: ["insert expression and ':'"] + ), + ], + fixedSource: """ + switch x { + @<#identifier#> case <#expression#>: + } + """ ) } @@ -3049,7 +3068,7 @@ final class StatementExpressionTests: ParserTestCase { ), DiagnosticSpec( locationMarker: "4️⃣", - message: #"extraneous code ')"' at top level"# + message: #"unexpected code ')"' in source file"# ), ], fixedSource: #""" @@ -3079,7 +3098,7 @@ final class StatementExpressionTests: ParserTestCase { ), DiagnosticSpec( locationMarker: "3️⃣", - message: #"extraneous code ')"' at top level"# + message: #"unexpected code ')"' in source file"# ), ], fixedSource: #""" @@ -3573,7 +3592,7 @@ final class StatementExpressionTests: ParserTestCase { #endif """, diagnostics: [ - DiagnosticSpec(message: "unexpected brace before class") + DiagnosticSpec(message: "unexpected brace in conditional compilation clause") ] ) } diff --git a/Tests/SwiftParserTest/Parser+EntryTests.swift b/Tests/SwiftParserTest/Parser+EntryTests.swift index c0b84809f86..91ca7a12e2c 100644 --- a/Tests/SwiftParserTest/Parser+EntryTests.swift +++ b/Tests/SwiftParserTest/Parser+EntryTests.swift @@ -37,6 +37,72 @@ class EntryTests: ParserTestCase { ) } + func testDeclSyntaxParseIfConfig() throws { + assertParse( + """ + #if FLAG + func test() {} + #endif + """, + { DeclSyntax.parse(from: &$0) }, + substructure: IfConfigDeclSyntax( + clauses: IfConfigClauseListSyntax([ + IfConfigClauseSyntax( + poundKeyword: .poundIfToken(), + condition: ExprSyntax(DeclReferenceExprSyntax(baseName: .identifier("FLAG"))), + elements: .init([ + MemberBlockItemSyntax( + decl: DeclSyntax( + FunctionDeclSyntax( + funcKeyword: .keyword(.func), + name: .identifier("test"), + signature: FunctionSignatureSyntax( + parameterClause: FunctionParameterClauseSyntax(parameters: []) + ), + body: CodeBlockSyntax(statements: []) + ) + ) + ) + ]) + ) + ]) + ) + ) + } + + func testDeclSyntaxParseIfConfigAttr() throws { + assertParse( + """ + #if FLAG + @attr + #endif + func test() {} + """, + { DeclSyntax.parse(from: &$0) }, + substructure: FunctionDeclSyntax( + attributes: [ + .ifConfigDecl( + IfConfigDeclSyntax(clauses: [ + IfConfigClauseSyntax( + poundKeyword: .poundIfToken(), + condition: DeclReferenceExprSyntax(baseName: .identifier("FLAG")), + elements: .attributes([ + .attribute(AttributeSyntax(TypeSyntax(IdentifierTypeSyntax(name: .identifier("attr"))))) + ]) + ) + ]) + ) + ], + funcKeyword: .keyword(.func), + name: .identifier("test"), + signature: FunctionSignatureSyntax( + parameterClause: FunctionParameterClauseSyntax(parameters: []) + ), + body: CodeBlockSyntax(statements: []) + ) + ) + } + func testRemainderUnexpected() throws { assertParse( "func test() {} 1️⃣other tokens", diff --git a/Tests/SwiftParserTest/RegexLiteralTests.swift b/Tests/SwiftParserTest/RegexLiteralTests.swift index 56cf4d85dd7..3c84cb49c28 100644 --- a/Tests/SwiftParserTest/RegexLiteralTests.swift +++ b/Tests/SwiftParserTest/RegexLiteralTests.swift @@ -1388,7 +1388,7 @@ final class RegexLiteralTests: ParserTestCase { """, substructure: BinaryOperatorExprSyntax(operator: .binaryOperator("/")), diagnostics: [ - DiagnosticSpec(message: "extraneous code ':/def/' at top level") + DiagnosticSpec(message: "unexpected code ':/def/' in source file") ] ) } diff --git a/Tests/SwiftParserTest/StatementTests.swift b/Tests/SwiftParserTest/StatementTests.swift index d56274bb449..228aec6d6ee 100644 --- a/Tests/SwiftParserTest/StatementTests.swift +++ b/Tests/SwiftParserTest/StatementTests.swift @@ -257,6 +257,20 @@ final class StatementTests: ParserTestCase { ) } + func testUnknownDefaultAtStatement() { + assertParse( + """ + func test() { + 1️⃣@unknown default: + return + } + """, + diagnostics: [ + DiagnosticSpec(message: "'default' label can only appear inside a 'switch' statement") + ] + ) + } + func testMissingIfClauseIntroducer() { assertParse("if _ = 42 {}") } @@ -265,16 +279,32 @@ final class StatementTests: ParserTestCase { assertParse( """ func test1() { - 1️⃣@s return + @s 1️⃣return } func test2() { - 2️⃣@unknown return + @unknown 2️⃣return } """, diagnostics: [ - DiagnosticSpec(locationMarker: "1️⃣", message: "unexpected code '@s' before 'return' statement"), - DiagnosticSpec(locationMarker: "2️⃣", message: "unexpected code '@unknown' before 'return' statement"), - ] + DiagnosticSpec( + locationMarker: "1️⃣", + message: "expected declaration and ';' after attribute", + fixIts: ["insert declaration and ';'"] + ), + DiagnosticSpec( + locationMarker: "2️⃣", + message: "expected declaration and ';' after attribute", + fixIts: ["insert declaration and ';'"] + ), + ], + fixedSource: """ + func test1() { + @s <#declaration#>; return + } + func test2() { + @unknown <#declaration#>; return + } + """ ) } @@ -335,7 +365,7 @@ final class StatementTests: ParserTestCase { assertParse( "LABEL1️⃣:", diagnostics: [ - DiagnosticSpec(message: "extraneous code ':' at top level") + DiagnosticSpec(message: "unexpected code ':' in source file") ] ) } @@ -826,7 +856,7 @@ final class StatementTests: ParserTestCase { """, diagnostics: [ DiagnosticSpec(locationMarker: "1️⃣", message: "missing condition in 'if' statement"), - DiagnosticSpec(locationMarker: "2️⃣", message: "unexpected code in 'if' statement"), + DiagnosticSpec(locationMarker: "2️⃣", message: "unexpected 'in' keyword in 'if' statement"), ] ) } @@ -863,7 +893,7 @@ final class StatementTests: ParserTestCase { "guard test 1️⃣{ $0 } 2️⃣else {}", diagnostics: [ DiagnosticSpec(locationMarker: "1️⃣", message: "expected 'else' in 'guard' statement", fixIts: ["insert 'else'"]), - DiagnosticSpec(locationMarker: "2️⃣", message: "extraneous code 'else {}' at top level"), + DiagnosticSpec(locationMarker: "2️⃣", message: "unexpected code 'else {}' in source file"), ], fixedSource: "guard test else { $0 } else {}" ) @@ -876,7 +906,7 @@ final class StatementTests: ParserTestCase { """, diagnostics: [ DiagnosticSpec(locationMarker: "1️⃣", message: "expected 'else' in 'guard' statement", fixIts: ["insert 'else'"]), - DiagnosticSpec(locationMarker: "2️⃣", message: "extraneous code 'else {}' at top level"), + DiagnosticSpec(locationMarker: "2️⃣", message: "unexpected code 'else {}' in source file"), ], fixedSource: """ @@ -893,7 +923,7 @@ final class StatementTests: ParserTestCase { """, diagnostics: [ DiagnosticSpec(locationMarker: "1️⃣", message: "expected 'else' in 'guard' statement", fixIts: ["insert 'else'"]), - DiagnosticSpec(locationMarker: "2️⃣", message: "extraneous code 'else {}' at top level"), + DiagnosticSpec(locationMarker: "2️⃣", message: "unexpected code 'else {}' in source file"), ], fixedSource: """ guard test else { $0 @@ -909,8 +939,8 @@ final class StatementTests: ParserTestCase { """, diagnostics: [ DiagnosticSpec(locationMarker: "1️⃣", message: "expected 'else' in 'guard' statement", fixIts: ["insert 'else'"]), - DiagnosticSpec(locationMarker: "2️⃣", message: "unexpected code in 'guard' statement"), - DiagnosticSpec(locationMarker: "3️⃣", message: "extraneous code 'else {}' at top level"), + DiagnosticSpec(locationMarker: "2️⃣", message: "unexpected 'in' keyword in 'guard' statement"), + DiagnosticSpec(locationMarker: "3️⃣", message: "unexpected code 'else {}' in source file"), ], fixedSource: """ guard test else { x in diff --git a/Tests/SwiftParserTest/ValueGenericsTests.swift b/Tests/SwiftParserTest/ValueGenericsTests.swift index 7350ca0b749..0075a3b0a64 100644 --- a/Tests/SwiftParserTest/ValueGenericsTests.swift +++ b/Tests/SwiftParserTest/ValueGenericsTests.swift @@ -71,7 +71,7 @@ final class ValueGenericsTests: ParserTestCase { ), DiagnosticSpec( locationMarker: "2️⃣", - message: "extraneous code '>() {}' at top level" + message: "unexpected code '>() {}' in source file" ), ], fixedSource: """ diff --git a/Tests/SwiftParserTest/translated/EnumTests.swift b/Tests/SwiftParserTest/translated/EnumTests.swift index 946169ae67c..d199ed13f1e 100644 --- a/Tests/SwiftParserTest/translated/EnumTests.swift +++ b/Tests/SwiftParserTest/translated/EnumTests.swift @@ -1393,7 +1393,7 @@ final class EnumTests: ParserTestCase { assertParse( """ enum E_53662_PatternMatching { - case 1️⃣let 2️⃣.foo(x, y): + case 1️⃣let 2️⃣.3️⃣foo(x4️⃣, y5️⃣)6️⃣: } """, diagnostics: [ @@ -1404,12 +1404,32 @@ final class EnumTests: ParserTestCase { ), DiagnosticSpec( locationMarker: "2️⃣", - message: "unexpected code '.foo(x, y):' in enum" + message: "unexpected code '.' in enum" ), + DiagnosticSpec( + locationMarker: "3️⃣", + message: "expected 'func' in function", + fixIts: ["insert 'func'"] + ), + DiagnosticSpec( + locationMarker: "4️⃣", + message: "expected ':' and type in parameter", + fixIts: ["insert ':' and type"] + ), + DiagnosticSpec( + locationMarker: "5️⃣", + message: "expected ':' and type in parameter", + fixIts: ["insert ':' and type"] + ), + DiagnosticSpec( + locationMarker: "6️⃣", + message: "unexpected code ':' in enum" + ), + ], fixedSource: """ enum E_53662_PatternMatching { - case `let` .foo(x, y): + case `let` .func foo(x: <#type#>, y: <#type#>): } """ ) diff --git a/Tests/SwiftParserTest/translated/ErrorsTests.swift b/Tests/SwiftParserTest/translated/ErrorsTests.swift index 0f86ff82a38..cb79adc7483 100644 --- a/Tests/SwiftParserTest/translated/ErrorsTests.swift +++ b/Tests/SwiftParserTest/translated/ErrorsTests.swift @@ -300,9 +300,15 @@ final class ErrorsTests: ParserTestCase { ) ) ) - ) - ]), - UnexpectedNodesSyntax([TokenSyntax.keyword(.throws)]) + ), + CodeBlockItemSyntax( + item: .decl( + DeclSyntax( + UnexpectedCodeDeclSyntax(unexpectedCode: UnexpectedNodesSyntax([TokenSyntax.keyword(.throws)])) + ) + ) + ), + ]) ), diagnostics: [ DiagnosticSpec(message: "unexpected 'throws' keyword in function") diff --git a/Tests/SwiftParserTest/translated/EscapedIdentifiersTests.swift b/Tests/SwiftParserTest/translated/EscapedIdentifiersTests.swift index 939beb8b972..c47d921ec53 100644 --- a/Tests/SwiftParserTest/translated/EscapedIdentifiersTests.swift +++ b/Tests/SwiftParserTest/translated/EscapedIdentifiersTests.swift @@ -205,14 +205,22 @@ final class EscapedIdentifiersTests: ParserTestCase { assertParse( """ 1️⃣`multiline is - not allowed` = 5 + not2️⃣ allowed3️⃣` = 5 """, diagnostics: [ + DiagnosticSpec(locationMarker: "1️⃣", message: "unexpected code '`multiline is' in source file"), DiagnosticSpec( - locationMarker: "1️⃣", - message: "extraneous code at top level" - ) - ] + locationMarker: "2️⃣", + message: "consecutive statements on a line must be separated by newline or ';'", + fixIts: ["insert newline", "insert ';'"] + ), + DiagnosticSpec(locationMarker: "3️⃣", message: "unexpected code '` = 5' in source file"), + ], + fixedSource: """ + `multiline is + not + allowed` = 5 + """ ) } diff --git a/Tests/SwiftParserTest/translated/ForwardSlashRegexSkippingInvalidTests.swift b/Tests/SwiftParserTest/translated/ForwardSlashRegexSkippingInvalidTests.swift index 3959dc9d2bf..1c25a07b872 100644 --- a/Tests/SwiftParserTest/translated/ForwardSlashRegexSkippingInvalidTests.swift +++ b/Tests/SwiftParserTest/translated/ForwardSlashRegexSkippingInvalidTests.swift @@ -156,7 +156,7 @@ final class ForwardSlashRegexSkippingInvalidTests: ParserTestCase { 1️⃣} """, diagnostics: [ - DiagnosticSpec(message: "extraneous brace at top level") + DiagnosticSpec(message: "unexpected brace in source file") ] ) } @@ -322,7 +322,7 @@ final class ForwardSlashRegexSkippingInvalidTests: ParserTestCase { ), DiagnosticSpec( locationMarker: "3️⃣", - message: "extraneous brace at top level" + message: "unexpected brace in source file" ), ], applyFixIts: ["insert '/'"], @@ -365,11 +365,12 @@ final class ForwardSlashRegexSkippingInvalidTests: ParserTestCase { 0 /x}}1️⃣} / 2 - } + 2️⃣} } """, diagnostics: [ - DiagnosticSpec(message: "extraneous code at top level") + DiagnosticSpec(locationMarker: "1️⃣", message: "unexpected code '} /' in source file"), + DiagnosticSpec(locationMarker: "2️⃣", message: "unexpected braces in source file"), ] ) } @@ -381,10 +382,11 @@ final class ForwardSlashRegexSkippingInvalidTests: ParserTestCase { _ = 2 /x} 1️⃣/ .bitWidth - } + 2️⃣} """, diagnostics: [ - DiagnosticSpec(message: "extraneous code at top level") + DiagnosticSpec(locationMarker: "1️⃣", message: "unexpected code '/' in source file"), + DiagnosticSpec(locationMarker: "2️⃣", message: "unexpected brace in source file"), ] ) } diff --git a/Tests/SwiftParserTest/translated/ForwardSlashRegexTests.swift b/Tests/SwiftParserTest/translated/ForwardSlashRegexTests.swift index 25e94d39f4a..b916ad09c02 100644 --- a/Tests/SwiftParserTest/translated/ForwardSlashRegexTests.swift +++ b/Tests/SwiftParserTest/translated/ForwardSlashRegexTests.swift @@ -1423,7 +1423,7 @@ final class ForwardSlashRegexTests: ParserTestCase { _ = /\()1️⃣/ """#, diagnostics: [ - DiagnosticSpec(message: "extraneous code '/' at top level") + DiagnosticSpec(message: "unexpected code '/' in source file") ] ) } diff --git a/Tests/SwiftParserTest/translated/IfconfigExprTests.swift b/Tests/SwiftParserTest/translated/IfconfigExprTests.swift index 9307e79271e..423b586003e 100644 --- a/Tests/SwiftParserTest/translated/IfconfigExprTests.swift +++ b/Tests/SwiftParserTest/translated/IfconfigExprTests.swift @@ -105,7 +105,7 @@ final class IfconfigExprTests: ParserTestCase { } """#, diagnostics: [ - DiagnosticSpec(locationMarker: "1️⃣", message: "unexpected code '+ otherExpr' in conditional compilation block"), + DiagnosticSpec(locationMarker: "1️⃣", message: "unexpected code '+ otherExpr' in conditional compilation clause"), DiagnosticSpec( locationMarker: "2️⃣", message: #"unexpected code 'print("debug")' in conditional compilation block"# diff --git a/Tests/SwiftParserTest/translated/ModuleSelectorTests.swift b/Tests/SwiftParserTest/translated/ModuleSelectorTests.swift index 4e586e58953..5479ed1ea06 100644 --- a/Tests/SwiftParserTest/translated/ModuleSelectorTests.swift +++ b/Tests/SwiftParserTest/translated/ModuleSelectorTests.swift @@ -1116,7 +1116,7 @@ final class ModuleSelectorTests: ParserTestCase { """, diagnostics: [ DiagnosticSpec(message: "expected type in type annotation", fixIts: ["insert type"]), - DiagnosticSpec(message: "extraneous code '*::Int' at top level"), + DiagnosticSpec(message: "unexpected code '*::Int' in source file"), ], fixedSource: """ var c: <#type#>*::Int @@ -1303,7 +1303,7 @@ final class ModuleSelectorTests: ParserTestCase { ), DiagnosticSpec( locationMarker: "3️⃣", - message: "extraneous code ']' at top level" + message: "unexpected code ']' in source file" ), ], fixedSource: """ @@ -1344,7 +1344,7 @@ final class ModuleSelectorTests: ParserTestCase { var cIndex: String1️⃣.*::Index """, diagnostics: [ - DiagnosticSpec(message: "extraneous code '.*::Index' at top level") + DiagnosticSpec(message: "unexpected code '.*::Index' in source file") ] ) assertParse( @@ -1823,7 +1823,7 @@ final class ModuleSelectorTests: ParserTestCase { ), DiagnosticSpec( locationMarker: "2️⃣", - message: "extraneous code 'else { 0 }' at top level" + message: "unexpected code 'else { 0 }' in source file" ), ], fixedSource: """ diff --git a/Tests/SwiftParserTest/translated/MultilineErrorsTests.swift b/Tests/SwiftParserTest/translated/MultilineErrorsTests.swift index e9f57e14bac..56e6ffea3d7 100644 --- a/Tests/SwiftParserTest/translated/MultilineErrorsTests.swift +++ b/Tests/SwiftParserTest/translated/MultilineErrorsTests.swift @@ -563,7 +563,7 @@ final class MultilineErrorsTests: ParserTestCase { ), DiagnosticSpec( locationMarker: "4️⃣", - message: #"extraneous code ')!"' at top level"# + message: #"unexpected code ')!"' in source file"# ), ], fixedSource: ##""" @@ -597,7 +597,7 @@ final class MultilineErrorsTests: ParserTestCase { ), DiagnosticSpec( locationMarker: "2️⃣", - message: #"extraneous code ')!"' at top level"# + message: #"unexpected code ')!"' in source file"# ), ], fixedSource: ##""" diff --git a/Tests/SwiftParserTest/translated/OperatorDeclTests.swift b/Tests/SwiftParserTest/translated/OperatorDeclTests.swift index a8e79ba960f..2ddb23d46b1 100644 --- a/Tests/SwiftParserTest/translated/OperatorDeclTests.swift +++ b/Tests/SwiftParserTest/translated/OperatorDeclTests.swift @@ -214,7 +214,7 @@ final class OperatorDeclTests: ParserTestCase { prefix operator %%+ """, diagnostics: [ - DiagnosticSpec(message: "unexpected ';' separator", fixIts: ["remove ';'"]) + DiagnosticSpec(message: "standalone ';' statements are not allowed", fixIts: ["remove ';'"]) ], fixedSource: """ diff --git a/Tests/SwiftParserTest/translated/OptionalTests.swift b/Tests/SwiftParserTest/translated/OptionalTests.swift index f29ee9511b3..542021fb26d 100644 --- a/Tests/SwiftParserTest/translated/OptionalTests.swift +++ b/Tests/SwiftParserTest/translated/OptionalTests.swift @@ -33,7 +33,7 @@ final class OptionalTests: ParserTestCase { var b : A 1️⃣? """, diagnostics: [ - DiagnosticSpec(message: "extraneous code '?' at top level") + DiagnosticSpec(message: "unexpected code '?' in source file") ] ) } diff --git a/Tests/SwiftParserTest/translated/PoundAssertTests.swift b/Tests/SwiftParserTest/translated/PoundAssertTests.swift index f2b57c8b173..4ee9fa6a2fc 100644 --- a/Tests/SwiftParserTest/translated/PoundAssertTests.swift +++ b/Tests/SwiftParserTest/translated/PoundAssertTests.swift @@ -41,7 +41,7 @@ final class PoundAssertTests: ParserTestCase { message: "consecutive statements on a line must be separated by newline or ';'", fixIts: ["insert newline", "insert ';'"] ), - DiagnosticSpec(locationMarker: "2️⃣", message: #"extraneous code ', "error message")' at top level"#), + DiagnosticSpec(locationMarker: "2️⃣", message: #"unexpected code ', "error message")' in source file"#), ], applyFixIts: ["insert newline"], fixedSource: #""" @@ -61,7 +61,7 @@ final class PoundAssertTests: ParserTestCase { message: "consecutive statements on a line must be separated by newline or ';'", fixIts: ["insert newline", "insert ';'"] ), - DiagnosticSpec(locationMarker: "2️⃣", message: #"extraneous code ', "error message")' at top level"#), + DiagnosticSpec(locationMarker: "2️⃣", message: #"unexpected code ', "error message")' in source file"#), ], applyFixIts: ["insert ';'"], fixedSource: #""" diff --git a/Tests/SwiftParserTest/translated/RecoveryLibraryTests.swift b/Tests/SwiftParserTest/translated/RecoveryLibraryTests.swift index a6f511902ab..e9311734db4 100644 --- a/Tests/SwiftParserTest/translated/RecoveryLibraryTests.swift +++ b/Tests/SwiftParserTest/translated/RecoveryLibraryTests.swift @@ -35,15 +35,15 @@ final class RecoveryLibraryTests: ParserTestCase { diagnostics: [ DiagnosticSpec( locationMarker: "1️⃣", - message: "unexpected braces before function", + message: "unexpected braces in source file", highlight: """ // Check that we handle multiple consecutive right braces. } } """ ), - DiagnosticSpec(locationMarker: "2️⃣", message: "unexpected braces before function"), - DiagnosticSpec(locationMarker: "3️⃣", message: "extraneous braces at top level"), + DiagnosticSpec(locationMarker: "2️⃣", message: "unexpected braces in source file"), + DiagnosticSpec(locationMarker: "3️⃣", message: "unexpected braces in source file"), ] ) } diff --git a/Tests/SwiftParserTest/translated/RecoveryTests.swift b/Tests/SwiftParserTest/translated/RecoveryTests.swift index aba6d85f9ea..dcc01fdcfef 100644 --- a/Tests/SwiftParserTest/translated/RecoveryTests.swift +++ b/Tests/SwiftParserTest/translated/RecoveryTests.swift @@ -28,7 +28,7 @@ final class RecoveryTests: ParserTestCase { diagnostics: [ DiagnosticSpec( message: - "unexpected code ') this line is invalid, but we will stop at the keyword below...' before 'return' statement" + "unexpected code ') this line is invalid, but we will stop at the keyword below...' in function" ) ] ) @@ -45,7 +45,7 @@ final class RecoveryTests: ParserTestCase { """#, diagnostics: [ DiagnosticSpec( - message: "unexpected code ') this line is invalid, but we will stop at the declaration...' before function" + message: "unexpected code ') this line is invalid, but we will stop at the declaration...' in function" ) ] ) @@ -93,7 +93,7 @@ final class RecoveryTests: ParserTestCase { ), DiagnosticSpec( locationMarker: "4️⃣", - message: "unexpected code in function" + message: "unexpected code ', b : Int' in function" ), ], applyFixIts: ["insert '>'", "insert expression"], @@ -140,7 +140,7 @@ final class RecoveryTests: ParserTestCase { diagnostics: [ DiagnosticSpec( locationMarker: "1️⃣", - message: "unexpected brace before function" + message: "unexpected brace in source file" ), DiagnosticSpec( locationMarker: "2️⃣", @@ -174,7 +174,7 @@ final class RecoveryTests: ParserTestCase { diagnostics: [ DiagnosticSpec( locationMarker: "1️⃣", - message: "unexpected brace before function" + message: "unexpected brace in source file" ), DiagnosticSpec( locationMarker: "2️⃣", @@ -975,14 +975,16 @@ final class RecoveryTests: ParserTestCase { func testRecovery54() { assertParse( """ - struct NoBracesStruct11️⃣() + struct NoBracesStruct11️⃣()2️⃣ """, diagnostics: [ - DiagnosticSpec(message: "expected member block in struct", fixIts: ["insert member block"]) + DiagnosticSpec(locationMarker: "1️⃣", message: "expected '{' in struct", fixIts: ["insert '{'"]), + DiagnosticSpec(locationMarker: "1️⃣", message: "unexpected code '()' in struct"), + DiagnosticSpec(locationMarker: "2️⃣", message: "expected '}' to end struct", fixIts: ["insert '}'"]), ], fixedSource: """ - struct NoBracesStruct1 { - }() + struct NoBracesStruct1 {() + } """ ) } @@ -993,33 +995,31 @@ final class RecoveryTests: ParserTestCase { enum NoBracesUnion11️⃣() class NoBracesClass12️⃣() protocol NoBracesProtocol13️⃣() - extension NoBracesStruct14️⃣() + extension NoBracesStruct14️⃣()5️⃣ """, diagnostics: [ DiagnosticSpec(locationMarker: "1️⃣", message: "expected '{' in enum", fixIts: ["insert '{'"]), - DiagnosticSpec(locationMarker: "1️⃣", message: "unexpected code '()' before class"), + DiagnosticSpec(locationMarker: "1️⃣", message: "unexpected code '()' in enum"), DiagnosticSpec(locationMarker: "2️⃣", message: "expected '{' in class", fixIts: ["insert '{'"]), - DiagnosticSpec(locationMarker: "2️⃣", message: "unexpected code '()' before protocol"), + DiagnosticSpec(locationMarker: "2️⃣", message: "unexpected code '()' in class"), DiagnosticSpec(locationMarker: "3️⃣", message: "expected '{' in protocol", fixIts: ["insert '{'"]), - DiagnosticSpec(locationMarker: "3️⃣", message: "unexpected code '()' before extension"), - DiagnosticSpec( - locationMarker: "4️⃣", - message: "expected member block in extension", - fixIts: ["insert member block"] - ), - DiagnosticSpec(locationMarker: "4️⃣", message: "expected '}' to end protocol", fixIts: ["insert '}'"]), - DiagnosticSpec(locationMarker: "4️⃣", message: "expected '}' to end class", fixIts: ["insert '}'"]), - DiagnosticSpec(locationMarker: "4️⃣", message: "expected '}' to end enum", fixIts: ["insert '}'"]), + DiagnosticSpec(locationMarker: "3️⃣", message: "unexpected code '()' in protocol"), + DiagnosticSpec(locationMarker: "4️⃣", message: "expected '{' in extension", fixIts: ["insert '{'"]), + DiagnosticSpec(locationMarker: "4️⃣", message: "unexpected code '()' in extension"), + DiagnosticSpec(locationMarker: "5️⃣", message: "expected '}' to end extension", fixIts: ["insert '}'"]), + DiagnosticSpec(locationMarker: "5️⃣", message: "expected '}' to end protocol", fixIts: ["insert '}'"]), + DiagnosticSpec(locationMarker: "5️⃣", message: "expected '}' to end class", fixIts: ["insert '}'"]), + DiagnosticSpec(locationMarker: "5️⃣", message: "expected '}' to end enum", fixIts: ["insert '}'"]), ], fixedSource: """ enum NoBracesUnion1 {() class NoBracesClass1 {() protocol NoBracesProtocol1 {() - extension NoBracesStruct1 { + extension NoBracesStruct1 {() + } } } } - }() """ ) } @@ -1112,7 +1112,7 @@ final class RecoveryTests: ParserTestCase { assertParse( """ enum EE 1️⃣EE where T : Multi { - case a2️⃣ 3️⃣a + case a 2️⃣a case b } """, @@ -1122,18 +1122,12 @@ final class RecoveryTests: ParserTestCase { message: "found an unexpected second identifier in enum; is there an accidental break?", fixIts: ["join the identifiers together"] ), - DiagnosticSpec( - locationMarker: "2️⃣", - message: "consecutive declarations on a line must be separated by newline or ';'", - fixIts: ["insert newline", "insert ';'"] - ), - DiagnosticSpec(locationMarker: "3️⃣", message: "unexpected code 'a' before enum case"), + DiagnosticSpec(locationMarker: "2️⃣", message: "unexpected code 'a' in enum"), ], applyFixIts: ["join the identifiers together", "insert newline"], fixedSource: """ enum EEEE where T : Multi { - case a - a + case a a case b } """ @@ -1144,7 +1138,7 @@ final class RecoveryTests: ParserTestCase { assertParse( """ enum EE 1️⃣EE where T : Multi { - case a2️⃣ 3️⃣a + case a 2️⃣a case b } """, @@ -1154,17 +1148,11 @@ final class RecoveryTests: ParserTestCase { message: "found an unexpected second identifier in enum; is there an accidental break?", fixIts: ["join the identifiers together"] ), - DiagnosticSpec( - locationMarker: "2️⃣", - message: "consecutive declarations on a line must be separated by newline or ';'", - fixIts: ["insert newline", "insert ';'"] - ), - DiagnosticSpec(locationMarker: "3️⃣", message: "unexpected code 'a' before enum case"), + DiagnosticSpec(locationMarker: "2️⃣", message: "unexpected code 'a' in enum"), ], - applyFixIts: ["join the identifiers together", "insert ';'"], fixedSource: """ enum EEEE where T : Multi { - case a;a + case a a case b } """ @@ -1733,7 +1721,7 @@ final class RecoveryTests: ParserTestCase { """, diagnostics: [ DiagnosticSpec( - message: "extraneous code ']' at top level" + message: "unexpected code ']' in source file" ) ] ) @@ -1750,7 +1738,7 @@ final class RecoveryTests: ParserTestCase { notes: [NoteSpec(message: "to match this opening '<'")], fixIts: ["insert '>'"] ), - DiagnosticSpec(message: "extraneous code ']>' at top level"), + DiagnosticSpec(message: "unexpected code ']>' in source file"), ], fixedSource: """ let a2: Set]> @@ -1772,12 +1760,7 @@ final class RecoveryTests: ParserTestCase { diagnostics: [ DiagnosticSpec( locationMarker: "1️⃣", - message: "consecutive declarations on a line must be separated by newline or ';'", - fixIts: ["insert newline", "insert ';'"] - ), - DiagnosticSpec( - locationMarker: "1️⃣", - message: "unexpected code ':' before variable" + message: "unexpected code ':' in struct" ), DiagnosticSpec( locationMarker: "2️⃣", @@ -1812,8 +1795,7 @@ final class RecoveryTests: ParserTestCase { ], fixedSource: """ struct ErrorTypeInVarDeclDictionaryType { - let a1: String - : + let a1: String: let a2: [String: Int] let a3: [String: [Int]] let a4: [String: Int] @@ -1829,7 +1811,7 @@ final class RecoveryTests: ParserTestCase { """, diagnostics: [ DiagnosticSpec( - message: "extraneous code ']' at top level" + message: "unexpected code ']' in source file" ) ] ) @@ -2076,7 +2058,7 @@ final class RecoveryTests: ParserTestCase { } """, diagnostics: [ - DiagnosticSpec(message: "unexpected code before typealias declaration") + DiagnosticSpec(message: "unexpected code in struct") ] ) } @@ -2430,7 +2412,7 @@ final class RecoveryTests: ParserTestCase { ), DiagnosticSpec( locationMarker: "2️⃣", - message: "unexpected code '> {}' in 'switch' statement" + message: "unexpected code '> {}' in switch case" ), ], fixedSource: """ @@ -2502,7 +2484,7 @@ final class RecoveryTests: ParserTestCase { ), DiagnosticSpec( locationMarker: "4️⃣", - message: "unexpected code ')}' before struct" + message: "unexpected code ')}' in source file" ), DiagnosticSpec( locationMarker: "5️⃣", @@ -2597,7 +2579,7 @@ final class RecoveryTests: ParserTestCase { } """, diagnostics: [ - DiagnosticSpec(message: "extraneous code at top level") + DiagnosticSpec(message: "unexpected code in source file") ] ) } @@ -2932,7 +2914,7 @@ final class RecoveryTests: ParserTestCase { ), DiagnosticSpec( locationMarker: "4️⃣", - message: "extraneous brace at top level" + message: "unexpected brace in source file" ), ], applyFixIts: ["remove operator body", "insert newline"], @@ -2969,7 +2951,7 @@ final class RecoveryTests: ParserTestCase { ), DiagnosticSpec( locationMarker: "4️⃣", - message: "extraneous brace at top level" + message: "unexpected brace in source file" ), ], applyFixIts: ["remove operator body", "insert ';'"], @@ -3251,8 +3233,196 @@ final class RecoveryTests: ParserTestCase { assertParse( "func foo() -> Int1️⃣:", diagnostics: [ - DiagnosticSpec(message: "extraneous code ':' at top level") + DiagnosticSpec(message: "unexpected code ':' in source file") + ] + ) + } + + func testStatementAfterAttribute() { + assertParse( + """ + @attr1️⃣ + guard foo else {} + struct S {} + """, + substructure: CodeBlockItemListSyntax([ + CodeBlockItemSyntax( + item: .decl( + DeclSyntax( + MissingDeclSyntax( + attributes: [ + .attribute( + AttributeSyntax( + atSign: .atSignToken(), + attributeName: IdentifierTypeSyntax(name: .identifier("attr")) + ) + ) + ], + modifiers: [], + placeholder: .identifier("<#declaration#>", presence: .missing) + ) + ) + ) + ), + CodeBlockItemSyntax( + item: .init( + GuardStmtSyntax( + guardKeyword: .keyword(.guard), + conditions: ConditionElementListSyntax([ + ConditionElementSyntax( + condition: ConditionElementSyntax.Condition(DeclReferenceExprSyntax(baseName: .identifier("foo"))) + ) + ]), + elseKeyword: .keyword(.else), + body: CodeBlockSyntax( + leftBrace: .leftBraceToken(), + statements: CodeBlockItemListSyntax([]), + rightBrace: .rightBraceToken() + ) + ) + ) + ), + CodeBlockItemSyntax( + item: .init( + StructDeclSyntax( + attributes: AttributeListSyntax([]), + modifiers: DeclModifierListSyntax([]), + structKeyword: .keyword(.struct), + name: .identifier("S"), + memberBlock: MemberBlockSyntax( + leftBrace: .leftBraceToken(), + members: MemberBlockItemListSyntax([]), + rightBrace: .rightBraceToken() + ) + ) + ) + ), + ]), + diagnostics: [ + DiagnosticSpec(message: "expected declaration after attribute", fixIts: ["insert declaration"]) + ], + fixedSource: """ + @attr <#declaration#> + guard foo else {} + struct S {} + """ + ) + } + + func testUnexpectedBeforeAttributeInMemberBlock() { + assertParse( + """ + struct S { + 1️⃣do {} + @attr func foo() + } + """, + substructure: StructDeclSyntax( + name: .identifier("S"), + memberBlock: MemberBlockSyntax( + members: [ + MemberBlockItemSyntax( + decl: UnexpectedCodeDeclSyntax( + unexpectedCode: UnexpectedNodesSyntax([ + TokenSyntax.keyword(.do), + TokenSyntax.leftBraceToken(), + TokenSyntax.rightBraceToken(), + ]) + ) + ), + MemberBlockItemSyntax( + decl: FunctionDeclSyntax( + attributes: [ + .attribute(AttributeSyntax(attributeName: IdentifierTypeSyntax(name: .identifier("attr")))) + ], + name: .identifier("foo"), + signature: FunctionSignatureSyntax( + parameterClause: FunctionParameterClauseSyntax( + leftParen: .leftParenToken(), + parameters: [], + rightParen: .rightParenToken() + ) + ) + ) + ), + ] + ) + ), + diagnostics: [ + DiagnosticSpec(message: "unexpected code 'do {}' in struct") ] ) } + + func testAttrInCodeBlock() { + assertParse( + """ + func foo() { + @attr1️⃣ + } + struct S {} + """, + substructure: CodeBlockItemListSyntax([ + CodeBlockItemSyntax( + item: .init( + FunctionDeclSyntax( + funcKeyword: .keyword(.func), + name: .identifier("foo"), + signature: FunctionSignatureSyntax( + parameterClause: FunctionParameterClauseSyntax( + leftParen: .leftParenToken(), + parameters: FunctionParameterListSyntax([]), + rightParen: .rightParenToken() + ) + ), + body: CodeBlockSyntax( + leftBrace: .leftBraceToken(), + statements: CodeBlockItemListSyntax([ + CodeBlockItemSyntax( + item: .init( + MissingDeclSyntax( + attributes: AttributeListSyntax([ + .attribute( + AttributeSyntax( + atSign: .atSignToken(), + attributeName: TypeSyntax(IdentifierTypeSyntax(name: .identifier("attr"))) + ) + ) + ]), + placeholder: .identifier("<#declaration#>", presence: .missing) + ) + ) + ) + ]), + rightBrace: .rightBraceToken() + ) + ) + ) + ), + CodeBlockItemSyntax( + item: CodeBlockItemSyntax.Item( + StructDeclSyntax( + structKeyword: .keyword(.struct), + name: .identifier("S"), + memberBlock: MemberBlockSyntax( + leftBrace: .leftBraceToken(), + members: MemberBlockItemListSyntax([]), + rightBrace: .rightBraceToken() + ) + ) + ) + ), + ]), + diagnostics: [ + // FIXME: expected *declaration* after attribute + DiagnosticSpec(message: "expected statements after attribute", fixIts: ["insert statements"]) + ], + fixedSource: """ + func foo() { + @attr <#declaration#> + } + struct S {} + """ + ) + } } diff --git a/Tests/SwiftParserTest/translated/TypealiasTests.swift b/Tests/SwiftParserTest/translated/TypealiasTests.swift index 05e769667ac..32ebbb88e5a 100644 --- a/Tests/SwiftParserTest/translated/TypealiasTests.swift +++ b/Tests/SwiftParserTest/translated/TypealiasTests.swift @@ -168,7 +168,7 @@ final class TypealiasTests: ParserTestCase { message: "expected '=' in typealias declaration", fixIts: ["replace ':' with '='"] ), - DiagnosticSpec(locationMarker: "2️⃣", message: "extraneous code ', Float' at top level"), + DiagnosticSpec(locationMarker: "2️⃣", message: "unexpected code ', Float' in source file"), ], fixedSource: """ typealias Recovery5 = Int, Float @@ -183,7 +183,7 @@ final class TypealiasTests: ParserTestCase { """, diagnostics: [ DiagnosticSpec(message: "expected type in typealias declaration", fixIts: ["insert type"]), - DiagnosticSpec(message: "extraneous code '=' at top level"), + DiagnosticSpec(message: "unexpected code '=' in source file"), ], fixedSource: """ typealias Recovery6 = <#type#>=