Skip to content

Commit 04d4676

Browse files
committed
[AST] Extend @_inheritActorContext attribute to support optional always modifier
By default (currently) the closure passed to a parameter with `@_inheritActorContext` would only inherit isolation from `nonisolated`, global actor isolated or actor context when "self" is captured by the closure. `always` changes this behavior to always inherit actor isolation from context regardless of whether it's captured or not.
1 parent 36de3a8 commit 04d4676

17 files changed

+249
-10
lines changed

docs/ReferenceGuides/UnderscoredAttributes.md

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -600,6 +600,43 @@ inherit the actor context (i.e. what actor it should be run on) based on the
600600
declaration site of the closure rather than be non-Sendable. This does not do
601601
anything if the closure is synchronous.
602602

603+
This works with global actors as expected:
604+
605+
```swift
606+
@MainActor
607+
func test() {
608+
Task { /* main actor isolated */ }
609+
}
610+
```
611+
612+
However, for the inference to work with instance actors (i.e. `isolated` parameters),
613+
the closure must capture the isolated parameter explicitly:
614+
615+
```swift
616+
func test(actor: isolated (any Actor)) {
617+
Task { /* non isolated */ } // !!!
618+
}
619+
620+
func test(actor: isolated (any Actor)) {
621+
Task { // @_inheritActorContext
622+
_ = actor // 'actor'-isolated
623+
}
624+
}
625+
```
626+
627+
The attribute takes an optional modifier '`always`', which changes this behavior
628+
and *always* captures the enclosing isolated context, rather than forcing developers
629+
to perform the explicit capture themselfes:
630+
631+
```swift
632+
func test(actor: isolated (any Actor)) {
633+
Task.immediate { // @_inheritActorContext(always)
634+
// 'actor'-isolated!
635+
// (without having to capture 'actor explicitly')
636+
}
637+
}
638+
```
639+
603640
DISCUSSION: The reason why this does nothing when the closure is synchronous is
604641
since it does not have the ability to hop to the appropriate executor before it
605642
is run, so we may create concurrency errors.

include/swift/AST/Attr.h

Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -230,6 +230,10 @@ class DeclAttribute : public AttributeBase {
230230
Modifier : NumNonIsolatedModifierBits
231231
);
232232

233+
SWIFT_INLINE_BITFIELD(InheritActorContextAttr, DeclAttribute, NumInheritActorContextKindBits,
234+
Modifier : NumInheritActorContextKindBits
235+
);
236+
233237
SWIFT_INLINE_BITFIELD_FULL(AllowFeatureSuppressionAttr, DeclAttribute, 1+31,
234238
: NumPadBits,
235239
Inverted : 1,
@@ -3011,6 +3015,50 @@ class NonisolatedAttr final : public DeclAttribute {
30113015
}
30123016
};
30133017

3018+
/// Represents @_inheritActorContext modifier.
3019+
class InheritActorContextAttr final : public DeclAttribute {
3020+
public:
3021+
InheritActorContextAttr(SourceLoc atLoc, SourceRange range,
3022+
InheritActorContextModifier modifier, bool implicit)
3023+
: DeclAttribute(DeclAttrKind::InheritActorContext, atLoc, range,
3024+
implicit) {
3025+
Bits.InheritActorContextAttr.Modifier = static_cast<unsigned>(modifier);
3026+
assert((getModifier() == modifier) && "not enough bits for modifier");
3027+
}
3028+
3029+
InheritActorContextModifier getModifier() const {
3030+
return static_cast<InheritActorContextModifier>(
3031+
Bits.InheritActorContextAttr.Modifier);
3032+
}
3033+
3034+
bool isAlways() const {
3035+
return getModifier() == InheritActorContextModifier::Always;
3036+
}
3037+
3038+
static InheritActorContextAttr *
3039+
createImplicit(ASTContext &ctx, InheritActorContextModifier modifier =
3040+
InheritActorContextModifier::None) {
3041+
return new (ctx)
3042+
InheritActorContextAttr(/*atLoc*/ {}, /*range*/ {}, modifier,
3043+
/*implicit=*/true);
3044+
}
3045+
3046+
static bool classof(const DeclAttribute *DA) {
3047+
return DA->getKind() == DeclAttrKind::InheritActorContext;
3048+
}
3049+
3050+
/// Create a copy of this attribute.
3051+
InheritActorContextAttr *clone(ASTContext &ctx) const {
3052+
return new (ctx)
3053+
InheritActorContextAttr(AtLoc, Range, getModifier(), isImplicit());
3054+
}
3055+
3056+
bool isEquivalent(const InheritActorContextAttr *other,
3057+
Decl *attachedTo) const {
3058+
return getModifier() == other->getModifier();
3059+
}
3060+
};
3061+
30143062
/// A macro role attribute, spelled with either @attached or @freestanding,
30153063
/// which declares one of the roles that a given macro can inhabit.
30163064
class MacroRoleAttr final

