Add test for modeling kotlin metadata annotations and arguments

Bug: 158124557
Change-Id: Id98e33df1171eedcfbd12c31bc4236bde6926189
diff --git a/src/main/java/com/android/tools/r8/kotlin/KotlinMetadataWriter.java b/src/main/java/com/android/tools/r8/kotlin/KotlinMetadataWriter.java
index c1eb1d2..1a51644 100644
--- a/src/main/java/com/android/tools/r8/kotlin/KotlinMetadataWriter.java
+++ b/src/main/java/com/android/tools/r8/kotlin/KotlinMetadataWriter.java
@@ -11,6 +11,7 @@
 import com.android.tools.r8.utils.Action;
 import com.android.tools.r8.utils.StringUtils;
 import java.io.PrintStream;
+import java.util.Collection;
 import java.util.Comparator;
 import java.util.List;
 import java.util.Map;
@@ -20,6 +21,7 @@
 import kotlinx.metadata.InconsistentKotlinMetadataException;
 import kotlinx.metadata.KmAnnotation;
 import kotlinx.metadata.KmAnnotationArgument;
+import kotlinx.metadata.KmAnnotationArgument.ArrayValue;
 import kotlinx.metadata.KmClass;
 import kotlinx.metadata.KmConstructor;
 import kotlinx.metadata.KmContract;
@@ -177,7 +179,7 @@
       String indent,
       String typeDescription,
       StringBuilder sb,
-      List<T> items,
+      Collection<T> items,
       BiConsumer<String, T> appendItem) {
     if (items.isEmpty()) {
       sb.append(typeDescription).append("[]");
@@ -768,13 +770,47 @@
               sb,
               nextIndent -> {
                 Map<String, KmAnnotationArgument<?>> arguments = kmAnnotation.getArguments();
-                for (String key : arguments.keySet()) {
-                  appendKeyValue(nextIndent, key, sb, arguments.get(key).toString());
-                }
+                appendKmList(
+                    nextIndent,
+                    "{ key: String, value: KmAnnotationArgument<?> }",
+                    sb,
+                    arguments.keySet(),
+                    (nextNextIndent, key) -> {
+                      appendKmSection(
+                          nextNextIndent,
+                          "",
+                          sb,
+                          nextNextNextIndent -> {
+                            appendKeyValue(
+                                nextNextNextIndent,
+                                key,
+                                sb,
+                                nextNextNextNextIndent -> {
+                                  appendKmArgument(nextNextNextIndent, sb, arguments.get(key));
+                                });
+                          });
+                    });
               });
         });
   }
 
