Use Enum#valueOf(String) in type switch

- This creates an edge from enumEq methods to
  the clinit of the corresponding enum.

Change-Id: I271afccdd11129c1e255e452ef5975e534b96c1a
diff --git a/src/main/java/com/android/tools/r8/graph/FieldAccessFlags.java b/src/main/java/com/android/tools/r8/graph/FieldAccessFlags.java
index cc2e10c..a3581c9 100644
--- a/src/main/java/com/android/tools/r8/graph/FieldAccessFlags.java
+++ b/src/main/java/com/android/tools/r8/graph/FieldAccessFlags.java
@@ -137,6 +137,10 @@
     set(Constants.ACC_ENUM);
   }
 
+  public void unsetEnum() {
+    unset(Constants.ACC_ENUM);
+  }
+
   public static class Builder extends BuilderBase<Builder, FieldAccessFlags> {
 
     public Builder() {
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/typeswitch/SwitchHelperGenerator.java b/src/main/java/com/android/tools/r8/ir/desugar/typeswitch/SwitchHelperGenerator.java
index 72098d9..5010220 100644
--- a/src/main/java/com/android/tools/r8/ir/desugar/typeswitch/SwitchHelperGenerator.java
+++ b/src/main/java/com/android/tools/r8/ir/desugar/typeswitch/SwitchHelperGenerator.java
@@ -6,9 +6,9 @@
 
 import static com.android.tools.r8.ir.synthetic.TypeSwitchSyntheticCfCodeProvider.allowsInlinedIntegerEquality;
 
-import com.android.tools.r8.cf.code.CfConstClass;
 import com.android.tools.r8.cf.code.CfConstNumber;
 import com.android.tools.r8.cf.code.CfInstruction;
+import com.android.tools.r8.cf.code.CfInvoke;
 import com.android.tools.r8.cf.code.CfNewArray;
 import com.android.tools.r8.cf.code.CfReturnVoid;
 import com.android.tools.r8.cf.code.CfStaticFieldWrite;
@@ -146,7 +146,21 @@
           CfCode cfCode = TypeSwitchMethods.TypeSwitchMethods_switchEnumEq(factory, methodSig);
           List<CfInstruction> newInstructions =
               ListUtils.map(
-                  cfCode.getInstructions(), i -> i.isConstClass() ? new CfConstClass(enumType) : i);
+                  cfCode.getInstructions(),
+                  i -> {
+                    if (i.isInvokeStatic()) {
+                      CfInvoke invoke = i.asInvoke();
+                      if (invoke.getMethod().getName().isIdenticalTo(factory.valueOfMethodName)) {
+                        DexMethod newMethod =
+                            factory.createMethod(
+                                enumType,
+                                factory.createProto(enumType, factory.stringType),
+                                factory.valueOfMethodName);
+                        return new CfInvoke(invoke.getOpcode(), newMethod, invoke.isInterface());
+                      }
+                    }
+                    return i;
+                  });
           cfCode.setInstructions(newInstructions);
           return cfCode;
         },
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/typeswitch/TypeSwitchIRRewriter.java b/src/main/java/com/android/tools/r8/ir/desugar/typeswitch/TypeSwitchIRRewriter.java
index ea11bfe..3727b03 100644
--- a/src/main/java/com/android/tools/r8/ir/desugar/typeswitch/TypeSwitchIRRewriter.java
+++ b/src/main/java/com/android/tools/r8/ir/desugar/typeswitch/TypeSwitchIRRewriter.java
@@ -4,7 +4,6 @@
 
 package com.android.tools.r8.ir.desugar.typeswitch;
 
-import com.android.tools.r8.dex.code.CfOrDexInstruction;
 import com.android.tools.r8.graph.AppView;
 import com.android.tools.r8.graph.DefaultUseRegistryWithResult;
 import com.android.tools.r8.graph.DexField;
