I am trying to create a tagged type (in Scala 2.x) in the same way that Shapeless (2.x) does but it fails with a ClassCastException
:
object Main {
sealed trait TaggedProps[Props] extends Any
final class KeyAddingStage(private val args: Array[Any]) extends AnyVal
type RenderedProps[Props] = KeyAddingStage with TaggedProps[Props]
object Name {
case class Props(name: String)
def component(props: Props): KeyAddingStage = new KeyAddingStage(
Array(
props
)
)
def apply(name: String): RenderedProps[Props] = component(Props(name)).asInstanceOf[RenderedProps[Props]]
}
def main(args: Array[String]): Unit = {
val first: RenderedProps[Name.Props] = Name("Jason")
val firstStage: KeyAddingStage = first
println(first == firstStage)
val props: Seq[RenderedProps[Name.Props]] = Seq(first)
val propsHead: RenderedProps[Name.Props] = props.head
println(firstStage == propsHead)
}
}
Fails with:
[error] java.lang.ClassCastException: [Ljava.lang.Object; cannot be cast to Main$KeyAddingStage
[error] at Main$.main(test.scala:26)
[error] at Main.main(test.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)
I initially found this in a Scala.js project and I thought that it was a Scala.js bug but it does in fact fail on the JVM too.
I found a workaround which is to do it in a similar way to scala-newtype:
import scala.language.implicitConversions
object Main {
final class KeyAddingStage(private val args: Array[Any]) extends AnyVal
object KeyAddingStage {
implicit def build(stage: KeyAddingStage): String = stage.toString
}
type RenderedProps[Props] = RenderedProps.Type[Props]
object RenderedProps {
// This must be a structural type like it is in @newtype
type Base = {
type __RenderedProps__newtype
}
trait Tag[Props] extends Any
// But we can put the type we are extending here after Base so that it works in the same way as @newsubtype
type Type[Props] <: Base with KeyAddingStage with Tag[Props]
def apply[Props](stage: KeyAddingStage): RenderedProps[Props] = stage.asInstanceOf[RenderedProps[Props]]
}
object Name {
case class Props(name: String)
def component(props: Props): KeyAddingStage = new KeyAddingStage(
Array(
props.asInstanceOf[Any]
)
)
def apply(name: String): RenderedProps[Props] = RenderedProps(component(Props(name)))
}
def main(args: Array[String]): Unit = {
val first: RenderedProps[Name.Props] = Name("Jason")
val firstStage: KeyAddingStage = first
val str: String = first
println(first == firstStage)
val props: Seq[RenderedProps[Name.Props]] = Seq(first)
val propsHead: RenderedProps[Name.Props] = props.head
println(firstStage == propsHead)
}
}
My question is whether this should be considered a Scala compiler bug/improvement or not?
I haven't looked at the Java bytecode but from the JS side it seems relatively easy to solve.