Error when methods with @CovariantReturnType are not kept

Bug: b/211362069
Change-Id: Ifbca5553a4d09b4fc62cac13b5c2cb3a9f10086c
diff --git a/src/main/java/com/android/tools/r8/desugar/covariantreturntype/CovariantReturnTypeAnnotationTransformer.java b/src/main/java/com/android/tools/r8/desugar/covariantreturntype/CovariantReturnTypeAnnotationTransformer.java
index ea193a5..6bc1e23 100644
--- a/src/main/java/com/android/tools/r8/desugar/covariantreturntype/CovariantReturnTypeAnnotationTransformer.java
+++ b/src/main/java/com/android/tools/r8/desugar/covariantreturntype/CovariantReturnTypeAnnotationTransformer.java
@@ -119,9 +119,6 @@
       CovariantReturnTypeAnnotationTransformerEventConsumer eventConsumer,
       ExecutorService executorService)
       throws ExecutionException {
-    if (methodsToProcess.isEmpty()) {
-      return;
-    }
     ThreadUtils.processMap(
         methodsToProcess,
         (clazz, methods) -> {
diff --git a/src/main/java/com/android/tools/r8/errors/NonKeptMethodWithCovariantReturnTypeAnnotationDiagnostic.java b/src/main/java/com/android/tools/r8/errors/NonKeptMethodWithCovariantReturnTypeAnnotationDiagnostic.java
new file mode 100644
index 0000000..a737aa4
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/errors/NonKeptMethodWithCovariantReturnTypeAnnotationDiagnostic.java
@@ -0,0 +1,43 @@
+// Copyright (c) 2024, 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.errors;
+
+import com.android.tools.r8.Diagnostic;
+import com.android.tools.r8.graph.ProgramMethod;
+import com.android.tools.r8.keepanno.annotations.KeepForApi;
+import com.android.tools.r8.origin.Origin;
+import com.android.tools.r8.position.MethodPosition;
+import com.android.tools.r8.position.Position;
+import com.android.tools.r8.references.MethodReference;
+import com.android.tools.r8.utils.MethodReferenceUtils;
+
+@KeepForApi
+public class NonKeptMethodWithCovariantReturnTypeAnnotationDiagnostic implements Diagnostic {
+
+  private final Origin origin;
+  private final MethodReference method;
+  private final MethodPosition position;
+
+  public NonKeptMethodWithCovariantReturnTypeAnnotationDiagnostic(ProgramMethod method) {
+    this.origin = method.getOrigin();
+    this.method = method.getMethodReference();
+    this.position = MethodPosition.create(method);
+  }
+
+  @Override
+  public Origin getOrigin() {
+    return origin;
+  }
+
+  @Override
+  public Position getPosition() {
+    return position;
+  }
+
+  @Override
+  public String getDiagnosticMessage() {
+    return "Methods with @CovariantReturnType annotations should be kept, but was not: "
+        + MethodReferenceUtils.toSourceString(method);
+  }
+}
diff --git a/src/main/java/com/android/tools/r8/shaking/Enqueuer.java b/src/main/java/com/android/tools/r8/shaking/Enqueuer.java
index bf8be36..8850e03 100644
--- a/src/main/java/com/android/tools/r8/shaking/Enqueuer.java
+++ b/src/main/java/com/android/tools/r8/shaking/Enqueuer.java
@@ -26,7 +26,7 @@
 import com.android.tools.r8.dex.IndexedItemCollection;
 import com.android.tools.r8.dex.code.CfOrDexInstruction;
 import com.android.tools.r8.errors.InterfaceDesugarMissingTypeDiagnostic;
-import com.android.tools.r8.errors.Unimplemented;
+import com.android.tools.r8.errors.NonKeptMethodWithCovariantReturnTypeAnnotationDiagnostic;
 import com.android.tools.r8.errors.Unreachable;
 import com.android.tools.r8.experimental.graphinfo.GraphConsumer;
 import com.android.tools.r8.features.IsolatedFeatureSplitsChecker;
@@ -4475,19 +4475,26 @@
   }
 
   private void processCovariantReturnTypeAnnotations() throws ExecutionException {
+    if (pendingCovariantReturnTypeDesugaring.isEmpty()) {
+      return;
+    }
+    ProgramMethodMap<Diagnostic> errors = ProgramMethodMap.createConcurrent();
     covariantReturnTypeAnnotationTransformer.processMethods(
         pendingCovariantReturnTypeDesugaring,
         (bridge, target) -> {
-          KeepMethodInfo.Joiner bridgeKeepInfo = getKeepInfoForCovariantReturnTypeBridge(target);
+          KeepMethodInfo.Joiner bridgeKeepInfo =
+              getKeepInfoForCovariantReturnTypeBridge(target, errors);
           keepInfo.registerCompilerSynthesizedMethod(bridge);
           applyMinimumKeepInfoWhenLiveOrTargeted(bridge, bridgeKeepInfo);
           profileCollectionAdditions.addMethodIfContextIsInProfile(bridge, target);
         },
         executorService);
+    errors.forEachValue(appView.reporter()::error);
     pendingCovariantReturnTypeDesugaring.clear();
   }
 
-  private KeepMethodInfo.Joiner getKeepInfoForCovariantReturnTypeBridge(ProgramMethod target) {
+  private KeepMethodInfo.Joiner getKeepInfoForCovariantReturnTypeBridge(
+      ProgramMethod target, ProgramMethodMap<Diagnostic> errors) {
     KeepInfo.Joiner<?, ?, ?> targetKeepInfo =
         appView
             .rootSet()
@@ -4500,8 +4507,7 @@
     if ((options.isMinifying() && targetKeepInfo.isMinificationAllowed())
         || (options.isOptimizing() && targetKeepInfo.isOptimizationAllowed())
         || (options.isShrinking() && targetKeepInfo.isShrinkingAllowed())) {
-      // TODO(b/211362069): Report a fatal diagnostic explaining the problem.
-      throw new Unimplemented();
+      errors.computeIfAbsent(target, NonKeptMethodWithCovariantReturnTypeAnnotationDiagnostic::new);
     }
     return targetKeepInfo.asMethodJoiner();
   }
diff --git a/src/test/java/com/android/tools/r8/ir/desugar/annotations/CovariantReturnTypeAnnotationTransformerR8Test.java b/src/test/java/com/android/tools/r8/ir/desugar/annotations/CovariantReturnTypeAnnotationTransformerR8Test.java
index e8ff92c..37f133f 100644
--- a/src/test/java/com/android/tools/r8/ir/desugar/annotations/CovariantReturnTypeAnnotationTransformerR8Test.java
+++ b/src/test/java/com/android/tools/r8/ir/desugar/annotations/CovariantReturnTypeAnnotationTransformerR8Test.java
@@ -3,18 +3,33 @@
 // BSD-style license that can be found in the LICENSE file.
 package com.android.tools.r8.ir.desugar.annotations;
 
+import static com.android.tools.r8.DiagnosticsMatcher.diagnosticMessage;
+import static com.android.tools.r8.DiagnosticsMatcher.diagnosticType;
 import static com.android.tools.r8.utils.codeinspector.AssertUtils.assertFailsCompilation;
+import static org.hamcrest.CoreMatchers.allOf;
+import static org.hamcrest.CoreMatchers.equalTo;
 
+import com.android.tools.r8.Diagnostic;
 import com.android.tools.r8.R8FullTestBuilder;
 import com.android.tools.r8.R8TestCompileResult;
 import com.android.tools.r8.TestBase;
+import com.android.tools.r8.TestCompilerBuilder.DiagnosticsConsumer;
+import com.android.tools.r8.TestDiagnosticMessages;
 import com.android.tools.r8.TestParameters;
 import com.android.tools.r8.TestParametersCollection;
 import com.android.tools.r8.ThrowableConsumer;
 import com.android.tools.r8.ToolHelper;
+import com.android.tools.r8.errors.NonKeptMethodWithCovariantReturnTypeAnnotationDiagnostic;
 import com.android.tools.r8.ir.desugar.annotations.CovariantReturnType.CovariantReturnTypes;
+import com.android.tools.r8.references.MethodReference;
+import com.android.tools.r8.references.Reference;
+import com.android.tools.r8.utils.ListUtils;
+import com.android.tools.r8.utils.MethodReferenceUtils;
+import com.google.common.collect.ImmutableList;
 import com.google.common.collect.ImmutableMap;
+import java.util.List;
 import java.util.Map;
+import org.hamcrest.Matcher;
 import org.junit.Test;
 import org.junit.runner.RunWith;
 import org.junit.runners.Parameterized;
@@ -75,7 +90,8 @@
             compileWithR8(
                 testBuilder ->
                     testBuilder.addKeepRules(
-                        "-keep,allowobfuscation public class * { public <methods>; }")));
+                        "-keep,allowobfuscation public class * { public <methods>; }"),
+                this::inspectDiagnostics));
   }
 
   @Test
