Refactor enum instruction analysis into method per instruction type

This also rewrites EnumUnboxer$Reason to a non-enum class, such that each Reason can be extended with state that explains why the instruction could not be unboxed.

Change-Id: Idda816df98f9d0b264259ac84eeb0fceea75f5fd
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 ce781ec..f4c8028 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
@@ -5,6 +5,21 @@
 package com.android.tools.r8.ir.optimize.enums;
 
 import static com.android.tools.r8.ir.analysis.type.Nullability.definitelyNotNull;
+import static com.android.tools.r8.ir.code.Opcodes.ARRAY_GET;
+import static com.android.tools.r8.ir.code.Opcodes.ARRAY_LENGTH;
+import static com.android.tools.r8.ir.code.Opcodes.ARRAY_PUT;
+import static com.android.tools.r8.ir.code.Opcodes.ASSUME;
+import static com.android.tools.r8.ir.code.Opcodes.CHECK_CAST;
+import static com.android.tools.r8.ir.code.Opcodes.IF;
+import static com.android.tools.r8.ir.code.Opcodes.INSTANCE_GET;
+import static com.android.tools.r8.ir.code.Opcodes.INSTANCE_PUT;
+import static com.android.tools.r8.ir.code.Opcodes.INVOKE_DIRECT;
+import static com.android.tools.r8.ir.code.Opcodes.INVOKE_INTERFACE;
+import static com.android.tools.r8.ir.code.Opcodes.INVOKE_STATIC;
+import static com.android.tools.r8.ir.code.Opcodes.INVOKE_SUPER;
+import static com.android.tools.r8.ir.code.Opcodes.INVOKE_VIRTUAL;
+import static com.android.tools.r8.ir.code.Opcodes.RETURN;
+import static com.android.tools.r8.ir.code.Opcodes.STATIC_PUT;
 
 import com.android.tools.r8.graph.AccessFlags;
 import com.android.tools.r8.graph.AppView;
@@ -34,7 +49,10 @@
 import com.android.tools.r8.ir.analysis.value.AbstractValue;
 import com.android.tools.r8.ir.analysis.value.EnumValuesObjectState;
 import com.android.tools.r8.ir.analysis.value.ObjectState;
+import com.android.tools.r8.ir.code.ArrayGet;
+import com.android.tools.r8.ir.code.ArrayLength;
 import com.android.tools.r8.ir.code.ArrayPut;
+import com.android.tools.r8.ir.code.Assume;
 import com.android.tools.r8.ir.code.BasicBlock;
 import com.android.tools.r8.ir.code.CheckCast;
 import com.android.tools.r8.ir.code.ConstClass;
@@ -48,6 +66,7 @@
 import com.android.tools.r8.ir.code.MemberType;
 import com.android.tools.r8.ir.code.Opcodes;
 import com.android.tools.r8.ir.code.Phi;
+import com.android.tools.r8.ir.code.Return;
 import com.android.tools.r8.ir.code.Value;
 import com.android.tools.r8.ir.conversion.IRConverter;
 import com.android.tools.r8.ir.conversion.PostMethodProcessor;
@@ -57,6 +76,7 @@
 import com.android.tools.r8.ir.optimize.enums.EnumInstanceFieldData.EnumInstanceFieldMappingData;
 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.info.FieldOptimizationInfo;
 import com.android.tools.r8.ir.optimize.info.MethodOptimizationInfo;
 import com.android.tools.r8.ir.optimize.info.OptimizationFeedback.OptimizationInfoFixer;
@@ -186,13 +206,13 @@
           case Opcodes.CHECK_CAST:
             analyzeCheckCast(instruction.asCheckCast(), eligibleEnums);
             break;
-          case Opcodes.INVOKE_STATIC:
+          case INVOKE_STATIC:
             analyzeInvokeStatic(instruction.asInvokeStatic(), eligibleEnums, code.context());
             break;
           case Opcodes.STATIC_GET:
           case Opcodes.INSTANCE_GET:
           case Opcodes.STATIC_PUT:
-          case Opcodes.INSTANCE_PUT:
+          case INSTANCE_PUT:
             analyzeFieldInstruction(instruction.asFieldInstruction(), code);
             break;
           default: // Nothing to do for other instructions.
