Skip to content

Commit 71e23ac

Browse files
committed
Check consistency of isolation of conformances within other conformances
When a protocol conformance somehow depends on an isolated conformance, it must itself be isolated to the same global actor as the conformance on which it depends.
1 parent 52c46f8 commit 71e23ac

File tree

5 files changed

+157
-0
lines changed

5 files changed

+157
-0
lines changed

include/swift/AST/DiagnosticsSema.def

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2733,6 +2733,12 @@ ERROR(isolated_conformance_not_global_actor_isolated,none,
27332733
ERROR(isolated_conformance_experimental_feature,none,
27342734
"isolated conformances require experimental feature "
27352735
" 'IsolatedConformances'", ())
2736+
ERROR(nonisolated_conformance_depends_on_isolated_conformance,none,
2737+
"conformance of %0 to %1 depends on %2 conformance of %3 to %4; mark it as 'isolated'",
2738+
(Type, DeclName, ActorIsolation, Type, DeclName))
2739+
ERROR(isolated_conformance_mismatch_with_associated_isolation,none,
2740+
"%0 conformance of %1 to %2 cannot depend on %3 conformance of %4 to %5",
2741+
(ActorIsolation, Type, DeclName, ActorIsolation, Type, DeclName))
27362742
WARNING(remove_public_import,none,
27372743
"public import of %0 was not used in public declarations or inlinable code",
27382744
(Identifier))

lib/Sema/TypeCheckConcurrency.cpp

Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,7 @@
3030
#include "swift/AST/ImportCache.h"
3131
#include "swift/AST/Initializer.h"
3232
#include "swift/AST/NameLookupRequests.h"
33+
#include "swift/AST/PackConformance.h"
3334
#include "swift/AST/ParameterList.h"
3435
#include "swift/AST/ProtocolConformance.h"
3536
#include "swift/AST/TypeCheckRequests.h"
@@ -7669,3 +7670,56 @@ bool swift::diagnoseNonSendableFromDeinit(
76697670
diag::non_sendable_from_deinit,
76707671
var->getDescriptiveKind(), var->getName());
76717672
}
7673+
7674+
bool swift::forEachIsolatedConformance(
7675+
ProtocolConformanceRef conformance,
7676+
llvm::function_ref<bool(ProtocolConformance*)> body
7677+
) {
7678+
if (conformance.isInvalid() || conformance.isAbstract())
7679+
return false;
7680+
7681+
if (conformance.isPack()) {
7682+
auto pack = conformance.getPack()->getPatternConformances();
7683+
for (auto conformance : pack) {
7684+
if (forEachIsolatedConformance(conformance, body))
7685+
return true;
7686+
}
7687+
7688+
return false;
7689+
}
7690+
7691+
// Is this an isolated conformance?
7692+
auto concrete = conformance.getConcrete();
7693+
if (auto normal =
7694+
dyn_cast<NormalProtocolConformance>(concrete->getRootConformance())) {
7695+
if (normal->isIsolated()) {
7696+
if (body(concrete))
7697+
return true;
7698+
}
7699+
}
7700+
7701+
// Check conformances that are part of this conformance.
7702+
auto subMap = concrete->getSubstitutionMap();
7703+
for (auto conformance : subMap.getConformances()) {
7704+
if (forEachIsolatedConformance(conformance, body))
7705+
return true;
7706+
}
7707+
7708+
return false;
7709+
}
7710+
7711+
ActorIsolation swift::getConformanceIsolation(ProtocolConformance *conformance) {
7712+
auto rootNormal =
7713+
dyn_cast<NormalProtocolConformance>(conformance->getRootConformance());
7714+
if (!rootNormal)
7715+
return ActorIsolation::forNonisolated(false);
7716+
7717+
if (!rootNormal->isIsolated())
7718+
return ActorIsolation::forNonisolated(false);
7719+
7720+
auto nominal = rootNormal->getDeclContext()->getSelfNominalTypeDecl();
7721+
if (!nominal)
7722+
return ActorIsolation::forNonisolated(false);
7723+
7724+
return getActorIsolation(nominal);
7725+
}

lib/Sema/TypeCheckConcurrency.h

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -699,6 +699,19 @@ void introduceUnsafeInheritExecutorReplacements(
699699
void introduceUnsafeInheritExecutorReplacements(
700700
const DeclContext *dc, Type base, SourceLoc loc, LookupResult &result);
701701

702+
/// Enumerate all of the isolated conformances in the given conformance.
703+
///
704+
/// The given `body` will be called on each isolated conformance. If it ever
705+
/// returns `true`, this function will abort the search and return `true`.
706+
bool forEachIsolatedConformance(
707+
ProtocolConformanceRef conformance,
708+
llvm::function_ref<bool(ProtocolConformance*)> body
709+
);
710+
711+
/// Determine the isolation of the given conformance. This only applies to
712+
/// the immediate conformance, not any conformances on which it depends.
713+
ActorIsolation getConformanceIsolation(ProtocolConformance *conformance);
714+
702715
} // end namespace swift
703716

