1

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!

Dmytro Mitin
  • 48,194
  • 3
  • 28
  • 66
Steve Waldman
  • 13,689
  • 1
  • 35
  • 45

1 Answers1

2

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)
Dmytro Mitin
  • 48,194
  • 3
  • 28
  • 66
  • Perfect. Thank you. I wonder why `_.dealias.simplified.show` returns quoted Strings for the type arguments, but not for the base names? – Steve Waldman Jan 09 '23 at 00:53
  • @SteveWaldman Probably different instances of `Show` (`Printer`). `repr` and `name` have type `TypeRepr` while `recursiveCanonicalNameImpl[a]` or `hlp(a)` return `Expr[String]`. – Dmytro Mitin Jan 09 '23 at 01:09
  • @SteveWaldman So it's not `_.dealias.simplified.show` that returns quoted strings. – Dmytro Mitin Jan 09 '23 at 01:59