I would like to pattern match certain function and other variable references inside a macro traversing over a Tree
. Currently, I am matching based on symbol.fullname
like this:
tree match {
case "$f($x)" if f.symbol != null
&& f.symbol.fullName == "_root_.mypackage.MyClass.myfunc" =>
...
case "$f($x)" if f.symbol != null
&& f.symbol.fullName == "_root_.mypackage.MyClass2.myfunc2" =>
...
case ...
}
However, I would like to additionally check during compilation of this macro that those functions actually exists, eg instead of having String, I would like to directly use references, such that the IDE can tell me whether I had a typo in the 'myfunc' name. I was thinking of using using reify
, but I am not sure whether that works.
Now lets assume i was looking for println
instead of MyClass.myfunc
. The first problem I encountered is that you cannot reify(println)
or any other function reference directly, because in Scala there are no function references, so you have to write reify(println _)
or more specificly in this case reify(println (_: String))
to select what println function we want to call. With the following code I collected all symbols that are inside the expression, but sadly only Predef (from Predef.println) is found but not println itself:
println(reify( println (_:String) ).tree
.collect { case x if x.symbol != null => x.symbol.fullName } )
// List(<none>, <none>, <none>, scala.Predef, <none>, <none>, scala.Predef, <none>, <none>, scala.Predef)
Any ideas for getting the name of something in scala (currently using 2.12)?
reify(println)
compiles.
How do you imagine symbol of function println (_:String)
? Where is this function defined? In Predef
two methods are defined:
def println(): Unit
def println(x: Any): Unit
Try
val mirror = scala.reflect.runtime.universe.runtimeMirror(this.getClass.getClassLoader) // at runtime
// val mirror = c.mirror // at compile time
mirror.staticClass("mypackage.MyClass").typeSignature.decl(TermName("myfunc"))
// method myfunc
or
typeOf[MyClass].decl(TermName("myfunc"))
// method myfunc
for MyClass#myfunc
definitions.PredefModule.typeSignature.decl(TermName("println")).alternatives
// List(method println, method println)
for both println
definitions.PredefModule.typeSignature.decl(TermName("println")).alternatives
.filter(_.asMethod.paramLists.map(_.map(_.typeSignature)) == List(List(definitions.AnyTpe)))
// List(method println)
for println(Any):Unit
.
For example
def foo(x: Any): Unit = macro impl
def impl(c: blackbox.Context)(x: c.Tree): c.Tree = {
import c.universe._
val printlnSymb = definitions.PredefModule.typeSignature.decl(TermName("println")).alternatives
.filter(_.asMethod.paramLists.map(_.map(_.typeSignature)) == List(List(definitions.AnyTpe)))
.head
x match {
case q"$f($x)" if f.symbol == printlnSymb =>
println("test")
}
q"()"
}
foo(println(1)) //Warning:scalac: test
reify( println (_:String) ).tree.collect { ...
produces only List(<none>, <none>, <none>, scala.Predef, <none>, <none>, scala.Predef, <none>, <none>, scala.Predef)
because tree reify( println (_:String) ).tree
is not typechecked (for typechecked tree it produces List($anonfun, x$1, java.lang.String, scala.Predef.println, scala.Predef.println, scala.Predef, x$1, java.lang.String)
).
So another option is to do c.typecheck
def impl(c: blackbox.Context)(x: c.Tree): c.Tree = {
import c.universe._
val printlnSymb = (c.typecheck(q"println(_:Any)", silent = false) match {
case q"($_) => $p($_)" => p
}).symbol
//val printlnSymb = (c.typecheck(reify { println(_:Any) }.tree, silent = false) match {
// case q"($_) => $p($_)" => p
//}).symbol
x match {
case q"$f($x)" if f.symbol == printlnSymb =>
println("test")
}
q"()"
}
To get the symbols of a reified expressions, the tree of the expressions needs another explicitly pass through typechecking, so the following code works:
val mySym1 = c.typecheck(reify(mypackage.myfunction1 _).tree) match {
case q"{(..$_) => $f(..$_)}" => f.symbol
}
val mySym2 = c.typecheck(reify(mypackage.myfunction2 _).tree) match {
case q"{(..$_) => $f(..$_)}" => f.symbol
}
println(mySym.fullName)
// _root_.mypackage.myfunction1
Then you can match based on the symbol:
tree match {
case "$f($x)" if f.symbol != null => f.symbol match {
case x if x == mySym1 => ...
case x if x == mySym2 => ...
case ...
}
case ...
}
There is actually a function called def symbolOf[X]: TypeSymbol
that can be used to get a TypeSymbol from a Type. But this only works for TypeSymbols, not TermSymbols, so we cannot do this on functions.
However, if this is our own function, we can replace the definition from def myfunc(): Int = ...
with object myfunc { def apply(): Int = ... }
. As each object has its own type (called myfunc.type
) we can now do the following
package main
object myfunc { def apply(): Int = 1 }
object myfunc2 { def apply(): Int = 1 }
...
tree match {
case "$f.apply($x)" if f.symbol == symbolOf[myfunc.type] => ...
case "$f.apply($x)" if f.symbol == symbolOf[myfunc2.type] => ...
case ...
}
The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.