简体   繁体   中英

Get Name of Reified Function Using Scala Macros

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.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM