I think the SendableMetatype name isn't a good one for the role this (marker) protocol plays any more. Something like NonisolatedConformances would be more accurate to what it does.
It's more general, yes, and something similar is described in future work. Note that your #2, which infers nonisolated on conformance requirements T: P when T is Sendable, is effectively a type restriction from the user perspective... but it's implemented as an inference rule that desugars down to the conformance restrictions.
My main concern is around the complexity of introducing a new kind of requirement, T: nonisolated P, into the language/formal model/implementation. I don't really know yet how it's going to work out; new kinds of requirements can have ripple effects.
Stepping back a little bit, this proposal involves a source-breaking change that will start to reject some generic code that carries conformances across an isolation boundary. We think that:
a. Most generic code never crosses any isolation boundaries, so it is unaffected, and
b. Most generic code that does carry a conformance T: P across an isolation boundary also carries a value of the conforming type across an isolation boundary, so it will also have a constraint T: Sendable.
Under these assumptions, the source-breaking change has very little practical impact. The code that does break looks a bit like this:
protocol P {
static func f()
}
func callF<T: P>(_: T.Type) {
Task.detached {
T.f() // needs to error, because it carries T: P across an isolation boundary
}
}
We need a type-checking model that produces that error, and a way to describe to that model that T:P must not be allowed to use an isolated conformance so we can fix callF. According to (b), that could be T: Sendable, but that's a stronger constraint than we really want for this code. So we're seeking the constraint in between.
Something like T: SendableMetatype or T: NonisolatedConformances expresses it as a type-based property, the same way T: Sendable does. The inference rule for Sendable is then encoded in protocol inheritance:
/*@marker*/ protocol SendableMetatype { }
/*@marker*/ protocol Sendable: SendableMetatype { }
Your suggested nonisolated conformances express it as a conformance property. This is more general when there are multiple conformances involved, e.g.,
protocol P {
static func f()
}
protocol Q {
static func G()
}
func callFG<T>(_: T.Type) where T: P, T: nonisolated Q {
Task.detached {
T.f() // needs to error, because it carries T: P across an isolation boundary
T.g() // okay, because `T` has a nonisolated conformance to Q
}
}
In the proposal's model (T: NonisolatedConformances or T: SendableMetatype), there's no way to express that T: P can be isolated but T: nonisolated Q cannot. I suspect we don't need this flexibility, and would rather not introduce the complexity of this model into the system if we don't need it.
In writing this, I'm convincing myself that I prefer the non-sendable-metatype model from earlier pitches, rather than the partial implementation of your nonisolated conformance requirements that is in the proposal as under review. I don't think we need the complexity of the model you propose, and the non-sendable-metatype model fits neatly into the Sendable design we have today.
As noted in future directions, I think your "sendable protocol conformances" are equivalent to "nonisolated conformances", but that the isolation terminology is better here.
Yes, some of the code tested used Swift Testing.
You could make the protocol inherit from SendableMetatype, which would prevent an isolated conformance to it.
Doug