Skip to content

Ensure that isolated conformances originate in the same isolation domain #79760

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions include/swift/AST/DiagnosticsSema.def
Original file line number Diff line number Diff line change
Expand Up @@ -8320,6 +8320,9 @@ 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))
ERROR(isolated_conformance_wrong_domain,none,
"%0 isolated conformance of %1 to %2 cannot be used in %3 context",
(ActorIsolation, Type, DeclName, ActorIsolation))

//===----------------------------------------------------------------------===//
// MARK: @execution Attribute
Expand Down
112 changes: 112 additions & 0 deletions lib/Sema/TypeCheckConcurrency.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@
#include "MiscDiagnostics.h"
#include "TypeCheckDistributed.h"
#include "TypeCheckInvertible.h"
#include "TypeCheckProtocol.h"
#include "TypeCheckType.h"
#include "TypeChecker.h"
#include "swift/AST/ASTWalker.h"
Expand Down Expand Up @@ -3175,6 +3176,18 @@ namespace {
checkDefaultArgument(defaultArg);
}

if (auto erasureExpr = dyn_cast<ErasureExpr>(expr)) {
checkIsolatedConformancesInContext(
erasureExpr->getConformances(), erasureExpr->getLoc(),
getDeclContext());
}

if (auto *underlyingToOpaque = dyn_cast<UnderlyingToOpaqueExpr>(expr)) {
checkIsolatedConformancesInContext(
underlyingToOpaque->substitutions, underlyingToOpaque->getLoc(),
getDeclContext());
}

return Action::Continue(expr);
}

Expand Down Expand Up @@ -4282,6 +4295,9 @@ namespace {
if (!declRef)
return false;

// Make sure isolated conformances are formed in the right context.
checkIsolatedConformancesInContext(declRef, loc, getDeclContext());

auto decl = declRef.getDecl();

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

return getActorIsolation(nominal);
}

namespace {
/// Identifies isolated conformances whose isolation differs from the
/// context's isolation.
class MismatchedIsolatedConformances {
llvm::TinyPtrVector<ProtocolConformance *> badIsolatedConformances;
DeclContext *fromDC;
mutable std::optional<ActorIsolation> fromIsolation;

public:
MismatchedIsolatedConformances(const DeclContext *fromDC)
: fromDC(const_cast<DeclContext *>(fromDC)) { }

ActorIsolation getContextIsolation() const {
if (!fromIsolation)
fromIsolation = getActorIsolationOfContext(fromDC);

return *fromIsolation;
}

ArrayRef<ProtocolConformance *> getBadIsolatedConformances() const {
return badIsolatedConformances;
}

explicit operator bool() const { return !badIsolatedConformances.empty(); }

bool operator()(ProtocolConformanceRef conformance) {
if (conformance.isAbstract() || conformance.isPack())
return false;

auto concrete = conformance.getConcrete();
auto normal = dyn_cast<NormalProtocolConformance>(
concrete->getRootConformance());
if (!normal)
return false;

if (!normal->isIsolated())
return false;

auto conformanceIsolation = getConformanceIsolation(concrete);
if (conformanceIsolation == getContextIsolation())
return true;

badIsolatedConformances.push_back(concrete);
return false;
}

/// If there were any bad isolated conformances, diagnose them and return
/// true. Otherwise, returns false.
bool diagnose(SourceLoc loc) const {
if (badIsolatedConformances.empty())
return false;

ASTContext &ctx = fromDC->getASTContext();
auto firstConformance = badIsolatedConformances.front();
ctx.Diags.diagnose(
loc, diag::isolated_conformance_wrong_domain,
getConformanceIsolation(firstConformance),
firstConformance->getType(),
firstConformance->getProtocol()->getName(),
getContextIsolation());
return true;
}
};

}

bool swift::checkIsolatedConformancesInContext(
ConcreteDeclRef declRef, SourceLoc loc, const DeclContext *dc) {
MismatchedIsolatedConformances mismatched(dc);
forEachConformance(declRef, mismatched);
return mismatched.diagnose(loc);
}

