S-JIS[2011-09-02/2011-09-08] 変更履歴
Scala2.9.1のコンパイラプラグインのClassDef(class:クラス定義)のメモ。
classで定義されたクラスは、コンパイラプラグインではClassDefで表現されている。
override def apply(unit: CompilationUnit): Unit = {
printClass(unit.body)
}
def printClass(tree: Tree) {
for (c @ ClassDef(mods, name, tparams, impl) <- tree) {
println("symbol=" + c.symbol)
println("sym.ann=" + c.symbol.annotations)
println("mods=" + mods)
println("name=" + name)
println("type-params=" + tparams)
println("impl=" + impl)
val Template(parents, self, body) = impl
println("impl.parents=" + parents)
println("impl.self =" + self)
println("impl.body =" + body)
}
}
例として、以下のようなソースをコンパイルしてみる。
@Deprecated private class Sample1[A]
| 値 | 備考 | 例 | ||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|
| symbol | Symbol | クラス名の情報 | class Sample1 |
|||||||||
symbol.annotations |
List[AnnotationInfo] | クラスに付けられたアノテーション | List(java.lang.Deprecated) |
|||||||||
| mods | Modifiers | クラスの修飾子 | Modifiers(private, , Map(4 ->
source-Sample1.scala,line-2,offset=13)) |
|||||||||
| name | TypeName | クラス名 | Sample1 |
|||||||||
| tparams | List[TypeDef] | 型引数の定義 | List(type A>: Nothing <: Any) |
|||||||||
| impl | Template | クラス定義の中身[/2011-09-08]
|
java.lang.Object with ScalaObject { |
|||||||||
modsにはannotationsというフィールドがあるのでアノテーション情報を取れそうに思えるのだが、入っていない。(Scala2.9.1)
@Deprecated(Javaの非推奨)や@deprecated(Scalaの非推奨)が付けられたクラスだけを表示する例。
override def newPhase(prev: Phase) = new StdPhase(prev) {
override def name = selfPlugin.name
override def apply(unit: CompilationUnit): Unit = {
printDeprecated(unit.body)
}
}
def printDeprecated(tree: Tree) {
def isDeprecated(c: ClassDef) = c.symbol.annotations.map(_.atp.toString()) exists (name =>
name == "java.lang.Deprecated" || name == "deprecated")
for (c @ ClassDef(_, _, _, _) <- tree if isDeprecated(c)) {
println(c)
}
}
ClassDefのsymbol.annotationsはList[AnnotaionInfo]。1つのクラスに複数のアノテーションが付けられるので、リストになっている。
| 値 | 備考 | |
|---|---|---|
| atp | Type | アノテーションの型(アノテーション名(クラス名)) |
| args | List[Tree] | アノテーションの引数(値) |
| stringArg(index) | Option[String] | 指定位置のアノテーションの値を文字列として返す。 位置が範囲外のときはNoneを返す。 |
| intArg(index) | Option[Int] | 指定位置のアノテーションの値を数値として返す。 位置が範囲外の場合や数値でない場合はNoneを返す。 |
| assocs | List[(Name, ClassfileAnnotArg)] | |
atp.toString()による文字列比較でなく、Symbolを使った比較も出来る。[2011-09-06]
(
ここで言うSymbolはscala.Symbolではなく、global.Symbolのこと)
val JavaDeprecated = definitions.getClass("java.lang.Deprecated")
val ScalaDeprecated = definitions.getClass("scala.deprecated")
def isDeprecated(c: ClassDef) = c.symbol.hasAnnotation(JavaDeprecated) || c.symbol.hasAnnotation(ScalaDeprecated)
クラスにCloseableがミックスインされていたらCloseableを削除してSerializableを付ける例。[2011-09-08]
class MyTransformer(unit: CompilationUnit) extends Transformer {
override def transform(tree: Tree) = {
super.transform(preTransform(tree))
}
val CloseableTpe = definitions.getClass("java.io.Closeable").tpe
def serializableTree = TypeTree(definitions.getClass("java.io.Serializable").tpe)
def preTransform(tree: Tree): Tree = tree match {
case c @ ClassDef(mods, name, tparams, impl @ Template(parents, self, body)) if parents.exists(_.tpe == CloseableTpe) =>
val parent2 = parents.filterNot(_.tpe == CloseableTpe) :+ serializableTree //ミックスインしているトレイトを増減
val impl2 = treeCopy.Template(impl, parent2, self, body)
val c2 = treeCopy.ClassDef(c, mods, name, tparams, impl2)
val info = c2.symbol.info
c2.symbol.info = ClassInfoType(parent2.map(_.tpe), info.decls, info.typeSymbol) //シンボル内のミックスイン情報を更新
c2 //返り値
case tree => tree
}
}
クラス定義で継承している親クラスおよびミックスインしているトレイトは、ClassDefのimpl.parentsで取得できる。
parentsはList[Tree]で、一番先頭が親クラスで残りがトレイト。少なくとも必ずScalaObjectトレイトが入っているようだ。
Listなので、トレイトを表すTreeを削除したり追加したりして新しいListを作り、それを元に新しいTemplateを作り、新しいClassDefを作れば親トレイトが変更できる。
ところが親クラス・トレイトの情報はClassDefのsymbol内でも保持している。
後続のチェック処理ではむしろシンボル内の情報を参照しているようなので、シンボル情報も変更する必要がある。
これが分かるまで一週間くらいかかった…orz