Introduce diagnostics for API level related errors.

Bug: 154778581
Change-Id: I55a2c94439cb0d39e55474ec9df04ab5804277c4
diff --git a/src/main/java/com/android/tools/r8/ApiLevelException.java b/src/main/java/com/android/tools/r8/ApiLevelException.java
index 0ad75c7..d58f0ec 100644
--- a/src/main/java/com/android/tools/r8/ApiLevelException.java
+++ b/src/main/java/com/android/tools/r8/ApiLevelException.java
@@ -16,7 +16,7 @@
     assert unsupportedFeatures != null;
   }
 
-  private static String makeMessage(
+  public static String makeMessage(
       AndroidApiLevel minApiLevel, String unsupportedFeatures, String sourceString) {
     String message =
         unsupportedFeatures
diff --git a/src/main/java/com/android/tools/r8/dex/FileWriter.java b/src/main/java/com/android/tools/r8/dex/FileWriter.java
index c4040a2..867f32d 100644
--- a/src/main/java/com/android/tools/r8/dex/FileWriter.java
+++ b/src/main/java/com/android/tools/r8/dex/FileWriter.java
@@ -5,10 +5,13 @@
 
 import static com.android.tools.r8.utils.LebUtils.sizeAsUleb128;
 
-import com.android.tools.r8.ApiLevelException;
 import com.android.tools.r8.ByteBufferProvider;
 import com.android.tools.r8.code.Instruction;
 import com.android.tools.r8.errors.CompilationError;
+import com.android.tools.r8.errors.DefaultInterfaceMethodDiagnostic;
+import com.android.tools.r8.errors.InvokeCustomDiagnostic;
+import com.android.tools.r8.errors.PrivateInterfaceMethodDiagnostic;
+import com.android.tools.r8.errors.StaticInterfaceMethodDiagnostic;
 import com.android.tools.r8.graph.DexAnnotation;
 import com.android.tools.r8.graph.DexAnnotationDirectory;
 import com.android.tools.r8.graph.DexAnnotationElement;
@@ -48,6 +51,7 @@
 import com.android.tools.r8.naming.MemberNaming.MethodSignature;
 import com.android.tools.r8.naming.MemberNaming.Signature;
 import com.android.tools.r8.naming.NamingLens;
+import com.android.tools.r8.position.MethodPosition;
 import com.android.tools.r8.utils.AndroidApiLevel;
 import com.android.tools.r8.utils.DexVersion;
 import com.android.tools.r8.utils.InternalOptions;
@@ -267,10 +271,8 @@
     }
     if (method.accessFlags.isStatic()) {
       if (!options.canUseDefaultAndStaticInterfaceMethods()) {
-        throw new ApiLevelException(
-            AndroidApiLevel.N,
-            "Static interface methods",
-            method.method.toSourceString());
+        throw options.reporter.fatalError(
+            new StaticInterfaceMethodDiagnostic(new MethodPosition(method.method)));
       }
 
     } else {
@@ -280,10 +282,8 @@
       }
       if (!method.accessFlags.isAbstract() && !method.accessFlags.isPrivate() &&
           !options.canUseDefaultAndStaticInterfaceMethods()) {
-        throw new ApiLevelException(
-            AndroidApiLevel.N,
-            "Default interface methods",
-            method.method.toSourceString());
+        throw options.reporter.fatalError(
+            new DefaultInterfaceMethodDiagnostic(new MethodPosition(method.method)));
       }
     }
 
@@ -291,10 +291,8 @@
       if (options.canUsePrivateInterfaceMethods()) {
         return;
       }
-      throw new ApiLevelException(
-          AndroidApiLevel.N,
-          "Private interface methods",
-          method.method.toSourceString());
+      throw options.reporter.fatalError(
+          new PrivateInterfaceMethodDiagnostic(new MethodPosition(method.method)));
     }
 
     if (!method.accessFlags.isPublic()) {
@@ -1383,10 +1381,7 @@
 
   private void checkThatInvokeCustomIsAllowed() {
     if (!options.canUseInvokeCustom()) {
-      throw new ApiLevelException(
-          AndroidApiLevel.O,
-          "Invoke-customs",
-          null /* sourceString */);
+      throw options.reporter.fatalError(new InvokeCustomDiagnostic());
     }
   }
 }
