Strange error in operator overload resolution

Continuing from Associative arithmetic operation order matters in Swift? - #8 by tera.

The MRE is merely:

struct Foo {
    static func + (lhs: Foo, rhs: Foo) -> Foo { .init() }
struct Bar {
    static func + (lhs: Bar, rhs: Bar) -> Foo { .init() }

let a = Bar()
let b = Bar()
let foo: Foo = (a + b) + (a + b)

In 5.8, any of the following changes allow it to compile:

  • Initializing foo out-of-line:
    let foo: Foo
    foo = (a + b) + (a + b)
  • Replacing any of the four operands with Bar(), e.g:
    let foo: Foo = (a + Bar()) + (a + b)
    Curiously, spelling this as .init() instead of Bar() does not work.
  • Adding as Foo after the first parenthesized expression:
    let foo = (a + b) as Foo + (a + b)
    Putting the as Foo at the end of the line instead does not fix the problem.

However, as written, the compiler assumes I mean this overload of the operator on RangeReplaceableCollection, and complains that:

  1. The expression as a whole is of type Bar, not Foo;
  2. Bar does not conform to that protocol.

Where on earth does it get RangeReplaceableCollection collection from, and why does it not see the definition that's right there?


cc @hborla @xedin based on the heterogeneous typing of the + overload typing in the correct solution here this smells like it's related to some of the performance heuristics in operator overloading. This should be accepted, right?

When typechecking fails in the face of an overloaded declaration, the compiler will try to make a best guess about which overload you actually meant to call based on a bunch of heuristics that roughly boil down to "how close were you to calling this overload." Overloads which would require entirely different types are in a very blurry sense more 'distant' than overloads which 'only' require you to declare a conformance.

With a heavily overloaded declaration like + this can have very surprising results because the 'closest' match based on heuristics might be an overload that you had no idea about and no intention of ever using. And IME especially in cases where there's a correct overload which the compiler fails to see for some reason, the overload we actually end up diagnosing can appear very surprising.


Interestingly, if I make Foo conform to AdditiveArithmetic (adding static let zero and static func -), the errors change, but the solutions/workarounds don't. At the very least the errors cease to be misleading, but they aren't exactly helpful, either:

main.swift:12:19: error: cannot convert value of type 'Foo' to expected argument type 'Bar'
let foo: Foo = (a + b) + (a + b)
main.swift:12:29: error: cannot convert value of type 'Foo' to expected argument type 'Bar'
let foo: Foo = (a + b) + (a + b)