Repository: square/retrofit
Issue: Can't get the genericity?

# my service

```
public interface BaseHttpService {
    @POST("/{requestUrl}")
    <T> void post(@Path("requestUrl") String requestUrl, @QueryMap Map<String, Object> map, Callback<T> callback);
}
```
# my request

```
 BaseHttpService service = restAdapter.create(BaseHttpService.class);
 Map<String, Object> params = new HashMap<>();
 addParams(0, params);
 service.post("xxx", params, callback);
```
# my callback

```
 private Callback<DishListResponse<DishDetail>> callback = new Callback<DishListResponse<DishDetail>>() {
        @Override
        public void success(DishListResponse<DishDetail> dishListResponse, Response response) {
        }

        @Override
        public void failure(RetrofitError retrofitError) {
        }
    };
```
# the exception

```
    java.lang.ClassCastException: java.util.LinkedHashMap cannot be cast to xxx.DishListResponse
            at xxx$2.success(DishKitchenFragment.java:116)
            at retrofit.CallbackRunnable$1.run(CallbackRunnable.java:45)
            at android.os.Handler.handleCallback(Handler.java:615)
            at android.os.Handler.dispatchMessage(Handler.java:92)
            at android.os.Looper.loop(Looper.java:137)
            at android.app.ActivityThread.main(ActivityThread.java:4898)
            at java.lang.reflect.Method.invokeNative(Native Method)
            at java.lang.reflect.Method.invoke(Method.java:511)
            at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:1008)
            at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:775)
            at dalvik.system.NativeStart.main(Native Method)
```
# but change callback like this , it's ok.

```
@POST("/xxx")
void listDish(@QueryMap Map<String, Object> map, Callback<DishListResponse<DishDetail>> callback);
```


Issue comments:
This will never work because we do not have access to the type information. I will leave the issue open as a reminder to throw an exception when parsing the method in this case.


Pull request review comments:
Not a type variable


Patch that resolved the issue:
diff --git a/retrofit/src/main/java/retrofit/MethodInfo.java b/retrofit/src/main/java/retrofit/MethodInfo.java
index d766030e1a..ff50e6cf42 100644
--- a/retrofit/src/main/java/retrofit/MethodInfo.java
+++ b/retrofit/src/main/java/retrofit/MethodInfo.java
@@ -236,6 +236,9 @@ private void parseResponseType() {
           + factories);
     }
     Type responseType = adapter.responseType();
