注釈処理で遭遇したよくわからないエラー※未解決

自作の注釈処理を作ってたらよくわからないエラーに遭遇した。
わかっている範囲でどういうエラーかメモ。

# 環境
**Java**

`openjdk 10.0.2`

**Gradle**

`Gradle 4.10`

**Eclipse**

`4.8.0 (Photon)` ※[Pleiades](http://mergedoc.osdn.jp/)


# 実装
```text:フォルダ構成
|-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` がそれを利用している

```java: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` というクラスを自動生成している


```java:MyInterface.java
package foo;

public interface MyInterface {}
```

- ただのインターフェース

```java: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` インスタンスを受け付ける

```java:ConcreteClass.java
package foo;

public class ConcreteClass extends AutoGeneratedClass {}
```

- 注釈処理が自動生成する `AutoGeneratedClass` を継承して `ConcreteClass` を作る

```java:Foo.java
package foo;

@MyAnnotation(clazz=ConcreteClass.class)
public class Foo {}
```

- `MyAnnotation` を実際に使っているクラス
- `clazz` 要素には `ConcreteClass` の `Class` インスタンスを設定している

## クラス図で整理
![apt.jpg](https://qiita-image-store.s3.amazonaws.com/0/28302/6c95880e-7dc8-5d25-6493-8fea63408005.jpeg)

# 発生する事象
これをコンパイルすると次のようになる。

```
> 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` コマンドのみでコンパイルしてみたが、やはりエラーは発生した

## 型引数の上限の指定をやめると発生しなくなる
```diff:MyAnnotation.java
package foo;

...

public @interface MyAnnotation {
-   Class<? extends MyInterface> clazz();
+   Class<?> clazz();
}
```

- `MyAnnotation` の `clazz` 要素に指定していた `MyInterface` の上限を外す

```text:コンパイル結果
> gradle :foo:compileJava
...
BUILD SUCCESSFUL in 3s
```

- すると、コンパイルが通るようになる
- 謎い

## 上限を自動生成されるクラスにするとエラーにならない
```diff:MyAnnotation.java
package foo;

...
public @interface MyAnnotation {
-   Class<? extends MyInterface> clazz();
+   Class<? extends AutoGeneratedClass> clazz();
}
```

- `clazz` の上限を `MyInterface` から自動生成クラスである `AutoGeneratedClass` に変更する

```text:コンパイル結果
> gradle :foo:compileJava
...
BUILD SUCCESSFUL in 3s
```

- コンパイルは通るようになる
- 謎い

## clazz で指定するのを自動生成されたクラスにしてもエラーにならない
```diff:Foo.java
package foo;

- @MyAnnotation(clazz=ConcreteClass.class)
+ @MyAnnotation(clazz=AutoGeneratedClass.class)
public class Foo {}
```

- `MyAnnotation` の方は `Class<? extends MyInterface>` の上限指定にしている

```text:コンパイル結果
...
BUILD SUCCESSFUL in 4s
```

- こちらもコンパイルは通る
- インターフェース -> 自動生成クラス -> 自作クラス の関係になったときにエラーになるっぽい

## ConcreteClass に依存すること自体は問題ない
```diff:Foo.java
package foo;

- @MyAnnotation(clazz=ConcreteClass.class)
+ @MyAnnotation
public class Foo {
+   private Class<?> clazz = ConcreteClass.class;
}
```

- `MyAnnotation` の `clazz` 要素を削除
- フィールドで `ConcreteClass` を使用
- `ConcreteClass.class` も参照してみる

```text:コンパイル結果
> gradle :foo:compileJava
BUILD SUCCESSFUL in 3s
```

- コンパイルは通る
- 自動生成されたクラスのサブクラスに依存すること自体は問題ないみたい

## 処理対象でないアノテーションで参照してもダメ
```java:OtherAnnotation.java
package foo;

...

@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
public @interface OtherAnnotation {
    Class<? extends MyInterface> clazz();
}
```

- `MyAnnotation` と同じ定義の `OtherAnnotation.java` を作成

```diff:Foo.java
package foo;

- @MyAnnotation(clazz=ConcreteClass.class)
+ @MyAnnotation
+ @OtherAnnotation(clazz=ConcreteClass.class)
public class Foo {}
```

- `Foo` に `OtherAnnotation` を追加
- `MyAnnotation` の方の `clazz` 要素は削除している

```text:コンパイル結果
> 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` のときと同じようにエラーになった
- 注釈処理の対象としたアノテーションかどうかは関係ないっぽい

# 注釈処理と関係ないところで書いててもダメ
```java:OtherClass.java
package foo;

@OtherAnnotation(clazz=ConcreteClass.class)
public class OtherClass {}
```

```diff:Foo.java
package foo;

- @MyAnnotation(clazz=ConcreteClass.class)
+ @MyAnnotation
public class Foo {}
```

- `OtherAnnotation` は１つ前のやつと同じもの
- 注釈処理とは関係ない `OtherClass` の中で `@OtherAnnotation(clazz=ConcreteClass.class)` を使用している

```text:コンパイル結果
> 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 の注釈処理で実行する

# 結論
よくわからない。