rharter / auto-value-parcel

An Android Parcelable extension for Google's AutoValue.
Apache License 2.0
655 stars 64 forks source link

Generic Serializable causes compilation error with auto-value-parcel #121

Closed tasomaniac closed 1 year ago

tasomaniac commented 7 years ago

I have a field with type Class<T> Class is Serializable so auto-value-parcel tries to put that as a Serializable. This is fine.

The problem is in the CREATOR method. Since it is a static context, it cannot access the type. Either you need to make CREATOR typed or you should remove the type inside Creator. This will cause a warning of course but that can be suppressed.

Right now, it just does not compile. Here is a simple class to reproduce it.

@AutoValue
public abstract class Example<T extends Number> implements Parcelable {

  abstract Class<T> type();

}
ZacSweers commented 4 years ago

@tasomaniac do you have an example snippet of what this should generate?

tasomaniac commented 4 years ago

It's 2 years since I don't use AutoValue anymore. IIRC, it was easy to reproduce with the AutoValue class above. If you look at the generated code, it should be fairly easy.

eamonnmcmanus commented 3 years ago

A user here at Google has run into a similar problem. I was able to reproduce their case by modifying the parameterizedType() test method in AutoValueParcelExtensionTest like this:

    JavaFileObject intf = JavaFileObjects.forSourceString("test.Intf", ""
            + "package test;\n"
            + "import android.os.Parcelable;\n"
            + "interface Intf extends Parcelable {}"
    );
    JavaFileObject source = JavaFileObjects.forSourceString("test.Test", ""
            + "package test;\n"
            + "import android.os.Parcelable;\n"
            + "import com.google.auto.value.AutoValue;\n"
            + "@AutoValue public abstract class Test<T extends Intf> implements Parcelable {\n"
            + "public abstract T tea();\n"
            + "}"
    );
...

Then the test fails like this:

warning: No processor claimed any of these annotations: com.google.auto.value.AutoValue
warning: No processor claimed any of these annotations: javax.annotation.Generated
android/os/Parcel.java:22: warning: [rawtypes] found raw type: java.util.HashMap
  HashMap readHashMap(ClassLoader cl);
  ^
  missing type arguments for generic class java.util.HashMap<K,V>
android/os/Parcel.java:23: warning: [rawtypes] found raw type: java.util.ArrayList
  ArrayList readArrayList(ClassLoader cl);
  ^
  missing type arguments for generic class java.util.ArrayList<E>
android/os/Parcel.java:41: warning: [rawtypes] found raw type: java.util.Map
  void writeMap(Map in);
                ^
  missing type arguments for generic class java.util.Map<K,V>
android/os/Parcel.java:42: warning: [rawtypes] found raw type: java.util.List
  void writeList(List in);
                 ^
  missing type arguments for generic class java.util.List<E>
/SOURCE_OUTPUT/test/AutoValue_Test.java:10: warning: [rawtypes] found raw type: test.AutoValue_Test
  public static final Parcelable.Creator<AutoValue_Test> CREATOR = new Parcelable.Creator<AutoValue_Test>() {
                                         ^
  missing type arguments for generic class test.AutoValue_Test<T>
/SOURCE_OUTPUT/test/AutoValue_Test.java:10: warning: [rawtypes] found raw type: test.AutoValue_Test
  public static final Parcelable.Creator<AutoValue_Test> CREATOR = new Parcelable.Creator<AutoValue_Test>() {
                                                                                          ^
  missing type arguments for generic class test.AutoValue_Test<T>
/SOURCE_OUTPUT/test/AutoValue_Test.java:12: warning: [rawtypes] found raw type: test.AutoValue_Test
    public AutoValue_Test createFromParcel(Parcel in) {
           ^
  missing type arguments for generic class test.AutoValue_Test<T>
/SOURCE_OUTPUT/test/AutoValue_Test.java:13: warning: [rawtypes] found raw type: test.AutoValue_Test
      return new AutoValue_Test(
                 ^
  missing type arguments for generic class test.AutoValue_Test<T>
/SOURCE_OUTPUT/test/AutoValue_Test.java:14: error: non-static type variable T cannot be referenced from a static context
          (T) in.readParcelable(Test.class.getClassLoader())
           ^
/SOURCE_OUTPUT/test/AutoValue_Test.java:18: warning: [rawtypes] found raw type: test.AutoValue_Test
    public AutoValue_Test[] newArray(int size) {
           ^
  missing type arguments for generic class test.AutoValue_Test<T>
/SOURCE_OUTPUT/test/AutoValue_Test.java:19: warning: [rawtypes] found raw type: test.AutoValue_Test
      return new AutoValue_Test[size];
                 ^
  missing type arguments for generic class test.AutoValue_Test<T>

There's a lot of noise from the rawtypes warnings, but there is one error in the bunch:

/SOURCE_OUTPUT/test/AutoValue_Test.java:14: error: non-static type variable T cannot be referenced from a static context
          (T) in.readParcelable(Test.class.getClassLoader())
           ^

The problem is that the static CREATOR field looks like this:

final class AutoValue_Test<T extends Intf> extends $AutoValue_Test<T> {
  public static final Parcelable.Creator<AutoValue_Test> CREATOR = new Parcelable.Creator<AutoValue_Test>() {
    @Override
    public AutoValue_Test createFromParcel(Parcel in) {
      return new AutoValue_Test(
          (T) in.readParcelable(Test.class.getClassLoader())
      );
    }
    @Override
    public AutoValue_Test[] newArray(int size) {
      return new AutoValue_Test[size];
    }
  };

That cast to (T) fails because there is no T in scope (we are inside the initializer of a static field). In the actual user code, there was a second field of type Optional<T> which caused a second error because of the cast to (Optional<T>). I mention that because a fix that detects just the exact T case would not be enough.

With plain <T extends Parcelable>, as in the unmodified parameterizedType() test, there is no cast in the generated code. The relevant code is here:

      TypeName check = property.type instanceof TypeVariableName
          ? ((TypeVariableName) property.type).bounds.get(0)
          : property.type;
      if (!check.equals(PARCELABLE)) {
        block.add("($T) ", property.type);
      }
      block.add("in.readParcelable($T.class.getClassLoader())", autoValueType);

In the exact case of <T extends Parcelable>, this omits the cast, but in every other case it is inserted. A cast is in fact needed for the Optional<T> case, though it would need to be (Optional) or (Optional<?>). Perhaps in the presence of a type parameter, the code should look to see if that parameter is mentioned in the type in question, and if so cast to the erased type?