Reproduce missing string switch desugaring when using JDK-23

Code generation of the bootstrap method signature changed.

Bug: b/382880986
Change-Id: Id49898891c75780f4122adb05c652aee50e9ea1c
diff --git a/src/test/examplesJava21/switchpatternmatching/EnumLessCasesAtRuntimeSwitchTest.java b/src/test/examplesJava21/switchpatternmatching/EnumLessCasesAtRuntimeSwitchTest.java
index dbb7dcc..f9d6bc9 100644
--- a/src/test/examplesJava21/switchpatternmatching/EnumLessCasesAtRuntimeSwitchTest.java
+++ b/src/test/examplesJava21/switchpatternmatching/EnumLessCasesAtRuntimeSwitchTest.java
@@ -3,10 +3,10 @@
 // BSD-style license that can be found in the LICENSE file.
 package switchpatternmatching;
 
+import static com.android.tools.r8.desugar.switchpatternmatching.SwitchTestHelper.hasJdk21EnumSwitch;
+import static com.android.tools.r8.desugar.switchpatternmatching.SwitchTestHelper.hasJdk21TypeSwitch;
 import static org.junit.Assert.assertTrue;
 import static org.junit.Assume.assumeTrue;
-import static switchpatternmatching.SwitchTestHelper.hasJdk21EnumSwitch;
-import static switchpatternmatching.SwitchTestHelper.hasJdk21TypeSwitch;
 
 import com.android.tools.r8.JdkClassFileProvider;
 import com.android.tools.r8.TestBase;
diff --git a/src/test/examplesJava21/switchpatternmatching/EnumMoreCasesAtRuntimeSwitchTest.java b/src/test/examplesJava21/switchpatternmatching/EnumMoreCasesAtRuntimeSwitchTest.java
index 3846dd4..77a6e53 100644
--- a/src/test/examplesJava21/switchpatternmatching/EnumMoreCasesAtRuntimeSwitchTest.java
+++ b/src/test/examplesJava21/switchpatternmatching/EnumMoreCasesAtRuntimeSwitchTest.java
@@ -3,12 +3,12 @@
 // BSD-style license that can be found in the LICENSE file.
 package switchpatternmatching;
 
+import static com.android.tools.r8.desugar.switchpatternmatching.SwitchTestHelper.desugarMatchException;
+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.junit.Assert.assertTrue;
 import static org.junit.Assume.assumeTrue;
-import static switchpatternmatching.SwitchTestHelper.desugarMatchException;
-import static switchpatternmatching.SwitchTestHelper.hasJdk21EnumSwitch;
-import static switchpatternmatching.SwitchTestHelper.hasJdk21TypeSwitch;
-import static switchpatternmatching.SwitchTestHelper.matchException;
 
 import com.android.tools.r8.JdkClassFileProvider;
 import com.android.tools.r8.TestBase;
diff --git a/src/test/examplesJava21/switchpatternmatching/EnumSwitchTest.java b/src/test/examplesJava21/switchpatternmatching/EnumSwitchTest.java
index 9cdfb74..bd16f68 100644
--- a/src/test/examplesJava21/switchpatternmatching/EnumSwitchTest.java
+++ b/src/test/examplesJava21/switchpatternmatching/EnumSwitchTest.java
@@ -3,11 +3,11 @@
 // BSD-style license that can be found in the LICENSE file.
 package switchpatternmatching;
 
+import static com.android.tools.r8.desugar.switchpatternmatching.SwitchTestHelper.desugarMatchException;
+import static com.android.tools.r8.desugar.switchpatternmatching.SwitchTestHelper.hasJdk21TypeSwitch;
+import static com.android.tools.r8.desugar.switchpatternmatching.SwitchTestHelper.matchException;
 import static org.junit.Assert.assertTrue;
 import static org.junit.Assume.assumeTrue;
-import static switchpatternmatching.SwitchTestHelper.desugarMatchException;
-import static switchpatternmatching.SwitchTestHelper.hasJdk21TypeSwitch;
-import static switchpatternmatching.SwitchTestHelper.matchException;
 
 import com.android.tools.r8.JdkClassFileProvider;
 import com.android.tools.r8.TestBase;
