jedesah
jedesah

Reputation: 3033

What is the best way to re-establish type coherence after transforming a Tree with Macros

I have the following macro:

def testMacro[T](x: T): Option[T] = macro testMacroImpl[T]

def testMacroImpl[T: c.WeakTypeTag](c: Context)(x: c.Expr[T]): c.Expr[Option[T]] = {
    import c.universe._
    val newTree = x.tree match {
      case Block(List(first), second) => {
        val newFirst = first match {
          case ValDef(mods, name, _, rhs) => ValDef(mods, name, EmptyTree, q"Some($rhs)")
        }
        Block(List(newFirst), second)
      }
    }
    c.Expr[Option[T]](newTree)
  }

Basically, this should just transforms this:

testMacro {
    val aa = "hello"
    aa
}

into

{
    val aa = Some("hello")
    aa
}

However, it fails with the following error:

[error]  found   : String
[error]  required: Option[String]
[error]     val f = IdiomBracket.testMacro{
[error]   
                                          ^

My investigation shows that this is because it receives a typed tree for which the identifier aa has type String. Because of the code transformation, it is now of type Option[String] but the identifier's type has not been updated. I tried creating a new (untyped) identifier, and that only makes the error this:

[error]  found   : <notype>
[error]  required: Option[String]
[error]     val f = IdiomBracket.testMacro{
[error]                                   ^

I have tried type checking the tree before sending it back in the hopes that that would fill in the correct type but that seems to have no effect.

For reference, here is the same macro that creates a new Ident and does typechecking which unfortunately still does not work.

def testMacroImpl[T: c.WeakTypeTag](c: Context)(x: c.Expr[T]): c.Expr[Option[T]] = {
    import c.universe._
    val newTree = x.tree match {
      case Block(List(first), second) => {
        val newFirst = first match {
          case ValDef(mods, name, _, rhs) => ValDef(mods, name, EmptyTree, q"Some($rhs)")
        }
        val newSecond = second match {
          case ident: Ident => Ident(ident.name)
        }
        Block(List(newFirst), newSecond)
      }
    }
    c.Expr[Option[T]](c.typecheck(newTree))
  }

Upvotes: 5

Views: 170

Answers (2)

jedesah
jedesah

Reputation: 3033

This line:

case ValDef(mods, name, _, rhs) => ValDef(mods, name, EmptyTree, q"Some($rhs)")

needs to be changed to this line:

case ValDef(mods, name, _, rhs) => ValDef(mods, name, TypeTree(), q"Some($rhs)")

It probably never makes sense to put EmptyTree as the type in a ValDef. Too bad the compiler could not tell me that, would have saved myself 48 hours of part time work.

Upvotes: 4

Ashalynd
Ashalynd

Reputation: 12563

How about doing a quasiquote match? For me this worked:

case q"val $a = $b" => q"val $a = Some($b)"

Below a complete example with console log (in Scala 2.11.4 and with macro bundles)

val universe: reflect.runtime.universe.type = reflect.runtime.universe
import universe._
import reflect.runtime.currentMirror
import tools.reflect.ToolBox
val toolbox = currentMirror.mkToolBox()
import scala.reflect.macros.blackbox.Context
import scala.language.experimental.macros

class Impl(val c:Context) {
    def optIt[T: c.WeakTypeTag](x:c.Expr[T]) = {
    import c.universe._
    val newTree = x.tree match {
      case Block(List(first), second) => {
        val newFirst = first match {
          case q"val $a = $b" => q"val $a = Some($b)"
        }
        val newSecond = second match {
          case ident: Ident => Ident(ident.name)
          case x => println(s"x=$x"); x
        }
        Block(List(newFirst), newSecond)
      }
    }
    c.Expr[Option[T]](c.typecheck(newTree))
  }
}

scala> def testMacro[T](x:T) = macro Impl.optIt[T]
warning: there was one deprecation warning; re-run with -deprecation for details
defined term macro testMacro: [T](x: T)Option[T]

scala> testMacro {
     | val aa = "hello"
     | aa
     | }
res4: Option[String] = Some(hello)

scala> testMacro {
     | val ii = 5
     | ii
     | }
res5: Option[Int] = Some(5)

Upvotes: 1

Related Questions