@@ -898,211 +918,280 @@
 
   private Reason instructionAllowEnumUnboxing(
       Instruction instruction, IRCode code, DexProgramClass enumClass, Value enumValue) {
+    ProgramMethod context = code.context();
+    switch (instruction.opcode()) {
+      case ASSUME:
+        return analyzeAssumeUser(instruction.asAssume(), code, context, enumClass, enumValue);
+      case ARRAY_GET:
+        return analyzeArrayGetUser(instruction.asArrayGet(), code, context, enumClass, enumValue);
+      case ARRAY_LENGTH:
+        return analyzeArrayLengthUser(
+            instruction.asArrayLength(), code, context, enumClass, enumValue);
+      case ARRAY_PUT:
+        return analyzeArrayPutUser(instruction.asArrayPut(), code, context, enumClass, enumValue);
+      case CHECK_CAST:
+        return analyzeCheckCastUser(instruction.asCheckCast(), code, context, enumClass, enumValue);
+      case IF:
+        return analyzeIfUser(instruction.asIf(), code, context, enumClass, enumValue);
+      case INSTANCE_GET:
+        return analyzeInstanceGetUser(
+            instruction.asInstanceGet(), code, context, enumClass, enumValue);
+      case INSTANCE_PUT:
+        return analyzeFieldPutUser(
+            instruction.asInstancePut(), code, context, enumClass, enumValue);
+      case INVOKE_DIRECT:
+      case INVOKE_INTERFACE:
+      case INVOKE_STATIC:
+      case INVOKE_SUPER:
+      case INVOKE_VIRTUAL:
+        return analyzeInvokeUser(instruction.asInvokeMethod(), code, context, enumClass, enumValue);
+      case RETURN:
+        return analyzeReturnUser(instruction.asReturn(), code, context, enumClass, enumValue);
+      case STATIC_PUT:
+        return analyzeFieldPutUser(instruction.asStaticPut(), code, context, enumClass, enumValue);
+      default:
+        return Reason.OTHER_UNSUPPORTED_INSTRUCTION;
+    }
+  }
 
