From b5b764c93403a68ebac783a45e12073a54df57ad Mon Sep 17 00:00:00 2001 From: Rintaro Ishizaki Date: Fri, 21 Mar 2025 14:23:49 -0700 Subject: [PATCH] [Parser] Always enable `abiAttribute` feature `ABIAttributeArgumentsSyntax` is still under `@_spi(ExperimentalLanguageFeatures)`. Not parsing the interior of `@abi` attribute causes catastrophic breakage to the tree. Having "unknown" syntax kind in the tree is better than dealing with structually broken trees. --- .../SyntaxSupport/ExperimentalFeatures.swift | 11 +++ .../ExperimentalFeaturesFile.swift | 13 +++ Sources/SwiftParser/Parser.swift | 2 +- .../generated/ExperimentalFeatures.swift | 5 ++ Tests/SwiftParserTest/AttributeTests.swift | 81 +++++++------------ 5 files changed, 57 insertions(+), 55 deletions(-) diff --git a/CodeGeneration/Sources/SyntaxSupport/ExperimentalFeatures.swift b/CodeGeneration/Sources/SyntaxSupport/ExperimentalFeatures.swift index affe9c1b03f..7994dc25c47 100644 --- a/CodeGeneration/Sources/SyntaxSupport/ExperimentalFeatures.swift +++ b/CodeGeneration/Sources/SyntaxSupport/ExperimentalFeatures.swift @@ -81,6 +81,17 @@ public enum ExperimentalFeature: String, CaseIterable { } } + public var isAlwaysEnabledInParser: Bool { + switch self { + case .abiAttribute: + // `@abi` attributes should be always parsed because not parsing its interior + // breaks the tree catastrophically. + return true + default: + return false + } + } + /// The token that represents the experimental feature case name. public var token: TokenSyntax { .identifier(rawValue) diff --git a/CodeGeneration/Sources/generate-swift-syntax/templates/swiftparser/ExperimentalFeaturesFile.swift b/CodeGeneration/Sources/generate-swift-syntax/templates/swiftparser/ExperimentalFeaturesFile.swift index 0879e1a3a41..3bdd853655e 100644 --- a/CodeGeneration/Sources/generate-swift-syntax/templates/swiftparser/ExperimentalFeaturesFile.swift +++ b/CodeGeneration/Sources/generate-swift-syntax/templates/swiftparser/ExperimentalFeaturesFile.swift @@ -40,6 +40,19 @@ let experimentalFeaturesFile = SourceFileSyntax(leadingTrivia: copyrightHeader) ) } + try! VariableDeclSyntax( + """ + /// Set of features always enabled. + static var alwaysEnabledFeatures: Self + """ + ) { + ArrayExprSyntax { + for feature in ExperimentalFeature.allCases where feature.isAlwaysEnabledInParser { + ArrayElementSyntax(expression: ExprSyntax(".\(feature.token)")) + } + } + } + try! InitializerDeclSyntax( """ /// Creates a new value representing the experimental feature with the diff --git a/Sources/SwiftParser/Parser.swift b/Sources/SwiftParser/Parser.swift index 6d6e8b37cb3..48863fbca89 100644 --- a/Sources/SwiftParser/Parser.swift +++ b/Sources/SwiftParser/Parser.swift @@ -237,7 +237,7 @@ public struct Parser { self.maximumNestingLevel = maximumNestingLevel ?? Self.defaultMaximumNestingLevel self.swiftVersion = swiftVersion ?? Self.defaultSwiftVersion - self.experimentalFeatures = experimentalFeatures + self.experimentalFeatures = experimentalFeatures.union(.alwaysEnabledFeatures) self.lookaheadTrackerOwner = LookaheadTrackerOwner() self.lexemes = Lexer.tokenize(input, lookaheadTracker: lookaheadTrackerOwner.lookaheadTracker) diff --git a/Sources/SwiftParser/generated/ExperimentalFeatures.swift b/Sources/SwiftParser/generated/ExperimentalFeatures.swift index e31d3628ee3..15d758cbbfc 100644 --- a/Sources/SwiftParser/generated/ExperimentalFeatures.swift +++ b/Sources/SwiftParser/generated/ExperimentalFeatures.swift @@ -58,6 +58,11 @@ extension Parser.ExperimentalFeatures { /// Whether to enable the parsing of sugar type for InlineArray. public static let inlineArrayTypeSugar = Self (rawValue: 1 << 10) + /// Set of features always enabled. + static var alwaysEnabledFeatures: Self { + [.abiAttribute] + } + /// 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) { diff --git a/Tests/SwiftParserTest/AttributeTests.swift b/Tests/SwiftParserTest/AttributeTests.swift index 6415986681d..5d82301c61e 100644 --- a/Tests/SwiftParserTest/AttributeTests.swift +++ b/Tests/SwiftParserTest/AttributeTests.swift @@ -960,23 +960,20 @@ final class AttributeTests: ParserTestCase { parameterClause: FunctionParameterClauseSyntax {}, returnClause: ReturnClauseSyntax(type: TypeSyntax("Int")) ) - ) {}, - experimentalFeatures: [.abiAttribute] + ) {} ) assertParse( """ @abi(associatedtype AssocTy) associatedtype AssocTy - """, - experimentalFeatures: [.abiAttribute] + """ ) assertParse( """ @abi(deinit) deinit {} - """, - experimentalFeatures: [.abiAttribute] + """ ) assertParse( """ @@ -984,50 +981,43 @@ final class AttributeTests: ParserTestCase { @abi(case someCase) case someCase } - """, - experimentalFeatures: [.abiAttribute] + """ ) assertParse( """ @abi(func fn()) func fn() - """, - experimentalFeatures: [.abiAttribute] + """ ) assertParse( """ @abi(init()) init() {} - """, - experimentalFeatures: [.abiAttribute] + """ ) assertParse( """ @abi(subscript(i: Int) -> Element) subscript(i: Int) -> Element {} - """, - experimentalFeatures: [.abiAttribute] + """ ) assertParse( """ @abi(typealias Typealias = @escaping () -> Void) typealias Typealias = () -> Void - """, - experimentalFeatures: [.abiAttribute] + """ ) assertParse( """ @abi(let c1, c2) let c1, c2 - """, - experimentalFeatures: [.abiAttribute] + """ ) assertParse( """ @abi(var v1, v2) var v1, v2 - """, - experimentalFeatures: [.abiAttribute] + """ ) assertParse( @@ -1042,8 +1032,7 @@ final class AttributeTests: ParserTestCase { ), diagnostics: [ DiagnosticSpec(locationMarker: "1️⃣", message: "editor placeholder in source file") - ], - experimentalFeatures: [.abiAttribute] + ] ) assertParse( @@ -1067,8 +1056,7 @@ final class AttributeTests: ParserTestCase { ), diagnostics: [ DiagnosticSpec(locationMarker: "1️⃣", message: "import is not permitted as ABI-providing declaration") - ], - experimentalFeatures: [.abiAttribute] + ] ) // @@ -1079,15 +1067,13 @@ final class AttributeTests: ParserTestCase { """ @abi(associatedtype AssocTy = T) associatedtype AssocTy - """, - experimentalFeatures: [.abiAttribute] + """ ) assertParse( """ @abi(deinit {}) deinit {} - """, - experimentalFeatures: [.abiAttribute] + """ ) assertParse( """ @@ -1095,50 +1081,43 @@ final class AttributeTests: ParserTestCase { @abi(case someCase = 42) case someCase } - """, - experimentalFeatures: [.abiAttribute] + """ ) assertParse( """ @abi(func fn() {}) func fn() - """, - experimentalFeatures: [.abiAttribute] + """ ) assertParse( """ @abi(init() {}) init() {} - """, - experimentalFeatures: [.abiAttribute] + """ ) assertParse( """ @abi(subscript(i: Int) -> Element { get {} set {} }) subscript(i: Int) -> Element {} - """, - experimentalFeatures: [.abiAttribute] + """ ) assertParse( """ @abi(let c1 = 1, c2 = 2) let c1, c2 - """, - experimentalFeatures: [.abiAttribute] + """ ) assertParse( """ @abi(var v1 = 1, v2 = 2) var v1, v2 - """, - experimentalFeatures: [.abiAttribute] + """ ) assertParse( """ @abi(var v3 { get {} set {} }) var v3 - """, - experimentalFeatures: [.abiAttribute] + """ ) // @@ -1160,8 +1139,7 @@ final class AttributeTests: ParserTestCase { fixedSource: """ @abi(var <#pattern#>) var v1 - """, - experimentalFeatures: [.abiAttribute] + """ ) assertParse( """ @@ -1184,8 +1162,7 @@ final class AttributeTests: ParserTestCase { fixedSource: """ @abi(var v2) var v2 - """, - experimentalFeatures: [.abiAttribute] + """ ) assertParse( """ @@ -1203,8 +1180,7 @@ final class AttributeTests: ParserTestCase { fixedSource: """ @abi(<#declaration#>) func fn2() {} - """, - experimentalFeatures: [.abiAttribute] + """ ) assertParse( """ @@ -1221,8 +1197,7 @@ final class AttributeTests: ParserTestCase { fixedSource: """ @abi(<#declaration#>) func fn3() {} - """, - experimentalFeatures: [.abiAttribute] + """ ) assertParse( """ @@ -1244,8 +1219,7 @@ final class AttributeTests: ParserTestCase { fixedSource: """ @abi(<#declaration#>) func fn4_abi()) func fn4() {} - """, - experimentalFeatures: [.abiAttribute] + """ ) // `#if` is banned inside an `@abi` attribute. @@ -1278,8 +1252,7 @@ final class AttributeTests: ParserTestCase { func _fn() throws(E) ) func fn() throws(E) {} - """, - experimentalFeatures: [.abiAttribute] + """ ) }