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

test/api-digester/Outputs/cake-abi.json

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -267,6 +267,13 @@
267267
"usr": "s:s9EscapableP",
268268
"mangledName": "$ss9EscapableP"
269269
},
270+
{
271+
"kind": "Conformance",
272+
"name": "SendableMetatype",
273+
"printedName": "SendableMetatype",
274+
"usr": "s:s16SendableMetatypeP",
275+
"mangledName": "$ss16SendableMetatypeP"
276+
},
270277
{
271278
"kind": "Conformance",
272279
"name": "P2",
@@ -1067,6 +1074,13 @@
10671074
"printedName": "Escapable",
10681075
"usr": "s:s9EscapableP",
10691076
"mangledName": "$ss9EscapableP"
1077+
},
1078+
{
1079+
"kind": "Conformance",
1080+
"name": "SendableMetatype",
1081+
"printedName": "SendableMetatype",
1082+
"usr": "s:s16SendableMetatypeP",
1083+
"mangledName": "$ss16SendableMetatypeP"
10701084
}
10711085
]
10721086
},
@@ -2224,6 +2238,13 @@
22242238
"usr": "s:s8SendableP",
22252239
"mangledName": "$ss8SendableP"
22262240
},
2241+
{
2242+
"kind": "Conformance",
2243+
"name": "SendableMetatype",
2244+
"printedName": "SendableMetatype",
2245+
"usr": "s:s16SendableMetatypeP",
2246+
"mangledName": "$ss16SendableMetatypeP"
2247+
},
22272248
{
22282249
"kind": "Conformance",
22292250
"name": "SIMDScalar",

test/api-digester/Outputs/cake.json

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -272,6 +272,13 @@
272272
"usr": "s:s9EscapableP",
273273
"mangledName": "$ss9EscapableP"
274274
},
275+
{
276+
"kind": "Conformance",
277+
"name": "SendableMetatype",
278+
"printedName": "SendableMetatype",
279+
"usr": "s:s16SendableMetatypeP",
280+
"mangledName": "$ss16SendableMetatypeP"
281+
},
275282
{
276283
"kind": "Conformance",
277284
"name": "P2",
@@ -1010,6 +1017,13 @@
10101017
"printedName": "Escapable",
10111018
"usr": "s:s9EscapableP",
10121019
"mangledName": "$ss9EscapableP"
1020+
},
1021+
{
1022+
"kind": "Conformance",
1023+
"name": "SendableMetatype",
1024+
"printedName": "SendableMetatype",
1025+
"usr": "s:s16SendableMetatypeP",
1026+
"mangledName": "$ss16SendableMetatypeP"
10131027
}
10141028
]
10151029
},
@@ -2104,6 +2118,13 @@
21042118
"usr": "s:s8SendableP",
21052119
"mangledName": "$ss8SendableP"
21062120
},
2121+
{
2122+
"kind": "Conformance",
2123+
"name": "SendableMetatype",
2124+
"printedName": "SendableMetatype",
2125+
"usr": "s:s16SendableMetatypeP",
2126+
"mangledName": "$ss16SendableMetatypeP"
2127+
},
21072128
{
21082129
"kind": "Conformance",
21092130
"name": "SIMDScalar",

test/api-digester/Outputs/stability-stdlib-source-base.swift.expected

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -360,3 +360,6 @@ Func ContiguousArray.withUnsafeBufferPointer(_:) is now without rethrows
360360
Func ContiguousArray.withUnsafeMutableBufferPointer(_:) has generic signature change from <Element, R> to <Element, R, E where E : Swift.Error>
361361
Func ContiguousArray.withUnsafeMutableBufferPointer(_:) has parameter 0 type change from (inout Swift.UnsafeMutableBufferPointer<Element>) throws -> R to (inout Swift.UnsafeMutableBufferPointer<Element>) throws(E) -> R
362362
Func ContiguousArray.withUnsafeMutableBufferPointer(_:) is now without rethrows
363+
364+
Protocol CodingKey has added inherited protocol SendableMetatype
365+
Protocol Error has added inherited protocol SendableMetatype

test/api-digester/stability-concurrency-abi.test

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -75,6 +75,9 @@ Protocol GlobalActor has added inherited protocol Escapable
7575
Protocol SerialExecutor has added inherited protocol Copyable
7676
Protocol SerialExecutor has added inherited protocol Escapable
7777
Protocol AsyncSequence is now without @rethrows
78+
Protocol Actor has added inherited protocol SendableMetatype
79+
Protocol Executor has added inherited protocol SendableMetatype
80+
Protocol SerialExecutor has added inherited protocol SendableMetatype
7881

7982
// #isolated adoption in with...Continuation
8083
// api-digester is not aware of silgen_name trickery we do to keep this ABI compatible

test/api-digester/stability-stdlib-abi-without-asserts.test

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -825,4 +825,8 @@ Struct String.Index has added a conformance to an existing protocol CustomDebugS
825825
Enum _SwiftifyInfo is a new API without @available attribute
826826
Enum _SwiftifyExpr is a new API without @available attribute
827827
Enum _DependenceType is a new API without @available attribute
828+
829+
Protocol CodingKey has added inherited protocol SendableMetatype
830+
Protocol Error has added inherited protocol SendableMetatype
831+
828832
// *** DO NOT DISABLE OR XFAIL THIS TEST. *** (See comment above.)

0 commit comments

Comments
 (0)