Skip to content

Commit 3223cf1

Browse files
committed
[Concurrency] Improve await/try checking in 'async let' initializers.
Customize diagnostics when an 'await' is missing in an 'async let' initializer. While here, fix the coverage checking so we also diagnose a missing 'try'.
1 parent 33cfbbd commit 3223cf1

File tree

3 files changed

+45
-5
lines changed

3 files changed

+45
-5
lines changed

include/swift/AST/DiagnosticsSema.def

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4087,6 +4087,9 @@ ERROR(async_call_without_await,none,
40874087
"call is 'async' but is not marked with 'await'", ())
40884088
ERROR(async_call_without_await_in_autoclosure,none,
40894089
"call is 'async' in an autoclosure argument that is not marked with 'await'", ())
4090+
ERROR(async_call_without_await_in_async_let,none,
4091+
"call is 'async' in an 'async let' initializer that is not marked "
4092+
"with 'await'", ())
40904093
WARNING(no_async_in_await,none,
40914094
"no calls to 'async' functions occur within 'await' expression", ())
40924095
ERROR(async_call_in_illegal_context,none,

lib/Sema/TypeCheckEffects.cpp

Lines changed: 29 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1306,11 +1306,27 @@ class Context {
13061306
highlight = apply->getSourceRange();
13071307

13081308
auto diag = diag::async_call_without_await;
1309+
13091310
// To produce a better error message, check if it is an autoclosure.
13101311
// We do not use 'Context::isAutoClosure' b/c it gives conservative answers.
1311-
if (Function && llvm::isa_and_nonnull<AutoClosureExpr>(
1312-
Function->getAbstractClosureExpr()))
1313-
diag = diag::async_call_without_await_in_autoclosure;
1312+
if (Function) {
1313+
if (auto autoclosure = dyn_cast_or_null<AutoClosureExpr>(
1314+
Function->getAbstractClosureExpr())) {
1315+
switch (autoclosure->getThunkKind()) {
1316+
case AutoClosureExpr::Kind::None:
1317+
diag = diag::async_call_without_await_in_autoclosure;
1318+
break;
1319+
1320+
case AutoClosureExpr::Kind::AsyncLet:
1321+
diag = diag::async_call_without_await_in_async_let;
1322+
break;
1323+
1324+
case AutoClosureExpr::Kind::SingleCurryThunk:
1325+
case AutoClosureExpr::Kind::DoubleCurryThunk:
1326+
break;
1327+
}
1328+
}
1329+
}
13141330

13151331
ctx.Diags.diagnose(node.getStartLoc(), diag)
13161332
.fixItInsert(node.getStartLoc(), "await ")
@@ -1636,24 +1652,32 @@ class CheckEffectsCoverage : public EffectsHandlingWalker<CheckEffectsCoverage>
16361652
ShouldRecurse_t checkAutoClosure(AutoClosureExpr *E) {
16371653
ContextScope scope(*this, Context::forClosure(E));
16381654
scope.enterSubFunction();
1639-
scope.resetCoverageForAutoclosureBody();
16401655

1656+
bool shouldPreserveCoverage = true;
16411657
switch (E->getThunkKind()) {
16421658
case AutoClosureExpr::Kind::DoubleCurryThunk:
16431659
case AutoClosureExpr::Kind::SingleCurryThunk:
16441660
// Curry thunks aren't actually a call to the asynchronous function.
16451661
// Assume that async is covered in such contexts.
1662+
scope.resetCoverageForAutoclosureBody();
16461663
Flags.set(ContextFlags::IsAsyncCovered);
16471664
break;
16481665

16491666
case AutoClosureExpr::Kind::None:
1667+
scope.resetCoverageForAutoclosureBody();
1668+
break;
1669+
16501670
case AutoClosureExpr::Kind::AsyncLet:
1671+
scope.resetCoverage();
1672+
shouldPreserveCoverage = false;
16511673
break;
16521674
}
16531675

16541676
E->getBody()->walk(*this);
16551677

1656-
scope.preserveCoverageFromAutoclosureBody();
1678+
if (shouldPreserveCoverage)
1679+
scope.preserveCoverageFromAutoclosureBody();
1680+
16571681
return ShouldNotRecurse;
16581682
}
16591683

test/expr/unary/async_await.swift

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -148,6 +148,9 @@ func validAsyncFunction() async throws {
148148
// Async let checking
149149
func mightThrow() throws { }
150150

151+
func getIntUnsafely() throws -> Int { 0 }
152+
func getIntUnsafelyAsync() async throws -> Int { 0 }
153+
151154
extension Error {
152155
var number: Int { 0 }
153156
}
@@ -162,6 +165,16 @@ func testAsyncLet() async throws {
162165
} catch let e where e.number == x { // expected-error{{async let 'x' cannot be referenced in a catch guard expression}}
163166
} catch {
164167
}
168+
169+
async let x1 = getIntUnsafely() // expected-error{{call can throw but is not marked with 'try'}}
170+
// expected-note@-1{{did you mean to use 'try'}}
171+
// expected-note@-2{{did you mean to handle error as optional value?}}
172+
// expected-note@-3{{did you mean to disable error propagation?}}
173+
174+
async let x2 = getInt() // expected-error{{call is 'async' in an 'async let' initializer that is not marked with 'await'}}
175+
176+
_ = await x1
177+
_ = await x2
165178
}
166179

167180
// expected-note@+2 4{{add 'async' to function 'testAsyncLetOutOfAsync()' to make it asynchronous}}

0 commit comments

Comments
 (0)