-    // All invokes in the library are invalid, besides a few cherry picked cases such as ordinal().
-    if (instruction.isInvokeMethod()) {
-      InvokeMethod invokeMethod = instruction.asInvokeMethod();
-      if (invokeMethod.getInvokedMethod().holder.isArrayType()) {
-        // The only valid methods is clone for values() to be correct.
-        if (invokeMethod.getInvokedMethod().name == factory.cloneMethodName) {
-          return Reason.ELIGIBLE;
-        }
-        return Reason.INVALID_INVOKE_ON_ARRAY;
-      }
-      DexClassAndMethod singleTarget = invokeMethod.lookupSingleTarget(appView, code.context());
-      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()) {
-            // The enum instance initializer is allowed to be called only from the enum clinit.
-            return Reason.ELIGIBLE;
-          } else {
-            return Reason.INVALID_INIT;
-          }
-        }
-        // 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 (invokeMethod.getArgument(offset + i) == enumValue) {
-            if (singleTarget.getReference().getParameter(i).toBaseType(factory) != enumClass.type) {
-              return Reason.GENERIC_INVOKE;
-            }
-          }
-        }
-        if (invokeMethod.isInvokeMethodWithReceiver()) {
-          Value receiver = invokeMethod.asInvokeMethodWithReceiver().getReceiver();
-          if (receiver == enumValue && dexClass.isInterface()) {
-            return Reason.DEFAULT_METHOD_INVOKE;
-          }
-        }
+  private Reason analyzeAssumeUser(
+      Assume assume,
+      IRCode code,
+      ProgramMethod context,
+      DexProgramClass enumClass,
+      Value enumValue) {
+    return validateEnumUsages(code, assume.outValue(), enumClass);
+  }
+
+  private Reason analyzeArrayGetUser(
+      ArrayGet arrayGet,
+      IRCode code,
+      ProgramMethod context,
+      DexProgramClass enumClass,
+      Value enumValue) {
+    // MyEnum[] array = ...; array[0]; is valid.
+    return Reason.ELIGIBLE;
+  }
+
+  private Reason analyzeArrayLengthUser(
+      ArrayLength arrayLength,
+      IRCode code,
+      ProgramMethod context,
+      DexProgramClass enumClass,
+      Value enumValue) {
+    // MyEnum[] array = ...; array.length; is valid.
+    return Reason.ELIGIBLE;
+  }
+
+  private Reason analyzeArrayPutUser(
+      ArrayPut arrayPut,
+      IRCode code,
+      ProgramMethod context,
+      DexProgramClass enumClass,
+      Value enumValue) {
+    // MyEnum[] array; array[0] = MyEnum.A; is valid.
+    // MyEnum[][] array2d; MyEnum[] array; array2d[0] = array; is valid.
+    // MyEnum[]^N array; MyEnum[]^(N-1) element; array[0] = element; is valid.
+    // We need to prove that the value to put in and the array have correct types.
+    assert arrayPut.getMemberType() == MemberType.OBJECT;
+    TypeElement arrayType = arrayPut.array().getType();
+    assert arrayType.isArrayType();
+    assert arrayType.asArrayType().getBaseType().isClassType();
+    ClassTypeElement arrayBaseType = arrayType.asArrayType().getBaseType().asClassType();
+    TypeElement valueBaseType = arrayPut.value().getType();
+    if (valueBaseType.isArrayType()) {
+      assert valueBaseType.asArrayType().getBaseType().isClassType();
+      assert valueBaseType.asArrayType().getNesting() == arrayType.asArrayType().getNesting() - 1;
+      valueBaseType = valueBaseType.asArrayType().getBaseType();
+    }
+    if (arrayBaseType.equalUpToNullability(valueBaseType)
+        && arrayBaseType.getClassType() == enumClass.type) {
+      return Reason.ELIGIBLE;
+    }
+    return Reason.INVALID_ARRAY_PUT;
+  }
+
+  private Reason analyzeCheckCastUser(
+      CheckCast checkCast,
+      IRCode code,
+      ProgramMethod context,
+      DexProgramClass enumClass,
+      Value enumValue) {
+    if (allowCheckCast(checkCast)) {
+      return Reason.ELIGIBLE;
+    }
+    return Reason.DOWN_CAST;
+  }
+
+  // A field put is valid only if the field is not on an enum, and the field type and the valuePut
+  // have identical enum type.
+  private Reason analyzeFieldPutUser(
+      FieldInstruction fieldPut,
+      IRCode code,
+      ProgramMethod context,
+      DexProgramClass enumClass,
+      Value enumValue) {
+    assert fieldPut.isInstancePut() || fieldPut.isStaticPut();
+    DexEncodedField field = appView.appInfo().resolveField(fieldPut.getField()).getResolvedField();
+    if (field == null) {
+      return Reason.INVALID_FIELD_PUT;
+    }
+    DexProgramClass dexClass = appView.programDefinitionFor(field.getHolderType(), code.context());
+    if (dexClass == null) {
+      return Reason.INVALID_FIELD_PUT;
+    }
+    if (fieldPut.isInstancePut() && fieldPut.asInstancePut().object() == enumValue) {
+      return Reason.ELIGIBLE;
+    }
+    // The put value has to be of the field type.
+    if (field.getReference().type.toBaseType(factory) != enumClass.type) {
+      return Reason.TYPE_MISMATCH_FIELD_PUT;
+    }
+    return Reason.ELIGIBLE;
+  }
+
+  // An If using enum as inValue is valid if it matches e == null
+  // or e == X with X of same enum type as e. Ex: if (e == MyEnum.A).
+  private Reason analyzeIfUser(
+      If theIf, IRCode code, ProgramMethod context, DexProgramClass enumClass, Value enumValue) {
+    assert (theIf.getType() == If.Type.EQ || theIf.getType() == If.Type.NE)
+        : "Comparing a reference with " + theIf.getType().toString();
+    // e == null.
+    if (theIf.isZeroTest()) {
+      return Reason.ELIGIBLE;
+    }
+    // e == MyEnum.X
+    TypeElement leftType = theIf.lhs().getType();
+    TypeElement rightType = theIf.rhs().getType();
+    if (leftType.equalUpToNullability(rightType)) {
+      assert leftType.isClassType();
+      assert leftType.asClassType().getClassType() == enumClass.type;
+      return Reason.ELIGIBLE;
+    }
+    return Reason.INVALID_IF_TYPES;
+  }
+
+  private Reason analyzeInstanceGetUser(
+      InstanceGet instanceGet,
+      IRCode code,
+      ProgramMethod context,
+      DexProgramClass enumClass,
+      Value enumValue) {
+    assert instanceGet.getField().holder == enumClass.type;
+    DexField field = instanceGet.getField();
+    enumUnboxingCandidatesInfo.addRequiredEnumInstanceFieldData(enumClass.type, field);
+    return Reason.ELIGIBLE;
+  }
+
+  // All invokes in the library are invalid, besides a few cherry picked cases such as ordinal().
+  private Reason analyzeInvokeUser(
+      InvokeMethod invoke,
+      IRCode code,
+      ProgramMethod context,
+      DexProgramClass enumClass,
+      Value enumValue) {
+    if (invoke.getInvokedMethod().holder.isArrayType()) {
+      // The only valid methods is clone for values() to be correct.
+      if (invoke.getInvokedMethod().name == factory.cloneMethodName) {
         return Reason.ELIGIBLE;
       }
-      if (dexClass.isClasspathClass()) {
-        return Reason.INVALID_INVOKE;
+      return Reason.INVALID_INVOKE_ON_ARRAY;
+    }
+    DexClassAndMethod singleTarget = invoke.lookupSingleTarget(appView, code.context());
+    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()) {
+          // The enum instance initializer is allowed to be called only from the enum clinit.
+          return Reason.ELIGIBLE;
+        } else {
+          return Reason.INVALID_INIT;
+        }
       }
