Как я могу повторно использовать поддеревья определения (AST) в макросе?

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

Предположим, у нас есть этот код:

val y = transform {
  val x = 3
  x
}
println(y) // prints 3

где «трансформировать» это задействованный макрос. Хотя может показаться, что он абсолютно ничего не делает, он действительно преобразовывает показанный блок в это выражение:

3 match { case x => x }

Это делается с помощью этой реализации макроса:

def transform(c: Context)(block: c.Expr[Int]): c.Expr[Int] = {
  import c.universe._
  import definitions._

  block.tree match {
    /* {
     *   val xNam = xVal
     *   xExp
     * }
     */
    case Block(List(ValDef(_, xNam, _, xVal)), xExp) =>
      println("# " + showRaw(xExp)) // prints Ident(newTermName("x"))
      c.Expr(
        Match(
          xVal, 
          List(CaseDef(
            Bind(xNam, Ident(newTermName("_"))),
            EmptyTree,
            /* xExp */ Ident(newTermName("x")) ))))
    case _ => 
      c.error(c.enclosingPosition, "Can't transform block to function")
      block  // keep original expression
  }
}

Заметить, чтоxNam соответствует имени переменной,xVal соответствует его связанному значению и, наконец,xExp соответствует выражению, содержащему переменную. Хорошо, если я распечатаю необработанное дерево xExp, я получаюIdent(newTermName("x"))и это именно то, что установлено в случае RHS. Поскольку выражение может быть изменено (например, x + 2 вместо x), это не является правильным решением для меня. То, что я хочу сделать, это повторно использовать дерево xExp (см. Комментарий xExp), изменяя при этом «x» apos; значение (это определение во входном выражении, но будет выходной переменной LHS в выходном), но оно запускает длинную ошибку, обобщенную в:

symbol value x does not exist in org.habla.main.Main$delayedInit$body.apply); see the error output for details.

Мое текущее решение состоит в разборе xExp для установки всех Ident на новые, но это полностью зависит от внутренних компонентов компилятора и, таким образом, временного обходного пути. Очевидно, что xExp поставляется вместе с дополнительной информацией, которую предлагает showRaw. Как я могу очистить этот xExp для разрешения & x; роль переменной случая? Кто-нибудь может объяснить всю картину этой ошибки?

PS: я безуспешно пытался использовать семейство замещающих * методов изTreeApi но мне не хватает основ, чтобы понять его значение.

 jeslg04 июл. 2012 г., 17:31
У меня сейчас нет теста, но я полагаю, что этого было достаточно с применением сброса на XExp (сброс всего c.Expr работает наверняка). Я не пытался применить метод к блоку ввода.
 jeslg04 июл. 2012 г., 17:10
Да, это так. Он работал в показанном коде, а также в довольно сложном дереве с несколькими переменными «изменения».
 Daniel C. Sobral04 июл. 2012 г., 17:17
Вы назвалиresetAllAttrs наc.Expr результат, наblockили на каком-то выбранном дереве?
 Eugene Burmako29 дек. 2013 г., 10:24
Я рекомендую использоватьresetLocalAttrsтак как это гораздо менее разрушительно:github.com/scala/scala/pull/3305
 Daniel C. Sobral04 июл. 2012 г., 17:01
СделалresetAllAttrs работа, в конце концов?

Ответы на вопрос(1)

Решение Вопроса

Разборка входных выражений и их повторная сборка другим способом - важный сценарий в макрологии (это то, что мы делаем внутренне вreify макро). Но, к сожалению, в настоящее время это не особенно легко.

Проблема в том, что входные аргументы макроса достигают реализации макроса уже проверенной на тип. Это и благословение, и проклятие.

Особый интерес для нас представляет тот факт, что привязки переменных в деревьях, соответствующих аргументам, уже установлены. Это означает, что всеIdent а такжеSelect узлы имеют своиsym заполненные поля, указывающие на определения, к которым относятся эти узлы.

