Solved: Scala 3 inline: how to retain compile-time type information of a structure?

I am trying to use an HList as implemented here. I was hoping to be able to traverse this structure using inline functions to do some simple compile time “code generation”.

I can do this this using the following code (see all of the code in scastie):

  inline def split[L <: Tup](left:L): List[String] =
    inline left match
      case _: EmpT.type => List("Empty")
      case cons: TCons[_, _] => 
        "NonEmpty" :: split(cons.tail)

However, this only works if I explicitly type the “HList”:

val m4 = split(1+:EmpT: TCons[Int,EmpT.type])
val tup5: 5 +: 6 +: EmpT.type = 5 +: 6 +: EmpT
val m5 = split(tup5)

However, this won’t work:

val tup6 = TCons(5, TCons(6,  Tup.EmpT)) 
val m6 = split(tup6)

It fails with:

cannot reduce inline match with
 scrutinee:  data.Data2.tup6 : (data.Data2.tup6 : data.Data2.Tup)
 patterns :  case _:data.Data2.Tup#EmpT.type
             case cons @ _:data.Data2.Tup.TCons[_ @  >: Nothing <: Any, _ @  >: Nothing <: Any]

So my question is, how can one retain all of the types without having to explicitly type each element of the HList?


If I use the sealed trait version instead and singletons (see below), it works:

sealed trait Tup
case object EmpT extends Tup
case class TCons[H, T <: Tup](head: H, tail: T) extends Tup

But some things don’t seem consistent, so I reported it here (solution provided for Singleton):

As per the link provided in the discussion of the issue above, this is documented:

“Generally, the type of a enum case constructor application will
be widened to the underlying enum type, unless a more specific
type is expected. This is a subtle difference with respect to
normal case classes. The classes making up the cases do exist,
and can be unveiled, either by constructing them directly with
a new, or by explicitly providing an expected type.”

1 Like