Experimental support for checkenumunboxed

Bug: 234557774
Change-Id: I8d98ae5b27294d345e21900729b6abd641f7db23
diff --git a/src/main/java/com/android/tools/r8/R8Command.java b/src/main/java/com/android/tools/r8/R8Command.java
index 91040b0..8d323ab 100644
--- a/src/main/java/com/android/tools/r8/R8Command.java
+++ b/src/main/java/com/android/tools/r8/R8Command.java
@@ -663,6 +663,10 @@
 
     }
 
+    void setEnableExperimentalCheckEnumUnboxed() {
+      parserOptionsBuilder.setEnableExperimentalCheckEnumUnboxed(true);
+    }
+
     void setEnableExperimentalConvertCheckNotNull() {
       parserOptionsBuilder.setEnableExperimentalConvertCheckNotNull(true);
     }
diff --git a/src/main/java/com/android/tools/r8/errors/CheckEnumUnboxedDiagnostic.java b/src/main/java/com/android/tools/r8/errors/CheckEnumUnboxedDiagnostic.java
new file mode 100644
index 0000000..3339192
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/errors/CheckEnumUnboxedDiagnostic.java
@@ -0,0 +1,68 @@
+// Copyright (c) 2022, 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.errors;
+
+import com.android.tools.r8.Diagnostic;
+import com.android.tools.r8.Keep;
+import com.android.tools.r8.graph.DexClass;
+import com.android.tools.r8.graph.DexProgramClass;
+import com.android.tools.r8.origin.Origin;
+import com.android.tools.r8.position.Position;
+import com.google.common.collect.ImmutableList;
+import java.util.Comparator;
+import java.util.List;
+
+@Keep
+public class CheckEnumUnboxedDiagnostic implements Diagnostic {
+
+  private final List<String> messages;
+
+  CheckEnumUnboxedDiagnostic(List<String> messages) {
+    this.messages = messages;
+  }
+
+  public static Builder builder() {
+    return new Builder();
+  }
+
+  /** The origin of a -checkenumunboxed failure is not unique. (The whole app is to blame.) */
+  @Override
+  public Origin getOrigin() {
+    return Origin.unknown();
+  }
+
+  /** The position of a -checkenumunboxed failure is always unknown. */
+  @Override
+  public Position getPosition() {
+    return Position.UNKNOWN;
+  }
+
+  @Override
+  public String getDiagnosticMessage() {
+    StringBuilder builder = new StringBuilder("Enum unboxing checks failed.");
+    for (String message : messages) {
+      builder.append(System.lineSeparator());
+      builder.append(message);
+    }
+    return builder.toString();
+  }
+
+  public static class Builder {
+
+    private final ImmutableList.Builder<String> messagesBuilder = ImmutableList.builder();
+
+    public Builder addFailedEnums(List<DexProgramClass> failed) {
+      failed.sort(Comparator.comparing(DexClass::getType));
+      for (DexProgramClass clazz : failed) {
+        messagesBuilder.add("Enum " + clazz.getTypeName() + " was not unboxed.");
+      }
+      return this;
+    }
+
+    public CheckEnumUnboxedDiagnostic build() {
+      return new CheckEnumUnboxedDiagnostic(messagesBuilder.build());
+    }
+  }
+}
diff --git a/src/main/java/com/android/tools/r8/ir/conversion/IRConverter.java b/src/main/java/com/android/tools/r8/ir/conversion/IRConverter.java
index 2dc8029..f94cde1 100644
--- a/src/main/java/com/android/tools/r8/ir/conversion/IRConverter.java
+++ b/src/main/java/com/android/tools/r8/ir/conversion/IRConverter.java
@@ -741,6 +741,7 @@
 
     outliner.rewriteWithLens();
     enumUnboxer.unboxEnums(appView, this, postMethodProcessorBuilder, executorService, feedback);
+    appView.unboxedEnums().checkEnumsUnboxed(appView);
 
     GraphLens graphLensForSecondaryOptimizationPass = appView.graphLens();
 
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/enums/EnumDataMap.java b/src/main/java/com/android/tools/r8/ir/optimize/enums/EnumDataMap.java
index 264fc7b..00c4f50 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/enums/EnumDataMap.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/enums/EnumDataMap.java
@@ -4,13 +4,18 @@
 
 package com.android.tools.r8.ir.optimize.enums;
 
