Skip to content

Commit 064e0c6

Browse files
committed
Ensure that isolated conformances originate in the same isolation domain
This is the missing check for "rule #1" in the isolated conformances proposal, which states that an isolated conformance can only be referenced within the same isolation domain as the conformance. For example, a main-actor-isolated conformance can only be used within main-actor code.
1 parent 297564f commit 064e0c6

File tree

6 files changed

+288
-0
lines changed

6 files changed

+288
-0
lines changed

include/swift/AST/DiagnosticsSema.def

+3
Original file line numberDiff line numberDiff line change
@@ -8320,6 +8320,9 @@ ERROR(isolated_conformance_with_sendable_simple,none,
83208320
"isolated conformance of %0 to %1 cannot be used to satisfy conformance "
83218321
"requirement for a `Sendable` type parameter ",
83228322
(Type, DeclName))
8323+
ERROR(isolated_conformance_wrong_domain,none,
8324+
"%0 isolated conformance of %1 to %2 cannot be used in %3 context",
8325+
(ActorIsolation, Type, DeclName, ActorIsolation))
83238326

83248327
//===----------------------------------------------------------------------===//
83258328
// MARK: @execution Attribute

lib/Sema/TypeCheckConcurrency.cpp

+112
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@
1818
#include "MiscDiagnostics.h"
1919
#include "TypeCheckDistributed.h"
2020
#include "TypeCheckInvertible.h"
21+
#include "TypeCheckProtocol.h"
2122
#include "TypeCheckType.h"
2223
#include "TypeChecker.h"
2324
#include "swift/AST/ASTWalker.h"
@@ -3175,6 +3176,18 @@ namespace {
31753176
checkDefaultArgument(defaultArg);
31763177
}
31773178

3179+
if (auto erasureExpr = dyn_cast<ErasureExpr>(expr)) {
3180+
checkIsolatedConformancesInContext(
3181+
erasureExpr->getConformances(), erasureExpr->getLoc(),
3182+
getDeclContext());
3183+
}
3184+
3185+
if (auto *underlyingToOpaque = dyn_cast<UnderlyingToOpaqueExpr>(expr)) {
3186+
checkIsolatedConformancesInContext(
3187+
underlyingToOpaque->substitutions, underlyingToOpaque->getLoc(),
3188+
getDeclContext());
3189+
}
3190+
31783191
return Action::Continue(expr);
31793192
}
31803193

