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/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/DiagnosticsSema.def b/include/swift/AST/DiagnosticsSema.def index 11c7acea4058a..683897eddeb75 100644 --- a/include/swift/AST/DiagnosticsSema.def +++ b/include/swift/AST/DiagnosticsSema.def @@ -8319,31 +8319,28 @@ 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)) 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 " + "%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/include/swift/AST/ProtocolConformance.h b/include/swift/AST/ProtocolConformance.h index 3969f60624915..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. @@ -240,6 +246,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 +543,7 @@ class NormalProtocolConformance : public RootProtocolConformance, { friend class ValueWitnessRequest; friend class TypeWitnessRequest; + friend class ConformanceIsolationRequest; /// The protocol being conformed to. ProtocolDecl *Protocol; @@ -570,6 +585,20 @@ class NormalProtocolConformance : public RootProtocolConformance, void resolveLazyInfo() const; + /// 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, SourceLoc loc, DeclContext *dc, @@ -593,6 +622,9 @@ class NormalProtocolConformance : public RootProtocolConformance, Bits.NormalProtocolConformance.HasComputedAssociatedConformances = false; Bits.NormalProtocolConformance.SourceKind = unsigned(ConformanceEntryKind::Explicit); + Bits.NormalProtocolConformance.IsComputedNonisolated = false; + Bits.NormalProtocolConformance.HasExplicitGlobalActor = false; + setExplicitGlobalActorIsolation(options.getGlobalActorIsolationType()); } /// Get the protocol being conformed to. @@ -634,7 +666,8 @@ class NormalProtocolConformance : public RootProtocolConformance, void setInvalid() { Bits.NormalProtocolConformance.IsInvalid = true; } ProtocolConformanceOptions getOptions() const { - return ProtocolConformanceOptions(Bits.NormalProtocolConformance.Options); + return ProtocolConformanceOptions(Bits.NormalProtocolConformance.Options, + getExplicitGlobalActorIsolation()); } /// Whether this is an "unchecked" conformance. @@ -669,11 +702,6 @@ class NormalProtocolConformance : public RootProtocolConformance, return getOptions().contains(ProtocolConformanceFlags::Preconcurrency); } - /// Whether this is an isolated conformance. - bool isIsolated() const { - return getOptions().contains(ProtocolConformanceFlags::Isolated); - } - /// Retrieve the location of `@preconcurrency`, if there is one and it is /// known. SourceLoc getPreconcurrencyLoc() const { return PreconcurrencyLoc; } 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..56b31f382da48 100644 --- a/include/swift/AST/TypeCheckRequests.h +++ b/include/swift/AST/TypeCheckRequests.h @@ -475,6 +475,28 @@ 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: + // Separate caching. + bool isCached() const { return true; } + std::optional getCachedResult() const; + void cacheResult(ActorIsolation result) const; +}; + /// Determine whether the given declaration is 'final'. class IsFinalRequest : public SimpleRequest> 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/ASTPrinter.cpp b/lib/AST/ASTPrinter.cpp index 989e7a405a159..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 << "~"; }); @@ -7915,8 +7927,7 @@ static void getSyntacticInheritanceClause(const ProtocolDecl *proto, if (auto superclassTy = genericSig->getSuperclassBound( 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/ProtocolConformance.cpp b/lib/AST/ProtocolConformance.cpp index 056a30f50a6d4..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" @@ -481,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 4ff02da84ae87..3647d4bfe964e 100644 --- a/lib/AST/TypeCheckRequests.cpp +++ b/lib/AST/TypeCheckRequests.cpp @@ -1368,6 +1368,43 @@ void AssociatedConformanceRequest::cacheResult( conformance->setAssociatedConformance(index, assocConf); } +//----------------------------------------------------------------------------// +// ConformanceIsolationRequest computation. +//----------------------------------------------------------------------------// +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. + auto conformance = std::get<0>(getStorage()); + auto rootNormal = + dyn_cast(conformance->getRootConformance()); + if (!rootNormal) + return ActorIsolation::forNonisolated(false); + + // Was actor isolation non-isolated? + if (rootNormal->isComputedNonisolated()) + return ActorIsolation::forNonisolated(false); + + ASTContext &ctx = rootNormal->getDeclContext()->getASTContext(); + return ctx.evaluator.getCachedNonEmptyOutput(*this); +} + +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; + } + + ASTContext &ctx = rootNormal->getDeclContext()->getASTContext(); + ctx.evaluator.cacheNonEmptyOutput(*this, std::move(result)); +} + //----------------------------------------------------------------------------// // HasCircularInheritedProtocolsRequest computation. //----------------------------------------------------------------------------// diff --git a/lib/AST/TypeRepr.cpp b/lib/AST/TypeRepr.cpp index ca58f16951bca..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(); @@ -161,6 +166,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..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->isIsolated()); + Flags = Flags.withHasGlobalActorIsolation(isolation.isGlobalActor()); } else { Flags = Flags.withIsRetroactive(false) .withIsSynthesizedNonUnique(false); @@ -2435,13 +2431,11 @@ namespace { return; auto normal = cast(Conformance); - assert(normal->isIsolated()); 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/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/CSDiagnostics.cpp b/lib/Sema/CSDiagnostics.cpp index 796545f92e312..8dda76009c7aa 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(), + conformance->getIsolation()); auto selectedOverload = getCalleeOverloadChoiceIfAvailable(getLocator()); if (!selectedOverload) diff --git a/lib/Sema/TypeCheckConcurrency.cpp b/lib/Sema/TypeCheckConcurrency.cpp index 596141cb72842..e293d91888a1a 100644 --- a/lib/Sema/TypeCheckConcurrency.cpp +++ b/lib/Sema/TypeCheckConcurrency.cpp @@ -7868,20 +7868,73 @@ bool swift::diagnoseNonSendableFromDeinit( var->getDescriptiveKind(), var->getName()); } -ActorIsolation swift::getConformanceIsolation(ProtocolConformance *conformance) { +ActorIsolation ProtocolConformance::getIsolation() const { + ASTContext &ctx = getDeclContext()->getASTContext(); + auto conformance = const_cast(this); + return evaluateOrDefault( + ctx.evaluator, ConformanceIsolationRequest{conformance}, + ActorIsolation()); +} + +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->isIsolated()) + // If the conformance is explicitly non-isolated, report that. + if (rootNormal->getOptions().contains(ProtocolConformanceFlags::Nonisolated)) return ActorIsolation::forNonisolated(false); - auto nominal = rootNormal->getDeclContext()->getSelfNominalTypeDecl(); - if (!nominal) + // If there is an explicitly-specified global actor on the isolation, + // resolve it and report it. + 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) { + 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 ActorIsolation::forGlobalActor(globalActorType); + } + + auto dc = rootNormal->getDeclContext(); + ASTContext &ctx = dc->getASTContext(); + 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); - return getActorIsolation(nominal); + // 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) { + auto nominalIsolation = getActorIsolation(nominal); + if (nominalIsolation.isMainActor()) { + return nominalIsolation; + } + } + + return ActorIsolation::forNonisolated(false); } namespace { @@ -7919,11 +7972,9 @@ namespace { if (!normal) return false; - if (!normal->isIsolated()) - return false; - - auto conformanceIsolation = getConformanceIsolation(concrete); - if (conformanceIsolation == getContextIsolation()) + auto conformanceIsolation = concrete->getIsolation(); + if (!conformanceIsolation.isGlobalActor() || + conformanceIsolation == getContextIsolation()) return true; badIsolatedConformances.push_back(concrete); @@ -7940,7 +7991,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 586a976929bfb..8ed5b44d73893 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(), + 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 da71a261ad8c5..07698078fdfe9 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->isIsolated()) { - 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->isIsolated() && + !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, @@ -3399,14 +3382,12 @@ 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 (Conformance->isIsolated() && - refResult.isolation.isGlobalActor() && - isolationMatchesEnclosingType( - refResult.isolation, DC->getSelfNominalTypeDecl())) { + 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. + auto conformanceIsolation = Conformance->getIsolation(); + if (conformanceIsolation.isGlobalActor() && + refResult.isolation == conformanceIsolation) { sameConcurrencyDomain = true; isIsolatedConformance = true; } @@ -3414,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. @@ -3590,16 +3572,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->isIsolated() && !Conformance->isPreconcurrency() && + !Conformance->isIsolated() && + !Conformance->isPreconcurrency() && !suggestedPreconcurrencyOrIsolated && !requirementIsolation.isActorIsolated()) { - if (Context.LangOpts.hasFeature(Feature::IsolatedConformances)) { + if (Context.LangOpts.hasFeature(Feature::IsolatedConformances) && + refResult.isolation.isGlobalActor()) { + 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(), @@ -5232,28 +5220,33 @@ 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->isIsolated()) { + if (!outerIsolation.isGlobalActor()) { + std::string globalActorStr = "@" + + 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() - ).fixItInsert(conformance->getProtocolNameLoc(), "isolated "); + isolatedConformance->getProtocol()->getName(), + globalActorStr + ).fixItInsert(conformance->getProtocolNameLoc(), + globalActorStr + " "); return true; } // 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(), diff --git a/lib/Sema/TypeCheckType.cpp b/lib/Sema/TypeCheckType.cpp index c3bbace5ebb8a..ed1f91f6ccb2c 100644 --- a/lib/Sema/TypeCheckType.cpp +++ b/lib/Sema/TypeCheckType.cpp @@ -3495,9 +3495,17 @@ 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))) + handleInheritedOnly(claim(attrs)) || + handleInheritedOnly(claim(attrs))) return ty; if (auto retroactiveAttr = 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..5371b176325bf 100644 --- a/lib/Serialization/Serialization.cpp +++ b/lib/Serialization/Serialization.cpp @@ -1927,6 +1927,24 @@ void Serializer::writeLocalNormalProtocolConformance( data.push_back(witness.getEnterIsolation().has_value() ? 1 : 0); }, /*useResolver=*/true); + // Figure out the isolation of the conformance. + Type globalActorType; + switch (auto isolation = conformance->getIsolation()) { + 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 +1955,7 @@ void Serializer::writeLocalNormalProtocolConformance( numValueWitnesses, numSignatureConformances, conformance->getOptions().toRaw(), + addTypeRef(globalActorType), data); } @@ -4104,9 +4123,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..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: isolated P { +protocol Q { + func g() +} + +nonisolated class MyClass: @MainActor P { func f() { print("MyClass.f()") @@ -26,6 +29,22 @@ class MyClass: isolated 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 2db33ffcc5183..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}} } // ---------------------------------------------------------------------------- @@ -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'}} @@ -22,12 +22,12 @@ 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 { func f() { } } -extension SomeActor: isolated 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,14 +35,15 @@ struct SomeGlobalActor { static let shared = SomeActor() } -// Isolation of the function 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: isolated P { - @SomeGlobalActor func f() { } // expected-error{{global actor 'SomeGlobalActor'-isolated instance method 'f()' cannot be used to satisfy nonisolated requirement from protocol 'P'}} +class CMismatchedIsolation: @SomeGlobalActor P { + @SomeGlobalActor func f() { } } @MainActor -class C: isolated P { +class C: @MainActor P { func f() { } // okay } @@ -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,25 +63,25 @@ 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 } @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 } @@ -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/Concurrency/isolated_conformance_default_actor.swift b/test/Concurrency/isolated_conformance_default_actor.swift new file mode 100644 index 0000000000000..088725fba2c67 --- /dev/null +++ b/test/Concurrency/isolated_conformance_default_actor.swift @@ -0,0 +1,59 @@ +// 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}} +} + +@MainActor +protocol Q { + func g() +} + +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 +} + +// 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 { + @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) { } +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}} + 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() + + // Okay, these are nonisolated conformances. + let _: any Q = CExplicitMainActor() + let _: any Q = CImplicitMainActor() +} 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/ModuleInterface/isolated_conformance.swift b/test/ModuleInterface/isolated_conformance.swift new file mode 100644 index 0000000000000..23d93fcdd4572 --- /dev/null +++ b/test/ModuleInterface/isolated_conformance.swift @@ -0,0 +1,22 @@ +// 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() { } +} + +extension MyClass: nonisolated Equatable { + nonisolated public static func ==(lhs: MyClass, rhs: MyClass) -> Bool { + false + } +} 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..beeb48aa1068a --- /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 conformance of 'MyClass' to 'MyProtocol' cannot be used in nonisolated context}} +}