Skip to content

Documentation help needed: given [T]: CanEqual[T, T] resolves CanEquals of different types #18631

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
joan38 opened this issue Oct 2, 2023 · 10 comments
Labels
area:documentation good first issue Perfect for someone who wants to get started contributing itype:enhancement

Comments

@joan38
Copy link
Contributor

joan38 commented Oct 2, 2023

Compiler version: 3.3.1
Compiler option: -language:strictEquality

The following code compiles fine. But why? Where does it finds CanEqual[Test, Test2] of different types?

case class Test(i: Int)
case class Test2(i: Int)

object Eq:
  given [T]: CanEqual[T, T] = CanEqual.derived

  @main def run(): Unit =
    Test(1) == Test2(2)
    ()

https://scastie.scala-lang.org/X6boEpObQXee5uhESbviuQ

Thanks

@joan38 joan38 added itype:bug stat:needs triage Every issue needs to have an "area" and "itype" label labels Oct 2, 2023
@rayrobdod
Copy link

I believe that the generic given is allowing scala to find a CanEqual[Test1 | Test2, Test1 | Test2], which due to CanEqual's variance, satisfies the need for a CanEqual[Test1, Test2].

@som-snytt
Copy link
Contributor

for contrast

  given [T <: Product]: CanEqual[T, T] = CanEqual.derived

  @main def run(): Unit = Test(1) == Test2(2) && Test(1) == 42

I assumed it was taking T as Any or similar, but I'm not sure what compiler options tell me the debug.

@bishabosha
Copy link
Member

if I add this extra method:

def doEqual[T1, T2](t1: T1, t2: T2)(using CanEqual[T1, T2]) = t1 == t2 

it becomes clear that doEqual(Test(1), Test2(2)) infers T to be Test1 | Test2, using the -Xprint:typer flag to debug:

@main def run(): Unit =
  {
    Eq.doEqual[Test, Test2](Test.apply(1), Test2.apply(2))(
      Eq.given_CanEqual_T_T[Test | Test2])
    ()
  }

@bishabosha
Copy link
Member

bishabosha commented Oct 2, 2023

I believe this changed since #15642, but might be wrong.

However even in 3.0.0 this still compiled, just with Object inferred as the argument

@bishabosha
Copy link
Member

bishabosha commented Oct 2, 2023

I'd say this is by design because inference can always widen (EDIT: a contravariant type parameter) to make a constraint work

@dwijnand dwijnand added area:infer and removed stat:needs triage Every issue needs to have an "area" and "itype" label labels Oct 2, 2023
@joan38
Copy link
Contributor Author

joan38 commented Oct 2, 2023

Why is this "by design" only for given CanEqual?
Do we now have different behavior depending on which given we are talking about?
For example if I define my own equals, now the same rules don't apply:

case class Test(i: Int)
case class Test2(i: Int)

trait MyEq[A, B]

object Eq:
  given [T]: MyEq[T, T] = ???

  def myEquals[A, B](a: A, b: B)(using myEq: MyEq[A, B]) = ???

  @main def run(): Unit =
    myEquals(Test(1), Test2(2))
    ()

https://scastie.scala-lang.org/gCcmi7hgQBqu6eN9XlIySA

That does not sound ok to me.

@rayrobdod
Copy link

Eq is contravariant. If MyEq is made contravariant, then the same rules will apply to MyEq as to Eq: https://scastie.scala-lang.org/BIbZaKwFR7ihbTQGl7lJCg

@rayrobdod
Copy link

rayrobdod commented Oct 3, 2023

Otherwise, you end up with strict equals being too strict, and not being able to compare things that should be comparable.

Like how in munit, which tries to have a strict equals with def assertEquals[A, B](a: A, b: B)(implicit ev: B <:< A): Boolean, has a compilation error for assertEquals(Nil, value:List[Int]) because it is not the case that Nil.type <:< List[Int], even though it makes sense to be able to compare two lists even if one of the lists can be determined to be a Nil.type and not just a List[Int].

@bishabosha
Copy link
Member

Perhaps we should add a recommendation in the Docs (API as well) to not define a "universal" CanEqual

@bishabosha bishabosha added area:documentation itype:enhancement good first issue Perfect for someone who wants to get started contributing and removed itype:bug area:infer labels Oct 3, 2023
@bishabosha bishabosha changed the title given [T]: CanEqual[T, T] resolves CanEquals of different types Documentation help needed: given [T]: CanEqual[T, T] resolves CanEquals of different types Oct 3, 2023
mbovel pushed a commit to mbovel/dotty that referenced this issue Oct 3, 2023
Previously, we rejected the case where a symbol of a self type selection
was private if was not of the enclosing class. But that symbol could shadow
a non-private symbol in a base class, so have to treat that case as well.

Fixes scala#18631
Kordyjan pushed a commit to dotty-staging/dotty that referenced this issue Nov 20, 2023
Previously, we rejected the case where a symbol of a self type selection
was private if was not of the enclosing class. But that symbol could shadow
a non-private symbol in a base class, so have to treat that case as well.

Fixes scala#18631
@som-snytt
Copy link
Contributor

The commit references are spurious typos. (The test name in that commit is also a typo.)

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
area:documentation good first issue Perfect for someone who wants to get started contributing itype:enhancement
Projects
None yet
Development

No branches or pull requests

5 participants