Reland "Unify first and final vertical class merging"

This reverts commit a2b282df67c4540b245b9347bd0db6ebcb07f17b.

Change-Id: I768778adc984b6b7bdfc7f46de74f7b403492f51
diff --git a/src/main/java/com/android/tools/r8/graph/AppView.java b/src/main/java/com/android/tools/r8/graph/AppView.java
index 65933c8..3ea094d 100644
--- a/src/main/java/com/android/tools/r8/graph/AppView.java
+++ b/src/main/java/com/android/tools/r8/graph/AppView.java
@@ -716,6 +716,14 @@
     this.kotlinMetadataLens = kotlinMetadataLens;
   }
 
+  public boolean hasInitializedClassesInInstanceMethods() {
+    return initializedClassesInInstanceMethods != null;
+  }
+
+  public InitializedClassesInInstanceMethods getInitializedClassesInInstanceMethods() {
+    return initializedClassesInInstanceMethods;
+  }
+
   public void setInitializedClassesInInstanceMethods(
       InitializedClassesInInstanceMethods initializedClassesInInstanceMethods) {
     this.initializedClassesInInstanceMethods = initializedClassesInInstanceMethods;
@@ -1246,6 +1254,20 @@
                 public boolean shouldRun() {
                   return !appView.cfByteCodePassThrough.isEmpty();
                 }
+              },
+              new ThreadTask() {
+                @Override
+                public void run(Timing timing) {
+                  appView.setInitializedClassesInInstanceMethods(
+                      appView
+                          .getInitializedClassesInInstanceMethods()
+                          .rewrittenWithLens(lens, appliedLens));
+                }
+
+                @Override
+                public boolean shouldRun() {
+                  return appView.hasInitializedClassesInInstanceMethods();
+                }
               });
         });
 
diff --git a/src/main/java/com/android/tools/r8/graph/analysis/InitializedClassesInInstanceMethodsAnalysis.java b/src/main/java/com/android/tools/r8/graph/analysis/InitializedClassesInInstanceMethodsAnalysis.java
index 42ca59e..ddb64d7 100644
--- a/src/main/java/com/android/tools/r8/graph/analysis/InitializedClassesInInstanceMethodsAnalysis.java
+++ b/src/main/java/com/android/tools/r8/graph/analysis/InitializedClassesInInstanceMethodsAnalysis.java
@@ -9,9 +9,11 @@
 import com.android.tools.r8.graph.DexProgramClass;
 import com.android.tools.r8.graph.DexType;
 import com.android.tools.r8.graph.ProgramMethod;
+import com.android.tools.r8.graph.lens.GraphLens;
 import com.android.tools.r8.ir.analysis.type.ClassTypeElement;
 import com.android.tools.r8.shaking.Enqueuer;
 import com.android.tools.r8.shaking.EnqueuerWorklist;
+import com.android.tools.r8.utils.MapUtils;
 import java.util.IdentityHashMap;
 import java.util.Map;
 
@@ -50,6 +52,26 @@
       // transitively.
       return !subject.isInterface();
     }
+
+    public InitializedClassesInInstanceMethods rewrittenWithLens(
+        GraphLens lens, GraphLens appliedLens) {
+      return new InitializedClassesInInstanceMethods(
+          appView,
+          MapUtils.transform(
+              mapping,
+              IdentityHashMap::new,
+              key -> {
+                DexType rewrittenKey = lens.lookupType(key, appliedLens);
+                return rewrittenKey.isPrimitiveType() ? null : rewrittenKey;
+              },
+              value -> {
+                DexType rewrittenValue = lens.lookupType(value, appliedLens);
+                return rewrittenValue.isPrimitiveType() ? null : rewrittenValue;
+              },
+              (key, value, otherValue) ->
+                  ClassTypeElement.computeLeastUpperBoundOfClasses(
+                      appView.appInfo(), value, otherValue)));
+    }
   }
 
   private final AppView<? extends AppInfoWithClassHierarchy> appView;
diff --git a/src/main/java/com/android/tools/r8/ir/conversion/LensCodeRewriter.java b/src/main/java/com/android/tools/r8/ir/conversion/LensCodeRewriter.java
index b4dbe9f..13ce76b 100644
--- a/src/main/java/com/android/tools/r8/ir/conversion/LensCodeRewriter.java
+++ b/src/main/java/com/android/tools/r8/ir/conversion/LensCodeRewriter.java
@@ -87,7 +87,6 @@
 import com.android.tools.r8.ir.code.InstructionListIterator;
 import com.android.tools.r8.ir.code.Invoke;
 import com.android.tools.r8.ir.code.InvokeCustom;
-import com.android.tools.r8.ir.code.InvokeDirect;
 import com.android.tools.r8.ir.code.InvokeMethod;
 import com.android.tools.r8.ir.code.InvokeMultiNewArray;
 import com.android.tools.r8.ir.code.InvokePolymorphic;
@@ -116,7 +115,6 @@
 import com.android.tools.r8.utils.InternalOptions;
 import com.android.tools.r8.utils.LazyBox;
 import com.android.tools.r8.verticalclassmerging.InterfaceTypeToClassTypeLensCodeRewriterHelper;
-import com.android.tools.r8.verticalclassmerging.VerticallyMergedClasses;
 import com.google.common.collect.Sets;
 import java.util.ArrayDeque;
 import java.util.ArrayList;
@@ -397,9 +395,6 @@
                 assert false;
                 continue;
               }
-              if (invoke.isInvokeDirect()) {
-                checkInvokeDirect(method.getReference(), invoke.asInvokeDirect());
-              }
               MethodLookupResult lensLookup =
                   graphLens.lookupMethod(
                       invokedMethod, method.getReference(), invoke.getType(), codeLens);
@@ -1313,54 +1308,6 @@
     return TypeElement.getNull();
   }
 
