Skip to content

Commit 5bb290c

Browse files
committed
[Concurrency] Allow async closures to be annotated with global actors.
Allow an asynchronous closure to be annotated with a global actor, which isolates it to that actor. For example: ```swift Task.runDetached { @mainactor in // runs on main actor } ```
1 parent 7c336ea commit 5bb290c

File tree

6 files changed

+116
-13
lines changed

6 files changed

+116
-13
lines changed

include/swift/AST/DiagnosticsSema.def

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4387,6 +4387,9 @@ ERROR(global_actor_on_struct_property,none,
43874387
ERROR(actor_isolation_multiple_attr,none,
43884388
"%0 %1 has multiple actor-isolation attributes ('%2' and '%3')",
43894389
(DescriptiveDeclKind, DeclName, StringRef, StringRef))
4390+
ERROR(global_actor_isolated_synchronous_closure,none,
4391+
"closure isolated to global actor %0 must be 'async'",
4392+
(Type))
43904393

43914394
ERROR(actor_isolation_override_mismatch,none,
43924395
"%0 %1 %2 has different actor isolation from %3 overridden declaration",

include/swift/AST/TypeCheckRequests.h

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -962,7 +962,8 @@ using CustomAttrNominalPair = std::pair<CustomAttr *, NominalTypeDecl *>;
962962
class GlobalActorAttributeRequest :
963963
public SimpleRequest<
964964
GlobalActorAttributeRequest,
965-
Optional<CustomAttrNominalPair>(Decl *),
965+
Optional<CustomAttrNominalPair>(
966+
llvm::PointerUnion<Decl *, ClosureExpr *>),
966967
RequestFlags::Cached> {
967968
public:
968969
using SimpleRequest::SimpleRequest;
@@ -972,7 +973,8 @@ class GlobalActorAttributeRequest :
972973

973974
// Evaluation.
974975
Optional<std::pair<CustomAttr *, NominalTypeDecl *>>
975-
evaluate(Evaluator &evaluator, Decl *decl) const;
976+
evaluate(
977+
Evaluator &evaluator, llvm::PointerUnion<Decl *, ClosureExpr *>) const;
976978

977979
public:
978980
// Caching

include/swift/AST/TypeCheckerTypeIDZone.def

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -102,7 +102,8 @@ SWIFT_REQUEST(TypeChecker, GlobalActorInstanceRequest,
102102
VarDecl *(NominalTypeDecl *),
103103
Cached, NoLocationInfo)
104104
SWIFT_REQUEST(TypeChecker, GlobalActorAttributeRequest,
105-
Optional<CustomAttrNominalPair>(Decl *),
105+
Optional<CustomAttrNominalPair>(
106+
llvm::PointerUnion<Decl *, ClosureExpr *>),
106107
Cached, NoLocationInfo)
107108
SWIFT_REQUEST(TypeChecker, ActorIsolationRequest,
108109
ActorIsolationState(ValueDecl *),

lib/Sema/TypeCheckAttr.cpp

Lines changed: 25 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5482,9 +5482,10 @@ namespace {
54825482
class ClosureAttributeChecker
54835483
: public AttributeVisitor<ClosureAttributeChecker> {
54845484
ASTContext &ctx;
5485+
ClosureExpr *closure;
54855486
public:
54865487
ClosureAttributeChecker(ClosureExpr *closure)
5487-
: ctx(closure->getASTContext()) { }
5488+
: ctx(closure->getASTContext()), closure(closure) { }
54885489

54895490
void visitDeclAttribute(DeclAttribute *attr) {
54905491
ctx.Diags.diagnose(
@@ -5497,6 +5498,29 @@ class ClosureAttributeChecker
54975498
void visitConcurrentAttr(ConcurrentAttr *attr) {
54985499
// Nothing else to check.
54995500
}
5501+
5502+
void visitCustomAttr(CustomAttr *attr) {
5503+
// Check whether this custom attribute is the global actor attribute.
5504+
auto globalActorAttr = evaluateOrDefault(
5505+
ctx.evaluator, GlobalActorAttributeRequest{closure}, None);
5506+
if (globalActorAttr && globalActorAttr->first == attr)
5507+
return;
5508+
5509+
// Otherwise, it's an error.
5510+
std::string typeName;
5511+
if (auto typeRepr = attr->getTypeRepr()) {
5512+
llvm::raw_string_ostream out(typeName);
5513+
typeRepr->print(out);
5514+
} else {
5515+
typeName = attr->getType().getString();
5516+
}
5517+
5518+
ctx.Diags.diagnose(
5519+
attr->getLocation(), diag::unsupported_closure_attr,
5520+
attr->isDeclModifier(), typeName)
5521+
.fixItRemove(attr->getRangeWithAt());
5522+
attr->setInvalid();
5523+
}
55005524
};
55015525

55025526
}

lib/Sema/TypeCheckConcurrency.cpp

Lines changed: 70 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -403,13 +403,26 @@ VarDecl *GlobalActorInstanceRequest::evaluate(
403403

404404
Optional<std::pair<CustomAttr *, NominalTypeDecl *>>
405405
GlobalActorAttributeRequest::evaluate(
406-
Evaluator &evaluator, Decl *decl) const {
407-
ASTContext &ctx = decl->getASTContext();
408-
auto dc = decl->getDeclContext();
406+
Evaluator &evaluator,
407+
llvm::PointerUnion<Decl *, ClosureExpr *> subject) const {
408+
DeclContext *dc;
409+
DeclAttributes *declAttrs;
410+
SourceLoc loc;
411+
if (auto decl = subject.dyn_cast<Decl *>()) {
412+
dc = decl->getDeclContext();
413+
declAttrs = &decl->getAttrs();
414+
loc = decl->getLoc();
415+
} else {
416+
auto closure = subject.get<ClosureExpr *>();
417+
dc = closure;
418+
declAttrs = &closure->getAttrs();
419+
loc = closure->getLoc();
420+
}
421+
ASTContext &ctx = dc->getASTContext();
409422
CustomAttr *globalActorAttr = nullptr;
410423
NominalTypeDecl *globalActorNominal = nullptr;
411424

412-
for (auto attr : decl->getAttrs().getAttributes<CustomAttr>()) {
425+
for (auto attr : declAttrs->getAttributes<CustomAttr>()) {
413426
auto mutableAttr = const_cast<CustomAttr *>(attr);
414427
// Figure out which nominal declaration this custom attribute refers to.
415428
auto nominal = evaluateOrDefault(ctx.evaluator,
@@ -424,10 +437,10 @@ GlobalActorAttributeRequest::evaluate(
424437
if (!nominal->isGlobalActor())
425438
continue;
426439

427-
// Only a single global actor can be applied to a given declaration.
440+
// Only a single global actor can be applied to a given entity.
428441
if (globalActorAttr) {
429-
decl->diagnose(
430-
diag::multiple_global_actors, globalActorNominal->getName(),
442+
ctx.Diags.diagnose(
443+
loc, diag::multiple_global_actors, globalActorNominal->getName(),
431444
nominal->getName());
432445
continue;
433446
}
@@ -439,8 +452,14 @@ GlobalActorAttributeRequest::evaluate(
439452
if (!globalActorAttr)
440453
return None;
441454

455+
// Closures can always have a global actor attached.
456+
if (auto closure = subject.dyn_cast<ClosureExpr *>()) {
457+
return std::make_pair(globalActorAttr, globalActorNominal);
458+
}
459+
442460
// Check that a global actor attribute makes sense on this kind of
443461
// declaration.
462+
auto decl = subject.get<Decl *>();
444463
if (auto nominal = dyn_cast<NominalTypeDecl>(decl)) {
445464
// Nominal types are okay...
446465
if (auto classDecl = dyn_cast<ClassDecl>(nominal)){
@@ -1384,8 +1403,13 @@ namespace {
13841403

13851404
auto dc = const_cast<DeclContext *>(constDC);
13861405
while (!dc->isModuleScopeContext()) {
1387-
// Look through non-escaping closures.
13881406
if (auto closure = dyn_cast<AbstractClosureExpr>(dc)) {
1407+
// If this closure has specific isolation, use it.
1408+
auto closureIsolation = getActorIsolationOfContext(dc);
1409+
if (closureIsolation != ActorIsolation::Independent)
1410+
return closureIsolation;
1411+
1412+
// Look through non-escaping closures.
13891413
if (auto type = closure->getType()) {
13901414
if (auto fnType = type->getAs<AnyFunctionType>()) {
13911415
if (fnType->isNoEscape()) {
@@ -1816,12 +1840,50 @@ namespace {
18161840
llvm_unreachable("unhandled actor isolation kind!");
18171841
}
18181842

1843+
// Attempt to resolve the global actor type of a closure.
1844+
Type resolveGlobalActorType(ClosureExpr *closure) {
1845+
auto globalActorAttr = evaluateOrDefault(
1846+
ctx.evaluator, GlobalActorAttributeRequest{closure}, None);
1847+
if (!globalActorAttr)
1848+
return Type();
1849+
1850+
Type globalActor = evaluateOrDefault(
1851+
ctx.evaluator,
1852+
CustomAttrTypeRequest{
1853+
globalActorAttr->first, closure, CustomAttrTypeKind::GlobalActor},
1854+
Type());
1855+
if (!globalActor || globalActor->hasError())
1856+
return Type();
1857+
1858+
// Actor-isolated closures must be async.
1859+
bool isAsync = false;
1860+
if (Type closureType = closure->getType()) {
1861+
if (auto closureFnType = closureType->getAs<FunctionType>())
1862+
isAsync = closureFnType->isAsync();
1863+
}
1864+
1865+
if (!isAsync) {
1866+
ctx.Diags.diagnose(
1867+
closure->getLoc(), diag::global_actor_isolated_synchronous_closure,
1868+
globalActor);
1869+
return Type();
1870+
}
1871+
1872+
return globalActor;
1873+
}
1874+
18191875
/// Determine the isolation of a particular closure.
18201876
///
18211877
/// This function assumes that enclosing closures have already had their
18221878
/// isolation checked.
18231879
ClosureActorIsolation determineClosureIsolation(
18241880
AbstractClosureExpr *closure) {
1881+
// If the closure specifies a global actor, use it.
1882+
if (auto explicitClosure = dyn_cast<ClosureExpr>(closure)) {
1883+
if (Type globalActorType = resolveGlobalActorType(explicitClosure))
1884+
return ClosureActorIsolation::forGlobalActor(globalActorType);
1885+
}
1886+
18251887
// Escaping and concurrent closures are always actor-independent.
18261888
if (isEscapingClosure(closure) || isConcurrentClosure(closure))
18271889
return ClosureActorIsolation::forIndependent();

test/Concurrency/actor_isolation.swift

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@ func acceptConcurrentClosure<T>(_: @concurrent () -> T) { }
1010
func acceptEscapingClosure<T>(_: @escaping () -> T) { }
1111
func acceptEscapingClosure<T>(_: @escaping (String) -> ()) async -> T? { nil }
1212

13-
func acceptAsyncClosure<T>(_: () async -> T) { }
13+
@discardableResult func acceptAsyncClosure<T>(_: () async -> T) -> T { }
1414
func acceptEscapingAsyncClosure<T>(_: @escaping () async -> T) { }
1515

1616

@@ -233,6 +233,17 @@ struct GenericGlobalActor<T> {
233233
}
234234
@asyncHandler @SomeOtherGlobalActor func goo2() { await goo1() }
235235

236+
func testGlobalActorClosures() {
237+
let _: Int = acceptAsyncClosure { @SomeGlobalActor in
238+
syncGlobalActorFunc()
239+
syncOtherGlobalActorFunc() // expected-error{{call is 'async' but is not marked with 'await'}}
240+
await syncOtherGlobalActorFunc()
241+
return 17
242+
}
243+
244+
acceptConcurrentClosure { @SomeGlobalActor in 5 } // expected-error{{closure isolated to global actor 'SomeGlobalActor' must be 'async'}}
245+
}
246+
236247
extension MyActor {
237248
@SomeGlobalActor func onGlobalActor(otherActor: MyActor) async {
238249
// Access to other functions in this actor are okay.

0 commit comments

Comments
 (0)