3
votes

The following macro generates a case class Person and returns an instance of this class. It uses whitebox macros so the compiler can infer the type Person. This allows the macro client to call p.name even though this field was generated inside the macro.

import scala.language.experimental.macros

class MacroDefs(val c: scala.reflect.macros.whitebox.Context) {
  import c.universe._

  def createCaseImpl(): c.Expr[Product] = {
    val code: Tree = q""" case class Person(name:String); new Person("Joe") """
    c.Expr(code)
  }
}

object Macros {
  def createCase(): Product = macro MacrosDef.createCaseImpl
}

object Test {
  def main(args: Array[String]) {
    val p = Macros.createCase()
    println("Name: " + p.name)
  }
}

The code works, but the compiler is using structural types to access p.name, as you can see from the warning message below (and I confirmed it by decompiling the generated bytecode):

Warning:(5, 54) inferred existential type Person forSome { type Person <: Product with Serializable{val name: String; def copy(name: String): Person; def copy$default$1: String @scala.annotation.unchecked.uncheckedVariance} }, which cannot be expressed by wildcards,  should be enabled
by making the implicit value scala.language.existentials visible.
    val c = Macros.createCase()
                                                 ^

Since structural types rely on Java Reflection, I'm concerned about performance.

My question is if there is a better way of doing this so that the compiler will use standard method invocations instead of structural types and reflection. I'm using Scala 2.11.6.

A second minor question: Intellij does not seem to play well with whitebox macros and marks the access to p.name as invalid, saying that the field is not known, even though it will compile and run with scalac. Is there a way of making Intellij aware of whitebox macros?

1
Have you looked at the macro docs on public type providers?Martin Ring
As @MartinRing says, you probably want to use macro annotations here, but you can also use vampire methods to avoid the cost of reflective access on the structural type (which may not be an issue for your application, anyway).Travis Brown

1 Answers

0
votes

Following the suggestions from @MartinRing and @TravisBrown, I used Macro Annotations to solve the problem. This article was also very useful: http://www.47deg.com/blog/scala-macros-annotate-your-case-classes. I'm posting the code here for future reference.

In the client code, I annotate a wrapper object, which will then be expanded by the macro to contain a new case class Person and an instance of this class. Contrary to the solution with anonymous type providers which uses structural types and reflection, the generated code uses standard method invocations.

@personGenerator object Wrapper

object Main {
  def main(args: Array[String]) {
    println("Name: " + Wrapper.thePerson.name)
    val other = new Wrapper.Person("Joan")
    println("Other: " + other.name)
  }
}

The following is the implementation of the annotation and of the macro:

class personGenerator extends StaticAnnotation {
  def macroTransform(annottees: Any*): Any = macro MacroDefs.personGenerator_impl
}

class MacroDefs(val c: scala.reflect.macros.whitebox.Context) {
  import c.universe._

  def personGenerator_impl(annottees: c.Expr[Any]*): c.Expr[Any] = {
    annottees.map(_.tree) match {
      case List(q"object $name { ..$body }") =>
        val code = q"""
            object $name {
              ..$body
              case class Person(name:String)
              def thePerson = new Person("Joe")
            }
          """
        c.Expr[Any](code)
    }
  }
}