From 0e873e723c67ae1a9c6e2cda40220a9d6a3be8e1 Mon Sep 17 00:00:00 2001 From: Doug Gregor Date: Wed, 12 Mar 2025 16:12:17 -0700 Subject: [PATCH 01/10] [Isolated conformances] Change syntax to @ P Instead of using the `isolated P` syntax, switch to specifying the global actor type directly, e.g., class MyClass: @MainActor MyProto { ... } No functionality change at this point --- include/swift/AST/ConformanceAttributes.h | 22 +++- include/swift/AST/Decl.h | 16 ++- include/swift/AST/ProtocolConformance.h | 18 ++- .../swift/AST/ProtocolConformanceOptions.h | 103 +++++++++++++++++- include/swift/AST/TypeAttr.def | 1 + include/swift/AST/TypeCheckRequests.h | 19 ++++ include/swift/AST/TypeCheckerTypeIDZone.def | 3 + include/swift/AST/TypeRepr.h | 4 + lib/AST/ASTPrinter.cpp | 3 +- lib/AST/ConformanceLookupTable.h | 12 +- lib/AST/Decl.cpp | 18 ++- lib/AST/NameLookup.cpp | 29 ++--- lib/AST/ProtocolConformanceRef.cpp | 2 +- lib/AST/TypeCheckRequests.cpp | 16 +++ lib/AST/TypeRepr.cpp | 14 +++ lib/ASTGen/Sources/ASTGen/TypeAttrs.swift | 1 + lib/IDE/CompletionLookup.cpp | 1 + lib/IRGen/GenProto.cpp | 4 +- lib/Sema/TypeCheckConcurrency.cpp | 38 ++++++- lib/Sema/TypeCheckProtocol.cpp | 9 +- lib/Sema/TypeCheckType.cpp | 7 ++ lib/Serialization/Deserialization.cpp | 23 +++- lib/Serialization/ModuleFormat.h | 3 +- lib/Serialization/Serialization.cpp | 31 +++++- .../Runtime/isolated_conformance.swift | 2 +- test/Concurrency/isolated_conformance.swift | 16 +-- test/IRGen/isolated_conformance.swift | 2 +- .../Inputs/def_isolated_conformance.swift | 10 ++ test/Serialization/isolated_conformance.swift | 16 +++ 29 files changed, 365 insertions(+), 78 deletions(-) create mode 100644 test/Serialization/Inputs/def_isolated_conformance.swift create mode 100644 test/Serialization/isolated_conformance.swift diff --git a/include/swift/AST/ConformanceAttributes.h b/include/swift/AST/ConformanceAttributes.h index aa3ec21dff4dc..48eada1249311 100644 --- a/include/swift/AST/ConformanceAttributes.h +++ b/include/swift/AST/ConformanceAttributes.h @@ -17,6 +17,8 @@ namespace swift { +class TypeExpr; + /// Describes all of the attributes that can occur on a conformance. struct ConformanceAttributes { /// The location of the "unchecked" attribute, if present. @@ -28,9 +30,15 @@ struct ConformanceAttributes { /// The location of the "unsafe" attribute if present. SourceLoc unsafeLoc; - /// The location of the "@isolated" attribute if present. - SourceLoc isolatedLoc; - + /// The location of the "nonisolated" modifier, if present. + SourceLoc nonisolatedLoc; + + /// The location of the '@' prior to the global actor type. + SourceLoc globalActorAtLoc; + + /// The global actor type to which this conformance is isolated. + TypeExpr *globalActorType = nullptr; + /// Merge other conformance attributes into this set. ConformanceAttributes & operator |=(const ConformanceAttributes &other) { @@ -40,8 +48,12 @@ struct ConformanceAttributes { preconcurrencyLoc = other.preconcurrencyLoc; if (other.unsafeLoc.isValid()) unsafeLoc = other.unsafeLoc; - if (other.isolatedLoc.isValid()) - isolatedLoc = other.isolatedLoc; + if (other.nonisolatedLoc.isValid()) + nonisolatedLoc = other.nonisolatedLoc; + if (other.globalActorType && !globalActorType) { + globalActorAtLoc = other.globalActorAtLoc; + globalActorType = other.globalActorType; + } return *this; } }; diff --git a/include/swift/AST/Decl.h b/include/swift/AST/Decl.h index c8cefef02c6ab..1c0431278ce87 100644 --- a/include/swift/AST/Decl.h +++ b/include/swift/AST/Decl.h @@ -1806,16 +1806,20 @@ struct InheritedEntry : public TypeLoc { /// This is true in cases like ~Copyable but not (P & ~Copyable). bool IsSuppressed : 1; + /// The global actor isolation provided (for a conformance). + TypeExpr *globalActorIsolationType = nullptr; + public: InheritedEntry(const TypeLoc &typeLoc); InheritedEntry(const TypeLoc &typeLoc, ProtocolConformanceOptions options, bool isSuppressed = false) : TypeLoc(typeLoc), RawOptions(options.toRaw()), - IsSuppressed(isSuppressed) {} + IsSuppressed(isSuppressed), + globalActorIsolationType(options.getGlobalActorIsolationType()) {} ProtocolConformanceOptions getOptions() const { - return ProtocolConformanceOptions(RawOptions); + return ProtocolConformanceOptions(RawOptions, globalActorIsolationType); } bool isUnchecked() const { @@ -1827,8 +1831,12 @@ struct InheritedEntry : public TypeLoc { bool isPreconcurrency() const { return getOptions().contains(ProtocolConformanceFlags::Preconcurrency); } - bool isIsolated() const { - return getOptions().contains(ProtocolConformanceFlags::Isolated); + bool isNonisolated() const { + return getOptions().contains(ProtocolConformanceFlags::Nonisolated); + } + + TypeExpr *getGlobalActorIsolationType() const { + return globalActorIsolationType; } ExplicitSafety getExplicitSafety() const { diff --git a/include/swift/AST/ProtocolConformance.h b/include/swift/AST/ProtocolConformance.h index 3969f60624915..30f04f36dc7c0 100644 --- a/include/swift/AST/ProtocolConformance.h +++ b/include/swift/AST/ProtocolConformance.h @@ -546,6 +546,9 @@ class NormalProtocolConformance : public RootProtocolConformance, /// NominalTypeDecl that declared the conformance. DeclContext *Context; + /// The global actor isolation for this conformance, if there is one. + TypeExpr *globalActorIsolation = nullptr; + NormalProtocolConformance *ImplyingConformance = nullptr; /// The mapping of individual requirements in the protocol over to @@ -593,6 +596,7 @@ class NormalProtocolConformance : public RootProtocolConformance, Bits.NormalProtocolConformance.HasComputedAssociatedConformances = false; Bits.NormalProtocolConformance.SourceKind = unsigned(ConformanceEntryKind::Explicit); + globalActorIsolation = options.getGlobalActorIsolationType(); } /// Get the protocol being conformed to. @@ -634,7 +638,8 @@ class NormalProtocolConformance : public RootProtocolConformance, void setInvalid() { Bits.NormalProtocolConformance.IsInvalid = true; } ProtocolConformanceOptions getOptions() const { - return ProtocolConformanceOptions(Bits.NormalProtocolConformance.Options); + return ProtocolConformanceOptions(Bits.NormalProtocolConformance.Options, + globalActorIsolation); } /// Whether this is an "unchecked" conformance. @@ -669,9 +674,14 @@ class NormalProtocolConformance : public RootProtocolConformance, return getOptions().contains(ProtocolConformanceFlags::Preconcurrency); } - /// Whether this is an isolated conformance. - bool isIsolated() const { - return getOptions().contains(ProtocolConformanceFlags::Isolated); + /// Whether this is a global-actor isolated conformance. + bool isGlobalActorIsolated() const { + return getOptions().contains( + ProtocolConformanceFlags::GlobalActorIsolated); + } + + TypeExpr *getGlobalActorIsolation() const { + return globalActorIsolation; } /// Retrieve the location of `@preconcurrency`, if there is one and it is diff --git a/include/swift/AST/ProtocolConformanceOptions.h b/include/swift/AST/ProtocolConformanceOptions.h index 002186bf307d2..2c319344b29f7 100644 --- a/include/swift/AST/ProtocolConformanceOptions.h +++ b/include/swift/AST/ProtocolConformanceOptions.h @@ -20,6 +20,8 @@ namespace swift { +class TypeExpr; + /// Flags that describe extra attributes on protocol conformances. enum class ProtocolConformanceFlags { /// @unchecked conformance @@ -34,16 +36,107 @@ enum class ProtocolConformanceFlags { /// @retroactive conformance Retroactive = 0x08, - /// @isolated conformance - Isolated = 0x10, + /// nonisolated, which suppresses and inferred global actor isolation + Nonisolated = 0x10, + + /// The conformance is global-actor-isolated; the global actor will be + /// stored separately. + GlobalActorIsolated = 0x20, // Note: whenever you add a bit here, update // NumProtocolConformanceOptions below. }; +template +struct OptionSetStorageType; + +template +struct OptionSetStorageType> { + using Type = StorageType; +}; + /// Options that describe extra attributes on protocol conformances. -using ProtocolConformanceOptions = - OptionSet; +class ProtocolConformanceOptions { + /// The set of options. + OptionSet options; + + /// Global actor isolation for this conformance. + TypeExpr *globalActorIsolationType = nullptr; + +public: + using StorageType = + OptionSetStorageType>::Type; + + ProtocolConformanceOptions() { } + + ProtocolConformanceOptions(ProtocolConformanceFlags flag) + : ProtocolConformanceOptions(static_cast(flag), nullptr) { } + + ProtocolConformanceOptions(StorageType flagBits, + TypeExpr *globalActorIsolationType) + : options(flagBits), globalActorIsolationType(globalActorIsolationType) { + assert(options.contains(ProtocolConformanceFlags::GlobalActorIsolated) == + (bool)globalActorIsolationType); + } + + bool contains(ProtocolConformanceFlags flag) const { + return options.contains(flag); + } + + TypeExpr *getGlobalActorIsolationType() const { + return globalActorIsolationType; + } + + void setGlobalActorIsolation(TypeExpr *globalActorIsolationType) { + options |= ProtocolConformanceFlags::GlobalActorIsolated; + this->globalActorIsolationType = globalActorIsolationType; + } + + /// Retrieve the raw bits for just the flags part of the options. You also + /// need to get the global actor isolation (separately) to reconstitute the + /// options. + StorageType toRaw() const { + return options.toRaw(); + } + + ProtocolConformanceOptions &operator|=(ProtocolConformanceFlags flag) { + assert(flag != ProtocolConformanceFlags::GlobalActorIsolated && + "global actor isolation requires a type; use setGlobalActorIsolation"); + options |= flag; + return *this; + } + + ProtocolConformanceOptions & + operator|=(const ProtocolConformanceOptions &other) { + options |= other.options; + if (other.globalActorIsolationType && !globalActorIsolationType) + globalActorIsolationType = other.globalActorIsolationType; + return *this; + } + + ProtocolConformanceOptions &operator-=(ProtocolConformanceFlags flag) { + options -= flag; + if (flag == ProtocolConformanceFlags::GlobalActorIsolated) + globalActorIsolationType = nullptr; + return *this; + } + + friend ProtocolConformanceOptions operator|( + const ProtocolConformanceOptions &lhs, + const ProtocolConformanceOptions &rhs) { + ProtocolConformanceOptions result(lhs); + result |= rhs; + return result; + } + + friend ProtocolConformanceOptions operator-( + const ProtocolConformanceOptions &lhs, + ProtocolConformanceFlags flag) { + ProtocolConformanceOptions result(lhs); + result -= flag; + return result; + } +}; inline ProtocolConformanceOptions operator|( ProtocolConformanceFlags flag1, @@ -52,7 +145,7 @@ inline ProtocolConformanceOptions operator|( } enum : unsigned { - NumProtocolConformanceOptions = 5 + NumProtocolConformanceOptions = 6 }; } // end namespace swift diff --git a/include/swift/AST/TypeAttr.def b/include/swift/AST/TypeAttr.def index 3ca35339f5937..811e85a08d865 100644 --- a/include/swift/AST/TypeAttr.def +++ b/include/swift/AST/TypeAttr.def @@ -65,6 +65,7 @@ SIMPLE_TYPE_ATTR(_local, Local) SIMPLE_TYPE_ATTR(_noMetadata, NoMetadata) TYPE_ATTR(_opaqueReturnTypeOf, OpaqueReturnTypeOf) TYPE_ATTR(isolated, Isolated) +SIMPLE_TYPE_ATTR(nonisolated, Nonisolated) SIMPLE_TYPE_ATTR(_addressable, Addressable) TYPE_ATTR(execution, Execution) diff --git a/include/swift/AST/TypeCheckRequests.h b/include/swift/AST/TypeCheckRequests.h index 0aa005bb773fe..d036fe70d35b0 100644 --- a/include/swift/AST/TypeCheckRequests.h +++ b/include/swift/AST/TypeCheckRequests.h @@ -475,6 +475,25 @@ class ConformanceHasEffectRequest : bool isCached() const { return true; } }; +class ConformanceIsolationRequest : + public SimpleRequest { +public: + using SimpleRequest::SimpleRequest; + +private: + friend SimpleRequest; + + // Evaluation. + ActorIsolation + evaluate(Evaluator &evaluator, ProtocolConformance *conformance) const; + +public: + // Caching. + bool isCached() const; +}; + /// Determine whether the given declaration is 'final'. class IsFinalRequest : public SimpleRequestgetSuperclassBound( proto->getSelfInterfaceType())) { Results.emplace_back(TypeLoc::withoutLoc(superclassTy), - ProtocolConformanceOptions(), - /*isPreconcurrency=*/false); + ProtocolConformanceOptions()); } InvertibleProtocolSet inverses = InvertibleProtocolSet::allKnown(); diff --git a/lib/AST/ConformanceLookupTable.h b/lib/AST/ConformanceLookupTable.h index f43ab190aefe3..8199526975ce8 100644 --- a/lib/AST/ConformanceLookupTable.h +++ b/lib/AST/ConformanceLookupTable.h @@ -154,8 +154,10 @@ class ConformanceLookupTable : public ASTAllocated { options |= ProtocolConformanceFlags::Preconcurrency; if (getUnsafeLoc().isValid()) options |= ProtocolConformanceFlags::Unsafe; - if (getIsolatedLoc().isValid()) - options |= ProtocolConformanceFlags::Isolated; + if (getNonisolatedLoc().isValid()) + options |= ProtocolConformanceFlags::Nonisolated; + if (attributes.globalActorType) + options.setGlobalActorIsolation(attributes.globalActorType); return options; } @@ -211,9 +213,9 @@ class ConformanceLookupTable : public ASTAllocated { return attributes.unsafeLoc; } - /// The location of the @isolated attribute, if any. - SourceLoc getIsolatedLoc() const { - return attributes.isolatedLoc; + /// The location of the isolated modifier, if any. + SourceLoc getNonisolatedLoc() const { + return attributes.nonisolatedLoc; } /// For an inherited conformance, retrieve the class declaration diff --git a/lib/AST/Decl.cpp b/lib/AST/Decl.cpp index bdc9c24b076fb..8adf1e85a95f0 100644 --- a/lib/AST/Decl.cpp +++ b/lib/AST/Decl.cpp @@ -1766,8 +1766,7 @@ NominalTypeDecl::takeConformanceLoaderSlow() { } InheritedEntry::InheritedEntry(const TypeLoc &typeLoc) - : InheritedEntry(typeLoc, ProtocolConformanceOptions(), - /*isPreconcurrency=*/false) { + : InheritedEntry(typeLoc, ProtocolConformanceOptions()) { if (auto typeRepr = typeLoc.getTypeRepr()) { if (typeRepr->findAttrLoc(TypeAttrKind::Unchecked).isValid()) setOption(ProtocolConformanceFlags::Unchecked); @@ -1777,8 +1776,19 @@ InheritedEntry::InheritedEntry(const TypeLoc &typeLoc) setOption(ProtocolConformanceFlags::Unsafe); if (typeRepr->findAttrLoc(TypeAttrKind::Preconcurrency).isValid()) setOption(ProtocolConformanceFlags::Preconcurrency); - if (typeRepr->findAttrLoc(TypeAttrKind::Isolated).isValid()) - setOption(ProtocolConformanceFlags::Isolated); + if (typeRepr->findAttrLoc(TypeAttrKind::Nonisolated).isValid()) + setOption(ProtocolConformanceFlags::Nonisolated); + + // Dig out the custom attribute that should be the global actor isolation. + if (auto customAttr = typeRepr->findCustomAttr()) { + if (!customAttr->hasArgs()) { + if (auto customAttrTypeExpr = customAttr->getTypeExpr()) { + globalActorIsolationType = customAttrTypeExpr; + RawOptions |= static_cast( + ProtocolConformanceFlags::GlobalActorIsolated); + } + } + } } } diff --git a/lib/AST/NameLookup.cpp b/lib/AST/NameLookup.cpp index 2b86c2887e36d..2db9e19db5996 100644 --- a/lib/AST/NameLookup.cpp +++ b/lib/AST/NameLookup.cpp @@ -3952,21 +3952,6 @@ CustomAttrNominalRequest::evaluate(Evaluator &evaluator, return nullptr; } -/// Find the location of 'isolated' within this type representation. -static SourceLoc findIsolatedLoc(TypeRepr *typeRepr) { - do { - if (auto isolatedTypeRepr = dyn_cast(typeRepr)) - return isolatedTypeRepr->getLoc(); - - if (auto attrTypeRepr = dyn_cast(typeRepr)) { - typeRepr = attrTypeRepr->getTypeRepr(); - continue; - } - - return SourceLoc(); - } while (true); -} - /// Decompose the ith inheritance clause entry to a list of type declarations, /// inverses, and optional AnyObject member. void swift::getDirectlyInheritedNominalTypeDecls( @@ -4005,9 +3990,17 @@ void swift::getDirectlyInheritedNominalTypeDecls( attributes.uncheckedLoc = typeRepr->findAttrLoc(TypeAttrKind::Unchecked); attributes.preconcurrencyLoc = typeRepr->findAttrLoc(TypeAttrKind::Preconcurrency); attributes.unsafeLoc = typeRepr->findAttrLoc(TypeAttrKind::Unsafe); - - // Look for an IsolatedTypeRepr. - attributes.isolatedLoc = findIsolatedLoc(typeRepr); + attributes.nonisolatedLoc = typeRepr->findAttrLoc(TypeAttrKind::Nonisolated); + + // Dig out the custom attribute that should be the global actor isolation. + if (auto customAttr = typeRepr->findCustomAttr()) { + if (!customAttr->hasArgs()) { + if (auto customAttrTypeExpr = customAttr->getTypeExpr()) { + attributes.globalActorAtLoc = customAttr->AtLoc; + attributes.globalActorType = customAttrTypeExpr; + } + } + } } // Form the result. diff --git a/lib/AST/ProtocolConformanceRef.cpp b/lib/AST/ProtocolConformanceRef.cpp index 507856a2402ae..d2eb023001b5c 100644 --- a/lib/AST/ProtocolConformanceRef.cpp +++ b/lib/AST/ProtocolConformanceRef.cpp @@ -406,7 +406,7 @@ bool ProtocolConformanceRef::forEachIsolatedConformance( auto concrete = getConcrete(); if (auto normal = dyn_cast(concrete->getRootConformance())) { - if (normal->isIsolated()) { + if (normal->isGlobalActorIsolated()) { if (body(concrete)) return true; } diff --git a/lib/AST/TypeCheckRequests.cpp b/lib/AST/TypeCheckRequests.cpp index 4ff02da84ae87..a716dcb3d454a 100644 --- a/lib/AST/TypeCheckRequests.cpp +++ b/lib/AST/TypeCheckRequests.cpp @@ -1368,6 +1368,22 @@ void AssociatedConformanceRequest::cacheResult( conformance->setAssociatedConformance(index, assocConf); } +//----------------------------------------------------------------------------// +// ConformanceIsolationRequest computation. +//----------------------------------------------------------------------------// +bool ConformanceIsolationRequest::isCached() const { + // We only want to cache for global-actor-isolated conformances. For + // everything else, which is nearly every conformance, this request quickly + // returns "nonisolated" so there is no point in caching it. + auto conformance = std::get<0>(getStorage()); + auto rootNormal = + dyn_cast(conformance->getRootConformance()); + if (!rootNormal) + return false; + + return rootNormal->isGlobalActorIsolated(); +} + //----------------------------------------------------------------------------// // HasCircularInheritedProtocolsRequest computation. //----------------------------------------------------------------------------// diff --git a/lib/AST/TypeRepr.cpp b/lib/AST/TypeRepr.cpp index ca58f16951bca..7bc5e99c2e42e 100644 --- a/lib/AST/TypeRepr.cpp +++ b/lib/AST/TypeRepr.cpp @@ -161,6 +161,20 @@ SourceLoc TypeRepr::findAttrLoc(TypeAttrKind kind) const { return SourceLoc(); } +CustomAttr *TypeRepr::findCustomAttr() const { + auto typeRepr = this; + while (auto attrTypeRepr = dyn_cast(typeRepr)) { + for (auto attr : attrTypeRepr->getAttrs()) { + if (auto typeAttr = attr.dyn_cast()) + return typeAttr; + } + + typeRepr = attrTypeRepr->getTypeRepr(); + } + + return nullptr; +} + DeclRefTypeRepr::DeclRefTypeRepr(TypeReprKind K, DeclNameRef Name, DeclNameLoc NameLoc, unsigned NumGenericArgs, bool HasAngleBrackets) diff --git a/lib/ASTGen/Sources/ASTGen/TypeAttrs.swift b/lib/ASTGen/Sources/ASTGen/TypeAttrs.swift index ffa84c81abb0c..beb82d46e09f6 100644 --- a/lib/ASTGen/Sources/ASTGen/TypeAttrs.swift +++ b/lib/ASTGen/Sources/ASTGen/TypeAttrs.swift @@ -53,6 +53,7 @@ extension ASTGenVisitor { .preconcurrency, .local, .noMetadata, + .nonisolated, .packGuaranteed, .packInout, .packOut, diff --git a/lib/IDE/CompletionLookup.cpp b/lib/IDE/CompletionLookup.cpp index 91939902395c2..7f2c8692b3778 100644 --- a/lib/IDE/CompletionLookup.cpp +++ b/lib/IDE/CompletionLookup.cpp @@ -3209,6 +3209,7 @@ void CompletionLookup::getTypeAttributeKeywordCompletions( switch (Kind) { case TypeAttrKind::Retroactive: case TypeAttrKind::Preconcurrency: + case TypeAttrKind::Nonisolated: case TypeAttrKind::Unchecked: case TypeAttrKind::Unsafe: // These attributes are only available in inheritance clasuses. diff --git a/lib/IRGen/GenProto.cpp b/lib/IRGen/GenProto.cpp index 8c5bcff7c4c33..98f04e65ee87b 100644 --- a/lib/IRGen/GenProto.cpp +++ b/lib/IRGen/GenProto.cpp @@ -2241,7 +2241,7 @@ namespace { Flags = Flags.withIsRetroactive(conf->isRetroactive()); Flags = Flags.withIsSynthesizedNonUnique(conf->isSynthesizedNonUnique()); Flags = Flags.withIsConformanceOfProtocol(conf->isConformanceOfProtocol()); - Flags = Flags.withHasGlobalActorIsolation(conf->isIsolated()); + Flags = Flags.withHasGlobalActorIsolation(conf->isGlobalActorIsolated()); } else { Flags = Flags.withIsRetroactive(false) .withIsSynthesizedNonUnique(false); @@ -2435,7 +2435,7 @@ namespace { return; auto normal = cast(Conformance); - assert(normal->isIsolated()); + assert(normal->isGlobalActorIsolated()); auto nominal = normal->getDeclContext()->getSelfNominalTypeDecl(); // Add global actor type. diff --git a/lib/Sema/TypeCheckConcurrency.cpp b/lib/Sema/TypeCheckConcurrency.cpp index 596141cb72842..b0ec75d5d3f09 100644 --- a/lib/Sema/TypeCheckConcurrency.cpp +++ b/lib/Sema/TypeCheckConcurrency.cpp @@ -7869,19 +7869,45 @@ bool swift::diagnoseNonSendableFromDeinit( } ActorIsolation swift::getConformanceIsolation(ProtocolConformance *conformance) { + ASTContext &ctx = conformance->getDeclContext()->getASTContext(); + return evaluateOrDefault( + ctx.evaluator, ConformanceIsolationRequest{conformance}, + ActorIsolation()); +} + +ActorIsolation +ConformanceIsolationRequest::evaluate(Evaluator &evaluator, ProtocolConformance *conformance) const { auto rootNormal = dyn_cast(conformance->getRootConformance()); if (!rootNormal) return ActorIsolation::forNonisolated(false); - if (!rootNormal->isIsolated()) + if (!rootNormal->isGlobalActorIsolated()) return ActorIsolation::forNonisolated(false); - auto nominal = rootNormal->getDeclContext()->getSelfNominalTypeDecl(); - if (!nominal) - return ActorIsolation::forNonisolated(false); + auto globalActorTypeExpr = rootNormal->getGlobalActorIsolation(); + assert(globalActorTypeExpr && "global actor type is out-of-sync"); + + // If we don't already have a resolved global actor type, resolve it now. + Type globalActorType = globalActorTypeExpr->getInstanceType(); + if (!globalActorType) { + const auto resolution = TypeResolution::forInterface( + rootNormal->getDeclContext(), std::nullopt, + /*unboundTyOpener*/ nullptr, + /*placeholderHandler*/ nullptr, + /*packElementOpener*/ nullptr); + globalActorType = resolution.resolveType(globalActorTypeExpr->getTypeRepr()); + if (!globalActorType) + return ActorIsolation::forNonisolated(false); + + // Cache the resolved type. + globalActorTypeExpr->setType(MetatypeType::get(globalActorType)); + } + + // FIXME: Make sure the type actually is a global actor type, map it into + // context, etc. - return getActorIsolation(nominal); + return ActorIsolation::forGlobalActor(globalActorType); } namespace { @@ -7919,7 +7945,7 @@ namespace { if (!normal) return false; - if (!normal->isIsolated()) + if (!normal->isGlobalActorIsolated()) return false; auto conformanceIsolation = getConformanceIsolation(concrete); diff --git a/lib/Sema/TypeCheckProtocol.cpp b/lib/Sema/TypeCheckProtocol.cpp index da71a261ad8c5..c70f637181627 100644 --- a/lib/Sema/TypeCheckProtocol.cpp +++ b/lib/Sema/TypeCheckProtocol.cpp @@ -2544,7 +2544,7 @@ checkIndividualConformance(NormalProtocolConformance *conformance) { // Complain if the conformance is isolated but the conforming type is // not global-actor-isolated. - if (conformance->isIsolated()) { + if (conformance->isGlobalActorIsolated()) { auto enclosingNominal = DC->getSelfNominalTypeDecl(); if (!enclosingNominal || !getActorIsolation(enclosingNominal).isGlobalActor()) { @@ -3403,7 +3403,7 @@ ConformanceChecker::checkActorIsolation(ValueDecl *requirement, // If the conformance itself is isolated, and the witness isolation // matches the enclosing type's isolation, treat this as being in the // same concurrency domain. - if (Conformance->isIsolated() && + if (Conformance->isGlobalActorIsolated() && refResult.isolation.isGlobalActor() && isolationMatchesEnclosingType( refResult.isolation, DC->getSelfNominalTypeDecl())) { @@ -3592,7 +3592,8 @@ ConformanceChecker::checkActorIsolation(ValueDecl *requirement, // Another way to address the issue is to mark the conformance as // "isolated" or "@preconcurrency". if (Conformance->getSourceKind() == ConformanceEntryKind::Explicit && - !Conformance->isIsolated() && !Conformance->isPreconcurrency() && + !Conformance->isGlobalActorIsolated() && + !Conformance->isPreconcurrency() && !suggestedPreconcurrencyOrIsolated && !requirementIsolation.isActorIsolated()) { if (Context.LangOpts.hasFeature(Feature::IsolatedConformances)) { @@ -5237,7 +5238,7 @@ static void ensureRequirementsAreSatisfied(ASTContext &ctx, [&](ProtocolConformance *isolatedConformance) { // If the conformance we're checking isn't isolated at all, it // needs "isolated". - if (!conformance->isIsolated()) { + if (!conformance->isGlobalActorIsolated()) { ctx.Diags.diagnose( conformance->getLoc(), diag::nonisolated_conformance_depends_on_isolated_conformance, diff --git a/lib/Sema/TypeCheckType.cpp b/lib/Sema/TypeCheckType.cpp index c3bbace5ebb8a..4e9e01609dded 100644 --- a/lib/Sema/TypeCheckType.cpp +++ b/lib/Sema/TypeCheckType.cpp @@ -3495,6 +3495,13 @@ TypeResolver::resolveAttributedType(TypeRepr *repr, TypeResolutionOptions option return false; }; + // If we're in an inheritance clause, check for a global actor. + if (options.is(TypeResolverContext::Inherited)) { + CustomAttr *customAttr = nullptr; + (void)resolveGlobalActor(repr->getLoc(), options, + customAttr, attrs); + } + if (handleInheritedOnly(claim(attrs)) || handleInheritedOnly(claim(attrs)) || handleInheritedOnly(claim(attrs))) diff --git a/lib/Serialization/Deserialization.cpp b/lib/Serialization/Deserialization.cpp index 2622fb178c9fb..26a8fe2284ffe 100644 --- a/lib/Serialization/Deserialization.cpp +++ b/lib/Serialization/Deserialization.cpp @@ -1018,12 +1018,15 @@ ProtocolConformanceDeserializer::readNormalProtocolConformance( DeclContextID contextID; unsigned valueCount, typeCount, conformanceCount; unsigned rawOptions; + TypeID globalActorTypeID; ArrayRef rawIDs; NormalProtocolConformanceLayout::readRecord(scratch, protoID, contextID, typeCount, valueCount, conformanceCount, - rawOptions, rawIDs); + rawOptions, + globalActorTypeID, + rawIDs); auto doOrError = MF.getDeclContextChecked(contextID); if (!doOrError) @@ -1042,10 +1045,19 @@ ProtocolConformanceDeserializer::readNormalProtocolConformance( PrettyStackTraceDecl traceTo("... to", proto); + auto globalActorTypeOrError = MF.getTypeChecked(globalActorTypeID); + if (!globalActorTypeOrError) + return globalActorTypeOrError.takeError(); + auto globalActorType = globalActorTypeOrError.get(); + + TypeExpr *globalActorTypeExpr = nullptr; + if (globalActorType) + globalActorTypeExpr = TypeExpr::createImplicit(globalActorType, ctx); + auto conformance = ctx.getNormalConformance( conformingType, proto, SourceLoc(), dc, ProtocolConformanceState::Incomplete, - ProtocolConformanceOptions(rawOptions)); + ProtocolConformanceOptions(rawOptions, globalActorTypeExpr)); if (conformance->isConformanceOfProtocol()) { auto &C = dc->getASTContext(); @@ -3345,8 +3357,8 @@ class DeclDeserializer { // The next bits are the protocol conformance options. // Update the mask below whenever this changes. - static_assert(NumProtocolConformanceOptions == 5); - ProtocolConformanceOptions options(rawID & 0x1F); + static_assert(NumProtocolConformanceOptions == 6); + ProtocolConformanceOptions options(rawID & 0x3F, /*global actor*/nullptr); rawID = rawID >> NumProtocolConformanceOptions; TypeID typeID = rawID; @@ -8658,6 +8670,7 @@ void ModuleFile::finishNormalConformance(NormalProtocolConformance *conformance, DeclID protoID; DeclContextID contextID; + TypeID globalActorTypeID; unsigned valueCount, typeCount, conformanceCount, rawOptions; ArrayRef rawIDs; SmallVector scratch; @@ -8670,7 +8683,7 @@ void ModuleFile::finishNormalConformance(NormalProtocolConformance *conformance, NormalProtocolConformanceLayout::readRecord( scratch, protoID, contextID, typeCount, valueCount, conformanceCount, - rawOptions, rawIDs); + rawOptions, globalActorTypeID, rawIDs); const ProtocolDecl *proto = conformance->getProtocol(); diff --git a/lib/Serialization/ModuleFormat.h b/lib/Serialization/ModuleFormat.h index d1384dcc53a51..0977f8f2baf4a 100644 --- a/lib/Serialization/ModuleFormat.h +++ b/lib/Serialization/ModuleFormat.h @@ -58,7 +58,7 @@ const uint16_t SWIFTMODULE_VERSION_MAJOR = 0; /// describe what change you made. The content of this comment isn't important; /// it just ensures a conflict if two people change the module format. /// Don't worry about adhering to the 80-column limit for this line. -const uint16_t SWIFTMODULE_VERSION_MINOR = 924; // ExtensibleEnums feature +const uint16_t SWIFTMODULE_VERSION_MINOR = 925; // isolated conformances /// A standard hash seed used for all string hashes in a serialized module. /// @@ -2086,6 +2086,7 @@ namespace decls_block { BCVBR<5>, // value mapping count BCVBR<5>, // requirement signature conformance count BCVBR<5>, // options + TypeIDField, // global actor isolation of conformance BCArray // The array contains requirement signature conformances, then // type witnesses, then value witnesses. diff --git a/lib/Serialization/Serialization.cpp b/lib/Serialization/Serialization.cpp index 8291dee8777a8..8bdd4d991181c 100644 --- a/lib/Serialization/Serialization.cpp +++ b/lib/Serialization/Serialization.cpp @@ -1927,6 +1927,27 @@ void Serializer::writeLocalNormalProtocolConformance( data.push_back(witness.getEnterIsolation().has_value() ? 1 : 0); }, /*useResolver=*/true); + // Figure out the isolation of the conformance. + Type globalActorType; + auto isolation = evaluateOrDefault( + getASTContext().evaluator, + ConformanceIsolationRequest{conformance}, swift::ActorIsolation()); + switch (isolation) { + case swift::ActorIsolation::Unspecified: + case swift::ActorIsolation::Nonisolated: + break; + + case swift::ActorIsolation::GlobalActor: + globalActorType = isolation.getGlobalActor(); + break; + + case swift::ActorIsolation::ActorInstance: + case swift::ActorIsolation::NonisolatedUnsafe: + case swift::ActorIsolation::Erased: + case swift::ActorIsolation::CallerIsolationInheriting: + llvm_unreachable("Conformances cannot have this kind of isolation"); + } + unsigned abbrCode = DeclTypeAbbrCodes[NormalProtocolConformanceLayout::Code]; auto ownerID = addDeclContextRef(conformance->getDeclContext()); @@ -1937,6 +1958,7 @@ void Serializer::writeLocalNormalProtocolConformance( numValueWitnesses, numSignatureConformances, conformance->getOptions().toRaw(), + addTypeRef(globalActorType), data); } @@ -4104,9 +4126,14 @@ class Serializer::DeclSerializer : public DeclVisitor { uint64_t typeRef = S.addTypeRef(inherited.getType()); uint64_t originalTypeRef = typeRef; - // Encode options in the low bits; + // Encode options in the low bits. + // Note that we drop the global actor isolation bit, because we don't + // serialize this information. This information is available in the + // conformance itself. + auto inheritedOptions = + inherited.getOptions() - ProtocolConformanceFlags::GlobalActorIsolated; typeRef = (typeRef << NumProtocolConformanceOptions) | - inherited.getOptions().toRaw(); + inheritedOptions.toRaw(); // Encode "suppressed" in the next bit. typeRef = (typeRef << 1) | (inherited.isSuppressed() ? 0x01 : 0x00); diff --git a/test/Concurrency/Runtime/isolated_conformance.swift b/test/Concurrency/Runtime/isolated_conformance.swift index 490c2b6186301..f7adcfc856825 100644 --- a/test/Concurrency/Runtime/isolated_conformance.swift +++ b/test/Concurrency/Runtime/isolated_conformance.swift @@ -17,7 +17,7 @@ protocol P { } @MainActor -class MyClass: isolated P { +class MyClass: @MainActor P { func f() { print("MyClass.f()") diff --git a/test/Concurrency/isolated_conformance.swift b/test/Concurrency/isolated_conformance.swift index 2db33ffcc5183..30494259c23fe 100644 --- a/test/Concurrency/isolated_conformance.swift +++ b/test/Concurrency/isolated_conformance.swift @@ -22,11 +22,11 @@ class CWithNonIsolated: P { actor SomeActor { } // Isolated conformances need a global-actor-constrained type. -class CNonIsolated: isolated P { // expected-error{{isolated conformance is only permitted on global-actor-isolated types}} +class CNonIsolated: @MainActor P { // expected-error{{isolated conformance is only permitted on global-actor-isolated types}} func f() { } } -extension SomeActor: isolated P { // expected-error{{isolated conformance is only permitted on global-actor-isolated types}} +extension SomeActor: @MainActor P { // expected-error{{isolated conformance is only permitted on global-actor-isolated types}} nonisolated func f() { } } @@ -35,14 +35,14 @@ struct SomeGlobalActor { static let shared = SomeActor() } -// Isolation of the function needs to match that of the enclosing type. +// Isolation of the conformance needs to match that of the enclosing type. @MainActor -class CMismatchedIsolation: isolated P { +class CMismatchedIsolation: @SomeGlobalActor P { @SomeGlobalActor func f() { } // expected-error{{global actor 'SomeGlobalActor'-isolated instance method 'f()' cannot be used to satisfy nonisolated requirement from protocol 'P'}} } @MainActor -class C: isolated P { +class C: @MainActor P { func f() { } // okay } @@ -69,18 +69,18 @@ struct SMissingIsolationViaWrapper: Q { } @SomeGlobalActor -class C2: isolated P { +class C2: @SomeGlobalActor P { func f() { } } @MainActor -struct S: isolated Q { +struct S: @MainActor Q { typealias A = C } // expected-error@+2{{main actor-isolated conformance of 'SMismatchedActors' to 'Q' cannot depend on global actor 'SomeGlobalActor'-isolated conformance of 'C2' to 'P'}} @MainActor -struct SMismatchedActors: isolated Q { +struct SMismatchedActors: @MainActor Q { typealias A = C2 } diff --git a/test/IRGen/isolated_conformance.swift b/test/IRGen/isolated_conformance.swift index f6e614c19c426..6240933188ed3 100644 --- a/test/IRGen/isolated_conformance.swift +++ b/test/IRGen/isolated_conformance.swift @@ -17,6 +17,6 @@ protocol P { // CHECK-SAME: ScM // CHECK-SAME: $sScMs11GlobalActorsMc" @MainActor -struct X: isolated P { +struct X: @MainActor P { func f() { } } diff --git a/test/Serialization/Inputs/def_isolated_conformance.swift b/test/Serialization/Inputs/def_isolated_conformance.swift new file mode 100644 index 0000000000000..a339a12a77764 --- /dev/null +++ b/test/Serialization/Inputs/def_isolated_conformance.swift @@ -0,0 +1,10 @@ +public protocol MyProtocol { + func f() +} + +@MainActor +public class MyClass { } + +extension MyClass: @MainActor MyProtocol { + @MainActor public func f() { } +} diff --git a/test/Serialization/isolated_conformance.swift b/test/Serialization/isolated_conformance.swift new file mode 100644 index 0000000000000..7cc433bd449d0 --- /dev/null +++ b/test/Serialization/isolated_conformance.swift @@ -0,0 +1,16 @@ +// RUN: %empty-directory(%t) +// RUN: %target-swift-frontend -emit-module -target %target-swift-5.1-abi-triple -swift-version 6 -enable-experimental-feature IsolatedConformances -o %t/def_isolated_conformance.swiftmodule %S/Inputs/def_isolated_conformance.swift + +// RUN: %target-swift-frontend -typecheck -verify -target %target-swift-5.1-abi-triple -swift-version 6 -enable-experimental-feature IsolatedConformances %s -I %t + +// REQUIRES: swift_feature_IsolatedConformances +// REQUIRES: concurrency + +import def_isolated_conformance + +func acceptMyProtocol(_: some MyProtocol) { } + +nonisolated func f(mc: MyClass) { + acceptMyProtocol(mc) + // expected-error@-1{{main actor-isolated isolated conformance of 'MyClass' to 'MyProtocol' cannot be used in nonisolated context}} +} From 71a516b5f7e021ce2afad846009429de636210e8 Mon Sep 17 00:00:00 2001 From: Doug Gregor Date: Wed, 12 Mar 2025 16:34:19 -0700 Subject: [PATCH 02/10] Print isolated conformances in textual interfaces --- lib/AST/ASTPrinter.cpp | 12 ++++++++++++ test/ModuleInterface/isolated_conformance.swift | 16 ++++++++++++++++ 2 files changed, 28 insertions(+) create mode 100644 test/ModuleInterface/isolated_conformance.swift diff --git a/lib/AST/ASTPrinter.cpp b/lib/AST/ASTPrinter.cpp index aba0c15f45729..53fd6837a1ff1 100644 --- a/lib/AST/ASTPrinter.cpp +++ b/lib/AST/ASTPrinter.cpp @@ -2924,6 +2924,18 @@ void PrintAST::printInherited(const Decl *decl) { Printer << "@unsafe "; break; } + + if (auto globalActor = inherited.getGlobalActorIsolationType()) { + TypeLoc globalActorTL(globalActor->getTypeRepr(), + globalActor->getInstanceType()); + Printer << "@"; + printTypeLoc(globalActorTL); + Printer << " "; + } + + if (inherited.isNonisolated()) + Printer << "nonisolated "; + if (inherited.isSuppressed()) Printer << "~"; }); diff --git a/test/ModuleInterface/isolated_conformance.swift b/test/ModuleInterface/isolated_conformance.swift new file mode 100644 index 0000000000000..53fb38ae3b7fb --- /dev/null +++ b/test/ModuleInterface/isolated_conformance.swift @@ -0,0 +1,16 @@ +// RUN: %target-swift-frontend -typecheck -swift-version 6 -enable-library-evolution -module-name isolated_conformance -enable-experimental-feature IsolatedConformances -emit-module-interface-path - %s | %FileCheck %s + +// REQUIRES: swift_feature_IsolatedConformances +// REQUIRES: concurrency + +public protocol MyProtocol { + func f() +} + +@MainActor +public class MyClass { } + +// CHECK: extension isolated_conformance.MyClass : @MainActor isolated_conformance.MyProtocol { +extension MyClass: @MainActor MyProtocol { + @MainActor public func f() { } +} From 4f2476116d045ecfa1d5ad1c538976f9b100f71c Mon Sep 17 00:00:00 2001 From: Doug Gregor Date: Wed, 12 Mar 2025 16:46:35 -0700 Subject: [PATCH 03/10] Suggest global actor isolation rather than 'isolated' for conformances Update diagnostic and Fix-It to suggest global actor isolation on a conformance (e.g, `@MainActor P`) rather than `isolated`. --- include/swift/AST/DiagnosticsSema.def | 4 ++-- lib/Sema/TypeCheckProtocol.cpp | 11 ++++++++--- test/Concurrency/isolated_conformance.swift | 2 +- 3 files changed, 11 insertions(+), 6 deletions(-) diff --git a/include/swift/AST/DiagnosticsSema.def b/include/swift/AST/DiagnosticsSema.def index 11c7acea4058a..72243f118876c 100644 --- a/include/swift/AST/DiagnosticsSema.def +++ b/include/swift/AST/DiagnosticsSema.def @@ -8332,8 +8332,8 @@ ERROR(isolated_conformance_mismatch_with_associated_isolation,none, "%0 conformance of %1 to %2 cannot depend on %3 conformance of %4 to %5", (ActorIsolation, Type, DeclName, ActorIsolation, Type, DeclName)) NOTE(add_isolated_to_conformance,none, - "add 'isolated' to the %0 conformance to restrict it to %1 code", - (DeclName, ActorIsolation)) + "add '%0' to the %1 conformance to restrict it to %2 code", + (StringRef, DeclName, ActorIsolation)) ERROR(isolated_conformance_with_sendable,none, "isolated conformance of %0 to %1 cannot be used to satisfy conformance " "requirement for a %select{`Sendable`|`SendableMetatype`}2 type " diff --git a/lib/Sema/TypeCheckProtocol.cpp b/lib/Sema/TypeCheckProtocol.cpp index c70f637181627..dc6917044a35a 100644 --- a/lib/Sema/TypeCheckProtocol.cpp +++ b/lib/Sema/TypeCheckProtocol.cpp @@ -3590,17 +3590,22 @@ ConformanceChecker::checkActorIsolation(ValueDecl *requirement, } // Another way to address the issue is to mark the conformance as - // "isolated" or "@preconcurrency". + // isolated to the global actor or "@preconcurrency". if (Conformance->getSourceKind() == ConformanceEntryKind::Explicit && !Conformance->isGlobalActorIsolated() && !Conformance->isPreconcurrency() && !suggestedPreconcurrencyOrIsolated && - !requirementIsolation.isActorIsolated()) { + !requirementIsolation.isActorIsolated() && + refResult.isolation.isGlobalActor()) { if (Context.LangOpts.hasFeature(Feature::IsolatedConformances)) { + std::string globalActorStr = "@" + + refResult.isolation.getGlobalActor().getString(); Context.Diags.diagnose(Conformance->getProtocolNameLoc(), diag::add_isolated_to_conformance, + globalActorStr, Proto->getName(), refResult.isolation) - .fixItInsert(Conformance->getProtocolNameLoc(), "isolated "); + .fixItInsert(Conformance->getProtocolNameLoc(), + globalActorStr + " "); } Context.Diags.diagnose(Conformance->getProtocolNameLoc(), diff --git a/test/Concurrency/isolated_conformance.swift b/test/Concurrency/isolated_conformance.swift index 30494259c23fe..d5840cdf8e55f 100644 --- a/test/Concurrency/isolated_conformance.swift +++ b/test/Concurrency/isolated_conformance.swift @@ -12,7 +12,7 @@ protocol P { // ---------------------------------------------------------------------------- // expected-note@+3{{add '@preconcurrency' to the 'P' conformance to defer isolation checking to run time}}{{25-25=@preconcurrency }} -// expected-note@+2{{add 'isolated' to the 'P' conformance to restrict it to main actor-isolated code}}{{25-25=isolated }} +// expected-note@+2{{add '@MainActor' to the 'P' conformance to restrict it to main actor-isolated code}}{{25-25=@MainActor }} @MainActor class CWithNonIsolated: P { func f() { } // expected-error{{main actor-isolated instance method 'f()' cannot be used to satisfy nonisolated requirement from protocol 'P'}} From 3837661b84ff80a40350912d45254ddf3009e415 Mon Sep 17 00:00:00 2001 From: Doug Gregor Date: Wed, 12 Mar 2025 17:43:16 -0700 Subject: [PATCH 04/10] [Isolated conformances] Allow conformance isolation to differ from the type's With the move to explicitly specifying the global actor for an isolated conformance, we can now have conformances whose isolation differs from that of the type, including having actors with global-actor-isolated conformances. Introduce this generalization to match the proposal, and update/add tests accordingly. --- include/swift/AST/DiagnosticsSema.def | 19 ++--- lib/Sema/CSDiagnostics.cpp | 4 +- lib/Sema/TypeCheckGeneric.cpp | 4 +- lib/Sema/TypeCheckProtocol.cpp | 45 ++++------- .../Runtime/isolated_conformance.swift | 78 +++++++++++++++++-- test/Concurrency/isolated_conformance.swift | 39 ++++++---- .../isolated_conformance.swift | 2 +- test/Serialization/isolated_conformance.swift | 2 +- 8 files changed, 129 insertions(+), 64 deletions(-) diff --git a/include/swift/AST/DiagnosticsSema.def b/include/swift/AST/DiagnosticsSema.def index 72243f118876c..683897eddeb75 100644 --- a/include/swift/AST/DiagnosticsSema.def +++ b/include/swift/AST/DiagnosticsSema.def @@ -8319,15 +8319,12 @@ ERROR(attr_abi_incompatible_with_silgen_name,none, //===----------------------------------------------------------------------===// // MARK: Isolated conformances //===----------------------------------------------------------------------===// -ERROR(isolated_conformance_not_global_actor_isolated,none, - "isolated conformance is only permitted on global-actor-isolated types", - ()) ERROR(isolated_conformance_experimental_feature,none, "isolated conformances require experimental feature " " 'IsolatedConformances'", ()) ERROR(nonisolated_conformance_depends_on_isolated_conformance,none, - "conformance of %0 to %1 depends on %2 conformance of %3 to %4; mark it as 'isolated'", - (Type, DeclName, ActorIsolation, Type, DeclName)) + "conformance of %0 to %1 depends on %2 conformance of %3 to %4; mark it as '%5'", + (Type, DeclName, ActorIsolation, Type, DeclName, StringRef)) ERROR(isolated_conformance_mismatch_with_associated_isolation,none, "%0 conformance of %1 to %2 cannot depend on %3 conformance of %4 to %5", (ActorIsolation, Type, DeclName, ActorIsolation, Type, DeclName)) @@ -8335,15 +8332,15 @@ NOTE(add_isolated_to_conformance,none, "add '%0' to the %1 conformance to restrict it to %2 code", (StringRef, DeclName, ActorIsolation)) ERROR(isolated_conformance_with_sendable,none, - "isolated conformance of %0 to %1 cannot be used to satisfy conformance " + "%4 conformance of %0 to %1 cannot be used to satisfy conformance " "requirement for a %select{`Sendable`|`SendableMetatype`}2 type " - "parameter %3", (Type, DeclName, bool, Type)) + "parameter %3", (Type, DeclName, bool, Type, ActorIsolation)) ERROR(isolated_conformance_with_sendable_simple,none, - "isolated conformance of %0 to %1 cannot be used to satisfy conformance " - "requirement for a `Sendable` type parameter ", - (Type, DeclName)) + "%2 conformance of %0 to %1 cannot be used to satisfy " + "conformance requirement for a `Sendable` type parameter ", + (Type, DeclName, ActorIsolation)) ERROR(isolated_conformance_wrong_domain,none, - "%0 isolated conformance of %1 to %2 cannot be used in %3 context", + "%0 conformance of %1 to %2 cannot be used in %3 context", (ActorIsolation, Type, DeclName, ActorIsolation)) //===----------------------------------------------------------------------===// diff --git a/lib/Sema/CSDiagnostics.cpp b/lib/Sema/CSDiagnostics.cpp index 796545f92e312..cb78366d28d45 100644 --- a/lib/Sema/CSDiagnostics.cpp +++ b/lib/Sema/CSDiagnostics.cpp @@ -16,6 +16,7 @@ #include "CSDiagnostics.h" #include "MiscDiagnostics.h" +#include "TypeCheckConcurrency.h" #include "TypeCheckProtocol.h" #include "TypeCheckType.h" #include "TypoCorrection.h" @@ -9566,7 +9567,8 @@ bool IncorrectInlineArrayLiteralCount::diagnoseAsError() { bool DisallowedIsolatedConformance::diagnoseAsError() { emitDiagnostic(diag::isolated_conformance_with_sendable_simple, conformance->getType(), - conformance->getProtocol()->getName()); + conformance->getProtocol()->getName(), + getConformanceIsolation(conformance)); auto selectedOverload = getCalleeOverloadChoiceIfAvailable(getLocator()); if (!selectedOverload) diff --git a/lib/Sema/TypeCheckGeneric.cpp b/lib/Sema/TypeCheckGeneric.cpp index 586a976929bfb..d9bd251fd4eb7 100644 --- a/lib/Sema/TypeCheckGeneric.cpp +++ b/lib/Sema/TypeCheckGeneric.cpp @@ -13,6 +13,7 @@ // This file implements support for generics. // //===----------------------------------------------------------------------===// +#include "TypeCheckConcurrency.h" #include "TypeCheckProtocol.h" #include "TypeCheckType.h" #include "TypeChecker.h" @@ -936,7 +937,8 @@ void TypeChecker::diagnoseRequirementFailure( reqFailureInfo .IsolatedConformanceProto->isSpecificProtocol( KnownProtocolKind::SendableMetatype), - req.getFirstType()); + req.getFirstType(), + getConformanceIsolation(isolatedConformance)); diagnosticNote = diag::type_does_not_inherit_or_conform_requirement; break; } diff --git a/lib/Sema/TypeCheckProtocol.cpp b/lib/Sema/TypeCheckProtocol.cpp index dc6917044a35a..4a75f656b98a7 100644 --- a/lib/Sema/TypeCheckProtocol.cpp +++ b/lib/Sema/TypeCheckProtocol.cpp @@ -2542,20 +2542,11 @@ checkIndividualConformance(NormalProtocolConformance *conformance) { ComplainLoc, diag::unchecked_conformance_not_special, ProtoType); } - // Complain if the conformance is isolated but the conforming type is - // not global-actor-isolated. - if (conformance->isGlobalActorIsolated()) { - auto enclosingNominal = DC->getSelfNominalTypeDecl(); - if (!enclosingNominal || - !getActorIsolation(enclosingNominal).isGlobalActor()) { - Context.Diags.diagnose( - ComplainLoc, diag::isolated_conformance_not_global_actor_isolated); - } - - if (!Context.LangOpts.hasFeature(Feature::IsolatedConformances)) { - Context.Diags.diagnose( - ComplainLoc, diag::isolated_conformance_experimental_feature); - } + // Complain if the global-actor-isolated conformances are not enabled. + if (conformance->isGlobalActorIsolated() && + !Context.LangOpts.hasFeature(Feature::IsolatedConformances)) { + Context.Diags.diagnose( + ComplainLoc, diag::isolated_conformance_experimental_feature); } bool allowImpliedConditionalConformance = false; @@ -3330,14 +3321,6 @@ static bool hasExplicitGlobalActorAttr(ValueDecl *decl) { return !globalActorAttr->first->isImplicit(); } -/// Determine whether the given actor isolation matches that of the enclosing -/// type. -static bool isolationMatchesEnclosingType( - ActorIsolation isolation, NominalTypeDecl *nominal) { - auto nominalIsolation = getActorIsolation(nominal); - return isolation == nominalIsolation; -} - std::optional ConformanceChecker::checkActorIsolation(ValueDecl *requirement, ValueDecl *witness, @@ -3400,13 +3383,10 @@ ConformanceChecker::checkActorIsolation(ValueDecl *requirement, return std::nullopt; case ActorReferenceResult::EntersActor: - // If the conformance itself is isolated, and the witness isolation - // matches the enclosing type's isolation, treat this as being in the - // same concurrency domain. + // If the conformance itself is isolated to the same isolation domain as + // the witness, treat this as being in the same concurrency domain. if (Conformance->isGlobalActorIsolated() && - refResult.isolation.isGlobalActor() && - isolationMatchesEnclosingType( - refResult.isolation, DC->getSelfNominalTypeDecl())) { + refResult.isolation == getConformanceIsolation(Conformance)) { sameConcurrencyDomain = true; isIsolatedConformance = true; } @@ -5244,14 +5224,19 @@ static void ensureRequirementsAreSatisfied(ASTContext &ctx, // If the conformance we're checking isn't isolated at all, it // needs "isolated". if (!conformance->isGlobalActorIsolated()) { + auto isolation = getConformanceIsolation(isolatedConformance); + std::string globalActorStr = "@" + + isolation.getGlobalActor().getString(); ctx.Diags.diagnose( conformance->getLoc(), diag::nonisolated_conformance_depends_on_isolated_conformance, typeInContext, conformance->getProtocol()->getName(), getConformanceIsolation(isolatedConformance), isolatedConformance->getType(), - isolatedConformance->getProtocol()->getName() - ).fixItInsert(conformance->getProtocolNameLoc(), "isolated "); + isolatedConformance->getProtocol()->getName(), + globalActorStr + ).fixItInsert(conformance->getProtocolNameLoc(), + globalActorStr + " "); return true; } diff --git a/test/Concurrency/Runtime/isolated_conformance.swift b/test/Concurrency/Runtime/isolated_conformance.swift index f7adcfc856825..d9c3327363978 100644 --- a/test/Concurrency/Runtime/isolated_conformance.swift +++ b/test/Concurrency/Runtime/isolated_conformance.swift @@ -16,8 +16,11 @@ protocol P { func f() } -@MainActor -class MyClass: @MainActor P { +protocol Q { + func g() +} + +nonisolated class MyClass: @MainActor P { func f() { print("MyClass.f()") @@ -26,6 +29,22 @@ class MyClass: @MainActor P { } } +actor SomeActor { } + +@globalActor +struct SomeGlobalActor { + static let shared = SomeActor() +} + +extension MyClass: @SomeGlobalActor Q { + @SomeGlobalActor func g() { + print("MyClass.g()") + + // Make sure we're on this actor. + SomeGlobalActor.shared.assumeIsolated { _ in } + } +} + struct Wrapper { var wrapped: T } @@ -37,6 +56,13 @@ extension Wrapper: P where T: P { } } +extension Wrapper: Q where T: Q { + func g() { + print("Wrapper for ", terminator: "") + wrapped.g() + } +} + @available(SwiftStdlib 5.9, *) struct WrapMany { var wrapped: (repeat each T) @@ -49,12 +75,21 @@ extension WrapMany: P where repeat each T: P { } } -extension Int: P { +@available(SwiftStdlib 5.9, *) +extension WrapMany: Q where repeat each T: Q { + func g() { + print("Wrapper for many") + } +} + +extension Int: P, Q { func f() { } + func g() { } } -extension String: P { +extension String: P, Q { func f() { } + func g() { } } func tryCastToP(_ value: any Sendable) -> Bool { @@ -67,12 +102,22 @@ func tryCastToP(_ value: any Sendable) -> Bool { return false } +func tryCastToQ(_ value: any Sendable) -> Bool { + if let q = value as? any Q { + q.g() + return true + } + + print("Conformance did not match") + return false +} + // CHECK: Testing on the main actor // CHECK-NEXT: MyClass.f() // CHECK-NEXT: Wrapper for MyClass.f() print("Testing on the main actor") -let mc = MyClass() -let wrappedMC = Wrapper(wrapped: mc) +nonisolated let mc = MyClass() +nonisolated let wrappedMC = Wrapper(wrapped: mc) precondition(tryCastToP(mc)) precondition(tryCastToP(wrappedMC)) @@ -96,6 +141,24 @@ await Task.detached { @MainActor in }.value +// CHECK: Testing a separate task on a different global actor +// CHECK-NEXT: MyClass.g() +// CHECK-NEXT: Wrapper for MyClass.g() +print("Testing a separate task on a different global actor") +await Task.detached { @SomeGlobalActor in + precondition(tryCastToQ(mc)) + precondition(tryCastToQ(wrappedMC)) + + if #available(SwiftStdlib 5.9, *) { + let wrappedMany = WrapMany(wrapped: (17, mc, "Pack")) + precondition(tryCastToQ(wrappedMany)) + } + + // Not on the main actor any more. + precondition(!tryCastToP(mc)) + precondition(!tryCastToP(wrappedMC)) +}.value + // CHECK: Testing a separate task off the main actor print("Testing a separate task off the main actor") await Task.detached { @@ -103,6 +166,9 @@ await Task.detached { precondition(!tryCastToP(mc)) precondition(!tryCastToP(wrappedMC)) + precondition(!tryCastToQ(mc)) + precondition(!tryCastToQ(wrappedMC)) + let wrappedMany = WrapMany(wrapped: (17, mc, "Pack")) precondition(!tryCastToP(wrappedMany)) } else { diff --git a/test/Concurrency/isolated_conformance.swift b/test/Concurrency/isolated_conformance.swift index d5840cdf8e55f..1873846ff4ae3 100644 --- a/test/Concurrency/isolated_conformance.swift +++ b/test/Concurrency/isolated_conformance.swift @@ -4,7 +4,7 @@ // REQUIRES: concurrency protocol P { - func f() // expected-note 2{{mark the protocol requirement 'f()' 'async' to allow actor-isolated conformances}} + func f() // expected-note{{mark the protocol requirement 'f()' 'async' to allow actor-isolated conformances}} } // ---------------------------------------------------------------------------- @@ -22,12 +22,12 @@ class CWithNonIsolated: P { actor SomeActor { } // Isolated conformances need a global-actor-constrained type. -class CNonIsolated: @MainActor P { // expected-error{{isolated conformance is only permitted on global-actor-isolated types}} +class CNonIsolated: @MainActor P { func f() { } } -extension SomeActor: @MainActor P { // expected-error{{isolated conformance is only permitted on global-actor-isolated types}} - nonisolated func f() { } +extension SomeActor: @MainActor P { + @MainActor func f() { } } @globalActor @@ -35,10 +35,11 @@ struct SomeGlobalActor { static let shared = SomeActor() } -// Isolation of the conformance needs to match that of the enclosing type. +// Isolation of the conformance can be different from that of the enclosing +// type, so long as the witnesses match up. @MainActor class CMismatchedIsolation: @SomeGlobalActor P { - @SomeGlobalActor func f() { } // expected-error{{global actor 'SomeGlobalActor'-isolated instance method 'f()' cannot be used to satisfy nonisolated requirement from protocol 'P'}} + @SomeGlobalActor func f() { } } @MainActor @@ -52,7 +53,7 @@ protocol Q { associatedtype A: P } -// expected-error@+2{{conformance of 'SMissingIsolation' to 'Q' depends on main actor-isolated conformance of 'C' to 'P'; mark it as 'isolated'}}{{27-27=isolated }} +// expected-error@+2{{conformance of 'SMissingIsolation' to 'Q' depends on main actor-isolated conformance of 'C' to 'P'; mark it as '@MainActor'}}{{27-27=@MainActor }} @MainActor struct SMissingIsolation: Q { typealias A = C @@ -62,7 +63,7 @@ struct PWrapper: P { func f() { } } -// expected-error@+2{{conformance of 'SMissingIsolationViaWrapper' to 'Q' depends on main actor-isolated conformance of 'C' to 'P'; mark it as 'isolated'}} +// expected-error@+2{{conformance of 'SMissingIsolationViaWrapper' to 'Q' depends on main actor-isolated conformance of 'C' to 'P'; mark it as '@MainActor'}} @MainActor struct SMissingIsolationViaWrapper: Q { typealias A = PWrapper @@ -111,18 +112,30 @@ func acceptSendableP(_: T) { } // expected-note@-1{{'acceptSendableP' declared here}} func acceptSendableMetaP(_: T) { } -// expected-note@-1{{'acceptSendableMetaP' declared here}} +// expected-note@-1 3{{'acceptSendableMetaP' declared here}} @MainActor func testIsolationConformancesInCall(c: C) { acceptP(c) // okay - acceptSendableP(c) // expected-error{{isolated conformance of 'C' to 'P' cannot be used to satisfy conformance requirement for a `Sendable` type parameter}} + acceptSendableP(c) // expected-error{{main actor-isolated conformance of 'C' to 'P' cannot be used to satisfy conformance requirement for a `Sendable` type parameter}} acceptSendableMetaP(c) // expected-error{{isolated conformance of 'C' to 'P' cannot be used to satisfy conformance requirement for a `Sendable` type parameter}} } +@MainActor +func testIsolatedConformancesOfActor(a: SomeActor) { + acceptP(a) + acceptSendableMetaP(a) // expected-error{{main actor-isolated conformance of 'SomeActor' to 'P' cannot be used to satisfy conformance requirement for a `Sendable` type parameter}} +} + +@SomeGlobalActor +func testIsolatedConformancesOfOtherGlobalActor(c: CMismatchedIsolation) { + acceptP(c) + acceptSendableMetaP(c) // expected-error{{global actor 'SomeGlobalActor'-isolated conformance of 'CMismatchedIsolation' to 'P' cannot be used to satisfy conformance requirement for a `Sendable` type parameter}} +} + func testIsolationConformancesFromOutside(c: C) { - acceptP(c) // expected-error{{main actor-isolated isolated conformance of 'C' to 'P' cannot be used in nonisolated context}} - let _: any P = c // expected-error{{main actor-isolated isolated conformance of 'C' to 'P' cannot be used in nonisolated context}} - let _ = PWrapper() // expected-error{{main actor-isolated isolated conformance of 'C' to 'P' cannot be used in nonisolated context}} + acceptP(c) // expected-error{{main actor-isolated conformance of 'C' to 'P' cannot be used in nonisolated context}} + let _: any P = c // expected-error{{main actor-isolated conformance of 'C' to 'P' cannot be used in nonisolated context}} + let _ = PWrapper() // expected-error{{main actor-isolated conformance of 'C' to 'P' cannot be used in nonisolated context}} } diff --git a/test/ModuleInterface/isolated_conformance.swift b/test/ModuleInterface/isolated_conformance.swift index 53fb38ae3b7fb..86010ed6e85b3 100644 --- a/test/ModuleInterface/isolated_conformance.swift +++ b/test/ModuleInterface/isolated_conformance.swift @@ -10,7 +10,7 @@ public protocol MyProtocol { @MainActor public class MyClass { } -// CHECK: extension isolated_conformance.MyClass : @MainActor isolated_conformance.MyProtocol { +// CHECK: extension isolated_conformance.MyClass : @{{.*}}MainActor isolated_conformance.MyProtocol { extension MyClass: @MainActor MyProtocol { @MainActor public func f() { } } diff --git a/test/Serialization/isolated_conformance.swift b/test/Serialization/isolated_conformance.swift index 7cc433bd449d0..beeb48aa1068a 100644 --- a/test/Serialization/isolated_conformance.swift +++ b/test/Serialization/isolated_conformance.swift @@ -12,5 +12,5 @@ func acceptMyProtocol(_: some MyProtocol) { } nonisolated func f(mc: MyClass) { acceptMyProtocol(mc) - // expected-error@-1{{main actor-isolated isolated conformance of 'MyClass' to 'MyProtocol' cannot be used in nonisolated context}} + // expected-error@-1{{main actor-isolated conformance of 'MyClass' to 'MyProtocol' cannot be used in nonisolated context}} } From 0cfa048c0af23653b074fb06a94589602995d665 Mon Sep 17 00:00:00 2001 From: Doug Gregor Date: Wed, 12 Mar 2025 18:30:56 -0700 Subject: [PATCH 05/10] [Isolated conformances] Start parsing 'nonisolated' on conformances It doesn't mean anything yet, but parse it and make sure it reproduces in the module interface. --- lib/Parse/ParseType.cpp | 9 +++++++++ lib/Sema/TypeCheckType.cpp | 3 ++- test/ModuleInterface/isolated_conformance.swift | 6 ++++++ 3 files changed, 17 insertions(+), 1 deletion(-) diff --git a/lib/Parse/ParseType.cpp b/lib/Parse/ParseType.cpp index c5428f1c03d62..00934a7cd6869 100644 --- a/lib/Parse/ParseType.cpp +++ b/lib/Parse/ParseType.cpp @@ -413,6 +413,15 @@ ParserResult Parser::parseTypeScalar( return makeParserCodeCompletionResult(ET); } + // "nonisolated" for attribute lists. + if (reason == ParseTypeReason::InheritanceClause && + Tok.isContextualKeyword("nonisolated")) { + SourceLoc nonisolatedLoc = consumeToken(); + parsedAttributeList.Attributes.push_back( + TypeAttribute::createSimple(Context, TypeAttrKind::Nonisolated, + SourceLoc(), nonisolatedLoc)); + } + // Parse generic parameters in SIL mode. GenericParamList *generics = nullptr; SourceLoc substitutedLoc; diff --git a/lib/Sema/TypeCheckType.cpp b/lib/Sema/TypeCheckType.cpp index 4e9e01609dded..ed1f91f6ccb2c 100644 --- a/lib/Sema/TypeCheckType.cpp +++ b/lib/Sema/TypeCheckType.cpp @@ -3504,7 +3504,8 @@ TypeResolver::resolveAttributedType(TypeRepr *repr, TypeResolutionOptions option if (handleInheritedOnly(claim(attrs)) || handleInheritedOnly(claim(attrs)) || - handleInheritedOnly(claim(attrs))) + handleInheritedOnly(claim(attrs)) || + handleInheritedOnly(claim(attrs))) return ty; if (auto retroactiveAttr = claim(attrs)) { diff --git a/test/ModuleInterface/isolated_conformance.swift b/test/ModuleInterface/isolated_conformance.swift index 86010ed6e85b3..23d93fcdd4572 100644 --- a/test/ModuleInterface/isolated_conformance.swift +++ b/test/ModuleInterface/isolated_conformance.swift @@ -14,3 +14,9 @@ public class MyClass { } extension MyClass: @MainActor MyProtocol { @MainActor public func f() { } } + +extension MyClass: nonisolated Equatable { + nonisolated public static func ==(lhs: MyClass, rhs: MyClass) -> Bool { + false + } +} From c58e22fccc0badfa58b90171702131c447196a86 Mon Sep 17 00:00:00 2001 From: Doug Gregor Date: Wed, 12 Mar 2025 22:33:00 -0700 Subject: [PATCH 06/10] Simplify APIs for accessing conformance isolation The NormalProtocolConformance APIs for checking for an explicitly-written isolation on a conformance were easy to get to, and the real semantic API was buried in the type checker, leading to some unprincipled checking. Instead, create a central ProtocolConformance::getIsolation() to get the (semantic) actor isolation, and let that be the only place that will access the explicitly-written global actor isolation for a conformance. Update all call sites appropriately. --- include/swift/AST/ProtocolConformance.h | 22 ++++++++++++---------- lib/AST/ProtocolConformance.cpp | 11 +++++++++++ lib/AST/ProtocolConformanceRef.cpp | 2 +- lib/AST/TypeCheckRequests.cpp | 13 ++++++++++++- lib/IRGen/GenProto.cpp | 12 +++--------- lib/Sema/CSDiagnostics.cpp | 2 +- lib/Sema/TypeCheckConcurrency.cpp | 19 +++++++++---------- lib/Sema/TypeCheckConcurrency.h | 4 ---- lib/Sema/TypeCheckGeneric.cpp | 2 +- lib/Sema/TypeCheckProtocol.cpp | 24 +++++++++++++----------- 10 files changed, 63 insertions(+), 48 deletions(-) diff --git a/include/swift/AST/ProtocolConformance.h b/include/swift/AST/ProtocolConformance.h index 30f04f36dc7c0..4c8d0944676e3 100644 --- a/include/swift/AST/ProtocolConformance.h +++ b/include/swift/AST/ProtocolConformance.h @@ -240,6 +240,14 @@ class alignas(1 << DeclAlignInBits) ProtocolConformance /// Otherwise a new conformance will be created. ProtocolConformance *getCanonicalConformance(); + /// Determine the actor isolation of this conformance. + ActorIsolation getIsolation() const; + + /// Determine whether this conformance is isolated to an actor. + bool isIsolated() const { + return getIsolation().isActorIsolated(); + } + /// Return true if the conformance has a witness for the given associated /// type. bool hasTypeWitness(AssociatedTypeDecl *assocType) const; @@ -529,6 +537,7 @@ class NormalProtocolConformance : public RootProtocolConformance, { friend class ValueWitnessRequest; friend class TypeWitnessRequest; + friend class ConformanceIsolationRequest; /// The protocol being conformed to. ProtocolDecl *Protocol; @@ -573,6 +582,9 @@ class NormalProtocolConformance : public RootProtocolConformance, void resolveLazyInfo() const; + /// Set up global actor isolation for this conformance. + void setGlobalActorIsolation(Type globalActorType); + public: NormalProtocolConformance(Type conformingType, ProtocolDecl *protocol, SourceLoc loc, DeclContext *dc, @@ -674,16 +686,6 @@ class NormalProtocolConformance : public RootProtocolConformance, return getOptions().contains(ProtocolConformanceFlags::Preconcurrency); } - /// Whether this is a global-actor isolated conformance. - bool isGlobalActorIsolated() const { - return getOptions().contains( - ProtocolConformanceFlags::GlobalActorIsolated); - } - - TypeExpr *getGlobalActorIsolation() const { - return globalActorIsolation; - } - /// Retrieve the location of `@preconcurrency`, if there is one and it is /// known. SourceLoc getPreconcurrencyLoc() const { return PreconcurrencyLoc; } diff --git a/lib/AST/ProtocolConformance.cpp b/lib/AST/ProtocolConformance.cpp index 056a30f50a6d4..b2b391dc93b2c 100644 --- a/lib/AST/ProtocolConformance.cpp +++ b/lib/AST/ProtocolConformance.cpp @@ -474,6 +474,17 @@ void NormalProtocolConformance::resolveLazyInfo() const { loader->finishNormalConformance(mutableThis, LoaderContextData); } +void NormalProtocolConformance::setGlobalActorIsolation(Type globalActorType) { + ASTContext &ctx = getDeclContext()->getASTContext(); + if (globalActorIsolation) + globalActorIsolation->setType(MetatypeType::get(globalActorType)); + else + globalActorIsolation = TypeExpr::createImplicit(globalActorType, ctx); + + Bits.NormalProtocolConformance.Options |= + static_cast(ProtocolConformanceFlags::GlobalActorIsolated); +} + void NormalProtocolConformance::setLazyLoader(LazyConformanceLoader *loader, uint64_t contextData) { assert(!Loader && "already has a loader"); diff --git a/lib/AST/ProtocolConformanceRef.cpp b/lib/AST/ProtocolConformanceRef.cpp index d2eb023001b5c..507856a2402ae 100644 --- a/lib/AST/ProtocolConformanceRef.cpp +++ b/lib/AST/ProtocolConformanceRef.cpp @@ -406,7 +406,7 @@ bool ProtocolConformanceRef::forEachIsolatedConformance( auto concrete = getConcrete(); if (auto normal = dyn_cast(concrete->getRootConformance())) { - if (normal->isGlobalActorIsolated()) { + if (normal->isIsolated()) { if (body(concrete)) return true; } diff --git a/lib/AST/TypeCheckRequests.cpp b/lib/AST/TypeCheckRequests.cpp index a716dcb3d454a..5a8d8654bce9e 100644 --- a/lib/AST/TypeCheckRequests.cpp +++ b/lib/AST/TypeCheckRequests.cpp @@ -1381,7 +1381,18 @@ bool ConformanceIsolationRequest::isCached() const { if (!rootNormal) return false; - return rootNormal->isGlobalActorIsolated(); + if (rootNormal->globalActorIsolation) + return true; + + // If we might infer conformance isolation, then we should cache the result. + auto dc = rootNormal->getDeclContext(); + auto nominal = dc->getSelfNominalTypeDecl(); + if (nominal && + ((isa(nominal) && cast(nominal)->isActor()) || + nominal->getSemanticAttrs().hasAttribute())) + return false; + + return true; } //----------------------------------------------------------------------------// diff --git a/lib/IRGen/GenProto.cpp b/lib/IRGen/GenProto.cpp index 98f04e65ee87b..f0a623b45ed54 100644 --- a/lib/IRGen/GenProto.cpp +++ b/lib/IRGen/GenProto.cpp @@ -94,11 +94,6 @@ using namespace swift; using namespace irgen; -namespace swift { - // FIXME: Move this on to ProtocolConformance? - ActorIsolation getConformanceIsolation(ProtocolConformance *conformance); -} - namespace { /// A class for computing how to pass arguments to a polymorphic @@ -2238,10 +2233,11 @@ namespace { void addFlags() { // Miscellaneous flags. if (auto conf = dyn_cast(Conformance)) { + auto isolation = conf->getIsolation(); Flags = Flags.withIsRetroactive(conf->isRetroactive()); Flags = Flags.withIsSynthesizedNonUnique(conf->isSynthesizedNonUnique()); Flags = Flags.withIsConformanceOfProtocol(conf->isConformanceOfProtocol()); - Flags = Flags.withHasGlobalActorIsolation(conf->isGlobalActorIsolated()); + Flags = Flags.withHasGlobalActorIsolation(isolation.isGlobalActor()); } else { Flags = Flags.withIsRetroactive(false) .withIsSynthesizedNonUnique(false); @@ -2435,13 +2431,11 @@ namespace { return; auto normal = cast(Conformance); - assert(normal->isGlobalActorIsolated()); auto nominal = normal->getDeclContext()->getSelfNominalTypeDecl(); // Add global actor type. auto sig = nominal->getGenericSignatureOfContext(); - auto isolation = getConformanceIsolation( - const_cast(Conformance)); + auto isolation = Conformance->getIsolation(); assert(isolation.isGlobalActor()); Type globalActorType = isolation.getGlobalActor(); auto globalActorTypeName = IGM.getTypeRef( diff --git a/lib/Sema/CSDiagnostics.cpp b/lib/Sema/CSDiagnostics.cpp index cb78366d28d45..8dda76009c7aa 100644 --- a/lib/Sema/CSDiagnostics.cpp +++ b/lib/Sema/CSDiagnostics.cpp @@ -9568,7 +9568,7 @@ bool DisallowedIsolatedConformance::diagnoseAsError() { emitDiagnostic(diag::isolated_conformance_with_sendable_simple, conformance->getType(), conformance->getProtocol()->getName(), - getConformanceIsolation(conformance)); + conformance->getIsolation()); auto selectedOverload = getCalleeOverloadChoiceIfAvailable(getLocator()); if (!selectedOverload) diff --git a/lib/Sema/TypeCheckConcurrency.cpp b/lib/Sema/TypeCheckConcurrency.cpp index b0ec75d5d3f09..2f5416e8ad1e2 100644 --- a/lib/Sema/TypeCheckConcurrency.cpp +++ b/lib/Sema/TypeCheckConcurrency.cpp @@ -7868,8 +7868,9 @@ bool swift::diagnoseNonSendableFromDeinit( var->getDescriptiveKind(), var->getName()); } -ActorIsolation swift::getConformanceIsolation(ProtocolConformance *conformance) { - ASTContext &ctx = conformance->getDeclContext()->getASTContext(); +ActorIsolation ProtocolConformance::getIsolation() const { + ASTContext &ctx = getDeclContext()->getASTContext(); + auto conformance = const_cast(this); return evaluateOrDefault( ctx.evaluator, ConformanceIsolationRequest{conformance}, ActorIsolation()); @@ -7882,10 +7883,10 @@ ConformanceIsolationRequest::evaluate(Evaluator &evaluator, ProtocolConformance if (!rootNormal) return ActorIsolation::forNonisolated(false); - if (!rootNormal->isGlobalActorIsolated()) + if (!rootNormal->globalActorIsolation) return ActorIsolation::forNonisolated(false); - auto globalActorTypeExpr = rootNormal->getGlobalActorIsolation(); + auto globalActorTypeExpr = rootNormal->globalActorIsolation; assert(globalActorTypeExpr && "global actor type is out-of-sync"); // If we don't already have a resolved global actor type, resolve it now. @@ -7945,11 +7946,9 @@ namespace { if (!normal) return false; - if (!normal->isGlobalActorIsolated()) - return false; - - auto conformanceIsolation = getConformanceIsolation(concrete); - if (conformanceIsolation == getContextIsolation()) + auto conformanceIsolation = concrete->getIsolation(); + if (!conformanceIsolation.isGlobalActor() || + conformanceIsolation == getContextIsolation()) return true; badIsolatedConformances.push_back(concrete); @@ -7966,7 +7965,7 @@ namespace { auto firstConformance = badIsolatedConformances.front(); ctx.Diags.diagnose( loc, diag::isolated_conformance_wrong_domain, - getConformanceIsolation(firstConformance), + firstConformance->getIsolation(), firstConformance->getType(), firstConformance->getProtocol()->getName(), getContextIsolation()); diff --git a/lib/Sema/TypeCheckConcurrency.h b/lib/Sema/TypeCheckConcurrency.h index 0eea9b1ebdae4..7ec9985f889ee 100644 --- a/lib/Sema/TypeCheckConcurrency.h +++ b/lib/Sema/TypeCheckConcurrency.h @@ -699,10 +699,6 @@ void introduceUnsafeInheritExecutorReplacements( void introduceUnsafeInheritExecutorReplacements( const DeclContext *dc, Type base, SourceLoc loc, LookupResult &result); -/// Determine the isolation of the given conformance. This only applies to -/// the immediate conformance, not any conformances on which it depends. -ActorIsolation getConformanceIsolation(ProtocolConformance *conformance); - /// Check for correct use of isolated conformances in the given reference. /// /// This checks that any isolated conformances that occur in the given diff --git a/lib/Sema/TypeCheckGeneric.cpp b/lib/Sema/TypeCheckGeneric.cpp index d9bd251fd4eb7..8ed5b44d73893 100644 --- a/lib/Sema/TypeCheckGeneric.cpp +++ b/lib/Sema/TypeCheckGeneric.cpp @@ -938,7 +938,7 @@ void TypeChecker::diagnoseRequirementFailure( .IsolatedConformanceProto->isSpecificProtocol( KnownProtocolKind::SendableMetatype), req.getFirstType(), - getConformanceIsolation(isolatedConformance)); + isolatedConformance->getIsolation()); diagnosticNote = diag::type_does_not_inherit_or_conform_requirement; break; } diff --git a/lib/Sema/TypeCheckProtocol.cpp b/lib/Sema/TypeCheckProtocol.cpp index 4a75f656b98a7..87fe9f34ad525 100644 --- a/lib/Sema/TypeCheckProtocol.cpp +++ b/lib/Sema/TypeCheckProtocol.cpp @@ -2543,7 +2543,7 @@ checkIndividualConformance(NormalProtocolConformance *conformance) { } // Complain if the global-actor-isolated conformances are not enabled. - if (conformance->isGlobalActorIsolated() && + if (conformance->isIsolated() && !Context.LangOpts.hasFeature(Feature::IsolatedConformances)) { Context.Diags.diagnose( ComplainLoc, diag::isolated_conformance_experimental_feature); @@ -3382,11 +3382,12 @@ ConformanceChecker::checkActorIsolation(ValueDecl *requirement, } return std::nullopt; - case ActorReferenceResult::EntersActor: + case ActorReferenceResult::EntersActor: { // If the conformance itself is isolated to the same isolation domain as // the witness, treat this as being in the same concurrency domain. - if (Conformance->isGlobalActorIsolated() && - refResult.isolation == getConformanceIsolation(Conformance)) { + auto conformanceIsolation = Conformance->getIsolation(); + if (conformanceIsolation.isGlobalActor() && + refResult.isolation == conformanceIsolation) { sameConcurrencyDomain = true; isIsolatedConformance = true; } @@ -3394,6 +3395,7 @@ ConformanceChecker::checkActorIsolation(ValueDecl *requirement, // Handled below. break; } + } // Keep track of what modifiers are missing from the requirement and witness, // so we can decide what to diagnose. @@ -3572,7 +3574,7 @@ ConformanceChecker::checkActorIsolation(ValueDecl *requirement, // Another way to address the issue is to mark the conformance as // isolated to the global actor or "@preconcurrency". if (Conformance->getSourceKind() == ConformanceEntryKind::Explicit && - !Conformance->isGlobalActorIsolated() && + !Conformance->getIsolation().isGlobalActor() && !Conformance->isPreconcurrency() && !suggestedPreconcurrencyOrIsolated && !requirementIsolation.isActorIsolated() && @@ -5218,20 +5220,22 @@ static void ensureRequirementsAreSatisfied(ASTContext &ctx, } if (!diagnosedIsolatedConformanceIssue) { + auto outerIsolation = conformance->getIsolation(); bool foundIssue = ProtocolConformanceRef(assocConf) .forEachIsolatedConformance( [&](ProtocolConformance *isolatedConformance) { + auto innerIsolation = isolatedConformance->getIsolation(); + // If the conformance we're checking isn't isolated at all, it // needs "isolated". - if (!conformance->isGlobalActorIsolated()) { - auto isolation = getConformanceIsolation(isolatedConformance); + if (!outerIsolation.isGlobalActor()) { std::string globalActorStr = "@" + - isolation.getGlobalActor().getString(); + innerIsolation.getGlobalActor().getString(); ctx.Diags.diagnose( conformance->getLoc(), diag::nonisolated_conformance_depends_on_isolated_conformance, typeInContext, conformance->getProtocol()->getName(), - getConformanceIsolation(isolatedConformance), + innerIsolation, isolatedConformance->getType(), isolatedConformance->getProtocol()->getName(), globalActorStr @@ -5243,8 +5247,6 @@ static void ensureRequirementsAreSatisfied(ASTContext &ctx, // The conformance is isolated, but we need it to have the same // isolation as the other isolated conformance we found. - auto outerIsolation = getConformanceIsolation(conformance); - auto innerIsolation = getConformanceIsolation(isolatedConformance); if (outerIsolation != innerIsolation) { ctx.Diags.diagnose( conformance->getLoc(), From 6eb79052f63e33ebe2179a5caca183f99dc96310 Mon Sep 17 00:00:00 2001 From: Doug Gregor Date: Wed, 12 Mar 2025 23:13:11 -0700 Subject: [PATCH 07/10] [Isolated conformances] Infer main actor isolation under UnspecifiedMeansMainActorIsolated When code in the current module defaults to main actor (under SE-0466), also infer main-actor isolation for protocol conformances of main-actor isolated types. --- lib/AST/TypeCheckRequests.cpp | 5 ++ lib/AST/TypeRepr.cpp | 9 ++- lib/Sema/TypeCheckConcurrency.cpp | 59 ++++++++++++------- lib/Serialization/Serialization.cpp | 5 +- .../isolated_conformance_default_actor.swift | 40 +++++++++++++ 5 files changed, 92 insertions(+), 26 deletions(-) create mode 100644 test/Concurrency/isolated_conformance_default_actor.swift diff --git a/lib/AST/TypeCheckRequests.cpp b/lib/AST/TypeCheckRequests.cpp index 5a8d8654bce9e..7f5dec7e013e7 100644 --- a/lib/AST/TypeCheckRequests.cpp +++ b/lib/AST/TypeCheckRequests.cpp @@ -1381,6 +1381,11 @@ bool ConformanceIsolationRequest::isCached() const { if (!rootNormal) return false; + // We know this is nonisolated. + if (rootNormal->getOptions().contains(ProtocolConformanceFlags::Nonisolated)) + return false; + + // Explicit global actor isolation needs to be checked. if (rootNormal->globalActorIsolation) return true; diff --git a/lib/AST/TypeRepr.cpp b/lib/AST/TypeRepr.cpp index 7bc5e99c2e42e..e6e64eb9ddacc 100644 --- a/lib/AST/TypeRepr.cpp +++ b/lib/AST/TypeRepr.cpp @@ -151,8 +151,13 @@ SourceLoc TypeRepr::findAttrLoc(TypeAttrKind kind) const { while (auto attrTypeRepr = dyn_cast(typeRepr)) { for (auto attr : attrTypeRepr->getAttrs()) { if (auto typeAttr = attr.dyn_cast()) - if (typeAttr->getKind() == kind) - return typeAttr->getStartLoc(); + if (typeAttr->getKind() == kind) { + auto startLoc = typeAttr->getStartLoc(); + if (startLoc.isValid()) + return startLoc; + + return typeAttr->getAttrLoc(); + } } typeRepr = attrTypeRepr->getTypeRepr(); diff --git a/lib/Sema/TypeCheckConcurrency.cpp b/lib/Sema/TypeCheckConcurrency.cpp index 2f5416e8ad1e2..88fdff085a290 100644 --- a/lib/Sema/TypeCheckConcurrency.cpp +++ b/lib/Sema/TypeCheckConcurrency.cpp @@ -7878,37 +7878,56 @@ ActorIsolation ProtocolConformance::getIsolation() const { ActorIsolation ConformanceIsolationRequest::evaluate(Evaluator &evaluator, ProtocolConformance *conformance) const { + // Only normal protocol conformances can be isolated. auto rootNormal = dyn_cast(conformance->getRootConformance()); if (!rootNormal) return ActorIsolation::forNonisolated(false); - if (!rootNormal->globalActorIsolation) + // If the conformance is explicitly non-isolated, report that. + if (rootNormal->getOptions().contains(ProtocolConformanceFlags::Nonisolated)) return ActorIsolation::forNonisolated(false); - auto globalActorTypeExpr = rootNormal->globalActorIsolation; - assert(globalActorTypeExpr && "global actor type is out-of-sync"); - - // If we don't already have a resolved global actor type, resolve it now. - Type globalActorType = globalActorTypeExpr->getInstanceType(); - if (!globalActorType) { - const auto resolution = TypeResolution::forInterface( - rootNormal->getDeclContext(), std::nullopt, - /*unboundTyOpener*/ nullptr, - /*placeholderHandler*/ nullptr, - /*packElementOpener*/ nullptr); - globalActorType = resolution.resolveType(globalActorTypeExpr->getTypeRepr()); - if (!globalActorType) - return ActorIsolation::forNonisolated(false); + // If there is an explicitly-specified global actor on the isolation, + // resolve it and report it. + if (auto globalActorTypeExpr = rootNormal->globalActorIsolation) { + // If we don't already have a resolved global actor type, resolve it now. + Type globalActorType = globalActorTypeExpr->getInstanceType(); + if (!globalActorType) { + const auto resolution = TypeResolution::forInterface( + rootNormal->getDeclContext(), std::nullopt, + /*unboundTyOpener*/ nullptr, + /*placeholderHandler*/ nullptr, + /*packElementOpener*/ nullptr); + globalActorType = resolution.resolveType(globalActorTypeExpr->getTypeRepr()); + if (!globalActorType) + return ActorIsolation::forNonisolated(false); + + // Cache the resolved type. + globalActorTypeExpr->setType(MetatypeType::get(globalActorType)); + } + + // FIXME: Make sure the type actually is a global actor type, map it into + // context, etc. - // Cache the resolved type. - globalActorTypeExpr->setType(MetatypeType::get(globalActorType)); + return ActorIsolation::forGlobalActor(globalActorType); } - // FIXME: Make sure the type actually is a global actor type, map it into - // context, etc. + // In a context where we are inferring @MainActor, if the conforming type + // is on the main actor, then the conformance is, too. + auto dc = rootNormal->getDeclContext(); + ASTContext &ctx = dc->getASTContext(); + auto nominal = dc->getSelfNominalTypeDecl(); + if (ctx.LangOpts.hasFeature(Feature::UnspecifiedMeansMainActorIsolated) && + nominal) { + auto nominalIsolation = getActorIsolation(nominal); + if (nominalIsolation.isMainActor()) { + rootNormal->setGlobalActorIsolation(nominalIsolation.getGlobalActor()); + return nominalIsolation; + } + } - return ActorIsolation::forGlobalActor(globalActorType); + return ActorIsolation::forNonisolated(false); } namespace { diff --git a/lib/Serialization/Serialization.cpp b/lib/Serialization/Serialization.cpp index 8bdd4d991181c..5371b176325bf 100644 --- a/lib/Serialization/Serialization.cpp +++ b/lib/Serialization/Serialization.cpp @@ -1929,10 +1929,7 @@ void Serializer::writeLocalNormalProtocolConformance( // Figure out the isolation of the conformance. Type globalActorType; - auto isolation = evaluateOrDefault( - getASTContext().evaluator, - ConformanceIsolationRequest{conformance}, swift::ActorIsolation()); - switch (isolation) { + switch (auto isolation = conformance->getIsolation()) { case swift::ActorIsolation::Unspecified: case swift::ActorIsolation::Nonisolated: break; diff --git a/test/Concurrency/isolated_conformance_default_actor.swift b/test/Concurrency/isolated_conformance_default_actor.swift new file mode 100644 index 0000000000000..ab016edfd731d --- /dev/null +++ b/test/Concurrency/isolated_conformance_default_actor.swift @@ -0,0 +1,40 @@ +// RUN: %target-swift-frontend -typecheck -verify -target %target-swift-5.1-abi-triple -swift-version 6 -enable-experimental-feature IsolatedConformances -enable-experimental-feature UnspecifiedMeansMainActorIsolated %s + +// REQUIRES: swift_feature_IsolatedConformances +// REQUIRES: swift_feature_UnspecifiedMeansMainActorIsolated +// REQUIRES: concurrency + +nonisolated +protocol P { + func f() // expected-note{{mark the protocol requirement 'f()' 'async' to allow actor-isolated conformances}} +} + +class CImplicitMainActorNonisolatedConformance: nonisolated P { + func f() { } // error: explicitly nonisolated conformance +} + + +@MainActor +class CExplicitMainActor: P { + func f() { } // okay! conformance above is isolated +} + +class CImplicitMainActor: P { + func f() { } // okay! conformance above is isolated +} + +// expected-note@+2{{add '@preconcurrency' to the 'P' conformance to defer isolation checking to run time}} +// expected-note@+1{{add '@MainActor' to the 'P' conformance to restrict it to main actor-isolated code}} +nonisolated class CNonIsolated: P { + @MainActor func f() { } // expected-error{{main actor-isolated instance method 'f()' cannot be used to satisfy nonisolated requirement from protocol 'P'}} +} + +func acceptSendablePMeta(_: T.Type) { } + +nonisolated func testConformancesFromNonisolated() { + let _: any P = CExplicitMainActor() // expected-error{{main actor-isolated conformance of 'CExplicitMainActor' to 'P' cannot be used in nonisolated context}} + let _: any P = CImplicitMainActor() // expected-error{{main actor-isolated conformance of 'CImplicitMainActor' to 'P' cannot be used in nonisolated context}} + + let _: any P = CNonIsolated() + let _: any P = CImplicitMainActorNonisolatedConformance() +} From 836fcbe900c4d30504301bc8a407cec932a27722 Mon Sep 17 00:00:00 2001 From: Doug Gregor Date: Thu, 13 Mar 2025 08:16:36 -0700 Subject: [PATCH 08/10] Don't infer isolated conformances when the feature isn't enabled --- lib/Sema/TypeCheckConcurrency.cpp | 7 +++++-- lib/Sema/TypeCheckProtocol.cpp | 8 ++++---- 2 files changed, 9 insertions(+), 6 deletions(-) diff --git a/lib/Sema/TypeCheckConcurrency.cpp b/lib/Sema/TypeCheckConcurrency.cpp index 88fdff085a290..9ebc6047ebfbf 100644 --- a/lib/Sema/TypeCheckConcurrency.cpp +++ b/lib/Sema/TypeCheckConcurrency.cpp @@ -7913,10 +7913,13 @@ ConformanceIsolationRequest::evaluate(Evaluator &evaluator, ProtocolConformance return ActorIsolation::forGlobalActor(globalActorType); } - // In a context where we are inferring @MainActor, if the conforming type - // is on the main actor, then the conformance is, too. auto dc = rootNormal->getDeclContext(); ASTContext &ctx = dc->getASTContext(); + if (!ctx.LangOpts.hasFeature(Feature::IsolatedConformances)) + return ActorIsolation::forNonisolated(false); + + // In a context where we are inferring @MainActor, if the conforming type + // is on the main actor, then the conformance is, too. auto nominal = dc->getSelfNominalTypeDecl(); if (ctx.LangOpts.hasFeature(Feature::UnspecifiedMeansMainActorIsolated) && nominal) { diff --git a/lib/Sema/TypeCheckProtocol.cpp b/lib/Sema/TypeCheckProtocol.cpp index 87fe9f34ad525..07698078fdfe9 100644 --- a/lib/Sema/TypeCheckProtocol.cpp +++ b/lib/Sema/TypeCheckProtocol.cpp @@ -3574,12 +3574,12 @@ ConformanceChecker::checkActorIsolation(ValueDecl *requirement, // Another way to address the issue is to mark the conformance as // isolated to the global actor or "@preconcurrency". if (Conformance->getSourceKind() == ConformanceEntryKind::Explicit && - !Conformance->getIsolation().isGlobalActor() && + !Conformance->isIsolated() && !Conformance->isPreconcurrency() && !suggestedPreconcurrencyOrIsolated && - !requirementIsolation.isActorIsolated() && - refResult.isolation.isGlobalActor()) { - if (Context.LangOpts.hasFeature(Feature::IsolatedConformances)) { + !requirementIsolation.isActorIsolated()) { + if (Context.LangOpts.hasFeature(Feature::IsolatedConformances) && + refResult.isolation.isGlobalActor()) { std::string globalActorStr = "@" + refResult.isolation.getGlobalActor().getString(); Context.Diags.diagnose(Conformance->getProtocolNameLoc(), From c7fd48aa674c66c52e8a6b07ab1253ed613e7f1c Mon Sep 17 00:00:00 2001 From: Doug Gregor Date: Thu, 13 Mar 2025 17:38:54 -0700 Subject: [PATCH 09/10] Rework storage for conformance isolation Switch over to split caching for the conformance isolation request, which optimizes for the common case where the conformance is nonisolated. Also put the explicit global actor TypeExpr* in an ASTContext side table, so we don't take a pointer's worth of storage in every conformance. For that side table, introduce a new "ASTContext::GlobalCache" that's there only for side tables, so we don't have to go add get/set operations to ASTContext and recompile the world every time we want to add a side table like this. Thanks, Slava! --- include/swift/AST/ASTContext.h | 8 +++++ include/swift/AST/ASTContextGlobalCache.h | 34 +++++++++++++++++++ include/swift/AST/ProtocolConformance.h | 32 +++++++++++++----- include/swift/AST/TypeCheckRequests.h | 9 +++-- include/swift/AST/TypeCheckerTypeIDZone.def | 3 +- lib/AST/ASTContext.cpp | 8 +++++ lib/AST/ProtocolConformance.cpp | 32 ++++++++++++------ lib/AST/TypeCheckRequests.cpp | 37 ++++++++++++--------- lib/Sema/TypeCheckConcurrency.cpp | 3 +- 9 files changed, 125 insertions(+), 41 deletions(-) create mode 100644 include/swift/AST/ASTContextGlobalCache.h diff --git a/include/swift/AST/ASTContext.h b/include/swift/AST/ASTContext.h index ccc874f310f36..c492f859a8623 100644 --- a/include/swift/AST/ASTContext.h +++ b/include/swift/AST/ASTContext.h @@ -279,6 +279,14 @@ class ASTContext final { struct Implementation; Implementation &getImpl() const; + struct GlobalCache; + + /// Retrieve a reference to the global cache within this ASTContext, + /// which is a place where we can stash side tables without having to + /// recompile the world every time we add a side table. See + /// "swift/AST/ASTContextGlobalCache.h" + GlobalCache &getGlobalCache() const; + friend ConstraintCheckerArenaRAII; void operator delete(void *Data) throw(); diff --git a/include/swift/AST/ASTContextGlobalCache.h b/include/swift/AST/ASTContextGlobalCache.h new file mode 100644 index 0000000000000..fb197700b24e0 --- /dev/null +++ b/include/swift/AST/ASTContextGlobalCache.h @@ -0,0 +1,34 @@ +//===--- ASTContextGlobalCache.h - AST Context Cache ------------*- C++ -*-===// +// +// This source file is part of the Swift.org open source project +// +// Copyright (c) 2014 - 2020 Apple Inc. and the Swift project authors +// Licensed under Apache License v2.0 with Runtime Library Exception +// +// See https://swift.org/LICENSE.txt for license information +// See https://swift.org/CONTRIBUTORS.txt for the list of Swift project authors +// +//===----------------------------------------------------------------------===// +// +// This file defines the ASTContext::GlobalCache type. DO NOT include this +// header from any other header: it should only be included in those .cpp +// files that need to access the side tables. There are no include guards to +// force the issue. +// +//===----------------------------------------------------------------------===// + +#include "swift/AST/ASTContext.h" + +namespace swift { + +/// A collection of side tables associated with the ASTContext itself, meant +// as +struct ASTContext::GlobalCache { + /// Mapping from normal protocol conformances to the explicitly-specified + /// global actor isolations, e.g., when the conformance was spelled + /// `@MainActor P` or similar. + llvm::DenseMap + conformanceExplicitGlobalActorIsolation; +}; + +} // end namespace diff --git a/include/swift/AST/ProtocolConformance.h b/include/swift/AST/ProtocolConformance.h index 4c8d0944676e3..f7739cd74d02e 100644 --- a/include/swift/AST/ProtocolConformance.h +++ b/include/swift/AST/ProtocolConformance.h @@ -147,7 +147,7 @@ class alignas(1 << DeclAlignInBits) ProtocolConformance SWIFT_INLINE_BITFIELD_EMPTY(RootProtocolConformance, ProtocolConformance); SWIFT_INLINE_BITFIELD_FULL(NormalProtocolConformance, RootProtocolConformance, - 1+1+1+ + 1+1+1+1+1+ bitmax(NumProtocolConformanceOptions,8)+ bitmax(NumProtocolConformanceStateBits,8)+ bitmax(NumConformanceEntryKindBits,8), @@ -161,6 +161,12 @@ class alignas(1 << DeclAlignInBits) ProtocolConformance /// this conformance. IsPreconcurrencyEffectful : 1, + /// Whether the computed actor isolation is nonisolated. + IsComputedNonisolated : 1, + + /// Whether there is an explicit global actor specified for this + /// conformance. + HasExplicitGlobalActor : 1, : NumPadBits, /// Options. @@ -555,9 +561,6 @@ class NormalProtocolConformance : public RootProtocolConformance, /// NominalTypeDecl that declared the conformance. DeclContext *Context; - /// The global actor isolation for this conformance, if there is one. - TypeExpr *globalActorIsolation = nullptr; - NormalProtocolConformance *ImplyingConformance = nullptr; /// The mapping of individual requirements in the protocol over to @@ -582,8 +585,19 @@ class NormalProtocolConformance : public RootProtocolConformance, void resolveLazyInfo() const; - /// Set up global actor isolation for this conformance. - void setGlobalActorIsolation(Type globalActorType); + /// Retrieve the explicitly-specified global actor isolation. + TypeExpr *getExplicitGlobalActorIsolation() const; + + // Record the explicitly-specified global actor isolation. + void setExplicitGlobalActorIsolation(TypeExpr *typeExpr); + + bool isComputedNonisolated() const { + return Bits.NormalProtocolConformance.IsComputedNonisolated; + } + + void setComputedNonnisolated(bool value = true) { + Bits.NormalProtocolConformance.IsComputedNonisolated = value; + } public: NormalProtocolConformance(Type conformingType, ProtocolDecl *protocol, @@ -608,7 +622,9 @@ class NormalProtocolConformance : public RootProtocolConformance, Bits.NormalProtocolConformance.HasComputedAssociatedConformances = false; Bits.NormalProtocolConformance.SourceKind = unsigned(ConformanceEntryKind::Explicit); - globalActorIsolation = options.getGlobalActorIsolationType(); + Bits.NormalProtocolConformance.IsComputedNonisolated = false; + Bits.NormalProtocolConformance.HasExplicitGlobalActor = false; + setExplicitGlobalActorIsolation(options.getGlobalActorIsolationType()); } /// Get the protocol being conformed to. @@ -651,7 +667,7 @@ class NormalProtocolConformance : public RootProtocolConformance, ProtocolConformanceOptions getOptions() const { return ProtocolConformanceOptions(Bits.NormalProtocolConformance.Options, - globalActorIsolation); + getExplicitGlobalActorIsolation()); } /// Whether this is an "unchecked" conformance. diff --git a/include/swift/AST/TypeCheckRequests.h b/include/swift/AST/TypeCheckRequests.h index d036fe70d35b0..56b31f382da48 100644 --- a/include/swift/AST/TypeCheckRequests.h +++ b/include/swift/AST/TypeCheckRequests.h @@ -478,7 +478,8 @@ class ConformanceHasEffectRequest : class ConformanceIsolationRequest : public SimpleRequest { + RequestFlags::SeparatelyCached | + RequestFlags::SplitCached> { public: using SimpleRequest::SimpleRequest; @@ -490,8 +491,10 @@ class ConformanceIsolationRequest : evaluate(Evaluator &evaluator, ProtocolConformance *conformance) const; public: - // Caching. - bool isCached() const; + // Separate caching. + bool isCached() const { return true; } + std::optional getCachedResult() const; + void cacheResult(ActorIsolation result) const; }; /// Determine whether the given declaration is 'final'. diff --git a/include/swift/AST/TypeCheckerTypeIDZone.def b/include/swift/AST/TypeCheckerTypeIDZone.def index 9eedcd69bba90..11865b5505cbc 100644 --- a/include/swift/AST/TypeCheckerTypeIDZone.def +++ b/include/swift/AST/TypeCheckerTypeIDZone.def @@ -408,7 +408,8 @@ SWIFT_REQUEST(TypeChecker, ConformanceHasEffectRequest, bool(EffectKind, ProtocolConformanceRef), Cached, NoLocationInfo) SWIFT_REQUEST(TypeChecker, ConformanceIsolationRequest, - ActorIsolation(ProtocolConformance *), Cached, + ActorIsolation(ProtocolConformance *), + SeparatelyCached | SplitCached, NoLocationInfo) SWIFT_REQUEST(TypeChecker, ResolveTypeRequest, Type (const TypeResolution *, TypeRepr *, GenericParamList *), diff --git a/lib/AST/ASTContext.cpp b/lib/AST/ASTContext.cpp index 9f7ab7f111bd4..89b9316bcfcb3 100644 --- a/lib/AST/ASTContext.cpp +++ b/lib/AST/ASTContext.cpp @@ -18,6 +18,7 @@ #include "ClangTypeConverter.h" #include "ForeignRepresentationInfo.h" #include "SubstitutionMapStorage.h" +#include "swift/AST/ASTContextGlobalCache.h" #include "swift/ABI/MetadataValues.h" #include "swift/AST/AvailabilityContextStorage.h" #include "swift/AST/ClangModuleLoader.h" @@ -270,6 +271,9 @@ struct ASTContext::Implementation { llvm::BumpPtrAllocator Allocator; // used in later initializations + /// The global cache of side tables for random things. + GlobalCache globalCache; + /// The set of cleanups to be called when the ASTContext is destroyed. std::vector> Cleanups; @@ -747,6 +751,10 @@ inline ASTContext::Implementation &ASTContext::getImpl() const { return *reinterpret_cast(pointer + offset); } +ASTContext::GlobalCache &ASTContext::getGlobalCache() const { + return getImpl().globalCache; +} + void ASTContext::operator delete(void *Data) throw() { AlignedFree(Data); } diff --git a/lib/AST/ProtocolConformance.cpp b/lib/AST/ProtocolConformance.cpp index b2b391dc93b2c..06aa6aa597b50 100644 --- a/lib/AST/ProtocolConformance.cpp +++ b/lib/AST/ProtocolConformance.cpp @@ -17,6 +17,7 @@ #include "swift/AST/ProtocolConformance.h" #include "ConformanceLookupTable.h" #include "swift/AST/ASTContext.h" +#include "swift/AST/ASTContextGlobalCache.h" #include "swift/AST/ConformanceLookup.h" #include "swift/AST/Decl.h" #include "swift/AST/DistributedDecl.h" @@ -474,17 +475,6 @@ void NormalProtocolConformance::resolveLazyInfo() const { loader->finishNormalConformance(mutableThis, LoaderContextData); } -void NormalProtocolConformance::setGlobalActorIsolation(Type globalActorType) { - ASTContext &ctx = getDeclContext()->getASTContext(); - if (globalActorIsolation) - globalActorIsolation->setType(MetatypeType::get(globalActorType)); - else - globalActorIsolation = TypeExpr::createImplicit(globalActorType, ctx); - - Bits.NormalProtocolConformance.Options |= - static_cast(ProtocolConformanceFlags::GlobalActorIsolated); -} - void NormalProtocolConformance::setLazyLoader(LazyConformanceLoader *loader, uint64_t contextData) { assert(!Loader && "already has a loader"); @@ -492,6 +482,26 @@ void NormalProtocolConformance::setLazyLoader(LazyConformanceLoader *loader, LoaderContextData = contextData; } +TypeExpr *NormalProtocolConformance::getExplicitGlobalActorIsolation() const { + if (!Bits.NormalProtocolConformance.HasExplicitGlobalActor) + return nullptr; + + ASTContext &ctx = getDeclContext()->getASTContext(); + return ctx.getGlobalCache().conformanceExplicitGlobalActorIsolation[this]; +} + +void +NormalProtocolConformance::setExplicitGlobalActorIsolation(TypeExpr *typeExpr) { + if (!typeExpr) { + Bits.NormalProtocolConformance.HasExplicitGlobalActor = false; + return; + } + + Bits.NormalProtocolConformance.HasExplicitGlobalActor = true; + ASTContext &ctx = getDeclContext()->getASTContext(); + ctx.getGlobalCache().conformanceExplicitGlobalActorIsolation[this] = typeExpr; +} + namespace { class PrettyStackTraceRequirement : public llvm::PrettyStackTraceEntry { const char *Action; diff --git a/lib/AST/TypeCheckRequests.cpp b/lib/AST/TypeCheckRequests.cpp index 7f5dec7e013e7..3647d4bfe964e 100644 --- a/lib/AST/TypeCheckRequests.cpp +++ b/lib/AST/TypeCheckRequests.cpp @@ -1371,7 +1371,8 @@ void AssociatedConformanceRequest::cacheResult( //----------------------------------------------------------------------------// // ConformanceIsolationRequest computation. //----------------------------------------------------------------------------// -bool ConformanceIsolationRequest::isCached() const { +std::optional +ConformanceIsolationRequest::getCachedResult() const { // We only want to cache for global-actor-isolated conformances. For // everything else, which is nearly every conformance, this request quickly // returns "nonisolated" so there is no point in caching it. @@ -1379,25 +1380,29 @@ bool ConformanceIsolationRequest::isCached() const { auto rootNormal = dyn_cast(conformance->getRootConformance()); if (!rootNormal) - return false; + return ActorIsolation::forNonisolated(false); - // We know this is nonisolated. - if (rootNormal->getOptions().contains(ProtocolConformanceFlags::Nonisolated)) - return false; + // Was actor isolation non-isolated? + if (rootNormal->isComputedNonisolated()) + return ActorIsolation::forNonisolated(false); - // Explicit global actor isolation needs to be checked. - if (rootNormal->globalActorIsolation) - return true; + ASTContext &ctx = rootNormal->getDeclContext()->getASTContext(); + return ctx.evaluator.getCachedNonEmptyOutput(*this); +} - // If we might infer conformance isolation, then we should cache the result. - auto dc = rootNormal->getDeclContext(); - auto nominal = dc->getSelfNominalTypeDecl(); - if (nominal && - ((isa(nominal) && cast(nominal)->isActor()) || - nominal->getSemanticAttrs().hasAttribute())) - return false; +void ConformanceIsolationRequest::cacheResult(ActorIsolation result) const { + auto conformance = std::get<0>(getStorage()); + auto rootNormal = + cast(conformance->getRootConformance()); + + // Common case: conformance is nonisolated. + if (result.isNonisolated()) { + rootNormal->setComputedNonnisolated(); + return; + } - return true; + ASTContext &ctx = rootNormal->getDeclContext()->getASTContext(); + ctx.evaluator.cacheNonEmptyOutput(*this, std::move(result)); } //----------------------------------------------------------------------------// diff --git a/lib/Sema/TypeCheckConcurrency.cpp b/lib/Sema/TypeCheckConcurrency.cpp index 9ebc6047ebfbf..89f4f05a983cc 100644 --- a/lib/Sema/TypeCheckConcurrency.cpp +++ b/lib/Sema/TypeCheckConcurrency.cpp @@ -7890,7 +7890,7 @@ ConformanceIsolationRequest::evaluate(Evaluator &evaluator, ProtocolConformance // If there is an explicitly-specified global actor on the isolation, // resolve it and report it. - if (auto globalActorTypeExpr = rootNormal->globalActorIsolation) { + if (auto globalActorTypeExpr = rootNormal->getExplicitGlobalActorIsolation()) { // If we don't already have a resolved global actor type, resolve it now. Type globalActorType = globalActorTypeExpr->getInstanceType(); if (!globalActorType) { @@ -7925,7 +7925,6 @@ ConformanceIsolationRequest::evaluate(Evaluator &evaluator, ProtocolConformance nominal) { auto nominalIsolation = getActorIsolation(nominal); if (nominalIsolation.isMainActor()) { - rootNormal->setGlobalActorIsolation(nominalIsolation.getGlobalActor()); return nominalIsolation; } } From 01b2789dc829f3f9621b6a7af18554469c696c0f Mon Sep 17 00:00:00 2001 From: Doug Gregor Date: Thu, 13 Mar 2025 17:47:26 -0700 Subject: [PATCH 10/10] Do not infer conformance isolation to an isolated protocol --- lib/Sema/TypeCheckConcurrency.cpp | 5 +++++ .../isolated_conformance_default_actor.swift | 19 +++++++++++++++++++ 2 files changed, 24 insertions(+) diff --git a/lib/Sema/TypeCheckConcurrency.cpp b/lib/Sema/TypeCheckConcurrency.cpp index 89f4f05a983cc..e293d91888a1a 100644 --- a/lib/Sema/TypeCheckConcurrency.cpp +++ b/lib/Sema/TypeCheckConcurrency.cpp @@ -7918,6 +7918,11 @@ ConformanceIsolationRequest::evaluate(Evaluator &evaluator, ProtocolConformance if (!ctx.LangOpts.hasFeature(Feature::IsolatedConformances)) return ActorIsolation::forNonisolated(false); + // If the protocol itself is isolated, don't infer isolation for the + // conformance. + if (getActorIsolation(rootNormal->getProtocol()).isActorIsolated()) + return ActorIsolation::forNonisolated(false); + // In a context where we are inferring @MainActor, if the conforming type // is on the main actor, then the conformance is, too. auto nominal = dc->getSelfNominalTypeDecl(); diff --git a/test/Concurrency/isolated_conformance_default_actor.swift b/test/Concurrency/isolated_conformance_default_actor.swift index ab016edfd731d..088725fba2c67 100644 --- a/test/Concurrency/isolated_conformance_default_actor.swift +++ b/test/Concurrency/isolated_conformance_default_actor.swift @@ -9,6 +9,11 @@ protocol P { func f() // expected-note{{mark the protocol requirement 'f()' 'async' to allow actor-isolated conformances}} } +@MainActor +protocol Q { + func g() +} + class CImplicitMainActorNonisolatedConformance: nonisolated P { func f() { } // error: explicitly nonisolated conformance } @@ -23,6 +28,15 @@ class CImplicitMainActor: P { func f() { } // okay! conformance above is isolated } +// If the protocol itself is isolated, don't do anything. +extension CExplicitMainActor: Q { + func g() { } +} + +extension CImplicitMainActor: Q { + func g() { } +} + // expected-note@+2{{add '@preconcurrency' to the 'P' conformance to defer isolation checking to run time}} // expected-note@+1{{add '@MainActor' to the 'P' conformance to restrict it to main actor-isolated code}} nonisolated class CNonIsolated: P { @@ -30,6 +44,7 @@ nonisolated class CNonIsolated: P { } func acceptSendablePMeta(_: T.Type) { } +func acceptSendableQMeta(_: T.Type) { } nonisolated func testConformancesFromNonisolated() { let _: any P = CExplicitMainActor() // expected-error{{main actor-isolated conformance of 'CExplicitMainActor' to 'P' cannot be used in nonisolated context}} @@ -37,4 +52,8 @@ nonisolated func testConformancesFromNonisolated() { let _: any P = CNonIsolated() let _: any P = CImplicitMainActorNonisolatedConformance() + + // Okay, these are nonisolated conformances. + let _: any Q = CExplicitMainActor() + let _: any Q = CImplicitMainActor() }