scalascala-macrosscala-reflectscala-quasiquotes

merge `Tree` and `List[Tree]` in scala compile-time reflection


I have scala macro that creates the constructor for a class on the fly.
So for example if we have a class case class PersonConfig(name: String, age: Int, isFemale: Boolean). I have the Tree structure for the classname and the arguments passed to the class as shown below

@ val className = q"PersonConfig"
className: Ident = Ident(PersonConfig)

@ val args = List(q""""Jyn Erso"""", q"26", q"true")
args: List[Literal] = List(Literal(Constant("Jyn Erso")), Literal(Constant(26)), Literal(Constant(true)))

Now to make the AST structure that would create an instance of PersonConfig (i.e. PersonConfig("Jyn Erso", 26, true)) I will have to combine className and args values. The Challenge here is that the args can be of any size since this macro can be used to construct constructors for many different classes.

currently the obvious but less DRY and verbose solution is to pattern match on the args parameter and create the AST structure as shown below.

import scala.reflect.runtime.universe
def makeExpr(className: universe.Tree, args: List[universe.Tree]): universe.Tree = {
  args.reverse match {
    case node1 :: Nil => q"$className($node1)"
    case arg1 :: arg2 :: Nil => q"$className($arg1, $arg2)"
    case arg1 :: arg2 :: arg3 :: Nil => q"$className($arg1, $arg2, $arg3)"
    case arg1 :: arg2 :: arg3 :: arg4 :: Nil => q"$className($arg1, $arg2, $arg3, $arg4)"
    case arg1 :: arg2 :: arg3 :: arg4 :: arg5 :: Nil => q"$className($arg1, $arg2, $arg3, $arg4, $arg5)"
    case Nil => throw new Exception(s"argument list for class ${className.toString} cannot be empty")
    case _ => throw new Exception(s"argument list for class ${className.toString} is too long")
  }

}

But is there a better way to handle this efficiently and which is more DRY?. such as using foldLeft or other equivalent method to achieve what makeExpr function does?


Solution

  • I managed to get this done using foldLeft as shown below.

      def makeExpr(c: blackbox.Context)(className: c.Tree, args: List[c.Tree]): c.universe.Tree = {
        import c.universe._
        args.reverse match {
          case head :: tail => tail.foldLeft(q"$className($head)")({
           case (q"$_(..$params)", node) => q"$className(..${params :+ node})"          })
          case Nil => throw new MacroException(s"argument list for class ${className.toString} cannot be empty")
        }
      }