Skip to content

Commit 92d0327

Browse files
committed
[SE-0470] Treat metatype of archetype/existential type with no protocol requirements as Sendable
A metatype for an archetype or existential with no (non-marker) protocol requirements cannot, by definition, carry any (isolated) protocol conformances with it, so it's safe to treat such metatypes as Sendable.
1 parent 97a47e7 commit 92d0327

File tree

4 files changed

+111
-31
lines changed

4 files changed

+111
-31
lines changed

lib/AST/ConformanceLookup.cpp

Lines changed: 61 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -66,6 +66,15 @@ swift::collectExistentialConformances(CanType fromType,
6666
return fromType->getASTContext().AllocateCopy(conformances);
6767
}
6868

69+
static bool containsNonMarkerProtocols(ArrayRef<ProtocolDecl *> protocols) {
70+
for (auto proto : protocols) {
71+
if (!proto->isMarkerProtocol())
72+
return true;
73+
}
74+
75+
return false;
76+
}
77+
6978
ProtocolConformanceRef
7079
swift::lookupExistentialConformance(Type type, ProtocolDecl *protocol) {
7180
ASTContext &ctx = protocol->getASTContext();
@@ -146,6 +155,12 @@ swift::lookupExistentialConformance(Type type, ProtocolDecl *protocol) {
146155
if (auto conformance = lookupSuperclassConformance(layout.getSuperclass()))
147156
return conformance;
148157

158+
// If the protocol is SendableMetatype, and there are no non-marker protocol
159+
// requirements, allow it via self-conformance.
160+
if (protocol->isSpecificProtocol(KnownProtocolKind::SendableMetatype) &&
161+
!containsNonMarkerProtocols(layout.getProtocols()))
162+
return ProtocolConformanceRef(ctx.getSelfConformance(protocol));
163+
149164
// We didn't find our protocol in the existential's list; it doesn't
150165
// conform.
151166
return ProtocolConformanceRef::forInvalid();
@@ -377,6 +392,48 @@ static ProtocolConformanceRef getBuiltinFunctionTypeConformance(
377392
return ProtocolConformanceRef::forMissingOrInvalid(type, protocol);
378393
}
379394

395+
/// Given the instance type of a metatype, determine whether the metatype is
396+
/// Sendable.
397+
///
398+
// Metatypes are generally Sendable, but with isolated conformances we
399+
// cannot assume that metatypes based on type parameters are Sendable.
400+
// Therefore, check for conformance to SendableMetatype.
401+
static bool metatypeWithInstanceTypeIsSendable(Type instanceType) {
402+
ASTContext &ctx = instanceType->getASTContext();
403+
404+
// If we don't have the SendableMetatype protocol at all, just assume all
405+
// metatypes are Sendable.
406+
auto sendableMetatypeProto =
407+
ctx.getProtocol(KnownProtocolKind::SendableMetatype);
408+
if (!sendableMetatypeProto)
409+
return true;
410+
411+
// If the instance type is a type parameter, it is not necessarily
412+
// SendableMetatype. There will need to be a SendableMetatype requirement,
413+
// but we do not have the generic environment to check that.
414+
if (instanceType->isTypeParameter())
415+
return false;
416+
417+
// If the instance type conforms to SendableMetatype, then its
418+
// metatype is Sendable.
419+
auto instanceConformance = lookupConformance(
420+
instanceType, sendableMetatypeProto);
421+
if (!instanceConformance.isInvalid() &&
422+
!instanceConformance.hasMissingConformance())
423+
return true;
424+
425+
// If this is an archetype that is non-SendableMetatype, but there are no
426+
// non-marker protocol requirements that could carry conformances, treat
427+
// the metatype as Sendable.
428+
if (auto archetype = instanceType->getAs<ArchetypeType>()) {
429+
if (!containsNonMarkerProtocols(archetype->getConformsTo()))
430+
return true;
431+
}
432+
433+
// The instance type is non-Sendable.
434+
return false;
435+
}
436+
380437
/// Synthesize a builtin metatype type conformance to the given protocol, if
381438
/// appropriate.
382439
static ProtocolConformanceRef getBuiltinMetaTypeTypeConformance(
@@ -386,30 +443,11 @@ static ProtocolConformanceRef getBuiltinMetaTypeTypeConformance(
386443
// All metatypes are Copyable, Escapable, and BitwiseCopyable.
387444
if (auto kp = protocol->getKnownProtocolKind()) {
388445
switch (*kp) {
389-
case KnownProtocolKind::Sendable: {
390-
// Metatypes are generally Sendable, but with isolated conformances we
391-
// cannot assume that metatypes based on type parameters are Sendable.
392-
// Therefore, check for conformance to SendableMetatype.
393-
auto sendableMetatypeProto =
394-
ctx.getProtocol(KnownProtocolKind::SendableMetatype);
395-
if (sendableMetatypeProto) {
396-
Type instanceType = metatypeType->getInstanceType();
397-
398-
// If the instance type is a type parameter, it is not necessarily
399-
// Sendable. There will need to be a Sendable requirement.
400-
if (instanceType->isTypeParameter())
401-
break;
402-
403-
// If the instance type conforms to SendableMetatype, then its
404-
// metatype is Sendable.
405-
auto instanceConformance = lookupConformance(
406-
instanceType, sendableMetatypeProto);
407-
if (instanceConformance.isInvalid() ||
408-
instanceConformance.hasMissingConformance())
409-
break;
410-
}
446+
case KnownProtocolKind::Sendable:
447+
if (!metatypeWithInstanceTypeIsSendable(metatypeType->getInstanceType()))
448+
break;
449+
411450
LLVM_FALLTHROUGH;
412-
}
413451

414452
case KnownProtocolKind::Copyable:
415453
case KnownProtocolKind::Escapable:

lib/SILGen/SILGenDynamicCast.cpp

Lines changed: 19 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@
1818
#include "ExitableFullExpr.h"
1919
#include "swift/Basic/Assertions.h"
2020
#include "swift/AST/ConformanceLookup.h"
21+
#include "swift/AST/ExistentialLayout.h"
2122
#include "swift/SIL/DynamicCasts.h"
2223
#include "swift/SIL/SILArgument.h"
2324
#include "swift/SIL/TypeLowering.h"
@@ -302,21 +303,38 @@ namespace {
302303
return CastStrategy::Address;
303304
}
304305

306+
static bool containsNonMarkerProtocols(ArrayRef<ProtocolDecl *> protocols) {
307+
for (auto proto : protocols) {
308+
if (!proto->isMarkerProtocol())
309+
return true;
310+
}
311+
312+
return false;
313+
}
314+
305315
CastingIsolatedConformances computedIsolatedConformances() const {
306316
// Non-existential types don't carry conformances, so we always allow
307317
// isolated conformances.
308318
if (!TargetType->isAnyExistentialType())
309319
return CastingIsolatedConformances::Allow;
310320

311321
// If there is a conformance to SendableMetatype, then this existential
312-
// can leave the current isolation domain. Prohibit isolated conformances.
322+
// can leave the current isolation domain.
313323
ASTContext &ctx = TargetType->getASTContext();
314324
Type checkType;
315325
if (auto existentialMetatype = TargetType->getAs<ExistentialMetatypeType>())
316326
checkType = existentialMetatype->getInstanceType();
317327
else
318328
checkType = TargetType;
319329

330+
// If there are no non-marker protocols in the existential, there's no
331+
// need to prohibit isolated conformances.
332+
auto layout = checkType->getExistentialLayout();
333+
if (!containsNonMarkerProtocols(layout.getProtocols()))
334+
return CastingIsolatedConformances::Allow;
335+
336+
// If the type conforms to SendableMetatype, prohibit isolated
337+
// conformances.
320338
auto proto = ctx.getProtocol(KnownProtocolKind::SendableMetatype);
321339
if (proto && lookupConformance(checkType, proto, /*allowMissing=*/false))
322340
return CastingIsolatedConformances::Prohibit;

test/Concurrency/sendable_metatype.swift

Lines changed: 15 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,8 @@
22

33
// REQUIRES: concurrency
44

5+
protocol P {
6+
}
57

68
protocol Q {
79
static func g()
@@ -31,15 +33,21 @@ nonisolated func passMetaSmuggled<T: Q>(_: T.Type) {
3133

3234
nonisolated func passMetaSmuggledAny<T: Q>(_: T.Type) {
3335
let x: Any.Type = T.self
34-
Task.detached { // expected-error{{risks causing data races}}
35-
acceptMeta(x) // expected-note{{closure captures 'x' which is accessible to code in the current task}}
36+
Task.detached {
37+
acceptMeta(x)
38+
}
39+
}
40+
41+
nonisolated func captureThroughMetaValMoReqs<T>(_: T.Type) {
42+
let x = T.self
43+
Task.detached {
44+
_ = x
3645
}
3746
}
3847

3948
nonisolated func passToMainActorSmuggledAny<T: Q>(_: T.Type) async {
4049
let x: Any.Type = T.self
41-
await acceptMetaOnMainActor(x) // expected-error{{sending value of non-Sendable type '(Any).Type' risks causing data races}}
42-
// expected-note@-1{{sending task-isolated value of non-Sendable type '(Any).Type' to main actor-isolated global function}}
50+
await acceptMetaOnMainActor(x)
4351
}
4452

4553
// -------------------------------------------------------------------------
@@ -69,9 +77,9 @@ nonisolated func passSendableToMainActorSmuggledAny<T: Sendable>(_: T.Type) asyn
6977
// -------------------------------------------------------------------------
7078
// Existential opening
7179
// -------------------------------------------------------------------------
72-
nonisolated func passMetaSmuggledAnyFromExistential(_ qT: Q.Type) {
73-
let x: Any.Type = qT
74-
Task.detached { // expected-error{{risks causing data races}}
80+
nonisolated func passMetaSmuggledAnyFromExistential(_ pqT: (P & Q).Type) {
81+
let x: P.Type = pqT
82+
Task.detached { // expected-error{{passing closure as a 'sending' parameter risks causing data races between code in the current task and concurrent execution of the closure}}
7583
acceptMeta(x) // expected-note{{closure captures 'x' which is accessible to code in the current task}}
7684
}
7785
}

test/Concurrency/sendable_metatype_typecheck.swift

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,15 @@ protocol Q {
99
static func g()
1010
}
1111

12+
13+
// Sendability of existential metatypes
14+
fileprivate nonisolated let anyObjectArray: [AnyClass] = []
15+
16+
func testSendableExistential() {
17+
_ = anyObjectArray
18+
}
19+
20+
1221
nonisolated func acceptMeta<T>(_: T.Type) { }
1322

1423
nonisolated func staticCallThroughMetaVal<T: Q>(_: T.Type) {
@@ -18,6 +27,13 @@ nonisolated func staticCallThroughMetaVal<T: Q>(_: T.Type) {
1827
}
1928
}
2029

30+
nonisolated func captureThroughMetaValMoReqs<T>(_: T.Type) {
31+
let x = T.self
32+
Task.detached {
33+
_ = x
34+
}
35+
}
36+
2137
nonisolated func passMetaVal<T: Q>(_: T.Type) {
2238
let x = T.self // expected-error{{capture of non-sendable type 'T.Type' in an isolated closure}}
2339
Task.detached {

0 commit comments

Comments
 (0)