Skip to content

Commit 6eb133b

Browse files
authored
Merge pull request #37263 from hborla/async-closure-diagnostics
[Concurrency] Improve diagnostics for missing `await`
2 parents 8d7eede + b6a3434 commit 6eb133b

8 files changed

+84
-34
lines changed

lib/Sema/CSApply.cpp

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@
2525
#include "swift/AST/ASTVisitor.h"
2626
#include "swift/AST/ASTWalker.h"
2727
#include "swift/AST/ClangModuleLoader.h"
28+
#include "swift/AST/Effects.h"
2829
#include "swift/AST/ExistentialLayout.h"
2930
#include "swift/AST/GenericEnvironment.h"
3031
#include "swift/AST/GenericSignature.h"
@@ -7043,6 +7044,38 @@ Expr *ExprRewriter::coerceToType(Expr *expr, Type toType,
70437044
}
70447045
}
70457046

7047+
/// Whether the given effect should be propagated to a closure expression.
7048+
auto shouldApplyEffect = [&](EffectKind kind) -> bool {
7049+
auto last = locator.last();
7050+
if (!(last && last->is<LocatorPathElt::ApplyArgToParam>()))
7051+
return true;
7052+
7053+
// The effect should not be applied if the closure is an argument
7054+
// to a function where that effect is polymorphic.
7055+
if (auto *call = getAsExpr<ApplyExpr>(locator.getAnchor())) {
7056+
if (auto *declRef = dyn_cast<DeclRefExpr>(call->getFn())) {
7057+
if (auto *fn = dyn_cast<AbstractFunctionDecl>(declRef->getDecl()))
7058+
return !fn->hasPolymorphicEffect(kind);
7059+
}
7060+
}
7061+
7062+
return true;
7063+
};
7064+
7065+
// If we have a ClosureExpr, we can safely propagate 'async' to the closure.
7066+
fromEI = fromFunc->getExtInfo();
7067+
if (toEI.isAsync() && !fromEI.isAsync() && shouldApplyEffect(EffectKind::Async)) {
7068+
auto newFromFuncType = fromFunc->withExtInfo(fromEI.withAsync());
7069+
if (applyTypeToClosureExpr(cs, expr, newFromFuncType)) {
7070+
fromFunc = newFromFuncType->castTo<FunctionType>();
7071+
7072+
// Propagating 'async' might have satisfied the entire conversion.
7073+
// If so, we're done, otherwise keep converting.
7074+
if (fromFunc->isEqual(toType))
7075+
return expr;
7076+
}
7077+
}
7078+
70467079
// If we have a ClosureExpr, then we can safely propagate the 'no escape'
70477080
// bit to the closure without invalidating prior analysis.
70487081
fromEI = fromFunc->getExtInfo();

lib/Sema/TypeCheckConcurrency.cpp

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1727,7 +1727,8 @@ namespace {
17271727
}
17281728

17291729
// Mark as implicitly async.
1730-
apply->setImplicitlyAsync(true);
1730+
if (!fnType->getExtInfo().isAsync())
1731+
apply->setImplicitlyAsync(true);
17311732

17321733
// If we don't need to check for sendability, we're done.
17331734
if (!shouldDiagnoseNonSendableViolations(ctx.LangOpts))

lib/Sema/TypeCheckEffects.cpp

Lines changed: 12 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2872,8 +2872,18 @@ class CheckEffectsCoverage : public EffectsHandlingWalker<CheckEffectsCoverage>
28722872
}
28732873
continue;
28742874
}
2875-
Ctx.Diags.diagnose(diag.expr.getStartLoc(),
2876-
diag::async_access_without_await, 0);
2875+
2876+
auto *call = dyn_cast<ApplyExpr>(&diag.expr);
2877+
if (call && call->implicitlyAsync()) {
2878+
// Emit a tailored note if the call is implicitly async, meaning the
2879+
// callee is isolated to an actor.
2880+
auto callee = call->getCalledValue();
2881+
Ctx.Diags.diagnose(diag.expr.getStartLoc(), diag::actor_isolated_sync_func,
2882+
callee->getDescriptiveKind(), callee->getName());
2883+
} else {
2884+
Ctx.Diags.diagnose(diag.expr.getStartLoc(),
2885+
diag::async_access_without_await, 0);
2886+
}
28772887

