Skip to content

Commit 9722df8

Browse files
committed
[Concurrency] Require references to 'async let' to have an 'await'.
Extend effects checking to ensure that each reference to a variable bound by an 'async let' is covered by an "await" expression and occurs in a suitable context.
1 parent 7f73877 commit 9722df8

File tree

7 files changed

+102
-11
lines changed

7 files changed

+102
-11
lines changed

include/swift/AST/Decl.h

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4727,6 +4727,9 @@ class VarDecl : public AbstractStorageDecl {
47274727
/// getSpecifier() == Specifier::Default.
47284728
bool isLet() const { return getIntroducer() == Introducer::Let; }
47294729

4730+
/// Is this an "async let" property?
4731+
bool isAsyncLet() const;
4732+
47304733
Introducer getIntroducer() const {
47314734
return Introducer(Bits.VarDecl.Introducer);
47324735
}

include/swift/AST/DiagnosticsSema.def

Lines changed: 9 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -4098,9 +4098,9 @@ ERROR(await_in_illegal_context,none,
40984098
"%select{<<ERROR>>|a default argument|a property initializer|a global variable initializer|an enum case raw value|a catch pattern|a catch guard expression|a defer body}0",
40994099
(unsigned))
41004100
ERROR(async_in_nonasync_function,none,
4101-
"%select{'async'|'await'}0 in %select{a function|an autoclosure}1 that "
4102-
"does not support concurrency",
4103-
(bool, bool))
4101+
"%select{'async'|'await'|'async let'}0 in "
4102+
"%select{a function|an autoclosure}1 that does not support concurrency",
4103+
(unsigned, bool))
41044104
NOTE(note_add_async_to_function,none,
41054105
"add 'async' to function %0 to make it asynchronous", (DeclName))
41064106
NOTE(note_add_asynchandler_to_function,none,
@@ -4126,6 +4126,12 @@ ERROR(async_let_not_initialized,none,
41264126
"'async let' binding requires an initializer expression", ())
41274127
ERROR(async_let_no_variables,none,
41284128
"'async let' requires at least one named variable", ())
4129+
ERROR(async_let_without_await,none,
4130+
"reference to async let %0 is not marked with 'await'", (DeclName))
4131+
ERROR(async_let_in_illegal_context,none,
4132+
"async let %0 cannot be referenced in "
4133+
"%select{<<ERROR>>|a default argument|a property initializer|a global variable initializer|an enum case raw value|a catch pattern|a catch guard expression|a defer body}1",
4134+
(DeclName, unsigned))
41294135

41304136
ERROR(asynchandler_non_func,none,
41314137
"'@asyncHandler' can only be applied to functions",

lib/AST/Decl.cpp

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5740,6 +5740,10 @@ bool VarDecl::isMemberwiseInitialized(bool preferDeclaredProperties) const {
57405740
return true;
57415741
}
57425742

5743+
bool VarDecl::isAsyncLet() const {
5744+
return getAttrs().hasAttribute<AsyncAttr>();
5745+
}
5746+
57435747
void ParamDecl::setSpecifier(Specifier specifier) {
57445748
// FIXME: Revisit this; in particular shouldn't __owned parameters be
57455749
// ::Let also?

lib/Sema/TypeCheckEffects.cpp

Lines changed: 51 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -219,6 +219,8 @@ class EffectsHandlingWalker : public ASTWalker {
219219
recurse = asImpl().checkOptionalTry(optionalTryExpr);
220220
} else if (auto apply = dyn_cast<ApplyExpr>(E)) {
221221
recurse = asImpl().checkApply(apply);
222+
} else if (auto declRef = dyn_cast<DeclRefExpr>(E)) {
223+
recurse = asImpl().checkDeclRef(declRef);
222224
} else if (auto interpolated = dyn_cast<InterpolatedStringLiteralExpr>(E)) {
223225
recurse = asImpl().checkInterpolatedStringLiteral(interpolated);
224226
}
@@ -656,6 +658,9 @@ class ApplyClassifier {
656658
Result = std::max(Result, classification.getResult());
657659
return ShouldRecurse;
658660
}
661+
ShouldRecurse_t checkDeclRef(DeclRefExpr *E) {
662+
return ShouldNotRecurse;
663+
}
659664
ShouldRecurse_t checkThrow(ThrowStmt *E) {
660665
Result = ThrowingKind::Throws;
661666
return ShouldRecurse;
@@ -1271,6 +1276,18 @@ class Context {
12711276
SourceRange highlight;
12721277

12731278
// Generate more specific messages in some cases.
1279+
1280+
// Reference to an 'async let' missing an 'await'.
1281+
if (auto declRef = dyn_cast_or_null<DeclRefExpr>(node.dyn_cast<Expr *>())) {
1282+
if (auto var = dyn_cast<VarDecl>(declRef->getDecl())) {
1283+
if (var->isAsyncLet()) {
1284+
ctx.Diags.diagnose(
1285+
declRef->getLoc(), diag::async_let_without_await, var->getName());
1286+
return;
1287+
}
1288+
}
1289+
}
1290+
12741291
if (auto apply = dyn_cast_or_null<ApplyExpr>(node.dyn_cast<Expr*>()))
12751292
highlight = apply->getSourceRange();
12761293

@@ -1293,6 +1310,17 @@ class Context {
12931310
static_cast<unsigned>(getKind()));
12941311
return;
12951312
}
1313+
1314+
if (auto declRef = dyn_cast<DeclRefExpr>(e)) {
1315+
if (auto var = dyn_cast<VarDecl>(declRef->getDecl())) {
1316+
if (var->isAsyncLet()) {
1317+
Diags.diagnose(
1318+
e->getLoc(), diag::async_let_in_illegal_context,
1319+
var->getName(), static_cast<unsigned>(getKind()));
1320+
return;
1321+
}
1322+
}
1323+
}
12961324
}
12971325

12981326
Diags.diagnose(node.getStartLoc(), diag::await_in_illegal_context,
@@ -1312,11 +1340,17 @@ class Context {
13121340

13131341
void diagnoseUnhandledAsyncSite(DiagnosticEngine &Diags, ASTNode node) {
13141342
switch (getKind()) {
1315-
case Kind::PotentiallyHandled:
1343+
case Kind::PotentiallyHandled: {
1344+
unsigned kind = 0;
1345+
if (node.isExpr(ExprKind::Await))
1346+
kind = 1;
1347+
else if (node.isExpr(ExprKind::DeclRef))
1348+
kind = 2;
13161349
Diags.diagnose(node.getStartLoc(), diag::async_in_nonasync_function,
1317-
node.isExpr(ExprKind::Await), isAutoClosure());
1350+
kind, isAutoClosure());
13181351
maybeAddAsyncNote(Diags);
13191352
return;
1353+
}
13201354

13211355
case Kind::EnumElementInitializer:
13221356
case Kind::GlobalVarInitializer:
@@ -1694,6 +1728,21 @@ class CheckEffectsCoverage : public EffectsHandlingWalker<CheckEffectsCoverage>
16941728
return !type || type->hasError() ? ShouldNotRecurse : ShouldRecurse;
16951729
}
16961730

1731+
ShouldRecurse_t checkDeclRef(DeclRefExpr *E) {
1732+
if (auto decl = E->getDecl()) {
1733+
if (auto var = dyn_cast<VarDecl>(decl)) {
1734+
// "Async let" declarations are treated as an asynchronous call
1735+
// (to the underlying task's "get").
1736+
if (var->isAsyncLet()) {
1737+
checkThrowAsyncSite(
1738+
E, /*requiresTry=*/false, Classification::forAsync());
1739+
}
1740+
}
1741+
}
1742+
1743+
return ShouldNotRecurse;
1744+
}
1745+
16971746
ShouldRecurse_t
16981747
checkInterpolatedStringLiteral(InterpolatedStringLiteralExpr *E) {
16991748
ContextScope scope(*this, CurContext.withInterpolatedString(E));

test/Parse/async.swift

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -59,7 +59,7 @@ func getIntSomeday() async -> Int { 5 }
5959

6060
func testAsyncLet() async {
6161
async let x = await getIntSomeday()
62-
_ = x
62+
_ = await x
6363
}
6464

6565
async func asyncIncorrectly() { } // expected-error{{'async' must be written after the parameter list of a function}}{{1-7=}}{{30-30= async}}

test/decl/var/async_let.swift

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -16,11 +16,11 @@ func testAsyncFunc() async {
1616
async var x = 17 // expected-error{{'async' can only be used with 'let' declarations}}{{9-12=let}}
1717
async let (_, _) = (1, 2), y2 = 7 // expected-error{{'async let' requires at least one named variable}}
1818
async let y: Int // expected-error{{'async let' binding requires an initializer expression}}
19-
_ = x
19+
_ = await x
2020
_ = y
21-
_ = z1
22-
_ = z2
23-
_ = x2
24-
x = 1
21+
_ = await z1
22+
_ = await z2
23+
_ = await x2
24+
await x = 1
2525
_ = y2
2626
}

test/expr/unary/async_await.swift

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -144,3 +144,32 @@ func invalidAsyncFunction() async {
144144
func validAsyncFunction() async throws {
145145
_ = try await throwingAndAsync()
146146
}
147+
148+
// Async let checking
149+
func mightThrow() throws { }
150+
151+
extension Error {
152+
var number: Int { 0 }
153+
}
154+
155+
func testAsyncLet() async throws {
156+
async let x = await getInt()
157+
print(x) // expected-error{{reference to async let 'x' is not marked with 'await'}}
158+
print(await x)
159+
160+
do {
161+
try mightThrow()
162+
} catch let e where e.number == x { // expected-error{{async let 'x' cannot be referenced in a catch guard expression}}
163+
} catch {
164+
}
165+
}
166+
167+
// expected-note@+2 2{{add 'async' to function 'testAsyncLetOutOfAsync()' to make it asynchronous}}
168+
// expected-note@+1 2{{add '@asyncHandler' to function 'testAsyncLetOutOfAsync()' to create an implicit asynchronous context}}
169+
func testAsyncLetOutOfAsync() {
170+
async let x = 1 // ERROR?
171+
172+
_ = await x // expected-error{{'async let' in a function that does not support concurrency}}
173+
_ = x // expected-error{{'async let' in a function that does not support concurrency}}
174+
}
175+

0 commit comments

Comments
 (0)