Support fields of type Enum

Bug:147860220
Change-Id: I196d43cf7e46e6c7898bd0ccd197d4617a0ebbf3
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/EnumUnboxer.java b/src/main/java/com/android/tools/r8/ir/optimize/EnumUnboxer.java
index 8e46dd3..88166e5 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/EnumUnboxer.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/EnumUnboxer.java
@@ -6,6 +6,7 @@
 
 import com.android.tools.r8.graph.AppView;
 import com.android.tools.r8.graph.DexClass;
+import com.android.tools.r8.graph.DexEncodedField;
 import com.android.tools.r8.graph.DexEncodedMethod;
 import com.android.tools.r8.graph.DexField;
 import com.android.tools.r8.graph.DexItemFactory;
@@ -15,6 +16,7 @@
 import com.android.tools.r8.ir.analysis.type.ArrayTypeLatticeElement;
 import com.android.tools.r8.ir.analysis.type.TypeLatticeElement;
 import com.android.tools.r8.ir.code.BasicBlock;
+import com.android.tools.r8.ir.code.FieldInstruction;
 import com.android.tools.r8.ir.code.IRCode;
 import com.android.tools.r8.ir.code.Instruction;
 import com.android.tools.r8.ir.code.InvokeMethod;
@@ -226,6 +228,28 @@
       return Reason.ELIGIBLE;
     }
 
+    // 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());
+      if (field == null) {
+        return Reason.INVALID_FIELD_PUT;
+      }
+      DexProgramClass dexClass = appView.definitionForProgramType(field.field.holder);
+      if (dexClass == null) {
+        return Reason.INVALID_FIELD_PUT;
+      }
+      if (dexClass.isEnum()) {
+        return Reason.FIELD_PUT_ON_ENUM;
+      }
+      // The put value has to be of the field type.
+      if (field.field.type != enumClass.type) {
+        return Reason.TYPE_MISSMATCH_FIELD_PUT;
+      }
+      return Reason.ELIGIBLE;
+    }
+
     if (instruction.isAssume()) {
       Value outValue = instruction.outValue();
       return validateEnumUsages(code, outValue.uniqueUsers(), outValue.uniquePhiUsers(), enumClass);
@@ -291,6 +315,9 @@
     NAME_INVOKE,
     UNSUPPORTED_LIBRARY_CALL,
     MISSING_INFO_MAP,
+    INVALID_FIELD_PUT,
+    FIELD_PUT_ON_ENUM,
+    TYPE_MISSMATCH_FIELD_PUT,
     OTHER_UNSUPPORTED_INSTRUCTION;
   }
 }
diff --git a/src/test/java/com/android/tools/r8/TestShrinkerBuilder.java b/src/test/java/com/android/tools/r8/TestShrinkerBuilder.java
index 19c6edd..2defd19 100644
--- a/src/test/java/com/android/tools/r8/TestShrinkerBuilder.java
+++ b/src/test/java/com/android/tools/r8/TestShrinkerBuilder.java
@@ -150,6 +150,13 @@
     return addKeepMainRule(mainClass.getTypeName());
   }
 
+  public T addKeepMainRules(Class<?>[] mainClasses) {
+    for (Class<?> mainClass : mainClasses) {
+      this.addKeepMainRule(mainClass);
+    }
+    return self();
+  }
+
   public T addKeepMainRule(String mainClass) {
     return addKeepRules(
         "-keep class " + mainClass + " { public static void main(java.lang.String[]); }");
diff --git a/src/test/java/com/android/tools/r8/enumunboxing/FailingMethodEnumUnboxingAnalysisTest.java b/src/test/java/com/android/tools/r8/enumunboxing/FailingMethodEnumUnboxingAnalysisTest.java
index 57bbc5c..762380a 100644
--- a/src/test/java/com/android/tools/r8/enumunboxing/FailingMethodEnumUnboxingAnalysisTest.java
+++ b/src/test/java/com/android/tools/r8/enumunboxing/FailingMethodEnumUnboxingAnalysisTest.java
@@ -26,8 +26,8 @@
   private static final Class<?>[] FAILURES = {
     NullCheck.class,
     Check.class,
-    FieldPut.class,
-    FieldPutObject.class,
+    InstanceFieldPutObject.class,
+    StaticFieldPutObject.class,
     ToString.class,
     EnumSetTest.class,
     FailingPhi.class
@@ -81,8 +81,10 @@
     assertTrue(inspector.clazz(NullCheck.class).uniqueMethodWithName("nullCheck").isPresent());
     assertTrue(inspector.clazz(Check.class).uniqueMethodWithName("check").isPresent());
 
-    assertEquals(1, inspector.clazz(FieldPut.class).allInstanceFields().size());
-    assertEquals(1, inspector.clazz(FieldPutObject.class).allInstanceFields().size());
+    assertEquals(
+        1, inspector.clazz(InstanceFieldPutObject.class).getDexClass().instanceFields().size());
+    assertEquals(
+        1, inspector.clazz(StaticFieldPutObject.class).getDexClass().staticFields().size());
 
     assertTrue(inspector.clazz(FailingPhi.class).uniqueMethodWithName("switchOn").isPresent());
   }
@@ -131,29 +133,7 @@
     }
   }
 
