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();
+ }
+ }
+}