Should tagged types work for value classes?

I am trying to create a tagged type 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 but with a subtle disctinction (Support @newsubtype for value classes · Issue #73 · estatico/scala-newtype · GitHub):

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)
  }
}

There is a long standing issue in Shapeless that seems related to this: Runtime exception if the value of record field is Any or AnyRef · Issue #44 · milessabin/shapeless · GitHub

My question is whether this should be considered a Scala compiler bug/improvement or not?

The same thing happens in Scala 3 FWIW.

I ran it through javap and the structural refinement type causes it to be treated as java/lang/Object and all the checkcast ops are gone which is a great trick but feels like it ought to have better support.

I originally asked this on StackOverflow: scala - Should tagged types work for value classes? - Stack Overflow