Skip to content

Commit 52c46f8

Browse files
committed
Parsing and type checking for the definition of isolated conformances
Allow a conformance to be "isolated", meaning that it stays in the same isolation domain as the conforming type. Only allow this for global-actor-isolated types. When a conformance is isolated, a nonisolated requirement can be witnessed by a declaration with the same global actor isolation as the enclosing type.
1 parent 14dd14d commit 52c46f8

15 files changed

+175
-24
lines changed

include/swift/AST/ConformanceAttributes.h

+5
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,9 @@ struct ConformanceAttributes {
2727

2828
/// The location of the "unsafe" attribute if present.
2929
SourceLoc unsafeLoc;
30+
31+
/// The location of the "@isolated" attribute if present.
32+
SourceLoc isolatedLoc;
3033

3134
/// Merge other conformance attributes into this set.
3235
ConformanceAttributes &
@@ -37,6 +40,8 @@ struct ConformanceAttributes {
3740
preconcurrencyLoc = other.preconcurrencyLoc;
3841
if (other.unsafeLoc.isValid())
3942
unsafeLoc = other.unsafeLoc;
43+
if (other.isolatedLoc.isValid())
44+
isolatedLoc = other.isolatedLoc;
4045
return *this;
4146
}
4247
};

include/swift/AST/Decl.h

