Add compareTo and compareToIgnoreCase to compile-time string computation

Bug: 119364907
Change-Id: I90817bbd254ff58dcff9c0ab3a1cf07839534ed7
diff --git a/src/main/java/com/android/tools/r8/graph/DexItemFactory.java b/src/main/java/com/android/tools/r8/graph/DexItemFactory.java
index 1757459..e7b3967 100644
--- a/src/main/java/com/android/tools/r8/graph/DexItemFactory.java
+++ b/src/main/java/com/android/tools/r8/graph/DexItemFactory.java
@@ -140,6 +140,8 @@
   public final DexString contentEqualsMethodName = createString("contentEquals");
   public final DexString indexOfMethodName = createString("indexOf");
   public final DexString lastIndexOfMethodName = createString("lastIndexOf");
+  public final DexString compareToMethodName = createString("compareTo");
+  public final DexString compareToIgnoreCaseMethodName = createString("compareToIgnoreCase");
   public final DexString cloneMethodName = createString("clone");
 
   public final DexString valueOfMethodName = createString("valueOf");
@@ -613,6 +615,8 @@
     public final DexMethod indexOfString;
     public final DexMethod lastIndexOfInt;
     public final DexMethod lastIndexOfString;
+    public final DexMethod compareTo;
+    public final DexMethod compareToIgnoreCase;
 
     public final DexMethod valueOf;
     public final DexMethod toString;
@@ -649,6 +653,11 @@
           createMethod(stringDescriptor, lastIndexOfMethodName, intDescriptor, needsOneString);
       lastIndexOfInt =
           createMethod(stringDescriptor, lastIndexOfMethodName, intDescriptor, needsOneInt);
+      compareTo =
+          createMethod(stringDescriptor, compareToMethodName, intDescriptor, needsOneString);
+      compareToIgnoreCase =
+          createMethod(stringDescriptor, compareToIgnoreCaseMethodName, intDescriptor,
+              needsOneString);
 
       valueOf = createMethod(
           stringDescriptor, valueOfMethodName, stringDescriptor, needsOneObject);
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/string/StringOptimizer.java b/src/main/java/com/android/tools/r8/ir/optimize/string/StringOptimizer.java
index 3fc442a..5dfbb78 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/string/StringOptimizer.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/string/StringOptimizer.java
@@ -16,6 +16,7 @@
 import com.android.tools.r8.graph.DexEncodedMethod;
 import com.android.tools.r8.graph.DexItemFactory;
 import com.android.tools.r8.graph.DexMethod;
+import com.android.tools.r8.graph.DexString;
 import com.android.tools.r8.graph.DexType;
 import com.android.tools.r8.ir.analysis.EscapeAnalysis;
 import com.android.tools.r8.ir.analysis.type.TypeLatticeElement;
@@ -53,16 +54,18 @@
 
   // int String#length()
   // boolean String#isEmpty()
-  // boolean String#startsWith(str)
-  // boolean String#endsWith(str)
-  // boolean String#contains(str)
-  // boolean String#equals(str)
-  // boolean String#equalsIgnoreCase(str)
-  // boolean String#contentEquals(str)
-  // int String#indexOf(str)
+  // boolean String#startsWith(String)
+  // boolean String#endsWith(String)
+  // boolean String#contains(String)
+  // boolean String#equals(String)
+  // boolean String#equalsIgnoreCase(String)
+  // boolean String#contentEquals(String)
+  // int String#indexOf(String)
   // int String#indexOf(int)
-  // int String#lastIndexOf(str)
+  // int String#lastIndexOf(String)
   // int String#lastIndexOf(int)
+  // int String#compareTo(String)
+  // int String#compareToIgnoreCase(String)
   public void computeTrivialOperationsOnConstString(IRCode code) {
     if (!code.hasConstString) {
       return;
@@ -102,6 +105,10 @@
         operatorWithInt = String::lastIndexOf;
       } else if (invokedMethod == factory.stringMethods.lastIndexOfString) {
         operatorWithString = String::lastIndexOf;
+      } else if (invokedMethod == factory.stringMethods.compareTo) {
+        operatorWithString = String::compareTo;
+      } else if (invokedMethod == factory.stringMethods.compareToIgnoreCase) {
+        operatorWithString = String::compareToIgnoreCase;
       } else {
         continue;
       }
@@ -111,12 +118,12 @@
           || !rcv.isConstant()) {
         continue;
       }
+      DexString rcvString = rcv.definition.asConstString().getValue();
 
       ConstNumber constNumber;
       if (operatorWithNoArg != null) {
         assert invoke.inValues().size() == 1;
-        ConstString rcvString = rcv.definition.asConstString();
-        int v = operatorWithNoArg.apply(rcvString.getValue().toString());
+        int v = operatorWithNoArg.apply(rcvString.toString());
         constNumber = code.createIntConstant(v);
       } else if (operatorWithString != null) {
         assert invoke.inValues().size() == 2;
@@ -126,10 +133,9 @@
             || !arg.isConstant()) {
           continue;
         }
-        ConstString rcvString = rcv.definition.asConstString();
-        ConstString argString = arg.definition.asConstString();
         int v = operatorWithString.apply(
-            rcvString.getValue().toString(), argString.getValue().toString());
+            rcvString.toString(),
+            arg.definition.asConstString().getValue().toString());
         constNumber = code.createIntConstant(v);
       } else {
         assert operatorWithInt != null;
@@ -140,10 +146,9 @@
             || !arg.isConstant()) {
           continue;
         }