+import com.android.tools.r8.errors.CheckEnumUnboxedDiagnostic;
+import com.android.tools.r8.graph.AppView;
 import com.android.tools.r8.graph.DexField;
 import com.android.tools.r8.graph.DexProgramClass;
 import com.android.tools.r8.graph.DexType;
 import com.android.tools.r8.graph.ProgramField;
 import com.android.tools.r8.ir.optimize.enums.EnumInstanceFieldData.EnumInstanceFieldKnownData;
+import com.android.tools.r8.shaking.AppInfoWithLiveness;
 import com.google.common.collect.ImmutableMap;
 import com.google.common.collect.ImmutableSet;
+import java.util.ArrayList;
+import java.util.List;
 import java.util.Set;
 
 public class EnumDataMap {
@@ -24,6 +29,23 @@
     this.map = map;
   }
 
+  public void checkEnumsUnboxed(AppView<AppInfoWithLiveness> appView) {
+    List<DexProgramClass> failed = new ArrayList<>();
+    for (DexProgramClass clazz : appView.appInfo().classes()) {
+      if (clazz.isEnum()) {
+        if (appView.getKeepInfo(clazz).isCheckEnumUnboxedEnabled(appView.options())
+            && !isUnboxedEnum(clazz)) {
+          failed.add(clazz);
+        }
+      }
+    }
+    if (!failed.isEmpty()) {
+      CheckEnumUnboxedDiagnostic diagnostic =
+          CheckEnumUnboxedDiagnostic.builder().addFailedEnums(failed).build();
+      throw appView.reporter().fatalError(diagnostic);
+    }
+  }
+
   public boolean isUnboxedEnum(DexProgramClass clazz) {
     return isUnboxedEnum(clazz.getType());
   }
diff --git a/src/main/java/com/android/tools/r8/shaking/CheckEnumUnboxedRule.java b/src/main/java/com/android/tools/r8/shaking/CheckEnumUnboxedRule.java
new file mode 100644
index 0000000..da2fbbe
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/shaking/CheckEnumUnboxedRule.java
@@ -0,0 +1,84 @@
+// Copyright (c) 2022, 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.shaking;
+
+import com.android.tools.r8.origin.Origin;
+import com.android.tools.r8.position.Position;
+import java.util.List;
+
+public class CheckEnumUnboxedRule extends ProguardConfigurationRule {
+
+  public static final String RULE_NAME = "checkenumunboxed";
+
+  public static class Builder
+      extends ProguardConfigurationRule.Builder<CheckEnumUnboxedRule, Builder> {
+
+    private Builder() {
+      super();
+    }
+
+    @Override
+    public Builder self() {
+      return this;
+    }
+
+    @Override
+    public CheckEnumUnboxedRule build() {
+      return new CheckEnumUnboxedRule(
+          origin,
+          getPosition(),
+          source,
+          buildClassAnnotations(),
+          classAccessFlags,
+          negatedClassAccessFlags,
+          classTypeNegated,
+          classType,
+          classNames,
+          buildInheritanceAnnotations(),
+          inheritanceClassName,
+          inheritanceIsExtends,
+          memberRules);
+    }
+  }
+
+  protected CheckEnumUnboxedRule(
+      Origin origin,
+      Position position,
+      String source,
+      List<ProguardTypeMatcher> classAnnotations,
+      ProguardAccessFlags classAccessFlags,
+      ProguardAccessFlags negatedClassAccessFlags,
+      boolean classTypeNegated,
+      ProguardClassType classType,
+      ProguardClassNameList classNames,
+      List<ProguardTypeMatcher> inheritanceAnnotations,
+      ProguardTypeMatcher inheritanceClassName,
+      boolean inheritanceIsExtends,
+      List<ProguardMemberRule> memberRules) {
+    super(
+        origin,
+        position,
+        source,
+        classAnnotations,
+        classAccessFlags,
+        negatedClassAccessFlags,
+        classTypeNegated,
+        classType,
+        classNames,
+        inheritanceAnnotations,
+        inheritanceClassName,
+        inheritanceIsExtends,
+        memberRules);
+  }
+
+  public static Builder builder() {
+    return new Builder();
+  }
+
+  @Override
+  String typeString() {
+    return RULE_NAME;
+  }
+}
diff --git a/src/main/java/com/android/tools/r8/shaking/KeepClassInfo.java b/src/main/java/com/android/tools/r8/shaking/KeepClassInfo.java
index bf969b7..7199fcb 100644
--- a/src/main/java/com/android/tools/r8/shaking/KeepClassInfo.java
+++ b/src/main/java/com/android/tools/r8/shaking/KeepClassInfo.java
@@ -32,8 +32,11 @@
     return bottom().joiner();
   }
 