-  @SuppressWarnings("ReferenceEquality")
-  // If the given invoke is on the form "invoke-direct A.<init>, v0, ..." and the definition of
-  // value v0 is "new-instance v0, B", where B is a subtype of A (see the Art800 and B116282409
-  // tests), then fail with a compilation error if A has previously been merged into B.
-  //
-  // The motivation for this is that the vertical class merger cannot easily recognize the above
-  // code pattern, since it runs prior to IR construction. Therefore, we currently allow merging
-  // A and B although this will lead to invalid code, because this code pattern does generally
-  // not occur in practice (it leads to a verification error on the JVM, but not on Art).
-  private void checkInvokeDirect(DexMethod method, InvokeDirect invoke) {
-    VerticallyMergedClasses verticallyMergedClasses = appView.getVerticallyMergedClasses();
-    if (verticallyMergedClasses == null) {
-      // No need to check the invocation.
-      return;
-    }
-    DexMethod invokedMethod = invoke.getInvokedMethod();
-    if (invokedMethod.name != factory.constructorMethodName) {
-      // Not a constructor call.
-      return;
-    }
-    if (invoke.arguments().isEmpty()) {
-      // The new instance should always be passed to the constructor call, but continue gracefully.
-      return;
-    }
-    Value receiver = invoke.arguments().get(0);
-    if (!receiver.isPhi() && receiver.definition.isNewInstance()) {
-      NewInstance newInstance = receiver.definition.asNewInstance();
-      if (newInstance.clazz != invokedMethod.holder
-          && verticallyMergedClasses.hasBeenMergedIntoSubtype(invokedMethod.holder)) {
-        // Generated code will not work. Fail with a compilation error.
-        throw appView
-            .options()
-            .reporter
-            .fatalError(
-                String.format(
-                    "Unable to rewrite `invoke-direct %s.<init>(new %s, ...)` in method `%s` after "
-                        + "type `%s` was merged into `%s`. Please add the following rule to your "
-                        + "Proguard configuration file: `-keep,allowobfuscation class %s`.",
-                    invokedMethod.holder.toSourceString(),
-                    newInstance.clazz,
-                    method.toSourceString(),
-                    invokedMethod.holder,
-                    verticallyMergedClasses.getTargetFor(invokedMethod.holder),
-                    invokedMethod.holder.toSourceString()));
-      }
-    }
-  }
-
   /**
    * Due to class merging, it is possible that two exception classes have been merged into one. This
    * function removes catch handlers where the guards ended up being the same as a previous one.
diff --git a/src/main/java/com/android/tools/r8/shaking/GraphReporter.java b/src/main/java/com/android/tools/r8/shaking/GraphReporter.java
index 1e63e51..3ae9270 100644
--- a/src/main/java/com/android/tools/r8/shaking/GraphReporter.java
+++ b/src/main/java/com/android/tools/r8/shaking/GraphReporter.java
@@ -33,13 +33,13 @@
 import com.android.tools.r8.shaking.KeepReason.ReflectiveUseFrom;
 import com.android.tools.r8.utils.DequeUtils;
 import com.android.tools.r8.utils.InternalOptions;
+import com.android.tools.r8.utils.SetUtils;
 import com.google.common.collect.ImmutableList;
 import com.google.common.collect.ImmutableList.Builder;
 import com.google.common.collect.Sets;
 import java.util.Collection;
 import java.util.Collections;
 import java.util.Deque;
-import java.util.HashSet;
 import java.util.IdentityHashMap;
 import java.util.Map;
 import java.util.Set;
@@ -532,14 +532,12 @@
     }
     if (rule instanceof ProguardIfRule) {
       ProguardIfRule ifRule = (ProguardIfRule) rule;
-      assert !ifRule.getPreconditions().isEmpty();
+      assert ifRule.getPrecondition() != null;
       return ruleNodes.computeIfAbsent(
           ifRule,
           key -> {
-            Set<GraphNode> preconditions = new HashSet<>(ifRule.getPreconditions().size());
-            for (DexReference condition : ifRule.getPreconditions()) {
-              preconditions.add(getGraphNode(condition));
-            }
+            Set<GraphNode> preconditions =
+                SetUtils.newHashSet(getGraphNode(ifRule.getPrecondition().getReference()));
             return new KeepRuleGraphNode(ifRule, preconditions);
           });
     }
diff --git a/src/main/java/com/android/tools/r8/shaking/IfRuleEvaluator.java b/src/main/java/com/android/tools/r8/shaking/IfRuleEvaluator.java
index 32ec312..44ba57d 100644
--- a/src/main/java/com/android/tools/r8/shaking/IfRuleEvaluator.java
+++ b/src/main/java/com/android/tools/r8/shaking/IfRuleEvaluator.java
@@ -3,8 +3,6 @@
 // BSD-style license that can be found in the LICENSE file.
 package com.android.tools.r8.shaking;
 
-import static com.android.tools.r8.graph.DexProgramClass.asProgramClassOrNull;
-
 import com.android.tools.r8.graph.AppInfoWithClassHierarchy;
 import com.android.tools.r8.graph.AppView;
 import com.android.tools.r8.graph.DexClass;
@@ -14,17 +12,14 @@
 import com.android.tools.r8.graph.DexEncodedField;
 import com.android.tools.r8.graph.DexItemFactory;
 import com.android.tools.r8.graph.DexProgramClass;
-import com.android.tools.r8.graph.DexReference;
-import com.android.tools.r8.graph.DexType;
 import com.android.tools.r8.graph.SubtypingInfo;
-import com.android.tools.r8.shaking.InlineRule.Type;
+import com.android.tools.r8.shaking.InlineRule.InlineRuleType;
 import com.android.tools.r8.shaking.RootSetUtils.ConsequentRootSet;
 import com.android.tools.r8.shaking.RootSetUtils.ConsequentRootSetBuilder;
 import com.android.tools.r8.shaking.RootSetUtils.RootSetBuilder;
 import com.android.tools.r8.threading.TaskCollection;
 import com.android.tools.r8.utils.InternalOptions.TestingOptions.ProguardIfRuleEvaluationData;
 import com.google.common.base.Equivalence.Wrapper;
-import com.google.common.collect.ImmutableSet;
 import com.google.common.collect.Iterables;
 import com.google.common.collect.Sets;
 import java.util.ArrayList;
@@ -98,48 +93,12 @@
                 if (appView.options().testing.measureProguardIfRuleEvaluations) {
                   ifRuleEvaluationData.numberOfProguardIfRuleMemberEvaluations++;
                 }
-                boolean matched = evaluateIfRuleMembersAndMaterialize(ifRule, clazz, clazz);
+                boolean matched = evaluateIfRuleMembersAndMaterialize(ifRule, clazz);
                 if (matched && canRemoveSubsequentKeepRule(ifRule)) {
                   toRemove.add(ifRule);
                 }
               }
             }
-
-            // Check if one of the types that have been merged into `clazz` satisfies the if-rule.
-            if (appView.getVerticallyMergedClasses() != null) {
-              Iterable<DexType> sources =
-                  appView.getVerticallyMergedClasses().getSourcesFor(clazz.type);
-              for (DexType sourceType : sources) {
-                // Note that, although `sourceType` has been merged into `type`, the dex class for
-                // `sourceType` is still available until the second round of tree shaking. This
-                // way we can still retrieve the access flags of `sourceType`.
-                DexProgramClass sourceClass =
-                    asProgramClassOrNull(
-                        appView.appInfo().definitionForWithoutExistenceAssert(sourceType));
-                if (sourceClass == null) {
-                  // TODO(b/266049507): The evaluation of -if rules in the final round of tree
-                  //  shaking and during -whyareyoukeeping should be the same. Currently the pruning
-                  //  of classes changes behavior.
-                  assert enqueuer.getMode().isWhyAreYouKeeping();
-                  continue;
-                }
-                if (appView.options().testing.measureProguardIfRuleEvaluations) {
-                  ifRuleEvaluationData.numberOfProguardIfRuleClassEvaluations++;
-                }
-                if (evaluateClassForIfRule(ifRuleKey, sourceClass)) {
-                  for (ProguardIfRule ifRule : ifRulesInEquivalence) {
-                    registerClassCapture(ifRule, sourceClass, clazz);
-                    if (appView.options().testing.measureProguardIfRuleEvaluations) {
-                      ifRuleEvaluationData.numberOfProguardIfRuleMemberEvaluations++;
-                    }
-                    if (evaluateIfRuleMembersAndMaterialize(ifRule, sourceClass, clazz)
-                        && canRemoveSubsequentKeepRule(ifRule)) {
-                      toRemove.add(ifRule);
-                    }
-                  }
-                }
-              }
-            }
           }
           if (ifRulesInEquivalence.size() == toRemove.size()) {
             it.remove();
@@ -203,12 +162,11 @@
     return true;
   }
 
-  @SuppressWarnings("ReferenceEquality")
-  private boolean evaluateIfRuleMembersAndMaterialize(
-      ProguardIfRule rule, DexClass sourceClass, DexClass targetClass) throws ExecutionException {
+  private boolean evaluateIfRuleMembersAndMaterialize(ProguardIfRule rule, DexProgramClass clazz)
+      throws ExecutionException {
     Collection<ProguardMemberRule> memberKeepRules = rule.getMemberRules();
     if (memberKeepRules.isEmpty()) {
-      materializeIfRule(rule, ImmutableSet.of(sourceClass.getReference()));
+      materializeIfRule(rule, clazz);
       return true;
     }
 
@@ -216,12 +174,12 @@
     Set<DexDefinition> filteredMembers = Sets.newIdentityHashSet();
     Iterables.addAll(
         filteredMembers,
-        targetClass.fields(
+        clazz.fields(
             f -> {
               // Fields that are javac inlined are unsound as predicates for conditional rules.
               // Ignore any such field members and record it for possible reporting later.
               if (isFieldInlinedByJavaC(f)) {
-                fieldsInlinedByJavaC.add(DexClassAndField.create(targetClass, f));
+                fieldsInlinedByJavaC.add(DexClassAndField.create(clazz, f));
                 return false;
               }
               // Fields referenced only by -keep may not be referenced, we therefore have to
@@ -229,18 +187,24 @@
               return (enqueuer.isFieldLive(f)
                       || enqueuer.isFieldReferenced(f)
                       || f.getOptimizationInfo().valueHasBeenPropagated())
-                  && (appView.graphLens().getOriginalFieldSignature(f.getReference()).holder
-                      == sourceClass.type);
+                  && appView
+                      .graphLens()
+                      .getOriginalFieldSignature(f.getReference())
+                      .getHolderType()
+                      .isIdenticalTo(clazz.getType());
             }));
     Iterables.addAll(
         filteredMembers,
-        targetClass.methods(
+        clazz.methods(
             m ->
                 (enqueuer.isMethodLive(m)
                         || enqueuer.isMethodTargeted(m)
                         || m.getOptimizationInfo().returnValueHasBeenPropagated())
-                    && appView.graphLens().getOriginalMethodSignature(m.getReference()).holder
-                        == sourceClass.type));
+                    && appView
+                        .graphLens()
+                        .getOriginalMethodSignature(m.getReference())
+                        .getHolderType()
+                        .isIdenticalTo(clazz.getType())));
 
     // Check if the rule could hypothetically have matched a javac inlined field.
     // If so mark the rule. Reporting happens only if the rule is otherwise unused.
@@ -271,11 +235,11 @@
         Sets.combinations(filteredMembers, memberKeepRules.size())) {
       Collection<DexClassAndField> fieldsInCombination =
           DexDefinition.filterDexEncodedField(
-                  combination.stream(), field -> DexClassAndField.create(targetClass, field))
+                  combination.stream(), field -> DexClassAndField.create(clazz, field))
               .collect(Collectors.toList());
       Collection<DexClassAndMethod> methodsInCombination =
           DexDefinition.filterDexEncodedMethod(
-                  combination.stream(), method -> DexClassAndMethod.create(targetClass, method))
+                  combination.stream(), method -> DexClassAndMethod.create(clazz, method))
               .collect(Collectors.toList());
       // Member rules are combined as AND logic: if found unsatisfied member rule, this
       // combination of live members is not a good fit.
@@ -287,7 +251,7 @@
                           || rootSetBuilder.ruleSatisfiedByMethods(
                               memberRule, methodsInCombination));
       if (satisfied) {
-        materializeIfRule(rule, ImmutableSet.of(sourceClass.getReference()));
+        materializeIfRule(rule, clazz);
         if (canRemoveSubsequentKeepRule(rule)) {
           return true;
         }
@@ -305,25 +269,17 @@
     return field.getOrComputeIsInlinableByJavaC(appView.dexItemFactory());
   }
 
-  @SuppressWarnings("BadImport")
-  private void materializeIfRule(ProguardIfRule rule, Set<DexReference> preconditions)
+  private void materializeIfRule(ProguardIfRule rule, DexProgramClass precondition)
       throws ExecutionException {
     DexItemFactory dexItemFactory = appView.dexItemFactory();
-    ProguardIfRule materializedRule = rule.materialize(dexItemFactory, preconditions);
+    ProguardIfRule materializedRule = rule.materialize(dexItemFactory, precondition);
 
     if (enqueuer.getMode().isInitialTreeShaking()
         && !rule.isUsed()
         && !rule.isTrivalAllClassMatch()) {
-      // We need to abort class inlining of classes that could be matched by the condition of this
-      // -if rule.
-      ClassInlineRule neverClassInlineRuleForCondition =
-          materializedRule.neverClassInlineRuleForCondition(dexItemFactory);
-      if (neverClassInlineRuleForCondition != null) {
-        rootSetBuilder.runPerRule(tasks, neverClassInlineRuleForCondition, null);
-      }
-
       InlineRule neverInlineForClassInliningRuleForCondition =
-          materializedRule.neverInlineRuleForCondition(dexItemFactory, Type.NEVER_CLASS_INLINE);
+          materializedRule.neverInlineRuleForCondition(
+              dexItemFactory, InlineRuleType.NEVER_CLASS_INLINE);
       if (neverInlineForClassInliningRuleForCondition != null) {
         rootSetBuilder.runPerRule(tasks, neverInlineForClassInliningRuleForCondition, null);
       }
@@ -332,17 +288,18 @@
       // ensure that the subsequent rule will be applied again in the second round of tree
       // shaking.
       InlineRule neverInlineRuleForCondition =
-          materializedRule.neverInlineRuleForCondition(dexItemFactory, Type.NEVER);
+          materializedRule.neverInlineRuleForCondition(dexItemFactory, InlineRuleType.NEVER);
       if (neverInlineRuleForCondition != null) {
         rootSetBuilder.runPerRule(tasks, neverInlineRuleForCondition, null);
       }
 
-      // Prevent horizontal class merging of any -if rule members.
-      NoHorizontalClassMergingRule noHorizontalClassMergingRule =
-          materializedRule.noHorizontalClassMergingRuleForCondition(dexItemFactory);
-      if (noHorizontalClassMergingRule != null) {
-        rootSetBuilder.runPerRule(tasks, noHorizontalClassMergingRule, null);
-      }
+      rootSetBuilder
+          .getDependentMinimumKeepInfo()
+          .getOrCreateUnconditionalMinimumKeepInfoFor(precondition.getType())
+          .asClassJoiner()
+          .disallowClassInlining()
+          .disallowHorizontalClassMerging()
+          .disallowVerticalClassMerging();
     }
 
     // Keep whatever is required by the -if rule.
diff --git a/src/main/java/com/android/tools/r8/shaking/InlineRule.java b/src/main/java/com/android/tools/r8/shaking/InlineRule.java
index cc813ad..4949e5a 100644
--- a/src/main/java/com/android/tools/r8/shaking/InlineRule.java
+++ b/src/main/java/com/android/tools/r8/shaking/InlineRule.java
@@ -10,7 +10,7 @@
 
 public class InlineRule extends ProguardConfigurationRule {
 
-  public enum Type {
+  public enum InlineRuleType {
     ALWAYS,
     NEVER,
     NEVER_CLASS_INLINE,
@@ -24,14 +24,14 @@
       super();
     }
 
-    Type type;
+    InlineRuleType type;
 
     @Override
     public Builder self() {
       return this;
     }
 
-    public Builder setType(Type type) {
+    public Builder setType(InlineRuleType type) {
       this.type = type;
       return this;
     }
@@ -56,7 +56,7 @@
     }
   }
 
-  private final Type type;
+  private final InlineRuleType type;
 
   protected InlineRule(
       Origin origin,
@@ -72,7 +72,7 @@
       ProguardTypeMatcher inheritanceClassName,
       boolean inheritanceIsExtends,
       List<ProguardMemberRule> memberRules,
-      Type type) {
+      InlineRuleType type) {
     super(
         origin,
         position,
@@ -94,7 +94,7 @@
     return new Builder();
   }
 
-  public Type getType() {
+  public InlineRuleType getType() {
     return type;
   }
 
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 6ae7d4c..b2dcea5 100644
--- a/src/main/java/com/android/tools/r8/shaking/ProguardConfigurationParser.java
+++ b/src/main/java/com/android/tools/r8/shaking/ProguardConfigurationParser.java
@@ -18,6 +18,7 @@
 import com.android.tools.r8.position.Position;
 import com.android.tools.r8.position.TextPosition;
 import com.android.tools.r8.position.TextRange;
+import com.android.tools.r8.shaking.InlineRule.InlineRuleType;
 import com.android.tools.r8.shaking.ProguardConfiguration.Builder;
 import com.android.tools.r8.shaking.ProguardTypeMatcher.ClassOrType;
 import com.android.tools.r8.shaking.ProguardWildcard.BackReference;
@@ -464,7 +465,7 @@
       } else if (acceptString("alwaysinline")) {
         InlineRule rule =
             parseRuleWithClassSpec(
-                optionStart, InlineRule.builder().setType(InlineRule.Type.ALWAYS));
+                optionStart, InlineRule.builder().setType(InlineRuleType.ALWAYS));
         configurationBuilder.addRule(rule);
       } else if (acceptString("adaptclassstrings")) {
         parseClassFilter(configurationBuilder::addAdaptClassStringsPattern);
@@ -566,14 +567,14 @@
         if (acceptString("neverinline")) {
           InlineRule rule =
               parseRuleWithClassSpec(
-                  optionStart, InlineRule.builder().setType(InlineRule.Type.NEVER));
+                  optionStart, InlineRule.builder().setType(InlineRuleType.NEVER));
           configurationBuilder.addRule(rule);
           return true;
         }
         if (acceptString("neversinglecallerinline")) {
           InlineRule rule =
               parseRuleWithClassSpec(
-                  optionStart, InlineRule.builder().setType(InlineRule.Type.NEVER_SINGLE_CALLER));
+                  optionStart, InlineRule.builder().setType(InlineRuleType.NEVER_SINGLE_CALLER));
           configurationBuilder.addRule(rule);
           return true;
         }
diff --git a/src/main/java/com/android/tools/r8/shaking/ProguardIfRule.java b/src/main/java/com/android/tools/r8/shaking/ProguardIfRule.java
index 2279a8e..cb3757c 100644
--- a/src/main/java/com/android/tools/r8/shaking/ProguardIfRule.java
+++ b/src/main/java/com/android/tools/r8/shaking/ProguardIfRule.java
@@ -5,9 +5,10 @@
 
 import com.android.tools.r8.graph.DexField;
 import com.android.tools.r8.graph.DexItemFactory;
-import com.android.tools.r8.graph.DexReference;
+import com.android.tools.r8.graph.DexProgramClass;
 import com.android.tools.r8.origin.Origin;
 import com.android.tools.r8.position.Position;
+import com.android.tools.r8.shaking.InlineRule.InlineRuleType;
 import com.google.common.collect.Iterables;
 import java.util.List;
 import java.util.Map;
@@ -25,21 +26,14 @@
         }
       };
 
-  private static final Origin NO_HORIZONTAL_CLASS_MERGING_ORIGIN =
-      new Origin(Origin.root()) {
-        @Override
-        public String part() {
-          return "<SYNTHETIC_NO_HORIZONTAL_CLASS_MERGING_RULE>";
-        }
-      };
-
-  private final Set<DexReference> preconditions;
+  private final DexProgramClass precondition;
   final ProguardKeepRule subsequentRule;
 
   private Map<DexField, DexField> inlinableFieldsInPrecondition = new ConcurrentHashMap<>();
 
-  public Set<DexReference> getPreconditions() {
-    return preconditions;
+  public DexProgramClass getPrecondition() {
+    assert precondition != null;
+    return precondition;
   }
 
   public ProguardKeepRule getSubsequentRule() {
@@ -112,7 +106,7 @@
       boolean inheritanceIsExtends,
       List<ProguardMemberRule> memberRules,
       ProguardKeepRule subsequentRule,
-      Set<DexReference> preconditions) {
+      DexProgramClass precondition) {
     super(
         origin,
         position,
@@ -130,7 +124,7 @@
         ProguardKeepRuleType.CONDITIONAL,
         ProguardKeepRuleModifiers.builder().build());
     this.subsequentRule = subsequentRule;
-    this.preconditions = preconditions;
+    this.precondition = precondition;
   }
 
   public static Builder builder() {
@@ -153,7 +147,7 @@
   }
 
   protected ProguardIfRule materialize(
-      DexItemFactory dexItemFactory, Set<DexReference> preconditions) {
+      DexItemFactory dexItemFactory, DexProgramClass precondition) {
     return new ProguardIfRule(
         getOrigin(),
         getPosition(),
@@ -175,27 +169,7 @@
                 .map(memberRule -> memberRule.materialize(dexItemFactory))
                 .collect(Collectors.toList()),
         subsequentRule.materialize(dexItemFactory),
-        preconditions);
-  }
-
-  protected ClassInlineRule neverClassInlineRuleForCondition(DexItemFactory dexItemFactory) {
-    return new ClassInlineRule(
-        NEVER_INLINE_ORIGIN,
-        Position.UNKNOWN,
-        null,
-        ProguardTypeMatcher.materializeList(getClassAnnotations(), dexItemFactory),
-        getClassAccessFlags(),
-        getNegatedClassAccessFlags(),
-        getClassTypeNegated(),
-        getClassType(),
-        getClassNames().materialize(dexItemFactory),
-        ProguardTypeMatcher.materializeList(getInheritanceAnnotations(), dexItemFactory),
-        getInheritanceClassName() == null
-            ? null
-            : getInheritanceClassName().materialize(dexItemFactory),
-        getInheritanceIsExtends(),
-        getMemberRules(),
-        ClassInlineRule.Type.NEVER);
+        precondition);
   }
 
   /**
@@ -221,7 +195,7 @@
    * -neverinline rule for the condition of the -if rule.
    */
   protected InlineRule neverInlineRuleForCondition(
-      DexItemFactory dexItemFactory, InlineRule.Type type) {
+      DexItemFactory dexItemFactory, InlineRuleType type) {
     if (getMemberRules() == null || getMemberRules().isEmpty()) {
       return null;
     }
@@ -247,36 +221,6 @@
         type);
   }
 
