Skip to content

Commit 779a36b

Browse files
committed
[Concurrency] Require 'try' on accessing 'async let' with throwing initializer.
When an 'async let' initializer can throw, any access to one of the variables in the 'async let' can also throw, so require such accesses to be annotated with 'try'.
1 parent 3223cf1 commit 779a36b

File tree

3 files changed

+40
-5
lines changed

3 files changed

+40
-5
lines changed

include/swift/AST/DiagnosticsSema.def

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4067,6 +4067,8 @@ ERROR(throwing_interpolation_without_try,none,
40674067
"interpolation can throw but is not marked with 'try'", ())
40684068
ERROR(throwing_call_without_try,none,
40694069
"call can throw but is not marked with 'try'", ())
4070+
ERROR(throwing_async_let_without_try,none,
4071+
"reading 'async let' can throw but is not marked with 'try'", ())
40704072
NOTE(note_forgot_try,none,
40714073
"did you mean to use 'try'?", ())
40724074
NOTE(note_error_to_optional,none,

lib/Sema/TypeCheckEffects.cpp

Lines changed: 30 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -269,6 +269,9 @@ class PotentialThrowReason {
269269
/// The function calls an unconditionally throwing function.
270270
CallThrows,
271271

272+
/// The initializer of an 'async let' unconditionally throws.
273+
AsyncLetThrows,
274+
272275
/// The function is 'rethrows', and it was passed an explicit
273276
/// argument that was not rethrowing-only in this context.
274277
CallRethrowsWithExplicitThrowingArgument,
@@ -282,7 +285,8 @@ class PotentialThrowReason {
282285
switch (k) {
283286
case Kind::Throw: return "Throw";
284287
case Kind::CallThrows: return "CallThrows";
285-
case Kind::CallRethrowsWithExplicitThrowingArgument:
288+
case Kind::AsyncLetThrows: return "AsyncLetThrows";
289+
case Kind::CallRethrowsWithExplicitThrowingArgument:
286290
return "CallRethrowsWithExplicitThrowingArgument";
287291
case Kind::CallRethrowsWithDefaultThrowingArgument:
288292
return "CallRethrowsWithDefaultThrowingArgument";
@@ -309,6 +313,9 @@ class PotentialThrowReason {
309313
static PotentialThrowReason forThrow() {
310314
return PotentialThrowReason(Kind::Throw);
311315
}
316+
static PotentialThrowReason forThrowingAsyncLet() {
317+
return PotentialThrowReason(Kind::AsyncLetThrows);
318+
}
312319

313320
Kind getKind() const { return TheKind; }
314321

@@ -1124,6 +1131,7 @@ class Context {
11241131
case PotentialThrowReason::Kind::Throw:
11251132
llvm_unreachable("should already have been covered");
11261133
case PotentialThrowReason::Kind::CallThrows:
1134+
case PotentialThrowReason::Kind::AsyncLetThrows:
11271135
// Already fully diagnosed.
11281136
return;
11291137
case PotentialThrowReason::Kind::CallRethrowsWithExplicitThrowingArgument:
@@ -1141,6 +1149,9 @@ class Context {
11411149
const PotentialThrowReason &reason) {
11421150
auto &Diags = ctx.Diags;
11431151
auto message = diag::throwing_call_without_try;
1152+
if (reason.getKind() == PotentialThrowReason::Kind::AsyncLetThrows)
1153+
message = diag::throwing_async_let_without_try;
1154+
11441155
auto loc = E.getStartLoc();
11451156
SourceLoc insertLoc;
11461157
SourceRange highlight;
@@ -1781,10 +1792,25 @@ class CheckEffectsCoverage : public EffectsHandlingWalker<CheckEffectsCoverage>
17811792
if (auto decl = E->getDecl()) {
17821793
if (auto var = dyn_cast<VarDecl>(decl)) {
17831794
// "Async let" declarations are treated as an asynchronous call
1784-
// (to the underlying task's "get").
1795+
// (to the underlying task's "get"). If the initializer was throwing,
1796+
// then the access is also treated as throwing.
17851797
if (var->isAsyncLet()) {
1786-
checkThrowAsyncSite(
1787-
E, /*requiresTry=*/false, Classification::forAsync());
1798+
// If the initializer could throw, we will have a 'try' in the
1799+
// application of its autoclosure.
1800+
bool throws = false;
1801+
if (auto init = var->getParentInitializer()) {
1802+
if (auto await = dyn_cast<AwaitExpr>(init))
1803+
init = await->getSubExpr();
1804+
if (isa<TryExpr>(init))
1805+
throws = true;
1806+
}
1807+
1808+
auto classification =
1809+
throws ? Classification::forThrow(
1810+
PotentialThrowReason::forThrowingAsyncLet(),
1811+
/*isAsync=*/true)
1812+
: Classification::forAsync();
1813+
checkThrowAsyncSite(E, /*requiresTry=*/throws, classification);
17881814
}
17891815
}
17901816
}

test/expr/unary/async_await.swift

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -173,8 +173,15 @@ func testAsyncLet() async throws {
173173

174174
async let x2 = getInt() // expected-error{{call is 'async' in an 'async let' initializer that is not marked with 'await'}}
175175

176-
_ = await x1
176+
async let x3 = try getIntUnsafely()
177+
async let x4 = try! getIntUnsafely()
178+
async let x5 = try? getIntUnsafely()
179+
180+
_ = await x1 // expected-error{{reading 'async let' can throw but is not marked with 'try'}}
177181
_ = await x2
182+
_ = await try x3
183+
_ = await x4
184+
_ = await x5
178185
}
179186

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

0 commit comments

Comments
 (0)