Extend testing of keep annotation @UsesReflectionToConstruct

Test for using repeated annotations to keep multiple constructors.

Bug: b/392865072
Change-Id: I791d80e4a16d3f2f96ff00f019d5bcdbe25f6e36
diff --git a/src/keepanno/java/com/android/tools/r8/keepanno/asm/KeepEdgeReader.java b/src/keepanno/java/com/android/tools/r8/keepanno/asm/KeepEdgeReader.java
index d251880..48b6260 100644
--- a/src/keepanno/java/com/android/tools/r8/keepanno/asm/KeepEdgeReader.java
+++ b/src/keepanno/java/com/android/tools/r8/keepanno/asm/KeepEdgeReader.java
@@ -109,7 +109,9 @@
         || AnnotationConstants.UsedByReflection.isDescriptor(descriptor)
         || AnnotationConstants.UsedByNative.isDescriptor(descriptor)
         || AnnotationConstants.CheckRemoved.isDescriptor(descriptor)
-        || AnnotationConstants.CheckOptimizedOut.isDescriptor(descriptor)) {
+        || AnnotationConstants.CheckOptimizedOut.isDescriptor(descriptor)
+        || AnnotationConstants.UsesReflectionToConstruct.isKotlinRepeatableContainerDescriptor(
+            descriptor)) {
       return true;
     }
     return false;
