In Swift, a generic type conforms to a protocol in exactly one way. The way that it conforms is determined statically at compile-time, where the conformance is declared.
A “conformance” to a protocol means, essentially, a lookup table to identify the implementation satisfying each of the protocol’s requirements.
So at the point where Foo
is declared as conforming to P
, the compiler constructs a table to specify, for each requirement of P
, which member of Foo
satisfies that requirement.
This is a single table for Foo
, which does not depend on the generic parameter Bar
. Every instance of Foo
conforms to P
in the same way, using the same table.
At the point where Foo
conforms to P
, there is only one available implementation for the bar
requirement, namely the stored property in Foo
.
There is only one available implementation for the printTypeOfBar
requirement, namely the function in the unconstrained extension P
.
And there is also only one available implementation for the typeOfBar
requirement, namely the computed property in the unconstrained extension P
.
The constrained extension is not available for all possible instances of Foo
, so the version of typeOfBar
found there cannot be the implementation that goes in the conformance table. The conformance table must work for every instance of Foo
.
That means Foo
conforms to P
by using the version of typeOfBar
from the unconstrained extension.
When you write foo.typeOfBar
, the compiler performs static dispatch on the instance foo
. It sees two possible implementations for typeOfBar
: one from the unconstrained extension on P
, and one from the constrained extension.
The compiler chooses the version from the constrained extension, since it is a closer match to the type of foo
. That is why you see “Int” as the first output.
When you write foo.printTypeOfBar()
, the compiler still performs static dispatch on the instance foo
. It sees only one possible implementation, from the unconstrained extension on P
.
Inside the body of printTypeOfBar
, however, the call to typeOfBar
uses dynamic dispatch through the protocol requirement. This is because the implementation is located in an extension of P
, where the only thing known about Self
is that it conforms to P
.
So printTypeOfBar
calls the version of typeOfBar
found in the conformance table for Foo
, which as we saw earlier is the one from the unconstrained extension on P
.
That is why you see “Not Int” as the second output.