Extend char conversion tests for StringBuilder optimizer

Bug: b/384844007
Change-Id: Ieea67366da780d48649b66e9c44c172a27a08d74
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/string/B384844007Test.java b/src/test/java/com/android/tools/r8/ir/optimize/string/B384844007Test.java
deleted file mode 100644
index 748e0c2..0000000
--- a/src/test/java/com/android/tools/r8/ir/optimize/string/B384844007Test.java
+++ /dev/null
@@ -1,68 +0,0 @@
-// 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.ir.optimize.string;
-
-import com.android.tools.r8.TestBase;
-import com.android.tools.r8.TestParameters;
-import com.android.tools.r8.TestParametersCollection;
-import com.android.tools.r8.utils.StringUtils;
-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 B384844007Test 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("i = 65523");
-
-  @Test
-  public void testJvm() throws Exception {
-    parameters.assumeJvmTestParameters();
-    testForJvm(parameters)
-        .addInnerClasses(getClass())
-        .run(parameters.getRuntime(), TestClass.class)
-        .assertSuccessWithOutput(EXPECTED_OUTPUT);
-  }
-
-  @Test
-  public void testD8() throws Exception {
-    parameters.assumeDexRuntime();
-    testForD8(parameters.getBackend())
-        .addInnerClasses(getClass())
-        .setMinApi(parameters)
-        .run(parameters.getRuntime(), TestClass.class)
-        .assertSuccessWithOutput(EXPECTED_OUTPUT);
-  }
-
-  @Test
-  public void testR8() throws Exception {
-    testForR8(parameters.getBackend())
-        .addInnerClasses(getClass())
-        .addKeepMainRule(TestClass.class)
-        .setMinApi(parameters)
-        .run(parameters.getRuntime(), TestClass.class)
-        .assertSuccessWithOutput(EXPECTED_OUTPUT);
-  }
-
-  static class TestClass {
-
-    public static void main(String[] args) {
-      int i = -13;
-      char c = (char) i;
-      i = c;
-      System.out.println("i = " + i);
-    }
-  }
-}
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/string/StringBuilderWithCharConversionTest.java b/src/test/java/com/android/tools/r8/ir/optimize/string/StringBuilderWithCharConversionTest.java
new file mode 100644
index 0000000..b83eb50
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/ir/optimize/string/StringBuilderWithCharConversionTest.java
@@ -0,0 +1,140 @@
+// 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.ir.optimize.string;
+
+import static org.junit.Assert.assertTrue;
+
+import com.android.tools.r8.NeverInline;
+import com.android.tools.r8.TestBase;
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.TestParametersCollection;
+import com.android.tools.r8.utils.StringUtils;
+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 StringBuilderWithCharConversionTest 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("i = 65523", "i = 13", "i = 65535", "i = 65535", "i = 32767", "i = 32768");
+
+  @Test
+  public void testJvm() throws Exception {
+    parameters.assumeJvmTestParameters();
+    testForJvm(parameters)
+        .addInnerClasses(getClass())
+        .run(parameters.getRuntime(), TestClass.class)
+        .assertSuccessWithOutput(EXPECTED_OUTPUT);
+  }
+
+  @Test
+  public void testD8() throws Exception {
+    parameters.assumeDexRuntime();
+    testForD8(parameters.getBackend())
+        .addInnerClasses(getClass())
+        .setMinApi(parameters)
+        .run(parameters.getRuntime(), TestClass.class)
+        .assertSuccessWithOutput(EXPECTED_OUTPUT);
+  }
+
+  @Test
+  public void testR8() throws Exception {
+    testForR8(parameters.getBackend())
+        .addInnerClasses(getClass())
+        .addKeepMainRule(TestClass.class)
+        .setMinApi(parameters)
+        .enableInliningAnnotations()
+        .run(parameters.getRuntime(), TestClass.class)
+        .inspect(
+            inspector -> {
+              for (int i = 1; i < 6; i++) {
+                assertTrue(
+                    inspector
+                        .clazz(TestClass.class)
+                        .uniqueMethodWithOriginalName("f" + i)
+                        .streamInstructions()
+                        .noneMatch(
+                            instruction ->
+                                instruction.isConstNumber() || instruction.isNumberConversion()));
+              }
+            })
+        .assertSuccessWithOutput(EXPECTED_OUTPUT);
+  }
+
+  static class TestClass {
+
+    @NeverInline
+    public static void f1() {
+      int i = -13;
+      char c = (char) i;
+      i = c;
+      System.out.println("i = " + i);
+    }
+
+    @NeverInline
+    public static void f2() {
+      int i = 13;
+      char c = (char) i;
+      i = c;
+      System.out.println("i = " + i);
+    }
+
+    @NeverInline
+    public static void f3() {
+      //        7------07------07------07------0
+      int i = 0b00000000000000001111111111111111;
+      char c = (char) i;
+      i = c;
+      System.out.println("i = " + i);
+    }
+
+    @NeverInline
+    public static void f4() {
+      //        7------07------07------07------0
+      int i = 0b00000000000000011111111111111111;
+      char c = (char) i;
+      i = c;
+      System.out.println("i = " + i);
+    }
+
+    @NeverInline
+    public static void f5() {
+      //        7------07------07------07------0
+      int i = 0b00000000000000000111111111111111;
+      char c = (char) i;
+      i = c;
+      System.out.println("i = " + i);
+    }
+
+    @NeverInline
+    public static void f6() {
+      //        7------07------07------07------0
+      int i = 0b00000000000000001000000000000000;
+      char c = (char) i;
+      i = c;
+      System.out.println("i = " + i);
+    }
+
+    public static void main(String[] args) {
+      f1();
+      f2();
+      f3();
+      f4();
+      f5();
+      f6();
+    }
+  }
+}
diff --git a/src/test/testbase/java/com/android/tools/r8/utils/codeinspector/CfInstructionSubject.java b/src/test/testbase/java/com/android/tools/r8/utils/codeinspector/CfInstructionSubject.java
index 7ad1a8f..4c0102e 100644
--- a/src/test/testbase/java/com/android/tools/r8/utils/codeinspector/CfInstructionSubject.java
+++ b/src/test/testbase/java/com/android/tools/r8/utils/codeinspector/CfInstructionSubject.java
@@ -29,6 +29,7 @@
 import com.android.tools.r8.cf.code.CfNew;
 import com.android.tools.r8.cf.code.CfNewArray;
 import com.android.tools.r8.cf.code.CfNop;
