Need Help Understanding Protocols and Generics

…okay, now I’ve found something even more mind-bending. We can apparently get static dispatch in a generic context constrained to a composition of a class and protocol.

And it is different from what we would get on either the concrete class itself, or the base class in the constraint, or when generically constrained to the protocol, or in an existential of the protocol:

class C: P {}
class D: C, Q { var id: String { "D" } }
func testCQ<T: C & Q>(_ t: T) { print(t.id, t.id2) }

let d = D()
print(d.id, d.id2)            // D P   (7)
testCQ(d)                     // Q P   (8)
printGenericConstrainedToQ(d) // P P   (9)

Specifically, the left side of (8) prints "Q".

The only difference between testCQ and printGenericConstrainedToQ is the presence of “C &” in the generic constraint on testCQ.

Both are constrained to Q.

C itself is only constrained to P, which Q also inherits from P so that is no new information.

C does not provide any concrete implementations, so its witness for P.id is the default provided in extension P, which prints “P”.

The concrete implementation of D.id is unavailable inside testCQ, and indeed it is not called.

Instead we get the overload from extension Q, which I can only reconcile as being due to static overload resolution on the composition C & Q.

But I would have expected dynamic dispatch to the protocol requirement, meaning d’s witness for P.id, which prints “P”.

I think the fact that (8) does not print “P P” is a bug.

(Although, the fact that (9) does not print “D P” could also be considered a bug, in which case (8) should print the same.)

1 Like