Scala compiler issue or JVM boxing subtlty?

80 Views Asked by At

The following function is part of some code I have to interface with a particularly nasty (as in untyped) Java API. It is intended to safely perform casts where the context 'knows' what the type should be, but I want to make sure I won't have bad failure modes in some unknown edge case at runtime:

def safeCast[A](o: Any): Option[A] =
  Try(o.asInstanceOf[A]).toOption

Now look what happens when it's used in a REPL session:

scala> val testDouble: Double = 1.0

testDouble: Double = 1.0

scala> safeCast[Int](testDouble)

res0: Option[Int] = Some(1.0)

res0 claims to have type Option[Int] but value Some(1.0) (i.e. - Some[Double]). A class cast exception follows if we try to map over this Option.

This behavior only happens with a polymorphic safeCast. If we tighten to a specific type:

def safeIntCast(o: Any): Option[Int] = Try(o.asInstanceOf[Int]).toOption

then we get:

scala> safeIntCast(testDouble)

res1: Option[Int] = None

Hence the polymorphism is somehow interacting with the boxing (I suspect??) or with a compiler issue (bug??). The compiler version used was 2.12.2

Can anyone provide an explanation for this?

3

There are 3 best solutions below

0
On

Please, see here and here for further reference. It explains a lot about Scala asInstanceOf.

Hope it helps!

0
On

This is because of type erasure, the type T is unknown at runtime. When you map the type is known (because it was evaluated) and you are getting ClassCastException.

But you can use class tag to get type in runtime:

import scala.reflect.ClassTag
import scala.util.Try

object Application extends App {

  def safeCast[A](o: Any)(implicit ct: ClassTag[A]): Option[A] =
    Try(ct.runtimeClass.cast(o).asInstanceOf[A]).toOption

}
0
On

There is no such thing as .asInstanceOf[A] for a generic type A, it simply disappears at runtime:

$ echo 'class Demo { def safeCast[A](o: Any) = o.asInstanceOf[A] }' > test.scala
$ scalac test.scala
$ cfr test.scala
$ javap -c Demo
Compiled from "test.scala"
public class Demo {
  public <A> A safeCast(java.lang.Object);
    Code:
       0: aload_1
       1: areturn

  public Demo();
    Code:
       0: aload_0
       1: invokespecial #18                 // Method java/lang/Object."<init>":()V
       4: return
}