Roman
Roman

Reputation: 1498

Contradictory protocol conformances

Can anybody explain please, what is going on here:

struct Test {
    var value: Int
}

// -----------------------------------
protocol Test1: Equatable {
    var value: Int { get set }
}
extension Test1 {
    static func == (lhs: Self, rhs: Self) -> Bool {
        lhs.value == rhs.value + 1
    }
}
// -----------------------------------
protocol Test2: Equatable {
    var value: Int { get set }
}
extension Test2 {
    static func == (lhs: Self, rhs: Self) -> Bool {
        lhs.value == rhs.value + 2
    }
}
// -----------------------------------

extension Test: Test1 {}
extension Test: Test2 {}

let a = Test(value: 5)
let b = Test(value: 5)

print(a == b) // true, which is unexpected

If conforms only to Test1 or only to Test2 it works as expected.
Conforming to Test1 and Test2. At first I thought that the order will matter. But looks like it just cancels each other! Without any warnings. Which is very counterintuitive.

Upvotes: 3

Views: 87

Answers (1)

Sweeper
Sweeper

Reputation: 271735

Note: Test conforms to Equatable not because of the two extensions you provided, but because of the auto-generated Equatable implementation. As you said, if there were only those two extensions, it would be ambiguous which == is the Equatable implementation.

Neither of the == in the Test1 or Test2 protocol extensions are called. Instead, the auto-generated Equatable implementation is called. As you may recall, a == operator is auto-generated for types whose properties are all Equatable, and is declared to conform to Equatable itself.

This is because members declared in extensions use static binding, so when there are multiple versions of the same member available, the version declared in the extension will only be chosen if the compile time type is the extension's type. For example:

protocol P { }
class C : P { func f() { print("C") } }
extension P { func f() { print("P") } }
C().f() // C
(C() as P).f() // P

In a == b, both a and b are Test, so none of the extension operators gets chosen. However, since Test1 and Test2 both use Self, you can only use them as generic constraints, and can't cast to them either. Therefore, I don't think you will be able to call the == declared in the extensions at all.

Anyway, if you want to see an error message saying that there are duplicate == operators available:

struct Test {
    var value: Int
    var x: Any? // now there is no auto-generated Equatable implementation!
}

error: type 'Test' does not conform to protocol 'Equatable' extension Test: Test1 {} ^

note: candidate exactly matches

   static func == (lhs: Self, rhs: Self) -> Bool {
               ^

note: candidate exactly matches

   static func == (lhs: Self, rhs: Self) -> Bool {
               ^

If you remove one of the extensions, then Test conforms to Equatable because of the extension, as there is no more ambiguity. Therefore, the auto-generated implementation isn't auto-generated anymore, and there is only one == to choose from - the one declared in the extension.

Upvotes: 3

Related Questions