1

I faced a problem. I'd like to make a printer for any type in scala. for example i have a case class

  class AAA(i: Int, s: String, o: Option[Int], bbb: BBB)
  class BBB(l: List[Int])
def explainType[T]: String

and i need a function which can take a type of this class and return a string like

AAA(i: Int, s: String, o: Option(if possible with inner type), bbb:(l: List(if possible with inner type))

I don't really care about format, it needs to be self-explanatory. If it's possible to print it as json, i'd like that. If you know any existing library for that kind of thing, please let me know.

Thanks in advance.

AminMal
  • 3,070
  • 2
  • 6
  • 15
  • 2
    Well the first question would be, why you even need this? Second, yeah **Shapeless** and a custom typeclass is what you want, third I would guess things like **Chimey** or a json schema may already do this – Luis Miguel Mejía Suárez Mar 25 '23 at 00:04

2 Answers2

3

If your classes are case classes you can implement the type class Explain etc. with Shapeless and Circe

// I'm using Shapeless but still need this macro :)

import scala.language.experimental.macros
import scala.reflect.macros.whitebox // libraryDependencies += scalaOrganization.value % "scala-reflect" % scalaVersion.value

trait ToName[A] {
  type Out <: String with Singleton
}
object ToName {
  type Aux[A, Out0 <: String with Singleton] = ToName[A] { type Out = Out0 }

  implicit def mkToName[A, Out <: String with Singleton]: Aux[A, Out] = macro mkToNameImpl[A]

  def mkToNameImpl[A: c.WeakTypeTag](c: whitebox.Context): c.Tree = {
    import c.universe._
    val A = weakTypeOf[A]
    val toNameTpe = weakTypeOf[ToName[A]]
    q"""
      new $toNameTpe {
        type Out = ${A.typeSymbol.name.toString}
      }
    """
  }
}
// in a different subproject

import io.circe.{Json, JsonObject} // libraryDependencies += "io.circe" %% "circe-core" % "0.14.5"
import shapeless.{::, HList, HNil, LabelledGeneric, Typeable, Witness} // libraryDependencies += "com.chuusai" %% "shapeless" % "2.3.10"
import shapeless.labelled.{FieldType, field}
import shapeless.tag.@@

trait ToNameSymbol[A] {
  type Out <: Symbol
}
object ToNameSymbol {
  type Aux[A, Out0 <: Symbol] = ToNameSymbol[A] {type Out = Out0}

  implicit def mkToNameSymbol[A](implicit toName: ToName[A]): Aux[A, Symbol @@ toName.Out] = null
}

trait LabelledGenericWithName[T <: Product] {
  type Repr <: FieldType[_, _ <: HList]
  def to(t: T): Repr
  def from(r: Repr): T
}
object LabelledGenericWithName {
  type Aux[T <: Product, Repr0 <: FieldType[_, _ <: HList]] = LabelledGenericWithName[T] {type Repr = Repr0}
  def instance[T <: Product, Repr0 <: FieldType[_, _ <: HList]](f: T => Repr0, g: Repr0 => T): Aux[T, Repr0] = new LabelledGenericWithName[T] {
    override type Repr = Repr0
    override def to(t: T): Repr = f(t)
    override def from(r: Repr): T = g(r)
  }

  implicit def mkLabelledGenericWithName[T <: Product, L <: HList](implicit
    labelledGeneric: LabelledGeneric.Aux[T, L],
    toNameSymbol: ToNameSymbol[T]
  ): Aux[T, FieldType[toNameSymbol.Out, L]] = instance(
    t => field[toNameSymbol.Out](labelledGeneric.to(t)),
    t => labelledGeneric.from(t)
  )
}

trait DeepLabelledGeneric[T <: Product] {
  type Repr <: FieldType[_, _ <: HList]
  def to(t: T): Repr
  def from(r: Repr): T
}
object DeepLabelledGeneric {
  type Aux[T <: Product, Repr0 <: FieldType[_, _ <: HList]] = DeepLabelledGeneric[T] {type Repr = Repr0}
  def instance[T <: Product, Repr0 <: FieldType[_, _ <: HList]](f: T => Repr0, g: Repr0 => T): Aux[T, Repr0] = new DeepLabelledGeneric[T] {
    override type Repr = Repr0
    override def to(t: T): Repr = f(t)
    override def from(r: Repr): T = g(r)
  }

  implicit def mkDeepLabelledGeneric[A <: Product, K <: Symbol, L <: HList, L1 <: HList](implicit
    labelledGenericWithName: LabelledGenericWithName.Aux[A, FieldType[K, L]],
    hListDeepLabelledGeneric: HListDeepLabelledGeneric.Aux[L, L1]
  ): Aux[A, FieldType[K, L1]] =
    instance(
      a => field[K](hListDeepLabelledGeneric.to(labelledGenericWithName.to(a))),
      l1 => labelledGenericWithName.from(field[K](hListDeepLabelledGeneric.from(l1)) : FieldType[K, L])
    )
}

trait HListDeepLabelledGeneric[T <: HList] {
  type Repr <: HList
  def to(t: T): Repr
  def from(r: Repr): T
}
trait LowPriorityHListDeepLabelledGeneric {
  type Aux[T <: HList, Repr0 <: HList] = HListDeepLabelledGeneric[T] {type Repr = Repr0}
  def instance[T <: HList, Repr0 <: HList](f: T => Repr0, g: Repr0 => T): Aux[T, Repr0] = new HListDeepLabelledGeneric[T] {
    override type Repr = Repr0
    override def to(t: T): Repr = f(t)
    override def from(r: Repr): T = g(r)
  }

  implicit def headNotCaseClass[H, T <: HList, T_hListDeepLGen <: HList](implicit
    tailHListDeepLabelledGeneric: HListDeepLabelledGeneric.Aux[T, T_hListDeepLGen]
  ): Aux[H :: T, H :: T_hListDeepLGen] = instance({
    case h :: t => h :: tailHListDeepLabelledGeneric.to(t)
  }, {
    case h :: t => h :: tailHListDeepLabelledGeneric.from(t)
  })
}
object HListDeepLabelledGeneric extends LowPriorityHListDeepLabelledGeneric {
  implicit val hNil: Aux[HNil, HNil] = instance(identity, identity)

  implicit def headCaseClass[K <: Symbol, H <: Product, T <: HList, H_deepLGen <: FieldType[_, _ <: HList], T_hListDeepLGen <: HList](implicit
    headDeepLabelledGeneric: DeepLabelledGeneric.Aux[H, H_deepLGen],
    tailHListDeepLabelledGeneric: HListDeepLabelledGeneric.Aux[T, T_hListDeepLGen]
  ): Aux[FieldType[K, H] :: T, FieldType[K, H_deepLGen] :: T_hListDeepLGen] = instance({
    case h :: t => field[K](headDeepLabelledGeneric.to(h)) :: tailHListDeepLabelledGeneric.to(t)
  }, {
    case h :: t => field[K](headDeepLabelledGeneric.from(h)) :: tailHListDeepLabelledGeneric.from(t)
  })
}

trait Explain[T <: Product] {
  def apply(): JsonObject
}
object Explain {
  implicit def mkExplain[T <: Product, K <: Symbol, L <: HList](implicit
    deepLabelledGeneric: DeepLabelledGeneric.Aux[T, FieldType[K, L]],
    hListExplain: HListExplain[L],
    witness: Witness.Aux[K]
  ): Explain[T] = () => JsonObject(witness.value.name -> Json.fromJsonObject(hListExplain()))
}

trait HListExplain[L <: HList] {
  def apply(): JsonObject
}
object HListExplain {
  implicit val hNil: HListExplain[HNil] = () => JsonObject()

  implicit def headNotHList[K <: Symbol, H, T <: HList](implicit
    tailHListExplain: HListExplain[T],
    witness: Witness.Aux[K],
    typeable: Typeable[H]
  ): HListExplain[FieldType[K, H] :: T] =
    () => (witness.value.name -> Json.fromString(typeable.describe)) +: tailHListExplain()

  implicit def headHList[K <: Symbol, K1 <: Symbol, H <: HList, T <: HList](implicit
    headHListExplain: HListExplain[H],
    tailHListExplain: HListExplain[T],
    witness: Witness.Aux[K],
    witness1: Witness.Aux[K1],
  ): HListExplain[FieldType[K, FieldType[K1, H]] :: T] =
    () => (witness.value.name -> Json.obj(witness1.value.name -> Json.fromJsonObject(headHListExplain()))) +: tailHListExplain()
}

def explainType[T <: Product](implicit explain: Explain[T]): Json = Json.fromJsonObject(explain())

case class AAA(i: Int, s: String, o: Option[Int], bbb: BBB)
case class BBB(l: List[Int])

explainType[AAA]
//{
//  "AAA" : {
//    "i" : "Int",
//    "s" : "String",
//    "o" : "Option[Int]",
//    "bbb" : {
//      "BBB" : {
//        "l" : "List[Int]"
//      }
//    }
//  }
//}

Shapeless - How to derive LabelledGeneric for Coproduct (ToName)

Deriving nested shapeless lenses using only a type (DeepGeneric)

Getting Case Class definition which points to another Case Class (DeepLabelledGeneric)

Scala case classes and recursive reflection (DeepGeneric)

Get case class field's name and type with shapeless

How to get field names and field types from a Generic Type in Scala?

How to get the name of a class as a string literal at compile time using shapeless?


If the classes are not necessarily case classes you can implement the type class Explain etc. with macros. Namely, you can replace standard shapeless.Generic, LabelledGeneric, DefaultSymbolicLabelling with the implementations below working not only with case classes. Also, comparing with the above implementation, I'm replacing the upper bound <: Product with a type class (context bound) IsCaseClassLike. Otherwise, if I just remove <: Product and do not add any constraint then the compiler starts to look for generic representation of standard classes like Int, String etc.

import shapeless.{Annotation, DepFn0, HList, Refute}
import scala.annotation.StaticAnnotation
import scala.language.experimental.macros
import scala.reflect.macros.whitebox

trait ToName[A] {
  type Out <: String with Singleton
}
object ToName {
  type Aux[A, Out0 <: String with Singleton] = ToName[A] { type Out = Out0 }

  implicit def mkToName[A, Out <: String with Singleton]: Aux[A, Out] = macro mkToNameImpl[A]

  def mkToNameImpl[A: c.WeakTypeTag](c: whitebox.Context): c.Tree = {
    import c.universe._
    val A = weakTypeOf[A]
    val toNameTpe = weakTypeOf[ToName[A]]
    q"""
      new $toNameTpe {
        type Out = ${A.typeSymbol.name.toString}
      }
    """
  }
}

// class data extends StaticAnnotation

trait IsCaseClassLike[T]
object IsCaseClassLike {
   // nothing should be converted to HList except classes annotated with @data
// implicit def mkIsCaseClassLike[T](implicit ev: Annotation[data, T]): IsCaseClassLike[T] = null

  // everything should be converted to HList except standard classes
  implicit def mkIsCaseClassLike[T](implicit ev: Refute[IsStandard[T]]): IsCaseClassLike[T] = null
}

trait IsStandard[T]
object IsStandard {
  implicit def mkIsStandard[T]: IsStandard[T] = macro mkIsStandardImpl[T]

  def mkIsStandardImpl[T: c.WeakTypeTag](c: whitebox.Context): c.Tree = {
    import c.universe._
    val symbol = symbolOf[T]
    if (Seq("scala.", "java.", "shapeless.").exists(symbol.fullName.startsWith)) q"null"
    else c.abort(c.enclosingPosition, s"$symbol is not standard class")
  }
//    implicit val int: IsStandard[Int] = null
//    implicit val str: IsStandard[String] = null
//    implicit def opt[A]: IsStandard[Option[A]] = null
//    implicit def list[A]: IsStandard[List[A]] = null
//    implicit def hlist[L <: HList]: IsStandard[L] = null
}

trait Generic[T] {
  type Repr <: HList
  def to(t: T): Repr
  def from(r: Repr): T
}
object Generic {
  type Aux[T, Repr0 <: HList] = Generic[T] {type Repr = Repr0}
  def instance[T, R <: HList](f: T => R, g: R => T): Aux[T, R] = new Generic[T] {
    override type Repr = R
    override def to(t: T): Repr = f(t)
    override def from(r: Repr): T = g(r)
  }

  implicit def mkGeneric[T, R <: HList](implicit
    ev: IsCaseClassLike[T]
  ): Aux[T, R] = macro mkGenericImpl[T]

  def mkGenericImpl[T : c.WeakTypeTag](c: whitebox.Context)(ev: c.Tree): c.Tree = {
    import c.universe._

    val T = weakTypeOf[T]
    val genTpe = weakTypeOf[Generic[T]]
    val fieldTypes = T.decls.filter(s => s.isTerm && !s.isMethod).map(_.typeSignature)
    val range = 0 until fieldTypes.size
    val names = range.map(i => q"${TermName(s"arg$i")}")
    val namePatterns = range.map(i => pq"${TermName(s"arg$i")}")
    val sh = q"_root_.shapeless"
    val (reprTpe, repr) =
      fieldTypes.zip(names)
        .foldRight[(Tree, Tree)]((tq"$sh.HNil", q"$sh.HNil")) { case ((tpe, name), (accTpe, acc)) =>
          val accTpe1 = tq"$sh.::[$tpe, $accTpe]"
          (accTpe1, q"new $accTpe1($name, $acc)")
        }
    val reprPattern = namePatterns.foldRight[Tree](pq"$sh.HNil")((name, acc) => pq"$sh.::($name, $acc)")
    val reprCase = cq"$reprPattern => new $T(..$names)"

    val to = 
      if (T.companion != NoType && T.companion.decl(TermName("unapply")) != NoSymbol) {
        val classCase = cq"${T.typeSymbol.companion}(..$namePatterns) => $repr"
        q"t match { case $classCase }"
      } else q"_root_.scala.Predef.???"

    val from = q"r match { case $reprCase }"

    q"""
      new $genTpe {
        override type Repr = $reprTpe
        override def to(t: $T): Repr   = $to
        override def from(r: Repr): $T = $from
      }
    """
  }
}

trait DefaultSymbolicLabelling[T] extends DepFn0 {
  type Out <: HList
}
object DefaultSymbolicLabelling {
  type Aux[T, Out0 <: HList] = DefaultSymbolicLabelling[T] {type Out = Out0}

  implicit def mkDefaultSymbolicLabelling[T, Out <: HList](implicit
    ev: IsCaseClassLike[T]
  ): Aux[T, Out] = macro mkDefaultSymbolicLabellingImpl[T]

  def mkDefaultSymbolicLabellingImpl[T: c.WeakTypeTag](c: whitebox.Context)(ev: c.Tree): c.Tree = {
    import c.universe._
    val T = weakTypeOf[T]
    val dslTpe = weakTypeOf[DefaultSymbolicLabelling[T]]
    val fieldNames = T.decls.filter(s => s.isTerm && !s.isMethod).map(_.name.toString.stripSuffix(" "))
    val sh = q"_root_.shapeless"
    val Sym = q"_root_.scala.Symbol"
    val SymT = tq"_root_.scala.Symbol"
    val (outTpe, out) =
      fieldNames.foldRight[(Tree, Tree)]((tq"$sh.HNil", q"$sh.HNil")) { case (name, (accTpe, acc)) =>
        val accTpe1 = tq"$sh.::[$sh.tag.@@[$SymT, $name], $accTpe]"

        (
          accTpe1,
          q"""
            new $accTpe1(
              $sh.tag.apply[$name].apply[$SymT]($Sym.apply($name)),
              $acc
            )
          """
        )
      }

    q"""
      new $dslTpe {
        override type Out = $outTpe
        override def apply(): Out = $out
      }
    """
  }
}
import io.circe.{Json, JsonObject}
import shapeless.{::, HList, HNil, Typeable, Witness, Unpack2}
import shapeless.labelled.{FieldType, KeyTag, field}
import shapeless.ops.hlist.ZipWithKeys
import shapeless.tag.@@

trait LabelledGeneric[T] {
  type Repr <: HList
  def to(t: T): Repr
  def from(r: Repr): T
}
object LabelledGeneric {
  type Aux[T, Repr0 <: HList] = LabelledGeneric[T] {type Repr = Repr0}
  def instance[T, Repr0 <: HList](f: T => Repr0, g: Repr0 => T): Aux[T, Repr0] = new LabelledGeneric[T] {
    override type Repr = Repr0
    override def to(t: T): Repr = f(t)
    override def from(r: Repr): T = g(r)
  }

  implicit def mkLabelledGeneric[T, K <: HList, V <: HList, R <: HList](implicit
    ev0: IsCaseClassLike[T],
    lab: DefaultSymbolicLabelling.Aux[T, K],
    gen: Generic.Aux[T, V],
    zip: ZipWithKeys.Aux[K, V, R],
    ev: R <:< V
  ): Aux[T, R] = instance(t => zip(gen.to(t)), gen.from(_))
}

trait ToNameSymbol[A] {
  type Out <: Symbol
}
object ToNameSymbol {
  type Aux[A, Out0 <: Symbol] = ToNameSymbol[A] {type Out = Out0}

  implicit def mkToNameSymbol[A](implicit
    toName: ToName[A]
  ): Aux[A, Symbol @@ toName.Out] = null
}

trait LabelledGenericWithName[T] {
  type Repr <: FieldType[_, _ <: HList]
  def to(t: T): Repr
  def from(r: Repr): T
}
object LabelledGenericWithName {
  type Aux[T, Repr0 <: FieldType[_, _ <: HList]] = LabelledGenericWithName[T] {type Repr = Repr0}
  def instance[T, Repr0 <: FieldType[_, _ <: HList]](f: T => Repr0, g: Repr0 => T): Aux[T, Repr0] = new LabelledGenericWithName[T] {
    override type Repr = Repr0
    override def to(t: T): Repr = f(t)
    override def from(r: Repr): T = g(r)
  }

  implicit def mkLabelledGenericWithName[T, L <: HList](implicit
    ev: IsCaseClassLike[T],
    labelledGeneric: LabelledGeneric.Aux[T, L],
    toNameSymbol: ToNameSymbol[T]
  ): Aux[T, FieldType[toNameSymbol.Out, L]] =
    instance(
      t => field[toNameSymbol.Out](labelledGeneric.to(t)),
      t => labelledGeneric.from(t)
    )
}

trait DeepLabelledGeneric[T] {
  type Repr <: FieldType[_, _ <: HList]
  def to(t: T): Repr
  def from(r: Repr): T
}
object DeepLabelledGeneric {
  type Aux[T, Repr0 <: FieldType[_, _ <: HList]] = DeepLabelledGeneric[T] {type Repr = Repr0}
  def instance[T, Repr0 <: FieldType[_, _ <: HList]](f: T => Repr0, g: Repr0 => T): Aux[T, Repr0] = new DeepLabelledGeneric[T] {
    override type Repr = Repr0
    override def to(t: T): Repr = f(t)
    override def from(r: Repr): T = g(r)
  }

  implicit def mkDeepLabelledGeneric[A, K <: Symbol, L <: HList, L1 <: HList](implicit
    ev: IsCaseClassLike[A],
    labelledGenericWithName: LabelledGenericWithName.Aux[A, FieldType[K, L]],
    hListDeepLabelledGeneric: HListDeepLabelledGeneric.Aux[L, L1]
  ): Aux[A, FieldType[K, L1]] =
    instance(
      a => field[K](hListDeepLabelledGeneric.to(labelledGenericWithName.to(a))),
      l1 => labelledGenericWithName.from(field[K](hListDeepLabelledGeneric.from(l1)) : FieldType[K, L])
    )
}

trait HListDeepLabelledGeneric[T <: HList] {
  type Repr <: HList
  def to(t: T): Repr
  def from(r: Repr): T
}
trait LowPriorityHListDeepLabelledGeneric {
  type Aux[T <: HList, Repr0 <: HList] = HListDeepLabelledGeneric[T] {type Repr = Repr0}
  def instance[T <: HList, Repr0 <: HList](f: T => Repr0, g: Repr0 => T): Aux[T, Repr0] = new HListDeepLabelledGeneric[T] {
    override type Repr = Repr0
    override def to(t: T): Repr = f(t)
    override def from(r: Repr): T = g(r)
  }

  implicit def headNotCaseClass[H, T <: HList, T_hListDeepLGen <: HList](implicit
    tailHListDeepLabelledGeneric: HListDeepLabelledGeneric.Aux[T, T_hListDeepLGen]
  ): Aux[H :: T, H :: T_hListDeepLGen] = instance({
    case h :: t => h :: tailHListDeepLabelledGeneric.to(t)
  }, {
    case h :: t => h :: tailHListDeepLabelledGeneric.from(t)
  })
}
object HListDeepLabelledGeneric extends LowPriorityHListDeepLabelledGeneric {
  implicit val hNil: Aux[HNil, HNil] = instance(identity, identity)

  implicit def headCaseClass[K <: Symbol, H, T <: HList, H_deepLGen <: FieldType[_, _ <: HList], T_hListDeepLGen <: HList](implicit
    ev: IsCaseClassLike[H],
    headDeepLabelledGeneric: DeepLabelledGeneric.Aux[H, H_deepLGen],
    tailHListDeepLabelledGeneric: HListDeepLabelledGeneric.Aux[T, T_hListDeepLGen]
  ): Aux[FieldType[K, H] :: T, FieldType[K, H_deepLGen] :: T_hListDeepLGen] = instance({
    case h :: t => field[K](headDeepLabelledGeneric.to(h)) :: tailHListDeepLabelledGeneric.to(t)
  }, {
    case h :: t => field[K](headDeepLabelledGeneric.from(h)) :: tailHListDeepLabelledGeneric.from(t)
  })
}

trait Explain[T] {
  def apply(): JsonObject
}
object Explain {
  implicit def mkExplain[T, /*FT <: FieldType[_, _ <: HList],*/ K <: Symbol, L <: HList](implicit
    ev: IsCaseClassLike[T],
      // I was afraid that .Aux[T, FieldType[K, L]] will be over-constrained implicit, see (*)
    deepLabelledGeneric: DeepLabelledGeneric.Aux[T, FieldType[K, L]], //DeepLabelledGeneric.Aux[T, FT],
    // ev1: Unpack2[FT, FieldType, K, L], // FT <:< FieldType[K, L], // FT =:= FieldType[K, L], // FT <:< KeyTag[K, L],
    hListExplain: HListExplain[L],
    witness: Witness.Aux[K]
  ): Explain[T] = () => JsonObject(witness.value.name -> Json.fromJsonObject(hListExplain()))
}

trait HListExplain[L <: HList] {
  def apply(): JsonObject
}
object HListExplain {
  implicit val hNil: HListExplain[HNil] = () => JsonObject()

  implicit def headNotHList[K <: Symbol, H, T <: HList](implicit
    tailHListExplain: HListExplain[T],
    witness: Witness.Aux[K],
    typeable: Typeable[H]
  ): HListExplain[FieldType[K, H] :: T] =
    () => (witness.value.name -> Json.fromString(typeable.describe)) +: tailHListExplain()

  implicit def headHList[K <: Symbol, K1 <: Symbol, H <: HList, T <: HList](implicit
    headHListExplain: HListExplain[H],
    tailHListExplain: HListExplain[T],
    witness: Witness.Aux[K],
    witness1: Witness.Aux[K1],
  ): HListExplain[FieldType[K, FieldType[K1, H]] :: T] =
    () => (witness.value.name -> Json.obj(witness1.value.name -> Json.fromJsonObject(headHListExplain()))) +: tailHListExplain()
}

def explainType[T](implicit explain: Explain[T]): Json = Json.fromJsonObject(explain())

/*@data*/
/*case*/ class AAA(val i: Int, val s: String, o: Option[Int], bbb: BBB)

/*@data*/
/*case*/ class BBB(l: List[Int])

explainType[AAA]
//{
//  "AAA" : {
//    "i" : "Int",
//    "s" : "String",
//    "o" : "Option[Int]",
//    "bbb" : {
//      "BBB" : {
//        "l" : "List[Int]"
//      }
//    }
//  }
//}

(*) Why is this implicit resolution failing? , HList foldLeft with tuple as zero

Dmytro Mitin
  • 48,194
  • 3
  • 28
  • 66
1

If you are using Scala 3(aka dotty), here is a much more simple solution without any third library dependent.

The use cases :

λ scalac Macros.scala

λ scala -cp .
Welcome to Scala 3.2.0-RC2.
Type in expressions for evaluation. Or try :help.

scala> import Macros.explainType

scala> explainType[(Int, Double, String)]
val res0: String = (Int, Double, String)

scala> explainType[List[Option[Int]]]
val res1: String = List[Option[Int]]

scala> case class BBB(l: List[Int])
// defined case class BBB

scala> case class AAA(i: Int, s: String, o: Option[Int], bbb: BBB)
// defined case class AAA

scala> explainType[BBB]
val res2: String = BBB(l :List[Int])

scala> explainType[AAA]
val res3: String = AAA(i :Int, s :String, o :Option[Int], bbb :BBB(l :List[Int]))

Here is the implementation:

// Save it as Macros.scala
object Macros:

    import scala.compiletime.*
    import scala.deriving.Mirror
    import scala.quoted.*

    def simpleNameImp[A](using Type[A], Quotes): Expr[String] =
        Expr(Type.show[A].replaceAll("""(scala|java)\.([$\w]+\.)*""", ""))

    inline def simpleName[A]: String = ${ simpleNameImp[A] }

    def isInnerTypeImp[A](using Type[A], Quotes): Expr[Boolean] =
        Expr(Type.show[A].startsWith("scala.") || Type.show[A].startsWith("java."))

    inline def isInnerType[A]: Boolean = ${ isInnerTypeImp[A] }

    inline def tupleName[T]: List[String] = inline erasedValue[T] match
        case _ : EmptyTuple => Nil
        case _ : (h *: t)   => explainType[h] :: tupleName[t]

    inline def explainType[A]: String = inline erasedValue[A] match
        case _: Tuple            => tupleName[A].mkString("(", ", ", ")")
        case _ if isInnerType[A] => simpleName[A]
        case _                   => summonFrom {
            case m: Mirror.Of[A] =>
                val name   = constValue[m.MirroredLabel]
                val labels = constValueTuple[m.MirroredElemLabels]
                val types  = tupleName[m.MirroredElemTypes]
                name + (labels.toArray zip types).map { (lab, typ) => s"$lab :$typ" }.mkString("(", ", ", ")")
            case _               => simpleName[A]
        }
Eastsun
  • 18,526
  • 6
  • 57
  • 81