Skip to content

[Concurrency] Implement type checking for 'async let' declarations. #34573

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 9 commits into from
Nov 5, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions include/swift/AST/Attr.def
Original file line number Diff line number Diff line change
Expand Up @@ -588,6 +588,12 @@ SIMPLE_DECL_ATTR(_specializeExtension, SpecializeExtension,
ABIStableToAdd | ABIStableToRemove | APIStableToAdd | APIStableToRemove,
105)

CONTEXTUAL_SIMPLE_DECL_ATTR(async, Async,
DeclModifier | OnVar | OnFunc | ConcurrencyOnly |
ABIBreakingToAdd | ABIBreakingToRemove |
APIBreakingToAdd | APIBreakingToRemove,
106)

#undef TYPE_ATTR
#undef DECL_ATTR_ALIAS
#undef CONTEXTUAL_DECL_ATTR_ALIAS
Expand Down
6 changes: 6 additions & 0 deletions include/swift/AST/Decl.h
Original file line number Diff line number Diff line change
Expand Up @@ -1784,6 +1784,9 @@ class PatternBindingDecl final : public Decl,
/// Is the pattern binding entry for this variable currently being computed?
bool isComputingPatternBindingEntry(const VarDecl *vd) const;

/// Is this an "async let" declaration?
bool isAsyncLet() const;

