Skip to content

Commit 7e8f21c

Browse files
committed
[SwiftParser] Parse @abi attribute arguments even without feature flag
When the parser see `@abi` attribute without the language feature enabled, not parsing the interior causes catastrophic breakage in the tree. To avoid that, parse the argument decl is parsed, but instead of generating `ABIAttributeArgumentsSyntax`, store it as a `UnexpectedNodesSyntax` after the left-paren.
1 parent e4bdcbf commit 7e8f21c

File tree

3 files changed

+87
-28
lines changed

3 files changed

+87
-28
lines changed

Sources/SwiftParser/Attributes.swift

+42-26
Original file line numberDiff line numberDiff line change
@@ -66,7 +66,7 @@ extension Parser {
6666
case TokenSpec(._specialize): self = ._specialize
6767
case TokenSpec(._spi_available): self = ._spi_available
6868
case TokenSpec(.`rethrows`): self = .rethrows
69-
case TokenSpec(.abi) where experimentalFeatures.contains(.abiAttribute): self = .abi
69+
case TokenSpec(.abi): self = .abi
7070
case TokenSpec(.attached): self = .attached
7171
case TokenSpec(.available): self = .available
7272
case TokenSpec(.backDeployed): self = .backDeployed
@@ -139,8 +139,12 @@ extension Parser {
139139
/// - parseMissingArguments: If provided, called instead of `parseArgument` when an argument list was required but no opening parenthesis was present.
140140
mutating func parseAttribute(
141141
argumentMode: AttributeArgumentMode,
142-
parseArguments: (inout Parser) -> RawAttributeSyntax.Arguments,
143-
parseMissingArguments: ((inout Parser) -> RawAttributeSyntax.Arguments)? = nil
142+
parseArguments: (inout Parser) -> (
143+
unexpectedBefore: RawUnexpectedNodesSyntax?, arguments: RawAttributeSyntax.Arguments
144+
),
145+
parseMissingArguments: (
146+
(inout Parser) -> (unexpectedBefore: RawUnexpectedNodesSyntax?, arguments: RawAttributeSyntax.Arguments)
147+
)? = nil
144148
) -> RawAttributeListSyntax.Element {
145149
var (unexpectedBeforeAtSign, atSign) = self.expect(.atSign)
146150
if atSign.trailingTriviaByteLength > 0 || self.currentToken.leadingTriviaByteLength > 0 {
@@ -175,11 +179,12 @@ extension Parser {
175179
)
176180
leftParen = leftParen.tokenView.withTokenDiagnostic(tokenDiagnostic: diagnostic, arena: self.arena)
177181
}
182+
let unexpectedBeforeArguments: RawUnexpectedNodesSyntax?
178183
let argument: RawAttributeSyntax.Arguments
179184
if let parseMissingArguments, leftParen.presence == .missing {
180-
argument = parseMissingArguments(&self)
185+
(unexpectedBeforeArguments, argument) = parseMissingArguments(&self)
181186
} else {
182-
argument = parseArguments(&self)
187+
(unexpectedBeforeArguments, argument) = parseArguments(&self)
183188
}
184189
let (unexpectedBeforeRightParen, rightParen) = self.expect(.rightParen)
185190
return .attribute(
@@ -189,6 +194,7 @@ extension Parser {
189194
attributeName: attributeName,
190195
unexpectedBeforeLeftParen,
191196
leftParen: leftParen,
197+
unexpectedBeforeArguments,
192198
arguments: argument,
193199
unexpectedBeforeRightParen,
194200
rightParen: rightParen,
@@ -224,41 +230,41 @@ extension Parser {
224230
switch peek(isAtAnyIn: DeclarationAttributeWithSpecialSyntax.self) {
225231
case .abi:
226232
return parseAttribute(argumentMode: .required) { parser in
227-
return .abiArguments(parser.parseABIAttributeArguments())
233+
return parser.parseABIAttributeArguments()
228234
} parseMissingArguments: { parser in
229-
return .abiArguments(parser.parseABIAttributeArguments(missingLParen: true))
235+
return parser.parseABIAttributeArguments(missingLParen: true)
230236
}
231237
case .available, ._spi_available:
232238
return parseAttribute(argumentMode: .required) { parser in
233-
return .availability(parser.parseAvailabilityArgumentSpecList())
239+
return (nil, .availability(parser.parseAvailabilityArgumentSpecList()))
234240
}
235241
case .backDeployed, ._backDeploy:
236242
return parseAttribute(argumentMode: .required) { parser in
237-
return .backDeployedArguments(parser.parseBackDeployedAttributeArguments())
243+
return (nil, .backDeployedArguments(parser.parseBackDeployedAttributeArguments()))
238244
}
239245
case .differentiable:
240246
return parseAttribute(argumentMode: .required) { parser in
241-
return .differentiableArguments(parser.parseDifferentiableAttributeArguments())
247+
return (nil, .differentiableArguments(parser.parseDifferentiableAttributeArguments()))
242248
}
243249
case .derivative, .transpose:
244250
return parseAttribute(argumentMode: .required) { parser in
245-
return .derivativeRegistrationArguments(parser.parseDerivativeAttributeArguments())
251+
return (nil, .derivativeRegistrationArguments(parser.parseDerivativeAttributeArguments()))
246252
}
247253
case .objc:
248254
return parseAttribute(argumentMode: .optional) { parser in
249-
return .objCName(parser.parseObjectiveCSelector())
255+
return (nil, .objCName(parser.parseObjectiveCSelector()))
250256
}
251257
case ._specialize:
252258
return parseAttribute(argumentMode: .required) { parser in
253-
return .specializeArguments(parser.parseSpecializeAttributeArgumentList())
259+
return (nil, .specializeArguments(parser.parseSpecializeAttributeArgumentList()))
254260
}
255261
case ._dynamicReplacement:
256262
return parseAttribute(argumentMode: .required) { parser in
257-
return .dynamicReplacementArguments(parser.parseDynamicReplacementAttributeArguments())
263+
return (nil, .dynamicReplacementArguments(parser.parseDynamicReplacementAttributeArguments()))
258264
}
259265
case ._documentation:
260266
return parseAttribute(argumentMode: .required) { parser in
261-
return .documentationArguments(parser.parseDocumentationAttributeArguments())
267+
return (nil, .documentationArguments(parser.parseDocumentationAttributeArguments()))
262268
}
263269
case ._effects:
264270
return parseAttribute(argumentMode: .required) { parser in
@@ -268,20 +274,20 @@ extension Parser {
268274
while !parser.at(.rightParen, .endOfFile) {
269275
tokens.append(parser.consumeAnyToken())
270276
}
271-
return .effectsArguments(RawEffectsAttributeArgumentListSyntax(elements: tokens, arena: parser.arena))
277+
return (nil, .effectsArguments(RawEffectsAttributeArgumentListSyntax(elements: tokens, arena: parser.arena)))
272278
}
273279
case ._implements:
274280
return parseAttribute(argumentMode: .required) { parser in
275-
return .implementsArguments(parser.parseImplementsAttributeArguments())
281+
return (nil, .implementsArguments(parser.parseImplementsAttributeArguments()))
276282
}
277283
case ._originallyDefinedIn:
278284
return parseAttribute(argumentMode: .required) { parser in
279-
return .originallyDefinedInArguments(parser.parseOriginallyDefinedInAttributeArguments())
285+
return (nil, .originallyDefinedInArguments(parser.parseOriginallyDefinedInAttributeArguments()))
280286
}
281287
case .attached, .freestanding:
282288
return parseAttribute(argumentMode: .customAttribute) { parser in
283289
let arguments = parser.parseMacroRoleArguments()
284-
return .argumentList(RawLabeledExprListSyntax(elements: arguments, arena: parser.arena))
290+
return (nil, .argumentList(RawLabeledExprListSyntax(elements: arguments, arena: parser.arena)))
285291
}
286292
case .rethrows:
287293
let (unexpectedBeforeAtSign, atSign) = self.expect(.atSign)
@@ -308,7 +314,7 @@ extension Parser {
308314
pattern: .none,
309315
allowTrailingComma: true
310316
)
311-
return .argumentList(RawLabeledExprListSyntax(elements: arguments, arena: parser.arena))
317+
return (nil, .argumentList(RawLabeledExprListSyntax(elements: arguments, arena: parser.arena)))
312318
}
313319
}
314320
}
@@ -786,9 +792,11 @@ extension Parser {
786792
/// Parse the arguments inside an `@abi(...)` attribute.
787793
///
788794
/// - Parameter missingLParen: `true` if the opening paren for the argument list was missing.
789-
mutating func parseABIAttributeArguments(missingLParen: Bool = false) -> RawABIAttributeArgumentsSyntax {
790-
func makeMissingProviderArguments(unexpectedBefore: [RawSyntax]) -> RawABIAttributeArgumentsSyntax {
791-
return RawABIAttributeArgumentsSyntax(
795+
mutating func parseABIAttributeArguments(
796+
missingLParen: Bool = false
797+
) -> (RawUnexpectedNodesSyntax?, RawAttributeSyntax.Arguments) {
798+
func makeMissingProviderArguments(unexpectedBefore: [RawSyntax]) -> RawAttributeSyntax.Arguments {
799+
let args = RawABIAttributeArgumentsSyntax(
792800
provider: .missing(
793801
RawMissingDeclSyntax(
794802
unexpectedBefore.isEmpty ? nil : RawUnexpectedNodesSyntax(elements: unexpectedBefore, arena: self.arena),
@@ -800,6 +808,7 @@ extension Parser {
800808
),
801809
arena: self.arena
802810
)
811+
return .abiArguments(args)
803812
}
804813

805814
// Consider the three kinds of mistakes we might see here:
@@ -815,16 +824,23 @@ extension Parser {
815824
// In lieu of that, we judge that recovering gracefully from #3 is more important than #2 and therefore do not even
816825
// attempt to parse the argument unless we've seen a left paren.
817826
guard !missingLParen && !self.at(.rightParen) else {
818-
return makeMissingProviderArguments(unexpectedBefore: [])
827+
return (nil, makeMissingProviderArguments(unexpectedBefore: []))
819828
}
820829

821830
let decl = parseDeclaration(in: .argumentList)
822831

832+
guard experimentalFeatures.contains(.abiAttribute) else {
833+
return (
834+
RawUnexpectedNodesSyntax([decl], arena: self.arena),
835+
.argumentList(RawLabeledExprListSyntax(elements: [], arena: self.arena))
836+
)
837+
}
838+
823839
guard let provider = RawABIAttributeArgumentsSyntax.Provider(decl) else {
824-
return makeMissingProviderArguments(unexpectedBefore: [decl.raw])
840+
return (nil, makeMissingProviderArguments(unexpectedBefore: [decl.raw]))
825841
}
826842

827-
return RawABIAttributeArgumentsSyntax(provider: provider, arena: self.arena)
843+
return (nil, .abiArguments(RawABIAttributeArgumentsSyntax(provider: provider, arena: self.arena)))
828844
}
829845
}
830846

Sources/SwiftParser/Types.swift

+2-2
Original file line numberDiff line numberDiff line change
@@ -1291,15 +1291,15 @@ extension Parser {
12911291

12921292
case .isolated:
12931293
return parseAttribute(argumentMode: .required) { parser in
1294-
return .argumentList(parser.parseIsolatedAttributeArguments())
1294+
return (nil, .argumentList(parser.parseIsolatedAttributeArguments()))
12951295
}
12961296
case .convention, ._opaqueReturnTypeOf, nil: // Custom attribute
12971297
return parseAttribute(argumentMode: .customAttribute) { parser in
12981298
let arguments = parser.parseArgumentListElements(
12991299
pattern: .none,
13001300
allowTrailingComma: true
13011301
)
1302-
return .argumentList(RawLabeledExprListSyntax(elements: arguments, arena: parser.arena))
1302+
return (nil, .argumentList(RawLabeledExprListSyntax(elements: arguments, arena: parser.arena)))
13031303
}
13041304

13051305
}

Tests/SwiftParserTest/AttributeTests.swift

+43
Original file line numberDiff line numberDiff line change
@@ -1283,6 +1283,49 @@ final class AttributeTests: ParserTestCase {
12831283
)
12841284
}
12851285

1286+
func testABIAttributeWithoutFeature() throws {
1287+
assertParse(
1288+
"""
1289+
@abi(1️⃣func fn() -> Int2️⃣)
1290+
func fn1() -> Int { }
1291+
""",
1292+
substructure: FunctionDeclSyntax(
1293+
attributes: [
1294+
.attribute(
1295+
AttributeSyntax(
1296+
attributeName: TypeSyntax("abi"),
1297+
leftParen: .leftParenToken(),
1298+
[Syntax(try FunctionDeclSyntax("func fn() -> Int"))],
1299+
arguments: .argumentList([]),
1300+
rightParen: .rightParenToken()
1301+
)
1302+
)
1303+
],
1304+
name: "fn1",
1305+
signature: FunctionSignatureSyntax(
1306+
parameterClause: FunctionParameterClauseSyntax {},
1307+
returnClause: ReturnClauseSyntax(type: TypeSyntax("Int"))
1308+
)
1309+
) {},
1310+
diagnostics: [
1311+
DiagnosticSpec(
1312+
locationMarker: "1️⃣",
1313+
message: "unexpected code 'func fn() -> Int' in attribute"
1314+
),
1315+
DiagnosticSpec(
1316+
locationMarker: "2️⃣",
1317+
message: "expected argument for '@abi' attribute",
1318+
fixIts: ["insert attribute argument"]
1319+
),
1320+
],
1321+
fixedSource: """
1322+
@abi(func fn() -> Int)
1323+
func fn1() -> Int { }
1324+
""",
1325+
experimentalFeatures: []
1326+
)
1327+
}
1328+
12861329
func testSpaceBetweenAtAndAttribute() {
12871330
assertParse(
12881331
"@1️⃣ custom func foo() {}",

0 commit comments

Comments
 (0)