Skip to content

Commit d7ec3a3

Browse files
Merge pull request #83094 from swiftlang/jepa-main
Sema: Make `@concurrent` not imply `async` on closures
2 parents af85e06 + 58d5059 commit d7ec3a3

File tree

5 files changed

+96
-23
lines changed

5 files changed

+96
-23
lines changed

lib/Sema/ConstraintSystem.cpp

Lines changed: 1 addition & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1451,17 +1451,11 @@ FunctionType::ExtInfo ClosureEffectsRequest::evaluate(
14511451
if (!body)
14521452
return ASTExtInfoBuilder().withSendable(sendable).build();
14531453

1454-
// `@concurrent` attribute is only valid on asynchronous function types.
1455-
bool asyncFromAttr = false;
1456-
if (expr->getAttrs().hasAttribute<ConcurrentAttr>()) {
1457-
asyncFromAttr = true;
1458-
}
1459-
14601454
auto throwFinder = FindInnerThrows(expr);
14611455
body->walk(throwFinder);
14621456
return ASTExtInfoBuilder()
14631457
.withThrows(throwFinder.foundThrow(), /*FIXME:*/Type())
1464-
.withAsync(asyncFromAttr || bool(findAsyncNode(expr)))
1458+
.withAsync(bool(findAsyncNode(expr)))
14651459
.withSendable(sendable)
14661460
.build();
14671461
}

lib/Sema/MiscDiagnostics.cpp

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -305,6 +305,11 @@ static void diagSyntacticUseRestrictions(const Expr *E, const DeclContext *DC,
305305
}
306306
}
307307

308+
// Performs checks on a closure.
309+
if (auto *closure = dyn_cast<ClosureExpr>(E)) {
310+
checkClosure(closure);
311+
}
312+
308313
// Specially diagnose some checked casts that are illegal.
309314
if (auto cast = dyn_cast<CheckedCastExpr>(E)) {
310315
checkCheckedCastExpr(cast);
@@ -1455,6 +1460,33 @@ static void diagSyntacticUseRestrictions(const Expr *E, const DeclContext *DC,
14551460
}
14561461
}
14571462
}
1463+
1464+
void checkClosure(ClosureExpr *closure) {
1465+
if (closure->isImplicit()) {
1466+
return;
1467+
}
1468+
1469+
auto attr = closure->getAttrs().getAttribute<ConcurrentAttr>();
1470+
if (!attr) {
1471+
return;
1472+
}
1473+
1474+
if (closure->getAsyncLoc().isValid()) {
1475+
return;
1476+
}
1477+
1478+
if (closure->getType()->castTo<AnyFunctionType>()->isAsync()) {
1479+
return;
1480+
}
1481+
1482+
// `@concurrent` is not allowed on synchronous closures, but this is
1483+
// likely to change, so diagnose this here, post solution application,
1484+
// instead of introducing an "implies `async`" inference rule that won't
1485+
// make sense in the nearish future.
1486+
Ctx.Diags.diagnose(attr->getLocation(),
1487+
diag::execution_behavior_only_on_async_closure, attr);
1488+
attr->setInvalid();
1489+
}
14581490
};
14591491

14601492
DiagnoseWalker Walker(DC, isExprStmt);

lib/Sema/TypeCheckAttr.cpp

Lines changed: 0 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -8296,16 +8296,6 @@ class ClosureAttributeChecker
82968296
}
82978297