-  protected NoHorizontalClassMergingRule noHorizontalClassMergingRuleForCondition(
-      DexItemFactory dexItemFactory) {
-    List<ProguardMemberRule> memberRules = null;
-    if (getMemberRules() != null) {
-      memberRules =
-          getMemberRules().stream()
-              .map(memberRule -> memberRule.materialize(dexItemFactory))
-              .collect(Collectors.toList());
-    }
-
-    return NoHorizontalClassMergingRule.builder()
-        .setOrigin(NO_HORIZONTAL_CLASS_MERGING_ORIGIN)
-        .addClassAnnotations(
-            ProguardTypeMatcher.materializeList(getClassAnnotations(), dexItemFactory))
-        .setClassAccessFlags(getClassAccessFlags())
-        .setNegatedClassAccessFlags(getNegatedClassAccessFlags())
-        .setClassType(getClassType())
-        .setClassTypeNegated(getClassTypeNegated())
-        .setClassNames(getClassNames().materialize(dexItemFactory))
-        .addInheritanceAnnotations(
-            ProguardTypeMatcher.materializeList(getInheritanceAnnotations(), dexItemFactory))
-        .setInheritanceClassName(
-            getInheritanceClassName() == null
-                ? null
-                : getInheritanceClassName().materialize(dexItemFactory))
-        .setInheritanceIsExtends(getInheritanceIsExtends())
-        .setMemberRules(memberRules)
-        .build();
-  }
-
   @Override
   public boolean equals(Object o) {
     if (!(o instanceof ProguardIfRule)) {
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 cecee47..9d30ea3 100644
--- a/src/main/java/com/android/tools/r8/shaking/RootSetUtils.java
+++ b/src/main/java/com/android/tools/r8/shaking/RootSetUtils.java
@@ -173,6 +173,10 @@
           null);
     }
 
