From 785547e412c1f75aa1ed24f77c1191b9c38a8111 Mon Sep 17 00:00:00 2001 From: Becca Royal-Gordon Date: Thu, 24 Apr 2025 20:39:38 -0700 Subject: [PATCH 01/33] Make `@abi` non-experimental --- .../SyntaxSupport/AttributeNodes.swift | 4 +- .../SyntaxSupport/ExperimentalFeatures.swift | 5 - .../Sources/SyntaxSupport/KeywordSpec.swift | 2 +- Sources/SwiftParser/Attributes.swift | 24 ++-- .../generated/ExperimentalFeatures.swift | 11 +- .../generated/SwiftSyntax.md | 1 + Sources/SwiftSyntax/generated/Keyword.swift | 1 - .../generated/SyntaxAnyVisitor.swift | 2 - .../SwiftSyntax/generated/SyntaxEnum.swift | 1 - .../SwiftSyntax/generated/SyntaxKind.swift | 1 - .../generated/SyntaxRewriter.swift | 3 +- .../SwiftSyntax/generated/SyntaxVisitor.swift | 6 +- .../generated/raw/RawSyntaxNodesAB.swift | 3 - .../generated/syntaxNodes/SyntaxNodesAB.swift | 29 ++-- .../generated/syntaxNodes/SyntaxNodesD.swift | 4 + .../generated/syntaxNodes/SyntaxNodesEF.swift | 8 ++ .../syntaxNodes/SyntaxNodesGHI.swift | 4 + .../syntaxNodes/SyntaxNodesJKLMN.swift | 4 + .../syntaxNodes/SyntaxNodesQRS.swift | 4 + .../syntaxNodes/SyntaxNodesTUVWXYZ.swift | 8 ++ Tests/SwiftParserTest/AttributeTests.swift | 124 ++++-------------- 21 files changed, 86 insertions(+), 163 deletions(-) diff --git a/CodeGeneration/Sources/SyntaxSupport/AttributeNodes.swift b/CodeGeneration/Sources/SyntaxSupport/AttributeNodes.swift index 26041a0cf89..3347cf8bb41 100644 --- a/CodeGeneration/Sources/SyntaxSupport/AttributeNodes.swift +++ b/CodeGeneration/Sources/SyntaxSupport/AttributeNodes.swift @@ -122,8 +122,7 @@ public let ATTRIBUTE_NODES: [Node] = [ Child( name: "abiArguments", // Special arguments for declaration syntax. e.g. @abi(func abiName() -> Int) - kind: .node(kind: .abiAttributeArguments), - experimentalFeature: .abiAttribute + kind: .node(kind: .abiAttributeArguments) ), ]), documentation: """ @@ -256,7 +255,6 @@ public let ATTRIBUTE_NODES: [Node] = [ Node( kind: .abiAttributeArguments, base: .syntax, - experimentalFeature: .abiAttribute, nameForDiagnostics: "ABI-providing declaration", documentation: "The arguments of the '@abi' attribute", children: [ diff --git a/CodeGeneration/Sources/SyntaxSupport/ExperimentalFeatures.swift b/CodeGeneration/Sources/SyntaxSupport/ExperimentalFeatures.swift index bdc09c1f462..aa36cd8ba53 100644 --- a/CodeGeneration/Sources/SyntaxSupport/ExperimentalFeatures.swift +++ b/CodeGeneration/Sources/SyntaxSupport/ExperimentalFeatures.swift @@ -19,7 +19,6 @@ public enum ExperimentalFeature: String, CaseIterable { case nonescapableTypes case trailingComma case coroutineAccessors - case abiAttribute case keypathWithMethodMembers case oldOwnershipOperatorSpellings case inlineArrayTypeSugar @@ -39,8 +38,6 @@ public enum ExperimentalFeature: String, CaseIterable { return "TrailingComma" case .coroutineAccessors: return "CoroutineAccessors" - case .abiAttribute: - return "ABIAttribute" case .keypathWithMethodMembers: return "KeypathWithMethodMembers" case .oldOwnershipOperatorSpellings: @@ -65,8 +62,6 @@ public enum ExperimentalFeature: String, CaseIterable { return "trailing commas" case .coroutineAccessors: return "coroutine accessors" - case .abiAttribute: - return "@abi attribute" case .keypathWithMethodMembers: return "keypaths with method members" case .oldOwnershipOperatorSpellings: diff --git a/CodeGeneration/Sources/SyntaxSupport/KeywordSpec.swift b/CodeGeneration/Sources/SyntaxSupport/KeywordSpec.swift index 7c4b72eeafb..1a32f708965 100644 --- a/CodeGeneration/Sources/SyntaxSupport/KeywordSpec.swift +++ b/CodeGeneration/Sources/SyntaxSupport/KeywordSpec.swift @@ -357,7 +357,7 @@ public enum Keyword: CaseIterable { case ._version: return KeywordSpec("_version") case .abi: - return KeywordSpec("abi", experimentalFeature: .abiAttribute) + return KeywordSpec("abi") case .accesses: return KeywordSpec("accesses") case .actor: diff --git a/Sources/SwiftParser/Attributes.swift b/Sources/SwiftParser/Attributes.swift index 3cfc6ae801d..c29fa786846 100644 --- a/Sources/SwiftParser/Attributes.swift +++ b/Sources/SwiftParser/Attributes.swift @@ -230,9 +230,9 @@ extension Parser { switch peek(isAtAnyIn: DeclarationAttributeWithSpecialSyntax.self) { case .abi: return parseAttribute(argumentMode: .required) { parser in - return parser.parseABIAttributeArguments() + return (nil, .abiArguments(parser.parseABIAttributeArguments())) } parseMissingArguments: { parser in - return parser.parseABIAttributeArguments(missingLParen: true) + return (nil, .abiArguments(parser.parseABIAttributeArguments(missingLParen: true))) } case .available, ._spi_available: return parseAttribute(argumentMode: .required) { parser in @@ -794,9 +794,9 @@ extension Parser { /// - Parameter missingLParen: `true` if the opening paren for the argument list was missing. mutating func parseABIAttributeArguments( missingLParen: Bool = false - ) -> (RawUnexpectedNodesSyntax?, RawAttributeSyntax.Arguments) { - func makeMissingProviderArguments(unexpectedBefore: [RawSyntax]) -> RawAttributeSyntax.Arguments { - let args = RawABIAttributeArgumentsSyntax( + ) -> RawABIAttributeArgumentsSyntax { + func makeMissingProviderArguments(unexpectedBefore: [RawSyntax]) -> RawABIAttributeArgumentsSyntax { + return RawABIAttributeArgumentsSyntax( provider: .missing( RawMissingDeclSyntax( unexpectedBefore.isEmpty ? nil : RawUnexpectedNodesSyntax(elements: unexpectedBefore, arena: self.arena), @@ -808,7 +808,6 @@ extension Parser { ), arena: self.arena ) - return .abiArguments(args) } // Consider the three kinds of mistakes we might see here: @@ -824,23 +823,16 @@ extension Parser { // In lieu of that, we judge that recovering gracefully from #3 is more important than #2 and therefore do not even // attempt to parse the argument unless we've seen a left paren. guard !missingLParen && !self.at(.rightParen) else { - return (nil, makeMissingProviderArguments(unexpectedBefore: [])) + return makeMissingProviderArguments(unexpectedBefore: []) } let decl = parseDeclaration(in: .argumentList) - guard experimentalFeatures.contains(.abiAttribute) else { - return ( - RawUnexpectedNodesSyntax([decl], arena: self.arena), - .argumentList(RawLabeledExprListSyntax(elements: [], arena: self.arena)) - ) - } - guard let provider = RawABIAttributeArgumentsSyntax.Provider(decl) else { - return (nil, makeMissingProviderArguments(unexpectedBefore: [decl.raw])) + return makeMissingProviderArguments(unexpectedBefore: [decl.raw]) } - return (nil, .abiArguments(RawABIAttributeArgumentsSyntax(provider: provider, arena: self.arena))) + return RawABIAttributeArgumentsSyntax(provider: provider, arena: self.arena) } } diff --git a/Sources/SwiftParser/generated/ExperimentalFeatures.swift b/Sources/SwiftParser/generated/ExperimentalFeatures.swift index 77c5206bb99..9513b06aac7 100644 --- a/Sources/SwiftParser/generated/ExperimentalFeatures.swift +++ b/Sources/SwiftParser/generated/ExperimentalFeatures.swift @@ -43,17 +43,14 @@ extension Parser.ExperimentalFeatures { /// Whether to enable the parsing of coroutine accessors. public static let coroutineAccessors = Self (rawValue: 1 << 5) - /// Whether to enable the parsing of @abi attribute. - public static let abiAttribute = Self (rawValue: 1 << 6) - /// Whether to enable the parsing of keypaths with method members. - public static let keypathWithMethodMembers = Self (rawValue: 1 << 7) + public static let keypathWithMethodMembers = Self (rawValue: 1 << 6) /// Whether to enable the parsing of `_move` and `_borrow` as ownership operators. - public static let oldOwnershipOperatorSpellings = Self (rawValue: 1 << 8) + public static let oldOwnershipOperatorSpellings = Self (rawValue: 1 << 7) /// Whether to enable the parsing of sugar type for InlineArray. - public static let inlineArrayTypeSugar = Self (rawValue: 1 << 9) + public static let inlineArrayTypeSugar = Self (rawValue: 1 << 8) /// Creates a new value representing the experimental feature with the /// given name, or returns nil if the name is not recognized. @@ -71,8 +68,6 @@ extension Parser.ExperimentalFeatures { self = .trailingComma case "CoroutineAccessors": self = .coroutineAccessors - case "ABIAttribute": - self = .abiAttribute case "KeypathWithMethodMembers": self = .keypathWithMethodMembers case "OldOwnershipOperatorSpellings": diff --git a/Sources/SwiftSyntax/Documentation.docc/generated/SwiftSyntax.md b/Sources/SwiftSyntax/Documentation.docc/generated/SwiftSyntax.md index 4ec126dbe4f..ee9216345d3 100644 --- a/Sources/SwiftSyntax/Documentation.docc/generated/SwiftSyntax.md +++ b/Sources/SwiftSyntax/Documentation.docc/generated/SwiftSyntax.md @@ -303,6 +303,7 @@ allows Swift tools to parse, inspect, generate, and transform Swift source code. ### Attributes +- - - - diff --git a/Sources/SwiftSyntax/generated/Keyword.swift b/Sources/SwiftSyntax/generated/Keyword.swift index 20b36ff1455..6f049eb7936 100644 --- a/Sources/SwiftSyntax/generated/Keyword.swift +++ b/Sources/SwiftSyntax/generated/Keyword.swift @@ -54,7 +54,6 @@ public enum Keyword: UInt8, Hashable, Sendable { case _underlyingVersion case _UnknownLayout case _version - @_spi(ExperimentalLanguageFeatures) case abi case accesses case actor diff --git a/Sources/SwiftSyntax/generated/SyntaxAnyVisitor.swift b/Sources/SwiftSyntax/generated/SyntaxAnyVisitor.swift index 124561440e0..761e69e41d9 100644 --- a/Sources/SwiftSyntax/generated/SyntaxAnyVisitor.swift +++ b/Sources/SwiftSyntax/generated/SyntaxAnyVisitor.swift @@ -57,12 +57,10 @@ open class SyntaxAnyVisitor: SyntaxVisitor { visitAnyPost(node._syntaxNode) } - @_spi(ExperimentalLanguageFeatures) override open func visit(_ node: ABIAttributeArgumentsSyntax) -> SyntaxVisitorContinueKind { return visitAny(node._syntaxNode) } - @_spi(ExperimentalLanguageFeatures) override open func visitPost(_ node: ABIAttributeArgumentsSyntax) { visitAnyPost(node._syntaxNode) } diff --git a/Sources/SwiftSyntax/generated/SyntaxEnum.swift b/Sources/SwiftSyntax/generated/SyntaxEnum.swift index 283c1d82d03..5f70ddd32a8 100644 --- a/Sources/SwiftSyntax/generated/SyntaxEnum.swift +++ b/Sources/SwiftSyntax/generated/SyntaxEnum.swift @@ -16,7 +16,6 @@ /// Enum to exhaustively switch over all different syntax nodes. public enum SyntaxEnum: Sendable { case token(TokenSyntax) - @_spi(ExperimentalLanguageFeatures) case abiAttributeArguments(ABIAttributeArgumentsSyntax) @_spi(Compiler) case accessorBlockFile(AccessorBlockFileSyntax) diff --git a/Sources/SwiftSyntax/generated/SyntaxKind.swift b/Sources/SwiftSyntax/generated/SyntaxKind.swift index 62883dd7ac3..f1d00716562 100644 --- a/Sources/SwiftSyntax/generated/SyntaxKind.swift +++ b/Sources/SwiftSyntax/generated/SyntaxKind.swift @@ -16,7 +16,6 @@ /// Enumerates the known kinds of Syntax represented in the Syntax tree. public enum SyntaxKind: Sendable { case token - @_spi(ExperimentalLanguageFeatures) case abiAttributeArguments @_spi(Compiler) case accessorBlockFile diff --git a/Sources/SwiftSyntax/generated/SyntaxRewriter.swift b/Sources/SwiftSyntax/generated/SyntaxRewriter.swift index 77eb14701ba..06778189fb7 100644 --- a/Sources/SwiftSyntax/generated/SyntaxRewriter.swift +++ b/Sources/SwiftSyntax/generated/SyntaxRewriter.swift @@ -99,10 +99,9 @@ open class SyntaxRewriter { return token } - /// Visit a `ABIAttributeArgumentsSyntax`. + /// Visit a ``ABIAttributeArgumentsSyntax``. /// - Parameter node: the node that is being visited /// - Returns: the rewritten node - @_spi(ExperimentalLanguageFeatures) open func visit(_ node: ABIAttributeArgumentsSyntax) -> ABIAttributeArgumentsSyntax { return ABIAttributeArgumentsSyntax(unsafeCasting: visitChildren(node._syntaxNode)) } diff --git a/Sources/SwiftSyntax/generated/SyntaxVisitor.swift b/Sources/SwiftSyntax/generated/SyntaxVisitor.swift index 739a203874c..d600655152f 100644 --- a/Sources/SwiftSyntax/generated/SyntaxVisitor.swift +++ b/Sources/SwiftSyntax/generated/SyntaxVisitor.swift @@ -35,17 +35,15 @@ open class SyntaxVisitor { dispatchVisit(Syntax(node)) } - /// Visiting `ABIAttributeArgumentsSyntax` specifically. + /// Visiting ``ABIAttributeArgumentsSyntax`` specifically. /// - Parameter node: the node we are visiting. /// - Returns: how should we continue visiting. - @_spi(ExperimentalLanguageFeatures) open func visit(_ node: ABIAttributeArgumentsSyntax) -> SyntaxVisitorContinueKind { return .visitChildren } - /// The function called after visiting `ABIAttributeArgumentsSyntax` and its descendants. + /// The function called after visiting ``ABIAttributeArgumentsSyntax`` and its descendants. /// - node: the node we just finished visiting. - @_spi(ExperimentalLanguageFeatures) open func visitPost(_ node: ABIAttributeArgumentsSyntax) { } diff --git a/Sources/SwiftSyntax/generated/raw/RawSyntaxNodesAB.swift b/Sources/SwiftSyntax/generated/raw/RawSyntaxNodesAB.swift index 5c824e33f3c..2c820290b8b 100644 --- a/Sources/SwiftSyntax/generated/raw/RawSyntaxNodesAB.swift +++ b/Sources/SwiftSyntax/generated/raw/RawSyntaxNodesAB.swift @@ -13,7 +13,6 @@ // Do not edit directly! // swift-format-ignore-file -@_spi(ExperimentalLanguageFeatures) @_spi(RawSyntax) public struct RawABIAttributeArgumentsSyntax: RawSyntaxNodeProtocol { public enum Provider: RawSyntaxNodeProtocol { @@ -1615,8 +1614,6 @@ public struct RawAttributeSyntax: RawSyntaxNodeProtocol { case dynamicReplacementArguments(RawDynamicReplacementAttributeArgumentsSyntax) case effectsArguments(RawEffectsAttributeArgumentListSyntax) case documentationArguments(RawDocumentationAttributeArgumentListSyntax) - /// - Note: Requires experimental feature `abiAttribute`. - @_spi(ExperimentalLanguageFeatures) case abiArguments(RawABIAttributeArgumentsSyntax) public static func isKindOf(_ raw: RawSyntax) -> Bool { diff --git a/Sources/SwiftSyntax/generated/syntaxNodes/SyntaxNodesAB.swift b/Sources/SwiftSyntax/generated/syntaxNodes/SyntaxNodesAB.swift index 1a444b28949..6baae95f2e3 100644 --- a/Sources/SwiftSyntax/generated/syntaxNodes/SyntaxNodesAB.swift +++ b/Sources/SwiftSyntax/generated/syntaxNodes/SyntaxNodesAB.swift @@ -17,8 +17,6 @@ /// The arguments of the '@abi' attribute /// -/// - Note: Requires experimental feature `abiAttribute`. -/// /// ### Children /// /// - `provider`: (``AssociatedTypeDeclSyntax`` | ``DeinitializerDeclSyntax`` | ``EnumCaseDeclSyntax`` | ``FunctionDeclSyntax`` | ``InitializerDeclSyntax`` | ``MissingDeclSyntax`` | ``SubscriptDeclSyntax`` | ``TypeAliasDeclSyntax`` | ``VariableDeclSyntax``) @@ -26,7 +24,6 @@ /// ### Contained in /// /// - ``AttributeSyntax``.``AttributeSyntax/arguments`` -@_spi(ExperimentalLanguageFeatures) public struct ABIAttributeArgumentsSyntax: SyntaxProtocol, SyntaxHashable, _LeafSyntaxNodeProtocol { public enum Provider: SyntaxChildChoices, SyntaxHashable { case associatedType(AssociatedTypeDeclSyntax) @@ -2600,6 +2597,10 @@ public struct AssignmentExprSyntax: ExprSyntaxProtocol, SyntaxHashable, _LeafExp /// - `inheritanceClause`: ``InheritanceClauseSyntax``? /// - `initializer`: ``TypeInitializerClauseSyntax``? /// - `genericWhereClause`: ``GenericWhereClauseSyntax``? +/// +/// ### Contained in +/// +/// - ``ABIAttributeArgumentsSyntax``.``ABIAttributeArgumentsSyntax/provider`` public struct AssociatedTypeDeclSyntax: DeclSyntaxProtocol, SyntaxHashable, _LeafDeclSyntaxNodeProtocol { public let _syntaxNode: Syntax @@ -3097,7 +3098,7 @@ public struct AttributeClauseFileSyntax: SyntaxProtocol, SyntaxHashable, _LeafSy /// - `atSign`: `@` /// - `attributeName`: ``TypeSyntax`` /// - `leftParen`: `(`? -/// - `arguments`: (``LabeledExprListSyntax`` | ``AvailabilityArgumentListSyntax`` | ``SpecializeAttributeArgumentListSyntax`` | ``ObjCSelectorPieceListSyntax`` | ``ImplementsAttributeArgumentsSyntax`` | ``DifferentiableAttributeArgumentsSyntax`` | ``DerivativeAttributeArgumentsSyntax`` | ``BackDeployedAttributeArgumentsSyntax`` | ``OriginallyDefinedInAttributeArgumentsSyntax`` | ``DynamicReplacementAttributeArgumentsSyntax`` | ``EffectsAttributeArgumentListSyntax`` | ``DocumentationAttributeArgumentListSyntax`` | `ABIAttributeArgumentsSyntax`)? +/// - `arguments`: (``LabeledExprListSyntax`` | ``AvailabilityArgumentListSyntax`` | ``SpecializeAttributeArgumentListSyntax`` | ``ObjCSelectorPieceListSyntax`` | ``ImplementsAttributeArgumentsSyntax`` | ``DifferentiableAttributeArgumentsSyntax`` | ``DerivativeAttributeArgumentsSyntax`` | ``BackDeployedAttributeArgumentsSyntax`` | ``OriginallyDefinedInAttributeArgumentsSyntax`` | ``DynamicReplacementAttributeArgumentsSyntax`` | ``EffectsAttributeArgumentListSyntax`` | ``DocumentationAttributeArgumentListSyntax`` | ``ABIAttributeArgumentsSyntax``)? /// - `rightParen`: `)`? /// /// ### Contained in @@ -3118,8 +3119,6 @@ public struct AttributeSyntax: SyntaxProtocol, SyntaxHashable, _LeafSyntaxNodePr case dynamicReplacementArguments(DynamicReplacementAttributeArgumentsSyntax) case effectsArguments(EffectsAttributeArgumentListSyntax) case documentationArguments(DocumentationAttributeArgumentListSyntax) - /// - Note: Requires experimental feature `abiAttribute`. - @_spi(ExperimentalLanguageFeatures) case abiArguments(ABIAttributeArgumentsSyntax) public var _syntaxNode: Syntax { @@ -3201,8 +3200,6 @@ public struct AttributeSyntax: SyntaxProtocol, SyntaxHashable, _LeafSyntaxNodePr self = .documentationArguments(node) } - /// - Note: Requires experimental feature `abiAttribute`. - @_spi(ExperimentalLanguageFeatures) public init(_ node: ABIAttributeArgumentsSyntax) { self = .abiArguments(node) } @@ -3521,30 +3518,24 @@ public struct AttributeSyntax: SyntaxProtocol, SyntaxHashable, _LeafSyntaxNodePr return self.as(DocumentationAttributeArgumentListSyntax.self)! } - /// Checks if the current syntax node can be cast to `ABIAttributeArgumentsSyntax`. + /// Checks if the current syntax node can be cast to ``ABIAttributeArgumentsSyntax``. /// /// - Returns: `true` if the node can be cast, `false` otherwise. - /// - Note: Requires experimental feature `abiAttribute`. - @_spi(ExperimentalLanguageFeatures) public func `is`(_ syntaxType: ABIAttributeArgumentsSyntax.Type) -> Bool { return self.as(syntaxType) != nil } - /// Attempts to cast the current syntax node to `ABIAttributeArgumentsSyntax`. + /// Attempts to cast the current syntax node to ``ABIAttributeArgumentsSyntax``. /// - /// - Returns: An instance of `ABIAttributeArgumentsSyntax`, or `nil` if the cast fails. - /// - Note: Requires experimental feature `abiAttribute`. - @_spi(ExperimentalLanguageFeatures) + /// - Returns: An instance of ``ABIAttributeArgumentsSyntax``, or `nil` if the cast fails. public func `as`(_ syntaxType: ABIAttributeArgumentsSyntax.Type) -> ABIAttributeArgumentsSyntax? { return ABIAttributeArgumentsSyntax.init(self) } - /// Force-casts the current syntax node to `ABIAttributeArgumentsSyntax`. + /// Force-casts the current syntax node to ``ABIAttributeArgumentsSyntax``. /// - /// - Returns: An instance of `ABIAttributeArgumentsSyntax`. + /// - Returns: An instance of ``ABIAttributeArgumentsSyntax``. /// - Warning: This function will crash if the cast is not possible. Use `as` to safely attempt a cast. - /// - Note: Requires experimental feature `abiAttribute`. - @_spi(ExperimentalLanguageFeatures) public func cast(_ syntaxType: ABIAttributeArgumentsSyntax.Type) -> ABIAttributeArgumentsSyntax { return self.as(ABIAttributeArgumentsSyntax.self)! } diff --git a/Sources/SwiftSyntax/generated/syntaxNodes/SyntaxNodesD.swift b/Sources/SwiftSyntax/generated/syntaxNodes/SyntaxNodesD.swift index 6c8b49198a3..4891b0cdf97 100644 --- a/Sources/SwiftSyntax/generated/syntaxNodes/SyntaxNodesD.swift +++ b/Sources/SwiftSyntax/generated/syntaxNodes/SyntaxNodesD.swift @@ -893,6 +893,10 @@ public struct DeferStmtSyntax: StmtSyntaxProtocol, SyntaxHashable, _LeafStmtSynt /// - `deinitKeyword`: `deinit` /// - `effectSpecifiers`: ``DeinitializerEffectSpecifiersSyntax``? /// - `body`: ``CodeBlockSyntax``? +/// +/// ### Contained in +/// +/// - ``ABIAttributeArgumentsSyntax``.``ABIAttributeArgumentsSyntax/provider`` public struct DeinitializerDeclSyntax: DeclSyntaxProtocol, SyntaxHashable, _LeafDeclSyntaxNodeProtocol { public let _syntaxNode: Syntax diff --git a/Sources/SwiftSyntax/generated/syntaxNodes/SyntaxNodesEF.swift b/Sources/SwiftSyntax/generated/syntaxNodes/SyntaxNodesEF.swift index 2c9f89946cf..41c56cd8b1d 100644 --- a/Sources/SwiftSyntax/generated/syntaxNodes/SyntaxNodesEF.swift +++ b/Sources/SwiftSyntax/generated/syntaxNodes/SyntaxNodesEF.swift @@ -312,6 +312,10 @@ public struct EditorPlaceholderExprSyntax: ExprSyntaxProtocol, SyntaxHashable, _ /// - `modifiers`: ``DeclModifierListSyntax`` /// - `caseKeyword`: `case` /// - `elements`: ``EnumCaseElementListSyntax`` +/// +/// ### Contained in +/// +/// - ``ABIAttributeArgumentsSyntax``.``ABIAttributeArgumentsSyntax/provider`` public struct EnumCaseDeclSyntax: DeclSyntaxProtocol, SyntaxHashable, _LeafDeclSyntaxNodeProtocol { public let _syntaxNode: Syntax @@ -3312,6 +3316,10 @@ public struct FunctionCallExprSyntax: ExprSyntaxProtocol, SyntaxHashable, _LeafE /// - `signature`: ``FunctionSignatureSyntax`` /// - `genericWhereClause`: ``GenericWhereClauseSyntax``? /// - `body`: ``CodeBlockSyntax``? +/// +/// ### Contained in +/// +/// - ``ABIAttributeArgumentsSyntax``.``ABIAttributeArgumentsSyntax/provider`` public struct FunctionDeclSyntax: DeclSyntaxProtocol, SyntaxHashable, _LeafDeclSyntaxNodeProtocol { public let _syntaxNode: Syntax diff --git a/Sources/SwiftSyntax/generated/syntaxNodes/SyntaxNodesGHI.swift b/Sources/SwiftSyntax/generated/syntaxNodes/SyntaxNodesGHI.swift index d28ff8b926b..6fd799b9e7f 100644 --- a/Sources/SwiftSyntax/generated/syntaxNodes/SyntaxNodesGHI.swift +++ b/Sources/SwiftSyntax/generated/syntaxNodes/SyntaxNodesGHI.swift @@ -4022,6 +4022,10 @@ public struct InitializerClauseSyntax: SyntaxProtocol, SyntaxHashable, _LeafSynt /// - `signature`: ``FunctionSignatureSyntax`` /// - `genericWhereClause`: ``GenericWhereClauseSyntax``? /// - `body`: ``CodeBlockSyntax``? +/// +/// ### Contained in +/// +/// - ``ABIAttributeArgumentsSyntax``.``ABIAttributeArgumentsSyntax/provider`` public struct InitializerDeclSyntax: DeclSyntaxProtocol, SyntaxHashable, _LeafDeclSyntaxNodeProtocol { public let _syntaxNode: Syntax diff --git a/Sources/SwiftSyntax/generated/syntaxNodes/SyntaxNodesJKLMN.swift b/Sources/SwiftSyntax/generated/syntaxNodes/SyntaxNodesJKLMN.swift index fe4e496d5ab..902e11c9a85 100644 --- a/Sources/SwiftSyntax/generated/syntaxNodes/SyntaxNodesJKLMN.swift +++ b/Sources/SwiftSyntax/generated/syntaxNodes/SyntaxNodesJKLMN.swift @@ -4536,6 +4536,10 @@ public struct MetatypeTypeSyntax: TypeSyntaxProtocol, SyntaxHashable, _LeafTypeS /// - `attributes`: ``AttributeListSyntax`` /// - `modifiers`: ``DeclModifierListSyntax`` /// - `placeholder`: `` +/// +/// ### Contained in +/// +/// - ``ABIAttributeArgumentsSyntax``.``ABIAttributeArgumentsSyntax/provider`` public struct MissingDeclSyntax: DeclSyntaxProtocol, SyntaxHashable, _LeafDeclSyntaxNodeProtocol { public let _syntaxNode: Syntax diff --git a/Sources/SwiftSyntax/generated/syntaxNodes/SyntaxNodesQRS.swift b/Sources/SwiftSyntax/generated/syntaxNodes/SyntaxNodesQRS.swift index 2431b585fc8..1a705af1c4d 100644 --- a/Sources/SwiftSyntax/generated/syntaxNodes/SyntaxNodesQRS.swift +++ b/Sources/SwiftSyntax/generated/syntaxNodes/SyntaxNodesQRS.swift @@ -3108,6 +3108,10 @@ public struct SubscriptCallExprSyntax: ExprSyntaxProtocol, SyntaxHashable, _Leaf /// - `returnClause`: ``ReturnClauseSyntax`` /// - `genericWhereClause`: ``GenericWhereClauseSyntax``? /// - `accessorBlock`: ``AccessorBlockSyntax``? +/// +/// ### Contained in +/// +/// - ``ABIAttributeArgumentsSyntax``.``ABIAttributeArgumentsSyntax/provider`` public struct SubscriptDeclSyntax: DeclSyntaxProtocol, SyntaxHashable, _LeafDeclSyntaxNodeProtocol { public let _syntaxNode: Syntax diff --git a/Sources/SwiftSyntax/generated/syntaxNodes/SyntaxNodesTUVWXYZ.swift b/Sources/SwiftSyntax/generated/syntaxNodes/SyntaxNodesTUVWXYZ.swift index 88aa709709f..cf0c4b33f19 100644 --- a/Sources/SwiftSyntax/generated/syntaxNodes/SyntaxNodesTUVWXYZ.swift +++ b/Sources/SwiftSyntax/generated/syntaxNodes/SyntaxNodesTUVWXYZ.swift @@ -1851,6 +1851,10 @@ public struct TupleTypeSyntax: TypeSyntaxProtocol, SyntaxHashable, _LeafTypeSynt /// - `genericParameterClause`: ``GenericParameterClauseSyntax``? /// - `initializer`: ``TypeInitializerClauseSyntax`` /// - `genericWhereClause`: ``GenericWhereClauseSyntax``? +/// +/// ### Contained in +/// +/// - ``ABIAttributeArgumentsSyntax``.``ABIAttributeArgumentsSyntax/provider`` public struct TypeAliasDeclSyntax: DeclSyntaxProtocol, SyntaxHashable, _LeafDeclSyntaxNodeProtocol { public let _syntaxNode: Syntax @@ -3219,6 +3223,10 @@ public struct ValueBindingPatternSyntax: PatternSyntaxProtocol, SyntaxHashable, /// - `modifiers`: ``DeclModifierListSyntax`` /// - `bindingSpecifier`: (`let` | `var` | `inout` | `_mutating` | `_borrowing` | `_consuming`) /// - `bindings`: ``PatternBindingListSyntax`` +/// +/// ### Contained in +/// +/// - ``ABIAttributeArgumentsSyntax``.``ABIAttributeArgumentsSyntax/provider`` public struct VariableDeclSyntax: DeclSyntaxProtocol, SyntaxHashable, _LeafDeclSyntaxNodeProtocol { public let _syntaxNode: Syntax diff --git a/Tests/SwiftParserTest/AttributeTests.swift b/Tests/SwiftParserTest/AttributeTests.swift index 1bc34070d1b..5d82301c61e 100644 --- a/Tests/SwiftParserTest/AttributeTests.swift +++ b/Tests/SwiftParserTest/AttributeTests.swift @@ -960,23 +960,20 @@ final class AttributeTests: ParserTestCase { parameterClause: FunctionParameterClauseSyntax {}, returnClause: ReturnClauseSyntax(type: TypeSyntax("Int")) ) - ) {}, - experimentalFeatures: [.abiAttribute] + ) {} ) assertParse( """ @abi(associatedtype AssocTy) associatedtype AssocTy - """, - experimentalFeatures: [.abiAttribute] + """ ) assertParse( """ @abi(deinit) deinit {} - """, - experimentalFeatures: [.abiAttribute] + """ ) assertParse( """ @@ -984,50 +981,43 @@ final class AttributeTests: ParserTestCase { @abi(case someCase) case someCase } - """, - experimentalFeatures: [.abiAttribute] + """ ) assertParse( """ @abi(func fn()) func fn() - """, - experimentalFeatures: [.abiAttribute] + """ ) assertParse( """ @abi(init()) init() {} - """, - experimentalFeatures: [.abiAttribute] + """ ) assertParse( """ @abi(subscript(i: Int) -> Element) subscript(i: Int) -> Element {} - """, - experimentalFeatures: [.abiAttribute] + """ ) assertParse( """ @abi(typealias Typealias = @escaping () -> Void) typealias Typealias = () -> Void - """, - experimentalFeatures: [.abiAttribute] + """ ) assertParse( """ @abi(let c1, c2) let c1, c2 - """, - experimentalFeatures: [.abiAttribute] + """ ) assertParse( """ @abi(var v1, v2) var v1, v2 - """, - experimentalFeatures: [.abiAttribute] + """ ) assertParse( @@ -1042,8 +1032,7 @@ final class AttributeTests: ParserTestCase { ), diagnostics: [ DiagnosticSpec(locationMarker: "1️⃣", message: "editor placeholder in source file") - ], - experimentalFeatures: [.abiAttribute] + ] ) assertParse( @@ -1067,8 +1056,7 @@ final class AttributeTests: ParserTestCase { ), diagnostics: [ DiagnosticSpec(locationMarker: "1️⃣", message: "import is not permitted as ABI-providing declaration") - ], - experimentalFeatures: [.abiAttribute] + ] ) // @@ -1079,15 +1067,13 @@ final class AttributeTests: ParserTestCase { """ @abi(associatedtype AssocTy = T) associatedtype AssocTy - """, - experimentalFeatures: [.abiAttribute] + """ ) assertParse( """ @abi(deinit {}) deinit {} - """, - experimentalFeatures: [.abiAttribute] + """ ) assertParse( """ @@ -1095,50 +1081,43 @@ final class AttributeTests: ParserTestCase { @abi(case someCase = 42) case someCase } - """, - experimentalFeatures: [.abiAttribute] + """ ) assertParse( """ @abi(func fn() {}) func fn() - """, - experimentalFeatures: [.abiAttribute] + """ ) assertParse( """ @abi(init() {}) init() {} - """, - experimentalFeatures: [.abiAttribute] + """ ) assertParse( """ @abi(subscript(i: Int) -> Element { get {} set {} }) subscript(i: Int) -> Element {} - """, - experimentalFeatures: [.abiAttribute] + """ ) assertParse( """ @abi(let c1 = 1, c2 = 2) let c1, c2 - """, - experimentalFeatures: [.abiAttribute] + """ ) assertParse( """ @abi(var v1 = 1, v2 = 2) var v1, v2 - """, - experimentalFeatures: [.abiAttribute] + """ ) assertParse( """ @abi(var v3 { get {} set {} }) var v3 - """, - experimentalFeatures: [.abiAttribute] + """ ) // @@ -1160,8 +1139,7 @@ final class AttributeTests: ParserTestCase { fixedSource: """ @abi(var <#pattern#>) var v1 - """, - experimentalFeatures: [.abiAttribute] + """ ) assertParse( """ @@ -1184,8 +1162,7 @@ final class AttributeTests: ParserTestCase { fixedSource: """ @abi(var v2) var v2 - """, - experimentalFeatures: [.abiAttribute] + """ ) assertParse( """ @@ -1203,8 +1180,7 @@ final class AttributeTests: ParserTestCase { fixedSource: """ @abi(<#declaration#>) func fn2() {} - """, - experimentalFeatures: [.abiAttribute] + """ ) assertParse( """ @@ -1221,8 +1197,7 @@ final class AttributeTests: ParserTestCase { fixedSource: """ @abi(<#declaration#>) func fn3() {} - """, - experimentalFeatures: [.abiAttribute] + """ ) assertParse( """ @@ -1244,8 +1219,7 @@ final class AttributeTests: ParserTestCase { fixedSource: """ @abi(<#declaration#>) func fn4_abi()) func fn4() {} - """, - experimentalFeatures: [.abiAttribute] + """ ) // `#if` is banned inside an `@abi` attribute. @@ -1278,51 +1252,7 @@ final class AttributeTests: ParserTestCase { func _fn() throws(E) ) func fn() throws(E) {} - """, - experimentalFeatures: [.abiAttribute] - ) - } - - func testABIAttributeWithoutFeature() throws { - assertParse( - """ - @abi(1️⃣func fn() -> Int2️⃣) - func fn1() -> Int { } - """, - substructure: FunctionDeclSyntax( - attributes: [ - .attribute( - AttributeSyntax( - attributeName: TypeSyntax("abi"), - leftParen: .leftParenToken(), - [Syntax(try FunctionDeclSyntax("func fn() -> Int"))], - arguments: .argumentList([]), - rightParen: .rightParenToken() - ) - ) - ], - name: "fn1", - signature: FunctionSignatureSyntax( - parameterClause: FunctionParameterClauseSyntax {}, - returnClause: ReturnClauseSyntax(type: TypeSyntax("Int")) - ) - ) {}, - diagnostics: [ - DiagnosticSpec( - locationMarker: "1️⃣", - message: "unexpected code 'func fn() -> Int' in attribute" - ), - DiagnosticSpec( - locationMarker: "2️⃣", - message: "expected argument for '@abi' attribute", - fixIts: ["insert attribute argument"] - ), - ], - fixedSource: """ - @abi(func fn() -> Int) - func fn1() -> Int { } - """, - experimentalFeatures: [] + """ ) } From b9d90d9098a29db76d74c7887bd9abceb9ae3d05 Mon Sep 17 00:00:00 2001 From: Ben Barham Date: Fri, 9 May 2025 12:39:13 -0700 Subject: [PATCH 02/33] [CI] Remove skip for ready for review This unfortunately ends up causing CI checks to reset and always skipped after marking a PR ready for review. --- .github/workflows/pull_request.yml | 9 ++------- 1 file changed, 2 insertions(+), 7 deletions(-) diff --git a/.github/workflows/pull_request.yml b/.github/workflows/pull_request.yml index fd8e6f14272..62868ba50eb 100644 --- a/.github/workflows/pull_request.yml +++ b/.github/workflows/pull_request.yml @@ -1,5 +1,7 @@ name: Pull request +# PRs created by GitHub Actions don't kick off further actions (https://github.com/peter-evans/create-pull-request/blob/d57e551ebc1a16dee0b8c9ea6d24dba7627a6e35/docs/concepts-guidelines.md#triggering-further-workflow-runs). +# As a workaround, we mark automerge PRs that are created by GitHub actions as draft and trigger the GitHub actions by marking the PR as ready for review. We'd prefer not re-triggering testing on a normal user's PR in this case, but skipping them causes the checks to reset. on: pull_request: types: [opened, reopened, synchronize, ready_for_review] @@ -11,20 +13,15 @@ concurrency: jobs: tests: name: Test - # PRs created by GitHub Actions don't kick off further actions (https://github.com/peter-evans/create-pull-request/blob/d57e551ebc1a16dee0b8c9ea6d24dba7627a6e35/docs/concepts-guidelines.md#triggering-further-workflow-runs). - # As a workaround, we mark automerge PRs that are created by GitHub actions as draft and trigger the GitHub actions by marking the PR as ready for review. But we don't want to re-trigger testing this when a normal user's PR is marked as ready for review. - if: (github.event.action != 'ready_for_review') || (github.event.action == 'ready_for_review' && github.event.pull_request.user.login == 'github-actions[bot]') uses: swiftlang/github-workflows/.github/workflows/swift_package_test.yml@main soundness: name: Soundness - if: (github.event.action != 'ready_for_review') || (github.event.action == 'ready_for_review' && github.event.pull_request.user.login == 'github-actions[bot]') uses: swiftlang/github-workflows/.github/workflows/soundness.yml@main with: api_breakage_check_enabled: false # https://github.com/swiftlang/swift-syntax/issues/3010 docs_check_additional_arguments: "--disable-parameters-and-returns-validation" verify_source_code: name: Validate generated code - if: (github.event.action != 'ready_for_review') || (github.event.action == 'ready_for_review' && github.event.pull_request.user.login == 'github-actions[bot]') runs-on: ubuntu-latest container: image: swift:latest @@ -37,7 +34,6 @@ jobs: run: "./swift-syntax-dev-utils verify-source-code --toolchain /usr" test_using_swift_syntax_dev_utils_linux: name: Run tests using swift-syntax-dev-utils (Linux) - if: (github.event.action != 'ready_for_review') || (github.event.action == 'ready_for_review' && github.event.pull_request.user.login == 'github-actions[bot]') runs-on: ubuntu-latest container: image: swift:latest @@ -50,7 +46,6 @@ jobs: run: "./swift-syntax-dev-utils test --enable-rawsyntax-validation --enable-test-fuzzing --toolchain /usr" test_using_swift_syntax_dev_utils_windows: name: Run tests using swift-syntax-dev-utils (Windows) - if: (github.event.action != 'ready_for_review') || (github.event.action == 'ready_for_review' && github.event.pull_request.user.login == 'github-actions[bot]') runs-on: windows-2022 steps: - name: Pull Docker image From d95175a34c9b3ac0c2389da30dffe175d9e2e33c Mon Sep 17 00:00:00 2001 From: Ben Barham Date: Fri, 9 May 2025 12:41:01 -0700 Subject: [PATCH 03/33] Update the allowed user for publishing releases to bnbarham --- .github/workflows/publish_release.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/publish_release.yml b/.github/workflows/publish_release.yml index 44f68cce276..715bdcbcc8b 100644 --- a/.github/workflows/publish_release.yml +++ b/.github/workflows/publish_release.yml @@ -24,7 +24,7 @@ jobs: runs-on: ubuntu-latest steps: - run: | - if [[ "${{ github.triggering_actor }}" != "ahoppen" ]]; then + if [[ "${{ github.triggering_actor }}" != "bnbarham" ]]; then echo "${{ github.triggering_actor }} is not allowed to create a release" exit 1 fi From cc3a7a2b14f48b2139a3a1a4064393801f7cd32b Mon Sep 17 00:00:00 2001 From: Ben Barham Date: Wed, 14 May 2025 14:14:30 -0700 Subject: [PATCH 04/33] Add more code owners --- .github/CODEOWNERS | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS index 96e24e703e1..2d063195ca8 100644 --- a/.github/CODEOWNERS +++ b/.github/CODEOWNERS @@ -8,7 +8,7 @@ # Order is important. The last matching pattern has the most precedence. # Owner of anything in SwiftSyntax not owned by anyone else. -* @ahoppen @bnbarham +* @ahoppen @bnbarham @hamishknight @rintaro # Macros /Sources/SwiftSyntaxMacros @DougGregor From 462f81933a2f3438c0332a13ae403286b055d35c Mon Sep 17 00:00:00 2001 From: Cal Stephens Date: Mon, 19 May 2025 09:07:22 -0700 Subject: [PATCH 05/33] Add trailing comma support in cases missing from Swift 6.1 --- Sources/SwiftParser/Attributes.swift | 17 ++++++- Sources/SwiftParser/Availability.swift | 5 ++ Sources/SwiftParser/Nominals.swift | 5 ++ Sources/SwiftParser/Types.swift | 5 ++ Tests/SwiftParserTest/AttributeTests.swift | 48 +++++++++++++++++++ Tests/SwiftParserTest/DeclarationTests.swift | 27 +++++++++++ Tests/SwiftParserTest/TypeTests.swift | 38 +++++++++++++++ .../translated/AvailabilityQueryTests.swift | 12 +---- ...AvailabilityQueryUnavailabilityTests.swift | 28 ++++++----- 9 files changed, 161 insertions(+), 24 deletions(-) diff --git a/Sources/SwiftParser/Attributes.swift b/Sources/SwiftParser/Attributes.swift index c29fa786846..7df62ae0617 100644 --- a/Sources/SwiftParser/Attributes.swift +++ b/Sources/SwiftParser/Attributes.swift @@ -362,7 +362,7 @@ extension Parser { let additionalArgs = self.parseArgumentListElements( pattern: .none, flavor: .attributeArguments, - allowTrailingComma: false + allowTrailingComma: true ) return [roleElement] + additionalArgs } @@ -852,6 +852,11 @@ extension Parser { arena: self.arena ) ) + + // If this was a trailing closure then there are no more elements + if self.at(.rightParen) { + break + } } while keepGoing != nil return RawBackDeployedAttributeArgumentsSyntax( unexpectedBeforeLabel, @@ -883,6 +888,11 @@ extension Parser { arena: self.arena ) ) + + // If this was a trailing closure then there are no more elements + if self.at(.rightParen) { + break + } } while keepGoing != nil return RawOriginallyDefinedInAttributeArgumentsSyntax( @@ -1001,6 +1011,11 @@ extension Parser { arena: self.arena ) ) + + // If this was a trailing closure then there are no more elements + if self.at(.rightParen) { + break + } } while keepGoing != nil return RawDocumentationAttributeArgumentListSyntax(elements: arguments, arena: self.arena) diff --git a/Sources/SwiftParser/Availability.swift b/Sources/SwiftParser/Availability.swift index fc0979937a2..caa41583d10 100644 --- a/Sources/SwiftParser/Availability.swift +++ b/Sources/SwiftParser/Availability.swift @@ -47,6 +47,11 @@ extension Parser { arena: self.arena ) ) + + // If this was a trailing closure, there are no more elements + if self.at(.rightParen) { + break + } } while keepGoing != nil && self.hasProgressed(&availabilityArgumentProgress) } diff --git a/Sources/SwiftParser/Nominals.swift b/Sources/SwiftParser/Nominals.swift index bbd15cf5618..eebdcd6fa0c 100644 --- a/Sources/SwiftParser/Nominals.swift +++ b/Sources/SwiftParser/Nominals.swift @@ -361,6 +361,11 @@ extension Parser { arena: self.arena ) ) + + // If this was a trailing comma, there are no more elements + if at(prefix: ">") { + break + } } while keepGoing != nil && self.hasProgressed(&loopProgress) } let rangle = self.expectWithoutRecovery(prefix: ">", as: .rightAngle) diff --git a/Sources/SwiftParser/Types.swift b/Sources/SwiftParser/Types.swift index 67c7604ce0f..098215aab30 100644 --- a/Sources/SwiftParser/Types.swift +++ b/Sources/SwiftParser/Types.swift @@ -430,6 +430,11 @@ extension Parser { arena: self.arena ) ) + + // If this was a trailing comma, we're done parsing the list + if self.at(prefix: ">") { + break + } } while keepGoing != nil && self.hasProgressed(&loopProgress) } diff --git a/Tests/SwiftParserTest/AttributeTests.swift b/Tests/SwiftParserTest/AttributeTests.swift index 5d82301c61e..2159c1f0b63 100644 --- a/Tests/SwiftParserTest/AttributeTests.swift +++ b/Tests/SwiftParserTest/AttributeTests.swift @@ -470,6 +470,15 @@ final class AttributeTests: ParserTestCase { """ ) + assertParse( + """ + @backDeployed( + before: macOS 12.0, + ) + struct Foo {} + """ + ) + assertParse( """ @backDeployed(before: macos 12.0, iOS 15.0) @@ -477,6 +486,15 @@ final class AttributeTests: ParserTestCase { """ ) + assertParse( + """ + @backDeployed( + before: macos 12.0, + iOS 15.0,) + struct Foo {} + """ + ) + assertParse( """ @available(macOS 11.0, *) @@ -537,6 +555,16 @@ final class AttributeTests: ParserTestCase { """ ) + assertParse( + """ + @_originallyDefinedIn( + module: "ToasterKit", + macOS 10.15, + ) + struct Vehicle {} + """ + ) + assertParse( """ @_originallyDefinedIn(module: "ToasterKit", macOS 10.15, iOS 13) @@ -846,6 +874,26 @@ final class AttributeTests: ParserTestCase { } """ ) + + assertParse( + """ + @attached( + member, + names: named(deinit), + ) + macro m() + """ + ) + + assertParse( + """ + @attached( + extension, + conformances: P1, P2, + ) + macro AddAllConformances() + """ + ) } func testAttachedExtensionAttribute() { diff --git a/Tests/SwiftParserTest/DeclarationTests.swift b/Tests/SwiftParserTest/DeclarationTests.swift index 7ec097ec5bd..f55aa4c1a3d 100644 --- a/Tests/SwiftParserTest/DeclarationTests.swift +++ b/Tests/SwiftParserTest/DeclarationTests.swift @@ -3529,4 +3529,31 @@ final class DeclarationTests: ParserTestCase { ] ) } + + func testTrailingCommas() { + assertParse( + """ + protocol Baaz< + Foo, + Bar, + > { + associatedtype Foo + associatedtype Bar + } + """ + ) + + assertParse( + """ + struct Foo< + T1, + T2, + T3, + >: Baaz< + T1, + T2, + > {} + """ + ) + } } diff --git a/Tests/SwiftParserTest/TypeTests.swift b/Tests/SwiftParserTest/TypeTests.swift index 6e49d5fa75d..2d317551c14 100644 --- a/Tests/SwiftParserTest/TypeTests.swift +++ b/Tests/SwiftParserTest/TypeTests.swift @@ -734,6 +734,44 @@ final class TypeTests: ParserTestCase { fixedSource: "func foo(test: nonisolated(nonsendinghello) () async -> Void)" ) } + + func testTrailingCommas() { + assertParse( + """ + let foo: ( + bar: String, + quux: String, + ) + """ + ) + + assertParse( + """ + let closure: ( + String, + String, + ) -> ( + bar: String, + quux: String, + ) + """ + ) + + assertParse( + """ + struct Foo {} + + typealias Bar< + T1, + T2, + > = Foo< + T1, + T2, + Bool, + > + """ + ) + } } final class InlineArrayTypeTests: ParserTestCase { diff --git a/Tests/SwiftParserTest/translated/AvailabilityQueryTests.swift b/Tests/SwiftParserTest/translated/AvailabilityQueryTests.swift index 4425e329fd5..6646317adca 100644 --- a/Tests/SwiftParserTest/translated/AvailabilityQueryTests.swift +++ b/Tests/SwiftParserTest/translated/AvailabilityQueryTests.swift @@ -400,17 +400,7 @@ final class AvailabilityQueryTests: ParserTestCase { """ if #available(OSX 10.51,1️⃣) { } - """, - diagnostics: [ - DiagnosticSpec( - message: "expected version restriction in availability argument", - fixIts: ["insert version restriction"] - ) - ], - fixedSource: """ - if #available(OSX 10.51, <#identifier#>) { - } - """ + """ ) } diff --git a/Tests/SwiftParserTest/translated/AvailabilityQueryUnavailabilityTests.swift b/Tests/SwiftParserTest/translated/AvailabilityQueryUnavailabilityTests.swift index 24d1827bcc0..d99f63155f4 100644 --- a/Tests/SwiftParserTest/translated/AvailabilityQueryUnavailabilityTests.swift +++ b/Tests/SwiftParserTest/translated/AvailabilityQueryUnavailabilityTests.swift @@ -381,19 +381,9 @@ final class AvailabilityQueryUnavailabilityTests: ParserTestCase { func testAvailabilityQueryUnavailability23() { assertParse( """ - if #unavailable(OSX 10.51,1️⃣) { + if #unavailable(OSX 10.51,) { } - """, - diagnostics: [ - DiagnosticSpec( - message: "expected version restriction in availability argument", - fixIts: ["insert version restriction"] - ) - ], - fixedSource: """ - if #unavailable(OSX 10.51, <#identifier#>) { - } - """ + """ ) } @@ -611,4 +601,18 @@ final class AvailabilityQueryUnavailabilityTests: ParserTestCase { ] ) } + + func testTrailingComma() { + assertParse( + """ + func fooDeprecated() { + if #available( + iOS 18.0, + macOS 14.0, + *, + ) {} + } + """ + ) + } } From 793ccbdb3e3de408f0e9a46b0d00f268dae96095 Mon Sep 17 00:00:00 2001 From: Cal Stephens Date: Mon, 19 May 2025 15:52:54 -0700 Subject: [PATCH 06/33] Revert attribute changes --- Sources/SwiftParser/Attributes.swift | 17 +------ Sources/SwiftParser/Availability.swift | 5 -- Tests/SwiftParserTest/AttributeTests.swift | 48 ------------------- .../translated/AvailabilityQueryTests.swift | 12 ++++- ...AvailabilityQueryUnavailabilityTests.swift | 28 +++++------ 5 files changed, 24 insertions(+), 86 deletions(-) diff --git a/Sources/SwiftParser/Attributes.swift b/Sources/SwiftParser/Attributes.swift index 7df62ae0617..c29fa786846 100644 --- a/Sources/SwiftParser/Attributes.swift +++ b/Sources/SwiftParser/Attributes.swift @@ -362,7 +362,7 @@ extension Parser { let additionalArgs = self.parseArgumentListElements( pattern: .none, flavor: .attributeArguments, - allowTrailingComma: true + allowTrailingComma: false ) return [roleElement] + additionalArgs } @@ -852,11 +852,6 @@ extension Parser { arena: self.arena ) ) - - // If this was a trailing closure then there are no more elements - if self.at(.rightParen) { - break - } } while keepGoing != nil return RawBackDeployedAttributeArgumentsSyntax( unexpectedBeforeLabel, @@ -888,11 +883,6 @@ extension Parser { arena: self.arena ) ) - - // If this was a trailing closure then there are no more elements - if self.at(.rightParen) { - break - } } while keepGoing != nil return RawOriginallyDefinedInAttributeArgumentsSyntax( @@ -1011,11 +1001,6 @@ extension Parser { arena: self.arena ) ) - - // If this was a trailing closure then there are no more elements - if self.at(.rightParen) { - break - } } while keepGoing != nil return RawDocumentationAttributeArgumentListSyntax(elements: arguments, arena: self.arena) diff --git a/Sources/SwiftParser/Availability.swift b/Sources/SwiftParser/Availability.swift index caa41583d10..fc0979937a2 100644 --- a/Sources/SwiftParser/Availability.swift +++ b/Sources/SwiftParser/Availability.swift @@ -47,11 +47,6 @@ extension Parser { arena: self.arena ) ) - - // If this was a trailing closure, there are no more elements - if self.at(.rightParen) { - break - } } while keepGoing != nil && self.hasProgressed(&availabilityArgumentProgress) } diff --git a/Tests/SwiftParserTest/AttributeTests.swift b/Tests/SwiftParserTest/AttributeTests.swift index 2159c1f0b63..5d82301c61e 100644 --- a/Tests/SwiftParserTest/AttributeTests.swift +++ b/Tests/SwiftParserTest/AttributeTests.swift @@ -470,15 +470,6 @@ final class AttributeTests: ParserTestCase { """ ) - assertParse( - """ - @backDeployed( - before: macOS 12.0, - ) - struct Foo {} - """ - ) - assertParse( """ @backDeployed(before: macos 12.0, iOS 15.0) @@ -486,15 +477,6 @@ final class AttributeTests: ParserTestCase { """ ) - assertParse( - """ - @backDeployed( - before: macos 12.0, - iOS 15.0,) - struct Foo {} - """ - ) - assertParse( """ @available(macOS 11.0, *) @@ -555,16 +537,6 @@ final class AttributeTests: ParserTestCase { """ ) - assertParse( - """ - @_originallyDefinedIn( - module: "ToasterKit", - macOS 10.15, - ) - struct Vehicle {} - """ - ) - assertParse( """ @_originallyDefinedIn(module: "ToasterKit", macOS 10.15, iOS 13) @@ -874,26 +846,6 @@ final class AttributeTests: ParserTestCase { } """ ) - - assertParse( - """ - @attached( - member, - names: named(deinit), - ) - macro m() - """ - ) - - assertParse( - """ - @attached( - extension, - conformances: P1, P2, - ) - macro AddAllConformances() - """ - ) } func testAttachedExtensionAttribute() { diff --git a/Tests/SwiftParserTest/translated/AvailabilityQueryTests.swift b/Tests/SwiftParserTest/translated/AvailabilityQueryTests.swift index 6646317adca..4425e329fd5 100644 --- a/Tests/SwiftParserTest/translated/AvailabilityQueryTests.swift +++ b/Tests/SwiftParserTest/translated/AvailabilityQueryTests.swift @@ -400,7 +400,17 @@ final class AvailabilityQueryTests: ParserTestCase { """ if #available(OSX 10.51,1️⃣) { } - """ + """, + diagnostics: [ + DiagnosticSpec( + message: "expected version restriction in availability argument", + fixIts: ["insert version restriction"] + ) + ], + fixedSource: """ + if #available(OSX 10.51, <#identifier#>) { + } + """ ) } diff --git a/Tests/SwiftParserTest/translated/AvailabilityQueryUnavailabilityTests.swift b/Tests/SwiftParserTest/translated/AvailabilityQueryUnavailabilityTests.swift index d99f63155f4..24d1827bcc0 100644 --- a/Tests/SwiftParserTest/translated/AvailabilityQueryUnavailabilityTests.swift +++ b/Tests/SwiftParserTest/translated/AvailabilityQueryUnavailabilityTests.swift @@ -381,9 +381,19 @@ final class AvailabilityQueryUnavailabilityTests: ParserTestCase { func testAvailabilityQueryUnavailability23() { assertParse( """ - if #unavailable(OSX 10.51,) { + if #unavailable(OSX 10.51,1️⃣) { } - """ + """, + diagnostics: [ + DiagnosticSpec( + message: "expected version restriction in availability argument", + fixIts: ["insert version restriction"] + ) + ], + fixedSource: """ + if #unavailable(OSX 10.51, <#identifier#>) { + } + """ ) } @@ -601,18 +611,4 @@ final class AvailabilityQueryUnavailabilityTests: ParserTestCase { ] ) } - - func testTrailingComma() { - assertParse( - """ - func fooDeprecated() { - if #available( - iOS 18.0, - macOS 14.0, - *, - ) {} - } - """ - ) - } } From f7848539d72e852f67a1ae8bec269321b607b415 Mon Sep 17 00:00:00 2001 From: Cal Stephens Date: Mon, 19 May 2025 19:00:38 -0700 Subject: [PATCH 07/33] Support trailing commas in types within expressions --- Sources/SwiftParser/Types.swift | 3 ++- Tests/SwiftParserTest/ExpressionTests.swift | 20 ++++++++++++++++++++ 2 files changed, 22 insertions(+), 1 deletion(-) diff --git a/Sources/SwiftParser/Types.swift b/Sources/SwiftParser/Types.swift index 098215aab30..58eacfa59b9 100644 --- a/Sources/SwiftParser/Types.swift +++ b/Sources/SwiftParser/Types.swift @@ -1057,7 +1057,8 @@ extension Parser.Lookahead { return false } // Parse the comma, if the list continues. - } while self.consume(if: .comma) != nil && self.hasProgressed(&loopProgress) + // This could be the trailing comma. + } while self.consume(if: .comma) != nil && !self.at(prefix: ">") && self.hasProgressed(&loopProgress) } guard self.consume(ifPrefix: ">", as: .rightAngle) != nil else { diff --git a/Tests/SwiftParserTest/ExpressionTests.swift b/Tests/SwiftParserTest/ExpressionTests.swift index e63d753d2da..3eb7d83add2 100644 --- a/Tests/SwiftParserTest/ExpressionTests.swift +++ b/Tests/SwiftParserTest/ExpressionTests.swift @@ -2125,6 +2125,26 @@ final class ExpressionTests: ParserTestCase { """ ) } + + func testTrailingCommasInTypeExpressions() { + assertParse( + """ + let _ = Foo2.self + """ + ) + + assertParse( + """ + let _ = Foo2() + """ + ) + + assertParse( + """ + let _ = ((Int, Bool, String,) -> Void).self + """ + ) + } } final class MemberExprTests: ParserTestCase { From d754c2db557a2f2af4a33a8dd945b95a415cdea7 Mon Sep 17 00:00:00 2001 From: Arnold Schwaighofer Date: Tue, 20 May 2025 10:20:04 -0700 Subject: [PATCH 08/33] Add support for SE-0460 @specialized Add support to parse the non-underscored version of the @specialized attribute. rdar://150033316 --- .../SyntaxSupport/AttributeNodes.swift | 18 +++++ .../Sources/SyntaxSupport/KeywordSpec.swift | 3 + .../SyntaxSupport/SyntaxNodeKind.swift | 2 + Sources/SwiftParser/Attributes.swift | 12 +++ Sources/SwiftParser/TokenPrecedence.swift | 1 + .../SyntaxKindNameForDiagnostics.swift | 2 + .../generated/SwiftSyntax.md | 1 + .../generated/ChildNameForKeyPath.swift | 6 ++ Sources/SwiftSyntax/generated/Keyword.swift | 4 + .../generated/RenamedNodesCompatibility.swift | 8 ++ .../generated/SyntaxAnyVisitor.swift | 8 ++ .../generated/SyntaxBaseNodes.swift | 1 + .../SwiftSyntax/generated/SyntaxEnum.swift | 3 + .../SwiftSyntax/generated/SyntaxKind.swift | 3 + .../generated/SyntaxRewriter.swift | 16 ++++ .../SwiftSyntax/generated/SyntaxVisitor.swift | 24 ++++++ .../generated/raw/RawSyntaxNodesAB.swift | 7 +- .../generated/raw/RawSyntaxNodesQRS.swift | 58 +++++++++++++ .../generated/raw/RawSyntaxValidation.swift | 8 ++ .../generated/syntaxNodes/SyntaxNodesAB.swift | 34 +++++++- .../syntaxNodes/SyntaxNodesGHI.swift | 1 + .../syntaxNodes/SyntaxNodesQRS.swift | 81 +++++++++++++++++++ Tests/SwiftParserTest/AttributeTests.swift | 30 +++++++ Tests/SwiftParserTest/DeclarationTests.swift | 9 +++ 24 files changed, 338 insertions(+), 2 deletions(-) diff --git a/CodeGeneration/Sources/SyntaxSupport/AttributeNodes.swift b/CodeGeneration/Sources/SyntaxSupport/AttributeNodes.swift index 3347cf8bb41..d960ec5db26 100644 --- a/CodeGeneration/Sources/SyntaxSupport/AttributeNodes.swift +++ b/CodeGeneration/Sources/SyntaxSupport/AttributeNodes.swift @@ -74,6 +74,11 @@ public let ATTRIBUTE_NODES: [Node] = [ // Special arguments for keyword decl name e.g. 'subscript(_:)', and availability arguments. kind: .node(kind: .specializeAttributeArgumentList) ), + Child( + name: "specializedArguments", + // Special arguments for generic where clause. + kind: .node(kind: .specializedAttributeArgument) + ), Child( name: "objCName", // Special arguments for Objective-C names. e.g. 'methodNameWithArg1:Arg2:' @@ -717,6 +722,19 @@ public let ATTRIBUTE_NODES: [Node] = [ ] ), + Node( + kind: .specializedAttributeArgument, + base: .syntax, + nameForDiagnostics: "argument to '@specialized", + documentation: "The generic where clause for the `@specialized` attribute", + children: [ + Child( + name: "genericWhereClause", + kind: .node(kind: .genericWhereClause) + ) + ] + ), + Node( kind: .specializeTargetFunctionArgument, base: .syntax, diff --git a/CodeGeneration/Sources/SyntaxSupport/KeywordSpec.swift b/CodeGeneration/Sources/SyntaxSupport/KeywordSpec.swift index 1a32f708965..a0e6730e18d 100644 --- a/CodeGeneration/Sources/SyntaxSupport/KeywordSpec.swift +++ b/CodeGeneration/Sources/SyntaxSupport/KeywordSpec.swift @@ -104,6 +104,7 @@ public enum Keyword: CaseIterable { case _PackageDescription case _read case _RefCountedObject + case specialized case _specialize case _spi_available case _Trivial @@ -340,6 +341,8 @@ public enum Keyword: CaseIterable { return KeywordSpec("_read") case ._RefCountedObject: return KeywordSpec("_RefCountedObject") + case .specialized: + return KeywordSpec("specialized") case ._specialize: return KeywordSpec("_specialize") case ._spi_available: diff --git a/CodeGeneration/Sources/SyntaxSupport/SyntaxNodeKind.swift b/CodeGeneration/Sources/SyntaxSupport/SyntaxNodeKind.swift index 6e50fe39500..98cd248e22b 100644 --- a/CodeGeneration/Sources/SyntaxSupport/SyntaxNodeKind.swift +++ b/CodeGeneration/Sources/SyntaxSupport/SyntaxNodeKind.swift @@ -259,6 +259,7 @@ public enum SyntaxNodeKind: String, CaseIterable, IdentifierConvertible, TypeCon case simpleStringLiteralSegmentList case someOrAnyType case sourceFile + case specializedAttributeArgument case specializeAttributeArgumentList case specializeAvailabilityArgument case specializeTargetFunctionArgument @@ -454,6 +455,7 @@ public enum SyntaxNodeKind: String, CaseIterable, IdentifierConvertible, TypeCon case .someOrAnyType: return "constrainedSugarType" case .simpleTypeSpecifier: return "typeSpecifier" case .specializeAttributeArgumentList: return "specializeAttributeSpecList" + case .specializedAttributeArgument: return "specializedAttribute" case .specializeAvailabilityArgument: return "availabilityEntry" case .specializeTargetFunctionArgument: return "targetFunctionEntry" case .stringLiteralSegmentList: return "stringLiteralSegments" diff --git a/Sources/SwiftParser/Attributes.swift b/Sources/SwiftParser/Attributes.swift index c29fa786846..5946e76cdff 100644 --- a/Sources/SwiftParser/Attributes.swift +++ b/Sources/SwiftParser/Attributes.swift @@ -41,6 +41,7 @@ extension Parser { case _effects case _implements case _originallyDefinedIn + case specialized case _specialize case _spi_available case `rethrows` @@ -63,6 +64,7 @@ extension Parser { case TokenSpec(._effects): self = ._effects case TokenSpec(._implements): self = ._implements case TokenSpec(._originallyDefinedIn): self = ._originallyDefinedIn + case TokenSpec(.specialized): self = .specialized case TokenSpec(._specialize): self = ._specialize case TokenSpec(._spi_available): self = ._spi_available case TokenSpec(.`rethrows`): self = .rethrows @@ -89,6 +91,7 @@ extension Parser { case ._effects: return .keyword(._effects) case ._implements: return .keyword(._implements) case ._originallyDefinedIn: return .keyword(._originallyDefinedIn) + case .specialized: return .keyword(.specialized) case ._specialize: return .keyword(._specialize) case ._spi_available: return .keyword(._spi_available) case .`rethrows`: return .keyword(.rethrows) @@ -254,6 +257,10 @@ extension Parser { return parseAttribute(argumentMode: .optional) { parser in return (nil, .objCName(parser.parseObjectiveCSelector())) } + case .specialized: + return parseAttribute(argumentMode: .required) { parser in + return (nil, .specializedArguments(parser.parseSpecializedAttributeArgument())) + } case ._specialize: return parseAttribute(argumentMode: .required) { parser in return (nil, .specializeArguments(parser.parseSpecializeAttributeArgumentList())) @@ -645,6 +652,11 @@ extension Parser { } extension Parser { + mutating func parseSpecializedAttributeArgument() -> RawSpecializedAttributeArgumentSyntax { + let whereClause = self.parseGenericWhereClause() + return RawSpecializedAttributeArgumentSyntax(genericWhereClause: whereClause, arena: self.arena) + } + mutating func parseSpecializeAttributeArgumentList() -> RawSpecializeAttributeArgumentListSyntax { var elements = [RawSpecializeAttributeArgumentListSyntax.Element]() // Parse optional "exported" and "kind" labeled parameters. diff --git a/Sources/SwiftParser/TokenPrecedence.swift b/Sources/SwiftParser/TokenPrecedence.swift index 01fdbc06f78..c0a9a04bd08 100644 --- a/Sources/SwiftParser/TokenPrecedence.swift +++ b/Sources/SwiftParser/TokenPrecedence.swift @@ -267,6 +267,7 @@ enum TokenPrecedence: Comparable { ._effects, ._implements, ._originallyDefinedIn, + .specialized, ._specialize, ._spi_available, .abi, diff --git a/Sources/SwiftParserDiagnostics/generated/SyntaxKindNameForDiagnostics.swift b/Sources/SwiftParserDiagnostics/generated/SyntaxKindNameForDiagnostics.swift index 9a9d00393e7..6c4448e9dbd 100644 --- a/Sources/SwiftParserDiagnostics/generated/SyntaxKindNameForDiagnostics.swift +++ b/Sources/SwiftParserDiagnostics/generated/SyntaxKindNameForDiagnostics.swift @@ -362,6 +362,8 @@ extension SyntaxKind { return "availability entry" case .specializeTargetFunctionArgument: return "attribute argument" + case .specializedAttributeArgument: + return "argument to '@specialized" case .stringLiteralExpr: return "string literal" case .structDecl: diff --git a/Sources/SwiftSyntax/Documentation.docc/generated/SwiftSyntax.md b/Sources/SwiftSyntax/Documentation.docc/generated/SwiftSyntax.md index ee9216345d3..124109abadf 100644 --- a/Sources/SwiftSyntax/Documentation.docc/generated/SwiftSyntax.md +++ b/Sources/SwiftSyntax/Documentation.docc/generated/SwiftSyntax.md @@ -327,6 +327,7 @@ allows Swift tools to parse, inspect, generate, and transform Swift source code. - - - +- ### Miscellaneous Syntax diff --git a/Sources/SwiftSyntax/generated/ChildNameForKeyPath.swift b/Sources/SwiftSyntax/generated/ChildNameForKeyPath.swift index 802b6ae8003..079b4f842f3 100644 --- a/Sources/SwiftSyntax/generated/ChildNameForKeyPath.swift +++ b/Sources/SwiftSyntax/generated/ChildNameForKeyPath.swift @@ -2962,6 +2962,12 @@ public func childName(_ keyPath: AnyKeyPath) -> String? { return "trailingComma" case \SpecializeTargetFunctionArgumentSyntax.unexpectedAfterTrailingComma: return "unexpectedAfterTrailingComma" + case \SpecializedAttributeArgumentSyntax.unexpectedBeforeGenericWhereClause: + return "unexpectedBeforeGenericWhereClause" + case \SpecializedAttributeArgumentSyntax.genericWhereClause: + return "genericWhereClause" + case \SpecializedAttributeArgumentSyntax.unexpectedAfterGenericWhereClause: + return "unexpectedAfterGenericWhereClause" case \StringLiteralExprSyntax.unexpectedBeforeOpeningPounds: return "unexpectedBeforeOpeningPounds" case \StringLiteralExprSyntax.openingPounds: diff --git a/Sources/SwiftSyntax/generated/Keyword.swift b/Sources/SwiftSyntax/generated/Keyword.swift index 6f049eb7936..3a848bf62b7 100644 --- a/Sources/SwiftSyntax/generated/Keyword.swift +++ b/Sources/SwiftSyntax/generated/Keyword.swift @@ -46,6 +46,7 @@ public enum Keyword: UInt8, Hashable, Sendable { case _PackageDescription case _read case _RefCountedObject + case specialized case _specialize case _spi_available case _Trivial @@ -601,6 +602,8 @@ public enum Keyword: UInt8, Hashable, Sendable { self = ._implements case "_noMetadata": self = ._noMetadata + case "specialized": + self = .specialized case "_specialize": self = ._specialize case "autoclosure": @@ -789,6 +792,7 @@ public enum Keyword: UInt8, Hashable, Sendable { "_PackageDescription", "_read", "_RefCountedObject", + "specialized", "_specialize", "_spi_available", "_Trivial", diff --git a/Sources/SwiftSyntax/generated/RenamedNodesCompatibility.swift b/Sources/SwiftSyntax/generated/RenamedNodesCompatibility.swift index d9eed528b19..3aa308c9fc1 100644 --- a/Sources/SwiftSyntax/generated/RenamedNodesCompatibility.swift +++ b/Sources/SwiftSyntax/generated/RenamedNodesCompatibility.swift @@ -178,6 +178,9 @@ public typealias SpecializeAttributeSpecListSyntax = SpecializeAttributeArgument @available(*, deprecated, renamed: "GenericSpecializationExprSyntax") public typealias SpecializeExprSyntax = GenericSpecializationExprSyntax +@available(*, deprecated, renamed: "SpecializedAttributeArgumentSyntax") +public typealias SpecializedAttributeSyntax = SpecializedAttributeArgumentSyntax + @available(*, deprecated, renamed: "StringLiteralSegmentListSyntax") public typealias StringLiteralSegmentsSyntax = StringLiteralSegmentListSyntax @@ -493,6 +496,11 @@ extension SyntaxKind { return .genericSpecializationExpr } + @available(*, deprecated, renamed: "SpecializedAttributeArgumentSyntax") + public static var specializedAttribute: Self { + return .specializedAttributeArgument + } + @available(*, deprecated, renamed: "StringLiteralSegmentListSyntax") public static var stringLiteralSegments: Self { return .stringLiteralSegmentList diff --git a/Sources/SwiftSyntax/generated/SyntaxAnyVisitor.swift b/Sources/SwiftSyntax/generated/SyntaxAnyVisitor.swift index 761e69e41d9..b8c4128e2d9 100644 --- a/Sources/SwiftSyntax/generated/SyntaxAnyVisitor.swift +++ b/Sources/SwiftSyntax/generated/SyntaxAnyVisitor.swift @@ -1983,6 +1983,14 @@ open class SyntaxAnyVisitor: SyntaxVisitor { visitAnyPost(node._syntaxNode) } + override open func visit(_ node: SpecializedAttributeArgumentSyntax) -> SyntaxVisitorContinueKind { + return visitAny(node._syntaxNode) + } + + override open func visitPost(_ node: SpecializedAttributeArgumentSyntax) { + visitAnyPost(node._syntaxNode) + } + override open func visit(_ node: StringLiteralExprSyntax) -> SyntaxVisitorContinueKind { return visitAny(node._syntaxNode) } diff --git a/Sources/SwiftSyntax/generated/SyntaxBaseNodes.swift b/Sources/SwiftSyntax/generated/SyntaxBaseNodes.swift index 8c1a8444f88..cf9cf32bbb7 100644 --- a/Sources/SwiftSyntax/generated/SyntaxBaseNodes.swift +++ b/Sources/SwiftSyntax/generated/SyntaxBaseNodes.swift @@ -1749,6 +1749,7 @@ extension Syntax { .node(SpecializeAttributeArgumentListSyntax.self), .node(SpecializeAvailabilityArgumentSyntax.self), .node(SpecializeTargetFunctionArgumentSyntax.self), + .node(SpecializedAttributeArgumentSyntax.self), .node(StringLiteralExprSyntax.self), .node(StringLiteralSegmentListSyntax.self), .node(StringSegmentSyntax.self), diff --git a/Sources/SwiftSyntax/generated/SyntaxEnum.swift b/Sources/SwiftSyntax/generated/SyntaxEnum.swift index 5f70ddd32a8..629a783306d 100644 --- a/Sources/SwiftSyntax/generated/SyntaxEnum.swift +++ b/Sources/SwiftSyntax/generated/SyntaxEnum.swift @@ -265,6 +265,7 @@ public enum SyntaxEnum: Sendable { case specializeAttributeArgumentList(SpecializeAttributeArgumentListSyntax) case specializeAvailabilityArgument(SpecializeAvailabilityArgumentSyntax) case specializeTargetFunctionArgument(SpecializeTargetFunctionArgumentSyntax) + case specializedAttributeArgument(SpecializedAttributeArgumentSyntax) case stringLiteralExpr(StringLiteralExprSyntax) case stringLiteralSegmentList(StringLiteralSegmentListSyntax) case stringSegment(StringSegmentSyntax) @@ -800,6 +801,8 @@ extension Syntax { return .specializeAvailabilityArgument(SpecializeAvailabilityArgumentSyntax(self)!) case .specializeTargetFunctionArgument: return .specializeTargetFunctionArgument(SpecializeTargetFunctionArgumentSyntax(self)!) + case .specializedAttributeArgument: + return .specializedAttributeArgument(SpecializedAttributeArgumentSyntax(self)!) case .stringLiteralExpr: return .stringLiteralExpr(StringLiteralExprSyntax(self)!) case .stringLiteralSegmentList: diff --git a/Sources/SwiftSyntax/generated/SyntaxKind.swift b/Sources/SwiftSyntax/generated/SyntaxKind.swift index f1d00716562..7c24764a8c0 100644 --- a/Sources/SwiftSyntax/generated/SyntaxKind.swift +++ b/Sources/SwiftSyntax/generated/SyntaxKind.swift @@ -265,6 +265,7 @@ public enum SyntaxKind: Sendable { case specializeAttributeArgumentList case specializeAvailabilityArgument case specializeTargetFunctionArgument + case specializedAttributeArgument case stringLiteralExpr case stringLiteralSegmentList case stringSegment @@ -925,6 +926,8 @@ public enum SyntaxKind: Sendable { return SpecializeAvailabilityArgumentSyntax.self case .specializeTargetFunctionArgument: return SpecializeTargetFunctionArgumentSyntax.self + case .specializedAttributeArgument: + return SpecializedAttributeArgumentSyntax.self case .stringLiteralExpr: return StringLiteralExprSyntax.self case .stringLiteralSegmentList: diff --git a/Sources/SwiftSyntax/generated/SyntaxRewriter.swift b/Sources/SwiftSyntax/generated/SyntaxRewriter.swift index 06778189fb7..fce415516c9 100644 --- a/Sources/SwiftSyntax/generated/SyntaxRewriter.swift +++ b/Sources/SwiftSyntax/generated/SyntaxRewriter.swift @@ -1776,6 +1776,13 @@ open class SyntaxRewriter { return SpecializeTargetFunctionArgumentSyntax(unsafeCasting: visitChildren(node._syntaxNode)) } + /// Visit a ``SpecializedAttributeArgumentSyntax``. + /// - Parameter node: the node that is being visited + /// - Returns: the rewritten node + open func visit(_ node: SpecializedAttributeArgumentSyntax) -> SpecializedAttributeArgumentSyntax { + return SpecializedAttributeArgumentSyntax(unsafeCasting: visitChildren(node._syntaxNode)) + } + /// Visit a ``StringLiteralExprSyntax``. /// - Parameter node: the node that is being visited /// - Returns: the rewritten node @@ -3357,6 +3364,11 @@ open class SyntaxRewriter { Syntax(visit(SpecializeTargetFunctionArgumentSyntax(unsafeCasting: node))) } + @inline(never) + private func visitSpecializedAttributeArgumentSyntaxImpl(_ node: Syntax) -> Syntax { + Syntax(visit(SpecializedAttributeArgumentSyntax(unsafeCasting: node))) + } + @inline(never) private func visitStringLiteralExprSyntaxImpl(_ node: Syntax) -> Syntax { Syntax(visit(StringLiteralExprSyntax(unsafeCasting: node))) @@ -4111,6 +4123,8 @@ open class SyntaxRewriter { return self.visitSpecializeAvailabilityArgumentSyntaxImpl(_:) case .specializeTargetFunctionArgument: return self.visitSpecializeTargetFunctionArgumentSyntaxImpl(_:) + case .specializedAttributeArgument: + return self.visitSpecializedAttributeArgumentSyntaxImpl(_:) case .stringLiteralExpr: return self.visitStringLiteralExprSyntaxImpl(_:) case .stringLiteralSegmentList: @@ -4697,6 +4711,8 @@ open class SyntaxRewriter { return visitSpecializeAvailabilityArgumentSyntaxImpl(node) case .specializeTargetFunctionArgument: return visitSpecializeTargetFunctionArgumentSyntaxImpl(node) + case .specializedAttributeArgument: + return visitSpecializedAttributeArgumentSyntaxImpl(node) case .stringLiteralExpr: return visitStringLiteralExprSyntaxImpl(node) case .stringLiteralSegmentList: diff --git a/Sources/SwiftSyntax/generated/SyntaxVisitor.swift b/Sources/SwiftSyntax/generated/SyntaxVisitor.swift index d600655152f..d7a5c5b9b0f 100644 --- a/Sources/SwiftSyntax/generated/SyntaxVisitor.swift +++ b/Sources/SwiftSyntax/generated/SyntaxVisitor.swift @@ -2913,6 +2913,18 @@ open class SyntaxVisitor { open func visitPost(_ node: SpecializeTargetFunctionArgumentSyntax) { } + /// Visiting ``SpecializedAttributeArgumentSyntax`` specifically. + /// - Parameter node: the node we are visiting. + /// - Returns: how should we continue visiting. + open func visit(_ node: SpecializedAttributeArgumentSyntax) -> SyntaxVisitorContinueKind { + return .visitChildren + } + + /// The function called after visiting ``SpecializedAttributeArgumentSyntax`` and its descendants. + /// - node: the node we just finished visiting. + open func visitPost(_ node: SpecializedAttributeArgumentSyntax) { + } + /// Visiting ``StringLiteralExprSyntax`` specifically. /// - Parameter node: the node we are visiting. /// - Returns: how should we continue visiting. @@ -5438,6 +5450,14 @@ open class SyntaxVisitor { visitPost(SpecializeTargetFunctionArgumentSyntax(unsafeCasting: node)) } + @inline(never) + private func visitSpecializedAttributeArgumentSyntaxImpl(_ node: Syntax) { + if visit(SpecializedAttributeArgumentSyntax(unsafeCasting: node)) == .visitChildren { + visitChildren(node) + } + visitPost(SpecializedAttributeArgumentSyntax(unsafeCasting: node)) + } + @inline(never) private func visitStringLiteralExprSyntaxImpl(_ node: Syntax) { if visit(StringLiteralExprSyntax(unsafeCasting: node)) == .visitChildren { @@ -6342,6 +6362,8 @@ open class SyntaxVisitor { return self.visitSpecializeAvailabilityArgumentSyntaxImpl(_:) case .specializeTargetFunctionArgument: return self.visitSpecializeTargetFunctionArgumentSyntaxImpl(_:) + case .specializedAttributeArgument: + return self.visitSpecializedAttributeArgumentSyntaxImpl(_:) case .stringLiteralExpr: return self.visitStringLiteralExprSyntaxImpl(_:) case .stringLiteralSegmentList: @@ -6928,6 +6950,8 @@ open class SyntaxVisitor { self.visitSpecializeAvailabilityArgumentSyntaxImpl(node) case .specializeTargetFunctionArgument: self.visitSpecializeTargetFunctionArgumentSyntaxImpl(node) + case .specializedAttributeArgument: + self.visitSpecializedAttributeArgumentSyntaxImpl(node) case .stringLiteralExpr: self.visitStringLiteralExprSyntaxImpl(node) case .stringLiteralSegmentList: diff --git a/Sources/SwiftSyntax/generated/raw/RawSyntaxNodesAB.swift b/Sources/SwiftSyntax/generated/raw/RawSyntaxNodesAB.swift index 2c820290b8b..1dc419a7df4 100644 --- a/Sources/SwiftSyntax/generated/raw/RawSyntaxNodesAB.swift +++ b/Sources/SwiftSyntax/generated/raw/RawSyntaxNodesAB.swift @@ -1605,6 +1605,7 @@ public struct RawAttributeSyntax: RawSyntaxNodeProtocol { case argumentList(RawLabeledExprListSyntax) case availability(RawAvailabilityArgumentListSyntax) case specializeArguments(RawSpecializeAttributeArgumentListSyntax) + case specializedArguments(RawSpecializedAttributeArgumentSyntax) case objCName(RawObjCSelectorPieceListSyntax) case implementsArguments(RawImplementsAttributeArgumentsSyntax) case differentiableArguments(RawDifferentiableAttributeArgumentsSyntax) @@ -1617,7 +1618,7 @@ public struct RawAttributeSyntax: RawSyntaxNodeProtocol { case abiArguments(RawABIAttributeArgumentsSyntax) public static func isKindOf(_ raw: RawSyntax) -> Bool { - RawLabeledExprListSyntax.isKindOf(raw) || RawAvailabilityArgumentListSyntax.isKindOf(raw) || RawSpecializeAttributeArgumentListSyntax.isKindOf(raw) || RawObjCSelectorPieceListSyntax.isKindOf(raw) || RawImplementsAttributeArgumentsSyntax.isKindOf(raw) || RawDifferentiableAttributeArgumentsSyntax.isKindOf(raw) || RawDerivativeAttributeArgumentsSyntax.isKindOf(raw) || RawBackDeployedAttributeArgumentsSyntax.isKindOf(raw) || RawOriginallyDefinedInAttributeArgumentsSyntax.isKindOf(raw) || RawDynamicReplacementAttributeArgumentsSyntax.isKindOf(raw) || RawEffectsAttributeArgumentListSyntax.isKindOf(raw) || RawDocumentationAttributeArgumentListSyntax.isKindOf(raw) || RawABIAttributeArgumentsSyntax.isKindOf(raw) + RawLabeledExprListSyntax.isKindOf(raw) || RawAvailabilityArgumentListSyntax.isKindOf(raw) || RawSpecializeAttributeArgumentListSyntax.isKindOf(raw) || RawSpecializedAttributeArgumentSyntax.isKindOf(raw) || RawObjCSelectorPieceListSyntax.isKindOf(raw) || RawImplementsAttributeArgumentsSyntax.isKindOf(raw) || RawDifferentiableAttributeArgumentsSyntax.isKindOf(raw) || RawDerivativeAttributeArgumentsSyntax.isKindOf(raw) || RawBackDeployedAttributeArgumentsSyntax.isKindOf(raw) || RawOriginallyDefinedInAttributeArgumentsSyntax.isKindOf(raw) || RawDynamicReplacementAttributeArgumentsSyntax.isKindOf(raw) || RawEffectsAttributeArgumentListSyntax.isKindOf(raw) || RawDocumentationAttributeArgumentListSyntax.isKindOf(raw) || RawABIAttributeArgumentsSyntax.isKindOf(raw) } public var raw: RawSyntax { @@ -1628,6 +1629,8 @@ public struct RawAttributeSyntax: RawSyntaxNodeProtocol { return node.raw case .specializeArguments(let node): return node.raw + case .specializedArguments(let node): + return node.raw case .objCName(let node): return node.raw case .implementsArguments(let node): @@ -1658,6 +1661,8 @@ public struct RawAttributeSyntax: RawSyntaxNodeProtocol { self = .availability(node) } else if let node = node.as(RawSpecializeAttributeArgumentListSyntax.self) { self = .specializeArguments(node) + } else if let node = node.as(RawSpecializedAttributeArgumentSyntax.self) { + self = .specializedArguments(node) } else if let node = node.as(RawObjCSelectorPieceListSyntax.self) { self = .objCName(node) } else if let node = node.as(RawImplementsAttributeArgumentsSyntax.self) { diff --git a/Sources/SwiftSyntax/generated/raw/RawSyntaxNodesQRS.swift b/Sources/SwiftSyntax/generated/raw/RawSyntaxNodesQRS.swift index e4313ae32a7..3092d21e457 100644 --- a/Sources/SwiftSyntax/generated/raw/RawSyntaxNodesQRS.swift +++ b/Sources/SwiftSyntax/generated/raw/RawSyntaxNodesQRS.swift @@ -1190,6 +1190,64 @@ public struct RawSpecializeTargetFunctionArgumentSyntax: RawSyntaxNodeProtocol { } } +@_spi(RawSyntax) +public struct RawSpecializedAttributeArgumentSyntax: RawSyntaxNodeProtocol { + @_spi(RawSyntax) + public var layoutView: RawSyntaxLayoutView { + return raw.layoutView! + } + + public static func isKindOf(_ raw: RawSyntax) -> Bool { + return raw.kind == .specializedAttributeArgument + } + + 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( + _ unexpectedBeforeGenericWhereClause: RawUnexpectedNodesSyntax? = nil, + genericWhereClause: RawGenericWhereClauseSyntax, + _ unexpectedAfterGenericWhereClause: RawUnexpectedNodesSyntax? = nil, + arena: __shared RawSyntaxArena + ) { + let raw = RawSyntax.makeLayout( + kind: .specializedAttributeArgument, uninitializedCount: 3, arena: arena) { layout in + layout.initialize(repeating: nil) + layout[0] = unexpectedBeforeGenericWhereClause?.raw + layout[1] = genericWhereClause.raw + layout[2] = unexpectedAfterGenericWhereClause?.raw + } + self.init(unchecked: raw) + } + + public var unexpectedBeforeGenericWhereClause: RawUnexpectedNodesSyntax? { + layoutView.children[0].map(RawUnexpectedNodesSyntax.init(raw:)) + } + + public var genericWhereClause: RawGenericWhereClauseSyntax { + layoutView.children[1].map(RawGenericWhereClauseSyntax.init(raw:))! + } + + public var unexpectedAfterGenericWhereClause: RawUnexpectedNodesSyntax? { + layoutView.children[2].map(RawUnexpectedNodesSyntax.init(raw:)) + } +} + @_spi(RawSyntax) public struct RawStmtSyntax: RawStmtSyntaxNodeProtocol { @_spi(RawSyntax) diff --git a/Sources/SwiftSyntax/generated/raw/RawSyntaxValidation.swift b/Sources/SwiftSyntax/generated/raw/RawSyntaxValidation.swift index 0ca5359f6e7..182d2d0aeba 100644 --- a/Sources/SwiftSyntax/generated/raw/RawSyntaxValidation.swift +++ b/Sources/SwiftSyntax/generated/raw/RawSyntaxValidation.swift @@ -2638,6 +2638,12 @@ func validateLayout(layout: RawSyntaxBuffer, as kind: SyntaxKind) { assertNoError(kind, 7, verify(layout[7], as: RawTokenSyntax?.self, tokenChoices: [.tokenKind(.comma)])) assertNoError(kind, 8, verify(layout[8], as: RawUnexpectedNodesSyntax?.self)) } + func validateSpecializedAttributeArgumentSyntax(kind: SyntaxKind, layout: RawSyntaxBuffer) { + assert(layout.count == 3) + assertNoError(kind, 0, verify(layout[0], as: RawUnexpectedNodesSyntax?.self)) + assertNoError(kind, 1, verify(layout[1], as: RawGenericWhereClauseSyntax.self)) + assertNoError(kind, 2, verify(layout[2], as: RawUnexpectedNodesSyntax?.self)) + } func validateStringLiteralExprSyntax(kind: SyntaxKind, layout: RawSyntaxBuffer) { assert(layout.count == 11) assertNoError(kind, 0, verify(layout[0], as: RawUnexpectedNodesSyntax?.self)) @@ -3606,6 +3612,8 @@ func validateLayout(layout: RawSyntaxBuffer, as kind: SyntaxKind) { validateSpecializeAvailabilityArgumentSyntax(kind: kind, layout: layout) case .specializeTargetFunctionArgument: validateSpecializeTargetFunctionArgumentSyntax(kind: kind, layout: layout) + case .specializedAttributeArgument: + validateSpecializedAttributeArgumentSyntax(kind: kind, layout: layout) case .stringLiteralExpr: validateStringLiteralExprSyntax(kind: kind, layout: layout) case .stringLiteralSegmentList: diff --git a/Sources/SwiftSyntax/generated/syntaxNodes/SyntaxNodesAB.swift b/Sources/SwiftSyntax/generated/syntaxNodes/SyntaxNodesAB.swift index 6baae95f2e3..c36ad53e31d 100644 --- a/Sources/SwiftSyntax/generated/syntaxNodes/SyntaxNodesAB.swift +++ b/Sources/SwiftSyntax/generated/syntaxNodes/SyntaxNodesAB.swift @@ -3098,7 +3098,7 @@ public struct AttributeClauseFileSyntax: SyntaxProtocol, SyntaxHashable, _LeafSy /// - `atSign`: `@` /// - `attributeName`: ``TypeSyntax`` /// - `leftParen`: `(`? -/// - `arguments`: (``LabeledExprListSyntax`` | ``AvailabilityArgumentListSyntax`` | ``SpecializeAttributeArgumentListSyntax`` | ``ObjCSelectorPieceListSyntax`` | ``ImplementsAttributeArgumentsSyntax`` | ``DifferentiableAttributeArgumentsSyntax`` | ``DerivativeAttributeArgumentsSyntax`` | ``BackDeployedAttributeArgumentsSyntax`` | ``OriginallyDefinedInAttributeArgumentsSyntax`` | ``DynamicReplacementAttributeArgumentsSyntax`` | ``EffectsAttributeArgumentListSyntax`` | ``DocumentationAttributeArgumentListSyntax`` | ``ABIAttributeArgumentsSyntax``)? +/// - `arguments`: (``LabeledExprListSyntax`` | ``AvailabilityArgumentListSyntax`` | ``SpecializeAttributeArgumentListSyntax`` | ``SpecializedAttributeArgumentSyntax`` | ``ObjCSelectorPieceListSyntax`` | ``ImplementsAttributeArgumentsSyntax`` | ``DifferentiableAttributeArgumentsSyntax`` | ``DerivativeAttributeArgumentsSyntax`` | ``BackDeployedAttributeArgumentsSyntax`` | ``OriginallyDefinedInAttributeArgumentsSyntax`` | ``DynamicReplacementAttributeArgumentsSyntax`` | ``EffectsAttributeArgumentListSyntax`` | ``DocumentationAttributeArgumentListSyntax`` | ``ABIAttributeArgumentsSyntax``)? /// - `rightParen`: `)`? /// /// ### Contained in @@ -3110,6 +3110,7 @@ public struct AttributeSyntax: SyntaxProtocol, SyntaxHashable, _LeafSyntaxNodePr case argumentList(LabeledExprListSyntax) case availability(AvailabilityArgumentListSyntax) case specializeArguments(SpecializeAttributeArgumentListSyntax) + case specializedArguments(SpecializedAttributeArgumentSyntax) case objCName(ObjCSelectorPieceListSyntax) case implementsArguments(ImplementsAttributeArgumentsSyntax) case differentiableArguments(DifferentiableAttributeArgumentsSyntax) @@ -3129,6 +3130,8 @@ public struct AttributeSyntax: SyntaxProtocol, SyntaxHashable, _LeafSyntaxNodePr return node._syntaxNode case .specializeArguments(let node): return node._syntaxNode + case .specializedArguments(let node): + return node._syntaxNode case .objCName(let node): return node._syntaxNode case .implementsArguments(let node): @@ -3164,6 +3167,10 @@ public struct AttributeSyntax: SyntaxProtocol, SyntaxHashable, _LeafSyntaxNodePr self = .specializeArguments(node) } + public init(_ node: SpecializedAttributeArgumentSyntax) { + self = .specializedArguments(node) + } + public init(_ node: ObjCSelectorPieceListSyntax) { self = .objCName(node) } @@ -3211,6 +3218,8 @@ public struct AttributeSyntax: SyntaxProtocol, SyntaxHashable, _LeafSyntaxNodePr self = .availability(node) } else if let node = node.as(SpecializeAttributeArgumentListSyntax.self) { self = .specializeArguments(node) + } else if let node = node.as(SpecializedAttributeArgumentSyntax.self) { + self = .specializedArguments(node) } else if let node = node.as(ObjCSelectorPieceListSyntax.self) { self = .objCName(node) } else if let node = node.as(ImplementsAttributeArgumentsSyntax.self) { @@ -3241,6 +3250,7 @@ public struct AttributeSyntax: SyntaxProtocol, SyntaxHashable, _LeafSyntaxNodePr .node(LabeledExprListSyntax.self), .node(AvailabilityArgumentListSyntax.self), .node(SpecializeAttributeArgumentListSyntax.self), + .node(SpecializedAttributeArgumentSyntax.self), .node(ObjCSelectorPieceListSyntax.self), .node(ImplementsAttributeArgumentsSyntax.self), .node(DifferentiableAttributeArgumentsSyntax.self), @@ -3320,6 +3330,28 @@ public struct AttributeSyntax: SyntaxProtocol, SyntaxHashable, _LeafSyntaxNodePr return self.as(SpecializeAttributeArgumentListSyntax.self)! } + /// Checks if the current syntax node can be cast to ``SpecializedAttributeArgumentSyntax``. + /// + /// - Returns: `true` if the node can be cast, `false` otherwise. + public func `is`(_ syntaxType: SpecializedAttributeArgumentSyntax.Type) -> Bool { + return self.as(syntaxType) != nil + } + + /// Attempts to cast the current syntax node to ``SpecializedAttributeArgumentSyntax``. + /// + /// - Returns: An instance of ``SpecializedAttributeArgumentSyntax``, or `nil` if the cast fails. + public func `as`(_ syntaxType: SpecializedAttributeArgumentSyntax.Type) -> SpecializedAttributeArgumentSyntax? { + return SpecializedAttributeArgumentSyntax.init(self) + } + + /// Force-casts the current syntax node to ``SpecializedAttributeArgumentSyntax``. + /// + /// - Returns: An instance of ``SpecializedAttributeArgumentSyntax``. + /// - Warning: This function will crash if the cast is not possible. Use `as` to safely attempt a cast. + public func cast(_ syntaxType: SpecializedAttributeArgumentSyntax.Type) -> SpecializedAttributeArgumentSyntax { + return self.as(SpecializedAttributeArgumentSyntax.self)! + } + /// Checks if the current syntax node can be cast to ``ObjCSelectorPieceListSyntax``. /// /// - Returns: `true` if the node can be cast, `false` otherwise. diff --git a/Sources/SwiftSyntax/generated/syntaxNodes/SyntaxNodesGHI.swift b/Sources/SwiftSyntax/generated/syntaxNodes/SyntaxNodesGHI.swift index 6fd799b9e7f..b61d7b799ca 100644 --- a/Sources/SwiftSyntax/generated/syntaxNodes/SyntaxNodesGHI.swift +++ b/Sources/SwiftSyntax/generated/syntaxNodes/SyntaxNodesGHI.swift @@ -1261,6 +1261,7 @@ public struct GenericSpecializationExprSyntax: ExprSyntaxProtocol, SyntaxHashabl /// - ``MacroDeclSyntax``.``MacroDeclSyntax/genericWhereClause`` /// - ``ProtocolDeclSyntax``.``ProtocolDeclSyntax/genericWhereClause`` /// - ``SpecializeAttributeArgumentListSyntax`` +/// - ``SpecializedAttributeArgumentSyntax``.``SpecializedAttributeArgumentSyntax/genericWhereClause`` /// - ``StructDeclSyntax``.``StructDeclSyntax/genericWhereClause`` /// - ``SubscriptDeclSyntax``.``SubscriptDeclSyntax/genericWhereClause`` /// - ``TypeAliasDeclSyntax``.``TypeAliasDeclSyntax/genericWhereClause`` diff --git a/Sources/SwiftSyntax/generated/syntaxNodes/SyntaxNodesQRS.swift b/Sources/SwiftSyntax/generated/syntaxNodes/SyntaxNodesQRS.swift index 1a705af1c4d..e8af78d650f 100644 --- a/Sources/SwiftSyntax/generated/syntaxNodes/SyntaxNodesQRS.swift +++ b/Sources/SwiftSyntax/generated/syntaxNodes/SyntaxNodesQRS.swift @@ -2062,6 +2062,87 @@ public struct SpecializeTargetFunctionArgumentSyntax: SyntaxProtocol, SyntaxHash ]) } +// MARK: - SpecializedAttributeArgumentSyntax + +/// The generic where clause for the `@specialized` attribute +/// +/// ### Children +/// +/// - `genericWhereClause`: ``GenericWhereClauseSyntax`` +/// +/// ### Contained in +/// +/// - ``AttributeSyntax``.``AttributeSyntax/arguments`` +public struct SpecializedAttributeArgumentSyntax: SyntaxProtocol, SyntaxHashable, _LeafSyntaxNodeProtocol { + public let _syntaxNode: Syntax + + public init?(_ node: __shared some SyntaxProtocol) { + guard node.raw.kind == .specializedAttributeArgument 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, + _ unexpectedBeforeGenericWhereClause: UnexpectedNodesSyntax? = nil, + genericWhereClause: GenericWhereClauseSyntax, + _ unexpectedAfterGenericWhereClause: UnexpectedNodesSyntax? = nil, + 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(), (unexpectedBeforeGenericWhereClause, genericWhereClause, unexpectedAfterGenericWhereClause))) { (arena, _) in + let layout: [RawSyntax?] = [unexpectedBeforeGenericWhereClause?.raw, genericWhereClause.raw, unexpectedAfterGenericWhereClause?.raw] + let raw = RawSyntax.makeLayout( + kind: SyntaxKind.specializedAttributeArgument, + from: layout, + arena: arena, + leadingTrivia: leadingTrivia, + trailingTrivia: trailingTrivia + ) + return Syntax.forRoot(raw, rawNodeArena: arena).cast(Self.self) + } + } + + public var unexpectedBeforeGenericWhereClause: 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(SpecializedAttributeArgumentSyntax.self) + } + } + + public var genericWhereClause: GenericWhereClauseSyntax { + get { + return Syntax(self).child(at: 1)!.cast(GenericWhereClauseSyntax.self) + } + set(value) { + self = Syntax(self).replacingChild(at: 1, with: Syntax(value), rawAllocationArena: RawSyntaxArena()).cast(SpecializedAttributeArgumentSyntax.self) + } + } + + public var unexpectedAfterGenericWhereClause: UnexpectedNodesSyntax? { + get { + return Syntax(self).child(at: 2)?.cast(UnexpectedNodesSyntax.self) + } + set(value) { + self = Syntax(self).replacingChild(at: 2, with: Syntax(value), rawAllocationArena: RawSyntaxArena()).cast(SpecializedAttributeArgumentSyntax.self) + } + } + + public static let structure: SyntaxNodeStructure = .layout([\Self.unexpectedBeforeGenericWhereClause, \Self.genericWhereClause, \Self.unexpectedAfterGenericWhereClause]) +} + // MARK: - StringLiteralExprSyntax /// A string literal. diff --git a/Tests/SwiftParserTest/AttributeTests.swift b/Tests/SwiftParserTest/AttributeTests.swift index 5d82301c61e..695df2b4735 100644 --- a/Tests/SwiftParserTest/AttributeTests.swift +++ b/Tests/SwiftParserTest/AttributeTests.swift @@ -39,6 +39,30 @@ final class AttributeTests: ParserTestCase { } """ ) + + assertParse( + """ + @specializedℹ️(1️⃣ + func 2️⃣foo() { + } + """, + diagnostics: [ + DiagnosticSpec( + message: "expected argument for '@specialized' attribute", + fixIts: ["insert attribute argument"] + ), + DiagnosticSpec( + message: "expected ')' to end attribute", + notes: [NoteSpec(message: "to match this opening '('")], + fixIts: ["insert ')'"] + ), + ], + fixedSource: """ + @specialized(where <#type#> == <#type#>) + func foo() { + } + """ + ) } func testMissingGenericTypeToAttribute() { @@ -87,6 +111,12 @@ final class AttributeTests: ParserTestCase { func foo(_ t: T) {} """ ) + assertParse( + """ + @specialized(where T : Int) + func foo(_ t: T) {} + """ + ) } func testMissingClosingParenToAttribute() { diff --git a/Tests/SwiftParserTest/DeclarationTests.swift b/Tests/SwiftParserTest/DeclarationTests.swift index 7ec097ec5bd..77d621a8f64 100644 --- a/Tests/SwiftParserTest/DeclarationTests.swift +++ b/Tests/SwiftParserTest/DeclarationTests.swift @@ -770,6 +770,15 @@ final class DeclarationTests: ParserTestCase { } """ ) + assertParse( + """ + @specialized(where Array == Int) + @specialized(where T.Element == Int) + public func funcWithComplexSpecializeRequirements(t: T) -> Int { + return 55555 + } + """ + ) } func testParseRetroactiveExtension() { From 0efe93161ae29830b57fd82317bbf697ae99cf9f Mon Sep 17 00:00:00 2001 From: Ben Barham Date: Fri, 23 May 2025 17:27:57 -0700 Subject: [PATCH 09/33] Add a `SendableMetatype` conformance to `Macro` `Macro.Type` is used in various `Error`s and since SE-0470, needs to conform to `SendableMetatype`. --- Sources/SwiftSyntaxMacros/MacroProtocols/Macro.swift | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/Sources/SwiftSyntaxMacros/MacroProtocols/Macro.swift b/Sources/SwiftSyntaxMacros/MacroProtocols/Macro.swift index 6203a6f9127..3045b364475 100644 --- a/Sources/SwiftSyntaxMacros/MacroProtocols/Macro.swift +++ b/Sources/SwiftSyntaxMacros/MacroProtocols/Macro.swift @@ -10,9 +10,18 @@ // //===----------------------------------------------------------------------===// +#if compiler(>=6.2) +/// Describes a macro. +public protocol Macro: SendableMetatype { + /// How the resulting expansion should be formatted, `.auto` by default. + /// Use `.disabled` for the expansion to be used as is. + static var formatMode: FormatMode { get } +} +#else /// Describes a macro. public protocol Macro { /// How the resulting expansion should be formatted, `.auto` by default. /// Use `.disabled` for the expansion to be used as is. static var formatMode: FormatMode { get } } +#endif From 07fff59cf4f449d347bfbd9844910fe0b8803916 Mon Sep 17 00:00:00 2001 From: Hamish Knight Date: Fri, 30 May 2025 14:14:22 +0100 Subject: [PATCH 10/33] Change InlineArray sugar separator `x` -> `of` --- .../Sources/SyntaxSupport/KeywordSpec.swift | 3 -- .../Sources/SyntaxSupport/TypeNodes.swift | 6 +-- .../ValidateSyntaxNodes.swift | 5 ++- Sources/SwiftParser/TokenPrecedence.swift | 1 - Sources/SwiftParser/Types.swift | 14 +++---- Sources/SwiftSyntax/generated/Keyword.swift | 10 ----- .../generated/raw/RawSyntaxValidation.swift | 2 +- .../syntaxNodes/SyntaxNodesGHI.swift | 10 ++--- .../SwiftParserTest/ExpressionTypeTests.swift | 40 +++++++++---------- Tests/SwiftParserTest/TypeTests.swift | 32 +++++++-------- 10 files changed, 55 insertions(+), 68 deletions(-) diff --git a/CodeGeneration/Sources/SyntaxSupport/KeywordSpec.swift b/CodeGeneration/Sources/SyntaxSupport/KeywordSpec.swift index a0e6730e18d..eeb66ea4b56 100644 --- a/CodeGeneration/Sources/SyntaxSupport/KeywordSpec.swift +++ b/CodeGeneration/Sources/SyntaxSupport/KeywordSpec.swift @@ -276,7 +276,6 @@ public enum Keyword: CaseIterable { case `while` case willSet case wrt - case x case yield public var spec: KeywordSpec { @@ -685,8 +684,6 @@ public enum Keyword: CaseIterable { return KeywordSpec("willSet") case .wrt: return KeywordSpec("wrt") - case .x: - return KeywordSpec("x", experimentalFeature: .inlineArrayTypeSugar) case .yield: return KeywordSpec("yield") } diff --git a/CodeGeneration/Sources/SyntaxSupport/TypeNodes.swift b/CodeGeneration/Sources/SyntaxSupport/TypeNodes.swift index 3d65755fa45..ab81dde2a2d 100644 --- a/CodeGeneration/Sources/SyntaxSupport/TypeNodes.swift +++ b/CodeGeneration/Sources/SyntaxSupport/TypeNodes.swift @@ -304,7 +304,7 @@ public let TYPE_NODES: [Node] = [ base: .type, experimentalFeature: .inlineArrayTypeSugar, nameForDiagnostics: "inline array type", - documentation: "An inline array type `[3 x Int]`, sugar for `InlineArray<3, Int>`.", + documentation: "An inline array type `[3 of Int]`, sugar for `InlineArray<3, Int>`.", children: [ Child( name: "leftSquare", @@ -317,12 +317,12 @@ public let TYPE_NODES: [Node] = [ documentation: """ The `count` argument for the inline array type. - - Note: In semantically valid Swift code, this is always an integer or a wildcard type, e.g `_` in `[_ x Int]`. + - Note: In semantically valid Swift code, this is always an integer or a wildcard type, e.g `_` in `[_ of Int]`. """ ), Child( name: "separator", - kind: .token(choices: [.keyword(.x)]) + kind: .token(choices: [.keyword(.of)]) ), Child( name: "element", diff --git a/CodeGeneration/Tests/ValidateSyntaxNodes/ValidateSyntaxNodes.swift b/CodeGeneration/Tests/ValidateSyntaxNodes/ValidateSyntaxNodes.swift index d2eceb9334f..164b1505895 100644 --- a/CodeGeneration/Tests/ValidateSyntaxNodes/ValidateSyntaxNodes.swift +++ b/CodeGeneration/Tests/ValidateSyntaxNodes/ValidateSyntaxNodes.swift @@ -372,10 +372,11 @@ class ValidateSyntaxNodes: XCTestCase { message: "child 'defaultKeyword' has a single keyword as its only token choice and is followed by a colon. It should thus be named 'defaultLabel'" ), - // 'separator' is more descriptive than 'xKeyword' + // 'separator' is more descriptive than 'ofKeyword' ValidationFailure( node: .inlineArrayType, - message: "child 'separator' has a single keyword as its only token choice and should thus be named 'xKeyword'" + message: + "child 'separator' has a single keyword as its only token choice and should thus be named 'ofKeyword'" ), ] ) diff --git a/Sources/SwiftParser/TokenPrecedence.swift b/Sources/SwiftParser/TokenPrecedence.swift index c0a9a04bd08..2879548d56c 100644 --- a/Sources/SwiftParser/TokenPrecedence.swift +++ b/Sources/SwiftParser/TokenPrecedence.swift @@ -345,7 +345,6 @@ enum TokenPrecedence: Comparable { .visibility, .weak, .wrt, - .x, .unsafe: self = .exprKeyword #if RESILIENT_LIBRARIES diff --git a/Sources/SwiftParser/Types.swift b/Sources/SwiftParser/Types.swift index 58eacfa59b9..68a6428ef49 100644 --- a/Sources/SwiftParser/Types.swift +++ b/Sources/SwiftParser/Types.swift @@ -649,11 +649,11 @@ extension Parser { precondition(self.experimentalFeatures.contains(.inlineArrayTypeSugar)) // We allow both values and types here and for the element type for - // better recovery in cases where the user writes e.g '[Int x 3]'. + // better recovery in cases where the user writes e.g '[Int of 3]'. let count = self.parseGenericArgumentType() let (unexpectedBeforeSeparator, separator) = self.expect( - TokenSpec(.x, allowAtStartOfLine: false) + TokenSpec(.of, allowAtStartOfLine: false) ) let element = self.parseGenericArgumentType() @@ -879,18 +879,18 @@ extension Parser.Lookahead { return false } - // We must have at least '[ x', which cannot be any other + // We must have at least '[ of', which cannot be any other // kind of expression or type. We specifically look for both types and // integers for better recovery in e.g cases where the user writes e.g - // '[Int x 2]'. We only do type-scalar since variadics would be ambiguous - // e.g 'Int...x'. + // '[Int of 2]'. We only do type-scalar since variadics would be ambiguous + // e.g 'Int...of'. guard self.canParseTypeScalar() || self.canParseIntegerLiteral() else { return false } // We don't currently allow multi-line since that would require // disambiguation with array literals. - return self.consume(if: TokenSpec(.x, allowAtStartOfLine: false)) != nil + return self.consume(if: TokenSpec(.of, allowAtStartOfLine: false)) != nil } mutating func canParseInlineArrayTypeBody() -> Bool { @@ -898,7 +898,7 @@ extension Parser.Lookahead { return false } // Note we look for both types and integers for better recovery in e.g cases - // where the user writes e.g '[Int x 2]'. + // where the user writes e.g '[Int of 2]'. guard self.canParseGenericArgument() else { return false } diff --git a/Sources/SwiftSyntax/generated/Keyword.swift b/Sources/SwiftSyntax/generated/Keyword.swift index 3a848bf62b7..aedba46b9f1 100644 --- a/Sources/SwiftSyntax/generated/Keyword.swift +++ b/Sources/SwiftSyntax/generated/Keyword.swift @@ -222,19 +222,10 @@ public enum Keyword: UInt8, Hashable, Sendable { case `while` case willSet case wrt - @_spi(ExperimentalLanguageFeatures) - case x case yield @_spi(RawSyntax) public init?(_ text: SyntaxText) { switch text.count { - case 1: - switch text { - case "x": - self = .x - default: - return nil - } case 2: switch text { case "as": @@ -964,7 +955,6 @@ public enum Keyword: UInt8, Hashable, Sendable { "while", "willSet", "wrt", - "x", "yield", ] diff --git a/Sources/SwiftSyntax/generated/raw/RawSyntaxValidation.swift b/Sources/SwiftSyntax/generated/raw/RawSyntaxValidation.swift index 182d2d0aeba..4eb22f87384 100644 --- a/Sources/SwiftSyntax/generated/raw/RawSyntaxValidation.swift +++ b/Sources/SwiftSyntax/generated/raw/RawSyntaxValidation.swift @@ -1748,7 +1748,7 @@ func validateLayout(layout: RawSyntaxBuffer, as kind: SyntaxKind) { assertNoError(kind, 2, verify(layout[2], as: RawUnexpectedNodesSyntax?.self)) assertNoError(kind, 3, verify(layout[3], as: RawGenericArgumentSyntax.self)) assertNoError(kind, 4, verify(layout[4], as: RawUnexpectedNodesSyntax?.self)) - assertNoError(kind, 5, verify(layout[5], as: RawTokenSyntax.self, tokenChoices: [.keyword("x")])) + assertNoError(kind, 5, verify(layout[5], as: RawTokenSyntax.self, tokenChoices: [.keyword("of")])) assertNoError(kind, 6, verify(layout[6], as: RawUnexpectedNodesSyntax?.self)) assertNoError(kind, 7, verify(layout[7], as: RawGenericArgumentSyntax.self)) assertNoError(kind, 8, verify(layout[8], as: RawUnexpectedNodesSyntax?.self)) diff --git a/Sources/SwiftSyntax/generated/syntaxNodes/SyntaxNodesGHI.swift b/Sources/SwiftSyntax/generated/syntaxNodes/SyntaxNodesGHI.swift index b61d7b799ca..b6eec06d4b0 100644 --- a/Sources/SwiftSyntax/generated/syntaxNodes/SyntaxNodesGHI.swift +++ b/Sources/SwiftSyntax/generated/syntaxNodes/SyntaxNodesGHI.swift @@ -4373,7 +4373,7 @@ public struct InitializerDeclSyntax: DeclSyntaxProtocol, SyntaxHashable, _LeafDe // MARK: - InlineArrayTypeSyntax -/// An inline array type `[3 x Int]`, sugar for `InlineArray<3, Int>`. +/// An inline array type `[3 of Int]`, sugar for `InlineArray<3, Int>`. /// /// - Note: Requires experimental feature `inlineArrayTypeSugar`. /// @@ -4381,7 +4381,7 @@ public struct InitializerDeclSyntax: DeclSyntaxProtocol, SyntaxHashable, _LeafDe /// /// - `leftSquare`: `[` /// - `count`: ``GenericArgumentSyntax`` -/// - `separator`: `x` +/// - `separator`: `of` /// - `element`: ``GenericArgumentSyntax`` /// - `rightSquare`: `]` @_spi(ExperimentalLanguageFeatures) @@ -4412,7 +4412,7 @@ public struct InlineArrayTypeSyntax: TypeSyntaxProtocol, SyntaxHashable, _LeafTy _ unexpectedBetweenLeftSquareAndCount: UnexpectedNodesSyntax? = nil, count: GenericArgumentSyntax, _ unexpectedBetweenCountAndSeparator: UnexpectedNodesSyntax? = nil, - separator: TokenSyntax = .keyword(.x), + separator: TokenSyntax = .keyword(.of), _ unexpectedBetweenSeparatorAndElement: UnexpectedNodesSyntax? = nil, element: GenericArgumentSyntax, _ unexpectedBetweenElementAndRightSquare: UnexpectedNodesSyntax? = nil, @@ -4491,7 +4491,7 @@ public struct InlineArrayTypeSyntax: TypeSyntaxProtocol, SyntaxHashable, _LeafTy /// The `count` argument for the inline array type. /// - /// - Note: In semantically valid Swift code, this is always an integer or a wildcard type, e.g `_` in `[_ x Int]`. + /// - Note: In semantically valid Swift code, this is always an integer or a wildcard type, e.g `_` in `[_ of Int]`. public var count: GenericArgumentSyntax { get { return Syntax(self).child(at: 3)!.cast(GenericArgumentSyntax.self) @@ -4512,7 +4512,7 @@ public struct InlineArrayTypeSyntax: TypeSyntaxProtocol, SyntaxHashable, _LeafTy /// ### Tokens /// - /// For syntax trees generated by the parser, this is guaranteed to be `x`. + /// For syntax trees generated by the parser, this is guaranteed to be `of`. public var separator: TokenSyntax { get { return Syntax(self).child(at: 5)!.cast(TokenSyntax.self) diff --git a/Tests/SwiftParserTest/ExpressionTypeTests.swift b/Tests/SwiftParserTest/ExpressionTypeTests.swift index 5def6ec1241..f9f6587dbe4 100644 --- a/Tests/SwiftParserTest/ExpressionTypeTests.swift +++ b/Tests/SwiftParserTest/ExpressionTypeTests.swift @@ -111,26 +111,26 @@ final class ExpressionTypeTests: ParserTestCase { // Make sure we can handle cases where the type is spelled first in // an InlineArray sugar type. let cases: [UInt: String] = [ - #line: "[3 x Int]", - #line: "[[3 x Int]]", - #line: "[[Int x 3]]", - #line: "[_ x Int]", - #line: "[Int x Int]", - #line: "[@escaping () -> Int x Int]", - #line: "[Int.Type x Int]", - #line: "[sending P & Q x Int]", - #line: "[(some P & Q) -> Int x Int]", - #line: "[~P x Int]", - #line: "[(Int, String) x Int]", - #line: "[G x Int]", - #line: "[[3 x Int] x Int]", - #line: "[[Int] x Int]", - #line: "[_ x Int]", - #line: "[_? x Int]", - #line: "[_?x Int]", - #line: "[_! x Int]", - #line: "[_!x Int]", - #line: "[Int?x Int]", + #line: "[3 of Int]", + #line: "[[3 of Int]]", + #line: "[[Int of 3]]", + #line: "[_ of Int]", + #line: "[Int of Int]", + #line: "[@escaping () -> Int of Int]", + #line: "[Int.Type of Int]", + #line: "[sending P & Q of Int]", + #line: "[(some P & Q) -> Int of Int]", + #line: "[~P of Int]", + #line: "[(Int, String) of Int]", + #line: "[G of Int]", + #line: "[[3 of Int] of Int]", + #line: "[[Int] of Int]", + #line: "[_ of Int]", + #line: "[_? of Int]", + #line: "[_?of Int]", + #line: "[_! of Int]", + #line: "[_!of Int]", + #line: "[Int?of Int]", ] for (line, type) in cases { assertParse( diff --git a/Tests/SwiftParserTest/TypeTests.swift b/Tests/SwiftParserTest/TypeTests.swift index 2d317551c14..4ab3316b6b7 100644 --- a/Tests/SwiftParserTest/TypeTests.swift +++ b/Tests/SwiftParserTest/TypeTests.swift @@ -781,18 +781,18 @@ final class InlineArrayTypeTests: ParserTestCase { func testBasic() { assertParse( - "[3 x Int]", + "[3 of Int]", substructure: InlineArrayTypeSyntax( count: .init(argument: .expr("3")), - separator: .keyword(.x), + separator: .keyword(.of), element: .init(argument: .type(TypeSyntax("Int"))) ) ) assertParse( - "[Int x _]", + "[Int of _]", substructure: InlineArrayTypeSyntax( count: .init(argument: .type(TypeSyntax("Int"))), - separator: .keyword(.x), + separator: .keyword(.of), element: .init(argument: .type(TypeSyntax("_"))) ) ) @@ -804,7 +804,7 @@ final class InlineArrayTypeTests: ParserTestCase { """ S<[ 3 - 1️⃣x + 1️⃣of Int ]>() """, @@ -815,7 +815,7 @@ final class InlineArrayTypeTests: ParserTestCase { assertParse( """ S<[3 - 1️⃣x + 1️⃣of Int ]>() """, @@ -826,23 +826,23 @@ final class InlineArrayTypeTests: ParserTestCase { assertParse( """ S<[3 - 1️⃣x Int]>() + 1️⃣of Int]>() """, diagnostics: [ - DiagnosticSpec(message: "unexpected code 'x Int' in array") + DiagnosticSpec(message: "unexpected code 'of Int' in array") ] ) // These are okay. assertParse( """ - S<[3 x + S<[3 of Int]>() """ ) assertParse( """ S<[ - 3 x Int + 3 of Int ]>() """ ) @@ -850,17 +850,17 @@ final class InlineArrayTypeTests: ParserTestCase { func testDiagnostics() { assertParse( - "2️⃣[3 x1️⃣", + "2️⃣[3 of1️⃣", diagnostics: [ DiagnosticSpec( message: "expected element type and ']' to end inline array type", fixIts: ["insert element type and ']'"] ) ], - fixedSource: "[3 x <#type#>]" + fixedSource: "[3 of <#type#>]" ) assertParse( - "ℹ️[3 x Int1️⃣", + "ℹ️[3 of Int1️⃣", diagnostics: [ DiagnosticSpec( message: "expected ']' to end inline array type", @@ -868,12 +868,12 @@ final class InlineArrayTypeTests: ParserTestCase { fixIts: ["insert ']'"] ) ], - fixedSource: "[3 x Int]" + fixedSource: "[3 of Int]" ) } func testEllipsis() { - // Make sure this isn't parsed as ' x ' - assertParse("[x...x]") + // Make sure this isn't parsed as ' of ' + assertParse("[x...of]") } } From 95bebf62c5498f49eead0bfaf64ddd020b9013f6 Mon Sep 17 00:00:00 2001 From: Pavel Yaskevich Date: Fri, 30 May 2025 00:39:36 -0700 Subject: [PATCH 11/33] [SwiftParser] SE-0478: Implement `using` declaration under an experimental feature This is an alternative spelling of `typealias DefaultIsolation = (MainActor | nonisolated)` that was proposed as part of SE-0478. `using` declaration accepts an attribute or a modifier and currently could be used to set a default actor isolation per file, but could be extended to support other use-cases in the future. Implementation uses `DefaultIsolationPerFile` experimental feature flag to hide the syntax. --- .../Sources/SyntaxSupport/DeclNodes.swift | 31 +++ .../SyntaxSupport/ExperimentalFeatures.swift | 5 + .../Sources/SyntaxSupport/KeywordSpec.swift | 3 + .../SyntaxSupport/SyntaxNodeKind.swift | 1 + Sources/SwiftParser/Declarations.swift | 73 ++++++ Sources/SwiftParser/TokenPrecedence.swift | 2 +- Sources/SwiftParser/TokenSpecSet.swift | 3 + .../generated/ExperimentalFeatures.swift | 5 + .../SyntaxKindNameForDiagnostics.swift | 2 + .../generated/ChildNameForKeyPath.swift | 10 + Sources/SwiftSyntax/generated/Keyword.swift | 4 + .../generated/SyntaxAnyVisitor.swift | 10 + .../generated/SyntaxBaseNodes.swift | 4 +- .../SwiftSyntax/generated/SyntaxEnum.swift | 8 + .../SwiftSyntax/generated/SyntaxKind.swift | 4 + .../generated/SyntaxRewriter.swift | 17 ++ .../SwiftSyntax/generated/SyntaxVisitor.swift | 26 +++ .../generated/raw/RawSyntaxNodesD.swift | 2 +- .../generated/raw/RawSyntaxNodesTUVWXYZ.swift | 102 +++++++++ .../generated/raw/RawSyntaxValidation.swift | 11 + .../syntaxNodes/SyntaxNodesTUVWXYZ.swift | 216 ++++++++++++++++++ Tests/SwiftParserTest/DeclarationTests.swift | 133 ++++++++++- 22 files changed, 668 insertions(+), 4 deletions(-) diff --git a/CodeGeneration/Sources/SyntaxSupport/DeclNodes.swift b/CodeGeneration/Sources/SyntaxSupport/DeclNodes.swift index 0dea2d63426..25ecbf84785 100644 --- a/CodeGeneration/Sources/SyntaxSupport/DeclNodes.swift +++ b/CodeGeneration/Sources/SyntaxSupport/DeclNodes.swift @@ -1307,6 +1307,37 @@ public let DECL_NODES: [Node] = [ ] ), + Node( + kind: .usingDecl, + base: .decl, + experimentalFeature: .defaultIsolationPerFile, + nameForDiagnostics: "using", + documentation: """ + A `using` declaration, currently used to control actor isolation within the current file. + + An example of a `using` declaration is + + ```swift + using @MainActor + ``` + """, + children: [ + Child( + name: "usingKeyword", + kind: .token(choices: [.keyword(.using)]), + documentation: "The `using` keyword for this declaration." + ), + Child( + name: "specifier", + kind: .nodeChoices(choices: [ + Child(name: "attribute", kind: .node(kind: .attribute)), + Child(name: "modifier", kind: .token(choices: [.token(.identifier)])), + ]), + documentation: "The specifier that could be either an attribute or a modifier." + ), + ] + ), + Node( kind: .inheritedTypeList, base: .syntaxCollection, diff --git a/CodeGeneration/Sources/SyntaxSupport/ExperimentalFeatures.swift b/CodeGeneration/Sources/SyntaxSupport/ExperimentalFeatures.swift index aa36cd8ba53..70cd36c9a7d 100644 --- a/CodeGeneration/Sources/SyntaxSupport/ExperimentalFeatures.swift +++ b/CodeGeneration/Sources/SyntaxSupport/ExperimentalFeatures.swift @@ -22,6 +22,7 @@ public enum ExperimentalFeature: String, CaseIterable { case keypathWithMethodMembers case oldOwnershipOperatorSpellings case inlineArrayTypeSugar + case defaultIsolationPerFile /// The name of the feature as it is written in the compiler's `Features.def` file. public var featureName: String { @@ -44,6 +45,8 @@ public enum ExperimentalFeature: String, CaseIterable { return "OldOwnershipOperatorSpellings" case .inlineArrayTypeSugar: return "InlineArrayTypeSugar" + case .defaultIsolationPerFile: + return "DefaultIsolationPerFile" } } @@ -68,6 +71,8 @@ public enum ExperimentalFeature: String, CaseIterable { return "`_move` and `_borrow` as ownership operators" case .inlineArrayTypeSugar: return "sugar type for InlineArray" + case .defaultIsolationPerFile: + return "set default actor isolation for a file" } } diff --git a/CodeGeneration/Sources/SyntaxSupport/KeywordSpec.swift b/CodeGeneration/Sources/SyntaxSupport/KeywordSpec.swift index a0e6730e18d..acb5442c252 100644 --- a/CodeGeneration/Sources/SyntaxSupport/KeywordSpec.swift +++ b/CodeGeneration/Sources/SyntaxSupport/KeywordSpec.swift @@ -269,6 +269,7 @@ public enum Keyword: CaseIterable { case unsafe case unsafeAddress case unsafeMutableAddress + case using case `var` case visibility case weak @@ -671,6 +672,8 @@ public enum Keyword: CaseIterable { return KeywordSpec("unsafeAddress") case .unsafeMutableAddress: return KeywordSpec("unsafeMutableAddress") + case .using: + return KeywordSpec("using") case .var: return KeywordSpec("var", isLexerClassified: true) case .visibility: diff --git a/CodeGeneration/Sources/SyntaxSupport/SyntaxNodeKind.swift b/CodeGeneration/Sources/SyntaxSupport/SyntaxNodeKind.swift index 98cd248e22b..011058348cd 100644 --- a/CodeGeneration/Sources/SyntaxSupport/SyntaxNodeKind.swift +++ b/CodeGeneration/Sources/SyntaxSupport/SyntaxNodeKind.swift @@ -305,6 +305,7 @@ public enum SyntaxNodeKind: String, CaseIterable, IdentifierConvertible, TypeCon case unresolvedIsExpr case unresolvedTernaryExpr case unsafeExpr + case usingDecl case valueBindingPattern case variableDecl case versionComponent diff --git a/Sources/SwiftParser/Declarations.swift b/Sources/SwiftParser/Declarations.swift index a9e4a01f449..6ffffd81438 100644 --- a/Sources/SwiftParser/Declarations.swift +++ b/Sources/SwiftParser/Declarations.swift @@ -140,6 +140,29 @@ extension TokenConsumer { // Otherwise, parse it as an expression. return false + case .lhs(.using): + // This declaration doesn't support attributes or modifiers + if hasAttribute || hasModifier { + return false + } + + var lookahead = subparser.lookahead() + + // Consume 'using' + lookahead.consumeAnyToken() + + // Allow parsing 'using' as declaration only if + // it's immediately followed by either `@` or + // an identifier. + if lookahead.atStartOfLine { + return false + } + + guard lookahead.at(.atSign) || lookahead.at(.identifier) else { + return false + } + + return true case .some(_): // All other decl start keywords unconditionally start a decl. return true @@ -338,6 +361,8 @@ extension Parser { return RawDeclSyntax(self.parseMacroDeclaration(attrs: attrs, introducerHandle: handle)) case (.lhs(.pound), let handle)?: return RawDeclSyntax(self.parseMacroExpansionDeclaration(attrs, handle)) + case (.lhs(.using), let handle)?: + return RawDeclSyntax(self.parseUsingDeclaration(attrs: attrs, introducerHandle: handle)) case (.rhs, let handle)?: return RawDeclSyntax(self.parseBindingDeclaration(attrs, handle, in: context)) case nil: @@ -439,6 +464,54 @@ extension Parser { } } +extension Parser { + mutating func parseUsingDeclaration( + attrs: DeclAttributes, + introducerHandle handle: RecoveryConsumptionHandle + ) -> RawUsingDeclSyntax { + let unexpectedAttributes: RawUnexpectedNodesSyntax? = + if !attrs.attributes.isEmpty { + RawUnexpectedNodesSyntax(attrs.attributes.elements, arena: self.arena) + } else { + nil + } + + let unexpectedModifiers: RawUnexpectedNodesSyntax? = + if !attrs.modifiers.isEmpty { + RawUnexpectedNodesSyntax(attrs.modifiers.elements, arena: self.arena) + } else { + nil + } + + let (unexpectedBeforeKeyword, usingKeyword) = self.eat(handle) + + let unexpectedBeforeUsingKeyword = RawUnexpectedNodesSyntax( + combining: unexpectedAttributes, + unexpectedModifiers, + unexpectedBeforeKeyword, + arena: self.arena + ) + + if self.at(.atSign), case .attribute(let attribute) = self.parseAttribute() { + return RawUsingDeclSyntax( + unexpectedBeforeUsingKeyword, + usingKeyword: usingKeyword, + specifier: .attribute(attribute), + arena: self.arena + ) + } + + let modifier = self.expectWithoutRecovery(.identifier) + + return RawUsingDeclSyntax( + unexpectedBeforeUsingKeyword, + usingKeyword: usingKeyword, + specifier: .modifier(modifier), + arena: self.arena + ) + } +} + extension Parser { /// Parse an extension declaration. mutating func parseExtensionDeclaration( diff --git a/Sources/SwiftParser/TokenPrecedence.swift b/Sources/SwiftParser/TokenPrecedence.swift index c0a9a04bd08..5e3f1becbe6 100644 --- a/Sources/SwiftParser/TokenPrecedence.swift +++ b/Sources/SwiftParser/TokenPrecedence.swift @@ -241,7 +241,7 @@ enum TokenPrecedence: Comparable { .get, .set, .didSet, .willSet, .unsafeAddress, .addressWithOwner, .addressWithNativeOwner, .unsafeMutableAddress, .mutableAddressWithOwner, .mutableAddressWithNativeOwner, ._read, .read, ._modify, .modify, // Misc - .import: + .import, .using: self = .declKeyword case // `TypeAttribute` diff --git a/Sources/SwiftParser/TokenSpecSet.swift b/Sources/SwiftParser/TokenSpecSet.swift index 03294198301..b80c3794821 100644 --- a/Sources/SwiftParser/TokenSpecSet.swift +++ b/Sources/SwiftParser/TokenSpecSet.swift @@ -290,6 +290,7 @@ enum PureDeclarationKeyword: TokenSpecSet { case `subscript` case `typealias` case pound + case using init?(lexeme: Lexer.Lexeme, experimentalFeatures: Parser.ExperimentalFeatures) { switch PrepareForKeywordMatch(lexeme) { @@ -311,6 +312,7 @@ enum PureDeclarationKeyword: TokenSpecSet { case TokenSpec(.subscript): self = .subscript case TokenSpec(.typealias): self = .typealias case TokenSpec(.pound): self = .pound + case TokenSpec(.using) where experimentalFeatures.contains(.defaultIsolationPerFile): self = .using default: return nil } } @@ -335,6 +337,7 @@ enum PureDeclarationKeyword: TokenSpecSet { case .subscript: return .keyword(.subscript) case .typealias: return .keyword(.typealias) case .pound: return TokenSpec(.pound, recoveryPrecedence: .openingPoundIf) + case .using: return TokenSpec(.using) } } } diff --git a/Sources/SwiftParser/generated/ExperimentalFeatures.swift b/Sources/SwiftParser/generated/ExperimentalFeatures.swift index 9513b06aac7..c8c8c373856 100644 --- a/Sources/SwiftParser/generated/ExperimentalFeatures.swift +++ b/Sources/SwiftParser/generated/ExperimentalFeatures.swift @@ -52,6 +52,9 @@ extension Parser.ExperimentalFeatures { /// Whether to enable the parsing of sugar type for InlineArray. public static let inlineArrayTypeSugar = Self (rawValue: 1 << 8) + /// Whether to enable the parsing of set default actor isolation for a file. + public static let defaultIsolationPerFile = Self (rawValue: 1 << 9) + /// Creates a new value representing the experimental feature with the /// given name, or returns nil if the name is not recognized. public init?(name: String) { @@ -74,6 +77,8 @@ extension Parser.ExperimentalFeatures { self = .oldOwnershipOperatorSpellings case "InlineArrayTypeSugar": self = .inlineArrayTypeSugar + case "DefaultIsolationPerFile": + self = .defaultIsolationPerFile default: return nil } diff --git a/Sources/SwiftParserDiagnostics/generated/SyntaxKindNameForDiagnostics.swift b/Sources/SwiftParserDiagnostics/generated/SyntaxKindNameForDiagnostics.swift index 6c4448e9dbd..52e2023df3e 100644 --- a/Sources/SwiftParserDiagnostics/generated/SyntaxKindNameForDiagnostics.swift +++ b/Sources/SwiftParserDiagnostics/generated/SyntaxKindNameForDiagnostics.swift @@ -408,6 +408,8 @@ extension SyntaxKind { return "ternary operator" case .unsafeExpr: return "'unsafe' expression" + case .usingDecl: + return "using" case .valueBindingPattern: return "value binding pattern" case .variableDecl: diff --git a/Sources/SwiftSyntax/generated/ChildNameForKeyPath.swift b/Sources/SwiftSyntax/generated/ChildNameForKeyPath.swift index 079b4f842f3..d64489aa01b 100644 --- a/Sources/SwiftSyntax/generated/ChildNameForKeyPath.swift +++ b/Sources/SwiftSyntax/generated/ChildNameForKeyPath.swift @@ -3450,6 +3450,16 @@ public func childName(_ keyPath: AnyKeyPath) -> String? { return "expression" case \UnsafeExprSyntax.unexpectedAfterExpression: return "unexpectedAfterExpression" + case \UsingDeclSyntax.unexpectedBeforeUsingKeyword: + return "unexpectedBeforeUsingKeyword" + case \UsingDeclSyntax.usingKeyword: + return "usingKeyword" + case \UsingDeclSyntax.unexpectedBetweenUsingKeywordAndSpecifier: + return "unexpectedBetweenUsingKeywordAndSpecifier" + case \UsingDeclSyntax.specifier: + return "specifier" + case \UsingDeclSyntax.unexpectedAfterSpecifier: + return "unexpectedAfterSpecifier" case \ValueBindingPatternSyntax.unexpectedBeforeBindingSpecifier: return "unexpectedBeforeBindingSpecifier" case \ValueBindingPatternSyntax.bindingSpecifier: diff --git a/Sources/SwiftSyntax/generated/Keyword.swift b/Sources/SwiftSyntax/generated/Keyword.swift index 3a848bf62b7..3a4d16de4b9 100644 --- a/Sources/SwiftSyntax/generated/Keyword.swift +++ b/Sources/SwiftSyntax/generated/Keyword.swift @@ -215,6 +215,7 @@ public enum Keyword: UInt8, Hashable, Sendable { case unsafe case unsafeAddress case unsafeMutableAddress + case using case `var` case visibility case weak @@ -376,6 +377,8 @@ public enum Keyword: UInt8, Hashable, Sendable { self = .swift case "throw": self = .throw + case "using": + self = .using case "where": self = .where case "while": @@ -957,6 +960,7 @@ public enum Keyword: UInt8, Hashable, Sendable { "unsafe", "unsafeAddress", "unsafeMutableAddress", + "using", "var", "visibility", "weak", diff --git a/Sources/SwiftSyntax/generated/SyntaxAnyVisitor.swift b/Sources/SwiftSyntax/generated/SyntaxAnyVisitor.swift index b8c4128e2d9..134ae3fcaf8 100644 --- a/Sources/SwiftSyntax/generated/SyntaxAnyVisitor.swift +++ b/Sources/SwiftSyntax/generated/SyntaxAnyVisitor.swift @@ -2297,6 +2297,16 @@ open class SyntaxAnyVisitor: SyntaxVisitor { visitAnyPost(node._syntaxNode) } + @_spi(ExperimentalLanguageFeatures) + override open func visit(_ node: UsingDeclSyntax) -> SyntaxVisitorContinueKind { + return visitAny(node._syntaxNode) + } + + @_spi(ExperimentalLanguageFeatures) + override open func visitPost(_ node: UsingDeclSyntax) { + visitAnyPost(node._syntaxNode) + } + override open func visit(_ node: ValueBindingPatternSyntax) -> SyntaxVisitorContinueKind { return visitAny(node._syntaxNode) } diff --git a/Sources/SwiftSyntax/generated/SyntaxBaseNodes.swift b/Sources/SwiftSyntax/generated/SyntaxBaseNodes.swift index cf9cf32bbb7..3268316597d 100644 --- a/Sources/SwiftSyntax/generated/SyntaxBaseNodes.swift +++ b/Sources/SwiftSyntax/generated/SyntaxBaseNodes.swift @@ -214,7 +214,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, .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, .usingDecl, .variableDecl: self._syntaxNode = node._syntaxNode default: return nil @@ -262,6 +262,7 @@ public struct DeclSyntax: DeclSyntaxProtocol, SyntaxHashable { .node(StructDeclSyntax.self), .node(SubscriptDeclSyntax.self), .node(TypeAliasDeclSyntax.self), + .node(UsingDeclSyntax.self), .node(VariableDeclSyntax.self) ]) } @@ -1788,6 +1789,7 @@ extension Syntax { .node(UnresolvedIsExprSyntax.self), .node(UnresolvedTernaryExprSyntax.self), .node(UnsafeExprSyntax.self), + .node(UsingDeclSyntax.self), .node(ValueBindingPatternSyntax.self), .node(VariableDeclSyntax.self), .node(VersionComponentListSyntax.self), diff --git a/Sources/SwiftSyntax/generated/SyntaxEnum.swift b/Sources/SwiftSyntax/generated/SyntaxEnum.swift index 629a783306d..b94aae22e71 100644 --- a/Sources/SwiftSyntax/generated/SyntaxEnum.swift +++ b/Sources/SwiftSyntax/generated/SyntaxEnum.swift @@ -305,6 +305,8 @@ public enum SyntaxEnum: Sendable { case unresolvedIsExpr(UnresolvedIsExprSyntax) case unresolvedTernaryExpr(UnresolvedTernaryExprSyntax) case unsafeExpr(UnsafeExprSyntax) + @_spi(ExperimentalLanguageFeatures) + case usingDecl(UsingDeclSyntax) case valueBindingPattern(ValueBindingPatternSyntax) case variableDecl(VariableDeclSyntax) case versionComponentList(VersionComponentListSyntax) @@ -879,6 +881,8 @@ extension Syntax { return .unresolvedTernaryExpr(UnresolvedTernaryExprSyntax(self)!) case .unsafeExpr: return .unsafeExpr(UnsafeExprSyntax(self)!) + case .usingDecl: + return .usingDecl(UsingDeclSyntax(self)!) case .valueBindingPattern: return .valueBindingPattern(ValueBindingPatternSyntax(self)!) case .variableDecl: @@ -932,6 +936,8 @@ public enum DeclSyntaxEnum { case structDecl(StructDeclSyntax) case subscriptDecl(SubscriptDeclSyntax) case typeAliasDecl(TypeAliasDeclSyntax) + @_spi(ExperimentalLanguageFeatures) + case usingDecl(UsingDeclSyntax) case variableDecl(VariableDeclSyntax) } @@ -985,6 +991,8 @@ extension DeclSyntax { return .subscriptDecl(SubscriptDeclSyntax(self)!) case .typeAliasDecl: return .typeAliasDecl(TypeAliasDeclSyntax(self)!) + case .usingDecl: + return .usingDecl(UsingDeclSyntax(self)!) case .variableDecl: return .variableDecl(VariableDeclSyntax(self)!) default: diff --git a/Sources/SwiftSyntax/generated/SyntaxKind.swift b/Sources/SwiftSyntax/generated/SyntaxKind.swift index 7c24764a8c0..dcfaf4e18b0 100644 --- a/Sources/SwiftSyntax/generated/SyntaxKind.swift +++ b/Sources/SwiftSyntax/generated/SyntaxKind.swift @@ -305,6 +305,8 @@ public enum SyntaxKind: Sendable { case unresolvedIsExpr case unresolvedTernaryExpr case unsafeExpr + @_spi(ExperimentalLanguageFeatures) + case usingDecl case valueBindingPattern case variableDecl case versionComponentList @@ -1004,6 +1006,8 @@ public enum SyntaxKind: Sendable { return UnresolvedTernaryExprSyntax.self case .unsafeExpr: return UnsafeExprSyntax.self + case .usingDecl: + return UsingDeclSyntax.self case .valueBindingPattern: return ValueBindingPatternSyntax.self case .variableDecl: diff --git a/Sources/SwiftSyntax/generated/SyntaxRewriter.swift b/Sources/SwiftSyntax/generated/SyntaxRewriter.swift index fce415516c9..c82f5b54f20 100644 --- a/Sources/SwiftSyntax/generated/SyntaxRewriter.swift +++ b/Sources/SwiftSyntax/generated/SyntaxRewriter.swift @@ -2050,6 +2050,14 @@ open class SyntaxRewriter { return ExprSyntax(UnsafeExprSyntax(unsafeCasting: visitChildren(node._syntaxNode))) } + /// Visit a `UsingDeclSyntax`. + /// - Parameter node: the node that is being visited + /// - Returns: the rewritten node + @_spi(ExperimentalLanguageFeatures) + open func visit(_ node: UsingDeclSyntax) -> DeclSyntax { + return DeclSyntax(UsingDeclSyntax(unsafeCasting: visitChildren(node._syntaxNode))) + } + /// Visit a ``ValueBindingPatternSyntax``. /// - Parameter node: the node that is being visited /// - Returns: the rewritten node @@ -3559,6 +3567,11 @@ open class SyntaxRewriter { Syntax(visit(UnsafeExprSyntax(unsafeCasting: node))) } + @inline(never) + private func visitUsingDeclSyntaxImpl(_ node: Syntax) -> Syntax { + Syntax(visit(UsingDeclSyntax(unsafeCasting: node))) + } + @inline(never) private func visitValueBindingPatternSyntaxImpl(_ node: Syntax) -> Syntax { Syntax(visit(ValueBindingPatternSyntax(unsafeCasting: node))) @@ -4201,6 +4214,8 @@ open class SyntaxRewriter { return self.visitUnresolvedTernaryExprSyntaxImpl(_:) case .unsafeExpr: return self.visitUnsafeExprSyntaxImpl(_:) + case .usingDecl: + return self.visitUsingDeclSyntaxImpl(_:) case .valueBindingPattern: return self.visitValueBindingPatternSyntaxImpl(_:) case .variableDecl: @@ -4789,6 +4804,8 @@ open class SyntaxRewriter { return visitUnresolvedTernaryExprSyntaxImpl(node) case .unsafeExpr: return visitUnsafeExprSyntaxImpl(node) + case .usingDecl: + return visitUsingDeclSyntaxImpl(node) case .valueBindingPattern: return visitValueBindingPatternSyntaxImpl(node) case .variableDecl: diff --git a/Sources/SwiftSyntax/generated/SyntaxVisitor.swift b/Sources/SwiftSyntax/generated/SyntaxVisitor.swift index d7a5c5b9b0f..914b87f2692 100644 --- a/Sources/SwiftSyntax/generated/SyntaxVisitor.swift +++ b/Sources/SwiftSyntax/generated/SyntaxVisitor.swift @@ -3383,6 +3383,20 @@ open class SyntaxVisitor { open func visitPost(_ node: UnsafeExprSyntax) { } + /// Visiting `UsingDeclSyntax` specifically. + /// - Parameter node: the node we are visiting. + /// - Returns: how should we continue visiting. + @_spi(ExperimentalLanguageFeatures) + open func visit(_ node: UsingDeclSyntax) -> SyntaxVisitorContinueKind { + return .visitChildren + } + + /// The function called after visiting `UsingDeclSyntax` and its descendants. + /// - node: the node we just finished visiting. + @_spi(ExperimentalLanguageFeatures) + open func visitPost(_ node: UsingDeclSyntax) { + } + /// Visiting ``ValueBindingPatternSyntax`` specifically. /// - Parameter node: the node we are visiting. /// - Returns: how should we continue visiting. @@ -5762,6 +5776,14 @@ open class SyntaxVisitor { visitPost(UnsafeExprSyntax(unsafeCasting: node)) } + @inline(never) + private func visitUsingDeclSyntaxImpl(_ node: Syntax) { + if visit(UsingDeclSyntax(unsafeCasting: node)) == .visitChildren { + visitChildren(node) + } + visitPost(UsingDeclSyntax(unsafeCasting: node)) + } + @inline(never) private func visitValueBindingPatternSyntaxImpl(_ node: Syntax) { if visit(ValueBindingPatternSyntax(unsafeCasting: node)) == .visitChildren { @@ -6440,6 +6462,8 @@ open class SyntaxVisitor { return self.visitUnresolvedTernaryExprSyntaxImpl(_:) case .unsafeExpr: return self.visitUnsafeExprSyntaxImpl(_:) + case .usingDecl: + return self.visitUsingDeclSyntaxImpl(_:) case .valueBindingPattern: return self.visitValueBindingPatternSyntaxImpl(_:) case .variableDecl: @@ -7028,6 +7052,8 @@ open class SyntaxVisitor { self.visitUnresolvedTernaryExprSyntaxImpl(node) case .unsafeExpr: self.visitUnsafeExprSyntaxImpl(node) + case .usingDecl: + self.visitUsingDeclSyntaxImpl(node) case .valueBindingPattern: self.visitValueBindingPatternSyntaxImpl(node) case .variableDecl: diff --git a/Sources/SwiftSyntax/generated/raw/RawSyntaxNodesD.swift b/Sources/SwiftSyntax/generated/raw/RawSyntaxNodesD.swift index d9f5b337aea..79480c99c6b 100644 --- a/Sources/SwiftSyntax/generated/raw/RawSyntaxNodesD.swift +++ b/Sources/SwiftSyntax/generated/raw/RawSyntaxNodesD.swift @@ -499,7 +499,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, .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, .usingDecl, .variableDecl: return true default: return false diff --git a/Sources/SwiftSyntax/generated/raw/RawSyntaxNodesTUVWXYZ.swift b/Sources/SwiftSyntax/generated/raw/RawSyntaxNodesTUVWXYZ.swift index e4b5a2b69a0..704219505b9 100644 --- a/Sources/SwiftSyntax/generated/raw/RawSyntaxNodesTUVWXYZ.swift +++ b/Sources/SwiftSyntax/generated/raw/RawSyntaxNodesTUVWXYZ.swift @@ -1863,6 +1863,108 @@ public struct RawUnsafeExprSyntax: RawExprSyntaxNodeProtocol { } } +@_spi(ExperimentalLanguageFeatures) +@_spi(RawSyntax) +public struct RawUsingDeclSyntax: RawDeclSyntaxNodeProtocol { + public enum Specifier: RawSyntaxNodeProtocol { + case attribute(RawAttributeSyntax) + /// ### Tokens + /// + /// For syntax trees generated by the parser, this is guaranteed to be ``. + case modifier(RawTokenSyntax) + + public static func isKindOf(_ raw: RawSyntax) -> Bool { + RawAttributeSyntax.isKindOf(raw) || RawTokenSyntax.isKindOf(raw) + } + + public var raw: RawSyntax { + switch self { + case .attribute(let node): + return node.raw + case .modifier(let node): + return node.raw + } + } + + public init?(_ node: __shared some RawSyntaxNodeProtocol) { + if let node = node.as(RawAttributeSyntax.self) { + self = .attribute(node) + } else if let node = node.as(RawTokenSyntax.self) { + self = .modifier(node) + } else { + return nil + } + } + } + + @_spi(RawSyntax) + public var layoutView: RawSyntaxLayoutView { + return raw.layoutView! + } + + public static func isKindOf(_ raw: RawSyntax) -> Bool { + return raw.kind == .usingDecl + } + + 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( + _ unexpectedBeforeUsingKeyword: RawUnexpectedNodesSyntax? = nil, + usingKeyword: RawTokenSyntax, + _ unexpectedBetweenUsingKeywordAndSpecifier: RawUnexpectedNodesSyntax? = nil, + specifier: Specifier, + _ unexpectedAfterSpecifier: RawUnexpectedNodesSyntax? = nil, + arena: __shared RawSyntaxArena + ) { + let raw = RawSyntax.makeLayout( + kind: .usingDecl, uninitializedCount: 5, arena: arena) { layout in + layout.initialize(repeating: nil) + layout[0] = unexpectedBeforeUsingKeyword?.raw + layout[1] = usingKeyword.raw + layout[2] = unexpectedBetweenUsingKeywordAndSpecifier?.raw + layout[3] = specifier.raw + layout[4] = unexpectedAfterSpecifier?.raw + } + self.init(unchecked: raw) + } + + public var unexpectedBeforeUsingKeyword: RawUnexpectedNodesSyntax? { + layoutView.children[0].map(RawUnexpectedNodesSyntax.init(raw:)) + } + + public var usingKeyword: RawTokenSyntax { + layoutView.children[1].map(RawTokenSyntax.init(raw:))! + } + + public var unexpectedBetweenUsingKeywordAndSpecifier: RawUnexpectedNodesSyntax? { + layoutView.children[2].map(RawUnexpectedNodesSyntax.init(raw:)) + } + + public var specifier: RawSyntax { + layoutView.children[3]! + } + + public var unexpectedAfterSpecifier: RawUnexpectedNodesSyntax? { + layoutView.children[4].map(RawUnexpectedNodesSyntax.init(raw:)) + } +} + @_spi(RawSyntax) public struct RawValueBindingPatternSyntax: RawPatternSyntaxNodeProtocol { @_spi(RawSyntax) diff --git a/Sources/SwiftSyntax/generated/raw/RawSyntaxValidation.swift b/Sources/SwiftSyntax/generated/raw/RawSyntaxValidation.swift index 182d2d0aeba..0a72751ad92 100644 --- a/Sources/SwiftSyntax/generated/raw/RawSyntaxValidation.swift +++ b/Sources/SwiftSyntax/generated/raw/RawSyntaxValidation.swift @@ -3021,6 +3021,15 @@ func validateLayout(layout: RawSyntaxBuffer, as kind: SyntaxKind) { assertNoError(kind, 3, verify(layout[3], as: RawExprSyntax.self)) assertNoError(kind, 4, verify(layout[4], as: RawUnexpectedNodesSyntax?.self)) } + func validateUsingDeclSyntax(kind: SyntaxKind, layout: RawSyntaxBuffer) { + assert(layout.count == 5) + assertNoError(kind, 0, verify(layout[0], as: RawUnexpectedNodesSyntax?.self)) + assertNoError(kind, 1, verify(layout[1], as: RawTokenSyntax.self, tokenChoices: [.keyword("using")])) + assertNoError(kind, 2, verify(layout[2], as: RawUnexpectedNodesSyntax?.self)) + assertAnyHasNoError(kind, 3, [ + verify(layout[3], as: RawSyntax.self)]) + assertNoError(kind, 4, verify(layout[4], as: RawUnexpectedNodesSyntax?.self)) + } func validateValueBindingPatternSyntax(kind: SyntaxKind, layout: RawSyntaxBuffer) { assert(layout.count == 5) assertNoError(kind, 0, verify(layout[0], as: RawUnexpectedNodesSyntax?.self)) @@ -3690,6 +3699,8 @@ func validateLayout(layout: RawSyntaxBuffer, as kind: SyntaxKind) { validateUnresolvedTernaryExprSyntax(kind: kind, layout: layout) case .unsafeExpr: validateUnsafeExprSyntax(kind: kind, layout: layout) + case .usingDecl: + validateUsingDeclSyntax(kind: kind, layout: layout) case .valueBindingPattern: validateValueBindingPatternSyntax(kind: kind, layout: layout) case .variableDecl: diff --git a/Sources/SwiftSyntax/generated/syntaxNodes/SyntaxNodesTUVWXYZ.swift b/Sources/SwiftSyntax/generated/syntaxNodes/SyntaxNodesTUVWXYZ.swift index cf0c4b33f19..bf5973151ba 100644 --- a/Sources/SwiftSyntax/generated/syntaxNodes/SyntaxNodesTUVWXYZ.swift +++ b/Sources/SwiftSyntax/generated/syntaxNodes/SyntaxNodesTUVWXYZ.swift @@ -3086,6 +3086,222 @@ public struct UnsafeExprSyntax: ExprSyntaxProtocol, SyntaxHashable, _LeafExprSyn ]) } +// MARK: - UsingDeclSyntax + +/// A `using` declaration, currently used to control actor isolation within the current file. +/// +/// An example of a `using` declaration is +/// +/// ```swift +/// using @MainActor +/// ``` +/// +/// - Note: Requires experimental feature `defaultIsolationPerFile`. +/// +/// ### Children +/// +/// - `usingKeyword`: `using` +/// - `specifier`: (``AttributeSyntax`` | ``) +@_spi(ExperimentalLanguageFeatures) +public struct UsingDeclSyntax: DeclSyntaxProtocol, SyntaxHashable, _LeafDeclSyntaxNodeProtocol { + public enum Specifier: SyntaxChildChoices, SyntaxHashable { + case attribute(AttributeSyntax) + /// ### Tokens + /// + /// For syntax trees generated by the parser, this is guaranteed to be ``. + case modifier(TokenSyntax) + + public var _syntaxNode: Syntax { + switch self { + case .attribute(let node): + return node._syntaxNode + case .modifier(let node): + return node._syntaxNode + } + } + + public init(_ node: AttributeSyntax) { + self = .attribute(node) + } + + public init(_ node: TokenSyntax) { + self = .modifier(node) + } + + public init?(_ node: __shared some SyntaxProtocol) { + if let node = node.as(AttributeSyntax.self) { + self = .attribute(node) + } else if let node = node.as(TokenSyntax.self) { + self = .modifier(node) + } else { + return nil + } + } + + public static var structure: SyntaxNodeStructure { + return .choices([.node(AttributeSyntax.self), .node(TokenSyntax.self)]) + } + + /// Checks if the current syntax node can be cast to ``AttributeSyntax``. + /// + /// - Returns: `true` if the node can be cast, `false` otherwise. + public func `is`(_ syntaxType: AttributeSyntax.Type) -> Bool { + return self.as(syntaxType) != nil + } + + /// Attempts to cast the current syntax node to ``AttributeSyntax``. + /// + /// - Returns: An instance of ``AttributeSyntax``, or `nil` if the cast fails. + public func `as`(_ syntaxType: AttributeSyntax.Type) -> AttributeSyntax? { + return AttributeSyntax.init(self) + } + + /// Force-casts the current syntax node to ``AttributeSyntax``. + /// + /// - Returns: An instance of ``AttributeSyntax``. + /// - Warning: This function will crash if the cast is not possible. Use `as` to safely attempt a cast. + public func cast(_ syntaxType: AttributeSyntax.Type) -> AttributeSyntax { + return self.as(AttributeSyntax.self)! + } + + /// Checks if the current syntax node can be cast to ``TokenSyntax``. + /// + /// - Returns: `true` if the node can be cast, `false` otherwise. + public func `is`(_ syntaxType: TokenSyntax.Type) -> Bool { + return self.as(syntaxType) != nil + } + + /// Attempts to cast the current syntax node to ``TokenSyntax``. + /// + /// - Returns: An instance of ``TokenSyntax``, or `nil` if the cast fails. + public func `as`(_ syntaxType: TokenSyntax.Type) -> TokenSyntax? { + return TokenSyntax.init(self) + } + + /// Force-casts the current syntax node to ``TokenSyntax``. + /// + /// - Returns: An instance of ``TokenSyntax``. + /// - Warning: This function will crash if the cast is not possible. Use `as` to safely attempt a cast. + public func cast(_ syntaxType: TokenSyntax.Type) -> TokenSyntax { + return self.as(TokenSyntax.self)! + } + } + + public let _syntaxNode: Syntax + + public init?(_ node: __shared some SyntaxProtocol) { + guard node.raw.kind == .usingDecl 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. + /// - usingKeyword: The `using` keyword for this declaration. + /// - specifier: The specifier that could be either an attribute or a modifier. + /// - 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, + _ unexpectedBeforeUsingKeyword: UnexpectedNodesSyntax? = nil, + usingKeyword: TokenSyntax = .keyword(.using), + _ unexpectedBetweenUsingKeywordAndSpecifier: UnexpectedNodesSyntax? = nil, + specifier: Specifier, + _ unexpectedAfterSpecifier: UnexpectedNodesSyntax? = nil, + 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(), ( + unexpectedBeforeUsingKeyword, + usingKeyword, + unexpectedBetweenUsingKeywordAndSpecifier, + specifier, + unexpectedAfterSpecifier + ))) { (arena, _) in + let layout: [RawSyntax?] = [ + unexpectedBeforeUsingKeyword?.raw, + usingKeyword.raw, + unexpectedBetweenUsingKeywordAndSpecifier?.raw, + specifier.raw, + unexpectedAfterSpecifier?.raw + ] + let raw = RawSyntax.makeLayout( + kind: SyntaxKind.usingDecl, + from: layout, + arena: arena, + leadingTrivia: leadingTrivia, + trailingTrivia: trailingTrivia + ) + return Syntax.forRoot(raw, rawNodeArena: arena).cast(Self.self) + } + } + + public var unexpectedBeforeUsingKeyword: 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(UsingDeclSyntax.self) + } + } + + /// The `using` keyword for this declaration. + /// + /// ### Tokens + /// + /// For syntax trees generated by the parser, this is guaranteed to be `using`. + public var usingKeyword: TokenSyntax { + get { + return Syntax(self).child(at: 1)!.cast(TokenSyntax.self) + } + set(value) { + self = Syntax(self).replacingChild(at: 1, with: Syntax(value), rawAllocationArena: RawSyntaxArena()).cast(UsingDeclSyntax.self) + } + } + + public var unexpectedBetweenUsingKeywordAndSpecifier: UnexpectedNodesSyntax? { + get { + return Syntax(self).child(at: 2)?.cast(UnexpectedNodesSyntax.self) + } + set(value) { + self = Syntax(self).replacingChild(at: 2, with: Syntax(value), rawAllocationArena: RawSyntaxArena()).cast(UsingDeclSyntax.self) + } + } + + /// The specifier that could be either an attribute or a modifier. + public var specifier: Specifier { + get { + return Syntax(self).child(at: 3)!.cast(Specifier.self) + } + set(value) { + self = Syntax(self).replacingChild(at: 3, with: Syntax(value), rawAllocationArena: RawSyntaxArena()).cast(UsingDeclSyntax.self) + } + } + + public var unexpectedAfterSpecifier: UnexpectedNodesSyntax? { + get { + return Syntax(self).child(at: 4)?.cast(UnexpectedNodesSyntax.self) + } + set(value) { + self = Syntax(self).replacingChild(at: 4, with: Syntax(value), rawAllocationArena: RawSyntaxArena()).cast(UsingDeclSyntax.self) + } + } + + public static let structure: SyntaxNodeStructure = .layout([ + \Self.unexpectedBeforeUsingKeyword, + \Self.usingKeyword, + \Self.unexpectedBetweenUsingKeywordAndSpecifier, + \Self.specifier, + \Self.unexpectedAfterSpecifier + ]) +} + // MARK: - ValueBindingPatternSyntax /// ### Children diff --git a/Tests/SwiftParserTest/DeclarationTests.swift b/Tests/SwiftParserTest/DeclarationTests.swift index ee7f9c7305d..c76a7f78459 100644 --- a/Tests/SwiftParserTest/DeclarationTests.swift +++ b/Tests/SwiftParserTest/DeclarationTests.swift @@ -12,7 +12,7 @@ import SwiftBasicFormat @_spi(Testing) @_spi(RawSyntax) @_spi(ExperimentalLanguageFeatures) import SwiftParser -@_spi(RawSyntax) import SwiftSyntax +@_spi(RawSyntax) @_spi(ExperimentalLanguageFeatures) import SwiftSyntax import SwiftSyntaxBuilder import XCTest @@ -3566,3 +3566,134 @@ final class DeclarationTests: ParserTestCase { ) } } + +final class UsingDeclarationTests: ParserTestCase { + override var experimentalFeatures: Parser.ExperimentalFeatures { + [.defaultIsolationPerFile] + } + + func testUsing() { + assertParse( + "using @MainActor", + substructure: UsingDeclSyntax( + usingKeyword: .keyword(.using), + specifier: .attribute( + AttributeSyntax( + attributeName: IdentifierTypeSyntax( + name: .identifier("MainActor") + ) + ) + ) + ) + ) + assertParse( + "using nonisolated", + substructure: UsingDeclSyntax( + usingKeyword: .keyword(.using), + specifier: .modifier(.identifier("nonisolated")) + ) + ) + + assertParse( + "using @Test", + substructure: UsingDeclSyntax( + usingKeyword: .keyword(.using), + specifier: .attribute( + AttributeSyntax( + attributeName: IdentifierTypeSyntax( + name: .identifier("Test") + ) + ) + ) + ) + ) + + assertParse( + "using test", + substructure: UsingDeclSyntax( + usingKeyword: .keyword(.using), + specifier: .modifier(.identifier("test")) + ) + ) + + assertParse( + """ + nonisolated + using + """, + substructure: CodeBlockSyntax( + DeclReferenceExprSyntax(baseName: .identifier("using")) + ) + ) + + assertParse( + """ + @MainActor + using + """, + substructure: CodeBlockSyntax( + DeclReferenceExprSyntax(baseName: .identifier("using")) + ) + ) + + assertParse( + """ + using + @MainActor 1️⃣ + """, + diagnostics: [ + DiagnosticSpec( + message: "expected declaration after attribute", + fixIts: ["insert declaration"] + ) + ], + fixedSource: + """ + using + @MainActor <#declaration#> + """ + ) + + assertParse( + """ + using + nonisolated + """, + substructure: CodeBlockSyntax( + DeclReferenceExprSyntax(baseName: .identifier("using")) + ) + ) + + assertParse( + """ + func + using (x: Int) {} + """ + ) + + assertParse( + """ + func + using + (x: Int) {} + """ + ) + + assertParse( + """ + let + using = 42 + """ + ) + + assertParse("let (x: Int, using: String) = (x: 42, using: \"\")") + + assertParse( + """ + do { + using @MainActor + } + """ + ) + } +} From 93f240894327421a21d80c9f59df04f90be457bb Mon Sep 17 00:00:00 2001 From: Hamish Knight Date: Wed, 4 Jun 2025 12:33:27 +0100 Subject: [PATCH 12/33] Handle macro expansion args in placeholder expansion Update `ExpandEditorPlaceholdersToLiteralClosures` and `CallToTrailingClosures` such that they can handle macro expansion exprs and decls. This unfortunately requires changing them such that they take a `Syntax` input and output to satisfy the conformance to `SyntaxRefactoringProvider`, but that seems preferable to making the refactoring types themselves generic. If in the future we decide to expose `CallLikeSyntax` as a public protocol, we could decide to expose additional generic `refactor` overloads here. However it's not clear there's enough clients of these APIs to motivate doing that in this PR. --- Release Notes/602.md | 8 +- Sources/SwiftRefactor/CMakeLists.txt | 1 + Sources/SwiftRefactor/CallLikeSyntax.swift | 35 +++++++ .../CallToTrailingClosures.swift | 28 +++++- .../ExpandEditorPlaceholder.swift | 38 +++++--- .../ExpandEditorPlaceholderTests.swift | 97 +++++++++++++++++-- 6 files changed, 186 insertions(+), 21 deletions(-) create mode 100644 Sources/SwiftRefactor/CallLikeSyntax.swift diff --git a/Release Notes/602.md b/Release Notes/602.md index 0d5750bdd2e..7ef16677d8f 100644 --- a/Release Notes/602.md +++ b/Release Notes/602.md @@ -54,7 +54,13 @@ - Pull Request: https://github.com/swiftlang/swift-syntax/pull/3028 - Migration steps: Use `AttributeSyntax.Arguments.argumentList(LabeledExprListSyntax)` instead. - Notes: Removed cases from `AttributeSyntax.Arguments`: `token(TokenSyntax)`, `string(StringLiteralExprSyntax)`, `conventionArguments(ConventionAttributeArgumentsSyntax)`, `conventionWitnessMethodArguments(ConventionWitnessMethodAttributeArgumentsSyntax)`, `opaqueReturnTypeOfAttributeArguments(OpaqueReturnTypeOfAttributeArgumentsSyntax)`, `exposeAttributeArguments(ExposeAttributeArgumentsSyntax)`, `underscorePrivateAttributeArguments(UnderscorePrivateAttributeArgumentsSyntax)`, and `unavailableFromAsyncArguments(UnavailableFromAsyncAttributeArgumentsSyntax)`. Removed Syntax kinds: `ConventionAttributeArgumentsSyntax`, `ConventionWitnessMethodAttributeArgumentsSyntax`, `OpaqueReturnTypeOfAttributeArgumentsSyntax`, `ExposeAttributeArgumentsSyntax`, `UnderscorePrivateAttributeArgumentsSyntax`, and `UnavailableFromAsyncAttributeArgumentsSyntax`. -, + +- `ExpandEditorPlaceholdersToLiteralClosures` & `CallToTrailingClosures` now take a `Syntax` parameter + - Description: These refactorings now take an arbitrary `Syntax` and return a `Syntax?`. If a non-function-like syntax node is passed, `nil` is returned. The previous `FunctionCallExprSyntax` overloads are deprecated. + - Pull Request: https://github.com/swiftlang/swift-syntax/pull/3092 + - Migration steps: Insert a `Syntax(...)` initializer call for the argument, and cast the result with `.as(...)` if necessary. + - Notes: This allows the refactorings to correctly handle macro expansion expressions and declarations. + ## Template - *Affected API or two word description* diff --git a/Sources/SwiftRefactor/CMakeLists.txt b/Sources/SwiftRefactor/CMakeLists.txt index 1864c71f2ab..8494f3b91cc 100644 --- a/Sources/SwiftRefactor/CMakeLists.txt +++ b/Sources/SwiftRefactor/CMakeLists.txt @@ -8,6 +8,7 @@ add_swift_syntax_library(SwiftRefactor AddSeparatorsToIntegerLiteral.swift + CallLikeSyntax.swift CallToTrailingClosures.swift ConvertComputedPropertyToStored.swift ConvertComputedPropertyToZeroParameterFunction.swift diff --git a/Sources/SwiftRefactor/CallLikeSyntax.swift b/Sources/SwiftRefactor/CallLikeSyntax.swift new file mode 100644 index 00000000000..c620a52fe97 --- /dev/null +++ b/Sources/SwiftRefactor/CallLikeSyntax.swift @@ -0,0 +1,35 @@ +//===----------------------------------------------------------------------===// +// +// This source file is part of the Swift.org open source project +// +// Copyright (c) 2025 Apple Inc. and the Swift project authors +// Licensed under Apache License v2.0 with Runtime Library Exception +// +// See https://swift.org/LICENSE.txt for license information +// See https://swift.org/CONTRIBUTORS.txt for the list of Swift project authors +// +//===----------------------------------------------------------------------===// + +#if compiler(>=6) +public import SwiftSyntax +#else +import SwiftSyntax +#endif + +// TODO: We ought to consider exposing this as a public syntax protocol. +@_spi(Testing) public protocol CallLikeSyntax: SyntaxProtocol { + var arguments: LabeledExprListSyntax { get set } + var leftParen: TokenSyntax? { get set } + var rightParen: TokenSyntax? { get set } + var trailingClosure: ClosureExprSyntax? { get set } + var additionalTrailingClosures: MultipleTrailingClosureElementListSyntax { get set } +} +@_spi(Testing) extension FunctionCallExprSyntax: CallLikeSyntax {} +@_spi(Testing) extension MacroExpansionExprSyntax: CallLikeSyntax {} +@_spi(Testing) extension MacroExpansionDeclSyntax: CallLikeSyntax {} + +extension SyntaxProtocol { + @_spi(Testing) public func asProtocol(_: CallLikeSyntax.Protocol) -> (any CallLikeSyntax)? { + Syntax(self).asProtocol(SyntaxProtocol.self) as? CallLikeSyntax + } +} diff --git a/Sources/SwiftRefactor/CallToTrailingClosures.swift b/Sources/SwiftRefactor/CallToTrailingClosures.swift index f2781d4b7ed..858c1185b61 100644 --- a/Sources/SwiftRefactor/CallToTrailingClosures.swift +++ b/Sources/SwiftRefactor/CallToTrailingClosures.swift @@ -50,19 +50,41 @@ public struct CallToTrailingClosures: SyntaxRefactoringProvider { } } + public typealias Input = Syntax + public typealias Output = Syntax + + /// Apply the refactoring to a given syntax node. If either a + /// non-function-like syntax node is passed, or the refactoring fails, + /// `nil` is returned. // TODO: Rather than returning nil, we should consider throwing errors with // appropriate messages instead. + public static func refactor( + syntax: Syntax, + in context: Context = Context() + ) -> Syntax? { + guard let call = syntax.asProtocol(CallLikeSyntax.self) else { return nil } + return Syntax(fromProtocol: _refactor(syntax: call, in: context)) + } + + @available(*, deprecated, message: "Pass a Syntax argument instead of FunctionCallExprSyntax") public static func refactor( syntax call: FunctionCallExprSyntax, in context: Context = Context() ) -> FunctionCallExprSyntax? { + _refactor(syntax: call, in: context) + } + + internal static func _refactor( + syntax call: C, + in context: Context = Context() + ) -> C? { let converted = call.convertToTrailingClosures(from: context.startAtArgument) - return converted?.formatted().as(FunctionCallExprSyntax.self) + return converted?.formatted().as(C.self) } } -extension FunctionCallExprSyntax { - fileprivate func convertToTrailingClosures(from startAtArgument: Int) -> FunctionCallExprSyntax? { +extension CallLikeSyntax { + fileprivate func convertToTrailingClosures(from startAtArgument: Int) -> Self? { guard trailingClosure == nil, additionalTrailingClosures.isEmpty, leftParen != nil, rightParen != nil else { // Already have trailing closures return nil diff --git a/Sources/SwiftRefactor/ExpandEditorPlaceholder.swift b/Sources/SwiftRefactor/ExpandEditorPlaceholder.swift index 7bf87269489..50854f65e37 100644 --- a/Sources/SwiftRefactor/ExpandEditorPlaceholder.swift +++ b/Sources/SwiftRefactor/ExpandEditorPlaceholder.swift @@ -180,7 +180,7 @@ public struct ExpandEditorPlaceholder: EditRefactoringProvider { placeholder.baseName.isEditorPlaceholder, let arg = placeholder.parent?.as(LabeledExprSyntax.self), let argList = arg.parent?.as(LabeledExprListSyntax.self), - let call = argList.parent?.as(FunctionCallExprSyntax.self), + let call = argList.parent?.asProtocol(CallLikeSyntax.self), let expandedClosures = ExpandEditorPlaceholdersToLiteralClosures.expandClosurePlaceholders( in: call, ifIncluded: arg, @@ -266,6 +266,26 @@ public struct ExpandEditorPlaceholdersToLiteralClosures: SyntaxRefactoringProvid } } + public typealias Input = Syntax + public typealias Output = Syntax + + /// Apply the refactoring to a given syntax node. If either a + /// non-function-like syntax node is passed, or the refactoring fails, + /// `nil` is returned. + public static func refactor( + syntax: Syntax, + in context: Context = Context() + ) -> Syntax? { + guard let call = syntax.asProtocol(CallLikeSyntax.self) else { return nil } + let expanded = Self.expandClosurePlaceholders( + in: call, + ifIncluded: nil, + context: context + ) + return Syntax(fromProtocol: expanded) + } + + @available(*, deprecated, message: "Pass a Syntax argument instead of FunctionCallExprSyntax") public static func refactor( syntax call: FunctionCallExprSyntax, in context: Context = Context() @@ -282,11 +302,11 @@ public struct ExpandEditorPlaceholdersToLiteralClosures: SyntaxRefactoringProvid /// closure, then return a replacement of this call with one that uses /// closures based on the function types provided by each editor placeholder. /// Otherwise return nil. - fileprivate static func expandClosurePlaceholders( - in call: FunctionCallExprSyntax, + fileprivate static func expandClosurePlaceholders( + in call: C, ifIncluded arg: LabeledExprSyntax?, context: Context - ) -> FunctionCallExprSyntax? { + ) -> C? { switch context.format { case let .custom(formatter, allowNestedPlaceholders: allowNesting): let expanded = call.expandClosurePlaceholders( @@ -305,11 +325,7 @@ public struct ExpandEditorPlaceholdersToLiteralClosures: SyntaxRefactoringProvid let callToTrailingContext = CallToTrailingClosures.Context( startAtArgument: call.arguments.count - expanded.numClosures ) - guard let trailing = CallToTrailingClosures.refactor(syntax: expanded.expr, in: callToTrailingContext) else { - return nil - } - - return trailing + return CallToTrailingClosures._refactor(syntax: expanded.expr, in: callToTrailingContext) } } } @@ -382,7 +398,7 @@ extension TupleTypeElementSyntax { } } -extension FunctionCallExprSyntax { +extension CallLikeSyntax { /// If the given argument is `nil` or one of the last arguments that are all /// function-typed placeholders and this call doesn't have a trailing /// closure, then return a replacement of this call with one that uses @@ -393,7 +409,7 @@ extension FunctionCallExprSyntax { indentationWidth: Trivia? = nil, customFormat: BasicFormat? = nil, allowNestedPlaceholders: Bool = false - ) -> (expr: FunctionCallExprSyntax, numClosures: Int)? { + ) -> (expr: Self, numClosures: Int)? { var includedArg = false var argsToExpand = 0 for arg in arguments.reversed() { diff --git a/Tests/SwiftRefactorTest/ExpandEditorPlaceholderTests.swift b/Tests/SwiftRefactorTest/ExpandEditorPlaceholderTests.swift index 859963a737c..97d5d1660b2 100644 --- a/Tests/SwiftRefactorTest/ExpandEditorPlaceholderTests.swift +++ b/Tests/SwiftRefactorTest/ExpandEditorPlaceholderTests.swift @@ -451,6 +451,58 @@ final class ExpandEditorPlaceholderTests: XCTestCase { format: .testCustom() ) } + + func testMacroTrailingClosureExpansion1() throws { + try assertRefactorPlaceholderToken( + "#foo(\(closurePlaceholder), \(intPlaceholder))", + expected: """ + { + \(voidPlaceholder) + } + """ + ) + } + + func testMacroTrailingClosureExpansion2() throws { + let call = "#foo(fn: \(closureWithArgPlaceholder))" + let expanded = """ + #foo { someInt in + \(stringPlaceholder) + } + """ + + try assertRefactorPlaceholderCall(call, expected: expanded) + try assertExpandEditorPlaceholdersToClosures(call, expected: expanded) + } + + func testMacroTrailingClosureExpansion3() throws { + let call = "#foo(fn1: \(closurePlaceholder), fn2: \(closureWithArgPlaceholder))" + let expanded = """ + #foo { + \(voidPlaceholder) + } fn2: { someInt in + \(stringPlaceholder) + } + """ + + try assertRefactorPlaceholderCall(call, expected: expanded) + try assertExpandEditorPlaceholdersToClosures(call, expected: expanded) + } + + func testMacroTrailingClosureExpansion4() throws { + try assertExpandEditorPlaceholdersToClosures( + decl: """ + #foo(fn1: \(closurePlaceholder), fn2: \(closurePlaceholder)) + """, + expected: """ + #foo { + \(voidPlaceholder) + } fn2: { + \(voidPlaceholder) + } + """ + ) + } } fileprivate func assertRefactorPlaceholder( @@ -489,7 +541,7 @@ fileprivate func assertRefactorPlaceholderCall( line: UInt = #line ) throws { var parser = Parser(expr) - let call = try XCTUnwrap(ExprSyntax.parse(from: &parser).as(FunctionCallExprSyntax.self), file: file, line: line) + let call = try XCTUnwrap(ExprSyntax.parse(from: &parser).asProtocol(CallLikeSyntax.self), file: file, line: line) let arg = call.arguments[call.arguments.index(at: placeholder)] let token: TokenSyntax = try XCTUnwrap(arg.expression.as(DeclReferenceExprSyntax.self), file: file, line: line) .baseName @@ -513,7 +565,7 @@ fileprivate func assertRefactorPlaceholderToken( line: UInt = #line ) throws { var parser = Parser(expr) - let call = try XCTUnwrap(ExprSyntax.parse(from: &parser).as(FunctionCallExprSyntax.self), file: file, line: line) + let call = try XCTUnwrap(ExprSyntax.parse(from: &parser).asProtocol(CallLikeSyntax.self), file: file, line: line) let arg = call.arguments[call.arguments.index(at: placeholder)] let token: TokenSyntax = try XCTUnwrap(arg.expression.as(DeclReferenceExprSyntax.self), file: file, line: line) .baseName @@ -529,15 +581,12 @@ fileprivate func assertRefactorPlaceholderToken( } fileprivate func assertExpandEditorPlaceholdersToClosures( - _ expr: String, + _ call: some CallLikeSyntax, expected: String, format: ExpandEditorPlaceholdersToLiteralClosures.Context.Format = .trailing(indentationWidth: nil), file: StaticString = #filePath, line: UInt = #line ) throws { - var parser = Parser(expr) - let call = try XCTUnwrap(ExprSyntax.parse(from: &parser).as(FunctionCallExprSyntax.self), file: file, line: line) - try assertRefactor( call, context: ExpandEditorPlaceholdersToLiteralClosures.Context(format: format), @@ -548,6 +597,42 @@ fileprivate func assertExpandEditorPlaceholdersToClosures( ) } +fileprivate func assertExpandEditorPlaceholdersToClosures( + _ expr: String, + expected: String, + format: ExpandEditorPlaceholdersToLiteralClosures.Context.Format = .trailing(indentationWidth: nil), + file: StaticString = #filePath, + line: UInt = #line +) throws { + var parser = Parser(expr) + let call = try XCTUnwrap(ExprSyntax.parse(from: &parser).asProtocol(CallLikeSyntax.self), file: file, line: line) + try assertExpandEditorPlaceholdersToClosures( + call, + expected: expected, + format: format, + file: file, + line: line + ) +} + +fileprivate func assertExpandEditorPlaceholdersToClosures( + decl: String, + expected: String, + format: ExpandEditorPlaceholdersToLiteralClosures.Context.Format = .trailing(indentationWidth: nil), + file: StaticString = #filePath, + line: UInt = #line +) throws { + var parser = Parser(decl) + let call = try XCTUnwrap(DeclSyntax.parse(from: &parser).asProtocol(CallLikeSyntax.self), file: file, line: line) + try assertExpandEditorPlaceholdersToClosures( + call, + expected: expected, + format: format, + file: file, + line: line + ) +} + fileprivate extension ExpandEditorPlaceholdersToLiteralClosures.Context.Format { static func testCustom(indentationWidth: Trivia? = nil) -> Self { .custom(CustomClosureFormat(indentationWidth: indentationWidth), allowNestedPlaceholders: true) From 9a3c9620a1765793a0701c63588d78989b3ac490 Mon Sep 17 00:00:00 2001 From: Alex Hoppen Date: Tue, 10 Jun 2025 15:19:03 +0200 Subject: [PATCH 13/33] Relax precondition in `postProcessMultilineStringLiteral` MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The only place where we were dropping unexpected tokens or trivia around a string segment was when inferring the indentation of the closing quote. Instead of having a precondition, add the conditions that the last segment doesn’t have any of these here and fall back to the same logic that is used when the indentaiton could not be inferred. Fixes #3097 --- Sources/SwiftParser/StringLiterals.swift | 31 ++++------------- .../translated/StringLiteralEofTests.swift | 33 +++++++++++++++++++ 2 files changed, 40 insertions(+), 24 deletions(-) diff --git a/Sources/SwiftParser/StringLiterals.swift b/Sources/SwiftParser/StringLiterals.swift index b03c8877def..d3b9c8adc4d 100644 --- a/Sources/SwiftParser/StringLiterals.swift +++ b/Sources/SwiftParser/StringLiterals.swift @@ -340,22 +340,6 @@ extension Parser { unexpectedBeforeClosingQuote: [RawTokenSyntax], closingQuote: RawTokenSyntax ) { - // ------------------------------------------------------------------------- - // Precondition - - precondition( - allSegments.allSatisfy { - if case .stringSegment(let segment) = $0 { - return segment.unexpectedBeforeContent == nil - && segment.unexpectedAfterContent == nil - && segment.content.leadingTriviaByteLength == 0 - } else { - return true - } - }, - "String segment produced by the lexer should not have unexpected text or trivia because we would drop it during post-processing" - ) - // ------------------------------------------------------------------------- // Variables @@ -395,6 +379,9 @@ extension Parser { // Parse indentation of the closing quote if let lastSegment, + lastSegment.unexpectedBeforeContent == nil, + lastSegment.unexpectedAfterContent == nil, + lastSegment.content.leadingTriviaByteLength == 0, let parsedTrivia = parseIndentationTrivia(text: lastSegment.content.tokenText) { indentationTrivia = parsedTrivia @@ -409,10 +396,9 @@ extension Parser { arena: self.arena ) } else { - if let lastSegment = lastSegment { - indentationTrivia = TriviaParser.parseTrivia(lastSegment.content.tokenText, position: .leading).prefix(while: { - $0.isIndentationWhitespace - }) + if let lastSegment { + indentationTrivia = TriviaParser.parseTrivia(lastSegment.content.tokenText, position: .leading) + .prefix(while: \.isIndentationWhitespace) let indentationByteLength = indentationTrivia.reduce(0, { $0 + $1.byteLength }) indentation = SyntaxText(rebasing: lastSegment.content.tokenText[0..)" + """# + ) + } } From 902e76e4c8c3d0e65b7c1b3b3dde6a3a925853a5 Mon Sep 17 00:00:00 2001 From: Alex Hoppen Date: Mon, 16 Jun 2025 14:58:17 +0200 Subject: [PATCH 14/33] Fix a crash if we had a dollar identifier as the second parameter label in a closure not followed by a colon There was a mismatch between the call to `eat` and `at`. Switch the pair to a `consume(if:)` to avoid the issue. Fixes swiftlang/swift-syntax#3103 --- Sources/SwiftParser/Types.swift | 8 ++++---- Tests/SwiftParserTest/ExpressionTests.swift | 20 ++++++++++++++++++++ 2 files changed, 24 insertions(+), 4 deletions(-) diff --git a/Sources/SwiftParser/Types.swift b/Sources/SwiftParser/Types.swift index 68a6428ef49..67ad666c5df 100644 --- a/Sources/SwiftParser/Types.swift +++ b/Sources/SwiftParser/Types.swift @@ -944,13 +944,13 @@ extension Parser.Lookahead { // by a type annotation. if self.startsParameterName(isClosure: false, allowMisplacedSpecifierRecovery: false) { self.consumeAnyToken() + // If we have a secondary argument label, consume it. if self.atArgumentLabel() { self.consumeAnyToken() - guard self.at(.colon) else { - return false - } } - self.eat(.colon) + guard self.consume(if: .colon) != nil else { + return false + } // Parse a type. guard self.canParseType() else { diff --git a/Tests/SwiftParserTest/ExpressionTests.swift b/Tests/SwiftParserTest/ExpressionTests.swift index 3eb7d83add2..40e31d34fc7 100644 --- a/Tests/SwiftParserTest/ExpressionTests.swift +++ b/Tests/SwiftParserTest/ExpressionTests.swift @@ -2145,6 +2145,26 @@ final class ExpressionTests: ParserTestCase { """ ) } + + func testSecondaryArgumentLabelDollarIdentifierInClosure() { + assertParse( + """ + ℹ️{ a1️⃣: (a $ + """, + diagnostics: [ + DiagnosticSpec( + 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 $ + """ + ) + } } final class MemberExprTests: ParserTestCase { From c4f53b3ebaaf44e852c9a468f19ddb4cd5ca11e7 Mon Sep 17 00:00:00 2001 From: Anthony Latsis Date: Sat, 21 Jun 2025 00:03:37 +0100 Subject: [PATCH 15/33] .swift-format: Enable `ReturnVoidInsteadOfEmptyTuple` --- .swift-format | 3 ++- Sources/SwiftParser/IncrementalParseTransition.swift | 2 +- Sources/SwiftSyntax/SourceLocation.swift | 10 +++++----- 3 files changed, 8 insertions(+), 7 deletions(-) diff --git a/.swift-format b/.swift-format index 41a022f2627..c258291d37e 100644 --- a/.swift-format +++ b/.swift-format @@ -13,6 +13,7 @@ "NoBlockComments": false, "OrderedImports": true, "UseLetInEveryBoundCaseVariable": false, - "UseSynthesizedInitializer": false + "UseSynthesizedInitializer": false, + "ReturnVoidInsteadOfEmptyTuple": true, } } diff --git a/Sources/SwiftParser/IncrementalParseTransition.swift b/Sources/SwiftParser/IncrementalParseTransition.swift index c2ebb127817..730f5112da0 100644 --- a/Sources/SwiftParser/IncrementalParseTransition.swift +++ b/Sources/SwiftParser/IncrementalParseTransition.swift @@ -47,7 +47,7 @@ extension Parser { /// /// This is also used for testing purposes to ensure incremental reparsing /// worked as expected. -public typealias ReusedNodeCallback = (_ node: Syntax) -> () +public typealias ReusedNodeCallback = (_ node: Syntax) -> Void /// Keeps track of a previously parsed syntax tree and the source edits that /// occurred since it was created. diff --git a/Sources/SwiftSyntax/SourceLocation.swift b/Sources/SwiftSyntax/SourceLocation.swift index dcca9d2a74f..4cd64d612b9 100644 --- a/Sources/SwiftSyntax/SourceLocation.swift +++ b/Sources/SwiftSyntax/SourceLocation.swift @@ -701,7 +701,7 @@ fileprivate extension SyntaxText { /// - Returns: The position at the end of the walk. func forEachEndOfLine( position: AbsolutePosition, - body: (AbsolutePosition) -> () + body: (AbsolutePosition) -> Void ) -> AbsolutePosition { guard let startPtr = buffer.baseAddress else { return position @@ -745,7 +745,7 @@ fileprivate extension RawTriviaPiece { /// - Returns: The position at the end of the walk. func forEachEndOfLine( position: AbsolutePosition, - body: (AbsolutePosition) -> () + body: (AbsolutePosition) -> Void ) -> AbsolutePosition { var position = position switch self { @@ -788,7 +788,7 @@ fileprivate extension RawTriviaPieceBuffer { /// - Returns: The position at the end of the walk. func forEachEndOfLine( position: AbsolutePosition, - body: (AbsolutePosition) -> () + body: (AbsolutePosition) -> Void ) -> AbsolutePosition { var position = position for piece in self { @@ -810,8 +810,8 @@ fileprivate extension RawSyntax { /// - Returns: The position at the end of the walk. func forEachEndOfLine( position: AbsolutePosition, - body: (AbsolutePosition) -> (), - handleSourceLocationDirective: (_ position: AbsolutePosition, _ rawSyntax: RawSyntax) -> () + body: (AbsolutePosition) -> Void, + handleSourceLocationDirective: (_ position: AbsolutePosition, _ rawSyntax: RawSyntax) -> Void ) -> AbsolutePosition { var position = position switch self.rawData.payload { From c186fe348e2160771c6dc664361ceb249216c431 Mon Sep 17 00:00:00 2001 From: Doug Gregor Date: Mon, 23 Jun 2025 11:23:11 -0700 Subject: [PATCH 16/33] Add "late specifiers" to attributed types. This is needed to account for 'nonisolated' occuring after type attributes within a protocol conformance. --- CodeGeneration/Sources/SyntaxSupport/Child.swift | 9 +++++---- .../Sources/SyntaxSupport/GrammarGenerator.swift | 2 +- CodeGeneration/Sources/SyntaxSupport/Node.swift | 2 +- CodeGeneration/Sources/SyntaxSupport/TypeNodes.swift | 7 +++++++ CodeGeneration/Sources/Utils/SyntaxBuildableChild.swift | 4 ++-- .../swiftsyntax/RenamedChildrenCompatibilityFile.swift | 3 ++- .../templates/swiftsyntax/SyntaxNodesFile.swift | 2 +- 7 files changed, 19 insertions(+), 10 deletions(-) diff --git a/CodeGeneration/Sources/SyntaxSupport/Child.swift b/CodeGeneration/Sources/SyntaxSupport/Child.swift index 683c0441953..ce631b5950e 100644 --- a/CodeGeneration/Sources/SyntaxSupport/Child.swift +++ b/CodeGeneration/Sources/SyntaxSupport/Child.swift @@ -47,7 +47,8 @@ public enum ChildKind { kind: SyntaxNodeKind, collectionElementName: String? = nil, defaultsToEmpty: Bool = false, - deprecatedCollectionElementName: String? = nil + deprecatedCollectionElementName: String? = nil, + generateDeprecatedAddFunction: Bool = true ) /// The child is a token that matches one of the given `choices`. /// If `requiresLeadingSpace` or `requiresTrailingSpace` is not `nil`, it @@ -132,7 +133,7 @@ public class Child: NodeChoiceConvertible { return kind case .nodeChoices: return .syntax - case .collection(kind: let kind, _, _, _): + case .collection(kind: let kind, _, _, _, _): return kind case .token: return .token @@ -268,7 +269,7 @@ public class Child: NodeChoiceConvertible { /// Whether this child has syntax kind `UnexpectedNodes`. public var isUnexpectedNodes: Bool { switch kind { - case .collection(kind: .unexpectedNodes, _, _, _): + case .collection(kind: .unexpectedNodes, _, _, _, _): return true default: return false @@ -283,7 +284,7 @@ public class Child: NodeChoiceConvertible { return choices.isEmpty case .node(let kind): return kind.isBase - case .collection(kind: let kind, _, _, _): + case .collection(kind: let kind, _, _, _, _): return kind.isBase case .token: return false diff --git a/CodeGeneration/Sources/SyntaxSupport/GrammarGenerator.swift b/CodeGeneration/Sources/SyntaxSupport/GrammarGenerator.swift index ae84735dbf9..4d28dff2564 100644 --- a/CodeGeneration/Sources/SyntaxSupport/GrammarGenerator.swift +++ b/CodeGeneration/Sources/SyntaxSupport/GrammarGenerator.swift @@ -41,7 +41,7 @@ struct GrammarGenerator { case .nodeChoices(let choices, _): let choicesDescriptions = choices.map { grammar(for: $0) } return "(\(choicesDescriptions.joined(separator: " | ")))\(optionality)" - case .collection(kind: let kind, _, _, _): + case .collection(kind: let kind, _, _, _, _): return "\(kind.doccLink)\(optionality)" case .token(let choices, _, _): if choices.count == 1 { diff --git a/CodeGeneration/Sources/SyntaxSupport/Node.swift b/CodeGeneration/Sources/SyntaxSupport/Node.swift index 913473c1c85..f1341f7e355 100644 --- a/CodeGeneration/Sources/SyntaxSupport/Node.swift +++ b/CodeGeneration/Sources/SyntaxSupport/Node.swift @@ -391,7 +391,7 @@ fileprivate extension Child { return [kind] case .nodeChoices(let choices, _): return choices.flatMap(\.kinds) - case .collection(kind: let kind, _, _, _): + case .collection(kind: let kind, _, _, _, _): return [kind] case .token: return [.token] diff --git a/CodeGeneration/Sources/SyntaxSupport/TypeNodes.swift b/CodeGeneration/Sources/SyntaxSupport/TypeNodes.swift index ab81dde2a2d..1c19e2018d4 100644 --- a/CodeGeneration/Sources/SyntaxSupport/TypeNodes.swift +++ b/CodeGeneration/Sources/SyntaxSupport/TypeNodes.swift @@ -57,6 +57,13 @@ public let TYPE_NODES: [Node] = [ kind: .collection(kind: .attributeList, collectionElementName: "Attribute", defaultsToEmpty: true), documentation: "A list of attributes that can be attached to the type, such as `@escaping`." ), + Child( + name: "lateSpecifiers", + kind: .collection(kind: .typeSpecifierList, collectionElementName: "Specifier", defaultsToEmpty: true, + generateDeprecatedAddFunction: false), + documentation: + "A list of specifiers that can be attached to the type after the attributes, such as 'nonisolated'." + ), Child( name: "baseType", kind: .node(kind: .type), diff --git a/CodeGeneration/Sources/Utils/SyntaxBuildableChild.swift b/CodeGeneration/Sources/Utils/SyntaxBuildableChild.swift index 9ad78761e04..6e4fb79941a 100644 --- a/CodeGeneration/Sources/Utils/SyntaxBuildableChild.swift +++ b/CodeGeneration/Sources/Utils/SyntaxBuildableChild.swift @@ -33,7 +33,7 @@ extension Child { buildableKind = .node(kind: kind) case .nodeChoices: buildableKind = .node(kind: .syntax) - case .collection(kind: let kind, _, _, _): + case .collection(kind: let kind, _, _, _, _): buildableKind = .node(kind: kind) case .token: buildableKind = .token(self.tokenKind!) @@ -65,7 +65,7 @@ extension Child { return ExprSyntax("nil") } } - if case .collection(_, _, defaultsToEmpty: true, _) = kind { + if case .collection(_, _, defaultsToEmpty: true, _, _) = kind { return ExprSyntax("[]") } guard let token = token, isToken else { diff --git a/CodeGeneration/Sources/generate-swift-syntax/templates/swiftsyntax/RenamedChildrenCompatibilityFile.swift b/CodeGeneration/Sources/generate-swift-syntax/templates/swiftsyntax/RenamedChildrenCompatibilityFile.swift index aea1375e54c..77f4548117b 100644 --- a/CodeGeneration/Sources/generate-swift-syntax/templates/swiftsyntax/RenamedChildrenCompatibilityFile.swift +++ b/CodeGeneration/Sources/generate-swift-syntax/templates/swiftsyntax/RenamedChildrenCompatibilityFile.swift @@ -85,7 +85,8 @@ func makeCompatibilityAddMethod(for child: Child) -> DeclSyntax? { kind: _, collectionElementName: let collectionElementName?, defaultsToEmpty: _, - deprecatedCollectionElementName: let deprecatedCollectionElementName? + deprecatedCollectionElementName: let deprecatedCollectionElementName?, + generateDeprecatedAddFunction: _ ) = child.kind { let childEltType = childNode.collectionElementType.syntaxBaseName diff --git a/CodeGeneration/Sources/generate-swift-syntax/templates/swiftsyntax/SyntaxNodesFile.swift b/CodeGeneration/Sources/generate-swift-syntax/templates/swiftsyntax/SyntaxNodesFile.swift index 6c5bd8bad67..703448adfd0 100644 --- a/CodeGeneration/Sources/generate-swift-syntax/templates/swiftsyntax/SyntaxNodesFile.swift +++ b/CodeGeneration/Sources/generate-swift-syntax/templates/swiftsyntax/SyntaxNodesFile.swift @@ -176,7 +176,7 @@ func syntaxNode(nodesStartingWith: [Character]) -> SourceFileSyntax { // If needed, this could be added in the future, but for now withUnexpected should be sufficient. if let childNode = SYNTAX_NODE_MAP[child.syntaxNodeKind]?.collectionNode, !child.isUnexpectedNodes, - case .collection(_, collectionElementName: let childElt?, _, _) = child.kind + case .collection(_, collectionElementName: let childElt?, _, _, generateDeprecatedAddFunction: true) = child.kind { let childEltType = childNode.collectionElementType.syntaxBaseName From 6ecd03296ab0dca889a739f71cf1dca9cb8abdf2 Mon Sep 17 00:00:00 2001 From: Doug Gregor Date: Mon, 23 Jun 2025 11:29:58 -0700 Subject: [PATCH 17/33] Regenerate syntax nodes --- .../generated/ChildNameForKeyPath.swift | 8 +++- .../generated/SyntaxCollections.swift | 1 + .../generated/raw/RawSyntaxNodesAB.swift | 28 +++++++---- .../generated/raw/RawSyntaxValidation.swift | 6 ++- .../generated/syntaxNodes/SyntaxNodesAB.swift | 47 +++++++++++++++---- 5 files changed, 69 insertions(+), 21 deletions(-) diff --git a/Sources/SwiftSyntax/generated/ChildNameForKeyPath.swift b/Sources/SwiftSyntax/generated/ChildNameForKeyPath.swift index d64489aa01b..0bcd20aee62 100644 --- a/Sources/SwiftSyntax/generated/ChildNameForKeyPath.swift +++ b/Sources/SwiftSyntax/generated/ChildNameForKeyPath.swift @@ -286,8 +286,12 @@ public func childName(_ keyPath: AnyKeyPath) -> String? { return "unexpectedBetweenSpecifiersAndAttributes" case \AttributedTypeSyntax.attributes: return "attributes" - case \AttributedTypeSyntax.unexpectedBetweenAttributesAndBaseType: - return "unexpectedBetweenAttributesAndBaseType" + case \AttributedTypeSyntax.unexpectedBetweenAttributesAndLateSpecifiers: + return "unexpectedBetweenAttributesAndLateSpecifiers" + case \AttributedTypeSyntax.lateSpecifiers: + return "lateSpecifiers" + case \AttributedTypeSyntax.unexpectedBetweenLateSpecifiersAndBaseType: + return "unexpectedBetweenLateSpecifiersAndBaseType" case \AttributedTypeSyntax.baseType: return "baseType" case \AttributedTypeSyntax.unexpectedAfterBaseType: diff --git a/Sources/SwiftSyntax/generated/SyntaxCollections.swift b/Sources/SwiftSyntax/generated/SyntaxCollections.swift index a34717855b7..529785471e9 100644 --- a/Sources/SwiftSyntax/generated/SyntaxCollections.swift +++ b/Sources/SwiftSyntax/generated/SyntaxCollections.swift @@ -1918,6 +1918,7 @@ public struct TupleTypeElementListSyntax: SyntaxCollection, SyntaxHashable { /// ### Contained in /// /// - ``AttributedTypeSyntax``.``AttributedTypeSyntax/specifiers`` +/// - ``AttributedTypeSyntax``.``AttributedTypeSyntax/lateSpecifiers`` public struct TypeSpecifierListSyntax: SyntaxCollection, SyntaxHashable { public enum Element: SyntaxChildChoices, SyntaxHashable { /// A specifier that can be attached to a type to eg. mark a parameter as `inout` or `consuming` diff --git a/Sources/SwiftSyntax/generated/raw/RawSyntaxNodesAB.swift b/Sources/SwiftSyntax/generated/raw/RawSyntaxNodesAB.swift index 1dc419a7df4..a04d4c66d1a 100644 --- a/Sources/SwiftSyntax/generated/raw/RawSyntaxNodesAB.swift +++ b/Sources/SwiftSyntax/generated/raw/RawSyntaxNodesAB.swift @@ -1827,21 +1827,25 @@ public struct RawAttributedTypeSyntax: RawTypeSyntaxNodeProtocol { specifiers: RawTypeSpecifierListSyntax, _ unexpectedBetweenSpecifiersAndAttributes: RawUnexpectedNodesSyntax? = nil, attributes: RawAttributeListSyntax, - _ unexpectedBetweenAttributesAndBaseType: RawUnexpectedNodesSyntax? = nil, + _ unexpectedBetweenAttributesAndLateSpecifiers: RawUnexpectedNodesSyntax? = nil, + lateSpecifiers: RawTypeSpecifierListSyntax, + _ unexpectedBetweenLateSpecifiersAndBaseType: RawUnexpectedNodesSyntax? = nil, baseType: some RawTypeSyntaxNodeProtocol, _ unexpectedAfterBaseType: RawUnexpectedNodesSyntax? = nil, arena: __shared RawSyntaxArena ) { let raw = RawSyntax.makeLayout( - kind: .attributedType, uninitializedCount: 7, arena: arena) { layout in + kind: .attributedType, uninitializedCount: 9, arena: arena) { layout in layout.initialize(repeating: nil) layout[0] = unexpectedBeforeSpecifiers?.raw layout[1] = specifiers.raw layout[2] = unexpectedBetweenSpecifiersAndAttributes?.raw layout[3] = attributes.raw - layout[4] = unexpectedBetweenAttributesAndBaseType?.raw - layout[5] = baseType.raw - layout[6] = unexpectedAfterBaseType?.raw + layout[4] = unexpectedBetweenAttributesAndLateSpecifiers?.raw + layout[5] = lateSpecifiers.raw + layout[6] = unexpectedBetweenLateSpecifiersAndBaseType?.raw + layout[7] = baseType.raw + layout[8] = unexpectedAfterBaseType?.raw } self.init(unchecked: raw) } @@ -1862,16 +1866,24 @@ public struct RawAttributedTypeSyntax: RawTypeSyntaxNodeProtocol { layoutView.children[3].map(RawAttributeListSyntax.init(raw:))! } - public var unexpectedBetweenAttributesAndBaseType: RawUnexpectedNodesSyntax? { + public var unexpectedBetweenAttributesAndLateSpecifiers: RawUnexpectedNodesSyntax? { layoutView.children[4].map(RawUnexpectedNodesSyntax.init(raw:)) } + public var lateSpecifiers: RawTypeSpecifierListSyntax { + layoutView.children[5].map(RawTypeSpecifierListSyntax.init(raw:))! + } + + public var unexpectedBetweenLateSpecifiersAndBaseType: RawUnexpectedNodesSyntax? { + layoutView.children[6].map(RawUnexpectedNodesSyntax.init(raw:)) + } + public var baseType: RawTypeSyntax { - layoutView.children[5].map(RawTypeSyntax.init(raw:))! + layoutView.children[7].map(RawTypeSyntax.init(raw:))! } public var unexpectedAfterBaseType: RawUnexpectedNodesSyntax? { - layoutView.children[6].map(RawUnexpectedNodesSyntax.init(raw:)) + layoutView.children[8].map(RawUnexpectedNodesSyntax.init(raw:)) } } diff --git a/Sources/SwiftSyntax/generated/raw/RawSyntaxValidation.swift b/Sources/SwiftSyntax/generated/raw/RawSyntaxValidation.swift index 7a85c165961..4def2848664 100644 --- a/Sources/SwiftSyntax/generated/raw/RawSyntaxValidation.swift +++ b/Sources/SwiftSyntax/generated/raw/RawSyntaxValidation.swift @@ -422,14 +422,16 @@ func validateLayout(layout: RawSyntaxBuffer, as kind: SyntaxKind) { assertNoError(kind, 10, verify(layout[10], as: RawUnexpectedNodesSyntax?.self)) } func validateAttributedTypeSyntax(kind: SyntaxKind, layout: RawSyntaxBuffer) { - assert(layout.count == 7) + assert(layout.count == 9) assertNoError(kind, 0, verify(layout[0], as: RawUnexpectedNodesSyntax?.self)) assertNoError(kind, 1, verify(layout[1], as: RawTypeSpecifierListSyntax.self)) assertNoError(kind, 2, verify(layout[2], as: RawUnexpectedNodesSyntax?.self)) assertNoError(kind, 3, verify(layout[3], as: RawAttributeListSyntax.self)) assertNoError(kind, 4, verify(layout[4], as: RawUnexpectedNodesSyntax?.self)) - assertNoError(kind, 5, verify(layout[5], as: RawTypeSyntax.self)) + assertNoError(kind, 5, verify(layout[5], as: RawTypeSpecifierListSyntax.self)) assertNoError(kind, 6, verify(layout[6], as: RawUnexpectedNodesSyntax?.self)) + assertNoError(kind, 7, verify(layout[7], as: RawTypeSyntax.self)) + assertNoError(kind, 8, verify(layout[8], as: RawUnexpectedNodesSyntax?.self)) } func validateAvailabilityArgumentListSyntax(kind: SyntaxKind, layout: RawSyntaxBuffer) { for (index, element) in layout.enumerated() { diff --git a/Sources/SwiftSyntax/generated/syntaxNodes/SyntaxNodesAB.swift b/Sources/SwiftSyntax/generated/syntaxNodes/SyntaxNodesAB.swift index c36ad53e31d..4e07265bc4a 100644 --- a/Sources/SwiftSyntax/generated/syntaxNodes/SyntaxNodesAB.swift +++ b/Sources/SwiftSyntax/generated/syntaxNodes/SyntaxNodesAB.swift @@ -3789,6 +3789,7 @@ public struct AttributeSyntax: SyntaxProtocol, SyntaxHashable, _LeafSyntaxNodePr /// /// - `specifiers`: ``TypeSpecifierListSyntax`` /// - `attributes`: ``AttributeListSyntax`` +/// - `lateSpecifiers`: ``TypeSpecifierListSyntax`` /// - `baseType`: ``TypeSyntax`` public struct AttributedTypeSyntax: TypeSyntaxProtocol, SyntaxHashable, _LeafTypeSyntaxNodeProtocol { public let _syntaxNode: Syntax @@ -3809,6 +3810,7 @@ public struct AttributedTypeSyntax: TypeSyntaxProtocol, SyntaxHashable, _LeafTyp /// - 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. /// - specifiers: A list of specifiers that can be attached to the type, such as `inout`, `isolated`, or `consuming`. /// - attributes: A list of attributes that can be attached to the type, such as `@escaping`. + /// - lateSpecifiers: A list of specifiers that can be attached to the type after the attributes, such as 'nonisolated'. /// - baseType: The type to with the specifiers and attributes are applied. /// - 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( @@ -3817,7 +3819,9 @@ public struct AttributedTypeSyntax: TypeSyntaxProtocol, SyntaxHashable, _LeafTyp specifiers: TypeSpecifierListSyntax = [], _ unexpectedBetweenSpecifiersAndAttributes: UnexpectedNodesSyntax? = nil, attributes: AttributeListSyntax = [], - _ unexpectedBetweenAttributesAndBaseType: UnexpectedNodesSyntax? = nil, + _ unexpectedBetweenAttributesAndLateSpecifiers: UnexpectedNodesSyntax? = nil, + lateSpecifiers: TypeSpecifierListSyntax = [], + _ unexpectedBetweenLateSpecifiersAndBaseType: UnexpectedNodesSyntax? = nil, baseType: some TypeSyntaxProtocol, _ unexpectedAfterBaseType: UnexpectedNodesSyntax? = nil, trailingTrivia: Trivia? = nil @@ -3829,7 +3833,9 @@ public struct AttributedTypeSyntax: TypeSyntaxProtocol, SyntaxHashable, _LeafTyp specifiers, unexpectedBetweenSpecifiersAndAttributes, attributes, - unexpectedBetweenAttributesAndBaseType, + unexpectedBetweenAttributesAndLateSpecifiers, + lateSpecifiers, + unexpectedBetweenLateSpecifiersAndBaseType, baseType, unexpectedAfterBaseType ))) { (arena, _) in @@ -3838,7 +3844,9 @@ public struct AttributedTypeSyntax: TypeSyntaxProtocol, SyntaxHashable, _LeafTyp specifiers.raw, unexpectedBetweenSpecifiersAndAttributes?.raw, attributes.raw, - unexpectedBetweenAttributesAndBaseType?.raw, + unexpectedBetweenAttributesAndLateSpecifiers?.raw, + lateSpecifiers.raw, + unexpectedBetweenLateSpecifiersAndBaseType?.raw, baseType.raw, unexpectedAfterBaseType?.raw ] @@ -3945,7 +3953,7 @@ public struct AttributedTypeSyntax: TypeSyntaxProtocol, SyntaxHashable, _LeafTyp .cast(AttributedTypeSyntax.self) } - public var unexpectedBetweenAttributesAndBaseType: UnexpectedNodesSyntax? { + public var unexpectedBetweenAttributesAndLateSpecifiers: UnexpectedNodesSyntax? { get { return Syntax(self).child(at: 4)?.cast(UnexpectedNodesSyntax.self) } @@ -3954,17 +3962,17 @@ public struct AttributedTypeSyntax: TypeSyntaxProtocol, SyntaxHashable, _LeafTyp } } - /// The type to with the specifiers and attributes are applied. - public var baseType: TypeSyntax { + /// A list of specifiers that can be attached to the type after the attributes, such as 'nonisolated'. + public var lateSpecifiers: TypeSpecifierListSyntax { get { - return Syntax(self).child(at: 5)!.cast(TypeSyntax.self) + return Syntax(self).child(at: 5)!.cast(TypeSpecifierListSyntax.self) } set(value) { self = Syntax(self).replacingChild(at: 5, with: Syntax(value), rawAllocationArena: RawSyntaxArena()).cast(AttributedTypeSyntax.self) } } - public var unexpectedAfterBaseType: UnexpectedNodesSyntax? { + public var unexpectedBetweenLateSpecifiersAndBaseType: UnexpectedNodesSyntax? { get { return Syntax(self).child(at: 6)?.cast(UnexpectedNodesSyntax.self) } @@ -3973,12 +3981,33 @@ public struct AttributedTypeSyntax: TypeSyntaxProtocol, SyntaxHashable, _LeafTyp } } + /// The type to with the specifiers and attributes are applied. + public var baseType: TypeSyntax { + get { + return Syntax(self).child(at: 7)!.cast(TypeSyntax.self) + } + set(value) { + self = Syntax(self).replacingChild(at: 7, with: Syntax(value), rawAllocationArena: RawSyntaxArena()).cast(AttributedTypeSyntax.self) + } + } + + public var unexpectedAfterBaseType: UnexpectedNodesSyntax? { + get { + return Syntax(self).child(at: 8)?.cast(UnexpectedNodesSyntax.self) + } + set(value) { + self = Syntax(self).replacingChild(at: 8, with: Syntax(value), rawAllocationArena: RawSyntaxArena()).cast(AttributedTypeSyntax.self) + } + } + public static let structure: SyntaxNodeStructure = .layout([ \Self.unexpectedBeforeSpecifiers, \Self.specifiers, \Self.unexpectedBetweenSpecifiersAndAttributes, \Self.attributes, - \Self.unexpectedBetweenAttributesAndBaseType, + \Self.unexpectedBetweenAttributesAndLateSpecifiers, + \Self.lateSpecifiers, + \Self.unexpectedBetweenLateSpecifiersAndBaseType, \Self.baseType, \Self.unexpectedAfterBaseType ]) From 1856087f45e9463bd55a73a0568628c1e265b5eb Mon Sep 17 00:00:00 2001 From: Doug Gregor Date: Mon, 23 Jun 2025 12:07:26 -0700 Subject: [PATCH 18/33] Parse "late" specifiers for type attributes The only "late" specifier at the moment is `nonisolated`, used by protocol conformances. Fixes issue #3109. --- Sources/SwiftParser/Types.swift | 23 +++++++++++++++++--- Tests/SwiftParserTest/DeclarationTests.swift | 11 ++++++++++ 2 files changed, 31 insertions(+), 3 deletions(-) diff --git a/Sources/SwiftParser/Types.swift b/Sources/SwiftParser/Types.swift index 67ad666c5df..d1ee40187fa 100644 --- a/Sources/SwiftParser/Types.swift +++ b/Sources/SwiftParser/Types.swift @@ -100,6 +100,7 @@ extension Parser { RawAttributedTypeSyntax( specifiers: specifiersAndAttributes.specifiers, attributes: specifiersAndAttributes.attributes, + lateSpecifiers: specifiersAndAttributes.lateSpecifiers, baseType: base, arena: self.arena ) @@ -1221,7 +1222,8 @@ extension Parser { misplacedSpecifiers: [RawTokenSyntax] = [] ) -> ( specifiers: RawTypeSpecifierListSyntax, - attributes: RawAttributeListSyntax + attributes: RawAttributeListSyntax, + lateSpecifiers: RawTypeSpecifierListSyntax )? { var specifiers: [RawTypeSpecifierListSyntax.Element] = [] SPECIFIER_PARSING: while canHaveParameterSpecifier { @@ -1260,7 +1262,14 @@ extension Parser { attributes = nil } - guard !specifiers.isEmpty || attributes != nil else { + // Only handle `nonisolated` as a late specifier. + var lateSpecifiers: [RawTypeSpecifierListSyntax.Element] = [] + if self.at(.keyword(.nonisolated)) && + !(self.peek(isAt: .leftParen) && self.peek().isAtStartOfLine) { + lateSpecifiers.append(parseNonisolatedTypeSpecifier()) + } + + guard !specifiers.isEmpty || attributes != nil || !lateSpecifiers.isEmpty else { // No specifiers or attributes on this type return nil } @@ -1271,9 +1280,17 @@ extension Parser { specifierList = RawTypeSpecifierListSyntax(elements: specifiers, arena: arena) } + let lateSpecifierList: RawTypeSpecifierListSyntax + if lateSpecifiers.isEmpty { + lateSpecifierList = self.emptyCollection(RawTypeSpecifierListSyntax.self) + } else { + lateSpecifierList = RawTypeSpecifierListSyntax(elements: lateSpecifiers, arena: arena) + } + return ( specifierList, - attributes ?? self.emptyCollection(RawAttributeListSyntax.self) + attributes ?? self.emptyCollection(RawAttributeListSyntax.self), + lateSpecifierList ) } diff --git a/Tests/SwiftParserTest/DeclarationTests.swift b/Tests/SwiftParserTest/DeclarationTests.swift index c76a7f78459..6a7bac74f68 100644 --- a/Tests/SwiftParserTest/DeclarationTests.swift +++ b/Tests/SwiftParserTest/DeclarationTests.swift @@ -816,6 +816,17 @@ final class DeclarationTests: ParserTestCase { """ ) + assertParse( + """ + extension Int: @preconcurrency nonisolated Q {} + """ + ) + + assertParse( + """ + extension Int: @unsafe nonisolated Q {} + """ + ) } func testParseDynamicReplacement() { From 1710601d5fe34d7ea9ce0a5473ddbe8aad1e6b16 Mon Sep 17 00:00:00 2001 From: Doug Gregor Date: Mon, 23 Jun 2025 12:20:06 -0700 Subject: [PATCH 19/33] Formatting --- CodeGeneration/Sources/SyntaxSupport/TypeNodes.swift | 8 ++++++-- .../templates/swiftsyntax/SyntaxNodesFile.swift | 3 ++- Sources/SwiftParser/Types.swift | 3 +-- 3 files changed, 9 insertions(+), 5 deletions(-) diff --git a/CodeGeneration/Sources/SyntaxSupport/TypeNodes.swift b/CodeGeneration/Sources/SyntaxSupport/TypeNodes.swift index 1c19e2018d4..12dd1d385cc 100644 --- a/CodeGeneration/Sources/SyntaxSupport/TypeNodes.swift +++ b/CodeGeneration/Sources/SyntaxSupport/TypeNodes.swift @@ -59,8 +59,12 @@ public let TYPE_NODES: [Node] = [ ), Child( name: "lateSpecifiers", - kind: .collection(kind: .typeSpecifierList, collectionElementName: "Specifier", defaultsToEmpty: true, - generateDeprecatedAddFunction: false), + kind: .collection( + kind: .typeSpecifierList, + collectionElementName: "Specifier", + defaultsToEmpty: true, + generateDeprecatedAddFunction: false + ), documentation: "A list of specifiers that can be attached to the type after the attributes, such as 'nonisolated'." ), diff --git a/CodeGeneration/Sources/generate-swift-syntax/templates/swiftsyntax/SyntaxNodesFile.swift b/CodeGeneration/Sources/generate-swift-syntax/templates/swiftsyntax/SyntaxNodesFile.swift index 703448adfd0..3cfc96d85fd 100644 --- a/CodeGeneration/Sources/generate-swift-syntax/templates/swiftsyntax/SyntaxNodesFile.swift +++ b/CodeGeneration/Sources/generate-swift-syntax/templates/swiftsyntax/SyntaxNodesFile.swift @@ -176,7 +176,8 @@ func syntaxNode(nodesStartingWith: [Character]) -> SourceFileSyntax { // If needed, this could be added in the future, but for now withUnexpected should be sufficient. if let childNode = SYNTAX_NODE_MAP[child.syntaxNodeKind]?.collectionNode, !child.isUnexpectedNodes, - case .collection(_, collectionElementName: let childElt?, _, _, generateDeprecatedAddFunction: true) = child.kind + case .collection(_, collectionElementName: let childElt?, _, _, generateDeprecatedAddFunction: true) = child + .kind { let childEltType = childNode.collectionElementType.syntaxBaseName diff --git a/Sources/SwiftParser/Types.swift b/Sources/SwiftParser/Types.swift index d1ee40187fa..16654b66aef 100644 --- a/Sources/SwiftParser/Types.swift +++ b/Sources/SwiftParser/Types.swift @@ -1264,8 +1264,7 @@ extension Parser { // Only handle `nonisolated` as a late specifier. var lateSpecifiers: [RawTypeSpecifierListSyntax.Element] = [] - if self.at(.keyword(.nonisolated)) && - !(self.peek(isAt: .leftParen) && self.peek().isAtStartOfLine) { + if self.at(.keyword(.nonisolated)) && !(self.peek(isAt: .leftParen) && self.peek().isAtStartOfLine) { lateSpecifiers.append(parseNonisolatedTypeSpecifier()) } From 75583ab2b91ab6a3bada1d7a3034f26b5799fbe4 Mon Sep 17 00:00:00 2001 From: Doug Gregor Date: Mon, 23 Jun 2025 12:31:46 -0700 Subject: [PATCH 20/33] Only parse 'nonisolated' as a specifier when we can have specifiers --- Sources/SwiftParser/Types.swift | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/Sources/SwiftParser/Types.swift b/Sources/SwiftParser/Types.swift index 16654b66aef..deb3ef9a617 100644 --- a/Sources/SwiftParser/Types.swift +++ b/Sources/SwiftParser/Types.swift @@ -1264,7 +1264,9 @@ extension Parser { // Only handle `nonisolated` as a late specifier. var lateSpecifiers: [RawTypeSpecifierListSyntax.Element] = [] - if self.at(.keyword(.nonisolated)) && !(self.peek(isAt: .leftParen) && self.peek().isAtStartOfLine) { + if self.at(.keyword(.nonisolated)) && !(self.peek(isAt: .leftParen) && self.peek().isAtStartOfLine) + && canHaveParameterSpecifier + { lateSpecifiers.append(parseNonisolatedTypeSpecifier()) } From ec9139ea0b6117ec19eeffbb2667864acd34200b Mon Sep 17 00:00:00 2001 From: Doug Gregor Date: Mon, 23 Jun 2025 14:11:18 -0700 Subject: [PATCH 21/33] Fix tests --- .../Tests/ValidateSyntaxNodes/ValidateSyntaxNodes.swift | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/CodeGeneration/Tests/ValidateSyntaxNodes/ValidateSyntaxNodes.swift b/CodeGeneration/Tests/ValidateSyntaxNodes/ValidateSyntaxNodes.swift index 164b1505895..e1b9a73df15 100644 --- a/CodeGeneration/Tests/ValidateSyntaxNodes/ValidateSyntaxNodes.swift +++ b/CodeGeneration/Tests/ValidateSyntaxNodes/ValidateSyntaxNodes.swift @@ -55,13 +55,13 @@ fileprivate extension ChildKind { return kind == otherKind case (.nodeChoices(let choices, _), .nodeChoices(let otherChoices, _)): return choices.count == otherChoices.count && zip(choices, otherChoices).allSatisfy { $0.hasSameType(as: $1) } - case (.collection(kind: let kind, _, _, _), .collection(kind: let otherKind, _, _, _)): + case (.collection(kind: let kind, _, _, _, _), .collection(kind: let otherKind, _, _, _, _)): return kind == otherKind case (.token(let choices, _, _), .token(let otherChoices, _, _)): return choices == otherChoices - case (.node(let kind), .collection(kind: let otherKind, _, _, _)): + case (.node(let kind), .collection(kind: let otherKind, _, _, _, _)): return kind == otherKind - case (.collection(kind: let kind, _, _, _), .node(let otherKind)): + case (.collection(kind: let kind, _, _, _, _), .node(let otherKind)): return kind == otherKind default: return false From a298b41033122e865d59b757abb97a59bde6f8b9 Mon Sep 17 00:00:00 2001 From: Doug Gregor Date: Tue, 24 Jun 2025 06:35:50 -0700 Subject: [PATCH 22/33] Improve formatting --- .../templates/swiftsyntax/SyntaxNodesFile.swift | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/CodeGeneration/Sources/generate-swift-syntax/templates/swiftsyntax/SyntaxNodesFile.swift b/CodeGeneration/Sources/generate-swift-syntax/templates/swiftsyntax/SyntaxNodesFile.swift index 3cfc96d85fd..cb3a5f184eb 100644 --- a/CodeGeneration/Sources/generate-swift-syntax/templates/swiftsyntax/SyntaxNodesFile.swift +++ b/CodeGeneration/Sources/generate-swift-syntax/templates/swiftsyntax/SyntaxNodesFile.swift @@ -176,8 +176,8 @@ func syntaxNode(nodesStartingWith: [Character]) -> SourceFileSyntax { // If needed, this could be added in the future, but for now withUnexpected should be sufficient. if let childNode = SYNTAX_NODE_MAP[child.syntaxNodeKind]?.collectionNode, !child.isUnexpectedNodes, - case .collection(_, collectionElementName: let childElt?, _, _, generateDeprecatedAddFunction: true) = child - .kind + case .collection(_, collectionElementName: let childElt?, _, _, generateDeprecatedAddFunction: true) = + child.kind { let childEltType = childNode.collectionElementType.syntaxBaseName From eeaf6e86326e36b8ef2450c856b73652b0bf6d32 Mon Sep 17 00:00:00 2001 From: Anthony Latsis Date: Wed, 25 Jun 2025 04:44:14 +0100 Subject: [PATCH 23/33] [MacroExamples] AddAsync: Handle implicit `Void` result --- .../Implementation/Peer/AddAsyncMacro.swift | 4 +- .../Peer/AddAsyncMacroTests.swift | 78 ++++++++++++++++++- 2 files changed, 78 insertions(+), 4 deletions(-) diff --git a/Examples/Sources/MacroExamples/Implementation/Peer/AddAsyncMacro.swift b/Examples/Sources/MacroExamples/Implementation/Peer/AddAsyncMacro.swift index 923f002c6c4..1ebe01a8a07 100644 --- a/Examples/Sources/MacroExamples/Implementation/Peer/AddAsyncMacro.swift +++ b/Examples/Sources/MacroExamples/Implementation/Peer/AddAsyncMacro.swift @@ -42,7 +42,9 @@ public struct AddAsyncMacro: PeerMacro { } // This only makes sense void functions - if funcDecl.signature.returnClause?.type.as(IdentifierTypeSyntax.self)?.name.text != "Void" { + if let returnClause = funcDecl.signature.returnClause, + returnClause.type.as(IdentifierTypeSyntax.self)?.name.text != "Void" + { throw CustomError.message( "@addAsync requires an function that returns void" ) diff --git a/Examples/Tests/MacroExamples/Implementation/Peer/AddAsyncMacroTests.swift b/Examples/Tests/MacroExamples/Implementation/Peer/AddAsyncMacroTests.swift index 79178633770..f936f425dd0 100644 --- a/Examples/Tests/MacroExamples/Implementation/Peer/AddAsyncMacroTests.swift +++ b/Examples/Tests/MacroExamples/Implementation/Peer/AddAsyncMacroTests.swift @@ -79,6 +79,32 @@ final class AddAsyncMacroTests: XCTestCase { ) } + func testImplicitVoidResult() { + assertMacroExpansion( + """ + @AddAsync + func d(a: Int, completionBlock: @escaping (Bool) -> Void) { + } + """, + expandedSource: """ + func d(a: Int, completionBlock: @escaping (Bool) -> Void) { + } + + func d(a: Int) async -> Bool { + await withCheckedContinuation { continuation in + d(a: a) { returnValue in + + continuation.resume(returning: returnValue) + } + } + + } + """, + macros: macros, + indentationWidth: .spaces(2) + ) + } + func testExpansionOnStoredPropertyEmitsError() { assertMacroExpansion( """ @@ -105,18 +131,18 @@ final class AddAsyncMacroTests: XCTestCase { ) } - func testExpansionOnAsyncFunctionEmitsError() { + func testNonVoidResult() { assertMacroExpansion( """ struct Test { @AddAsync - async func sayHello() { + func sayHello() -> Int { } } """, expandedSource: """ struct Test { - async func sayHello() { + func sayHello() -> Int { } } """, @@ -132,4 +158,50 @@ final class AddAsyncMacroTests: XCTestCase { indentationWidth: .spaces(2) ) } + + func testAlreadyAsync() { + assertMacroExpansion( + """ + @AddAsync + func d(a: Int, completionBlock: @escaping (Bool) -> Void) async { + } + """, + expandedSource: """ + func d(a: Int, completionBlock: @escaping (Bool) -> Void) async { + } + """, + diagnostics: [ + DiagnosticSpec( + message: "@addAsync requires an non async function", + line: 1, + column: 1, + severity: .error + ) + ], + macros: macros, + indentationWidth: .spaces(2) + ) + } + + func testNoCompletionHandler() { + assertMacroExpansion( + """ + @AddAsync + func sayHello(x: Int) {} + """, + expandedSource: """ + func sayHello(x: Int) {} + """, + diagnostics: [ + DiagnosticSpec( + message: "@addAsync requires an function that has a completion handler as last parameter", + line: 1, + column: 1, + severity: .error + ) + ], + macros: macros, + indentationWidth: .spaces(2) + ) + } } From 74a8c07424f020bf4835e44b27a80411e5c809e3 Mon Sep 17 00:00:00 2001 From: Anthony Latsis Date: Wed, 25 Jun 2025 04:44:27 +0100 Subject: [PATCH 24/33] .swift-format: Enable `NoVoidReturnOnFunctionSignature` --- .swift-format | 1 + .../MacroExamples/Playground/PeerMacrosPlayground.swift | 4 ++-- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/.swift-format b/.swift-format index c258291d37e..176687e2845 100644 --- a/.swift-format +++ b/.swift-format @@ -15,5 +15,6 @@ "UseLetInEveryBoundCaseVariable": false, "UseSynthesizedInitializer": false, "ReturnVoidInsteadOfEmptyTuple": true, + "NoVoidReturnOnFunctionSignature": true, } } diff --git a/Examples/Sources/MacroExamples/Playground/PeerMacrosPlayground.swift b/Examples/Sources/MacroExamples/Playground/PeerMacrosPlayground.swift index bc5ba242ca1..f4bfaf335fc 100644 --- a/Examples/Sources/MacroExamples/Playground/PeerMacrosPlayground.swift +++ b/Examples/Sources/MacroExamples/Playground/PeerMacrosPlayground.swift @@ -17,12 +17,12 @@ func runPeerMacrosPlayground() { struct MyStruct { @AddAsync - func c(a: Int, for b: String, _ value: Double, completionBlock: @escaping (Result) -> Void) -> Void { + func c(a: Int, for b: String, _ value: Double, completionBlock: @escaping (Result) -> Void) { completionBlock(.success("a: \(a), b: \(b), value: \(value)")) } @AddAsync - func d(a: Int, for b: String, _ value: Double, completionBlock: @escaping (Bool) -> Void) -> Void { + func d(a: Int, for b: String, _ value: Double, completionBlock: @escaping (Bool) -> Void) { completionBlock(true) } } From 28c2a432d046782a8cf7e4694aaeae07fcb83f6c Mon Sep 17 00:00:00 2001 From: Hamish Knight Date: Fri, 27 Jun 2025 19:26:09 +0100 Subject: [PATCH 25/33] Avoid eating `"""` as closing `"` delimiter Previously we could incorrectly try to eat `"""` as the closing delimiter for e.g `"a"""`. Make sure we only eat a single `"`. --- Sources/SwiftParser/Lexer/Cursor.swift | 116 +++++++++++--------- Tests/SwiftParserTest/ExpressionTests.swift | 56 ++++++++++ 2 files changed, 120 insertions(+), 52 deletions(-) diff --git a/Sources/SwiftParser/Lexer/Cursor.swift b/Sources/SwiftParser/Lexer/Cursor.swift index 13d2af8c289..24262fbbbab 100644 --- a/Sources/SwiftParser/Lexer/Cursor.swift +++ b/Sources/SwiftParser/Lexer/Cursor.swift @@ -79,7 +79,7 @@ extension Lexer.Cursor { /// The lexer has finished lexing the contents of a string literal and is now /// looking for the closing quote. - case afterStringLiteral(isRawString: Bool) + case afterStringLiteral(kind: StringLiteralKind, isRawString: Bool) /// The lexer has lexed the closing quote of a string literal that had raw /// string delimiters and is now looking for the closing raw string delimiters. @@ -453,8 +453,8 @@ extension Lexer.Cursor { result = lexAfterRawStringDelimiter(delimiterLength: delimiterLength) case .inStringLiteral(kind: let stringLiteralKind, delimiterLength: let delimiterLength): result = lexInStringLiteral(stringLiteralKind: stringLiteralKind, delimiterLength: delimiterLength) - case .afterStringLiteral(isRawString: _): - result = lexAfterStringLiteral() + case .afterStringLiteral(kind: let stringLiteralKind, isRawString: _): + result = lexAfterStringLiteral(stringLiteralKind: stringLiteralKind) case .afterClosingStringQuote: result = lexAfterClosingStringQuote() case .inStringInterpolationStart(stringLiteralKind: let stringLiteralKind): @@ -998,7 +998,7 @@ extension Lexer.Cursor { case "0", "1", "2", "3", "4", "5", "6", "7", "8", "9": return self.lexNumber() case #"'"#, #"""#: - return self.lexStringQuote(isOpening: true, leadingDelimiterLength: 0) + return self.lexStringQuote(matchingOpening: nil, leadingDelimiterLength: 0) case "`": return self.lexEscapedIdentifier() @@ -1029,7 +1029,7 @@ extension Lexer.Cursor { private mutating func lexAfterRawStringDelimiter(delimiterLength: Int) -> Lexer.Result { switch self.peek() { case #"'"#, #"""#: - return self.lexStringQuote(isOpening: true, leadingDelimiterLength: delimiterLength) + return self.lexStringQuote(matchingOpening: nil, leadingDelimiterLength: delimiterLength) case nil: return Lexer.Result(.endOfFile) default: @@ -1037,10 +1037,10 @@ extension Lexer.Cursor { } } - private mutating func lexAfterStringLiteral() -> Lexer.Result { + private mutating func lexAfterStringLiteral(stringLiteralKind: StringLiteralKind) -> Lexer.Result { switch self.peek() { case #"'"#, #"""#: - return self.lexStringQuote(isOpening: false, leadingDelimiterLength: 0) + return self.lexStringQuote(matchingOpening: stringLiteralKind, leadingDelimiterLength: 0) case nil: return Lexer.Result(.endOfFile) default: @@ -1796,9 +1796,9 @@ extension Lexer.Cursor { extension Lexer.Cursor { private func stateTransitionAfterLexingStringQuote(kind: StringLiteralKind) -> Lexer.StateTransition { switch currentState { - case .afterStringLiteral(isRawString: true): + case .afterStringLiteral(kind: _, isRawString: true): return .replace(newState: .afterClosingStringQuote) - case .afterStringLiteral(isRawString: false): + case .afterStringLiteral(kind: _, isRawString: false): return .pop case .afterRawStringDelimiter(delimiterLength: let delimiterLength): return .replace(newState: .inStringLiteral(kind: kind, delimiterLength: delimiterLength)) @@ -1809,9 +1809,12 @@ extension Lexer.Cursor { } } - /// `isOpening` is `true` if this string quote is the opening quote of a string - /// literal and `false` if we are lexing the closing quote of a string literal. - mutating func lexStringQuote(isOpening: Bool, leadingDelimiterLength: Int) -> Lexer.Result { + /// `matchingOpening` is the opening literal kind if this string quote is the + /// closing quote of a string literal, `nil` if it's the opening quote. + mutating func lexStringQuote( + matchingOpening: StringLiteralKind?, + leadingDelimiterLength: Int + ) -> Lexer.Result { if self.advance(matching: "'") { return Lexer.Result(.singleQuote, stateTransition: stateTransitionAfterLexingStringQuote(kind: .singleQuote)) } @@ -1819,52 +1822,57 @@ extension Lexer.Cursor { let firstQuoteConsumed = self.advance(matching: #"""#) precondition(firstQuoteConsumed) + // Check to see if we have a multi-line delimiter. If we're matching an + // opening '"' then we want to bail since e.g `"a"""` shouldn't try to eat + // the '"""' as its closing delimiter. var lookingForMultilineString = self - if lookingForMultilineString.advance(matching: #"""#), lookingForMultilineString.advance(matching: #"""#) { - if leadingDelimiterLength > 0 { - // If this is a string literal, check if we have the closing delimiter on the same line to correctly parse things like `#"""#` as a single line string containing a quote. - var isSingleLineString = lookingForMultilineString - - if isSingleLineString.advanceIfStringDelimiter(delimiterLength: leadingDelimiterLength) { - // If we have the correct number of delimiters now, we have something like `#"""#`. - // This is a single-line string. - return Lexer.Result(.stringQuote, stateTransition: stateTransitionAfterLexingStringQuote(kind: .singleLine)) - } + if matchingOpening == .singleLine + || !(lookingForMultilineString.advance(matching: #"""#) && lookingForMultilineString.advance(matching: #"""#)) + { + return Lexer.Result(.stringQuote, stateTransition: stateTransitionAfterLexingStringQuote(kind: .singleLine)) + } - // Scan ahead until the end of the line. Every time we see a closing - // quote, check if it is followed by the correct number of closing delimiters. - while isSingleLineString.is(notAt: "\r", "\n") { - if isSingleLineString.advance(if: { $0 == #"""# }) { - if isSingleLineString.advanceIfStringDelimiter(delimiterLength: leadingDelimiterLength) { - return Lexer.Result( - .stringQuote, - stateTransition: stateTransitionAfterLexingStringQuote(kind: .singleLine) - ) - } - continue - } - _ = isSingleLineString.advance() - } + if leadingDelimiterLength > 0 { + // If this is a string literal, check if we have the closing delimiter on the same line to correctly parse things like `#"""#` as a single line string containing a quote. + var isSingleLineString = lookingForMultilineString + + if isSingleLineString.advanceIfStringDelimiter(delimiterLength: leadingDelimiterLength) { + // If we have the correct number of delimiters now, we have something like `#"""#`. + // This is a single-line string. + return Lexer.Result(.stringQuote, stateTransition: stateTransitionAfterLexingStringQuote(kind: .singleLine)) } - self = lookingForMultilineString - let trailingTriviaLexingMode: TriviaLexingMode? - if isOpening && self.is(at: "\n", "\r") { - // The opening quote of a multi-line string literal must be followed by - // a newline that's not part of the represented string. - trailingTriviaLexingMode = .escapedNewlineInMultiLineStringLiteral - } else { - trailingTriviaLexingMode = nil + // Scan ahead until the end of the line. Every time we see a closing + // quote, check if it is followed by the correct number of closing delimiters. + while isSingleLineString.is(notAt: "\r", "\n") { + if isSingleLineString.advance(if: { $0 == #"""# }) { + if isSingleLineString.advanceIfStringDelimiter(delimiterLength: leadingDelimiterLength) { + return Lexer.Result( + .stringQuote, + stateTransition: stateTransitionAfterLexingStringQuote(kind: .singleLine) + ) + } + continue + } + _ = isSingleLineString.advance() } + } - return Lexer.Result( - .multilineStringQuote, - stateTransition: stateTransitionAfterLexingStringQuote(kind: .multiLine), - trailingTriviaLexingMode: trailingTriviaLexingMode - ) + self = lookingForMultilineString + let trailingTriviaLexingMode: TriviaLexingMode? + if matchingOpening == nil && self.is(at: "\n", "\r") { + // The opening quote of a multi-line string literal must be followed by + // a newline that's not part of the represented string. + trailingTriviaLexingMode = .escapedNewlineInMultiLineStringLiteral } else { - return Lexer.Result(.stringQuote, stateTransition: stateTransitionAfterLexingStringQuote(kind: .singleLine)) + trailingTriviaLexingMode = nil } + + return Lexer.Result( + .multilineStringQuote, + stateTransition: stateTransitionAfterLexingStringQuote(kind: .multiLine), + trailingTriviaLexingMode: trailingTriviaLexingMode + ) } /// Returns `true` if the cursor is positioned at `\##(` with `delimiterLength` @@ -1935,7 +1943,9 @@ extension Lexer.Cursor { return Lexer.Result( .stringSegment, error: error, - stateTransition: .replace(newState: .afterStringLiteral(isRawString: delimiterLength > 0)) + stateTransition: .replace( + newState: .afterStringLiteral(kind: stringLiteralKind, isRawString: delimiterLength > 0) + ) ) default: break @@ -1967,7 +1977,9 @@ extension Lexer.Cursor { return Lexer.Result( .stringSegment, error: error, - stateTransition: .replace(newState: .afterStringLiteral(isRawString: delimiterLength > 0)) + stateTransition: .replace( + newState: .afterStringLiteral(kind: stringLiteralKind, isRawString: delimiterLength > 0) + ) ) } } diff --git a/Tests/SwiftParserTest/ExpressionTests.swift b/Tests/SwiftParserTest/ExpressionTests.swift index 40e31d34fc7..e47abffd938 100644 --- a/Tests/SwiftParserTest/ExpressionTests.swift +++ b/Tests/SwiftParserTest/ExpressionTests.swift @@ -1882,6 +1882,62 @@ final class ExpressionTests: ParserTestCase { ) } + func testInvalidMultiLineClosingDelimiter() { + assertParse( + #""" + "a"1️⃣""2️⃣ a3️⃣ a4️⃣ℹ️"""5️⃣ + """#, + diagnostics: [ + DiagnosticSpec( + locationMarker: "1️⃣", + message: "consecutive statements on a line must be separated by newline or ';'", + fixIts: [ + "insert newline", "insert ';'", + ] + ), + DiagnosticSpec( + locationMarker: "2️⃣", + message: "consecutive statements on a line must be separated by newline or ';'", + fixIts: [ + "insert newline", "insert ';'", + ] + ), + DiagnosticSpec( + locationMarker: "3️⃣", + message: "consecutive statements on a line must be separated by newline or ';'", + fixIts: [ + "insert newline", "insert ';'", + ] + ), + DiagnosticSpec( + locationMarker: "4️⃣", + message: "consecutive statements on a line must be separated by newline or ';'", + fixIts: [ + "insert newline", "insert ';'", + ] + ), + DiagnosticSpec( + locationMarker: "5️⃣", + message: #"expected '"""' to end string literal"#, + notes: [ + NoteSpec(message: #"to match this opening '"""'"#) + ], + fixIts: [ + #"insert '"""'"# + ] + ), + ], + fixedSource: #""" + "a" + "" + a + a + """ + """ + """# + ) + } + func testEmptyLineInMultilineStringLiteral() { assertParse( #""" From fdaf1be16322c76985385e4f1c627d158b6bac3b Mon Sep 17 00:00:00 2001 From: Anthony Latsis Date: Tue, 1 Jul 2025 22:33:07 +0100 Subject: [PATCH 26/33] SwiftIDEUtilsTest: Add tests for `FixItApplier` --- .../SwiftIDEUtilsTest/FixItApplierTests.swift | 341 ++++++++++++++++++ 1 file changed, 341 insertions(+) create mode 100644 Tests/SwiftIDEUtilsTest/FixItApplierTests.swift diff --git a/Tests/SwiftIDEUtilsTest/FixItApplierTests.swift b/Tests/SwiftIDEUtilsTest/FixItApplierTests.swift new file mode 100644 index 00000000000..51540be17bf --- /dev/null +++ b/Tests/SwiftIDEUtilsTest/FixItApplierTests.swift @@ -0,0 +1,341 @@ +//===----------------------------------------------------------------------===// +// +// This source file is part of the Swift.org open source project +// +// Copyright (c) 2025 Apple Inc. and the Swift project authors +// Licensed under Apache License v2.0 with Runtime Library Exception +// +// See https://swift.org/LICENSE.txt for license information +// See https://swift.org/CONTRIBUTORS.txt for the list of Swift project authors +// +//===----------------------------------------------------------------------===// + +@_spi(FixItApplier) import SwiftIDEUtils +import SwiftSyntax +import XCTest + +private extension SourceEdit { + init(range: Range, replacement: String) { + self.init( + range: AbsolutePosition(utf8Offset: range.lowerBound)..) { + if subrange.isEmpty { return } + var lower = subrange.lowerBound + var upper = subrange.upperBound + while lower < upper { + formIndex(before: &upper) + swapAt(lower, upper) + formIndex(after: &lower) + } + } +} + +private extension MutableCollection where Self: BidirectionalCollection, Element: Comparable { + /// Permutes this collection's elements through all the lexical orderings. + /// + /// Call `nextPermutation()` repeatedly starting with the collection in sorted + /// order. When the full cycle of all permutations has been completed, the + /// collection will be back in sorted order and this method will return + /// `false`. + /// + /// - Returns: A Boolean value indicating whether the collection still has + /// remaining permutations. When this method returns `false`, the collection + /// is in ascending order according to `areInIncreasingOrder`. + /// + /// - Complexity: O(*n*), where *n* is the length of the collection. + mutating func nextPermutation(upperBound: Index? = nil) -> Bool { + // Ensure we have > 1 element in the collection. + guard !isEmpty else { return false } + var i = index(before: endIndex) + if i == startIndex { return false } + + let upperBound = upperBound ?? endIndex + + while true { + let ip1 = i + formIndex(before: &i) + + // Find the last ascending pair (ie. ..., a, b, ... where a < b) + if self[i] < self[ip1] { + // Find the last element greater than self[i] + // swift-format-ignore: NeverForceUnwrap + // This is _always_ at most `ip1` due to if statement above + let j = lastIndex(where: { self[i] < $0 })! + + // At this point we have something like this: + // 0, 1, 4, 3, 2 + // ^ ^ + // i j + swapAt(i, j) + self.reverse(subrange: ip1.. Date: Wed, 2 Jul 2025 12:02:31 +0100 Subject: [PATCH 27/33] SwiftSyntax: Conform `AbsolutePosition` to `Strideable` --- Sources/SwiftIDEUtils/FixItApplier.swift | 16 +++++----------- Sources/SwiftSyntax/AbsolutePosition.swift | 12 +++++++++--- 2 files changed, 14 insertions(+), 14 deletions(-) diff --git a/Sources/SwiftIDEUtils/FixItApplier.swift b/Sources/SwiftIDEUtils/FixItApplier.swift index de5785ff941..3c242c702f3 100644 --- a/Sources/SwiftIDEUtils/FixItApplier.swift +++ b/Sources/SwiftIDEUtils/FixItApplier.swift @@ -68,7 +68,7 @@ public enum FixItApplier { source.replaceSubrange(startIndex.. SourceEdit? in - if remainingEdit.replacementRange.overlaps(edit.replacementRange) { + if remainingEdit.range.overlaps(edit.range) { // The edit overlaps with the previous edit. We can't apply both // without conflicts. Apply the one that's listed first and drop the // later edit. @@ -78,12 +78,10 @@ public enum FixItApplier { // If the remaining edit starts after or at the end of the edit that we just applied, // shift it by the current edit's difference in length. if edit.endUtf8Offset <= remainingEdit.startUtf8Offset { - let startPosition = AbsolutePosition( - utf8Offset: remainingEdit.startUtf8Offset - edit.replacementRange.count + edit.replacementLength.utf8Length - ) - let endPosition = AbsolutePosition( - utf8Offset: remainingEdit.endUtf8Offset - edit.replacementRange.count + edit.replacementLength.utf8Length - ) + let shift = edit.replacementLength.utf8Length - edit.range.count + let startPosition = AbsolutePosition(utf8Offset: remainingEdit.startUtf8Offset + shift) + let endPosition = AbsolutePosition(utf8Offset: remainingEdit.endUtf8Offset + shift) + return SourceEdit(range: startPosition.. { - return startUtf8Offset.. Bool { + return lhs.utf8Offset < rhs.utf8Offset + } +} + +extension AbsolutePosition: Strideable { public func advanced(by offset: Int) -> AbsolutePosition { - return AbsolutePosition(utf8Offset: self.utf8Offset + offset) + AbsolutePosition(utf8Offset: self.utf8Offset + offset) } - public static func < (lhs: AbsolutePosition, rhs: AbsolutePosition) -> Bool { - return lhs.utf8Offset < rhs.utf8Offset + public func distance(to other: AbsolutePosition) -> Int { + self.utf8Offset.distance(to: other.utf8Offset) } } From 9084149697fdd1727bf24e4f0a8dc2b421170473 Mon Sep 17 00:00:00 2001 From: Anthony Latsis Date: Wed, 2 Jul 2025 15:58:12 +0100 Subject: [PATCH 28/33] FixItApplier: Accept syntax tree generically --- Sources/SwiftIDEUtils/FixItApplier.swift | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Sources/SwiftIDEUtils/FixItApplier.swift b/Sources/SwiftIDEUtils/FixItApplier.swift index 3c242c702f3..b27641956b1 100644 --- a/Sources/SwiftIDEUtils/FixItApplier.swift +++ b/Sources/SwiftIDEUtils/FixItApplier.swift @@ -32,7 +32,7 @@ public enum FixItApplier { public static func applyFixes( from diagnostics: [Diagnostic], filterByMessages messages: [String]?, - to tree: any SyntaxProtocol + to tree: some SyntaxProtocol ) -> String { let messages = messages ?? diagnostics.compactMap { $0.fixIts.first?.message.message } @@ -54,7 +54,7 @@ public enum FixItApplier { /// - Returns: A `String` representation of the modified syntax tree after applying the edits. public static func apply( edits: [SourceEdit], - to tree: any SyntaxProtocol + to tree: some SyntaxProtocol ) -> String { var edits = edits var source = tree.description From b953b57af39630d26c7f54e375198a4bd7a4d0fb Mon Sep 17 00:00:00 2001 From: Anthony Latsis Date: Tue, 1 Jul 2025 23:43:52 +0100 Subject: [PATCH 29/33] FixItApplier: Eliminate quadratic `compactMap` --- Sources/SwiftIDEUtils/FixItApplier.swift | 44 +++++++++++++++++------- 1 file changed, 32 insertions(+), 12 deletions(-) diff --git a/Sources/SwiftIDEUtils/FixItApplier.swift b/Sources/SwiftIDEUtils/FixItApplier.swift index b27641956b1..539e4a310b6 100644 --- a/Sources/SwiftIDEUtils/FixItApplier.swift +++ b/Sources/SwiftIDEUtils/FixItApplier.swift @@ -46,12 +46,12 @@ public enum FixItApplier { return self.apply(edits: edits, to: tree) } - /// Apply the given edits to the syntax tree. + /// Applies the given edits to the given syntax tree. /// /// - Parameters: - /// - edits: The edits to apply to the syntax tree - /// - tree: he syntax tree to which the edits should be applied. - /// - Returns: A `String` representation of the modified syntax tree after applying the edits. + /// - edits: The edits to apply. + /// - tree: The syntax tree to which the edits should be applied. + /// - Returns: A `String` representation of the modified syntax tree. public static func apply( edits: [SourceEdit], to tree: some SyntaxProtocol @@ -62,17 +62,27 @@ public enum FixItApplier { while let edit = edits.first { edits = Array(edits.dropFirst()) + // Empty edits do nothing. + guard !edit.isEmpty else { + continue + } + let startIndex = source.utf8.index(source.utf8.startIndex, offsetBy: edit.startUtf8Offset) let endIndex = source.utf8.index(source.utf8.startIndex, offsetBy: edit.endUtf8Offset) source.replaceSubrange(startIndex.. SourceEdit? in - if remainingEdit.range.overlaps(edit.range) { + // Drop any subsequent edits that conflict with one we just applied, and + // adjust the range of the rest. + for i in edits.indices { + let remainingEdit = edits[i] + + guard !remainingEdit.range.overlaps(edit.range) else { // The edit overlaps with the previous edit. We can't apply both - // without conflicts. Apply the one that's listed first and drop the - // later edit. - return nil + // without conflicts. Drop this one by swapping it for a no-op + // edit. + edits[i] = SourceEdit() + continue } // If the remaining edit starts after or at the end of the edit that we just applied, @@ -82,10 +92,8 @@ public enum FixItApplier { let startPosition = AbsolutePosition(utf8Offset: remainingEdit.startUtf8Offset + shift) let endPosition = AbsolutePosition(utf8Offset: remainingEdit.endUtf8Offset + shift) - return SourceEdit(range: startPosition.. Date: Wed, 2 Jul 2025 07:09:05 +0100 Subject: [PATCH 30/33] FixItApplier: Eliminate quadratic `dropFirst` --- Sources/SwiftIDEUtils/FixItApplier.swift | 33 +++++++++++++++++------- 1 file changed, 24 insertions(+), 9 deletions(-) diff --git a/Sources/SwiftIDEUtils/FixItApplier.swift b/Sources/SwiftIDEUtils/FixItApplier.swift index 539e4a310b6..22d873e5435 100644 --- a/Sources/SwiftIDEUtils/FixItApplier.swift +++ b/Sources/SwiftIDEUtils/FixItApplier.swift @@ -59,29 +59,37 @@ public enum FixItApplier { var edits = edits var source = tree.description - while let edit = edits.first { - edits = Array(edits.dropFirst()) + for var editIndex in edits.indices { + let edit = edits[editIndex] // Empty edits do nothing. guard !edit.isEmpty else { continue } - let startIndex = source.utf8.index(source.utf8.startIndex, offsetBy: edit.startUtf8Offset) - let endIndex = source.utf8.index(source.utf8.startIndex, offsetBy: edit.endUtf8Offset) + do { + let utf8 = source.utf8 + let startIndex = utf8.index(utf8.startIndex, offsetBy: edit.startUtf8Offset) + let endIndex = utf8.index(utf8.startIndex, offsetBy: edit.endUtf8Offset) - source.replaceSubrange(startIndex.. Index { + self.formIndex(after: &index) as Void + return index + } +} + private extension SourceEdit { var startUtf8Offset: Int { return range.lowerBound.utf8Offset From df0f832da66c7f509674c725b048d3fbf5286045 Mon Sep 17 00:00:00 2001 From: Anthony Latsis Date: Wed, 2 Jul 2025 15:55:34 +0100 Subject: [PATCH 31/33] FixItApplier: Add parameter for skipping duplicate insertions --- Sources/SwiftIDEUtils/FixItApplier.swift | 33 ++++- .../SwiftIDEUtilsTest/FixItApplierTests.swift | 138 ++++++++++++++---- 2 files changed, 134 insertions(+), 37 deletions(-) diff --git a/Sources/SwiftIDEUtils/FixItApplier.swift b/Sources/SwiftIDEUtils/FixItApplier.swift index 22d873e5435..241a57c89bc 100644 --- a/Sources/SwiftIDEUtils/FixItApplier.swift +++ b/Sources/SwiftIDEUtils/FixItApplier.swift @@ -27,12 +27,15 @@ public enum FixItApplier { /// - filterByMessages: An optional array of message strings to filter which Fix-Its to apply. /// If `nil`, the first Fix-It from each diagnostic is applied. /// - tree: The syntax tree to which the Fix-Its will be applied. + /// - allowDuplicateInsertions: Whether to apply duplicate insertions. + /// Defaults to `true`. /// /// - Returns: A `String` representation of the modified syntax tree after applying the Fix-Its. public static func applyFixes( from diagnostics: [Diagnostic], filterByMessages messages: [String]?, - to tree: some SyntaxProtocol + to tree: some SyntaxProtocol, + allowDuplicateInsertions: Bool = true ) -> String { let messages = messages ?? diagnostics.compactMap { $0.fixIts.first?.message.message } @@ -43,7 +46,7 @@ public enum FixItApplier { .filter { messages.contains($0.message.message) } .flatMap(\.edits) - return self.apply(edits: edits, to: tree) + return self.apply(edits: edits, to: tree, allowDuplicateInsertions: allowDuplicateInsertions) } /// Applies the given edits to the given syntax tree. @@ -51,10 +54,14 @@ public enum FixItApplier { /// - Parameters: /// - edits: The edits to apply. /// - tree: The syntax tree to which the edits should be applied. + /// - allowDuplicateInsertions: Whether to apply duplicate insertions. + /// Defaults to `true`. + /// /// - Returns: A `String` representation of the modified syntax tree. public static func apply( edits: [SourceEdit], - to tree: some SyntaxProtocol + to tree: some SyntaxProtocol, + allowDuplicateInsertions: Bool = true ) -> String { var edits = edits var source = tree.description @@ -85,10 +92,22 @@ public enum FixItApplier { continue } - guard !remainingEdit.range.overlaps(edit.range) else { - // The edit overlaps with the previous edit. We can't apply both - // without conflicts. Drop this one by swapping it for a no-op - // edit. + func shouldDropRemainingEdit() -> Bool { + // Insertions never conflict between themselves, unless we were asked + // to drop duplicate insertions. + if edit.range.isEmpty && remainingEdit.range.isEmpty { + if allowDuplicateInsertions { + return false + } + + return edit == remainingEdit + } + + return remainingEdit.range.overlaps(edit.range) + } + + guard !shouldDropRemainingEdit() else { + // Drop the edit by swapping it for an empty one. edits[editIndex] = SourceEdit() continue } diff --git a/Tests/SwiftIDEUtilsTest/FixItApplierTests.swift b/Tests/SwiftIDEUtilsTest/FixItApplierTests.swift index 51540be17bf..0761b3978a8 100644 --- a/Tests/SwiftIDEUtilsTest/FixItApplierTests.swift +++ b/Tests/SwiftIDEUtilsTest/FixItApplierTests.swift @@ -166,7 +166,9 @@ class FixItApplierApplyEditsTests: XCTestCase { .init(range: 3..<7, replacement: "cd"), ], // The second edit is skipped. - possibleOutputs: ["aboo = 1", "varcd = 1"] + outputs: [ + .init(oneOf: "aboo = 1", "varcd = 1") + ] ) } @@ -180,19 +182,36 @@ class FixItApplierApplyEditsTests: XCTestCase { .init(range: 0..<5, replacement: "_"), .init(range: 0..<3, replacement: "let"), ], - possibleOutputs: ["_ = 11", "let x = 11"] + outputs: [ + .init(oneOf: "_ = 11", "let x = 11") + ] ) } func testOverlappingInsertions() { + assertAppliedEdits( + to: "x = 1", + edits: [ + .init(range: 1..<1, replacement: "y"), + .init(range: 1..<1, replacement: "z"), + ], + outputs: [ + .init(oneOf: "xyz = 1", "xzy = 1") + ] + ) + assertAppliedEdits( to: "x = 1", edits: [ .init(range: 0..<0, replacement: "var "), .init(range: 0..<0, replacement: "var "), + .init(range: 4..<5, replacement: "2"), .init(range: 0..<0, replacement: "var "), ], - output: "var var var x = 1" + outputs: [ + .init(deterministic: "var var var x = 2", allowDuplicateInsertions: true), + .init(deterministic: "var x = 2", allowDuplicateInsertions: false), + ] ) } @@ -214,47 +233,105 @@ class FixItApplierApplyEditsTests: XCTestCase { .init(range: 2..<2, replacement: "a"), // Insertion ], // FIXME: This behavior where these edits are not considered overlapping doesn't feel desirable - possibleOutputs: ["_x = 1", "_ a= 1"] + outputs: [ + .init(oneOf: "_x = 1", "_ a= 1") + ] ) } } -/// Asserts that at least one element in `possibleOutputs` matches the result -/// of applying an array of edits to `input`, for all permutations of `edits`. +/// Represents an output expectation. +private struct OutputExpectation { + var possibleOutputs: [String] + var allowDuplicateInsertions: Bool? + var line: UInt + + /// Create a deterministic output expectation for the given value of + /// `allowDuplicateInsertions`. If `allowDuplicateInsertions` is `nil`, + /// the expectation holds for both `true` and `false`. + init( + deterministic output: String, + allowDuplicateInsertions: Bool? = nil, + line: UInt = #line + ) { + self.possibleOutputs = [output] + self.allowDuplicateInsertions = allowDuplicateInsertions + self.line = line + } + + /// Create a "one of the given possible outputs" expectation for the given + /// value of `allowDuplicateInsertions`. If `allowDuplicateInsertions` is + /// `nil`, the expectation holds for both `true` and `false`. + init( + oneOf possibleOutputs: String..., + allowDuplicateInsertions: Bool? = nil, + line: UInt = #line + ) { + self.possibleOutputs = possibleOutputs + self.allowDuplicateInsertions = allowDuplicateInsertions + self.line = line + } +} + +/// Asserts that the given outputs match the result of applying an array of +/// edits to `input`, for all permutations of `edits`. private func assertAppliedEdits( to tree: SourceFileSyntax, edits: [SourceEdit], - possibleOutputs: [String], - line: UInt = #line + outputs: [OutputExpectation] ) { - precondition(!possibleOutputs.isEmpty) + precondition(!outputs.isEmpty) - var indices = Array(edits.indices) - while true { - let editsPermutation = indices.map { edits[$0] } + NEXT_OUTPUT: for output in outputs { + let allowDuplicateInsertionsValues = + if let value = output.allowDuplicateInsertions { + [value] + } else { + [true, false] + } - let actualOutput = FixItApplier.apply(edits: editsPermutation, to: tree) - guard possibleOutputs.contains(actualOutput) else { - XCTFail( - """ - Actual output \"\(actualOutput)\" does not match one of \(possibleOutputs) - Edits: - \(editsPermutation) - """, - line: line - ) - return - } + let possibleOutputs = output.possibleOutputs + + // Check this output against all permutations of edits. + var indices = Array(edits.indices) + while true { + let editsPermutation = indices.map { edits[$0] } - let keepGoing = indices.nextPermutation() - guard keepGoing else { - break + for allowDuplicateInsertionsValue in allowDuplicateInsertionsValues { + let actualOutput = FixItApplier.apply( + edits: editsPermutation, + to: tree, + allowDuplicateInsertions: allowDuplicateInsertionsValue + ) + + guard possibleOutputs.contains(actualOutput) else { + XCTFail( + """ + Actual output \"\(actualOutput)\" does not match one of \(possibleOutputs) + Edits: + \(editsPermutation) + allowDuplicateInsertions: + \(allowDuplicateInsertionsValue) + """, + line: output.line + ) + + // Fail once for all permutations to avoid excessive logging. + continue NEXT_OUTPUT + } + } + + let keepGoing = indices.nextPermutation() + guard keepGoing else { + break + } } } } /// Asserts that `output` matches the result of applying an array of edits to -/// `input`, for all permutations of `edits`. +/// `input`, for all permutations of `edits` and for `allowDuplicateInsertions` +/// both `true` and `false`. private func assertAppliedEdits( to tree: SourceFileSyntax, edits: [SourceEdit], @@ -264,8 +341,9 @@ private func assertAppliedEdits( assertAppliedEdits( to: tree, edits: edits, - possibleOutputs: [output], - line: line + outputs: [ + .init(deterministic: output, allowDuplicateInsertions: nil, line: line) + ] ) } From a00cbca02ca7997e722228846966d5492871f47f Mon Sep 17 00:00:00 2001 From: Anthony Latsis Date: Wed, 2 Jul 2025 16:30:11 +0100 Subject: [PATCH 32/33] FixItApplier: Non-empty insertions can conflict with other edits --- Sources/SwiftIDEUtils/FixItApplier.swift | 10 +++++++++- Tests/SwiftIDEUtilsTest/FixItApplierTests.swift | 3 +-- 2 files changed, 10 insertions(+), 3 deletions(-) diff --git a/Sources/SwiftIDEUtils/FixItApplier.swift b/Sources/SwiftIDEUtils/FixItApplier.swift index 241a57c89bc..6253710930d 100644 --- a/Sources/SwiftIDEUtils/FixItApplier.swift +++ b/Sources/SwiftIDEUtils/FixItApplier.swift @@ -103,7 +103,15 @@ public enum FixItApplier { return edit == remainingEdit } - return remainingEdit.range.overlaps(edit.range) + // Edits conflict in the following cases: + // + // - Their ranges have a common element. + // - One's range is empty and its lower bound is strictly within the + // other's range. So 0..<2 also conflicts with 1..<1, but not with + // 0..<0 or 2..<2. + // + return edit.endUtf8Offset > remainingEdit.startUtf8Offset + && edit.startUtf8Offset < remainingEdit.endUtf8Offset } guard !shouldDropRemainingEdit() else { diff --git a/Tests/SwiftIDEUtilsTest/FixItApplierTests.swift b/Tests/SwiftIDEUtilsTest/FixItApplierTests.swift index 0761b3978a8..d45be14f3c5 100644 --- a/Tests/SwiftIDEUtilsTest/FixItApplierTests.swift +++ b/Tests/SwiftIDEUtilsTest/FixItApplierTests.swift @@ -232,9 +232,8 @@ class FixItApplierApplyEditsTests: XCTestCase { .init(range: 0..<5, replacement: "_"), // Replacement .init(range: 2..<2, replacement: "a"), // Insertion ], - // FIXME: This behavior where these edits are not considered overlapping doesn't feel desirable outputs: [ - .init(oneOf: "_x = 1", "_ a= 1") + .init(oneOf: "_ = 1", "vaar x = 1") ] ) } From acff7c11b3688a9e9501726fb652b74ab2c77b01 Mon Sep 17 00:00:00 2001 From: Ueeek Date: Sat, 12 Jul 2025 13:47:06 +0900 Subject: [PATCH 33/33] typo: Avaialability -> Availability --- Tests/SwiftLexicalLookupTest/NameLookupTests.swift | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Tests/SwiftLexicalLookupTest/NameLookupTests.swift b/Tests/SwiftLexicalLookupTest/NameLookupTests.swift index 9594ec5e350..6c800128a35 100644 --- a/Tests/SwiftLexicalLookupTest/NameLookupTests.swift +++ b/Tests/SwiftLexicalLookupTest/NameLookupTests.swift @@ -717,7 +717,7 @@ final class testNameLookup: XCTestCase { ) } - func testTypeDeclAvaialabilityInSequentialScope() { + func testTypeDeclAvailabilityInSequentialScope() { let declExpectation: [ResultExpectation] = [ .fromScope( CodeBlockSyntax.self,