String concatenation desugaring.

Implements desugaring of string concatenations handles with invokedynamic
call bootstrapped with makeConcat or makeConcatWithConstants methods of
StringConcatFactory class.

Note, this implementation does not implement support of `constants` passed
to makeConcatWithConstants bootstrap method, since they don't seem to be used.

Bug: 67230058
Change-Id: I890c9390c1b8514e3d2ae69af0d2a8fceafea15d
diff --git a/src/test/examplesAndroidO/stringconcat/StringConcat.java b/src/test/examplesAndroidO/stringconcat/StringConcat.java
new file mode 100644
index 0000000..9b726b3
--- /dev/null
+++ b/src/test/examplesAndroidO/stringconcat/StringConcat.java
@@ -0,0 +1,269 @@
+// Copyright (c) 2017, 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 stringconcat;
+
+public class StringConcat {
+  private static void check(String actual, String expected) {
+    if (expected.equals(actual)) {
+      return;
+    }
+    throw new AssertionError(
+        "Test method failed: expected=[" + expected + "], actual=[" + actual + "]");
+  }
+
+  // --------- used 'makeConcat' signatures ---------
+
+  private static String makeConcat() {
+    throw new AssertionError("unreachable");
+  }
+
+  private static String makeConcat(String s) {
+    throw new AssertionError("unreachable");
+  }
+
+  private static String makeConcat(char[] s) {
+    throw new AssertionError("unreachable");
+  }
+
+  private static String makeConcat(Object o) {
+    throw new AssertionError("unreachable");
+  }
+
+  private static String makeConcat(boolean o) {
+    throw new AssertionError("unreachable");
+  }
+
+  private static String makeConcat(char o) {
+    throw new AssertionError("unreachable");
+  }
+
+  private static String makeConcat(byte o) {
+    throw new AssertionError("unreachable");
+  }
+
+  private static String makeConcat(short o) {
+    throw new AssertionError("unreachable");
+  }
+
+  private static String makeConcat(int o) {
+    throw new AssertionError("unreachable");
+  }
+
+  private static String makeConcat(long o) {
+    throw new AssertionError("unreachable");
+  }
+
+  private static String makeConcat(float o) {
+    throw new AssertionError("unreachable");
+  }
+
+  private static String makeConcat(double o) {
+    throw new AssertionError("unreachable");
+  }
+
+  private static String makeConcat(Object o, String st, boolean z,
+      char c, byte b, short s, int i, long l, float f, double d) {
+    throw new AssertionError("unreachable");
+  }
+
+  // --------- used 'makeConcatWithConstants' signatures ---------
+
+  private static String makeConcatWithConstants(String recipe) {
+    throw new AssertionError("unreachable");
+  }
+
+  private static String makeConcatWithConstants(String s, String recipe) {
+    throw new AssertionError("unreachable");
+  }
+
+  private static String makeConcatWithConstants(char[] s, String recipe) {
+    throw new AssertionError("unreachable");
+  }
+
+  private static String makeConcatWithConstants(Object o, String recipe) {
+    throw new AssertionError("unreachable");
+  }
+
+  private static String makeConcatWithConstants(boolean o, String recipe) {
+    throw new AssertionError("unreachable");
+  }
+
+  private static String makeConcatWithConstants(char o, String recipe) {
+    throw new AssertionError("unreachable");
+  }
+
+  private static String makeConcatWithConstants(byte o, String recipe) {
+    throw new AssertionError("unreachable");
+  }
+
+  private static String makeConcatWithConstants(short o, String recipe) {
+    throw new AssertionError("unreachable");
+  }
+
+  private static String makeConcatWithConstants(int o, String recipe) {
+    throw new AssertionError("unreachable");
+  }
+
+  private static String makeConcatWithConstants(long o, String recipe) {
+    throw new AssertionError("unreachable");
+  }
+
+  private static String makeConcatWithConstants(float o, String recipe) {
+    throw new AssertionError("unreachable");
+  }
+
+  private static String makeConcatWithConstants(double o, String recipe) {
+    throw new AssertionError("unreachable");
+  }
+
+  private static String makeConcatWithConstants(Object o, String st, boolean z,
+      char c, byte b, short s, int i, long l, float f, double d, String recipe) {
+    throw new AssertionError("unreachable");
+  }
+
+  private static String makeConcatWithConstants(int i, String s, String recipe, String sConst) {
+    throw new AssertionError("unreachable");
+  }
+
+  private static String makeConcatWithConstants(
+      int i, String s, String recipe, String sConstA, String sConstB, String sConstC) {
+    throw new AssertionError("unreachable");
+  }
+
+  // ------------------------------------------------
+
+  private static void testEmpty() {
+    check(makeConcat(), "");
+    makeConcat();
+
+    check(makeConcatWithConstants("RECIPE:"), "");
+    check(makeConcatWithConstants("RECIPE:12-34"), "12-34");
+    makeConcatWithConstants("RECIPE:a");
+  }
+
+  private static void testSingleValueString() {
+    check(makeConcat("str"), "str");
+    check(makeConcat((String) null), "null");
+
+    check(makeConcatWithConstants("()", "RECIPE:prefix\u0001suffix"), "prefix()suffix");
+    check(makeConcatWithConstants("()", "RECIPE:prefix\u0001"), "prefix()");
+    check(makeConcatWithConstants("()", "RECIPE:\u0001suffix"), "()suffix");
+    check(makeConcatWithConstants("()", "RECIPE:\u0001"), "()");
+  }
+
+  private static void testSingleValueArray() {
+    // Unchecked since Array.toString() is non-deterministic.
+    makeConcat(new char[] { 'a', 'b' });
+    makeConcatWithConstants(new char[] { 'a', 'b' }, "RECIPE:prefix\u0001suffix");
+  }
+
+  private static void testSingleValueObject() {
+    check(makeConcat((Object) "object"), "object");
+    check(makeConcat((Object) 1.234), "1.234");
+    check(makeConcat((Object) null), "null");
+
+    check(
+        makeConcatWithConstants((Object) "object", "RECIPE:prefix\u0001suffix"),
+        "prefixobjectsuffix");
+    check(
+        makeConcatWithConstants((Object) 1.234, "RECIPE:prefix\u0001suffix"),
+        "prefix1.234suffix");
+    check(
+        makeConcatWithConstants((Object) null, "RECIPE:prefix\u0001suffix"),
+        "prefixnullsuffix");
+  }
+
+  private static void testSingleValuePrimitive() {
+    check(makeConcat(true), "true");
+    check(makeConcat((char) 65), "A");
+    check(makeConcat((byte) 1), "1");
+    check(makeConcat((short) 2), "2");
+    check(makeConcat(3), "3");
+    check(makeConcat((long) 4), "4");
+    check(makeConcat((float) 5), "5.0");
+    check(makeConcat((double) 6), "6.0");
+
+    check(makeConcatWithConstants(true, "RECIPE:prefix\u0001suffix"), "prefixtruesuffix");
+    check(makeConcatWithConstants((char) 65, "RECIPE:prefix\u0001suffix"), "prefixAsuffix");
+    check(makeConcatWithConstants((byte) 1, "RECIPE:prefix\u0001suffix"), "prefix1suffix");
+    check(makeConcatWithConstants((short) 2, "RECIPE:prefix\u0001suffix"), "prefix2suffix");
+    check(makeConcatWithConstants(3, "RECIPE:prefix\u0001suffix"), "prefix3suffix");
+    check(makeConcatWithConstants((long) 4, "RECIPE:prefix\u0001suffix"), "prefix4suffix");
+    check(makeConcatWithConstants((float) 5, "RECIPE:prefix\u0001suffix"), "prefix5.0suffix");
+    check(makeConcatWithConstants((double) 6, "RECIPE:prefix\u0001suffix"), "prefix6.0suffix");
+  }
+
+  private static void testAllTypes(Object o, String st, boolean z,
+      char c, byte b, short s, int i, long l, float f, double d) {
+    check(makeConcat(o, st, z, c, b, s, i, l, f, d), "nullstrtrueA12345.06.0");
+    check(makeConcatWithConstants(o, st, z, c, b, s, i, l, f, d,
+        "RECIPE:[\u0001-\u0001>\u0001===\u0001\u0001\u0001alpha\u0001beta\u0001\u0001]\u0001"),
+        "[null-str>true===A12alpha3beta45.0]6.0");
+  }
+
+  private static void testInExceptionContext(Object o, String st, boolean z,
+      char c, byte b, short s, int i, long l, float f, double d) {
+    check(makeConcat((long) 4), "4");
+    try {
+      check(makeConcat(o, st, z, c, b, s, i, l, f, d), "nullstrtrueA12345.06.0");
+      check(makeConcatWithConstants(o, st, z, c, b, s, i, l, f, d,
+          "RECIPE:\u0001\u0001\u0001\u0001\u0001\u0001\u0001\u0001\u0001\u0001"),
+          "nullstrtrueA12345.06.0");
+      try {
+        check(makeConcat("try-try"), "try-try");
+        throw new IndexOutOfBoundsException();
+      } catch (NullPointerException re) {
+        throw new AssertionError("UNREACHABLE");
+      } catch (Exception re) {
+        check(makeConcatWithConstants(o, st, z, c, b, s, i, l, f, d,
+            "RECIPE:(\u0001, \u0001, \u0001, \u0001, \u0001, "
+                + "\u0001, \u0001, \u0001, \u0001, \u0001)"),
+            "(null, str, true, A, 1, 2, 3, 4, 5.0, 6.0)");
+        throw new IndexOutOfBoundsException();
+      }
+    } catch (IndexOutOfBoundsException re) {
+      check(makeConcat("bar"), "bar");
+      check(makeConcatWithConstants("foo", "RECIPE:bar -> \u0001"), "bar -> foo");
+      try {
+        check(makeConcatWithConstants("inside", "RECIPE:try \u0001 try"), "try inside try");
+        throw new NullPointerException();
+      } catch (IndexOutOfBoundsException e) {
+        throw new AssertionError("UNREACHABLE");
+      } catch (NullPointerException npe) {
+        check(makeConcat(o, st, z, c, b, s, i, l, f, d), "nullstrtrueA12345.06.0");
+      }
+    } catch (Exception re) {
+      throw new AssertionError("UNREACHABLE");
+    }
+  }
+
+  private static void testConcatWitConstants() {
+    check(
+        makeConcatWithConstants(
+            123, "abc", "RECIPE:arg=\u0001; const=\u0002; arg=\u0001", "str"
+        ),
+        "arg=123; const=str; arg=abc");
+    check(
+        makeConcatWithConstants(
+            123, "abc", "RECIPE:\u0002arg=\u0001\u0002arg=\u0001\u0002",
+            "prefix-", "-infix-", "-suffix"
+        ),
+        "prefix-arg=123-infix-arg=abc-suffix");
+  }
+
+  // ------------------------------------------------
+
+  public static void main(String[] args) {
+    testEmpty();
+    testSingleValueString();
+    testSingleValueArray();
+    testSingleValueObject();
+    testSingleValuePrimitive();
+    testAllTypes(null, "str", true, (char) 65,
+        (byte) 1, (short) 2, 3, (long) 4, (float) 5, (double) 6);
+    testInExceptionContext(null, "str", true, (char) 65,
+        (byte) 1, (short) 2, 3, (long) 4, (float) 5, (double) 6);
+    testConcatWitConstants();
+  }
+}