Add test for type annotations on definitions and in signatures

Bug: b/271543766
Change-Id: Ia6c995fbba76bc5c4621ad2e1342d164e2d23f22
diff --git a/src/test/java/com/android/tools/r8/TestShrinkerBuilder.java b/src/test/java/com/android/tools/r8/TestShrinkerBuilder.java
index 1523915..704ce16 100644
--- a/src/test/java/com/android/tools/r8/TestShrinkerBuilder.java
+++ b/src/test/java/com/android/tools/r8/TestShrinkerBuilder.java
@@ -438,6 +438,14 @@
     return addKeepAttributes(ProguardKeepAttributes.RUNTIME_VISIBLE_PARAMETER_ANNOTATIONS);
   }
 
+  public T addKeepRuntimeVisibleTypeAnnotations() {
+    return addKeepAttributes(ProguardKeepAttributes.RUNTIME_VISIBLE_TYPE_ANNOTATIONS);
+  }
+
+  public T addKeepRuntimeInvisibleTypeAnnotations() {
+    return addKeepAttributes(ProguardKeepAttributes.RUNTIME_INVISIBLE_TYPE_ANNOTATIONS);
+  }
+
   public T addKeepAllAttributes() {
     return addKeepAttributes("*");
   }
diff --git a/src/test/java/com/android/tools/r8/annotations/TypeUseAnnotationWithGenericsTest.java b/src/test/java/com/android/tools/r8/annotations/TypeUseAnnotationWithGenericsTest.java
new file mode 100644
index 0000000..b5dbbe9
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/annotations/TypeUseAnnotationWithGenericsTest.java
@@ -0,0 +1,120 @@
+// Copyright (c) 2023, 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.annotations;
+
+import static org.junit.Assume.assumeTrue;
+
+import com.android.tools.r8.TestBase;
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.TestParametersCollection;
+import com.android.tools.r8.annotations.testclasses.MainWithTypeAndGeneric;
+import com.android.tools.r8.annotations.testclasses.NotNullTestClass;
+import com.android.tools.r8.annotations.testclasses.NotNullTestRuntime;
+import com.android.tools.r8.annotations.testclasses.SuperInterface;
+import com.android.tools.r8.annotations.testclasses.TestClassWithTypeAndGenericAnnotations;
+import com.android.tools.r8.utils.StringUtils;
+import java.lang.reflect.AnnotatedType;
+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 TypeUseAnnotationWithGenericsTest extends TestBase {
+
+  private final String EXPECTED_JVM =
+      StringUtils.joinLines(
+          "printAnnotation - Class: " + typeName(NotNullTestRuntime.class),
+          "printAnnotation - Class: NULL",
+          "printAnnotation - Extends(0): " + typeName(NotNullTestRuntime.class),
+          "printAnnotation - Implements(0): " + typeName(NotNullTestRuntime.class),
+          "printAnnotation - Field: NULL",
+          "printAnnotation - Field: NULL",
+          "printAnnotation - Field(0): " + typeName(NotNullTestRuntime.class),
+          "printAnnotation - Method: NULL",
+          "printAnnotation - Method: NULL",
+          "printAnnotation - MethodReturnType(0): " + typeName(NotNullTestRuntime.class),
+          "printAnnotation - MethodParameter at 0(0): " + typeName(NotNullTestRuntime.class),
+          "printAnnotation - MethodParameter at 1(0): " + typeName(NotNullTestRuntime.class),
+          "printAnnotation - MethodException at 0(0): " + typeName(NotNullTestRuntime.class),
+          "printAnnotation - MethodException at 1(0): " + typeName(NotNullTestRuntime.class),
+          "Hello World!");
+
+  private final String EXPECTED_R8 =
+      StringUtils.joinLines(
+          "printAnnotation - Class: " + typeName(NotNullTestRuntime.class),
+          "printAnnotation - Class: NULL",
+          "printAnnotation - Field: NULL",
+          "printAnnotation - Field: NULL",
+          "printAnnotation - Method: NULL",
+          "printAnnotation - Method: NULL",
+          "Hello World!");
+
+  @Parameter() public TestParameters parameters;
+
+  @Parameters(name = "{0}")
+  public static TestParametersCollection data() {
+    return getTestParameters().withAllRuntimesAndApiLevels().build();
+  }
+
+  @Test
+  public void testJvm() throws Exception {
+    assumeTrue(parameters.isCfRuntime());
+    testForJvm(parameters)
+        .addProgramClasses(
+            MainWithTypeAndGeneric.class,
+            NotNullTestClass.class,
+            NotNullTestRuntime.class,
+            TestClassWithTypeAndGenericAnnotations.class,
+            SuperInterface.class)
+        .run(parameters.getRuntime(), MainWithTypeAndGeneric.class)
+        .assertSuccessWithOutputLines(EXPECTED_JVM);
+  }
+
+  @Test
+  public void testD8() throws Exception {
+    assumeTrue(parameters.isDexRuntime());
+    testForD8(parameters.getBackend())
+        .addProgramClasses(
+            MainWithTypeAndGeneric.class,
+            NotNullTestClass.class,
+            NotNullTestRuntime.class,
+            TestClassWithTypeAndGenericAnnotations.class,
+            SuperInterface.class)
+        .setMinApi(parameters)
+        .run(parameters.getRuntime(), MainWithTypeAndGeneric.class)
+        .assertFailureWithErrorThatThrows(NoSuchMethodError.class);
+  }
+
+  @Test
+  public void testR8() throws Exception {
+    testForR8(parameters.getBackend())
+        .addProgramClasses(
+            MainWithTypeAndGeneric.class,
+            NotNullTestClass.class,
+            NotNullTestRuntime.class,
+            TestClassWithTypeAndGenericAnnotations.class,
+            SuperInterface.class)
+        .setMinApi(parameters)
+        .addKeepClassRules(NotNullTestClass.class, NotNullTestRuntime.class, SuperInterface.class)
+        .addKeepRuntimeVisibleAnnotations()
+        .addKeepRuntimeInvisibleAnnotations()
+        .addKeepRuntimeVisibleTypeAnnotations()
+        .addKeepRuntimeInvisibleTypeAnnotations()
+        .addKeepAttributeSignature()
+        .addKeepMainRule(MainWithTypeAndGeneric.class)
+        .addKeepClassAndMembersRules(TestClassWithTypeAndGenericAnnotations.class)
+        .applyIf(parameters.isDexRuntime(), b -> b.addDontWarn(AnnotatedType.class))
+        .compile()
+        .inspect(
+            inspector -> {
+              // TODO(b/271543766): Add inspection of annotated types, even runtime invisible.
+            })
+        .run(parameters.getRuntime(), MainWithTypeAndGeneric.class)
+        .assertFailureWithErrorThatThrowsIf(parameters.isDexRuntime(), NoSuchMethodError.class)
+        .assertSuccessWithOutputLinesIf(parameters.isCfRuntime(), EXPECTED_R8);
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/annotations/testclasses/MainWithTypeAndGeneric.java b/src/test/java/com/android/tools/r8/annotations/testclasses/MainWithTypeAndGeneric.java
new file mode 100644
index 0000000..b1ac103
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/annotations/testclasses/MainWithTypeAndGeneric.java
@@ -0,0 +1,59 @@
+// Copyright (c) 2023, 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.annotations.testclasses;
+
+import java.lang.annotation.Annotation;
+import java.lang.reflect.AnnotatedType;
+import java.lang.reflect.Field;
+import java.lang.reflect.Method;
+import java.util.List;
+
+public class MainWithTypeAndGeneric {
+
+  public static void main(String[] args) throws Exception {
+    Class<TestClassWithTypeAndGenericAnnotations> testClass =
+        TestClassWithTypeAndGenericAnnotations.class;
+    printAnnotation("Class", testClass.getAnnotation(NotNullTestRuntime.class));
+    printAnnotation("Class", testClass.getAnnotation(NotNullTestClass.class));
+    printAnnotatedType("Extends", testClass.getAnnotatedSuperclass());
+    for (AnnotatedType annotatedInterface : testClass.getAnnotatedInterfaces()) {
+      printAnnotatedType("Implements", annotatedInterface);
+    }
+    Field field = testClass.getDeclaredField("field");
+    printAnnotation("Field", field.getAnnotation(NotNullTestRuntime.class));
+    printAnnotation("Field", field.getAnnotation(NotNullTestClass.class));
+    printAnnotatedType("Field", field.getAnnotatedType());
+    Method method = testClass.getDeclaredMethod("method", int.class, List.class, Object.class);
+    printAnnotation("Method", method.getAnnotation(NotNullTestRuntime.class));
+    printAnnotation("Method", method.getAnnotation(NotNullTestClass.class));
+    printAnnotatedType("MethodReturnType", method.getAnnotatedReturnType());
+    for (Annotation[] parameterAnnotation : method.getParameterAnnotations()) {
+      for (Annotation annotation : parameterAnnotation) {
+        printAnnotation("MethodParameter", annotation);
+      }
+    }
+    for (int i = 0; i < method.getAnnotatedParameterTypes().length; i++) {
+      printAnnotatedType("MethodParameter at " + i, method.getAnnotatedParameterTypes()[i]);
+    }
+    for (int i = 0; i < method.getAnnotatedExceptionTypes().length; i++) {
+      printAnnotatedType("MethodException at " + i, method.getAnnotatedExceptionTypes()[i]);
+    }
+    System.out.println("Hello World!");
+  }
+
+  public static void printAnnotation(String name, Annotation annotation) {
+    System.out.println(
+        "printAnnotation - "
+            + name
+            + ": "
+            + (annotation == null ? "NULL" : annotation.annotationType().getName()));
+  }
+
+  public static void printAnnotatedType(String name, AnnotatedType annotatedType) {
+    for (int i = 0; i < annotatedType.getAnnotations().length; i++) {
+      printAnnotation(name + "(" + i + ")", annotatedType.getAnnotations()[i]);
+    }
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/annotations/testclasses/NotNullTestClass.java b/src/test/java/com/android/tools/r8/annotations/testclasses/NotNullTestClass.java
new file mode 100644
index 0000000..aa5a53d
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/annotations/testclasses/NotNullTestClass.java
@@ -0,0 +1,14 @@
+// Copyright (c) 2023, 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.annotations.testclasses;
+
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+
+@Target(ElementType.TYPE_USE)
+@Retention(RetentionPolicy.CLASS)
+public @interface NotNullTestClass {}
diff --git a/src/test/java/com/android/tools/r8/annotations/testclasses/NotNullTestRuntime.java b/src/test/java/com/android/tools/r8/annotations/testclasses/NotNullTestRuntime.java
new file mode 100644
index 0000000..52c5ced
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/annotations/testclasses/NotNullTestRuntime.java
@@ -0,0 +1,14 @@
+// Copyright (c) 2023, 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.annotations.testclasses;
+
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+
+@Target(ElementType.TYPE_USE)
+@Retention(RetentionPolicy.RUNTIME)
+public @interface NotNullTestRuntime {}
diff --git a/src/test/java/com/android/tools/r8/annotations/testclasses/SuperInterface.java b/src/test/java/com/android/tools/r8/annotations/testclasses/SuperInterface.java
new file mode 100644
index 0000000..61e6ae6
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/annotations/testclasses/SuperInterface.java
@@ -0,0 +1,7 @@
+// Copyright (c) 2023, 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.annotations.testclasses;
+
+public interface SuperInterface<T> {}
diff --git a/src/test/java/com/android/tools/r8/annotations/testclasses/TestClassWithTypeAndGenericAnnotations.java b/src/test/java/com/android/tools/r8/annotations/testclasses/TestClassWithTypeAndGenericAnnotations.java
new file mode 100644
index 0000000..fe1853e
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/annotations/testclasses/TestClassWithTypeAndGenericAnnotations.java
@@ -0,0 +1,39 @@
+// Copyright (c) 2023, 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.annotations.testclasses;
+
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.List;
+
+@NotNullTestRuntime
+@NotNullTestClass
+public class TestClassWithTypeAndGenericAnnotations<@NotNullTestRuntime @NotNullTestClass T>
+    extends @NotNullTestRuntime @NotNullTestClass Object
+    implements @NotNullTestRuntime @NotNullTestClass SuperInterface<
+        @NotNullTestRuntime @NotNullTestClass T> {
+
+  @NotNullTestRuntime @NotNullTestClass
+  List<@NotNullTestRuntime @NotNullTestClass Object> field = null;
+
+  @NotNullTestRuntime
+  @NotNullTestClass
+  <@NotNullTestRuntime @NotNullTestClass S>
+      List<@NotNullTestRuntime @NotNullTestClass Object> method(
+          @NotNullTestRuntime @NotNullTestClass int foo,
+          @NotNullTestRuntime @NotNullTestClass
+              List<@NotNullTestRuntime @NotNullTestClass String> bar,
+          S s)
+          throws @NotNullTestClass @NotNullTestRuntime RuntimeException,
+              @NotNullTestClass @NotNullTestRuntime IOException {
+    @NotNullTestRuntime
+    @NotNullTestClass
+    Object local = System.currentTimeMillis() > 0 ? new Object() : foo;
+    ArrayList<@NotNullTestRuntime @NotNullTestClass Object> objects = new ArrayList<>();
+    objects.add(foo);
+    this.field = objects;
+    return objects;
+  }
+}