Skip to content

Commit 0bf5b6c

Browse files
authored
Merge pull request #2902 from beccadax/abi-changed-your-first-name
Partial SwiftSyntax support for experimental `@abi` attribute
2 parents 6562544 + 1739cb3 commit 0bf5b6c

26 files changed

+1451
-23
lines changed

CodeGeneration/Sources/SyntaxSupport/AttributeNodes.swift

Lines changed: 29 additions & 0 deletions
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,30 @@ 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: .nodeChoices(choices: [
285+
Child(name: "associatedType", kind: .node(kind: .associatedTypeDecl)),
286+
Child(name: "deinitializer", kind: .node(kind: .deinitializerDecl)),
287+
Child(name: "enumCase", kind: .node(kind: .enumCaseDecl)),
288+
Child(name: "function", kind: .node(kind: .functionDecl)),
289+
Child(name: "initializer", kind: .node(kind: .initializerDecl)),
290+
Child(name: "missing", kind: .node(kind: .missingDecl)),
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+
])
295+
)
296+
]
297+
),
298+
270299
Node(
271300
kind: .conventionAttributeArguments,
272301
base: .syntax,

CodeGeneration/Sources/SyntaxSupport/ExperimentalFeatures.swift

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@ public enum ExperimentalFeature: String, CaseIterable {
2020
case trailingComma
2121
case coroutineAccessors
2222
case valueGenerics
23+
case abiAttribute
2324

2425
/// The name of the feature as it is written in the compiler's `Features.def` file.
2526
public var featureName: String {
@@ -38,6 +39,8 @@ public enum ExperimentalFeature: String, CaseIterable {
3839
return "CoroutineAccessors"
3940
case .valueGenerics:
4041
return "ValueGenerics"
42+
case .abiAttribute:
43+
return "ABIAttribute"
4144
}
4245
}
4346

@@ -58,6 +61,8 @@ public enum ExperimentalFeature: String, CaseIterable {
5861
return "coroutine accessors"
5962
case .valueGenerics:
6063
return "value generics"
64+
case .abiAttribute:
65+
return "@abi attribute"
6166
}
6267
}
6368

CodeGeneration/Sources/SyntaxSupport/KeywordSpec.swift

Lines changed: 3 additions & 0 deletions
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
@@ -405,6 +406,8 @@ public enum Keyword: CaseIterable {
405406
return KeywordSpec("_UnknownLayout")
406407
case ._version:
407408
return KeywordSpec("_version")
409+
case .abi:
410+
return KeywordSpec("abi", experimentalFeature: .abiAttribute)
408411
case .accesses:
409412
return KeywordSpec("accesses")
410413
case .actor:

CodeGeneration/Sources/SyntaxSupport/SyntaxNodeKind.swift

Lines changed: 11 additions & 1 deletion
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
@@ -340,14 +341,23 @@ public enum SyntaxNodeKind: String, CaseIterable, IdentifierConvertible, TypeCon
340341
return .identifier(rawValue)
341342
}
342343

344+
public var uppercasedFirstWordRawValue: String {
345+
switch self {
346+
case .abiAttributeArguments:
347+
"ABIAttributeArguments"
348+
default:
349+
rawValue.withFirstCharacterUppercased
350+
}
351+
}
352+
343353
public var syntaxType: TypeSyntax {
344354
switch self {
345355
case .syntax:
346356
return "Syntax"
347357
case .syntaxCollection:
348358
return "SyntaxCollection"
349359
default:
350-
return "\(raw: rawValue.withFirstCharacterUppercased)Syntax"
360+
return "\(raw: uppercasedFirstWordRawValue)Syntax"
351361
}
352362
}
353363

CodeGeneration/Sources/Utils/SyntaxBuildableType.swift

Lines changed: 1 addition & 1 deletion
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

Lines changed: 1 addition & 1 deletion
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

Lines changed: 71 additions & 4 deletions
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,12 @@ extension Parser {
213223
)
214224
leftParen = leftParen.tokenView.withTokenDiagnostic(tokenDiagnostic: diagnostic, arena: self.arena)
215225
}
216-
let argument = parseArguments(&self)
226+
let argument: RawAttributeSyntax.Arguments
227+
if let parseMissingArguments, leftParen.presence == .missing {
228+
argument = parseMissingArguments(&self)
229+
} else {
230+
argument = parseArguments(&self)
231+
}
217232
let (unexpectedBeforeRightParen, rightParen) = self.expect(.rightParen)
218233
return .attribute(
219234
RawAttributeSyntax(
@@ -255,6 +270,12 @@ extension Parser {
255270
}
256271

257272
switch peek(isAtAnyIn: DeclarationAttributeWithSpecialSyntax.self) {
273+
case .abi:
274+
return parseAttribute(argumentMode: .required) { parser in
275+
return .abiArguments(parser.parseABIAttributeArguments())
276+
} parseMissingArguments: { parser in
277+
return .abiArguments(parser.parseABIAttributeArguments(missingLParen: true))
278+
}
258279
case .available, ._spi_available:
259280
return parseAttribute(argumentMode: .required) { parser in
260281
return .availability(parser.parseAvailabilityArgumentSpecList())
@@ -918,6 +939,52 @@ extension Parser {
918939
}
919940
}
920941

942+
extension Parser {
943+
/// Parse the arguments inside an `@abi(...)` attribute.
944+
///
945+
/// - Parameter missingLParen: `true` if the opening paren for the argument list was missing.
946+
mutating func parseABIAttributeArguments(missingLParen: Bool = false) -> RawABIAttributeArgumentsSyntax {
947+
func makeMissingProviderArguments(unexpectedBefore: [RawSyntax]) -> RawABIAttributeArgumentsSyntax {
948+
return RawABIAttributeArgumentsSyntax(
949+
provider: .missing(
950+
RawMissingDeclSyntax(
951+
unexpectedBefore.isEmpty ? nil : RawUnexpectedNodesSyntax(elements: unexpectedBefore, arena: self.arena),
952+
attributes: self.emptyCollection(RawAttributeListSyntax.self),
953+
modifiers: self.emptyCollection(RawDeclModifierListSyntax.self),
954+
placeholder: self.missingToken(.identifier, text: "<#declaration#>"),
955+
arena: arena
956+
)
957+
),
958+
arena: self.arena
959+
)
960+
}
961+
962+
// Consider the three kinds of mistakes we might see here:
963+
//
964+
// 1. The user forgot the argument: `@abi(<<here>>) var x: Int`
965+
// 2. The user forgot the left paren: `@abi<<here>> var x_abi: Int) var x: Int`
966+
// 3. The user forgot the whole argument list: `@abi<<here>> var x: Int`
967+
//
968+
// It's difficult to write code that recovers from both #2 and #3. The problem is that in both cases, what comes
969+
// next looks like a declaration, so a simple lookahead cannot distinguish between them--you'd have to parse all
970+
// the way to the closing paren. (And what if *that's* also missing?)
971+
//
972+
// In lieu of that, we judge that recovering gracefully from #3 is more important than #2 and therefore do not even
973+
// attempt to parse the argument unless we've seen a left paren.
974+
guard !missingLParen && !self.at(.rightParen) else {
975+
return makeMissingProviderArguments(unexpectedBefore: [])
976+
}
977+
978+
let decl = parseDeclaration(in: .argumentList)
979+
980+
guard let provider = RawABIAttributeArgumentsSyntax.Provider(decl) else {
981+
return makeMissingProviderArguments(unexpectedBefore: [decl.raw])
982+
}
983+
984+
return RawABIAttributeArgumentsSyntax(provider: provider, arena: self.arena)
985+
}
986+
}
987+
921988
extension Parser {
922989
mutating func parseBackDeployedAttributeArguments() -> RawBackDeployedAttributeArgumentsSyntax {
923990
let (unexpectedBeforeLabel, label) = self.expect(.keyword(.before))

0 commit comments

Comments
 (0)