Skip to content

Commit 92774e0

Browse files
committed
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.
1 parent 1f93566 commit 92774e0

File tree

6 files changed

+97
-43
lines changed

6 files changed

+97
-43
lines changed

include/swift/AST/GenericSignature.h

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -376,6 +376,19 @@ class alignas(1 << TypeAlignInBits) GenericSignatureImpl final
376376
/// the given protocol.
377377
bool requiresProtocol(Type type, ProtocolDecl *proto) const;
378378

379+
/// Determine whether a conformance requirement of the given type to the
380+
/// given protocol prohibits the use of an isolated conformance.
381+
///
382+
/// The use of an isolated conformance to satisfy a requirement T: P is
383+
/// prohibited when T is a type parameter and T, or some type that can be
384+
/// used to reach T, also conforms to Sendable or SendableMetatype. In that
385+
/// case, the conforming type and the protocol (Sendable or SendableMetatype)
386+
/// is returned.
387+
///
388+
/// If there is no such requirement, returns std::nullopt.
389+
std::optional<std::pair<Type, ProtocolDecl *>>
390+
prohibitsIsolatedConformance(Type type) const;
391+
379392
/// Determine whether the given dependent type is equal to a concrete type.
380393
bool isConcreteType(Type type) const;
381394

lib/AST/GenericSignature.cpp

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -371,6 +371,44 @@ bool GenericSignatureImpl::requiresProtocol(Type type,
371371
return getRequirementMachine()->requiresProtocol(type, proto);
372372
}
373373

