Search code examples
scalascala-macrosscala-3

Scala 3 Manifest replacement


My task is to print out type information in Java-like notation (using <, > for type arguments notation). In scala 2 I have this small method using scala.reflect.Manifest as a source for type symbol and it's parameters:

def typeOf[T](implicit manifest: Manifest[T]): String = {
  def loop[T0](m: Manifest[T0]): String =
    if (m.typeArguments.isEmpty) m.runtimeClass.getSimpleName
    else {
      val typeArguments = m.typeArguments.map(loop(_)).mkString(",")
      raw"""${m.runtimeClass.getSimpleName}<$typeArguments>"""
    }
  loop(manifest)
}

Unfortunately in Scala 3 Manifests are not available. Is there a Scala 3 native way to rewrite this? I'm open to some inline macro stuff. What I have tried so far is

inline def typeOf[T]: String = ${typeOfImpl}

private def typeOfImpl[T: Type](using Quotes): Expr[String] =
  import quotes.reflect.*

  val tree = TypeTree.of[T]
  tree.show 
  //    ^^ call is parameterized with Printer but AFAIK there's no way
  //       to provide your own implementation for it. You can to chose
  //       from predefined ones. So how do I proceed from here?

I know that Scala types can't be all represented as Java types. I aim to cover only simple ones that the original method was able to cover. No wildcards or existentials, only fully resolved types like:

  • List[String] res: List<String>
  • List[Option[String]] res: List<Option<String>>
  • Map[String,Option[Int]] res: Map<String,Option<Int>>

Solution

  • The final working solution I came up with was:

    def typeOfImpl[T: Type](using Quotes): Expr[String] = {
      import quotes.reflect.*
    
      TypeRepr.of[T] match {
        case AppliedType(tpr, args) =>
          val typeName   = Expr(tpr.show)
          val typeArguments = Expr.ofList(args.map {
            _.asType match {
              case '[t] => typeOfImpl[t]
            }
          })
          '{
            val tpeName = ${ typeName }
            val typeArgs  = ${ typeArguments }
            typeArgs.mkString(tpeName + "<", ", ", ">")
          }
        case tpr: TypeRef => Expr(tpr.show)
        case other =>
            report.errorAndAbort(s"unsupported type: ${other.show}", Position.ofMacroExpansion)
      }
    }