@@ -4282,6 +4295,9 @@ namespace {
42824295
if (!declRef)
42834296
return false;
42844297

4298+
// Make sure isolated conformances are formed in the right context.
4299+
checkIsolatedConformancesInContext(declRef, loc, getDeclContext());
4300+
42854301
auto decl = declRef.getDecl();
42864302

42874303
// If this declaration is a callee from the enclosing application,
@@ -7684,3 +7700,99 @@ ActorIsolation swift::getConformanceIsolation(ProtocolConformance *conformance)
76847700

76857701
return getActorIsolation(nominal);
76867702
}
7703+
7704+
namespace {
7705+
/// Identifies isolated conformances whose isolation differs from the
7706+
/// context's isolation.
7707+
class MismatchedIsolatedConformances {
7708+
llvm::TinyPtrVector<ProtocolConformance *> badIsolatedConformances;
7709+
DeclContext *fromDC;
7710+
mutable std::optional<ActorIsolation> fromIsolation;
7711+
7712+
public:
7713+
MismatchedIsolatedConformances(const DeclContext *fromDC)
7714+
: fromDC(const_cast<DeclContext *>(fromDC)) { }
7715+
7716+
ActorIsolation getContextIsolation() const {
7717+
if (!fromIsolation)
7718+
fromIsolation = getActorIsolationOfContext(fromDC);
7719+
7720+
return *fromIsolation;
7721+
}
7722+
7723+
ArrayRef<ProtocolConformance *> getBadIsolatedConformances() const {
7724+
return badIsolatedConformances;
7725+
}
7726+
7727+
explicit operator bool() const { return !badIsolatedConformances.empty(); }
7728+
7729+
bool operator()(ProtocolConformanceRef conformance) {
7730+
if (conformance.isAbstract() || conformance.isPack())
7731+
return false;
7732+
7733+
auto concrete = conformance.getConcrete();
7734+
auto normal = dyn_cast<NormalProtocolConformance>(
7735+
concrete->getRootConformance());
7736+
if (!normal)
7737+
return false;
7738+
7739+
if (!normal->isIsolated())
7740+
return false;
7741+
7742+
auto conformanceIsolation = getConformanceIsolation(concrete);
7743+
if (conformanceIsolation == getContextIsolation())
7744+
return true;
7745+
7746+
badIsolatedConformances.push_back(concrete);
7747+
return false;
7748+
}
7749+
7750+
/// If there were any bad isolated conformances, diagnose them and return
7751+
/// true. Otherwise, returns false.
7752+
bool diagnose(SourceLoc loc) const {
7753+
if (badIsolatedConformances.empty())
7754+
return false;
7755+
7756+
ASTContext &ctx = fromDC->getASTContext();
7757+
auto firstConformance = badIsolatedConformances.front();
7758+
ctx.Diags.diagnose(
7759+
loc, diag::isolated_conformance_wrong_domain,
7760+
getConformanceIsolation(firstConformance),
7761+
firstConformance->getType(),
7762+
firstConformance->getProtocol()->getName(),
7763+
getContextIsolation());
7764+
return true;
7765+
}
7766+
};
7767+
7768+
}
7769+
7770+
bool swift::checkIsolatedConformancesInContext(
7771+
ConcreteDeclRef declRef, SourceLoc loc, const DeclContext *dc) {
7772+
MismatchedIsolatedConformances mismatched(dc);
7773+
forEachConformance(declRef, mismatched);
7774+
return mismatched.diagnose(loc);
7775+
}
7776+
7777+
bool swift::checkIsolatedConformancesInContext(
7778+
ArrayRef<ProtocolConformanceRef> conformances, SourceLoc loc,
7779+
const DeclContext *dc) {
7780+
MismatchedIsolatedConformances mismatched(dc);
7781+
for (auto conformance: conformances)
7782+
forEachConformance(conformance, mismatched);
7783+
return mismatched.diagnose(loc);
7784+
}
7785+
7786+
bool swift::checkIsolatedConformancesInContext(
7787+
SubstitutionMap subs, SourceLoc loc, const DeclContext *dc) {
7788+
MismatchedIsolatedConformances mismatched(dc);
7789+
forEachConformance(subs, mismatched);
7790+
return mismatched.diagnose(loc);
7791+
}
7792+
7793+
bool swift::checkIsolatedConformancesInContext(
7794+
Type type, SourceLoc loc, const DeclContext *dc) {
7795+
MismatchedIsolatedConformances mismatched(dc);
7796+
forEachConformance(type, mismatched);
7797+
return mismatched.diagnose(loc);
7798+
}

lib/Sema/TypeCheckConcurrency.h

+31
Original file line numberDiff line numberDiff line change
@@ -703,6 +703,37 @@ void introduceUnsafeInheritExecutorReplacements(
703703
/// the immediate conformance, not any conformances on which it depends.
704704
ActorIsolation getConformanceIsolation(ProtocolConformance *conformance);
705705

706+
/// Check for correct use of isolated conformances in the given reference.
707+
///
708+
/// This checks that any isolated conformances that occur in the given
709+
/// declaration reference match the isolated of the context.
710+
bool checkIsolatedConformancesInContext(
711+
ConcreteDeclRef declRef, SourceLoc loc, const DeclContext *dc);
712+
713+
/// Check for correct use of isolated conformances in the set given set of
714+
/// protocol conformances.
715+
///
716+
/// This checks that any isolated conformances that occur in the given
717+
/// declaration reference match the isolated of the context.
718+
bool checkIsolatedConformancesInContext(
719+
ArrayRef<ProtocolConformanceRef> conformances, SourceLoc loc,
720+
const DeclContext *dc);
721+
722+
/// Check for correct use of isolated conformances in the given substitution
723+
/// map.
724+
///
725+
/// This checks that any isolated conformances that occur in the given
726+
/// substitution map match the isolated of the context.
727+
bool checkIsolatedConformancesInContext(
728+
SubstitutionMap subs, SourceLoc loc, const DeclContext *dc);
729+
730+
/// Check for correct use of isolated conformances in the given type.
731+
///
732+
/// This checks that any isolated conformances that occur in the given
733+
/// type match the isolated of the context.
734+
bool checkIsolatedConformancesInContext(
735+
Type type, SourceLoc loc, const DeclContext *dc);
736+
706737
} // end namespace swift
707738

