milessabin / shapeless

Generic programming for Scala

Geek Repo:Geek Repo

Github PK Tool:Github PK Tool

Upcasting tagged type throws ClassCastException

kubukoz opened this issue · comments

Welcome to the Ammonite Repl 2.0.1
(Scala 2.13.1 Java 11.0.5)
If you like Ammonite, please support our development at www.patreon.com/lihaoyi
@ import $ivy.`com.chuusai::shapeless:2.3.3`
import $ivy.$

@ import shapeless.tag, tag.@@, tag.Tagged
import shapeless.tag, tag.@@, tag.Tagged

@ trait MyTag
defined trait MyTag

@ val a: Int @@ MyTag = tag[MyTag].apply(42)
a: Int @@ MyTag = 42

@ val b: tag.Tagged[MyTag] = a
java.lang.ClassCastException: class java.lang.Integer cannot be cast to class shapeless.tag$Tagged (java.lang.Integer is in module java.base of loader 'bootstrap'; shapeless.tag$Tagged is in unnamed module of loader ammonite.runtime.SpecialClassLoader @1ffaf86)
  ammonite.$sess.cmd4$.<clinit>(cmd4.sc:1)

I checked on Java 8 as well, same result.

That cannot work. The erasure of Int @@ MyTag is Int (that is intentional).
We cannot retroactively add superclasses to an existing class, let alone a primitive.
Any reason why you would want to do that?

No reason in particular, but upcasting is considered a relatively safe operation and someone might try to do that. I would certainly not expect it to throw an exception...

Ahh, right, I understand the problem now.

Notice the difference between these two casts,

scala> 23.asInstanceOf[Int with String]
res0: Int with String = 23

scala> 23.asInstanceOf[String]
java.lang.ClassCastException: java.lang.Integer cannot be cast to java.lang.String
  ... 36 elided

What's going on is that the target type of the cast is erased. In the first case the erasure of Int with String is Int, because the erasure of a refined type is just the unrefined type. In the second case there is no refinement, so the erasure is String. Unsurprisingly the first cast is just fine after erasure, whereas the second cast most definitely is not.

I agree that it's a bit surprising to see a CCE in what should be a simple widening, but it's just an artefact of this encoding of tags.