Skip to content

[SwiftParser] Parse @abi attribute arguments even without feature flag #3062

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 1 commit into from
Apr 23, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
68 changes: 42 additions & 26 deletions Sources/SwiftParser/Attributes.swift
Original file line number Diff line number Diff line change
Expand Up @@ -66,7 +66,7 @@ extension Parser {
case TokenSpec(._specialize): self = ._specialize
case TokenSpec(._spi_available): self = ._spi_available
case TokenSpec(.`rethrows`): self = .rethrows
case TokenSpec(.abi) where experimentalFeatures.contains(.abiAttribute): self = .abi
case TokenSpec(.abi): self = .abi
case TokenSpec(.attached): self = .attached
case TokenSpec(.available): self = .available
case TokenSpec(.backDeployed): self = .backDeployed
Expand Down Expand Up @@ -139,8 +139,12 @@ extension Parser {
/// - parseMissingArguments: If provided, called instead of `parseArgument` when an argument list was required but no opening parenthesis was present.
mutating func parseAttribute(
argumentMode: AttributeArgumentMode,
parseArguments: (inout Parser) -> RawAttributeSyntax.Arguments,
parseMissingArguments: ((inout Parser) -> RawAttributeSyntax.Arguments)? = nil
parseArguments: (inout Parser) -> (
unexpectedBefore: RawUnexpectedNodesSyntax?, arguments: RawAttributeSyntax.Arguments
),
parseMissingArguments: (
(inout Parser) -> (unexpectedBefore: RawUnexpectedNodesSyntax?, arguments: RawAttributeSyntax.Arguments)
)? = nil
) -> RawAttributeListSyntax.Element {
var (unexpectedBeforeAtSign, atSign) = self.expect(.atSign)
if atSign.trailingTriviaByteLength > 0 || self.currentToken.leadingTriviaByteLength > 0 {
Expand Down Expand Up @@ -175,11 +179,12 @@ extension Parser {
)
leftParen = leftParen.tokenView.withTokenDiagnostic(tokenDiagnostic: diagnostic, arena: self.arena)
}
let unexpectedBeforeArguments: RawUnexpectedNodesSyntax?
let argument: RawAttributeSyntax.Arguments
if let parseMissingArguments, leftParen.presence == .missing {
argument = parseMissingArguments(&self)
(unexpectedBeforeArguments, argument) = parseMissingArguments(&self)
} else {
argument = parseArguments(&self)
(unexpectedBeforeArguments, argument) = parseArguments(&self)
}
let (unexpectedBeforeRightParen, rightParen) = self.expect(.rightParen)
return .attribute(
Expand All @@ -189,6 +194,7 @@ extension Parser {
attributeName: attributeName,
unexpectedBeforeLeftParen,
leftParen: leftParen,
unexpectedBeforeArguments,
arguments: argument,
unexpectedBeforeRightParen,
rightParen: rightParen,
Expand Down Expand Up @@ -224,41 +230,41 @@ extension Parser {
switch peek(isAtAnyIn: DeclarationAttributeWithSpecialSyntax.self) {
case .abi:
return parseAttribute(argumentMode: .required) { parser in
return .abiArguments(parser.parseABIAttributeArguments())
return parser.parseABIAttributeArguments()
} parseMissingArguments: { parser in
return .abiArguments(parser.parseABIAttributeArguments(missingLParen: true))
return parser.parseABIAttributeArguments(missingLParen: true)
}
case .available, ._spi_available:
return parseAttribute(argumentMode: .required) { parser in
return .availability(parser.parseAvailabilityArgumentSpecList())
return (nil, .availability(parser.parseAvailabilityArgumentSpecList()))
}
case .backDeployed, ._backDeploy:
return parseAttribute(argumentMode: .required) { parser in
return .backDeployedArguments(parser.parseBackDeployedAttributeArguments())
return (nil, .backDeployedArguments(parser.parseBackDeployedAttributeArguments()))
}
case .differentiable:
return parseAttribute(argumentMode: .required) { parser in
return .differentiableArguments(parser.parseDifferentiableAttributeArguments())
return (nil, .differentiableArguments(parser.parseDifferentiableAttributeArguments()))
}
case .derivative, .transpose:
return parseAttribute(argumentMode: .required) { parser in
return .derivativeRegistrationArguments(parser.parseDerivativeAttributeArguments())
return (nil, .derivativeRegistrationArguments(parser.parseDerivativeAttributeArguments()))
}
case .objc:
return parseAttribute(argumentMode: .optional) { parser in
return .objCName(parser.parseObjectiveCSelector())
return (nil, .objCName(parser.parseObjectiveCSelector()))
}
case ._specialize:
return parseAttribute(argumentMode: .required) { parser in
return .specializeArguments(parser.parseSpecializeAttributeArgumentList())
return (nil, .specializeArguments(parser.parseSpecializeAttributeArgumentList()))
}
case ._dynamicReplacement:
return parseAttribute(argumentMode: .required) { parser in
return .dynamicReplacementArguments(parser.parseDynamicReplacementAttributeArguments())
return (nil, .dynamicReplacementArguments(parser.parseDynamicReplacementAttributeArguments()))
}
case ._documentation:
return parseAttribute(argumentMode: .required) { parser in
return .documentationArguments(parser.parseDocumentationAttributeArguments())
return (nil, .documentationArguments(parser.parseDocumentationAttributeArguments()))
}
case ._effects:
return parseAttribute(argumentMode: .required) { parser in
Expand All @@ -268,20 +274,20 @@ extension Parser {
while !parser.at(.rightParen, .endOfFile) {
tokens.append(parser.consumeAnyToken())
}
return .effectsArguments(RawEffectsAttributeArgumentListSyntax(elements: tokens, arena: parser.arena))
return (nil, .effectsArguments(RawEffectsAttributeArgumentListSyntax(elements: tokens, arena: parser.arena)))
}
case ._implements:
return parseAttribute(argumentMode: .required) { parser in
return .implementsArguments(parser.parseImplementsAttributeArguments())
return (nil, .implementsArguments(parser.parseImplementsAttributeArguments()))
}
case ._originallyDefinedIn:
return parseAttribute(argumentMode: .required) { parser in
return .originallyDefinedInArguments(parser.parseOriginallyDefinedInAttributeArguments())
return (nil, .originallyDefinedInArguments(parser.parseOriginallyDefinedInAttributeArguments()))
}
case .attached, .freestanding:
return parseAttribute(argumentMode: .customAttribute) { parser in
let arguments = parser.parseMacroRoleArguments()
return .argumentList(RawLabeledExprListSyntax(elements: arguments, arena: parser.arena))
return (nil, .argumentList(RawLabeledExprListSyntax(elements: arguments, arena: parser.arena)))
}
case .rethrows:
let (unexpectedBeforeAtSign, atSign) = self.expect(.atSign)
Expand All @@ -308,7 +314,7 @@ extension Parser {
pattern: .none,
allowTrailingComma: true
)
return .argumentList(RawLabeledExprListSyntax(elements: arguments, arena: parser.arena))
return (nil, .argumentList(RawLabeledExprListSyntax(elements: arguments, arena: parser.arena)))
}
}
}
Expand Down Expand Up @@ -786,9 +792,11 @@ extension Parser {
/// Parse the arguments inside an `@abi(...)` attribute.
///
/// - Parameter missingLParen: `true` if the opening paren for the argument list was missing.
mutating func parseABIAttributeArguments(missingLParen: Bool = false) -> RawABIAttributeArgumentsSyntax {
func makeMissingProviderArguments(unexpectedBefore: [RawSyntax]) -> RawABIAttributeArgumentsSyntax {
return RawABIAttributeArgumentsSyntax(
mutating func parseABIAttributeArguments(
missingLParen: Bool = false
) -> (RawUnexpectedNodesSyntax?, RawAttributeSyntax.Arguments) {
func makeMissingProviderArguments(unexpectedBefore: [RawSyntax]) -> RawAttributeSyntax.Arguments {
let args = RawABIAttributeArgumentsSyntax(
provider: .missing(
RawMissingDeclSyntax(
unexpectedBefore.isEmpty ? nil : RawUnexpectedNodesSyntax(elements: unexpectedBefore, arena: self.arena),
Expand All @@ -800,6 +808,7 @@ extension Parser {
),
arena: self.arena
)
return .abiArguments(args)
}

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

let decl = parseDeclaration(in: .argumentList)

guard experimentalFeatures.contains(.abiAttribute) else {
return (
RawUnexpectedNodesSyntax([decl], arena: self.arena),
.argumentList(RawLabeledExprListSyntax(elements: [], arena: self.arena))
)
}

guard let provider = RawABIAttributeArgumentsSyntax.Provider(decl) else {
return makeMissingProviderArguments(unexpectedBefore: [decl.raw])
return (nil, makeMissingProviderArguments(unexpectedBefore: [decl.raw]))
}

return RawABIAttributeArgumentsSyntax(provider: provider, arena: self.arena)
return (nil, .abiArguments(RawABIAttributeArgumentsSyntax(provider: provider, arena: self.arena)))
}
}

Expand Down
4 changes: 2 additions & 2 deletions Sources/SwiftParser/Types.swift
Original file line number Diff line number Diff line change
Expand Up @@ -1291,15 +1291,15 @@ extension Parser {

case .isolated:
return parseAttribute(argumentMode: .required) { parser in
return .argumentList(parser.parseIsolatedAttributeArguments())
return (nil, .argumentList(parser.parseIsolatedAttributeArguments()))
}
case .convention, ._opaqueReturnTypeOf, nil: // Custom attribute
return parseAttribute(argumentMode: .customAttribute) { parser in
let arguments = parser.parseArgumentListElements(
pattern: .none,
allowTrailingComma: true
)
return .argumentList(RawLabeledExprListSyntax(elements: arguments, arena: parser.arena))
return (nil, .argumentList(RawLabeledExprListSyntax(elements: arguments, arena: parser.arena)))
}

}
Expand Down
43 changes: 43 additions & 0 deletions Tests/SwiftParserTest/AttributeTests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -1283,6 +1283,49 @@ final class AttributeTests: ParserTestCase {
)
}

func testABIAttributeWithoutFeature() throws {
assertParse(
"""
@abi(1️⃣func fn() -> Int2️⃣)
func fn1() -> Int { }
""",
substructure: FunctionDeclSyntax(
attributes: [
.attribute(
AttributeSyntax(
attributeName: TypeSyntax("abi"),
leftParen: .leftParenToken(),
[Syntax(try FunctionDeclSyntax("func fn() -> Int"))],
arguments: .argumentList([]),
rightParen: .rightParenToken()
)
)
],
name: "fn1",
signature: FunctionSignatureSyntax(
parameterClause: FunctionParameterClauseSyntax {},
returnClause: ReturnClauseSyntax(type: TypeSyntax("Int"))
)
) {},
diagnostics: [
DiagnosticSpec(
locationMarker: "1️⃣",
message: "unexpected code 'func fn() -> Int' in attribute"
),
DiagnosticSpec(
locationMarker: "2️⃣",
message: "expected argument for '@abi' attribute",
fixIts: ["insert attribute argument"]
),
],
fixedSource: """
@abi(func fn() -> Int)
func fn1() -> Int { }
""",
experimentalFeatures: []
)
}

func testSpaceBetweenAtAndAttribute() {
assertParse(
"@1️⃣ custom func foo() {}",
Expand Down