Maintain no-horizontal-merge set obtained by conditional rules

Change-Id: If522b3991e6628d169371ecf232edbcf75f873b4
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 200fbc6..1fcb8e6 100644
--- a/src/main/java/com/android/tools/r8/shaking/RootSetUtils.java
+++ b/src/main/java/com/android/tools/r8/shaking/RootSetUtils.java
@@ -474,6 +474,7 @@
       return new ConsequentRootSet(
           neverInlineDueToSingleCaller,
           neverClassInline,
+          noHorizontalClassMerging,
           dependentMinimumKeepInfo,
           dependentKeepClassCompatRule,
           Lists.newArrayList(delayedRootSetActionItems),
@@ -1793,6 +1794,7 @@
 
     final Set<DexMethod> neverInlineDueToSingleCaller;
     final Set<DexType> neverClassInline;
+    final Set<DexType> noHorizontalClassMerging;
     private final DependentMinimumKeepInfoCollection dependentMinimumKeepInfo;
     final Map<DexType, Set<ProguardKeepRuleBase>> dependentKeepClassCompatRule;
     final List<DelayedRootSetActionItem> delayedRootSetActionItems;
@@ -1801,12 +1803,14 @@
     RootSetBase(
         Set<DexMethod> neverInlineDueToSingleCaller,
         Set<DexType> neverClassInline,
+        Set<DexType> noHorizontalClassMerging,
         DependentMinimumKeepInfoCollection dependentMinimumKeepInfo,
         Map<DexType, Set<ProguardKeepRuleBase>> dependentKeepClassCompatRule,
         List<DelayedRootSetActionItem> delayedRootSetActionItems,
         ProgramMethodMap<ProgramMethod> pendingMethodMoveInverse) {
       this.neverInlineDueToSingleCaller = neverInlineDueToSingleCaller;
       this.neverClassInline = neverClassInline;
+      this.noHorizontalClassMerging = noHorizontalClassMerging;
       this.dependentMinimumKeepInfo = dependentMinimumKeepInfo;
       this.dependentKeepClassCompatRule = dependentKeepClassCompatRule;
       this.delayedRootSetActionItems = delayedRootSetActionItems;
@@ -1833,7 +1837,6 @@
     public final PredicateSet<DexType> alwaysClassInline;
     public final Set<DexType> noUnusedInterfaceRemoval;
     public final Set<DexType> noVerticalClassMerging;
-    public final Set<DexType> noHorizontalClassMerging;
     public final Set<DexMember<?, ?>> neverPropagateValue;
     public final Map<DexReference, ProguardMemberRule> mayHaveSideEffects;
     public final Set<DexMember<?, ?>> identifierNameStrings;
@@ -1863,6 +1866,7 @@
       super(
           neverInlineDueToSingleCaller,
           neverClassInline,
+          noHorizontalClassMerging,
           dependentMinimumKeepInfo,
           dependentKeepClassCompatRule,
           delayedRootSetActionItems,
@@ -1876,7 +1880,6 @@
       this.alwaysClassInline = alwaysClassInline;
       this.noUnusedInterfaceRemoval = noUnusedInterfaceRemoval;
       this.noVerticalClassMerging = noVerticalClassMerging;
-      this.noHorizontalClassMerging = noHorizontalClassMerging;
       this.neverPropagateValue = neverPropagateValue;
       this.mayHaveSideEffects = mayHaveSideEffects;
       this.identifierNameStrings = Collections.unmodifiableSet(identifierNameStrings);
@@ -1909,6 +1912,7 @@
     void addConsequentRootSet(ConsequentRootSet consequentRootSet) {
       neverInlineDueToSingleCaller.addAll(consequentRootSet.neverInlineDueToSingleCaller);
       neverClassInline.addAll(consequentRootSet.neverClassInline);
+      noHorizontalClassMerging.addAll(consequentRootSet.noHorizontalClassMerging);
       consequentRootSet.dependentKeepClassCompatRule.forEach(
           (type, rules) ->
               dependentKeepClassCompatRule
@@ -2235,6 +2239,7 @@
     ConsequentRootSet(
         Set<DexMethod> neverInlineDueToSingleCaller,
         Set<DexType> neverClassInline,
+        Set<DexType> noHorizontalClassMerging,
         DependentMinimumKeepInfoCollection dependentMinimumKeepInfo,
         Map<DexType, Set<ProguardKeepRuleBase>> dependentKeepClassCompatRule,
         List<DelayedRootSetActionItem> delayedRootSetActionItems,
@@ -2242,6 +2247,7 @@
       super(
           neverInlineDueToSingleCaller,
           neverClassInline,
+          noHorizontalClassMerging,
           dependentMinimumKeepInfo,
           dependentKeepClassCompatRule,
           delayedRootSetActionItems,
diff --git a/src/test/java/com/android/tools/r8/keepanno/doctests/ConditionalMethodRulesAndHorizontalMergingTest.java b/src/test/java/com/android/tools/r8/keepanno/doctests/ConditionalMethodRulesAndHorizontalMergingTest.java
new file mode 100644
index 0000000..ee1748d
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/keepanno/doctests/ConditionalMethodRulesAndHorizontalMergingTest.java
@@ -0,0 +1,139 @@
+// Copyright (c) 2023, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+package com.android.tools.r8.keepanno.doctests;
+
+import com.android.tools.r8.TestBase;
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.TestParametersCollection;
+import com.android.tools.r8.keepanno.doctests.ConditionalMethodRulesAndHorizontalMergingTest.Example1.BaseClass;
+import com.android.tools.r8.keepanno.doctests.ConditionalMethodRulesAndHorizontalMergingTest.Example1.MyHiddenMethodCaller;
+import com.android.tools.r8.keepanno.doctests.ConditionalMethodRulesAndHorizontalMergingTest.Example2.MyFieldValuePrinter;
+import com.android.tools.r8.keepanno.doctests.ConditionalMethodRulesAndHorizontalMergingTest.Example2.PrintableFieldInterface;
+import com.android.tools.r8.utils.AndroidApiLevel;
+import com.android.tools.r8.utils.StringUtils;
+import com.google.common.collect.ImmutableList;
+import java.lang.reflect.Field;
+import java.util.List;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+
+@RunWith(Parameterized.class)
+public class ConditionalMethodRulesAndHorizontalMergingTest extends TestBase {
+
+  static final String EXPECTED =
+      StringUtils.lines("on Base", "on Sub", "intField = 42", "stringField = Hello!");
+
+  private final TestParameters parameters;
+
+  @Parameterized.Parameters(name = "{0}")
+  public static TestParametersCollection data() {
+    return getTestParameters().withDefaultRuntimes().withApiLevel(AndroidApiLevel.B).build();
+  }
+
+  public ConditionalMethodRulesAndHorizontalMergingTest(TestParameters parameters) {
+    this.parameters = parameters;
+  }
+
+  @Test
+  public void testReference() throws Exception {
+    testForRuntime(parameters)
+        .addProgramClasses(TestClass.class)
+        .addProgramClassesAndInnerClasses(getExampleClasses())
+        .run(parameters.getRuntime(), TestClass.class)
+        .assertSuccessWithOutput(EXPECTED);
+  }
+
+  @Test
+  public void testR8() throws Exception {
+    testForR8(parameters.getBackend())
+        .addKeepMainRule(TestClass.class)
+        // The following rules are what was generated by the keepanno extraction for the doc tests.
+        .addKeepRules(
+            "-if class " + typeName(MyHiddenMethodCaller.class) + " {",
+            "  void callHiddenMethod(" + typeName(BaseClass.class) + ");",
+            "}",
+            "-keepclassmembers class * extends " + typeName(BaseClass.class) + " {",
+            "  *** hiddenMethod();",
+            "}",
+            "-if class " + typeName(MyHiddenMethodCaller.class) + " {",
+            "  void callHiddenMethod(" + typeName(BaseClass.class) + ");",
+            "}",
+            "-keepclassmembers class " + typeName(BaseClass.class) + " {",
+            "  *** hiddenMethod();",
+            "}",
+            "-if class " + typeName(MyFieldValuePrinter.class) + " {",
+            "  void printFieldValues(" + typeName(PrintableFieldInterface.class) + ");",
+            "}",
+            "-keepclassmembers class * extends " + typeName(PrintableFieldInterface.class) + " {",
+            "  *** *;",
+            "}"
+            // Removed the rule variant on the base interface as it does not have fields.
+            )
+        .addProgramClasses(TestClass.class)
+        .addProgramClassesAndInnerClasses(getExampleClasses())
+        .setMinApi(parameters)
+        .run(parameters.getRuntime(), TestClass.class)
+        .assertSuccessWithOutput(EXPECTED);
+  }
+
+  public List<Class<?>> getExampleClasses() {
+    return ImmutableList.of(Example1.class, Example2.class);
+  }
+
+  static class TestClass {
+    public static void main(String[] args) throws Exception {
+      Example1.run();
+      Example2.run();
+    }
+  }
+
+  static class Example1 {
+
+    static class BaseClass {
+      void hiddenMethod() {
+        System.out.println("on Base");
+      }
+    }
+
+    static class SubClass extends BaseClass {
+      void hiddenMethod() {
+        System.out.println("on Sub");
+      }
+    }
+
+    public static class MyHiddenMethodCaller {
+      public void callHiddenMethod(BaseClass base) throws Exception {
+        base.getClass().getDeclaredMethod("hiddenMethod").invoke(base);
+      }
+    }
+
+    static void run() throws Exception {
+      new MyHiddenMethodCaller().callHiddenMethod(new BaseClass());
+      new MyHiddenMethodCaller().callHiddenMethod(new SubClass());
+    }
+  }
+
+  static class Example2 {
+
+    interface PrintableFieldInterface {}
+
+    static class ClassWithFields implements PrintableFieldInterface {
+      final int intField = 42;
+      String stringField = "Hello!";
+    }
+
+    public static class MyFieldValuePrinter {
+      public void printFieldValues(PrintableFieldInterface objectWithFields) throws Exception {
+        for (Field field : objectWithFields.getClass().getDeclaredFields()) {
+          System.out.println(field.getName() + " = " + field.get(objectWithFields));
+        }
+      }
+    }
+
+    static void run() throws Exception {
+      new MyFieldValuePrinter().printFieldValues(new ClassWithFields());
+    }
+  }
+}