374+
std::optional<std::pair<Type, ProtocolDecl *>>
375+
GenericSignatureImpl::prohibitsIsolatedConformance(Type type) const {
376+
type = getReducedType(type);
377+
378+
if (!type->isTypeParameter())
379+
return std::nullopt;
380+
381+
// An isolated conformance cannot be used in a context where the type
382+
// parameter can escape the isolation domain in which the conformance
383+
// was formed. To establish this, we look for Sendable or SendableMetatype
384+
// requirements on the type parameter itself.
385+
ASTContext &ctx = type->getASTContext();
386+
auto sendableProto = ctx.getProtocol(KnownProtocolKind::Sendable);
387+
auto sendableMetatypeProto =
388+
ctx.getProtocol(KnownProtocolKind::SendableMetatype);
389+
390+
// Check for a conformance requirement to SendableMetatype, which is
391+
// implied by Sendable.
392+
if (sendableMetatypeProto && requiresProtocol(type, sendableMetatypeProto)) {
393+
// Check for a conformance requirement to Sendable and return that if
394+
// it exists, because it's more recognizable and specific.
395+
if (sendableProto && requiresProtocol(type, sendableProto))
396+
return std::make_pair(type, sendableProto);
397+
398+
return std::make_pair(type, sendableMetatypeProto);
399+
}
400+
401+
// If this is a nested type, also check whether the parent type conforms to
402+
// SendableMetatype, because one can derive this type from the parent type.
403+
// FIXME: This is not a complete check, because there are other ways in which
404+
// one might be able to derive this type. This needs to determine whether
405+
// there is any path from a SendableMetatype-conforming type to this type.
406+
if (auto depMemTy = type->getAs<DependentMemberType>())
407+
return prohibitsIsolatedConformance(depMemTy->getBase());
408+
409+
return std::nullopt;
410+
}
411+
374412
/// Determine whether the given dependent type is equal to a concrete type.
375413
bool GenericSignatureImpl::isConcreteType(Type type) const {
376414
assert(type->isTypeParameter() && "Expected a type parameter");

lib/Sema/TypeCheckGeneric.cpp

Lines changed: 10 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -1064,17 +1064,19 @@ CheckGenericArgumentsResult TypeChecker::checkGenericArgumentsForDiagnostics(
10641064
break;
10651065
}
10661066

1067-
if (!isolatedConformances.empty()) {
1067+
if (!isolatedConformances.empty() && signature) {
10681068
// Dig out the original type parameter for the requirement.
10691069
// FIXME: req might not be the right pre-substituted requirement,
10701070
// if this came from a conditional requirement.
1071-
if (auto failedProtocol =
1072-
typeParameterProhibitsIsolatedConformance(req.getFirstType(),
1073-
signature)) {
1074-
return CheckGenericArgumentsResult::createIsolatedConformanceFailure(
1075-
req, substReq,
1076-
TinyPtrVector<ProtocolConformanceRef>(isolatedConformances),
1077-
*failedProtocol);
1071+
for (const auto &isolatedConformance : isolatedConformances) {
1072+
(void)isolatedConformance;
1073+
if (auto failed =
1074+
signature->prohibitsIsolatedConformance(req.getFirstType())) {
1075+
return CheckGenericArgumentsResult::createIsolatedConformanceFailure(
1076+
req, substReq,
1077+
TinyPtrVector<ProtocolConformanceRef>(isolatedConformances),
1078+
failed->second);
1079+
}
10781080
}
10791081
}
10801082
}
@@ -1181,28 +1183,3 @@ Type StructuralTypeRequest::evaluate(Evaluator &evaluator,
11811183

11821184
return TypeAliasType::get(typeAlias, parent, genericArgs, result);
11831185
}
1184-
1185-
std::optional<ProtocolDecl *> swift::typeParameterProhibitsIsolatedConformance(
1186-
Type type, GenericSignature signature) {
1187-
if (!type->isTypeParameter())
1188-
return std::nullopt;
1189-
1190-
// An isolated conformance cannot be used in a context where the type
1191-
// parameter can escape the isolation domain in which the conformance
1192-
// was formed. To establish this, we look for Sendable or SendableMetatype
1193-
// requirements on the type parameter itself.
1194-
ASTContext &ctx = type->getASTContext();
1195-
auto sendableProto = ctx.getProtocol(KnownProtocolKind::Sendable);
1196-
auto sendableMetatypeProto =
1197-
ctx.getProtocol(KnownProtocolKind::SendableMetatype);
1198-
1199-
if (sendableProto &&
1200-
signature->requiresProtocol(type, sendableProto))
1201-
return sendableProto;
1202-
1203-
if (sendableMetatypeProto &&
1204-
signature->requiresProtocol(type, sendableMetatypeProto))
1205-
return sendableMetatypeProto;
1206-
1207-
return std::nullopt;
1208-
}

lib/Sema/TypeChecker.h

Lines changed: 0 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1535,14 +1535,6 @@ bool maybeDiagnoseMissingImportForMember(const ValueDecl *decl,
15351535
/// source file.
15361536
void diagnoseMissingImports(SourceFile &sf);
15371537

1538-
/// Determine whether the type parameter has requirements that would prohibit
1539-
/// it from using any isolated conformances.
1540-
///
1541-
/// Returns the protocol to which the type conforms that causes the conflict,
1542-
/// which can be either Sendable or SendableMetatype.
1543-
std::optional<ProtocolDecl *> typeParameterProhibitsIsolatedConformance(
1544-
Type type, GenericSignature signature);
1545-
15461538
} // end namespace swift
15471539

15481540
#endif

lib/Sema/TypeOfReference.cpp

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1230,8 +1230,8 @@ void ConstraintSystem::openGenericRequirement(
12301230

12311231
// Check whether the given type parameter has requirements that
12321232
// prohibit it from using an isolated conformance.
1233-
if (typeParameterProhibitsIsolatedConformance(req.getFirstType(),
1234-
signature))
1233+
if (signature &&
1234+
signature->prohibitsIsolatedConformance(req.getFirstType()))
12351235
prohibitIsolatedConformance = true;
12361236

12371237
openedReq = Requirement(kind, openedFirst, req.getSecondType());

test/Concurrency/isolated_conformance.swift

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -153,3 +153,37 @@ func testIsolationConformancesFromOutside(c: C) {
153153
let _: any P = c // expected-error{{main actor-isolated conformance of 'C' to 'P' cannot be used in nonisolated context}}
154154
let _ = PWrapper<C>() // expected-error{{main actor-isolated conformance of 'C' to 'P' cannot be used in nonisolated context}}
155155
}
156+
157+
protocol HasAssociatedType {
158+
associatedtype A
159+
}
160+
161+
func acceptHasAssocWithP<T: HasAssociatedType>(_: T) where T.A: P { }
162+
163+
func acceptSendableHasAssocWithP<T: Sendable & HasAssociatedType>(_: T) where T.A: P { }
164+
// expected-note@-1{{'acceptSendableHasAssocWithP' declared here}}
165+
166+
167+
struct HoldsC: HasAssociatedType {
168+
typealias A = C
169+
}
170+
171+
extension HasAssociatedType {
172+
static func acceptAliased<T: P>(_: T.Type) where A == T { }
173+
}
174+
175+
extension HasAssociatedType where Self: Sendable {
176+
static func acceptSendableAliased<T: P>(_: T.Type) where A == T { }
177+
}
178+
179+
func testIsolatedConformancesOnAssociatedTypes(hc: HoldsC, c: C) {
180+
acceptHasAssocWithP(hc)
181+
acceptSendableHasAssocWithP(hc) // expected-error{{main actor-isolated conformance of 'C' to 'P' cannot satisfy conformance requirement for a 'Sendable' type parameter }}
182+
183+
HoldsC.acceptAliased(C.self) // okay
184+
185+
// FIXME: the following should produce an error, because the isolated
186+
// conformance of C: P can cross isolation boundaries via the Sendable Self's
187+
// associated type.
188+
HoldsC.acceptSendableAliased(C.self)
189+
}

0 commit comments

Comments
 (0)