Question

I am evaluating the possibility and effort to create a macro annotation to turn this:

@txn class Foo(var bar: Int)

into this:

import concurrent.stm.{Ref, InTxn}

class Foo(bar0: Int) {
  private val _bar = Ref(bar0)

  def bar(implicit tx: InTxn): Int = _bar()
  def bar_=(value: Int)(implicit tx: InTxn): Unit = _bar() = value
}

(or perhaps creating a Foo companion object with apply method, no sure yet)

Now what I'm seeing from the ClassDef body is something like this

List(<paramaccessor> var bar: Int = _, 
  def <init>(bar: Int) = {
    super.<init>();
    ()
  })

So bar appears as param-accessor with no init (_), and also as argument to the <init> method.

How would I go about rewriting that body?

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

class txn extends StaticAnnotation {
  def macroTransform(annottees: Any*) = macro txnMacro.impl
}
object txnMacro {
  def impl(c: Context)(annottees: c.Expr[Any]*): c.Expr[Any] = {
    import c.universe._
    val inputs = annottees.map(_.tree).toList

    def reportInvalidAnnotationTarget(): Unit =
      c.error(c.enclosingPosition,
        "This annotation can only be used on a class definition")

    val expandees: List[Tree] = inputs match {
      case cd @ ClassDef(mods, nme, tp, tmp @ Template(parents, self, body)) :: Nil =>
        println(s"Body: $body")
        ???

      case _ =>
        reportInvalidAnnotationTarget()
        inputs
    }

    val outputs = expandees
    c.Expr[Any](Block(outputs, Literal(Constant(()))))
  }
}
Was it helpful?

Solution

As Eugene suggests, using quasiquotes can make this much easier. I haven't put together a full solution, but I tried some pieces so I think something along these lines will work:

case q"class $name extends $parent with ..$traits { ..$body }"=>
  val newBody = body.flatMap {
    case q"var $varName = $varBody" =>
       List(
         //put the three defs you need here.  some variant of this ...
         q"""private val ${stringToTermName("_" + varName.toString)} = Ref(${stringToTermName(varName.name + "0")})""",
         //etc ...
       )
    case other => other
  }
  q"class $name extends $parent with ..$ttraits { ..${(newBody).toList} }"

You might find my blog post relevant: http://imranrashid.com/posts/learning-scala-macros/

The most relevant code snippet is here: https://github.com/squito/learn_macros/blob/master/macros/src/main/scala/com/imranrashid/oleander/macros/FillTraitDefs.scala#L78

You may also find this useful for defining the getters & setters: https://groups.google.com/forum/#!searchin/scala-user/macro|sort:date/scala-user/Ug75FW73mJI/F1ijU0uxZAUJ

Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top