Skip to content

Commit 377ca35

Browse files
authored
Merge pull request swiftlang#41061 from etcwilde/ewilde/async-top-level-detection
Concurrent top-level detection
2 parents edf98aa + 9c02a3e commit 377ca35

17 files changed

+322
-88
lines changed

include/swift/AST/SourceFile.h

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -582,6 +582,9 @@ class SourceFile final : public FileUnit {
582582

583583
ArrayRef<OpaqueTypeDecl *> getOpaqueReturnTypeDecls();
584584

585+
/// Returns true if the source file contains concurrency in the top-level
586+
bool isAsyncTopLevelSourceFile() const;
587+
585588
private:
586589

587590
/// If not \c None, the underlying vector contains the parsed tokens of this

include/swift/AST/Stmt.h

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -161,7 +161,7 @@ class BraceStmt final : public Stmt,
161161

162162
SourceLoc getLBraceLoc() const { return LBLoc; }
163163
SourceLoc getRBraceLoc() const { return RBLoc; }
164-
164+
165165
SourceRange getSourceRange() const { return SourceRange(LBLoc, RBLoc); }
166166

167167
bool empty() const { return getNumElements() == 0; }
@@ -182,7 +182,9 @@ class BraceStmt final : public Stmt,
182182
ArrayRef<ASTNode> getElements() const {
183183
return {getTrailingObjects<ASTNode>(), Bits.BraceStmt.NumElements};
184184
}
185-
185+
186+
ASTNode findAsyncNode();
187+
186188
static bool classof(const Stmt *S) { return S->getKind() == StmtKind::Brace; }
187189
};
188190

include/swift/AST/TypeCheckRequests.h

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3283,6 +3283,21 @@ class ClosureEffectsRequest
32833283
bool isCached() const { return true; }
32843284
};
32853285

3286+
class GetSourceFileAsyncNode
3287+
: public SimpleRequest<GetSourceFileAsyncNode, ASTNode(const SourceFile *),
3288+
RequestFlags::Cached> {
3289+
public:
3290+
using SimpleRequest::SimpleRequest;
3291+
3292+
private:
3293+
friend SimpleRequest;
3294+
3295+
ASTNode evaluate(Evaluator &evaluator, const SourceFile *) const;
3296+
3297+
public:
3298+
bool isCached() const { return true; }
3299+
};
3300+
32863301
void simple_display(llvm::raw_ostream &out, Type value);
32873302
void simple_display(llvm::raw_ostream &out, const TypeRepr *TyR);
32883303
void simple_display(llvm::raw_ostream &out, ImplicitMemberAction action);

