Skip to content

Commit 203d2c2

Browse files
authored
Merge pull request #34573 from DougGregor/async-let
[Concurrency] Implement type checking for 'async let' declarations.
2 parents 937fee4 + 4017888 commit 203d2c2

22 files changed

+477
-22
lines changed

include/swift/AST/Attr.def

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -588,6 +588,12 @@ SIMPLE_DECL_ATTR(_specializeExtension, SpecializeExtension,
588588
ABIStableToAdd | ABIStableToRemove | APIStableToAdd | APIStableToRemove,
589589
105)
590590

591+
CONTEXTUAL_SIMPLE_DECL_ATTR(async, Async,
592+
DeclModifier | OnVar | OnFunc | ConcurrencyOnly |
593+
ABIBreakingToAdd | ABIBreakingToRemove |
594+
APIBreakingToAdd | APIBreakingToRemove,
595+
106)
596+
591597
#undef TYPE_ATTR
592598
#undef DECL_ATTR_ALIAS
593599
#undef CONTEXTUAL_DECL_ATTR_ALIAS

include/swift/AST/Decl.h

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1784,6 +1784,9 @@ class PatternBindingDecl final : public Decl,
17841784
/// Is the pattern binding entry for this variable currently being computed?
17851785
bool isComputingPatternBindingEntry(const VarDecl *vd) const;
17861786

