[keepanno] Improve handling of types provided by name

Map Kotlin primitive types to corresponding Java primitive types.

No support for Kotlin unsigned types for now. See b/444622187

Bug: b/392865072
Bug: b/444622187

Change-Id: I2557a7787444fe5bfe0223f1f76947dcd86e3a04
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 7d4245c..3352549 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
@@ -69,6 +69,7 @@
 import com.android.tools.r8.keepanno.ast.ParsingContext.FieldParsingContext;
 import com.android.tools.r8.keepanno.ast.ParsingContext.MethodParsingContext;
 import com.android.tools.r8.keepanno.ast.ParsingContext.PropertyParsingContext;
+import com.android.tools.r8.keepanno.utils.DescriptorUtils;
 import com.google.common.collect.ImmutableList;
 import java.lang.annotation.RetentionPolicy;
 import java.util.ArrayList;
@@ -127,6 +128,29 @@
     return false;
   }
 
+  private static String kotlinTypeToJavaType(String kotlinType) {
+    switch (kotlinType) {
+      case "Boolean":
+        return "boolean";
+      case "Byte":
+        return "byte";
+      case "Short":
+        return "short";
+      case "Char":
+        return "char";
+      case "Int":
+        return "int";
+      case "Long":
+        return "long";
+      case "Float":
+        return "float";
+      case "Double":
+        return "double";
+      default:
+        return kotlinType;
+    }
+  }
+
   public static List<KeepDeclaration> readKeepEdges(byte[] classFileBytes) {
     return internalReadKeepEdges(classFileBytes, true);
   }
@@ -1489,7 +1513,9 @@
     public void visit(String name, Object value) {
       assert name == null;
       if (value instanceof String) {
-        builder.addParameterTypePattern(KeepTypePattern.fromDescriptor("L" + value + ";"));
+        builder.addParameterTypePattern(
+            KeepTypePattern.fromDescriptor(
+                DescriptorUtils.javaTypeToDescriptor(kotlinTypeToJavaType((String) value))));
       } else {
         super.visit(name, value);
       }
@@ -1868,11 +1894,17 @@
         return;
       }
       if (name.equals(UsesReflectionToAccessMethod.returnTypeName) && value instanceof String) {
-        returnType =
-            KeepMethodReturnTypePattern.fromType(
-                KeepTypePattern.fromClass(
-                    KeepClassPattern.fromName(
-                        KeepQualifiedClassNamePattern.exact((String) value))));
+        if (value.equals("void") || value.equals("Unit")) {
+          returnType = KeepMethodReturnTypePattern.voidType();
+        } else {
+          returnType =
+              KeepMethodReturnTypePattern.fromType(
+                  KeepTypePattern.fromClass(
+                      KeepClassPattern.fromName(
+                          KeepQualifiedClassNamePattern.exactFromDescriptor(
+                              DescriptorUtils.javaTypeToDescriptor(
+                                  kotlinTypeToJavaType((String) value))))));
+        }
         return;
       }
       super.visit(name, value);
diff --git a/src/keepanno/java/com/android/tools/r8/keepanno/keeprules/RulePrintingUtils.java b/src/keepanno/java/com/android/tools/r8/keepanno/keeprules/RulePrintingUtils.java
index 9289894..51a508c 100644
--- a/src/keepanno/java/com/android/tools/r8/keepanno/keeprules/RulePrintingUtils.java
+++ b/src/keepanno/java/com/android/tools/r8/keepanno/keeprules/RulePrintingUtils.java
@@ -390,6 +390,8 @@
         return "byte";
       case 'S':
         return "short";
+      case 'C':
+        return "char";
       case 'I':
         return "int";
       case 'J':
diff --git a/src/keepanno/java/com/android/tools/r8/keepanno/utils/DescriptorUtils.java b/src/keepanno/java/com/android/tools/r8/keepanno/utils/DescriptorUtils.java
index 6694cbc..c928efa 100644
--- a/src/keepanno/java/com/android/tools/r8/keepanno/utils/DescriptorUtils.java
+++ b/src/keepanno/java/com/android/tools/r8/keepanno/utils/DescriptorUtils.java
@@ -3,7 +3,28 @@
 // BSD-style license that can be found in the LICENSE file.
 package com.android.tools.r8.keepanno.utils;
 