-      assert dexClass.isLibraryClass();
-      DexMethod singleTargetReference = singleTarget.getReference();
-      if (dexClass.type != 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.
-        if (singleTargetReference == factory.javaLangSystemMethods.identityHashCode) {
-          return Reason.ELIGIBLE;
+      // 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;
+          }
         }
-        if (singleTargetReference == factory.stringMembers.valueOf) {
-          addRequiredNameData(enumClass.type);
-          return Reason.ELIGIBLE;
-        }
-        if (singleTargetReference == factory.objectMembers.getClass
-            && (!invokeMethod.hasOutValue() || !invokeMethod.outValue().hasAnyUsers())) {
-          // This is a hidden null check.
-          return Reason.ELIGIBLE;
-        }
-        if (singleTargetReference == factory.objectsMethods.requireNonNull
-            || singleTargetReference == factory.objectsMethods.requireNonNullWithMessage) {
-          return Reason.ELIGIBLE;
-        }
-        return Reason.UNSUPPORTED_LIBRARY_CALL;
       }
-      // TODO(b/147860220): EnumSet and EnumMap may be interesting to model.
-      if (singleTargetReference == factory.enumMembers.compareTo) {
+      if (invoke.isInvokeMethodWithReceiver()) {
+        Value receiver = invoke.asInvokeMethodWithReceiver().getReceiver();
+        if (receiver == enumValue && dexClass.isInterface()) {
+          return Reason.DEFAULT_METHOD_INVOKE;
+        }
+      }
+      return Reason.ELIGIBLE;
+    }
+    if (dexClass.isClasspathClass()) {
+      return Reason.INVALID_INVOKE;
+    }
+    assert dexClass.isLibraryClass();
+    DexMethod singleTargetReference = singleTarget.getReference();
+    if (dexClass.type != 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.
+      if (singleTargetReference == factory.javaLangSystemMethods.identityHashCode) {
         return Reason.ELIGIBLE;
-      } else if (singleTargetReference == factory.enumMembers.equals) {
-        return Reason.ELIGIBLE;
-      } else if (singleTargetReference == factory.enumMembers.nameMethod
-          || singleTargetReference == factory.enumMembers.toString) {
-        assert invokeMethod.asInvokeMethodWithReceiver().getReceiver() == enumValue;
+      }
+      if (singleTargetReference == factory.stringMembers.valueOf) {
         addRequiredNameData(enumClass.type);
         return Reason.ELIGIBLE;
-      } else if (singleTargetReference == factory.enumMembers.ordinalMethod) {
+      }
+      if (singleTargetReference == factory.objectMembers.getClass
+          && (!invoke.hasOutValue() || !invoke.outValue().hasAnyUsers())) {
+        // This is a hidden null check.
         return Reason.ELIGIBLE;
-      } else if (singleTargetReference == factory.enumMembers.hashCode) {
+      }
+      if (singleTargetReference == factory.objectsMethods.requireNonNull
+          || singleTargetReference == factory.objectsMethods.requireNonNullWithMessage) {
         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) {
-          return Reason.ELIGIBLE;
-        }
       }
       return Reason.UNSUPPORTED_LIBRARY_CALL;
     }