+    public DependentMinimumKeepInfoCollection getDependentMinimumKeepInfo() {
+      return dependentMinimumKeepInfo;
+    }
+
     boolean isMainDexRootSetBuilder() {
       return false;
     }
diff --git a/src/main/java/com/android/tools/r8/utils/MapUtils.java b/src/main/java/com/android/tools/r8/utils/MapUtils.java
index ed49f66..c991a7d 100644
--- a/src/main/java/com/android/tools/r8/utils/MapUtils.java
+++ b/src/main/java/com/android/tools/r8/utils/MapUtils.java
@@ -122,6 +122,9 @@
             return;
           }
           V2 newValue = valueMapping.apply(key, value);
+          if (newValue == null) {
+            return;
+          }
           V2 existingValue = result.put(newKey, newValue);
           if (existingValue != null) {
             result.put(newKey, valueMerger.apply(newKey, existingValue, newValue));
diff --git a/src/main/java/com/android/tools/r8/verticalclassmerging/IncompleteVerticalClassMergerBridgeCode.java b/src/main/java/com/android/tools/r8/verticalclassmerging/IncompleteVerticalClassMergerBridgeCode.java
index d8856f9..f852310 100644
--- a/src/main/java/com/android/tools/r8/verticalclassmerging/IncompleteVerticalClassMergerBridgeCode.java
+++ b/src/main/java/com/android/tools/r8/verticalclassmerging/IncompleteVerticalClassMergerBridgeCode.java
@@ -17,7 +17,6 @@
 import com.android.tools.r8.graph.DexType;
 import com.android.tools.r8.graph.ProgramMethod;
 import com.android.tools.r8.graph.UseRegistry;
-import com.android.tools.r8.graph.lens.GraphLens;
 import com.android.tools.r8.ir.analysis.type.TypeElement;
 import com.android.tools.r8.ir.code.IRCode;
 import com.android.tools.r8.ir.code.InvokeType;
@@ -34,7 +33,7 @@
 
 /**
  * A short-lived piece of code that will be converted into {@link LirCode} using {@link
- * #toLirCode(AppView)}.
+ * #toLirCode(AppView, VerticalClassMergerGraphLens, ClassMergerMode)}.
  */
 public class IncompleteVerticalClassMergerBridgeCode extends Code {
 
@@ -87,10 +86,7 @@
     method = lens.getNextBridgeMethodSignature(method);
   }
 
-  public LirCode<?> toLirCode(
-      AppView<AppInfoWithLiveness> appView,
-      VerticalClassMergerGraphLens lens,
-      ClassMergerMode mode) {
+  public LirCode<?> toLirCode(AppView<AppInfoWithLiveness> appView) {
     boolean isD8R8Synthesized = true;
     LirEncodingStrategy<Value, Integer> strategy =
         LirStrategy.getDefaultStrategy().getEncodingStrategy();
@@ -128,21 +124,7 @@
       lirBuilder.addReturn(returnValue);
     }
 
-    LirCode<Integer> lirCode = lirBuilder.build();
-    return mode.isFinal()
-        ? lirCode
-        : new LirCode<>(lirCode) {
-
-          @Override
-          public boolean hasExplicitCodeLens() {
-            return true;
-          }
-
-          @Override
-          public GraphLens getCodeLens(AppView<?> appView) {
-            return lens;
-          }
-        };
+    return lirBuilder.build();
   }
 
   // Implement Code.
diff --git a/src/main/java/com/android/tools/r8/verticalclassmerging/VerticalClassMerger.java b/src/main/java/com/android/tools/r8/verticalclassmerging/VerticalClassMerger.java
index 943f954..6772ced 100644
--- a/src/main/java/com/android/tools/r8/verticalclassmerging/VerticalClassMerger.java
+++ b/src/main/java/com/android/tools/r8/verticalclassmerging/VerticalClassMerger.java
@@ -143,10 +143,11 @@
     rewriteCodeWithLens(executorService, timing);
 
     // Remove merged classes from app now that the code is fully rewritten.
-    removeMergedClasses(verticalClassMergerResult.getVerticallyMergedClasses(), timing);
+    removeMergedClasses(
+        verticalClassMergerResult.getVerticallyMergedClasses(), executorService, timing);
 
     // Convert the (incomplete) synthesized bridges to LIR.
-    finalizeSynthesizedBridges(verticalClassMergerResult.getSynthesizedBridges(), lens, timing);
+    finalizeSynthesizedBridges(verticalClassMergerResult.getSynthesizedBridges(), timing);
 
     // Finally update the code lens to signal that the code is fully up to date.
     markRewrittenWithLens(executorService, timing);
@@ -247,9 +248,6 @@
 
   private void rewriteCodeWithLens(ExecutorService executorService, Timing timing)
       throws ExecutionException {
-    if (mode.isInitial()) {
-      return;
-    }
     LirConverter.rewriteLirWithLens(appView, timing, executorService);
     new IdentifierMinifier(appView).rewriteDexItemBasedConstStringInStaticFields(executorService);
   }
@@ -292,11 +290,14 @@
     timing.end();
   }
 
