Unify first and final vertical class merging

This changes the first round of vertical class merging so that the vertical class merger now produces fully lens rewritten LIR code. This is consistent with the subsequent phases of vertical class merging.

For consistency with other optimizations, this CL also changes so that we no longer allow vertical class merging of classes that match -if rule preconditions.

As a result of this, there are no outstanding lenses when we enter the primary optimization pass.

Fixes: b/266049507
Fixes: b/318805362
Change-Id: I6a413d7658b8fed08a1c73e1dc8a8b19acc7972e
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 d849002..5b7d4e2 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..1d9d03b 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,14 @@
     @Override
     public void configure(R8FullTestBuilder builder) {
       super.configure(builder);
-      builder.enableNoHorizontalClassMergingAnnotations();
+      builder
+          .addVerticallyMergedClassesInspector(
+              inspector ->
+                  inspector
+                      .applyIf(enableVerticalClassMerging, i -> i.assertMergedIntoSubtype(A.class))
+                      .assertNoOtherClassesMerged())
+          .enableInliningAnnotations()
+          .enableNoHorizontalClassMergingAnnotations();
     }
 
     @Override
@@ -125,8 +131,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..93c9dec 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,15 @@
     @Override
     public void configure(R8FullTestBuilder builder) {
       super.configure(builder);
-      builder.enableNoHorizontalClassMergingAnnotations().enableSideEffectAnnotations();
+      builder
+          .addVerticallyMergedClassesInspector(
+              inspector ->
+                  inspector
+                      .applyIf(enableVerticalClassMerging, i -> i.assertMergedIntoSubtype(A.class))
+                      .assertNoOtherClassesMerged())
+          .enableInliningAnnotations()
+          .enableNoHorizontalClassMergingAnnotations()
+          .enableSideEffectAnnotations();
     }
 
     @Override
@@ -134,8 +141,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());
       }
     }
   }