Why can't I pass Protocol.Type to the general T.Type parameter?

I worked with Swinject, and the problem is that I was joking. I've been stuck with this for almost the whole day. I suspect that this is due to the fact that Swift is a state language, but I'm not quite sure.

I summarized my problem on this playground

protocol Protocol {} class Class: Protocol {} let test: Protocol.Type = Class.self func printType(confromingClassType: Protocol.Type) { print(confromingClassType) } func printType<Service>(serviceType: Service.Type) { print(serviceType) } print(Class.self) // "Class" printType(serviceType: Class.self) // "Class" print(test) // "Class" printType(confromingClassType: test) // "Class" printType(serviceType: test) // "note: expected an argument list of type '(serviceType: Service.Type)'" 

I tried different solutions like test.self or type (of: test), but none of them work.

So, I think that I can not call a function with a common parameter represented as a variable?

+7
generics swift protocols
source share
2 answers

P.Type vs. P.Protocol

There are two types of protocol metatypes. For some protocol P and corresponding type C :

  • A P.Protocol describes the type of protocol itself (the only value it can hold is P.self ).
  • A P.Type describes a specific protocol type. It may contain the value C.self , but not P.self because the protocols do not match themselves (although One exception to this rule is Any , as Any is a top type , so any metatype value can be printed as Any.Type , including Any.self ).

The problem you are facing is that for a given generic placeholder T , when T is the protocol of P , T.Type not P.Type is P.Protocol .

So, if we go back to your example:

 protocol P {} class C : P {} func printType<T>(serviceType: T.Type) { print(serviceType) } let test: P.Type = C.self // Cannot invoke 'printType' with an argument list of type '(serviceType: P.Type)' printType(serviceType: test) 

We cannot pass test as an argument to printType(serviceType:) . What for? Because test is P.Type ; and there is no substitute for T , which makes the serviceType: parameter serviceType: accept P.Type .

If we replace in P for T , the parameter will take P.Protocol :

 printType(serviceType: P.self) // fine, P.self is of type P.Protocol, not P.Type 

If we substitute T specific type, for example C , the parameter accepts C.Type :

 printType(serviceType: C.self) // C.self is of type C.Type 

Hacking with protocol extension

Okay, so we learned that if we can substitute a specific type for T , we can pass C.Type function. Can we replace the dynamic type that wraps P.Type ? Unfortunately, this requires the discovery of the existential function of the language, which is currently not available to users.

However, Swift implicitly opens up the existences when accessing members in an instance or metatepe based on the protocol (i.e. it digs out the type of runtime and makes it available as a generic placeholder). We can use this fact in the extension of the protocol:

 protocol P {} class C : P {} func printType<T>(serviceType: T.Type) { print("T.self = \(T.self)") print("serviceType = \(serviceType)") } extension P { static func callPrintType/*<Self : P>*/(/*_ self: Self.Type*/) { printType(serviceType: self) } } let test: P.Type = C.self test.callPrintType() // T.self = C // serviceType = C 

There are quite a few things here, so unzip it a bit:

  • The extension element callPrintType() on P has an implicit shared placeholder Self , which is limited to P The implicit parameter Self is entered using this placeholder.

  • When calling callPrintType() on P.Type Swift implicitly digs out the dynamic type that wraps P.Type (this is an existential discovery) and uses it to match the Self placeholder.Then it passes this dynamic metatype to the implicit Self parameter.

  • So, Self will execute C , which can then be redirected to printType common T placeholder.


Why is T.Type not P.Type when T == P ?

You will notice how the above workaround works because we avoided replacing P with a common placeholder T But why, when substituting in the protocol type P for T is T T.Type not P.Type ?

Ok, consider:

 func foo<T>(_: T.Type) { let t: T.Type = T.self print(t) } 

What if we replaced in P by T ? If T.Type is P.Type , then we have:

 func foo(_: P.Type) { // Cannot convert value of type 'P.Protocol' to specified type 'P.Type' let p: P.Type = P.self print(p) } 

what is illegal; we cannot assign P.self to P.Type , as it is a type of P.Protocol , not P.Type .

So, the result is that if you want the function parameter to accept a metatype that describes a specific type that matches P (and not just the specific concrete type) - you just need the P.Type parameter, not the generics. Generics do not model heterogeneous types, for what types of protocols.

And this is exactly what you have with printType(conformingClassType:) :

 func printType(conformingClassType: P.Type) { print(conformingClassType) } printType(conformingClassType: test) // okay 

You can pass test to it because it has a parameter of type P.Type . But you will notice that this means that we cannot pass P.self to it, since it is not a P.Type type:

 // Cannot convert value of type 'P.Protocol' to expected argument type 'P.Type' printType(conformingClassType: P.self) 
+9
source share

I ran your code on the playground, and it seems that for this reason it does not compile

 let test: Protocol.Type = Class.self 

If you remove the type declaration for test , the code will work and print Class.Type on line 15 .

So, the following code compiles and runs:

 protocol Protocol {} class Class: Protocol {} let test = Class.self func printType<Service>(serviceType: Service.Type) { print(serviceType) } print(Class.Type.self) // "Class.Type" printType(serviceType: Class.Type.self) // "Class.Type" print(type(of: test)) // "Class.Type" printType(serviceType: type(of: test)) // "Class.Type" 

I hope this helps with your problem.


Edit

The error that I get on the playground with the source code is as follows:

 Playground execution failed: error: Untitled Page 2.xcplaygroundpage:9:1: error: cannot invoke 'printType' with an argument list of type '(serviceType: Protocol.Type.Type)' printType(serviceType: type(of: test)) // "Class.Type" 

This means that you call Type 2 times, so the code will not compile, because the type you are already calling is a method with an argument of type Protocol.Type .

If you change the method signature as follows:

let test: Protocol.Type = Class.self

 func printType<Service>(serviceType: Service) { print(serviceType) } 

everything will compile and work correctly, printing Class.Type

This is also the reason why my first version of the answer will be compiled, since it will assign the correct type to test , it can call .Type only once.

0
source share

All Articles