Yes, it's the same things. In the example with P
, Q
, and X<T>
. I think expanding the example is useful to illustrate the "conforms in one way" bit, e.g., add
struct NotEquatable { }
print( callId( X<NotEquatable>() ) ) // "1"
When the conformance of X<T>: P
is established, there is only a single definition of id
that is guaranteed to work for any T
: the one on the extension of P
. So that one gets picked, and will be the same regardless of what T
ends up being substituted with. The conformances for any X<T>
to P
behave uniformly.
Y'all have found override
and @_nonoverride
. One can use @_nonoverride
to introduce a new requirement in a more-refined protocol, which then selects a witness on its terms. Let's extend the example to introduce a non-overriding id
requirement in Q
.
protocol P { var id: Int { get } }
extension P { var id: Int { 1 } }
protocol Q: P {
@_nonoverride var id: Int { get } // introduces a unique requirement `id`
}
extension Q { var id: Int { 2 } }
struct X<T>: P {}
extension X: Q where T: Equatable {}
func callIdAsP<T: P>(_ t: T) -> Int { t.id } // dispatches through `P.id`
func callIdAsQ<T: Q>(_ t: T) -> Int { t.id } // dispatches through `Q.id`
print( callIdAsP( X<Int>() ) ) // "1"
print( callIdAsQ( X<Int>() ) ) // "2"
In the conformance of X<T>: P
, the id
defined in the extension of P
is the only viable witness that works for any T
. Hence, the requirement P.id
is satisfied by the id
defined in the extension of `That's the same answer we had in the prior example.
In the conformance of X<T>: Q
, both the id
defined in the extension of P
and the id
defined in the extension of Q
can be used to satisfy the requirement Q.id
. The id
defined in the extension of Q
is more specific, so that is used to satisfy the requirement Q.id
.
These two choices are made independently.
Now, to the uses. In callIdAsP
, the only way to type-check t.id
is to go through the requirement P.id
, so we get "1" in the call given X<Int>
.
In callIsAsQ
, the expression t.id
can either call through the requirement P.id
or the requirement Q.id
. Overload resolution picks Q.id
, because it is more specific, so we get "2" in the call given X<Int>
.
We can change the semantics. That might need to be staged in with a version bump (e.g., Swift 6), but I wouldn't get too caught up with the "when" part of this. Figure out what the model should be.
The model I've been imagining would collect the set of potential witnesses at the point where the conformance was declared, but delay the selection of the "best" witness to satisfy a given requirement until enough information is available---whether that happens at compile time or at run time.
For example, rewind back to the conformance X<T>: P
. The id
defined in the extension of P
is a viable witness that works for any T
. The id
defined in the extension of Q
is potentially viable; it depends on whether the actual type substituted for T
conforms to Equatable
and, if so, it is more specific than the id
defined in the extension of P
. Hence, we would need to delay the decision of the "best" witness until we have a concrete type substituted in for T
. For T=Int
, Int
conforms to Equatable
so the id
defined in the extension of Q
will be selected as the witness. For T=NotEquatable
, the id
defines in the extension of P
will be selected as the witness.
For the conformance X<T>: Q, both the
iddefined in the extension of
Pand the
iddefined in the extension of
Qare viable, and the one in the extension of
Qis better, so the one in the extension of
Q` is selected as the witness. There is no reason to delay the decision until concrete types are available, because the decision won't change.
All of the examples written/mentioned above would print 2
for X<Int>
, regardless of how they are called. And 1
for X<NotEquatable>
, of course.
In this model, @_nonoverride
can probably remain a hidden relic, needed only to maintain ABI for the Standard Library where it is used.
The shape of a protocol's witness table is established by the protocol itself; each type that conforms to a protocol must provide a witness table that matches that shape. The protocol Q
from the original example does not contain an entry for id
; only P
contains such an entry, because P
declares the requirement id
. What you describe matches up with the @_nonoverride
trick I describe above.
There are 135 messages in this thread, 65 in this thread, and 67 in this thread.
Doug (just one me)