Skip to content

Commit b9f1e7f

Browse files
committed
[Concurrency] Add @_inheritActorContext hidden parameter attribute.
This new attribute can be used on parameters of `@Sendable async` type to indicate that the closures arguments passed to such parameters should inherit the actor context where they are formed, which is not the normal behavior for `@Sendable` closures. Another part of rdar://76927008.
1 parent 19a7fa6 commit b9f1e7f

File tree

11 files changed

+123
-21
lines changed

11 files changed

+123
-21
lines changed

include/swift/AST/Attr.def

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -652,6 +652,11 @@ SIMPLE_DECL_ATTR(_implicitSelfCapture, ImplicitSelfCapture,
652652
ABIStableToAdd | ABIStableToRemove | APIStableToAdd | APIBreakingToRemove,
653653
115)
654654

655+
SIMPLE_DECL_ATTR(_inheritActorContext, InheritActorContext,
656+
OnParam | UserInaccessible |
657+
ABIStableToAdd | ABIStableToRemove | APIBreakingToAdd | APIBreakingToRemove,
658+
116)
659+
655660
#undef TYPE_ATTR
656661
#undef DECL_ATTR_ALIAS
657662
#undef CONTEXTUAL_DECL_ATTR_ALIAS

include/swift/AST/Expr.h

Lines changed: 17 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -292,14 +292,18 @@ class alignas(8) Expr {
292292
Kind : 2
293293
);
294294

