From 35a9cde9560eafb447bd97b58f52822560d1f948 Mon Sep 17 00:00:00 2001 From: Pavel Yaskevich Date: Fri, 4 Apr 2025 16:32:29 -0700 Subject: [PATCH 01/11] [SwiftParser] Allow nonisolated to accept `nonsending` modifier --- .../Sources/SyntaxSupport/KeywordSpec.swift | 3 ++ Sources/SwiftParser/Modifiers.swift | 6 +++- Sources/SwiftParser/TokenPrecedence.swift | 1 + Sources/SwiftSyntax/generated/Keyword.swift | 4 +++ Tests/SwiftParserTest/DeclarationTests.swift | 35 +++++++++++++++++-- 5 files changed, 45 insertions(+), 4 deletions(-) diff --git a/CodeGeneration/Sources/SyntaxSupport/KeywordSpec.swift b/CodeGeneration/Sources/SyntaxSupport/KeywordSpec.swift index 2cf01f217b0..7c4b72eeafb 100644 --- a/CodeGeneration/Sources/SyntaxSupport/KeywordSpec.swift +++ b/CodeGeneration/Sources/SyntaxSupport/KeywordSpec.swift @@ -210,6 +210,7 @@ public enum Keyword: CaseIterable { case none case nonisolated case nonmutating + case nonsending case objc case obsoleted case of @@ -551,6 +552,8 @@ public enum Keyword: CaseIterable { return KeywordSpec("nonisolated") case .nonmutating: return KeywordSpec("nonmutating") + case .nonsending: + return KeywordSpec("nonsending") case .objc: return KeywordSpec("objc") case .obsoleted: diff --git a/Sources/SwiftParser/Modifiers.swift b/Sources/SwiftParser/Modifiers.swift index 73a6066a9cf..57ef2b302ae 100644 --- a/Sources/SwiftParser/Modifiers.swift +++ b/Sources/SwiftParser/Modifiers.swift @@ -236,7 +236,11 @@ extension Parser { let detail: RawDeclModifierDetailSyntax? if self.at(.leftParen) { let (unexpectedBeforeLeftParen, leftParen) = self.expect(.leftParen) - let (unexpectedBeforeDetailToken, detailToken) = self.expect(TokenSpec(.unsafe, remapping: .identifier)) + let (unexpectedBeforeDetailToken, detailToken) = self.expect( + TokenSpec(.unsafe, remapping: .identifier), + TokenSpec(.nonsending, remapping: .identifier), + default: TokenSpec(.identifier) + ) let (unexpectedBeforeRightParen, rightParen) = self.expect(.rightParen) detail = RawDeclModifierDetailSyntax( unexpectedBeforeLeftParen, diff --git a/Sources/SwiftParser/TokenPrecedence.swift b/Sources/SwiftParser/TokenPrecedence.swift index 39784f5d2aa..01fdbc06f78 100644 --- a/Sources/SwiftParser/TokenPrecedence.swift +++ b/Sources/SwiftParser/TokenPrecedence.swift @@ -326,6 +326,7 @@ enum TokenPrecedence: Comparable { .module, .noasync, .none, + .nonsending, .obsoleted, .of, .Protocol, diff --git a/Sources/SwiftSyntax/generated/Keyword.swift b/Sources/SwiftSyntax/generated/Keyword.swift index 2abff52c6f4..20b36ff1455 100644 --- a/Sources/SwiftSyntax/generated/Keyword.swift +++ b/Sources/SwiftSyntax/generated/Keyword.swift @@ -155,6 +155,7 @@ public enum Keyword: UInt8, Hashable, Sendable { case none case nonisolated case nonmutating + case nonsending case objc case obsoleted case of @@ -584,6 +585,8 @@ public enum Keyword: UInt8, Hashable, Sendable { self = .higherThan case "introduced": self = .introduced + case "nonsending": + self = .nonsending case "visibility": self = .visibility default: @@ -893,6 +896,7 @@ public enum Keyword: UInt8, Hashable, Sendable { "none", "nonisolated", "nonmutating", + "nonsending", "objc", "obsoleted", "of", diff --git a/Tests/SwiftParserTest/DeclarationTests.swift b/Tests/SwiftParserTest/DeclarationTests.swift index 261edaf59e6..7ec097ec5bd 100644 --- a/Tests/SwiftParserTest/DeclarationTests.swift +++ b/Tests/SwiftParserTest/DeclarationTests.swift @@ -261,8 +261,8 @@ final class DeclarationTests: ParserTestCase { """, diagnostics: [ DiagnosticSpec( - message: "expected 'unsafe' in modifier", - fixIts: ["replace 'safe' with 'unsafe'"] + message: "expected identifier in modifier", + fixIts: ["replace 'safe' with identifier"] ) ], fixedSource: """ @@ -271,7 +271,36 @@ final class DeclarationTests: ParserTestCase { struct A { nonisolated(unsafe) let b = 0 nonisolated(unsafe) var c: Int { 0 } - nonisolated(unsafe) let d = 0 + nonisolated(<#identifier#>) let d = 0 + } + """ + ) + } + + func testNonisolatedNonSendingParsing() { + assertParse( + """ + nonisolated(nonsending) let a = 0 + + struct A { + nonisolated(nonsending) let b = 0 + nonisolated(nonsending) var c: Int { 0 } + nonisolated(1️⃣sending) let d = 0 + } + """, + diagnostics: [ + DiagnosticSpec( + message: "expected identifier in modifier", + fixIts: ["replace 'sending' with identifier"] + ) + ], + fixedSource: """ + nonisolated(nonsending) let a = 0 + + struct A { + nonisolated(nonsending) let b = 0 + nonisolated(nonsending) var c: Int { 0 } + nonisolated(<#identifier#>) let d = 0 } """ ) From a7f65cd80ec35a587a7e57c35f662f1b6dffc981 Mon Sep 17 00:00:00 2001 From: Pavel Yaskevich Date: Mon, 7 Apr 2025 14:39:45 -0700 Subject: [PATCH 02/11] [SwiftParser] Allow `nonisolated` modifier to have a single `nonsending` argument This is part of SE-0461 proposal where `nonisolated` has to be marked as `nonsending` in type context to indicate that the function type it's attached to is caller isolated. --- .../SyntaxSupport/SyntaxNodeKind.swift | 3 + .../Sources/SyntaxSupport/TypeNodes.swift | 49 ++- Sources/SwiftParser/Types.swift | 36 +++ .../generated/Parser+TokenSpecSet.swift | 9 - .../SyntaxExtensions.swift | 1 + .../SyntaxKindNameForDiagnostics.swift | 2 + .../generated/SwiftSyntax.md | 2 + .../generated/ChildNameForKeyPath.swift | 24 ++ .../generated/SyntaxAnyVisitor.swift | 16 + .../generated/SyntaxBaseNodes.swift | 2 + .../generated/SyntaxCollections.swift | 35 ++- .../SwiftSyntax/generated/SyntaxEnum.swift | 6 + .../SwiftSyntax/generated/SyntaxKind.swift | 6 + .../generated/SyntaxRewriter.swift | 32 ++ .../SwiftSyntax/generated/SyntaxTraits.swift | 2 + .../SwiftSyntax/generated/SyntaxVisitor.swift | 48 +++ .../generated/raw/RawSyntaxNodesJKLMN.swift | 152 ++++++++++ .../generated/raw/RawSyntaxNodesTUVWXYZ.swift | 7 +- .../generated/raw/RawSyntaxValidation.swift | 26 +- .../syntaxNodes/SyntaxNodesJKLMN.swift | 280 ++++++++++++++++++ .../syntaxNodes/SyntaxNodesQRS.swift | 3 +- .../generated/ResultBuilders.swift | 4 + Tests/SwiftParserTest/TypeTests.swift | 32 ++ 23 files changed, 759 insertions(+), 18 deletions(-) diff --git a/CodeGeneration/Sources/SyntaxSupport/SyntaxNodeKind.swift b/CodeGeneration/Sources/SyntaxSupport/SyntaxNodeKind.swift index ab07a05f470..6e50fe39500 100644 --- a/CodeGeneration/Sources/SyntaxSupport/SyntaxNodeKind.swift +++ b/CodeGeneration/Sources/SyntaxSupport/SyntaxNodeKind.swift @@ -210,6 +210,9 @@ public enum SyntaxNodeKind: String, CaseIterable, IdentifierConvertible, TypeCon case multipleTrailingClosureElementList case namedOpaqueReturnType case nilLiteralExpr + case nonisolatedSpecifierArgument + case nonisolatedSpecifierArgumentList + case nonisolatedTypeSpecifier case objCSelectorPiece case objCSelectorPieceList case operatorDecl diff --git a/CodeGeneration/Sources/SyntaxSupport/TypeNodes.swift b/CodeGeneration/Sources/SyntaxSupport/TypeNodes.swift index 9b9aa3c6e1e..3d65755fa45 100644 --- a/CodeGeneration/Sources/SyntaxSupport/TypeNodes.swift +++ b/CodeGeneration/Sources/SyntaxSupport/TypeNodes.swift @@ -676,6 +676,52 @@ public let TYPE_NODES: [Node] = [ ] ), + Node( + kind: .nonisolatedSpecifierArgument, + base: .syntax, + nameForDiagnostics: nil, + documentation: """ + A single argument that can be added to a nonisolated specifier: 'nonsending'. + + ### Example + `data` in `func foo(data: nonisolated(nonsending) () async -> Void) -> X` + """, + traits: [ + "Parenthesized" + ], + children: [ + Child( + name: "leftParen", + kind: .token(choices: [.token(.leftParen)]) + ), + Child( + name: "nonsendingKeyword", + kind: .token(choices: [.keyword(.nonsending)]) + ), + Child( + name: "rightParen", + kind: .token(choices: [.token(.rightParen)]) + ), + ] + ), + + Node( + kind: .nonisolatedTypeSpecifier, + base: .syntax, + nameForDiagnostics: "'nonisolated' specifier", + children: [ + Child( + name: "nonisolatedKeyword", + kind: .token(choices: [.keyword(.nonisolated)]) + ), + Child( + name: "argument", + kind: .node(kind: .nonisolatedSpecifierArgument), + isOptional: true + ), + ] + ), + Node( kind: .simpleTypeSpecifier, base: .syntax, @@ -689,7 +735,6 @@ public let TYPE_NODES: [Node] = [ .keyword(.__shared), .keyword(.__owned), .keyword(.isolated), - .keyword(.nonisolated), .keyword(._const), .keyword(.borrowing), .keyword(.consuming), @@ -704,6 +749,6 @@ public let TYPE_NODES: [Node] = [ kind: .typeSpecifierList, base: .syntaxCollection, nameForDiagnostics: nil, - elementChoices: [.simpleTypeSpecifier, .lifetimeTypeSpecifier] + elementChoices: [.simpleTypeSpecifier, .lifetimeTypeSpecifier, .nonisolatedTypeSpecifier] ), ] diff --git a/Sources/SwiftParser/Types.swift b/Sources/SwiftParser/Types.swift index 373788f7d73..11f7de1fc5a 100644 --- a/Sources/SwiftParser/Types.swift +++ b/Sources/SwiftParser/Types.swift @@ -1056,6 +1056,40 @@ extension Parser { return .lifetimeTypeSpecifier(lifetimeSpecifier) } + private mutating func parseNonisolatedTypeSpecifier() -> RawTypeSpecifierListSyntax.Element { + let (unexpectedBeforeNonisolatedKeyword, nonisolatedKeyword) = self.expect(.keyword(.nonisolated)) + + guard let leftParen = self.consume(if: .leftParen) else { + let nonisolatedSpecifier = RawNonisolatedTypeSpecifierSyntax( + unexpectedBeforeNonisolatedKeyword, + nonisolatedKeyword: nonisolatedKeyword, + argument: nil, + arena: self.arena + ) + return .nonisolatedTypeSpecifier(nonisolatedSpecifier) + } + + let (unexpectedBeforeModifier, modifier) = self.expect(.keyword(.nonsending)) + let (unexpectedBeforeRightParen, rightParen) = self.expect(.rightParen) + + let argument = RawNonisolatedSpecifierArgumentSyntax( + leftParen: leftParen, + unexpectedBeforeModifier, + nonsendingKeyword: modifier, + unexpectedBeforeRightParen, + rightParen: rightParen, + arena: self.arena + ) + + let nonisolatedSpecifier = RawNonisolatedTypeSpecifierSyntax( + unexpectedBeforeNonisolatedKeyword, + nonisolatedKeyword: nonisolatedKeyword, + argument: argument, + arena: self.arena + ) + return .nonisolatedTypeSpecifier(nonisolatedSpecifier) + } + private mutating func parseSimpleTypeSpecifier( specifierHandle: TokenConsumptionHandle ) -> RawTypeSpecifierListSyntax.Element { @@ -1079,6 +1113,8 @@ extension Parser { } else { break SPECIFIER_PARSING } + } else if self.at(.keyword(.nonisolated)) { + specifiers.append(parseNonisolatedTypeSpecifier()) } else { break SPECIFIER_PARSING } diff --git a/Sources/SwiftParser/generated/Parser+TokenSpecSet.swift b/Sources/SwiftParser/generated/Parser+TokenSpecSet.swift index 1aaa0d9c734..bcdb11f0b95 100644 --- a/Sources/SwiftParser/generated/Parser+TokenSpecSet.swift +++ b/Sources/SwiftParser/generated/Parser+TokenSpecSet.swift @@ -3503,7 +3503,6 @@ extension SimpleTypeSpecifierSyntax { case __shared case __owned case isolated - case nonisolated case _const case borrowing case consuming @@ -3519,8 +3518,6 @@ extension SimpleTypeSpecifierSyntax { self = .__owned case TokenSpec(.isolated): self = .isolated - case TokenSpec(.nonisolated): - self = .nonisolated case TokenSpec(._const): self = ._const case TokenSpec(.borrowing): @@ -3544,8 +3541,6 @@ extension SimpleTypeSpecifierSyntax { self = .__owned case TokenSpec(.isolated): self = .isolated - case TokenSpec(.nonisolated): - self = .nonisolated case TokenSpec(._const): self = ._const case TokenSpec(.borrowing): @@ -3569,8 +3564,6 @@ extension SimpleTypeSpecifierSyntax { return .keyword(.__owned) case .isolated: return .keyword(.isolated) - case .nonisolated: - return .keyword(.nonisolated) case ._const: return .keyword(._const) case .borrowing: @@ -3596,8 +3589,6 @@ extension SimpleTypeSpecifierSyntax { return .keyword(.__owned) case .isolated: return .keyword(.isolated) - case .nonisolated: - return .keyword(.nonisolated) case ._const: return .keyword(._const) case .borrowing: diff --git a/Sources/SwiftParserDiagnostics/SyntaxExtensions.swift b/Sources/SwiftParserDiagnostics/SyntaxExtensions.swift index 07fb77669b2..3dc0aa8e82b 100644 --- a/Sources/SwiftParserDiagnostics/SyntaxExtensions.swift +++ b/Sources/SwiftParserDiagnostics/SyntaxExtensions.swift @@ -212,6 +212,7 @@ extension TypeSpecifierListSyntax { switch specifier { case .simpleTypeSpecifier(let specifier): return specifier.specifier case .lifetimeTypeSpecifier: return nil + case .nonisolatedTypeSpecifier: return nil #if RESILIENT_LIBRARIES @unknown default: fatalError() diff --git a/Sources/SwiftParserDiagnostics/generated/SyntaxKindNameForDiagnostics.swift b/Sources/SwiftParserDiagnostics/generated/SyntaxKindNameForDiagnostics.swift index a5afdfbf444..9a9d00393e7 100644 --- a/Sources/SwiftParserDiagnostics/generated/SyntaxKindNameForDiagnostics.swift +++ b/Sources/SwiftParserDiagnostics/generated/SyntaxKindNameForDiagnostics.swift @@ -294,6 +294,8 @@ extension SyntaxKind { return "trailing closure" case .namedOpaqueReturnType: return "named opaque return type" + case .nonisolatedTypeSpecifier: + return "'nonisolated' specifier" case .objCSelectorPieceList: return "Objective-C selector" case .objCSelectorPiece: diff --git a/Sources/SwiftSyntax/Documentation.docc/generated/SwiftSyntax.md b/Sources/SwiftSyntax/Documentation.docc/generated/SwiftSyntax.md index 6d74ef33dcb..4ec126dbe4f 100644 --- a/Sources/SwiftSyntax/Documentation.docc/generated/SwiftSyntax.md +++ b/Sources/SwiftSyntax/Documentation.docc/generated/SwiftSyntax.md @@ -294,6 +294,7 @@ allows Swift tools to parse, inspect, generate, and transform Swift source code. - - - +- - - - @@ -357,6 +358,7 @@ allows Swift tools to parse, inspect, generate, and transform Swift source code. - - - +- - - - diff --git a/Sources/SwiftSyntax/generated/ChildNameForKeyPath.swift b/Sources/SwiftSyntax/generated/ChildNameForKeyPath.swift index 4f0645c8267..802b6ae8003 100644 --- a/Sources/SwiftSyntax/generated/ChildNameForKeyPath.swift +++ b/Sources/SwiftSyntax/generated/ChildNameForKeyPath.swift @@ -2370,6 +2370,30 @@ public func childName(_ keyPath: AnyKeyPath) -> String? { return "nilKeyword" case \NilLiteralExprSyntax.unexpectedAfterNilKeyword: return "unexpectedAfterNilKeyword" + case \NonisolatedSpecifierArgumentSyntax.unexpectedBeforeLeftParen: + return "unexpectedBeforeLeftParen" + case \NonisolatedSpecifierArgumentSyntax.leftParen: + return "leftParen" + case \NonisolatedSpecifierArgumentSyntax.unexpectedBetweenLeftParenAndNonsendingKeyword: + return "unexpectedBetweenLeftParenAndNonsendingKeyword" + case \NonisolatedSpecifierArgumentSyntax.nonsendingKeyword: + return "nonsendingKeyword" + case \NonisolatedSpecifierArgumentSyntax.unexpectedBetweenNonsendingKeywordAndRightParen: + return "unexpectedBetweenNonsendingKeywordAndRightParen" + case \NonisolatedSpecifierArgumentSyntax.rightParen: + return "rightParen" + case \NonisolatedSpecifierArgumentSyntax.unexpectedAfterRightParen: + return "unexpectedAfterRightParen" + case \NonisolatedTypeSpecifierSyntax.unexpectedBeforeNonisolatedKeyword: + return "unexpectedBeforeNonisolatedKeyword" + case \NonisolatedTypeSpecifierSyntax.nonisolatedKeyword: + return "nonisolatedKeyword" + case \NonisolatedTypeSpecifierSyntax.unexpectedBetweenNonisolatedKeywordAndArgument: + return "unexpectedBetweenNonisolatedKeywordAndArgument" + case \NonisolatedTypeSpecifierSyntax.argument: + return "argument" + case \NonisolatedTypeSpecifierSyntax.unexpectedAfterArgument: + return "unexpectedAfterArgument" case \ObjCSelectorPieceSyntax.unexpectedBeforeName: return "unexpectedBeforeName" case \ObjCSelectorPieceSyntax.name: diff --git a/Sources/SwiftSyntax/generated/SyntaxAnyVisitor.swift b/Sources/SwiftSyntax/generated/SyntaxAnyVisitor.swift index a48aab9ea71..124561440e0 100644 --- a/Sources/SwiftSyntax/generated/SyntaxAnyVisitor.swift +++ b/Sources/SwiftSyntax/generated/SyntaxAnyVisitor.swift @@ -1585,6 +1585,22 @@ open class SyntaxAnyVisitor: SyntaxVisitor { visitAnyPost(node._syntaxNode) } + override open func visit(_ node: NonisolatedSpecifierArgumentSyntax) -> SyntaxVisitorContinueKind { + return visitAny(node._syntaxNode) + } + + override open func visitPost(_ node: NonisolatedSpecifierArgumentSyntax) { + visitAnyPost(node._syntaxNode) + } + + override open func visit(_ node: NonisolatedTypeSpecifierSyntax) -> SyntaxVisitorContinueKind { + return visitAny(node._syntaxNode) + } + + override open func visitPost(_ node: NonisolatedTypeSpecifierSyntax) { + visitAnyPost(node._syntaxNode) + } + override open func visit(_ node: ObjCSelectorPieceListSyntax) -> SyntaxVisitorContinueKind { return visitAny(node._syntaxNode) } diff --git a/Sources/SwiftSyntax/generated/SyntaxBaseNodes.swift b/Sources/SwiftSyntax/generated/SyntaxBaseNodes.swift index 89d739db3ed..8c1a8444f88 100644 --- a/Sources/SwiftSyntax/generated/SyntaxBaseNodes.swift +++ b/Sources/SwiftSyntax/generated/SyntaxBaseNodes.swift @@ -1699,6 +1699,8 @@ extension Syntax { .node(MultipleTrailingClosureElementSyntax.self), .node(NamedOpaqueReturnTypeSyntax.self), .node(NilLiteralExprSyntax.self), + .node(NonisolatedSpecifierArgumentSyntax.self), + .node(NonisolatedTypeSpecifierSyntax.self), .node(ObjCSelectorPieceListSyntax.self), .node(ObjCSelectorPieceSyntax.self), .node(OperatorDeclSyntax.self), diff --git a/Sources/SwiftSyntax/generated/SyntaxCollections.swift b/Sources/SwiftSyntax/generated/SyntaxCollections.swift index 05305c97829..a34717855b7 100644 --- a/Sources/SwiftSyntax/generated/SyntaxCollections.swift +++ b/Sources/SwiftSyntax/generated/SyntaxCollections.swift @@ -1913,7 +1913,7 @@ public struct TupleTypeElementListSyntax: SyntaxCollection, SyntaxHashable { /// ### Children /// -/// (``SimpleTypeSpecifierSyntax`` | `LifetimeTypeSpecifierSyntax`) `*` +/// (``SimpleTypeSpecifierSyntax`` | `LifetimeTypeSpecifierSyntax` | ``NonisolatedTypeSpecifierSyntax``) `*` /// /// ### Contained in /// @@ -1926,6 +1926,7 @@ public struct TypeSpecifierListSyntax: SyntaxCollection, SyntaxHashable { /// - Note: Requires experimental feature `nonescapableTypes`. @_spi(ExperimentalLanguageFeatures) case lifetimeTypeSpecifier(LifetimeTypeSpecifierSyntax) + case nonisolatedTypeSpecifier(NonisolatedTypeSpecifierSyntax) public var _syntaxNode: Syntax { switch self { @@ -1933,6 +1934,8 @@ public struct TypeSpecifierListSyntax: SyntaxCollection, SyntaxHashable { return node._syntaxNode case .lifetimeTypeSpecifier(let node): return node._syntaxNode + case .nonisolatedTypeSpecifier(let node): + return node._syntaxNode } } @@ -1946,18 +1949,24 @@ public struct TypeSpecifierListSyntax: SyntaxCollection, SyntaxHashable { self = .lifetimeTypeSpecifier(node) } + public init(_ node: NonisolatedTypeSpecifierSyntax) { + self = .nonisolatedTypeSpecifier(node) + } + public init?(_ node: __shared some SyntaxProtocol) { if let node = node.as(SimpleTypeSpecifierSyntax.self) { self = .simpleTypeSpecifier(node) } else if let node = node.as(LifetimeTypeSpecifierSyntax.self) { self = .lifetimeTypeSpecifier(node) + } else if let node = node.as(NonisolatedTypeSpecifierSyntax.self) { + self = .nonisolatedTypeSpecifier(node) } else { return nil } } public static var structure: SyntaxNodeStructure { - return .choices([.node(SimpleTypeSpecifierSyntax.self), .node(LifetimeTypeSpecifierSyntax.self)]) + return .choices([.node(SimpleTypeSpecifierSyntax.self), .node(LifetimeTypeSpecifierSyntax.self), .node(NonisolatedTypeSpecifierSyntax.self)]) } /// Checks if the current syntax node can be cast to ``SimpleTypeSpecifierSyntax``. @@ -2009,6 +2018,28 @@ public struct TypeSpecifierListSyntax: SyntaxCollection, SyntaxHashable { public func cast(_ syntaxType: LifetimeTypeSpecifierSyntax.Type) -> LifetimeTypeSpecifierSyntax { return self.as(LifetimeTypeSpecifierSyntax.self)! } + + /// Checks if the current syntax node can be cast to ``NonisolatedTypeSpecifierSyntax``. + /// + /// - Returns: `true` if the node can be cast, `false` otherwise. + public func `is`(_ syntaxType: NonisolatedTypeSpecifierSyntax.Type) -> Bool { + return self.as(syntaxType) != nil + } + + /// Attempts to cast the current syntax node to ``NonisolatedTypeSpecifierSyntax``. + /// + /// - Returns: An instance of ``NonisolatedTypeSpecifierSyntax``, or `nil` if the cast fails. + public func `as`(_ syntaxType: NonisolatedTypeSpecifierSyntax.Type) -> NonisolatedTypeSpecifierSyntax? { + return NonisolatedTypeSpecifierSyntax.init(self) + } + + /// Force-casts the current syntax node to ``NonisolatedTypeSpecifierSyntax``. + /// + /// - Returns: An instance of ``NonisolatedTypeSpecifierSyntax``. + /// - Warning: This function will crash if the cast is not possible. Use `as` to safely attempt a cast. + public func cast(_ syntaxType: NonisolatedTypeSpecifierSyntax.Type) -> NonisolatedTypeSpecifierSyntax { + return self.as(NonisolatedTypeSpecifierSyntax.self)! + } } public let _syntaxNode: Syntax diff --git a/Sources/SwiftSyntax/generated/SyntaxEnum.swift b/Sources/SwiftSyntax/generated/SyntaxEnum.swift index dacab406c29..283c1d82d03 100644 --- a/Sources/SwiftSyntax/generated/SyntaxEnum.swift +++ b/Sources/SwiftSyntax/generated/SyntaxEnum.swift @@ -216,6 +216,8 @@ public enum SyntaxEnum: Sendable { case multipleTrailingClosureElement(MultipleTrailingClosureElementSyntax) case namedOpaqueReturnType(NamedOpaqueReturnTypeSyntax) case nilLiteralExpr(NilLiteralExprSyntax) + case nonisolatedSpecifierArgument(NonisolatedSpecifierArgumentSyntax) + case nonisolatedTypeSpecifier(NonisolatedTypeSpecifierSyntax) case objCSelectorPieceList(ObjCSelectorPieceListSyntax) case objCSelectorPiece(ObjCSelectorPieceSyntax) case operatorDecl(OperatorDeclSyntax) @@ -699,6 +701,10 @@ extension Syntax { return .namedOpaqueReturnType(NamedOpaqueReturnTypeSyntax(self)!) case .nilLiteralExpr: return .nilLiteralExpr(NilLiteralExprSyntax(self)!) + case .nonisolatedSpecifierArgument: + return .nonisolatedSpecifierArgument(NonisolatedSpecifierArgumentSyntax(self)!) + case .nonisolatedTypeSpecifier: + return .nonisolatedTypeSpecifier(NonisolatedTypeSpecifierSyntax(self)!) case .objCSelectorPieceList: return .objCSelectorPieceList(ObjCSelectorPieceListSyntax(self)!) case .objCSelectorPiece: diff --git a/Sources/SwiftSyntax/generated/SyntaxKind.swift b/Sources/SwiftSyntax/generated/SyntaxKind.swift index f1476b00ef4..62883dd7ac3 100644 --- a/Sources/SwiftSyntax/generated/SyntaxKind.swift +++ b/Sources/SwiftSyntax/generated/SyntaxKind.swift @@ -216,6 +216,8 @@ public enum SyntaxKind: Sendable { case multipleTrailingClosureElement case namedOpaqueReturnType case nilLiteralExpr + case nonisolatedSpecifierArgument + case nonisolatedTypeSpecifier case objCSelectorPieceList case objCSelectorPiece case operatorDecl @@ -824,6 +826,10 @@ public enum SyntaxKind: Sendable { return NamedOpaqueReturnTypeSyntax.self case .nilLiteralExpr: return NilLiteralExprSyntax.self + case .nonisolatedSpecifierArgument: + return NonisolatedSpecifierArgumentSyntax.self + case .nonisolatedTypeSpecifier: + return NonisolatedTypeSpecifierSyntax.self case .objCSelectorPieceList: return ObjCSelectorPieceListSyntax.self case .objCSelectorPiece: diff --git a/Sources/SwiftSyntax/generated/SyntaxRewriter.swift b/Sources/SwiftSyntax/generated/SyntaxRewriter.swift index fe361ee516f..77eb14701ba 100644 --- a/Sources/SwiftSyntax/generated/SyntaxRewriter.swift +++ b/Sources/SwiftSyntax/generated/SyntaxRewriter.swift @@ -1427,6 +1427,20 @@ open class SyntaxRewriter { return ExprSyntax(NilLiteralExprSyntax(unsafeCasting: visitChildren(node._syntaxNode))) } + /// Visit a ``NonisolatedSpecifierArgumentSyntax``. + /// - Parameter node: the node that is being visited + /// - Returns: the rewritten node + open func visit(_ node: NonisolatedSpecifierArgumentSyntax) -> NonisolatedSpecifierArgumentSyntax { + return NonisolatedSpecifierArgumentSyntax(unsafeCasting: visitChildren(node._syntaxNode)) + } + + /// Visit a ``NonisolatedTypeSpecifierSyntax``. + /// - Parameter node: the node that is being visited + /// - Returns: the rewritten node + open func visit(_ node: NonisolatedTypeSpecifierSyntax) -> NonisolatedTypeSpecifierSyntax { + return NonisolatedTypeSpecifierSyntax(unsafeCasting: visitChildren(node._syntaxNode)) + } + /// Visit a ``ObjCSelectorPieceListSyntax``. /// - Parameter node: the node that is being visited /// - Returns: the rewritten node @@ -3094,6 +3108,16 @@ open class SyntaxRewriter { Syntax(visit(NilLiteralExprSyntax(unsafeCasting: node))) } + @inline(never) + private func visitNonisolatedSpecifierArgumentSyntaxImpl(_ node: Syntax) -> Syntax { + Syntax(visit(NonisolatedSpecifierArgumentSyntax(unsafeCasting: node))) + } + + @inline(never) + private func visitNonisolatedTypeSpecifierSyntaxImpl(_ node: Syntax) -> Syntax { + Syntax(visit(NonisolatedTypeSpecifierSyntax(unsafeCasting: node))) + } + @inline(never) private func visitObjCSelectorPieceListSyntaxImpl(_ node: Syntax) -> Syntax { Syntax(visit(ObjCSelectorPieceListSyntax(unsafeCasting: node))) @@ -3988,6 +4012,10 @@ open class SyntaxRewriter { return self.visitNamedOpaqueReturnTypeSyntaxImpl(_:) case .nilLiteralExpr: return self.visitNilLiteralExprSyntaxImpl(_:) + case .nonisolatedSpecifierArgument: + return self.visitNonisolatedSpecifierArgumentSyntaxImpl(_:) + case .nonisolatedTypeSpecifier: + return self.visitNonisolatedTypeSpecifierSyntaxImpl(_:) case .objCSelectorPieceList: return self.visitObjCSelectorPieceListSyntaxImpl(_:) case .objCSelectorPiece: @@ -4570,6 +4598,10 @@ open class SyntaxRewriter { return visitNamedOpaqueReturnTypeSyntaxImpl(node) case .nilLiteralExpr: return visitNilLiteralExprSyntaxImpl(node) + case .nonisolatedSpecifierArgument: + return visitNonisolatedSpecifierArgumentSyntaxImpl(node) + case .nonisolatedTypeSpecifier: + return visitNonisolatedTypeSpecifierSyntaxImpl(node) case .objCSelectorPieceList: return visitObjCSelectorPieceListSyntaxImpl(node) case .objCSelectorPiece: diff --git a/Sources/SwiftSyntax/generated/SyntaxTraits.swift b/Sources/SwiftSyntax/generated/SyntaxTraits.swift index bf4e7438523..368b529b45b 100644 --- a/Sources/SwiftSyntax/generated/SyntaxTraits.swift +++ b/Sources/SwiftSyntax/generated/SyntaxTraits.swift @@ -802,6 +802,8 @@ extension MissingSyntax: MissingNodeSyntax {} extension MissingTypeSyntax: MissingNodeSyntax {} +extension NonisolatedSpecifierArgumentSyntax: ParenthesizedSyntax {} + extension OperatorDeclSyntax: NamedDeclSyntax {} extension PatternBindingSyntax: WithTrailingCommaSyntax {} diff --git a/Sources/SwiftSyntax/generated/SyntaxVisitor.swift b/Sources/SwiftSyntax/generated/SyntaxVisitor.swift index f6c0bbd35f2..739a203874c 100644 --- a/Sources/SwiftSyntax/generated/SyntaxVisitor.swift +++ b/Sources/SwiftSyntax/generated/SyntaxVisitor.swift @@ -2315,6 +2315,30 @@ open class SyntaxVisitor { open func visitPost(_ node: NilLiteralExprSyntax) { } + /// Visiting ``NonisolatedSpecifierArgumentSyntax`` specifically. + /// - Parameter node: the node we are visiting. + /// - Returns: how should we continue visiting. + open func visit(_ node: NonisolatedSpecifierArgumentSyntax) -> SyntaxVisitorContinueKind { + return .visitChildren + } + + /// The function called after visiting ``NonisolatedSpecifierArgumentSyntax`` and its descendants. + /// - node: the node we just finished visiting. + open func visitPost(_ node: NonisolatedSpecifierArgumentSyntax) { + } + + /// Visiting ``NonisolatedTypeSpecifierSyntax`` specifically. + /// - Parameter node: the node we are visiting. + /// - Returns: how should we continue visiting. + open func visit(_ node: NonisolatedTypeSpecifierSyntax) -> SyntaxVisitorContinueKind { + return .visitChildren + } + + /// The function called after visiting ``NonisolatedTypeSpecifierSyntax`` and its descendants. + /// - node: the node we just finished visiting. + open func visitPost(_ node: NonisolatedTypeSpecifierSyntax) { + } + /// Visiting ``ObjCSelectorPieceListSyntax`` specifically. /// - Parameter node: the node we are visiting. /// - Returns: how should we continue visiting. @@ -5016,6 +5040,22 @@ open class SyntaxVisitor { visitPost(NilLiteralExprSyntax(unsafeCasting: node)) } + @inline(never) + private func visitNonisolatedSpecifierArgumentSyntaxImpl(_ node: Syntax) { + if visit(NonisolatedSpecifierArgumentSyntax(unsafeCasting: node)) == .visitChildren { + visitChildren(node) + } + visitPost(NonisolatedSpecifierArgumentSyntax(unsafeCasting: node)) + } + + @inline(never) + private func visitNonisolatedTypeSpecifierSyntaxImpl(_ node: Syntax) { + if visit(NonisolatedTypeSpecifierSyntax(unsafeCasting: node)) == .visitChildren { + visitChildren(node) + } + visitPost(NonisolatedTypeSpecifierSyntax(unsafeCasting: node)) + } + @inline(never) private func visitObjCSelectorPieceListSyntaxImpl(_ node: Syntax) { if visit(ObjCSelectorPieceListSyntax(unsafeCasting: node)) == .visitChildren { @@ -6204,6 +6244,10 @@ open class SyntaxVisitor { return self.visitNamedOpaqueReturnTypeSyntaxImpl(_:) case .nilLiteralExpr: return self.visitNilLiteralExprSyntaxImpl(_:) + case .nonisolatedSpecifierArgument: + return self.visitNonisolatedSpecifierArgumentSyntaxImpl(_:) + case .nonisolatedTypeSpecifier: + return self.visitNonisolatedTypeSpecifierSyntaxImpl(_:) case .objCSelectorPieceList: return self.visitObjCSelectorPieceListSyntaxImpl(_:) case .objCSelectorPiece: @@ -6786,6 +6830,10 @@ open class SyntaxVisitor { self.visitNamedOpaqueReturnTypeSyntaxImpl(node) case .nilLiteralExpr: self.visitNilLiteralExprSyntaxImpl(node) + case .nonisolatedSpecifierArgument: + self.visitNonisolatedSpecifierArgumentSyntaxImpl(node) + case .nonisolatedTypeSpecifier: + self.visitNonisolatedTypeSpecifierSyntaxImpl(node) case .objCSelectorPieceList: self.visitObjCSelectorPieceListSyntaxImpl(node) case .objCSelectorPiece: diff --git a/Sources/SwiftSyntax/generated/raw/RawSyntaxNodesJKLMN.swift b/Sources/SwiftSyntax/generated/raw/RawSyntaxNodesJKLMN.swift index 5f8cbf238d2..8e6766d2c4f 100644 --- a/Sources/SwiftSyntax/generated/raw/RawSyntaxNodesJKLMN.swift +++ b/Sources/SwiftSyntax/generated/raw/RawSyntaxNodesJKLMN.swift @@ -2957,3 +2957,155 @@ public struct RawNilLiteralExprSyntax: RawExprSyntaxNodeProtocol { layoutView.children[2].map(RawUnexpectedNodesSyntax.init(raw:)) } } + +@_spi(RawSyntax) +public struct RawNonisolatedSpecifierArgumentSyntax: RawSyntaxNodeProtocol { + @_spi(RawSyntax) + public var layoutView: RawSyntaxLayoutView { + return raw.layoutView! + } + + public static func isKindOf(_ raw: RawSyntax) -> Bool { + return raw.kind == .nonisolatedSpecifierArgument + } + + 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( + _ unexpectedBeforeLeftParen: RawUnexpectedNodesSyntax? = nil, + leftParen: RawTokenSyntax, + _ unexpectedBetweenLeftParenAndNonsendingKeyword: RawUnexpectedNodesSyntax? = nil, + nonsendingKeyword: RawTokenSyntax, + _ unexpectedBetweenNonsendingKeywordAndRightParen: RawUnexpectedNodesSyntax? = nil, + rightParen: RawTokenSyntax, + _ unexpectedAfterRightParen: RawUnexpectedNodesSyntax? = nil, + arena: __shared RawSyntaxArena + ) { + let raw = RawSyntax.makeLayout( + kind: .nonisolatedSpecifierArgument, uninitializedCount: 7, arena: arena) { layout in + layout.initialize(repeating: nil) + layout[0] = unexpectedBeforeLeftParen?.raw + layout[1] = leftParen.raw + layout[2] = unexpectedBetweenLeftParenAndNonsendingKeyword?.raw + layout[3] = nonsendingKeyword.raw + layout[4] = unexpectedBetweenNonsendingKeywordAndRightParen?.raw + layout[5] = rightParen.raw + layout[6] = unexpectedAfterRightParen?.raw + } + self.init(unchecked: raw) + } + + public var unexpectedBeforeLeftParen: RawUnexpectedNodesSyntax? { + layoutView.children[0].map(RawUnexpectedNodesSyntax.init(raw:)) + } + + public var leftParen: RawTokenSyntax { + layoutView.children[1].map(RawTokenSyntax.init(raw:))! + } + + public var unexpectedBetweenLeftParenAndNonsendingKeyword: RawUnexpectedNodesSyntax? { + layoutView.children[2].map(RawUnexpectedNodesSyntax.init(raw:)) + } + + public var nonsendingKeyword: RawTokenSyntax { + layoutView.children[3].map(RawTokenSyntax.init(raw:))! + } + + public var unexpectedBetweenNonsendingKeywordAndRightParen: RawUnexpectedNodesSyntax? { + layoutView.children[4].map(RawUnexpectedNodesSyntax.init(raw:)) + } + + public var rightParen: RawTokenSyntax { + layoutView.children[5].map(RawTokenSyntax.init(raw:))! + } + + public var unexpectedAfterRightParen: RawUnexpectedNodesSyntax? { + layoutView.children[6].map(RawUnexpectedNodesSyntax.init(raw:)) + } +} + +@_spi(RawSyntax) +public struct RawNonisolatedTypeSpecifierSyntax: RawSyntaxNodeProtocol { + @_spi(RawSyntax) + public var layoutView: RawSyntaxLayoutView { + return raw.layoutView! + } + + public static func isKindOf(_ raw: RawSyntax) -> Bool { + return raw.kind == .nonisolatedTypeSpecifier + } + + 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( + _ unexpectedBeforeNonisolatedKeyword: RawUnexpectedNodesSyntax? = nil, + nonisolatedKeyword: RawTokenSyntax, + _ unexpectedBetweenNonisolatedKeywordAndArgument: RawUnexpectedNodesSyntax? = nil, + argument: RawNonisolatedSpecifierArgumentSyntax?, + _ unexpectedAfterArgument: RawUnexpectedNodesSyntax? = nil, + arena: __shared RawSyntaxArena + ) { + let raw = RawSyntax.makeLayout( + kind: .nonisolatedTypeSpecifier, uninitializedCount: 5, arena: arena) { layout in + layout.initialize(repeating: nil) + layout[0] = unexpectedBeforeNonisolatedKeyword?.raw + layout[1] = nonisolatedKeyword.raw + layout[2] = unexpectedBetweenNonisolatedKeywordAndArgument?.raw + layout[3] = argument?.raw + layout[4] = unexpectedAfterArgument?.raw + } + self.init(unchecked: raw) + } + + public var unexpectedBeforeNonisolatedKeyword: RawUnexpectedNodesSyntax? { + layoutView.children[0].map(RawUnexpectedNodesSyntax.init(raw:)) + } + + public var nonisolatedKeyword: RawTokenSyntax { + layoutView.children[1].map(RawTokenSyntax.init(raw:))! + } + + public var unexpectedBetweenNonisolatedKeywordAndArgument: RawUnexpectedNodesSyntax? { + layoutView.children[2].map(RawUnexpectedNodesSyntax.init(raw:)) + } + + public var argument: RawNonisolatedSpecifierArgumentSyntax? { + layoutView.children[3].map(RawNonisolatedSpecifierArgumentSyntax.init(raw:)) + } + + public var unexpectedAfterArgument: RawUnexpectedNodesSyntax? { + layoutView.children[4].map(RawUnexpectedNodesSyntax.init(raw:)) + } +} diff --git a/Sources/SwiftSyntax/generated/raw/RawSyntaxNodesTUVWXYZ.swift b/Sources/SwiftSyntax/generated/raw/RawSyntaxNodesTUVWXYZ.swift index 9dafdb8a7bf..e4b5a2b69a0 100644 --- a/Sources/SwiftSyntax/generated/raw/RawSyntaxNodesTUVWXYZ.swift +++ b/Sources/SwiftSyntax/generated/raw/RawSyntaxNodesTUVWXYZ.swift @@ -1416,9 +1416,10 @@ public struct RawTypeSpecifierListSyntax: RawSyntaxNodeProtocol { /// - Note: Requires experimental feature `nonescapableTypes`. @_spi(ExperimentalLanguageFeatures) case lifetimeTypeSpecifier(RawLifetimeTypeSpecifierSyntax) + case nonisolatedTypeSpecifier(RawNonisolatedTypeSpecifierSyntax) public static func isKindOf(_ raw: RawSyntax) -> Bool { - RawSimpleTypeSpecifierSyntax.isKindOf(raw) || RawLifetimeTypeSpecifierSyntax.isKindOf(raw) + RawSimpleTypeSpecifierSyntax.isKindOf(raw) || RawLifetimeTypeSpecifierSyntax.isKindOf(raw) || RawNonisolatedTypeSpecifierSyntax.isKindOf(raw) } public var raw: RawSyntax { @@ -1427,6 +1428,8 @@ public struct RawTypeSpecifierListSyntax: RawSyntaxNodeProtocol { return node.raw case .lifetimeTypeSpecifier(let node): return node.raw + case .nonisolatedTypeSpecifier(let node): + return node.raw } } @@ -1435,6 +1438,8 @@ public struct RawTypeSpecifierListSyntax: RawSyntaxNodeProtocol { self = .simpleTypeSpecifier(node) } else if let node = node.as(RawLifetimeTypeSpecifierSyntax.self) { self = .lifetimeTypeSpecifier(node) + } else if let node = node.as(RawNonisolatedTypeSpecifierSyntax.self) { + self = .nonisolatedTypeSpecifier(node) } else { return nil } diff --git a/Sources/SwiftSyntax/generated/raw/RawSyntaxValidation.swift b/Sources/SwiftSyntax/generated/raw/RawSyntaxValidation.swift index bfbbbbe2053..0ca5359f6e7 100644 --- a/Sources/SwiftSyntax/generated/raw/RawSyntaxValidation.swift +++ b/Sources/SwiftSyntax/generated/raw/RawSyntaxValidation.swift @@ -2150,6 +2150,24 @@ func validateLayout(layout: RawSyntaxBuffer, as kind: SyntaxKind) { assertNoError(kind, 1, verify(layout[1], as: RawTokenSyntax.self, tokenChoices: [.keyword("nil")])) assertNoError(kind, 2, verify(layout[2], as: RawUnexpectedNodesSyntax?.self)) } + func validateNonisolatedSpecifierArgumentSyntax(kind: SyntaxKind, layout: RawSyntaxBuffer) { + assert(layout.count == 7) + assertNoError(kind, 0, verify(layout[0], as: RawUnexpectedNodesSyntax?.self)) + assertNoError(kind, 1, verify(layout[1], as: RawTokenSyntax.self, tokenChoices: [.tokenKind(.leftParen)])) + assertNoError(kind, 2, verify(layout[2], as: RawUnexpectedNodesSyntax?.self)) + assertNoError(kind, 3, verify(layout[3], as: RawTokenSyntax.self, tokenChoices: [.keyword("nonsending")])) + assertNoError(kind, 4, verify(layout[4], as: RawUnexpectedNodesSyntax?.self)) + assertNoError(kind, 5, verify(layout[5], as: RawTokenSyntax.self, tokenChoices: [.tokenKind(.rightParen)])) + assertNoError(kind, 6, verify(layout[6], as: RawUnexpectedNodesSyntax?.self)) + } + func validateNonisolatedTypeSpecifierSyntax(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("nonisolated")])) + assertNoError(kind, 2, verify(layout[2], as: RawUnexpectedNodesSyntax?.self)) + assertNoError(kind, 3, verify(layout[3], as: RawNonisolatedSpecifierArgumentSyntax?.self)) + assertNoError(kind, 4, verify(layout[4], as: RawUnexpectedNodesSyntax?.self)) + } func validateObjCSelectorPieceListSyntax(kind: SyntaxKind, layout: RawSyntaxBuffer) { for (index, element) in layout.enumerated() { assertNoError(kind, index, verify(element, as: RawObjCSelectorPieceSyntax.self)) @@ -2561,7 +2579,6 @@ func validateLayout(layout: RawSyntaxBuffer, as kind: SyntaxKind) { .keyword("__shared"), .keyword("__owned"), .keyword("isolated"), - .keyword("nonisolated"), .keyword("_const"), .keyword("borrowing"), .keyword("consuming"), @@ -2957,7 +2974,8 @@ func validateLayout(layout: RawSyntaxBuffer, as kind: SyntaxKind) { for (index, element) in layout.enumerated() { assertAnyHasNoError(kind, index, [ verify(element, as: RawSimpleTypeSpecifierSyntax.self), - verify(element, as: RawLifetimeTypeSpecifierSyntax.self)]) + verify(element, as: RawLifetimeTypeSpecifierSyntax.self), + verify(element, as: RawNonisolatedTypeSpecifierSyntax.self)]) } } func validateUnexpectedNodesSyntax(kind: SyntaxKind, layout: RawSyntaxBuffer) { @@ -3488,6 +3506,10 @@ func validateLayout(layout: RawSyntaxBuffer, as kind: SyntaxKind) { validateNamedOpaqueReturnTypeSyntax(kind: kind, layout: layout) case .nilLiteralExpr: validateNilLiteralExprSyntax(kind: kind, layout: layout) + case .nonisolatedSpecifierArgument: + validateNonisolatedSpecifierArgumentSyntax(kind: kind, layout: layout) + case .nonisolatedTypeSpecifier: + validateNonisolatedTypeSpecifierSyntax(kind: kind, layout: layout) case .objCSelectorPieceList: validateObjCSelectorPieceListSyntax(kind: kind, layout: layout) case .objCSelectorPiece: diff --git a/Sources/SwiftSyntax/generated/syntaxNodes/SyntaxNodesJKLMN.swift b/Sources/SwiftSyntax/generated/syntaxNodes/SyntaxNodesJKLMN.swift index 686cb752a0b..fe4e496d5ab 100644 --- a/Sources/SwiftSyntax/generated/syntaxNodes/SyntaxNodesJKLMN.swift +++ b/Sources/SwiftSyntax/generated/syntaxNodes/SyntaxNodesJKLMN.swift @@ -5507,3 +5507,283 @@ public struct NilLiteralExprSyntax: ExprSyntaxProtocol, SyntaxHashable, _LeafExp public static let structure: SyntaxNodeStructure = .layout([\Self.unexpectedBeforeNilKeyword, \Self.nilKeyword, \Self.unexpectedAfterNilKeyword]) } + +// MARK: - NonisolatedSpecifierArgumentSyntax + +/// A single argument that can be added to a nonisolated specifier: 'nonsending'. +/// +/// ### Example +/// `data` in `func foo(data: nonisolated(nonsending) () async -> Void) -> X` +/// +/// ### Children +/// +/// - `leftParen`: `(` +/// - `nonsendingKeyword`: `nonsending` +/// - `rightParen`: `)` +/// +/// ### Contained in +/// +/// - ``NonisolatedTypeSpecifierSyntax``.``NonisolatedTypeSpecifierSyntax/argument`` +public struct NonisolatedSpecifierArgumentSyntax: SyntaxProtocol, SyntaxHashable, _LeafSyntaxNodeProtocol { + public let _syntaxNode: Syntax + + public init?(_ node: __shared some SyntaxProtocol) { + guard node.raw.kind == .nonisolatedSpecifierArgument 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, + _ unexpectedBeforeLeftParen: UnexpectedNodesSyntax? = nil, + leftParen: TokenSyntax = .leftParenToken(), + _ unexpectedBetweenLeftParenAndNonsendingKeyword: UnexpectedNodesSyntax? = nil, + nonsendingKeyword: TokenSyntax = .keyword(.nonsending), + _ unexpectedBetweenNonsendingKeywordAndRightParen: UnexpectedNodesSyntax? = nil, + rightParen: TokenSyntax = .rightParenToken(), + _ unexpectedAfterRightParen: 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(), ( + unexpectedBeforeLeftParen, + leftParen, + unexpectedBetweenLeftParenAndNonsendingKeyword, + nonsendingKeyword, + unexpectedBetweenNonsendingKeywordAndRightParen, + rightParen, + unexpectedAfterRightParen + ))) { (arena, _) in + let layout: [RawSyntax?] = [ + unexpectedBeforeLeftParen?.raw, + leftParen.raw, + unexpectedBetweenLeftParenAndNonsendingKeyword?.raw, + nonsendingKeyword.raw, + unexpectedBetweenNonsendingKeywordAndRightParen?.raw, + rightParen.raw, + unexpectedAfterRightParen?.raw + ] + let raw = RawSyntax.makeLayout( + kind: SyntaxKind.nonisolatedSpecifierArgument, + from: layout, + arena: arena, + leadingTrivia: leadingTrivia, + trailingTrivia: trailingTrivia + ) + return Syntax.forRoot(raw, rawNodeArena: arena).cast(Self.self) + } + } + + public var unexpectedBeforeLeftParen: 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(NonisolatedSpecifierArgumentSyntax.self) + } + } + + /// ### Tokens + /// + /// For syntax trees generated by the parser, this is guaranteed to be `(`. + public var leftParen: 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(NonisolatedSpecifierArgumentSyntax.self) + } + } + + public var unexpectedBetweenLeftParenAndNonsendingKeyword: 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(NonisolatedSpecifierArgumentSyntax.self) + } + } + + /// ### Tokens + /// + /// For syntax trees generated by the parser, this is guaranteed to be `nonsending`. + public var nonsendingKeyword: TokenSyntax { + get { + return Syntax(self).child(at: 3)!.cast(TokenSyntax.self) + } + set(value) { + self = Syntax(self).replacingChild(at: 3, with: Syntax(value), rawAllocationArena: RawSyntaxArena()).cast(NonisolatedSpecifierArgumentSyntax.self) + } + } + + public var unexpectedBetweenNonsendingKeywordAndRightParen: 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(NonisolatedSpecifierArgumentSyntax.self) + } + } + + /// ### Tokens + /// + /// For syntax trees generated by the parser, this is guaranteed to be `)`. + public var rightParen: TokenSyntax { + get { + return Syntax(self).child(at: 5)!.cast(TokenSyntax.self) + } + set(value) { + self = Syntax(self).replacingChild(at: 5, with: Syntax(value), rawAllocationArena: RawSyntaxArena()).cast(NonisolatedSpecifierArgumentSyntax.self) + } + } + + public var unexpectedAfterRightParen: UnexpectedNodesSyntax? { + get { + return Syntax(self).child(at: 6)?.cast(UnexpectedNodesSyntax.self) + } + set(value) { + self = Syntax(self).replacingChild(at: 6, with: Syntax(value), rawAllocationArena: RawSyntaxArena()).cast(NonisolatedSpecifierArgumentSyntax.self) + } + } + + public static let structure: SyntaxNodeStructure = .layout([ + \Self.unexpectedBeforeLeftParen, + \Self.leftParen, + \Self.unexpectedBetweenLeftParenAndNonsendingKeyword, + \Self.nonsendingKeyword, + \Self.unexpectedBetweenNonsendingKeywordAndRightParen, + \Self.rightParen, + \Self.unexpectedAfterRightParen + ]) +} + +// MARK: - NonisolatedTypeSpecifierSyntax + +/// ### Children +/// +/// - `nonisolatedKeyword`: `nonisolated` +/// - `argument`: ``NonisolatedSpecifierArgumentSyntax``? +/// +/// ### Contained in +/// +/// - ``TypeSpecifierListSyntax`` +public struct NonisolatedTypeSpecifierSyntax: SyntaxProtocol, SyntaxHashable, _LeafSyntaxNodeProtocol { + public let _syntaxNode: Syntax + + public init?(_ node: __shared some SyntaxProtocol) { + guard node.raw.kind == .nonisolatedTypeSpecifier 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, + _ unexpectedBeforeNonisolatedKeyword: UnexpectedNodesSyntax? = nil, + nonisolatedKeyword: TokenSyntax = .keyword(.nonisolated), + _ unexpectedBetweenNonisolatedKeywordAndArgument: UnexpectedNodesSyntax? = nil, + argument: NonisolatedSpecifierArgumentSyntax? = nil, + _ unexpectedAfterArgument: 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(), ( + unexpectedBeforeNonisolatedKeyword, + nonisolatedKeyword, + unexpectedBetweenNonisolatedKeywordAndArgument, + argument, + unexpectedAfterArgument + ))) { (arena, _) in + let layout: [RawSyntax?] = [ + unexpectedBeforeNonisolatedKeyword?.raw, + nonisolatedKeyword.raw, + unexpectedBetweenNonisolatedKeywordAndArgument?.raw, + argument?.raw, + unexpectedAfterArgument?.raw + ] + let raw = RawSyntax.makeLayout( + kind: SyntaxKind.nonisolatedTypeSpecifier, + from: layout, + arena: arena, + leadingTrivia: leadingTrivia, + trailingTrivia: trailingTrivia + ) + return Syntax.forRoot(raw, rawNodeArena: arena).cast(Self.self) + } + } + + public var unexpectedBeforeNonisolatedKeyword: 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(NonisolatedTypeSpecifierSyntax.self) + } + } + + /// ### Tokens + /// + /// For syntax trees generated by the parser, this is guaranteed to be `nonisolated`. + public var nonisolatedKeyword: 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(NonisolatedTypeSpecifierSyntax.self) + } + } + + public var unexpectedBetweenNonisolatedKeywordAndArgument: 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(NonisolatedTypeSpecifierSyntax.self) + } + } + + public var argument: NonisolatedSpecifierArgumentSyntax? { + get { + return Syntax(self).child(at: 3)?.cast(NonisolatedSpecifierArgumentSyntax.self) + } + set(value) { + self = Syntax(self).replacingChild(at: 3, with: Syntax(value), rawAllocationArena: RawSyntaxArena()).cast(NonisolatedTypeSpecifierSyntax.self) + } + } + + public var unexpectedAfterArgument: 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(NonisolatedTypeSpecifierSyntax.self) + } + } + + public static let structure: SyntaxNodeStructure = .layout([ + \Self.unexpectedBeforeNonisolatedKeyword, + \Self.nonisolatedKeyword, + \Self.unexpectedBetweenNonisolatedKeywordAndArgument, + \Self.argument, + \Self.unexpectedAfterArgument + ]) +} diff --git a/Sources/SwiftSyntax/generated/syntaxNodes/SyntaxNodesQRS.swift b/Sources/SwiftSyntax/generated/syntaxNodes/SyntaxNodesQRS.swift index 0be78ad525d..2431b585fc8 100644 --- a/Sources/SwiftSyntax/generated/syntaxNodes/SyntaxNodesQRS.swift +++ b/Sources/SwiftSyntax/generated/syntaxNodes/SyntaxNodesQRS.swift @@ -1267,7 +1267,7 @@ public struct SimpleStringLiteralExprSyntax: ExprSyntaxProtocol, SyntaxHashable, /// /// ### Children /// -/// - `specifier`: (`inout` | `__shared` | `__owned` | `isolated` | `nonisolated` | `_const` | `borrowing` | `consuming` | `sending`) +/// - `specifier`: (`inout` | `__shared` | `__owned` | `isolated` | `_const` | `borrowing` | `consuming` | `sending`) /// /// ### Contained in /// @@ -1331,7 +1331,6 @@ public struct SimpleTypeSpecifierSyntax: SyntaxProtocol, SyntaxHashable, _LeafSy /// - `__shared` /// - `__owned` /// - `isolated` - /// - `nonisolated` /// - `_const` /// - `borrowing` /// - `consuming` diff --git a/Sources/SwiftSyntaxBuilder/generated/ResultBuilders.swift b/Sources/SwiftSyntaxBuilder/generated/ResultBuilders.swift index c6c34003dfa..f1c1a598825 100644 --- a/Sources/SwiftSyntaxBuilder/generated/ResultBuilders.swift +++ b/Sources/SwiftSyntaxBuilder/generated/ResultBuilders.swift @@ -697,6 +697,10 @@ public struct TypeSpecifierListBuilder: ListBuilder { public static func buildExpression(_ expression: LifetimeTypeSpecifierSyntax) -> Component { buildExpression(.init(expression)) } + + public static func buildExpression(_ expression: NonisolatedTypeSpecifierSyntax) -> Component { + buildExpression(.init(expression)) + } } extension TypeSpecifierListSyntax { diff --git a/Tests/SwiftParserTest/TypeTests.swift b/Tests/SwiftParserTest/TypeTests.swift index 588c1be8beb..d74d919c073 100644 --- a/Tests/SwiftParserTest/TypeTests.swift +++ b/Tests/SwiftParserTest/TypeTests.swift @@ -537,6 +537,38 @@ final class TypeTests: ParserTestCase { experimentalFeatures: [.nonescapableTypes] ) } + + func testNonisolatedSpecifier() { + assertParse("let _: nonisolated(nonsending) () async -> Void = {}") + assertParse("let _: [nonisolated(nonsending) () async -> Void]") + assertParse("let _ = [String: (nonisolated(nonsending) () async -> Void)?].self") + assertParse("let _ = Array Void>()") + assertParse("func foo(test: nonisolated(nonsending) () async -> Void)") + assertParse("func foo(test: nonisolated(nonsending) @escaping () async -> Void) {}") + + assertParse( + "func foo(test: nonisolated(1️⃣) () async -> Void)", + diagnostics: [ + DiagnosticSpec( + message: "expected 'nonsending' in 'nonisolated' specifier", + fixIts: ["insert 'nonsending'"] + ) + ], + fixedSource: "func foo(test: nonisolated(nonsending) () async -> Void)" + ) + + assertParse( + "func foo(test: nonisolated(1️⃣hello) () async -> Void)", + diagnostics: [ + DiagnosticSpec( + message: "expected 'nonsending' in 'nonisolated' specifier", + fixIts: ["insert 'nonsending'"] + ), + DiagnosticSpec(message: "unexpected code 'hello' in 'nonisolated' specifier"), + ], + fixedSource: "func foo(test: nonisolated(nonsendinghello) () async -> Void)" + ) + } } final class InlineArrayTypeTests: ParserTestCase { From 3977abe2c08038467789b5526d9c9823f1be6404 Mon Sep 17 00:00:00 2001 From: Pavel Yaskevich Date: Mon, 7 Apr 2025 15:00:49 -0700 Subject: [PATCH 03/11] [SwiftParser] Make `nonisolated(nonsending)` parsing check before consuming '(' It's possible that `(nonsending)` was omitted and instead we are at the parameter list of a function type. Checking ahead allows for a better diagnostics and recovery. --- Sources/SwiftParser/Attributes.swift | 6 +++--- Sources/SwiftParser/Types.swift | 21 +++++++++++++++++++++ Tests/SwiftParserTest/TypeTests.swift | 14 ++++++++++++++ 3 files changed, 38 insertions(+), 3 deletions(-) diff --git a/Sources/SwiftParser/Attributes.swift b/Sources/SwiftParser/Attributes.swift index 72af60d312c..2f066fd6c39 100644 --- a/Sources/SwiftParser/Attributes.swift +++ b/Sources/SwiftParser/Attributes.swift @@ -157,7 +157,7 @@ extension Parser { shouldParseArgument = true case .customAttribute: shouldParseArgument = - self.withLookahead { $0.atCustomAttributeArgument() } + self.withLookahead { $0.atAttributeOrSpecifierArgument() } && self.at(TokenSpec(.leftParen, allowAtStartOfLine: false)) case .optional: shouldParseArgument = self.at(.leftParen) @@ -1002,7 +1002,7 @@ extension Parser { // MARK: Lookahead extension Parser.Lookahead { - mutating func atCustomAttributeArgument() -> Bool { + mutating func atAttributeOrSpecifierArgument() -> Bool { var lookahead = self.lookahead() lookahead.skipSingle() @@ -1036,7 +1036,7 @@ extension Parser.Lookahead { } if self.at(TokenSpec(.leftParen, allowAtStartOfLine: false)) - && self.withLookahead({ $0.atCustomAttributeArgument() }) + && self.withLookahead({ $0.atAttributeOrSpecifierArgument() }) { self.skipSingle() } diff --git a/Sources/SwiftParser/Types.swift b/Sources/SwiftParser/Types.swift index 11f7de1fc5a..0d2d2166f03 100644 --- a/Sources/SwiftParser/Types.swift +++ b/Sources/SwiftParser/Types.swift @@ -1059,6 +1059,27 @@ extension Parser { private mutating func parseNonisolatedTypeSpecifier() -> RawTypeSpecifierListSyntax.Element { let (unexpectedBeforeNonisolatedKeyword, nonisolatedKeyword) = self.expect(.keyword(.nonisolated)) + // Avoid being to greedy about `(` since this modifier should be associated with + // function types, it's possible that the argument is omitted and what follows + // is a function type i.e. `nonisolated () async -> Void`. + if self.at(.leftParen) && !withLookahead({ $0.atAttributeOrSpecifierArgument() }) { + let argument = RawNonisolatedSpecifierArgumentSyntax( + leftParen: missingToken(.leftParen), + nonsendingKeyword: missingToken(.keyword(.nonsending)), + rightParen: missingToken(.rightParen), + arena: self.arena + ) + + let nonisolatedSpecifier = RawNonisolatedTypeSpecifierSyntax( + unexpectedBeforeNonisolatedKeyword, + nonisolatedKeyword: nonisolatedKeyword, + argument: argument, + arena: self.arena + ) + return .nonisolatedTypeSpecifier(nonisolatedSpecifier) + } + + // nonisolated without an argument is valid in some positions i.e. inheritance clause. guard let leftParen = self.consume(if: .leftParen) else { let nonisolatedSpecifier = RawNonisolatedTypeSpecifierSyntax( unexpectedBeforeNonisolatedKeyword, diff --git a/Tests/SwiftParserTest/TypeTests.swift b/Tests/SwiftParserTest/TypeTests.swift index d74d919c073..1cd9ea36470 100644 --- a/Tests/SwiftParserTest/TypeTests.swift +++ b/Tests/SwiftParserTest/TypeTests.swift @@ -546,10 +546,23 @@ final class TypeTests: ParserTestCase { assertParse("func foo(test: nonisolated(nonsending) () async -> Void)") assertParse("func foo(test: nonisolated(nonsending) @escaping () async -> Void) {}") + assertParse( + "func foo(test: nonisolated1️⃣ () async -> Void)", + diagnostics: [ + DiagnosticSpec( + locationMarker: "1️⃣", + message: "expected '(nonsending)' in 'nonisolated' specifier", + fixIts: ["insert '(nonsending)'"] + ) + ], + fixedSource: "func foo(test: nonisolated(nonsending) () async -> Void)" + ) + assertParse( "func foo(test: nonisolated(1️⃣) () async -> Void)", diagnostics: [ DiagnosticSpec( + locationMarker: "1️⃣", message: "expected 'nonsending' in 'nonisolated' specifier", fixIts: ["insert 'nonsending'"] ) @@ -561,6 +574,7 @@ final class TypeTests: ParserTestCase { "func foo(test: nonisolated(1️⃣hello) () async -> Void)", diagnostics: [ DiagnosticSpec( + locationMarker: "1️⃣", message: "expected 'nonsending' in 'nonisolated' specifier", fixIts: ["insert 'nonsending'"] ), From 9ffe4b81521fbe353bde08613cf2f928bead3127 Mon Sep 17 00:00:00 2001 From: Pavel Yaskevich Date: Tue, 8 Apr 2025 12:04:17 -0700 Subject: [PATCH 04/11] [SwiftParser] Lookahead: Teach `skipTypeAttributeList` about modifiers with arguments --- Sources/SwiftParser/Types.swift | 63 ++++++++++++++++++++++----- Tests/SwiftParserTest/TypeTests.swift | 31 +++++++++++++ 2 files changed, 84 insertions(+), 10 deletions(-) diff --git a/Sources/SwiftParser/Types.swift b/Sources/SwiftParser/Types.swift index 0d2d2166f03..535b6140bd7 100644 --- a/Sources/SwiftParser/Types.swift +++ b/Sources/SwiftParser/Types.swift @@ -691,11 +691,27 @@ extension Parser.Lookahead { var specifierProgress = LoopProgressCondition() // TODO: Can we model isolated/_const so that they're specified in both canParse* and parse*? while canHaveParameterSpecifier, - self.at(anyIn: SimpleTypeSpecifierSyntax.SpecifierOptions.self) != nil || self.at(.keyword(.isolated)) - || self.at(.keyword(._const)), + self.at(anyIn: SimpleTypeSpecifierSyntax.SpecifierOptions.self) != nil + || self.at(.keyword(.nonisolated), .keyword(.dependsOn)), self.hasProgressed(&specifierProgress) { - self.consumeAnyToken() + switch self.currentToken { + case .keyword(.nonisolated), .keyword(.dependsOn): + self.consumeAnyToken() + + // The argument is missing but it still could be a valid modifier, + // i.e. `nonisolated` in an inheritance clause. + guard self.at(.leftParen) else { + continue + } + + if self.withLookahead({ $0.atAttributeOrSpecifierArgument() }) { + skipSingle() + } + + default: + self.consumeAnyToken() + } } var attributeProgress = LoopProgressCondition() @@ -1059,10 +1075,24 @@ extension Parser { private mutating func parseNonisolatedTypeSpecifier() -> RawTypeSpecifierListSyntax.Element { let (unexpectedBeforeNonisolatedKeyword, nonisolatedKeyword) = self.expect(.keyword(.nonisolated)) - // Avoid being to greedy about `(` since this modifier should be associated with - // function types, it's possible that the argument is omitted and what follows - // is a function type i.e. `nonisolated () async -> Void`. - if self.at(.leftParen) && !withLookahead({ $0.atAttributeOrSpecifierArgument() }) { + // If the next token is not '(' this could mean two things: + // - What follows is a type and we should allow it because + // using `nonsisolated` without an argument is allowed in + // an inheritance clause. + // - The '(nonsending)' was omitted. + if !self.at(.leftParen) { + // `nonisolated P<...>` is allowed in an inheritance clause. + if withLookahead({ $0.canParseTypeIdentifier() }) { + let nonisolatedSpecifier = RawNonisolatedTypeSpecifierSyntax( + unexpectedBeforeNonisolatedKeyword, + nonisolatedKeyword: nonisolatedKeyword, + argument: nil, + arena: self.arena + ) + return .nonisolatedTypeSpecifier(nonisolatedSpecifier) + } + + // Otherwise require '(nonsending)' let argument = RawNonisolatedSpecifierArgumentSyntax( leftParen: missingToken(.leftParen), nonsendingKeyword: missingToken(.keyword(.nonsending)), @@ -1076,24 +1106,37 @@ extension Parser { argument: argument, arena: self.arena ) + return .nonisolatedTypeSpecifier(nonisolatedSpecifier) } - // nonisolated without an argument is valid in some positions i.e. inheritance clause. - guard let leftParen = self.consume(if: .leftParen) else { + // Avoid being to greedy about `(` since this modifier should be associated with + // function types, it's possible that the argument is omitted and what follows + // is a function type i.e. `nonisolated () async -> Void`. + if self.at(.leftParen) && !withLookahead({ $0.atAttributeOrSpecifierArgument() }) { + let argument = RawNonisolatedSpecifierArgumentSyntax( + leftParen: missingToken(.leftParen), + nonsendingKeyword: missingToken(.keyword(.nonsending)), + rightParen: missingToken(.rightParen), + arena: self.arena + ) + let nonisolatedSpecifier = RawNonisolatedTypeSpecifierSyntax( unexpectedBeforeNonisolatedKeyword, nonisolatedKeyword: nonisolatedKeyword, - argument: nil, + argument: argument, arena: self.arena ) + return .nonisolatedTypeSpecifier(nonisolatedSpecifier) } + let (unexpectedBeforeLeftParen, leftParen) = self.expect(.leftParen) let (unexpectedBeforeModifier, modifier) = self.expect(.keyword(.nonsending)) let (unexpectedBeforeRightParen, rightParen) = self.expect(.rightParen) let argument = RawNonisolatedSpecifierArgumentSyntax( + unexpectedBeforeLeftParen, leftParen: leftParen, unexpectedBeforeModifier, nonsendingKeyword: modifier, diff --git a/Tests/SwiftParserTest/TypeTests.swift b/Tests/SwiftParserTest/TypeTests.swift index 1cd9ea36470..b047fce523f 100644 --- a/Tests/SwiftParserTest/TypeTests.swift +++ b/Tests/SwiftParserTest/TypeTests.swift @@ -485,6 +485,20 @@ final class TypeTests: ParserTestCase { experimentalFeatures: [.nonescapableTypes] ) + assertParse( + "func foo() -> dependsOn1️⃣ @Sendable (Int, isolated (any Actor)?) async throws -> Void", + diagnostics: [ + DiagnosticSpec( + locationMarker: "1️⃣", + message: "expected '(', parameter reference, and ')' in lifetime specifier", + fixIts: ["insert '(', parameter reference, and ')'"] + ) + ], + fixedSource: + "func foo() -> dependsOn(<#identifier#>) @Sendable (Int, isolated (any Actor)?) async throws -> Void", + experimentalFeatures: [.nonescapableTypes] + ) + assertParse( "func foo() -> dependsOn(1️⃣*) X", diagnostics: [ @@ -545,6 +559,23 @@ final class TypeTests: ParserTestCase { assertParse("let _ = Array Void>()") assertParse("func foo(test: nonisolated(nonsending) () async -> Void)") assertParse("func foo(test: nonisolated(nonsending) @escaping () async -> Void) {}") + assertParse("test(S Void>(), type(of: concurrentTest))") + assertParse("S Void>()") + assertParse("let _ = S Void>()") + assertParse("struct S : nonisolated P {}") + assertParse("let _ = [nonisolated()]") + + assertParse( + "Foo Void>()", + diagnostics: [ + DiagnosticSpec( + locationMarker: "1️⃣", + message: "expected '(nonsending)' in 'nonisolated' specifier", + fixIts: ["insert '(nonsending)'"] + ) + ], + fixedSource: "Foo Void>()" + ) assertParse( "func foo(test: nonisolated1️⃣ () async -> Void)", From 2b13304c449f1cd81270f225d72cc41e35e989fc Mon Sep 17 00:00:00 2001 From: Pavel Yaskevich Date: Wed, 9 Apr 2025 12:04:26 -0700 Subject: [PATCH 05/11] [SwiftParser] Account for a newline between `nonisolated` and `(` --- Sources/SwiftParser/Types.swift | 8 ++++++-- Tests/SwiftParserTest/TypeTests.swift | 22 ++++++++++++++++++++++ 2 files changed, 28 insertions(+), 2 deletions(-) diff --git a/Sources/SwiftParser/Types.swift b/Sources/SwiftParser/Types.swift index 535b6140bd7..3f5c15e0981 100644 --- a/Sources/SwiftParser/Types.swift +++ b/Sources/SwiftParser/Types.swift @@ -689,7 +689,6 @@ extension Parser.Lookahead { mutating func skipTypeAttributeList() { var specifierProgress = LoopProgressCondition() - // TODO: Can we model isolated/_const so that they're specified in both canParse* and parse*? while canHaveParameterSpecifier, self.at(anyIn: SimpleTypeSpecifierSyntax.SpecifierOptions.self) != nil || self.at(.keyword(.nonisolated), .keyword(.dependsOn)), @@ -701,7 +700,7 @@ extension Parser.Lookahead { // The argument is missing but it still could be a valid modifier, // i.e. `nonisolated` in an inheritance clause. - guard self.at(.leftParen) else { + guard self.at(TokenSpec(.leftParen, allowAtStartOfLine: false)) else { continue } @@ -1178,6 +1177,11 @@ extension Parser { break SPECIFIER_PARSING } } else if self.at(.keyword(.nonisolated)) { + // If '(' is located on the new line 'nonisolated' cannot be parsed + // as a specifier. + if self.peek(isAt: .leftParen) && self.peek().isAtStartOfLine { + break SPECIFIER_PARSING + } specifiers.append(parseNonisolatedTypeSpecifier()) } else { break SPECIFIER_PARSING diff --git a/Tests/SwiftParserTest/TypeTests.swift b/Tests/SwiftParserTest/TypeTests.swift index b047fce523f..057ae2dd3dd 100644 --- a/Tests/SwiftParserTest/TypeTests.swift +++ b/Tests/SwiftParserTest/TypeTests.swift @@ -565,6 +565,28 @@ final class TypeTests: ParserTestCase { assertParse("struct S : nonisolated P {}") assertParse("let _ = [nonisolated()]") + assertParse( + """ + let x: nonisolated + (hello) + """ + ) + + assertParse( + """ + struct S: nonisolated + P { + } + """ + ) + + assertParse( + """ + let x: nonisolated + (Int) async -> Void = {} + """ + ) + assertParse( "Foo Void>()", diagnostics: [ From d0c3e616d59c4b5993da4422322665dfb99b4434 Mon Sep 17 00:00:00 2001 From: Alex Hoppen Date: Thu, 10 Apr 2025 09:00:23 -0700 Subject: [PATCH 06/11] Add GitHub action that automatically creates a PR to merge main into a release branch In the first period after branching the release branch, we want to include many changes from `main` also in the release branch. This workflow automatically creates a PR every Monday to merge main into the release branch. Later in the release cycle we should stop this practice to avoid landing risky changes by disabling this workflow. Similar to https://github.com/swiftlang/swift-format/pull/986 --- .github/workflows/automerge.yml | 14 ++++++++++++++ 1 file changed, 14 insertions(+) create mode 100644 .github/workflows/automerge.yml diff --git a/.github/workflows/automerge.yml b/.github/workflows/automerge.yml new file mode 100644 index 00000000000..3d8b1595646 --- /dev/null +++ b/.github/workflows/automerge.yml @@ -0,0 +1,14 @@ +name: Create PR to merge main into release branch +# In the first period after branching the release branch, we typically want to include many changes from `main` in the release branch. This workflow automatically creates a PR every Monday to merge main into the release branch. +# Later in the release cycle we should stop this practice to avoid landing risky changes by disabling this workflow. To do so, disable the workflow as described in https://docs.github.com/en/actions/managing-workflow-runs-and-deployments/managing-workflow-runs/disabling-and-enabling-a-workflow +on: + schedule: + - cron: '0 9 * * MON' + workflow_dispatch: +jobs: + create_merge_pr: + name: Create PR to merge main into release branch + uses: swiftlang/github-workflows/.github/workflows/create_automerge_pr.yml@main + with: + base_branch: release/6.2 + if: (github.event_name == 'schedule' && github.repository == 'swiftlang/swift-syntax') || (github.event_name != 'schedule') # Ensure that we don't run this on a schedule in a fork From b9f80c744b0fd08bf6b123adad14f58e8472afd0 Mon Sep 17 00:00:00 2001 From: Pavel Yaskevich Date: Fri, 11 Apr 2025 12:12:20 -0700 Subject: [PATCH 07/11] [SwiftParser] Avoid consuming invalid `dependsOn` and `nonisolated` modifiers while skipping Otherwise in situations like `nonisolated()` `canParseType` would return `true` because `()` is valid type. --- Sources/SwiftParser/Types.swift | 65 +++++++++++++++++++++++++++++---- 1 file changed, 58 insertions(+), 7 deletions(-) diff --git a/Sources/SwiftParser/Types.swift b/Sources/SwiftParser/Types.swift index 3f5c15e0981..416f06ddc1f 100644 --- a/Sources/SwiftParser/Types.swift +++ b/Sources/SwiftParser/Types.swift @@ -687,7 +687,7 @@ extension Parser.Lookahead { return true } - mutating func skipTypeAttributeList() { + mutating func canParseTypeAttributeList() -> Bool { var specifierProgress = LoopProgressCondition() while canHaveParameterSpecifier, self.at(anyIn: SimpleTypeSpecifierSyntax.SpecifierOptions.self) != nil @@ -695,19 +695,66 @@ extension Parser.Lookahead { self.hasProgressed(&specifierProgress) { switch self.currentToken { - case .keyword(.nonisolated), .keyword(.dependsOn): + case .keyword(.nonisolated): + let canParseNonisolated = self.withLookahead({ + // Consume 'nonisolated' + $0.consumeAnyToken() + + // The argument is missing but it still could be a valid modifier, + // i.e. `nonisolated` in an inheritance clause. + guard $0.at(TokenSpec(.leftParen, allowAtStartOfLine: false)) else { + return true + } + + // Consume '(' + $0.consumeAnyToken() + + // nonisolated accepts a single modifier at the moment: 'nonsending' + // we need to check for that explicitly to avoid misinterpreting this + // keyword to be a modifier when it isn't i.e. `[nonisolated(42)]` + guard $0.consume(if: TokenSpec(.nonsending, allowAtStartOfLine: false)) != nil else { + return false + } + + return $0.consume(if: TokenSpec(.rightParen, allowAtStartOfLine: false)) != nil + }) + + guard canParseNonisolated else { + return false + } + self.consumeAnyToken() - // The argument is missing but it still could be a valid modifier, - // i.e. `nonisolated` in an inheritance clause. guard self.at(TokenSpec(.leftParen, allowAtStartOfLine: false)) else { continue } - if self.withLookahead({ $0.atAttributeOrSpecifierArgument() }) { - skipSingle() + self.skipSingle() + + case .keyword(.dependsOn): + let canParseDependsOn = self.withLookahead({ + // Consume 'dependsOn' + $0.consumeAnyToken() + + if $0.currentToken.isAtStartOfLine { + return false + } + + // `dependsOn` requires an argument list. + guard $0.atAttributeOrSpecifierArgument() else { + return false + } + + return true + }) + + guard canParseDependsOn else { + return false } + self.consumeAnyToken() + self.skipSingle() + default: self.consumeAnyToken() } @@ -718,10 +765,14 @@ extension Parser.Lookahead { self.consumeAnyToken() self.skipTypeAttribute() } + + return true } mutating func canParseTypeScalar() -> Bool { - self.skipTypeAttributeList() + guard self.canParseTypeAttributeList() else { + return false + } guard self.canParseSimpleOrCompositionType() else { return false From 2ae464ed7bda23468100cefd6e2257856ca38bcf Mon Sep 17 00:00:00 2001 From: Pavel Yaskevich Date: Fri, 11 Apr 2025 12:14:19 -0700 Subject: [PATCH 08/11] [SwiftParser] Parse `nonisolated` as modifier only if it has `nonsending` in expression context --- Sources/SwiftParser/Expressions.swift | 30 +++++- Tests/SwiftParserTest/TypeTests.swift | 126 +++++++++++++++++++++++--- 2 files changed, 140 insertions(+), 16 deletions(-) diff --git a/Sources/SwiftParser/Expressions.swift b/Sources/SwiftParser/Expressions.swift index 11bcdf4ee1a..70e2a433f56 100644 --- a/Sources/SwiftParser/Expressions.swift +++ b/Sources/SwiftParser/Expressions.swift @@ -381,13 +381,39 @@ extension Parser { } } + /// Make sure that we only accept `nonisolated(nonsending)` as a valid type specifier + /// in expression context to minimize source compatibility impact. + func canParseNonisolatedAsSpecifierInExpressionContext() -> Bool { + return withLookahead { + guard $0.consume(if: .keyword(.nonisolated)) != nil else { + return false + } + + if $0.currentToken.isAtStartOfLine { + return false + } + + guard $0.consume(if: .leftParen) != nil else { + return false + } + + guard $0.consume(if: TokenSpec(.nonsending, allowAtStartOfLine: false)) != nil else { + return false + } + + return $0.at(TokenSpec(.rightParen, allowAtStartOfLine: false)) + } + } + /// Parse an expression sequence element. mutating func parseSequenceExpressionElement( flavor: ExprFlavor, pattern: PatternContext = .none ) -> RawExprSyntax { - // Try to parse '@' sign or 'inout' as an attributed typerepr. - if self.at(.atSign, .keyword(.inout)) { + // Try to parse '@' sign, 'inout', or 'nonisolated' as an attributed typerepr. + if self.at(.atSign, .keyword(.inout)) + || self.canParseNonisolatedAsSpecifierInExpressionContext() + { var lookahead = self.lookahead() if lookahead.canParseType() { let type = self.parseType() diff --git a/Tests/SwiftParserTest/TypeTests.swift b/Tests/SwiftParserTest/TypeTests.swift index 057ae2dd3dd..6e49d5fa75d 100644 --- a/Tests/SwiftParserTest/TypeTests.swift +++ b/Tests/SwiftParserTest/TypeTests.swift @@ -553,23 +553,117 @@ final class TypeTests: ParserTestCase { } func testNonisolatedSpecifier() { - assertParse("let _: nonisolated(nonsending) () async -> Void = {}") - assertParse("let _: [nonisolated(nonsending) () async -> Void]") - assertParse("let _ = [String: (nonisolated(nonsending) () async -> Void)?].self") - assertParse("let _ = Array Void>()") - assertParse("func foo(test: nonisolated(nonsending) () async -> Void)") - assertParse("func foo(test: nonisolated(nonsending) @escaping () async -> Void) {}") - assertParse("test(S Void>(), type(of: concurrentTest))") - assertParse("S Void>()") - assertParse("let _ = S Void>()") - assertParse("struct S : nonisolated P {}") - assertParse("let _ = [nonisolated()]") + assertParse( + """ + let x = nonisolated + print("hello") + """, + substructure: DeclReferenceExprSyntax( + baseName: .identifier("nonisolated") + ) + ) + + assertParse( + "let _: nonisolated(nonsending) () async -> Void = {}", + substructure: NonisolatedTypeSpecifierSyntax( + nonisolatedKeyword: .keyword(.nonisolated), + argument: NonisolatedSpecifierArgumentSyntax(nonsendingKeyword: .keyword(.nonsending)) + ) + ) + assertParse( + "let _: [nonisolated(nonsending) () async -> Void]", + substructure: NonisolatedTypeSpecifierSyntax( + nonisolatedKeyword: .keyword(.nonisolated), + argument: NonisolatedSpecifierArgumentSyntax(nonsendingKeyword: .keyword(.nonsending)) + ) + ) + + assertParse( + "let _ = [String: (nonisolated(nonsending) () async -> Void)?].self", + substructure: NonisolatedTypeSpecifierSyntax( + nonisolatedKeyword: .keyword(.nonisolated), + argument: NonisolatedSpecifierArgumentSyntax(nonsendingKeyword: .keyword(.nonsending)) + ) + ) + + assertParse( + "let _ = Array Void>()", + substructure: NonisolatedTypeSpecifierSyntax( + nonisolatedKeyword: .keyword(.nonisolated), + argument: NonisolatedSpecifierArgumentSyntax(nonsendingKeyword: .keyword(.nonsending)) + ) + ) + assertParse( + "func foo(test: nonisolated(nonsending) () async -> Void)", + substructure: NonisolatedTypeSpecifierSyntax( + nonisolatedKeyword: .keyword(.nonisolated), + argument: NonisolatedSpecifierArgumentSyntax(nonsendingKeyword: .keyword(.nonsending)) + ) + ) + assertParse( + "func foo(test: nonisolated(nonsending) @escaping () async -> Void) {}", + substructure: NonisolatedTypeSpecifierSyntax( + nonisolatedKeyword: .keyword(.nonisolated), + argument: NonisolatedSpecifierArgumentSyntax(nonsendingKeyword: .keyword(.nonsending)) + ) + ) + assertParse( + "test(S Void>(), type(of: concurrentTest))", + substructure: NonisolatedTypeSpecifierSyntax( + nonisolatedKeyword: .keyword(.nonisolated), + argument: NonisolatedSpecifierArgumentSyntax(nonsendingKeyword: .keyword(.nonsending)) + ) + ) + assertParse( + "S Void>()", + substructure: NonisolatedTypeSpecifierSyntax( + nonisolatedKeyword: .keyword(.nonisolated), + argument: NonisolatedSpecifierArgumentSyntax(nonsendingKeyword: .keyword(.nonsending)) + ) + ) + assertParse( + "let _ = S Void>()", + substructure: NonisolatedTypeSpecifierSyntax( + nonisolatedKeyword: .keyword(.nonisolated), + argument: NonisolatedSpecifierArgumentSyntax(nonsendingKeyword: .keyword(.nonsending)) + ) + ) + + assertParse( + "struct S : nonisolated P {}", + substructure: NonisolatedTypeSpecifierSyntax( + nonisolatedKeyword: .keyword(.nonisolated) + ) + ) + + assertParse( + "let _ = [nonisolated()]", + substructure: DeclReferenceExprSyntax( + baseName: .identifier("nonisolated") + ) + ) + + assertParse( + "let _ = [nonisolated(nonsending) () async -> Void]()", + substructure: NonisolatedTypeSpecifierSyntax( + nonisolatedKeyword: .keyword(.nonisolated), + argument: NonisolatedSpecifierArgumentSyntax(nonsendingKeyword: .keyword(.nonsending)) + ) + ) + + assertParse( + "_ = S()", + substructure: GenericArgumentSyntax.Argument( + IdentifierTypeSyntax(name: .identifier("nonisolated")) + ) + ) assertParse( """ let x: nonisolated (hello) - """ + """, + substructure: IdentifierTypeSyntax(name: .identifier("nonisolated")) ) assertParse( @@ -577,14 +671,18 @@ final class TypeTests: ParserTestCase { struct S: nonisolated P { } - """ + """, + substructure: NonisolatedTypeSpecifierSyntax( + nonisolatedKeyword: .keyword(.nonisolated) + ) ) assertParse( """ let x: nonisolated (Int) async -> Void = {} - """ + """, + substructure: IdentifierTypeSyntax(name: .identifier("nonisolated")) ) assertParse( From 14e4fc4e58b30be079939fca6a61f7e669c154ad Mon Sep 17 00:00:00 2001 From: Doug Gregor Date: Wed, 16 Apr 2025 17:39:25 -0700 Subject: [PATCH 09/11] Add a new API DiagnosticsFormatter.formattedMessage(_:) for messages without source location info Some tools want to use the diagnostics formatter to produce diagnostics that don't relate to source code, or for which the source code isn't available. This API allows them to do so while maintaining consistent presentation. The first client is the compiler itself, when it doesn't have a source location for a specific diagnostic. --- Release Notes/602.md | 5 +++ .../DiagnosticsFormatter.swift | 6 ++++ .../DiagnosticFormatterTests.swift | 31 +++++++++++++++++++ 3 files changed, 42 insertions(+) create mode 100644 Tests/SwiftDiagnosticsTest/DiagnosticFormatterTests.swift diff --git a/Release Notes/602.md b/Release Notes/602.md index 12c16b08703..66a9a9d6017 100644 --- a/Release Notes/602.md +++ b/Release Notes/602.md @@ -12,6 +12,11 @@ - Pull Request: https://github.com/swiftlang/swift-syntax/pull/2981 - Migration steps: None required. The new `category` property has optional type, and there is a default implementation that returns `nil`. Types that conform to `DiagnosticMessage` can choose to implement this property and provide a category when appropriate. +- `DiagnosticsFormatter` has a new method, `formattedMessage`, that formats a diagnostic message without any corresponding syntax node. + - Description: Some tools want to use the diagnostics formatter to produce diagnostics that don't relate to source code, or for which the source code isn't available. This API allows them to do so while maintaining consistent presentation. + - Pull Request: TBD + - Migration steps: None required. + - `FixIt.Change` has a new case `replaceText` that performs a textual replacement of a range of text with another string. - Description: The other `FixIt.Change` cases provide structured modifications to syntax trees, such as replacing specific notes. Some diff --git a/Sources/SwiftDiagnostics/DiagnosticsFormatter.swift b/Sources/SwiftDiagnostics/DiagnosticsFormatter.swift index 9717ecb4ac7..e93bc1d609f 100644 --- a/Sources/SwiftDiagnostics/DiagnosticsFormatter.swift +++ b/Sources/SwiftDiagnostics/DiagnosticsFormatter.swift @@ -360,6 +360,12 @@ public struct DiagnosticsFormatter { ) } + /// Produce a string that formats the given diagnostic message with any + /// source-location information. + public func formattedMessage(_ message: some DiagnosticMessage) -> String { + diagnosticDecorator.decorateDiagnosticMessage(message) + } + /// Produce a string containing "footnotes" for each of the diagnostic /// category provided that has associated documentation. Each category /// is printed in Markdown link format, e.g., diff --git a/Tests/SwiftDiagnosticsTest/DiagnosticFormatterTests.swift b/Tests/SwiftDiagnosticsTest/DiagnosticFormatterTests.swift new file mode 100644 index 00000000000..2980b046504 --- /dev/null +++ b/Tests/SwiftDiagnosticsTest/DiagnosticFormatterTests.swift @@ -0,0 +1,31 @@ +//===----------------------------------------------------------------------===// +// +// This source file is part of the Swift.org open source project +// +// Copyright (c) 2014 - 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 +// +//===----------------------------------------------------------------------===// + +import SwiftDiagnostics +import XCTest + +final class DiagnosticFormatterTests: XCTestCase { + func testFormattedMessage() { + let message = SimpleDiagnosticMessage( + message: "something went wrong", + diagnosticID: MessageID(domain: "swift-syntax", id: "testing"), + severity: .error, + category: DiagnosticCategory( + name: "Testing", + documentationURL: "http://example.com" + ) + ) + + let formattedText = DiagnosticsFormatter().formattedMessage(message) + XCTAssertEqual(formattedText, "error: something went wrong [#Testing]") + } +} From 8ced3b647ecbaafcf8e8ba294c77c1c82caae0e5 Mon Sep 17 00:00:00 2001 From: Doug Gregor Date: Wed, 16 Apr 2025 17:41:49 -0700 Subject: [PATCH 10/11] Add pull request number --- Release Notes/602.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Release Notes/602.md b/Release Notes/602.md index 66a9a9d6017..0d5750bdd2e 100644 --- a/Release Notes/602.md +++ b/Release Notes/602.md @@ -14,7 +14,7 @@ - `DiagnosticsFormatter` has a new method, `formattedMessage`, that formats a diagnostic message without any corresponding syntax node. - Description: Some tools want to use the diagnostics formatter to produce diagnostics that don't relate to source code, or for which the source code isn't available. This API allows them to do so while maintaining consistent presentation. - - Pull Request: TBD + - Pull Request: https://github.com/swiftlang/swift-syntax/pull/3059 - Migration steps: None required. - `FixIt.Change` has a new case `replaceText` that performs a textual replacement of a range of text with another string. From cae75da56964afd9343969a86a509643964a2a1b Mon Sep 17 00:00:00 2001 From: Alex Hoppen Date: Thu, 17 Apr 2025 09:57:32 -0700 Subject: [PATCH 11/11] Grant write permissions for content an pull requests to auto merge GitHub action --- .github/workflows/automerge.yml | 3 +++ 1 file changed, 3 insertions(+) diff --git a/.github/workflows/automerge.yml b/.github/workflows/automerge.yml index 3d8b1595646..b260e72b4ed 100644 --- a/.github/workflows/automerge.yml +++ b/.github/workflows/automerge.yml @@ -11,4 +11,7 @@ jobs: uses: swiftlang/github-workflows/.github/workflows/create_automerge_pr.yml@main with: base_branch: release/6.2 + permissions: + contents: write + pull-requests: write if: (github.event_name == 'schedule' && github.repository == 'swiftlang/swift-syntax') || (github.event_name != 'schedule') # Ensure that we don't run this on a schedule in a fork