+4-6
Original file line numberDiff line numberDiff line change
@@ -1836,12 +1836,13 @@ struct InheritedEntry : public TypeLoc {
18361836
bool isPreconcurrency() const {
18371837
return getOptions().contains(ProtocolConformanceFlags::Preconcurrency);
18381838
}
1839+
bool isIsolated() const {
1840+
return getOptions().contains(ProtocolConformanceFlags::Isolated);
1841+
}
18391842

18401843
ExplicitSafety getExplicitSafety() const {
18411844
if (getOptions().contains(ProtocolConformanceFlags::Unsafe))
18421845
return ExplicitSafety::Unsafe;
1843-
if (getOptions().contains(ProtocolConformanceFlags::Safe))
1844-
return ExplicitSafety::Safe;
18451846
return ExplicitSafety::Unspecified;
18461847
}
18471848

@@ -1852,13 +1853,10 @@ struct InheritedEntry : public TypeLoc {
18521853
}
18531854

18541855
void setOption(ExplicitSafety safety) {
1855-
RawOptions = (getOptions() - ProtocolConformanceFlags::Unsafe
1856-
- ProtocolConformanceFlags::Safe).toRaw();
1856+
RawOptions = (getOptions() - ProtocolConformanceFlags::Unsafe).toRaw();
18571857
switch (safety) {
18581858
case ExplicitSafety::Unspecified:
1859-
break;
18601859
case ExplicitSafety::Safe:
1861-
RawOptions = (getOptions() | ProtocolConformanceFlags::Safe).toRaw();
18621860
break;
18631861
case ExplicitSafety::Unsafe:
18641862
RawOptions = (getOptions() | ProtocolConformanceFlags::Unsafe).toRaw();

include/swift/AST/DiagnosticsSema.def

+9
Original file line numberDiff line numberDiff line change
@@ -2722,8 +2722,17 @@ WARNING(add_predates_concurrency_import,none,
27222722
GROUPED_WARNING(remove_predates_concurrency_import,PreconcurrencyImport,
27232723
DefaultIgnore,
27242724
"'@preconcurrency' attribute on module %0 has no effect", (Identifier))
2725+
NOTE(add_isolated_to_conformance,none,
2726+
"add 'isolated' to the %0 conformance to restrict it to %1 code",
2727+
(DeclName, ActorIsolation))
27252728
NOTE(add_preconcurrency_to_conformance,none,
27262729
"add '@preconcurrency' to the %0 conformance to defer isolation checking to run time", (DeclName))
2730+
ERROR(isolated_conformance_not_global_actor_isolated,none,
2731+
"isolated conformance is only permitted on global-actor-isolated types",
2732+
())
2733+
ERROR(isolated_conformance_experimental_feature,none,
2734+
"isolated conformances require experimental feature "
2735+
" 'IsolatedConformances'", ())
27272736
WARNING(remove_public_import,none,
27282737
"public import of %0 was not used in public declarations or inlinable code",
27292738
(Identifier))

include/swift/AST/ProtocolConformance.h

+5-2
Original file line numberDiff line numberDiff line change
@@ -669,6 +669,11 @@ class NormalProtocolConformance : public RootProtocolConformance,
669669
return getOptions().contains(ProtocolConformanceFlags::Preconcurrency);
670670
}
671671

672+
/// Whether this is an isolated conformance.
673+
bool isIsolated() const {
674+
return getOptions().contains(ProtocolConformanceFlags::Isolated);
675+
}
676+
672677
/// Retrieve the location of `@preconcurrency`, if there is one and it is
673678
/// known.
674679
SourceLoc getPreconcurrencyLoc() const { return PreconcurrencyLoc; }
@@ -678,8 +683,6 @@ class NormalProtocolConformance : public RootProtocolConformance,
678683
ExplicitSafety getExplicitSafety() const {
679684
if (getOptions().contains(ProtocolConformanceFlags::Unsafe))
680685
return ExplicitSafety::Unsafe;
681-
if (getOptions().contains(ProtocolConformanceFlags::Safe))
682-
return ExplicitSafety::Safe;
683686
return ExplicitSafety::Unspecified;
684687
}
685688

include/swift/AST/ProtocolConformanceOptions.h

+2-2
Original file line numberDiff line numberDiff line change
@@ -34,8 +34,8 @@ enum class ProtocolConformanceFlags {
3434
/// @retroactive conformance
3535
Retroactive = 0x08,
3636

37-
/// @safe conformance
38-
Safe = 0x10,
37+
/// @isolated conformance
38+
Isolated = 0x10,
3939

4040
// Note: whenever you add a bit here, update
4141
// NumProtocolConformanceOptions below.

include/swift/Basic/Features.def

+4
Original file line numberDiff line numberDiff line change
@@ -450,11 +450,15 @@ SUPPRESSIBLE_EXPERIMENTAL_FEATURE(CustomAvailability, true)
450450
/// Be strict about the Sendable conformance of metatypes.
451451
EXPERIMENTAL_FEATURE(StrictSendableMetatypes, true)
452452

453+
453454
/// Allow public enumerations to be extensible by default
454455
/// regardless of whether the module they are declared in
455456
/// is resilient or not.
456457
EXPERIMENTAL_FEATURE(ExtensibleEnums, true)
457458

459+
/// Allow isolated conformances.
460+
EXPERIMENTAL_FEATURE(IsolatedConformances, true)
461+
458462
#undef EXPERIMENTAL_FEATURE_EXCLUDED_FROM_MODULE_INTERFACE
459463
#undef EXPERIMENTAL_FEATURE
460464
#undef UPCOMING_FEATURE

lib/AST/ConformanceLookupTable.h

+7
Original file line numberDiff line numberDiff line change
@@ -154,6 +154,8 @@ class ConformanceLookupTable : public ASTAllocated<ConformanceLookupTable> {
154154
options |= ProtocolConformanceFlags::Preconcurrency;
155155
if (getUnsafeLoc().isValid())
156156
options |= ProtocolConformanceFlags::Unsafe;
157+
if (getIsolatedLoc().isValid())
158+
options |= ProtocolConformanceFlags::Isolated;
157159
return options;
158160
}
159161

@@ -209,6 +211,11 @@ class ConformanceLookupTable : public ASTAllocated<ConformanceLookupTable> {
209211
return attributes.unsafeLoc;
210212
}
211213

214+
/// The location of the @isolated attribute, if any.
215+
SourceLoc getIsolatedLoc() const {
216+
return attributes.isolatedLoc;
217+
}
218+
212219
/// For an inherited conformance, retrieve the class declaration
213220
/// for the inheriting class.
214221
ClassDecl *getInheritingClass() const {

lib/AST/Decl.cpp

+2
Original file line numberDiff line numberDiff line change
@@ -1780,6 +1780,8 @@ InheritedEntry::InheritedEntry(const TypeLoc &typeLoc)
17801780
setOption(ProtocolConformanceFlags::Unsafe);
17811781
if (typeRepr->findAttrLoc(TypeAttrKind::Preconcurrency).isValid())
17821782
setOption(ProtocolConformanceFlags::Preconcurrency);
1783+
if (typeRepr->findAttrLoc(TypeAttrKind::Isolated).isValid())
1784+
setOption(ProtocolConformanceFlags::Isolated);
17831785
}
17841786
}
17851787

lib/AST/FeatureSet.cpp

+5
Original file line numberDiff line numberDiff line change
@@ -348,6 +348,11 @@ static bool usesFeatureABIAttribute(Decl *decl) {
348348
return getABIAttr(decl) != nullptr;
349349
}
350350

351+
static bool usesFeatureIsolatedConformances(Decl *decl) {
352+
// FIXME: Check conformances associated with this decl?
353+
return false;
354+
}
355+
351356
UNINTERESTING_FEATURE(WarnUnsafe)
352357
UNINTERESTING_FEATURE(SafeInteropWrappers)
353358
UNINTERESTING_FEATURE(AssumeResilientCxxTypes)

lib/AST/NameLookup.cpp

+24-1
Original file line numberDiff line numberDiff line change
@@ -3144,6 +3144,12 @@ directReferencesForTypeRepr(Evaluator &evaluator, ASTContext &ctx,
31443144
attributed->getTypeRepr(), dc, options);
31453145
}
31463146

3147+
case TypeReprKind::Isolated: {
3148+
auto isolated = cast<IsolatedTypeRepr>(typeRepr);
3149+
return directReferencesForTypeRepr(evaluator, ctx,
3150+
isolated->getBase(), dc, options);
3151+
}
3152+
31473153
case TypeReprKind::Composition: {
31483154
auto composition = cast<CompositionTypeRepr>(typeRepr);
31493155
for (auto component : composition->getTypes()) {
@@ -3217,7 +3223,6 @@ directReferencesForTypeRepr(Evaluator &evaluator, ASTContext &ctx,
32173223
case TypeReprKind::Error:
32183224
case TypeReprKind::Function:
32193225
case TypeReprKind::Ownership:
3220-
case TypeReprKind::Isolated:
32213226
case TypeReprKind::CompileTimeConst:
32223227
case TypeReprKind::Metatype:
32233228
case TypeReprKind::Protocol:
@@ -3933,6 +3938,21 @@ CustomAttrNominalRequest::evaluate(Evaluator &evaluator,
39333938
return nullptr;
39343939
}
39353940

3941+
/// Find the location of 'isolated' within this type representation.
3942+
static SourceLoc findIsolatedLoc(TypeRepr *typeRepr) {
3943+
do {
3944+
if (auto isolatedTypeRepr = dyn_cast<IsolatedTypeRepr>(typeRepr))
3945+
return isolatedTypeRepr->getLoc();
3946+
3947+
if (auto attrTypeRepr = dyn_cast<AttributedTypeRepr>(typeRepr)) {
3948+
typeRepr = attrTypeRepr->getTypeRepr();
3949+
continue;
3950+
}
3951+
3952+
return SourceLoc();
3953+
} while (true);
3954+
}
3955+
39363956
/// Decompose the ith inheritance clause entry to a list of type declarations,
39373957
/// inverses, and optional AnyObject member.
39383958
void swift::getDirectlyInheritedNominalTypeDecls(
@@ -3971,6 +3991,9 @@ void swift::getDirectlyInheritedNominalTypeDecls(
39713991
attributes.uncheckedLoc = typeRepr->findAttrLoc(TypeAttrKind::Unchecked);
39723992
attributes.preconcurrencyLoc = typeRepr->findAttrLoc(TypeAttrKind::Preconcurrency);
39733993
attributes.unsafeLoc = typeRepr->findAttrLoc(TypeAttrKind::Unsafe);
3994+
3995+
// Look for an IsolatedTypeRepr.
3996+
attributes.isolatedLoc = findIsolatedLoc(typeRepr);
39743997
}
39753998

39763999
// Form the result.

lib/Parse/ParseType.cpp

+3-1
Original file line numberDiff line numberDiff line change
@@ -163,7 +163,9 @@ ParserResult<TypeRepr> Parser::parseTypeSimple(
163163
Diag<> MessageID, ParseTypeReason reason) {
164164
ParserResult<TypeRepr> ty;
165165

166-
if (isParameterSpecifier()) {
166+
if (isParameterSpecifier() &&
167+
!(!Context.LangOpts.hasFeature(Feature::IsolatedConformances) &&
168+
Tok.isContextualKeyword("isolated"))) {
167169
// Type specifier should already be parsed before here. This only happens
168170
// for construct like 'P1 & inout P2'.
169171
diagnose(Tok.getLoc(), diag::attr_only_on_parameters, Tok.getRawText());

lib/Sema/TypeCheckProtocol.cpp

+56-7
Original file line numberDiff line numberDiff line change
@@ -2541,6 +2541,22 @@ checkIndividualConformance(NormalProtocolConformance *conformance) {
25412541
ComplainLoc, diag::unchecked_conformance_not_special, ProtoType);
25422542
}
25432543

2544+
// Complain if the conformance is isolated but the conforming type is
2545+
// not global-actor-isolated.
2546+
if (conformance->isIsolated()) {
2547+
auto enclosingNominal = DC->getSelfNominalTypeDecl();
2548+
if (!enclosingNominal ||
2549+
!getActorIsolation(enclosingNominal).isGlobalActor()) {
2550+
Context.Diags.diagnose(
2551+
ComplainLoc, diag::isolated_conformance_not_global_actor_isolated);
2552+
}
2553+
2554+
if (!Context.LangOpts.hasFeature(Feature::IsolatedConformances)) {
2555+
Context.Diags.diagnose(
2556+
ComplainLoc, diag::isolated_conformance_experimental_feature);
2557+
}
2558+
}
2559+
25442560
bool allowImpliedConditionalConformance = false;
25452561
if (Proto->isSpecificProtocol(KnownProtocolKind::Sendable)) {
25462562
// In -swift-version 5 mode, a conditional conformance to a protocol can imply
@@ -3313,6 +3329,14 @@ static bool hasExplicitGlobalActorAttr(ValueDecl *decl) {
33133329
return !globalActorAttr->first->isImplicit();
33143330
}
33153331

3332+
/// Determine whether the given actor isolation matches that of the enclosing
3333+
/// type.
3334+
static bool isolationMatchesEnclosingType(
3335+
ActorIsolation isolation, NominalTypeDecl *nominal) {
3336+
auto nominalIsolation = getActorIsolation(nominal);
3337+
return isolation == nominalIsolation;
3338+
}
3339+
33163340
std::optional<ActorIsolation>
33173341
ConformanceChecker::checkActorIsolation(ValueDecl *requirement,
33183342
ValueDecl *witness,
@@ -3342,7 +3366,8 @@ ConformanceChecker::checkActorIsolation(ValueDecl *requirement,
33423366
Conformance->isPreconcurrency() &&
33433367
!(requirementIsolation.isActorIsolated() ||
33443368
requirement->getAttrs().hasAttribute<NonisolatedAttr>());
3345-
3369+
bool isIsolatedConformance = false;
3370+
33463371
switch (refResult) {
33473372
case ActorReferenceResult::SameConcurrencyDomain:
33483373
// If the witness has distributed-actor isolation, we have extra
@@ -3374,6 +3399,17 @@ ConformanceChecker::checkActorIsolation(ValueDecl *requirement,
33743399

33753400
return std::nullopt;
33763401
case ActorReferenceResult::EntersActor:
3402+
// If the conformance itself is isolated, and the witness isolation
3403+
// matches the enclosing type's isolation, treat this as being in the
3404+
// same concurrency domain.
3405+
if (Conformance->isIsolated() &&
3406+
refResult.isolation.isGlobalActor() &&
3407+
isolationMatchesEnclosingType(
3408+
refResult.isolation, DC->getSelfNominalTypeDecl())) {
3409+
sameConcurrencyDomain = true;
3410+
isIsolatedConformance = true;
3411+
}
3412+
33773413
// Handled below.
33783414
break;
33793415
}
@@ -3446,7 +3482,12 @@ ConformanceChecker::checkActorIsolation(ValueDecl *requirement,
34463482

34473483
// If we aren't missing anything or this is a witness to a `@preconcurrency`
34483484
// conformance, do a Sendable check and move on.
3449-
if (!missingOptions || isPreconcurrency) {
3485+
if (!missingOptions || isPreconcurrency || isIsolatedConformance) {
3486+
// An isolated conformance won't ever leave the isolation domain in which
3487+
// it was created, so there is nothing to check.
3488+
if (isIsolatedConformance)
3489+
return std::nullopt;
3490+
34503491
// FIXME: Disable Sendable checking when the witness is an initializer
34513492
// that is explicitly marked nonisolated.
34523493
if (isa<ConstructorDecl>(witness) &&
@@ -3546,18 +3587,26 @@ ConformanceChecker::checkActorIsolation(ValueDecl *requirement,
35463587
witness->diagnose(diag::note_add_nonisolated_to_decl, witness)
35473588
.fixItInsert(witness->getAttributeInsertionLoc(true), "nonisolated ");
35483589
}
3549-
3590+
35503591
// Another way to address the issue is to mark the conformance as
3551-
// "preconcurrency".
3592+
// "isolated" or "@preconcurrency".
35523593
if (Conformance->getSourceKind() == ConformanceEntryKind::Explicit &&
3553-
!Conformance->isPreconcurrency() &&
3554-
!suggestedPreconcurrency &&
3594+
!Conformance->isIsolated() && !Conformance->isPreconcurrency() &&
3595+
!suggestedPreconcurrencyOrIsolated &&
35553596
!requirementIsolation.isActorIsolated()) {
3597+
if (Context.LangOpts.hasFeature(Feature::IsolatedConformances)) {
3598+
Context.Diags.diagnose(Conformance->getProtocolNameLoc(),
3599+
diag::add_isolated_to_conformance,
3600+
Proto->getName(), refResult.isolation)
3601+
.fixItInsert(Conformance->getProtocolNameLoc(), "isolated ");
3602+
}
3603+
35563604
Context.Diags.diagnose(Conformance->getProtocolNameLoc(),
35573605
diag::add_preconcurrency_to_conformance,
35583606
Proto->getName())
35593607
.fixItInsert(Conformance->getProtocolNameLoc(), "@preconcurrency ");
3560-
suggestedPreconcurrency = true;
3608+
3609+
suggestedPreconcurrencyOrIsolated = true;
35613610
}
35623611
}
35633612

lib/Sema/TypeCheckProtocol.h

+2-2
Original file line numberDiff line numberDiff line change
@@ -112,8 +112,8 @@ enum class ResolveWitnessResult {
112112
/// This helper class handles most of the details of checking whether a
113113
/// given type (\c Adoptee) conforms to a protocol (\c Proto).
114114
class ConformanceChecker : public WitnessChecker {
115-
/// Whether we already suggested adding `@preconcurrency`.
116-
bool suggestedPreconcurrency = false;
115+
/// Whether we already suggested adding `@preconcurrency` or 'isolated'.
116+
bool suggestedPreconcurrencyOrIsolated = false;
117117

118118
public:
119119
NormalProtocolConformance *Conformance;

lib/Sema/TypeCheckType.cpp

+5-3
Original file line numberDiff line numberDiff line change
@@ -5175,8 +5175,9 @@ NeverNullType
51755175
TypeResolver::resolveIsolatedTypeRepr(IsolatedTypeRepr *repr,
51765176
TypeResolutionOptions options) {
51775177
// isolated is only value for non-EnumCaseDecl parameters.
5178-
if (!options.is(TypeResolverContext::FunctionInput) ||
5179-
options.hasBase(TypeResolverContext::EnumElementDecl)) {
5178+
if ((!options.is(TypeResolverContext::FunctionInput) ||
5179+
options.hasBase(TypeResolverContext::EnumElementDecl)) &&
5180+
!options.is(TypeResolverContext::Inherited)) {
51805181
diagnoseInvalid(
51815182
repr, repr->getSpecifierLoc(), diag::attr_only_on_parameters,
51825183
"isolated");
@@ -5197,7 +5198,8 @@ TypeResolver::resolveIsolatedTypeRepr(IsolatedTypeRepr *repr,
51975198
unwrappedType = dynamicSelfType->getSelfType();
51985199
}
51995200

5200-
if (inStage(TypeResolutionStage::Interface)) {
5201+
if (inStage(TypeResolutionStage::Interface) &&
5202+
!options.is(TypeResolverContext::Inherited)) {
52015203
if (auto *env = resolution.getGenericSignature().getGenericEnvironment())
52025204
unwrappedType = env->mapTypeIntoContext(unwrappedType);
52035205

0 commit comments

Comments
 (0)