Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to build a dynamic sequence in a scala macro?

I have a scala macro which outputs nested case classes. I can assemble fragments of expressions created using reify to build up the nested case classes programmatically:

case class Foo(name: String)
case class Bar(foo: Foo)

def foo(name: String) = { 
  c.universe reify { 
    Foo(c.literal(name).splice)
  }
}

def bar(foo: Expr[Foo]) = {
  c.universe reify { 
    Bar(foo.splice)
  }
}

// output Bar(Foo("MyFoo"))
c.Expr( bar(foo("MyFoo").asInstanceOf[Expr[Foo]]).tree )

Things work well apart from the annoying cast (how can i fix that?). Where I am stuck is when I want the macro to output some case class which require a collection of inner objects whos size varies based on what the marco sees within the AST it is processing.

So if I have a class which takes a sequence of foo:

 case class Baz(foos: Seq[Foo])

I would like to do something along the lines of:

def baz(foos: Seq[Expr[Foo]]): Expr[Baz] = {
  val flipped: Expr[Seq[Foo]] = ???  // convert foos from Seq[Expr[Foo]] to Expr[Seq[Foo]]
  c.universe reify {
    Baz(flipped.splice)
  }
}

// now pack any number of Foos into an output Baz as required
c.Expr(baz(Seq(foo("MyFoo1"),foo("MyFoo2"))).tree)

I am unable to convert a Seq[Expr[Foo]] into an Expr[Seq[Foo]] to do the splice such that I can pack a variable number nested objects into the macro output. How can I reify a dynamically built list to use as the constructor arg?

like image 820
simbo1905 Avatar asked Oct 31 '13 17:10

simbo1905


1 Answers

You can provide a type parameter to reify to avoid the casts. Turning the sequence of expressions inside out is a little trickier, but not much:

case class Foo(name: String)
case class Bar(foo: Foo)
case class Baz(foos: Seq[Foo])

import scala.language.experimental.macros
import scala.reflect.macros.Context

def demo: Baz = macro demo_impl
def demo_impl(c: Context) = {
  import c.universe._

  def foo(name: String) = reify[Foo](Foo(c.literal(name).splice))
  def bar(foo: Expr[Foo]) = reify[Bar](Bar(foo.splice))

  def baz(foos: Seq[Expr[Foo]]): Expr[Baz] = {
    val flipped: Expr[Seq[Foo]] = c.Expr[Seq[Foo]](
      Apply(
        Select(reify(Seq).tree, newTermName("apply")),
        foos.map(_.tree).toList
      )
    )

    reify(Baz(flipped.splice))
  }

  baz(Seq(foo("MyFoo1"), foo("MyFoo2")))
}

Life is so much better in paradise, though:

def demo: Baz = macro demo_impl
def demo_impl(c: Context) = {
  import c.universe._

  def foo(name: String) = c.Expr(q"Foo($name)")
  def bar(foo: Expr[Foo]) = c.Expr(q"Bar($foo)")
  def baz(foos: Seq[Expr[Foo]]) = c.Expr(q"Baz(Seq(..$foos))")

  baz(Seq(foo("MyFoo1"), foo("MyFoo2")))
}

This is exactly equivalent to the first implementation, but uses quasiquotes—which are now available in 2.10 as a compiler plugin—instead of reification and manual tree construction.

like image 142
Travis Brown Avatar answered Nov 15 '22 07:11

Travis Brown