-
-    // A field put is valid only if the field is not on an enum, and the field type and the valuePut
-    // have identical enum type.
-    if (instruction.isFieldPut()) {
-      FieldInstruction fieldInstruction = instruction.asFieldInstruction();
-      DexEncodedField field =
-          appView.appInfo().resolveField(fieldInstruction.getField()).getResolvedField();
-      if (field == null) {
-        return Reason.INVALID_FIELD_PUT;
-      }
-      DexProgramClass dexClass =
-          appView.programDefinitionFor(field.getHolderType(), code.context());
-      if (dexClass == null) {
-        return Reason.INVALID_FIELD_PUT;
-      }
-      if (fieldInstruction.isInstancePut()
-          && fieldInstruction.asInstancePut().object() == enumValue) {
+    // TODO(b/147860220): EnumSet and EnumMap may be interesting to model.
+    if (singleTargetReference == factory.enumMembers.compareTo) {
+      return Reason.ELIGIBLE;
+    } else if (singleTargetReference == factory.enumMembers.equals) {
+      return Reason.ELIGIBLE;
+    } else if (singleTargetReference == factory.enumMembers.nameMethod
+        || singleTargetReference == factory.enumMembers.toString) {
+      assert invoke.asInvokeMethodWithReceiver().getReceiver() == enumValue;
+      addRequiredNameData(enumClass.type);
+      return Reason.ELIGIBLE;
+    } else if (singleTargetReference == factory.enumMembers.ordinalMethod) {
+      return Reason.ELIGIBLE;
+    } else if (singleTargetReference == factory.enumMembers.hashCode) {
+      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) {
         return Reason.ELIGIBLE;
       }
-      // The put value has to be of the field type.
-      if (field.getReference().type.toBaseType(factory) != enumClass.type) {
-        return Reason.TYPE_MISMATCH_FIELD_PUT;
-      }
-      return Reason.ELIGIBLE;
     }
+    return Reason.UNSUPPORTED_LIBRARY_CALL;
+  }
 
-    if (instruction.isInstanceGet()) {
-      InstanceGet instanceGet = instruction.asInstanceGet();
-      assert instanceGet.getField().holder == enumClass.type;
-      DexField field = instanceGet.getField();
-      enumUnboxingCandidatesInfo.addRequiredEnumInstanceFieldData(enumClass.type, field);
-      return Reason.ELIGIBLE;
+  // Return is used for valueOf methods.
+  private Reason analyzeReturnUser(
+      Return theReturn,
+      IRCode code,
+      ProgramMethod context,
+      DexProgramClass enumClass,
+      Value enumValue) {
+    DexType returnType = context.getReturnType();
+    if (returnType != enumClass.type && returnType.toBaseType(factory) != enumClass.type) {
+      return Reason.IMPLICIT_UP_CAST_IN_RETURN;
     }
-
-    // An If using enum as inValue is valid if it matches e == null
-    // or e == X with X of same enum type as e. Ex: if (e == MyEnum.A).
-    if (instruction.isIf()) {
-      If anIf = instruction.asIf();
-      assert (anIf.getType() == If.Type.EQ || anIf.getType() == If.Type.NE)
-          : "Comparing a reference with " + anIf.getType().toString();
-      // e == null.
-      if (anIf.isZeroTest()) {
-        return Reason.ELIGIBLE;
-      }
-      // e == MyEnum.X
-      TypeElement leftType = anIf.lhs().getType();
-      TypeElement rightType = anIf.rhs().getType();
-      if (leftType.equalUpToNullability(rightType)) {
-        assert leftType.isClassType();
-        assert leftType.asClassType().getClassType() == enumClass.type;
-        return Reason.ELIGIBLE;
-      }
-      return Reason.INVALID_IF_TYPES;
-    }
-
-    if (instruction.isCheckCast()) {
-      if (allowCheckCast(instruction.asCheckCast())) {
-        return Reason.ELIGIBLE;
-      }
-      return Reason.DOWN_CAST;
-    }
-
-    if (instruction.isArrayLength()) {
-      // MyEnum[] array = ...; array.length; is valid.
-      return Reason.ELIGIBLE;
-    }
-
-    if (instruction.isArrayGet()) {
-      // MyEnum[] array = ...; array[0]; is valid.
-      return Reason.ELIGIBLE;
-    }
-
-    if (instruction.isArrayPut()) {
-      // MyEnum[] array; array[0] = MyEnum.A; is valid.
-      // MyEnum[][] array2d; MyEnum[] array; array2d[0] = array; is valid.
-      // MyEnum[]^N array; MyEnum[]^(N-1) element; array[0] = element; is valid.
-      // We need to prove that the value to put in and the array have correct types.
-      ArrayPut arrayPut = instruction.asArrayPut();
-      assert arrayPut.getMemberType() == MemberType.OBJECT;
-      TypeElement arrayType = arrayPut.array().getType();
-      assert arrayType.isArrayType();
-      assert arrayType.asArrayType().getBaseType().isClassType();
-      ClassTypeElement arrayBaseType = arrayType.asArrayType().getBaseType().asClassType();
-      TypeElement valueBaseType = arrayPut.value().getType();
-      if (valueBaseType.isArrayType()) {
-        assert valueBaseType.asArrayType().getBaseType().isClassType();
-        assert valueBaseType.asArrayType().getNesting() == arrayType.asArrayType().getNesting() - 1;
-        valueBaseType = valueBaseType.asArrayType().getBaseType();
-      }
-      if (arrayBaseType.equalUpToNullability(valueBaseType)
-          && arrayBaseType.getClassType() == enumClass.type) {
-        return Reason.ELIGIBLE;
-      }
-      return Reason.INVALID_ARRAY_PUT;
-    }
-
-    if (instruction.isAssume()) {
-      Value outValue = instruction.outValue();
-      return validateEnumUsages(code, outValue, enumClass);
-    }
-
-    // Return is used for valueOf methods.
-    if (instruction.isReturn()) {
-      DexType returnType = code.method().getReference().proto.returnType;
-      if (returnType != enumClass.type && returnType.toBaseType(factory) != enumClass.type) {
-        return Reason.IMPLICIT_UP_CAST_IN_RETURN;
-      }
-      return Reason.ELIGIBLE;
-    }
-
-    return Reason.OTHER_UNSUPPORTED_INSTRUCTION;
+    return Reason.ELIGIBLE;
   }
 
   private void reportEnumsAnalysis() {
@@ -1148,41 +1237,4 @@
       enumUnboxerRewriter.synthesizeEnumUnboxingUtilityMethods(converter, executorService);
     }
   }
