From 8d5614e277da2b2c451996654c050fef34451670 Mon Sep 17 00:00:00 2001 From: Alex Hoppen Date: Tue, 26 Mar 2024 00:29:30 +0100 Subject: [PATCH 1/2] =?UTF-8?q?Only=20mark=20keyword=20as=20experimental?= =?UTF-8?q?=20when=20using=20a=20Swift=20=E2=89=A55.8=20compiler?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit In most other parts of the code base we only use the SPI attribute for Swift ≥5.8 compilers. If we unconditionally mark a keywords as SPI we run into issues if a syntax node (which is only experimental in Swift 5.8+) uses that keyword as a default value in its initializers. The concrete case here is the usage of `.dependsOn`. --- .../Sources/SyntaxSupport/KeywordSpec.swift | 13 ++++++++-- .../generated/Parser+TokenSpecSet.swift | 26 +++++++++++++++++++ Sources/SwiftSyntax/generated/Keyword.swift | 16 ++++++++++++ 3 files changed, 53 insertions(+), 2 deletions(-) diff --git a/CodeGeneration/Sources/SyntaxSupport/KeywordSpec.swift b/CodeGeneration/Sources/SyntaxSupport/KeywordSpec.swift index eaff61fad2f..d66fc645917 100644 --- a/CodeGeneration/Sources/SyntaxSupport/KeywordSpec.swift +++ b/CodeGeneration/Sources/SyntaxSupport/KeywordSpec.swift @@ -43,8 +43,17 @@ public struct KeywordSpec { /// /// This is typically used to mark APIs as SPI when the keyword is part of an experimental language feature. public var apiAttributes: AttributeListSyntax { - guard isExperimental else { return "" } - return AttributeListSyntax("@_spi(ExperimentalLanguageFeatures)").with(\.trailingTrivia, .newline) + let attrList = AttributeListSyntax { + if isExperimental { + let experimentalSPI: AttributeListSyntax = """ + #if compiler(>=5.8) + @_spi(ExperimentalLanguageFeatures) + #endif + """ + experimentalSPI.with(\.trailingTrivia, .newline) + } + } + return attrList.with(\.trailingTrivia, attrList.isEmpty ? [] : .newline) } /// Initializes a new `KeywordSpec` instance. diff --git a/Sources/SwiftParser/generated/Parser+TokenSpecSet.swift b/Sources/SwiftParser/generated/Parser+TokenSpecSet.swift index 02e91851066..952b584e300 100644 --- a/Sources/SwiftParser/generated/Parser+TokenSpecSet.swift +++ b/Sources/SwiftParser/generated/Parser+TokenSpecSet.swift @@ -802,11 +802,15 @@ extension DeclModifierSyntax { case `private` case `public` case reasync + #if compiler(>=5.8) @_spi(ExperimentalLanguageFeatures) + #endif case _resultDependsOnSelf case required case `static` + #if compiler(>=5.8) @_spi(ExperimentalLanguageFeatures) + #endif case transferring case unowned case weak @@ -2907,11 +2911,17 @@ extension OptionalBindingConditionSyntax { case `let` case `var` case `inout` + #if compiler(>=5.8) @_spi(ExperimentalLanguageFeatures) + #endif case _mutating + #if compiler(>=5.8) @_spi(ExperimentalLanguageFeatures) + #endif case _borrowing + #if compiler(>=5.8) @_spi(ExperimentalLanguageFeatures) + #endif case _consuming init?(lexeme: Lexer.Lexeme, experimentalFeatures: Parser.ExperimentalFeatures) { @@ -3332,9 +3342,13 @@ extension SimpleTypeSpecifierSyntax { case _const case borrowing case consuming + #if compiler(>=5.8) @_spi(ExperimentalLanguageFeatures) + #endif case transferring + #if compiler(>=5.8) @_spi(ExperimentalLanguageFeatures) + #endif case _resultDependsOn init?(lexeme: Lexer.Lexeme, experimentalFeatures: Parser.ExperimentalFeatures) { @@ -3879,11 +3893,17 @@ extension ValueBindingPatternSyntax { case `let` case `var` case `inout` + #if compiler(>=5.8) @_spi(ExperimentalLanguageFeatures) + #endif case _mutating + #if compiler(>=5.8) @_spi(ExperimentalLanguageFeatures) + #endif case _borrowing + #if compiler(>=5.8) @_spi(ExperimentalLanguageFeatures) + #endif case _consuming init?(lexeme: Lexer.Lexeme, experimentalFeatures: Parser.ExperimentalFeatures) { @@ -3970,11 +3990,17 @@ extension VariableDeclSyntax { case `let` case `var` case `inout` + #if compiler(>=5.8) @_spi(ExperimentalLanguageFeatures) + #endif case _mutating + #if compiler(>=5.8) @_spi(ExperimentalLanguageFeatures) + #endif case _borrowing + #if compiler(>=5.8) @_spi(ExperimentalLanguageFeatures) + #endif case _consuming init?(lexeme: Lexer.Lexeme, experimentalFeatures: Parser.ExperimentalFeatures) { diff --git a/Sources/SwiftSyntax/generated/Keyword.swift b/Sources/SwiftSyntax/generated/Keyword.swift index d38e92e96f6..29a0460c2e7 100644 --- a/Sources/SwiftSyntax/generated/Keyword.swift +++ b/Sources/SwiftSyntax/generated/Keyword.swift @@ -20,14 +20,18 @@ public enum Keyword: UInt8, Hashable, Sendable { case _alignment case _backDeploy case _borrow + #if compiler(>=5.8) @_spi(ExperimentalLanguageFeatures) + #endif case _borrowing case _BridgeObject case _cdecl case _Class case _compilerInitialized case _const + #if compiler(>=5.8) @_spi(ExperimentalLanguageFeatures) + #endif case _consuming case _documentation case _dynamicReplacement @@ -39,7 +43,9 @@ public enum Keyword: UInt8, Hashable, Sendable { case _local case _modify case _move + #if compiler(>=5.8) @_spi(ExperimentalLanguageFeatures) + #endif case _mutating case _NativeClass case _NativeRefCountedObject @@ -104,7 +110,9 @@ public enum Keyword: UInt8, Hashable, Sendable { case `default` case `defer` case `deinit` + #if compiler(>=5.8) @_spi(ExperimentalLanguageFeatures) + #endif case dependsOn case deprecated case derivative @@ -187,9 +195,13 @@ public enum Keyword: UInt8, Hashable, Sendable { case renamed case `repeat` case required + #if compiler(>=5.8) @_spi(ExperimentalLanguageFeatures) + #endif case _resultDependsOn + #if compiler(>=5.8) @_spi(ExperimentalLanguageFeatures) + #endif case _resultDependsOnSelf case `rethrows` case retroactive @@ -197,7 +209,9 @@ public enum Keyword: UInt8, Hashable, Sendable { case reverse case right case safe + #if compiler(>=5.8) @_spi(ExperimentalLanguageFeatures) + #endif case scoped case `self` case `Self` @@ -217,7 +231,9 @@ public enum Keyword: UInt8, Hashable, Sendable { case then case `throw` case `throws` + #if compiler(>=5.8) @_spi(ExperimentalLanguageFeatures) + #endif case transferring case transpose case `true` From 1f0e7d560f17eb0660895a073e8dfe61c0056746 Mon Sep 17 00:00:00 2001 From: Alex Hoppen Date: Tue, 26 Mar 2024 00:31:38 +0100 Subject: [PATCH 2/2] Mark `Syntax.Info` as `@unchecked Sendable` We were relying on the `nonisolated(unsafe)` annotation of `Info.info` to make `Sendable` checking pass. But that feature is only available in Swift 6.0. To be able to build swift-syntax without warnings using Swift <6.0, we need to use `@unchecked Sendable` here. --- Sources/SwiftSyntax/Syntax.swift | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/Sources/SwiftSyntax/Syntax.swift b/Sources/SwiftSyntax/Syntax.swift index 5e9ad2cd13e..e4aee94a2b7 100644 --- a/Sources/SwiftSyntax/Syntax.swift +++ b/Sources/SwiftSyntax/Syntax.swift @@ -16,7 +16,11 @@ public struct Syntax: SyntaxProtocol, SyntaxHashable { /// We need a heap indirection to store a syntax node's parent. We could use an indirect enum here but explicitly /// modelling it using a class allows us to re-use these heap-allocated objects in `SyntaxVisitor`. - final class Info: Sendable { + /// + /// - Note: `@unchecked Sendable` because `info` is mutable. In Swift 6 and above the variable can be declared as + /// `nonisolated(unsafe)` but that attribute doesn't exist in previous Swift versions and a checked Sendable + /// conformance generates a warning. + final class Info: @unchecked Sendable { // For root node. struct Root: Sendable { private var arena: RetainedSyntaxArena