From 95bebf62c5498f49eead0bfaf64ddd020b9013f6 Mon Sep 17 00:00:00 2001 From: Pavel Yaskevich Date: Fri, 30 May 2025 00:39:36 -0700 Subject: [PATCH] [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 + } + """ + ) + } +}