-  private void removeMergedClasses(VerticallyMergedClasses verticallyMergedClasses, Timing timing) {
+  private void removeMergedClasses(
+      VerticallyMergedClasses verticallyMergedClasses,
+      ExecutorService executorService,
+      Timing timing)
+      throws ExecutionException {
     if (mode.isInitial()) {
       return;
     }
-
     timing.begin("Remove merged classes");
     DirectMappedDexApplication newApplication =
         appView
@@ -305,13 +306,17 @@
             .builder()
             .removeProgramClasses(clazz -> verticallyMergedClasses.isMergeSource(clazz.getType()))
             .build();
-    appView.setAppInfo(appView.appInfo().rebuildWithLiveness(newApplication));
+    PrunedItems prunedItems =
+        PrunedItems.builder()
+            .addRemovedClasses(verticallyMergedClasses.getSources())
+            .setPrunedApp(newApplication)
+            .build();
+    appView.setAppInfo(appView.appInfo().prunedCopyFrom(prunedItems, executorService, timing));
     timing.end();
   }
 
   private void finalizeSynthesizedBridges(
       List<IncompleteVerticalClassMergerBridgeCode> bridges,
-      VerticalClassMergerGraphLens lens,
       Timing timing) {
     timing.begin("Finalize synthesized bridges");
     KeepInfoCollection keepInfo = appView.getKeepInfo();
@@ -323,7 +328,7 @@
       assert target != null;
 
       // Finalize code.
-      bridge.setCode(code.toLirCode(appView, lens, mode), appView);
+      bridge.setCode(code.toLirCode(appView), appView);
 
       // Copy keep info to newly synthesized methods.
       keepInfo.mutate(
@@ -335,9 +340,6 @@
 
   private void markRewrittenWithLens(ExecutorService executorService, Timing timing)
       throws ExecutionException {
-    if (mode.isInitial()) {
-      return;
-    }
     timing.begin("Mark rewritten with lens");
     appView.clearCodeRewritings(executorService, timing);
     timing.end();
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/B116282409.java b/src/test/java/com/android/tools/r8/ir/optimize/B116282409.java
index 6876898..0031854 100644
--- a/src/test/java/com/android/tools/r8/ir/optimize/B116282409.java
+++ b/src/test/java/com/android/tools/r8/ir/optimize/B116282409.java
@@ -4,24 +4,20 @@
 
 package com.android.tools.r8.ir.optimize;
 
+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.containsString;
-import static org.junit.Assert.assertFalse;
 
-import com.android.tools.r8.CompilationFailedException;
-import com.android.tools.r8.R8TestCompileResult;
 import com.android.tools.r8.TestParameters;
 import com.android.tools.r8.jasmin.JasminBuilder;
 import com.android.tools.r8.jasmin.JasminBuilder.ClassBuilder;
 import com.android.tools.r8.jasmin.JasminTestBase;
-import com.android.tools.r8.utils.AbortException;
 import com.android.tools.r8.utils.BooleanUtils;
+import com.android.tools.r8.utils.UnverifiableCfCodeDiagnostic;
 import com.google.common.collect.ImmutableList;
-import java.util.Arrays;
 import java.util.Collection;
 import java.util.List;
-import java.util.stream.Collectors;
-import org.hamcrest.BaseMatcher;
-import org.hamcrest.Description;
 import org.junit.BeforeClass;
 import org.junit.Rule;
 import org.junit.Test;
@@ -105,79 +101,36 @@
 
   @Test
   public void testR8() throws Exception {
-    if (enableVerticalClassMerging) {
-      exception.expect(CompilationFailedException.class);
-      exception.expectCause(
-          new CustomExceptionMatcher(
-              "Unable to rewrite `invoke-direct A.<init>(new B, ...)` in method "
-                  + "`void TestClass.main(java.lang.String[])` after type `A` was merged into `B`.",
-              "Please add the following rule to your Proguard configuration file: "
-                  + "`-keep,allowobfuscation class A`."));
-    }
-
-    R8TestCompileResult compileResult =
-        testForR8(parameters.getBackend())
-            .addProgramClassFileData(programClassFileData)
-            .addKeepMainRule("TestClass")
-            .addOptionsModification(
-                options ->
-                    options.getVerticalClassMergerOptions().setEnabled(enableVerticalClassMerging))
-            .allowDiagnosticWarningMessages()
-            .setMinApi(parameters)
-            .compile();
-
-    assertFalse(enableVerticalClassMerging);
-
-    compileResult
+    testForR8(parameters.getBackend())
+        .addProgramClassFileData(programClassFileData)
+        .addKeepMainRule("TestClass")
+        .addOptionsModification(
+            options ->
+                options.getVerticalClassMergerOptions().setEnabled(enableVerticalClassMerging))
+        .allowDiagnosticWarningMessages()
+        .setMinApi(parameters)
+        .compileWithExpectedDiagnostics(
+            diagnostics ->
+                diagnostics.assertWarningsMatch(
+                    allOf(
+                        diagnosticType(UnverifiableCfCodeDiagnostic.class),
+                        diagnosticMessage(
+                            containsString(
+                                "Constructor mismatch, expected constructor from B, but was"
+                                    + " A.<init>()")))))
         .run(parameters.getRuntime(), "TestClass")
         .applyIf(
-            parameters.isCfRuntime(),
+            (parameters.isCfRuntime() || parameters.getDexRuntimeVersion().isDalvik())
+                && !enableVerticalClassMerging,
             runResult ->
                 runResult
                     .assertFailureWithErrorThatThrows(VerifyError.class)
                     .assertFailureWithErrorThatMatches(
-                        containsString("Call to wrong initialization method")),
-            runResult -> {
-              if (parameters.getDexRuntimeVersion().isDalvik()) {
-                runResult.assertFailureWithErrorThatMatches(
-                    containsString(
-                        "VFY: invoke-direct <init> on super only allowed for 'this' in <init>"));
-              } else {
-                runResult.assertSuccessWithOutputLines("In A.<init>()", "42");
-              }
-            });
-  }
-
-  private static class CustomExceptionMatcher extends BaseMatcher<Throwable> {
-
-    private List<String> messages;
-
-    public CustomExceptionMatcher(String... messages) {
-      this.messages = Arrays.asList(messages);
-    }
-
-    @Override
-    public void describeTo(Description description) {
-      description
-          .appendText("a string containing ")
-          .appendText(
-              String.join(
-                  ", ", messages.stream().map(m -> "\"" + m + "\"").collect(Collectors.toList())));
-    }
-
-    @Override
-    public boolean matches(Object o) {
-      if (o instanceof AbortException) {
-        AbortException exception = (AbortException) o;
-        if (exception.getMessage() != null
-            && messages.stream().allMatch(message -> exception.getMessage().contains(message))) {
-          return true;
-        }
-        if (exception.getCause() != null) {
-          return matches(exception.getCause());
-        }
-      }
-      return false;
-    }
+                        containsString(
+                            parameters.isCfRuntime()
+                                ? "Call to wrong initialization method"
+                                : "VFY: invoke-direct <init> on super only allowed for 'this' in"
+                                    + " <init>")),
+            runResult -> runResult.assertSuccessWithOutputLines("In A.<init>()", "42"));
   }
 }
diff --git a/src/test/java/com/android/tools/r8/shaking/ifrule/verticalclassmerging/CheckDiscardedFailuresWithIfRulesAndVerticalClassMergingTest.java b/src/test/java/com/android/tools/r8/shaking/ifrule/verticalclassmerging/CheckDiscardedFailuresWithIfRulesAndVerticalClassMergingTest.java
index d422118..175b905 100644
--- a/src/test/java/com/android/tools/r8/shaking/ifrule/verticalclassmerging/CheckDiscardedFailuresWithIfRulesAndVerticalClassMergingTest.java
+++ b/src/test/java/com/android/tools/r8/shaking/ifrule/verticalclassmerging/CheckDiscardedFailuresWithIfRulesAndVerticalClassMergingTest.java
@@ -6,7 +6,6 @@
 
 import static com.android.tools.r8.DiagnosticsMatcher.diagnosticType;
 import static com.android.tools.r8.utils.codeinspector.Matchers.isPresent;
-import static com.android.tools.r8.utils.codeinspector.Matchers.notIf;
 import static org.hamcrest.MatcherAssert.assertThat;
 
 import com.android.tools.r8.NoVerticalClassMerging;
@@ -16,6 +15,7 @@
 import com.android.tools.r8.errors.CheckDiscardDiagnostic;
 import com.android.tools.r8.utils.BooleanUtils;
 import com.android.tools.r8.utils.codeinspector.AssertUtils;
+import com.android.tools.r8.utils.codeinspector.VerticallyMergedClassesInspector;
 import java.util.List;
 import org.junit.Test;
 import org.junit.runner.RunWith;
@@ -55,13 +55,7 @@
                     "-if class " + A.class.getTypeName(), "-keep class " + C.class.getTypeName())
                 .addNoVerticalClassMergingAnnotations()
                 .addVerticallyMergedClassesInspector(
-                    inspector -> {
-                      if (enableVerticalClassMerging) {
-                        inspector.assertMergedIntoSubtype(A.class);
-                      } else {
-                        inspector.assertNoClassesMerged();
-                      }
-                    })
+                    VerticallyMergedClassesInspector::assertNoClassesMerged)
                 // Intentionally fail compilation due to -checkdiscard. This triggers the
                 // (re)running of the Enqueuer after the final round of tree shaking, for generating
                 // -whyareyoukeeping output.
