More detailed enum unboxing diagnostics for debugging

Change-Id: I5b0491ca0953741a82e0935c6ea68a4a9ec23b85
diff --git a/src/main/java/com/android/tools/r8/graph/DexClassAndMethod.java b/src/main/java/com/android/tools/r8/graph/DexClassAndMethod.java
index 6ab995d..58862d7 100644
--- a/src/main/java/com/android/tools/r8/graph/DexClassAndMethod.java
+++ b/src/main/java/com/android/tools/r8/graph/DexClassAndMethod.java
@@ -48,6 +48,10 @@
     return getReference().asMethodReference();
   }
 
+  public DexType getParameter(int index) {
+    return getReference().getParameter(index);
+  }
+
   public DexTypeList getParameters() {
     return getReference().getParameters();
   }
diff --git a/src/main/java/com/android/tools/r8/ir/code/Invoke.java b/src/main/java/com/android/tools/r8/ir/code/Invoke.java
index 5793ec1..6fe8c6f 100644
--- a/src/main/java/com/android/tools/r8/ir/code/Invoke.java
+++ b/src/main/java/com/android/tools/r8/ir/code/Invoke.java
@@ -25,6 +25,7 @@
 import com.android.tools.r8.ir.analysis.type.Nullability;
 import com.android.tools.r8.ir.analysis.type.TypeElement;
 import com.android.tools.r8.ir.conversion.DexBuilder;
+import com.android.tools.r8.utils.BooleanUtils;
 import java.util.List;
 import java.util.Set;
 import org.objectweb.asm.Opcodes;
@@ -160,6 +161,11 @@
     return arguments().get(index);
   }
 
+  public Value getArgumentForParameter(int index) {
+    int offset = BooleanUtils.intValue(!isInvokeStatic());
+    return getArgument(index + offset);
+  }
+
   public Value getFirstArgument() {
     return getArgument(0);
   }
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/enums/EnumUnboxer.java b/src/main/java/com/android/tools/r8/ir/optimize/enums/EnumUnboxer.java
index b637199..3a65557 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/enums/EnumUnboxer.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/enums/EnumUnboxer.java
@@ -77,6 +77,13 @@
 import com.android.tools.r8.ir.optimize.enums.EnumInstanceFieldData.EnumInstanceFieldOrdinalData;
 import com.android.tools.r8.ir.optimize.enums.EnumInstanceFieldData.EnumInstanceFieldUnknownData;
 import com.android.tools.r8.ir.optimize.enums.eligibility.Reason;
+import com.android.tools.r8.ir.optimize.enums.eligibility.Reason.IllegalInvokeWithImpreciseParameterTypeReason;
+import com.android.tools.r8.ir.optimize.enums.eligibility.Reason.MissingContentsForEnumValuesArrayReason;
+import com.android.tools.r8.ir.optimize.enums.eligibility.Reason.MissingEnumStaticFieldValuesReason;
+import com.android.tools.r8.ir.optimize.enums.eligibility.Reason.MissingInstanceFieldValueForEnumInstanceReason;
+import com.android.tools.r8.ir.optimize.enums.eligibility.Reason.MissingObjectStateForEnumInstanceReason;
+import com.android.tools.r8.ir.optimize.enums.eligibility.Reason.UnsupportedInstanceFieldValueForEnumInstanceReason;
+import com.android.tools.r8.ir.optimize.enums.eligibility.Reason.UnsupportedLibraryInvokeReason;
 import com.android.tools.r8.ir.optimize.info.MutableFieldOptimizationInfo;
 import com.android.tools.r8.ir.optimize.info.OptimizationFeedback.OptimizationInfoFixer;
 import com.android.tools.r8.ir.optimize.info.OptimizationFeedbackDelayed;
@@ -84,7 +91,6 @@
 import com.android.tools.r8.shaking.AppInfoWithLiveness;
 import com.android.tools.r8.shaking.FieldAccessInfoCollectionModifier;
 import com.android.tools.r8.shaking.KeepInfoCollection;
-import com.android.tools.r8.utils.BooleanUtils;
 import com.android.tools.r8.utils.Reporter;
 import com.android.tools.r8.utils.StringDiagnostic;
 import com.android.tools.r8.utils.collections.ImmutableInt2ReferenceSortedMap;