+import com.android.tools.r8.cf.code.CfNumberConversion;
 import com.android.tools.r8.cf.code.CfPosition;
 import com.android.tools.r8.cf.code.CfReturn;
 import com.android.tools.r8.cf.code.CfReturnVoid;
@@ -216,6 +217,11 @@
   }
 
   @Override
+  public boolean isNumberConversion() {
+    return instruction instanceof CfNumberConversion;
+  }
+
+  @Override
   public boolean isGoto() {
     return instruction instanceof CfGoto;
   }
diff --git a/src/test/testbase/java/com/android/tools/r8/utils/codeinspector/DexInstructionSubject.java b/src/test/testbase/java/com/android/tools/r8/utils/codeinspector/DexInstructionSubject.java
index fee7623..90d28b3 100644
--- a/src/test/testbase/java/com/android/tools/r8/utils/codeinspector/DexInstructionSubject.java
+++ b/src/test/testbase/java/com/android/tools/r8/utils/codeinspector/DexInstructionSubject.java
@@ -49,9 +49,15 @@
 import com.android.tools.r8.dex.code.DexDivIntLit8;
 import com.android.tools.r8.dex.code.DexDivLong;
 import com.android.tools.r8.dex.code.DexDivLong2Addr;
+import com.android.tools.r8.dex.code.DexDoubleToFloat;
+import com.android.tools.r8.dex.code.DexDoubleToInt;
+import com.android.tools.r8.dex.code.DexDoubleToLong;
 import com.android.tools.r8.dex.code.DexFillArrayData;
 import com.android.tools.r8.dex.code.DexFilledNewArray;
 import com.android.tools.r8.dex.code.DexFilledNewArrayRange;
