自作の注釈処理を作ってたらよくわからないエラーに遭遇した。
わかっている範囲でどういうエラーかメモ。
環境
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 の注釈処理で実行する
結論
よくわからない。
