Search code examples
scalamacrosscala-quasiquotes

Lifting string variable using Scala quasiquotes


This is a simplified version of the problem I am facing but the underlying issue remains. After calling a macro, I want to generate case classes dynamically. I am able to retrieve parameters from macro call etc. The issue I am having is trying to use a string variable within quasiquotes. I essentially want to have the following:

def expand_impl(c: Context)(annottees: c.Expr[Any]*): c.Expr[Any] = {
    import c.universe._

    val toGen = "case class Foo()"

    val toReturn = c.Expr[Any](
        q"$toGen"
    )
    toReturn
}

However, the case class is not generated. Now I know that if I change toGen to q"case class Foo()" it will work, however toGen is a string I will generate after some other processing which returns a string, so I can't do that. Compiling it like this and manually looking at the the value of toReturn I get the following:

Expr[Any]("case class Foo()")

The string toGen is simply pasted in, along the quotes, meaning the case class is not generated.

I have looked for similar issues but can't find this example anywhere. How can I unquote the double quotes of a string variable within quasiquotes?


Solution

  • There is a parse method defined on Context. It returns a Tree, and because trees can be interpolated in quasiquotes, you can very easily mix and match parsing with quasiquoting. By example:

    scala> :paste
    // Entering paste mode (ctrl-D to finish)
    
    import scala.reflect.macros.whitebox.Context
    import scala.language.experimental.macros
    
    def test_impl(c: Context)(): c.Tree = {
      import c.universe._
      val tree = c.parse("""println(2)""")
      q"println(1); $tree; println(3)"
    }
    def test(): Unit = macro test_impl
    
    // Exiting paste mode, now interpreting.
    
    import scala.reflect.macros.whitebox.Context
    import scala.language.experimental.macros
    test_impl: (c: scala.reflect.macros.whitebox.Context)()c.Tree
    defined term macro test: ()Unit
    
    scala> test()
    1
    2
    3
    

    In this example I defined a def macro, but it should work just as well with macro annotations (as in your case).