Revert "Revert due to bot failures"

This reverts commit 4a23f4c6723db6c08f8d1117600d6a2792852eb9.

Change-Id: Ibcb0eab0c11b5b40e3259f3a8ae2800fcc8bdc47
diff --git a/src/main/java/com/android/tools/r8/R8.java b/src/main/java/com/android/tools/r8/R8.java
index f6279b7..b148ad9 100644
--- a/src/main/java/com/android/tools/r8/R8.java
+++ b/src/main/java/com/android/tools/r8/R8.java
@@ -541,6 +541,8 @@
       // Collect the already pruned types before creating a new app info without liveness.
       // TODO: we should avoid removing liveness.
       Set<DexType> prunedTypes = appView.withLiveness().appInfo().getPrunedTypes();
+      Set<DexType> prunedClasspathTypes =
+          appView.withLiveness().appInfo().getClasspathTypesIncludingPruned();
 
       assert ArtProfileCompletenessChecker.verify(appView);
 
@@ -584,6 +586,7 @@
                   ImmediateAppSubtypingInfo.create(appView),
                   keptGraphConsumer,
                   prunedTypes,
+                  prunedClasspathTypes,
                   finalRuntimeTypeCheckInfoBuilder);
           EnqueuerResult enqueuerResult =
               enqueuer.traceApplication(appView.rootSet(), executorService, timing);
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/main/java/com/android/tools/r8/naming/ClassNameMinifier.java b/src/main/java/com/android/tools/r8/naming/ClassNameMinifier.java
index 4d87bcd..1ccba6d 100644
--- a/src/main/java/com/android/tools/r8/naming/ClassNameMinifier.java
+++ b/src/main/java/com/android/tools/r8/naming/ClassNameMinifier.java
@@ -31,6 +31,7 @@
 import java.util.Map;
 import java.util.Map.Entry;
 import java.util.Set;
+import java.util.function.Function;
 import java.util.function.Predicate;
 
 class ClassNameMinifier {
@@ -88,17 +89,30 @@
   ClassRenaming computeRenaming(Timing timing) {
     // Collect names we have to keep.
     timing.begin("reserve");
+    if (appView.options().isMinifying()
+        && !appView.options().getProguardConfiguration().hasApplyMappingFile()) {
+      for (DexType reserved : appView.appInfo().getClasspathTypesIncludingPruned()) {
+        registerClassAsUsed(
+            reserved,
+            reserved.getDescriptor(),
+            appView.appInfo()::definitionForWithoutExistenceAssert);
+      }
+    }
     for (ProgramOrClasspathClass clazz : classes) {
-      DexString descriptor = classNamingStrategy.reservedDescriptor(clazz.getType());
+      DexType type = clazz.getType();
+      DexString descriptor = classNamingStrategy.reservedDescriptor(type, appView::definitionFor);
       if (descriptor != null) {
-        assert !renaming.containsKey(clazz.getType());
-        registerClassAsUsed(clazz.getType(), descriptor);
+        assert !renaming.containsKey(type);
+        registerClassAsUsed(type, descriptor, appView::definitionFor);
       }
     }
     appView
         .appInfo()
         .getMissingClasses()
-        .forEach(missingClass -> registerClassAsUsed(missingClass, missingClass.getDescriptor()));
+        .forEach(
+            missingClass ->
+                registerClassAsUsed(
+                    missingClass, missingClass.getDescriptor(), appView::definitionFor));
     timing.end();
 
     timing.begin("rename-classes");
@@ -167,29 +181,30 @@
       // return type. As we don't need the class, we can rename it to anything as long as it is
       // unique.
       assert appView.definitionFor(type) == null;
-      DexString descriptor = classNamingStrategy.reservedDescriptor(type);
+      DexString descriptor = classNamingStrategy.reservedDescriptor(type, appView::definitionFor);
       renaming.put(type, descriptor != null ? descriptor : topLevelState.nextTypeName(type));
     }
   }
 