@@ -81,14 +75,7 @@
                         diagnostics.assertNoMessages();
                       }
                     })
-                // TODO(b/266049507): It is questionable not to keep C when vertical class merging
-                // is enabled. A simple fix is to disable vertical class merging of classes matched
-                // by the -if condition.
-                .inspect(
-                    inspector ->
-                        assertThat(
-                            inspector.clazz(C.class),
-                            notIf(isPresent(), enableVerticalClassMerging)))
+                .inspect(inspector -> assertThat(inspector.clazz(C.class), isPresent()))
                 .run(parameters.getRuntime(), Main.class)
                 .assertSuccessWithOutputLines("B"));
   }
diff --git a/src/test/java/com/android/tools/r8/shaking/ifrule/verticalclassmerging/MergedFieldTypeTest.java b/src/test/java/com/android/tools/r8/shaking/ifrule/verticalclassmerging/MergedFieldTypeTest.java
index f28c2a5..3247e60 100644
--- a/src/test/java/com/android/tools/r8/shaking/ifrule/verticalclassmerging/MergedFieldTypeTest.java
+++ b/src/test/java/com/android/tools/r8/shaking/ifrule/verticalclassmerging/MergedFieldTypeTest.java
@@ -6,9 +6,7 @@
 
 import static com.android.tools.r8.utils.codeinspector.Matchers.isPresent;
 import static org.hamcrest.MatcherAssert.assertThat;