include/swift/AST/TypeCheckerTypeIDZone.def

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -378,3 +378,6 @@ SWIFT_REQUEST(TypeChecker, RenamedDeclRequest,
378378
SWIFT_REQUEST(TypeChecker, ClosureEffectsRequest,
379379
FunctionType::ExtInfo(ClosureExpr *),
380380
Cached, NoLocationInfo)
381+
SWIFT_REQUEST(TypeChecker, GetSourceFileAsyncNode,
382+
AwaitExpr *(const SourceFile *),
383+
Cached, NoLocationInfo)

lib/AST/Decl.cpp

Lines changed: 4 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -736,8 +736,8 @@ bool Decl::preconcurrency() const {
736736
// Variables declared in top-level code are @_predatesConcurrency
737737
if (const VarDecl *var = dyn_cast<VarDecl>(this)) {
738738
const LangOptions &langOpts = getASTContext().LangOpts;
739-
return !langOpts.isSwiftVersionAtLeast(6) &&
740-
langOpts.EnableExperimentalAsyncTopLevel && var->isTopLevelGlobal();
739+
return !langOpts.isSwiftVersionAtLeast(6) && var->isTopLevelGlobal() &&
740+
var->getDeclContext()->isAsyncContext();
741741
}
742742

743743
return false;
@@ -8946,9 +8946,8 @@ ActorIsolation swift::getActorIsolationOfContext(DeclContext *dc) {
89468946
}
89478947

89488948
if (auto *tld = dyn_cast<TopLevelCodeDecl>(dc)) {
8949-
ASTContext &ctx = dc->getASTContext();
8950-
if (ctx.LangOpts.EnableExperimentalAsyncTopLevel) {
8951-
if (Type mainActor = ctx.getMainActorType())
8949+
if (dc->isAsyncContext()) {
8950+
if (Type mainActor = dc->getASTContext().getMainActorType())
89528951
return ActorIsolation::forGlobalActor(mainActor, /*unsafe=*/false);
89538952
}
89548953
}

lib/AST/DeclContext.cpp

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1228,8 +1228,13 @@ bool DeclContext::isAsyncContext() const {
12281228
case DeclContextKind::GenericTypeDecl:
12291229
return false;
12301230
case DeclContextKind::FileUnit:
1231+
if (const SourceFile *sf = dyn_cast<SourceFile>(this))
1232+
return getASTContext().LangOpts.EnableExperimentalAsyncTopLevel &&
1233+
sf->isAsyncTopLevelSourceFile();
1234+
return false;
12311235
case DeclContextKind::TopLevelCodeDecl:
1232-
return getASTContext().LangOpts.EnableExperimentalAsyncTopLevel;
1236+
return getASTContext().LangOpts.EnableExperimentalAsyncTopLevel &&
1237+
getParent()->isAsyncContext();
12331238
case DeclContextKind::AbstractClosureExpr:
12341239
return cast<AbstractClosureExpr>(this)->isBodyAsync();
12351240
case DeclContextKind::AbstractFunctionDecl: {

lib/AST/Module.cpp

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3038,6 +3038,24 @@ SourceFile::lookupOpaqueResultType(StringRef MangledName) {
30383038
return nullptr;
30393039
}
30403040

3041+
bool SourceFile::isAsyncTopLevelSourceFile() const {
3042+
return isScriptMode() &&
3043+
(bool)evaluateOrDefault(getASTContext().evaluator,
3044+
GetSourceFileAsyncNode{this}, ASTNode());
3045+
}
3046+
3047+
ASTNode GetSourceFileAsyncNode::evaluate(Evaluator &eval,
3048+
const SourceFile *sf) const {
3049+
for (Decl *d : sf->getTopLevelDecls()) {
3050+
TopLevelCodeDecl *tld = dyn_cast<TopLevelCodeDecl>(d);
3051+
if (tld && tld->getBody()) {
3052+
if (ASTNode asyncNode = tld->getBody()->findAsyncNode())
3053+
return asyncNode;
3054+
}
3055+
}
3056+
return ASTNode();
3057+
}
3058+
30413059
//===----------------------------------------------------------------------===//
30423060
// SynthesizedFileUnit Implementation
30433061
//===----------------------------------------------------------------------===//

lib/AST/Stmt.cpp

Lines changed: 60 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@
1616

1717
#include "swift/AST/Stmt.h"
1818
#include "swift/AST/ASTContext.h"
19+
#include "swift/AST/ASTWalker.h"
1920
#include "swift/AST/Decl.h"
2021
#include "swift/AST/Expr.h"
2122
#include "swift/AST/Pattern.h"
@@ -155,6 +156,65 @@ BraceStmt *BraceStmt::create(ASTContext &ctx, SourceLoc lbloc,
155156
return ::new(Buffer) BraceStmt(lbloc, elts, rbloc, implicit);
156157
}
157158

159+
ASTNode BraceStmt::findAsyncNode() {
160+
// TODO: Statements don't track their ASTContext/evaluator, so I am not making
161+
// this a request. It probably should be a request at some point.
162+
//
163+
// While we're at it, it would be very nice if this could be a const
164+
// operation, but the AST-walking is not a const operation.
165+
166+
// A walker that looks for 'async' and 'await' expressions
167+
// that aren't nested within closures or nested declarations.
168+
class FindInnerAsync : public ASTWalker {
169+
ASTNode AsyncNode;
170+
171+
std::pair<bool, Expr *> walkToExprPre(Expr *expr) override {
172+
// If we've found an 'await', record it and terminate the traversal.
173+
if (isa<AwaitExpr>(expr)) {
174+
AsyncNode = expr;
175+
return {false, nullptr};
176+
}
177+
178+
// Do not recurse into other closures.
179+
if (isa<ClosureExpr>(expr))
180+
return {false, expr};
181+
182+
return {true, expr};
183+
}
184+
185+
bool walkToDeclPre(Decl *decl) override {
186+
// Do not walk into function or type declarations.
187+
if (auto *patternBinding = dyn_cast<PatternBindingDecl>(decl)) {
188+
if (patternBinding->isAsyncLet())
189+
AsyncNode = patternBinding;
190+
191+
return true;
192+
}
193+
194+
return false;
195+
}
196+
197+
std::pair<bool, Stmt *> walkToStmtPre(Stmt *stmt) override {
198+
if (auto forEach = dyn_cast<ForEachStmt>(stmt)) {
199+
if (forEach->getAwaitLoc().isValid()) {
200+
AsyncNode = forEach;
201+
return {false, nullptr};
202+
}
203+
}
204+
205+
return {true, stmt};
206+
}
207+
208+
public:
209+
ASTNode getAsyncNode() { return AsyncNode; }
210+
};
211+
212+
FindInnerAsync asyncFinder;
213+
walk(asyncFinder);
214+
215+
return asyncFinder.getAsyncNode();
216+
}
217+
158218
SourceLoc ReturnStmt::getStartLoc() const {
159219
if (ReturnLoc.isInvalid() && Result)
160220
return Result->getStartLoc();

lib/Sema/ConstraintSystem.cpp

Lines changed: 1 addition & 51 deletions
Original file line numberDiff line numberDiff line change
@@ -6111,55 +6111,5 @@ ASTNode constraints::findAsyncNode(ClosureExpr *closure) {
61116111
auto *body = closure->getBody();
61126112
if (!body)
61136113
return ASTNode();
6114-
6115-
// A walker that looks for 'async' and 'await' expressions
6116-
// that aren't nested within closures or nested declarations.
6117-
class FindInnerAsync : public ASTWalker {
6118-
ASTNode AsyncNode;
6119-
6120-
std::pair<bool, Expr *> walkToExprPre(Expr *expr) override {
6121-
// If we've found an 'await', record it and terminate the traversal.
6122-
if (isa<AwaitExpr>(expr)) {
6123-
AsyncNode = expr;
6124-
return {false, nullptr};
6125-
}
6126-
6127-
// Do not recurse into other closures.
6128-
if (isa<ClosureExpr>(expr))
6129-
return {false, expr};
6130-
6131-
return {true, expr};
6132-
}
6133-
6134-
bool walkToDeclPre(Decl *decl) override {
6135-
// Do not walk into function or type declarations.
6136-
if (auto *patternBinding = dyn_cast<PatternBindingDecl>(decl)) {
6137-
if (patternBinding->isAsyncLet())
6138-
AsyncNode = patternBinding;
6139-
6140-
return true;
6141-
}
6142-
6143-
return false;
6144-
}
6145-
6146-
std::pair<bool, Stmt *> walkToStmtPre(Stmt *stmt) override {
6147-
if (auto forEach = dyn_cast<ForEachStmt>(stmt)) {
6148-
if (forEach->getAwaitLoc().isValid()) {
6149-
AsyncNode = forEach;
6150-
return {false, nullptr};
6151-
}
6152-
}
6153-
6154-
return {true, stmt};
6155-
}
6156-
6157-
public:
6158-
ASTNode getAsyncNode() { return AsyncNode; }
6159-
};
6160-
6161-
FindInnerAsync asyncFinder;
6162-
body->walk(asyncFinder);
6163-
6164-
return asyncFinder.getAsyncNode();
6114+
return body->findAsyncNode();
61656115
}

lib/Sema/TypeCheckConcurrency.cpp

Lines changed: 3 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -271,8 +271,7 @@ GlobalActorAttributeRequest::evaluate(
271271
} else if (auto storage = dyn_cast<AbstractStorageDecl>(decl)) {
272272
// Subscripts and properties are fine...
273273
if (auto var = dyn_cast<VarDecl>(storage)) {
274-
if (var->isTopLevelGlobal() &&
275-
var->getASTContext().LangOpts.EnableExperimentalAsyncTopLevel) {
274+
if (var->isTopLevelGlobal() && var->getDeclContext()->isAsyncContext()) {
276275
var->diagnose(diag::global_actor_top_level_var)
277276
.highlight(globalActorAttr->getRangeWithAt());
278277
return None;
@@ -3538,10 +3537,8 @@ ActorIsolation ActorIsolationRequest::evaluate(
35383537
}
35393538

35403539
if (auto var = dyn_cast<VarDecl>(value)) {
3541-
ASTContext &ctx = var->getASTContext();
3542-
if (var->isTopLevelGlobal() &&
3543-
ctx.LangOpts.EnableExperimentalAsyncTopLevel) {
3544-
if (Type mainActor = ctx.getMainActorType())
3540+
if (var->isTopLevelGlobal() && var->getDeclContext()->isAsyncContext()) {
3541+
if (Type mainActor = var->getASTContext().getMainActorType())
35453542
return inferredIsolation(
35463543
ActorIsolation::forGlobalActor(mainActor,
35473544
/*unsafe=*/var->preconcurrency()));

lib/Sema/TypeCheckEffects.cpp

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1492,9 +1492,7 @@ class Context {
14921492
static Context forTopLevelCode(TopLevelCodeDecl *D) {
14931493
// Top-level code implicitly handles errors.
14941494
return Context(/*handlesErrors=*/true,
1495-
/*handlesAsync=*/
1496-
D->getASTContext().LangOpts.EnableExperimentalAsyncTopLevel,
1497-
None);
1495+
/*handlesAsync=*/D->isAsyncContext(), None);
14981496
}
14991497

15001498
static Context forFunction(AbstractFunctionDecl *D) {
Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,50 @@
1+
// RUN: %target-swift-frontend -typecheck -disable-availability-checking -enable-experimental-async-top-level -swift-version 5 %s -verify
2+
3+
// enable-experimental-async-top-level is passed and an await is used in the
4+
// top-level, so the top-level code is a concurrent context. Variables are
5+
// declared with `@_predatesConcurrency @MainActor`, and the top-level is run on
6+
// the main actor.
7+
8+
var a = 10 // expected-note {{mutation of this var is only permitted within the actor}}
9+
10+
func nonIsolatedSync() {
11+
// Okay because `a` is '@_predatesConcurrency'
12+
print(a)
13+
a = a + 10
14+
}
15+
16+
@MainActor
17+
func isolatedSync() {
18+
print(a)
19+
a = a + 10
20+
}
21+
22+
func nonIsolatedAsync() async {
23+
await print(a)
24+
a = a + 10
25+
// expected-error@-1:5 {{var 'a' isolated to global actor 'MainActor' can not be mutated from this context}}
26+
// expected-error@-2:9 {{expression is 'async' but is not marked with 'await'}}{{9-9=await }}
27+
// expected-note@-3:9 {{property access is 'async'}}
28+
}
29+
30+
@MainActor
31+
func isolatedAsync() async {
32+
print(a)
33+
a = a + 10
34+
}
35+
36+
nonIsolatedSync()
37+
isolatedSync()
38+
await nonIsolatedAsync()
39+
await isolatedAsync()
40+
41+
print(a)
42+
43+
if a > 10 {
44+
nonIsolatedSync()
45+
isolatedSync()
46+
await nonIsolatedAsync()
47+
await isolatedAsync()
48+
49+
print(a)
50+
}
Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,49 @@
1+
// RUN: %target-swift-frontend -typecheck -disable-availability-checking -enable-experimental-async-top-level -swift-version 6 %s -verify
2+
3+
var a = 10
4+
// expected-note@-1 2 {{var declared here}}
5+
// expected-note@-2 2 {{mutation of this var is only permitted within the actor}}
6+
7+
func nonIsolatedSync() { //expected-note 3 {{add '@MainActor' to make global function 'nonIsolatedSync()' part of global actor 'MainActor'}}
8+
print(a) // expected-error {{var 'a' isolated to global actor 'MainActor' can not be referenced from this synchronous context}}
9+
a = a + 10
10+
// expected-error@-1:5 {{var 'a' isolated to global actor 'MainActor' can not be mutated from this context}}
11+
// expected-error@-2:9 {{var 'a' isolated to global actor 'MainActor' can not be referenced from this synchronous context}}
12+
13+
}
14+
15+
@MainActor
16+
func isolatedSync() {
17+
print(a)
18+
a = a + 10
19+
}
20+
21+
func nonIsolatedAsync() async {
22+
await print(a)
23+
a = a + 10
24+
// expected-error@-1:5 {{var 'a' isolated to global actor 'MainActor' can not be mutated from this context}}
25+
// expected-error@-2:9 {{expression is 'async' but is not marked with 'await'}}
26+
// expected-note@-3:9 {{property access is 'async'}}
27+
}
28+
29+
@MainActor
30+
func isolatedAsync() async {
31+
print(a)
32+
a = a + 10
33+
}
34+
35+
nonIsolatedSync()
36+
isolatedSync()
37+
await nonIsolatedAsync()
38+
await isolatedAsync()
39+
40+
print(a)
41+
42+
if a > 10 {
43+
nonIsolatedSync()
44+
isolatedSync()
45+
await nonIsolatedAsync()
46+
await isolatedAsync()
47+
48+
print(a)
49+
}

0 commit comments

Comments
 (0)