diff --git a/src/test/java/com/android/tools/r8/keepanno/androidx/KeepUsesReflectionForInstantiationMultipleConstructorsTest.java b/src/test/java/com/android/tools/r8/keepanno/androidx/KeepUsesReflectionForInstantiationMultipleConstructorsTest.java
new file mode 100644
index 0000000..b877504
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/keepanno/androidx/KeepUsesReflectionForInstantiationMultipleConstructorsTest.java
@@ -0,0 +1,210 @@
+// Copyright (c) 2025, 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.androidx;
+
+import static com.android.tools.r8.ToolHelper.getFilesInTestFolderRelativeToClass;
+import static org.junit.Assert.assertEquals;
+
+import androidx.annotation.keep.UsesReflectionToConstruct;
+import com.android.tools.r8.utils.StringUtils;
+import com.google.common.collect.ImmutableList;
+import java.io.IOException;
+import java.nio.file.Path;
+import java.util.Collection;
+import org.junit.BeforeClass;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+
+@RunWith(Parameterized.class)
+public class KeepUsesReflectionForInstantiationMultipleConstructorsTest
+    extends KeepAnnoTestExtractedRulesBase {
+
+  // String constant to be referenced from annotations.
+  static final String classNameOfKeptClass =
+      "com.android.tools.r8.keepanno.androidx.KeepUsesReflectionForInstantiationMultipleConstructorsTest$KeptClass";
+
+  @Parameterized.Parameters(name = "{0}, {1}")
+  public static Collection<Object[]> data() {
+    assertEquals(KeptClass.class.getTypeName(), classNameOfKeptClass);
+    return buildParameters(
+        createParameters(getTestParameters().withDefaultRuntimes().withMaximumApiLevel().build()),
+        getKotlinTestParameters().withLatestCompiler().build());
+  }
+
+  @Override
+  protected String getExpectedOutputForJava() {
+    return StringUtils.lines("<init>(int)", "<init>(long)");
+  }
+
+  @Override
+  protected String getExpectedOutputForKotlin() {
+    // Kotlin secondary constructors has to delegate to the primary constructor.
+    return StringUtils.lines(
+        "fun `<init>`(kotlin.Int): com.android.tools.r8.keepanno.androidx.kt.KeptClass",
+        "<init>()",
+        "<init>(Int)",
+        "fun `<init>`(kotlin.Long): com.android.tools.r8.keepanno.androidx.kt.KeptClass",
+        "<init>()",
+        "<init>(Long)");
+  }
+
+  private static Collection<Path> getKotlinSources() {
+    try {
+      return getFilesInTestFolderRelativeToClass(
+          KeepUsesReflectionForInstantiationMultipleConstructorsTest.class,
+          "kt",
+          "IntAndLongArgsConstructors.kt",
+          "KeptClass.kt");
+    } catch (IOException e) {
+      throw new RuntimeException(e);
+    }
+  }
+
+  private static Collection<Path> getKotlinSourcesClassName() {
+    try {
+      return getFilesInTestFolderRelativeToClass(
+          KeepUsesReflectionForInstantiationMultipleConstructorsTest.class,
+          "kt",
+          "IntAndLongArgsConstructorsClassName.kt",
+          "KeptClass.kt");
+    } catch (IOException e) {
+      throw new RuntimeException(e);
+    }
+  }
+
+  @BeforeClass
+  public static void beforeClass() throws Exception {
+    compilationResults = getCompileMemoizerWithKeepAnnoLib(getKotlinSources());
+    compilationResultsClassName = getCompileMemoizerWithKeepAnnoLib(getKotlinSourcesClassName());
+  }
+
+  private ExpectedRules expectedRulesJava(Class<?> conditionClass) {
+    return ExpectedRules.builder()
+        .add(
+            ExpectedRule.builder()
+                .setConditionClass(conditionClass)
+                .setConditionMembers("{ void foo(java.lang.Class); }")
+                .setConsequentClass(KeptClass.class)
+                .setConsequentMembers("{ void <init>(int); }")
+                .build())
+        .add(
+            ExpectedRule.builder()
+                .setConditionClass(conditionClass)
+                .setConditionMembers("{ void foo(java.lang.Class); }")
+                .setConsequentClass(KeptClass.class)
+                .setConsequentMembers("{ void <init>(long); }")
+                .build())
+        .build();
+  }
+
+  private ExpectedRules expectedRulesKotlin(String conditionClass) {
+    return ExpectedRules.builder()
+        .add(
+            ExpectedRule.builder()
+                .setConditionClass(conditionClass)
+                .setConditionMembers("{ void foo(kotlin.reflect.KClass); }")
+                .setConsequentClass("com.android.tools.r8.keepanno.androidx.kt.KeptClass")
+                .setConsequentMembers("{ void <init>(int); }")
+                .build())
+        .add(
+            ExpectedRule.builder()
+                .setConditionClass(conditionClass)
+                .setConditionMembers("{ void foo(kotlin.reflect.KClass); }")
+                .setConsequentClass("com.android.tools.r8.keepanno.androidx.kt.KeptClass")
+                .setConsequentMembers("{ void <init>(long); }")
+                .build())
+        .build();
+  }
+
+  @Test
+  public void testIntAndLongArgsConstructors() throws Exception {
+    runTestExtractedRulesJava(
+        ImmutableList.of(IntAndLongArgsConstructors.class, KeptClass.class),
+        expectedRulesJava(IntAndLongArgsConstructors.class));
+  }
+
+  static class IntAndLongArgsConstructors {
+
+    @UsesReflectionToConstruct(
+        classConstant = KeptClass.class,
+        params = {int.class})
+    @UsesReflectionToConstruct(
+        classConstant = KeptClass.class,
+        params = {long.class})
+    public void foo(Class<KeptClass> clazz) throws Exception {
+      if (clazz != null) {
+        clazz.getDeclaredConstructor(int.class).newInstance(1);
+        clazz.getDeclaredConstructor(long.class).newInstance(2L);
+      }
+    }
+
+    public static void main(String[] args) throws Exception {
+      new IntAndLongArgsConstructors().foo(System.nanoTime() > 0 ? KeptClass.class : null);
+    }
+  }
+
+  @Test
+  public void testIntLongArgsConstructorsClassNames() throws Exception {
+    runTestExtractedRulesJava(
+        ImmutableList.of(IntAndLongConstructorsClassName.class, KeptClass.class),
+        expectedRulesJava(IntAndLongConstructorsClassName.class));
+  }
+
+  static class IntAndLongConstructorsClassName {
+
+    @UsesReflectionToConstruct(
+        className = classNameOfKeptClass,
+        params = {int.class})
+    @UsesReflectionToConstruct(
+        className = classNameOfKeptClass,
+        params = {long.class})
+    public void foo(Class<KeptClass> clazz) throws Exception {
+      if (clazz != null) {
+        clazz.getDeclaredConstructor(int.class).newInstance(1);
+        clazz.getDeclaredConstructor(long.class).newInstance(2L);
+      }
+    }
+
+    public static void main(String[] args) throws Exception {
+      new IntAndLongConstructorsClassName().foo(System.nanoTime() > 0 ? KeptClass.class : null);
+    }
+  }
+
+  @Test
+  public void testIntLongArgsConstructorsKotlin() throws Exception {
+    runTestExtractedRulesKotlin(
+        compilationResults,
+        "com.android.tools.r8.keepanno.androidx.kt.IntAndLongArgsConstructorsKt",
+        expectedRulesKotlin(
+            "com.android.tools.r8.keepanno.androidx.kt.IntAndLongArgsConstructors"));
+  }
+
+  @Test
+  public void testIntLongArgsConstructorsKotlinClassName() throws Exception {
+    runTestExtractedRulesKotlin(
+        compilationResultsClassName,
+        "com.android.tools.r8.keepanno.androidx.kt.IntAndLongArgsConstructorsClassNameKt",
+        expectedRulesKotlin(
+            "com.android.tools.r8.keepanno.androidx.kt.IntAndLongArgsConstructorsClassName"));
+  }
+
+  static class KeptClass {
+    KeptClass() {
+      System.out.println("<init>()");
+    }
+
+    KeptClass(int i) {
+      System.out.println("<init>(int)");
+    }
+
+    KeptClass(long j) {
+      System.out.println("<init>(long)");
+    }
+
+    KeptClass(String s) {
+      System.out.println("<init>(String)");
+    }
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/keepanno/androidx/kt/IntAndLongArgsConstructors.kt b/src/test/java/com/android/tools/r8/keepanno/androidx/kt/IntAndLongArgsConstructors.kt
new file mode 100644
index 0000000..557e89e
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/keepanno/androidx/kt/IntAndLongArgsConstructors.kt
@@ -0,0 +1,32 @@
+// Copyright (c) 2025, 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.androidx.kt
+
+import androidx.annotation.keep.UsesReflectionToConstruct
+import kotlin.reflect.KClass
+import kotlin.reflect.full.createType
+
+class IntAndLongArgsConstructors {
+
+  @UsesReflectionToConstruct(classConstant = KeptClass::class, params = [Int::class])
+  @UsesReflectionToConstruct(classConstant = KeptClass::class, params = [Long::class])
+  fun foo(clazz: KClass<KeptClass>?) {
+    val intConstructor =
+      clazz?.constructors?.first {
+        it.parameters.size == 1 && it.parameters.first().type == Int::class.createType()
+      }
+    println(intConstructor)
+    intConstructor?.call(1)
+    val longConstructor =
+      clazz?.constructors?.first {
+        it.parameters.size == 1 && it.parameters.first().type == Long::class.createType()
+      }
+    println(longConstructor)
+    longConstructor?.call(2L)
+  }
+}
+
+fun main() {
+  IntAndLongArgsConstructors().foo(if (System.nanoTime() > 0) KeptClass::class else null)
+}
diff --git a/src/test/java/com/android/tools/r8/keepanno/androidx/kt/IntAndLongArgsConstructorsClassName.kt b/src/test/java/com/android/tools/r8/keepanno/androidx/kt/IntAndLongArgsConstructorsClassName.kt
new file mode 100644
index 0000000..143ca75
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/keepanno/androidx/kt/IntAndLongArgsConstructorsClassName.kt
@@ -0,0 +1,38 @@
+// Copyright (c) 2025, 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.androidx.kt
+
+import androidx.annotation.keep.UsesReflectionToConstruct
+import kotlin.reflect.KClass
+import kotlin.reflect.full.createType
+
+class IntAndLongArgsConstructorsClassName {
+
+  @UsesReflectionToConstruct(
+    className = "com.android.tools.r8.keepanno.androidx.kt.KeptClass",
+    params = [Int::class],
+  )
+  @UsesReflectionToConstruct(
+    className = "com.android.tools.r8.keepanno.androidx.kt.KeptClass",
+    params = [Long::class],
+  )
+  fun foo(clazz: KClass<KeptClass>?) {
+    val intConstructor =
+      clazz?.constructors?.first {
+        it.parameters.size == 1 && it.parameters.first().type == Int::class.createType()
+      }
+    println(intConstructor)
+    intConstructor?.call(1)
+    val longConstructor =
+      clazz?.constructors?.first {
+        it.parameters.size == 1 && it.parameters.first().type == Long::class.createType()
+      }
+    println(longConstructor)
+    longConstructor?.call(2L)
+  }
+}
+
+fun main() {
+  IntAndLongArgsConstructorsClassName().foo(if (System.nanoTime() > 0) KeptClass::class else null)
+}
diff --git a/src/test/java/com/android/tools/r8/keepanno/androidx/kt/KeptClass.kt b/src/test/java/com/android/tools/r8/keepanno/androidx/kt/KeptClass.kt
index 131f961..89e2d02 100644
--- a/src/test/java/com/android/tools/r8/keepanno/androidx/kt/KeptClass.kt
+++ b/src/test/java/com/android/tools/r8/keepanno/androidx/kt/KeptClass.kt
@@ -15,4 +15,8 @@
   constructor(l: Long) : this() {
     println("<init>(Long)")
   }
+
+  constructor(s: String) : this() {
+    println("<init>(String)")
+  }
 }