Skip to content

Commit 58b590a

Browse files
committed
[Concurrency] Wrap the initializer of 'async let' in an autoclosure call.
The initializer of an 'async let' is executed as a separate child task that will run concurrently with the main body of the function. Model the semantics of this operation by wrapping the initializer in an async, escaping autoclosure (representing the evaluation of the child task), and then a call to that autoclosure (to This is useful both for actor isolation checking, which needs to treat the initializer as executing in concurrent code, and also (eventually) for code generation, which needs to have that code in a closure so that it can be passed off to the task-creation functions. There are a number of issues with this implementation producing extraneous diagnostics due to this closure transformation, which will be addressed in a follow-up commit.
1 parent dc4a119 commit 58b590a

File tree

6 files changed

+102
-2
lines changed

6 files changed

+102
-2
lines changed

lib/Sema/CSApply.cpp

Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7881,6 +7881,49 @@ 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(initializer, closureType);
7904+
7905+
// Call the autoclosure so that the AST types line up. SILGen will ignore the
7906+
// actual calls and translate them into a different mechanism.
7907+
auto autoclosureCall = CallExpr::createImplicit(
7908+
ctx, autoclosureExpr, { }, { });
7909+
autoclosureCall->setType(initializerType);
7910+
autoclosureCall->setThrows(throws);
7911+
7912+
// For a throwing expression, wrap the call in a 'try'.
7913+
Expr *resultInit = autoclosureCall;
7914+
if (throws) {
7915+
resultInit = new (ctx) TryExpr(
7916+
SourceLoc(), resultInit, initializerType, /*implicit=*/true);
7917+
}
7918+
7919+
// Wrap the call in an 'await'.
7920+
resultInit = new (ctx) AwaitExpr(
7921+
SourceLoc(), resultInit, initializerType, /*implicit=*/true);
7922+
7923+
cs.cacheExprTypes(resultInit);
7924+
return resultInit;
7925+
}
7926+
78847927
/// Apply the given solution to the initialization target.
78857928
///
78867929
/// \returns the resulting initialization expression.
@@ -7955,6 +7998,16 @@ static Optional<SolutionApplicationTarget> applySolutionToInitialization(
79557998
return None;
79567999
}
79578000

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

lib/Sema/TypeCheckEffects.cpp

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -533,6 +533,13 @@ class ApplyClassifier {
533533
return result;
534534
}
535535

536+
/// Classify a single expression without considering its enclosing context.
537+
ThrowingKind classifyExpr(Expr *expr) {
538+
FunctionBodyClassifier classifier(*this);
539+
expr->walk(classifier);
540+
return classifier.Result;
541+
}
542+
536543
private:
537544
/// Classify a throwing function according to our local knowledge of
538545
/// its implementation.
@@ -2015,3 +2022,7 @@ void TypeChecker::checkPropertyWrapperEffects(
20152022
CheckEffectsCoverage checker(ctx, Context::forPatternBinding(binding));
20162023
expr->walk(checker);
20172024
}
2025+
2026+
bool TypeChecker::canThrow(Expr *expr) {
2027+
return ApplyClassifier().classifyExpr(expr) == ThrowingKind::Throws;
2028+
}

lib/Sema/TypeChecker.h

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1062,6 +1062,9 @@ void checkInitializerEffects(Initializer *I, Expr *E);
10621062
void checkEnumElementEffects(EnumElementDecl *D, Expr *expr);
10631063
void checkPropertyWrapperEffects(PatternBindingDecl *binding, Expr *expr);
10641064

1065+
/// Whether the given expression can throw.
1066+
bool canThrow(Expr *expr);
1067+
10651068
/// If an expression references 'self.init' or 'super.init' in an
10661069
/// initializer context, returns the implicit 'self' decl of the constructor.
10671070
/// Otherwise, return nil.
Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
// RUN: %target-typecheck-verify-swift -enable-experimental-concurrency
2+
// REQUIRES: concurrency
3+
4+
actor class MyActor {
5+
let immutable: Int = 17
6+
var text: [String] = []
7+
8+
func synchronous() -> String { text.first ?? "nothing" } // expected-note 2 {{only asynchronous methods can be used outside the actor instance; do you want to add 'async'?}}
9+
func asynchronous() async -> String { synchronous() }
10+
11+
func testAsyncLetIsolation() async {
12+
async let x = self.synchronous()
13+
// expected-error @-1{{actor-isolated instance method 'synchronous()' is unsafe to reference in code that may execute concurrently}}
14+
15+
async let y = await self.asynchronous()
16+
17+
async let z = synchronous()
18+
// expected-error @-1{{actor-isolated instance method 'synchronous()' is unsafe to reference in code that may execute concurrently}}
19+
// FIXME: expected-error @-2{{call to method 'synchronous' in closure requires explicit use of 'self' to make capture semantics explicit}}
20+
// FIXME: expected-note @-3{{reference 'self.' explicitly}}
21+
22+
var localText = text // expected-note{{var declared here}}
23+
async let w = localText.removeLast()
24+
// expected-warning@-1{{local var 'localText' is unsafe to reference in code that may execute concurrently}}
25+
26+
_ = await x
27+
_ = await y
28+
_ = await z
29+
_ = await w
30+
}
31+
}

test/decl/var/async_let.swift

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ async let x = 1 // okay
66

77
struct X {
88
async let x = 1 // expected-error{{'async let' can only be used on local declarations}}
9+
// FIXME: expected-error@-1{{'async' call cannot occur in a property initializer}}
910
}
1011

1112
func testAsyncFunc() async {

test/expr/unary/async_await.swift

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -164,10 +164,11 @@ func testAsyncLet() async throws {
164164
}
165165
}
166166

167-
// expected-note@+2 3{{add 'async' to function 'testAsyncLetOutOfAsync()' to make it asynchronous}}
168-
// expected-note@+1 3{{add '@asyncHandler' to function 'testAsyncLetOutOfAsync()' to create an implicit asynchronous context}}
167+
// expected-note@+2 4{{add 'async' to function 'testAsyncLetOutOfAsync()' to make it asynchronous}}
168+
// expected-note@+1 4{{add '@asyncHandler' to function 'testAsyncLetOutOfAsync()' to create an implicit asynchronous context}}
169169
func testAsyncLetOutOfAsync() {
170170
async let x = 1 // expected-error{{'async let' in a function that does not support concurrency}}
171+
// FIXME: expected-error@-1{{'async' in a function that does not support concurrency}}
171172

172173
_ = await x // expected-error{{'async let' in a function that does not support concurrency}}
173174
_ = x // expected-error{{'async let' in a function that does not support concurrency}}

0 commit comments

Comments
 (0)