Move generic signature check for isolated conformances into GenericSignatureImpl

This is going to need a proper implementation in the requirement
machine. For the moment, provide a slightly-less-broken implementation
but leave a test case where we incorrectly accept racey code.

(cherry picked from commit 92774e0a3c)
This commit is contained in:
Doug Gregor
2025-04-05 14:11:53 -07:00
parent bda6ada2e6
commit 49ebfcbab2
6 changed files with 97 additions and 43 deletions

View File

@@ -376,6 +376,19 @@ public:
/// the given protocol.
bool requiresProtocol(Type type, ProtocolDecl *proto) const;
/// Determine whether a conformance requirement of the given type to the
/// given protocol prohibits the use of an isolated conformance.
///
/// The use of an isolated conformance to satisfy a requirement T: P is
/// prohibited when T is a type parameter and T, or some type that can be
/// used to reach T, also conforms to Sendable or SendableMetatype. In that
/// case, the conforming type and the protocol (Sendable or SendableMetatype)
/// is returned.
///
/// If there is no such requirement, returns std::nullopt.
std::optional<std::pair<Type, ProtocolDecl *>>
prohibitsIsolatedConformance(Type type) const;
/// Determine whether the given dependent type is equal to a concrete type.
bool isConcreteType(Type type) const;

View File

