Add tests for constructor merging

Bug: 189296638
Change-Id: I45f89cce32651b9304d0bd7b58192ed1aa8462fb
diff --git a/src/test/java/com/android/tools/r8/classmerging/horizontal/EquivalentConstructorsWithClassIdAfterUnusedArgumentRemovalMergingTest.java b/src/test/java/com/android/tools/r8/classmerging/horizontal/EquivalentConstructorsWithClassIdAfterUnusedArgumentRemovalMergingTest.java
new file mode 100644
index 0000000..5d9722b
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/classmerging/horizontal/EquivalentConstructorsWithClassIdAfterUnusedArgumentRemovalMergingTest.java
@@ -0,0 +1,120 @@
+// Copyright (c) 2021, 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.NeverClassInline;
+import com.android.tools.r8.NoHorizontalClassMerging;
+import com.android.tools.r8.TestBase;
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.TestParametersCollection;
+import com.android.tools.r8.utils.codeinspector.ClassSubject;
+import com.android.tools.r8.utils.codeinspector.FoundMethodSubject;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+import org.junit.runners.Parameterized.Parameters;
+
+@RunWith(Parameterized.class)
+public class EquivalentConstructorsWithClassIdAfterUnusedArgumentRemovalMergingTest
+    extends TestBase {
+
+  private final TestParameters parameters;
+
+  @Parameters(name = "{0}")
+  public static TestParametersCollection params() {
+    return getTestParameters().withAllRuntimesAndApiLevels().build();
+  }
+
+  public EquivalentConstructorsWithClassIdAfterUnusedArgumentRemovalMergingTest(
+      TestParameters parameters) {
+    this.parameters = parameters;
+  }
+
+  @Test
+  public void test() throws Exception {
+    testForR8(parameters.getBackend())
+        .addInnerClasses(getClass())
+        .addKeepMainRule(Main.class)
+        .addHorizontallyMergedClassesInspector(
+            inspector ->
+                inspector.assertIsCompleteMergeGroup(A.class, B.class).assertNoOtherClassesMerged())
+        .enableNeverClassInliningAnnotations()
+        .enableNoHorizontalClassMergingAnnotations()
+        .setMinApi(parameters.getApiLevel())
+        .compile()
+        .inspect(
+            inspector -> {
+              ClassSubject aClassSubject = inspector.clazz(A.class);
+              assertThat(aClassSubject, isPresent());
+              // TODO(b/189296638): Should be 1.
+              assertEquals(
+                  2, aClassSubject.allMethods(FoundMethodSubject::isInstanceInitializer).size());
+            })
+        .run(parameters.getRuntime(), Main.class)
+        .assertSuccessWithOutputLines("C", "D");
+  }
+
+  static class Main {
+
+    public static void main(String[] args) {
+      System.out.println(new A(new Object(), new C()));
+      System.out.println(new B(new Object(), new D()));
+    }
+  }
+
+  @NeverClassInline
+  static class A {
+
+    private final C c;
+
+    A(Object unused, C c) {
+      this.c = c;
+    }
+
+    @Override
+    public String toString() {
+      return c.toString();
+    }
+  }
+
+  @NeverClassInline
+  static class B {
+
+    private final D d;
+
+    B(Object unused, D d) {
+      this.d = d;
+    }
+
+    @Override
+    public String toString() {
+      return d.toString();
+    }
+  }
+
+  @NeverClassInline
+  @NoHorizontalClassMerging
+  static class C {
+
+    @Override
+    public String toString() {
+      return "C";
+    }
+  }
+
+  @NeverClassInline
+  @NoHorizontalClassMerging
+  static class D {
+
+    @Override
+    public String toString() {
+      return "D";
+    }
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/classmerging/horizontal/EquivalentConstructorsWithClassIdAndDifferentArgumentAndAssignmentOrderMergingTest.java b/src/test/java/com/android/tools/r8/classmerging/horizontal/EquivalentConstructorsWithClassIdAndDifferentArgumentAndAssignmentOrderMergingTest.java
new file mode 100644
index 0000000..e70cd5a
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/classmerging/horizontal/EquivalentConstructorsWithClassIdAndDifferentArgumentAndAssignmentOrderMergingTest.java
@@ -0,0 +1,125 @@
+// Copyright (c) 2021, 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.NeverClassInline;
+import com.android.tools.r8.NoHorizontalClassMerging;
+import com.android.tools.r8.TestBase;
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.TestParametersCollection;
+import com.android.tools.r8.utils.codeinspector.ClassSubject;
+import com.android.tools.r8.utils.codeinspector.FoundMethodSubject;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+import org.junit.runners.Parameterized.Parameters;
+
+@RunWith(Parameterized.class)
+public class EquivalentConstructorsWithClassIdAndDifferentArgumentAndAssignmentOrderMergingTest
+    extends TestBase {
+
+  private final TestParameters parameters;
+
+  @Parameters(name = "{0}")
+  public static TestParametersCollection params() {
+    return getTestParameters().withAllRuntimesAndApiLevels().build();
+  }
+
+  public EquivalentConstructorsWithClassIdAndDifferentArgumentAndAssignmentOrderMergingTest(
+      TestParameters parameters) {
+    this.parameters = parameters;
+  }
+
+  @Test
+  public void test() throws Exception {
+    testForR8(parameters.getBackend())
+        .addInnerClasses(getClass())
+        .addKeepMainRule(Main.class)
+        .addHorizontallyMergedClassesInspector(
+            inspector ->
+                inspector.assertIsCompleteMergeGroup(A.class, B.class).assertNoOtherClassesMerged())
+        .enableNeverClassInliningAnnotations()
+        .enableNoHorizontalClassMergingAnnotations()
+        .setMinApi(parameters.getApiLevel())
+        .compile()
+        .inspect(
+            inspector -> {
+              ClassSubject aClassSubject = inspector.clazz(A.class);
+              assertThat(aClassSubject, isPresent());
+              // TODO(b/189296638): Enable constructor merging by changing the constructor
+              // arguments.
+              assertEquals(
+                  2, aClassSubject.allMethods(FoundMethodSubject::isInstanceInitializer).size());
+            })
+        .run(parameters.getRuntime(), Main.class)
+        .assertSuccessWithOutputLines("CD", "CD");
+  }
+
+  static class Main {
+
+    public static void main(String[] args) {
+      System.out.println(new A(new C(), new D()));
+      System.out.println(new B(new D(), new C()));
+    }
+  }
+
+  @NeverClassInline
+  static class A {
+
+    private final C c;
+    private final D d;
+
+    A(C c, D d) {
+      this.c = c;
+      this.d = d;
+    }
+
+    @Override
+    public String toString() {
+      return c.toString() + d.toString();
+    }
+  }
+
+  @NeverClassInline
+  static class B {
+
+    private final C c;
+    private final D d;
+
+    B(D d, C c) {
+      this.d = d;
+      this.c = c;
+    }
+
+    @Override
+    public String toString() {
+      return c.toString() + d.toString();
+    }
+  }
+
+  @NeverClassInline
+  @NoHorizontalClassMerging
+  static class C {
+
+    @Override
+    public String toString() {
+      return "C";
+    }
+  }
+
+  @NeverClassInline
+  @NoHorizontalClassMerging
+  static class D {
+
+    @Override
+    public String toString() {
+      return "D";
+    }
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/classmerging/horizontal/EquivalentConstructorsWithClassIdAndDifferentArgumentOrderMergingTest.java b/src/test/java/com/android/tools/r8/classmerging/horizontal/EquivalentConstructorsWithClassIdAndDifferentArgumentOrderMergingTest.java
new file mode 100644
index 0000000..a5d7e26
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/classmerging/horizontal/EquivalentConstructorsWithClassIdAndDifferentArgumentOrderMergingTest.java
@@ -0,0 +1,126 @@
+// Copyright (c) 2021, 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.NeverClassInline;
+import com.android.tools.r8.NoHorizontalClassMerging;
+import com.android.tools.r8.TestBase;
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.TestParametersCollection;
+import com.android.tools.r8.utils.codeinspector.ClassSubject;
+import com.android.tools.r8.utils.codeinspector.FoundMethodSubject;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+import org.junit.runners.Parameterized.Parameters;
+
+@RunWith(Parameterized.class)
+public class EquivalentConstructorsWithClassIdAndDifferentArgumentOrderMergingTest
+    extends TestBase {
+
+  private final TestParameters parameters;
+
+  @Parameters(name = "{0}")
+  public static TestParametersCollection params() {
+    return getTestParameters().withAllRuntimesAndApiLevels().build();
+  }
+
+  public EquivalentConstructorsWithClassIdAndDifferentArgumentOrderMergingTest(
+      TestParameters parameters) {
+    this.parameters = parameters;
+  }
+
+  @Test
+  public void test() throws Exception {
+    testForR8(parameters.getBackend())
+        .addInnerClasses(getClass())
+        .addKeepMainRule(Main.class)
+        .addHorizontallyMergedClassesInspector(
+            inspector ->
+                inspector.assertIsCompleteMergeGroup(A.class, B.class).assertNoOtherClassesMerged())
+        .enableNeverClassInliningAnnotations()
+        .enableNoHorizontalClassMergingAnnotations()
+        .setMinApi(parameters.getApiLevel())
+        .compile()
+        .inspect(
+            inspector -> {
+              ClassSubject aClassSubject = inspector.clazz(A.class);
+              assertThat(aClassSubject, isPresent());
+
+              // TODO(b/189296638): Should be 1, but requires changing the order of arguments at
+              //  constructor call sites.
+              assertEquals(
+                  2, aClassSubject.allMethods(FoundMethodSubject::isInstanceInitializer).size());
+            })
+        .run(parameters.getRuntime(), Main.class)
+        .assertSuccessWithOutputLines("C0", "D1");
+  }
+
+  static class Main {
+
+    public static void main(String[] args) {
+      System.out.println(new A(0, new C()));
+      System.out.println(new B(new D(), 1));
+    }
+  }
+
+  @NeverClassInline
+  static class A {
+
+    private final C c;
+    private final int i;
+
+    A(int i, C c) {
+      this.c = c;
+      this.i = i;
+    }
+
+    @Override
+    public String toString() {
+      return c.toString() + i;
+    }
+  }
+
+  @NeverClassInline
+  static class B {
+
+    private final D d;
+    private final int i;
+
+    B(D d, int i) {
+      this.d = d;
+      this.i = i;
+    }
+
+    @Override
+    public String toString() {
+      return d.toString() + i;
+    }
+  }
+
+  @NeverClassInline
+  @NoHorizontalClassMerging
+  static class C {
+
+    @Override
+    public String toString() {
+      return "C";
+    }
+  }
+
+  @NeverClassInline
+  @NoHorizontalClassMerging
+  static class D {
+
+    @Override
+    public String toString() {
+      return "D";
+    }
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/classmerging/horizontal/EquivalentConstructorsWithClassIdAndDifferentAssignmentOrderMergingTest.java b/src/test/java/com/android/tools/r8/classmerging/horizontal/EquivalentConstructorsWithClassIdAndDifferentAssignmentOrderMergingTest.java
new file mode 100644
index 0000000..bfce2d6
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/classmerging/horizontal/EquivalentConstructorsWithClassIdAndDifferentAssignmentOrderMergingTest.java
@@ -0,0 +1,125 @@
+// Copyright (c) 2021, 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.NeverClassInline;
+import com.android.tools.r8.NoHorizontalClassMerging;
+import com.android.tools.r8.TestBase;
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.TestParametersCollection;
+import com.android.tools.r8.utils.codeinspector.ClassSubject;
+import com.android.tools.r8.utils.codeinspector.FoundMethodSubject;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+import org.junit.runners.Parameterized.Parameters;
+
+@RunWith(Parameterized.class)
+public class EquivalentConstructorsWithClassIdAndDifferentAssignmentOrderMergingTest
+    extends TestBase {
+
+  private final TestParameters parameters;
+
+  @Parameters(name = "{0}")
+  public static TestParametersCollection params() {
+    return getTestParameters().withAllRuntimesAndApiLevels().build();
+  }
+
+  public EquivalentConstructorsWithClassIdAndDifferentAssignmentOrderMergingTest(
+      TestParameters parameters) {
+    this.parameters = parameters;
+  }
+
+  @Test
+  public void test() throws Exception {
+    testForR8(parameters.getBackend())
+        .addInnerClasses(getClass())
+        .addKeepMainRule(Main.class)
+        .addHorizontallyMergedClassesInspector(
+            inspector ->
+                inspector.assertIsCompleteMergeGroup(A.class, B.class).assertNoOtherClassesMerged())
+        .enableNeverClassInliningAnnotations()
+        .enableNoHorizontalClassMergingAnnotations()
+        .setMinApi(parameters.getApiLevel())
+        .compile()
+        .inspect(
+            inspector -> {
+              ClassSubject aClassSubject = inspector.clazz(A.class);
+              assertThat(aClassSubject, isPresent());
+              // TODO(b/189296638): Should be 1.
+              assertEquals(
+                  2, aClassSubject.allMethods(FoundMethodSubject::isInstanceInitializer).size());
+            })
+        .run(parameters.getRuntime(), Main.class)
+        .assertSuccessWithOutputLines("CC", "DD");
+  }
+
+  static class Main {
+
+    public static void main(String[] args) {
+      System.out.println(new A(new C(), new C()));
+      System.out.println(new B(new D(), new D()));
+    }
+  }
+
+  @NeverClassInline
+  static class A {
+
+    private final C c;
+    private final C c2;
+
+    A(C c, C c2) {
+      this.c = c;
+      this.c2 = c2;
+    }
+
+    @Override
+    public String toString() {
+      return c.toString() + c2.toString();
+    }
+  }
+
+  @NeverClassInline
+  static class B {
+
+    private final D d;
+    private final D d2;
+
+    B(D d, D d2) {
+      // d2 intentionally assigned before d.
+      this.d2 = d2;
+      this.d = d;
+    }
+
+    @Override
+    public String toString() {
+      return d.toString() + d2.toString();
+    }
+  }
+
+  @NeverClassInline
+  @NoHorizontalClassMerging
+  static class C {
+
+    @Override
+    public String toString() {
+      return "C";
+    }
+  }
+
+  @NeverClassInline
+  @NoHorizontalClassMerging
+  static class D {
+
+    @Override
+    public String toString() {
+      return "D";
+    }
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/classmerging/horizontal/EquivalentConstructorsWithClassIdAndExtraNullsMergingTest.java b/src/test/java/com/android/tools/r8/classmerging/horizontal/EquivalentConstructorsWithClassIdAndExtraNullsMergingTest.java
new file mode 100644
index 0000000..5e144b0
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/classmerging/horizontal/EquivalentConstructorsWithClassIdAndExtraNullsMergingTest.java
@@ -0,0 +1,150 @@
+// Copyright (c) 2021, 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.NeverClassInline;
+import com.android.tools.r8.NoHorizontalClassMerging;
+import com.android.tools.r8.TestBase;
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.TestParametersCollection;
+import com.android.tools.r8.synthesis.SyntheticItemsTestUtils;
+import com.android.tools.r8.utils.codeinspector.ClassSubject;
+import com.android.tools.r8.utils.codeinspector.FoundMethodSubject;
+import org.junit.AssumptionViolatedException;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+import org.junit.runners.Parameterized.Parameters;
+
+@RunWith(Parameterized.class)
+public class EquivalentConstructorsWithClassIdAndExtraNullsMergingTest extends TestBase {
+
+  private final TestParameters parameters;
+
+  @Parameters(name = "{0}")
+  public static TestParametersCollection params() {
+    return getTestParameters().withAllRuntimesAndApiLevels().build();
+  }
+
+  public EquivalentConstructorsWithClassIdAndExtraNullsMergingTest(TestParameters parameters) {
+    this.parameters = parameters;
+  }
+
+  @Test
+  public void test() throws Exception {
+    testForR8(parameters.getBackend())
+        .addInnerClasses(getClass())
+        .addKeepMainRule(Main.class)
+        .addHorizontallyMergedClassesInspector(
+            inspector ->
+                inspector.assertIsCompleteMergeGroup(A.class, B.class).assertNoOtherClassesMerged())
+        .enableNeverClassInliningAnnotations()
+        .enableNoHorizontalClassMergingAnnotations()
+        .setMinApi(parameters.getApiLevel())
+        .compile()
+        .inspect(
+            inspector -> {
+              ClassSubject aClassSubject = inspector.clazz(A.class);
+              assertThat(aClassSubject, isPresent());
+              // TODO(b/189296638): Should be 2.
+              assertEquals(
+                  3, aClassSubject.allMethods(FoundMethodSubject::isInstanceInitializer).size());
+
+              ClassSubject nullArgumentClassSubject =
+                  inspector.allClasses().stream()
+                      .filter(
+                          clazz ->
+                              SyntheticItemsTestUtils.isHorizontalInitializerTypeArgument(
+                                  clazz.getOriginalReference()))
+                      .findFirst()
+                      // TODO(b/189296638): Should throw RuntimeException.
+                      .orElseThrow(
+                          () ->
+                              new AssumptionViolatedException(
+                                  "Expected Horizontal initializer type argument"));
+
+              assertThat(
+                  aClassSubject.method("void", "<init>", "java.lang.Object", "int"), isPresent());
+              assertThat(
+                  aClassSubject.method(
+                      "void",
+                      "<init>",
+                      "java.lang.Object",
+                      "int",
+                      nullArgumentClassSubject.getFinalName()),
+                  isPresent());
+            })
+        .run(parameters.getRuntime(), Main.class)
+        .assertSuccessWithOutputLines("C", "42", "C", "D");
+  }
+
+  static class Main {
+
+    public static void main(String[] args) {
+      System.out.println(new A(new C()));
+      System.out.println(new A(new C(), 42));
+      System.out.println(new B(new D()));
+    }
+  }
+
+  @NeverClassInline
+  static class A {
+
+    private final Object c;
+
+    A(C c) {
+      this.c = c;
+    }
+
+    A(Object c, int i) {
+      this.c = c;
+      System.out.println(i);
+    }
+
+    @Override
+    public String toString() {
+      return c.toString();
+    }
+  }
+
+  @NeverClassInline
+  static class B {
+
+    private final D d;
+
+    B(D d) {
+      this.d = d;
+    }
+
+    @Override
+    public String toString() {
+      return d.toString();
+    }
+  }
+
+  @NeverClassInline
+  @NoHorizontalClassMerging
+  static class C {
+
+    @Override
+    public String toString() {
+      return "C";
+    }
+  }
+
+  @NeverClassInline
+  @NoHorizontalClassMerging
+  static class D {
+
+    @Override
+    public String toString() {
+      return "D";
+    }
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/classmerging/horizontal/EquivalentConstructorsWithClassIdMergingTest.java b/src/test/java/com/android/tools/r8/classmerging/horizontal/EquivalentConstructorsWithClassIdMergingTest.java
new file mode 100644
index 0000000..59756bf
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/classmerging/horizontal/EquivalentConstructorsWithClassIdMergingTest.java
@@ -0,0 +1,118 @@
+// Copyright (c) 2021, 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.NeverClassInline;
+import com.android.tools.r8.NoHorizontalClassMerging;
+import com.android.tools.r8.TestBase;
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.TestParametersCollection;
+import com.android.tools.r8.utils.codeinspector.ClassSubject;
+import com.android.tools.r8.utils.codeinspector.FoundMethodSubject;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+import org.junit.runners.Parameterized.Parameters;
+
+@RunWith(Parameterized.class)
+public class EquivalentConstructorsWithClassIdMergingTest extends TestBase {
+
+  private final TestParameters parameters;
+
+  @Parameters(name = "{0}")
+  public static TestParametersCollection params() {
+    return getTestParameters().withAllRuntimesAndApiLevels().build();
+  }
+
+  public EquivalentConstructorsWithClassIdMergingTest(TestParameters parameters) {
+    this.parameters = parameters;
+  }
+
+  @Test
+  public void test() throws Exception {
+    testForR8(parameters.getBackend())
+        .addInnerClasses(getClass())
+        .addKeepMainRule(Main.class)
+        .addHorizontallyMergedClassesInspector(
+            inspector ->
+                inspector.assertIsCompleteMergeGroup(A.class, B.class).assertNoOtherClassesMerged())
+        .enableNeverClassInliningAnnotations()
+        .enableNoHorizontalClassMergingAnnotations()
+        .setMinApi(parameters.getApiLevel())
+        .compile()
+        .inspect(
+            inspector -> {
+              ClassSubject aClassSubject = inspector.clazz(A.class);
+              assertThat(aClassSubject, isPresent());
+              // TODO(b/189296638): Should be 1.
+              assertEquals(
+                  2, aClassSubject.allMethods(FoundMethodSubject::isInstanceInitializer).size());
+            })
+        .run(parameters.getRuntime(), Main.class)
+        .assertSuccessWithOutputLines("C", "D");
+  }
+
+  static class Main {
+
+    public static void main(String[] args) {
+      System.out.println(new A(new C()));
+      System.out.println(new B(new D()));
+    }
+  }
+
+  @NeverClassInline
+  static class A {
+
+    private final C c;
+
+    A(C c) {
+      this.c = c;
+    }
+
+    @Override
+    public String toString() {
+      return c.toString();
+    }
+  }
+
+  @NeverClassInline
+  static class B {
+
+    private final D d;
+
+    B(D d) {
+      this.d = d;
+    }
+
+    @Override
+    public String toString() {
+      return d.toString();
+    }
+  }
+
+  @NeverClassInline
+  @NoHorizontalClassMerging
+  static class C {
+
+    @Override
+    public String toString() {
+      return "C";
+    }
+  }
+
+  @NeverClassInline
+  @NoHorizontalClassMerging
+  static class D {
+
+    @Override
+    public String toString() {
+      return "D";
+    }
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/classmerging/horizontal/EquivalentConstructorsWithInterfaceValueToParentTest.java b/src/test/java/com/android/tools/r8/classmerging/horizontal/EquivalentConstructorsWithInterfaceValueToParentTest.java
new file mode 100644
index 0000000..8785c85
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/classmerging/horizontal/EquivalentConstructorsWithInterfaceValueToParentTest.java
@@ -0,0 +1,115 @@
+// Copyright (c) 2021, 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 com.android.tools.r8.NeverClassInline;
+import com.android.tools.r8.NoHorizontalClassMerging;
+import com.android.tools.r8.NoUnusedInterfaceRemoval;
+import com.android.tools.r8.NoVerticalClassMerging;
+import com.android.tools.r8.TestBase;
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.TestParametersCollection;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+import org.junit.runners.Parameterized.Parameters;
+
+@RunWith(Parameterized.class)
+public class EquivalentConstructorsWithInterfaceValueToParentTest extends TestBase {
+
+  private final TestParameters parameters;
+
+  @Parameters(name = "{0}")
+  public static TestParametersCollection data() {
+    return getTestParameters().withAllRuntimes().withAllApiLevelsAlsoForCf().build();
+  }
+
+  public EquivalentConstructorsWithInterfaceValueToParentTest(TestParameters parameters) {
+    this.parameters = parameters;
+  }
+
+  @Test
+  public void test() throws Exception {
+    testForR8(parameters.getBackend())
+        .addInnerClasses(getClass())
+        .addKeepMainRule(Main.class)
+        .enableNeverClassInliningAnnotations()
+        .enableNoHorizontalClassMergingAnnotations()
+        .enableNoUnusedInterfaceRemovalAnnotations()
+        .enableNoVerticalClassMergingAnnotations()
+        .setMinApi(parameters.getApiLevel())
+        .compile()
+        .run(parameters.getRuntime(), Main.class)
+        .assertSuccessWithOutputLines("K", "L");
+  }
+
+  static class Main {
+
+    public static void main(String[] args) {
+      new A(new KImpl());
+      new B(new LImpl());
+    }
+  }
+
+  static class Parent {
+    Parent(J j) {
+      j.m();
+    }
+  }
+
+  @NeverClassInline
+  static class A extends Parent {
+    // When merging this initializer with B(L), it is important that we choose the signature AB(J)
+    // and not AB(I) or AB(Object), or the program will not verify.
+    A(K k) {
+      super(k);
+    }
+  }
+
+  @NeverClassInline
+  static class B extends Parent {
+    B(L l) {
+      super(l);
+    }
+  }
+
+  @NoHorizontalClassMerging
+  static class KImpl implements K {
+    @Override
+    public void m() {
+      System.out.println("K");
+    }
+  }
+
+  @NoHorizontalClassMerging
+  static class LImpl implements L {
+    @Override
+    public void m() {
+      System.out.println("L");
+    }
+  }
+
+  @NoHorizontalClassMerging
+  @NoUnusedInterfaceRemoval
+  @NoVerticalClassMerging
+  interface I {
+    void m();
+  }
+
+  @NoHorizontalClassMerging
+  @NoUnusedInterfaceRemoval
+  @NoVerticalClassMerging
+  interface J extends I {}
+
+  @NoHorizontalClassMerging
+  @NoUnusedInterfaceRemoval
+  @NoVerticalClassMerging
+  interface K extends J {}
+
+  @NoHorizontalClassMerging
+  @NoUnusedInterfaceRemoval
+  @NoVerticalClassMerging
+  interface L extends J {}
+}
diff --git a/src/test/java/com/android/tools/r8/classmerging/horizontal/EquivalentConstructorsWithoutClassIdAfterUnusedArgumentRemovalMergingTest.java b/src/test/java/com/android/tools/r8/classmerging/horizontal/EquivalentConstructorsWithoutClassIdAfterUnusedArgumentRemovalMergingTest.java
new file mode 100644
index 0000000..d429057
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/classmerging/horizontal/EquivalentConstructorsWithoutClassIdAfterUnusedArgumentRemovalMergingTest.java
@@ -0,0 +1,122 @@
+// Copyright (c) 2021, 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.NeverClassInline;
+import com.android.tools.r8.NeverInline;
+import com.android.tools.r8.NoHorizontalClassMerging;
+import com.android.tools.r8.TestBase;
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.TestParametersCollection;
+import com.android.tools.r8.utils.codeinspector.ClassSubject;
+import com.android.tools.r8.utils.codeinspector.FoundMethodSubject;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+import org.junit.runners.Parameterized.Parameters;
+
+@RunWith(Parameterized.class)
+public class EquivalentConstructorsWithoutClassIdAfterUnusedArgumentRemovalMergingTest
+    extends TestBase {
+
+  private final TestParameters parameters;
+
+  @Parameters(name = "{0}")
+  public static TestParametersCollection params() {
+    return getTestParameters().withAllRuntimesAndApiLevels().build();
+  }
+
+  public EquivalentConstructorsWithoutClassIdAfterUnusedArgumentRemovalMergingTest(
+      TestParameters parameters) {
+    this.parameters = parameters;
+  }
+
+  @Test
+  public void test() throws Exception {
+    testForR8(parameters.getBackend())
+        .addInnerClasses(getClass())
+        .addKeepMainRule(Main.class)
+        .addHorizontallyMergedClassesInspector(
+            inspector ->
+                inspector.assertIsCompleteMergeGroup(A.class, B.class).assertNoOtherClassesMerged())
+        .enableInliningAnnotations()
+        .enableNeverClassInliningAnnotations()
+        .enableNoHorizontalClassMergingAnnotations()
+        .setMinApi(parameters.getApiLevel())
+        .compile()
+        .inspect(
+            inspector -> {
+              ClassSubject aClassSubject = inspector.clazz(A.class);
+              assertThat(aClassSubject, isPresent());
+              // TODO(b/189296638): Should be 1.
+              assertEquals(
+                  2, aClassSubject.allMethods(FoundMethodSubject::isInstanceInitializer).size());
+            })
+        .run(parameters.getRuntime(), Main.class)
+        .assertSuccessWithOutputLines("C", "D");
+  }
+
+  static class Main {
+
+    public static void main(String[] args) {
+      System.out.println(new A(new Object(), new C()).foo());
+      System.out.println(new B(new Object(), new D()).bar());
+    }
+  }
+
+  @NeverClassInline
+  static class A {
+
+    private final C c;
+
+    A(Object unused, C c) {
+      this.c = c;
+    }
+
+    @NeverInline
+    public String foo() {
+      return c.toString();
+    }
+  }
+
+  @NeverClassInline
+  static class B {
+
+    private final D d;
+
+    B(Object unused, D d) {
+      this.d = d;
+    }
+
+    @NeverInline
+    public String bar() {
+      return d.toString();
+    }
+  }
+
+  @NeverClassInline
+  @NoHorizontalClassMerging
+  static class C {
+
+    @Override
+    public String toString() {
+      return "C";
+    }
+  }
+
+  @NeverClassInline
+  @NoHorizontalClassMerging
+  static class D {
+
+    @Override
+    public String toString() {
+      return "D";
+    }
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/classmerging/horizontal/EquivalentConstructorsWithoutClassIdMergingTest.java b/src/test/java/com/android/tools/r8/classmerging/horizontal/EquivalentConstructorsWithoutClassIdMergingTest.java
new file mode 100644
index 0000000..a8b85e0
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/classmerging/horizontal/EquivalentConstructorsWithoutClassIdMergingTest.java
@@ -0,0 +1,120 @@
+// Copyright (c) 2021, 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.NeverClassInline;
+import com.android.tools.r8.NeverInline;
+import com.android.tools.r8.NoHorizontalClassMerging;
+import com.android.tools.r8.TestBase;
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.TestParametersCollection;
+import com.android.tools.r8.utils.codeinspector.ClassSubject;
+import com.android.tools.r8.utils.codeinspector.FoundMethodSubject;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+import org.junit.runners.Parameterized.Parameters;
+
+@RunWith(Parameterized.class)
+public class EquivalentConstructorsWithoutClassIdMergingTest extends TestBase {
+
+  private final TestParameters parameters;
+
+  @Parameters(name = "{0}")
+  public static TestParametersCollection params() {
+    return getTestParameters().withAllRuntimesAndApiLevels().build();
+  }
+
+  public EquivalentConstructorsWithoutClassIdMergingTest(TestParameters parameters) {
+    this.parameters = parameters;
+  }
+
+  @Test
+  public void test() throws Exception {
+    testForR8(parameters.getBackend())
+        .addInnerClasses(getClass())
+        .addKeepMainRule(Main.class)
+        .addHorizontallyMergedClassesInspector(
+            inspector ->
+                inspector.assertIsCompleteMergeGroup(A.class, B.class).assertNoOtherClassesMerged())
+        .enableInliningAnnotations()
+        .enableNeverClassInliningAnnotations()
+        .enableNoHorizontalClassMergingAnnotations()
+        .setMinApi(parameters.getApiLevel())
+        .compile()
+        .inspect(
+            inspector -> {
+              ClassSubject aClassSubject = inspector.clazz(A.class);
+              assertThat(aClassSubject, isPresent());
+              // TODO(b/189296638): Should be 1.
+              assertEquals(
+                  2, aClassSubject.allMethods(FoundMethodSubject::isInstanceInitializer).size());
+            })
+        .run(parameters.getRuntime(), Main.class)
+        .assertSuccessWithOutputLines("C", "D");
+  }
+
+  static class Main {
+
+    public static void main(String[] args) {
+      System.out.println(new A(new C()).foo());
+      System.out.println(new B(new D()).bar());
+    }
+  }
+
+  @NeverClassInline
+  static class A {
+
+    private final C c;
+
+    A(C c) {
+      this.c = c;
+    }
+
+    @NeverInline
+    public String foo() {
+      return c.toString();
+    }
+  }
+
+  @NeverClassInline
+  static class B {
+
+    private final D d;
+
+    B(D d) {
+      this.d = d;
+    }
+
+    @NeverInline
+    public String bar() {
+      return d.toString();
+    }
+  }
+
+  @NeverClassInline
+  @NoHorizontalClassMerging
+  static class C {
+
+    @Override
+    public String toString() {
+      return "C";
+    }
+  }
+
+  @NeverClassInline
+  @NoHorizontalClassMerging
+  static class D {
+
+    @Override
+    public String toString() {
+      return "D";
+    }
+  }
+}