708739
namespace llvm {

lib/Sema/TypeCheckProtocol.cpp

+104
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,7 @@
4343
#include "swift/AST/GenericSignature.h"
4444
#include "swift/AST/NameLookup.h"
4545
#include "swift/AST/NameLookupRequests.h"
46+
#include "swift/AST/PackConformance.h"
4647
#include "swift/AST/ParameterList.h"
4748
#include "swift/AST/PotentialMacroExpansions.h"
4849
#include "swift/AST/PrettyStackTrace.h"
@@ -7159,3 +7160,106 @@ void TypeChecker::inferDefaultWitnesses(ProtocolDecl *proto) {
71597160
req.getFirstType()->getCanonicalType(), requirementProto, conformance);
71607161
}
71617162
}
7163+
7164+
bool swift::forEachConformance(
7165+
SubstitutionMap subs,
7166+
llvm::function_ref<bool(ProtocolConformanceRef)> body) {
7167+
if (!subs)
7168+
return false;
7169+
7170+
for (auto type: subs.getReplacementTypes()) {
7171+
if (forEachConformance(type, body))
7172+
return true;
7173+
}
7174+
7175+
for (auto conformance: subs.getConformances()) {
7176+
if (forEachConformance(conformance, body))
7177+
return true;
7178+
}
7179+
7180+
return false;
7181+
}
7182+
7183+
bool swift::forEachConformance(
7184+
ProtocolConformanceRef conformance,
7185+
llvm::function_ref<bool(ProtocolConformanceRef)> body) {
7186+
// Visit this conformance.
7187+
if (body(conformance))
7188+
return true;
7189+
7190+
if (conformance.isInvalid() || conformance.isAbstract())
7191+
return false;
7192+
7193+
if (conformance.isPack()) {
7194+
auto pack = conformance.getPack()->getPatternConformances();
7195+
for (auto conformance : pack) {
7196+
if (forEachConformance(conformance, body))
7197+
return true;
7198+
}
7199+
7200+
return false;
7201+
}
7202+
7203+
// Check the substitution make within this conformance.
7204+
auto concrete = conformance.getConcrete();
7205+
if (forEachConformance(concrete->getSubstitutionMap(), body))
7206+
return true;
7207+
7208+
7209+
return false;
7210+
}
7211+
7212+
bool swift::forEachConformance(
7213+
Type type, llvm::function_ref<bool(ProtocolConformanceRef)> body) {
7214+
return type.findIf([&](Type type) {
7215+
if (auto typeAlias = dyn_cast<TypeAliasType>(type.getPointer())) {
7216+
if (forEachConformance(typeAlias->getSubstitutionMap(), body))
7217+
return true;
7218+
7219+
return false;
7220+
}
7221+
7222+
if (auto opaqueArchetype =
7223+
dyn_cast<OpaqueTypeArchetypeType>(type.getPointer())) {
7224+
if (forEachConformance(opaqueArchetype->getSubstitutions(), body))
7225+
return true;
7226+
7227+
return false;
7228+
}
7229+
7230+
// Look through type sugar.
7231+
if (auto sugarType = dyn_cast<SyntaxSugarType>(type.getPointer())) {
7232+
type = sugarType->getImplementationType();
7233+
}
7234+
7235+
if (auto boundGeneric = dyn_cast<BoundGenericType>(type.getPointer())) {
7236+
auto subs = boundGeneric->getContextSubstitutionMap();
7237+
if (forEachConformance(subs, body))
7238+
return true;
7239+
7240+
return false;
7241+
}
7242+
7243+
return false;
7244+
});
7245+
}
7246+
7247+
bool swift::forEachConformance(
7248+
ConcreteDeclRef declRef,
7249+
llvm::function_ref<bool(ProtocolConformanceRef)> body) {
7250+
if (!declRef)
7251+
return false;
7252+
7253+
Type type = declRef.getDecl()->getInterfaceType();
7254+
if (auto subs = declRef.getSubstitutions()) {
7255+
if (forEachConformance(subs, body))
7256+
return true;
7257+
7258+
type = type.subst(subs);
7259+
}
7260+
7261+
if (forEachConformance(type, body))
7262+
return true;
7263+
7264+
return false;
7265+
}

