11

私は Scala 組み込み DSL で作業しており、マクロは私の目的を達成するための主要なツールになりつつあります。入力マクロ式のサブツリーを結果のマクロ式に再利用しようとすると、エラーが発生します。状況は非常に複雑ですが、(願わくば) 理解できるように単純化しました。

次のコードがあるとします。

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

ここで、'transform' は関連するマクロです。まったく何もしないように見えるかもしれませんが、実際には、表示されているブロックを次の式に変換しています。

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 の raw ツリーを出力すると、Ident(newTermName("x"))が得られます。これは、RHS の場合に設定されているものとまったく同じです。式は変更される可能性があるため (x の代わりに x+2 など)、これは有効な解決策ではありません。私がやりたいことは、「x」の意味を変更しながら xExp ツリー (xExp コメントを参照) を再利用することです (これは入力式の定義ですが、出力式ではケース 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 が提供するよりも多くの情報が含まれていることは明らかです。「x」がケース変数の役割を果たせるようにするために、そのxExpをきれいにするにはどうすればよいですか? このエラーの全体像を説明できる人はいますか?

PS:私はTreeApiの代替* メソッド ファミリを使用しようとして失敗しましたが、その意味を理解するための基本が欠けています。

4

1 に答える 1

22

入力式を逆アセンブルし、別の方法で再アセンブルすることは、マクロロジーの重要なシナリオです (これは、reifyマクロの内部で行うことです)。しかし残念ながら、現時点では特に簡単ではありません。

問題は、マクロの入力引数が既に型チェックされているマクロの実装に到達することです。これは祝福でもあり、呪いでもあります。

特に興味深いのは、引数に対応するツリー内の変数バインディングが既に確立されているという事実です。これは、すべてのIdentおよびSelectノードのsymフィールドが入力され、これらのノードが参照する定義を指すことを意味します。

以下は、シンボルがどのように機能するかの例です。私の講演の 1 つから印刷物をコピーして貼り付けます (私の講演のほとんどの情報は現在廃止されているため、ここではリンクを提供しませんが、この特定の印刷物には永続的な有用性があります)。

>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 フェーズの後にツリーをダンプするようコンパイラーに要求し、ツリーに割り当てられたシンボルの一意の ID (存在する場合) を出力します。

結果のプリントアウトでは、識別子が対応する定義にリンクされていることがわかります。たとえばValDef("x", ...)、メソッド foo のパラメーターを表す は、id=9529 のメソッド シンボルを定義します。一方、Ident("x")メソッドの本体では、そのsymフィールドが同じシンボルに設定され、バインディングが確立されます。

さて、バインディングが scalac でどのように機能するかを見てきました。ここで、基本的な事実を紹介する絶好の機会です。

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ますが、対応する Ident のフィールドをクリーンアップしません。後続の型チェックでは、新しく作成された ValDef に新しいシンボルが割り当てられますが、結果に逐語的にコピーされる元の Ident には触れません。

型チェックの後、元の Ident はもはや存在しないシンボルを指しており (これはまさにエラー メッセージが言っていたことです:))、バイトコード生成中にクラッシュを引き起こします。

では、エラーを修正するにはどうすればよいでしょうか。残念ながら、簡単な答えはありません。

1 つのオプションはc.resetLocalAttrs、特定の AST ノード内のすべてのシンボルを再帰的に消去する を利用することです。後続の型チェックは、生成したコードがそれらを台無しにしないことを許可されたバインディングを再確立します (たとえば、x という名前の値を定義するブロックで xExp をラップすると、問題が発生します)。

別のオプションは、シンボルをいじることです。たとえば、resetLocalAttrs破損したバインディングのみを消去し、有効なバインディングには触れない独自のバインディングを作成できます。自分でシンボルを割り当てようとすることもできますが、それは狂気への短い道ですが、時にはそれを歩まなければならないこともあります.

まったくクールではありません、私は同意します。私たちはそれを認識しており、この根本的な問題を時々修正しようとするつもりです. ただし、現時点では、最終的な 2.10.0 リリース前のバグ修正で手一杯です。そのため、近い将来、この問題に対処することはできません。更新。追加情報については、 https://groups.google.com/forum/#!topic/scala-internals/rIyJ4yHdPDUを参照してください。


結論。バインディングが台無しになるため、悪いことが起こります。最初に resetLocalAttrs を試してみてください。うまくいかない場合は、雑用の準備をしてください。

于 2012-06-26T15:18:35.060 に答える