Skip to content

Commit 3e43b34

Browse files
committed
[sending] closure literals that are passed as sending parameters are now inferred to be nonisolated.
Consider the following piece of code and what the isolation is of the closure literal passed to doSomething(): ```swift func doSomething(_ f: sending () -> ()) { ... } @MyCustomActor func foo() async { doSomething { // What is the isolation here? } } ``` In this case, the isolation of the closure is @MyCustomActor. This is because non-Sendable closures are by default isolated to their current context (in this case @MyCustomActor since foo is @MyCustomActor isolated). This is a problem since 1. Our closure is a synchronous function that does not have the ability to hop to MyCustomActor to run said code. This could result in a concurrency hole caused by running the closure in doSomething() without hopping to MyCustomActor's executor. 2. In Region Based Isolation, a closure that is actor isolated cannot be sent, so we would immediately hit a region isolation error. To fix this issue, by default, if a closure literal is passed as a sending parameter, we make its isolation nonisolated. This ensures that it is disconnected and can be transferred safely. In the case of an async closure literal, we follow the same semantics, but we add an additional wrinkle: we keep support of inheritActorIsolation. If one marks an async closure literal with inheritActorIsolation, we allow for it to be passed as a sendable parameter since it is actually Sendable under the hood.
1 parent 7db2c6d commit 3e43b34

File tree

7 files changed

+481
-16
lines changed

7 files changed

+481
-16
lines changed

docs/ReferenceGuides/UnderscoredAttributes.md