diff --git a/src/test/examplesJava21/switchpatternmatching/EnumSwitchUsingEnumSwitchBootstrapMethod.java b/src/test/examplesJava21/switchpatternmatching/EnumSwitchUsingEnumSwitchBootstrapMethod.java
index aa6d288..095620c 100644
--- a/src/test/examplesJava21/switchpatternmatching/EnumSwitchUsingEnumSwitchBootstrapMethod.java
+++ b/src/test/examplesJava21/switchpatternmatching/EnumSwitchUsingEnumSwitchBootstrapMethod.java
@@ -3,9 +3,9 @@
 // BSD-style license that can be found in the LICENSE file.
 package switchpatternmatching;
 
+import static com.android.tools.r8.desugar.switchpatternmatching.SwitchTestHelper.hasJdk21EnumSwitch;
 import static org.junit.Assert.assertTrue;
 import static org.junit.Assume.assumeTrue;
-import static switchpatternmatching.SwitchTestHelper.hasJdk21EnumSwitch;
 
 import com.android.tools.r8.JdkClassFileProvider;
 import com.android.tools.r8.TestBase;
diff --git a/src/test/examplesJava21/switchpatternmatching/StringSwitchTest.java b/src/test/examplesJava21/switchpatternmatching/StringSwitchTest.java
index 27c3374..150951a 100644
--- a/src/test/examplesJava21/switchpatternmatching/StringSwitchTest.java
+++ b/src/test/examplesJava21/switchpatternmatching/StringSwitchTest.java
@@ -3,9 +3,9 @@
 // BSD-style license that can be found in the LICENSE file.
 package switchpatternmatching;
 
+import static com.android.tools.r8.desugar.switchpatternmatching.SwitchTestHelper.hasJdk21TypeSwitch;
 import static org.junit.Assert.assertTrue;
 import static org.junit.Assume.assumeTrue;
-import static switchpatternmatching.SwitchTestHelper.hasJdk21TypeSwitch;
 
 import com.android.tools.r8.JdkClassFileProvider;
 import com.android.tools.r8.TestBase;
@@ -21,8 +21,9 @@
 import org.junit.runners.Parameterized;
 import org.junit.runners.Parameterized.Parameter;
 import org.junit.runners.Parameterized.Parameters;
-import switchpatternmatching.TypeSwitchTest.Main;
 
+// 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 StringSwitchTest extends TestBase {
 
diff --git a/src/test/examplesJava21/switchpatternmatching/TypeSwitchMissingClassTest.java b/src/test/examplesJava21/switchpatternmatching/TypeSwitchMissingClassTest.java
index 82dc0d1..cd2e035 100644
--- a/src/test/examplesJava21/switchpatternmatching/TypeSwitchMissingClassTest.java
+++ b/src/test/examplesJava21/switchpatternmatching/TypeSwitchMissingClassTest.java
@@ -3,9 +3,9 @@
 // BSD-style license that can be found in the LICENSE file.
 package switchpatternmatching;
 
+import static com.android.tools.r8.desugar.switchpatternmatching.SwitchTestHelper.hasJdk21TypeSwitch;
 import static org.junit.Assert.assertTrue;
 import static org.junit.Assume.assumeTrue;
-import static switchpatternmatching.SwitchTestHelper.hasJdk21TypeSwitch;
 
 import com.android.tools.r8.JdkClassFileProvider;
 import com.android.tools.r8.TestBase;
diff --git a/src/test/examplesJava21/switchpatternmatching/TypeSwitchTest.java b/src/test/examplesJava21/switchpatternmatching/TypeSwitchTest.java
index 9002b29..19cee5b 100644
--- a/src/test/examplesJava21/switchpatternmatching/TypeSwitchTest.java
+++ b/src/test/examplesJava21/switchpatternmatching/TypeSwitchTest.java
@@ -3,9 +3,9 @@
 // BSD-style license that can be found in the LICENSE file.
 package switchpatternmatching;
 
+import static com.android.tools.r8.desugar.switchpatternmatching.SwitchTestHelper.hasJdk21TypeSwitch;
 import static org.junit.Assert.assertTrue;
 import static org.junit.Assume.assumeTrue;
