[CSGen] Prevent @concurrent on closures from skipping throws inference

Introduction of `@concurrent` attribute caused an unintended
side-effect in `ClosureEffectsRequest` since the attribute
could only be used on `async` types setting `async` too early
prevented body analysis for `throws` from running.

Resolves: rdar://151421590
This commit is contained in:
Pavel Yaskevich
2025-05-15 17:17:02 -07:00
parent a0aad5c0b0
commit cda9866b26
2 changed files with 28 additions and 6 deletions

View File

@@ -1414,11 +1414,6 @@ FunctionType::ExtInfo ClosureEffectsRequest::evaluate(
bool async = expr->getAsyncLoc().isValid();
bool sendable = expr->getAttrs().hasAttribute<SendableAttr>();
// `@concurrent` attribute is only valid on asynchronous function types.
if (expr->getAttrs().hasAttribute<ConcurrentAttr>()) {
async = true;
}
if (throws || async) {
return ASTExtInfoBuilder()
.withThrows(throws, /*FIXME:*/Type())
@@ -1432,11 +1427,17 @@ FunctionType::ExtInfo ClosureEffectsRequest::evaluate(
if (!body)
return ASTExtInfoBuilder().withSendable(sendable).build();
// `@concurrent` attribute is only valid on asynchronous function types.
bool asyncFromAttr = false;
if (expr->getAttrs().hasAttribute<ConcurrentAttr>()) {
asyncFromAttr = true;
}
auto throwFinder = FindInnerThrows(expr);
body->walk(throwFinder);
return ASTExtInfoBuilder()
.withThrows(throwFinder.foundThrow(), /*FIXME:*/Type())
.withAsync(bool(findAsyncNode(expr)))
.withAsync(asyncFromAttr || bool(findAsyncNode(expr)))
.withSendable(sendable)
.build();
}

View File

@@ -137,3 +137,24 @@ _ = { @MainActor @concurrent in
_ = { @concurrent () -> Int in
// expected-error@-1 {{@concurrent on non-async closure}}
}
// Make sure that explicit use of `@concurrent` doesn't interfere with inference of `throws` from the body.
do {
func acceptsThrowing(_ body: () async throws -> Void) async {
}
struct Invocation {
func throwingFn() async throws {
}
}
func test(invocation: Invocation) async {
await acceptsThrowing({ @concurrent in try await invocation.throwingFn() }) // Ok
await acceptsThrowing({ @concurrent [invocation] in try await invocation.throwingFn() }) // Ok
await acceptsThrowing({ @concurrent in // Ok
_ = 42
try await invocation.throwingFn()
})
}
}