Skip to content

[AST/Sema/SIL] Implement @_inheritActorContext(always) #81496

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 6 commits into from
May 15, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
37 changes: 37 additions & 0 deletions docs/ReferenceGuides/UnderscoredAttributes.md
Original file line number Diff line number Diff line change
Expand Up @@ -600,6 +600,43 @@ inherit the actor context (i.e. what actor it should be run on) based on the
declaration site of the closure rather than be non-Sendable. This does not do
anything if the closure is synchronous.

This works with global actors as expected:

```swift
@MainActor
func test() {
Task { /* main actor isolated */ }
}
```

However, for the inference to work with instance actors (i.e. `isolated` parameters),
the closure must capture the isolated parameter explicitly:

```swift
func test(actor: isolated (any Actor)) {
Task { /* non isolated */ } // !!!
}

func test(actor: isolated (any Actor)) {
Task { // @_inheritActorContext
_ = actor // 'actor'-isolated
}
}
```

The attribute takes an optional modifier '`always`', which changes this behavior
and *always* captures the enclosing isolated context, rather than forcing developers
to perform the explicit capture themselfes:

```swift
func test(actor: isolated (any Actor)) {
Task.immediate { // @_inheritActorContext(always)
// 'actor'-isolated!
// (without having to capture 'actor explicitly')
}
}
```

DISCUSSION: The reason why this does nothing when the closure is synchronous is
since it does not have the ability to hop to the appropriate executor before it
is run, so we may create concurrency errors.
Expand Down
12 changes: 12 additions & 0 deletions include/swift/AST/ASTBridging.h
Original file line number Diff line number Diff line change
Expand Up @@ -1254,6 +1254,18 @@ BridgedNonisolatedAttr_createParsed(BridgedASTContext cContext,
BridgedSourceRange cRange,
BridgedNonIsolatedModifier modifier);

enum ENUM_EXTENSIBILITY_ATTR(closed) BridgedInheritActorContextModifier {
BridgedInheritActorContextModifierNone,
BridgedInheritActorContextModifierAlways,
};

SWIFT_NAME("BridgedInheritActorContextAttr.createParsed(_:atLoc:range:modifier:)")
BridgedInheritActorContextAttr
BridgedInheritActorContextAttr_createParsed(BridgedASTContext cContext,
BridgedSourceLoc cAtLoc,
BridgedSourceRange cRange,
BridgedInheritActorContextModifier modifier);

SWIFT_NAME("BridgedObjCAttr.createParsedUnnamed(_:atLoc:attrNameLoc:)")
BridgedObjCAttr
BridgedObjCAttr_createParsedUnnamed(BridgedASTContext cContext,
Expand Down
48 changes: 48 additions & 0 deletions include/swift/AST/Attr.h
Original file line number Diff line number Diff line change
Expand Up @@ -230,6 +230,10 @@ class DeclAttribute : public AttributeBase {
Modifier : NumNonIsolatedModifierBits
);

SWIFT_INLINE_BITFIELD(InheritActorContextAttr, DeclAttribute, NumInheritActorContextKindBits,
Modifier : NumInheritActorContextKindBits
);

SWIFT_INLINE_BITFIELD_FULL(AllowFeatureSuppressionAttr, DeclAttribute, 1+31,
: NumPadBits,
Inverted : 1,
Expand Down Expand Up @@ -3011,6 +3015,50 @@ class NonisolatedAttr final : public DeclAttribute {
}
};

/// Represents @_inheritActorContext modifier.
class InheritActorContextAttr final : public DeclAttribute {
public:
InheritActorContextAttr(SourceLoc atLoc, SourceRange range,
InheritActorContextModifier modifier, bool implicit)
: DeclAttribute(DeclAttrKind::InheritActorContext, atLoc, range,
implicit) {
Bits.InheritActorContextAttr.Modifier = static_cast<unsigned>(modifier);
assert((getModifier() == modifier) && "not enough bits for modifier");
}

InheritActorContextModifier getModifier() const {
return static_cast<InheritActorContextModifier>(
Bits.InheritActorContextAttr.Modifier);
}

bool isAlways() const {
return getModifier() == InheritActorContextModifier::Always;
}

static InheritActorContextAttr *
createImplicit(ASTContext &ctx, InheritActorContextModifier modifier =
InheritActorContextModifier::None) {
return new (ctx)
InheritActorContextAttr(/*atLoc*/ {}, /*range*/ {}, modifier,
/*implicit=*/true);
}

static bool classof(const DeclAttribute *DA) {
return DA->getKind() == DeclAttrKind::InheritActorContext;
}

/// Create a copy of this attribute.
InheritActorContextAttr *clone(ASTContext &ctx) const {
return new (ctx)
InheritActorContextAttr(AtLoc, Range, getModifier(), isImplicit());
}

bool isEquivalent(const InheritActorContextAttr *other,
Decl *attachedTo) const {
return getModifier() == other->getModifier();
}
};

/// A macro role attribute, spelled with either @attached or @freestanding,
/// which declares one of the roles that a given macro can inhabit.
class MacroRoleAttr final
Expand Down
16 changes: 16 additions & 0 deletions include/swift/AST/AttrKind.h
Original file line number Diff line number Diff line change
Expand Up @@ -142,6 +142,22 @@ enum : unsigned {
static_cast<unsigned>(NonIsolatedModifier::Last_NonIsolatedModifier))
};

