scala / bug

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

CCE returning AnyVal from concrete method calling super that abstracts over the AnyVal type #12809

Open nafg opened 1 year ago

nafg commented 1 year ago

Reproduction steps

Scala version: Scala 2, including 2.13.11, but going way back too

Tested on JVM and JS. Confirmed it works on Scala 3 (both)

Code is at https://github.com/nafg/reproduce-anyval-cce

class Inner
class Outer(val inner: Inner) extends AnyVal
trait Abstract[F] {
  protected def helper: F
  protected def method(): F = helper
}
trait Concrete extends Abstract[Outer] {
  override protected def helper: Outer = new Outer(new Inner())
}
trait Public extends Concrete {
  def method2(): Outer = super.method()
}

object instance extends Public

instance.method2()

This is a minimized version of a real world problem. I discussed it at https://discord.com/channels/632150470000902164/635668814956068864/1119201503937302610

Translation:

Name in minimized Real symbol
Inner japgolly.scalajs.react.callback.Trampoline
Outer japgolly.scalajs.react.callback.CallbackTo
Abstract japgolly.scalajs.react.extra.BroadcasterF
Abstract#helper japgolly.scalajs.react.util.Effect.UnsafeSync#traverse_
Abstract#method japgolly.scalajs.react.extra.BroadcasterF#broadcast
Concrete japgolly.scalajs.react.extra.Broadcaster
Concrete#helper japgolly.scalajs.react.util.EffectCallback.callback#traverse_
Public io.github.nafg.scalajs.react.util.PublicBroadcaster

Problem

It crashes at runtime with Exception in thread "main" java.lang.ClassCastException: class Outer cannot be cast to class Inner

Note that removing super. in method2 fixes it. Originally I was overriding the same method just to make it public.

nafg commented 1 year ago

If there is a workaround for the case of override def method() = super.method() I'd appreciate knowing it. I'd like to be able to turn a library-protected method into a subclass-public method.

noresttherein commented 11 months ago

I don't think it should compile at all, at least it would be consistent with how it works in general. In general, you can't provide a value class as a type parameter to a super class if that type is either returned from a method (directly, not wrapped), or used as a method argument. You get the annoying error of 'bridge method generated for clashes with the method itself because they both have the same signature after erasure'. Now, I don't think this is an unsolvable issue, but last time I inquired, nobody was in a hurry to change it.

noresttherein commented 11 months ago

@nafg, if you'd like the same effect, you can go with 'poor man's opaque types` for Scala 2:

class Inner
type Outer //left abstract

def Outer(inner :Inner) :Outer = inner.asInstanceOf[Outer]

implicit class OuterExtension(val self :Outer) extends AnyVal {
   def inner :Inner = self.asInstanceOf[Inner]
    //any methods which would go in a value class Outer go here.
}

This trick is very useful exactly because it avoids both your problem and the problem of bridge method clashing with specialized override in a subclass.

nafg commented 11 months ago

Are you suggesting to rewrite scalajs-react that way?