1787+
/// Is this an "async let" declaration?
1788+
bool isAsyncLet() const;
1789+
17871790
/// Gets the text of the initializer expression for the pattern entry at the
17881791
/// given index, stripping out inactive branches of any #ifs inside the
17891792
/// expression.
@@ -4727,6 +4730,9 @@ class VarDecl : public AbstractStorageDecl {
47274730
/// getSpecifier() == Specifier::Default.
47284731
bool isLet() const { return getIntroducer() == Introducer::Let; }
47294732

4733+
/// Is this an "async let" property?
4734+
bool isAsyncLet() const;
4735+
47304736
Introducer getIntroducer() const {
47314737
return Introducer(Bits.VarDecl.Introducer);
47324738
}

include/swift/AST/DiagnosticsParse.def

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -324,6 +324,8 @@ ERROR(func_decl_expected_arrow,none,
324324
ERROR(operator_static_in_protocol,none,
325325
"operator '%0' declared in protocol must be 'static'",
326326
(StringRef))
327+
ERROR(async_func_modifier,none,
328+
"'async' must be written after the parameter list of a function", ())
327329

328330
// Enum
329331
ERROR(expected_lbrace_enum,PointsToFirstBadToken,

include/swift/AST/DiagnosticsSema.def

Lines changed: 23 additions & 3 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,
@@ -4087,6 +4089,9 @@ ERROR(async_call_without_await,none,
40874089
"call is 'async' but is not marked with 'await'", ())
40884090
ERROR(async_call_without_await_in_autoclosure,none,
40894091
"call is 'async' in an autoclosure argument that is not marked with 'await'", ())
4092+
ERROR(async_call_without_await_in_async_let,none,
4093+
"call is 'async' in an 'async let' initializer that is not marked "
4094+
"with 'await'", ())
40904095
WARNING(no_async_in_await,none,
40914096
"no calls to 'async' functions occur within 'await' expression", ())
40924097
ERROR(async_call_in_illegal_context,none,
@@ -4098,9 +4103,9 @@ ERROR(await_in_illegal_context,none,
40984103
"%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",
40994104
(unsigned))
41004105
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))
4106+
"%select{'async'|'await'|'async let'}0 in "
4107+
"%select{a function|an autoclosure}1 that does not support concurrency",
4108+
(unsigned, bool))
41044109
NOTE(note_add_async_to_function,none,
41054110
"add 'async' to function %0 to make it asynchronous", (DeclName))
41064111
NOTE(note_add_asynchandler_to_function,none,
@@ -4118,6 +4123,21 @@ NOTE(protocol_witness_async_conflict,none,
41184123
ERROR(async_autoclosure_nonasync_function,none,
41194124
"'async' autoclosure parameter in a non-'async' function", ())
41204125

4126+
ERROR(async_not_let,none,
4127+
"'async' can only be used with 'let' declarations", ())
4128+
ERROR(async_let_not_local,none,
4129+
"'async let' can only be used on local declarations", ())
4130+
ERROR(async_let_not_initialized,none,
4131+
"'async let' binding requires an initializer expression", ())
4132+
ERROR(async_let_no_variables,none,
4133+
"'async let' requires at least one named variable", ())
4134+
ERROR(async_let_without_await,none,
4135+
"reference to async let %0 is not marked with 'await'", (DeclName))
4136+
ERROR(async_let_in_illegal_context,none,
4137+
"async let %0 cannot be referenced in "
4138+
"%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",
4139+
(DeclName, unsigned))
4140+
41214141
ERROR(asynchandler_non_func,none,
41224142
"'@asyncHandler' can only be applied to functions",
41234143
())

include/swift/AST/Expr.h

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3903,7 +3903,10 @@ class AutoClosureExpr : public AbstractClosureExpr {
39033903

39043904
// An autoclosure with type (Self) -> (Args...) -> Result. Formed from type
39053905
// checking a partial application.
3906-
DoubleCurryThunk = 2
3906+
DoubleCurryThunk = 2,
3907+
3908+
// An autoclosure with type () -> Result that was formed for an async let.
3909+
AsyncLet = 3,
39073910
};
39083911

39093912
AutoClosureExpr(Expr *Body, Type ResultTy, unsigned Discriminator,

include/swift/Sema/ConstraintSystem.h

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4373,7 +4373,8 @@ class ConstraintSystem {
43734373
/// Build implicit autoclosure expression wrapping a given expression.
43744374
/// Given expression represents computed result of the closure.
43754375
Expr *buildAutoClosureExpr(Expr *expr, FunctionType *closureType,
4376-
bool isDefaultWrappedValue = false);
4376+
bool isDefaultWrappedValue = false,
4377+
bool isAsyncLetWrapper = false);
43774378

43784379
/// Builds a type-erased return expression that can be used in dynamic
43794380
/// replacement.

lib/AST/Decl.cpp

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1545,6 +1545,13 @@ StaticSpellingKind PatternBindingDecl::getCorrectStaticSpelling() const {
15451545
return getCorrectStaticSpellingForDecl(this);
15461546
}
15471547

1548+
bool PatternBindingDecl::isAsyncLet() const {
1549+
if (auto var = getAnchoringVarDecl(0))
1550+
return var->isAsyncLet();
1551+
1552+
return false;
1553+
}
1554+
15481555

15491556
bool PatternBindingDecl::hasStorage() const {
15501557
// Walk the pattern, to check to see if any of the VarDecls included in it
@@ -5740,6 +5747,10 @@ bool VarDecl::isMemberwiseInitialized(bool preferDeclaredProperties) const {
57405747
return true;
57415748
}
57425749

5750+
bool VarDecl::isAsyncLet() const {
5751+
return getAttrs().hasAttribute<AsyncAttr>();
5752+
}
5753+
57435754
void ParamDecl::setSpecifier(Specifier specifier) {
57445755
// FIXME: Revisit this; in particular shouldn't __owned parameters be
57455756
// ::Let also?

lib/AST/Expr.cpp

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2031,6 +2031,7 @@ Expr *AutoClosureExpr::getUnwrappedCurryThunkExpr() const {
20312031

20322032
switch (getThunkKind()) {
20332033
case AutoClosureExpr::Kind::None:
2034+
case AutoClosureExpr::Kind::AsyncLet:
20342035
break;
20352036

20362037
case AutoClosureExpr::Kind::SingleCurryThunk: {

lib/Parse/ParseDecl.cpp

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6558,6 +6558,19 @@ ParserResult<FuncDecl> Parser::parseDeclFunc(SourceLoc StaticLoc,
65586558

65596559
diagnoseWhereClauseInGenericParamList(GenericParams);
65606560

6561+
// If there was an 'async' modifier, put it in the right place for a function.
6562+
bool isAsync = asyncLoc.isValid();
6563+
if (auto asyncAttr = Attributes.getAttribute<AsyncAttr>()) {
6564+
SourceLoc insertLoc = Lexer::getLocForEndOfToken(
6565+
SourceMgr, BodyParams->getRParenLoc());
6566+
6567+
diagnose(asyncAttr->getLocation(), diag::async_func_modifier)
6568+
.fixItRemove(asyncAttr->getRange())
6569+
.fixItInsert(insertLoc, " async");
6570+
asyncAttr->setInvalid();
6571+
isAsync = true;
6572+
}
6573+
65616574
// Create the decl for the func and add it to the parent scope.
65626575
auto *FD = FuncDecl::create(Context, StaticLoc, StaticSpelling,
65636576
FuncLoc, FullName, NameLoc,

lib/SIL/IR/SILDeclRef.cpp

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -447,8 +447,15 @@ bool SILDeclRef::isTransparent() const {
447447

448448
if (hasAutoClosureExpr()) {
449449
auto *ace = getAutoClosureExpr();
450-
if (ace->getThunkKind() == AutoClosureExpr::Kind::None)
450+
switch (ace->getThunkKind()) {
451+
case AutoClosureExpr::Kind::None:
451452
return true;
453+
454+
case AutoClosureExpr::Kind::AsyncLet:
455+
case AutoClosureExpr::Kind::DoubleCurryThunk:
456+
case AutoClosureExpr::Kind::SingleCurryThunk:
457+
break;
458+
}
452459
}
453460

454461
if (hasDecl()) {

lib/Sema/CSApply.cpp

Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7881,6 +7881,51 @@ bool ConstraintSystem::applySolutionFixes(const Solution &solution) {
78817881
return diagnosedAnyErrors;
78827882
}
78837883

7884+
/// For the initializer of an `async let`, wrap it in an autoclosure and then
7885+
/// a call to that autoclosure, so that the code for the child task is captured
7886+
/// entirely within the autoclosure. This captures the semantics of the
7887+
/// operation but not the timing, e.g., the call itself will actually occur
7888+
/// when one of the variables in the async let is referenced.
7889+
static Expr *wrapAsyncLetInitializer(
7890+
ConstraintSystem &cs, Expr *initializer, DeclContext *dc) {
7891+
// Form the autoclosure type. It is always 'async', and will be 'throws'.
7892+
Type initializerType = initializer->getType();
7893+
bool throws = TypeChecker::canThrow(initializer);
7894+
auto extInfo = ASTExtInfoBuilder()
7895+
.withAsync()
7896+
.withThrows(throws)
7897+
.build();
7898+
7899+
// Form the autoclosure expression. The actual closure here encapsulates the
7900+
// child task.
7901+
auto closureType = FunctionType::get({ }, initializerType, extInfo);
7902+
ASTContext &ctx = dc->getASTContext();
7903+
Expr *autoclosureExpr = cs.buildAutoClosureExpr(
7904+
initializer, closureType, /*isDefaultWrappedValue=*/false,
7905+
/*isAsyncLetWrapper=*/true);
7906+
7907+
// Call the autoclosure so that the AST types line up. SILGen will ignore the
7908+
// actual calls and translate them into a different mechanism.
7909+
auto autoclosureCall = CallExpr::createImplicit(
7910+
ctx, autoclosureExpr, { }, { });
7911+
autoclosureCall->setType(initializerType);
7912+
autoclosureCall->setThrows(throws);
7913+
7914+
// For a throwing expression, wrap the call in a 'try'.
7915+
Expr *resultInit = autoclosureCall;
7916+
if (throws) {
7917+
resultInit = new (ctx) TryExpr(
7918+
SourceLoc(), resultInit, initializerType, /*implicit=*/true);
7919+
}
7920+
7921+
// Wrap the call in an 'await'.
7922+
resultInit = new (ctx) AwaitExpr(
7923+
SourceLoc(), resultInit, initializerType, /*implicit=*/true);
7924+
7925+
cs.cacheExprTypes(resultInit);
7926+
return resultInit;
7927+
}
7928+
78847929
/// Apply the given solution to the initialization target.
78857930
///
78867931
/// \returns the resulting initialization expression.
@@ -7955,6 +8000,16 @@ static Optional<SolutionApplicationTarget> applySolutionToInitialization(
79558000
return None;
79568001
}
79578002

8003+
// For an async let, wrap the initializer appropriately to make it a child
8004+
// task.
8005+
if (auto patternBinding = target.getInitializationPatternBindingDecl()) {
8006+
if (patternBinding->isAsyncLet()) {
8007+
resultTarget.setExpr(
8008+
wrapAsyncLetInitializer(
8009+
cs, resultTarget.getAsExpr(), resultTarget.getDeclContext()));
8010+
}
8011+
}
8012+
79588013
return resultTarget;
79598014
}
79608015

lib/Sema/ConstraintSystem.cpp

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4662,7 +4662,8 @@ ConstraintSystem::isConversionEphemeral(ConversionRestrictionKind conversion,
46624662

46634663
Expr *ConstraintSystem::buildAutoClosureExpr(Expr *expr,
46644664
FunctionType *closureType,
4665-
bool isDefaultWrappedValue) {
4665+
bool isDefaultWrappedValue,
4666+
bool isAsyncLetWrapper) {
46664667
auto &Context = DC->getASTContext();
46674668
bool isInDefaultArgumentContext = false;
46684669
if (auto *init = dyn_cast<Initializer>(DC)) {
@@ -4684,6 +4685,9 @@ Expr *ConstraintSystem::buildAutoClosureExpr(Expr *expr,
46844685

46854686
closure->setParameterList(ParameterList::createEmpty(Context));
46864687

4688+
if (isAsyncLetWrapper)
4689+
closure->setThunkKind(AutoClosureExpr::Kind::AsyncLet);
4690+
46874691
Expr *result = closure;
46884692

46894693
if (!newClosureType->isEqual(closureType)) {

lib/Sema/MiscDiagnostics.cpp

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1495,8 +1495,16 @@ static void diagnoseImplicitSelfUseInClosure(const Expr *E,
14951495
const AbstractClosureExpr *CE) {
14961496
// If the closure's type was inferred to be noescape, then it doesn't
14971497
// need qualification.
1498-
return !AnyFunctionRef(const_cast<AbstractClosureExpr *>(CE))
1499-
.isKnownNoEscape();
1498+
if (AnyFunctionRef(const_cast<AbstractClosureExpr *>(CE))
1499+
.isKnownNoEscape())
1500+
return false;
1501+
1502+
if (auto autoclosure = dyn_cast<AutoClosureExpr>(CE)) {
1503+
if (autoclosure->getThunkKind() == AutoClosureExpr::Kind::AsyncLet)
1504+
return false;
1505+
}
1506+
1507+
return true;
15001508
}
15011509

15021510

lib/Sema/TypeCheckAttr.cpp

Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -344,6 +344,59 @@ class AttributeChecker : public AttributeVisitor<AttributeChecker> {
344344

345345
(void)nominal->isGlobalActor();
346346
}
347+
348+
void visitAsyncAttr(AsyncAttr *attr) {
349+
auto var = dyn_cast<VarDecl>(D);
350+
if (!var)
351+
return;
352+
353+
auto patternBinding = var->getParentPatternBinding();
354+
if (!patternBinding)
355+
return; // already diagnosed
356+
357+
// "Async" modifier can only be applied to local declarations.
358+
if (!patternBinding->getDeclContext()->isLocalContext()) {
359+
diagnoseAndRemoveAttr(attr, diag::async_let_not_local);
360+
return;
361+
}
362+
363+
// Check each of the pattern binding entries.
364+
bool diagnosedVar = false;
365+
for (unsigned index : range(patternBinding->getNumPatternEntries())) {
366+
auto pattern = patternBinding->getPattern(index);
367+
368+
// Look for variables bound by this pattern.
369+
bool foundAnyVariable = false;
370+
bool isLet = true;
371+
pattern->forEachVariable([&](VarDecl *var) {
372+
if (!var->isLet())
373+
isLet = false;
374+
foundAnyVariable = true;
375+
});
376+
377+
// Each entry must bind at least one named variable, so that there is
378+
// something to "await".
379+
if (!foundAnyVariable) {
380+
diagnose(pattern->getLoc(), diag::async_let_no_variables);
381+
attr->setInvalid();
382+
return;
383+
}
384+
385+
// Async can only be used on an "async let".
386+
if (!isLet && !diagnosedVar) {
387+
diagnose(patternBinding->getLoc(), diag::async_not_let)
388+
.fixItReplace(patternBinding->getLoc(), "let");
389+
diagnosedVar = true;
390+
}
391+
392+
// Each pattern entry must have an initializer expression.
393+
if (patternBinding->getEqualLoc(index).isInvalid()) {
394+
diagnose(pattern->getLoc(), diag::async_let_not_initialized);
395+
attr->setInvalid();
396+
return;
397+
}
398+
}
399+
}
347400
};
348401
} // end anonymous namespace
349402

lib/Sema/TypeCheckDeclOverride.cpp

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1516,6 +1516,7 @@ namespace {
15161516
UNINTERESTING_ATTR(Actor)
15171517
UNINTERESTING_ATTR(ActorIndependent)
15181518
UNINTERESTING_ATTR(GlobalActor)
1519+
UNINTERESTING_ATTR(Async)
15191520
#undef UNINTERESTING_ATTR
15201521

15211522
void visitAvailableAttr(AvailableAttr *attr) {

0 commit comments

Comments
 (0)