@@ -338,7 +344,7 @@
         if (singleTarget != null && singleTarget.getReference() == factory.enumMembers.valueOf) {
           // The name data is required for the correct mapping from the enum name to the ordinal in
           // the valueOf utility method.
-          addRequiredNameData(enumType);
+          addRequiredNameData(enumClass);
           continue;
         }
       }
@@ -348,9 +354,9 @@
     eligibleEnums.add(enumType);
   }
 
-  private void addRequiredNameData(DexType enumType) {
+  private void addRequiredNameData(DexProgramClass enumClass) {
     enumUnboxingCandidatesInfo.addRequiredEnumInstanceFieldData(
-        enumType, factory.enumMembers.nameField);
+        enumClass, factory.enumMembers.nameField);
   }
 
   private boolean isUnboxableNameMethod(DexMethod method) {
@@ -507,10 +513,9 @@
   private EnumDataMap analyzeEnumInstances() {
     ImmutableMap.Builder<DexType, EnumData> builder = ImmutableMap.builder();
     enumUnboxingCandidatesInfo.forEachCandidateAndRequiredInstanceFieldData(
-        (enumClass, fields) -> {
-          EnumData data = buildData(enumClass, fields);
+        (enumClass, instanceFields) -> {
+          EnumData data = buildData(enumClass, instanceFields);
           if (data == null) {
-            markEnumAsUnboxable(Reason.MISSING_INSTANCE_FIELD_DATA, enumClass);
             return;
           }
           if (!debugLogEnabled || !debugLogs.containsKey(enumClass.getType())) {
@@ -521,7 +526,7 @@
     return new EnumDataMap(builder.build());
   }
 
-  private EnumData buildData(DexProgramClass enumClass, Set<DexField> fields) {
+  private EnumData buildData(DexProgramClass enumClass, Set<DexField> instanceFields) {
     // This map holds all the accessible fields to their unboxed value, so we can remap the field
     // read to the unboxed value.
     ImmutableMap.Builder<DexField, Integer> unboxedValues = ImmutableMap.builder();
@@ -535,6 +540,7 @@
 
     EnumStaticFieldValues enumStaticFieldValues = staticFieldValuesMap.get(enumClass.type);
     if (enumStaticFieldValues == null) {
+      reportFailure(enumClass, new MissingEnumStaticFieldValuesReason());
       return null;
     }
 
@@ -550,10 +556,16 @@
             continue;
           }
           // We could not track the content of that field. We bail out.
+          reportFailure(
+              enumClass, new MissingObjectStateForEnumInstanceReason(staticField.getReference()));
           return null;
         }
         OptionalInt optionalOrdinal = getOrdinal(enumState);
         if (!optionalOrdinal.isPresent()) {
+          reportFailure(
+              enumClass,
+              new MissingInstanceFieldValueForEnumInstanceReason(
+                  staticField.getReference(), factory.enumMembers.ordinalField));
           return null;
         }
         int ordinal = optionalOrdinal.getAsInt();
@@ -570,6 +582,8 @@
           // We could not track the content of that field. We bail out.
           // We could not track the content of that field, and the field could be a values field.
           // We conservatively bail out.
+          reportFailure(
+              enumClass, new MissingContentsForEnumValuesArrayReason(staticField.getReference()));
           return null;
         }
         assert valuesState.isEnumValuesObjectState();
@@ -600,25 +614,41 @@
     // The ordinalToObjectState map may have holes at this point, if some enum instances are never
     // used ($VALUES unused or removed, and enum instance field unused or removed), it contains
     // only data for reachable enum instance, that is what we're interested in.
-    ImmutableMap.Builder<DexField, EnumInstanceFieldKnownData> instanceFieldBuilder =
-        ImmutableMap.builder();
-    for (DexField instanceField : fields) {
-      EnumInstanceFieldData fieldData =
-          computeEnumFieldData(instanceField, enumClass, ordinalToObjectState);
-      if (fieldData.isUnknown()) {
-        return null;
-      }
-      instanceFieldBuilder.put(instanceField, fieldData.asEnumFieldKnownData());
+    ImmutableMap<DexField, EnumInstanceFieldKnownData> instanceFieldsData =
+        computeRequiredEnumInstanceFieldsData(enumClass, instanceFields, ordinalToObjectState);
+    if (instanceFieldsData == null) {
+      return null;
     }
 
     return new EnumData(
-        instanceFieldBuilder.build(),
+        instanceFieldsData,
         unboxedValues.build(),
         valuesField.build(),
         valuesContents == null ? EnumData.INVALID_VALUES_SIZE : valuesContents.getEnumValuesSize());
   }
 
-  private EnumInstanceFieldData computeEnumFieldData(
+  private ImmutableMap<DexField, EnumInstanceFieldKnownData> computeRequiredEnumInstanceFieldsData(
+      DexProgramClass enumClass,
+      Set<DexField> instanceFields,
+      Int2ReferenceMap<ObjectState> ordinalToObjectState) {
+    ImmutableMap.Builder<DexField, EnumInstanceFieldKnownData> builder = ImmutableMap.builder();
+    for (DexField instanceField : instanceFields) {
+      EnumInstanceFieldData fieldData =
+          computeRequiredEnumInstanceFieldData(instanceField, enumClass, ordinalToObjectState);
+      if (fieldData.isUnknown()) {
+        if (!debugLogEnabled) {
+          return null;
+        }
+        builder = null;
+      }
+      if (builder != null) {
+        builder.put(instanceField, fieldData.asEnumFieldKnownData());
+      }
+    }
+    return builder != null ? builder.build() : null;
+  }
+
+  private EnumInstanceFieldData computeRequiredEnumInstanceFieldData(
       DexField instanceField,
       DexProgramClass enumClass,
       Int2ReferenceMap<ObjectState> ordinalToObjectState) {
@@ -631,7 +661,15 @@
     for (Integer ordinal : ordinalToObjectState.keySet()) {
       ObjectState state = ordinalToObjectState.get(ordinal);
       AbstractValue fieldValue = state.getAbstractFieldValue(encodedInstanceField);
+      if (!fieldValue.isSingleValue()) {
+        reportFailure(
+            enumClass, new MissingInstanceFieldValueForEnumInstanceReason(ordinal, instanceField));
+        return EnumInstanceFieldUnknownData.getInstance();
+      }
       if (!(fieldValue.isSingleNumberValue() || fieldValue.isSingleStringValue())) {
+        reportFailure(
+            enumClass,
+            new UnsupportedInstanceFieldValueForEnumInstanceReason(ordinal, instanceField));
         return EnumInstanceFieldUnknownData.getInstance();
       }
       data.put(ordinalToUnboxedInt(ordinal), fieldValue);
@@ -1096,7 +1134,7 @@
       Value enumValue) {
     assert instanceGet.getField().holder == enumClass.type;
     DexField field = instanceGet.getField();
-    enumUnboxingCandidatesInfo.addRequiredEnumInstanceFieldData(enumClass.type, field);
+    enumUnboxingCandidatesInfo.addRequiredEnumInstanceFieldData(enumClass, field);
     return Reason.ELIGIBLE;
   }
 
@@ -1118,10 +1156,11 @@
     if (singleTarget == null) {
       return Reason.INVALID_INVOKE;
     }
-    DexClass dexClass = singleTarget.getHolder();
-    if (dexClass.isProgramClass()) {
-      if (dexClass.isEnum() && singleTarget.getDefinition().isInstanceInitializer()) {
-        if (code.method().getHolderType() == dexClass.type && code.method().isClassInitializer()) {
+    DexMethod singleTargetReference = singleTarget.getReference();
+    DexClass targetHolder = singleTarget.getHolder();
+    if (targetHolder.isProgramClass()) {
+      if (targetHolder.isEnum() && singleTarget.getDefinition().isInstanceInitializer()) {
+        if (code.context().getHolder() == targetHolder && code.method().isClassInitializer()) {
           // The enum instance initializer is allowed to be called only from the enum clinit.
           return Reason.ELIGIBLE;
         } else {
@@ -1130,28 +1169,28 @@
       }
       // Check that the enum-value only flows into parameters whose type exactly matches the
       // enum's type.
-      int offset = BooleanUtils.intValue(!singleTarget.getDefinition().isStatic());
-      for (int i = 0; i < singleTarget.getReference().getParameters().size(); i++) {
-        if (invoke.getArgument(offset + i) == enumValue) {
-          if (singleTarget.getReference().getParameter(i).toBaseType(factory) != enumClass.type) {
-            return Reason.GENERIC_INVOKE;
-          }
+      for (int i = 0; i < singleTarget.getParameters().size(); i++) {
+        if (invoke.getArgumentForParameter(i) == enumValue
+            && singleTarget.getParameter(i).toBaseType(factory) != enumClass.getType()) {
+          return new IllegalInvokeWithImpreciseParameterTypeReason(singleTargetReference);
         }
       }
       if (invoke.isInvokeMethodWithReceiver()) {
         Value receiver = invoke.asInvokeMethodWithReceiver().getReceiver();
-        if (receiver == enumValue && dexClass.isInterface()) {
+        if (receiver == enumValue && targetHolder.isInterface()) {
           return Reason.DEFAULT_METHOD_INVOKE;
         }
       }
       return Reason.ELIGIBLE;
     }
-    if (dexClass.isClasspathClass()) {
-      return Reason.INVALID_INVOKE;
+
+    if (targetHolder.isClasspathClass()) {
+      return Reason.INVALID_INVOKE_CLASSPATH;
     }
-    assert dexClass.isLibraryClass();
-    DexMethod singleTargetReference = singleTarget.getReference();
-    if (dexClass.type != factory.enumType) {
+
+    assert targetHolder.isLibraryClass();
+
+    if (targetHolder.getType() != factory.enumType) {
       // System.identityHashCode(Object) is supported for proto enums.
       // Object#getClass without outValue and Objects.requireNonNull are supported since R8
       // rewrites explicit null checks to such instructions.
@@ -1159,7 +1198,7 @@
         return Reason.ELIGIBLE;
       }
       if (singleTargetReference == factory.stringMembers.valueOf) {
-        addRequiredNameData(enumClass.type);
+        addRequiredNameData(enumClass);
         return Reason.ELIGIBLE;
       }
       if (singleTargetReference == factory.objectMembers.getClass
@@ -1171,7 +1210,7 @@
           || singleTargetReference == factory.objectsMethods.requireNonNullWithMessage) {
         return Reason.ELIGIBLE;
       }
-      return Reason.UNSUPPORTED_LIBRARY_CALL;
+      return new UnsupportedLibraryInvokeReason(singleTargetReference);
     }
     // TODO(b/147860220): EnumSet and EnumMap may be interesting to model.
     if (singleTargetReference == factory.enumMembers.compareTo) {
@@ -1181,7 +1220,7 @@
     } else if (singleTargetReference == factory.enumMembers.nameMethod
         || singleTargetReference == factory.enumMembers.toString) {
       assert invoke.asInvokeMethodWithReceiver().getReceiver() == enumValue;
-      addRequiredNameData(enumClass.type);
+      addRequiredNameData(enumClass);
       return Reason.ELIGIBLE;
     } else if (singleTargetReference == factory.enumMembers.ordinalMethod) {
       return Reason.ELIGIBLE;
@@ -1189,12 +1228,11 @@
       return Reason.ELIGIBLE;
     } else if (singleTargetReference == factory.enumMembers.constructor) {
       // Enum constructor call is allowed only if called from an enum initializer.
-      if (code.method().isInstanceInitializer()
-          && code.method().getHolderType() == enumClass.type) {
+      if (code.method().isInstanceInitializer() && code.context().getHolder() == enumClass) {
         return Reason.ELIGIBLE;
       }
     }
-    return Reason.UNSUPPORTED_LIBRARY_CALL;
+    return new UnsupportedLibraryInvokeReason(singleTargetReference);
   }
 
   // Return is used for valueOf methods.
@@ -1271,24 +1309,27 @@
     sb.append(System.lineSeparator());
 
     // Print information about how often a given Reason kind prevents enum unboxing.
-    Object2IntMap<Reason> reasonCount = new Object2IntOpenHashMap<>();
+    Object2IntMap<Object> reasonKindCount = new Object2IntOpenHashMap<>();
     debugLogs.forEach(
         (type, reasons) ->
-            reasons.forEach(reason -> reasonCount.put(reason, reasonCount.getInt(reason) + 1)));
-    List<Reason> differentReasons = new ArrayList<>(reasonCount.keySet());
-    differentReasons.sort(
-        (x, y) -> {
-          int freq = reasonCount.getInt(x) - reasonCount.getInt(y);
-          return freq != 0 ? freq : System.identityHashCode(x) - System.identityHashCode(y);
+            reasons.forEach(
+                reason ->
+                    reasonKindCount.put(reason.getKind(), reasonKindCount.getInt(reason) + 1)));
+    List<Object> differentReasonKinds = new ArrayList<>(reasonKindCount.keySet());
+    differentReasonKinds.sort(
+        (reasonKind, other) -> {
+          int freq = reasonKindCount.getInt(reasonKind) - reasonKindCount.getInt(other);
+          return freq != 0
+              ? freq
+              : System.identityHashCode(reasonKind) - System.identityHashCode(other);
         });
-    differentReasons.forEach(
-        x -> {
-          sb.append(x)
-              .append(" (")
-              .append(reasonCount.getInt(x))
-              .append(")")
-              .append(System.lineSeparator());
-        });
+    differentReasonKinds.forEach(
+        reasonKind ->
+            sb.append(reasonKind)
+                .append(" (")
+                .append(reasonKindCount.getInt(reasonKind))
+                .append(")")
+                .append(System.lineSeparator()));
 
     reporter.info(new StringDiagnostic(sb.toString()));
   }
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/enums/EnumUnboxingCandidateAnalysis.java b/src/main/java/com/android/tools/r8/ir/optimize/enums/EnumUnboxingCandidateAnalysis.java
index c45e71a9..62b05a9 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/enums/EnumUnboxingCandidateAnalysis.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/enums/EnumUnboxingCandidateAnalysis.java
@@ -13,6 +13,7 @@
 import com.android.tools.r8.graph.DexProto;
 import com.android.tools.r8.graph.DexType;
 import com.android.tools.r8.ir.optimize.enums.eligibility.Reason;
+import com.android.tools.r8.ir.optimize.enums.eligibility.Reason.UnsupportedStaticFieldReason;
 import com.android.tools.r8.shaking.AppInfoWithLiveness;
 import com.android.tools.r8.shaking.KeepInfoCollection;
 
@@ -70,9 +71,6 @@
       result = false;
     }
     if (!enumHasBasicStaticFields(clazz)) {
-      if (!enumUnboxer.reportFailure(clazz, Reason.UNEXPECTED_STATIC_FIELD)) {
-        return false;
-      }
       result = false;
     }
     return result;
@@ -81,32 +79,37 @@
   // The enum should have the $VALUES static field and only fields directly referencing the enum
   // instances.
   private boolean enumHasBasicStaticFields(DexProgramClass clazz) {
+    boolean result = true;
     for (DexEncodedField staticField : clazz.staticFields()) {
-      if (isEnumField(staticField, clazz.type)) {
+      if (isEnumField(staticField, clazz)) {
         // Enum field, valid, do nothing.
-      } else if (matchesValuesField(staticField, clazz.type, factory)) {
+      } else if (matchesValuesField(staticField, clazz, factory)) {
         // Field $VALUES, valid, do nothing.
       } else if (appView.appInfo().isFieldRead(staticField)) {
         // Only non read static fields are valid, and they are assumed unused.
-        return false;
+        if (!enumUnboxer.reportFailure(
+            clazz, new UnsupportedStaticFieldReason(staticField.getReference()))) {
+          return false;
+        }
+        result = false;
       }
     }
-    return true;
+    return result;
   }
 
-  static boolean isEnumField(DexEncodedField staticField, DexType enumType) {
-    return staticField.getReference().type == enumType
-        && staticField.accessFlags.isEnum()
-        && staticField.accessFlags.isFinal();
+  static boolean isEnumField(DexEncodedField staticField, DexProgramClass enumClass) {
+    return staticField.getType() == enumClass.getType()
+        && staticField.isEnum()
+        && staticField.isFinal();
   }
 
   static boolean matchesValuesField(
-      DexEncodedField staticField, DexType enumType, DexItemFactory factory) {
-    return staticField.getReference().type.isArrayType()
-        && staticField.getReference().type.toArrayElementType(factory) == enumType
-        && staticField.accessFlags.isSynthetic()
-        && staticField.accessFlags.isFinal()
-        && staticField.getReference().name == factory.enumValuesFieldName;
+      DexEncodedField staticField, DexProgramClass enumClass, DexItemFactory factory) {
+    return staticField.getType().isArrayType()
+        && staticField.getType().toArrayElementType(factory) == enumClass.getType()
+        && staticField.isSynthetic()
+        && staticField.isFinal()
+        && staticField.getName() == factory.enumValuesFieldName;
   }
 
   private void removeEnumsInAnnotations() {
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/enums/EnumUnboxingCandidateInfoCollection.java b/src/main/java/com/android/tools/r8/ir/optimize/enums/EnumUnboxingCandidateInfoCollection.java
index f36474d..7d85b8d 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/enums/EnumUnboxingCandidateInfoCollection.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/enums/EnumUnboxingCandidateInfoCollection.java
@@ -81,11 +81,11 @@
     info.addMethodDependency(programMethod);
   }
 
-  public void addRequiredEnumInstanceFieldData(DexType enumType, DexField field) {
+  public void addRequiredEnumInstanceFieldData(DexProgramClass enumClass, DexField field) {
     // The enumType may be removed concurrently map from enumTypeToInfo. It means in that
     // case the enum is no longer a candidate, and dependencies don't need to be recorded
     // anymore.
-    EnumUnboxingCandidateInfo info = enumTypeToInfo.get(enumType);
+    EnumUnboxingCandidateInfo info = enumTypeToInfo.get(enumClass.getType());
     if (info == null) {
       return;
     }
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/enums/eligibility/Reason.java b/src/main/java/com/android/tools/r8/ir/optimize/enums/eligibility/Reason.java
index 2cd2098..5aef5cc 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/enums/eligibility/Reason.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/enums/eligibility/Reason.java
@@ -4,46 +4,238 @@
 
 package com.android.tools.r8.ir.optimize.enums.eligibility;
 
-public class Reason {
-  public static final Reason ELIGIBLE = new Reason("ELIGIBLE");
-  public static final Reason ACCESSIBILITY = new Reason("ACCESSIBILITY");
-  public static final Reason ANNOTATION = new Reason("ANNOTATION");
-  public static final Reason PINNED = new Reason("PINNED");
-  public static final Reason DOWN_CAST = new Reason("DOWN_CAST");
-  public static final Reason SUBTYPES = new Reason("SUBTYPES");
-  public static final Reason MANY_INSTANCE_FIELDS = new Reason("MANY_INSTANCE_FIELDS");
-  public static final Reason GENERIC_INVOKE = new Reason("GENERIC_INVOKE");
-  public static final Reason DEFAULT_METHOD_INVOKE = new Reason("DEFAULT_METHOD_INVOKE");
-  public static final Reason UNEXPECTED_STATIC_FIELD = new Reason("UNEXPECTED_STATIC_FIELD");
-  public static final Reason UNRESOLVABLE_FIELD = new Reason("UNRESOLVABLE_FIELD");
-  public static final Reason CONST_CLASS = new Reason("CONST_CLASS");
-  public static final Reason INVALID_PHI = new Reason("INVALID_PHI");
-  public static final Reason NO_INIT = new Reason("NO_INIT");
-  public static final Reason INVALID_INIT = new Reason("INVALID_INIT");
-  public static final Reason INVALID_CLINIT = new Reason("INVALID_CLINIT");
-  public static final Reason INVALID_INVOKE = new Reason("INVALID_INVOKE");
-  public static final Reason INVALID_INVOKE_ON_ARRAY = new Reason("INVALID_INVOKE_ON_ARRAY");
-  public static final Reason IMPLICIT_UP_CAST_IN_RETURN = new Reason("IMPLICIT_UP_CAST_IN_RETURN");
-  public static final Reason UNSUPPORTED_LIBRARY_CALL = new Reason("UNSUPPORTED_LIBRARY_CALL");
+import com.android.tools.r8.graph.DexField;
+import com.android.tools.r8.graph.DexMethod;
+import com.google.common.collect.ImmutableList;
+
+public abstract class Reason {
+  public static final Reason ELIGIBLE = new StringReason("ELIGIBLE");
+  public static final Reason ACCESSIBILITY = new StringReason("ACCESSIBILITY");
+  public static final Reason ANNOTATION = new StringReason("ANNOTATION");
+  public static final Reason PINNED = new StringReason("PINNED");
+  public static final Reason DOWN_CAST = new StringReason("DOWN_CAST");
+  public static final Reason SUBTYPES = new StringReason("SUBTYPES");
+  public static final Reason MANY_INSTANCE_FIELDS = new StringReason("MANY_INSTANCE_FIELDS");
+  public static final Reason DEFAULT_METHOD_INVOKE = new StringReason("DEFAULT_METHOD_INVOKE");
+  public static final Reason UNRESOLVABLE_FIELD = new StringReason("UNRESOLVABLE_FIELD");
+  public static final Reason CONST_CLASS = new StringReason("CONST_CLASS");
+  public static final Reason INVALID_PHI = new StringReason("INVALID_PHI");
+  public static final Reason NO_INIT = new StringReason("NO_INIT");
+  public static final Reason INVALID_INIT = new StringReason("INVALID_INIT");
+  public static final Reason INVALID_CLINIT = new StringReason("INVALID_CLINIT");
+  public static final Reason INVALID_INVOKE = new StringReason("INVALID_INVOKE");
+  public static final Reason INVALID_INVOKE_CLASSPATH =
+      new StringReason("INVALID_INVOKE_CLASSPATH");
+  public static final Reason INVALID_INVOKE_ON_ARRAY = new StringReason("INVALID_INVOKE_ON_ARRAY");
+  public static final Reason IMPLICIT_UP_CAST_IN_RETURN =
+      new StringReason("IMPLICIT_UP_CAST_IN_RETURN");
   public static final Reason MISSING_INSTANCE_FIELD_DATA =
-      new Reason("MISSING_INSTANCE_FIELD_DATA");
-  public static final Reason INVALID_FIELD_PUT = new Reason("INVALID_FIELD_PUT");
-  public static final Reason INVALID_ARRAY_PUT = new Reason("INVALID_ARRAY_PUT");
-  public static final Reason TYPE_MISMATCH_FIELD_PUT = new Reason("TYPE_MISMATCH_FIELD_PUT");
-  public static final Reason INVALID_IF_TYPES = new Reason("INVALID_IF_TYPES");
+      new StringReason("MISSING_INSTANCE_FIELD_DATA");
+  public static final Reason INVALID_FIELD_PUT = new StringReason("INVALID_FIELD_PUT");
+  public static final Reason INVALID_ARRAY_PUT = new StringReason("INVALID_ARRAY_PUT");
+  public static final Reason TYPE_MISMATCH_FIELD_PUT = new StringReason("TYPE_MISMATCH_FIELD_PUT");
+  public static final Reason INVALID_IF_TYPES = new StringReason("INVALID_IF_TYPES");
   public static final Reason ENUM_METHOD_CALLED_WITH_NULL_RECEIVER =
-      new Reason("ENUM_METHOD_CALLED_WITH_NULL_RECEIVER");
+      new StringReason("ENUM_METHOD_CALLED_WITH_NULL_RECEIVER");
   public static final Reason OTHER_UNSUPPORTED_INSTRUCTION =
-      new Reason("OTHER_UNSUPPORTED_INSTRUCTION");
+      new StringReason("OTHER_UNSUPPORTED_INSTRUCTION");
 
-  private final String message;
-
-  public Reason(String message) {
-    this.message = message;
-  }
+  public abstract Object getKind();
 
   @Override
-  public String toString() {
-    return message;
+  public abstract String toString();
+
+  public static class StringReason extends Reason {
+
+    private final String message;
+
+    public StringReason(String message) {
+      this.message = message;
+    }
+
+    @Override
+    public Object getKind() {
+      return this;
+    }
+
+    @Override
+    public String toString() {
+      return message;
+    }
+  }
+
+  public static class IllegalInvokeWithImpreciseParameterTypeReason extends Reason {
+
+    private final DexMethod invokedMethod;
+
+    public IllegalInvokeWithImpreciseParameterTypeReason(DexMethod invokedMethod) {
+      this.invokedMethod = invokedMethod;
+    }
+
+    @Override
+    public Object getKind() {
+      return getClass();
+    }
+
+    @Override
+    public String toString() {
+      return "IllegalInvokeWithImpreciseParameterType(" + invokedMethod.toSourceString() + ")";
+    }
+  }
+
+  public static class MissingEnumStaticFieldValuesReason extends Reason {
+
+    @Override
+    public Object getKind() {
+      return getClass();
+    }
+
+    @Override
+    public String toString() {
+      return "MissingEnumStaticFieldValues";
+    }
+  }
+
+  public static class MissingContentsForEnumValuesArrayReason extends Reason {
+
+    private final DexField valuesField;
+
+    public MissingContentsForEnumValuesArrayReason(DexField valuesField) {
+      this.valuesField = valuesField;
+    }
+
+    @Override
+    public Object getKind() {
+      return getClass();
+    }
+
+    @Override
+    public String toString() {
+      return "MissingContentsForEnumValuesArray(" + valuesField.toSourceString() + ")";
+    }
+  }
+
+  public static class MissingInstanceFieldValueForEnumInstanceReason extends Reason {
+
+    private final DexField enumField;
+    private final int ordinal;
+    private final DexField instanceField;
+
+    public MissingInstanceFieldValueForEnumInstanceReason(
+        DexField enumField, DexField instanceField) {
+      this.enumField = enumField;
+      this.ordinal = -1;
+      this.instanceField = instanceField;
+    }
+
+    public MissingInstanceFieldValueForEnumInstanceReason(int ordinal, DexField instanceField) {
+      this.enumField = null;
+      this.ordinal = ordinal;
+      this.instanceField = instanceField;
+    }
+
+    @Override
+    public Object getKind() {
+      return getClass();
+    }
+
+    @Override
+    public String toString() {
+      if (enumField != null) {
+        return "MissingInstanceFieldValueForEnumInstance(enum field="
+            + enumField.toSourceString()
+            + ", instance field="
+            + instanceField.toSourceString()
+            + ")";
+      }
+      assert ordinal >= 0;
+      return "MissingInstanceFieldValueForEnumInstance(ordinal="
+          + ordinal
+          + ", instance field="
+          + instanceField.toSourceString()
+          + ")";
+    }
+  }
+
+  public static class MissingObjectStateForEnumInstanceReason extends Reason {
+
+    private final DexField enumField;
+
+    public MissingObjectStateForEnumInstanceReason(DexField enumField) {
+      this.enumField = enumField;
+    }
+
+    @Override
+    public Object getKind() {
+      return getClass();
+    }
+
+    @Override
+    public String toString() {
+      return "MissingObjectStateForEnumInstance(" + enumField + ")";
+    }
+  }
+
+  public static class UnsupportedInstanceFieldValueForEnumInstanceReason extends Reason {
+
+    private final int ordinal;
+    private final DexField instanceField;
+
+    public UnsupportedInstanceFieldValueForEnumInstanceReason(int ordinal, DexField instanceField) {
+      this.ordinal = ordinal;
+      this.instanceField = instanceField;
+    }
+
+    @Override
+    public Object getKind() {
+      return getClass();
+    }
+
+    @Override
+    public String toString() {
+      return "UnsupportedInstanceFieldValueForEnumInstance(ordinal="
+          + ordinal
+          + ", instance field="
+          + instanceField.toSourceString()
+          + ")";
+    }
+  }
+
+  public static class UnsupportedLibraryInvokeReason extends Reason {
+
+    private final DexMethod invokedMethod;
+
+    public UnsupportedLibraryInvokeReason(DexMethod invokedMethod) {
+      this.invokedMethod = invokedMethod;
+    }
+
+    @Override
+    public Object getKind() {
+      return ImmutableList.of(getClass(), invokedMethod);
+    }
+
+    @Override
+    public String toString() {
+      return "UnsupportedLibraryInvoke(" + invokedMethod.toSourceString() + ")";
+    }
+  }
+
+  public static class UnsupportedStaticFieldReason extends Reason {
+
+    private final DexField field;
+
+    public UnsupportedStaticFieldReason(DexField field) {
+      this.field = field;
+    }
+
+    @Override
+    public Object getKind() {
+      return getClass();
+    }
+
+    @Override
+    public String toString() {
+      return "UnsupportedStaticField(" + field.toSourceString() + ")";
+    }
   }
 }