diff --git a/src/main/java/com/android/tools/r8/errors/ApiLevelDiagnostic.java b/src/main/java/com/android/tools/r8/errors/ApiLevelDiagnostic.java
new file mode 100644
index 0000000..fde6860
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/errors/ApiLevelDiagnostic.java
@@ -0,0 +1,22 @@
+// 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.errors;
+
+import com.android.tools.r8.Diagnostic;
+import com.android.tools.r8.origin.Origin;
+import com.android.tools.r8.position.Position;
+
+// TODO(b/154778581): Flesh out this class/interface and keep it.
+public abstract class ApiLevelDiagnostic implements Diagnostic {
+
+  @Override
+  public Origin getOrigin() {
+    return Origin.unknown();
+  }
+
+  @Override
+  public Position getPosition() {
+    return Position.UNKNOWN;
+  }
+}
diff --git a/src/main/java/com/android/tools/r8/errors/DefaultInterfaceMethodDiagnostic.java b/src/main/java/com/android/tools/r8/errors/DefaultInterfaceMethodDiagnostic.java
new file mode 100644
index 0000000..b0dfe4e
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/errors/DefaultInterfaceMethodDiagnostic.java
@@ -0,0 +1,30 @@
+// 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.errors;
+
+import com.android.tools.r8.ApiLevelException;
+import com.android.tools.r8.position.MethodPosition;
+import com.android.tools.r8.position.Position;
+import com.android.tools.r8.utils.AndroidApiLevel;
+
+public class DefaultInterfaceMethodDiagnostic extends ApiLevelDiagnostic {
+
+  private final MethodPosition position;
+
+  public DefaultInterfaceMethodDiagnostic(MethodPosition position) {
+    assert position != null;
+    this.position = position;
+  }
+
+  @Override
+  public Position getPosition() {
+    return position;
+  }
+
+  @Override
+  public String getDiagnosticMessage() {
+    return ApiLevelException.makeMessage(
+        AndroidApiLevel.N, "Default interface methods", position.toString());
+  }
+}
diff --git a/src/main/java/com/android/tools/r8/errors/InvokeCustomDiagnostic.java b/src/main/java/com/android/tools/r8/errors/InvokeCustomDiagnostic.java
new file mode 100644
index 0000000..ab8ff24
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/errors/InvokeCustomDiagnostic.java
@@ -0,0 +1,15 @@
+// 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.errors;
+
+import com.android.tools.r8.ApiLevelException;
+import com.android.tools.r8.utils.AndroidApiLevel;
+
+public class InvokeCustomDiagnostic extends ApiLevelDiagnostic {
+
+  @Override
+  public String getDiagnosticMessage() {
+    return ApiLevelException.makeMessage(AndroidApiLevel.O, "Invoke-customs", null);
+  }
+}
diff --git a/src/main/java/com/android/tools/r8/errors/PrivateInterfaceMethodDiagnostic.java b/src/main/java/com/android/tools/r8/errors/PrivateInterfaceMethodDiagnostic.java
new file mode 100644
index 0000000..568a7a0
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/errors/PrivateInterfaceMethodDiagnostic.java
@@ -0,0 +1,30 @@
+// 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.errors;
+
+import com.android.tools.r8.ApiLevelException;
+import com.android.tools.r8.position.MethodPosition;
+import com.android.tools.r8.position.Position;
+import com.android.tools.r8.utils.AndroidApiLevel;
+
+public class PrivateInterfaceMethodDiagnostic extends ApiLevelDiagnostic {
+
+  private final MethodPosition position;
+
+  public PrivateInterfaceMethodDiagnostic(MethodPosition position) {
+    assert position != null;
+    this.position = position;
+  }
+
+  @Override
+  public Position getPosition() {
+    return position;
+  }
+
+  @Override
+  public String getDiagnosticMessage() {
+    return ApiLevelException.makeMessage(
+        AndroidApiLevel.N, "Private interface methods", position.toString());
+  }
+}
diff --git a/src/main/java/com/android/tools/r8/errors/StaticInterfaceMethodDiagnostic.java b/src/main/java/com/android/tools/r8/errors/StaticInterfaceMethodDiagnostic.java
new file mode 100644
index 0000000..cb00b94
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/errors/StaticInterfaceMethodDiagnostic.java
@@ -0,0 +1,30 @@
+// 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.errors;
+
+import com.android.tools.r8.ApiLevelException;
+import com.android.tools.r8.position.MethodPosition;
+import com.android.tools.r8.position.Position;
+import com.android.tools.r8.utils.AndroidApiLevel;
+
+public class StaticInterfaceMethodDiagnostic extends ApiLevelDiagnostic {
+
+  private final MethodPosition position;
+
+  public StaticInterfaceMethodDiagnostic(MethodPosition position) {
+    assert position != null;
+    this.position = position;
+  }
+
+  @Override
+  public Position getPosition() {
+    return position;
+  }
+
+  @Override
+  public String getDiagnosticMessage() {
+    return ApiLevelException.makeMessage(
+        AndroidApiLevel.N, "Static interface methods", position.toString());
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/TestCompilerBuilder.java b/src/test/java/com/android/tools/r8/TestCompilerBuilder.java
index be6ffbb..8ae9c0f 100644
--- a/src/test/java/com/android/tools/r8/TestCompilerBuilder.java
+++ b/src/test/java/com/android/tools/r8/TestCompilerBuilder.java
@@ -242,8 +242,12 @@
     return self();
   }
 
-  public T setEnableDesugaring(boolean enableDesugaring) {
-    builder.setEnableDesugaring(enableDesugaring);
+  public T disableDesugaring() {
+    return setDisableDesugaring(true);
+  }
+
+  public T setDisableDesugaring(boolean disableDesugaring) {
+    builder.setDisableDesugaring(disableDesugaring);
     return self();
   }
 
diff --git a/src/test/java/com/android/tools/r8/desugar/DesugarToClassFile.java b/src/test/java/com/android/tools/r8/desugar/DesugarToClassFile.java
index e8ac332..39f0ef1 100644
--- a/src/test/java/com/android/tools/r8/desugar/DesugarToClassFile.java
+++ b/src/test/java/com/android/tools/r8/desugar/DesugarToClassFile.java
@@ -65,7 +65,7 @@
       testForD8()
           .addProgramFiles(jar)
           .setMinApi(parameters.getApiLevel())
-          .setEnableDesugaring(false)
+          .disableDesugaring()
           .run(parameters.getRuntime(), TestClass.class)
           .assertSuccessWithOutputLines("Hello, world!", "I::foo");
     }
diff --git a/src/test/java/com/android/tools/r8/desugar/DesugarToClassFileInputCfVersion.java b/src/test/java/com/android/tools/r8/desugar/DesugarToClassFileInputCfVersion.java
index 0fd267d..49437ff 100644
--- a/src/test/java/com/android/tools/r8/desugar/DesugarToClassFileInputCfVersion.java
+++ b/src/test/java/com/android/tools/r8/desugar/DesugarToClassFileInputCfVersion.java
@@ -68,7 +68,7 @@
       testForD8()
           .addProgramFiles(jar)
           .setMinApi(parameters.getApiLevel())
-          .setEnableDesugaring(false)
+          .disableDesugaring()
           .run(parameters.getRuntime(), TestClass.class)
           .assertSuccessWithOutputLines("Hello, world!");
     }
