Search code examples
scalametaprogrammingscala-macrosscala-3

Providing the equivalent of a type parameter [T] from inside a Scala 3 macro


I'm, um, a very naive Scala 3 metaprogrammer. Apologies in advance.

I'm trying to canonicalize type names. Calling _.dealias.simplified.show on a TypeRepr does the job just fine on the base type, but it doesn't touch the type parameters. So, I'd like to iterate through the type params and call my canonicalizer on them recursively. After some trial and error and reading great intros by Adam Warsky and Eugene Yokota I've managed to iterate through the type params, but I can't figure out how to make the recursive call.

object Playpen:
  import scala.quoted.*

  inline def recursiveCanonicalName[T]: String = ${Playpen.recursiveCanonicalNameImpl[T]}

  def recursiveCanonicalNameImpl[T](using q : Quotes)( using tt : Type[T]) : Expr[String] =
    import quotes.reflect.*
    val repr = TypeRepr.of[T]
    repr.widenTermRefByName.dealias match
      case AppliedType(name, args) =>
        Expr(name.dealias.simplified.show + "[" + args.map(a => a.dealias.simplified.show /*(recursiveCanonicalNameImpl(q)(a.asType)*/).mkString(",") + "]")
      case _ =>
        Expr(repr.dealias.simplified.show)

The current version "works" to canonicalize one level of type params, but without recursion can't go deeper.

@ macroplay.Playpen.recursiveCanonicalName[Map[String,String]] 
res1: String = "scala.collection.immutable.Map[java.lang.String,java.lang.String]"


@ macroplay.Playpen.recursiveCanonicalName[Map[Seq[String],Seq[String]]] 
res3: String = "scala.collection.immutable.Map[scala.collection.immutable.Seq[scala.Predef.String],scala.collection.immutable.Seq[scala.Predef.String]]"

Any help (and your patience, scalameta makes me feel dumb) is greatly appreciated!


Solution

  • Try pattern matching by type quotation

    args.map(a =>
      a.asType match {
        case '[a] => recursiveCanonicalNameImpl[a].show.stripPrefix("\"").stripSuffix("\"")
      }
    )
    

    `tq` equivalent in Scala 3 macros

    Explicit type conversion in Scala 3 macros

    What Scala 3 syntax can match on a Type and its Type parameters in the context of a macro?

    Alternatively you can introduce a recursive helper function for TypeRepr argument rather than static type T

    def recursiveCanonicalNameImpl[T](using q: Quotes)(using tt: Type[T]): Expr[String] =
      import quotes.reflect.*
    
      def hlp(repr: TypeRepr): Expr[String] =
        repr.widenTermRefByName.dealias match
          case AppliedType(name, args) =>
            Expr(name.dealias.simplified.show + "[" + args.map(a =>
              hlp(a).show.stripPrefix("\"").stripSuffix("\"")
            ).mkString(",") + "]")
          case _ =>
            Expr(repr.dealias.simplified.show)
    
      val repr = TypeRepr.of[T]
      hlp(repr)