Type captured by the struct does not get interpreted as one would expect

Hi all, I have a question about the following piece of code:

protocol P {}
struct S: P {}
struct TypeCaster<TypeCastingTo> {
    init(_ _: TypeCastingTo.Type) {}
    func cast<TypeCastingFrom>(_ _: TypeCastingFrom.Type) -> TypeCastingTo.Type? {
        return TypeCastingFrom.self as? TypeCastingTo.Type
    }
}
let caster = TypeCaster(P.self)
let firstResult = caster.cast(S.self)
let secondResult = S.self as? P.Type
print(String(describing: firstResult), String(describing: secondResult))

I wonder, why in the first case we get a nil result?

1 Like

In a concrete context, P.Type (where P is a protocol) actually means something different than in a generic context. In a generic context, T.Type is always the type of T.self. But in a concrete context, P.Type refers to the supertype of all types conforming to P, rather than the type of P.self. The type of P.self is called P.Protocol. Notably, it is not the case that P.self is P.Type. Also, if S: P it is not the case that S.self is P.Protocol:

protocol P {}
struct S: P {}

func f<T>(_ t: T.Type) {
    print(T.Type.self)
}
print(P.Type.self) // P.Type
f(P.self) // P.Protocol
P.self is P.Type // false
S.self is P.Protocol // false
4 Likes

Thank you for a quick response. A follow up question would be, is it possible to capture the supertype of all types conforming to P in a generic context? That is, can we make the method

func cast<TypeCastingFrom>(_ _: TypeCastingFrom.Type) -> TypeCastingTo.Type? {
        return TypeCastingFrom.self as? TypeCastingTo.Type
    }

perform the casting of S as P, without TypeCaster having the explicit knowledge of P?

You can accomplish it like this:

protocol P {}
struct S: P {}
struct TypeCaster<TypeCastingTo> {
    init() {}
    func cast<TypeCastingFrom>(_ _: TypeCastingFrom.Type) -> TypeCastingTo? {
        return TypeCastingFrom.self as? TypeCastingTo
    }
}
let caster = TypeCaster<P.Type>()
let firstResult = caster.cast(S.self) // works!

Your initial design requires you to pass an instance with static type P.Type in order to construct an instance of TypeCaster, but this version allows you (forces you) to pass the cast-to type directly as a generic argument.

1 Like

Thank you, very nice! Exploring this, I find that either one of these works:

let caster = TypeCaster<P.Type>()
// or
let caster = TypeCaster(P.Type.self)

as long as we use

func cast<TypeCastingFrom>(_ _: TypeCastingFrom.Type) -> TypeCastingTo? {
        return TypeCastingFrom.self as? TypeCastingTo
}

Yep that would work too, anything where you are passing the type to cast to rather than an instance of the type to cast to should work. But there's no 'representative' instance of P.Type in the way there is for, like, S.Type (namely, S.self) since the only instantiations are types which conform to P, which may not even exist in the general case!

1 Like

It is perhaps easier to express this now that we have SE-0335. When the generic type parameter T is bound to any P (an existential type including all values whose types conform to P), the type T.Type refers to (any P).Type (the type of the existential type any P), which is not the same type as any (P.Type) (an existential type including all types that conform to P).

1 Like