Lines changed: 8 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -591,11 +591,14 @@ class C {
591591

592592
(Note that it is "inherit", not "inherits", unlike below.)
593593

594-
Marks that a `@Sendable async` closure argument should inherit the actor
595-
context (i.e. what actor it should be run on) based on the declaration site
596-
of the closure. This is different from the typical behavior, where the closure
597-
may be runnable anywhere unless its type specifically declares that it will
598-
run on a specific actor.
594+
Marks that a `@Sendable async` or `sendable async` closure argument should
595+
inherit the actor context (i.e. what actor it should be run on) based on the
596+
declaration site of the closure rather than be non-Sendable. This does not do
597+
anything if the closure is synchronous.
598+
599+
DISCUSSION: The reason why this does nothing when the closure is synchronous is
600+
since it does not have the ability to hop to the appropriate executor before it
601+
is run, so we may create concurrency errors.
599602

600603
## `@_inheritsConvenienceInitializers`
601604

include/swift/AST/Expr.h

Lines changed: 16 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -266,7 +266,7 @@ class alignas(8) Expr : public ASTAllocated<Expr> {
266266
Kind : 2
267267
);
268268

269-
SWIFT_INLINE_BITFIELD(ClosureExpr, AbstractClosureExpr, 1+1+1+1,
269+
SWIFT_INLINE_BITFIELD(ClosureExpr, AbstractClosureExpr, 1+1+1+1+1,
270270
/// True if closure parameters were synthesized from anonymous closure
271271
/// variables.
272272
HasAnonymousClosureVars : 1,
@@ -281,7 +281,10 @@ class alignas(8) Expr : public ASTAllocated<Expr> {
281281

282282
/// True if this closure's actor isolation behavior was determined by an
283283
/// \c \@preconcurrency declaration.
284-
IsolatedByPreconcurrency : 1
284+
IsolatedByPreconcurrency : 1,
285+
286+
/// True if this is a closure literal that is passed as a sending parameter.
287+
IsSendingParameter : 1
285288
);
286289

287290
SWIFT_INLINE_BITFIELD_FULL(BindOptionalExpr, Expr, 16,
@@ -4139,6 +4142,7 @@ class ClosureExpr : public AbstractClosureExpr {
41394142
Bits.ClosureExpr.HasAnonymousClosureVars = false;
41404143
Bits.ClosureExpr.ImplicitSelfCapture = false;
41414144
Bits.ClosureExpr.InheritActorContext = false;
4145+
Bits.ClosureExpr.IsSendingParameter = false;
41424146
}
41434147

41444148
SourceRange getSourceRange() const;
@@ -4194,6 +4198,16 @@ class ClosureExpr : public AbstractClosureExpr {
41944198
Bits.ClosureExpr.IsolatedByPreconcurrency = value;
41954199
}
41964200

4201+
/// Whether the closure is a closure literal that is passed as a sending
4202+
/// parameter.
4203+
bool isSendingParameter() const {
4204+
return Bits.ClosureExpr.IsSendingParameter;
4205+
}
4206+
4207+
void setSendingParameter(bool value = true) {
4208+
Bits.ClosureExpr.IsSendingParameter = value;
4209+
}
4210+
41974211
/// Determine whether this closure expression has an
41984212
/// explicitly-specified result type.
41994213
bool hasExplicitResultType() const { return ArrowLoc.isValid(); }

include/swift/AST/Types.h

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3832,6 +3832,7 @@ struct ParameterListInfo {
38323832
SmallBitVector implicitSelfCapture;
38333833
SmallBitVector inheritActorContext;
38343834
SmallBitVector variadicGenerics;
3835+
SmallBitVector isSending;
38353836

38363837
public:
38373838
ParameterListInfo() { }
@@ -3863,6 +3864,9 @@ struct ParameterListInfo {
38633864

38643865
bool isVariadicGenericParameter(unsigned paramIdx) const;
38653866

3867+
/// Returns true if this is a sending parameter.
3868+
bool isSendingParameter(unsigned paramIdx) const;
3869+
38663870
/// Retrieve the number of non-defaulted parameters.
38673871
unsigned numNonDefaultedParameters() const {
38683872
return defaultArguments.count();

lib/AST/Type.cpp

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1302,6 +1302,7 @@ ParameterListInfo::ParameterListInfo(
13021302
implicitSelfCapture.resize(params.size());
13031303
inheritActorContext.resize(params.size());
13041304
variadicGenerics.resize(params.size());
1305+
isSending.resize(params.size());
13051306

13061307
// No parameter owner means no parameter list means no default arguments
13071308
// - hand back the zeroed bitvector.
@@ -1365,6 +1366,10 @@ ParameterListInfo::ParameterListInfo(
13651366
if (param->getInterfaceType()->is<PackExpansionType>()) {
13661367
variadicGenerics.set(i);
13671368
}
1369+
1370+
if (param->isSending()) {
1371+
isSending.set(i);
1372+
}
13681373
}
13691374
}
13701375

@@ -1405,6 +1410,9 @@ bool ParameterListInfo::isVariadicGenericParameter(unsigned paramIdx) const {
14051410
: false;
14061411
}
14071412

1413+
bool ParameterListInfo::isSendingParameter(unsigned paramIdx) const {
1414+
return paramIdx < isSending.size() ? isSending[paramIdx] : false;
1415+
}
14081416

14091417
/// Turn a param list into a symbolic and printable representation that does not
14101418
/// include the types, something like (_:, b:, c:)

lib/Sema/CSApply.cpp

Lines changed: 13 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -5963,23 +5963,25 @@ static bool hasCurriedSelf(ConstraintSystem &cs, ConcreteDeclRef callee,
59635963
}
59645964

59655965
/// Apply the contextually Sendable flag to the given expression,
5966-
static void applyContextualClosureFlags(
5967-
Expr *expr, bool implicitSelfCapture, bool inheritActorContext) {
5966+
static void applyContextualClosureFlags(Expr *expr, bool implicitSelfCapture,
5967+
bool inheritActorContext,
5968+
bool isSendingParameter) {
59685969
if (auto closure = dyn_cast<ClosureExpr>(expr)) {
59695970
closure->setAllowsImplicitSelfCapture(implicitSelfCapture);
59705971
closure->setInheritsActorContext(inheritActorContext);
5972+
closure->setSendingParameter(isSendingParameter);
59715973
return;
59725974
}
59735975

59745976
if (auto captureList = dyn_cast<CaptureListExpr>(expr)) {
5975-
applyContextualClosureFlags(
5976-
captureList->getClosureBody(), implicitSelfCapture,
5977-
inheritActorContext);
5977+
applyContextualClosureFlags(captureList->getClosureBody(),
5978+
implicitSelfCapture, inheritActorContext,
5979+
isSendingParameter);
59785980
}
59795981

59805982
if (auto identity = dyn_cast<IdentityExpr>(expr)) {
5981-
applyContextualClosureFlags(
5982-
identity->getSubExpr(), implicitSelfCapture, inheritActorContext);
5983+
applyContextualClosureFlags(identity->getSubExpr(), implicitSelfCapture,
5984+
inheritActorContext, isSendingParameter);
59835985
}
59845986
}
59855987

@@ -6207,8 +6209,10 @@ ArgumentList *ExprRewriter::coerceCallArguments(
62076209
// implicit self capture or inheriting actor context.
62086210
bool isImplicitSelfCapture = paramInfo.isImplicitSelfCapture(paramIdx);
62096211
bool inheritsActorContext = paramInfo.inheritsActorContext(paramIdx);
6210-
applyContextualClosureFlags(
6211-
argExpr, isImplicitSelfCapture, inheritsActorContext);
6212+
bool isSendingParameter = paramInfo.isSendingParameter(paramIdx);
6213+
6214+
applyContextualClosureFlags(argExpr, isImplicitSelfCapture,
6215+
inheritsActorContext, isSendingParameter);
62126216

62136217
// If the types exactly match, this is easy.
62146218
auto paramType = param.getOldType();

lib/Sema/TypeCheckConcurrency.cpp

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4183,6 +4183,16 @@ namespace {
41834183
return ActorIsolation::forNonisolated(/*unsafe=*/false)
41844184
.withPreconcurrency(preconcurrency);
41854185

4186+
// If the explicit closure is used as a non-Sendable sending parameter,
4187+
// make it nonisolated by default instead of inferring its isolation from
4188+
// the context.
4189+
if (auto *explicitClosure = dyn_cast<ClosureExpr>(closure)) {
4190+
if (!explicitClosure->inheritsActorContext() &&
4191+
explicitClosure->isSendingParameter())
4192+
return ActorIsolation::forNonisolated(/*unsafe=*/false)
4193+
.withPreconcurrency(preconcurrency);
4194+
}
4195+
41864196
// A non-Sendable closure gets its isolation from its context.
41874197
auto parentIsolation = getActorIsolationOfContext(
41884198
closure->getParent(), getClosureActorIsolation);

0 commit comments

Comments
 (0)