-import static org.hamcrest.core.IsNot.not;
 import static org.junit.Assert.assertEquals;
-import static org.junit.Assert.assertNotEquals;
 
 import com.android.tools.r8.NeverClassInline;
 import com.android.tools.r8.NoAccessModification;
@@ -89,6 +87,11 @@
     public void configure(R8FullTestBuilder builder) {
       super.configure(builder);
       builder
+          .addVerticallyMergedClassesInspector(
+              inspector ->
+                  inspector
+                      .applyIf(enableVerticalClassMerging, i -> i.assertMergedIntoSubtype(A.class))
+                      .assertNoOtherClassesMerged())
           .enableNeverClassInliningAnnotations()
           .enableNoRedundantFieldLoadEliminationAnnotations();
     }
@@ -116,16 +119,13 @@
       assertThat(testClassSubject, isPresent());
 
       if (enableVerticalClassMerging) {
-        // Verify that SuperTestClass has been merged into TestClass.
-        assertThat(inspector.clazz(SuperTestClass.class), not(isPresent()));
-        assertEquals(
-            "java.lang.Object", testClassSubject.getDexProgramClass().superType.toSourceString());
-
         // Verify that TestClass.field has been removed.
         assertEquals(1, testClassSubject.allFields().size());
 
-        // Verify that there was a naming conflict such that SuperTestClass.field was renamed.
-        assertNotEquals("field", testClassSubject.allFields().get(0).getFinalName());
+        // Due to the -if rule, the SuperTestClass is only merged into TestClass after the final
+        // round of tree shaking, at which point TestClass.field has already been removed.
+        // Therefore, we expect no collision to have happened.
+        assertEquals("field", testClassSubject.allFields().get(0).getFinalName());
       }
     }
   }
