Skip to content

Merge main into release/6.2 #3067

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 4 commits into from
Apr 29, 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
9 changes: 8 additions & 1 deletion .github/workflows/pull_request.yml
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ name: Pull request

on:
pull_request:
types: [opened, reopened, synchronize]
types: [opened, reopened, synchronize, ready_for_review]

concurrency:
group: ${{ github.workflow }}-${{ github.ref }}
Expand All @@ -11,15 +11,20 @@ concurrency:
jobs:
tests:
name: Test
# PRs created by GitHub Actions don't kick off further actions (https://github.com/peter-evans/create-pull-request/blob/d57e551ebc1a16dee0b8c9ea6d24dba7627a6e35/docs/concepts-guidelines.md#triggering-further-workflow-runs).
# As a workaround, we mark automerge PRs that are created by GitHub actions as draft and trigger the GitHub actions by marking the PR as ready for review. But we don't want to re-trigger testing this when a normal user's PR is marked as ready for review.
if: (github.event.action != 'ready_for_review') || (github.event.action == 'ready_for_review' && github.event.pull_request.user.login == 'github-actions[bot]')
uses: swiftlang/github-workflows/.github/workflows/swift_package_test.yml@main
soundness:
name: Soundness
if: (github.event.action != 'ready_for_review') || (github.event.action == 'ready_for_review' && github.event.pull_request.user.login == 'github-actions[bot]')
uses: swiftlang/github-workflows/.github/workflows/soundness.yml@main
with:
api_breakage_check_enabled: false # https://github.com/swiftlang/swift-syntax/issues/3010
docs_check_additional_arguments: "--disable-parameters-and-returns-validation"
verify_source_code:
name: Validate generated code
if: (github.event.action != 'ready_for_review') || (github.event.action == 'ready_for_review' && github.event.pull_request.user.login == 'github-actions[bot]')
runs-on: ubuntu-latest
container:
image: swift:latest
Expand All @@ -32,6 +37,7 @@ jobs:
run: "./swift-syntax-dev-utils verify-source-code --toolchain /usr"
test_using_swift_syntax_dev_utils_linux:
name: Run tests using swift-syntax-dev-utils (Linux)
if: (github.event.action != 'ready_for_review') || (github.event.action == 'ready_for_review' && github.event.pull_request.user.login == 'github-actions[bot]')
runs-on: ubuntu-latest
container:
image: swift:latest
Expand All @@ -44,6 +50,7 @@ jobs:
run: "./swift-syntax-dev-utils test --enable-rawsyntax-validation --enable-test-fuzzing --toolchain /usr"
test_using_swift_syntax_dev_utils_windows:
name: Run tests using swift-syntax-dev-utils (Windows)
if: (github.event.action != 'ready_for_review') || (github.event.action == 'ready_for_review' && github.event.pull_request.user.login == 'github-actions[bot]')
runs-on: windows-2022
steps:
- name: Pull Docker image
Expand Down
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