diff --git a/include/swift/AST/ConformanceAttributes.h b/include/swift/AST/ConformanceAttributes.h index 4f9242c6b3902..aa3ec21dff4dc 100644 --- a/include/swift/AST/ConformanceAttributes.h +++ b/include/swift/AST/ConformanceAttributes.h @@ -27,6 +27,9 @@ struct ConformanceAttributes { /// The location of the "unsafe" attribute if present. SourceLoc unsafeLoc; + + /// The location of the "@isolated" attribute if present. + SourceLoc isolatedLoc; /// Merge other conformance attributes into this set. ConformanceAttributes & @@ -37,6 +40,8 @@ struct ConformanceAttributes { preconcurrencyLoc = other.preconcurrencyLoc; if (other.unsafeLoc.isValid()) unsafeLoc = other.unsafeLoc; + if (other.isolatedLoc.isValid()) + isolatedLoc = other.isolatedLoc; return *this; } }; diff --git a/include/swift/AST/Decl.h b/include/swift/AST/Decl.h index 1de660e903aec..92e7afe8eeb2d 100644 --- a/include/swift/AST/Decl.h +++ b/include/swift/AST/Decl.h @@ -1836,12 +1836,13 @@ struct InheritedEntry : public TypeLoc { bool isPreconcurrency() const { return getOptions().contains(ProtocolConformanceFlags::Preconcurrency); } + bool isIsolated() const { + return getOptions().contains(ProtocolConformanceFlags::Isolated); + } ExplicitSafety getExplicitSafety() const { if (getOptions().contains(ProtocolConformanceFlags::Unsafe)) return ExplicitSafety::Unsafe; - if (getOptions().contains(ProtocolConformanceFlags::Safe)) - return ExplicitSafety::Safe; return ExplicitSafety::Unspecified; } @@ -1852,13 +1853,10 @@ struct InheritedEntry : public TypeLoc { } void setOption(ExplicitSafety safety) { - RawOptions = (getOptions() - ProtocolConformanceFlags::Unsafe - - ProtocolConformanceFlags::Safe).toRaw(); + RawOptions = (getOptions() - ProtocolConformanceFlags::Unsafe).toRaw(); switch (safety) { case ExplicitSafety::Unspecified: - break; case ExplicitSafety::Safe: - RawOptions = (getOptions() | ProtocolConformanceFlags::Safe).toRaw(); break; case ExplicitSafety::Unsafe: RawOptions = (getOptions() | ProtocolConformanceFlags::Unsafe).toRaw(); diff --git a/include/swift/AST/DiagnosticsSema.def b/include/swift/AST/DiagnosticsSema.def index 95ad9917b891f..a560dad3c6b92 100644 --- a/include/swift/AST/DiagnosticsSema.def +++ b/include/swift/AST/DiagnosticsSema.def @@ -2722,8 +2722,23 @@ WARNING(add_predates_concurrency_import,none, GROUPED_WARNING(remove_predates_concurrency_import,PreconcurrencyImport, DefaultIgnore, "'@preconcurrency' attribute on module %0 has no effect", (Identifier)) +NOTE(add_isolated_to_conformance,none, + "add 'isolated' to the %0 conformance to restrict it to %1 code", + (DeclName, ActorIsolation)) NOTE(add_preconcurrency_to_conformance,none, "add '@preconcurrency' to the %0 conformance to defer isolation checking to run time", (DeclName)) +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)) +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)) WARNING(remove_public_import,none, "public import of %0 was not used in public declarations or inlinable code", (Identifier)) diff --git a/include/swift/AST/ProtocolConformance.h b/include/swift/AST/ProtocolConformance.h index 8d464ac19f587..3969f60624915 100644 --- a/include/swift/AST/ProtocolConformance.h +++ b/include/swift/AST/ProtocolConformance.h @@ -669,6 +669,11 @@ 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; } @@ -678,8 +683,6 @@ class NormalProtocolConformance : public RootProtocolConformance, ExplicitSafety getExplicitSafety() const { if (getOptions().contains(ProtocolConformanceFlags::Unsafe)) return ExplicitSafety::Unsafe; - if (getOptions().contains(ProtocolConformanceFlags::Safe)) - return ExplicitSafety::Safe; return ExplicitSafety::Unspecified; } diff --git a/include/swift/AST/ProtocolConformanceOptions.h b/include/swift/AST/ProtocolConformanceOptions.h index 0fc91203e88bc..002186bf307d2 100644 --- a/include/swift/AST/ProtocolConformanceOptions.h +++ b/include/swift/AST/ProtocolConformanceOptions.h @@ -34,8 +34,8 @@ enum class ProtocolConformanceFlags { /// @retroactive conformance Retroactive = 0x08, - /// @safe conformance - Safe = 0x10, + /// @isolated conformance + Isolated = 0x10, // Note: whenever you add a bit here, update // NumProtocolConformanceOptions below. diff --git a/include/swift/Basic/Features.def b/include/swift/Basic/Features.def index 773e7e38c2fc9..02777db557886 100644 --- a/include/swift/Basic/Features.def +++ b/include/swift/Basic/Features.def @@ -450,11 +450,15 @@ SUPPRESSIBLE_EXPERIMENTAL_FEATURE(CustomAvailability, true) /// Be strict about the Sendable conformance of metatypes. EXPERIMENTAL_FEATURE(StrictSendableMetatypes, true) + /// Allow public enumerations to be extensible by default /// regardless of whether the module they are declared in /// is resilient or not. EXPERIMENTAL_FEATURE(ExtensibleEnums, true) +/// Allow isolated conformances. +EXPERIMENTAL_FEATURE(IsolatedConformances, true) + #undef EXPERIMENTAL_FEATURE_EXCLUDED_FROM_MODULE_INTERFACE #undef EXPERIMENTAL_FEATURE #undef UPCOMING_FEATURE diff --git a/lib/AST/ConformanceLookupTable.h b/lib/AST/ConformanceLookupTable.h index 92ed8be8c8e06..f43ab190aefe3 100644 --- a/lib/AST/ConformanceLookupTable.h +++ b/lib/AST/ConformanceLookupTable.h @@ -154,6 +154,8 @@ class ConformanceLookupTable : public ASTAllocated { options |= ProtocolConformanceFlags::Preconcurrency; if (getUnsafeLoc().isValid()) options |= ProtocolConformanceFlags::Unsafe; + if (getIsolatedLoc().isValid()) + options |= ProtocolConformanceFlags::Isolated; return options; } @@ -209,6 +211,11 @@ class ConformanceLookupTable : public ASTAllocated { return attributes.unsafeLoc; } + /// The location of the @isolated attribute, if any. + SourceLoc getIsolatedLoc() const { + return attributes.isolatedLoc; + } + /// For an inherited conformance, retrieve the class declaration /// for the inheriting class. ClassDecl *getInheritingClass() const { diff --git a/lib/AST/Decl.cpp b/lib/AST/Decl.cpp index 40d78a7c9f07f..dd0dea8bd12cf 100644 --- a/lib/AST/Decl.cpp +++ b/lib/AST/Decl.cpp @@ -1780,6 +1780,8 @@ 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); } } diff --git a/lib/AST/FeatureSet.cpp b/lib/AST/FeatureSet.cpp index 1eb747ac39d2a..f2ae7d8170821 100644 --- a/lib/AST/FeatureSet.cpp +++ b/lib/AST/FeatureSet.cpp @@ -348,6 +348,11 @@ static bool usesFeatureABIAttribute(Decl *decl) { return getABIAttr(decl) != nullptr; } +static bool usesFeatureIsolatedConformances(Decl *decl) { + // FIXME: Check conformances associated with this decl? + return false; +} + UNINTERESTING_FEATURE(WarnUnsafe) UNINTERESTING_FEATURE(SafeInteropWrappers) UNINTERESTING_FEATURE(AssumeResilientCxxTypes) diff --git a/lib/AST/NameLookup.cpp b/lib/AST/NameLookup.cpp index cfc1a96373770..8f692d4900071 100644 --- a/lib/AST/NameLookup.cpp +++ b/lib/AST/NameLookup.cpp @@ -3144,6 +3144,12 @@ directReferencesForTypeRepr(Evaluator &evaluator, ASTContext &ctx, attributed->getTypeRepr(), dc, options); } + case TypeReprKind::Isolated: { + auto isolated = cast(typeRepr); + return directReferencesForTypeRepr(evaluator, ctx, + isolated->getBase(), dc, options); + } + case TypeReprKind::Composition: { auto composition = cast(typeRepr); for (auto component : composition->getTypes()) { @@ -3217,7 +3223,6 @@ directReferencesForTypeRepr(Evaluator &evaluator, ASTContext &ctx, case TypeReprKind::Error: case TypeReprKind::Function: case TypeReprKind::Ownership: - case TypeReprKind::Isolated: case TypeReprKind::CompileTimeConst: case TypeReprKind::Metatype: case TypeReprKind::Protocol: @@ -3933,6 +3938,21 @@ 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( @@ -3971,6 +3991,9 @@ 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); } // Form the result. diff --git a/lib/Parse/ParseType.cpp b/lib/Parse/ParseType.cpp index 31fd093e3b5a3..212e8cb432c36 100644 --- a/lib/Parse/ParseType.cpp +++ b/lib/Parse/ParseType.cpp @@ -163,7 +163,9 @@ ParserResult Parser::parseTypeSimple( Diag<> MessageID, ParseTypeReason reason) { ParserResult ty; - if (isParameterSpecifier()) { + if (isParameterSpecifier() && + !(!Context.LangOpts.hasFeature(Feature::IsolatedConformances) && + Tok.isContextualKeyword("isolated"))) { // Type specifier should already be parsed before here. This only happens // for construct like 'P1 & inout P2'. diagnose(Tok.getLoc(), diag::attr_only_on_parameters, Tok.getRawText()); diff --git a/lib/Sema/TypeCheckConcurrency.cpp b/lib/Sema/TypeCheckConcurrency.cpp index b989ce887f1ce..88a16a3a42149 100644 --- a/lib/Sema/TypeCheckConcurrency.cpp +++ b/lib/Sema/TypeCheckConcurrency.cpp @@ -30,6 +30,7 @@ #include "swift/AST/ImportCache.h" #include "swift/AST/Initializer.h" #include "swift/AST/NameLookupRequests.h" +#include "swift/AST/PackConformance.h" #include "swift/AST/ParameterList.h" #include "swift/AST/ProtocolConformance.h" #include "swift/AST/TypeCheckRequests.h" @@ -7669,3 +7670,56 @@ bool swift::diagnoseNonSendableFromDeinit( diag::non_sendable_from_deinit, var->getDescriptiveKind(), var->getName()); } + +bool swift::forEachIsolatedConformance( + ProtocolConformanceRef conformance, + llvm::function_ref body +) { + if (conformance.isInvalid() || conformance.isAbstract()) + return false; + + if (conformance.isPack()) { + auto pack = conformance.getPack()->getPatternConformances(); + for (auto conformance : pack) { + if (forEachIsolatedConformance(conformance, body)) + return true; + } + + return false; + } + + // Is this an isolated conformance? + auto concrete = conformance.getConcrete(); + if (auto normal = + dyn_cast(concrete->getRootConformance())) { + if (normal->isIsolated()) { + if (body(concrete)) + return true; + } + } + + // Check conformances that are part of this conformance. + auto subMap = concrete->getSubstitutionMap(); + for (auto conformance : subMap.getConformances()) { + if (forEachIsolatedConformance(conformance, body)) + return true; + } + + return false; +} + +ActorIsolation swift::getConformanceIsolation(ProtocolConformance *conformance) { + auto rootNormal = + dyn_cast(conformance->getRootConformance()); + if (!rootNormal) + return ActorIsolation::forNonisolated(false); + + if (!rootNormal->isIsolated()) + return ActorIsolation::forNonisolated(false); + + auto nominal = rootNormal->getDeclContext()->getSelfNominalTypeDecl(); + if (!nominal) + return ActorIsolation::forNonisolated(false); + + return getActorIsolation(nominal); +} diff --git a/lib/Sema/TypeCheckConcurrency.h b/lib/Sema/TypeCheckConcurrency.h index b61c085b16172..d63eb140b4a28 100644 --- a/lib/Sema/TypeCheckConcurrency.h +++ b/lib/Sema/TypeCheckConcurrency.h @@ -699,6 +699,19 @@ void introduceUnsafeInheritExecutorReplacements( void introduceUnsafeInheritExecutorReplacements( const DeclContext *dc, Type base, SourceLoc loc, LookupResult &result); +/// Enumerate all of the isolated conformances in the given conformance. +/// +/// The given `body` will be called on each isolated conformance. If it ever +/// returns `true`, this function will abort the search and return `true`. +bool forEachIsolatedConformance( + ProtocolConformanceRef conformance, + llvm::function_ref body +); + +/// 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); + } // end namespace swift namespace llvm { diff --git a/lib/Sema/TypeCheckProtocol.cpp b/lib/Sema/TypeCheckProtocol.cpp index b7b58ee0d2953..3d20638599452 100644 --- a/lib/Sema/TypeCheckProtocol.cpp +++ b/lib/Sema/TypeCheckProtocol.cpp @@ -2541,6 +2541,22 @@ 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); + } + } + bool allowImpliedConditionalConformance = false; if (Proto->isSpecificProtocol(KnownProtocolKind::Sendable)) { // In -swift-version 5 mode, a conditional conformance to a protocol can imply @@ -3313,6 +3329,14 @@ 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, @@ -3342,7 +3366,8 @@ ConformanceChecker::checkActorIsolation(ValueDecl *requirement, Conformance->isPreconcurrency() && !(requirementIsolation.isActorIsolated() || requirement->getAttrs().hasAttribute()); - + bool isIsolatedConformance = false; + switch (refResult) { case ActorReferenceResult::SameConcurrencyDomain: // If the witness has distributed-actor isolation, we have extra @@ -3374,6 +3399,17 @@ 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())) { + sameConcurrencyDomain = true; + isIsolatedConformance = true; + } + // Handled below. break; } @@ -3446,7 +3482,12 @@ ConformanceChecker::checkActorIsolation(ValueDecl *requirement, // If we aren't missing anything or this is a witness to a `@preconcurrency` // conformance, do a Sendable check and move on. - if (!missingOptions || isPreconcurrency) { + if (!missingOptions || isPreconcurrency || isIsolatedConformance) { + // An isolated conformance won't ever leave the isolation domain in which + // it was created, so there is nothing to check. + if (isIsolatedConformance) + return std::nullopt; + // FIXME: Disable Sendable checking when the witness is an initializer // that is explicitly marked nonisolated. if (isa(witness) && @@ -3546,18 +3587,26 @@ ConformanceChecker::checkActorIsolation(ValueDecl *requirement, witness->diagnose(diag::note_add_nonisolated_to_decl, witness) .fixItInsert(witness->getAttributeInsertionLoc(true), "nonisolated "); } - + // Another way to address the issue is to mark the conformance as - // "preconcurrency". + // "isolated" or "@preconcurrency". if (Conformance->getSourceKind() == ConformanceEntryKind::Explicit && - !Conformance->isPreconcurrency() && - !suggestedPreconcurrency && + !Conformance->isIsolated() && !Conformance->isPreconcurrency() && + !suggestedPreconcurrencyOrIsolated && !requirementIsolation.isActorIsolated()) { + if (Context.LangOpts.hasFeature(Feature::IsolatedConformances)) { + Context.Diags.diagnose(Conformance->getProtocolNameLoc(), + diag::add_isolated_to_conformance, + Proto->getName(), refResult.isolation) + .fixItInsert(Conformance->getProtocolNameLoc(), "isolated "); + } + Context.Diags.diagnose(Conformance->getProtocolNameLoc(), diag::add_preconcurrency_to_conformance, Proto->getName()) .fixItInsert(Conformance->getProtocolNameLoc(), "@preconcurrency "); - suggestedPreconcurrency = true; + + suggestedPreconcurrencyOrIsolated = true; } } @@ -5158,6 +5207,8 @@ static void ensureRequirementsAreSatisfied(ASTContext &ctx, if (where.isImplicit()) return; + bool diagnosedIsolatedConformanceIssue = false; + conformance->forEachAssociatedConformance( [&](Type depTy, ProtocolDecl *proto, unsigned index) { auto assocConf = conformance->getAssociatedConformance(depTy, proto); @@ -5181,6 +5232,50 @@ static void ensureRequirementsAreSatisfied(ASTContext &ctx, where.withRefinedAvailability(availability), depTy, replacementTy); } + if (!diagnosedIsolatedConformanceIssue) { + bool foundIssue = forEachIsolatedConformance( + ProtocolConformanceRef(assocConf), + [&](ProtocolConformance *isolatedConformance) { + // If the conformance we're checking isn't isolated at all, it + // needs "isolated". + if (!conformance->isIsolated()) { + 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 "); + + 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(), + diag::isolated_conformance_mismatch_with_associated_isolation, + outerIsolation, + typeInContext, conformance->getProtocol()->getName(), + innerIsolation, + isolatedConformance->getType(), + isolatedConformance->getProtocol()->getName() + ); + + return true; + } + + return false; + } + ); + + diagnosedIsolatedConformanceIssue = foundIssue; + } + return false; }); } diff --git a/lib/Sema/TypeCheckProtocol.h b/lib/Sema/TypeCheckProtocol.h index 07786ce68c42a..0e9360b2d3cdf 100644 --- a/lib/Sema/TypeCheckProtocol.h +++ b/lib/Sema/TypeCheckProtocol.h @@ -112,8 +112,8 @@ enum class ResolveWitnessResult { /// This helper class handles most of the details of checking whether a /// given type (\c Adoptee) conforms to a protocol (\c Proto). class ConformanceChecker : public WitnessChecker { - /// Whether we already suggested adding `@preconcurrency`. - bool suggestedPreconcurrency = false; + /// Whether we already suggested adding `@preconcurrency` or 'isolated'. + bool suggestedPreconcurrencyOrIsolated = false; public: NormalProtocolConformance *Conformance; diff --git a/lib/Sema/TypeCheckType.cpp b/lib/Sema/TypeCheckType.cpp index aa39887ce2279..e7041fd822316 100644 --- a/lib/Sema/TypeCheckType.cpp +++ b/lib/Sema/TypeCheckType.cpp @@ -5175,8 +5175,9 @@ NeverNullType TypeResolver::resolveIsolatedTypeRepr(IsolatedTypeRepr *repr, TypeResolutionOptions options) { // isolated is only value for non-EnumCaseDecl parameters. - if (!options.is(TypeResolverContext::FunctionInput) || - options.hasBase(TypeResolverContext::EnumElementDecl)) { + if ((!options.is(TypeResolverContext::FunctionInput) || + options.hasBase(TypeResolverContext::EnumElementDecl)) && + !options.is(TypeResolverContext::Inherited)) { diagnoseInvalid( repr, repr->getSpecifierLoc(), diag::attr_only_on_parameters, "isolated"); @@ -5197,7 +5198,8 @@ TypeResolver::resolveIsolatedTypeRepr(IsolatedTypeRepr *repr, unwrappedType = dynamicSelfType->getSelfType(); } - if (inStage(TypeResolutionStage::Interface)) { + if (inStage(TypeResolutionStage::Interface) && + !options.is(TypeResolverContext::Inherited)) { if (auto *env = resolution.getGenericSignature().getGenericEnvironment()) unwrappedType = env->mapTypeIntoContext(unwrappedType); diff --git a/test/Concurrency/isolated_conformance.swift b/test/Concurrency/isolated_conformance.swift new file mode 100644 index 0000000000000..0cea3701f3035 --- /dev/null +++ b/test/Concurrency/isolated_conformance.swift @@ -0,0 +1,80 @@ +// RUN: %target-swift-frontend -typecheck -verify -target %target-swift-5.1-abi-triple -swift-version 6 -enable-experimental-feature IsolatedConformances %s + +// REQUIRES: swift_feature_IsolatedConformances + +protocol P { + func f() // expected-note 2{{mark the protocol requirement 'f()' 'async' to allow actor-isolated conformances}} +} + +// 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 }} +@MainActor +class CWithNonIsolated: P { + func f() { } // expected-error{{main actor-isolated instance method 'f()' cannot be used to satisfy nonisolated requirement from protocol 'P'}} + // expected-note@-1{{add 'nonisolated' to 'f()' to make this instance method not isolated to the actor}} +} + +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}} + func f() { } +} + +extension SomeActor: isolated P { // expected-error{{isolated conformance is only permitted on global-actor-isolated types}} + nonisolated func f() { } +} + +@globalActor +struct SomeGlobalActor { + static let shared = SomeActor() +} + +// Isolation of the function needs to match that of the enclosing type. +@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'}} +} + +@MainActor +class C: isolated P { + func f() { } // okay +} + +// Associated conformances with isolation + +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 }} +@MainActor +struct SMissingIsolation: Q { + typealias A = C +} + +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'}} +@MainActor +struct SMissingIsolationViaWrapper: Q { + typealias A = PWrapper +} + +@SomeGlobalActor +class C2: isolated P { + func f() { } +} + +@MainActor +struct S: isolated 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 { + typealias A = C2 +}