Skip to content

Commit f7dfc81

Browse files
committed
Parse the @abi attribute in SwiftSyntax
Matches work in swiftlang/swift#76878.
1 parent bfce22b commit f7dfc81

26 files changed

+1235
-34
lines changed

CodeGeneration/Sources/SyntaxSupport/AttributeNodes.swift

+30
Original file line numberDiff line numberDiff line change
@@ -139,6 +139,11 @@ public let ATTRIBUTE_NODES: [Node] = [
139139
name: "documentationArguments",
140140
kind: .node(kind: .documentationAttributeArgumentList)
141141
),
142+
Child(
143+
name: "abiArguments",
144+
kind: .node(kind: .abiAttributeArguments),
145+
experimentalFeature: .abiAttribute
146+
)
142147
]),
143148
documentation: """
144149
The arguments of the attribute.
@@ -267,6 +272,31 @@ public let ATTRIBUTE_NODES: [Node] = [
267272
]
268273
),
269274

275+
Node(
276+
kind: .abiAttributeArguments,
277+
base: .syntax,
278+
experimentalFeature: .abiAttribute,
279+
nameForDiagnostics: "ABI-providing declaration",
280+
documentation: "The arguments of the '@abi' attribute",
281+
children: [
282+
Child(
283+
name: "provider",
284+
kind: ChildKind.nodeChoices(choices: [
285+
Child(name: "associatedType", kind: .node(kind: .associatedTypeDecl)),
286+
Child(name: "declGroup", kind: .node(kind: .declGroupHeader)),
287+
Child(name: "deinitializer", kind: .node(kind: .deinitializerDecl)),
288+
Child(name: "enumCase", kind: .node(kind: .enumCaseDecl)),
289+
Child(name: "function", kind: .node(kind: .functionDecl)),
290+
Child(name: "initializer", kind: .node(kind: .initializerDecl)),
291+
Child(name: "subscript", kind: .node(kind: .subscriptDecl)),
292+
Child(name: "typeAlias", kind: .node(kind: .typeAliasDecl)),
293+
Child(name: "variable", kind: .node(kind: .variableDecl)),
294+
Child(name: "unsupported", kind: .node(kind: .decl)),
295+
])
296+
),
297+
]
298+
),
299+
270300
Node(
271301
kind: .conventionAttributeArguments,
272302
base: .syntax,

CodeGeneration/Sources/SyntaxSupport/ExperimentalFeatures.swift

+3
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@ public enum ExperimentalFeature: String, CaseIterable {
1919
case nonescapableTypes
2020
case trailingComma
2121
case coroutineAccessors
22+
case abiAttribute
2223

2324
/// The name of the feature, which is used in the doc comment.
2425
public var featureName: String {
@@ -35,6 +36,8 @@ public enum ExperimentalFeature: String, CaseIterable {
3536
return "trailing comma"
3637
case .coroutineAccessors:
3738
return "CoroutineAccessors"
39+
case .abiAttribute:
40+
return "@abi attribute"
3841
}
3942
}
4043

CodeGeneration/Sources/SyntaxSupport/KeywordSpec.swift

+3
Original file line numberDiff line numberDiff line change
@@ -131,6 +131,7 @@ public enum Keyword: CaseIterable {
131131
case _underlyingVersion
132132
case _UnknownLayout
133133
case _version
134+
case abi
134135
case accesses
135136
case actor
136137
case addressWithNativeOwner
@@ -404,6 +405,8 @@ public enum Keyword: CaseIterable {
404405
return KeywordSpec("_UnknownLayout")
405406
case ._version:
406407
return KeywordSpec("_version")
408+
case .abi:
409+
return KeywordSpec("abi", experimentalFeature: .abiAttribute)
407410
case .accesses:
408411
return KeywordSpec("accesses")
409412
case .actor:

CodeGeneration/Sources/SyntaxSupport/SyntaxNodeKind.swift

+11-1
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@ public enum SyntaxNodeKind: String, CaseIterable, IdentifierConvertible, TypeCon
2222

2323
case _canImportExpr
2424
case _canImportVersionInfo
25+
case abiAttributeArguments
2526
case accessorBlock
2627
case accessorDecl
2728
case accessorDeclList
@@ -348,14 +349,23 @@ public enum SyntaxNodeKind: String, CaseIterable, IdentifierConvertible, TypeCon
348349
return .identifier(rawValue)
349350
}
350351

352+
public var uppercasedFirstWordRawValue: String {
353+
switch self {
354+
case .abiAttributeArguments:
355+
"ABIAttributeArguments"
356+
default:
357+
rawValue.withFirstCharacterUppercased
358+
}
359+
}
360+
351361
public var syntaxType: TypeSyntax {
352362
switch self {
353363
case .syntax:
354364
return "Syntax"
355365
case .syntaxCollection:
356366
return "SyntaxCollection"
357367
default:
358-
return "\(raw: rawValue.withFirstCharacterUppercased)Syntax"
368+
return "\(raw: uppercasedFirstWordRawValue)Syntax"
359369
}
360370
}
361371

CodeGeneration/Sources/Utils/SyntaxBuildableType.swift

+1-1
Original file line numberDiff line numberDiff line change
@@ -151,7 +151,7 @@ public struct SyntaxBuildableType: Hashable {
151151
public var resultBuilderType: TypeSyntax {
152152
switch kind {
153153
case .node(kind: let kind):
154-
return TypeSyntax("\(raw: kind.rawValue.withFirstCharacterUppercased)Builder")
154+
return TypeSyntax("\(raw: kind.uppercasedFirstWordRawValue)Builder")
155155
case .token:
156156
preconditionFailure("Tokens cannot be constructed using result builders")
157157
}

CodeGeneration/Sources/generate-swift-syntax/templates/swiftsyntax/SyntaxEnumFile.swift

+1-1
Original file line numberDiff line numberDiff line change
@@ -60,7 +60,7 @@ let syntaxEnumFile = SourceFileSyntax(leadingTrivia: copyrightHeader) {
6060

6161
for base in SYNTAX_NODES where base.kind.isBase {
6262
let baseKind = base.kind
63-
let baseName = baseKind.rawValue.withFirstCharacterUppercased
63+
let baseName = baseKind.uppercasedFirstWordRawValue
6464
let enumType: TypeSyntax = "\(raw: baseName)SyntaxEnum"
6565

6666
try! EnumDeclSyntax(

Sources/SwiftParser/Attributes.swift

+93-4
Original file line numberDiff line numberDiff line change
@@ -11,9 +11,9 @@
1111
//===----------------------------------------------------------------------===//
1212

1313
#if swift(>=6)
14-
@_spi(RawSyntax) internal import SwiftSyntax
14+
@_spi(ExperimentalLanguageFeatures) @_spi(RawSyntax) internal import SwiftSyntax
1515
#else
16-
@_spi(RawSyntax) import SwiftSyntax
16+
@_spi(ExperimentalLanguageFeatures) @_spi(RawSyntax) import SwiftSyntax
1717
#endif
1818

1919
extension Parser {
@@ -58,6 +58,7 @@ extension Parser {
5858
case _typeEraser
5959
case _unavailableFromAsync
6060
case `rethrows`
61+
case abi
6162
case attached
6263
case available
6364
case backDeployed
@@ -95,6 +96,7 @@ extension Parser {
9596
case TokenSpec(._typeEraser): self = ._typeEraser
9697
case TokenSpec(._unavailableFromAsync): self = ._unavailableFromAsync
9798
case TokenSpec(.`rethrows`): self = .rethrows
99+
case TokenSpec(.abi) where experimentalFeatures.contains(.abiAttribute): self = .abi
98100
case TokenSpec(.attached): self = .attached
99101
case TokenSpec(.available): self = .available
100102
case TokenSpec(.backDeployed): self = .backDeployed
@@ -136,6 +138,7 @@ extension Parser {
136138
case ._typeEraser: return .keyword(._typeEraser)
137139
case ._unavailableFromAsync: return .keyword(._unavailableFromAsync)
138140
case .`rethrows`: return .keyword(.rethrows)
141+
case .abi: return .keyword(.abi)
139142
case .attached: return .keyword(.attached)
140143
case .available: return .keyword(.available)
141144
case .backDeployed: return .keyword(.backDeployed)
@@ -176,9 +179,16 @@ extension Parser {
176179
case noArgument
177180
}
178181

182+
/// Parse the argument of an attribute, if it has one.
183+
///
184+
/// - Parameters:
185+
/// - argumentMode: Indicates whether the attribute must, may, or may not have an argument.
186+
/// - parseArguments: Called to parse the argument list. If there is an opening parenthesis, it will have already been consumed.
187+
/// - parseMissingArguments: If provided, called instead of `parseArgument` when an argument list was required but no opening parenthesis was present.
179188
mutating func parseAttribute(
180189
argumentMode: AttributeArgumentMode,
181-
parseArguments: (inout Parser) -> RawAttributeSyntax.Arguments
190+
parseArguments: (inout Parser) -> RawAttributeSyntax.Arguments,
191+
parseMissingArguments: ((inout Parser) -> RawAttributeSyntax.Arguments)? = nil
182192
) -> RawAttributeListSyntax.Element {
183193
var (unexpectedBeforeAtSign, atSign) = self.expect(.atSign)
184194
if atSign.trailingTriviaByteLength > 0 || self.currentToken.leadingTriviaByteLength > 0 {
@@ -213,7 +223,11 @@ extension Parser {
213223
)
214224
leftParen = leftParen.tokenView.withTokenDiagnostic(tokenDiagnostic: diagnostic, arena: self.arena)
215225
}
216-
let argument = parseArguments(&self)
226+
let argument = if let parseMissingArguments, leftParen.presence == .missing {
227+
parseMissingArguments(&self)
228+
} else {
229+
parseArguments(&self)
230+
}
217231
let (unexpectedBeforeRightParen, rightParen) = self.expect(.rightParen)
218232
return .attribute(
219233
RawAttributeSyntax(
@@ -255,6 +269,12 @@ extension Parser {
255269
}
256270

257271
switch peek(isAtAnyIn: DeclarationAttributeWithSpecialSyntax.self) {
272+
case .abi:
273+
return parseAttribute(argumentMode: .required) { parser in
274+
return .abiArguments(parser.parseABIAttributeArguments())
275+
} parseMissingArguments: { parser in
276+
return .abiArguments(parser.parseABIAttributeArguments(missing: true))
277+
}
258278
case .available, ._spi_available:
259279
return parseAttribute(argumentMode: .required) { parser in
260280
return .availability(parser.parseAvailabilityArgumentSpecList())
@@ -918,6 +938,75 @@ extension Parser {
918938
}
919939
}
920940

941+
extension Parser {
942+
mutating func parseABIAttributeArguments(missing: Bool = false) -> RawABIAttributeArgumentsSyntax {
943+
let providerDecl: RawABIAttributeArgumentsSyntax.Provider
944+
if missing || at(.rightParen) {
945+
// There are two situations where the left paren might be missing:
946+
// 1. The user just forgot the paren: `@abi var x_abi: Int) var x: Int`
947+
// 2. The user forgot the whole argument list: `@abi var x: Int`
948+
// Normally, we would distinguish these by seeing if whatever comes after
949+
// the attribute parses as an argument. However, for @abi the argument is
950+
// a decl, so in #2, we would consume the decl the attribute is attached
951+
// to! This leads to a lousy diagnostic in that situation.
952+
// Avoid this problem by simply returning a missing decl immediately.
953+
// FIXME: Could we look ahead to find an unbalanced parenthesis?
954+
providerDecl = .unsupported(
955+
RawDeclSyntax(
956+
RawMissingDeclSyntax(
957+
attributes: self.emptyCollection(RawAttributeListSyntax.self),
958+
modifiers: self.emptyCollection(RawDeclModifierListSyntax.self),
959+
arena: arena
960+
)
961+
)
962+
)
963+
} else {
964+
switch nextDeclarationKeyword() {
965+
case .group?:
966+
providerDecl = RawABIAttributeArgumentsSyntax.Provider(parseDeclarationGroupHeader())!
967+
968+
case .simple?, .binding?, nil:
969+
providerDecl = RawABIAttributeArgumentsSyntax.Provider(parseDeclaration(in: .argumentList))!
970+
}
971+
}
972+
973+
return RawABIAttributeArgumentsSyntax(provider: providerDecl, arena: arena)
974+
}
975+
976+
mutating func nextDeclarationKeyword() -> DeclarationKeyword? {
977+
return self.withLookahead { subparser in
978+
// Consume attributes.
979+
var attributeProgress = LoopProgressCondition()
980+
while subparser.hasProgressed(&attributeProgress) && subparser.at(.atSign) {
981+
_ = subparser.consumeAttributeList()
982+
}
983+
984+
// Consume modifiers.
985+
if subparser.currentToken.isLexerClassifiedKeyword || subparser.currentToken.rawTokenKind == .identifier {
986+
var modifierProgress = LoopProgressCondition()
987+
while let (modifierKind, handle) = subparser.at(anyIn: DeclarationModifier.self),
988+
subparser.hasProgressed(&modifierProgress)
989+
{
990+
if modifierKind == .class {
991+
// `class` is a modifier only if it's followed by an introducer or modifier.
992+
if subparser.peek(isAtAnyIn: DeclarationStart.self) == nil {
993+
break
994+
}
995+
}
996+
subparser.eat(handle)
997+
if subparser.at(.leftParen) && modifierKind.canHaveParenthesizedArgument {
998+
subparser.consumeAnyToken()
999+
subparser.consume(to: .rightParen)
1000+
}
1001+
}
1002+
}
1003+
1004+
// Is the next thing a declaration introducer?
1005+
return subparser.at(anyIn: DeclarationKeyword.self)?.spec
1006+
}
1007+
}
1008+
}
1009+
9211010
extension Parser {
9221011
mutating func parseBackDeployedAttributeArguments() -> RawBackDeployedAttributeArgumentsSyntax {
9231012
let (unexpectedBeforeLabel, label) = self.expect(.keyword(.before))

0 commit comments

Comments
 (0)