Generic `where` fails for protocol types

Swift seems unable to recognize a protocol type that conforms to another protocol type in a where clause.

Let's assume protocol A and protocol B: A; in this case where B: A fails. The same is true for the is operator.

This does not match user expectations and breaks some generics use cases. Example:

protocol A {}
protocol B: A {}
class C: B {}

func f<P>(_ p: P.Type, _ i: @autoclosure () -> P) {
    print("1")
}
func f<P>(_ p: P.Type, _ i: @autoclosure () -> P) where P: A {
    print("2")
}

f(B.self, C())
f(C.self, C())

I expect to see:

2
2

but instead, I see:

1
2

Does anyone have insight? Should I file a bug?

1 Like

This problem is further complicated by the fact that Swift does not support generic type parameters that conform to other type parameters (unless the latter is explicitly defined as a class type – it is impossible to define type parameters as protocol types).

Thus, it is impossible to fix the above problem by testing the concrete type:

protocol A {}
protocol B: A {}
class C: B {}

func f<P, I: P>(_ p: P.Type, _ i: @autoclosure () -> I) { // Type 'I' constrained to non-protocol, non-class type 'P'
    print("1")
}
func f<P, I: P>(_ p: P.Type, _ i: @autoclosure () -> I) where I: A { // Type 'I' constrained to non-protocol, non-class type 'P'
    print("2")
}

f(B.self, C())
f(C.self, C())

IMO Swift should allow this as well, but yield a compiler error on the call site if the user attempts to pass an incompatible concrete type to the parameter.

1 Like

B.self is the protocol metatype value, also now spelt (any B).self.

The existential type any B does not conform to any protocol, not even B. You can see this clearly diagnosed if you create a similar function with a constraint where P: B that doesn’t have another overload of the same name.

This is not a bug: in the general case, an existential type cannot conform to its corresponding protocol. For example, any FixedWidthInteger does not, semantically, fulfill the semantics of a fixed-width integer type.

2 Likes

It would be nice if there was a way to declare P to be a protocol type; I was hoping the Swift compiler could infer it, but it appears that it cannot:

// cannot use 'Protocol' with non-protocol type 'P'
// Type 'I' constrained to non-protocol, non-class type 'P'
func f<P, I: P>(_ p: P.Protocol, _ i: @autoclosure () -> I) {