-  private void registerClassAsUsed(DexType type, DexString descriptor) {
+  private void registerClassAsUsed(
+      DexType type, DexString descriptor, Function<DexType, DexClass> definitionFor) {
     renaming.put(type, descriptor);
     setUsedTypeName(descriptor.toString());
     if (keepInnerClassStructure) {
-      DexType outerClass = getOutClassForType(type);
+      DexType outerClass = getOutClassForType(type, definitionFor);
       if (outerClass != null) {
         if (!renaming.containsKey(outerClass)
-            && classNamingStrategy.reservedDescriptor(outerClass) == null) {
+            && classNamingStrategy.reservedDescriptor(outerClass, definitionFor) == null) {
           // The outer class was not previously kept and will not be kept.
           // We have to force keep the outer class now.
-          registerClassAsUsed(outerClass, outerClass.descriptor);
+          registerClassAsUsed(outerClass, outerClass.descriptor, definitionFor);
         }
       }
     }
   }
 
-  private DexType getOutClassForType(DexType type) {
-    DexClass clazz = appView.definitionFor(type);
+  private DexType getOutClassForType(DexType type, Function<DexType, DexClass> definitionFor) {
+    DexClass clazz = definitionFor.apply(type);
     if (clazz == null) {
       return null;
     }
@@ -211,7 +226,7 @@
       // When keeping the nesting structure of inner classes, bind this type to the live context.
       // Note that such live context might not be always the enclosing class. E.g., a local class
       // does not have a direct enclosing class, but we use the holder of the enclosing method here.
-      DexType outerClass = getOutClassForType(type);
+      DexType outerClass = getOutClassForType(type, appView::definitionFor);
       if (outerClass != null) {
         DexClass clazz = appView.definitionFor(type);
         assert clazz != null;
@@ -320,7 +335,7 @@
      * @param type The type to find a reserved descriptor for
      * @return The reserved descriptor
      */
-    DexString reservedDescriptor(DexType type);
+    DexString reservedDescriptor(DexType type, Function<DexType, DexClass> definitionFor);
 
     boolean isRenamedByApplyMapping(DexType type);
   }
diff --git a/src/main/java/com/android/tools/r8/naming/Minifier.java b/src/main/java/com/android/tools/r8/naming/Minifier.java
index 6828ffd..aae1702 100644
--- a/src/main/java/com/android/tools/r8/naming/Minifier.java
+++ b/src/main/java/com/android/tools/r8/naming/Minifier.java
@@ -45,6 +45,7 @@
 import java.util.concurrent.ExecutionException;
 import java.util.concurrent.ExecutorService;
 import java.util.function.BiPredicate;
+import java.util.function.Function;
 import java.util.function.Predicate;
 
 public class Minifier {
@@ -76,7 +77,7 @@
 
     assert new MinifiedRenaming(
             appView, classRenaming, MethodRenaming.empty(), FieldRenaming.empty())
-        .verifyNoCollisions(appView.appInfo().classes(), appView.dexItemFactory());
+        .verifyNoCollisions(appView.appInfo(), appView.dexItemFactory());
 
     MemberNamingStrategy minifyMembers = new MinifierMemberNamingStrategy(appView);
     timing.begin("MinifyMethods");
@@ -86,7 +87,7 @@
     timing.end();
 
     assert new MinifiedRenaming(appView, classRenaming, methodRenaming, FieldRenaming.empty())
-        .verifyNoCollisions(appView.appInfo().classes(), appView.dexItemFactory());
+        .verifyNoCollisions(appView.appInfo(), appView.dexItemFactory());
 
     timing.begin("MinifyFields");
     FieldRenaming fieldRenaming =
@@ -100,7 +101,7 @@
     timing.end();
 
     NamingLens lens = new MinifiedRenaming(appView, classRenaming, methodRenaming, fieldRenaming);
-    assert lens.verifyNoCollisions(appView.appInfo().classes(), appView.dexItemFactory());
+    assert lens.verifyNoCollisions(appView.appInfo(), appView.dexItemFactory());
 
     appView.testing().namingLensConsumer.accept(appView.dexItemFactory(), lens);
     appView.notifyOptimizationFinishedForTesting();
@@ -289,8 +290,8 @@
     }
 
     @Override
-    public DexString reservedDescriptor(DexType type) {
-      DexProgramClass clazz = asProgramClassOrNull(appView.definitionFor(type));
+    public DexString reservedDescriptor(DexType type, Function<DexType, DexClass> definitionFor) {
+      DexProgramClass clazz = asProgramClassOrNull(definitionFor.apply(type));
       if (clazz == null || !appView.getKeepInfo(clazz).isMinificationAllowed(appView.options())) {
         return type.getDescriptor();
       }
diff --git a/src/main/java/com/android/tools/r8/naming/NamingLens.java b/src/main/java/com/android/tools/r8/naming/NamingLens.java
index bc8b12f..dbe8fa4 100644
--- a/src/main/java/com/android/tools/r8/naming/NamingLens.java
+++ b/src/main/java/com/android/tools/r8/naming/NamingLens.java
@@ -24,6 +24,8 @@
 import com.android.tools.r8.utils.collections.DexClassAndMethodSet;
 import com.google.common.collect.Sets;
 import java.util.Arrays;
+import java.util.IdentityHashMap;
+import java.util.Map;
 import java.util.Set;
 
 /**
@@ -157,14 +159,35 @@
   public abstract boolean verifyRenamingConsistentWithResolution(DexMethod item);
 
   public final boolean verifyNoCollisions(
-      Iterable<DexProgramClass> classes, DexItemFactory dexItemFactory) {
+      AppInfoWithLiveness appInfo, DexItemFactory dexItemFactory) {
     Set<DexReference> references = Sets.newIdentityHashSet();
-    for (DexProgramClass clazz : classes) {
+    Map<DexType, DexType> classpathTypes = new IdentityHashMap<>();
+    appInfo
+        .getClasspathTypesIncludingPruned()
+        .forEach(
+            type -> {
+              DexType newType = lookupType(type, dexItemFactory);
+              classpathTypes.put(newType, type);
+            });
+    for (DexProgramClass clazz : appInfo.classes()) {
       {
         DexType newType = lookupType(clazz.type, dexItemFactory);
         boolean referencesChanged = references.add(newType);
         assert referencesChanged
             : "Duplicate definition of type `" + newType.toSourceString() + "`";
+        if (classpathTypes.containsKey(newType)) {
+          // If we have a classpath and program type, then it can be renamed the same way. It is
+          // forbidden to rename a program type into a classpath type, since it would lead to
+          // duplicated types.
+          assert classpathTypes.get(newType).isIdenticalTo(clazz.type)
+              : "Duplicate predecessor of type `"
+                  + newType.toSourceString()
+                  + "`: `"
+                  + classpathTypes.get(newType).toSourceString()
+                  + "`,`"
+                  + clazz.type.toSourceString()
+                  + "`";
+        }
       }
 
       for (DexEncodedField field : clazz.fields()) {
diff --git a/src/main/java/com/android/tools/r8/naming/ProguardMapMinifier.java b/src/main/java/com/android/tools/r8/naming/ProguardMapMinifier.java
index 78baeb5..1f16d8c 100644
--- a/src/main/java/com/android/tools/r8/naming/ProguardMapMinifier.java
+++ b/src/main/java/com/android/tools/r8/naming/ProguardMapMinifier.java
@@ -56,6 +56,7 @@
 import java.util.concurrent.ExecutionException;
 import java.util.concurrent.ExecutorService;
 import java.util.function.BiPredicate;
+import java.util.function.Function;
 import java.util.function.Predicate;
 
 /**
@@ -413,7 +414,7 @@
     }
 
     @Override
-    public DexString reservedDescriptor(DexType type) {
+    public DexString reservedDescriptor(DexType type, Function<DexType, DexClass> definitionFor) {
       // TODO(b/136694827): Check for already used and report an error. It seems like this can be
       //  done already in the reservation step for classes since there is only one 'path', unlike
       //  members that can be reserved differently in the hierarchy.
diff --git a/src/main/java/com/android/tools/r8/shaking/AppInfoWithLiveness.java b/src/main/java/com/android/tools/r8/shaking/AppInfoWithLiveness.java
index b9dee9f..3d5de3b 100644
--- a/src/main/java/com/android/tools/r8/shaking/AppInfoWithLiveness.java
+++ b/src/main/java/com/android/tools/r8/shaking/AppInfoWithLiveness.java
@@ -65,6 +65,7 @@
 import com.android.tools.r8.utils.InternalOptions;
 import com.android.tools.r8.utils.ListUtils;
 import com.android.tools.r8.utils.PredicateSet;
+import com.android.tools.r8.utils.SetUtils;
 import com.android.tools.r8.utils.Visibility;
 import com.android.tools.r8.utils.collections.DexClassAndMethodSet;
 import com.android.tools.r8.utils.collections.ProgramMethodSet;
@@ -171,6 +172,13 @@
    * ).
    */
   public final Object2BooleanMap<DexMember<?, ?>> identifierNameStrings;
+
+  /**
+   * A set of classpath types that are not referenced from the app, but which names can still lead
+   * to collusions in the minifier.
+   */
+  final Set<DexType> prunedClasspathTypes;
+
   /** A set of types that have been removed by the {@link TreePruner}. */
   final Set<DexType> prunedTypes;
   /** A map from switchmap class types to their corresponding switchmaps. */
@@ -205,6 +213,7 @@
       PredicateSet<DexType> alwaysClassInline,
       Object2BooleanMap<DexMember<?, ?>> identifierNameStrings,
       Set<DexType> prunedTypes,
+      Set<DexType> prunedClasspathTypes,
       Map<DexField, Int2ReferenceMap<DexField>> switchMaps,
       Set<DexType> lockCandidates,
       Map<DexType, Visibility> initClassReferences,
@@ -230,6 +239,7 @@
     this.alwaysClassInline = alwaysClassInline;
     this.identifierNameStrings = identifierNameStrings;
     this.prunedTypes = prunedTypes;
+    this.prunedClasspathTypes = prunedClasspathTypes;
     this.switchMaps = switchMaps;
     this.lockCandidates = lockCandidates;
     this.initClassReferences = initClassReferences;
@@ -263,6 +273,7 @@
         previous.alwaysClassInline,
         previous.identifierNameStrings,
         previous.prunedTypes,
+        previous.prunedClasspathTypes,
         previous.switchMaps,
         previous.lockCandidates,
         previous.initClassReferences,
@@ -299,6 +310,7 @@
         prunedItems.hasRemovedClasses()
             ? CollectionUtils.mergeSets(previous.prunedTypes, prunedItems.getRemovedClasses())
             : previous.prunedTypes,
+        previous.prunedClasspathTypes,
         previous.switchMaps,
         pruneClasses(previous.lockCandidates, prunedItems, tasks),
         pruneMapFromClasses(previous.initClassReferences, prunedItems, tasks),
@@ -439,6 +451,7 @@
         alwaysClassInline,
         identifierNameStrings,
         prunedTypes,
+        prunedClasspathTypes,
         switchMaps,
         lockCandidates,
         initClassReferences,
@@ -509,6 +522,7 @@
     this.alwaysClassInline = previous.alwaysClassInline;
     this.identifierNameStrings = previous.identifierNameStrings;
     this.prunedTypes = previous.prunedTypes;
+    this.prunedClasspathTypes = previous.prunedClasspathTypes;
     this.switchMaps = switchMaps;
     this.lockCandidates = previous.lockCandidates;
     this.initClassReferences = previous.initClassReferences;
@@ -1007,6 +1021,7 @@
         lens.rewriteReferenceKeys(identifierNameStrings),
         // Don't rewrite pruned types - the removed types are identified by their original name.
         prunedTypes,
+        prunedClasspathTypes,
         lens.rewriteFieldKeys(switchMaps),
         lens.rewriteReferences(lockCandidates),
         rewriteInitClassReferences(lens),
@@ -1046,6 +1061,18 @@
     return prunedTypes;
   }
 
+  public Set<DexType> getClasspathTypesIncludingPruned() {
+    assert checkIfObsolete();
+    Collection<DexClasspathClass> classpathClasses = app().asDirect().classpathClasses();
+    Set<DexType> classpath =
+        SetUtils.newIdentityHashSet(classpathClasses.size() + prunedClasspathTypes.size());
+    for (DexClasspathClass cp : classpathClasses) {
+      classpath.add(cp.getType());
+    }
+    classpath.addAll(prunedClasspathTypes);
+    return classpath;
+  }
+
   public DexClassAndMethod lookupSingleTarget(
       AppView<AppInfoWithLiveness> appView,
       InvokeType type,
diff --git a/src/main/java/com/android/tools/r8/shaking/Enqueuer.java b/src/main/java/com/android/tools/r8/shaking/Enqueuer.java
index f17e905..9788f61 100644
--- a/src/main/java/com/android/tools/r8/shaking/Enqueuer.java
+++ b/src/main/java/com/android/tools/r8/shaking/Enqueuer.java
@@ -334,6 +334,9 @@
   /** Set of types that was pruned during the first round of tree shaking. */
   private final Set<DexType> initialPrunedTypes;
 
+  /** List of classpath types that was pruned during the first round of tree shaking. */
+  private final Set<DexType> prunedClasspathTypes;
+
   private final Set<DexType> noClassMerging = Sets.newIdentityHashSet();
 
   /** Mapping from each unused interface to the set of live types that implements the interface. */
@@ -477,6 +480,7 @@
         keptGraphConsumer,
         mode,
         null,
+        null,
         null);
   }
 
@@ -488,6 +492,7 @@
       GraphConsumer keptGraphConsumer,
       Mode mode,
       Set<DexType> initialPrunedTypes,
+      Set<DexType> prunedClasspathTypes,
       RuntimeTypeCheckInfo.Builder runtimeTypeCheckInfoBuilder) {
     assert appView.appServices() != null;
     InternalOptions options = appView.options();
@@ -512,6 +517,7 @@
             ? ProguardCompatibilityActions.builder()
             : null;
     this.initialPrunedTypes = initialPrunedTypes;
+    this.prunedClasspathTypes = prunedClasspathTypes;
 
     if (options.isOptimizedResourceShrinking()) {
       R8ResourceShrinkerState resourceShrinkerState = appView.getResourceShrinkerState();
@@ -4530,6 +4536,15 @@
 
     // Add just referenced non-program types. We can't replace the program classes at this point as
     // they are needed in tree pruning.
+    ImmutableSet.Builder<DexType> prunedClasspathTypesBuilder = ImmutableSet.builder();
+    if (prunedClasspathTypes != null) {
+      prunedClasspathTypesBuilder.addAll(prunedClasspathTypes);
+    }
+    for (DexClasspathClass classpathClass : appInfo.app().asDirect().classpathClasses()) {
+      if (!classpathClasses.contains(classpathClass)) {
+        prunedClasspathTypesBuilder.add(classpathClass.getType());
+      }
+    }
     DirectMappedDexApplication app =
         appInfo
             .app()
@@ -4585,6 +4600,7 @@
             joinIdentifierNameStrings(
                 rootSet.identifierNameStrings, reflectiveIdentification.getIdentifierNameStrings()),
             emptySet(),
+            prunedClasspathTypesBuilder.build(),
             Collections.emptyMap(),
             lockCandidates,
             initClassReferences,
diff --git a/src/main/java/com/android/tools/r8/shaking/EnqueuerFactory.java b/src/main/java/com/android/tools/r8/shaking/EnqueuerFactory.java
index 1095378..b4f2984 100644
--- a/src/main/java/com/android/tools/r8/shaking/EnqueuerFactory.java
+++ b/src/main/java/com/android/tools/r8/shaking/EnqueuerFactory.java
@@ -36,6 +36,7 @@
       ImmediateAppSubtypingInfo subtypingInfo,
       GraphConsumer keptGraphConsumer,
       Set<DexType> initialPrunedTypes,
+      Set<DexType> prunedClasspathTypes,
       RuntimeTypeCheckInfo.Builder runtimeTypeCheckInfoBuilder) {
     ProfileCollectionAdditions profileCollectionAdditions =
         ProfileCollectionAdditions.create(appView);
@@ -48,6 +49,7 @@
             keptGraphConsumer,
             Mode.FINAL_TREE_SHAKING,
             initialPrunedTypes,
+            prunedClasspathTypes,
             runtimeTypeCheckInfoBuilder);
     appView.withProtoShrinker(
         shrinker -> enqueuer.setInitialDeadProtoTypes(shrinker.getDeadProtoTypes()));
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/java/com/android/tools/r8/naming/ProgramTypeRenamedAsClasspathTypeTest.java b/src/test/java/com/android/tools/r8/naming/ProgramTypeRenamedAsClasspathTypeTest.java
new file mode 100644
index 0000000..1ba9022
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/naming/ProgramTypeRenamedAsClasspathTypeTest.java
@@ -0,0 +1,115 @@
+// 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.naming;
+
+import static org.junit.Assert.assertEquals;
+
+import com.android.tools.r8.JdkClassFileProvider;
+import com.android.tools.r8.R8TestCompileResult;
+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.FoundClassSubject;
+import java.nio.file.Path;
+import java.util.HashSet;
+import java.util.Set;
+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 ProgramTypeRenamedAsClasspathTypeTest extends TestBase {
+
+  @Parameter 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("program side", "classpath side");
+
+  @Test
+  public void testJvm() throws Exception {
+    parameters.assumeJvmTestParameters();
+    testForJvm(parameters)
+        .addInnerClassesAndStrippedOuter(getClass())
+        .run(parameters.getRuntime(), ProgramMain.class)
+        .assertSuccessWithOutput(EXPECTED_OUTPUT);
+  }
+
+  @Test
+  public void testR8Split() throws Exception {
+    parameters.assumeR8TestParameters();
+    Set<String> types = new HashSet<>();
+    R8TestCompileResult compile =
+        testForR8(Backend.CF)
+            .addProgramClasses(ClasspathSide.class, ClasspathMain.class)
+            .addLibraryProvider(JdkClassFileProvider.fromSystemJdk())
+            .addKeepMainRule(ClasspathMain.class)
+            .addKeepClassAndMembersRulesWithAllowObfuscation(ClasspathSide.class)
+            .compile();
+    compile.inspect(
+        i -> {
+          assertEquals(2, i.allClasses().size());
+          for (FoundClassSubject clazz : i.allClasses()) {
+            types.add(clazz.getDexProgramClass().getTypeName());
+          }
+        });
+    Path classpath = compile.writeToZip();
+    Path runClasspath = testForD8(parameters).addProgramFiles(classpath).compile().writeToZip();
+    testForR8(parameters)
+        .addProgramClasses(ProgramSide.class, ProgramMain.class)
+        .addClasspathFiles(classpath)
+        .addKeepMainRule(ProgramMain.class)
+        .addKeepClassAndMembersRulesWithAllowObfuscation(ProgramSide.class)
+        .compile()
+        .inspect(
+            i -> {
+              assertEquals(2, i.allClasses().size());
+              for (FoundClassSubject clazz : i.allClasses()) {
+                types.add(clazz.getDexProgramClass().getTypeName());
+              }
+              assertEquals(4, types.size());
+            })
+        .addRunClasspathFiles(runClasspath)
+        .run(parameters.getRuntime(), ProgramMain.class)
+        .assertSuccessWithOutput(EXPECTED_OUTPUT);
+  }
+
+  static class ClasspathMain {
+    public static void main(String[] args) {
+      ClasspathSide.print();
+    }
+  }
+
+  static class ClasspathSide {
+    public static void print() {
+      System.out.println("classpath side");
+    }
+  }
+
+  public static class ProgramMain {
+    public static void main(String[] args) {
+      ProgramSide.print();
+      ClasspathMain.main(args);
+    }
+  }
+
+  static class ProgramSide {
+    public static void print() {
+      System.out.println("program side");
+    }
+  }
+}
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/EnumSwitchOldSyntaxV2Test.java b/src/test/java24/com/android/tools/r8/jdk24/switchpatternmatching/EnumSwitchOldSyntaxV2Test.java
index c1e3f1d..fede4cb 100644
--- a/src/test/java24/com/android/tools/r8/jdk24/switchpatternmatching/EnumSwitchOldSyntaxV2Test.java
+++ b/src/test/java24/com/android/tools/r8/jdk24/switchpatternmatching/EnumSwitchOldSyntaxV2Test.java
@@ -12,7 +12,6 @@
 import com.android.tools.r8.TestParametersCollection;
 import com.android.tools.r8.TestRuntime.CfVm;
 import com.android.tools.r8.utils.StringUtils;
-import org.junit.Assume;
 import org.junit.Test;
 import org.junit.runner.RunWith;
 import org.junit.runners.Parameterized;
@@ -69,7 +68,6 @@
 
   @Test
   public void testR8Split() throws Exception {
-    Assume.assumeFalse("TODO(b/414335863)", parameters.isRandomPartialCompilation());
     parameters.assumeR8TestParameters();
     R8TestCompileResult compile =
         testForR8(Backend.CF)
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(