Compiler Pluginを作ってみた

なにげなくScalaコンパイラのソースを眺めていたときに見つけたコード

// src/compiler/scala/tools/nsc/util/trace.scala
object trace {
  def apply[T](msg: String)(value: T): T = {
    println(msg+value)
    value
  }
  // ...
}

なんてことないけど気の効いたコードだ。このコードいいな、とつぶやいたら

という返事があった。おぉlazy val、その発想はなかった!
どうせなら全てのlazy valにStackTrace出力を埋め込むちょっとウザいCompiler Pluginを作ってみよう思い立った。

ところでCompiler Pluginってなによ?

Compiler Pluginへの入門はこの記事が参考になると思う。
http://blog.takeda-soft.jp/blog/show/399

簡単に言うとコンパイルの各フェイズの間でちょっかいを出すコードが書けてしまうすごい機能。
(「lispのマクロに対するOdersky先生の回答だ」みたいなこと言ってた人もいたような気がする)
上の記事ではゼロ除算をコンパイル時に検出するPluginが解説されていたが、Compiler Pluginではコンパイル時に抽象構文木(AST)そのものをいじってしまうこともできる。
そういうPluginの例としてはNonnull-check generatorが紹介されていた。

作ってみた

とりあえず

lazy val hoge = new Hoge()

みたいなのを

lazy val hoge = { Thread.currentThread.getStackTrace foreach println; new Hoge()}

に変換するだけの簡単なCommpilerPluginを作ってみた。

唯一めんどくさそうなのが付加するコードをASTにするところだが、これは[twitter:@xuwei_k]さんが紹介してくれたMkTreeを使えば簡単に得られる。

余談

ここまで「Compiler Pluginって簡単に作れるよ」的な説明をしてきたけど、実際はここに辿りつくまでに大変時間がかかった。というのも何を間違えたかCompiler Pluginを挿し込む場所が、上のコードではparserフェイズの後になっているが、当初typerフェイズの後にしていたからだ。
せっかつくったのでそのコードも載せてみる。

先に挙げたコードとまったく同じ事をしているわけだが、やたら長い。
そしてAST作るのだけでも面倒なのに、さらに各Treeに手で型付けするという苦行(笑。
よっぽどのことがない限り

val runsAfter = List[String]("parser")

をおすすめします。