Add a copy of all pattern matching switch tests for JDK-23
Code generation of the bootstrap method signature changed.
Bug: b/382880986
Change-Id: I116b9640f051aae3ecd1b2b62a16e03c234014e9
diff --git a/src/test/examplesJava21/switchpatternmatching/EnumLessCasesAtRuntimeSwitchTest.java b/src/test/examplesJava21/switchpatternmatching/EnumLessCasesAtRuntimeSwitchTest.java
index f9d6bc9..63c6d26 100644
--- a/src/test/examplesJava21/switchpatternmatching/EnumLessCasesAtRuntimeSwitchTest.java
+++ b/src/test/examplesJava21/switchpatternmatching/EnumLessCasesAtRuntimeSwitchTest.java
@@ -24,6 +24,8 @@
import org.junit.runners.Parameterized.Parameter;
import org.junit.runners.Parameterized.Parameters;
+// This test is copied into later JDK tests (currently JDK-23). The reason for the copy is that
+// from JDK-23 the code generation changed. Please update the copy as well if updating this test.
@RunWith(Parameterized.class)
public class EnumLessCasesAtRuntimeSwitchTest extends TestBase {
diff --git a/src/test/examplesJava21/switchpatternmatching/EnumMoreCasesAtRuntimeSwitchTest.java b/src/test/examplesJava21/switchpatternmatching/EnumMoreCasesAtRuntimeSwitchTest.java
index 77a6e53..575ebfd 100644
--- a/src/test/examplesJava21/switchpatternmatching/EnumMoreCasesAtRuntimeSwitchTest.java
+++ b/src/test/examplesJava21/switchpatternmatching/EnumMoreCasesAtRuntimeSwitchTest.java
@@ -26,6 +26,8 @@
import org.junit.runners.Parameterized.Parameter;
import org.junit.runners.Parameterized.Parameters;
+// This test is copied into later JDK tests (currently JDK-23). The reason for the copy is that
+// from JDK-23 the code generation changed. Please update the copy as well if updating this test.
@RunWith(Parameterized.class)
public class EnumMoreCasesAtRuntimeSwitchTest extends TestBase {
diff --git a/src/test/examplesJava21/switchpatternmatching/EnumSwitchTest.java b/src/test/examplesJava21/switchpatternmatching/EnumSwitchTest.java
index bd16f68..5b1f9fb 100644
--- a/src/test/examplesJava21/switchpatternmatching/EnumSwitchTest.java
+++ b/src/test/examplesJava21/switchpatternmatching/EnumSwitchTest.java
@@ -25,6 +25,8 @@
import org.junit.runners.Parameterized.Parameter;
import org.junit.runners.Parameterized.Parameters;
+// This test is copied into later JDK tests (currently JDK-23). The reason for the copy is that
+// from JDK-23 the code generation changed. Please update the copy as well if updating this test.
@RunWith(Parameterized.class)
public class EnumSwitchTest extends TestBase {
diff --git a/src/test/examplesJava21/switchpatternmatching/EnumSwitchUsingEnumSwitchBootstrapMethod.java b/src/test/examplesJava21/switchpatternmatching/EnumSwitchUsingEnumSwitchBootstrapMethodTest.java
similarity index 94%
rename from src/test/examplesJava21/switchpatternmatching/EnumSwitchUsingEnumSwitchBootstrapMethod.java
rename to src/test/examplesJava21/switchpatternmatching/EnumSwitchUsingEnumSwitchBootstrapMethodTest.java
index 095620c..3b9fe1b 100644
--- a/src/test/examplesJava21/switchpatternmatching/EnumSwitchUsingEnumSwitchBootstrapMethod.java
+++ b/src/test/examplesJava21/switchpatternmatching/EnumSwitchUsingEnumSwitchBootstrapMethodTest.java
@@ -22,8 +22,10 @@
import org.junit.runners.Parameterized.Parameter;
import org.junit.runners.Parameterized.Parameters;
+// This test is copied into later JDK tests (currently JDK-23). The reason for the copy is that
+// from JDK-23 the code generation changed. Please update the copy as well if updating this test.
@RunWith(Parameterized.class)
-public class EnumSwitchUsingEnumSwitchBootstrapMethod extends TestBase {
+public class EnumSwitchUsingEnumSwitchBootstrapMethodTest extends TestBase {
@Parameter public TestParameters parameters;
diff --git a/src/test/examplesJava21/switchpatternmatching/TypeSwitchMissingClassTest.java b/src/test/examplesJava21/switchpatternmatching/TypeSwitchMissingClassTest.java
index cd2e035..c037f90 100644
--- a/src/test/examplesJava21/switchpatternmatching/TypeSwitchMissingClassTest.java
+++ b/src/test/examplesJava21/switchpatternmatching/TypeSwitchMissingClassTest.java
@@ -24,6 +24,8 @@
import org.junit.runners.Parameterized.Parameter;
import org.junit.runners.Parameterized.Parameters;
+// This test is copied into later JDK tests (currently JDK-23). The reason for the copy is that
+// from JDK-23 the code generation changed. Please update the copy as well if updating this test.
@RunWith(Parameterized.class)
public class TypeSwitchMissingClassTest extends TestBase {
diff --git a/src/test/examplesJava21/switchpatternmatching/TypeSwitchTest.java b/src/test/examplesJava21/switchpatternmatching/TypeSwitchTest.java
index 19cee5b..83a428f 100644
--- a/src/test/examplesJava21/switchpatternmatching/TypeSwitchTest.java
+++ b/src/test/examplesJava21/switchpatternmatching/TypeSwitchTest.java
@@ -22,6 +22,8 @@
import org.junit.runners.Parameterized.Parameter;
import org.junit.runners.Parameterized.Parameters;
+// This test is copied into later JDK tests (currently JDK-23). The reason for the copy is that
+// from JDK-23 the code generation changed. Please update the copy as well if updating this test.
@RunWith(Parameterized.class)
public class TypeSwitchTest extends TestBase {
diff --git a/src/test/java23/com/android/tools/r8/java23/switchpatternmatching/EnumLessCasesAtRuntimeSwitchTest.java b/src/test/java23/com/android/tools/r8/java23/switchpatternmatching/EnumLessCasesAtRuntimeSwitchTest.java
new file mode 100644
index 0000000..e367e8a
--- /dev/null
+++ b/src/test/java23/com/android/tools/r8/java23/switchpatternmatching/EnumLessCasesAtRuntimeSwitchTest.java
@@ -0,0 +1,206 @@
+// 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.java23.switchpatternmatching;
+
+import static com.android.tools.r8.DiagnosticsMatcher.diagnosticMessage;
+import static com.android.tools.r8.desugar.switchpatternmatching.SwitchTestHelper.hasJdk21EnumSwitch;
+import static com.android.tools.r8.desugar.switchpatternmatching.SwitchTestHelper.hasJdk21TypeSwitch;
+import static org.hamcrest.CoreMatchers.containsString;
+import static org.junit.Assert.assertThrows;
+import static org.junit.Assert.assertTrue;
+import static org.junit.Assume.assumeTrue;
+
+import com.android.tools.r8.CompilationFailedException;
+import com.android.tools.r8.JdkClassFileProvider;
+import com.android.tools.r8.TestBase;
+import com.android.tools.r8.TestBuilder;
+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.AndroidApiLevel;
+import com.android.tools.r8.utils.StringUtils;
+import com.android.tools.r8.utils.codeinspector.CodeInspector;
+import org.junit.Assume;
+import org.junit.Ignore;
+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;
+
+// This is a copy of the same test from JDK-21. The reason for the copy is that from JDK-23 the
+// code generation for pattern matching switch changed (the bootstrap method signature used in the
+// invokedynamic changed).
+@RunWith(Parameterized.class)
+public class EnumLessCasesAtRuntimeSwitchTest extends TestBase {
+
+ @Parameter public TestParameters parameters;
+
+ @Parameters(name = "{0}")
+ public static TestParametersCollection data() {
+ return getTestParameters().withAllRuntimes().withAllApiLevelsAlsoForCf().build();
+ }
+
+ public static String EXPECTED_OUTPUT =
+ StringUtils.lines("TYPE", "null", "E1", "E3", "E5", "a C", "ENUM", "null", "1", "3", "0");
+
+ @Test
+ public void testJvm() throws Exception {
+ assumeTrue(parameters.isCfRuntime());
+ CodeInspector inspector = new CodeInspector(ToolHelper.getClassFileForTestClass(Main.class));
+ assertTrue(
+ hasJdk21EnumSwitch(inspector.clazz(Main.class).uniqueMethodWithOriginalName("enumSwitch")));
+ assertTrue(
+ hasJdk21TypeSwitch(inspector.clazz(Main.class).uniqueMethodWithOriginalName("typeSwitch")));
+
+ parameters.assumeJvmTestParameters();
+ testForJvm(parameters)
+ .apply(this::addModifiedProgramClasses)
+ .run(parameters.getRuntime(), Main.class)
+ .applyIf(
+ parameters.getCfRuntime().isNewerThanOrEqual(CfVm.JDK23),
+ r -> r.assertSuccessWithOutput(EXPECTED_OUTPUT),
+ r -> r.assertFailureWithErrorThatThrows(UnsupportedClassVersionError.class));
+ }
+
+ private <T extends TestBuilder<?, T>> void addModifiedProgramClasses(
+ TestBuilder<?, T> testBuilder) throws Exception {
+ testBuilder
+ .addStrippedOuter(getClass())
+ .addProgramClasses(FakeI.class, C.class, I.class, Main.class)
+ .addProgramClassFileData(
+ transformer(CompileTimeE.class)
+ .setImplements(FakeI.class)
+ .setClassDescriptor(RuntimeE.class.descriptorString())
+ .transform())
+ .addProgramClassFileData(
+ transformer(RuntimeE.class)
+ .setImplements(I.class)
+ .setClassDescriptor(CompileTimeE.class.descriptorString())
+ .transform());
+ }
+
+ @Test
+ public void testD8() throws Exception {
+ if (parameters.getApiLevel().isGreaterThanOrEqualTo(AndroidApiLevel.O)
+ && parameters.getApiLevel().isLessThan(AndroidApiLevel.BAKLAVA)) {
+ assertThrows(
+ CompilationFailedException.class,
+ () ->
+ testForD8(parameters.getBackend())
+ .apply(this::addModifiedProgramClasses)
+ .setMinApi(parameters)
+ .compileWithExpectedDiagnostics(
+ diagnostics -> {
+ diagnostics.assertOnlyErrors();
+ diagnostics.assertErrorsMatch(
+ diagnosticMessage(
+ containsString("DexValueConstDynamic should be desugared")));
+ }));
+ } else {
+ testForD8(parameters.getBackend())
+ .apply(this::addModifiedProgramClasses)
+ .setMinApi(parameters)
+ .run(parameters.getRuntime(), Main.class)
+ .assertFailure();
+ }
+ }
+
+ @Test
+ @Ignore("TODO(b/382880986) enable test when fixed.")
+ public void testR8() throws Exception {
+ parameters.assumeR8TestParameters();
+ Assume.assumeTrue(
+ parameters.isDexRuntime()
+ || (parameters.isCfRuntime()
+ && parameters.getCfRuntime().isNewerThanOrEqual(CfVm.JDK21)));
+ testForR8(parameters.getBackend())
+ .apply(this::addModifiedProgramClasses)
+ .applyIf(
+ parameters.isCfRuntime(),
+ b -> b.addLibraryProvider(JdkClassFileProvider.fromSystemJdk()))
+ .setMinApi(parameters)
+ .addKeepMainRule(Main.class)
+ .run(parameters.getRuntime(), Main.class)
+ .assertSuccessWithOutput(EXPECTED_OUTPUT);
+ }
+
+ sealed interface I permits CompileTimeE, C {}
+
+ public enum CompileTimeE implements I {
+ E1,
+ E2, // Case missing at runtime.
+ E3,
+ E4, // Case missing at runtime.
+ E5
+ }
+
+ interface FakeI {}
+
+ public enum RuntimeE implements FakeI {
+ E1,
+ E3,
+ E5
+ }
+
+ static final class C implements I {}
+
+ static class Main {
+
+ static void typeSwitch(I i) {
+ switch (i) {
+ case CompileTimeE.E1 -> {
+ System.out.println("E1");
+ }
+ case CompileTimeE.E2 -> { // Case missing at runtime.
+ System.out.println("E2");
+ }
+ case CompileTimeE.E3 -> {
+ System.out.println("E3");
+ }
+ case CompileTimeE.E4 -> { // Case missing at runtime.
+ System.out.println("E4");
+ }
+ case CompileTimeE.E5 -> {
+ System.out.println("E5");
+ }
+ case C c -> {
+ System.out.println("a C");
+ }
+ }
+ }
+
+ static void enumSwitch(CompileTimeE e) {
+ switch (e) {
+ case null -> System.out.println("null");
+ case CompileTimeE.E1 -> System.out.println("1");
+ case CompileTimeE.E2 -> System.out.println("2"); // Case missing at runtime.
+ case CompileTimeE t when t == CompileTimeE.E3 -> System.out.println("3");
+ case CompileTimeE t when t.name().equals("E4") ->
+ System.out.println("4"); // Case missing at runtime.
+ case CompileTimeE t -> System.out.println("0");
+ }
+ }
+
+ public static void main(String[] args) {
+ System.out.println("TYPE");
+ try {
+ typeSwitch(null);
+ } catch (NullPointerException e) {
+ System.out.println("null");
+ }
+ typeSwitch(CompileTimeE.E1);
+ typeSwitch(CompileTimeE.E3);
+ typeSwitch(CompileTimeE.E5);
+ typeSwitch(new C());
+
+ System.out.println("ENUM");
+ enumSwitch(null);
+ enumSwitch(CompileTimeE.E1);
+ enumSwitch(CompileTimeE.E3);
+ enumSwitch(CompileTimeE.E5);
+ }
+ }
+}
diff --git a/src/test/java23/com/android/tools/r8/java23/switchpatternmatching/EnumMoreCasesAtRuntimeSwitchTest.java b/src/test/java23/com/android/tools/r8/java23/switchpatternmatching/EnumMoreCasesAtRuntimeSwitchTest.java
new file mode 100644
index 0000000..8b3f44d
--- /dev/null
+++ b/src/test/java23/com/android/tools/r8/java23/switchpatternmatching/EnumMoreCasesAtRuntimeSwitchTest.java
@@ -0,0 +1,282 @@
+// 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.java23.switchpatternmatching;
+
+import static com.android.tools.r8.DiagnosticsMatcher.diagnosticMessage;
+import static com.android.tools.r8.desugar.switchpatternmatching.SwitchTestHelper.hasJdk21EnumSwitch;
+import static com.android.tools.r8.desugar.switchpatternmatching.SwitchTestHelper.hasJdk21TypeSwitch;
+import static com.android.tools.r8.desugar.switchpatternmatching.SwitchTestHelper.matchException;
+import static org.hamcrest.CoreMatchers.containsString;
+import static org.junit.Assert.assertThrows;
+import static org.junit.Assert.assertTrue;
+import static org.junit.Assume.assumeTrue;
+
+import com.android.tools.r8.CompilationFailedException;
+import com.android.tools.r8.JdkClassFileProvider;
+import com.android.tools.r8.TestBase;
+import com.android.tools.r8.TestBuilder;
+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.AndroidApiLevel;
+import com.android.tools.r8.utils.StringUtils;
+import com.android.tools.r8.utils.codeinspector.CodeInspector;
+import org.junit.Assume;
+import org.junit.Ignore;
+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;
+
+// This is a copy of the same test from JDK-21. The reason for the copy is that from JDK-23 the
+// code generation for pattern matching switch changed (the bootstrap method signature used in the
+// invokedynamic changed).
+@RunWith(Parameterized.class)
+public class EnumMoreCasesAtRuntimeSwitchTest extends TestBase {
+
+ @Parameter public TestParameters parameters;
+
+ @Parameters(name = "{0}")
+ public static TestParametersCollection data() {
+ return getTestParameters().withAllRuntimes().withAllApiLevelsAlsoForCf().build();
+ }
+
+ public static String EXPECTED_OUTPUT =
+ StringUtils.lines(
+ "TYPE",
+ "null",
+ "E1",
+ "class %s",
+ "E3",
+ "class %s",
+ "E5",
+ "a C",
+ "ENUM",
+ "null",
+ "1",
+ "0",
+ "3",
+ "4",
+ "0");
+
+ public static String UNEXPECTED_OUTPUT_R8_DEX =
+ StringUtils.lines(
+ "TYPE",
+ "null",
+ "E1",
+ "class %s",
+ "E3",
+ "class %s",
+ "E5",
+ "a C",
+ "ENUM",
+ "null",
+ "1",
+ "0",
+ "3",
+ "0", // This is the difference from the EXPECTED_OUTPUT.
+ "0");
+
+ @Test
+ public void testJvm() throws Exception {
+ assumeTrue(parameters.isCfRuntime());
+ CodeInspector inspector = new CodeInspector(ToolHelper.getClassFileForTestClass(Main.class));
+ assertTrue(
+ hasJdk21EnumSwitch(inspector.clazz(Main.class).uniqueMethodWithOriginalName("enumSwitch")));
+ assertTrue(
+ hasJdk21TypeSwitch(inspector.clazz(Main.class).uniqueMethodWithOriginalName("typeSwitch")));
+
+ parameters.assumeJvmTestParameters();
+ testForJvm(parameters)
+ .apply(this::addModifiedProgramClasses)
+ .run(parameters.getRuntime(), Main.class)
+ .applyIf(
+ parameters.getCfRuntime().isNewerThanOrEqual(CfVm.JDK23),
+ r ->
+ r.assertSuccessWithOutput(
+ String.format(EXPECTED_OUTPUT, matchException(), matchException())),
+ r -> r.assertFailureWithErrorThatThrows(UnsupportedClassVersionError.class));
+ }
+
+ private <T extends TestBuilder<?, T>> void addModifiedProgramClasses(
+ TestBuilder<?, T> testBuilder) throws Exception {
+ testBuilder
+ .addStrippedOuter(getClass())
+ .addProgramClasses(FakeI.class, C.class, I.class)
+ .addProgramClassFileData(
+ transformer(CompileTimeE.class)
+ .setImplements(FakeI.class)
+ .setClassDescriptor(RuntimeE.class.descriptorString())
+ .transform())
+ .addProgramClassFileData(
+ transformer(RuntimeE.class)
+ .setImplements(I.class)
+ .setClassDescriptor(CompileTimeE.class.descriptorString())
+ .transform())
+ .addProgramClassFileData(
+ transformer(Main.class)
+ .transformFieldInsnInMethod(
+ "getE2",
+ (opcode, owner, name, descriptor, visitor) -> {
+ visitor.visitFieldInsn(opcode, owner, "E2", descriptor);
+ })
+ .transformFieldInsnInMethod(
+ "getE4",
+ (opcode, owner, name, descriptor, visitor) -> {
+ visitor.visitFieldInsn(opcode, owner, "E4", descriptor);
+ })
+ .transform());
+ }
+
+ @Test
+ public void testD8() throws Exception {
+ if (parameters.getApiLevel().isGreaterThanOrEqualTo(AndroidApiLevel.O)
+ && parameters.getApiLevel().isLessThan(AndroidApiLevel.BAKLAVA)) {
+ assertThrows(
+ CompilationFailedException.class,
+ () ->
+ testForD8(parameters.getBackend())
+ .apply(this::addModifiedProgramClasses)
+ .setMinApi(parameters)
+ .compileWithExpectedDiagnostics(
+ diagnostics -> {
+ diagnostics.assertOnlyErrors();
+ diagnostics.assertErrorsMatch(
+ diagnosticMessage(
+ containsString("DexValueConstDynamic should be desugared")));
+ }));
+ } else {
+ testForD8(parameters.getBackend())
+ .apply(this::addModifiedProgramClasses)
+ .setMinApi(parameters)
+ .run(parameters.getRuntime(), Main.class)
+ .assertFailure();
+ }
+ }
+
+ @Test
+ @Ignore("TODO(b/382880986) enable test when fixed.")
+ public void testR8() throws Exception {
+ parameters.assumeR8TestParameters();
+ Assume.assumeTrue(
+ parameters.isDexRuntime()
+ || (parameters.isCfRuntime()
+ && parameters.getCfRuntime().isNewerThanOrEqual(CfVm.JDK21)));
+ testForR8(parameters.getBackend())
+ .apply(this::addModifiedProgramClasses)
+ .applyIf(
+ parameters.isCfRuntime(),
+ b -> b.addLibraryProvider(JdkClassFileProvider.fromSystemJdk()))
+ .setMinApi(parameters)
+ .addKeepMainRule(Main.class)
+ .run(parameters.getRuntime(), Main.class)
+ .applyIf(
+ parameters.isDexRuntime(),
+ // TODO(b/381825147): Should same output.
+ r ->
+ r.assertSuccessWithOutput(
+ String.format(
+ UNEXPECTED_OUTPUT_R8_DEX,
+ matchException(parameters),
+ matchException(parameters))),
+ r ->
+ r.assertSuccessWithOutput(
+ String.format(
+ EXPECTED_OUTPUT, matchException(parameters), matchException(parameters))));
+ }
+
+ sealed interface I permits CompileTimeE, C {}
+
+ public enum RuntimeE implements FakeI {
+ E1,
+ E2, // Case present at runtime.
+ E3,
+ E4, // Case present at runtime.
+ E5
+ }
+
+ interface FakeI {}
+
+ public enum CompileTimeE implements I {
+ E1,
+ E3,
+ E5
+ }
+
+ static final class C implements I {}
+
+ static class Main {
+
+ static void typeSwitch(I i) {
+ switch (i) {
+ case CompileTimeE.E1 -> {
+ System.out.println("E1");
+ }
+ case CompileTimeE.E3 -> {
+ System.out.println("E3");
+ }
+ case CompileTimeE.E5 -> {
+ System.out.println("E5");
+ }
+ case C c -> {
+ System.out.println("a C");
+ }
+ }
+ }
+
+ static void enumSwitch(CompileTimeE e) {
+ switch (e) {
+ case null -> System.out.println("null");
+ case CompileTimeE.E1 -> System.out.println("1");
+ case CompileTimeE t when t == CompileTimeE.E3 -> System.out.println("3");
+ case CompileTimeE t when t.name().equals("E4") ->
+ System.out.println("4"); // Case present at runtime.
+ case CompileTimeE t -> System.out.println("0");
+ }
+ }
+
+ public static CompileTimeE getE2() {
+ // Replaced by RuntimeE.E2;
+ return CompileTimeE.E1;
+ }
+
+ public static CompileTimeE getE4() {
+ // Replaced by RuntimeE.E4;
+ return CompileTimeE.E1;
+ }
+
+ public static void main(String[] args) {
+ System.out.println("TYPE");
+ try {
+ typeSwitch(null);
+ } catch (NullPointerException e) {
+ System.out.println("null");
+ }
+ typeSwitch(CompileTimeE.E1);
+ try {
+ typeSwitch(getE2());
+ } catch (Exception e) {
+ System.out.println(e.getClass().toString());
+ }
+ typeSwitch(CompileTimeE.E3);
+ try {
+ typeSwitch(getE4());
+ } catch (Exception e) {
+ System.out.println(e.getClass().toString());
+ }
+ typeSwitch(CompileTimeE.E5);
+ typeSwitch(new C());
+
+ System.out.println("ENUM");
+ enumSwitch(null);
+ enumSwitch(CompileTimeE.E1);
+ enumSwitch(getE2());
+ enumSwitch(CompileTimeE.E3);
+ enumSwitch(getE4());
+ enumSwitch(CompileTimeE.E5);
+ }
+ }
+}
diff --git a/src/test/java23/com/android/tools/r8/java23/switchpatternmatching/EnumSwitchTest.java b/src/test/java23/com/android/tools/r8/java23/switchpatternmatching/EnumSwitchTest.java
new file mode 100644
index 0000000..ddf16a8
--- /dev/null
+++ b/src/test/java23/com/android/tools/r8/java23/switchpatternmatching/EnumSwitchTest.java
@@ -0,0 +1,203 @@
+// 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.java23.switchpatternmatching;
+
+import static com.android.tools.r8.DiagnosticsMatcher.diagnosticMessage;
+import static com.android.tools.r8.desugar.switchpatternmatching.SwitchTestHelper.hasJdk21TypeSwitch;
+import static com.android.tools.r8.desugar.switchpatternmatching.SwitchTestHelper.matchException;
+import static org.hamcrest.CoreMatchers.containsString;
+import static org.junit.Assert.assertThrows;
+import static org.junit.Assert.assertTrue;
+import static org.junit.Assume.assumeTrue;
+
+import com.android.tools.r8.CompilationFailedException;
+import com.android.tools.r8.JdkClassFileProvider;
+import com.android.tools.r8.TestBase;
+import com.android.tools.r8.TestBuilder;
+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.AndroidApiLevel;
+import com.android.tools.r8.utils.StringUtils;
+import com.android.tools.r8.utils.codeinspector.CodeInspector;
+import org.junit.Assume;
+import org.junit.Ignore;
+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;
+
+// This is a copy of the same test from JDK-21. The reason for the copy is that from JDK-23 the
+// code generation for pattern matching switch changed (the bootstrap method signature used in the
+// invokedynamic changed).
+@RunWith(Parameterized.class)
+public class EnumSwitchTest extends TestBase {
+
+ @Parameter public TestParameters parameters;
+
+ @Parameters(name = "{0}")
+ public static TestParametersCollection data() {
+ return getTestParameters().withAllRuntimes().withAllApiLevelsAlsoForCf().build();
+ }
+
+ public static String EXPECTED_OUTPUT =
+ StringUtils.lines("null", "E1", "E2", "E3", "E4", "a C", "class %s");
+
+ @Test
+ public void testJvm() throws Exception {
+ assumeTrue(parameters.isCfRuntime());
+ CodeInspector inspector = new CodeInspector(ToolHelper.getClassFileForTestClass(Main.class));
+ assertTrue(
+ hasJdk21TypeSwitch(inspector.clazz(Main.class).uniqueMethodWithOriginalName("enumSwitch")));
+
+ parameters.assumeJvmTestParameters();
+ testForJvm(parameters)
+ .apply(this::addModifiedProgramClasses)
+ .run(parameters.getRuntime(), Main.class)
+ .applyIf(
+ parameters.getCfRuntime().isNewerThanOrEqual(CfVm.JDK23),
+ r -> r.assertSuccessWithOutput(String.format(EXPECTED_OUTPUT, matchException())),
+ r -> r.assertFailureWithErrorThatThrows(UnsupportedClassVersionError.class));
+ }
+
+ private <T extends TestBuilder<?, T>> void addModifiedProgramClasses(
+ TestBuilder<?, T> testBuilder) throws Exception {
+ testBuilder
+ .addStrippedOuter(getClass())
+ .addProgramClasses(FakeI.class, E.class, C.class)
+ .addProgramClassFileData(
+ transformer(I.class)
+ .setPermittedSubclasses(I.class, E.class, C.class, D.class)
+ .transform())
+ .addProgramClassFileData(transformer(D.class).setImplements(I.class).transform())
+ .addProgramClassFileData(
+ transformer(Main.class)
+ .transformTypeInsnInMethod(
+ "getD",
+ (opcode, type, visitor) ->
+ visitor.visitTypeInsn(opcode, "switchpatternmatching/EnumSwitchTest$D"))
+ .transformMethodInsnInMethod(
+ "getD",
+ (opcode, owner, name, descriptor, isInterface, visitor) -> {
+ assert name.equals("<init>");
+ visitor.visitMethodInsn(
+ opcode,
+ "switchpatternmatching/EnumSwitchTest$D",
+ name,
+ descriptor,
+ isInterface);
+ })
+ .transform());
+ }
+
+ @Test
+ public void testD8() throws Exception {
+ if (parameters.getApiLevel().isGreaterThanOrEqualTo(AndroidApiLevel.O)
+ && parameters.getApiLevel().isLessThan(AndroidApiLevel.BAKLAVA)) {
+ assertThrows(
+ CompilationFailedException.class,
+ () ->
+ testForD8(parameters.getBackend())
+ .apply(this::addModifiedProgramClasses)
+ .setMinApi(parameters)
+ .compileWithExpectedDiagnostics(
+ diagnostics -> {
+ diagnostics.assertOnlyErrors();
+ diagnostics.assertErrorsMatch(
+ diagnosticMessage(
+ containsString("DexValueConstDynamic should be desugared")));
+ }));
+ } else {
+ testForD8(parameters.getBackend())
+ .apply(this::addModifiedProgramClasses)
+ .setMinApi(parameters)
+ .run(parameters.getRuntime(), Main.class)
+ .assertFailure();
+ }
+ }
+
+ @Test
+ @Ignore("TODO(b/382880986) enable test when fixed.")
+ public void testR8() throws Exception {
+ parameters.assumeR8TestParameters();
+ Assume.assumeTrue(
+ parameters.isDexRuntime()
+ || (parameters.isCfRuntime()
+ && parameters.getCfRuntime().isNewerThanOrEqual(CfVm.JDK21)));
+ testForR8(parameters.getBackend())
+ .apply(this::addModifiedProgramClasses)
+ .applyIf(
+ parameters.isCfRuntime(),
+ b -> b.addLibraryProvider(JdkClassFileProvider.fromSystemJdk()))
+ .setMinApi(parameters)
+ .addKeepMainRule(Main.class)
+ .run(parameters.getRuntime(), Main.class)
+ .assertSuccessWithOutput(String.format(EXPECTED_OUTPUT, matchException(parameters)));
+ }
+
+ // D is added to the list of permitted subclasses to reproduce the MatchException.
+ sealed interface I permits E, C {}
+
+ interface FakeI {}
+
+ public enum E implements I {
+ E1,
+ E2,
+ E3,
+ E4
+ }
+
+ // Replaced with I.
+ static final class D implements FakeI {}
+
+ 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());
+ try {
+ enumSwitch(getD());
+ } catch (Throwable t) {
+ System.out.println(t.getClass());
+ }
+ }
+
+ public static I getD() {
+ // Replaced by new D();
+ return new C();
+ }
+ }
+}
diff --git a/src/test/examplesJava21/switchpatternmatching/EnumSwitchUsingEnumSwitchBootstrapMethod.java b/src/test/java23/com/android/tools/r8/java23/switchpatternmatching/EnumSwitchUsingEnumSwitchBootstrapMethodTest.java
similarity index 91%
copy from src/test/examplesJava21/switchpatternmatching/EnumSwitchUsingEnumSwitchBootstrapMethod.java
copy to src/test/java23/com/android/tools/r8/java23/switchpatternmatching/EnumSwitchUsingEnumSwitchBootstrapMethodTest.java
index 095620c..4f68404 100644
--- a/src/test/examplesJava21/switchpatternmatching/EnumSwitchUsingEnumSwitchBootstrapMethod.java
+++ b/src/test/java23/com/android/tools/r8/java23/switchpatternmatching/EnumSwitchUsingEnumSwitchBootstrapMethodTest.java
@@ -1,7 +1,7 @@
// 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;
+package com.android.tools.r8.java23.switchpatternmatching;
import static com.android.tools.r8.desugar.switchpatternmatching.SwitchTestHelper.hasJdk21EnumSwitch;
import static org.junit.Assert.assertTrue;
@@ -22,8 +22,11 @@
import org.junit.runners.Parameterized.Parameter;
import org.junit.runners.Parameterized.Parameters;
+// This is a copy of the same test from JDK-21. The reason for the copy is that from JDK-23 the
+// code generation for pattern matching switch changed (the bootstrap method signature used in the
+// invokedynamic changed).
@RunWith(Parameterized.class)
-public class EnumSwitchUsingEnumSwitchBootstrapMethod extends TestBase {
+public class EnumSwitchUsingEnumSwitchBootstrapMethodTest extends TestBase {
@Parameter public TestParameters parameters;
@@ -64,7 +67,7 @@
.addInnerClassesAndStrippedOuter(getClass())
.run(parameters.getRuntime(), Main.class)
.applyIf(
- parameters.getCfRuntime().isNewerThanOrEqual(CfVm.JDK21),
+ parameters.getCfRuntime().isNewerThanOrEqual(CfVm.JDK23),
r -> r.assertSuccessWithOutput(EXPECTED_OUTPUT),
r -> r.assertFailureWithErrorThatThrows(UnsupportedClassVersionError.class));
}
@@ -85,7 +88,7 @@
Assume.assumeTrue(
parameters.isDexRuntime()
|| (parameters.isCfRuntime()
- && parameters.getCfRuntime().isNewerThanOrEqual(CfVm.JDK21)));
+ && parameters.getCfRuntime().isNewerThanOrEqual(CfVm.JDK23)));
testForR8(parameters.getBackend())
.addInnerClassesAndStrippedOuter(getClass())
.applyIf(
diff --git a/src/test/java23/com/android/tools/r8/java23/switchpatternmatching/StringSwitchTest.java b/src/test/java23/com/android/tools/r8/java23/switchpatternmatching/StringSwitchTest.java
index 1f0a294..38f7007 100644
--- a/src/test/java23/com/android/tools/r8/java23/switchpatternmatching/StringSwitchTest.java
+++ b/src/test/java23/com/android/tools/r8/java23/switchpatternmatching/StringSwitchTest.java
@@ -27,7 +27,8 @@
import org.junit.runners.Parameterized.Parameters;
// This is a copy of the same test from JDK-21. The reason for the copy is that from JDK-23 the
-// code generation changed.
+// code generation for pattern matching switch changed (the bootstrap method signature used in the
+// invokedynamic changed).
@RunWith(Parameterized.class)
public class StringSwitchTest extends TestBase {
diff --git a/src/test/java23/com/android/tools/r8/java23/switchpatternmatching/TypeSwitchMissingClassTest.java b/src/test/java23/com/android/tools/r8/java23/switchpatternmatching/TypeSwitchMissingClassTest.java
new file mode 100644
index 0000000..8bb22d0
--- /dev/null
+++ b/src/test/java23/com/android/tools/r8/java23/switchpatternmatching/TypeSwitchMissingClassTest.java
@@ -0,0 +1,162 @@
+// 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.java23.switchpatternmatching;
+
+import static com.android.tools.r8.desugar.switchpatternmatching.SwitchTestHelper.hasJdk21TypeSwitch;
+import static org.junit.Assert.assertTrue;
+import static org.junit.Assume.assumeTrue;
+
+import com.android.tools.r8.JdkClassFileProvider;
+import com.android.tools.r8.TestBase;
+import com.android.tools.r8.TestBuilder;
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.TestRunResult;
+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 java.util.List;
+import org.junit.Assume;
+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;
+
+// This is a copy of the same test from JDK-21. The reason for the copy is that from JDK-23 the
+// code generation for pattern matching switch changed (the bootstrap method signature used in the
+// invokedynamic changed).
+@RunWith(Parameterized.class)
+public class TypeSwitchMissingClassTest extends TestBase {
+
+ @Parameter(0)
+ public TestParameters parameters;
+
+ @Parameter(1)
+ public ClassHolder present;
+
+ @Parameters(name = "{0}, {1}")
+ public static List<Object[]> data() {
+ return buildParameters(
+ getTestParameters().withAllRuntimes().withAllApiLevelsAlsoForCf().build(),
+ List.of(new ClassHolder(C.class), new ClassHolder(Color.class)));
+ }
+
+ // ClassHolder allows to correctly print parameters in the IntelliJ test IDE with {1}.
+ private static class ClassHolder {
+ public final Class<?> clazz;
+
+ private ClassHolder(Class<?> clazz) {
+ this.clazz = clazz;
+ }
+
+ @Override
+ public String toString() {
+ return clazz.getSimpleName();
+ }
+ }
+
+ public static String EXPECTED_OUTPUT =
+ StringUtils.lines("null", "String", "Array of int, length = 0", "Other");
+
+ @Test
+ public void testJvm() throws Exception {
+ assumeTrue(parameters.isCfRuntime());
+ CodeInspector inspector = new CodeInspector(ToolHelper.getClassFileForTestClass(Main.class));
+ assertTrue(
+ hasJdk21TypeSwitch(inspector.clazz(Main.class).uniqueMethodWithOriginalName("typeSwitch")));
+
+ parameters.assumeJvmTestParameters();
+ testForJvm(parameters)
+ .apply(this::addModifiedProgramClasses)
+ .run(parameters.getRuntime(), Main.class)
+ .applyIf(
+ parameters.getCfRuntime().isNewerThanOrEqual(CfVm.JDK23),
+ this::assertResult,
+ r -> r.assertFailureWithErrorThatThrows(UnsupportedClassVersionError.class));
+ }
+
+ private void assertResult(TestRunResult<?> r) {
+ if (present.clazz.equals(C.class)) {
+ r.assertSuccessWithOutput(EXPECTED_OUTPUT);
+ } else {
+ r.assertFailureWithErrorThatThrows(NoClassDefFoundError.class);
+ }
+ }
+
+ private <T extends TestBuilder<?, T>> void addModifiedProgramClasses(
+ TestBuilder<?, T> testBuilder) throws Exception {
+ testBuilder
+ .addProgramClassFileData(transformer(Main.class).clearNest().transform())
+ .addProgramClassFileData(transformer(present.clazz).clearNest().transform());
+ }
+
+ @Test
+ public void testD8() throws Exception {
+ testForD8(parameters.getBackend())
+ .apply(this::addModifiedProgramClasses)
+ .setMinApi(parameters)
+ .run(parameters.getRuntime(), Main.class)
+ .apply(this::assertResult);
+ }
+
+ @Test
+ public void testR8() throws Exception {
+ parameters.assumeR8TestParameters();
+ Assume.assumeTrue(
+ parameters.isDexRuntime()
+ || (parameters.isCfRuntime()
+ && parameters.getCfRuntime().isNewerThanOrEqual(CfVm.JDK23)));
+ testForR8(parameters.getBackend())
+ .apply(this::addModifiedProgramClasses)
+ .applyIf(
+ parameters.isCfRuntime(),
+ b -> b.addLibraryProvider(JdkClassFileProvider.fromSystemJdk()))
+ .addIgnoreWarnings(present.clazz.equals(Color.class))
+ .allowDiagnosticWarningMessages(present.clazz.equals(Color.class))
+ .addKeepMainRule(Main.class)
+ .setMinApi(parameters)
+ .run(parameters.getRuntime(), Main.class)
+ .apply(this::assertResult);
+ }
+
+ // Enum will be missing at runtime.
+ enum Color {
+ RED,
+ GREEN,
+ BLUE;
+ }
+
+ // Class will be missing at runtime.
+ static class C {
+
+ @Override
+ public String toString() {
+ return "CCC";
+ }
+ }
+
+ static class Main {
+
+ static void typeSwitch(Object obj) {
+ switch (obj) {
+ case null -> System.out.println("null");
+ case Color.RED -> System.out.println("RED!!!");
+ case Color.BLUE -> System.out.println("BLUE!!!");
+ case Color.GREEN -> System.out.println("GREEN!!!");
+ case String string -> System.out.println("String");
+ case C c -> System.out.println(c.toString() + "!!!");
+ 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(new int[] {});
+ typeSwitch(new Object());
+ }
+ }
+}
diff --git a/src/test/java23/com/android/tools/r8/java23/switchpatternmatching/TypeSwitchTest.java b/src/test/java23/com/android/tools/r8/java23/switchpatternmatching/TypeSwitchTest.java
new file mode 100644
index 0000000..05584bc
--- /dev/null
+++ b/src/test/java23/com/android/tools/r8/java23/switchpatternmatching/TypeSwitchTest.java
@@ -0,0 +1,120 @@
+// 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.java23.switchpatternmatching;
+
+import static com.android.tools.r8.desugar.switchpatternmatching.SwitchTestHelper.hasJdk21TypeSwitch;
+import static org.junit.Assert.assertTrue;
+import static org.junit.Assume.assumeTrue;
+
+import com.android.tools.r8.JdkClassFileProvider;
+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 org.junit.Assume;
+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;
+
+// This is a copy of the same test from JDK-21. The reason for the copy is that from JDK-23 the
+// code generation for pattern matching switch changed (the bootstrap method signature used in the
+// invokedynamic changed).
+@RunWith(Parameterized.class)
+public class TypeSwitchTest extends TestBase {
+
+ @Parameter public TestParameters parameters;
+
+ @Parameters(name = "{0}")
+ public static TestParametersCollection data() {
+ return getTestParameters().withAllRuntimes().withAllApiLevelsAlsoForCf().build();
+ }
+
+ public static String EXPECTED_OUTPUT =
+ StringUtils.lines(
+ "null", "String", "Color: RED", "Point: [0;0]", "Array of int, length = 0", "Other");
+
+ @Test
+ public void testJvm() throws Exception {
+ assumeTrue(parameters.isCfRuntime());
+ CodeInspector inspector = new CodeInspector(ToolHelper.getClassFileForTestClass(Main.class));
+ assertTrue(
+ hasJdk21TypeSwitch(inspector.clazz(Main.class).uniqueMethodWithOriginalName("typeSwitch")));
+
+ parameters.assumeJvmTestParameters();
+ testForJvm(parameters)
+ .addInnerClassesAndStrippedOuter(getClass())
+ .run(parameters.getRuntime(), Main.class)
+ .applyIf(
+ parameters.getCfRuntime().isNewerThanOrEqual(CfVm.JDK23),
+ r -> r.assertSuccessWithOutput(EXPECTED_OUTPUT),
+ r -> r.assertFailureWithErrorThatThrows(UnsupportedClassVersionError.class));
+ }
+
+ @Test
+ public void testD8() throws Exception {
+ testForD8(parameters.getBackend())
+ .addInnerClassesAndStrippedOuter(getClass())
+ .setMinApi(parameters)
+ .run(parameters.getRuntime(), Main.class)
+ .applyIf(
+ isRecordsFullyDesugaredForD8(parameters)
+ || runtimeWithRecordsSupport(parameters.getRuntime()),
+ r -> r.assertSuccessWithOutput(EXPECTED_OUTPUT),
+ r -> r.assertFailureWithErrorThatThrows(NoClassDefFoundError.class));
+ }
+
+ @Test
+ public void testR8() throws Exception {
+ parameters.assumeR8TestParameters();
+ Assume.assumeTrue(
+ parameters.isDexRuntime()
+ || (parameters.isCfRuntime()
+ && parameters.getCfRuntime().isNewerThanOrEqual(CfVm.JDK23)));
+ testForR8(parameters.getBackend())
+ .addInnerClassesAndStrippedOuter(getClass())
+ .applyIf(
+ parameters.isCfRuntime(),
+ b -> b.addLibraryProvider(JdkClassFileProvider.fromSystemJdk()))
+ .addKeepMainRule(Main.class)
+ .setMinApi(parameters)
+ .run(parameters.getRuntime(), Main.class)
+ .assertSuccessWithOutput(EXPECTED_OUTPUT);
+ }
+
+ record Point(int i, int j) {}
+
+ enum Color {
+ RED,
+ GREEN,
+ BLUE;
+ }
+
+ static class Main {
+
+ 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("Point: [" + point.i + ";" + point.j + "]");
+ 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());
+ }
+ }
+}