-import static switchpatternmatching.SwitchTestHelper.hasJdk21TypeSwitch;
 
 import com.android.tools.r8.JdkClassFileProvider;
 import com.android.tools.r8.TestBase;
@@ -21,7 +21,6 @@
 import org.junit.runners.Parameterized;
 import org.junit.runners.Parameterized.Parameter;
 import org.junit.runners.Parameterized.Parameters;
-import switchpatternmatching.StringSwitchTest.Main;
 
 @RunWith(Parameterized.class)
 public class TypeSwitchTest extends TestBase {
diff --git a/src/test/java23/com/android/tools/r8/java23/switchpatternmatching/StringSwitchRegress382880986Test.java b/src/test/java23/com/android/tools/r8/java23/switchpatternmatching/StringSwitchRegress382880986Test.java
new file mode 100644
index 0000000..d6c5acb
--- /dev/null
+++ b/src/test/java23/com/android/tools/r8/java23/switchpatternmatching/StringSwitchRegress382880986Test.java
@@ -0,0 +1,111 @@
+// 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.hamcrest.CoreMatchers.containsString;
+import static org.junit.Assert.assertTrue;
+
+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.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;
+
+@RunWith(Parameterized.class)
+public class StringSwitchRegress382880986Test extends TestBase {
+
+  @Parameter(0)
+  public TestParameters parameters;
+
+  @Parameters(name = "{0}")
+  public static TestParametersCollection data() {
+    return getTestParameters().withAllRuntimesAndApiLevels().build();
+  }
+
+  private static final String EXPECTED_OUTPUT = StringUtils.lines("1", "2", "3");
+
+  @Test
+  public void testJvm() throws Exception {
+    parameters.assumeJvmTestParameters();
+
+    CodeInspector inspector =
+        new CodeInspector(ToolHelper.getClassFileForTestClass(TestClass.class));
+    assertTrue(
+        hasJdk21TypeSwitch(inspector.clazz(TestClass.class).uniqueMethodWithOriginalName("m")));
+
+    testForJvm(parameters)
+        .addInnerClasses(getClass())
+        .run(parameters.getRuntime(), TestClass.class, "hello", "goodbye", "")
+        .applyIf(
+            parameters.getCfRuntime().isNewerThanOrEqual(CfVm.JDK23),
+            r -> r.assertSuccessWithOutput(EXPECTED_OUTPUT),
+            r -> r.assertFailureWithErrorThatThrows(UnsupportedClassVersionError.class));
+  }
+
+  @Test
+  public void testD8() throws Exception {
+    parameters.assumeDexRuntime();
+    testForD8(parameters.getBackend())
+        .addInnerClassesAndStrippedOuter(getClass())
+        .setMinApi(parameters)
+        .run(parameters.getRuntime(), TestClass.class, "hello", "goodbye", "")
+        // TODO(b/382880986): This should not fail.
+        .applyIf(
+            parameters.getApiLevel().isLessThan(AndroidApiLevel.O),
+            r ->
+                r.assertFailureWithErrorThatMatches(
+                    containsString("Instruction is unrepresentable in DEX")),
+            r -> r.assertFailureWithErrorThatThrows(NoClassDefFoundError.class));
+  }
+
+  @Test
+  @Ignore("TODO(b/382880986) enable test when fixed.")
+  public void testR8() throws Exception {
+    Assume.assumeTrue(
+        parameters.isDexRuntime()
+            || (parameters.isCfRuntime()
+                && parameters.getCfRuntime().isNewerThanOrEqual(CfVm.JDK23)));
+    testForR8(parameters.getBackend())
+        .addInnerClassesAndStrippedOuter(getClass())
+        .applyIf(
+            parameters.isCfRuntime(),
+            b -> b.addLibraryProvider(JdkClassFileProvider.fromSystemJdk()))
+        .setMinApi(parameters)
+        .addKeepMainRule(TestClass.class)
+        .run(parameters.getRuntime(), TestClass.class, "hello", "goodbye", "")
+        .assertSuccessWithOutput(EXPECTED_OUTPUT);
+  }
+
+  static class TestClass {
+    static final String hello = "hello";
+
+    static void m(String s) {
+      switch (s) {
+        case hello -> System.out.println(1);
+        case "goodbye" -> {
+          System.out.println(2);
+        }
+        case null, default -> System.out.println(3);
+      }
+    }
+
+    public static void main(String[] args) {
+      m(args[0]);
+      m(args[1]);
+      m(args[2]);
+    }
+  }
+}
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
new file mode 100644
index 0000000..1f0a294
--- /dev/null
+++ b/src/test/java23/com/android/tools/r8/java23/switchpatternmatching/StringSwitchTest.java
@@ -0,0 +1,148 @@
+// 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.ToolHelper.DexVm.Version.V6_0_1;
+import static com.android.tools.r8.desugar.switchpatternmatching.SwitchTestHelper.hasJdk21TypeSwitch;
+import static org.hamcrest.CoreMatchers.containsString;
+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.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 changed.
+@RunWith(Parameterized.class)
+public class StringSwitchTest 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", "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));
+    assertTrue(
+        hasJdk21TypeSwitch(
+            inspector.clazz(Main.class).uniqueMethodWithOriginalName("stringSwitch")));
+
+    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)
+        // TODO(b/382880986): This should not fail.
+        .applyIf(
+            parameters.isDexRuntime() && parameters.asDexRuntime().getVersion().isEqualTo(V6_0_1),
+            r ->
+                r.assertFailureWithErrorThatMatches(
+                    containsString(
+                        "Attempt to invoke virtual method 'boolean"
+                            + " java.lang.String.equalsIgnoreCase(java.lang.String)' on a null"
+                            + " object reference")),
+            parameters.getApiLevel().isLessThan(AndroidApiLevel.O),
+            r ->
+                r.assertFailureWithErrorThatMatches(
+                    containsString("Instruction is unrepresentable in DEX")),
+            parameters.isCfRuntime()
+                && (parameters.asCfRuntime().isNewerThanOrEqual(CfVm.JDK17)
+                    && parameters.asCfRuntime().isOlderThan(CfVm.JDK23)),
+            r -> r.assertFailureWithErrorThatThrows(BootstrapMethodError.class),
+            !parameters.isCfRuntime()
+                || parameters.isCfRuntime() && parameters.asCfRuntime().isOlderThan(CfVm.JDK17),
+            r -> r.assertFailureWithErrorThatThrows(NoClassDefFoundError.class),
+            r -> r.assertSuccessWithOutput(EXPECTED_OUTPUT));
+  }
+
+  @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())
+        .addInnerClassesAndStrippedOuter(getClass())
+        .applyIf(
+            parameters.isCfRuntime(),
+            b -> b.addLibraryProvider(JdkClassFileProvider.fromSystemJdk()))
+        .setMinApi(parameters)
+        .addKeepMainRule(Main.class)
+        .run(parameters.getRuntime(), Main.class)
+        .assertSuccessWithOutput(EXPECTED_OUTPUT);
+  }
+
+  static class Main {
+    static void stringSwitch(String string) {
+      switch (string) {
+        case null -> {
+          System.out.println("null");
+        }
+        case String s when s.equalsIgnoreCase("YES") -> {
+          System.out.println("yes");
+        }
+        case "y", "Y" -> {
+          System.out.println("y or Y");
+        }
+        case String s when s.equalsIgnoreCase("NO") -> {
+          System.out.println("no");
+        }
+        case "n", "N" -> {
+          System.out.println("n or N");
+        }
+        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/SwitchTestHelper.java b/src/test/testbase/java/com/android/tools/r8/desugar/switchpatternmatching/SwitchTestHelper.java
similarity index 96%
rename from src/test/examplesJava21/switchpatternmatching/SwitchTestHelper.java
rename to src/test/testbase/java/com/android/tools/r8/desugar/switchpatternmatching/SwitchTestHelper.java
index 03ad175..d6ffd0f 100644
--- a/src/test/examplesJava21/switchpatternmatching/SwitchTestHelper.java
+++ b/src/test/testbase/java/com/android/tools/r8/desugar/switchpatternmatching/SwitchTestHelper.java
@@ -2,7 +2,7 @@
 // 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.desugar.switchpatternmatching;
 
 import com.android.tools.r8.TestParameters;
 import com.android.tools.r8.utils.codeinspector.InstructionSubject;