/// Gets the text of the initializer expression for the pattern entry at the
/// given index, stripping out inactive branches of any #ifs inside the
/// expression.
Expand Down Expand Up @@ -4727,6 +4730,9 @@ class VarDecl : public AbstractStorageDecl {
/// getSpecifier() == Specifier::Default.
bool isLet() const { return getIntroducer() == Introducer::Let; }

/// Is this an "async let" property?
bool isAsyncLet() const;

Introducer getIntroducer() const {
return Introducer(Bits.VarDecl.Introducer);
}
Expand Down
2 changes: 2 additions & 0 deletions include/swift/AST/DiagnosticsParse.def
Original file line number Diff line number Diff line change
Expand Up @@ -324,6 +324,8 @@ ERROR(func_decl_expected_arrow,none,
ERROR(operator_static_in_protocol,none,
"operator '%0' declared in protocol must be 'static'",
(StringRef))
ERROR(async_func_modifier,none,
"'async' must be written after the parameter list of a function", ())

// Enum
ERROR(expected_lbrace_enum,PointsToFirstBadToken,
Expand Down
26 changes: 23 additions & 3 deletions include/swift/AST/DiagnosticsSema.def
Original file line number Diff line number Diff line change
Expand Up @@ -4067,6 +4067,8 @@ ERROR(throwing_interpolation_without_try,none,
"interpolation can throw but is not marked with 'try'", ())
ERROR(throwing_call_without_try,none,
"call can throw but is not marked with 'try'", ())
ERROR(throwing_async_let_without_try,none,
"reading 'async let' can throw but is not marked with 'try'", ())
NOTE(note_forgot_try,none,
"did you mean to use 'try'?", ())
NOTE(note_error_to_optional,none,
Expand All @@ -4087,6 +4089,9 @@ ERROR(async_call_without_await,none,
"call is 'async' but is not marked with 'await'", ())
ERROR(async_call_without_await_in_autoclosure,none,
"call is 'async' in an autoclosure argument that is not marked with 'await'", ())
ERROR(async_call_without_await_in_async_let,none,
"call is 'async' in an 'async let' initializer that is not marked "
"with 'await'", ())
WARNING(no_async_in_await,none,
"no calls to 'async' functions occur within 'await' expression", ())
ERROR(async_call_in_illegal_context,none,
Expand All @@ -4098,9 +4103,9 @@ ERROR(await_in_illegal_context,none,
"%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",
(unsigned))
ERROR(async_in_nonasync_function,none,
"%select{'async'|'await'}0 in %select{a function|an autoclosure}1 that "
"does not support concurrency",
(bool, bool))
"%select{'async'|'await'|'async let'}0 in "
"%select{a function|an autoclosure}1 that does not support concurrency",
(unsigned, bool))
NOTE(note_add_async_to_function,none,
"add 'async' to function %0 to make it asynchronous", (DeclName))
NOTE(note_add_asynchandler_to_function,none,
Expand All @@ -4118,6 +4123,21 @@ NOTE(protocol_witness_async_conflict,none,
ERROR(async_autoclosure_nonasync_function,none,
"'async' autoclosure parameter in a non-'async' function", ())

ERROR(async_not_let,none,
"'async' can only be used with 'let' declarations", ())
ERROR(async_let_not_local,none,
"'async let' can only be used on local declarations", ())
ERROR(async_let_not_initialized,none,
"'async let' binding requires an initializer expression", ())
ERROR(async_let_no_variables,none,
"'async let' requires at least one named variable", ())
ERROR(async_let_without_await,none,
"reference to async let %0 is not marked with 'await'", (DeclName))
ERROR(async_let_in_illegal_context,none,
"async let %0 cannot be referenced in "
"%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",
(DeclName, unsigned))

ERROR(asynchandler_non_func,none,
"'@asyncHandler' can only be applied to functions",
())
Expand Down
5 changes: 4 additions & 1 deletion include/swift/AST/Expr.h
Original file line number Diff line number Diff line change
Expand Up @@ -3903,7 +3903,10 @@ class AutoClosureExpr : public AbstractClosureExpr {

// An autoclosure with type (Self) -> (Args...) -> Result. Formed from type
// checking a partial application.
DoubleCurryThunk = 2
DoubleCurryThunk = 2,

// An autoclosure with type () -> Result that was formed for an async let.
AsyncLet = 3,
};

AutoClosureExpr(Expr *Body, Type ResultTy, unsigned Discriminator,
Expand Down
3 changes: 2 additions & 1 deletion include/swift/Sema/ConstraintSystem.h
Original file line number Diff line number Diff line change
Expand Up @@ -4373,7 +4373,8 @@ class ConstraintSystem {
/// Build implicit autoclosure expression wrapping a given expression.
/// Given expression represents computed result of the closure.
Expr *buildAutoClosureExpr(Expr *expr, FunctionType *closureType,
bool isDefaultWrappedValue = false);
bool isDefaultWrappedValue = false,
bool isAsyncLetWrapper = false);

/// Builds a type-erased return expression that can be used in dynamic
/// replacement.
Expand Down
11 changes: 11 additions & 0 deletions lib/AST/Decl.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1545,6 +1545,13 @@ StaticSpellingKind PatternBindingDecl::getCorrectStaticSpelling() const {
return getCorrectStaticSpellingForDecl(this);
}

bool PatternBindingDecl::isAsyncLet() const {
if (auto var = getAnchoringVarDecl(0))
return var->isAsyncLet();

return false;
}


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

bool VarDecl::isAsyncLet() const {
return getAttrs().hasAttribute<AsyncAttr>();
}

void ParamDecl::setSpecifier(Specifier specifier) {
// FIXME: Revisit this; in particular shouldn't __owned parameters be
// ::Let also?
Expand Down
1 change: 1 addition & 0 deletions lib/AST/Expr.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -2031,6 +2031,7 @@ Expr *AutoClosureExpr::getUnwrappedCurryThunkExpr() const {

switch (getThunkKind()) {
case AutoClosureExpr::Kind::None:
case AutoClosureExpr::Kind::AsyncLet:
break;

case AutoClosureExpr::Kind::SingleCurryThunk: {
Expand Down
13 changes: 13 additions & 0 deletions lib/Parse/ParseDecl.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -6558,6 +6558,19 @@ ParserResult<FuncDecl> Parser::parseDeclFunc(SourceLoc StaticLoc,

diagnoseWhereClauseInGenericParamList(GenericParams);

// If there was an 'async' modifier, put it in the right place for a function.
bool isAsync = asyncLoc.isValid();
if (auto asyncAttr = Attributes.getAttribute<AsyncAttr>()) {
SourceLoc insertLoc = Lexer::getLocForEndOfToken(
SourceMgr, BodyParams->getRParenLoc());

diagnose(asyncAttr->getLocation(), diag::async_func_modifier)
.fixItRemove(asyncAttr->getRange())
.fixItInsert(insertLoc, " async");
asyncAttr->setInvalid();
isAsync = true;
}

// Create the decl for the func and add it to the parent scope.
auto *FD = FuncDecl::create(Context, StaticLoc, StaticSpelling,
FuncLoc, FullName, NameLoc,
Expand Down
9 changes: 8 additions & 1 deletion lib/SIL/IR/SILDeclRef.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -447,8 +447,15 @@ bool SILDeclRef::isTransparent() const {

if (hasAutoClosureExpr()) {
auto *ace = getAutoClosureExpr();
if (ace->getThunkKind() == AutoClosureExpr::Kind::None)
switch (ace->getThunkKind()) {
case AutoClosureExpr::Kind::None:
return true;

case AutoClosureExpr::Kind::AsyncLet:
case AutoClosureExpr::Kind::DoubleCurryThunk:
case AutoClosureExpr::Kind::SingleCurryThunk:
break;
}
}

if (hasDecl()) {
Expand Down
55 changes: 55 additions & 0 deletions lib/Sema/CSApply.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -7881,6 +7881,51 @@ bool ConstraintSystem::applySolutionFixes(const Solution &solution) {
return diagnosedAnyErrors;
}

/// For the initializer of an `async let`, wrap it in an autoclosure and then
/// a call to that autoclosure, so that the code for the child task is captured
/// entirely within the autoclosure. This captures the semantics of the
/// operation but not the timing, e.g., the call itself will actually occur
/// when one of the variables in the async let is referenced.
static Expr *wrapAsyncLetInitializer(
ConstraintSystem &cs, Expr *initializer, DeclContext *dc) {
// Form the autoclosure type. It is always 'async', and will be 'throws'.
Type initializerType = initializer->getType();
bool throws = TypeChecker::canThrow(initializer);
auto extInfo = ASTExtInfoBuilder()
.withAsync()
.withThrows(throws)
.build();

// Form the autoclosure expression. The actual closure here encapsulates the
// child task.
auto closureType = FunctionType::get({ }, initializerType, extInfo);
ASTContext &ctx = dc->getASTContext();
Expr *autoclosureExpr = cs.buildAutoClosureExpr(
initializer, closureType, /*isDefaultWrappedValue=*/false,
/*isAsyncLetWrapper=*/true);

// Call the autoclosure so that the AST types line up. SILGen will ignore the
// actual calls and translate them into a different mechanism.
auto autoclosureCall = CallExpr::createImplicit(
ctx, autoclosureExpr, { }, { });
autoclosureCall->setType(initializerType);
autoclosureCall->setThrows(throws);

// For a throwing expression, wrap the call in a 'try'.
Expr *resultInit = autoclosureCall;
if (throws) {
resultInit = new (ctx) TryExpr(
SourceLoc(), resultInit, initializerType, /*implicit=*/true);
}

// Wrap the call in an 'await'.
resultInit = new (ctx) AwaitExpr(
SourceLoc(), resultInit, initializerType, /*implicit=*/true);

cs.cacheExprTypes(resultInit);
return resultInit;
}

/// Apply the given solution to the initialization target.
///
/// \returns the resulting initialization expression.
Expand Down Expand Up @@ -7955,6 +8000,16 @@ static Optional<SolutionApplicationTarget> applySolutionToInitialization(
return None;
}

// For an async let, wrap the initializer appropriately to make it a child
// task.
if (auto patternBinding = target.getInitializationPatternBindingDecl()) {
if (patternBinding->isAsyncLet()) {
resultTarget.setExpr(
wrapAsyncLetInitializer(
cs, resultTarget.getAsExpr(), resultTarget.getDeclContext()));
}
}

return resultTarget;
}

Expand Down
6 changes: 5 additions & 1 deletion lib/Sema/ConstraintSystem.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -4662,7 +4662,8 @@ ConstraintSystem::isConversionEphemeral(ConversionRestrictionKind conversion,

Expr *ConstraintSystem::buildAutoClosureExpr(Expr *expr,
FunctionType *closureType,
bool isDefaultWrappedValue) {
bool isDefaultWrappedValue,
bool isAsyncLetWrapper) {
auto &Context = DC->getASTContext();
bool isInDefaultArgumentContext = false;
if (auto *init = dyn_cast<Initializer>(DC)) {
Expand All @@ -4684,6 +4685,9 @@ Expr *ConstraintSystem::buildAutoClosureExpr(Expr *expr,

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

if (isAsyncLetWrapper)
closure->setThunkKind(AutoClosureExpr::Kind::AsyncLet);

Expr *result = closure;

if (!newClosureType->isEqual(closureType)) {
Expand Down
12 changes: 10 additions & 2 deletions lib/Sema/MiscDiagnostics.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1495,8 +1495,16 @@ static void diagnoseImplicitSelfUseInClosure(const Expr *E,
const AbstractClosureExpr *CE) {
// If the closure's type was inferred to be noescape, then it doesn't
// need qualification.
return !AnyFunctionRef(const_cast<AbstractClosureExpr *>(CE))
.isKnownNoEscape();
if (AnyFunctionRef(const_cast<AbstractClosureExpr *>(CE))
.isKnownNoEscape())
return false;

if (auto autoclosure = dyn_cast<AutoClosureExpr>(CE)) {
if (autoclosure->getThunkKind() == AutoClosureExpr::Kind::AsyncLet)
return false;
}

return true;
}


Expand Down
53 changes: 53 additions & 0 deletions lib/Sema/TypeCheckAttr.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -344,6 +344,59 @@ class AttributeChecker : public AttributeVisitor<AttributeChecker> {

(void)nominal->isGlobalActor();
}

void visitAsyncAttr(AsyncAttr *attr) {
auto var = dyn_cast<VarDecl>(D);
if (!var)
return;

auto patternBinding = var->getParentPatternBinding();
if (!patternBinding)
return; // already diagnosed

// "Async" modifier can only be applied to local declarations.
if (!patternBinding->getDeclContext()->isLocalContext()) {
diagnoseAndRemoveAttr(attr, diag::async_let_not_local);
return;
}

// Check each of the pattern binding entries.
bool diagnosedVar = false;
for (unsigned index : range(patternBinding->getNumPatternEntries())) {
auto pattern = patternBinding->getPattern(index);

// Look for variables bound by this pattern.
bool foundAnyVariable = false;
bool isLet = true;
pattern->forEachVariable([&](VarDecl *var) {
if (!var->isLet())
isLet = false;
foundAnyVariable = true;
});

// Each entry must bind at least one named variable, so that there is
// something to "await".
if (!foundAnyVariable) {
diagnose(pattern->getLoc(), diag::async_let_no_variables);
attr->setInvalid();
return;
}

// Async can only be used on an "async let".
if (!isLet && !diagnosedVar) {
diagnose(patternBinding->getLoc(), diag::async_not_let)
.fixItReplace(patternBinding->getLoc(), "let");
diagnosedVar = true;
}

// Each pattern entry must have an initializer expression.
if (patternBinding->getEqualLoc(index).isInvalid()) {
diagnose(pattern->getLoc(), diag::async_let_not_initialized);
attr->setInvalid();
return;
}
}
}
};
} // end anonymous namespace

Expand Down
1 change: 1 addition & 0 deletions lib/Sema/TypeCheckDeclOverride.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1516,6 +1516,7 @@ namespace {
UNINTERESTING_ATTR(Actor)
UNINTERESTING_ATTR(ActorIndependent)
UNINTERESTING_ATTR(GlobalActor)
UNINTERESTING_ATTR(Async)
#undef UNINTERESTING_ATTR

void visitAvailableAttr(AvailableAttr *attr) {
Expand Down
Loading