Skip to content

Ambiguous implicit error involving implicits defined in the companions of classes where one is a subclass of another (works in Scala 2) #12125

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

Closed
dlwh opened this issue Apr 16, 2021 · 9 comments
Labels
Milestone

Comments

@dlwh
Copy link

dlwh commented Apr 16, 2021

or at least, it's changed from Scala 2.

First I want to say that Scala 3 is turning out to be a very nice language and I’m excited for what we can do with it. Thank you.

I am in the process of trying to get Breeze compiling under Scala 3. There is a common pattern in Breeze in how I organize implicits, where for a class XVector I have a trait XVectorOps with a bunch of implicits in it that object XVector extends. Usually these are stacked a bit. In Scala 2 this all worked fine, but in Scala 3, this leads to problems in the case where there is a subtype relationship between different XVectors.

Compiler version

3.0.0-RC2

Minimized code

This is a minimized example that compiles fine in Scala 2 but does not work in Scala 3:

trait Op[T, U]

trait HasOps[+This] {
  def foo[TT>:This, U](u: U)(implicit op: Op[TT, U]) = op
}

trait Vector extends HasOps[Vector]

class DenseVector extends Vector with HasOps[DenseVector]

trait VectorOps {
  implicit def vOp: Op[Vector, Double] = ???
}

object Vector extends VectorOps

trait DenseVectorOps {
  implicit def dvOp: Op[DenseVector, Double] = ???
}

object DenseVector extends DenseVectorOps

object Foo {
  def bar() = {
    val dv = new DenseVector
    dv.foo(3.0)
  }
}

Output

26 |    dv.foo(3.0)
   |               ^
   |ambiguous implicit arguments: both method dvOp in trait DenseVectorOps and method vOp in trait VectorOps match type Op[TT, Double] of parameter op of method foo in trait HasOps

Expectation

This compiles fine in Scala 2, and if I remove the XVectorOps classes and inline them into the companions directly this compiles fine in Scala 3, but the inheritance trips it up.

Is this intentional?

@dlwh dlwh added the itype:bug label Apr 16, 2021
@dlwh
Copy link
Author

dlwh commented Apr 16, 2021

I believe this is distinct from #12123 and #7999

@smarter
Copy link
Member

smarter commented Apr 16, 2021

It's fairly similar to #12123 actually, you're doing an implicit search on Op[TT, U]) where TT >: This, in the DenseVector case this will match both Op[Vector, Double] and Op[DenseVector, Double], and neither is more specific than the other. You can make the compiler prefer the Op defined in the subclass by making it covariant in its first type parameter:

trait Op[+T, U]

@smarter smarter closed this as completed Apr 16, 2021
@dlwh
Copy link
Author

dlwh commented Apr 16, 2021

Thanks @smarter. I can't do that because Op is actually more like

trait Op[T, U] {
  def apply(t: T, u: U)
}

In Scala 2, I think the preference for dvOp comes via the Relative Weighting rule. (6.26.3)

The relative weight of an alternative AAA over an alternative BBB is a number from 0 to 2, defined as the sum of

    1 if AAA is as specific as BBB, 0 otherwise, and
    1 if AAA is defined in a class or object which is derived from the class or object defining BBB, 0 otherwise.

A class or object CCC is derived from a class or object DDD if one of the following holds:

    CCC is a subclass of DDD, or
    CCC is a companion object of a class derived from DDD, or
    DDD is a companion object of a class from which CCC is derived.

An alternative AAA is more specific than an alternative BBB if the relative weight of AAA over BBB is greater than the relative weight of BBB over AAA.

https://scala-lang.org/files/archive/spec/2.13/06-expressions.html#overloading-resolution

Is that removed from Scala 3? I couldn't find the spec.

@dlwh
Copy link
Author

dlwh commented Apr 16, 2021

(In fact, the reason I believe it different from 12123 is precisely this rule.) Note that it works fine if I remove the Ops traits, so I think that the definition of "defined in" is maybe change? I.e. this works fine, and it shouldn't by your logic?

trait Op[T, U]

trait HasOps[+This] {
  def foo[TT>:This, U](u: U)(implicit op: Op[TT, U]) = op
}

trait Vector extends HasOps[Vector]

class DenseVector extends Vector with HasOps[DenseVector]

object Vector {
  implicit def vOp: Op[Vector, Double] = ???
}

object DenseVector {
  implicit def dvOp: Op[DenseVector, Double] = ???
}

object Foo {
  def bar() = {
    val dv = new DenseVector
    dv.foo(3.0)
  }
}

@smarter
Copy link
Member

smarter commented Apr 16, 2021

Ah, you mean this rule in particular?

if AAA is defined in a class or object which is derived from the class or object defining BBB,

That rule still exists though it's been slightly modified, see point 6 of http://dotty.epfl.ch/docs/reference/changed-features/implicit-resolution.html

So maybe your original example should work indeed, dvOp and vOp are not declared in DenseVector and Vector, but they're clearly members of these classes. Reopening.

@smarter smarter reopened this Apr 16, 2021
@smarter smarter changed the title Another incorrect ambiguous implicit error? Ambiguous implicit error involving implicits defined in thecompanions of classes where one is a subclass of another (works in Scala 2) Apr 16, 2021
@smarter smarter added this to the 3.0.x milestone Apr 16, 2021
@smarter smarter changed the title Ambiguous implicit error involving implicits defined in thecompanions of classes where one is a subclass of another (works in Scala 2) Ambiguous implicit error involving implicits defined in the companions of classes where one is a subclass of another (works in Scala 2) Apr 16, 2021
@dlwh
Copy link
Author

dlwh commented Apr 16, 2021 via email

@odersky
Copy link
Contributor

odersky commented Apr 18, 2021

It runs afoul of the condition in point 8 of http://dotty.epfl.ch/docs/reference/changed-features/implicit-resolution.html.

The new rules are as follows: An implicit a defined in A is more specific than an implicit b defined in B if

  • A extends B, or
  • A is an object and the companion class of A extends B, or
  • A and B are objects, B does not inherit any implicit members from base classes (*), and the companion class of A extends the companion class of B.

It works if DenseVectorOps extends VectorOps.

@odersky
Copy link
Contributor

odersky commented Apr 18, 2021

And I don't see how this would work with Scala 2's spec either. There is no relationship between DenseVectorOps and VectorOps which is where the implicits are defined. So this looks like a Scala 2 bug to me.

@odersky odersky closed this as completed Apr 18, 2021
@dlwh
Copy link
Author

dlwh commented Apr 18, 2021 via email

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Projects
None yet
Development

No branches or pull requests

3 participants