28782888
continue;
28792889
}

test/Concurrency/actor_call_implicitly_async.swift

Lines changed: 9 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -207,26 +207,28 @@ extension BankAccount {
207207
func totalBalance(including other: BankAccount) async -> Int {
208208
//expected-error@+1{{expression is 'async' but is not marked with 'await'}}{{12-12=await }}
209209
return balance()
210-
+ other.balance() // expected-note{{call is 'async'}}
210+
+ other.balance() // expected-note{{calls to instance method 'balance()' from outside of its actor context are implicitly asynchronous}}
211211
}
212212

213213
func breakAccounts(other: BankAccount) async {
214214
// expected-error@+1{{expression is 'async' but is not marked with 'await'}}{{9-9=await }}
215-
_ = other.deposit( // expected-note{{call is 'async'}}
216-
other.withdraw( // expected-note{{call is 'async'}}
215+
_ = other.deposit( // expected-note{{calls to instance method 'deposit' from outside of its actor context are implicitly asynchronous}}
216+
other.withdraw( // expected-note{{calls to instance method 'withdraw' from outside of its actor context are implicitly asynchronous}}
217217
self.deposit(
218-
other.withdraw( // expected-note{{call is 'async'}}
219-
other.balance())))) // expected-note{{call is 'async'}}
218+
other.withdraw( // expected-note{{calls to instance method 'withdraw' from outside of its actor context are implicitly asynchronous}}
219+
other.balance())))) // expected-note{{calls to instance method 'balance()' from outside of its actor context are implicitly asynchronous}}
220220
}
221221
}
222222

