We accidentally started talking in an old thread. Specifically, this one: two protocols with the same method name
Anyway...
So I've been playing around with this a bit. Here's the code I ran, which is pretty similar to yours with the exception that I added foo
properties which weren't annotated:
protocol B { var foo: String {get} }
protocol C { var foo: String {get} }
protocol D { var foo: String {get} }
struct E: B, C {
var foo: String = "E"
@_implements(B, foo)
var fooForB: String { "B" }
@_implements(C, foo)
var fooForC: String { "C" }
}
struct F: C, D {
var foo: String = "F"
@_implements(C, foo)
var fooForC: String { "C" }
@_implements(D, foo)
var fooForD: String { "B" } // Not "D"... I'm proving something
}
let e = E()
let f = F()
func b<T: B>(_ v: T) { print(v.foo) }
func c<T: C>(_ v: T) { print(v.foo) }
func d<T: D>(_ v: T) { print(v.foo) }
func bc<T: B&C>(_ v: T) { print(v.foo) }
func cd<T: C&D>(_ v: T) { print(v.foo) }
b(e) // B
c(e) // C
c(f) // C
d(f) // B
bc(e) // C
cd(f) // B
print(e.foo) // Error: Ambiguous use of `foo`
print(f.foo) // Error: Ambiguous use of `foo`
It would seem that in generic code where a type is constrained to multiple overlapping protocols, the "winner" is currently decided alphabetically by the protocols' names. I tried swapping the order of the declarations and conformances in the structs, as well as the order of the constraints in the generic functions, and none of it mattered. I even made D
's version return "B" on the off chance the returned value could somehow matter. I'd prefer that fAB
and fBC
both throw errors and force you to be explicit. Also, note that in non-generic code, when you try to access the struct's stored property -- its only property that's actually named "foo" -- the compiler throws an "ambiguous use" error. I did find a way around this, though:
protocol A { var foo: String {get} }
extension E: A {}
extension F: A {}
func a<T: A>(_ v: T) { print(v.foo) }
a(e) // E
a(f) // F
// Works in non-generic code, too:
print((e as A).foo) // E
print((e as B).foo) // B
print((e as C).foo) // C
print((f as A).foo) // F
print((f as C).foo) // C
print((f as D).foo) // B
// This "as" trick does not work if you just try using a variable's own type,
// though. Apparently there's gotta be a protocol or something involved.
print((e as E).foo) // Error: Ambiguous use of `foo`
Does the fact that one of the protocols directly accesses the type's storage instead of going through some redirection mechanism change who wins the naming collision race?
func abc<T: A&B&C>(_ v: T) { print(v.foo) }
func acd<T: A&C&D>(_ v: T) { print(v.foo) }
abc(e) // C
acd(f) // B
protocol G { var foo: String {get} }
extension F: G {}
func cdg<T: C&D&G>(_ v: T) { print(v.foo) }
cdg(f) // F
Nope, still looks alphabetical.
Anyway, I think I agree that this is a good starting point. I think in non-generic code, in instances like this where a type's property (or function) has the same name as that of a protocol requirement's, we should have a way of disambiguating it. Since this functionality is currently hidden behind an "_", there's no need to maintain source compatibility. I'd be ok with either:
print(e.foo) // E
print((e as B).foo) // B
print((e as C).foo) // C
or, from my post last night:
print(e.foo) // E
print(e.B.foo) // B
print(e.C.foo) // C
Looking at it while I'm awake, I think I have an esthetic preference for the "as" version. I haven't thought about if either is more functional than the other. And of course, suggestions are welcome.
I also think that we should be able to list multiple protocols. Assuming the @implements
syntax, maybe this?
@implements(A&B.foo: Int)
var whatever: Int
Anyway, I've gotta run.