1
votes

This is a follow-up question of:

How to get the name of a case class field as a string/symbol at compile time using shapeless?

Assuming that I want to write a recursive converter that can convert a product type:

case class Prod (
  a: Int,
  b: String
)

into a Record, but unlike the above question which uses each case class fields (a, b) as keys, I want to use each class name or type/type-constructor name directly. So this product type becomes a Record at compile time:

"Int" ->> Int
"String" ->> String

(probably not a good enough use case, but you got the idea)

One key step of this is to use reflection to get the name of each class at compile time, and convert them into Singleton types or shapeless witness. I wonder if this capability is already provided somewhere? Or do I absolutely need a whitebox macro to make it happen?

1

1 Answers

2
votes

You'll have to write a macro

import shapeless.ops.hlist.Mapper
import shapeless.{Generic, HList, Poly1, Typeable}
import scala.language.experimental.macros
import scala.reflect.macros.whitebox

trait FieldTypes[A <: Product] {
  type Out <: HList
}
object FieldTypes {
  type Aux[A <: Product, Out0 <: HList] = FieldTypes[A] { type Out = Out0 }

  implicit def mkFieldTypes[A <: Product, L <: HList](implicit
    generic: Generic.Aux[A, L],
    mapper: Mapper[typeablePoly.type, L]
  ): Aux[A, mapper.Out] = null

  object typeablePoly extends Poly1 {
    implicit def cse[A](implicit typeable: Typeable[A]): Case[A] = macro cseImpl[A]
    def cseImpl[A: c.WeakTypeTag](c: whitebox.Context)(typeable: c.Tree): c.Tree = {
      import c.universe._
      val str = c.eval(c.Expr[String](c.untypecheck(q"$typeable.describe")))
      val tpA = weakTypeOf[A]
      q"null.asInstanceOf[FieldTypes.typeablePoly.Case.Aux[$tpA, _root_.shapeless.labelled.FieldType[$str, $tpA]]]"
    }
  }
}

Testing:

import shapeless.{HNil, ::}
import shapeless.labelled.FieldType

implicitly[FieldTypes.Aux[Prod, FieldType["Int", Int] :: FieldType["String", String] :: HNil]]