diff --git a/src/test/java/com/android/tools/r8/diagnostics/ApiLevelDiagnosticTest.java b/src/test/java/com/android/tools/r8/diagnostics/ApiLevelDiagnosticTest.java
new file mode 100644
index 0000000..40fab17
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/diagnostics/ApiLevelDiagnosticTest.java
@@ -0,0 +1,124 @@
+// 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.diagnostics;
+
+import static com.android.tools.r8.DiagnosticsMatcher.diagnosticMessage;
+import static org.hamcrest.CoreMatchers.startsWith;
+
+import com.android.tools.r8.CompilationFailedException;
+import com.android.tools.r8.TestBase;
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.TestParametersCollection;
+import com.android.tools.r8.utils.AndroidApiLevel;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+
+// TODO(b/154778581): Extend these tests with typed diagnostics and improved information.
+@RunWith(Parameterized.class)
+public class ApiLevelDiagnosticTest extends TestBase {
+
+  // Hard coded messages in AGP. See D8DexArchiveBuilder.
+
+  private static final String AGP_INVOKE_CUSTOM =
+      "Invoke-customs are only supported starting with Android O";
+
+  private static final String AGP_DEFAULT_INTERFACE_METHOD =
+      "Default interface methods are only supported starting with Android N (--min-api 24)";
+
+  private static final String AGP_STATIC_INTERFACE_METHOD =
+      "Static interface methods are only supported starting with Android N (--min-api 24)";
+
+  @Parameterized.Parameters(name = "{0}")
+  public static TestParametersCollection data() {
+    return getTestParameters().withNoneRuntime().build();
+  }
+
+  public ApiLevelDiagnosticTest(TestParameters parameters) {}
+
+  @Test(expected = CompilationFailedException.class)
+  public void testInvokeLambdaMetafactory() throws Exception {
+    testForD8()
+        .addProgramClassesAndInnerClasses(LambdaMetafactoryTest.class)
+        .setMinApi(AndroidApiLevel.B)
+        .disableDesugaring()
+        .compileWithExpectedDiagnostics(
+            diagnostics -> {
+              diagnostics
+                  .assertOnlyErrors()
+                  .assertErrorsMatch(diagnosticMessage(startsWith(AGP_INVOKE_CUSTOM)));
+            });
+  }
+
+  @Test(expected = CompilationFailedException.class)
+  public void testDefaultInterfaceMethods() throws Exception {
+    testForD8()
+        .addProgramClassesAndInnerClasses(DefaultInterfaceMethodsTest.class)
+        .setMinApi(AndroidApiLevel.B)
+        .disableDesugaring()
+        .compileWithExpectedDiagnostics(
+            diagnostics -> {
+              diagnostics
+                  .assertOnlyErrors()
+                  .assertErrorsMatch(diagnosticMessage(startsWith(AGP_DEFAULT_INTERFACE_METHOD)));
+            });
+  }
+
+  @Test(expected = CompilationFailedException.class)
+  public void testStaticInterfaceMethods() throws Exception {
+    testForD8()
+        .addProgramClassesAndInnerClasses(StaticInterfaceMethodsTest.class)
+        .setMinApi(AndroidApiLevel.B)
+        .disableDesugaring()
+        .compileWithExpectedDiagnostics(
+            diagnostics -> {
+              diagnostics
+                  .assertOnlyErrors()
+                  .assertErrorsMatch(diagnosticMessage(startsWith(AGP_STATIC_INTERFACE_METHOD)));
+            });
+  }
+
+  static class LambdaMetafactoryTest {
+
+    public interface StringSupplier {
+      String get();
+    }
+
+    public static void print(StringSupplier supplier) {
+      System.out.println(supplier.get());
+    }
+
+    public static void main(String[] args) {
+      print(() -> "Hello, world");
+    }
+  }
+
+  static class DefaultInterfaceMethodsTest {
+
+    interface WithDefaults {
+      default void foo() {
+        System.out.println("WithDefaults::foo");
+      }
+    }
+
+    static class MyClass implements WithDefaults {}
+
+    public static void main(String[] args) {
+      new MyClass().foo();
+    }
+  }
+
+  static class StaticInterfaceMethodsTest {
+
+    interface WithStatics {
+      static void foo() {
+        System.out.println("WithStatics::foo");
+      }
+    }
+
+    public static void main(String[] args) {
+      WithStatics.foo();
+    }
+  }
+}