Вот пример того, как работают символы. Я скопирую / вставлю распечатку из одного из моих выступлений (здесь я не даю ссылку, потому что большая часть информации в моих выступлениях устарела, но эта конкретная распечатка имеет вечную полезность):

>cat Foo.scala
def foo[T: TypeTag](x: Any) = x.asInstanceOf[T]
foo[Long](42)

>scalac -Xprint:typer -uniqid Foo.scala
[[syntax trees at end of typer]]// Scala source: Foo.scala
def foo#8339
  [T#8340 >: Nothing#4658 <: Any#4657]
  (x#9529: Any#4657)
  (implicit evidence$1#9530: TypeTag#7861[T#8341])
  : T#8340 =
x#9529.asInstanceOf#6023[T#8341];
Test#14.this.foo#8339[Long#1641](42)(scala#29.reflect#2514.`package`#3414.mirror#3463.TypeTag#10351.Long#10361)

Напомним, что мы пишем небольшой фрагмент, а затем компилируем его с помощью scalac, прося компилятор сбросить деревья после фазы typer, печатая уникальные идентификаторы символов, назначенных деревьям (если они есть).

В полученной распечатке мы видим, что идентификаторы были связаны с соответствующими определениями. Например, с одной стороны,ValDef("x", ...), который представляет параметр метода foo, определяет символ метода с id = 9529. С другой стороны,Ident("x") в теле метода получил своеsym поле установлено на тот же символ, который устанавливает привязку.

Хорошо, мы видели, как привязки работают в скаляре, и сейчас самое время представить фундаментальный факт.

If a symbol has been assigned to an AST node, 
then subsequent typechecks will never reassign it. 

Вот почему reify является гигиеническим. Вы можете взять результат reify и вставить его в произвольное дерево (которое, возможно, определяет переменные с конфликтующими именами) - исходные привязки останутся без изменений. Это работает, потому что reify сохраняет исходные символы, поэтому последующие проверки типов не будут повторно связывать узлы AST.

Теперь мы готовы объяснить ошибку, с которой вы столкнулись:

symbol value x does not exist in org.habla.main.Main$delayedInit$body.apply); see the error output for details.

Аргументtransform макрос содержит определение и ссылку на переменнуюx, Как мы только что узнали, это означает, что соответствующие ValDef и Ident будут иметь своиsym поля синхронизированы. Все идет нормально.

Однако, к сожалению, макрос портит установленную привязку. Он воссоздает ValDef, но не очищаетsym поле соответствующего идентификатора. Последующая проверка типов назначает новый символ вновь созданному ValDef, но не касается исходного идентификатора, который копируется в дословный результат.

После проверки типов исходный идентификатор указывает на символ, который больше не существует (это именно то, о чем говорилось в сообщении об ошибке :)), что приводит к сбою во время генерации байт-кода.

Итак, как мы можем исправить ошибку? К сожалению, нет простого ответа.

Одним из вариантов будет использованиеc.resetLocalAttrs, который рекурсивно стирает все символы в данном узле AST. Последующая проверка типов затем восстановит привязки при условии, что сгенерированный вами код не помешает им (если, например, вы заверните xExp в блок, который сам определяет значение с именем x, то у вас проблемы).

Другой вариант - возиться с символами. Например, вы можете написать свой собственныйresetLocalAttrs это только стирает испорченные привязки и не затрагивает действительные. Вы также можете попытаться назначить символы самостоятельно, но это короткий путь к безумию, хотя иногда его заставляют идти.

Совсем не круто, согласен. Мы знаем об этом и намерены иногда пытаться решить эту фундаментальную проблему. Однако прямо сейчас наши руки полны исправления ошибок до финальной версии 2.10.0, поэтому мы не сможем решить проблему в ближайшем будущем. UPD. Увидетьhttps://groups.google.com/forum/#!topic/scala-internals/rIyJ4yHdPDU для некоторой дополнительной информации.

Нижняя линия. Плохие вещи случаются, потому что перепутаны привязки. Попробуйте сначала выполнить resetLocalAttrs, и, если он не работает, подготовьтесь к рутинной работе.

Ваш ответ на вопрос