S-JIS[2011-09-09/2011-09-11] 変更履歴
Scala2.9.1のコンパイラプラグインのValDef(val・var:変数定義)のメモ。
|
変数の定義(val)は、コンパイラプラグインではValDefで表現されている。
varもValDefで表されており、属性(修飾子)で区別される。
ClassDefのクラス本体(impl.body)内のフィールド定義の他、自分型アノテーション(impl.self)も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 => |