Сохранить имена параметров метода в макросе scala

У меня есть интерфейс:

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 и контекст черного ящика.

Интерфейс на самом деле является источником java в отдельном проекте sbt, который я контролирую. Я пробовал скомпилировать:

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

Флаг параметров должен сохранять имена, но я все равно получаю тот же результат, что и раньше.


person mushroom    schedule 24.07.2016    source источник


Ответы (1)


Это не имеет ничего общего с макросами и все, что связано с системой отражения времени выполнения Scala. Короче говоря, и Java 8, и Scala 2.11 хотели иметь возможность искать имена параметров, и каждая реализовала для этого свою систему отражения.

Это отлично работает, если все написано на Scala, и вы скомпилируете его вместе (да!). Проблемы возникают, когда у вас есть класс Java, который нужно компилировать отдельно.

Наблюдения и проблема

Первое, на что следует обратить внимание, это то, что флаг -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, чтобы проверить имя информации о параметре, которое флаг 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$1, x$2, ... в качестве заполнителей, так же, как отражение в Java 8 генерирует имена arg0, arg1, ... в качестве заполнителей, когда мы проверяли скомпилированный типаж Scala. (И если бы мы не прошли -parameters, он сгенерировал бы эти имена даже для MyInterface.java.)

Решение

Лучшее решение (которое работает в 2.11), которое я могу придумать, чтобы получить имена параметров класса Java, - это использовать отражение Java из Scala. Что-то вроде

$ 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 это все в прошлом. Если я правильно читаю этот билет, это означает, что в 2.12 ваш код будет работать для классов Java, скомпилированных с -parameter, и мой метод отражения Java также будет работать для классов Scala.

Все хорошо, что хорошо кончается?

person Alec    schedule 03.08.2016