What is the best way to restore type coherence after tree conversion using 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 transform this:

 testMacro { val aa = "hello" aa } 

in

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

However, it does not work 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 gets a typed tree for which the identifier aa is of type String . Due to code conversion, it is now of type Option[String] , but the type of identifier is not updated. I tried to create a new (untyped) identifier and this only makes an error:

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

I tried the tree check type before sending it back in the hope that it fills the correct type, but this does not seem to have any effect.

For reference, here is the same macro that creates a new identifier and performs type checking, which, unfortunately, does not work yet.

 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)) } 
+5
source share
2 answers

This line:

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

need to change to this line:

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

It probably doesn't make sense to set EmptyTree as a type in ValDef. Too bad that the compiler could not tell me about it, would have saved 48 hours of working part-time.

+4
source

How about doing quasivot? For me it worked:

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

Below is a complete example with the console log (in Scala 2.11.4 and with macro accounts)

 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) 
+1
source

Source: https://habr.com/ru/post/1209727/


All Articles