+    if (Utils.hasTypeVariable(responseType)) {
+      throw methodError("Method response type must not include a type variable.");
+    }
     if (converter == null && responseType != ResponseBody.class) {
       throw methodError("Method response type is "
           + responseType
diff --git a/retrofit/src/main/java/retrofit/Utils.java b/retrofit/src/main/java/retrofit/Utils.java
index 6bec958783..8c8d1c33bc 100644
--- a/retrofit/src/main/java/retrofit/Utils.java
+++ b/retrofit/src/main/java/retrofit/Utils.java
@@ -81,6 +81,33 @@ public static Type getSingleParameterUpperBound(ParameterizedType type) {
     return paramType;
   }
 
+  public static boolean hasTypeVariable(Type type) {
+    if (type instanceof Class<?>) {
+      return false;
+    }
+    if (type instanceof ParameterizedType) {
+      ParameterizedType parameterizedType = (ParameterizedType) type;
+      for (Type typeArgument : parameterizedType.getActualTypeArguments()) {
+        if (hasTypeVariable(typeArgument)) {
+          return true;
+        }
+      }
+      return false;
+    }
+    if (type instanceof GenericArrayType) {
+      return hasTypeVariable(((GenericArrayType) type).getGenericComponentType());
+    }
+    if (type instanceof TypeVariable) {
+      return true;
+    }
+    if (type instanceof WildcardType) {
+      return true;
+    }
+    String className = type == null ? "null" : type.getClass().getName();
+    throw new IllegalArgumentException("Expected a Class, ParameterizedType, or "
+        + "GenericArrayType, but <" + type + "> is of type " + className);
+  }
+
   // This method is copyright 2008 Google Inc. and is taken from Gson under the Apache 2.0 license.
   public static Class<?> getRawType(Type type) {
     if (type instanceof Class<?>) {


Test patch:
diff --git a/retrofit/src/test/java/retrofit/RestAdapterTest.java b/retrofit/src/test/java/retrofit/RestAdapterTest.java
index 07bbca54fd..fa4aedb6a4 100644
--- a/retrofit/src/test/java/retrofit/RestAdapterTest.java
+++ b/retrofit/src/test/java/retrofit/RestAdapterTest.java
@@ -9,6 +9,9 @@
 import java.io.IOException;
 import java.lang.reflect.ParameterizedType;
 import java.lang.reflect.Type;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
 import java.util.concurrent.Future;
 import java.util.concurrent.atomic.AtomicBoolean;
 import org.junit.Rule;
@@ -34,6 +37,14 @@ interface FutureMethod {
   }
   interface Extending extends CallMethod {
   }
+  interface StringService {
+    @GET("/") String get();
+  }
+  interface BoundsService {
+    @GET("/") <T> Call<T> none();
+    @GET("/") <T extends ResponseBody> Call<T> upper();
+    @GET("/") <T> Call<List<Map<String, Set<T[]>>>> crazy();
+  }
 
   @SuppressWarnings("EqualsBetweenInconvertibleTypes") // We are explicitly testing this behavior.
   @Test public void objectMethodsStillWork() {
@@ -99,10 +110,6 @@ class MyCallAdapterFactory implements CallAdapter.Factory {
     assertThat(adapterCalled.get()).isTrue();
   }
 
-  interface StringService {
-    @GET("/") String get();
-  }
-
   @Test public void customReturnTypeAdapter() {
     class GreetingCallAdapterFactory implements CallAdapter.Factory {
       @Override public CallAdapter<?> get(Type returnType) {
@@ -154,7 +161,9 @@ class GreetingCallAdapterFactory implements CallAdapter.Factory {
       example.disallowed("Hi!");
       fail();
     } catch (IllegalArgumentException e) {
-      assertThat(e).hasMessage("CallMethod.disallowed: @Body parameter is class java.lang.String but no converter registered. Either add a converter to the RestAdapter or use RequestBody. (parameter #1)");
+      assertThat(e).hasMessage(
+          "CallMethod.disallowed: @Body parameter is class java.lang.String but no converter registered. "
+              + "Either add a converter to the RestAdapter or use RequestBody. (parameter #1)");
     }
   }
 
@@ -170,7 +179,9 @@ class GreetingCallAdapterFactory implements CallAdapter.Factory {
       example.disallowed();
       fail();
     } catch (IllegalArgumentException e) {
-      assertThat(e).hasMessage("CallMethod.disallowed: Method response type is class java.lang.String but no converter registered. Either add a converter to the RestAdapter or use ResponseBody.");
+      assertThat(e).hasMessage(
+          "CallMethod.disallowed: Method response type is class java.lang.String but no converter registered. "
+              + "Either add a converter to the RestAdapter or use ResponseBody.");
     }
   }
 
@@ -200,4 +211,52 @@ class GreetingCallAdapterFactory implements CallAdapter.Factory {
 
     assertThat(server.takeRequest().getBody().readUtf8()).isEqualTo("Hey");
   }
+
+  @Test public void typeVariableNoBoundThrows() {
+    RestAdapter ra = new RestAdapter.Builder()
+        .endpoint(server.getUrl("/").toString())
+        .converter(new StringConverter())
+        .build();
+    BoundsService example = ra.create(BoundsService.class);
+
+    try {
+      example.none();
+      fail();
+    } catch (IllegalArgumentException e) {
+      assertThat(e).hasMessage(
+          "BoundsService.none: Method response type must not include a type variable.");
+    }
+  }
+
+  @Test public void typeVariableUpperBoundThrows() {
+    RestAdapter ra = new RestAdapter.Builder()
+        .endpoint(server.getUrl("/").toString())
+        .converter(new StringConverter())
+        .build();
+    BoundsService example = ra.create(BoundsService.class);
+
+    try {
+      example.upper();
+      fail();
+    } catch (IllegalArgumentException e) {
+      assertThat(e).hasMessage(
+          "BoundsService.upper: Method response type must not include a type variable.");
+    }
+  }
+
+  @Test public void typeVariableNestedThrows() {
+    RestAdapter ra = new RestAdapter.Builder()
+        .endpoint(server.getUrl("/").toString())
+        .converter(new StringConverter())
+        .build();
+    BoundsService example = ra.create(BoundsService.class);
+
+    try {
+      example.crazy();
+      fail();
+    } catch (IllegalArgumentException e) {
+      assertThat(e).hasMessage(
+          "BoundsService.crazy: Method response type must not include a type variable.");
+    }
+  }
 }