S-JIS[2011-09-09/2011-09-11] 変更履歴

ValDefメモ

Scala2.9.1のコンパイラプラグインのValDef(valvar:変数定義)のメモ。


概要

変数の定義(val)は、コンパイラプラグインではValDefで表現されている。
varもValDefで表されており、属性(修飾子)で区別される。

ClassDefのクラス本体(impl.body)内のフィールド定義の他、自分型アノテーション(impl.self)もValDefで表される。


ValDefのプロパティー

備考
symbol Symbol   変数名の情報
symbol.annotations List[AnnotationInfo]   変数に付けられたアノテーション
mods Modifiers c 変数の修飾子。
varの場合はMUTABLEになっている。
name TermName c 変数名
tpt Tree c 変数の型
rhs Tree c 変数の初期値

※「c」はValDefのケースクラスで指定されている内容


クラスにフィールドを追加する例

クラスに「private var zzz$:Int = 123」を追加する例。

クラスにフィールドを追加するには、ClassDefのbodyにフィールド(ValDef)を追加すると共に、クラスのSymbolにフィールドのSymbolを追加する必要がある。
(ちなみに、どこからも参照されないprivateフィールドだと、作っても後続フェーズで削除される。
 また、Scalaではフィールドには基本的にアクセッサーメソッド経由でアクセスするので、アクセッサーメソッドも別途追加するべき)

class MyTransformer(unit: CompilationUnit) extends Transformer {
  override def transform(tree: Tree) = {
    super.transform(preTransform(tree))
  }
  import scala.reflect.generic.Flags

  def preTransform(tree: Tree): Tree = tree match {
    case c @ ClassDef(mods, name, tparams, impl @ Template(parents, self, body)) =>

      // 変数のSymbol
      val vs = c.symbol.newValue("zzz$")                         //変数名
      vs.flags = Flags.PROTECTED | Flags.MUTABLE | Flags.SYNTHETIC //変数の修飾子
      vs.info  = IntClass.tpe                                      //変数の型

      // 変数のValDef
      val vd = ValDef(
        Modifiers(vs.flags),         //変数の修飾子
        vs.name,                     //変数名
        TypeTree(vs.tpe),            //変数の型
        Literal(123).setType(vs.tpe) //変数の初期値
      )
      vd.symbol = vs
      vd.tpe = NoType

      // 新しいClassDefの作成
      val body2 = body :+ vd
      val impl2 = treeCopy.Template(impl, parents, self, body2)
      val c2 = treeCopy.ClassDef(c, mods, name, tparams, impl2)
      c2.symbol.info.decls.enter(vs) //クラスのSymbolに変数のSymbolを追加
      c2 //戻り値

    case tree => tree
  }
}

ValDefを作るにはいくつかオーバーロードが用意されている。[2011-09-11]

      val vd = ValDef(vs, Literal(123).setType(vs.tpe)) //シンボルの情報を引き継いだValDef
      val vd = ValDef(vs) //初期値の無いValDef(rhs(初期値・右辺値)はEmptyTreeになる)

変数のValDefを作る部分は、TreeDSLを使うと短く出来る。

      // 変数のValDef
      val vd = typer.typedValDef{ VAL(vs) === LIT(123) }

VALは変数定義、LITはリテラルのこと。「===」は代入らしい。
VARを使うとMUTABLEフラグが付加されるが、自分で付けている場合はVALでも構わない。
シンボル(symbol)や型(tpe)の情報も自動的に入れてくれるようだ。

参考: adding a field to an object companion class


フィールドを初期化する例

フィールドの型がTextで、newで初期化されていない場合にnewで初期化する例。[2011-09-10]

  val TextTpe = definitions.getClass("org.apache.hadoop.io.Text").tpe

  def preTransform(tree: Tree): Tree = tree match {
    case vd @ ValDef(mods, name, tpt, rhs) if tpt.tpe == TextTpe && vd.symbol.owner.isClass =>
      rhs match {
        case Apply(Select(New(_), _), _) =>
          vd //newだったら何も変更しない

        case _ => //new以外
          val rhs2 = typer.typed { NEW(TypeTree(TextTpe)) }
          treeCopy.ValDef(vd, mods, name, tpt, rhs2)
      }

    case tree => tree
  }

vdのcaseでオーナーがクラスかどうか確認しているのは、ValDefはメソッドの引数定義(セッターメソッドの引数とか)でもマッチする為。
実際に使う際はアノテーションが付いているかどうか等で判断する方がいいと思う。

new(コンストラクター)に引数がある場合は、NEWの第2引数以降が可変長引数なので、そこに指定する。

初期化方法が「_」やnullの場合を捕捉するには以下の様にする。

ソース rhsのマッチ方法
var 変数: Text = _ case EmptyTree =>
var 変数: Text = null case Literal(Constant(c)) if c == null =>
var 変数 = null:Text case Typed(Literal(Constant(c)), tpt) if c == null =>

コンパイラプラグインへ戻る / Scala目次へ戻る / 技術メモへ戻る
メールの送信先:ひしだま