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

时间:2016-07-24 02:21:59

标签: scala scala-macros scala-reflect scala-macro-paradise

我有一个界面:

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$1。为什么会发生这种情况并且有没有办法保留原始参数名称?

我使用scala版本2.11.8,macros paradise版本2.1.0和blackbox上下文。

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

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

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

1 个答案:

答案 0 :(得分:4)

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

如果一切都是Scala并且你一起编译它就行了(呃!)。当你有一个必须单独编译的Java类时会出现问题。

观察和问题

首先要注意的是-parameters标志仅在Java 8之后,与Scala 2.11差不多。因此Scala 2.11可能没有使用此功能来查找方法名称......请考虑以下内容

    使用javac -parameters MyInterface.java

    编译的
  • MyInterface.java

    public interface MyInterface {
      public int doSomething(int bar);
    }
    
  • 使用scalac MyTrait.scala

    编译的
  • MyTrait.scala

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

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

  

public abstract int MyInterface.doSomething(int)

     

参数名称:bar

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

  

public abstract int MyTrait.doSomething(int)

     

参数名称:arg0

然而,Scala查找自己的参数名称没有问题。这告诉我们Scala实际上根本不依赖于这个Java 8特性 - 它构建了自己的运行时系统来跟踪参数名称。然后,毫不奇怪,这对Java源代码的类不起作用。它生成名称x$1x$2,...作为占位符,与Java 8反射生成名称arg0arg1,...作为占位符的方式相同我们检查了编译的Scala特征。 (如果我们没有通过-parameters,即使是MyInterface.java也会生成这些名称。)

解决方案

最好的解决方案(在2.11中工作)我可以想出获取Java类的参数名称是使用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中,您的代码将适用于使用-parameter编译的Java类,并且我的Java反射黑客也适用于Scala类。

一切都很好,结束了吗?