Skip to content

Commit e24598b

Browse files
committed
Use a marker protocol SendableMetatype to model T.Type: Sendable
Introduce a marker protocol SendableMetatype that is used to indicate when the metatype of a type will conform to Sendable. Specifically, `T: SendableMetatype` implies `T.Type: Sendable`. When strict metatype sendability is enabled, metatypes are only sendable when `T: SendableMetatype`. All nominal types implicitly conform to `SendableMetatype`, as do the various builtin types, function types, etc. The `Sendable` marker protocol now inherits from `SendableMetatype`, so that `T: Sendable` implies `T.Type: Sendable`. Thank you Slava for the excellent idea!
1 parent ed6dccf commit e24598b

15 files changed

+113
-18
lines changed

include/swift/AST/KnownProtocols.def

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -107,6 +107,7 @@ PROTOCOL(Encodable)
107107
PROTOCOL(Decodable)
108108

109109
PROTOCOL(Sendable)
110+
PROTOCOL(SendableMetatype)
110111
PROTOCOL(UnsafeSendable)
111112

112113
PROTOCOL_(ObjectiveCBridgeable)

lib/AST/ConformanceLookup.cpp

Lines changed: 28 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -367,6 +367,8 @@ static ProtocolConformanceRef getBuiltinFunctionTypeConformance(
367367
if (isBitwiseCopyableFunctionType(functionType))
368368
return synthesizeConformance();
369369
break;
370+
case KnownProtocolKind::SendableMetatype:
371+
return synthesizeConformance();
370372
default:
371373
break;
372374
}
@@ -387,21 +389,22 @@ static ProtocolConformanceRef getBuiltinMetaTypeTypeConformance(
387389
case KnownProtocolKind::Sendable:
388390
// Metatypes are generally Sendable, but under StrictSendableMetatypes we
389391
// cannot assume that metatypes based on type parameters are Sendable.
392+
// Therefore, check for conformance to SendableMetatype.
390393
if (ctx.LangOpts.hasFeature(Feature::StrictSendableMetatypes)) {
391-
Type instanceType = metatypeType->getInstanceType();
392-
393-
// If the instance type is a type parameter, it is not necessarily
394-
// Sendable. There will need to be a Sendable requirement.
395-
if (instanceType->isTypeParameter())
396-
break;
397-
398-
// If the instance type is an archetype or existential, check whether
399-
// it conforms to Sendable.
400-
// FIXME: If the requirement machine were to infer T.Type: Sendable
401-
// from T: Sendable, we wouldn't need this for archetypes.
402-
if (instanceType->is<ArchetypeType>() ||
403-
instanceType->isAnyExistentialType()) {
404-
auto instanceConformance = lookupConformance(instanceType, protocol);
394+
auto sendableMetatypeProto =
395+
ctx.getProtocol(KnownProtocolKind::SendableMetatype);
396+
if (sendableMetatypeProto) {
397+
Type instanceType = metatypeType->getInstanceType();
398+
399+
// If the instance type is a type parameter, it is not necessarily
400+
// Sendable. There will need to be a Sendable requirement.
401+
if (instanceType->isTypeParameter())
402+
break;
403+
404+
// If the instance type conforms to SendableMetatype, then its
405+
// metatype is Sendable.
406+
auto instanceConformance = lookupConformance(
407+
instanceType, sendableMetatypeProto);
405408
if (instanceConformance.isInvalid() ||
406409
instanceConformance.hasMissingConformance())
407410
break;
@@ -414,6 +417,7 @@ static ProtocolConformanceRef getBuiltinMetaTypeTypeConformance(
414417
case KnownProtocolKind::Copyable:
415418
case KnownProtocolKind::Escapable:
416419
case KnownProtocolKind::BitwiseCopyable:
420+
case KnownProtocolKind::SendableMetatype:
417421
return ProtocolConformanceRef(
418422
ctx.getBuiltinConformance(type, protocol,
419423
BuiltinConformanceKind::Synthesized));
@@ -433,6 +437,7 @@ getBuiltinBuiltinTypeConformance(Type type, const BuiltinType *builtinType,
433437
if (auto kp = protocol->getKnownProtocolKind()) {
434438
switch (*kp) {
435439
case KnownProtocolKind::Sendable:
440+
case KnownProtocolKind::SendableMetatype:
436441
case KnownProtocolKind::Copyable:
437442
case KnownProtocolKind::Escapable: {
438443
ASTContext &ctx = protocol->getASTContext();
@@ -447,7 +452,8 @@ getBuiltinBuiltinTypeConformance(Type type, const BuiltinType *builtinType,
447452
break;
448453
}
449454

450-
// All other builtin types are Sendable, Copyable, and Escapable.
455+
// All other builtin types are Sendable, SendableMetatype, Copyable, and
456+
// Escapable.
451457
return ProtocolConformanceRef(
452458
ctx.getBuiltinConformance(type, protocol,
453459
BuiltinConformanceKind::Synthesized));
@@ -616,6 +622,13 @@ LookupConformanceInModuleRequest::evaluate(
616622
if (!nominal || isa<ProtocolDecl>(nominal))
617623
return ProtocolConformanceRef::forMissingOrInvalid(type, protocol);
618624

625+
// All nominal types implicitly conform to SendableMetatype.
626+
if (protocol->isSpecificProtocol(KnownProtocolKind::SendableMetatype)) {
627+
return ProtocolConformanceRef(
628+
ctx.getBuiltinConformance(type, protocol,
629+
BuiltinConformanceKind::Synthesized));
630+
}
631+
619632
// Expand conformances added by extension macros.
620633
//
621634
// FIXME: This expansion should only be done if the

lib/IRGen/GenMeta.cpp

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6985,6 +6985,7 @@ SpecialProtocol irgen::getSpecialProtocolID(ProtocolDecl *P) {
69856985
case KnownProtocolKind::Copyable:
69866986
case KnownProtocolKind::Escapable:
69876987
case KnownProtocolKind::BitwiseCopyable:
6988+
case KnownProtocolKind::SendableMetatype:
69886989
return SpecialProtocol::None;
69896990
}
69906991

lib/Sema/TypeCheckProtocol.cpp

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2339,7 +2339,8 @@ static void diagnoseConformanceImpliedByConditionalConformance(
23392339
/// to the given protocol. This should return true when @unchecked can be
23402340
/// used to disable those semantic checks.
23412341
static bool hasAdditionalSemanticChecks(ProtocolDecl *proto) {
2342-
return proto->isSpecificProtocol(KnownProtocolKind::Sendable);
2342+
return proto->isSpecificProtocol(KnownProtocolKind::Sendable) ||
2343+
proto->isSpecificProtocol(KnownProtocolKind::SendableMetatype);
23432344
}
23442345

23452346
/// Determine whether a conformance to this protocol can be determined at

stdlib/public/core/Sendable.swift

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,9 @@
1010
//
1111
//===----------------------------------------------------------------------===//
1212

13+
/// A type `T` whose metatype `T.Type` is `Sendable`.
14+
@_marker public protocol SendableMetatype: ~Copyable, ~Escapable { }
15+
1316
/// A thread-safe type whose values can be shared across arbitrary concurrent
1417
/// contexts without introducing a risk of data races. Values of the type may
1518
/// have no shared mutable state, or they may protect that state with a lock or
@@ -132,7 +135,7 @@
132135
/// ### Sendable Metatypes
133136
///
134137
/// Metatypes such as `Int.Type` implicitly conform to the `Sendable` protocol.
135-
@_marker public protocol Sendable: ~Copyable, ~Escapable { }
138+
@_marker public protocol Sendable: SendableMetatype, ~Copyable, ~Escapable { }
136139

137140
///
138141
/// A type whose values can safely be passed across concurrency domains by copying,

test/Concurrency/sendable_metatype.swift

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -54,6 +54,14 @@ nonisolated func passMetaWithSendableSmuggled<T: Sendable & Q>(_: T.Type) {
5454
}
5555
}
5656

57+
nonisolated func passMetaWithSendableSmuggled<T: SendableMetatype & Q>(_: T.Type) {
58+
let x: any Q.Type = T.self
59+
Task.detached {
60+
acceptMeta(x) // okay, because T: SendableMetatype implies T.Type: Sendable
61+
x.g() // okay, because T: SendableMetatype implies T.Type: Sendable
62+
}
63+
}
64+
5765
nonisolated func passSendableToMainActorSmuggledAny<T: Sendable>(_: T.Type) async {
5866
let x: Sendable.Type = T.self
5967
await acceptMetaOnMainActor(x)

test/Concurrency/sendable_metatype_typecheck.swift

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -51,10 +51,24 @@ nonisolated func passMetaSendable<T: Sendable & Q>(_: T.Type) {
5151
}
5252
}
5353

54+
nonisolated func passMetaSendableMeta<T: SendableMetatype & Q>(_: T.Type) {
55+
Task.detached {
56+
acceptMeta(T.self)
57+
}
58+
}
59+
5460
nonisolated func passMetaWithSendableVal<T: Sendable & Q>(_: T.Type) {
5561
let x = T.self
5662
Task.detached {
5763
acceptMeta(x) // okay, because T is Sendable implies T.Type: Sendable
5864
x.g() // okay, because T is Sendable implies T.Type: Sendable
5965
}
6066
}
67+
68+
nonisolated func passMetaWithMetaSendableVal<T: SendableMetatype & Q>(_: T.Type) {
69+
let x = T.self
70+
Task.detached {
71+
acceptMeta(x) // okay, because T is Sendable implies T.Type: Sendable
72+
x.g() // okay, because T is Sendable implies T.Type: Sendable
73+
}
74+
}

test/SymbolGraph/Symbols/SkipProtocolImplementations.swift

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -38,7 +38,7 @@
3838
// - ExtraStruct.Inner
3939
// (ExtraStruct.Inner will have two sourceOrigins because it has two relationships: a memberOf and a
4040
// conformsTo)
41-
// COUNT-COUNT-3: sourceOrigin
41+
// COUNT-COUNT-4: sourceOrigin
4242
// COUNT-NOT: sourceOrigin
4343

4444
public protocol SomeProtocol {

test/api-digester/Outputs/Cake-abi.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -111,6 +111,7 @@ cake: Class C7 has added a conformance to an existing protocol P1
111111
cake: Class SuperClassChange has added a conformance to an existing protocol P1
112112
cake: Enum IceKind has removed conformance to BitwiseCopyable
113113
cake: Enum IceKind has removed conformance to Sendable
114+
cake: Enum IceKind has removed conformance to SendableMetatype
114115
cake: Protocol P3 has added inherited protocol P4
115116
cake: Protocol P3 has removed inherited protocol P2
116117
cake: Struct fixedLayoutStruct has added a conformance to an existing protocol P2

test/api-digester/Outputs/Cake.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -51,6 +51,7 @@ cake: EnumElement FrozenKind.AddedCase has been added as a new enum case
5151
/* Conformance changes */
5252
cake: Enum IceKind has removed conformance to BitwiseCopyable
5353
cake: Enum IceKind has removed conformance to Sendable
54+
cake: Enum IceKind has removed conformance to SendableMetatype
5455
cake: Func ObjCProtocol.removeOptional() is no longer an optional requirement
5556
cake: Protocol P3 has added inherited protocol P4
5657
cake: Protocol P3 has removed inherited protocol P2

0 commit comments

Comments
 (0)