Skip to content

Overload in Scala 2 treated as Override in Dotty #4781

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
allanrenucci opened this issue Jul 9, 2018 · 10 comments
Closed

Overload in Scala 2 treated as Override in Dotty #4781

allanrenucci opened this issue Jul 9, 2018 · 10 comments

Comments

@allanrenucci
Copy link
Contributor

class A
class B extends A

class Map[T] { def foo(x: T): A = new A }
// This is an overload and not an override
class AnyRefMap[T <: AnyRef] extends Map[T] { def foo(y: T with AnyRef): B = new B }

object Test {
  (new AnyRefMap[String]).foo("Hello") // B
  (new AnyRefMap[String]: Map[String]).foo("Hello") // A
}
-- Error: tests/allan/Test.scala:32:50 -----------------------------------------
32 |class AnyRefMap[T <: AnyRef] extends Map[T] { def foo(y: T with AnyRef): B = new B }
   |                                                  ^
   |       error overriding method foo in class Map of type (x: T): A;
   |         method foo of type (y: T & AnyRef): B needs `override' modifier
one error found
@allanrenucci
Copy link
Contributor Author

@Blaisorblade
Copy link
Contributor

T with AnyRef is actually different from T in Scala2 but not Scala 3, hence compat:Scala2. And I'm not even sure scalac follow the Scala2 spec here.

I wonder if there's any sane way to support this in Dotty, even for Scala2 mode.

@allanrenucci
Copy link
Contributor Author

T with AnyRef is actually different from T in Scala2 but not Scala 3

T with AnyRef is not T in Dotty either:

scala> def foo[T](x: T & AnyRef) = 1
def foo[T](x: T & AnyRef): Int

scala> foo[String]("Hello")
val res2: Int = 1

scala> foo[Int](1)
1 |foo[Int](1)
  |         ^
  |         found:    Int(1)
  |         required: Int & AnyRef
  |         

@Blaisorblade
Copy link
Contributor

But if T <: AnyRef they should be equivalent, even if they can have different representations sometimes.
That is, we have T =:= T & AnyRef (which in Scala 3 means each is a subtype of the other), and it'd be a bug otherwise. And that's confirmed by the user-visible =:=:

scala> def foo[T <: AnyRef] = implicitly[T =:= T & AnyRef]
def foo[T <: AnyRef] => T =:= T

scala> foo[String]
val res1: String =:= String = <function1>

and they also can't be overloads because they erase to the same thing. Since Scala3 & is commutative unlike Scala 2 with, it's not clear you can even rely on T & Foo erasing unlike Foo & T:

scala> trait Foo {def foo[T <: AnyRef](t: T & AnyRef): Unit; def foo[T <: AnyRef](t: T): Unit }
1 |trait Foo {def foo[T <: AnyRef](t: T & AnyRef): Unit; def foo[T <: AnyRef](t: T): Unit }
  |                                                          ^
  |method foo in trait Foo is already defined as def foo: [T <: AnyRef](t: T & AnyRef): Unit at line 1.
  |The definitions have matching type signatures after erasure.

Still, it's true that sometimes the compiler keeps the distinction, but I don't think that's reliable (it's just like dealiasing). The corrected version of your example (with T <: AnyRef) confirms that.

scala> def foo[T <: AnyRef](x: T & AnyRef) = 1
def foo[T <: AnyRef](x: T & AnyRef): Int

scala> foo[String]("Hello")
val res0: Int = 1

scala>  foo[Int](1)
1 | foo[Int](1)
  |          ^
  |          found:    Int(1)
  |          required: Int & AnyRef
  |

@allanrenucci
Copy link
Contributor Author

@adriaanm confirmed this was a bug in scalac. @odersky suggested to use an extra dummy parameter if a function needs to be an overload:

class Map[T] { def foo(x: T): Int = 1 }

class AnyRefMap[T <: AnyRef] extends Map[T] {
  class Dummy
  def foo(y: T)(implicit $dummy: Dummy = null): Int = 2
}
scala> (new AnyRefMap[String]).foo("Hello")
val res1: Int = 2

scala> (new AnyRefMap[String]: Map[String]).foo("Hello") 
val res2: Int = 1

Note that I could only make it work with an implicit argument

@adriaanm
Copy link
Contributor

I'm pretty sure our implementation of =:= on refined types is wrong (and has been for years). I filed scala/scala-dev#530. tl;dr: it just descends structurally, doing =:= on all parent types, and some sketchy "subscope" checking. I checked the dotty implementation, which just does tp1 <:< tp2 && tp2 <:< tp1. 100% correct, but perhaps an optimization opportunity? :-)

@smarter
Copy link
Member

smarter commented Jul 11, 2018

@adriaanm would be interesting to try the naive correct implementation in Scala 2 and see if it has any performance impact.

@adriaanm
Copy link
Contributor

It would be interesting, yet non-trivial. Could shake out some more bugs too (i.e., consider me nerd-sniped ;-))

@Blaisorblade
Copy link
Contributor

Blaisorblade commented Jul 11, 2018

@adriaanm FWIW I thought Scala 2's =:= is SLS-required to not be tp1 <:< tp2 && tp2 <:< tp1. In particular, the spec demands that NOT A with B =:= B with A, tho they're mutual subtypes, tho of course this will change in Scala 3. Contrast:

EDIT: in particular, that means that the T =:= T with AnyRef from the OP fails.

@Blaisorblade
Copy link
Contributor

I've created #11035 to ensure that Scalac fixes this; this should be handled by 2.14 at the latest. Makes sense @adriaanm? @allanrenucci is fixing the stdlib, so this will be closed by #4825.

allanrenucci added a commit that referenced this issue Jul 23, 2018
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests

4 participants