在 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.java 用 javac -parameters MyInterface.java
编译
public interface MyInterface {
public int doSomething(int bar);
}
MyTrait.scala 用 scalac 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.
一切皆大欢喜?
我有一个接口:
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.java 用
编译javac -parameters MyInterface.java
public interface MyInterface { public int doSomething(int bar); }
MyTrait.scala 用
编译scalac 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.
一切皆大欢喜?