+import com.google.common.collect.ImmutableMap;
+import java.util.Map;
+
+// TODO(b/444623706): Avoid the duplication and multiple DescriptorUtils classes by making a shared
+// library for R8 and keepanno.
 public class DescriptorUtils {
+  public static final char DESCRIPTOR_PACKAGE_SEPARATOR = '/';
+  public static final char JAVA_PACKAGE_SEPARATOR = '.';
+
+  private static final Map<String, String> typeNameToLetterMap =
+      ImmutableMap.<String, String>builder()
+          .put("void", "V")
+          .put("boolean", "Z")
+          .put("byte", "B")
+          .put("short", "S")
+          .put("char", "C")
+          .put("int", "I")
+          .put("long", "J")
+          .put("float", "F")
+          .put("double", "D")
+          .build();
+
   public static boolean isValidClassDescriptor(String string) {
     if (string.length() < 3
         || string.charAt(0) != 'L'
@@ -22,4 +43,37 @@
     }
     return true;
   }
+
+  private static String internalToDescriptor(
+      String typeName, boolean shorty, boolean ignorePrimitives) {
+    String descriptor = null;
+    if (!ignorePrimitives) {
+      descriptor = typeNameToLetterMap.get(typeName);
+    }
+    if (descriptor != null) {
+      return descriptor;
+    }
+    // Must be some array or object type.
+    if (shorty) {
+      return "L";
+    }
+    if (typeName.endsWith("[]")) {
+      return "["
+          + internalToDescriptor(
+              typeName.substring(0, typeName.length() - 2), shorty, ignorePrimitives);
+    }
+    // Must be an object type.
+    return "L" + typeName.replace(JAVA_PACKAGE_SEPARATOR, DESCRIPTOR_PACKAGE_SEPARATOR) + ";";
+  }
+
+  /**
+   * Convert a Java type name to a descriptor string.
+   *
+   * @param typeName the java type name
+   * @return the descriptor string
+   */
+  public static String javaTypeToDescriptor(String typeName) {
+    assert typeName.indexOf(DESCRIPTOR_PACKAGE_SEPARATOR) == -1;
+    return internalToDescriptor(typeName, false, 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
index 87134e3..6b6f091 100644
--- a/src/test/java/com/android/tools/r8/keepanno/androidx/KeepUsesReflectionForInstantiationMultipleConstructorsTest.java
+++ b/src/test/java/com/android/tools/r8/keepanno/androidx/KeepUsesReflectionForInstantiationMultipleConstructorsTest.java
@@ -8,6 +8,8 @@
 
 import androidx.annotation.keep.UsesReflectionToConstruct;
 import com.android.tools.r8.ToolHelper.DexVm.Version;
+import com.android.tools.r8.references.Reference;
+import com.android.tools.r8.transformers.ClassFileTransformer.MethodPredicate;
 import com.android.tools.r8.utils.StringUtils;
 import com.google.common.collect.ImmutableList;
 import java.io.IOException;
@@ -182,6 +184,45 @@
         expectedRulesJava(IntAndLongConstructorsClassName.class));
   }
 
+  @Test
+  public void testPrimitiveTypesAsTypeName() throws Exception {
+    testExtractedRules(
+        ImmutableList.of(
+            setAnnotationOnMethod(
+                ClassWithAnnotation.class,
+                MethodPredicate.onName("foo"),
+                builder ->
+                    builder
+                        .setAnnotationClass(
+                            Reference.classFromBinaryName(
+                                Reference.classFromClass(UsesReflectionToConstruct.class)
+                                        .getBinaryName()
+                                    + "$Container"))
+                        .buildArray(
+                            "value",
+                            builder1 ->
+                                builder1
+                                    .setAnnotationField(
+                                        null,
+                                        builder2 ->
+                                            builder2
+                                                .setAnnotationClass(
+                                                    Reference.classFromClass(
+                                                        UsesReflectionToConstruct.class))
+                                                .setField("classConstant", KeptClass.class)
+                                                .setArray("parameterTypeNames", "Int"))
+                                    .setAnnotationField(
+                                        null,
+                                        builder3 ->
+                                            builder3
+                                                .setAnnotationClass(
+                                                    Reference.classFromClass(
+                                                        UsesReflectionToConstruct.class))
+                                                .setField("classConstant", KeptClass.class)
+                                                .setArray("parameterTypeNames", "Long"))))),
+        expectedRulesJava(ClassWithAnnotation.class));
+  }
+
   static class IntAndLongConstructorsClassName {
 
     @UsesReflectionToConstruct(
@@ -220,6 +261,21 @@
             "com.android.tools.r8.keepanno.androidx.kt.IntAndLongArgsConstructorsClassName"));
   }
 
+  // Test class without annotation to be used by multiple tests inserting annotations using a
+  // transformer.
+  static class ClassWithAnnotation {
+
+    public void foo(Class<KeptClass> clazz) throws Exception {
+      if (clazz != null) {
+        System.out.println(clazz.getDeclaredMethods().length);
+      }
+    }
+
+    public static void main(String[] args) throws Exception {
+      new ClassWithAnnotation().foo(System.nanoTime() > 0 ? KeptClass.class : null);
+    }
+  }
+
   static class KeptClass {
     KeptClass() {
       System.out.println("<init>()");
diff --git a/src/test/java/com/android/tools/r8/keepanno/androidx/KeepUsesReflectionToAccessMethodTest.java b/src/test/java/com/android/tools/r8/keepanno/androidx/KeepUsesReflectionToAccessMethodTest.java
index 8755cca..0b6e382 100644
--- a/src/test/java/com/android/tools/r8/keepanno/androidx/KeepUsesReflectionToAccessMethodTest.java
+++ b/src/test/java/com/android/tools/r8/keepanno/androidx/KeepUsesReflectionToAccessMethodTest.java
@@ -239,6 +239,45 @@
   }
 
   @Test
+  public void testPrimitiveTypesAsTypeName() throws Exception {
+    testExtractedRules(
+        ImmutableList.of(
+            setAnnotationOnMethod(
+                ClassWithAnnotation.class,
+                MethodPredicate.onName("foo"),
+                builder ->
+                    builder
+                        .setAnnotationClass(
+                            Reference.classFromClass(UsesReflectionToAccessMethod.class))
+                        .setField("classConstant", KeptClass.class)
+                        .setField("methodName", "m")
+                        .setArray(
+                            "parameterTypeNames",
+                            "boolean",
+                            "byte",
+                            "short",
+                            "int",
+                            "long",
+                            "float",
+                            "double",
+                            "char",
+                            "Boolean",
+                            "Byte",
+                            "Short",
+                            "Int",
+                            "Long",
+                            "Float",
+                            "Double",
+                            "Char")
+                        .setField("returnTypeName", "Unit"))),
+        getExpectedRulesJava(
+            ClassWithAnnotation.class,
+            "{ void m(boolean, byte, short, int, long, float, double, char, boolean, byte, short,"
+                + " int, long, float, double, char); }",
+            "{ void m$default(...); }"));
+  }
+
+  @Test
   public void testAnyReturnTypeAndAnyParametersKotlin() throws Exception {
     testExtractedRulesAndRunKotlin(
         compilationResults,