A few compiler gotchas

Found a few compiler gotchas in some trivial cases:

    _ = 0.0 == 0.0 // πŸ”΄ Error: Ambiguous use of operator '=='
    _ = 0.0 != 0.0 // yet this is fine
    _ = 0 == 0 // and this is fine

    _ = [0] == [0] // πŸ”΄ Error: Ambiguous use of operator '=='
    _ = [0.0] == [0.0] // yet this is fine
    _ = [0.0] != [0.0] // and this is fine
    _ = [0] != [0] // and this is fine
    _ = [0:0] == [0:0] // and this is fine
    _ = [0] < [0] // πŸ”΄ this compiles ?! but should it ?!

    let x = 0
    let y = 0
    _ = [x] < [y] // πŸ”΄ this also compiles ?! but should it ?!

    let a: [Int] = [0]
    let b: [Int] = [0]
    _ = a < b // Error: Binary operator '<' cannot be applied to two '[Int]' operands
    // finally expected error

That is mighty surprising, but at least the error goes away and it behaves as you would expect if you do not import Foundation


The one with [0] < [0] also only happens if you import Foundation. What I expect is happening there is that [0] is getting interpreted as some ExpressibleByArrayLiteral type other than Array. From a cursory examination, it looks like IndexPath may fit the bill, and it's provided by Foundation:

  9> let a: IndexPath = [1]
a: Foundation.IndexPath = 1 index {
  _indexes = single {
    single = 1
 10> let b: IndexPath = [2]
b: Foundation.IndexPath = 1 index {
  _indexes = single {
    single = 2
 11> a < b
$R7: Bool = true
1 Like

That may be because when not importing Foundation the only viable overload for == in this case is ==(Double, Double) -> Bool but when Foundation is imported ==(CGFloat, CGFloat) -> Bool because viable as well, therefore the ambiguity.

Maybe we can add a list of types that the compiler thinks needs to disambiguate from in this kind of diagnostics. Otherwise there doesn't seem to be an easy/straightforward way to figure out this kind of things.

In more detail:

error: MyPlayground.playground:15:5: error: ambiguous use of operator '=='
0.0 == 0.0

Foundation.OperationQueue:30:32: note: found this candidate
            public static func == (a: OperationQueue.SchedulerTimeType.Stride, b: OperationQueue.SchedulerTimeType.Stride) -> Bool

Foundation.Decimal:3:24: note: found this candidate
    public static func == (lhs: Decimal, rhs: Decimal) -> Bool

Foundation.RunLoop:30:32: note: found this candidate
            public static func == (a: RunLoop.SchedulerTimeType.Stride, b: RunLoop.SchedulerTimeType.Stride) -> Bool

Dispatch.DispatchQueue:35:32: note: found this candidate
            public static func == (a: DispatchQueue.SchedulerTimeType.Stride, b: DispatchQueue.SchedulerTimeType.Stride) -> Bool


I think this is worth a bug. The Foundation folks could consider adding @_disfavoredOverload to the relevant operators here, which may be enough to fix the issue.

However, I wonder if this is all arising as a consequence of changes which were made to favor overloads in other libraries over the standard library to avoid disruptions to third-party user codeβ€”maybe one thing that can be done in the compiler is to carve out an exception to the exception for Foundation. @scanon might have thoughts on this, I'd imagine...


We recently expanded the set of "libraries that lose in overload resolution" to include everything implicitly imported (IIRC, Swift module, _Concurrency, _StringProcessing). I don't think that we have a super-well-considered rationale around this just yet. I'll discuss it with the Foundation folks.


That'd be fantastic (and Dispatch folks if they're a separate group, maybe, given the last overload listed above?).

The limiting principle here I'd propose would be that these libraries known to the compiler already get special treatment and are uniquely positioned: while it cannot be known what third-party user code will be affected by new Swift standard library overloads, this is entirely knowable for Foundation, Dispatch, and CGFloat (whatever library is considered to vend it on any platform).

Therefore, hardcoding a hierarchy where the standard library loses to these libraries is not necessary to prevent such breakage; on the other hand, removing this hardcoded rule solves issues shown here, providing the freedom for these libraries to vend operators for their literal convertible types without having to worry about creating new weird situations like this.