lib/Sema/TypeCheckProtocol.h

+32
Original file line numberDiff line numberDiff line change
@@ -240,6 +240,38 @@ bool witnessHasImplementsAttrForRequiredName(ValueDecl *witness,
240240
bool witnessHasImplementsAttrForExactRequirement(ValueDecl *witness,
241241
ValueDecl *requirement);
242242

243+
/// Visit each conformance within the given type.
244+
///
245+
/// If `body` returns true for any conformance, this function stops the
246+
/// traversal and returns true.
247+
bool forEachConformance(
248+
Type type, llvm::function_ref<bool(ProtocolConformanceRef)> body);
249+
250+
/// Visit each conformance within the given conformance (including the given
251+
/// one).
252+
///
253+
/// If `body` returns true for any conformance, this function stops the
254+
/// traversal and returns true.
255+
bool forEachConformance(
256+
ProtocolConformanceRef conformance,
257+
llvm::function_ref<bool(ProtocolConformanceRef)> body);
258+
259+
/// Visit each conformance within the given substitution map.
260+
///
261+
/// If `body` returns true for any conformance, this function stops the
262+
/// traversal and returns true.
263+
bool forEachConformance(
264+
SubstitutionMap subs,
265+
llvm::function_ref<bool(ProtocolConformanceRef)> body);
266+
267+
/// Visit each conformance within the given declaration reference.
268+
///
269+
/// If `body` returns true for any conformance, this function stops the
270+
/// traversal and returns true.
271+
bool forEachConformance(
272+
ConcreteDeclRef declRef,
273+
llvm::function_ref<bool(ProtocolConformanceRef)> body);
274+
243275
}
244276

245277
#endif // SWIFT_SEMA_PROTOCOL_H

test/Concurrency/isolated_conformance.swift

+6
Original file line numberDiff line numberDiff line change
@@ -119,3 +119,9 @@ func testIsolationConformancesInCall(c: C) {
119119
acceptSendableP(c) // expected-error{{isolated conformance of 'C' to 'P' cannot be used to satisfy conformance requirement for a `Sendable` type parameter}}
120120
acceptSendableMetaP(c) // expected-error{{isolated conformance of 'C' to 'P' cannot be used to satisfy conformance requirement for a `Sendable` type parameter}}
121121
}
122+
123+
func testIsolationConformancesFromOutside(c: C) {
124+
acceptP(c) // expected-error{{main actor-isolated isolated conformance of 'C' to 'P' cannot be used in nonisolated context}}
125+
let _: any P = c // expected-error{{main actor-isolated isolated conformance of 'C' to 'P' cannot be used in nonisolated context}}
126+
let _ = PWrapper<C>() // expected-error{{main actor-isolated isolated conformance of 'C' to 'P' cannot be used in nonisolated context}}
127+
}

0 commit comments

Comments
 (0)