scala / bug

Scala 2 bug reports only. Please, no questions — proper bug reports only.
https://scala-lang.org
231 stars 21 forks source link

AbstractMethodError when implementing method with generic type of different arity #12365

Open steinybot opened 3 years ago

steinybot commented 3 years ago

reproduction steps

Initially reported as https://github.com/scala-js/scala-js/issues/4454.

See reproduction here: https://github.com/steinybot/bug-reports/tree/scalajs/non-existent-method

using Scala 2.13.5

trait TwoTypeParam[X, Y]
class OneTypeParam[X] extends TwoTypeParam[X, Any]

trait Parent {
  type L = TwoTypeParam[_, _]
  def childMethod: L
  def parentMethod: Any = childMethod
}

class Child extends Parent {
  override def childMethod: OneTypeParam[_] = ???
}

object Test extends App {
  (new Child).parentMethod
}

problem

This compiles and fails at runtime:

sbt:root> fooJVM/run
[info] Compiling 1 Scala source to C:\Users\sjrdo\Documents\Projets\bug-4454\jvm\target\scala-2.13\classes ...
[info] running Child
[error] (run-main-0) java.lang.AbstractMethodError: Method Child$.childMethod()LTwoTypeParam; is abstract
[error] java.lang.AbstractMethodError: Method Child$.childMethod()LTwoTypeParam; is abstract
[error]         at Child$.childMethod(Main.scala)
[error]         at Parent.parentMethod(Main.scala:14)
[error]         at Parent.parentMethod$(Main.scala:14)
[error]         at Child$.parentMethod(Main.scala:1)
[error]         at Child$.delayedEndpoint$Child$1(Main.scala:5)
[error]         at Child$delayedInit$body.apply(Main.scala:1)
[error]         at scala.Function0.apply$mcV$sp(Function0.scala:39)
[error]         at scala.Function0.apply$mcV$sp$(Function0.scala:39)
[error]         at scala.runtime.AbstractFunction0.apply$mcV$sp(AbstractFunction0.scala:17)
[error]         at scala.App.$anonfun$main$1(App.scala:76)
[error]         at scala.App.$anonfun$main$1$adapted(App.scala:76)
[error]         at scala.collection.IterableOnceOps.foreach(IterableOnce.scala:563)
[error]         at scala.collection.IterableOnceOps.foreach$(IterableOnce.scala:561)
[error]         at scala.collection.AbstractIterable.foreach(Iterable.scala:919)
[error]         at scala.App.main(App.scala:76)
[error]         at scala.App.main$(App.scala:74)
[error]         at Child$.main(Main.scala:1)
[error]         at Child.main(Main.scala)
[error]         at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
[error]         at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
[error]         at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
[error]         at java.lang.reflect.Method.invoke(Method.java:498)
[error] stack trace is suppressed; run last fooJVM / Compile / bgRun for the full output
[error] Nonzero exit code: 1
[error] (fooJVM / Compile / run) Nonzero exit code: 1
[error] Total time: 0 s, completed 18 mars 2021 19:08:03
SethTisue commented 3 years ago

I spent 10 or 15 minutes trying to minimize it further, but failed -- perhaps it really is minimized.

In particular, note that the problem goes away without the intermediary of the L type alias.

Combining type aliases and existentials is often There Be Dragons territory in Scala 2, but I couldn't find an obvious duplicate ticket; a couple bugs that seem perhaps related: #10363, #8635

SethTisue commented 3 years ago

under Scala 2, javap shows the emitted signatures for childMethod are:

whereas Scala 3(.0.0-RC2) emits:

Scala 3 emits a raw type, which seems surprising, and..... surely wrong?

SethTisue commented 3 years ago

I initially labeled this "fixed in Dotty" since Dotty accepts the code, but (as has emerged in conversation with Dale) and I think we should explore the possibility that both compilers are wrong to even allow this to compile.

In the Scala 3 case, the fact that it compiles because a raw type is involved (see previous comment) makes me even more suspicious about the whole thing. (But I have not gone digging in the Dotty tracker and/or codebase to see if this somehow considered normal.)

dwijnand commented 3 years ago

Here are my notes:

def childMethod: TwoTypeParam[A, B] forSome { type A; type B >: Nothing <: Any }
def childMethod: TwoTypeParam[A, B] forSome { type A; type B >: Any     <: Any }

The first is parent's definition of childMethod and the second is Child's definition. I don't think Child's definition is a covariant-returning override, but an overload instead - because the existential type B isn't equivalent. TL;DR I don't think it should compile.

smarter commented 3 years ago

Why do you expect this to not compile? OneTypeParam[_] is a subtype of TwoTypeParam[_, _], and overrides are allowed to covariantly refine the result type. Also the type you get out of javap doesn't actually matter for Scala or the JVM, it's just the generic signature that javac will use, you need to use javap -v to get the actual descriptor.

smarter commented 3 years ago

The issue here is that scala 2 is not emitting a bridge in Child for childMethod that returns TwoTypeParam, but scala 3 is.

SethTisue commented 3 years ago

Ahh, bridge methods, that didn't even occur to me.

If this working as intended in Scala 3, then I'm disinclined to dig any further on the Scala 2 side, but perhaps someone else will get interested.

dwijnand commented 3 years ago

OneTypeParam[_] is a subtype of TwoTypeParam[_, _]

If TwoTypeParam were covariant in its second type parameter I could understand that. But otherwise how is Any =:= _?

smarter commented 3 years ago

But otherwise how is Any =:= _?

That's just how wildcards work, any type which is within its bound is =:= to it, otherwise you couldn't pass an Array[Int] to a method that accepts an Array[_] for example.

dwijnand commented 3 years ago

That's just how wildcards work, any type which is within its bound is =:= to it, otherwise you couldn't pass an Array[Int] to a method that accepts an Array[_] for example.

Ah, I see. And you can pass a Show[String] as a Show[_] (contravariance).

som-snytt commented 3 years ago

This thread is missing a good bridge pun, such as "A Bridge Too Far," "Bridge on the river Kwai," etc. Oh, "Bridges of Madison County." Also just "The Bridge" and "The Bridge at Remagen." An intrepid soul might venture into the territory of "Bridget Jones's Diary."

som-snytt commented 3 years ago

Just happened to notice Be a bridge for Christ while following Mina Choi.

som-snytt commented 3 years ago

So now I look at "bridge" and it looks so weird. How do you pronounce that? What is that strange word?

Is this one of those words from Dutch which are always weird? I remember the original series "The Bridge" was something Scandinavian. To require a bridge implies that you are already isolated somehow, "out there".