scala / scala3

The Scala 3 compiler, also known as Dotty.
https://dotty.epfl.ch
Apache License 2.0
5.83k stars 1.05k forks source link

Methods available after implicit conversion are hidden by extension methods, even when signature does not match callsite #13300

Open hughsimpson opened 3 years ago

hughsimpson commented 3 years ago

Compiler version

3.0.1

Minimized code

import scala.language.implicitConversions

case class From(i: Any)
case class To(s: Any):
  def as[T]: T = s.asInstanceOf[T]
implicit def convert(f: From): To = To(f.i)
extension(f: Any)
  def as(conv: String => String) = println(conv(f.toString))

@main def main() = println(From((123, 321)).as[(Int, Int)])

Output

[error] 15 |@main def main() = println(From((123, 321)).as[(Int, Int)])
[error]    |                           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
[error]    |           method apply in trait Function1 does not take type parameters

Expectation

I would expect scalac to find the as method defined on To via the implicit conversion - however the method defined in the extension hides it. The equivalent code works in scala 2.13.

som-snytt commented 3 years ago

The weird error message is because you get as(_).

But the "hiding" doesn't have to do with the arg type or type args or whether Conversion is used.

The choice is correctly ambiguous in implicit scope; extension wins with both in lexical scope, even with error; conversion wins if extension is "hidden" in implicit scope.

I'd expect: try simple rewrite for extension method, then try implicit scope for objects offering the extension, and include conversions. Apparently conversion in lexical scope works as in Scala 2.

Two recent issues where it's not clear what the search order is or what is tried to make it typecheck: https://github.com/lampepfl/dotty/issues/12904 https://github.com/lampepfl/dotty/issues/12790

import scala.language.implicitConversions

class From(val i: Any)
object From:
  extension(f: Any) def g(x: Double): Unit = println(x)
  given Conversion[From, To] = (from: From) => To(from.i)
class To(s: Any):
  def g(x: String): Unit = println(x + x)

given Conversion[From, To] = (from: From) => To(from.i)
extension(f: Any) def g(x: Double): Unit = println(x)

@main def main() =
  val x = From((123, 321))
  println(x.g("X"))
  println(x.g(3.14))
odersky commented 3 years ago

Yes, basically it's one or the other. You should not have at the same time old style conversions and new style extensions. It might be possible to improve on this with a lot of work, but I doubt it would be very useful. I don't really want to touch that one myself. It's too much of a corner case to matter.