diff --git a/src/test/java/com/android/tools/r8/shaking/ifrule/verticalclassmerging/MergedParameterTypeTest.java b/src/test/java/com/android/tools/r8/shaking/ifrule/verticalclassmerging/MergedParameterTypeTest.java
index aa46e96..eb770fe 100644
--- a/src/test/java/com/android/tools/r8/shaking/ifrule/verticalclassmerging/MergedParameterTypeTest.java
+++ b/src/test/java/com/android/tools/r8/shaking/ifrule/verticalclassmerging/MergedParameterTypeTest.java
@@ -8,7 +8,6 @@
 import static com.android.tools.r8.utils.codeinspector.Matchers.isPresent;
 import static org.hamcrest.MatcherAssert.assertThat;
 import static org.junit.Assert.assertEquals;
-import static org.junit.Assert.assertNotEquals;
 
 import com.android.tools.r8.NoAccessModification;
 import com.android.tools.r8.NoHorizontalClassMerging;
@@ -87,7 +86,13 @@
     @Override
     public void configure(R8FullTestBuilder builder) {
       super.configure(builder);
-      builder.enableNoHorizontalClassMergingAnnotations();
+      builder
+          .addVerticallyMergedClassesInspector(
+              inspector ->
+                  inspector
+                      .applyIf(enableVerticalClassMerging, i -> i.assertMergedIntoSubtype(A.class))
+                      .assertNoOtherClassesMerged())
+          .enableNoHorizontalClassMergingAnnotations();
     }
 
     @Override
@@ -125,8 +130,10 @@
                 .collect(Collectors.toList());
         assertEquals(1, methods.size());
 
-        // Verify that there was a naming conflict such that SuperTestClass.method was renamed.
-        assertNotEquals("method", methods.get(0).getFinalName());
+        // Due to the -if rule, the SuperTestClass is only merged into TestClass after the final
+        // round of tree shaking, at which point TestClass.method has already been removed.
+        // Therefore, we expect no collision to have happened.
+        assertEquals("method", methods.get(0).getFinalName());
       }
     }
   }
diff --git a/src/test/java/com/android/tools/r8/shaking/ifrule/verticalclassmerging/MergedReturnTypeTest.java b/src/test/java/com/android/tools/r8/shaking/ifrule/verticalclassmerging/MergedReturnTypeTest.java
index 9e98e16..7bb0f91 100644
--- a/src/test/java/com/android/tools/r8/shaking/ifrule/verticalclassmerging/MergedReturnTypeTest.java
+++ b/src/test/java/com/android/tools/r8/shaking/ifrule/verticalclassmerging/MergedReturnTypeTest.java
@@ -8,7 +8,6 @@
 import static org.hamcrest.MatcherAssert.assertThat;
 import static org.hamcrest.core.IsNot.not;
 import static org.junit.Assert.assertEquals;
-import static org.junit.Assert.assertNotEquals;
 
 import com.android.tools.r8.AssumeMayHaveSideEffects;
 import com.android.tools.r8.NoAccessModification;
@@ -96,7 +95,14 @@
     @Override
     public void configure(R8FullTestBuilder builder) {
       super.configure(builder);
-      builder.enableNoHorizontalClassMergingAnnotations().enableSideEffectAnnotations();
+      builder
+          .addVerticallyMergedClassesInspector(
+              inspector ->
+                  inspector
+                      .applyIf(enableVerticalClassMerging, i -> i.assertMergedIntoSubtype(A.class))
+                      .assertNoOtherClassesMerged())
+          .enableNoHorizontalClassMergingAnnotations()
+          .enableSideEffectAnnotations();
     }
 
     @Override
@@ -134,8 +140,10 @@
                 .collect(Collectors.toList());
         assertEquals(1, methods.size());
 
-        // Verify that there was a naming conflict such that SuperTestClass.method was renamed.
-        assertNotEquals("method", methods.get(0).getFinalName());
+        // Due to the -if rule, the SuperTestClass is only merged into TestClass after the final
+        // round of tree shaking, at which point TestClass.method has already been removed.
+        // Therefore, we expect no collision to have happened.
+        assertEquals("method", methods.get(0).getFinalName());
       }
     }
   }