295-
SWIFT_INLINE_BITFIELD(ClosureExpr, AbstractClosureExpr, 1+1,
295+
SWIFT_INLINE_BITFIELD(ClosureExpr, AbstractClosureExpr, 1+1+1,
296296
/// True if closure parameters were synthesized from anonymous closure
297297
/// variables.
298298
HasAnonymousClosureVars : 1,
299299

300300
/// True if "self" can be captured implicitly without requiring "self."
301301
/// on each member reference.
302-
ImplicitSelfCapture : 1
302+
ImplicitSelfCapture : 1,
303+
304+
/// True if this @Sendable async closure parameter should implicitly
305+
/// inherit the actor context from where it was formed.
306+
InheritActorContext : 1
303307
);
304308

305309
SWIFT_INLINE_BITFIELD_FULL(BindOptionalExpr, Expr, 16,
@@ -3876,6 +3880,7 @@ class ClosureExpr : public AbstractClosureExpr {
38763880
setParameterList(params);
38773881
Bits.ClosureExpr.HasAnonymousClosureVars = false;
38783882
Bits.ClosureExpr.ImplicitSelfCapture = false;
3883+
Bits.ClosureExpr.InheritActorContext = false;
38793884
}
38803885

38813886
SourceRange getSourceRange() const;
@@ -3914,6 +3919,16 @@ class ClosureExpr : public AbstractClosureExpr {
39143919
Bits.ClosureExpr.ImplicitSelfCapture = value;
39153920
}
39163921

3922+
/// Whether this closure should implicitly inherit the actor context from
3923+
/// where it was formed. This only affects @Sendable async closures.
3924+
bool inheritsActorContext() const {
3925+
return Bits.ClosureExpr.InheritActorContext;
3926+
}
3927+
3928+
void setInheritsActorContext(bool value = true) {
3929+
Bits.ClosureExpr.InheritActorContext = value;
3930+
}
3931+
39173932
/// Determine whether this closure expression has an
39183933
/// explicitly-specified result type.
39193934
bool hasExplicitResultType() const { return ArrowLoc.isValid(); }

include/swift/AST/Types.h

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3382,6 +3382,7 @@ struct ParameterListInfo {
33823382
SmallBitVector unsafeSendable;
33833383
SmallBitVector unsafeMainActor;
33843384
SmallBitVector implicitSelfCapture;
3385+
SmallBitVector inheritActorContext;
33853386

33863387
public:
33873388
ParameterListInfo() { }
@@ -3414,6 +3415,10 @@ struct ParameterListInfo {
34143415
/// 'self' to be implicit, without requiring "self.".
34153416
bool isImplicitSelfCapture(unsigned paramIdx) const;
34163417

3418+
/// Whether the given parameter is a closure that should inherit the
3419+
/// actor context from the context in which it was created.
3420+
bool inheritsActorContext(unsigned paramIdx) const;
3421+
34173422
/// Whether there is any contextual information set on this parameter list.
34183423
bool anyContextualInfo() const;
34193424

lib/AST/ASTDumper.cpp

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2548,6 +2548,8 @@ class PrintExpr : public ExprVisitor<PrintExpr> {
25482548
PrintWithColorRAII(OS, ClosureModifierColor) << " single-expression";
25492549
if (E->allowsImplicitSelfCapture())
25502550
PrintWithColorRAII(OS, ClosureModifierColor) << " implicit-self";
2551+
if (E->inheritsActorContext())
2552+
PrintWithColorRAII(OS, ClosureModifierColor) << " inherits-actor-context";
25512553

25522554
if (E->getParameters()) {
25532555
OS << '\n';

lib/AST/Type.cpp

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -962,6 +962,7 @@ ParameterListInfo::ParameterListInfo(
962962
unsafeSendable.resize(params.size());
963963
unsafeMainActor.resize(params.size());
964964
implicitSelfCapture.resize(params.size());
965+
inheritActorContext.resize(params.size());
965966

966967
// No parameter owner means no parameter list means no default arguments
967968
// - hand back the zeroed bitvector.
@@ -1025,6 +1026,10 @@ ParameterListInfo::ParameterListInfo(
10251026
if (param->getAttrs().hasAttribute<ImplicitSelfCaptureAttr>()) {
10261027
implicitSelfCapture.set(i);
10271028
}
1029+
1030+
if (param->getAttrs().hasAttribute<InheritActorContextAttr>()) {
1031+
inheritActorContext.set(i);
1032+
}
10281033
}
10291034
}
10301035

@@ -1061,9 +1066,15 @@ bool ParameterListInfo::isImplicitSelfCapture(unsigned paramIdx) const {
10611066
: false;
10621067
}
10631068

1069+
bool ParameterListInfo::inheritsActorContext(unsigned paramIdx) const {
1070+
return paramIdx < inheritActorContext.size()
1071+
? inheritActorContext[paramIdx]
1072+
: false;
1073+
}
1074+
10641075
bool ParameterListInfo::anyContextualInfo() const {
10651076
return unsafeSendable.any() || unsafeMainActor.any() ||
1066-
implicitSelfCapture.any();
1077+
implicitSelfCapture.any() || inheritActorContext.any();
10671078
}
10681079

10691080
/// Turn a param list into a symbolic and printable representation that does not

lib/Sema/CSApply.cpp

Lines changed: 8 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -5710,17 +5710,19 @@ static bool hasCurriedSelf(ConstraintSystem &cs, ConcreteDeclRef callee,
57105710

57115711
/// Apply the contextually Sendable flag to the given expression,
57125712
static void applyContextualClosureFlags(
5713-
Expr *expr, bool sendable, bool forMainActor, bool implicitSelfCapture) {
5713+
Expr *expr, bool sendable, bool forMainActor, bool implicitSelfCapture,
5714+
bool inheritActorContext) {
57145715
if (auto closure = dyn_cast<ClosureExpr>(expr)) {
57155716
closure->setUnsafeConcurrent(sendable, forMainActor);
57165717
closure->setAllowsImplicitSelfCapture(implicitSelfCapture);
5718+
closure->setInheritsActorContext(inheritActorContext);
57175719
return;
57185720
}
57195721

57205722
if (auto captureList = dyn_cast<CaptureListExpr>(expr)) {
57215723
applyContextualClosureFlags(
57225724
captureList->getClosureBody(), sendable, forMainActor,
5723-
implicitSelfCapture);
5725+
implicitSelfCapture, inheritActorContext);
57245726
}
57255727
}
57265728

@@ -5958,9 +5960,10 @@ Expr *ExprRewriter::coerceCallArguments(
59585960
bool isMainActor = paramInfo.isUnsafeMainActor(paramIdx) ||
59595961
(isUnsafeSendable && apply && isMainDispatchQueue(apply->getFn()));
59605962
bool isImplicitSelfCapture = paramInfo.isImplicitSelfCapture(paramIdx);
5963+
bool inheritsActorContext = paramInfo.inheritsActorContext(paramIdx);
59615964
applyContextualClosureFlags(
59625965
arg, isUnsafeSendable && contextUsesConcurrencyFeatures(dc),
5963-
isMainActor, isImplicitSelfCapture);
5966+
isMainActor, isImplicitSelfCapture, inheritsActorContext);
59645967

59655968
// If the types exactly match, this is easy.
59665969
auto paramType = param.getOldType();
@@ -6976,8 +6979,8 @@ Expr *ExprRewriter::coerceToType(Expr *expr, Type toType,
69766979
}
69776980
}
69786981

6979-
// If we have a ClosureExpr, then we can safely propagate the 'concurrent'
6980-
// bit to the closure without invalidating prior analysis.
6982+
// If we have a ClosureExpr, then we can safely propagate @Sendable
6983+
// to the closure without invalidating prior analysis.
69816984
auto fromEI = fromFunc->getExtInfo();
69826985
if (toEI.isSendable() && !fromEI.isSendable()) {
69836986
auto newFromFuncType = fromFunc->withExtInfo(fromEI.withConcurrent());

lib/Sema/TypeCheckAttr.cpp

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -138,6 +138,7 @@ class AttributeChecker : public AttributeVisitor<AttributeChecker> {
138138
IGNORED_ATTR(UnsafeSendable)
139139
IGNORED_ATTR(UnsafeMainActor)
140140
IGNORED_ATTR(ImplicitSelfCapture)
141+
IGNORED_ATTR(InheritActorContext)
141142
#undef IGNORED_ATTR
142143

143144
void visitAlignmentAttr(AlignmentAttr *attr) {

lib/Sema/TypeCheckConcurrency.cpp

Lines changed: 19 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -773,19 +773,26 @@ static bool isAsyncCall(const ApplyExpr *call) {
773773
/// features.
774774
static bool shouldDiagnoseExistingDataRaces(const DeclContext *dc);
775775

776-
/// Determine whether this closure is escaping.
777-
static bool isSendableClosure(const AbstractClosureExpr *closure) {
776+
/// Determine whether this closure should be treated as Sendable.
777+
///
778+
/// \param forActorIsolation Whether this check is for the purposes of
779+
/// determining whether the closure must be non-isolated.
780+
static bool isSendableClosure(
781+
const AbstractClosureExpr *closure, bool forActorIsolation) {
782+
if (auto explicitClosure = dyn_cast<ClosureExpr>(closure)) {
783+
if (forActorIsolation && explicitClosure->inheritsActorContext())
784+
return false;
785+
786+
if (explicitClosure->isUnsafeSendable())
787+
return true;
788+
}
789+
778790
if (auto type = closure->getType()) {
779791
if (auto fnType = type->getAs<AnyFunctionType>())
780792
if (fnType->isSendable())
781793
return true;
782794
}
783795

784-
if (auto explicitClosure = dyn_cast<ClosureExpr>(closure)) {
785-
if (explicitClosure->isUnsafeSendable())
786-
return true;
787-
}
788-
789796
return false;
790797
}
791798

@@ -2113,7 +2120,7 @@ namespace {
21132120
}
21142121

21152122
if (auto closure = dyn_cast<AbstractClosureExpr>(dc)) {
2116-
if (isSendableClosure(closure)) {
2123+
if (isSendableClosure(closure, /*forActorIsolation=*/true)) {
21172124
return diag::actor_isolated_from_concurrent_closure;
21182125
}
21192126

@@ -2310,8 +2317,9 @@ namespace {
23102317
}
23112318
}
23122319

2313-
// Sendable closures are always actor-independent.
2314-
if (isSendableClosure(closure))
2320+
// Sendable closures are actor-independent unless the closure has
2321+
// specifically opted into inheriting actor isolation.
2322+
if (isSendableClosure(closure, /*forActorIsolation=*/true))
23152323
return ClosureActorIsolation::forIndependent();
23162324

23172325
// A non-escaping closure gets its isolation from its context.
@@ -2362,7 +2370,7 @@ bool ActorIsolationChecker::mayExecuteConcurrentlyWith(
23622370
while (useContext != defContext) {
23632371
// If we find a concurrent closure... it can be run concurrently.
23642372
if (auto closure = dyn_cast<AbstractClosureExpr>(useContext)) {
2365-
if (isSendableClosure(closure))
2373+
if (isSendableClosure(closure, /*forActorIsolation=*/false))
23662374
return true;
23672375
}
23682376

lib/Sema/TypeCheckDeclOverride.cpp

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1547,6 +1547,7 @@ namespace {
15471547
UNINTERESTING_ATTR(UnsafeSendable)
15481548
UNINTERESTING_ATTR(UnsafeMainActor)
15491549
UNINTERESTING_ATTR(ImplicitSelfCapture)
1550+
UNINTERESTING_ATTR(InheritActorContext)
15501551
#undef UNINTERESTING_ATTR
15511552

15521553
void visitAvailableAttr(AvailableAttr *attr) {

test/Concurrency/actor_isolation.swift

Lines changed: 28 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -70,7 +70,7 @@ actor MyActor: MySuperActor {
7070
class func synchronousClass() { }
7171
static func synchronousStatic() { }
7272

73-
func synchronous() -> String { text.first ?? "nothing" } // expected-note 19{{calls to instance method 'synchronous()' from outside of its actor context are implicitly asynchronous}}
73+
func synchronous() -> String { text.first ?? "nothing" } // expected-note 20{{calls to instance method 'synchronous()' from outside of its actor context are implicitly asynchronous}}
7474
func asynchronous() async -> String {
7575
super.superState += 4
7676
return synchronous()
@@ -754,3 +754,30 @@ func testCrossActorProtocol<T: P>(t: T) async {
754754
t.f() // expected-error{{call is 'async' but is not marked with 'await'}}
755755
t.g() // expected-error{{call is 'async' but is not marked with 'await'}}
756756
}
757+
758+
// ----------------------------------------------------------------------
759+
// @_
760+
// ----------------------------------------------------------------------
761+
func acceptAsyncSendableClosure<T>(_: @Sendable () async -> T) { }
762+
func acceptAsyncSendableClosureInheriting<T>(@_inheritActorContext _: @Sendable () async -> T) { }
763+
764+
@available(macOS 9999, iOS 9999, watchOS 9999, tvOS 9999, *)
765+
extension MyActor {
766+
func testSendableAndInheriting() {
767+
acceptAsyncSendableClosure {
768+
synchronous() // expected-error{{actor-isolated instance method 'synchronous()' cannot be referenced from a concurrent closure}}
769+
}
770+
771+
acceptAsyncSendableClosure {
772+
await synchronous() // ok
773+
}
774+
775+
acceptAsyncSendableClosureInheriting {
776+
synchronous() // okay
777+
}
778+
779+
acceptAsyncSendableClosureInheriting {
780+
await synchronous() // expected-warning{{no 'async' operations occur within 'await' expression}}
781+
}
782+
}
783+
}

test/Concurrency/global_actor_inference.swift

Lines changed: 25 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -506,10 +506,34 @@ func acceptClosure<T>(_: () -> T) { }
506506
// ----------------------------------------------------------------------
507507
func takesUnsafeMainActor(@_unsafeMainActor fn: () -> Void) { }
508508

509-
@MainActor func onlyOnMainActor() { }
509+
@MainActor func onlyOnMainActor() { } // expected-note{{calls to global function 'onlyOnMainActor()' from outside of its actor context are implicitly asynchronous}}
510510

511511
func useUnsafeMainActor() {
512512
takesUnsafeMainActor {
513513
onlyOnMainActor() // okay due to parameter attribute
514514
}
515515
}
516+
517+
// ----------------------------------------------------------------------
518+
// @_inheritActorContext
519+
// ----------------------------------------------------------------------
520+
func acceptAsyncSendableClosure<T>(_: @Sendable () async -> T) { }
521+
func acceptAsyncSendableClosureInheriting<T>(@_inheritActorContext _: @Sendable () async -> T) { }
522+
523+
@MainActor func testCallFromMainActor() {
524+
acceptAsyncSendableClosure {
525+
onlyOnMainActor() // expected-error{{call to main actor-isolated global function 'onlyOnMainActor()' in a synchronous nonisolated context}}
526+
}
527+
528+
acceptAsyncSendableClosure {
529+
await onlyOnMainActor() // okay
530+
}
531+
532+
acceptAsyncSendableClosureInheriting {
533+
onlyOnMainActor() // okay
534+
}
535+
536+
acceptAsyncSendableClosureInheriting {
537+
await onlyOnMainActor() // expected-warning{{no 'async' operations occur within 'await' expression}}
538+
}
539+
}

0 commit comments

Comments
 (0)