Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Scala macros assign param of deconstructed function

I'm currently playing around a bit with macros and maybe this is a bad idea anyway, but here's my problem:

I have the following macro:

def using[A <: { def close(): Unit }, B](resource: A)(f: A => B) = macro usingImpl[A, B]

def usingImpl[A <: { def close(): Unit }, B](c: Context)(resource: c.Expr[A])(f: c.Expr[A => B]): c.Expr[B] = {

  import c.universe._


  f.tree match {
    case Function(params, body) =>
      //val ValDef(modifiers, name, tpt, _) = params.head
      c.Expr[B](
        Block(
          List(
            //ValDef(modifiers, name, tpt, resource.tree)
            ValDef(params.head.symbol, resource.tree)
          ),
          body
        )
      )

    case _: Select =>
      reify {
        val res = resource.splice
        try {
          f.splice(res)
        } finally {
          res.close()
        }
      }
  }
}

In case of a Select, I simply call the function and close the resource, works fine. But in case of a Function, I'd like to assign the param value to the resource and call the body. When I'm using the deprecated creator of ValDef, that takes a Symbol and a Tree, everything works fine. If I'm using the out-commented 4-args creator, I get a compiler error, stating that the value x$1 is not in scope. When I look at the code that both versions produce, it looks exactly the same:

Expr[Int]({
  <synthetic> val x$1: Test.Foo = new Test.this.Foo();
  x$1.bar.+(23)
})

Is there maybe a way, to simply use params.head and assign a value? Thanks for any help!

edit:

I call the macro like this:

object Test extends App {
  import Macros._

  class Foo {
    def close() {}

    def bar = 3
  }

  println(using(new Foo)(_.bar + 3))
}

As I said, if I'm using the out-commented version, it gives me a compiler error, that prints the AST and at the end this message: [error] symbol value x$1 does not exist in Test$delayedInit$body.apply

And I'm using 2.10.1.

like image 465
drexin Avatar asked May 08 '13 22:05

drexin


1 Answers

Ah, now I can reproduce your error. Any time you get the "This entry seems to have slain the compiler." message and see lines like this in the stack trace:

at scala.reflect.internal.SymbolTable.abort(SymbolTable.scala:49)

Your next move should be to start wrapping stuff in c.resetAllAttrs. To be honest the reason I couldn't reproduce your error the first time around is because I had replaced body in the block with c.resetAllAttrs(body) after copying and pasting your code, without even thinking about it. It's just a reflex at this point.

See for example this little single abstract method class demo for a similar instance of a place where this trick is necessary.

In your case, if we have this:

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

object Macros {
  def using[A <: { def close(): Unit }, B](resource: A)(f: A => B) =
    macro usingImpl[A, B]

  def usingImpl[A <: { def close(): Unit }, B](c: Context)
    (resource: c.Expr[A])(f: c.Expr[A => B]): c.Expr[B] = {
    import c.universe._

    val expr = f.tree match {
      case Function(ValDef(modifiers, name, tpt, _) :: Nil, body) =>
        c.Expr[B](
          Block(
            ValDef(modifiers, name, tpt, resource.tree) :: Nil,
            c.resetAllAttrs(body)
          )
        )

      case _: Select => reify {
        val res = resource.splice
        try { f.splice(res) } finally { res.close() }
      }
    }

    println(expr)
    expr
  }
}

We'll see the following when we compile your test code:

Expr[B]({
  <synthetic> val x$1: $line3.$read.$iw.$iw.Test.Foo = new Test.this.Foo();
  x$1.bar.$plus(3)
})

Exactly as desired.

like image 130
Travis Brown Avatar answered Nov 10 '22 06:11

Travis Brown