+  private final boolean checkEnumUnboxed;
+
   private KeepClassInfo(Builder builder) {
     super(builder);
+    this.checkEnumUnboxed = builder.isCheckEnumUnboxedEnabled();
   }
 
   @Override
@@ -41,6 +44,14 @@
     return new Builder(this);
   }
 
+  public boolean isCheckEnumUnboxedEnabled(GlobalKeepInfoConfiguration configuration) {
+    return internalIsCheckEnumUnboxedEnabled();
+  }
+
+  boolean internalIsCheckEnumUnboxedEnabled() {
+    return checkEnumUnboxed;
+  }
+
   public Joiner joiner() {
     assert !isTop();
     return new Joiner(this);
@@ -87,12 +98,34 @@
 
   public static class Builder extends KeepInfo.Builder<Builder, KeepClassInfo> {
 
+    private boolean checkEnumUnboxed;
+
     private Builder() {
       super();
     }
 
     private Builder(KeepClassInfo original) {
       super(original);
+      checkEnumUnboxed = original.internalIsCheckEnumUnboxedEnabled();
+    }
+
+    // Check enum unboxed.
+
+    public boolean isCheckEnumUnboxedEnabled() {
+      return checkEnumUnboxed;
+    }
+
+    public Builder setCheckEnumUnboxed(boolean checkEnumUnboxed) {
+      this.checkEnumUnboxed = checkEnumUnboxed;
+      return self();
+    }
+
+    public Builder setCheckEnumUnboxed() {
+      return setCheckEnumUnboxed(true);
+    }
+
+    public Builder unsetCheckEnumUnboxed() {
+      return setCheckEnumUnboxed(false);
     }
 
     @Override
@@ -116,9 +149,25 @@
     }
 
     @Override
+    boolean internalIsEqualTo(KeepClassInfo other) {
+      return super.internalIsEqualTo(other)
+          && isCheckEnumUnboxedEnabled() == other.internalIsCheckEnumUnboxedEnabled();
+    }
+
+    @Override
     public KeepClassInfo doBuild() {
       return new KeepClassInfo(this);
     }
+
+    @Override
+    public Builder makeTop() {
+      return super.makeTop().unsetCheckEnumUnboxed();
+    }
+
+    @Override
+    public Builder makeBottom() {
+      return super.makeBottom().unsetCheckEnumUnboxed();
+    }
   }
 
   public static class Joiner extends KeepInfo.Joiner<Joiner, Builder, KeepClassInfo> {
@@ -127,6 +176,11 @@
       super(info.builder());
     }
 
+    public Joiner setCheckEnumUnboxed() {
+      builder.setCheckEnumUnboxed();
+      return self();
+    }
+
     @Override
     public Joiner asClassJoiner() {
       return this;
@@ -135,7 +189,8 @@
     @Override
     public Joiner merge(Joiner joiner) {
       // Should be extended to merge the fields of this class in case any are added.
-      return super.merge(joiner);
+      return super.merge(joiner)
+          .applyIf(joiner.builder.isCheckEnumUnboxedEnabled(), Joiner::setCheckEnumUnboxed);
     }
 
     @Override
diff --git a/src/main/java/com/android/tools/r8/shaking/ProguardConfigurationParser.java b/src/main/java/com/android/tools/r8/shaking/ProguardConfigurationParser.java
index 7dd6643..8b4210b 100644
--- a/src/main/java/com/android/tools/r8/shaking/ProguardConfigurationParser.java
+++ b/src/main/java/com/android/tools/r8/shaking/ProguardConfigurationParser.java
@@ -80,7 +80,7 @@
           "keepkotlinmetadata");
 
   private static final List<String> IGNORED_CLASS_DESCRIPTOR_OPTIONS =