-  static class FieldPut {
-
-    enum MyEnum {
-      A,
-      B,
-      C
-    }
-
-    MyEnum e;
-
-    public static void main(String[] args) {
-      FieldPut fieldPut = new FieldPut();
-      fieldPut.setA();
-      System.out.println(fieldPut.e.ordinal());
-      System.out.println(0);
-    }
-
-    void setA() {
-      e = MyEnum.A;
-    }
-  }
-
-  static class FieldPutObject {
+  static class InstanceFieldPutObject {
 
     enum MyEnum {
       A,
@@ -164,7 +144,7 @@
     Object e;
 
     public static void main(String[] args) {
-      FieldPutObject fieldPut = new FieldPutObject();
+      InstanceFieldPutObject fieldPut = new InstanceFieldPutObject();
       fieldPut.setA();
       Object obj = new Object();
       fieldPut.e = obj;
@@ -177,6 +157,29 @@
     }
   }
 
+  static class StaticFieldPutObject {
+
+    enum MyEnum {
+      A,
+      B,
+      C
+    }
+
+    static Object e;
+
+    public static void main(String[] args) {
+      setA();
+      Object obj = new Object();
+      e = obj;
+      System.out.println(e);
+      System.out.println(obj);
+    }
+
+    static void setA() {
+      e = MyEnum.A;
+    }
+  }
+
   static class ToString {
 
     enum MyEnum {
diff --git a/src/test/java/com/android/tools/r8/enumunboxing/FieldPutEnumUnboxingAnalysisTest.java b/src/test/java/com/android/tools/r8/enumunboxing/FieldPutEnumUnboxingAnalysisTest.java
new file mode 100644
index 0000000..2841689
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/enumunboxing/FieldPutEnumUnboxingAnalysisTest.java
@@ -0,0 +1,124 @@
+// Copyright (c) 2020, 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.enumunboxing;
+
+import static org.junit.Assert.assertEquals;
+
+import com.android.tools.r8.NeverInline;
+import com.android.tools.r8.R8TestCompileResult;
+import com.android.tools.r8.R8TestRunResult;
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.TestParametersCollection;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+import org.junit.runners.Parameterized.Parameters;
+
+@RunWith(Parameterized.class)
+public class FieldPutEnumUnboxingAnalysisTest extends EnumUnboxingTestBase {
+
+  private final TestParameters parameters;
+  private static final Class<?>[] INPUTS =
+      new Class<?>[] {InstanceFieldPut.class, StaticFieldPut.class};
+
+  @Parameters(name = "{0}")
+  public static TestParametersCollection data() {
+    return enumUnboxingTestParameters();
+  }
+
+  public FieldPutEnumUnboxingAnalysisTest(TestParameters parameters) {
+    this.parameters = parameters;
+  }
+
+  @Test
+  public void testEnumUnboxing() throws Exception {
+    R8TestCompileResult compile =
+        testForR8(parameters.getBackend())
+            .addInnerClasses(FieldPutEnumUnboxingAnalysisTest.class)
+            .addKeepMainRules(INPUTS)
+            .addKeepRules(KEEP_ENUM)
+            .addOptionsModification(this::enableEnumOptions)
+            .enableInliningAnnotations()
+            .setMinApi(parameters.getApiLevel())
+            .noMinification()
+            .compile()
+            .inspect(
+                i -> {
+                  assertEquals(
+                      1, i.clazz(InstanceFieldPut.class).getDexClass().instanceFields().size());
+                  assertEquals(
+                      1, i.clazz(StaticFieldPut.class).getDexClass().staticFields().size());
+                });
+
+    for (Class<?> input : INPUTS) {
+      R8TestRunResult run =
+          compile
+              .inspectDiagnosticMessages(
+                  m -> assertEnumIsUnboxed(input.getDeclaredClasses()[0], input.getSimpleName(), m))
+              .run(parameters.getRuntime(), input)
+              .assertSuccess();
+      assertLines2By2Correct(run.getStdOut());
+    }
+  }
+
+  static class InstanceFieldPut {
+
+    enum MyEnum {
+      A,
+      B,
+      C
+    }
+
+    MyEnum e;
+
+    public static void main(String[] args) {
+      InstanceFieldPut fieldPut = new InstanceFieldPut();
+      fieldPut.setA();
+      System.out.println(fieldPut.e.ordinal());
+      System.out.println(0);
+      fieldPut.setB();
+      System.out.println(fieldPut.e.ordinal());
+      System.out.println(1);
+    }
+
+    void setA() {
+      e = MyEnum.A;
+    }
+
+    void setB() {
+      e = MyEnum.B;
+    }
+  }
+
+  static class StaticFieldPut {
+
+    enum MyEnum {
+      A,
+      B,
+      C
+    }
+
+    static MyEnum e;
+
+    public static void main(String[] args) {
+      setA();
+      System.out.println(StaticFieldPut.e.ordinal());
+      System.out.println(0);
+      setB();
+      System.out.println(StaticFieldPut.e.ordinal());
+      System.out.println(1);
+    }
+
+    @NeverInline
+    static void setA() {
+      StaticFieldPut.e = MyEnum.A;
+    }
+
+    @NeverInline
+    static void setB() {
+      StaticFieldPut.e = MyEnum.B;
+    }
+  }
+}