Add a test for merging init type argument classes

Change-Id: Idc536eecb806b7d52bf7e4e323fa55a66627832c
diff --git a/src/main/java/com/android/tools/r8/optimize/argumentpropagation/ArgumentPropagatorProgramOptimizer.java b/src/main/java/com/android/tools/r8/optimize/argumentpropagation/ArgumentPropagatorProgramOptimizer.java
index 01d4dbc..7d3eb3d 100644
--- a/src/main/java/com/android/tools/r8/optimize/argumentpropagation/ArgumentPropagatorProgramOptimizer.java
+++ b/src/main/java/com/android/tools/r8/optimize/argumentpropagation/ArgumentPropagatorProgramOptimizer.java
@@ -43,6 +43,7 @@
 import com.android.tools.r8.utils.BooleanBox;
 import com.android.tools.r8.utils.IntBox;
 import com.android.tools.r8.utils.InternalOptions;
+import com.android.tools.r8.utils.InternalOptions.CallSiteOptimizationOptions;
 import com.android.tools.r8.utils.Pair;
 import com.android.tools.r8.utils.ThreadUtils;
 import com.android.tools.r8.utils.Timing;
@@ -216,6 +217,8 @@
 
     private final DexItemFactory dexItemFactory = appView.dexItemFactory();
     private final InternalOptions options = appView.options();
+    private final CallSiteOptimizationOptions callSiteOptimizationOptions =
+        appView.options().callSiteOptimizationOptions();
 
     private final Map<DexMethodSignature, AllowedPrototypeChanges>
         allowedPrototypeChangesForVirtualMethods = new HashMap<>();
@@ -776,13 +779,15 @@
       if (instanceInitializerSignatures.add(rewrittenMethod)) {
         return prototypeChanges;
       }
-      for (DexType extraArgumentType :
-          ImmutableList.of(dexItemFactory.intType, dexItemFactory.objectType)) {
-        RewrittenPrototypeDescription candidatePrototypeChanges =
-            prototypeChanges.withExtraParameters(new ExtraUnusedNullParameter(extraArgumentType));
-        rewrittenMethod = candidatePrototypeChanges.rewriteMethod(method, dexItemFactory);
-        if (instanceInitializerSignatures.add(rewrittenMethod)) {
-          return candidatePrototypeChanges;
+      if (!callSiteOptimizationOptions.isForceSyntheticsForInstanceInitializersEnabled()) {
+        for (DexType extraArgumentType :
+            ImmutableList.of(dexItemFactory.intType, dexItemFactory.objectType)) {
+          RewrittenPrototypeDescription candidatePrototypeChanges =
+              prototypeChanges.withExtraParameters(new ExtraUnusedNullParameter(extraArgumentType));
+          rewrittenMethod = candidatePrototypeChanges.rewriteMethod(method, dexItemFactory);
+          if (instanceInitializerSignatures.add(rewrittenMethod)) {
+            return candidatePrototypeChanges;
+          }
         }
       }
       DexType extraArgumentType =
diff --git a/src/main/java/com/android/tools/r8/utils/InternalOptions.java b/src/main/java/com/android/tools/r8/utils/InternalOptions.java
index 58cdf7e..359d95a 100644
--- a/src/main/java/com/android/tools/r8/utils/InternalOptions.java
+++ b/src/main/java/com/android/tools/r8/utils/InternalOptions.java
@@ -1275,6 +1275,8 @@
     private boolean enabled = true;
     private boolean enableMethodStaticizing = true;
 
+    private boolean forceSyntheticsForInstanceInitializers = false;
+
     public void disableOptimization() {
       enabled = false;
     }
@@ -1290,6 +1292,10 @@
       return enabled;
     }
 
+    public boolean isForceSyntheticsForInstanceInitializersEnabled() {
+      return forceSyntheticsForInstanceInitializers;
+    }
+
     public boolean isMethodStaticizingEnabled() {
       return enableMethodStaticizing;
     }
@@ -1303,6 +1309,12 @@
       return this;
     }
 