bool swift::checkIsolatedConformancesInContext(
ArrayRef<ProtocolConformanceRef> conformances, SourceLoc loc,
const DeclContext *dc) {
MismatchedIsolatedConformances mismatched(dc);
for (auto conformance: conformances)
forEachConformance(conformance, mismatched);
return mismatched.diagnose(loc);
}

bool swift::checkIsolatedConformancesInContext(
SubstitutionMap subs, SourceLoc loc, const DeclContext *dc) {
MismatchedIsolatedConformances mismatched(dc);
forEachConformance(subs, mismatched);
return mismatched.diagnose(loc);
}

bool swift::checkIsolatedConformancesInContext(
Type type, SourceLoc loc, const DeclContext *dc) {
MismatchedIsolatedConformances mismatched(dc);
forEachConformance(type, mismatched);
return mismatched.diagnose(loc);
}
31 changes: 31 additions & 0 deletions lib/Sema/TypeCheckConcurrency.h
Original file line number Diff line number Diff line change
Expand Up @@ -703,6 +703,37 @@ void introduceUnsafeInheritExecutorReplacements(
/// 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
/// declaration reference match the isolated of the context.
bool checkIsolatedConformancesInContext(
ConcreteDeclRef declRef, SourceLoc loc, const DeclContext *dc);

/// Check for correct use of isolated conformances in the set given set of
/// protocol conformances.
///
/// This checks that any isolated conformances that occur in the given
/// declaration reference match the isolated of the context.
bool checkIsolatedConformancesInContext(
ArrayRef<ProtocolConformanceRef> conformances, SourceLoc loc,
const DeclContext *dc);

/// Check for correct use of isolated conformances in the given substitution
/// map.
///
/// This checks that any isolated conformances that occur in the given
/// substitution map match the isolated of the context.
bool checkIsolatedConformancesInContext(
SubstitutionMap subs, SourceLoc loc, const DeclContext *dc);

/// Check for correct use of isolated conformances in the given type.
///
/// This checks that any isolated conformances that occur in the given
/// type match the isolated of the context.
bool checkIsolatedConformancesInContext(
Type type, SourceLoc loc, const DeclContext *dc);

} // end namespace swift

namespace llvm {
Expand Down
139 changes: 139 additions & 0 deletions lib/Sema/TypeCheckProtocol.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,7 @@
#include "swift/AST/GenericSignature.h"
#include "swift/AST/NameLookup.h"
#include "swift/AST/NameLookupRequests.h"
#include "swift/AST/PackConformance.h"
#include "swift/AST/ParameterList.h"
#include "swift/AST/PotentialMacroExpansions.h"
#include "swift/AST/PrettyStackTrace.h"
Expand Down Expand Up @@ -7159,3 +7160,141 @@ void TypeChecker::inferDefaultWitnesses(ProtocolDecl *proto) {
req.getFirstType()->getCanonicalType(), requirementProto, conformance);
}
}

bool swift::forEachConformance(
SubstitutionMap subs,
llvm::function_ref<bool(ProtocolConformanceRef)> body,
VisitedConformances *visitedConformances) {
if (!subs)
return false;

VisitedConformances visited;
if (!visitedConformances)
visitedConformances = &visited;

for (auto type: subs.getReplacementTypes()) {
if (forEachConformance(type, body, visitedConformances))
return true;
}

for (auto conformance: subs.getConformances()) {
if (forEachConformance(conformance, body, visitedConformances))
return true;
}

return false;
}

bool swift::forEachConformance(
ProtocolConformanceRef conformance,
llvm::function_ref<bool(ProtocolConformanceRef)> body,
VisitedConformances *visitedConformances) {
// Make sure we can store visited conformances.
VisitedConformances visited;
if (!visitedConformances)
visitedConformances = &visited;

if (conformance.isInvalid() || conformance.isAbstract())
return false;

if (conformance.isPack()) {
auto pack = conformance.getPack()->getPatternConformances();
for (auto conformance : pack) {
if (forEachConformance(conformance, body, visitedConformances))
return true;
}

return false;
}

// Extract the concrete conformance.
auto concrete = conformance.getConcrete();

// Prevent recursion.
if (!visitedConformances->insert(concrete).second)
return false;

// Visit this conformance.
if (body(conformance))
return true;

// Check the substitution map within this conformance.
if (forEachConformance(concrete->getSubstitutionMap(), body,
visitedConformances))
return true;

return false;
}