-
-  public enum Reason {
-    ELIGIBLE,
-    ACCESSIBILITY,
-    ANNOTATION,
-    PINNED,
-    DOWN_CAST,
-    SUBTYPES,
-    INTERFACE,
-    MANY_INSTANCE_FIELDS,
-    GENERIC_INVOKE,
-    DEFAULT_METHOD_INVOKE,
-    UNEXPECTED_STATIC_FIELD,
-    UNRESOLVABLE_FIELD,
-    CONST_CLASS,
-    INVALID_PHI,
-    NO_INIT,
-    INVALID_INIT,
-    INVALID_CLINIT,
-    INVALID_INVOKE,
-    INVALID_INVOKE_ON_ARRAY,
-    IMPLICIT_UP_CAST_IN_RETURN,
-    VALUE_OF_INVOKE,
-    VALUES_INVOKE,
-    COMPARE_TO_INVOKE,
-    UNSUPPORTED_LIBRARY_CALL,
-    MISSING_INSTANCE_FIELD_DATA,
-    INVALID_FIELD_READ,
-    INVALID_FIELD_PUT,
-    INVALID_ARRAY_PUT,
-    FIELD_PUT_ON_ENUM,
-    TYPE_MISMATCH_FIELD_PUT,
-    INVALID_IF_TYPES,
-    DYNAMIC_TYPE,
-    ENUM_METHOD_CALLED_WITH_NULL_RECEIVER,
-    OTHER_UNSUPPORTED_INSTRUCTION;
-  }
 }
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 0d1c0fa..9196ca3 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
@@ -12,7 +12,7 @@
 import com.android.tools.r8.graph.DexProgramClass;
 import com.android.tools.r8.graph.DexProto;
 import com.android.tools.r8.graph.DexType;
-import com.android.tools.r8.ir.optimize.enums.EnumUnboxer.Reason;
+import com.android.tools.r8.ir.optimize.enums.eligibility.Reason;
 import com.android.tools.r8.shaking.AppInfoWithLiveness;
 import com.android.tools.r8.shaking.KeepInfoCollection;
 
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
new file mode 100644
index 0000000..2cd2098
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/ir/optimize/enums/eligibility/Reason.java
@@ -0,0 +1,49 @@
+// Copyright (c) 2021, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+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");
+  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");
+  public static final Reason ENUM_METHOD_CALLED_WITH_NULL_RECEIVER =
+      new Reason("ENUM_METHOD_CALLED_WITH_NULL_RECEIVER");
+  public static final Reason OTHER_UNSUPPORTED_INSTRUCTION =
+      new Reason("OTHER_UNSUPPORTED_INSTRUCTION");
+
+  private final String message;
+
+  public Reason(String message) {
+    this.message = message;
+  }
+
+  @Override
+  public String toString() {
+    return message;
+  }
+}