@@ -35,7 +34,6 @@
 import com.android.tools.r8.utils.collections.BidirectionalManyToOneMap;
 import com.google.common.collect.ImmutableList;
 import java.util.IdentityHashMap;
-import java.util.ListIterator;
 import java.util.Map;
 import java.util.concurrent.ConcurrentHashMap;
 
@@ -77,12 +75,13 @@
         UseRegistryWithResult<DexType, ProgramMethod> registry =
             new DefaultUseRegistryWithResult<>(appView, uniqueStaticMethod) {
               @Override
-              public void registerConstClass(
-                  DexType type,
-                  ListIterator<? extends CfOrDexInstruction> iterator,
-                  boolean ignoreCompatRules) {
+              public void registerInvokeStatic(DexMethod method) {
                 assert getResult() == null;
-                setResult(type);
+                if (method.getName().isIdenticalTo(dexItemFactory().valueOfMethodName)
+                    && method.getArity() == 1
+                    && method.getParameter(0).isIdenticalTo(dexItemFactory().stringType)) {
+                  setResult(method.getHolderType());
+                }
               }
             };
         DexType enumType = uniqueStaticMethod.registerCodeReferencesWithResult(registry);
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/typeswitch/TypeSwitchMethods.java b/src/main/java/com/android/tools/r8/ir/desugar/typeswitch/TypeSwitchMethods.java
index 4bb8026..08611d3 100644
--- a/src/main/java/com/android/tools/r8/ir/desugar/typeswitch/TypeSwitchMethods.java
+++ b/src/main/java/com/android/tools/r8/ir/desugar/typeswitch/TypeSwitchMethods.java
@@ -11,7 +11,6 @@
 import com.android.tools.r8.cf.code.CfArrayLoad;
 import com.android.tools.r8.cf.code.CfArrayStore;
 import com.android.tools.r8.cf.code.CfCheckCast;
-import com.android.tools.r8.cf.code.CfConstClass;
 import com.android.tools.r8.cf.code.CfConstNull;
 import com.android.tools.r8.cf.code.CfConstNumber;
 import com.android.tools.r8.cf.code.CfFrame;
@@ -42,7 +41,8 @@
 public final class TypeSwitchMethods {
 
   public static void registerSynthesizedCodeReferences(DexItemFactory factory) {
-    factory.createSynthesizedType("Ljava/lang/Enum;");
+    factory.createSynthesizedType(
+        "Lcom/android/tools/r8/cfmethodgeneration/TypeSwitchMethods$Enum;");
     factory.createSynthesizedType("Ljava/lang/Number;");
     factory.createSynthesizedType("[Ljava/lang/Object;");
   }
@@ -60,66 +60,36 @@
     CfLabel label9 = new CfLabel();
     CfLabel label10 = new CfLabel();
     CfLabel label11 = new CfLabel();
-    CfLabel label12 = new CfLabel();
-    CfLabel label13 = new CfLabel();
-    CfLabel label14 = new CfLabel();
     return new CfCode(
         method.holder,
         4,
-        7,
+        6,
         ImmutableList.of(
             label0,
             new CfLoad(ValueType.OBJECT, 1),
             new CfLoad(ValueType.INT, 2),
             new CfArrayLoad(MemberType.OBJECT),
-            new CfIf(IfType.NE, ValueType.OBJECT, label11),
+            new CfIf(IfType.NE, ValueType.OBJECT, label8),
             label1,
             new CfConstNull(),
             new CfStore(ValueType.OBJECT, 4),
             label2,
-            new CfConstClass(factory.createType("Ljava/lang/Enum;")),
-            new CfStore(ValueType.OBJECT, 5),
-            label3,
-            new CfLoad(ValueType.OBJECT, 5),
-            new CfInvoke(
-                182,
-                factory.createMethod(
-                    factory.classType,
-                    factory.createProto(factory.booleanType),
-                    factory.createString("isEnum")),
-                false),
-            new CfIf(IfType.EQ, ValueType.INT, label6),
-            label4,
-            new CfLoad(ValueType.OBJECT, 5),
-            new CfStore(ValueType.OBJECT, 6),
-            label5,
-            new CfLoad(ValueType.OBJECT, 6),
             new CfLoad(ValueType.OBJECT, 3),
             new CfInvoke(
                 184,
                 factory.createMethod(
-                    factory.createType("Ljava/lang/Enum;"),
+                    factory.createType(
+                        "Lcom/android/tools/r8/cfmethodgeneration/TypeSwitchMethods$Enum;"),
                     factory.createProto(
-                        factory.createType("Ljava/lang/Enum;"),
-                        factory.classType,
+                        factory.createType(
+                            "Lcom/android/tools/r8/cfmethodgeneration/TypeSwitchMethods$Enum;"),
                         factory.stringType),
                     factory.createString("valueOf")),
                 false),
             new CfStore(ValueType.OBJECT, 4),
-            label6,
-            new CfFrame(
-                new Int2ObjectAVLTreeMap<>(
-                    new int[] {0, 1, 2, 3, 4},
-                    new FrameType[] {
-                      FrameType.initializedNonNullReference(factory.objectType),
-                      FrameType.initializedNonNullReference(
-                          factory.createType("[Ljava/lang/Object;")),
-                      FrameType.intType(),
-                      FrameType.initializedNonNullReference(factory.stringType),
-                      FrameType.initializedNonNullReference(factory.objectType)
-                    })),
-            new CfGoto(label8),
-            label7,
+            label3,
+            new CfGoto(label5),
+            label4,
             new CfFrame(
                 new Int2ObjectAVLTreeMap<>(
                     new int[] {0, 1, 2, 3, 4},
@@ -134,7 +104,7 @@
                 new ArrayDeque<>(
                     Arrays.asList(FrameType.initializedNonNullReference(factory.throwableType)))),
             new CfStore(ValueType.OBJECT, 5),
-            label8,
+            label5,
             new CfFrame(
                 new Int2ObjectAVLTreeMap<>(
                     new int[] {0, 1, 2, 3, 4},
@@ -149,7 +119,7 @@
             new CfLoad(ValueType.OBJECT, 1),
             new CfLoad(ValueType.INT, 2),
             new CfLoad(ValueType.OBJECT, 4),
-            new CfIf(IfType.NE, ValueType.OBJECT, label9),
+            new CfIf(IfType.NE, ValueType.OBJECT, label6),
             new CfNew(factory.objectType),
             new CfStackInstruction(CfStackInstruction.Opcode.Dup),
             new CfInvoke(
@@ -159,8 +129,8 @@
                     factory.createProto(factory.voidType),
                     factory.createString("<init>")),
                 false),
-            new CfGoto(label10),
-            label9,
+            new CfGoto(label7),
+            label6,
             new CfFrame(
                 new Int2ObjectAVLTreeMap<>(
                     new int[] {0, 1, 2, 3, 4},
@@ -178,7 +148,7 @@
                             factory.createType("[Ljava/lang/Object;")),
                         FrameType.intType()))),
             new CfLoad(ValueType.OBJECT, 4),
-            label10,
+            label7,
             new CfFrame(
                 new Int2ObjectAVLTreeMap<>(
                     new int[] {0, 1, 2, 3, 4},
@@ -197,7 +167,7 @@
                         FrameType.intType(),
                         FrameType.initializedNonNullReference(factory.objectType)))),
             new CfArrayStore(MemberType.OBJECT),
-            label11,
+            label8,
             new CfFrame(
                 new Int2ObjectAVLTreeMap<>(
                     new int[] {0, 1, 2, 3},
@@ -212,10 +182,10 @@
             new CfLoad(ValueType.OBJECT, 1),
             new CfLoad(ValueType.INT, 2),
             new CfArrayLoad(MemberType.OBJECT),
-            new CfIfCmp(IfType.NE, ValueType.OBJECT, label12),
+            new CfIfCmp(IfType.NE, ValueType.OBJECT, label9),
             new CfConstNumber(1, ValueType.INT),
-            new CfGoto(label13),
-            label12,
+            new CfGoto(label10),
+            label9,
             new CfFrame(
                 new Int2ObjectAVLTreeMap<>(
                     new int[] {0, 1, 2, 3},
@@ -227,7 +197,7 @@
                       FrameType.initializedNonNullReference(factory.stringType)
                     })),
             new CfConstNumber(0, ValueType.INT),
-            label13,
+            label10,
             new CfFrame(
                 new Int2ObjectAVLTreeMap<>(
                     new int[] {0, 1, 2, 3},
@@ -240,10 +210,10 @@
                     }),
                 new ArrayDeque<>(Arrays.asList(FrameType.intType()))),
             new CfReturn(ValueType.INT),
-            label14),
+            label11),
         ImmutableList.of(
             new CfTryCatch(
-                label2, label6, ImmutableList.of(factory.throwableType), ImmutableList.of(label7))),
+                label2, label3, ImmutableList.of(factory.throwableType), ImmutableList.of(label4))),
         ImmutableList.of());
   }
 
