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

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

我有一个界面:

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

我有一个宏,它迭代接口(interface)的方法并使用方法名称和参数进行处理。我通过执行以下操作访问方法名称:
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、宏天堂版本 2.1.0 和黑盒上下文。

该接口(interface)实际上是我控制的一个单独的 sbt 项目中的 java 源代码。我试过编译:
javacOptions in (Compile, compile) ++= Seq("-target", "1.8", "-source", "1.8", "-parameters")

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

最佳答案

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

如果一切都是 Scala 并且你一起编译它(duh!),这很好用。当您有一个必须单独编译的 Java 类时,就会出现问题。

观察和问题

首先要注意的是 -parameters flag 仅从 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检查 Java 8 -parameter 的参数信息名称flag 应该给我们。在 Java 编译的界面上运行它,我们得到(这里我缩写了一些输出)

    public abstract int MyInterface.doSomething(int)

    Parameter name: bar



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

    public abstract int MyTrait.doSomething(int)

    Parameter name: arg0



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

    解决方案

    我能想到的获取 Java 类的参数名称的最佳解决方案(适用于 2.11)是使用 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 )然后将其分支到您的初始解决方案或我上面提出的解决方案。

    future

    幸运的是,这在 Scala 2.12 中已成为过去。如果我没看错 this ticket ,这意味着在 2.12 中,您的代码将适用于使用 -parameter 编译的 Java 类而且,我的 Java 反射技巧也适用于 Scala 类。

    一切都好,结局好?

    关于scala - 在 scala 宏中保留方法参数名称,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/38548167/

    相关文章:

    scala - 如何在 ScalaTest 测试中正确使用 Spark?

    scala - 是否可以使用宏实现类似于 Scala 的 @BeanProperty 的东西?

    scala - 获取密封特征的子类

    java - 环绕具有不同签名的通用回调函数

    Scala getter/setter 作为宏

    scala - 比较两个字符串时,如何使 scalatest 匹配器忽略空格?

    Scala 宏检查匿名函数的树

    scala - 检索将分配给 Scala 宏调用的值的名称

    scala - Scala 反射中的去混叠类型

    scala - 从 Seq[T] 中提取类型 T