diff --git a/include/swift/Basic/Features.def b/include/swift/Basic/Features.def index 052d78b9bc4..aa94c41d100 100644 --- a/include/swift/Basic/Features.def +++ b/include/swift/Basic/Features.def @@ -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 diff --git a/lib/AST/FeatureSet.cpp b/lib/AST/FeatureSet.cpp index 33bf3e36b15..a39f398bf31 100644 --- a/lib/AST/FeatureSet.cpp +++ b/lib/AST/FeatureSet.cpp @@ -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) diff --git a/lib/Sema/DerivedConformance/DerivedConformanceCodable.cpp b/lib/Sema/DerivedConformance/DerivedConformanceCodable.cpp index ca062d96316..8d9b5bd0229 100644 --- a/lib/Sema/DerivedConformance/DerivedConformanceCodable.cpp +++ b/lib/Sema/DerivedConformance/DerivedConformanceCodable.cpp @@ -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 diff --git a/lib/Sema/TypeCheckConcurrency.cpp b/lib/Sema/TypeCheckConcurrency.cpp index 5da6a5ae084..4cd8adeccaf 100644 --- a/lib/Sema/TypeCheckConcurrency.cpp +++ b/lib/Sema/TypeCheckConcurrency.cpp @@ -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(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(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 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::tuplegetInnermostDeclContext(); 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(value) || isa(value) || - isa(value) || isa(value) || - isa(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(value); + if (!nominalTypeDecl && !isa(value)) { + nominalTypeDecl = value->getDeclContext()->getSelfNominalTypeDecl(); + } + if (nominalTypeDecl && + sendableConformanceRequiresNonisolated(nominalTypeDecl)) + return { }; + + // FIXME: deinit should be implicitly MainActor too. + if (isa(value) || isa(value) || + isa(value) || isa(value) || + isa(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 {};