704717
namespace llvm {

lib/Sema/TypeCheckProtocol.cpp

Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5207,6 +5207,8 @@ static void ensureRequirementsAreSatisfied(ASTContext &ctx,
52075207
if (where.isImplicit())
52085208
return;
52095209

5210+
bool diagnosedIsolatedConformanceIssue = false;
5211+
52105212
conformance->forEachAssociatedConformance(
52115213
[&](Type depTy, ProtocolDecl *proto, unsigned index) {
52125214
auto assocConf = conformance->getAssociatedConformance(depTy, proto);
@@ -5230,6 +5232,50 @@ static void ensureRequirementsAreSatisfied(ASTContext &ctx,
52305232
where.withRefinedAvailability(availability), depTy, replacementTy);
52315233
}
52325234

5235+
if (!diagnosedIsolatedConformanceIssue) {
5236+
bool foundIssue = forEachIsolatedConformance(
5237+
ProtocolConformanceRef(assocConf),
5238+
[&](ProtocolConformance *isolatedConformance) {
5239+
// If the conformance we're checking isn't isolated at all, it
5240+
// needs "isolated".
5241+
if (!conformance->isIsolated()) {
5242+
ctx.Diags.diagnose(
5243+
conformance->getLoc(),
5244+
diag::nonisolated_conformance_depends_on_isolated_conformance,
5245+
typeInContext, conformance->getProtocol()->getName(),
5246+
getConformanceIsolation(isolatedConformance),
5247+
isolatedConformance->getType(),
5248+
isolatedConformance->getProtocol()->getName()
5249+
).fixItInsert(conformance->getProtocolNameLoc(), "isolated ");
5250+
5251+
return true;
5252+
}
5253+
5254+
// The conformance is isolated, but we need it to have the same
5255+
// isolation as the other isolated conformance we found.
5256+
auto outerIsolation = getConformanceIsolation(conformance);
5257+
auto innerIsolation = getConformanceIsolation(isolatedConformance);
5258+
if (outerIsolation != innerIsolation) {
5259+
ctx.Diags.diagnose(
5260+
conformance->getLoc(),
5261+
diag::isolated_conformance_mismatch_with_associated_isolation,
5262+
outerIsolation,
5263+
typeInContext, conformance->getProtocol()->getName(),
5264+
innerIsolation,
5265+
isolatedConformance->getType(),
5266+
isolatedConformance->getProtocol()->getName()
5267+
);
5268+
5269+
return true;
5270+
}
5271+
5272+
return false;
5273+
}
5274+
);
5275+
5276+
diagnosedIsolatedConformanceIssue = foundIssue;
5277+
}
5278+
52335279
return false;
52345280
});
52355281
}

test/Concurrency/isolated_conformance.swift

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -40,3 +40,41 @@ class CMismatchedIsolation: isolated P {
4040
class C: isolated P {
4141
func f() { } // okay
4242
}
43+
44+
// Associated conformances with isolation
45+
46+
protocol Q {
47+
associatedtype A: P
48+
}
49+
50+
// 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 }}
51+
@MainActor
52+
struct SMissingIsolation: Q {
53+
typealias A = C
54+
}
55+
56+
struct PWrapper<T: P>: P {
57+
func f() { }
58+
}
59+
60+
// expected-error@+2{{conformance of 'SMissingIsolationViaWrapper' to 'Q' depends on main actor-isolated conformance of 'C' to 'P'; mark it as 'isolated'}}
61+
@MainActor
62+
struct SMissingIsolationViaWrapper: Q {
63+
typealias A = PWrapper<C>
64+
}
65+
66+
@SomeGlobalActor
67+
class C2: isolated P {
68+
func f() { }
69+
}
70+
71+
@MainActor
72+
struct S: isolated Q {
73+
typealias A = C
74+
}
75+
76+
// expected-error@+2{{main actor-isolated conformance of 'SMismatchedActors' to 'Q' cannot depend on global actor 'SomeGlobalActor'-isolated conformance of 'C2' to 'P'}}
77+
@MainActor
78+
struct SMismatchedActors: isolated Q {
79+
typealias A = C2
80+
}

0 commit comments

Comments
 (0)