enum class InheritActorContextModifier : uint8_t {
/// Inherit the actor execution context if the isolated parameter was
/// captured by the closure, context is nonisolated or isolated to a
/// global actor.
None = 0,
/// Always inherit the actor context, even when the isolated parameter
/// for the context is not closed over explicitly.
Always,
Last_InheritActorContextKind = Always
};

enum : unsigned {
NumInheritActorContextKindBits = countBitsUsed(static_cast<unsigned>(
InheritActorContextModifier::Last_InheritActorContextKind))
};

enum class DeclAttrKind : unsigned {
#define DECL_ATTR(_, CLASS, ...) CLASS,
#define LAST_DECL_ATTR(CLASS) Last_DeclAttr = CLASS,
Expand Down
5 changes: 3 additions & 2 deletions include/swift/AST/DeclAttr.def
Original file line number Diff line number Diff line change
Expand Up @@ -620,9 +620,10 @@ SIMPLE_DECL_ATTR(_implicitSelfCapture, ImplicitSelfCapture,
UserInaccessible | ABIStableToAdd | ABIStableToRemove | APIStableToAdd | APIBreakingToRemove | ForbiddenInABIAttr,
115)

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

SIMPLE_DECL_ATTR(_eagerMove, EagerMove,
Expand Down
17 changes: 17 additions & 0 deletions include/swift/AST/DiagnosticsSema.def
Original file line number Diff line number Diff line change
Expand Up @@ -8595,6 +8595,23 @@ GROUPED_ERROR(isolated_conformance_wrong_domain,IsolatedConformances,none,
"%0 conformance of %1 to %2 cannot be used in %3 context",
(ActorIsolation, Type, DeclName, ActorIsolation))

//===----------------------------------------------------------------------===//
// MARK: @_inheritActorContext
//===----------------------------------------------------------------------===//
ERROR(inherit_actor_context_only_on_func_types,none,
"%0 only applies to parameters with function types (got: %1)",
(DeclAttribute, Type))

ERROR(inherit_actor_context_only_on_sending_or_Sendable_params,none,
"%0 only applies to 'sending' parameters or parameters with "
"'@Sendable' function types",
(DeclAttribute))

ERROR(inherit_actor_context_only_on_async_or_isolation_erased_params,none,
"%0 only applies to '@isolated(any)' parameters or parameters with "
"asynchronous function types",
(DeclAttribute))

//===----------------------------------------------------------------------===//
// MARK: @concurrent and nonisolated(nonsending) attributes
//===----------------------------------------------------------------------===//
Expand Down
32 changes: 28 additions & 4 deletions include/swift/AST/Expr.h
Original file line number Diff line number Diff line change
Expand Up @@ -267,7 +267,7 @@ class alignas(8) Expr : public ASTAllocated<Expr> {
Kind : 2
);

SWIFT_INLINE_BITFIELD(ClosureExpr, AbstractClosureExpr, 1+1+1+1+1+1+1+1,
SWIFT_INLINE_BITFIELD(ClosureExpr, AbstractClosureExpr, 1+1+1+1+1+1+1+1+1,
/// True if closure parameters were synthesized from anonymous closure
/// variables.
HasAnonymousClosureVars : 1,
Expand All @@ -276,9 +276,11 @@ class alignas(8) Expr : public ASTAllocated<Expr> {
/// on each member reference.
ImplicitSelfCapture : 1,

/// True if this @Sendable async closure parameter should implicitly
/// inherit the actor context from where it was formed.
/// True if this closure parameter should implicitly inherit the actor
/// context from where it was formed.
InheritActorContext : 1,
/// The kind for inheritance - none or always at the moment.
InheritActorContextKind : 1,

/// True if this closure's actor isolation behavior was determined by an
/// \c \@preconcurrency declaration.
Expand Down Expand Up @@ -4318,6 +4320,7 @@ class ClosureExpr : public AbstractClosureExpr {
Bits.ClosureExpr.HasAnonymousClosureVars = false;
Bits.ClosureExpr.ImplicitSelfCapture = false;
Bits.ClosureExpr.InheritActorContext = false;
Bits.ClosureExpr.InheritActorContextKind = 0;
Bits.ClosureExpr.IsPassedToSendingParameter = false;
Bits.ClosureExpr.NoGlobalActorAttribute = false;
Bits.ClosureExpr.RequiresDynamicIsolationChecking = false;
Expand Down Expand Up @@ -4366,8 +4369,29 @@ class ClosureExpr : public AbstractClosureExpr {
return Bits.ClosureExpr.InheritActorContext;
}

void setInheritsActorContext(bool value = true) {
/// Whether this closure should _always_ implicitly inherit the actor context
/// regardless of whether the isolation parameter is captured or not.
bool alwaysInheritsActorContext() const {
if (!inheritsActorContext())
return false;
return getInheritActorIsolationModifier() ==
InheritActorContextModifier::Always;
}

void setInheritsActorContext(bool value = true,
InheritActorContextModifier modifier =
InheritActorContextModifier::None) {
Bits.ClosureExpr.InheritActorContext = value;
Bits.ClosureExpr.InheritActorContextKind = uint8_t(modifier);
assert((static_cast<InheritActorContextModifier>(
Bits.ClosureExpr.InheritActorContextKind) == modifier) &&
"not enough bits for modifier");
}

InheritActorContextModifier getInheritActorIsolationModifier() const {
assert(inheritsActorContext());
return static_cast<InheritActorContextModifier>(
Bits.ClosureExpr.InheritActorContextKind);
}

/// Whether the closure's concurrency behavior was determined by an
Expand Down
1 change: 1 addition & 0 deletions include/swift/AST/KnownIdentifiers.def
Original file line number Diff line number Diff line change
Expand Up @@ -325,6 +325,7 @@ IDENTIFIER(SerializationRequirement)
IDENTIFIER_WITH_NAME(builderSelf, "$builderSelf")

// Attribute options
IDENTIFIER(always)
IDENTIFIER_(_always)
IDENTIFIER_(assumed)
IDENTIFIER(checked)
Expand Down
4 changes: 3 additions & 1 deletion include/swift/AST/Types.h
Original file line number Diff line number Diff line change
Expand Up @@ -4049,6 +4049,7 @@ struct ParameterListInfo {
SmallBitVector propertyWrappers;
SmallBitVector implicitSelfCapture;
SmallBitVector inheritActorContext;
SmallBitVector alwaysInheritActorContext;
SmallBitVector variadicGenerics;
SmallBitVector sendingParameters;

Expand All @@ -4075,7 +4076,8 @@ struct ParameterListInfo {

/// Whether the given parameter is a closure that should inherit the
/// actor context from the context in which it was created.
bool inheritsActorContext(unsigned paramIdx) const;
std::pair<bool, InheritActorContextModifier>
inheritsActorContext(unsigned paramIdx) const;

bool isVariadicGenericParameter(unsigned paramIdx) const;

Expand Down
1 change: 1 addition & 0 deletions include/swift/Basic/Features.def
Original file line number Diff line number Diff line change
Expand Up @@ -259,6 +259,7 @@ LANGUAGE_FEATURE(IsolatedConformances, 407, "Global-actor isolated conformances"
LANGUAGE_FEATURE(ValueGenericsNameLookup, 452, "Value generics appearing as static members for namelookup")
LANGUAGE_FEATURE(GeneralizedIsSameMetaTypeBuiltin, 465, "Builtin.is_same_metatype with support for noncopyable/nonescapable types")
SUPPRESSIBLE_LANGUAGE_FEATURE(ABIAttributeSE0479, 479, "@abi attribute on functions, initializers, properties, and subscripts")
LANGUAGE_FEATURE(AlwaysInheritActorContext, 472, "@_inheritActorContext(always)")

// Swift 6
UPCOMING_FEATURE(ConciseMagicFile, 274, 6)
Expand Down
3 changes: 2 additions & 1 deletion include/swift/Parse/IDEInspectionCallbacks.h
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,8 @@ enum class ParameterizedDeclAttributeKind {
Available,
FreestandingMacro,
AttachedMacro,
StorageRestrictions
StorageRestrictions,
InheritActorContext
};

/// A bit of a hack. When completing inside the '@storageRestrictions'
Expand Down
7 changes: 6 additions & 1 deletion lib/AST/ASTDumper.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -4976,7 +4976,6 @@ class PrintAttribute : public AttributeVisitor<PrintAttribute, void, Label>,
TRIVIAL_ATTR_PRINTER(ImplicitSelfCapture, implicit_self_capture)
TRIVIAL_ATTR_PRINTER(Indirect, indirect)
TRIVIAL_ATTR_PRINTER(Infix, infix)
TRIVIAL_ATTR_PRINTER(InheritActorContext, inherit_actor_context)
TRIVIAL_ATTR_PRINTER(InheritsConvenienceInitializers,
inherits_convenience_initializers)
TRIVIAL_ATTR_PRINTER(Inlinable, inlinable)
Expand Down Expand Up @@ -5301,6 +5300,12 @@ class PrintAttribute : public AttributeVisitor<PrintAttribute, void, Label>,
printFlag(Attr->isNonSending(), "nonsending");
printFoot();
}
void visitInheritActorContextAttr(InheritActorContextAttr *Attr,
Label label) {
printCommon(Attr, "inherit_actor_context_attr", label);
printFlag(Attr->isAlways(), "always");
printFoot();
}
void visitObjCAttr(ObjCAttr *Attr, Label label) {
printCommon(Attr, "objc_attr", label);
if (Attr->hasName())
Expand Down
19 changes: 19 additions & 0 deletions lib/AST/Attr.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1531,6 +1531,18 @@ bool DeclAttribute::printImpl(ASTPrinter &Printer, const PrintOptions &Options,
break;
}

case DeclAttrKind::InheritActorContext: {
Printer.printAttrName("@_inheritActorContext");
switch (cast<InheritActorContextAttr>(this)->getModifier()) {
case InheritActorContextModifier::None:
break;
case InheritActorContextModifier::Always:
Printer << "(always)";
break;
}
break;
}

case DeclAttrKind::MacroRole: {
auto Attr = cast<MacroRoleAttr>(this);

Expand Down Expand Up @@ -1915,6 +1927,13 @@ StringRef DeclAttribute::getAttrName() const {
case NonIsolatedModifier::NonSending:
return "nonisolated(nonsending)";
}
case DeclAttrKind::InheritActorContext:
switch (cast<InheritActorContextAttr>(this)->getModifier()) {
case InheritActorContextModifier::None:
return "_inheritActorContext";
case InheritActorContextModifier::Always:
return "_inheritActorContext(always)";
}
case DeclAttrKind::MacroRole:
switch (cast<MacroRoleAttr>(this)->getMacroSyntax()) {
case MacroSyntax::Freestanding:
Expand Down
19 changes: 19 additions & 0 deletions lib/AST/Bridging/DeclAttributeBridging.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -649,6 +649,25 @@ BridgedNonisolatedAttr_createParsed(BridgedASTContext cContext,
/*implicit=*/false);
}

static InheritActorContextModifier
unbridged(BridgedInheritActorContextModifier modifier) {
switch (modifier) {
case BridgedInheritActorContextModifierNone:
return InheritActorContextModifier::None;
case BridgedInheritActorContextModifierAlways:
return InheritActorContextModifier::Always;
}
llvm_unreachable("unhandled enum value");
}

BridgedInheritActorContextAttr BridgedInheritActorContextAttr_createParsed(
BridgedASTContext cContext, BridgedSourceLoc cAtLoc,
BridgedSourceRange cRange, BridgedInheritActorContextModifier modifier) {
return new (cContext.unbridged()) InheritActorContextAttr(
cAtLoc.unbridged(), cRange.unbridged(), unbridged(modifier),
/*implicit=*/false);
}

BridgedObjCAttr
BridgedObjCAttr_createParsedUnnamed(BridgedASTContext cContext,
BridgedSourceLoc cAtLoc,
Expand Down
15 changes: 15 additions & 0 deletions lib/AST/FeatureSet.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -624,6 +624,21 @@ static bool usesFeatureExtensibleAttribute(Decl *decl) {
return decl->getAttrs().hasAttribute<ExtensibleAttr>();
}

static bool usesFeatureAlwaysInheritActorContext(Decl *decl) {
auto *VD = dyn_cast<ValueDecl>(decl);
if (!VD)
return false;

if (auto *PL = VD->getParameterList()) {
return llvm::any_of(*PL, [&](const ParamDecl *P) {
auto *attr = P->getAttrs().getAttribute<InheritActorContextAttr>();
return attr && attr->isAlways();
});
}

return false;
}

// ----------------------------------------------------------------------------
// MARK: - FeatureSet
// ----------------------------------------------------------------------------
Expand Down
Loading