@@ -85,7 +101,8 @@
             compileWithR8(
                 testBuilder ->
                     testBuilder.addKeepRules(
-                        "-keep,allowoptimization public class * { public <methods>; }")));
+                        "-keep,allowoptimization public class * { public <methods>; }"),
+                this::inspectDiagnostics));
   }
 
   @Test
@@ -96,11 +113,19 @@
                 testBuilder ->
                     testBuilder.addKeepRules(
                         "-if public class * -keep class <1> { public <methods>; }",
-                        "-keep public class *")));
+                        "-keep public class *"),
+                this::inspectDiagnostics));
   }
 
   private R8TestCompileResult compileWithR8(
       ThrowableConsumer<? super R8FullTestBuilder> configuration) throws Exception {
+    return compileWithR8(configuration, TestDiagnosticMessages::assertNoMessages);
+  }
+
+  private R8TestCompileResult compileWithR8(
+      ThrowableConsumer<? super R8FullTestBuilder> configuration,
+      DiagnosticsConsumer<?> diagnosticsConsumer)
+      throws Exception {
     return testForR8(parameters.getBackend())
         .addProgramClasses(A.class, D.class)
         .addProgramClassFileData(
@@ -138,7 +163,29 @@
         .addOptionsModification(options -> options.processCovariantReturnTypeAnnotations = true)
         .apply(configuration)
         .setMinApi(parameters)
-        .compile();
+        .compileWithExpectedDiagnostics(diagnosticsConsumer);
+  }
+
+  private void inspectDiagnostics(TestDiagnosticMessages diagnostics) throws Exception {
+    List<MethodReference> methods =
+        ImmutableList.of(
+            Reference.methodFromMethod(B.class.getDeclaredMethod("method")),
+            Reference.methodFromMethod(C.class.getDeclaredMethod("method")),
+            Reference.methodFromMethod(F.class.getDeclaredMethod("method")));
+    List<String> messages =
+        ListUtils.map(
+            methods,
+            method ->
+                "Methods with @CovariantReturnType annotations should be kept, but was not: "
+                    + MethodReferenceUtils.toSourceString(method));
+    List<Matcher<Diagnostic>> matchers =
+        ListUtils.map(
+            messages,
+            message ->
+                allOf(
+                    diagnosticType(NonKeptMethodWithCovariantReturnTypeAnnotationDiagnostic.class),
+                    diagnosticMessage(equalTo(message))));
+    diagnostics.assertErrorsMatch(matchers);
   }
 
   private void testOnRuntime(R8TestCompileResult r8CompileResult) throws Exception {