在 Scala 宏中保留方法参数名称

Preserve method parameter names in scala macro

我有一个接口:

trait MyInterface {
  def doSomething(usefulName : Int) : Unit
}

我有一个宏,它遍历接口的方法并使用方法名称和参数进行处理。我通过做这样的事情来访问方法名称:

val tpe = typeOf[MyInterface]

// Get lists of parameter names for each method
val listOfParamLists = tpe.decls
  .filter(_.isMethod)
  .map(_.asMethod.paramLists.head.map(sym => sym.asTerm.name))

如果我打印出 doSomething 的参数名称,usefulName 就变成了 x。为什么会发生这种情况,有没有办法保留原始参数名称?

我正在使用 scala 2.11.8 版、macros paradise 2.1.0 版和黑盒上下文。

该接口实际上是 java 我控制的一个单独的 sbt 项目中的源代码。我试过编译:

javacOptions in (Compile, compile) ++= Seq("-target", "1.8", "-source", "1.8", "-parameters")

参数标志应该保留名称,但我仍然得到与以前相同的结果。

这与宏无关,与 Scala 的运行时反射系统有关。简而言之,Java 8 和 Scala 2.11 都希望能够查找参数名称,并且都实现了他们的反射系统来实现这一点。

如果所有内容都是 Scala 并且您将其编译在一起,这就很好用了(duh!)。当您有一个必须单独编译的 Java class 时,就会出现问题。

观察和问题

首先要注意的是 -parameters 标志仅从 Java 8 开始出现,大约与 Scala 2.11 一样古老。所以 Scala 2.11 可能不会使用此功能来查找方法名称......考虑以下内容

  • MyInterface.javajavac -parameters MyInterface.java

    编译
    public interface MyInterface {
      public int doSomething(int bar);
    }
    
  • MyTrait.scalascalac MyTrait.scala

    编译
    class MyTrait {
      def doSomething(bar: Int): Int
    }
    

然后,我们可以用MethodParameterSpy查看Java8-parameter标志应该给我们的参数信息名称。 运行它在Java编译界面上,我们得到(这里我缩写了一些输出)

public abstract int MyInterface.doSomething(int)

Parameter name: bar

但是在编译的Scala中class,我们只得到

public abstract int MyTrait.doSomething(int)

Parameter name: arg0

然而,Scala 可以毫无问题地查找自己的参数名称。这告诉我们,Scala 实际上根本不依赖 Java8 特性——它构建了自己的运行时系统来跟踪参数名称。然后,这对来自 Java 来源的 classes 不起作用也就不足为奇了。它生成名称 x, x, ... 作为占位符,与 Java 8 反射生成名称 arg0, arg1, ... 的方式相同。 . 当我们检查编译后的 Scala 特征时作为占位符。 (如果我们没有通过 -parameters,它甚至会为 MyInterface.java 生成这些名称。)

解决方案

我能想出的最好的解决方案(在 2.11 中有效)来获取 Java class 的参数名称是使用来自 Scala 的 Java 反射。像

$ javac -parameters MyInterface.java
$ jar -cf MyInterface.jar MyInterface.class
$ scala -cp MyInterface.jar
scala> :pa
// Entering paste mode (ctrl-D to finish)

import java.lang.reflect._

Class.forName("MyInterface")
  .getDeclaredMethods
  .map(_.getParameters.map(_.getName))

// Exiting paste mode, now interpreting.

res: Array[Array[String]] = Array(Array(bar))

当然,这只有在你有 -parameter 标志时才有效(否则你会得到 arg0)。

我可能还应该提到,如果您不知道您的方法是从 Java 还是从 Scala 编译的,您可以随时调用 .isJava(例如:typeOf[MyInterface].decls.filter(_.isMethod).head.isJava ) 然后分支到您的初始解决方案或我上面提出的方案。

未来

幸运的是,这在 Scala 2.12 中已成为过去。如果我正确地阅读 this ticket,那意味着在 2.12 中你的代码将适用于 Java classes 编译 -parameter 并且,我的 Java 反射 hack 将也适用于 Scala classes.

一切皆大欢喜?