[SE-0466] Don't infer @MainActor on types conforming to Sendable

When the default isolation is main-actor, don't infer @MainActor
for a type that conforms to a protocol P in its primary definition when
P inherits from Sendable. Such types should remain non-isolated
because they're highly unlikely to be able to implement the P
conformance (which cannot be isolated).

Put this feature behind a new experimental flag,
SendableProhibitsMainActorInference.

Implements rdar://151029300
This commit is contained in:
Doug Gregor
2025-05-12 22:22:25 -07:00
parent 5552d37668
commit ee9f6f8db7
4 changed files with 90 additions and 14 deletions

View File

@@ -532,6 +532,10 @@ EXPERIMENTAL_FEATURE(DefaultIsolationPerFile, false)
/// Enable @_lifetime attribute
SUPPRESSIBLE_EXPERIMENTAL_FEATURE(Lifetimes, true)
/// Disable @MainActor inference when the primary definition of a type conforms
/// to SendableMetatype (or Sendable).
EXPERIMENTAL_FEATURE(SendableProhibitsMainActorInference, true)
#undef EXPERIMENTAL_FEATURE_EXCLUDED_FROM_MODULE_INTERFACE
#undef EXPERIMENTAL_FEATURE
#undef UPCOMING_FEATURE

View File

@@ -125,6 +125,7 @@ UNINTERESTING_FEATURE(StructLetDestructuring)
UNINTERESTING_FEATURE(MacrosOnImports)
UNINTERESTING_FEATURE(NonisolatedNonsendingByDefault)
UNINTERESTING_FEATURE(KeyPathWithMethodMembers)
UNINTERESTING_FEATURE(SendableProhibitsMainActorInference)
// TODO: Return true for inlinable function bodies with module selectors in them
UNINTERESTING_FEATURE(ModuleSelector)

View File

@@ -155,14 +155,16 @@ addImplicitCodingKeys(NominalTypeDecl *target,
enumDecl->setSynthesized();
enumDecl->setAccess(AccessLevel::Private);
switch (C.LangOpts.DefaultIsolationBehavior) {
case DefaultIsolation::MainActor:
enumDecl->getAttrs().add(NonisolatedAttr::createImplicit(C));
break;
if (!C.LangOpts.hasFeature(Feature::SendableProhibitsMainActorInference)) {
switch (C.LangOpts.DefaultIsolationBehavior) {
case DefaultIsolation::MainActor:
enumDecl->getAttrs().add(NonisolatedAttr::createImplicit(C));
break;
case DefaultIsolation::Nonisolated:
// Nothing to do.
break;
case DefaultIsolation::Nonisolated:
// Nothing to do.
break;
}
}
// For classes which inherit from something Encodable or Decodable, we

View File

@@ -5936,6 +5936,65 @@ static void addAttributesForActorIsolation(ValueDecl *value,
}
}
/// Determine whether there is a SendableMetatype conformance that requires that the nominal type
/// be nonisolated (preventing @MainActor inference).
static bool sendableConformanceRequiresNonisolated(NominalTypeDecl *nominal) {
ASTContext &ctx = nominal->getASTContext();
if (!ctx.LangOpts.hasFeature(Feature::SendableProhibitsMainActorInference))
return false;
if (isa<ProtocolDecl>(nominal))
return false;
auto sendable = ctx.getProtocol(KnownProtocolKind::Sendable);
auto sendableMetatype = ctx.getProtocol(KnownProtocolKind::SendableMetatype);
if (!sendableMetatype)
return false;
// Check whether any of the explicit conformances is to a
// SendableMetatype-inheriting protocol. We exclude direct conformance to
// Sendable here, because a global-actor-isolated type is implicitly Sendable,
// and writing Sendable explicitly
InvertibleProtocolSet inverses;
bool anyObject = false;
auto inherited = getDirectlyInheritedNominalTypeDecls(
nominal, inverses, anyObject);
for (const auto &entry : inherited) {
auto proto = dyn_cast<ProtocolDecl>(entry.Item);
if (proto && proto != sendable && proto->inheritsFrom(sendableMetatype))
return true;
}
// Check for member or extension macros that define conformances to
// SendableMetatype-inheriting protocols.
bool requiresNonisolated = false;
auto checkMacro = [&](MacroRole role, MacroDecl *macro) {
if (!macro || requiresNonisolated)
return;
SmallVector<ProtocolDecl *, 2> conformances;
macro->getIntroducedConformances(nominal, role, conformances);
for (auto proto : conformances) {
if (proto == sendableMetatype || proto->inheritsFrom(sendableMetatype)) {
requiresNonisolated = true;
break;
}
}
};
nominal->forEachAttachedMacro(
MacroRole::Member,
[&](CustomAttr * attr, MacroDecl *macro) {
checkMacro(MacroRole::Member, macro);
});
nominal->forEachAttachedMacro(
MacroRole::Extension,
[&](CustomAttr * attr, MacroDecl *macro) {
checkMacro(MacroRole::Extension, macro);
});
return requiresNonisolated;
}
/// Determine the default isolation and isolation source for this declaration,
/// which may still be overridden by other inference rules.
static std::tuple<InferredActorIsolation, ValueDecl *,
@@ -5955,16 +6014,27 @@ computeDefaultInferredActorIsolation(ValueDecl *value) {
auto *dc = value->getInnermostDeclContext();
while (dc && !inActorContext) {
if (auto *nominal = dc->getSelfNominalTypeDecl()) {
inActorContext = nominal->isAnyActor();
if (nominal->isAnyActor())
return {};
}
dc = dc->getParent();
}
if (!inActorContext) {
// FIXME: deinit should be implicitly MainActor too.
if (isa<TypeDecl>(value) || isa<ExtensionDecl>(value) ||
isa<AbstractStorageDecl>(value) || isa<FuncDecl>(value) ||
isa<ConstructorDecl>(value)) {
// If this is or is a non-type member of a nominal type that conforms to a
// SendableMetatype-inheriting protocol in its primary definition, disable
// @MainActor inference.
auto nominalTypeDecl = dyn_cast<NominalTypeDecl>(value);
if (!nominalTypeDecl && !isa<TypeDecl>(value)) {
nominalTypeDecl = value->getDeclContext()->getSelfNominalTypeDecl();
}
if (nominalTypeDecl &&
sendableConformanceRequiresNonisolated(nominalTypeDecl))
return { };
// FIXME: deinit should be implicitly MainActor too.
if (isa<TypeDecl>(value) || isa<ExtensionDecl>(value) ||
isa<AbstractStorageDecl>(value) || isa<FuncDecl>(value) ||
isa<ConstructorDecl>(value)) {
// Preconcurrency here is used to stage the diagnostics
// when users select `@MainActor` default isolation with
// non-strict concurrency modes (pre Swift 6).
@@ -5972,7 +6042,6 @@ computeDefaultInferredActorIsolation(ValueDecl *value) {
ActorIsolation::forGlobalActor(globalActor)
.withPreconcurrency(!ctx.LangOpts.isSwiftVersionAtLeast(6));
return {{{isolation, {}}, nullptr, {}}};
}
}
return {};