S-JIS[2011-09-10/2011-09-12] 変更履歴
Scala2.9.1のコンパイラプラグインのDefDef(def: メソッド定義)のメモ。
|
メソッドの定義(def)は、コンパイラプラグインではDefDefで表現されている。
値 | 備考 | ||
---|---|---|---|
symbol | Symbol | メソッド名の情報 | |
mods | Modifiers | c | メソッドの修飾子。 |
name | TermName | c | メソッド名 |
tparams | List[TypeDef] | c | 型引数 |
vparamss | List[List[ValDef]] | c | 引数リスト(Scalaでは引数のリストを複数持てるので、変数定義のListのListになっている) |
tpt | Tree | c | メソッドの戻り値の型 |
rhs | Tree | c | メソッド本体 |
※「c」はDefDefのケースクラスで指定されている内容
zzzという自分で追加したフィールドにアクセスするゲッターメソッド「def zzz:Int = zzz
」とセッターメソッド「def
zzz_=(x$1: Int):Unit = zzz = x$1
」を追加する例。
クラスにメソッドを追加するには、(フィールド(ValDef)と同様に、)ClassDefのbodyにDefDefを追加すると共に、クラスのSymbolに メソッドのSymbolを追加する必要がある。
この例ではTreeDSLを使用している。
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)) =>
atOwner(c.symbol) { //対象クラスをオーナーにする
val typer2 = typer.atOwner(currentOwner)
// フィールドのSymbol
// val vname = "zzz " //末尾にスペース付き
val vname = nme.getterToLocal("zzz")
val vs = currentOwner.newValue(vname)
vs.flags = MUTABLE | PRIVATE | LOCAL | TRIEDCOOKING | SYNTHETIC
vs.info = IntClass.tpe
// フィールドのValDef
val vd = typer2.typedValDef {
VAR(vs) === LIT(0)
}
currentOwner.info.decls.enter(vs) //クラスSymbolへの新フィールドの追加
// ゲッターメソッド // val gname = "zzz" // val gname = vs.name.toString.trim val gname = nme.getterName(vs.name) val gs = currentOwner.newMethod(gname, vs.pos) // val gs = vs.newGetter; gs.flags &= ~(PRIVATE | LOCAL) gs.flags |= ACCESSOR | TRIEDCOOKING | SYNTHETIC gs.info = NullaryMethodType(vs.tpe) val gd = typer2.typedDefDef { DEF(gs) === { This(currentOwner) DOT vs } } currentOwner.info.decls.enter(gs)
// セッターメソッド // val sname= "zzz_$eq" // val sname = scala.reflect.NameTransformer.encode("zzz_=") val sname = nme.getterToSetter(gs.name) val ss = currentOwner.newMethod(sname, vs.pos) ss.flags |= ACCESSOR | SYNTHETIC ss.info = MethodType(ss.newSyntheticValueParams(List(vs.tpe)), UnitClass.tpe) val sd = typer2.typedDefDef { DEF(ss) === { (This(currentOwner) DOT vs) === ss.ARG(0) } } currentOwner.info.decls.enter(ss)
// Treeへの反映 val body2 = body ::: List(vd, gd, sd) val impl2 = treeCopy.Template(impl, parents, self, body2) val c2 = treeCopy.ClassDef(c, mods, name, tparams, impl2) c2 //戻り値 }
case tree => tree } }
参考: adding a field to an object companion class
まず、(Transformerの)atOwnerを使って、クラスのSymbolをオーナーにしてみた。
atOwnerを呼び出すと、currentOwnerという変数が指定したSymbolになる。
(ただそれだけのようなので、atOwnerを使わずc.symbol
を直接使っても問題なさそう)
そして、typerのatOwnerを使って新しいtyperを用意している。[2011-09-11]
typerはContextという内部状態を持っていて、クラスや変数等を参照できるスコープのような役割を果たしている模様。
Typer#typedなんちゃらというメソッドは、必要な情報をコピーする他に、識別子(変数)をContextのスコープでアクセス可能かどうかという判定もしている。
例えば(トップレベルクラスの)ClassDefが呼ばれた時のスコープはそのクラスが属するパッケージとなっている。
typer2のオーナーはcurrentOwner(すなわち対象クラス)なので、スコープはそのクラス内となる。
上記の例でtyper.typed(typedDefDef)を使うと以下のようなエラーが発生する。
フィールドの属性 エラー内容 PRIVATE | LOCAL
PRIVATEException in thread "main" scala.tools.nsc.symtab.Types$TypeError: variable フィールド in class クラス cannot be accessed in クラス
typerのスコープはパッケージなので、クラス内のフィールドにアクセスできない…ということなのかな?
typer2.typedを使うとスコープがクラスなので、エラーにならない。
フィールド名には、末尾にスペースを付けている。[2011-09-11]
これは、メソッド名と全く同じ名前のフィールドは定義できないので、フィールドとメソッドのどちらかを別の名前にする必要がある為。
アクセッサーメソッド(ゲッター・セッター)によってアクセスするフィールドは他からアクセスできる必要が無い(アクセスさせない)ので、フィールド名の方を変化させている。
nmeというオブジェクトのgetterToLocalメソッドを使うと、スペース付きの名前に変換してくれる。
(この「Local」はメソッド内のローカル変数のことではなく、フィールド属性のLOCAL(つまりクラス内ローカル)という意味だと思う)
フィールドの属性(修飾子)は通常のフィールドだと「private[this]
」になっているようなので、それに合わせている。[/2011-09-11]
「[this]」の部分は、フィールド属性のLOCALで表す。
そして、クラスのシンボル情報のdeclsへのフィールドの登録を、メソッド定義より前の段階で行っている。
メソッド定義より前に登録しておかないと、メソッド定義の中でフィールドを見つけられない為。
(最後の方で新たに作るClassDefは元のClassDefの情報が引き継がれるので、先に登録しても問題ない)
メソッドのSymbolは「newMethod」で生成する。
(第2引数はソース内の位置を表すpos。省略するとNoPositionになる。[2011-09-12]
なるべくposを渡す方が良い。後続フェーズでエラーになったときにその位置を表示してくれるから。
上記の例ではフィールドのposを渡しているが、フィールド自体も新規作成だからposはNoPositionなので意味が無いけど^^;)
ゲッターメソッド名はフィールド名と同じ。(ただしスペースは除去する。nme.getterNameで除去してくれる)[/2011-09-11]
ゲッターの場合は「newGetter」というメソッドもあるのだが、元となる変数の属性(privateとか)を引き継ぐので、今回のパターンでは使えない。
(PRIVATEとかLOCALとかのフラグをリセットしてやれば使えるけど)
セッターの場合は「newSetter」というメソッドは無いが、nme.getterToSetterでゲッターメソッド名からセッターメソッド名を生成できる。
メソッドのシンボル情報(info)には、メソッドの引数の型と戻り値の型を指定する。
引数リスト自体が無い(メソッド名の後ろに丸括弧を付けない)場合はNullaryMethodTypeを使って戻り型のみ指定する。
MethodTypeの第1引数は引数リストのリスト。Scalaでは複数の引数リストを持つことが出来るので、こうなっているのだろう。
引数リストは引数(変数)のSymbolのリストだが、newSyntheticValueParamsを使うと引数名は自動的に付けてくれる(x$1とかになる)。
メソッド本体はtypeDefDefの中にTreeDSLを使って書いていくので、見ただけで何となく分かると思う。
ARGはメソッドの引数名を取得するDSL。「ss.ARG(0)
」は、メソッドの第1引数を表す。
TreeDSLでフィールドを指定する際、vs(Symbol型)でなくvs.name(Name型)を使うと、同名のフィールドとメソッドを区別できなくてエラーになる。(フィールド名にスペースが入っていない名前を使う場合)
val gd = typer.typedDefDef { DEF(gs) === { This(currentOwner) DOT vs.name } }
Exception in thread "main" scala.tools.nsc.symtab.Types$TypeError: ambiguous reference to overloaded definition, both method zzz in class クラス名 of type => Int and variable zzz in class クラス名 of type Int match expected type ? at scala.tools.nsc.typechecker.Contexts$Context.ambiguousError(Contexts.scala:332)
セッターメソッドの本体を、代入からフィールドのsetメソッド呼び出しに変える例。[2011-09-11]
「def v1_=(arg: Text) = this.v1 = arg
」
↓
「def v1_=(arg: Text) = this.v1.set(arg)
」(フィールドv1は別途インスタンス生成されている前提)
// 戻り型がUnitで引数がTextひとつのアクセッサーメソッド case dd @ DefDef(mods, name, tparams, vparamss @ List(List(ValDef(_, _, vtpt, _))), tpt, _) if tpt.tpe == UnitClass.tpe && vtpt.tpe == TextTpe && mods.hasFlag(ACCESSOR) => val cs = dd.symbol.owner //クラスのSymbol val ds = dd.symbol //メソッドのSymbol //対象フィールド val fn = nme.getterToLocal(nme.setterToGetter(name)) val fs = cs.info.decls.lookup(fn) val rhs2 = typer.atOwner(ds).typed { This(cs) DOT fs DOT "set" APPLY (ds.ARG(0)) } treeCopy.DefDef(dd, mods, name, tparams, vparamss, tpt, rhs2)
dd.symbolがメソッド定義のSymbolなので、dd.symbol.ownerはクラスのSymbolになる。
セッターメソッドの更新対象フィールドはクラスのSymbolの中から探す(lookup)。
戻り型がUnitで中身が空のメソッドは、TreeDSLでは以下のように書く。[2011-09-11]
「def メソッド(引数…): Unit = {}
」
val cs = クラスのSymbol val ds = cs.newMethod("メソッド名") ds.flags |= SYNTHETIC ds.info = MethodType(List(引数のSymbol,…), UnitClass.tpe) cs.info.decls.enter(ds) val dd = typer.atOwner(ds).typedDefDef { //× DEF(ds) === BLOCK() //× DEF(ds) === EmptyTree //× DEF(ds) === TypeTree(UnitClass.tpe) // DEF(ds) === LIT(()) DEF(ds) === UNIT }
LITはリテラル定義。
UNITは「LIT(())」として定義されている。(「()」はUnitの唯一の値(定数))
Javaのstaticメソッドを呼び出すには、該当クラスをオブジェクト(Module)として取得し、それに対してApplyする。[2011-09-11]
「Text.writeString(out, this.s)
」の例。
val TextModule = definitions.getModule("org.apache.hadoop.io.Text")
val cs = 自分のクラスを表すSymbol
val s = フィールドを表すSymbol
val out = DataOutputの変数を表すSymbol
val rhs = typer.atOwner(cs).typed {
Ident(TextModule) DOT "writeString" APPLY (Ident(out), This(cs) DOT s)
}