Skip to content

Commit de2dc9f

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

24 files changed

+597
-28
lines changed

Diff for: CodeGeneration/Sources/SyntaxSupport/AttributeNodes.swift

+19
Original file line numberDiff line numberDiff line change
@@ -141,6 +141,11 @@ public let ATTRIBUTE_NODES: [Node] = [
141141
name: "documentationArguments",
142142
kind: .node(kind: .documentationAttributeArgumentList)
143143
),
144+
Child(
145+
name: "abiArguments",
146+
kind: .node(kind: .abiAttributeArguments),
147+
experimentalFeature: .abiAttribute
148+
)
144149
]),
145150
documentation: """
146151
The arguments of the attribute.
@@ -251,6 +256,20 @@ public let ATTRIBUTE_NODES: [Node] = [
251256
]
252257
),
253258

259+
Node(
260+
kind: .abiAttributeArguments,
261+
base: .syntax,
262+
experimentalFeature: .abiAttribute,
263+
nameForDiagnostics: "ABI-providing declaration",
264+
documentation: "The arguments of the '@abi' attribute",
265+
children: [
266+
Child(
267+
name: "provider",
268+
kind: .node(kind: .decl)
269+
),
270+
]
271+
),
272+
254273
Node(
255274
kind: .conventionAttributeArguments,
256275
base: .syntax,

Diff for: 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

Diff for: 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:

Diff for: 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
@@ -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

Diff for: 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
}

Diff for: 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(

Diff for: Sources/SwiftParser/Attributes.swift

+51-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,33 @@ extension Parser {
918938
}
919939
}
920940

941+
extension Parser {
942+
mutating func parseABIAttributeArguments(missing: Bool = false) -> RawABIAttributeArgumentsSyntax {
943+
let providerDecl: RawDeclSyntax = if missing {
944+
// There are two situations where the left paren might be missing:
945+
// 1. The user just forgot the paren: `@abi var x_abi: Int) var x: Int`
946+
// 2. The user forgot the whole argument list: `@abi var x: Int`
947+
// Normally, we would distinguish these by seeing if whatever comes after
948+
// the attribute parses as an argument. However, for @abi the argument is
949+
// a decl, so in #2, we would consume the decl the attribute is attached
950+
// to! This leads to a lousy diagnostic in that situation.
951+
// Avoid this problem by simply returning a missing decl immediately.
952+
// FIXME: Could we look ahead to find an unbalanced parenthesis?
953+
RawDeclSyntax(
954+
RawMissingDeclSyntax(
955+
attributes: self.emptyCollection(RawAttributeListSyntax.self),
956+
modifiers: self.emptyCollection(RawDeclModifierListSyntax.self),
957+
arena: arena
958+
)
959+
)
960+
} else {
961+
parseDeclaration(in: .attribute)
962+
}
963+
964+
return RawABIAttributeArgumentsSyntax(provider: providerDecl, arena: arena)
965+
}
966+
}
967+
921968
extension Parser {
922969
mutating func parseBackDeployedAttributeArguments() -> RawBackDeployedAttributeArgumentsSyntax {
923970
let (unexpectedBeforeLabel, label) = self.expect(.keyword(.before))

Diff for: Sources/SwiftParser/Declarations.swift

+40-14
Original file line numberDiff line numberDiff line change
@@ -168,11 +168,24 @@ extension Parser {
168168
}
169169
}
170170

171+
/// Information about the syntactic position of the declaration being parsed.
172+
/// Used to tweak recovery and to permit missing bodies.
173+
enum DeclarationParsingContext {
174+
/// The declaration is at the top level of a file.
175+
case topLevelCode
176+
177+
/// The declaration is nested inside the member list of a DeclGroupSyntax.
178+
case memberList
179+
180+
/// The declaration is nested inside the argument list of an attribute.
181+
case attribute
182+
}
183+
171184
/// Parse a declaration.
172185
///
173-
/// If `inMemberDeclList` is `true`, we know that the next item must be a
186+
/// If `parseContext` is `memberList`, we know that the next item must be a
174187
/// declaration and thus start with a keyword. This allows further recovery.
175-
mutating func parseDeclaration(inMemberDeclList: Bool = false) -> RawDeclSyntax {
188+
mutating func parseDeclaration(in parseContext: DeclarationParsingContext = .topLevelCode) -> RawDeclSyntax {
176189
// If we are at a `#if` of attributes, the `#if` directive should be
177190
// parsed when we're parsing the attributes.
178191
if self.at(.poundIf) && !self.withLookahead({ $0.consumeIfConfigOfAttributes() }) {
@@ -221,7 +234,14 @@ extension Parser {
221234
// to parse.
222235
// If we are inside a memberDecl list, we don't want to eat closing braces (which most likely close the outer context)
223236
// while recovering to the declaration start.
224-
let recoveryPrecedence = inMemberDeclList ? TokenPrecedence.closingBrace : nil
237+
let recoveryPrecedence = switch parseContext {
238+
case .topLevelCode:
239+
Optional<TokenPrecedence>.none
240+
case .memberList:
241+
Optional(TokenPrecedence.closingBrace)
242+
case .attribute:
243+
Optional(TokenPrecedence.weakBracketed(closingDelimiter: .rightParen))
244+
}
225245
recoveryResult = self.canRecoverTo(anyIn: DeclarationKeyword.self, overrideRecoveryPrecedence: recoveryPrecedence)
226246
}
227247

@@ -230,28 +250,28 @@ extension Parser {
230250
return RawDeclSyntax(self.parseImportDeclaration(attrs, handle))
231251
case (.lhs(.class), let handle)?:
232252
return RawDeclSyntax(
233-
self.parseNominalTypeDeclaration(for: RawClassDeclSyntax.self, attrs: attrs, introucerHandle: handle)
253+
self.parseNominalTypeDeclaration(for: RawClassDeclSyntax.self, attrs: attrs, introucerHandle: handle, parseContext: parseContext)
234254
)
235255
case (.lhs(.enum), let handle)?:
236256
return RawDeclSyntax(
237-
self.parseNominalTypeDeclaration(for: RawEnumDeclSyntax.self, attrs: attrs, introucerHandle: handle)
257+
self.parseNominalTypeDeclaration(for: RawEnumDeclSyntax.self, attrs: attrs, introucerHandle: handle, parseContext: parseContext)
238258
)
239259
case (.lhs(.case), let handle)?:
240260
return RawDeclSyntax(self.parseEnumCaseDeclaration(attrs, handle))
241261
case (.lhs(.struct), let handle)?:
242262
return RawDeclSyntax(
243-
self.parseNominalTypeDeclaration(for: RawStructDeclSyntax.self, attrs: attrs, introucerHandle: handle)
263+
self.parseNominalTypeDeclaration(for: RawStructDeclSyntax.self, attrs: attrs, introucerHandle: handle, parseContext: parseContext)
244264
)
245265
case (.lhs(.protocol), let handle)?:
246266
return RawDeclSyntax(
247-
self.parseNominalTypeDeclaration(for: RawProtocolDeclSyntax.self, attrs: attrs, introucerHandle: handle)
267+
self.parseNominalTypeDeclaration(for: RawProtocolDeclSyntax.self, attrs: attrs, introucerHandle: handle, parseContext: parseContext)
248268
)
249269
case (.lhs(.associatedtype), let handle)?:
250270
return RawDeclSyntax(self.parseAssociatedTypeDeclaration(attrs, handle))
251271
case (.lhs(.typealias), let handle)?:
252272
return RawDeclSyntax(self.parseTypealiasDeclaration(attrs, handle))
253273
case (.lhs(.extension), let handle)?:
254-
return RawDeclSyntax(self.parseExtensionDeclaration(attrs, handle))
274+
return RawDeclSyntax(self.parseExtensionDeclaration(attrs, handle, parseContext: parseContext))
255275
case (.lhs(.func), let handle)?:
256276
return RawDeclSyntax(self.parseFuncDeclaration(attrs, handle))
257277
case (.lhs(.subscript), let handle)?:
@@ -266,19 +286,19 @@ extension Parser {
266286
return RawDeclSyntax(self.parsePrecedenceGroupDeclaration(attrs, handle))
267287
case (.lhs(.actor), let handle)?:
268288
return RawDeclSyntax(
269-
self.parseNominalTypeDeclaration(for: RawActorDeclSyntax.self, attrs: attrs, introucerHandle: handle)
289+
self.parseNominalTypeDeclaration(for: RawActorDeclSyntax.self, attrs: attrs, introucerHandle: handle, parseContext: parseContext)
270290
)
271291
case (.lhs(.macro), let handle)?:
272292
return RawDeclSyntax(self.parseMacroDeclaration(attrs: attrs, introducerHandle: handle))
273293
case (.lhs(.pound), let handle)?:
274294
return RawDeclSyntax(self.parseMacroExpansionDeclaration(attrs, handle))
275295
case (.rhs, let handle)?:
276-
return RawDeclSyntax(self.parseBindingDeclaration(attrs, handle, inMemberDeclList: inMemberDeclList))
296+
return RawDeclSyntax(self.parseBindingDeclaration(attrs, handle, inMemberDeclList: parseContext != .topLevelCode))
277297
case nil:
278298
break
279299
}
280300

281-
if inMemberDeclList {
301+
if parseContext != .topLevelCode {
282302
let isProbablyVarDecl = self.at(.identifier, .wildcard) && self.peek(isAt: .colon, .equal, .comma)
283303
let isProbablyTupleDecl = self.at(.leftParen) && self.peek(isAt: .identifier, .wildcard)
284304

@@ -377,7 +397,8 @@ extension Parser {
377397
/// Parse an extension declaration.
378398
mutating func parseExtensionDeclaration(
379399
_ attrs: DeclAttributes,
380-
_ handle: RecoveryConsumptionHandle
400+
_ handle: RecoveryConsumptionHandle,
401+
parseContext: DeclarationParsingContext
381402
) -> RawExtensionDeclSyntax {
382403
let (unexpectedBeforeExtensionKeyword, extensionKeyword) = self.eat(handle)
383404
let type = self.parseType()
@@ -395,7 +416,12 @@ extension Parser {
395416
} else {
396417
whereClause = nil
397418
}
398-
let memberBlock = self.parseMemberBlock(introducer: extensionKeyword)
419+
let memberBlock: RawMemberBlockSyntax?
420+
if parseContext == .attribute && !self.at(.leftBrace) {
421+
memberBlock = nil
422+
} else {
423+
memberBlock = self.parseMemberBlock(introducer: extensionKeyword)
424+
}
399425
return RawExtensionDeclSyntax(
400426
attributes: attrs.attributes,
401427
modifiers: attrs.modifiers,
@@ -746,7 +772,7 @@ extension Parser {
746772
if self.at(.poundSourceLocation) {
747773
decl = RawDeclSyntax(self.parsePoundSourceLocationDirective())
748774
} else {
749-
decl = self.parseDeclaration(inMemberDeclList: true)
775+
decl = self.parseDeclaration(in: .memberList)
750776
}
751777

752778
let semi = self.consume(if: .semicolon)

Diff for: Sources/SwiftParser/Nominals.swift

+8-2
Original file line numberDiff line numberDiff line change
@@ -211,7 +211,8 @@ extension Parser {
211211
mutating func parseNominalTypeDeclaration<T>(
212212
for T: T.Type,
213213
attrs: DeclAttributes,
214-
introucerHandle: RecoveryConsumptionHandle
214+
introucerHandle: RecoveryConsumptionHandle,
215+
parseContext: DeclarationParsingContext
215216
) -> T where T: NominalTypeDeclarationTrait {
216217
let (unexpectedBeforeIntroducerKeyword, introducerKeyword) = self.eat(introucerHandle)
217218
let (unexpectedBeforeName, name) = self.expectIdentifier(keywordRecovery: true)
@@ -258,7 +259,12 @@ extension Parser {
258259
whereClause = nil
259260
}
260261

261-
let memberBlock = self.parseMemberBlock(introducer: introducerKeyword)
262+
let memberBlock: RawMemberBlockSyntax?
263+
if parseContext == .attribute && !self.at(.leftBrace) {
264+
memberBlock = nil
265+
} else {
266+
memberBlock = self.parseMemberBlock(introducer: introducerKeyword)
267+
}
262268
return T.init(
263269
attributes: attrs.attributes,
264270
modifiers: attrs.modifiers,

Diff for: Sources/SwiftParser/TokenPrecedence.swift

+1
Original file line numberDiff line numberDiff line change
@@ -283,6 +283,7 @@ enum TokenPrecedence: Comparable {
283283
._swift_native_objc_runtime_base,
284284
._typeEraser,
285285
._unavailableFromAsync,
286+
.abi,
286287
.attached,
287288
.available,
288289
.backDeployed,

0 commit comments

Comments
 (0)