diff --git a/src/test/java/com/android/tools/r8/cfmethodgeneration/TypeSwitchMethods.java b/src/test/java/com/android/tools/r8/cfmethodgeneration/TypeSwitchMethods.java
index a9e65d0..16b1a6c 100644
--- a/src/test/java/com/android/tools/r8/cfmethodgeneration/TypeSwitchMethods.java
+++ b/src/test/java/com/android/tools/r8/cfmethodgeneration/TypeSwitchMethods.java
@@ -6,17 +6,17 @@
 
 public class TypeSwitchMethods {
 
+  private enum Enum {
+    A;
+  }
+
   // By design this is lock-free so the JVM may compute several times the same value.
   public static boolean switchEnumEq(Object value, Object[] cache, int index, String name) {
     if (cache[index] == null) {
       Object resolved = null;
       try {
         // Enum.class should be replaced when used by the correct enum class.
-        Class<?> clazz = Enum.class;
-        if (clazz.isEnum()) {
-          Class<? extends Enum> enumClazz = (Class<? extends Enum>) clazz;
-          resolved = Enum.valueOf(enumClazz, name);
-        }
+        resolved = Enum.valueOf(name);
       } catch (Throwable t) {
       }
       // R8 sets a sentinel if resolution has failed.
diff --git a/src/test/java24/com/android/tools/r8/jdk24/switchpatternmatching/EnumSwitchOldSyntaxTest.java b/src/test/java24/com/android/tools/r8/jdk24/switchpatternmatching/EnumSwitchOldSyntaxTest.java
index 52a69d3..7f4b2de 100644
--- a/src/test/java24/com/android/tools/r8/jdk24/switchpatternmatching/EnumSwitchOldSyntaxTest.java
+++ b/src/test/java24/com/android/tools/r8/jdk24/switchpatternmatching/EnumSwitchOldSyntaxTest.java
@@ -3,12 +3,16 @@
 // BSD-style license that can be found in the LICENSE file.
 package com.android.tools.r8.jdk24.switchpatternmatching;
 
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertTrue;
+
 import com.android.tools.r8.JdkClassFileProvider;
 import com.android.tools.r8.TestBase;
 import com.android.tools.r8.TestParameters;
 import com.android.tools.r8.TestParametersCollection;
 import com.android.tools.r8.TestRuntime.CfVm;
 import com.android.tools.r8.utils.StringUtils;
+import com.android.tools.r8.utils.codeinspector.CodeInspector;
 import org.junit.Test;
 import org.junit.runner.RunWith;
 import org.junit.runners.Parameterized;
@@ -60,9 +64,19 @@
         .addKeepMainRule(Main.class)
         .addKeepEnumsRule()
         .run(parameters.getRuntime(), Main.class)
+        .inspect(this::assert2Classes)
         .assertSuccessWithOutput(EXPECTED_OUTPUT);
   }
 
+  private void assert2Classes(CodeInspector i) {
+    if (parameters.getPartialCompilationTestParameters().isSome()) {
+      return;
+    }
+    assertTrue(i.clazz(E.class).isPresent());
+    assertTrue(i.clazz(Main.class).isPresent());
+    assertEquals(2, i.allClasses().size());
+  }
+
   public enum E {
     E1,
     E2,
diff --git a/src/test/java24/com/android/tools/r8/jdk24/switchpatternmatching/TypeSwitchEnumAsClassTest.java b/src/test/java24/com/android/tools/r8/jdk24/switchpatternmatching/TypeSwitchEnumAsClassTest.java
new file mode 100644
index 0000000..1e99ca6
--- /dev/null
+++ b/src/test/java24/com/android/tools/r8/jdk24/switchpatternmatching/TypeSwitchEnumAsClassTest.java
@@ -0,0 +1,130 @@
+// Copyright (c) 2025, 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.jdk24.switchpatternmatching;
+
+import static com.android.tools.r8.desugar.switchpatternmatching.SwitchTestHelper.hasJdk21TypeSwitch;
+import static org.junit.Assert.assertTrue;
+import static org.junit.Assume.assumeTrue;
+
+import com.android.tools.r8.JdkClassFileProvider;
+import com.android.tools.r8.TestBase;
+import com.android.tools.r8.TestBuilder;
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.TestParametersCollection;
+import com.android.tools.r8.TestRunResult;
+import com.android.tools.r8.TestRuntime.CfVm;
+import com.android.tools.r8.ToolHelper;
+import com.android.tools.r8.utils.StringUtils;
+import com.android.tools.r8.utils.codeinspector.CodeInspector;
+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 TypeSwitchEnumAsClassTest extends TestBase {
+
+  @Parameter(0)
+  public TestParameters parameters;
+
+  @Parameters(name = "{0}")
+  public static TestParametersCollection data() {
+    return getTestParameters()
+        .withCfRuntimesStartingFromIncluding(CfVm.JDK24)
+        .withDexRuntimes()
+        .withAllApiLevelsAlsoForCf()
+        .withPartialCompilation()
+        .build();
+  }
+
+  public static String EXPECTED_OUTPUT =
+      StringUtils.lines("null", "String", "Array of int, length = 0", "Other");
+
+  @Test
+  public void testJvm() throws Exception {
+    assumeTrue(parameters.isCfRuntime());
+    CodeInspector inspector = new CodeInspector(ToolHelper.getClassFileForTestClass(Main.class));
+    assertTrue(
+        hasJdk21TypeSwitch(inspector.clazz(Main.class).uniqueMethodWithOriginalName("typeSwitch")));
+
+    parameters.assumeJvmTestParameters();
+    testForJvm(parameters)
+        .apply(this::addModifiedProgramClasses)
+        .run(parameters.getRuntime(), Main.class)
+        .apply(this::assertResult);
+  }
+
+  private void assertResult(TestRunResult<?> r) {
+    r.assertSuccessWithOutput(EXPECTED_OUTPUT);
+  }
+
+  private <T extends TestBuilder<?, T>> void addModifiedProgramClasses(
+      TestBuilder<?, T> testBuilder) throws Exception {
+    testBuilder
+        .addProgramClassFileData(transformer(Main.class).clearNest().transform())
+        .addProgramClassFileData(transformer(C.class).clearNest().transform())
+        .addProgramClassFileData(transformer(Color.class).clearEnum().clearNest().transform());
+  }
+
+  @Test
+  public void testD8() throws Exception {
+    testForD8(parameters)
+        .apply(this::addModifiedProgramClasses)
+        .run(parameters.getRuntime(), Main.class)
+        .apply(this::assertResult);
+  }
+
+  @Test
+  public void testR8() throws Exception {
+    parameters.assumeR8TestParameters();
+    testForR8(parameters)
+        .apply(this::addModifiedProgramClasses)
+        .applyIf(
+            parameters.isCfRuntime(),
+            b -> b.addLibraryProvider(JdkClassFileProvider.fromSystemJdk()))
+        .addKeepMainRule(Main.class)
+        .run(parameters.getRuntime(), Main.class)
+        .apply(this::assertResult);
+  }
+
+  // Enum will be a class at runtime.
+  enum Color {
+    RED,
+    GREEN,
+    BLUE;
+  }
+
+  // Class will be missing at runtime.
+  static class C {
+
+    @Override
+    public String toString() {
+      return "CCC";
+    }
+  }
+
+  static class Main {
+
+    static void typeSwitch(Object obj) {
+      switch (obj) {
+        case null -> System.out.println("null");
+        case Color.RED -> System.out.println("RED!!!");
+        case Color.BLUE -> System.out.println("BLUE!!!");
+        case Color.GREEN -> System.out.println("GREEN!!!");
+        case String string -> System.out.println("String");
+        case C c -> System.out.println(c.toString() + "!!!");
+        case int[] intArray -> System.out.println("Array of int, length = " + intArray.length);
+        default -> System.out.println("Other");
+      }
+    }
+
+    public static void main(String[] args) {
+      typeSwitch(null);
+      typeSwitch("s");
+      typeSwitch(new int[] {});
+      typeSwitch(new Object());
+    }
+  }
+}
diff --git a/src/test/testbase/java/com/android/tools/r8/transformers/ClassFileTransformer.java b/src/test/testbase/java/com/android/tools/r8/transformers/ClassFileTransformer.java
index 8b3e63c..1998f5e 100644
--- a/src/test/testbase/java/com/android/tools/r8/transformers/ClassFileTransformer.java
+++ b/src/test/testbase/java/com/android/tools/r8/transformers/ClassFileTransformer.java
@@ -415,6 +415,53 @@
         });
   }
 
