swiftlang / swift

The Swift Programming Language
https://swift.org
Apache License 2.0
67.57k stars 10.36k forks source link

Misleading error message when comparing generics that vary in types #73029

Open Alc-Alc opened 6 months ago

Alc-Alc commented 6 months ago

Description

When the following code is run, the error I get is

error: binary operator '==' cannot be applied to two 'MyStruct<String>' operands

While it is apparent that generics that differ in types cannot be compared, the error message does not indicate that is indeed the problem here. It says it cannot compare "two 'MyStruct<String>' operands", however, one is a MyStruct<String> and other is a MyStruct<Int>.

Reproduction

struct MyStruct<T: Equatable> {
    let val: T

    init(_ val: T) {
        self.val = val
    }

    static func == (lhs: MyStruct, rhs: MyStruct) -> Bool{
        lhs.val == rhs.val
    }
}

print(MyStruct(0) == MyStruct(0))  // true
print(MyStruct("0") == MyStruct(0))

Expected behavior

I would expect an intuitive error message like so, that shows what exactly is the problem (types are different)

error: binary operator '==' cannot be applied to 'MyStruct<String>' and 'MyStruct<Int>' operands

Environment

swift-driver version: 1.90.11.1 Apple Swift version 5.10 (swiftlang-5.10.0.13 clang-1500.3.9.4)                                                                                                                                                              
Target: arm64-apple-macosx14.0

Additional information

Asked this in Swift forums and I was suggested to file a report here.

AnthonyLatsis commented 6 months ago

Observation: Given

struct S<T> {
  static func ==(lhs: S, rhs: S) -> Bool {
    true
  }
}

reproduces with

func getS<T>(value: T) -> S<T> {}

let _ = getS(value: "") == getS(value: 0)

but not

func getS<T>(type: T.Type) -> S<T> {}

let _ = getS(type: String.self) == getS(type: Int.self)
jamieQ commented 5 months ago

additional observation: if the struct & operator declarations are local the behavior differs and you get the expected diagnostic:

func test_local_op_ambig() {
    struct S<T> {
      static func ==(lhs: S, rhs: S) -> Bool {
        true
      }
    }

    func getS<T>(value: T) -> S<T> {}

    let _ = getS(value: "") == getS(value: 0) // Binary operator '==' cannot be applied to operands of type 'S<String>' and 'S<Int>'
}

seems likely this behavior is due to multiple valid solutions being found by the type constraint solver that have the same 'score' and the one being arbitrarily picked is more confusing than other valid solutions in this context. for posterity, the code that appears responsible for emitting the diagnostic is here.