自作の注釈処理を作ってたらよくわからないエラーに遭遇した。
わかっている範囲でどういうエラーかメモ。
環境
Java
openjdk 10.0.2
Gradle
Gradle 4.10
Eclipse
4.8.0 (Photon)
※Pleiades
実装
フォルダ構成
|-settings.gradle
|-build.gradle
|-foo/
| `-src/main/java/
| `-foo/
| |-Foo.java
| |-ConcreteClass.java
| |-MyAnnotation.java
| `-MyInterface.java
|
`-bar/
`src/main/
|-java/
| `-bar/
| `-MyProcessor.java
`-resources/
`-META-INF/services/
`-javax.annotation.processing.Processor
-
foo
,bar
のマルチプロジェクト -
bar
が注釈処理を実装していて、foo
がそれを利用している
MyProcessor.java
package bar;
import java.io.BufferedWriter;
import java.io.IOException;
import java.io.UncheckedIOException;
import java.util.Set;
import javax.annotation.processing.AbstractProcessor;
import javax.annotation.processing.RoundEnvironment;
import javax.lang.model.SourceVersion;
import javax.lang.model.element.TypeElement;
import javax.tools.JavaFileObject;
public class MyProcessor extends AbstractProcessor {
@Override
public boolean process(Set<? extends TypeElement> annotations, RoundEnvironment roundEnv) {
if (!annotations.isEmpty()) {
try {
JavaFileObject javaFileObject = this.processingEnv.getFiler().createSourceFile("foo.AutoGeneratedClass");
try (BufferedWriter writer = new BufferedWriter(javaFileObject.openWriter())) {
writer.write(
"package foo;\n" +
"import foo.MyInterface;\n" +
"public class AutoGeneratedClass implements MyInterface {}"
);
}
} catch (IOException e) {
throw new UncheckedIOException(e);
}
}
return true;
}
@Override
public SourceVersion getSupportedSourceVersion() {
return SourceVersion.latest();
}
@Override
public Set<String> getSupportedAnnotationTypes() {
return Set.of("foo.MyAnnotation");
}
}
-
MyAnnotation
アノテーションを処理対象とした注釈処理を実装 -
MyInterface
インターフェースを実装したAutoGeneratedClass
というクラスを自動生成している
MyInterface.java
package foo;
public interface MyInterface {}
- ただのインターフェース
MyAnnotation.java
package foo;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
public @interface MyAnnotation {
Class<? extends MyInterface> clazz();
}
- 処理対象のアノテーション
-
clazz
要素は、MyInterface
を上限とするClass
インスタンスを受け付ける
ConcreteClass.java
package foo;
public class ConcreteClass extends AutoGeneratedClass {}
- 注釈処理が自動生成する
AutoGeneratedClass
を継承してConcreteClass
を作る
Foo.java
package foo;
@MyAnnotation(clazz=ConcreteClass.class)
public class Foo {}
-
MyAnnotation
を実際に使っているクラス -
clazz
要素にはConcreteClass
のClass
インスタンスを設定している
クラス図で整理
発生する事象
これをコンパイルすると次のようになる。
> gradle :foo:compileJava
> Task :foo:compileJava FAILED
...\foo\src\main\java\foo\ConcreteClass.java:3: エラー: シンボルを見つけられません
public class ConcreteClass extends AutoGeneratedClass {
^
シンボル: クラス AutoGeneratedClass
...\foo\src\main\java\foo\Foo.java:3: エラー: 不適合な型: Class<ConcreteClass>をClass<? extends MyInterface>に変換できません:
@MyAnnotation(clazz=ConcreteClass.class)
^
エラー2個
- 自動生成されるはずの
AutoGeneratedClass
が見つけられずConcreteClass
のコンパイルに失敗している
いろいろ試す
Eclipse だと発生しない
- このプロジェクトをそのまま Eclipse に取り込んで注釈処理を実行すると、なぜかエラーは発生しない。
- 試しに Gradle を使わずに、素の
javac
コマンドのみでコンパイルしてみたが、やはりエラーは発生した
型引数の上限の指定をやめると発生しなくなる
MyAnnotation.java
package foo;
...
public @interface MyAnnotation {
- Class<? extends MyInterface> clazz();
+ Class<?> clazz();
}
-
MyAnnotation
のclazz
要素に指定していたMyInterface
の上限を外す
コンパイル結果
> gradle :foo:compileJava
...
BUILD SUCCESSFUL in 3s
- すると、コンパイルが通るようになる
- 謎い
上限を自動生成されるクラスにするとエラーにならない
MyAnnotation.java
package foo;
...
public @interface MyAnnotation {
- Class<? extends MyInterface> clazz();
+ Class<? extends AutoGeneratedClass> clazz();
}
-
clazz
の上限をMyInterface
から自動生成クラスであるAutoGeneratedClass
に変更する
コンパイル結果
> gradle :foo:compileJava
...
BUILD SUCCESSFUL in 3s
- コンパイルは通るようになる
- 謎い
clazz で指定するのを自動生成されたクラスにしてもエラーにならない
Foo.java
package foo;
- @MyAnnotation(clazz=ConcreteClass.class)
+ @MyAnnotation(clazz=AutoGeneratedClass.class)
public class Foo {}
-
MyAnnotation
の方はClass<? extends MyInterface>
の上限指定にしている
コンパイル結果
...
BUILD SUCCESSFUL in 4s
- こちらもコンパイルは通る
- インターフェース -> 自動生成クラス -> 自作クラス の関係になったときにエラーになるっぽい
ConcreteClass に依存すること自体は問題ない
Foo.java
package foo;
- @MyAnnotation(clazz=ConcreteClass.class)
+ @MyAnnotation
public class Foo {
+ private Class<?> clazz = ConcreteClass.class;
}
-
MyAnnotation
のclazz
要素を削除 - フィールドで
ConcreteClass
を使用 -
ConcreteClass.class
も参照してみる
コンパイル結果
> gradle :foo:compileJava
BUILD SUCCESSFUL in 3s
- コンパイルは通る
- 自動生成されたクラスのサブクラスに依存すること自体は問題ないみたい
処理対象でないアノテーションで参照してもダメ
OtherAnnotation.java
package foo;
...
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
public @interface OtherAnnotation {
Class<? extends MyInterface> clazz();
}
-
MyAnnotation
と同じ定義のOtherAnnotation.java
を作成
Foo.java
package foo;
- @MyAnnotation(clazz=ConcreteClass.class)
+ @MyAnnotation
+ @OtherAnnotation(clazz=ConcreteClass.class)
public class Foo {}
-
Foo
にOtherAnnotation
を追加 -
MyAnnotation
の方のclazz
要素は削除している
コンパイル結果
> gradle :foo:compileJava
> Task :foo:compileJava FAILED
...\foo\src\main\java\foo\ConcreteClass.java:3: エラー: シンボルを見つけられません
public class ConcreteClass extends AutoGeneratedClass {
^
シンボル: クラス AutoGeneratedClass
...\foo\src\main\java\foo\Foo.java:4: エラー: 不適合な型: Class<ConcreteClass>をClass<? extends MyInterface>に変換できません:
@OtherAnnotation(clazz=ConcreteClass.class)
^
エラー2個
-
MyAnnotation
のときと同じようにエラーになった - 注釈処理の対象としたアノテーションかどうかは関係ないっぽい
注釈処理と関係ないところで書いててもダメ
OtherClass.java
package foo;
@OtherAnnotation(clazz=ConcreteClass.class)
public class OtherClass {}
Foo.java
package foo;
- @MyAnnotation(clazz=ConcreteClass.class)
+ @MyAnnotation
public class Foo {}
-
OtherAnnotation
は1つ前のやつと同じもの - 注釈処理とは関係ない
OtherClass
の中で@OtherAnnotation(clazz=ConcreteClass.class)
を使用している
コンパイル結果
> Task :foo:compileJava FAILED
...\foo\src\main\java\foo\ConcreteClass.java:3: エラー: シンボルを見つけられません
public class ConcreteClass extends AutoGeneratedClass {
^
シンボル: クラス AutoGeneratedClass
...\foo\src\main\java\foo\OtherClass.java:3: エラー: 不適合な型: Class<ConcreteClass>をClass<? extends MyInterface>に変換できません:
@OtherAnnotation(clazz=ConcreteClass.class)
^
エラー2個
- 直接の注釈処理対象となるクラス以外であっても、
ConcreteClass.class
をアノテーションの要素で参照している場合はエラーになるっぽい
症状を整理
いろいろ試した結果を整理すると、次のような条件を満たすときにエラーになるっぽい?
- アノテーションの要素で、
Class
インスタンスを指定している - その
Class
インスタンスは、「自作インターフェース -> 注釈処理で自動生成されるクラス -> 自作クラス」という階層関係があるなかの「自作クラス」である - アノテーション側の
Class
インスタンスを受け取る要素の定義が、Class<? extends 自作インターフェース>
となっている - OpenJDK の注釈処理で実行する
結論
よくわからない。