auto-value-parcel icon indicating copy to clipboard operation
auto-value-parcel copied to clipboard

Generic Serializable causes compilation error with auto-value-parcel

Open tasomaniac opened this issue 7 years ago • 3 comments

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();

}

tasomaniac avatar Jul 03 '17 14:07 tasomaniac

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

ZacSweers avatar Jan 19 '20 06:01 ZacSweers

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.

tasomaniac avatar Jan 19 '20 08:01 tasomaniac

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?

eamonnmcmanus avatar Apr 30 '21 15:04 eamonnmcmanus