include/swift/AST/AttrKind.h

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -142,6 +142,22 @@ enum : unsigned {
142142
static_cast<unsigned>(NonIsolatedModifier::Last_NonIsolatedModifier))
143143
};
144144

145+
enum class InheritActorContextModifier : uint8_t {
146+
/// Inherit the actor execution context if the isolated parameter was
147+
/// captured by the closure, context is nonisolated or isolated to a
148+
/// global actor.
149+
None = 0,
150+
/// Always inherit the actor context, even when the isolated parameter
151+
/// for the context is not closed over explicitly.
152+
Always,
153+
Last_InheritActorContextKind = Always
154+
};
155+
156+
enum : unsigned {
157+
NumInheritActorContextKindBits = countBitsUsed(static_cast<unsigned>(
158+
InheritActorContextModifier::Last_InheritActorContextKind))
159+
};
160+
145161
enum class DeclAttrKind : unsigned {
146162
#define DECL_ATTR(_, CLASS, ...) CLASS,
147163
#define LAST_DECL_ATTR(CLASS) Last_DeclAttr = CLASS,

include/swift/AST/DeclAttr.def

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -620,9 +620,10 @@ SIMPLE_DECL_ATTR(_implicitSelfCapture, ImplicitSelfCapture,
620620
UserInaccessible | ABIStableToAdd | ABIStableToRemove | APIStableToAdd | APIBreakingToRemove | ForbiddenInABIAttr,
621621
115)
622622

623-
SIMPLE_DECL_ATTR(_inheritActorContext, InheritActorContext,
623+
DECL_ATTR(_inheritActorContext, InheritActorContext,
624624
OnParam,
625-
UserInaccessible | ABIStableToAdd | ABIStableToRemove | APIBreakingToAdd | APIBreakingToRemove | ForbiddenInABIAttr,
625+
// since the _inheritActorContext(always) forces an actor capture, it changes ABI of the closure this applies to
626+
UserInaccessible | ABIBreakingToAdd | ABIBreakingToRemove | APIBreakingToAdd | APIBreakingToRemove | UnconstrainedInABIAttr,
626627
116)
627628

628629
SIMPLE_DECL_ATTR(_eagerMove, EagerMove,

include/swift/AST/DiagnosticsSema.def

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8595,6 +8595,13 @@ GROUPED_ERROR(isolated_conformance_wrong_domain,IsolatedConformances,none,
85958595
"%0 conformance of %1 to %2 cannot be used in %3 context",
85968596
(ActorIsolation, Type, DeclName, ActorIsolation))
85978597
8598+
//===----------------------------------------------------------------------===//
8599+
// MARK: @_inheritActorContext
8600+
//===----------------------------------------------------------------------===//
8601+
ERROR(inherit_actor_context_only_on_func_types,none,
8602+
"%0 only applies to parameters with function types (got: %1)",
8603+
(DeclAttribute, Type))
8604+
85988605
//===----------------------------------------------------------------------===//
85998606
// MARK: @concurrent and nonisolated(nonsending) attributes
86008607
//===----------------------------------------------------------------------===//

include/swift/AST/KnownIdentifiers.def

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -325,6 +325,7 @@ IDENTIFIER(SerializationRequirement)
325325
IDENTIFIER_WITH_NAME(builderSelf, "$builderSelf")
326326

327327
// Attribute options
328+
IDENTIFIER(always)
328329
IDENTIFIER_(_always)
329330
IDENTIFIER_(assumed)
330331
IDENTIFIER(checked)

include/swift/Parse/IDEInspectionCallbacks.h

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -39,7 +39,8 @@ enum class ParameterizedDeclAttributeKind {
3939
Available,
4040
FreestandingMacro,
4141
AttachedMacro,
42-
StorageRestrictions
42+
StorageRestrictions,
43+
InheritActorContext
4344
};
4445

4546
/// A bit of a hack. When completing inside the '@storageRestrictions'

lib/AST/ASTDumper.cpp

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4976,7 +4976,6 @@ class PrintAttribute : public AttributeVisitor<PrintAttribute, void, Label>,
49764976
TRIVIAL_ATTR_PRINTER(ImplicitSelfCapture, implicit_self_capture)
49774977
TRIVIAL_ATTR_PRINTER(Indirect, indirect)
49784978
TRIVIAL_ATTR_PRINTER(Infix, infix)
4979-
TRIVIAL_ATTR_PRINTER(InheritActorContext, inherit_actor_context)
49804979
TRIVIAL_ATTR_PRINTER(InheritsConvenienceInitializers,
49814980
inherits_convenience_initializers)
49824981
TRIVIAL_ATTR_PRINTER(Inlinable, inlinable)
@@ -5301,6 +5300,12 @@ class PrintAttribute : public AttributeVisitor<PrintAttribute, void, Label>,
53015300
printFlag(Attr->isNonSending(), "nonsending");
53025301
printFoot();
53035302
}
5303+
void visitInheritActorContextAttr(InheritActorContextAttr *Attr,
5304+
Label label) {
5305+
printCommon(Attr, "inherit_actor_context_attr", label);
5306+
printFlag(Attr->isAlways(), "always");
5307+
printFoot();
5308+
}
53045309
void visitObjCAttr(ObjCAttr *Attr, Label label) {
53055310
printCommon(Attr, "objc_attr", label);
53065311
if (Attr->hasName())

lib/AST/Attr.cpp

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1531,6 +1531,18 @@ bool DeclAttribute::printImpl(ASTPrinter &Printer, const PrintOptions &Options,
15311531
break;
15321532
}
15331533

1534+
case DeclAttrKind::InheritActorContext: {
1535+
Printer.printAttrName("@_inheritActorContext");
1536+
switch (cast<InheritActorContextAttr>(this)->getModifier()) {
1537+
case InheritActorContextModifier::None:
1538+
break;
1539+
case InheritActorContextModifier::Always:
1540+
Printer << "(always)";
1541+
break;
1542+
}
1543+
break;
1544+
}
1545+
15341546
case DeclAttrKind::MacroRole: {
15351547
auto Attr = cast<MacroRoleAttr>(this);
15361548

@@ -1915,6 +1927,13 @@ StringRef DeclAttribute::getAttrName() const {
19151927
case NonIsolatedModifier::NonSending:
19161928
return "nonisolated(nonsending)";
19171929
}
1930+
case DeclAttrKind::InheritActorContext:
1931+
switch (cast<InheritActorContextAttr>(this)->getModifier()) {
1932+
case InheritActorContextModifier::None:
1933+
return "_inheritActorContext";
1934+
case InheritActorContextModifier::Always:
1935+
return "_inheritActorContext(always)";
1936+
}
19181937
case DeclAttrKind::MacroRole:
19191938
switch (cast<MacroRoleAttr>(this)->getMacroSyntax()) {
19201939
case MacroSyntax::Freestanding:

lib/IDE/CompletionLookup.cpp

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3141,6 +3141,9 @@ void CompletionLookup::getAttributeDeclParamCompletions(
31413141
addDeclAttrParamKeyword("unsafe", /*Parameters=*/{}, "", false);
31423142
addDeclAttrParamKeyword("nonsending", /*Parameters=*/{}, "", false);
31433143
break;
3144+
case ParameterizedDeclAttributeKind::InheritActorContext:
3145+
addDeclAttrParamKeyword("always", /*Parameters=*/{}, "", false);
3146+
break;
31443147
case ParameterizedDeclAttributeKind::AccessControl:
31453148
addDeclAttrParamKeyword("set", /*Parameters=*/{}, "", false);
31463149
break;

lib/Parse/ParseDecl.cpp

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3773,6 +3773,25 @@ ParserStatus Parser::parseNewDeclAttribute(DeclAttributes &Attributes,
37733773
}
37743774
break;
37753775
}
3776+
case DeclAttrKind::InheritActorContext: {
3777+
AttrRange = Loc;
3778+
std::optional<InheritActorContextModifier> Modifier(
3779+
InheritActorContextModifier::None);
3780+
Modifier = parseSingleAttrOption<InheritActorContextModifier>(
3781+
*this, Loc, AttrRange, AttrName, DK,
3782+
{{Context.Id_always, InheritActorContextModifier::Always}}, *Modifier,
3783+
ParameterizedDeclAttributeKind::InheritActorContext);
3784+
if (!Modifier) {
3785+
return makeParserSuccess();
3786+
}
3787+
3788+
if (!DiscardAttribute) {
3789+
Attributes.add(new (Context) InheritActorContextAttr(
3790+
AtLoc, AttrRange, *Modifier, /*implicit=*/false));
3791+
}
3792+
3793+
break;
3794+
}
37763795
case DeclAttrKind::MacroRole: {
37773796
auto syntax = (AttrName == "freestanding" ? MacroSyntax::Freestanding
37783797
: MacroSyntax::Attached);

lib/Sema/TypeCheckAttr.cpp

Lines changed: 19 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -184,7 +184,6 @@ class AttributeChecker : public AttributeVisitor<AttributeChecker> {
184184
IGNORED_ATTR(AtRethrows)
185185
IGNORED_ATTR(AtReasync)
186186
IGNORED_ATTR(ImplicitSelfCapture)
187-
IGNORED_ATTR(InheritActorContext)
188187
IGNORED_ATTR(Preconcurrency)
189188
IGNORED_ATTR(BackDeployed)
190189
IGNORED_ATTR(Documentation)
@@ -442,8 +441,10 @@ class AttributeChecker : public AttributeVisitor<AttributeChecker> {
442441
void visitNonisolatedAttr(NonisolatedAttr *attr);
443442
void visitIsolatedAttr(IsolatedAttr *attr);
444443

444+
void visitInheritActorContextAttr(InheritActorContextAttr *attr);
445+
445446
void visitNoImplicitCopyAttr(NoImplicitCopyAttr *attr);
446-
447+
447448
void visitAlwaysEmitConformanceMetadataAttr(AlwaysEmitConformanceMetadataAttr *attr);
448449

449450
void visitExtractConstantsFromMembersAttr(ExtractConstantsFromMembersAttr *attr);
@@ -7846,6 +7847,22 @@ void AttributeChecker::visitAsyncAttr(AsyncAttr *attr) {
78467847
}
78477848
}
78487849

7850+
void AttributeChecker::visitInheritActorContextAttr(
7851+
InheritActorContextAttr *attr) {
7852+
auto *P = dyn_cast<ParamDecl>(D);
7853+
if (!P)
7854+
return;
7855+
7856+
auto paramTy = P->getInterfaceType();
7857+
auto *funcTy =
7858+
paramTy->lookThroughAllOptionalTypes()->getAs<AnyFunctionType>();
7859+
if (!funcTy) {
7860+
diagnoseAndRemoveAttr(attr, diag::inherit_actor_context_only_on_func_types,
7861+
attr, paramTy);
7862+
return;
7863+
}
7864+
}
7865+
78497866
void AttributeChecker::visitMarkerAttr(MarkerAttr *attr) {
78507867
auto proto = dyn_cast<ProtocolDecl>(D);
78517868
if (!proto)

lib/Serialization/Deserialization.cpp

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6544,6 +6544,17 @@ llvm::Error DeclDeserializer::deserializeDeclCommon() {
65446544
break;
65456545
}
65466546

6547+
case decls_block::InheritActorContext_DECL_ATTR: {
6548+
unsigned modifier;
6549+
bool isImplicit{};
6550+
serialization::decls_block::InheritActorContextDeclAttrLayout::
6551+
readRecord(scratch, modifier, isImplicit);
6552+
Attr = new (ctx) InheritActorContextAttr(
6553+
{}, {}, static_cast<InheritActorContextModifier>(modifier),
6554+
isImplicit);
6555+
break;
6556+
}
6557+
65476558
case decls_block::MacroRole_DECL_ATTR: {
65486559
bool isImplicit;
65496560
uint8_t rawMacroSyntax;

lib/Serialization/ModuleFormat.h

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -58,7 +58,7 @@ const uint16_t SWIFTMODULE_VERSION_MAJOR = 0;
5858
/// describe what change you made. The content of this comment isn't important;
5959
/// it just ensures a conflict if two people change the module format.
6060
/// Don't worry about adhering to the 80-column limit for this line.
61-
const uint16_t SWIFTMODULE_VERSION_MINOR = 950; // DeclNameRef module selectors
61+
const uint16_t SWIFTMODULE_VERSION_MINOR = 951; // add modifier to @_inheritActorContext
6262

6363
/// A standard hash seed used for all string hashes in a serialized module.
6464
///
@@ -2556,6 +2556,12 @@ namespace decls_block {
25562556
BCFixed<1> // implicit flag
25572557
>;
25582558

2559+
using InheritActorContextDeclAttrLayout =
2560+
BCRecordLayout<InheritActorContext_DECL_ATTR,
2561+
BCFixed<1>, // the modifier (none = 0, always = 1)
2562+
BCFixed<1> // implicit flag
2563+
>;
2564+
25592565
using MacroRoleDeclAttrLayout = BCRecordLayout<
25602566
MacroRole_DECL_ATTR,
25612567
BCFixed<1>, // implicit flag

lib/Serialization/Serialization.cpp

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3451,6 +3451,16 @@ class Serializer::DeclSerializer : public DeclVisitor<DeclSerializer> {
34513451
return;
34523452
}
34533453

3454+
case DeclAttrKind::InheritActorContext: {
3455+
auto *theAttr = cast<InheritActorContextAttr>(DA);
3456+
auto abbrCode =
3457+
S.DeclTypeAbbrCodes[InheritActorContextDeclAttrLayout::Code];
3458+
InheritActorContextDeclAttrLayout::emitRecord(
3459+
S.Out, S.ScratchRecord, abbrCode,
3460+
static_cast<uint8_t>(theAttr->getModifier()), theAttr->isImplicit());
3461+
return;
3462+
}
3463+
34543464
case DeclAttrKind::MacroRole: {
34553465
auto *theAttr = cast<MacroRoleAttr>(DA);
34563466
auto abbrCode = S.DeclTypeAbbrCodes[MacroRoleDeclAttrLayout::Code];

test/attr/attr_abi.swift

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1406,16 +1406,19 @@ func nonEphemeral2(_: UnsafeRawPointer) {}
14061406
@abi(func disfavoredOverload3(_: UnsafeRawPointer))
14071407
func nonEphemeral3(@_nonEphemeral _: UnsafeRawPointer) {}
14081408

1409-
// @_inheritActorContext -- banned in @abi
1410-
@abi(func inheritActorContext1(@_inheritActorContext fn: @Sendable @escaping () async -> Void)) // expected-error {{unused '_inheritActorContext' attribute in '@abi'}} {{32-53=}}
1409+
// @_inheritActorContext
1410+
@abi(func inheritActorContext1(@_inheritActorContext fn: @Sendable @escaping () async -> Void))
14111411
func inheritActorContext1(@_inheritActorContext fn: @Sendable @escaping () async -> Void) {}
14121412

1413-
@abi(func inheritActorContext2(@_inheritActorContext fn: @Sendable @escaping () async -> Void)) // expected-error {{unused '_inheritActorContext' attribute in '@abi'}} {{32-53=}}
1413+
@abi(func inheritActorContext2(@_inheritActorContext fn: @Sendable @escaping () async -> Void))
14141414
func inheritActorContext2(fn: @Sendable @escaping () async -> Void) {}
14151415

14161416
@abi(func inheritActorContext3(fn: @Sendable @escaping () async -> Void))
14171417
func inheritActorContext3(@_inheritActorContext fn: @Sendable @escaping () async -> Void) {}
14181418

1419+
@abi(func inheritActorContext4(@_inheritActorContext(always) fn: @Sendable @escaping () async -> Void))
1420+
func inheritActorContext4(fn: @Sendable @escaping () async -> Void) {}
1421+
14191422
// @excusivity(checked/unchecked) -- banned in @abi
14201423
class Exclusivity {
14211424
@abi(var checked00: Int)

0 commit comments

Comments
 (0)