-      ImmutableList.of("isclassnamestring", "whyarenotsimple", "checkenumunboxed");
+      ImmutableList.of("isclassnamestring", "whyarenotsimple");
 
   private static final List<String> WARNED_SINGLE_ARG_OPTIONS = ImmutableList.of(
       // TODO(b/37137994): -outjars should be reported as errors, not just as warnings!
@@ -122,8 +122,9 @@
         dexItemFactory,
         reporter,
         ProguardConfigurationParserOptions.builder()
-            .setEnableExperimentalConvertCheckNotNull(true)
-            .setEnableExperimentalWhyAreYouNotInlining(true)
+            .setEnableExperimentalCheckEnumUnboxed(false)
+            .setEnableExperimentalConvertCheckNotNull(false)
+            .setEnableExperimentalWhyAreYouNotInlining(false)
             .setEnableTestingOptions(false)
             .build());
   }
@@ -484,6 +485,13 @@
 
     private boolean parseExperimentalOption(TextPosition optionStart)
         throws ProguardRuleParserException {
+      if (acceptString(CheckEnumUnboxedRule.RULE_NAME)) {
+        CheckEnumUnboxedRule checkEnumUnboxedRule = parseCheckEnumUnboxedRule(optionStart);
+        if (options.isExperimentalCheckEnumUnboxedEnabled()) {
+          configurationBuilder.addRule(checkEnumUnboxedRule);
+        }
+        return true;
+      }
       if (acceptString(ConvertCheckNotNullRule.RULE_NAME)) {
         ConvertCheckNotNullRule convertCheckNotNullRule = parseConvertCheckNotNullRule(optionStart);
         if (options.isExperimentalConvertCheckNotNullEnabled()) {
@@ -1717,6 +1725,17 @@
       return builder.build();
     }
 
+    private CheckEnumUnboxedRule parseCheckEnumUnboxedRule(Position start)
+        throws ProguardRuleParserException {
+      CheckEnumUnboxedRule.Builder builder =
+          CheckEnumUnboxedRule.builder().setOrigin(origin).setStart(start);
+      parseClassSpec(builder);
+      Position end = getPosition();
+      builder.setSource(getSourceSnippet(contents, start, end));
+      builder.setEnd(end);
+      return builder.build();
+    }
+
     private ConvertCheckNotNullRule parseConvertCheckNotNullRule(Position start)
         throws ProguardRuleParserException {
       ConvertCheckNotNullRule.Builder builder =
diff --git a/src/main/java/com/android/tools/r8/shaking/ProguardConfigurationParserOptions.java b/src/main/java/com/android/tools/r8/shaking/ProguardConfigurationParserOptions.java
index 66afbba..8811df7 100644
--- a/src/main/java/com/android/tools/r8/shaking/ProguardConfigurationParserOptions.java
+++ b/src/main/java/com/android/tools/r8/shaking/ProguardConfigurationParserOptions.java
@@ -8,14 +8,17 @@
 
 public class ProguardConfigurationParserOptions {
 
+  private final boolean enableExperimentalCheckEnumUnboxed;
   private final boolean enableExperimentalConvertCheckNotNull;
   private final boolean enableExperimentalWhyAreYouNotInlining;
   private final boolean enableTestingOptions;
 
   ProguardConfigurationParserOptions(
+      boolean enableExperimentalCheckEnumUnboxed,
       boolean enableExperimentalConvertCheckNotNull,
       boolean enableExperimentalWhyAreYouNotInlining,
       boolean enableTestingOptions) {
+    this.enableExperimentalCheckEnumUnboxed = enableExperimentalCheckEnumUnboxed;
     this.enableExperimentalConvertCheckNotNull = enableExperimentalConvertCheckNotNull;
     this.enableExperimentalWhyAreYouNotInlining = enableExperimentalWhyAreYouNotInlining;
     this.enableTestingOptions = enableTestingOptions;
@@ -25,6 +28,10 @@
     return new Builder();
   }
 
+  public boolean isExperimentalCheckEnumUnboxedEnabled() {
+    return enableExperimentalCheckEnumUnboxed;
+  }
+
   public boolean isExperimentalConvertCheckNotNullEnabled() {
     return enableExperimentalConvertCheckNotNull;
   }
@@ -39,11 +46,15 @@
 
   public static class Builder {
 
+    private boolean enableExperimentalCheckEnumUnboxed;
     private boolean enableExperimentalConvertCheckNotNull;
     private boolean enableExperimentalWhyAreYouNotInlining;
     private boolean enableTestingOptions;
 
     public Builder readEnvironment() {
+      enableExperimentalCheckEnumUnboxed =
+          parseSystemPropertyOrDefault(
+              "com.android.tools.r8.experimental.enablecheckenumunboxed", false);
       enableExperimentalConvertCheckNotNull =
           parseSystemPropertyOrDefault(
               "com.android.tools.r8.experimental.enableconvertchecknotnull", false);
@@ -55,6 +66,12 @@
       return this;
     }
 
+    public Builder setEnableExperimentalCheckEnumUnboxed(
+        boolean enableExperimentalCheckEnumUnboxed) {
+      this.enableExperimentalCheckEnumUnboxed = enableExperimentalCheckEnumUnboxed;
+      return this;
+    }
+
     public Builder setEnableExperimentalConvertCheckNotNull(
         boolean enableExperimentalConvertCheckNotNull) {
       this.enableExperimentalConvertCheckNotNull = enableExperimentalConvertCheckNotNull;
@@ -74,6 +91,7 @@
 
     public ProguardConfigurationParserOptions build() {
       return new ProguardConfigurationParserOptions(
+          enableExperimentalCheckEnumUnboxed,
           enableExperimentalConvertCheckNotNull,
           enableExperimentalWhyAreYouNotInlining,
           enableTestingOptions);
diff --git a/src/main/java/com/android/tools/r8/shaking/RootSetUtils.java b/src/main/java/com/android/tools/r8/shaking/RootSetUtils.java
index 762a307..d804fa6 100644
--- a/src/main/java/com/android/tools/r8/shaking/RootSetUtils.java
+++ b/src/main/java/com/android/tools/r8/shaking/RootSetUtils.java
@@ -261,6 +261,8 @@
         throw new Unreachable("-if rule will be evaluated separately, not here.");
       } else if (rule.isProguardCheckDiscardRule()) {
         evaluateCheckDiscardRule(clazz, rule.asProguardCheckDiscardRule());
+      } else if (rule instanceof CheckEnumUnboxedRule) {
+        evaluateCheckEnumUnboxedRule(clazz, (CheckEnumUnboxedRule) rule);
       } else if (rule instanceof ProguardWhyAreYouKeepingRule) {
         markClass(clazz, rule, ifRule);
         markMatchingVisibleMethods(clazz, memberKeepRules, rule, null, true, ifRule);
@@ -1477,6 +1479,36 @@
       context.markAsUsed();
     }
 
+    private void evaluateCheckEnumUnboxedRule(DexClass clazz, CheckEnumUnboxedRule rule) {
+      if (clazz.isProgramClass()) {
+        if (clazz.isEnum()) {
+          dependentMinimumKeepInfo
+              .getOrCreateUnconditionalMinimumKeepInfo()
+              .getOrCreateMinimumKeepInfoFor(clazz.getType())
+              .asClassJoiner()
+              .setCheckEnumUnboxed();
+        } else {
+          StringDiagnostic warning =
+              new StringDiagnostic(
+                  "The rule `"
+                      + rule
+                      + "` matches the non-enum class "
+                      + clazz.getTypeName()
+                      + ".");
+          appView.reporter().warning(warning);
+        }
+      } else {
+        StringDiagnostic warning =
+            new StringDiagnostic(
+                "The rule `"
+                    + rule
+                    + "` matches the non-program class "
+                    + clazz.getTypeName()
+                    + ".");
+        appView.reporter().warning(warning);
+      }
+    }
+
     private void evaluateKeepRule(
         ProgramDefinition item,
         ProguardKeepRule context,
diff --git a/src/test/java/com/android/tools/r8/CheckEnumUnboxed.java b/src/test/java/com/android/tools/r8/CheckEnumUnboxed.java
new file mode 100644
index 0000000..11d8719
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/CheckEnumUnboxed.java
@@ -0,0 +1,11 @@
+// Copyright (c) 2022, 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;
+
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Target;
+
+@Target({ElementType.TYPE})
+public @interface CheckEnumUnboxed {}
diff --git a/src/test/java/com/android/tools/r8/R8TestBuilder.java b/src/test/java/com/android/tools/r8/R8TestBuilder.java
index b3bb43e..ebe8352 100644
--- a/src/test/java/com/android/tools/r8/R8TestBuilder.java
+++ b/src/test/java/com/android/tools/r8/R8TestBuilder.java
@@ -15,6 +15,7 @@
 import com.android.tools.r8.errors.Unreachable;
 import com.android.tools.r8.experimental.graphinfo.GraphConsumer;
 import com.android.tools.r8.origin.Origin;
+import com.android.tools.r8.shaking.CheckEnumUnboxedRule;
 import com.android.tools.r8.shaking.CollectingGraphConsumer;
 import com.android.tools.r8.shaking.KeepUnusedReturnValueRule;
 import com.android.tools.r8.shaking.NoFieldTypeStrengtheningRule;
@@ -498,6 +499,12 @@
     return addOptionsModification(options -> options.testing.allowInliningOfSynthetics = false);
   }
 
+  public T enableCheckEnumUnboxedAnnotations() {
+    return addCheckEnumUnboxedAnnotation()
+        .addInternalMatchInterfaceRule(CheckEnumUnboxedRule.RULE_NAME, CheckEnumUnboxed.class)
+        .enableExperimentalCheckEnumUnboxed();
+  }
+
   public T enableKeepUnusedReturnValueAnnotations() {
     return addKeepUnusedReturnValueAnnotation()
         .addInternalMatchAnnotationOnMethodRule(
@@ -655,6 +662,11 @@
     return self();
   }
 
+  public T enableExperimentalCheckEnumUnboxed() {
+    builder.setEnableExperimentalCheckEnumUnboxed();
+    return self();
+  }
+
   public T enableExperimentalConvertCheckNotNull() {
     builder.setEnableExperimentalConvertCheckNotNull();
     return self();
diff --git a/src/test/java/com/android/tools/r8/TestShrinkerBuilder.java b/src/test/java/com/android/tools/r8/TestShrinkerBuilder.java
index a5c5302..58e5901 100644
--- a/src/test/java/com/android/tools/r8/TestShrinkerBuilder.java
+++ b/src/test/java/com/android/tools/r8/TestShrinkerBuilder.java
@@ -436,6 +436,10 @@
     return addTestingAnnotation(AssumeNoSideEffects.class);
   }
 
+  public final T addCheckEnumUnboxedAnnotation() {
+    return addTestingAnnotation(CheckEnumUnboxed.class);
+  }
+
   public final T addConstantArgumentAnnotations() {
     return addTestingAnnotation(KeepConstantArguments.class);
   }
diff --git a/src/test/java/com/android/tools/r8/enumunboxing/CheckEnumUnboxedTest.java b/src/test/java/com/android/tools/r8/enumunboxing/CheckEnumUnboxedTest.java
new file mode 100644
index 0000000..c5a786c
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/enumunboxing/CheckEnumUnboxedTest.java
@@ -0,0 +1,80 @@
+// Copyright (c) 2022, 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 com.android.tools.r8.DiagnosticsMatcher.diagnosticMessage;
+import static com.android.tools.r8.DiagnosticsMatcher.diagnosticType;
+import static org.hamcrest.CoreMatchers.allOf;
+import static org.hamcrest.CoreMatchers.equalTo;
+
+import com.android.tools.r8.CheckEnumUnboxed;
+import com.android.tools.r8.TestBase;
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.TestParametersCollection;
+import com.android.tools.r8.errors.CheckEnumUnboxedDiagnostic;
+import com.android.tools.r8.utils.codeinspector.AssertUtils;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+import org.junit.runners.Parameterized.Parameter;
+import org.junit.runners.Parameterized.Parameters;
+
+@RunWith(Parameterized.class)
+public class CheckEnumUnboxedTest extends TestBase {
+
+  @Parameter(0)
+  public TestParameters parameters;
+
+  @Parameters(name = "{0}")
+  public static TestParametersCollection data() {
+    return getTestParameters().withAllRuntimesAndApiLevels().build();
+  }
+
+  @Test
+  public void test() throws Exception {
+    AssertUtils.assertFailsCompilation(
+        () ->
+            testForR8(parameters.getBackend())
+                .addInnerClasses(getClass())
+                .addKeepMainRule(Main.class)
+                .enableCheckEnumUnboxedAnnotations()
+                .setMinApi(parameters.getApiLevel())
+                .compileWithExpectedDiagnostics(
+                    diagnostics -> {
+                      diagnostics.assertErrorsMatch(
+                          allOf(
+                              diagnosticType(CheckEnumUnboxedDiagnostic.class),
+                              diagnosticMessage(
+                                  equalTo(
+                                      "Enum unboxing checks failed."
+                                          + System.lineSeparator()
+                                          + "Enum "
+                                          + EscapingEnum.class.getTypeName()
+                                          + " was not unboxed."))));
+                    }));
+  }
+
+  static class Main {
+
+    public static void main(String[] args) {
+      EscapingEnum escapingEnum = System.currentTimeMillis() > 0 ? EscapingEnum.A : EscapingEnum.B;
+      System.out.println(escapingEnum);
+      UnboxedEnum unboxedEnum = System.currentTimeMillis() > 0 ? UnboxedEnum.A : UnboxedEnum.B;
+      System.out.println(unboxedEnum.ordinal());
+    }
+  }
+
+  @CheckEnumUnboxed
+  enum EscapingEnum {
+    A,
+    B
+  }
+
+  @CheckEnumUnboxed
+  enum UnboxedEnum {
+    A,
+    B
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/shaking/ProguardConfigurationParserTest.java b/src/test/java/com/android/tools/r8/shaking/ProguardConfigurationParserTest.java
index 5ff24ab..d0ef627 100644
--- a/src/test/java/com/android/tools/r8/shaking/ProguardConfigurationParserTest.java
+++ b/src/test/java/com/android/tools/r8/shaking/ProguardConfigurationParserTest.java
@@ -188,6 +188,7 @@
             new DexItemFactory(),
             reporter,
             ProguardConfigurationParserOptions.builder()
+                .setEnableExperimentalCheckEnumUnboxed(false)
                 .setEnableExperimentalConvertCheckNotNull(false)
                 .setEnableExperimentalWhyAreYouNotInlining(false)
                 .setEnableTestingOptions(true)
@@ -754,7 +755,16 @@
   @Test
   public void testConvertCheckNotNullWithoutReturn() {
     DexItemFactory dexItemFactory = new DexItemFactory();
-    ProguardConfigurationParser parser = new ProguardConfigurationParser(dexItemFactory, reporter);
+    ProguardConfigurationParser parser =
+        new ProguardConfigurationParser(
+            dexItemFactory,
+            reporter,
+            ProguardConfigurationParserOptions.builder()
+                .setEnableExperimentalCheckEnumUnboxed(false)
+                .setEnableExperimentalConvertCheckNotNull(true)
+                .setEnableExperimentalWhyAreYouNotInlining(false)
+                .setEnableTestingOptions(false)
+                .build());
     String rule = "-convertchecknotnull class C { void m(**, ...); }";
     parser.parse(createConfigurationForTesting(ImmutableList.of(rule)));
     verifyParserEndsCleanly();