-        ConstString rcvString = rcv.definition.asConstString();
-        ConstNumber argInt = arg.definition.asConstNumber();
         int v = operatorWithInt.apply(
-            rcvString.getValue().toString(), argInt.getIntValue());
+            rcvString.toString(),
+            arg.definition.asConstNumber().getIntValue());
         constNumber = code.createIntConstant(v);
       }
 
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/string/StringContentCheckTest.java b/src/test/java/com/android/tools/r8/ir/optimize/string/StringContentCheckTest.java
index cd3a131..7044366 100644
--- a/src/test/java/com/android/tools/r8/ir/optimize/string/StringContentCheckTest.java
+++ b/src/test/java/com/android/tools/r8/ir/optimize/string/StringContentCheckTest.java
@@ -35,7 +35,9 @@
         && "CONST".equalsIgnoreCase(arg)
         && "CONST".contentEquals(arg)
         && "CONST".indexOf(arg) > 0
-        && "CONST".lastIndexOf(arg) > 0;
+        && "CONST".lastIndexOf(arg) > 0
+        && "CONST".compareTo(arg) > 0
+        && "CONST".compareToIgnoreCase(arg) > 0;
   }
 
   public static void main(String[] args) {
@@ -52,6 +54,8 @@
       System.out.println(s1.indexOf("ix"));
       System.out.println(s1.lastIndexOf('f'));
       System.out.println(s1.lastIndexOf("ix"));
+      System.out.println(s1.compareTo("prefix-CONST-suffix") == 0);
+      System.out.println(s1.compareToIgnoreCase("PREFIX-const-SUFFIX") == 0);
     }
 
     {
@@ -68,6 +72,8 @@
       System.out.println(s2.indexOf("ix"));
       System.out.println(s2.lastIndexOf('f'));
       System.out.println(s2.lastIndexOf("ix"));
+      System.out.println(s2.compareTo("prefix-CONST-suffix") == 0);
+      System.out.println(s2.compareToIgnoreCase("pre-con-suf") == 0);
     }
 
     {
@@ -90,15 +96,15 @@
       StringContentCheckTestMain.class
   );
   private static final String JAVA_OUTPUT = StringUtils.lines(
-      // s1, contains
+      // s1, contains(String)
       "true",
-      // s1, startsWith
+      // s1, startsWith(String)
       "true",
-      // s1, endsWith
+      // s1, endsWith(String)
       "true",
-      // s1, equals
+      // s1, equals(String)
       "true",
-      // s1, equalsIgnoreCase
+      // s1, equalsIgnoreCase(String)
       "true",
       // s1, contentEquals(CharSequence)
       "true",
@@ -112,15 +118,19 @@
       "16",
       // s1, lastIndexOf(String)
       "17",
-      // s2, contains
+      // s1, compareTo(String)
+      "true",
+      // s1, compareToIgnoreCase(String)
+      "true",
+      // s2, contains(String)
       "false",
-      // s2, startsWith
+      // s2, startsWith(String)
       "false",
-      // s2, endsWith
+      // s2, endsWith(String)
       "false",
-      // s2, equals
+      // s2, equals(String)
       "false",
-      // s2, equalsIgnoreCase
+      // s2, equalsIgnoreCase(String)
       "false",
       // s2, contentEquals(CharSequence)
       "false",
@@ -134,6 +144,10 @@
       "-1",
       // s2, lastIndexOf(String)
       "-1",
+      // s2, compareTo(String)
+      "false",
+      // s2, compareToIgnoreCase(String)
+      "false",
       // argCouldBeNull
       "false"
   );
@@ -166,7 +180,9 @@
             || method.name.toString().equals("equalsIgnoreCase")
             || method.name.toString().equals("contentEquals")
             || method.name.toString().equals("indexOf")
-            || method.name.toString().equals("lastIndexOf"));
+            || method.name.toString().equals("lastIndexOf")
+            || method.name.toString().equals("compareTo")
+            || method.name.toString().equals("compareToIgnoreCase"));
   }
 
   private long countStringContentChecker(MethodSubject method) {
@@ -190,7 +206,7 @@
         "boolean", "argCouldBeNull", ImmutableList.of("java.lang.String"));
     assertThat(argCouldBeNull, isPresent());
     // Because of nullable argument, all checkers should remain.
-    assertEquals(8, countStringContentChecker(argCouldBeNull));
+    assertEquals(10, countStringContentChecker(argCouldBeNull));
   }
 
   @Test
@@ -202,14 +218,14 @@
         .addProgramClasses(CLASSES)
         .run(MAIN)
         .assertSuccessWithOutput(JAVA_OUTPUT);
-    test(result, 22);
+    test(result, 26);
 
     result = testForD8()
         .release()
         .addProgramClasses(CLASSES)
         .run(MAIN)
         .assertSuccessWithOutput(JAVA_OUTPUT);
-    test(result, 12);
+    test(result, 14);
   }
 
   @Test
@@ -221,6 +237,6 @@
         .addKeepMainRule(MAIN)
         .run(MAIN)
         .assertSuccessWithOutput(JAVA_OUTPUT);
-    test(result, 12);
+    test(result, 14);
   }
 }