Синтаксическое дерево Scala, возвращаемое из inferImplicitValue, не может оценить

Я пишу макрос Scala (Scala 2.11), в котором я хотел бы получить дерево, представляющее неявную переменную внутри макроса, используя inferImplicitValue, оценить это синтаксическое дерево и использовать значение. Я действительно сделал это, но, похоже, это работает не во всех обстоятельствах [1]. Я построил упрощенный пример, где это не удается.

// a class for implicit evidence
class DemoEvidence(val value: Int)

// define 'foo' method for invoking the macro
object demoModule {
  def foo: Int = macro DemoMacros.fooImpl
}

class DemoMacros(val c: whitebox.Context) {
  import c.universe._

  def fooImpl: Tree = {
    val vInt = try {
      // get the tree representing the implicit value
      val impl = c.inferImplicitValue(typeOf[DemoEvidence], silent = false)
      // print it out
      println(s"impl= $impl")
      // try to evaluate the tree (this is failing)
      val eval = c.eval(c.Expr[DemoEvidence](c.untypecheck(impl.duplicate)))
      eval.value
    } catch {
      case e: Throwable => {
        // on failure print out the failure message
        println(s"Eval failed with: $e\nStack trace:\n${e.printStackTrace}")
        0
      }
    }
    q"$vInt"  // return tree representing the integer value
  }
}

Если я скомпилирую вышеуказанное, а затем вызову его:

object demo {
  implicit val demoEvidence: DemoEvidence = new DemoEvidence(42)
  val i: Int = demoModule.foo
}

Я вижу сбой компиляции следующим образом:

impl= demo.this.demoEvidence
java.lang.reflect.InvocationTargetException
    at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
    at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
    at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
    at java.lang.reflect.Method.invoke(Method.java:498)
    at scala.tools.reflect.ToolBoxFactory$ToolBoxImpl$ToolBoxGlobal$$anonfun$compile$1.apply(ToolBoxFactory.scala:275)
...

Полный вывод находится по адресу: https://gist.github.com/erikerlandson/df48f64329be6ab9de9caef5be6ab9de9caef5be6b6ab9caef5be5be6ab9caef5f5

Итак, вы можете видеть, что он находит дерево для объявленного неявного значения demo.this.demoEvidence, но оценка этого дерева не выполняется. Я видел, как этот базовый подход работает в другом месте моего проекта. Не уверен, в чем разница и почему здесь не работает.

[1] ОБНОВЛЕНИЕ: если неявное значение определено в (под) проекте и скомпилировано, а затем используется вне этого проекта, оно работает должным образом. Это тот случай, когда этот подход работает для меня.

Итак, вопрос в том, является ли это просто фундаментальным ограничением, с которым мне приходится жить, или есть какой-то умный обходной путь, или это «ошибка» с выводом неявных значений внутри макросов, которые могут быть исправлены.

ОБНОВЛЕНИЕ: я отправил сообщение о проблеме Scala для этого: https://github.com/scala/scala-dev/issues/353


person eje    schedule 29.03.2017    source источник
comment
Вы могли бы хотя бы включить трассировку стека, а также toString исключения.   -  person Alexey Romanov    schedule 30.03.2017
comment
Я не видел никаких следов. Но я попробую удалить try / catch и посмотреть, что у меня получится   -  person eje    schedule 30.03.2017
comment
Или просто используйте e.printStackTrace в своем catch.   -  person Alexey Romanov    schedule 30.03.2017
comment
Я обновился звонком на printStackTrace; полный вывод находится здесь: gist.github.com/erikerlandson/df48f64329be6ab9de9caef5f5be4a, сейчас Weirdbe4a полный стек перед он печатает ошибку (и трассировка стека для ошибки пуста)   -  person eje    schedule 30.03.2017
comment
Одно различие между этим неудачным сценарием и сценарием, в котором он работает, заключается в том, что здесь я объявляю неявное значение и вызываю макрос, который его ищет, в том же блоке кода object demo. В рабочем сценарии имплициты объявляются и компилируются перед вызовом макросов, которые их ищут.   -  person eje    schedule 30.03.2017
comment
Я проверил, что если неявное значение определено в (под) проекте и скомпилировано, а затем используется вне этого проекта, оно работает должным образом. Итак, вопрос в том, является ли это просто фундаментальным ограничением или есть какое-то умное обходное решение.   -  person eje    schedule 30.03.2017
comment
Я не уверен в основной проблеме, но известно, что untypecheck иногда повреждает деревья ... scala-lang.org/api/current/scala-reflect/scala/reflect/macros/   -  person dk14    schedule 03.04.2017


Ответы (1)


Судя по трассировке стека, eval ожидает, что object demo будет существовать в форме файла классов для выполнения, что имеет смысл, учитывая, что значение, которое вы пытаетесь вычислить, зависит от val demoEvidence, который является членом object demo.

Но eval происходит во время проверки типов object demo, поэтому файл класса еще не существует, отсюда и ошибка. В версии с неявным значением, определенным в подпроекте, я полагаю, что подпроект компилируется первым, поэтому файлы классов, необходимые для eval, существуют, и поэтому оценка продолжается, как вы ожидали.

person Miles Sabin    schedule 03.04.2017
comment
Я понимаю, в чем проблема. Это делает inferImplicitValue немного асимметричным с обычными неявными параметрами, поскольку простое объявление foo с неявным параметром действительно работает в этом сценарии. В идеальном мире я надеялся, что inferImplicitValue вернет дерево, эквивалентное q"new DemoEvidence(42)" внутри макроса. Теоретически кажется, что это должно быть доступно где-то в текущем контексте макроса. Не уверен, насколько это возможно. - person eje; 03.04.2017
comment
В случае без макросов, который вы только что описали, нет вызова eval, поэтому нет необходимости компилировать код для выполнения ... проблема заключается в eval. - person Miles Sabin; 05.04.2017