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

ClassDefメモ

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]
parents List[Tree] 親クラス・トレイト
self ValDef 自分型注釈
body List[Tree] クラス本体
java.lang.Object with ScalaObject {
  def this(): Sample1[A] = {
    Sample1.super.this();
    ()
  }
}

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つのクラスに複数のアノテーションが付けられるので、リストになっている。

AnnotaionInfoの主なメソッド
備考
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


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