使用 Scala 宏获取具体化函数的名称

Get Name of Reified Function Using Scala Macros

我想在遍历 Tree 的宏中对某些函数和其他变量引用进行模式匹配。目前,我正在基于 symbol.fullname 进行匹配,如下所示:

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 ...
}

但是,我想在编译这个宏的过程中额外检查这些函数是否确实存在,例如我不想使用 String,而是想直接使用引用,这样 IDE 可以告诉我 'myfunc' 名称中是否有拼写错误。我正在考虑使用 using reify,但我不确定这是否有效。

现在假设我正在寻找 println 而不是 MyClass.myfunc。我遇到的第一个问题是你不能 reify(println) 或任何其他函数直接引用,因为在 Scala 中没有函数引用,所以你必须写 reify(println _) 或更具体地在这种情况下 reify(println (_: String)) 到 select 我们要调用什么 println 函数。使用以下代码,我收集了表达式中的所有符号,但遗憾的是只找到了 Predef(来自 Predef.println),但没有找到 println 本身:

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)

在 scala(目前使用 2.12)中获取名称的任何想法?

reify(println)编译。

你如何想象函数符号println (_:String)这个函数是在哪里定义的?在 Predef 中定义了两个 方法

def println(): Unit
def println(x: Any): Unit

尝试

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

typeOf[MyClass].decl(TermName("myfunc"))
// method myfunc

对于MyClass#myfunc

definitions.PredefModule.typeSignature.decl(TermName("println")).alternatives
// List(method println, method println)

两个println

definitions.PredefModule.typeSignature.decl(TermName("println")).alternatives
  .filter(_.asMethod.paramLists.map(_.map(_.typeSignature)) == List(List(definitions.AnyTpe)))
// List(method println)

对于println(Any):Unit

例如

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 { ... 只生成 List(<none>, <none>, <none>, scala.Predef, <none>, <none>, scala.Predef, <none>, <none>, scala.Predef) 因为树 reify( println (_:String) ).tree 没有类型检查(对于类型检查的树它生成 List($anonfun, x, java.lang.String, scala.Predef.println, scala.Predef.println, scala.Predef, x, java.lang.String))。

所以另一种选择是 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"()"
}

为了得到一个具体化的表达式的符号,表达式树需要另一个显式地通过类型检查,所以下面的代码有效:

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

然后可以根据符号匹配:

tree match {
  case "$f($x)" if f.symbol != null => f.symbol match {
    case x if x == mySym1 => ...
    case x if x == mySym2 => ...
    case ...
  }
  case ...
}

实际上有一个名为 def symbolOf[X]: TypeSymbol 的函数可用于从 Type 获取 TypeSymbol。但这只适用于 TypeSymbols,不适用于 TermSymbols,所以我们不能在函数上这样做。

但是,如果这是我们自己的函数,我们可以将 def myfunc(): Int = ... 中的定义替换为 object myfunc { def apply(): Int = ... }。由于每个对象都有自己的类型(称为myfunc.type),我们现在可以执行以下操作

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 ...
}