Add tests of some JDK-21 pattern matching for switch
See https://openjdk.org/jeps/441.
Bug: b/336510513
Bug: b/335613935
Change-Id: I05675bbf2c5ade7db4dcb92738392acf3694fd6d
diff --git a/d8_r8/test_modules/tests_java_17/build.gradle.kts b/d8_r8/test_modules/tests_java_17/build.gradle.kts
index f3c9100..2079c40 100644
--- a/d8_r8/test_modules/tests_java_17/build.gradle.kts
+++ b/d8_r8/test_modules/tests_java_17/build.gradle.kts
@@ -3,7 +3,6 @@
// BSD-style license that can be found in the LICENSE file.
import org.gradle.api.JavaVersion
-import org.jetbrains.kotlin.gradle.tasks.KotlinCompile
plugins {
`kotlin-dsl`
@@ -47,6 +46,8 @@
}
withType<Test> {
+ notCompatibleWithConfigurationCache(
+ "Failure storing the configuration cache: cannot serialize object of type 'org.gradle.api.internal.project.DefaultProject', a subtype of 'org.gradle.api.Project', as these are not supported with the configuration cache")
TestingState.setUpTestingState(this)
javaLauncher = getJavaLauncher(Jdk.JDK_17)
systemProperty("TEST_DATA_LOCATION",
diff --git a/d8_r8/test_modules/tests_java_21/build.gradle.kts b/d8_r8/test_modules/tests_java_21/build.gradle.kts
index ecbcef1..afc55d0 100644
--- a/d8_r8/test_modules/tests_java_21/build.gradle.kts
+++ b/d8_r8/test_modules/tests_java_21/build.gradle.kts
@@ -3,7 +3,6 @@
// BSD-style license that can be found in the LICENSE file.
import org.gradle.api.JavaVersion
-import org.jetbrains.kotlin.gradle.tasks.KotlinCompile
plugins {
`kotlin-dsl`
@@ -43,6 +42,8 @@
}
withType<Test> {
+ notCompatibleWithConfigurationCache(
+ "Failure storing the configuration cache: cannot serialize object of type 'org.gradle.api.internal.project.DefaultProject', a subtype of 'org.gradle.api.Project', as these are not supported with the configuration cache")
TestingState.setUpTestingState(this)
javaLauncher = getJavaLauncher(Jdk.JDK_21)
systemProperty("TEST_DATA_LOCATION",
diff --git a/src/test/examplesJava21/switchpatternmatching/EnumSwitchTest.java b/src/test/examplesJava21/switchpatternmatching/EnumSwitchTest.java
new file mode 100644
index 0000000..07f10ad
--- /dev/null
+++ b/src/test/examplesJava21/switchpatternmatching/EnumSwitchTest.java
@@ -0,0 +1,152 @@
+// 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 switchpatternmatching;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertThrows;
+import static org.junit.Assume.assumeTrue;
+
+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.TestRuntime.CfVm;
+import com.android.tools.r8.ToolHelper;
+import com.android.tools.r8.errors.CompilationError;
+import com.android.tools.r8.utils.StringUtils;
+import com.android.tools.r8.utils.codeinspector.CodeInspector;
+import com.android.tools.r8.utils.codeinspector.InstructionSubject;
+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 EnumSwitchTest extends TestBase {
+
+ @Parameter public TestParameters parameters;
+
+ @Parameters(name = "{0}")
+ public static TestParametersCollection data() {
+ return getTestParameters().withAllRuntimesAndApiLevels().build();
+ }
+
+ public static String EXPECTED_OUTPUT = StringUtils.lines("null", "E1", "E2", "E3", "E4", "a C");
+
+ @Test
+ public void testJvm() throws Exception {
+ assumeTrue(parameters.isCfRuntime());
+ CodeInspector inspector = new CodeInspector(ToolHelper.getClassFileForTestClass(Main.class));
+ // javac generated an invokedynamic using bootstrap method
+ // java.lang.runtime.SwitchBootstraps.typeSwitch.
+ try {
+ assertEquals(
+ 1,
+ inspector
+ .clazz(Main.class)
+ .uniqueMethodWithOriginalName("enumSwitch")
+ .streamInstructions()
+ .filter(InstructionSubject::isInvokeDynamic)
+ .map(
+ instruction ->
+ instruction
+ .asCfInstruction()
+ .getInstruction()
+ .asInvokeDynamic()
+ .getCallSite()
+ .getBootstrapMethod()
+ .member
+ .asDexMethod())
+ .filter(
+ method ->
+ method
+ .getHolderType()
+ .toString()
+ .contains("java.lang.runtime.SwitchBootstraps"))
+ .filter(method -> method.toString().contains("typeSwitch"))
+ .count());
+ } catch (CompilationError e) {
+ assertEquals("Could not parse code", e.getMessage());
+ assertEquals(
+ "Unsupported bootstrap static argument of type ConstantDynamic",
+ e.getCause().getMessage());
+ }
+
+ parameters.assumeJvmTestParameters();
+ testForJvm(parameters)
+ .addInnerClassesAndStrippedOuter(getClass())
+ .run(parameters.getRuntime(), Main.class)
+ .applyIf(
+ parameters.getCfRuntime().isNewerThanOrEqual(CfVm.JDK21),
+ r -> r.assertSuccessWithOutput(EXPECTED_OUTPUT),
+ r -> r.assertFailureWithErrorThatThrows(UnsupportedClassVersionError.class));
+ }
+
+ @Test
+ public void testD8() throws Exception {
+ parameters.assumeDexRuntime();
+ assertThrows(
+ CompilationFailedException.class,
+ () -> testForD8().addInnerClasses(getClass()).setMinApi(parameters).compile());
+ }
+
+ @Test
+ public void testR8() throws Exception {
+ assertThrows(
+ CompilationFailedException.class,
+ () ->
+ testForR8(parameters.getBackend())
+ .addInnerClasses(getClass())
+ .setMinApi(parameters)
+ .addKeepMainRule(Main.class)
+ .compile());
+ }
+
+ sealed interface I permits E, C {}
+
+ public enum E implements I {
+ E1,
+ E2,
+ E3,
+ E4
+ }
+
+ static final class C implements I {}
+
+ static class Main {
+ static void enumSwitch(I i) {
+ switch (i) {
+ case E.E1 -> {
+ System.out.println("E1");
+ }
+ case E.E2 -> {
+ System.out.println("E2");
+ }
+ case E.E3 -> {
+ System.out.println("E3");
+ }
+ case E.E4 -> {
+ System.out.println("E4");
+ }
+ case C c -> {
+ System.out.println("a C");
+ }
+ }
+ }
+
+ public static void main(String[] args) {
+ try {
+ enumSwitch(null);
+ } catch (NullPointerException e) {
+ System.out.println("null");
+ }
+ enumSwitch(E.E1);
+ enumSwitch(E.E2);
+ enumSwitch(E.E3);
+ enumSwitch(E.E4);
+ enumSwitch(new C());
+ }
+ }
+}
diff --git a/src/test/examplesJava21/switchpatternmatching/StringSwitchTest.java b/src/test/examplesJava21/switchpatternmatching/StringSwitchTest.java
new file mode 100644
index 0000000..a330d75
--- /dev/null
+++ b/src/test/examplesJava21/switchpatternmatching/StringSwitchTest.java
@@ -0,0 +1,138 @@
+// 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 switchpatternmatching;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertThrows;
+import static org.junit.Assume.assumeTrue;
+
+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.TestRuntime.CfVm;
+import com.android.tools.r8.ToolHelper;
+import com.android.tools.r8.utils.StringUtils;
+import com.android.tools.r8.utils.codeinspector.CodeInspector;
+import com.android.tools.r8.utils.codeinspector.InstructionSubject;
+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 StringSwitchTest extends TestBase {
+
+ @Parameter public TestParameters parameters;
+
+ @Parameters(name = "{0}")
+ public static TestParametersCollection data() {
+ return getTestParameters().withAllRuntimesAndApiLevels().build();
+ }
+
+ public static String EXPECTED_OUTPUT =
+ StringUtils.lines(
+ "null", "y or Y", "y or Y", "n or N", "n or N", "yes", "yes", "no", "no", "unknown");
+
+ @Test
+ public void testJvm() throws Exception {
+ assumeTrue(parameters.isCfRuntime());
+ CodeInspector inspector = new CodeInspector(ToolHelper.getClassFileForTestClass(Main.class));
+ // javac generated an invokedynamic using bootstrap method
+ // java.lang.runtime.SwitchBootstraps.typeSwitch.
+ assertEquals(
+ 1,
+ inspector
+ .clazz(Main.class)
+ .uniqueMethodWithOriginalName("stringSwitch")
+ .streamInstructions()
+ .filter(InstructionSubject::isInvokeDynamic)
+ .map(
+ instruction ->
+ instruction
+ .asCfInstruction()
+ .getInstruction()
+ .asInvokeDynamic()
+ .getCallSite()
+ .getBootstrapMethod()
+ .member
+ .asDexMethod())
+ .filter(
+ method ->
+ method
+ .getHolderType()
+ .toString()
+ .contains("java.lang.runtime.SwitchBootstraps"))
+ .filter(method -> method.toString().contains("typeSwitch"))
+ .count());
+
+ parameters.assumeJvmTestParameters();
+ testForJvm(parameters)
+ .addInnerClassesAndStrippedOuter(getClass())
+ .run(parameters.getRuntime(), Main.class)
+ .applyIf(
+ parameters.getCfRuntime().isNewerThanOrEqual(CfVm.JDK21),
+ r -> r.assertSuccessWithOutput(EXPECTED_OUTPUT),
+ r -> r.assertFailureWithErrorThatThrows(UnsupportedClassVersionError.class));
+ }
+
+ @Test
+ public void testD8() throws Exception {
+ parameters.assumeDexRuntime();
+ assertThrows(
+ CompilationFailedException.class,
+ () -> testForD8().addInnerClasses(getClass()).setMinApi(parameters).compile());
+ }
+
+ @Test
+ public void testR8() throws Exception {
+ assertThrows(
+ CompilationFailedException.class,
+ () ->
+ testForR8(parameters.getBackend())
+ .addInnerClasses(getClass())
+ .setMinApi(parameters)
+ .addKeepMainRule(Main.class)
+ .compile());
+ }
+
+ static class Main {
+ static void stringSwitch(String string) {
+ switch (string) {
+ case null -> {
+ System.out.println("null");
+ }
+ case "y", "Y" -> {
+ System.out.println("y or Y");
+ }
+ case "n", "N" -> {
+ System.out.println("n or N");
+ }
+ case String s when s.equalsIgnoreCase("YES") -> {
+ System.out.println("yes");
+ }
+ case String s when s.equalsIgnoreCase("NO") -> {
+ System.out.println("no");
+ }
+ case String s -> {
+ System.out.println("unknown");
+ }
+ }
+ }
+
+ public static void main(String[] args) {
+ stringSwitch(null);
+ stringSwitch("y");
+ stringSwitch("Y");
+ stringSwitch("n");
+ stringSwitch("N");
+ stringSwitch("yes");
+ stringSwitch("YES");
+ stringSwitch("no");
+ stringSwitch("NO");
+ stringSwitch("?");
+ }
+ }
+}
diff --git a/src/test/examplesJava21/switchpatternmatching/TypeSwitchTest.java b/src/test/examplesJava21/switchpatternmatching/TypeSwitchTest.java
new file mode 100644
index 0000000..5ad94ba
--- /dev/null
+++ b/src/test/examplesJava21/switchpatternmatching/TypeSwitchTest.java
@@ -0,0 +1,161 @@
+// 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 switchpatternmatching;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertThrows;
+import static org.junit.Assume.assumeTrue;
+
+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.TestRuntime.CfVm;
+import com.android.tools.r8.ToolHelper;
+import com.android.tools.r8.errors.CompilationError;
+import com.android.tools.r8.utils.StringUtils;
+import com.android.tools.r8.utils.codeinspector.CodeInspector;
+import com.android.tools.r8.utils.codeinspector.InstructionSubject;
+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 TypeSwitchTest extends TestBase {
+
+ @Parameter public TestParameters parameters;
+
+ @Parameters(name = "{0}")
+ public static TestParametersCollection data() {
+ return getTestParameters().withAllRuntimesAndApiLevels().build();
+ }
+
+ public static String EXPECTED_OUTPUT =
+ StringUtils.lines(
+ "null",
+ "String",
+ "Color: RED",
+ "Record class: Point[i=0, j=0]",
+ "Array of int, length = 0",
+ "Other");
+
+ @Test
+ public void testJvm() throws Exception {
+ assumeTrue(parameters.isCfRuntime());
+ CodeInspector inspector = new CodeInspector(ToolHelper.getClassFileForTestClass(Main.class));
+ // javac generated an invokedynamic using bootstrap method argument of an arrya type (sort 9
+ // is org.objectweb.asm.Type.ARRAY).
+ try {
+ inspector
+ .clazz(Main.class)
+ .uniqueMethodWithOriginalName("typeSwitch")
+ .streamInstructions()
+ .filter(InstructionSubject::isInvokeDynamic)
+ .count();
+ } catch (CompilationError e) {
+ assertEquals("Could not parse code", e.getMessage());
+ assertEquals("Type sort is not supported: 9", e.getCause().getMessage());
+ }
+ // javac generated an invokedynamic using bootstrap method
+ // java.lang.runtime.SwitchBootstraps.typeSwitch.
+ assertEquals(
+ 1,
+ inspector
+ .clazz(Main.class)
+ .uniqueMethodWithOriginalName("typeSwitchNoArray")
+ .streamInstructions()
+ .filter(InstructionSubject::isInvokeDynamic)
+ .map(
+ instruction ->
+ instruction
+ .asCfInstruction()
+ .getInstruction()
+ .asInvokeDynamic()
+ .getCallSite()
+ .getBootstrapMethod()
+ .member
+ .asDexMethod())
+ .filter(
+ method ->
+ method
+ .getHolderType()
+ .toString()
+ .contains("java.lang.runtime.SwitchBootstraps"))
+ .filter(method -> method.toString().contains("typeSwitch"))
+ .count());
+
+ parameters.assumeJvmTestParameters();
+ testForJvm(parameters)
+ .addInnerClassesAndStrippedOuter(getClass())
+ .run(parameters.getRuntime(), Main.class)
+ .applyIf(
+ parameters.getCfRuntime().isNewerThanOrEqual(CfVm.JDK21),
+ r -> r.assertSuccessWithOutput(EXPECTED_OUTPUT),
+ r -> r.assertFailureWithErrorThatThrows(UnsupportedClassVersionError.class));
+ }
+
+ @Test
+ public void testD8() throws Exception {
+ parameters.assumeDexRuntime();
+ assertThrows(
+ CompilationFailedException.class,
+ () -> testForD8().addInnerClasses(getClass()).setMinApi(parameters).compile());
+ }
+
+ @Test
+ public void testR8() throws Exception {
+ assertThrows(
+ CompilationFailedException.class,
+ () ->
+ testForR8(parameters.getBackend())
+ .addInnerClasses(getClass())
+ .setMinApi(parameters)
+ .addKeepMainRule(Main.class)
+ .compile());
+ }
+
+ record Point(int i, int j) {}
+
+ enum Color {
+ RED,
+ GREEN,
+ BLUE;
+ }
+
+ static class Main {
+
+ // No array version only for loading into code inspector.
+ static void typeSwitchNoArray(Object obj) {
+ switch (obj) {
+ case null -> System.out.println("null");
+ case String string -> System.out.println("String");
+ case Color color -> System.out.println("Color: " + color);
+ case Point point -> System.out.println("Record class: " + point);
+ default -> System.out.println("Other");
+ }
+ }
+
+ static void typeSwitch(Object obj) {
+ switch (obj) {
+ case null -> System.out.println("null");
+ case String string -> System.out.println("String");
+ case Color color -> System.out.println("Color: " + color);
+ case Point point -> System.out.println("Record class: " + point);
+ case int[] intArray -> System.out.println("Array of int, length = " + intArray.length);
+ default -> System.out.println("Other");
+ }
+ }
+
+ public static void main(String[] args) {
+ typeSwitch(null);
+ typeSwitch("s");
+ typeSwitch(Color.RED);
+ typeSwitch(new Point(0, 0));
+ typeSwitch(new int[] {});
+ typeSwitch(new Object());
+ }
+ }
+}