+import com.android.tools.r8.dex.code.DexFloatToDouble;
+import com.android.tools.r8.dex.code.DexFloatToInt;
+import com.android.tools.r8.dex.code.DexFloatToLong;
 import com.android.tools.r8.dex.code.DexGoto;
 import com.android.tools.r8.dex.code.DexIfEq;
 import com.android.tools.r8.dex.code.DexIfEqz;
@@ -74,6 +80,12 @@
 import com.android.tools.r8.dex.code.DexIgetWide;
 import com.android.tools.r8.dex.code.DexInstanceOf;
 import com.android.tools.r8.dex.code.DexInstruction;
+import com.android.tools.r8.dex.code.DexIntToByte;
+import com.android.tools.r8.dex.code.DexIntToChar;
+import com.android.tools.r8.dex.code.DexIntToDouble;
+import com.android.tools.r8.dex.code.DexIntToFloat;
+import com.android.tools.r8.dex.code.DexIntToLong;
+import com.android.tools.r8.dex.code.DexIntToShort;
 import com.android.tools.r8.dex.code.DexInvokeCustom;
 import com.android.tools.r8.dex.code.DexInvokeCustomRange;
 import com.android.tools.r8.dex.code.DexInvokeDirect;
@@ -94,6 +106,9 @@
 import com.android.tools.r8.dex.code.DexIputObject;
 import com.android.tools.r8.dex.code.DexIputShort;
 import com.android.tools.r8.dex.code.DexIputWide;
+import com.android.tools.r8.dex.code.DexLongToDouble;
+import com.android.tools.r8.dex.code.DexLongToFloat;
+import com.android.tools.r8.dex.code.DexLongToInt;
 import com.android.tools.r8.dex.code.DexMonitorEnter;
 import com.android.tools.r8.dex.code.DexMonitorExit;
 import com.android.tools.r8.dex.code.DexMove;
@@ -413,6 +428,25 @@
   }
 
   @Override
+  public boolean isNumberConversion() {
+    return instruction instanceof DexIntToByte
+        || instruction instanceof DexIntToShort
+        || instruction instanceof DexIntToLong
+        || instruction instanceof DexIntToChar
+        || instruction instanceof DexIntToDouble
+        || instruction instanceof DexIntToFloat
+        || instruction instanceof DexLongToInt
+        || instruction instanceof DexLongToFloat
+        || instruction instanceof DexLongToDouble
+        || instruction instanceof DexFloatToInt
+        || instruction instanceof DexFloatToLong
+        || instruction instanceof DexFloatToDouble
+        || instruction instanceof DexDoubleToInt
+        || instruction instanceof DexDoubleToLong
+        || instruction instanceof DexDoubleToFloat;
+  }
+
+  @Override
   public boolean isGoto() {
 
     return instruction instanceof DexGoto;
diff --git a/src/test/testbase/java/com/android/tools/r8/utils/codeinspector/InstructionSubject.java b/src/test/testbase/java/com/android/tools/r8/utils/codeinspector/InstructionSubject.java
index 983751f..49dffcd 100644
--- a/src/test/testbase/java/com/android/tools/r8/utils/codeinspector/InstructionSubject.java
+++ b/src/test/testbase/java/com/android/tools/r8/utils/codeinspector/InstructionSubject.java
@@ -94,6 +94,8 @@
 
   boolean isConstClass(String type);
 
+  boolean isNumberConversion();
+
   boolean isGoto();
 
   boolean isPop();