CLOVER🍀

That was when it all began.

Scalaのアノテーションの不思議

Scala Compiler Pluginを書いた時に、Scalaで初めてアノテーションを書いてみましたけれど、実はこの時Scalaでのアノテーションの使い方がよくわからずけっこう悩みました。

んで、ちょっと調べてみたんですけど…まあ、ハッキリしなかったってことは変わらないんですが。

Javaだと、通常アノテーションはこんな形で書きます。とりあえず、なんか設定項目が書きたかったので1つ書いておきました。

import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.TYPE, ElementType.METHOD})
public @interface JavaAnnotation {
    String param();
}

Retentionアノテーションに「RetentionPolicy.RUNTIME」を指定することで、クラスファイルに保持しておき、実行時にリフレクションで参照できるようになります。

これに対して、Scalaアノテーションを書く時には普通のクラスとして宣言します。この時、以下の3種類のクラスまたはトレイトのどれかを親として作成します。

  • scala.annotation.Annotationクラス
  • scala.annotation.StaticAnnotationトレイト
  • scala.annotation.ClassfileAnnotationトレイト

このうち、ClassfileAnnotationトレイトがJavaの「RetentionPolicy.RUNTIME」に対応するらしいのですが、実際には機能しておらず、コンパイルすると警告が出ます。

import scala.annotation.ClassfileAnnotation

class ScalaClassAnnotation(param: String) extends ClassfileAnnotation
[warn] /xxxxx/annotation-example/src/main/scala/ScalaClassAnnotation.scala:3: Implementation restriction: subclassing Classfile does not
[warn] make your annotation visible at runtime.  If that is what
[warn] you want, you must write the annotation class in Java.
[warn] class ScalaClassAnnotation(param: String) extends ClassfileAnnotation
[warn]       ^
[warn] one warning found

要するに、「実行時に見れるようにしたかったら、Javaで書けよ!」と言われているわけですな。というわけで、現時点のScalaではClassfileAnnotationは使えませんと。

まあ、Scalaで実行時に参照可能なアノテーションは定義できないことは、コップ本や獏本にも書かれていたので、その点はいいのですが…

問題は、残った2つの使い分けです。

StaticAnnotationトレイトを使わないと、コンパイラから見えないかというとそういうわけでもなく…それは前回実証済ですし。標準ライブラリを見てみるとScalaで定義されているアノテーションは圧倒的にStaticAnnotationトレイトのサブクラスが多いのですが、BeanInfoのようなコンパイル時に作用するアノテーションがAnnotationクラスのサブクラスとして定義されていたりするので、ますます意味不明です。

ちなみに、獏本にはStaticAnnotationトレイトの説明は、こんな感じで書かれていました。


Javaとの対応:
staticフィールド、@Target(ElementType.TYPE)

説明:
コンパイル単位にまたがって見える必要があり「静的な」メタデータを定義するアノテーションの親トレイト

…なんのこっちゃい。

雰囲気的にはStaticAnnotationトレイトをメインに考えるのが良さそうですが、それでいいのかどうかよくわかりませんね。

この辺りの情報は、Webでも少ない感じです。まあ、実行時に見えないという制限があるくせして、Scalaアノテーションを書こうなんて人、あんまりいませんよね…。

一応、AnnotationクラスおよびStaticAnnotationトレイトを継承して作成したアノテーションのサンプルを載せておきます。

import scala.annotation.Annotation

class ScalaAnnotation(param: String) extends Annotation
import scala.annotation.StaticAnnotation

class ScalaStaticAnnotation(param: String) extends StaticAnnotation

標準ライブラリで、StaticAnnotationトレイトのサブクラスは、以下になります。


BeanProperty
BooleanBeanProperty
SerialVersionUID
beanGetter
beanSetter
cloneable
cpsParam
deprecated
deprecatedName
elidable
field
getter
implicitNotFound
inline
native
noinline
param
remote
serializable
setter
specialized
strictfp
switch
tailrec
throws
transient
uncheckedStable
uncheckedVariance
varargs
volatile

また、StaticAnnotationトレイトはAnnotationクラスのサブクラスであり、Annotationクラスのみを継承したアノテーションは以下になります。


BeanDescription
BeanDisplayName
BeanInfo
BeanInfoSkip
TypeConstraint
unchecked

う〜ん、使い分けがわからんぞ…。