Two (or More, I Suppose) Protocols with Same-Name Requirements (cont)

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.

2 Likes

Oh! Oh! Better (IMHO, anyway) idea! Instead of @implements, @forward. Reusing the protocols from my previous post:

struct E: B, C {
    var foo: String = "E"
    @forward(A.foo, B.foo)
    var protocolFoo: String = "A or B"
}

Then we can use the same annotation for more powerful stuff like:

struct MyDictionaryWrapper: Collection {
    @forward(Collection) // or maybe `@forward(Collection.*)`
    var store: [Int:String] {
        willSet {
            // Insert data validation code here.
        }
    }
}
// Maybe with something like this for when you want to forward
// a conformance to a property that already exists.
extension MyDictionaryWrapper: CustomStringConvertible {
    @forward(CustomStringConvertible): \.store
}
1 Like

I've tried your solution @forward, but the compiler said, no such attribute. The @_implements
works perfectly. And your var B.foo: Int solution doesn't work anyway. It leads to a syntax error. Could you please tell which version of Swift you success of doing this? thks.

There isn’t an @forward attribute (yet, anyway), and this doesn’t work in any version of Swift. I’m just proposing syntax for the functionality.

OK, got it. Thank you for explaining this. :wink:

This is a good problem to solve. However, there's a more general problem that needs solving, which should serve as the foundation of a solution to this problem: we have no way in code to refer unambiguously to a particular function, method, property, protocol requirement, or witness for a protocol requirement. Someone needs to come up with a syntax and spell out what it means. Once we've done that, we can attack problems like this one, which will have been shrunk by virtue of our having solved the other one.

5 Likes