bool swift::forEachConformance(
Type type, llvm::function_ref<bool(ProtocolConformanceRef)> body,
VisitedConformances *visitedConformances) {
// Make sure we can store visited conformances.
VisitedConformances visited;
if (!visitedConformances)
visitedConformances = &visited;

// Prevent recursion.
if (!visitedConformances->insert(type.getPointer()).second)
return false;

return type.findIf([&](Type type) {
if (auto typeAlias = dyn_cast<TypeAliasType>(type.getPointer())) {
if (forEachConformance(typeAlias->getSubstitutionMap(), body,
visitedConformances))
return true;

return false;
}

if (auto opaqueArchetype =
dyn_cast<OpaqueTypeArchetypeType>(type.getPointer())) {
if (forEachConformance(opaqueArchetype->getSubstitutions(), body,
visitedConformances))
return true;

return false;
}

// Look through type sugar.
if (auto sugarType = dyn_cast<SyntaxSugarType>(type.getPointer())) {
type = sugarType->getImplementationType();
}

if (auto boundGeneric = dyn_cast<BoundGenericType>(type.getPointer())) {
auto subs = boundGeneric->getContextSubstitutionMap();
if (forEachConformance(subs, body, visitedConformances))
return true;

return false;
}

return false;
});
}

bool swift::forEachConformance(
ConcreteDeclRef declRef,
llvm::function_ref<bool(ProtocolConformanceRef)> body,
VisitedConformances *visitedConformances) {
if (!declRef)
return false;

// Make sure we can store visited conformances.
VisitedConformances visited;
if (!visitedConformances)
visitedConformances = &visited;

Type type = declRef.getDecl()->getInterfaceType();
if (auto subs = declRef.getSubstitutions()) {
if (forEachConformance(subs, body, visitedConformances))
return true;

type = type.subst(subs);
}

if (forEachConformance(type, body, visitedConformances))
return true;

return false;
}
38 changes: 38 additions & 0 deletions lib/Sema/TypeCheckProtocol.h
Original file line number Diff line number Diff line change
Expand Up @@ -240,6 +240,44 @@ bool witnessHasImplementsAttrForRequiredName(ValueDecl *witness,
bool witnessHasImplementsAttrForExactRequirement(ValueDecl *witness,
ValueDecl *requirement);

using VisitedConformances = llvm::SmallPtrSet<void *, 16>;

/// Visit each conformance within the given type.
///
/// If `body` returns true for any conformance, this function stops the
/// traversal and returns true.
bool forEachConformance(
Type type, llvm::function_ref<bool(ProtocolConformanceRef)> body,
VisitedConformances *visitedConformances = nullptr);

/// Visit each conformance within the given conformance (including the given
/// one).
///
/// If `body` returns true for any conformance, this function stops the
/// traversal and returns true.
bool forEachConformance(
ProtocolConformanceRef conformance,
llvm::function_ref<bool(ProtocolConformanceRef)> body,
VisitedConformances *visitedConformances = nullptr);

/// Visit each conformance within the given substitution map.
///
/// If `body` returns true for any conformance, this function stops the
/// traversal and returns true.
bool forEachConformance(
SubstitutionMap subs,
llvm::function_ref<bool(ProtocolConformanceRef)> body,
VisitedConformances *visitedConformances = nullptr);

/// Visit each conformance within the given declaration reference.
///
/// If `body` returns true for any conformance, this function stops the
/// traversal and returns true.
bool forEachConformance(
ConcreteDeclRef declRef,
llvm::function_ref<bool(ProtocolConformanceRef)> body,
VisitedConformances *visitedConformances = nullptr);

}

#endif // SWIFT_SEMA_PROTOCOL_H
6 changes: 6 additions & 0 deletions test/Concurrency/isolated_conformance.swift
Original file line number Diff line number Diff line change
Expand Up @@ -119,3 +119,9 @@ func testIsolationConformancesInCall(c: C) {
acceptSendableP(c) // expected-error{{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}}
}

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<C>() // expected-error{{main actor-isolated isolated conformance of 'C' to 'P' cannot be used in nonisolated context}}
}