The compiler uses structured types for the case class generated inside the whitebox macro

The following macro generates a class of the Person class and returns an instance of that class. It uses whitebox macros, so the compiler can infer the type Person. This allows the macro client to call p.name even if the 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 uses structured 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 am concerned about performance.

My question is if there is a better way to do this so that the compiler uses standard method calls instead of structured types and reflection. I am using Scala 2.11.6.

Second minor question: Intellij doesn't seem to work well with whitebox macros and notes that p.name is not accessed, stating that this field is unknown, although it will compile and run with scalac. Is there a way to make Intellij aware of whitebox macros?

+3


source to share


1 answer


Following the advice of @MartinRing and @TravisBrown, I used Macro annotations to solve the problem. This article is also very helpful: http://www.47deg.com/blog/scala-macros-annotate-your-case-classes . I am posting the code here for future reference.

In the client code, I annotate the wrapper object, which will then be expanded with a macro to contain the new Person class and an instance of that class. Unlike the anonymous type provider solution, which uses structured types and reflection, the generated code uses standard method calls.

@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)
  }
}

      



Below is the implementation of annotation and 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)
    }
  }
}

      

0


source







All Articles