RetorofitでGenericsを使おうとしてParameter type must not include a type variable or wildcardが発生した時の記録

## やりたかったこと

サーバーにログを送信するAPI。
ログの種類によってPayloadが変わるRetorofitのIFをジェネリクスにして楽したい。

```kotlin
interface SampleApi {
    @POST("log/")
    suspend fun <T : Payload> postLog(
        @Body
        log: LogData<T>
    ): Response<Unit>
}

@JsonClass(generateAdapter = true)
data class LogData<T : Payload>(
    @Json(name = "event_type")
    val eventType: String,
    @Json(name = "payload")
    val payload: T
)

interface Payload {
    val id: String
}

@JsonClass(generateAdapter = true)
data class NotificationPayload(
    @Json(name = "message")
    val message: String,
    @Json(name = "id")
    override val id: String
): Payload


@JsonClass(generateAdapter = true)
data class AppLunchPayload(
    @Json(name = "application_id")
    val applicationId: Double,
    @Json(name = "id")
    override val id: String
): Payload
```
## 問題
しかしこのAPIを呼び出すとなんと実行時に例外が発生してしまう。
どうやらBodyのデータでGenericsを指定している部分が良くないらしい...

```
java.lang.IllegalArgumentException: Parameter type must not include a type variable or wildcard: com.example.retorofit.model.LogData<T> (parameter #1)
    for method SampleApi.postLog
	at retrofit2.Utils.methodError(Utils.java:56)
	at retrofit2.Utils.methodError(Utils.java:44)
	at retrofit2.Utils.parameterError(Utils.java:68)
	at retrofit2.RequestFactory$Builder.validateResolvableType(RequestFactory.java:827)
	at retrofit2.RequestFactory$Builder.parseParameterAnnotation(RequestFactory.java:781)
	at retrofit2.RequestFactory$Builder.parseParameter(RequestFactory.java:335)
	at retrofit2.RequestFactory$Builder.build(RequestFactory.java:213)
	at retrofit2.RequestFactory.parseAnnotations(RequestFactory.java:67)
	at retrofit2.ServiceMethod.parseAnnotations(ServiceMethod.java:26)
	at retrofit2.Retrofit.loadServiceMethod(Retrofit.java:235)
	at retrofit2.Retrofit$1.invoke(Retrofit.java:177)
	at jdk.proxy3/jdk.proxy3.$Proxy16.postLog(Unknown Source)
	at 
 ...
 ```

## 解決策
RetrofitのIFでジェネリック型ではなく実装クラスを指定する。


```kotlin
public interface SampleApi {
    
    @POST("log/")
    suspend fun postNotificationLog(
        @Body
        log: LogData<NotificationPayload>
    ): Response<Unit>

    @POST("log/")
    suspend fun postAppLunchLog(
        @Body
        log: LogData<AppLunchPayload>
    ): Response<Unit>

    /*
    @POST("log/")
    suspend fun <T : Payload> postLog(
        @Body
        log: LogData<T>
    ): Response<Unit>
    */
}
```
無事例外が発生しなくなった。めでたしめでたし

## おまけ: 解決するまでにやったこと
### JvmSuppressWildcardsを指定する

ネット上にいくつか先人がおり `@JvmSuppressWildcards` を使うと良いと書いてある。
kotlinからjavaコードに変換される際、ジェネリクスがwildcardに変換されてしまうことがあるようだ。

https://stackoverflow.com/questions/57375401/parameter-type-must-not-include-a-type-variable-or-wildcard

https://blog.keithyokoma.dev/entry/2017/06/30/181103

`@JvmSuppressWildcards` を使うようにコードを書き直してみたが呼び出し時の例外は解消されない。困った。