82988298
void checkExecutionBehaviorAttribute(DeclAttribute *attr) {
8299-
// execution behavior attribute implies `async`.
8300-
if (closure->hasExplicitResultType() &&
8301-
closure->getAsyncLoc().isInvalid()) {
8302-
ctx.Diags
8303-
.diagnose(attr->getLocation(),
8304-
diag::execution_behavior_only_on_async_closure, attr)
8305-
.fixItRemove(attr->getRangeWithAt());
8306-
attr->setInvalid();
8307-
}
8308-
83098299
if (auto actorType = getExplicitGlobalActor(closure)) {
83108300
ctx.Diags
83118301
.diagnose(

test/Concurrency/attr_execution/conversions.swift

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -93,7 +93,7 @@ do {
9393

9494
do {
9595
let _: () -> Void = { @concurrent in
96-
// expected-error@-1 {{invalid conversion from 'async' function of type '() async -> Void' to synchronous function type '() -> Void'}}
96+
// expected-error@-1 {{cannot use @concurrent on non-async closure}}{{none}}
9797
}
9898
}
9999

test/attr/execution_behavior_attrs.swift

Lines changed: 62 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -125,17 +125,74 @@ struct IsolatedType {
125125
@concurrent func test() async {}
126126
}
127127

128-
_ = { @concurrent in // Ok
128+
// `@concurrent` on closures does not contribute to `async` inference.
129+
do {
130+
_ = { @concurrent in }
131+
// expected-error@-1:9 {{cannot use @concurrent on non-async closure}}{{none}}
132+
_ = { @concurrent () -> Int in }
133+
// expected-error@-1:9 {{cannot use @concurrent on non-async closure}}{{none}}
134+
// Explicit effect precludes effects inference from the body.
135+
_ = { @concurrent () throws in await awaitMe() }
136+
// expected-error@-1:9 {{cannot use @concurrent on non-async closure}}{{none}}
137+
// expected-error@-2:40 {{'async' call in a function that does not support concurrency}}{{none}}
138+
_ = { @concurrent () async in }
139+
_ = { @concurrent in await awaitMe() }
140+
141+
func awaitMe() async {}
142+
143+
do {
144+
func foo(_: () -> Void) {}
145+
func foo(_: () async -> Void) async {}
146+
147+
func sync() {
148+
foo { @concurrent in }
149+
// expected-error@-1:13 {{cannot use @concurrent on non-async closure}}{{none}}
150+
}
151+
// expected-note@+1:10 {{add 'async' to function 'sync2()' to make it asynchronous}}{{17-17= async}}
152+
func sync2() {
153+
foo { @concurrent in await awaitMe() }
154+
// expected-error@-1:7 {{'async' call in a function that does not support concurrency}}{{none}}
155+
}
156+
func async() async {
157+
// Even though the context is async, the sync overload is favored because
158+
// the closure is sync, so the error is expected.
159+
foo { @concurrent in }
160+
// expected-error@-1:13 {{cannot use @concurrent on non-async closure}}{{none}}
161+
162+
foo { @concurrent in await awaitMe() }
163+
// expected-error@-1:7 {{expression is 'async' but is not marked with 'await'}}{{7-7=await }}
164+
// expected-note@-2:7 {{call is 'async'}}{{none}}
165+
}
166+
}
167+
168+
do {
169+
func foo(_: (Int) async -> Void) {}
170+
func foo(_: (Int) -> Void) async {}
171+
172+
func sync() {
173+
// OK, sync overload picked.
174+
foo { @concurrent _ in }
175+
}
176+
func async() async {
177+
foo { @concurrent _ in }
178+
// expected-error@-1:13 {{cannot use @concurrent on non-async closure}}{{none}}
179+
// expected-error@-2:7 {{expression is 'async' but is not marked with 'await'}}{{7-7=await }}
180+
// expected-note@-3:7 {{call is 'async'}}{{none}}
181+
}
182+
}
183+
184+
do {
185+
func foo<T>(_: T) {}
186+
187+
foo({@concurrent in })
188+
// expected-error@-1:10 {{cannot use @concurrent on non-async closure}}{{none}}
189+
}
129190
}
130191

131192
_ = { @MainActor @concurrent in
132193
// expected-error@-1 {{cannot use @concurrent because function type is isolated to a global actor 'MainActor'}}
133194
}
134195

135-
_ = { @concurrent () -> Int in
136-
// expected-error@-1 {{@concurrent on non-async closure}}
137-
}
138-
139196
// Make sure that explicit use of `@concurrent` doesn't interfere with inference of `throws` from the body.
140197
do {
141198
func acceptsThrowing(_ body: () async throws -> Void) async {

0 commit comments

Comments
 (0)