+  // Note that this clears <init> and <clinit> methods.
+  public ClassFileTransformer clearEnum() {
+    return addClassTransformer(
+        new ClassTransformer() {
+          @Override
+          public void visit(
+              int version,
+              int access,
+              String name,
+              String signature,
+              String superName,
+              String[] interfaces) {
+            ClassAccessFlags classAccessFlags = ClassAccessFlags.fromCfAccessFlags(access);
+            assert classAccessFlags.isEnum();
+            classAccessFlags.unsetEnum();
+            String newSuper = superName.equals("java/lang/Enum") ? "java/lang/Object" : superName;
+            super.visit(
+                version,
+                classAccessFlags.getAsCfAccessFlags(),
+                name,
+                signature,
+                newSuper,
+                interfaces);
+          }
+
+          @Override
+          public MethodVisitor visitMethod(
+              int access, String name, String descriptor, String signature, String[] exceptions) {
+            if (name.equals("<clinit>") || name.equals("<init>")) {
+              return null;
+            }
+            return super.visitMethod(access, name, descriptor, signature, exceptions);
+          }
+
+          @Override
+          public FieldVisitor visitField(
+              int access, String name, String descriptor, String signature, Object value) {
+            FieldAccessFlags fieldAccessFlags = FieldAccessFlags.fromCfAccessFlags(access);
+            if (fieldAccessFlags.isEnum()) {
+              fieldAccessFlags.unsetEnum();
+            }
+            return super.visitField(
+                fieldAccessFlags.getAsCfAccessFlags(), name, descriptor, signature, value);
+          }
+        });
+  }
+
   public ClassFileTransformer clearNest() {
     return setMinVersion(CfVm.JDK11)
         .addClassTransformer(