223223
func anotherAsyncFunc() async {
224224
let a = BankAccount(initialDeposit: 34)
225225
let b = BankAccount(initialDeposit: 35)
226226

227-
// expected-error@+1{{expression is 'async' but is not marked with 'await'}} {{7-7=await }} expected-note@+1{{call is 'async'}}
227+
// expected-error@+2{{expression is 'async' but is not marked with 'await'}} {{7-7=await }}
228+
// expected-note@+1{{calls to instance method 'deposit' from outside of its actor context are implicitly asynchronous}}
228229
_ = a.deposit(1)
229-
// expected-error@+1{{expression is 'async' but is not marked with 'await'}} {{7-7=await }} expected-note@+1{{call is 'async'}}
230+
// expected-error@+2{{expression is 'async' but is not marked with 'await'}} {{7-7=await }}
231+
// expected-note@+1{{calls to instance method 'balance()' from outside of its actor context are implicitly asynchronous}}
230232
_ = b.balance()
231233

232234
_ = b.balance // expected-error {{actor-isolated instance method 'balance()' can only be referenced from inside the actor}}

test/Concurrency/actor_isolation.swift

Lines changed: 15 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -71,7 +71,7 @@ actor MyActor: MySuperActor { // expected-error{{actor types do not support inhe
7171
class func synchronousClass() { }
7272
static func synchronousStatic() { }
7373

74-
func synchronous() -> String { text.first ?? "nothing" } // expected-note 20{{calls to instance method 'synchronous()' from outside of its actor context are implicitly asynchronous}}
74+
func synchronous() -> String { text.first ?? "nothing" } // expected-note 19{{calls to instance method 'synchronous()' from outside of its actor context are implicitly asynchronous}}
7575
func asynchronous() async -> String {
7676
super.superState += 4
7777
return synchronous()
@@ -202,7 +202,7 @@ extension MyActor {
202202

203203
_ = otherActor.synchronous()
204204
// expected-error@-1{{expression is 'async' but is not marked with 'await'}}{{9-9=await }}
205-
// expected-note@-2{{call is 'async'}}
205+
// expected-note@-2{{calls to instance method 'synchronous()' from outside of its actor context are implicitly asynchronous}}
206206
_ = await otherActor.asynchronous()
207207
_ = otherActor.text[0]
208208
// expected-error@-1{{expression is 'async' but is not marked with 'await'}}{{9-9=await }}
@@ -222,7 +222,7 @@ extension MyActor {
222222

223223
// Global actors
224224
syncGlobalActorFunc() // expected-error{{expression is 'async' but is not marked with 'await'}}{{5-5=await }}
225-
// expected-note@-1{{call is 'async'}}
225+
// expected-note@-1{{calls to global function 'syncGlobalActorFunc()' from outside of its actor context are implicitly asynchronous}}
226226

227227
await asyncGlobalActorFunc()
228228

@@ -399,7 +399,7 @@ actor Crystal {
399399
@SomeGlobalActor func goo1() async {
400400
let _ = goo2
401401
// expected-error@+1{{expression is 'async' but is not marked with 'await'}}{{3-3=await }}
402-
goo2() // expected-note{{call is 'async'}}
402+
goo2() // expected-note{{calls to global function 'goo2()' from outside of its actor context are implicitly asynchronous}}
403403
}
404404
@available(macOS 9999, iOS 9999, watchOS 9999, tvOS 9999, *)
405405
@asyncHandler @SomeOtherGlobalActor func goo2() { await goo1() }
@@ -409,7 +409,7 @@ func testGlobalActorClosures() {
409409
let _: Int = acceptAsyncClosure { @SomeGlobalActor in
410410
syncGlobalActorFunc()
411411
syncOtherGlobalActorFunc() // expected-error{{expression is 'async' but is not marked with 'await'}}{{5-5=await }}
412-
// expected-note@-1{{call is 'async'}}
412+
// expected-note@-1{{calls to global function 'syncOtherGlobalActorFunc()' from outside of its actor context are implicitly asynchronous}}
413413

414414
await syncOtherGlobalActorFunc()
415415
return 17
@@ -434,7 +434,7 @@ extension MyActor {
434434
// expected-note@-1{{property access is 'async'}}
435435
_ = await mutable
436436
_ = synchronous() // expected-error{{expression is 'async' but is not marked with 'await'}}{{9-9=await }}
437-
// expected-note@-1{{call is 'async'}}
437+
// expected-note@-1{{calls to instance method 'synchronous()' from outside of its actor context are implicitly asynchronous}}
438438
_ = await synchronous()
439439
_ = text[0] // expected-error{{expression is 'async' but is not marked with 'await'}}
440440
// expected-note@-1{{property access is 'async'}}
@@ -445,7 +445,7 @@ extension MyActor {
445445
// we are outside of the actor instance.
446446
_ = self.immutable
447447
_ = self.synchronous() // expected-error{{expression is 'async' but is not marked with 'await'}}{{9-9=await }}
448-
// expected-note@-1{{call is 'async'}}
448+
// expected-note@-1{{calls to instance method 'synchronous()' from outside of its actor context are implicitly asynchronous}}
449449
_ = await self.synchronous()
450450

451451
_ = await self.asynchronous()
@@ -461,7 +461,7 @@ extension MyActor {
461461
// expected-note@-1{{property access is 'async'}}
462462
_ = await super.superState
463463
super.superMethod() // expected-error{{expression is 'async' but is not marked with 'await'}}{{5-5=await }}
464-
// expected-note@-1{{call is 'async'}}
464+
// expected-note@-1{{calls to instance method 'superMethod()' from outside of its actor context are implicitly asynchronous}}
465465

466466
await super.superMethod()
467467
await super.superAsyncMethod()
@@ -473,7 +473,7 @@ extension MyActor {
473473
// call asychronous methods
474474
_ = otherActor.immutable // okay
475475
_ = otherActor.synchronous() // expected-error{{expression is 'async' but is not marked with 'await'}}{{9-9=await }}
476-
// expected-note@-1{{call is 'async'}}
476+
// expected-note@-1{{calls to instance method 'synchronous()' from outside of its actor context are implicitly asynchronous}}
477477
_ = otherActor.synchronous // expected-error{{actor-isolated instance method 'synchronous()' can only be referenced on 'self'}}
478478
_ = await otherActor.asynchronous()
479479
_ = otherActor.text[0] // expected-error{{expression is 'async' but is not marked with 'await'}}{{9-9=await }}
@@ -534,7 +534,7 @@ func testGlobalRestrictions(actor: MyActor) async {
534534

535535
// any kind of method can be called from outside the actor, so long as it's marked with 'await'
536536
_ = actor.synchronous() // expected-error{{expression is 'async' but is not marked with 'await'}}{{7-7=await }}
537-
// expected-note@-1{{call is 'async}}
537+
// expected-note@-1{{calls to instance method 'synchronous()' from outside of its actor context are implicitly asynchronous}}
538538
_ = actor.asynchronous() // expected-error{{expression is 'async' but is not marked with 'await'}}{{7-7=await }}
539539
// expected-note@-1{{call is 'async'}}
540540

@@ -751,7 +751,7 @@ class SomeClassWithInits {
751751
await self.isolated() // expected-warning{{cannot use parameter 'self' with a non-sendable type 'SomeClassWithInits' from concurrently-executed code}}
752752
self.isolated() // expected-warning{{cannot use parameter 'self' with a non-sendable type 'SomeClassWithInits' from concurrently-executed code}}
753753
// expected-error@-1{{expression is 'async' but is not marked with 'await'}}{{7-7=await }}
754-
// expected-note@-2{{call is 'async'}}
754+
// expected-note@-2{{calls to instance method 'isolated()' from outside of its actor context are implicitly asynchronous}}
755755

756756
print(await self.mutableState) // expected-warning{{cannot use parameter 'self' with a non-sendable type 'SomeClassWithInits' from concurrently-executed code}}
757757
}
@@ -791,10 +791,10 @@ func testCrossActorProtocol<T: P>(t: T) async {
791791
await t.g()
792792
t.f()
793793
// expected-error@-1{{expression is 'async' but is not marked with 'await'}}{{3-3=await }}
794-
// expected-note@-2{{call is 'async'}}
794+
// expected-note@-2{{calls to instance method 'f()' from outside of its actor context are implicitly asynchronous}}
795795
t.g()
796796
// expected-error@-1{{expression is 'async' but is not marked with 'await'}}{{3-3=await }}
797-
// expected-note@-2{{call is 'async'}}
797+
// expected-note@-2{{calls to instance method 'g()' from outside of its actor context are implicitly asynchronous}}
798798
}
799799

800800
// ----------------------------------------------------------------------
@@ -807,7 +807,8 @@ func acceptAsyncSendableClosureInheriting<T>(@_inheritActorContext _: @Sendable
807807
extension MyActor {
808808
func testSendableAndInheriting() {
809809
acceptAsyncSendableClosure {
810-
synchronous() // expected-error{{actor-isolated instance method 'synchronous()' cannot be referenced from a concurrent closure}}
810+
synchronous() // expected-error{{expression is 'async' but is not marked with 'await'}}
811+
// expected-note@-1{{calls to instance method 'synchronous()' from outside of its actor context are implicitly asynchronous}}
811812
}
812813

813814
acceptAsyncSendableClosure {

test/Concurrency/global_actor_from_ordinary_context.swift

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -109,7 +109,7 @@ class Taylor {
109109
func fromAsync() async {
110110
let x = syncGlobActorFn
111111
// expected-error@+1{{expression is 'async' but is not marked with 'await'}}{{3-3=await }}
112-
x() // expected-note{{call is 'async'}}
112+
x() // expected-note{{calls to let 'x' from outside of its actor context are implicitly asynchronous}}
113113

114114

115115
let y = asyncGlobalActFn
@@ -120,7 +120,7 @@ func fromAsync() async {
120120

121121
let fn = a.method
122122
// expected-error@+1{{expression is 'async' but is not marked with 'await'}}{{3-3=await }}
123-
fn() //expected-note{{call is 'async}}
123+
fn() //expected-note{{calls to let 'fn' from outside of its actor context are implicitly asynchronous}}
124124
// expected-error@+1{{expression is 'async' but is not marked with 'await'}}{{7-7=await }}
125125
_ = a.const_memb // expected-note{{property access is 'async'}}
126126
// expected-error@+1{{expression is 'async' but is not marked with 'await'}}{{7-7=await }}

test/Concurrency/global_actor_function_types.swift

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -130,12 +130,14 @@ func testTypesConcurrencyContext() async {
130130
let _: () -> Int = f2 // expected-error{{converting function value of type '@SomeGlobalActor () -> Int' to '() -> Int' loses global actor 'SomeGlobalActor'}}
131131

132132
// expected-error@+1{{expression is 'async' but is not marked with 'await'}}{{7-7=await }}
133-
_ = f1() //expected-note{{call is 'async}}
133+
_ = f1() //expected-note{{calls to let 'f1' from outside of its actor context are implicitly asynchronous}}
134134
// expected-error@+1{{expression is 'async' but is not marked with 'await'}}{{7-7=await }}
135-
_ = f2() //expected-note{{call is 'async'}}
135+
_ = f2() //expected-note{{calls to let 'f2' from outside of its actor context are implicitly asynchronous}}
136136

137-
// expected-error@+1{{expression is 'async' but is not marked with 'await'}}{{7-7=await }}
138-
_ = f1() + f2() // expected-note 2 {{call is 'async'}}
137+
// expected-error@+3{{expression is 'async' but is not marked with 'await'}}{{7-7=await }}
138+
//expected-note@+2 {{calls to let 'f1' from outside of its actor context are implicitly asynchronous}}
139+
// expected-note@+1 {{calls to let 'f2' from outside of its actor context are implicitly asynchronous}}
140+
_ = f1() + f2()
139141

140142
_ = await f1()
141143
_ = await f2()

test/Concurrency/global_actor_inference.swift

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -238,7 +238,7 @@ class SubclassWithGlobalActors : SuperclassWithGlobalActors {
238238
onGenericGlobalActorString() // okay
239239

240240
// expected-error@+1{{expression is 'async' but is not marked with 'await'}}{{5-5=await }}
241-
onGenericGlobalActorInt() // expected-note{{call is 'async'}}
241+
onGenericGlobalActorInt() // expected-note{{calls to instance method 'onGenericGlobalActorInt()' from outside of its actor context are implicitly asynchronous}}
242242
}
243243
}
244244

@@ -253,7 +253,7 @@ class SubclassWithGlobalActors : SuperclassWithGlobalActors {
253253

254254
func bar() async {
255255
// expected-error@+1{{expression is 'async' but is not marked with 'await'}}{{3-3=await }}
256-
foo() // expected-note{{call is 'async'}}
256+
foo() // expected-note{{calls to global function 'foo()' from outside of its actor context are implicitly asynchronous}}
257257
}
258258

259259
// expected-note@+1 {{add '@SomeGlobalActor' to make global function 'barSync()' part of global actor 'SomeGlobalActor'}} {{1-1=@SomeGlobalActor }}
@@ -509,7 +509,7 @@ func acceptClosure<T>(_: () -> T) { }
509509
// ----------------------------------------------------------------------
510510
func takesUnsafeMainActor(@_unsafeMainActor fn: () -> Void) { }
511511

512-
@MainActor func onlyOnMainActor() { } // expected-note{{calls to global function 'onlyOnMainActor()' from outside of its actor context are implicitly asynchronous}}
512+
@MainActor func onlyOnMainActor() { }
513513

514514
func useUnsafeMainActor() {
515515
takesUnsafeMainActor {
@@ -525,7 +525,8 @@ func acceptAsyncSendableClosureInheriting<T>(@_inheritActorContext _: @Sendable
525525

526526
@MainActor func testCallFromMainActor() {
527527
acceptAsyncSendableClosure {
528-
onlyOnMainActor() // expected-error{{call to main actor-isolated global function 'onlyOnMainActor()' in a synchronous nonisolated context}}
528+
onlyOnMainActor() // expected-error{{expression is 'async' but is not marked with 'await'}}
529+
// expected-note@-1 {{calls to global function 'onlyOnMainActor()' from outside of its actor context are implicitly asynchronous}}
529530
}
530531

531532
acceptAsyncSendableClosure {

0 commit comments

Comments
 (0)