+  private static void appendKmArgument(
+      String indent, StringBuilder sb, KmAnnotationArgument<?> annotationArgument) {
+    if (annotationArgument instanceof KmAnnotationArgument.ArrayValue) {
+      List<KmAnnotationArgument<?>> value = ((ArrayValue) annotationArgument).getValue();
+      appendKmList(
+          indent,
+          "ArrayValue",
+          sb,
+          value,
+          (newIndent, annoArg) -> {
+            appendKmArgument(newIndent, sb, annoArg);
+          });
+    } else {
+      sb.append(annotationArgument.toString());
+    }
+  }
+
   private static void appendKmVersionRequirement(
       String indent, StringBuilder sb, List<KmVersionRequirement> kmVersionRequirements) {
     appendKeyValue(
diff --git a/src/test/java/com/android/tools/r8/kotlin/metadata/MetadataRewriteAnnotationTest.java b/src/test/java/com/android/tools/r8/kotlin/metadata/MetadataRewriteAnnotationTest.java
new file mode 100644
index 0000000..6fae341
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/kotlin/metadata/MetadataRewriteAnnotationTest.java
@@ -0,0 +1,182 @@
+// Copyright (c) 2020, 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.kotlin.metadata;
+
+import static com.android.tools.r8.KotlinCompilerTool.KOTLINC;
+import static com.android.tools.r8.utils.codeinspector.Matchers.isNotRenamed;
+import static com.android.tools.r8.utils.codeinspector.Matchers.isPresent;
+import static com.android.tools.r8.utils.codeinspector.Matchers.isRenamed;
+import static org.hamcrest.CoreMatchers.containsString;
+import static org.hamcrest.MatcherAssert.assertThat;
+import static org.junit.Assert.assertEquals;
+
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.ToolHelper;
+import com.android.tools.r8.ToolHelper.KotlinTargetVersion;
+import com.android.tools.r8.shaking.ProguardKeepAttributes;
+import com.android.tools.r8.utils.DescriptorUtils;
+import com.android.tools.r8.utils.StringUtils;
+import com.android.tools.r8.utils.codeinspector.ClassSubject;
+import com.android.tools.r8.utils.codeinspector.CodeInspector;
+import com.android.tools.r8.utils.codeinspector.KmTypeAliasSubject;
+import java.nio.file.Path;
+import java.util.Collection;
+import java.util.HashMap;
+import java.util.Map;
+import kotlinx.metadata.KmAnnotation;
+import kotlinx.metadata.KmAnnotationArgument;
+import kotlinx.metadata.KmAnnotationArgument.ArrayValue;
+import org.junit.BeforeClass;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+
+@RunWith(Parameterized.class)
+public class MetadataRewriteAnnotationTest extends KotlinMetadataTestBase {
+
+  private final String EXPECTED_SMOKE =
+      StringUtils.lines(
+          "class com.android.tools.r8.kotlin.metadata.annotation_lib.Foo",
+          "class com.android.tools.r8.kotlin.metadata.annotation_lib.Foo",
+          "class com.android.tools.r8.kotlin.metadata.annotation_lib.Bar",
+          "class com.android.tools.r8.kotlin.metadata.annotation_lib.Foo",
+          "UP",
+          "class com.android.tools.r8.kotlin.metadata.annotation_lib.Foo",
+          "LEFT",
+          "class com.android.tools.r8.kotlin.metadata.annotation_lib.Foo",
+          "RIGHT",
+          "class com.android.tools.r8.kotlin.metadata.annotation_lib.Foo",
+          "DOWN",
+          "class com.android.tools.r8.kotlin.metadata.annotation_lib.Foo",
+          "UP");
+  private static final String PKG_LIB = PKG + ".annotation_lib";
+  private static final String PKG_APP = PKG + ".annotation_app";
+  private static final String FOO_FINAL_NAME = "a.b.c";
+
+  @Parameterized.Parameters(name = "{0} target: {1}")
+  public static Collection<Object[]> data() {
+    return buildParameters(
+        getTestParameters().withCfRuntimes().build(), KotlinTargetVersion.values());
+  }
+
+  public MetadataRewriteAnnotationTest(
+      TestParameters parameters, KotlinTargetVersion targetVersion) {
+    super(targetVersion);
+    this.parameters = parameters;
+  }
+
+  private static Map<KotlinTargetVersion, Path> libJars = new HashMap<>();
+  private final TestParameters parameters;
+
+  @BeforeClass
+  public static void createLibJar() throws Exception {
+    for (KotlinTargetVersion targetVersion : KotlinTargetVersion.values()) {
+      Path baseLibJar =
+          kotlinc(KOTLINC, targetVersion)
+              .addSourceFiles(
+                  getKotlinFileInTest(DescriptorUtils.getBinaryNameFromJavaType(PKG_LIB), "lib"))
+              .compile();
+      libJars.put(targetVersion, baseLibJar);
+    }
+  }
+
+  @Test
+  public void smokeTest() throws Exception {
+    Path libJar = libJars.get(targetVersion);
+    Path output =
+        kotlinc(parameters.getRuntime().asCf(), KOTLINC, targetVersion)
+            .addClasspathFiles(libJar)
+            .addSourceFiles(
+                getKotlinFileInTest(DescriptorUtils.getBinaryNameFromJavaType(PKG_APP), "main"))
+            .setOutputPath(temp.newFolder().toPath())
+            .compile();
+    testForJvm()
+        .addRunClasspathFiles(
+            ToolHelper.getKotlinStdlibJar(), ToolHelper.getKotlinReflectJar(), libJar)
+        .addClasspath(output)
+        .run(parameters.getRuntime(), PKG_APP + ".MainKt")
+        .assertSuccessWithOutput(EXPECTED_SMOKE);
+  }
+
+  @Test
+  public void testMetadataForLib() throws Exception {
+    String fooTypeName = PKG_LIB + ".Foo";
+    Path libJar =
+        testForR8(parameters.getBackend())
+            .addProgramFiles(libJars.get(targetVersion))
+            /// Keep the annotations
+            .addKeepClassAndMembersRules(PKG_LIB + ".AnnoWithClassAndEnum")
+            .addKeepClassAndMembersRules(PKG_LIB + ".AnnoWithClassArr")
+            // Keep Foo but rename to test arguments
+            .addKeepClassAndMembersRulesWithAllowObfuscation(fooTypeName)
+            .addApplyMapping(fooTypeName + " -> " + FOO_FINAL_NAME + ":")
+            // Keep Direction but rename the enum
+            .addKeepClassAndMembersRules(PKG_LIB + ".Direction")
+            // Keep Bar and Baz and Quux because we are directly reflecting on them
+            .addKeepClassAndMembersRules(PKG_LIB + ".Bar")
+            .addKeepClassAndMembersRules(PKG_LIB + ".Baz")
+            .addKeepClassAndMembersRules(PKG_LIB + ".Quux")
+            // Keep the static class for the type alias
+            .addKeepClassAndMembersRules(PKG_LIB + ".LibKt")
+            .addKeepRuntimeVisibleAnnotations()
+            .addKeepAttributes(
+                ProguardKeepAttributes.RUNTIME_VISIBLE_ANNOTATIONS,
+                ProguardKeepAttributes.RUNTIME_VISIBLE_PARAMETER_ANNOTATIONS,
+                ProguardKeepAttributes.RUNTIME_VISIBLE_TYPE_ANNOTATIONS)
+            .compile()
+            .inspect(this::inspect)
+            .writeToZip();
+    Path output =
+        kotlinc(parameters.getRuntime().asCf(), KOTLINC, targetVersion)
+            .addClasspathFiles(libJar)
+            .addSourceFiles(
+                getKotlinFileInTest(DescriptorUtils.getBinaryNameFromJavaType(PKG_APP), "main"))
+            .compile();
+    testForJvm()
+        .addRunClasspathFiles(
+            ToolHelper.getKotlinStdlibJar(), ToolHelper.getKotlinReflectJar(), libJar)
+        .addProgramFiles(output)
+        .run(parameters.getRuntime(), PKG_APP + ".MainKt")
+        // TODO(b/155053894): Fails because reflect cannot find the class.
+        .assertFailureWithErrorThatMatches(containsString("KotlinReflectionInternalError"));
+  }
+
+  private void inspect(CodeInspector inspector) {
+    // Assert that foo is renamed.
+    ClassSubject foo = inspector.clazz(PKG_LIB + ".Foo");
+    assertThat(foo, isRenamed());
+    assertEquals(FOO_FINAL_NAME, foo.getFinalName());
+    // Assert that bar exists and is not renamed.
+    ClassSubject bar = inspector.clazz(PKG_LIB + ".Bar");
+    assertThat(bar, isPresent());
+    assertThat(bar, isNotRenamed());
+    // Check that the annotation type on the type alias has been renamed
+    inspectTypeAliasAnnotation(inspector, foo, bar);
+  }
+
+  private void inspectTypeAliasAnnotation(
+      CodeInspector inspector, ClassSubject foo, ClassSubject bar) {
+    ClassSubject libKt = inspector.clazz(PKG_LIB + ".LibKt");
+    assertThat(libKt, isPresent());
+    assertThat(libKt.getKmPackage(), isPresent());
+    KmTypeAliasSubject qux = libKt.getKmPackage().kmTypeAliasWithUniqueName("Qux");
+    assertThat(qux, isPresent());
+    assertEquals(1, qux.annotations().size());
+    KmAnnotation annotation = qux.annotations().get(0);
+    assertEquals(
+        DescriptorUtils.getBinaryNameFromJavaType(PKG_LIB) + "/AnnoWithClassArr",
+        annotation.getClassName());
+    Map<String, KmAnnotationArgument<?>> arguments = annotation.getArguments();
+    assertEquals(1, arguments.size());
+    ArrayValue classes = (ArrayValue) arguments.get("classes");
+    // TODO(b/155053894): This should be renamed.
+    assertEquals(
+        "KClassValue(value=" + foo.getOriginalBinaryName() + ")",
+        classes.getValue().get(0).toString());
+    assertEquals(
+        "KClassValue(value=" + bar.getFinalBinaryName() + ")",
+        classes.getValue().get(1).toString());
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/kotlin/metadata/MetadataRewriteCrossinlineAnonFunctionTest.java b/src/test/java/com/android/tools/r8/kotlin/metadata/MetadataRewriteCrossinlineAnonFunctionTest.java
index c6dd4fd..1ebdddb 100644
--- a/src/test/java/com/android/tools/r8/kotlin/metadata/MetadataRewriteCrossinlineAnonFunctionTest.java
+++ b/src/test/java/com/android/tools/r8/kotlin/metadata/MetadataRewriteCrossinlineAnonFunctionTest.java
@@ -76,7 +76,6 @@
     Path libJar =
         testForR8(parameters.getBackend())
             .addProgramFiles(libJars.get(targetVersion))
-            // Allow renaming A to ensure that we rename in the flexible upper bound type.
             .addKeepAllClassesRule()
             .addKeepAllAttributes()
             .compile()
diff --git a/src/test/java/com/android/tools/r8/kotlin/metadata/annotation_app/main.kt b/src/test/java/com/android/tools/r8/kotlin/metadata/annotation_app/main.kt
new file mode 100644
index 0000000..34d39b8
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/kotlin/metadata/annotation_app/main.kt
@@ -0,0 +1,37 @@
+// Copyright (c) 2020, 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.kotlin.metadata.annotation_app
+
+import com.android.tools.r8.kotlin.metadata.annotation_lib.AnnoWithClassAndEnum
+import com.android.tools.r8.kotlin.metadata.annotation_lib.AnnoWithClassArr
+import com.android.tools.r8.kotlin.metadata.annotation_lib.Bar
+import com.android.tools.r8.kotlin.metadata.annotation_lib.Baz
+import com.android.tools.r8.kotlin.metadata.annotation_lib.Quux
+import kotlin.reflect.full.primaryConstructor
+import kotlin.reflect.full.valueParameters
+
+fun main() {
+  Bar::class.primaryConstructor?.annotations?.get(0)?.printAnnoWithClassArr()
+  Baz::class.annotations.get(0).printAnnoWithClassArr()
+  Baz::class.annotations.get(1).printAnnoWithClassAndEnum()
+  Baz::prop.annotations.get(0).printAnnoWithClassAndEnum()
+  Baz::baz.annotations.get(0).printAnnoWithClassAndEnum()
+  Baz::baz.valueParameters.get(0).annotations.get(0).printAnnoWithClassAndEnum()
+  // We cannot reflect on annotations on typealiases:
+  // https://youtrack.jetbrains.com/issue/KT-21489
+  Quux::methodWithTypeAnnotations
+      .returnType.arguments.get(0).type?.annotations?.get(0)?.printAnnoWithClassAndEnum()
+}
+
+fun Annotation.printAnnoWithClassArr() {
+  val annoWithClassArr = this as AnnoWithClassArr
+  annoWithClassArr.classes.forEach { println(it) }
+}
+
+fun Annotation.printAnnoWithClassAndEnum() {
+  val annoWithClassAndEnum = this as AnnoWithClassAndEnum
+  println(annoWithClassAndEnum.clazz)
+  println(annoWithClassAndEnum.direction)
+}
\ No newline at end of file
diff --git a/src/test/java/com/android/tools/r8/kotlin/metadata/annotation_lib/lib.kt b/src/test/java/com/android/tools/r8/kotlin/metadata/annotation_lib/lib.kt
new file mode 100644
index 0000000..38a574c
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/kotlin/metadata/annotation_lib/lib.kt
@@ -0,0 +1,47 @@
+// Copyright (c) 2020, 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.kotlin.metadata.annotation_lib
+
+import kotlin.reflect.KClass
+
+enum class Direction {
+  UP, RIGHT, DOWN, LEFT
+}
+
+@Target(AnnotationTarget.CLASS, AnnotationTarget.FUNCTION,
+        AnnotationTarget.VALUE_PARAMETER,
+        AnnotationTarget.PROPERTY,
+        AnnotationTarget.TYPE)
+@Retention(AnnotationRetention.RUNTIME)
+annotation class AnnoWithClassAndEnum(val clazz : KClass<*>, val direction : Direction)
+
+@Target(AnnotationTarget.CLASS, AnnotationTarget.CONSTRUCTOR, AnnotationTarget.TYPEALIAS)
+@Retention(AnnotationRetention.RUNTIME)
+annotation class AnnoWithClassArr(val classes : Array<KClass<*>>)
+
+class Foo
+
+class Bar @AnnoWithClassArr([Foo::class]) constructor()
+
+@AnnoWithClassArr([Foo::class, Bar::class])
+@AnnoWithClassAndEnum(Foo::class, Direction.UP) class Baz {
+
+  @AnnoWithClassAndEnum(Foo::class, Direction.LEFT) val prop : Int = 0
+
+  @AnnoWithClassAndEnum(Foo::class, Direction.RIGHT) fun baz(@AnnoWithClassAndEnum(Foo::class, Direction.DOWN) foo: Int): Int {
+    return 1
+  }
+}
+
+@AnnoWithClassArr([Foo::class, Bar::class])
+typealias Qux = Foo
+
+
+class Quux {
+
+  fun methodWithTypeAnnotations() : Array<@AnnoWithClassAndEnum(Foo::class, Direction.UP) Int> {
+    return arrayOf(1)
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/utils/codeinspector/AbsentClassSubject.java b/src/test/java/com/android/tools/r8/utils/codeinspector/AbsentClassSubject.java
index 1491ce0..81b988e 100644
--- a/src/test/java/com/android/tools/r8/utils/codeinspector/AbsentClassSubject.java
+++ b/src/test/java/com/android/tools/r8/utils/codeinspector/AbsentClassSubject.java
@@ -94,6 +94,11 @@
   }
 
   @Override
+  public String getOriginalBinaryName() {
+    return null;
+  }
+
+  @Override
   public String getFinalName() {
     return null;
   }
diff --git a/src/test/java/com/android/tools/r8/utils/codeinspector/AbsentKmTypeAliasSubject.java b/src/test/java/com/android/tools/r8/utils/codeinspector/AbsentKmTypeAliasSubject.java
index 78ac796..622db02 100644
--- a/src/test/java/com/android/tools/r8/utils/codeinspector/AbsentKmTypeAliasSubject.java
+++ b/src/test/java/com/android/tools/r8/utils/codeinspector/AbsentKmTypeAliasSubject.java
@@ -5,7 +5,9 @@
 package com.android.tools.r8.utils.codeinspector;
 
 import com.android.tools.r8.errors.Unreachable;
+import com.google.common.collect.ImmutableList;
 import java.util.List;
+import kotlinx.metadata.KmAnnotation;
 
 public class AbsentKmTypeAliasSubject extends KmTypeAliasSubject {
 
@@ -48,4 +50,9 @@
   public KmTypeSubject underlyingType() {
     return null;
   }
+
+  @Override
+  public List<KmAnnotation> annotations() {
+    return ImmutableList.of();
+  }
 }
diff --git a/src/test/java/com/android/tools/r8/utils/codeinspector/ClassSubject.java b/src/test/java/com/android/tools/r8/utils/codeinspector/ClassSubject.java
index bede2a0..97a3b29 100644
--- a/src/test/java/com/android/tools/r8/utils/codeinspector/ClassSubject.java
+++ b/src/test/java/com/android/tools/r8/utils/codeinspector/ClassSubject.java
@@ -165,6 +165,8 @@
 
   public abstract String getOriginalDescriptor();
 
+  public abstract String getOriginalBinaryName();
+
   public abstract String getFinalName();
 
   public abstract String getFinalDescriptor();
diff --git a/src/test/java/com/android/tools/r8/utils/codeinspector/FoundClassSubject.java b/src/test/java/com/android/tools/r8/utils/codeinspector/FoundClassSubject.java
index 6774c2a..2ce3024 100644
--- a/src/test/java/com/android/tools/r8/utils/codeinspector/FoundClassSubject.java
+++ b/src/test/java/com/android/tools/r8/utils/codeinspector/FoundClassSubject.java
@@ -263,6 +263,11 @@
     }
   }
 
+  @Override
+  public String getOriginalBinaryName() {
+    return DescriptorUtils.getBinaryNameFromDescriptor(getOriginalDescriptor());
+  }
+
   public DexType getOriginalDexType(DexItemFactory dexItemFactory) {
     return dexItemFactory.createType(getOriginalDescriptor());
   }
diff --git a/src/test/java/com/android/tools/r8/utils/codeinspector/FoundKmTypeAliasSubject.java b/src/test/java/com/android/tools/r8/utils/codeinspector/FoundKmTypeAliasSubject.java
index a07125c..396ef98 100644
--- a/src/test/java/com/android/tools/r8/utils/codeinspector/FoundKmTypeAliasSubject.java
+++ b/src/test/java/com/android/tools/r8/utils/codeinspector/FoundKmTypeAliasSubject.java
@@ -9,6 +9,7 @@
 
 import java.util.List;
 import java.util.stream.Collectors;
+import kotlinx.metadata.KmAnnotation;
 import kotlinx.metadata.KmTypeAlias;
 
 public class FoundKmTypeAliasSubject extends KmTypeAliasSubject {
@@ -62,4 +63,9 @@
   public KmTypeSubject underlyingType() {
     return new KmTypeSubject(codeInspector, kmTypeAlias.underlyingType);
   }
+
+  @Override
+  public List<KmAnnotation> annotations() {
+    return kmTypeAlias.getAnnotations();
+  }
 }
diff --git a/src/test/java/com/android/tools/r8/utils/codeinspector/KmTypeAliasSubject.java b/src/test/java/com/android/tools/r8/utils/codeinspector/KmTypeAliasSubject.java
index f87d368..8ff3c90 100644
--- a/src/test/java/com/android/tools/r8/utils/codeinspector/KmTypeAliasSubject.java
+++ b/src/test/java/com/android/tools/r8/utils/codeinspector/KmTypeAliasSubject.java
@@ -5,6 +5,7 @@
 package com.android.tools.r8.utils.codeinspector;
 
 import java.util.List;
+import kotlinx.metadata.KmAnnotation;
 
 public abstract class KmTypeAliasSubject extends Subject {
 
@@ -17,4 +18,6 @@
   public abstract KmTypeSubject expandedType();
 
   public abstract KmTypeSubject underlyingType();
+
+  public abstract List<KmAnnotation> annotations();
 }