+    public CallSiteOptimizationOptions setForceSyntheticsForInstanceInitializers(
+        boolean forceSyntheticsForInstanceInitializers) {
+      this.forceSyntheticsForInstanceInitializers = forceSyntheticsForInstanceInitializers;
+      return this;
+    }
+
     public CallSiteOptimizationOptions setEnableMethodStaticizing(boolean enableMethodStaticizing) {
       this.enableMethodStaticizing = enableMethodStaticizing;
       return this;
diff --git a/src/test/java/com/android/tools/r8/classmerging/horizontal/HorizontalClassMergingOfInitArgumentTypesTest.java b/src/test/java/com/android/tools/r8/classmerging/horizontal/HorizontalClassMergingOfInitArgumentTypesTest.java
new file mode 100644
index 0000000..fc00578
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/classmerging/horizontal/HorizontalClassMergingOfInitArgumentTypesTest.java
@@ -0,0 +1,118 @@
+// Copyright (c) 2022, 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.classmerging.horizontal;
+
+import static com.android.tools.r8.utils.codeinspector.Matchers.isPresent;
+import static org.hamcrest.MatcherAssert.assertThat;
+import static org.junit.Assert.assertEquals;
+
+import com.android.tools.r8.NoHorizontalClassMerging;
+import com.android.tools.r8.TestBase;
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.synthesis.SyntheticItemsTestUtils;
+import com.android.tools.r8.utils.BooleanUtils;
+import java.util.List;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+import org.junit.runners.Parameterized.Parameter;
+import org.junit.runners.Parameterized.Parameters;
+
+@RunWith(Parameterized.class)
+public class HorizontalClassMergingOfInitArgumentTypesTest extends TestBase {
+
+  @Parameter(0)
+  public boolean enableHorizontalClassMerging;
+
+  @Parameter(1)
+  public TestParameters parameters;
+
+  @Parameters(name = "{1}, horizontal class merging: {0}")
+  public static List<Object[]> data() {
+    return buildParameters(
+        BooleanUtils.values(), getTestParameters().withAllRuntimesAndApiLevels().build());
+  }
+
+  @Test
+  public void test() throws Exception {
+    testForR8(parameters.getBackend())
+        .addInnerClasses(getClass())
+        .addKeepMainRule(Main.class)
+        .addOptionsModification(
+            options -> {
+              options.callSiteOptimizationOptions().setForceSyntheticsForInstanceInitializers(true);
+              options.horizontalClassMergerOptions().enableIf(enableHorizontalClassMerging);
+            })
+        .enableNoHorizontalClassMergingAnnotations()
+        .setMinApi(parameters.getApiLevel())
+        .compile()
+        .inspect(
+            inspector -> {
+              int expectedNumberOfSynthetics =
+                  2 - BooleanUtils.intValue(enableHorizontalClassMerging);
+              assertEquals(3 + expectedNumberOfSynthetics, inspector.allClasses().size());
+              assertThat(inspector.clazz(Main.class), isPresent());
+              assertThat(inspector.clazz(A.class), isPresent());
+              assertThat(inspector.clazz(B.class), isPresent());
+              assertEquals(
+                  expectedNumberOfSynthetics,
+                  inspector.allClasses().stream()
+                      .filter(
+                          clazz ->
+                              SyntheticItemsTestUtils.isExternalNonFixedInitializerTypeArgument(
+                                  clazz.getOriginalReference()))
+                      .count());
+            })
+        .run(parameters.getRuntime(), Main.class)
+        .assertSuccessWithOutputLines("A()", "A(Object)", "B()", "B(Object)");
+  }
+
+  static class Main {
+
+    public static void main(String[] args) {
+      String arg = args.length > 0 ? args[1] : null;
+      System.out.println(new A());
+      System.out.println(new A(arg));
+      System.out.println(new B());
+      System.out.println(new B(arg));
+    }
+  }
+
+  @NoHorizontalClassMerging
+  static class A {
+
+    boolean unused;
+
+    A() {}
+
+    // Unused argument removal will rewrite this into A(A$$ExternalSynthetic$IA0)
+    A(Object unused) {
+      this.unused = true;
+    }
+
+    @Override
+    public String toString() {
+      return unused ? "A(Object)" : "A()";
+    }
+  }
+
+  @NoHorizontalClassMerging
+  static class B {
+
+    boolean unused;
+
+    B() {}
+
+    // Unused argument removal will rewrite this into B(B$$ExternalSynthetic$IA0)
+    B(Object unused) {
+      this.unused = true;
+    }
+
+    @Override
+    public String toString() {
+      return unused ? "B(Object)" : "B()";
+    }
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/synthesis/SyntheticItemsTestUtils.java b/src/test/java/com/android/tools/r8/synthesis/SyntheticItemsTestUtils.java
index bb46834..8cc71c8 100644
--- a/src/test/java/com/android/tools/r8/synthesis/SyntheticItemsTestUtils.java
+++ b/src/test/java/com/android/tools/r8/synthesis/SyntheticItemsTestUtils.java
@@ -122,6 +122,11 @@
     return SyntheticNaming.isSynthetic(reference, null, SyntheticKind.INIT_TYPE_ARGUMENT);
   }
 
+  public static boolean isExternalNonFixedInitializerTypeArgument(ClassReference reference) {
+    return SyntheticNaming.isSynthetic(
+        reference, Phase.EXTERNAL, SyntheticKind.NON_FIXED_INIT_TYPE_ARGUMENT);
+  }
+
   public static boolean isHorizontalInitializerTypeArgument(ClassReference reference) {
     return SyntheticNaming.isSynthetic(
             reference, null, SyntheticKind.HORIZONTAL_INIT_TYPE_ARGUMENT_1)