@@ -371,6 +371,44 @@ bool GenericSignatureImpl::requiresProtocol(Type type,
return getRequirementMachine()->requiresProtocol(type, proto);
}
std::optional<std::pair<Type, ProtocolDecl *>>
GenericSignatureImpl::prohibitsIsolatedConformance(Type type) const {
type = getReducedType(type);
if (!type->isTypeParameter())
return std::nullopt;
// An isolated conformance cannot be used in a context where the type
// parameter can escape the isolation domain in which the conformance
// was formed. To establish this, we look for Sendable or SendableMetatype
// requirements on the type parameter itself.
ASTContext &ctx = type->getASTContext();
auto sendableProto = ctx.getProtocol(KnownProtocolKind::Sendable);
auto sendableMetatypeProto =
ctx.getProtocol(KnownProtocolKind::SendableMetatype);
// Check for a conformance requirement to SendableMetatype, which is
// implied by Sendable.
if (sendableMetatypeProto && requiresProtocol(type, sendableMetatypeProto)) {
// Check for a conformance requirement to Sendable and return that if
// it exists, because it's more recognizable and specific.
if (sendableProto && requiresProtocol(type, sendableProto))
return std::make_pair(type, sendableProto);
return std::make_pair(type, sendableMetatypeProto);
}
// If this is a nested type, also check whether the parent type conforms to
// SendableMetatype, because one can derive this type from the parent type.
// FIXME: This is not a complete check, because there are other ways in which
// one might be able to derive this type. This needs to determine whether
// there is any path from a SendableMetatype-conforming type to this type.
if (auto depMemTy = type->getAs<DependentMemberType>())
return prohibitsIsolatedConformance(depMemTy->getBase());
return std::nullopt;
}
/// Determine whether the given dependent type is equal to a concrete type.
bool GenericSignatureImpl::isConcreteType(Type type) const {
assert(type->isTypeParameter() && "Expected a type parameter");

View File

@@ -1064,17 +1064,19 @@ CheckGenericArgumentsResult TypeChecker::checkGenericArgumentsForDiagnostics(
break;
}
if (!isolatedConformances.empty()) {
if (!isolatedConformances.empty() && signature) {
// Dig out the original type parameter for the requirement.
// FIXME: req might not be the right pre-substituted requirement,
// if this came from a conditional requirement.
if (auto failedProtocol =
typeParameterProhibitsIsolatedConformance(req.getFirstType(),
signature)) {
return CheckGenericArgumentsResult::createIsolatedConformanceFailure(
req, substReq,
TinyPtrVector<ProtocolConformanceRef>(isolatedConformances),
*failedProtocol);
for (const auto &isolatedConformance : isolatedConformances) {
(void)isolatedConformance;
if (auto failed =
signature->prohibitsIsolatedConformance(req.getFirstType())) {
return CheckGenericArgumentsResult::createIsolatedConformanceFailure(
req, substReq,
TinyPtrVector<ProtocolConformanceRef>(isolatedConformances),
failed->second);
}
}
}
}
@@ -1181,28 +1183,3 @@ Type StructuralTypeRequest::evaluate(Evaluator &evaluator,
return TypeAliasType::get(typeAlias, parent, genericArgs, result);
}
std::optional<ProtocolDecl *> swift::typeParameterProhibitsIsolatedConformance(
Type type, GenericSignature signature) {
if (!type->isTypeParameter())
return std::nullopt;
// An isolated conformance cannot be used in a context where the type
// parameter can escape the isolation domain in which the conformance
// was formed. To establish this, we look for Sendable or SendableMetatype
// requirements on the type parameter itself.
ASTContext &ctx = type->getASTContext();
auto sendableProto = ctx.getProtocol(KnownProtocolKind::Sendable);
auto sendableMetatypeProto =
ctx.getProtocol(KnownProtocolKind::SendableMetatype);
if (sendableProto &&
signature->requiresProtocol(type, sendableProto))
return sendableProto;
if (sendableMetatypeProto &&
signature->requiresProtocol(type, sendableMetatypeProto))
return sendableMetatypeProto;
return std::nullopt;
}

View File

@@ -1535,14 +1535,6 @@ bool maybeDiagnoseMissingImportForMember(const ValueDecl *decl,
/// source file.
void diagnoseMissingImports(SourceFile &sf);
/// Determine whether the type parameter has requirements that would prohibit
/// it from using any isolated conformances.
///
/// Returns the protocol to which the type conforms that causes the conflict,
/// which can be either Sendable or SendableMetatype.
std::optional<ProtocolDecl *> typeParameterProhibitsIsolatedConformance(
Type type, GenericSignature signature);
} // end namespace swift
#endif

View File

@@ -1230,8 +1230,8 @@ void ConstraintSystem::openGenericRequirement(
// Check whether the given type parameter has requirements that
// prohibit it from using an isolated conformance.
if (typeParameterProhibitsIsolatedConformance(req.getFirstType(),
signature))
if (signature &&
signature->prohibitsIsolatedConformance(req.getFirstType()))
prohibitIsolatedConformance = true;
openedReq = Requirement(kind, openedFirst, req.getSecondType());

View File

@@ -153,3 +153,37 @@ func testIsolationConformancesFromOutside(c: C) {
let _: any P = c // expected-error{{main actor-isolated conformance of 'C' to 'P' cannot be used in nonisolated context}}
let _ = PWrapper<C>() // expected-error{{main actor-isolated conformance of 'C' to 'P' cannot be used in nonisolated context}}
}
protocol HasAssociatedType {
associatedtype A
}
func acceptHasAssocWithP<T: HasAssociatedType>(_: T) where T.A: P { }
func acceptSendableHasAssocWithP<T: Sendable & HasAssociatedType>(_: T) where T.A: P { }
// expected-note@-1{{'acceptSendableHasAssocWithP' declared here}}
struct HoldsC: HasAssociatedType {
typealias A = C
}
extension HasAssociatedType {
static func acceptAliased<T: P>(_: T.Type) where A == T { }
}
extension HasAssociatedType where Self: Sendable {
static func acceptSendableAliased<T: P>(_: T.Type) where A == T { }
}
func testIsolatedConformancesOnAssociatedTypes(hc: HoldsC, c: C) {
acceptHasAssocWithP(hc)
acceptSendableHasAssocWithP(hc) // expected-error{{main actor-isolated conformance of 'C' to 'P' cannot satisfy conformance requirement for a 'Sendable' type parameter }}
HoldsC.acceptAliased(C.self) // okay
// FIXME: the following should produce an error, because the isolated
// conformance of C: P can cross isolation boundaries via the Sendable Self's
// associated type.
HoldsC.acceptSendableAliased(C.self)
}