```kotlin
interface SampleApi {
    @POST("log/")
    @JvmSuppressWildcards
    suspend fun <T : Payload> postLog(
        @Body
        log: LogData<T>
    ): Response<Unit>
}

@JsonClass(generateAdapter = true)
@JvmSuppressWildcards
data class LogData<T : Payload>(
    @Json(name = "event_type")
    val eventType: String,
    @Json(name = "payload")
    val payload: T
)

interface Payload {
    val id: String
}

@JsonClass(generateAdapter = true)
data class NotificationPayload(
    @Json(name = "message")
    val message: String,
    @Json(name = "id")
    override val id: String
): Payload


@JsonClass(generateAdapter = true)
data class AppLunchPayload(
    @Json(name = "application_id")
    val applicationId: Double,
    @Json(name = "id")
    override val id: String
): Payload
```

#### Javaコードを確認
AndroidStudioでkotlinからjavaコードが生成できるのでどのようなコードになっているか見てみることにした。
wildcardにはなっていない :thinking: 
ということは`type variable`ということになる
```java

public interface SampleApi {
   @POST("log/")
   @JvmSuppressWildcards
   @Nullable
   Object postLog(@Body @NotNull LogData var1, @NotNull Continuation var2);
}

@JvmSuppressWildcards
public final class LogData {
   @NotNull
   private final String eventType;
   @NotNull
   private final Payload payload;

   @NotNull
   public final String getEventType() {
      return this.eventType;
   }

   @NotNull
   public final Payload getPayload() {
      return this.payload;
   }

   public LogData(
       @Json(name = "event_type") 
       @NotNull 
       String eventType, 
       
       @Json(name = "payload") 
       @NotNull 
       Payload payload
    ) {
      ...
   }

   @NotNull
   public final String component1() {
      return this.eventType;
   }

   @NotNull
   public final Payload component2() {
      return this.payload;
   }

   ...
}
```

#### Retrofitのソースコードを見てみる
デバッグをしながらRetrofitのソースコードを追ってみるとtype=TがTypeVariableと判定されエラーになっていた。

```java
package retrofit2;
final class RequestFactory {
    private void validateResolvableType(int p, Type type) {
        if (Utils.hasUnresolvableType(type)) {
            throw Utils.parameterError(this.method, p, "Parameter type must not include a type variable or wildcard: %s", new Object[]{type});
        }
    }
}
```

```java
package retrofit2;
final class Utils {
    static boolean hasUnresolvableType(@Nullable Type type) {
        if (type instanceof Class) {
            return false;
        } else if (type instanceof ParameterizedType) {
            ParameterizedType parameterizedType = (ParameterizedType) type;
            Type[] var2 = parameterizedType.getActualTypeArguments();
            int var3 = var2.length;

            for (int var4 = 0; var4 < var3; ++var4) {
                Type typeArgument = var2[var4];
                if (hasUnresolvableType(typeArgument)) {
                    return true;
                }
            }

            return false;
        } else if (type instanceof GenericArrayType) {
            return hasUnresolvableType(((GenericArrayType) type).getGenericComponentType());
        } else if (type instanceof TypeVariable) {
            return true;
        } else if (type instanceof WildcardType) {
            return true;
        } else {
            String className = type == null ? "null" : type.getClass().getName();
            throw new IllegalArgumentException("Expected a Class, ParameterizedType, or GenericArrayType, but <" + type + "> is of type " + className);
        }
    }
}
```

#### TypeVariableとは？？？

> TypeVariableは、型変数の種類の共通のスーパー・インタフェースです。型変数は、このパッケージで指定されているように、リフレクト・メソッドにより必要とされるときにはじめて作成されます。型変数tが型(つまり、クラス、インタフェース、あるいは注釈型) Tにより参照される場合、TはTを囲むn番目のクラスにより宣言されます(JLS 8.1.2を参照)。

https://docs.oracle.com/javase/jp/8/docs/api/java/lang/reflect/TypeVariable.html

とても難解だ...

とりあえずRetrofitはジェネリクス型はパラメーターとして利用できないということはわかった。`<T>`を使わない方法でAPIのIFを書くことにした。