Reland "Desugar lib Time API conversions"

This reverts commit 04dfb343db9e3af5533a6f9ffd3ffc8288410183.

Change-Id: Ie068c9f26efbcd2ee64fbd0e4b2691ef64c6161a
diff --git a/src/main/java/com/android/tools/r8/ir/code/InvokeMethodWithReceiver.java b/src/main/java/com/android/tools/r8/ir/code/InvokeMethodWithReceiver.java
index 9a71dc7..e3757da 100644
--- a/src/main/java/com/android/tools/r8/ir/code/InvokeMethodWithReceiver.java
+++ b/src/main/java/com/android/tools/r8/ir/code/InvokeMethodWithReceiver.java
@@ -33,6 +33,7 @@
   }
 
   public Value getReceiver() {
+    assert inValues.size() > 0;
     return inValues.get(0);
   }
 
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/DesugaredLibraryAPIConverter.java b/src/main/java/com/android/tools/r8/ir/desugar/DesugaredLibraryAPIConverter.java
index 0922cd2..9f22ded 100644
--- a/src/main/java/com/android/tools/r8/ir/desugar/DesugaredLibraryAPIConverter.java
+++ b/src/main/java/com/android/tools/r8/ir/desugar/DesugaredLibraryAPIConverter.java
@@ -19,9 +19,12 @@
 import com.android.tools.r8.ir.code.InvokeMethod;
 import com.android.tools.r8.ir.code.InvokeStatic;
 import com.android.tools.r8.ir.code.Value;
+import com.android.tools.r8.utils.BooleanUtils;
 import com.android.tools.r8.utils.DescriptorUtils;
 import com.android.tools.r8.utils.StringDiagnostic;
+import java.util.ArrayList;
 import java.util.Collections;
+import java.util.List;
 
 // TODO(b/134732760): In progress.
 // I convert library calls with desugared parameters/return values so they can work normally.
@@ -72,13 +75,17 @@
       if (dexClass == null || !dexClass.isLibraryClass()) {
         continue;
       }
+      // Library methods do not understand desugared types, hence desugared types have to be
+      // converted around non desugared library calls for the invoke to resolve.
       if (appView.rewritePrefix.hasRewrittenType(invokedMethod.proto.returnType)) {
-        addReturnConversion(code, invokeMethod, iterator);
+        rewriteLibraryInvoke(code, invokeMethod, iterator);
+        continue;
       }
       for (int i = 0; i < invokedMethod.proto.parameters.values.length; i++) {
         DexType argType = invokedMethod.proto.parameters.values[i];
         if (appView.rewritePrefix.hasRewrittenType(argType)) {
-          addParameterConversion(code, invokeMethod, iterator, argType, i);
+          rewriteLibraryInvoke(code, invokeMethod, iterator);
+          continue;
         }
       }
     }
@@ -109,117 +116,126 @@
     return vivifiedType;
   }
 
-  private void addParameterConversion(
-      IRCode code,
-      InvokeMethod invokeMethod,
-      InstructionListIterator iterator,
-      DexType argType,
-      int parameter) {
-    if (!appView
-        .options()
-        .desugaredLibraryConfiguration
-        .getCustomConversions()
-        .containsKey(argType)) {
-      // TODO(b/134732760): Add Wrapper Conversions.
-      warnInvalidInvoke(argType, invokeMethod.getInvokedMethod(), "parameter");
-      return;
-    }
-
-    Value inValue = invokeMethod.inValues().get(parameter);
-    DexType argVivifiedType = vivifiedTypeFor(argType);
-    DexType conversionHolder =
-        appView.options().desugaredLibraryConfiguration.getCustomConversions().get(argType);
-
-    // ConversionType has static method "type convert(rewrittenType)".
-    // But everything is going to be rewritten, so we need to call "vivifiedType convert(type)".
-    DexMethod conversionMethod =
-        factory.createMethod(
-            conversionHolder,
-            factory.createProto(argVivifiedType, argType),
-            factory.convertMethodName);
-    Value convertedValue =
-        code.createValue(
-            TypeLatticeElement.fromDexType(
-                argVivifiedType, inValue.getTypeLattice().nullability(), appView));
-    InvokeStatic conversionInstruction =
-        new InvokeStatic(conversionMethod, convertedValue, Collections.singletonList(inValue));
-    conversionInstruction.setPosition(invokeMethod.getPosition());
-    iterator.previous();
-    iterator.add(conversionInstruction);
-    iterator.next();
-
-    // Rewrite invoke (signature and inValue to rewrite).
-    DexMethod newDexMethod =
-        dexMethodWithDifferentParameter(
-            invokeMethod.getInvokedMethod(), argVivifiedType, parameter);
-    Invoke newInvokeMethod =
-        Invoke.create(
-            invokeMethod.getType(),
-            newDexMethod,
-            newDexMethod.proto,
-            invokeMethod.outValue(),
-            invokeMethod.inValues());
-    newInvokeMethod.replaceValue(parameter, conversionInstruction.outValue());
-    iterator.replaceCurrentInstruction(newInvokeMethod);
-  }
-
-  private void addReturnConversion(
+  private void rewriteLibraryInvoke(
       IRCode code, InvokeMethod invokeMethod, InstructionListIterator iterator) {
-    DexType returnType = invokeMethod.getReturnType();
-    if (!appView
-        .options()
-        .desugaredLibraryConfiguration
-        .getCustomConversions()
-        .containsKey(returnType)) {
-      // TODO(b/134732760): Add Wrapper Conversions.
-      warnInvalidInvoke(returnType, invokeMethod.getInvokedMethod(), "return");
-      return;
+    DexMethod invokedMethod = invokeMethod.getInvokedMethod();
+
+    // Create return conversion if required.
+    Instruction returnConversion = null;
+    DexType newReturnType;
+    DexType returnType = invokedMethod.proto.returnType;
+    if (appView.rewritePrefix.hasRewrittenType(returnType)) {
+      if (appView
+          .options()
+          .desugaredLibraryConfiguration
+          .getCustomConversions()
+          .containsKey(returnType)) {
+        newReturnType = vivifiedTypeFor(returnType);
+        // Return conversion added only if return value is used.
+        if (invokeMethod.outValue() != null
+            && invokeMethod.outValue().numberOfUsers() + invokeMethod.outValue().numberOfPhiUsers()
+                > 0) {
+          returnConversion =
+              createReturnConversionAndReplaceUses(code, invokeMethod, returnType, newReturnType);
+        }
+      } else {
+        // TODO(b/134732760): Add Wrapper Conversions.
+        warnInvalidInvoke(returnType, invokeMethod.getInvokedMethod(), "return");
+        newReturnType = returnType;
+      }
+    } else {
+      newReturnType = returnType;
     }
 
-    DexType returnVivifiedType = vivifiedTypeFor(returnType);
-    DexType conversionHolder =
-        appView.options().desugaredLibraryConfiguration.getCustomConversions().get(returnType);
+    // Create parameter conversions if required.
+    List<Instruction> parameterConversions = new ArrayList<>();
+    List<Value> newInValues = new ArrayList<>();
+    if (invokeMethod.isInvokeMethodWithReceiver()) {
+      assert !appView.rewritePrefix.hasRewrittenType(invokedMethod.holder);
+      newInValues.add(invokeMethod.asInvokeMethodWithReceiver().getReceiver());
+    }
+    int receiverShift = BooleanUtils.intValue(invokeMethod.isInvokeMethodWithReceiver());
+    DexType[] parameters = invokedMethod.proto.parameters.values;
+    DexType[] newParameters = parameters.clone();
+    for (int i = 0; i < parameters.length; i++) {
+      DexType argType = parameters[i];
+      if (appView.rewritePrefix.hasRewrittenType(argType)) {
+        if (appView
+            .options()
+            .desugaredLibraryConfiguration
+            .getCustomConversions()
+            .containsKey(argType)) {
+          DexType argVivifiedType = vivifiedTypeFor(argType);
+          Value inValue = invokeMethod.inValues().get(i + receiverShift);
+          newParameters[i] = argVivifiedType;
+          parameterConversions.add(
+              createParameterConversion(code, argType, argVivifiedType, inValue));
+          newInValues.add(parameterConversions.get(parameterConversions.size() - 1).outValue());
+        } else {
+          // TODO(b/134732760): Add Wrapper Conversions.
+          warnInvalidInvoke(argType, invokeMethod.getInvokedMethod(), "parameter");
+          newInValues.add(invokeMethod.inValues().get(i + receiverShift));
+        }
+      } else {
+        newInValues.add(invokeMethod.inValues().get(i + receiverShift));
+      }
+    }
 
-    // ConversionType has static method "rewrittenType convert(type)".
-    // But everything is going to be rewritten, so we need to call "type convert(vivifiedType)".
-    DexMethod conversionMethod =
-        factory.createMethod(
-            conversionHolder,
-            factory.createProto(returnType, returnVivifiedType),
-            factory.convertMethodName);
-    Value convertedValue =
-        code.createValue(
-            TypeLatticeElement.fromDexType(returnType, Nullability.maybeNull(), appView));
-    invokeMethod.outValue().replaceUsers(convertedValue);
-    InvokeStatic conversionInstruction =
-        new InvokeStatic(
-            conversionMethod, convertedValue, Collections.singletonList(invokeMethod.outValue()));
-    conversionInstruction.setPosition(invokeMethod.getPosition());
-
-    // Rewrite invoke (signature to rewrite).
+    // Patch the invoke with new types and new inValues.
+    DexProto newProto = factory.createProto(newReturnType, newParameters);
     DexMethod newDexMethod =
-        dexMethodWithDifferentReturn(invokeMethod.getInvokedMethod(), returnVivifiedType);
+        factory.createMethod(invokedMethod.holder, newProto, invokedMethod.name);
     Invoke newInvokeMethod =
         Invoke.create(
             invokeMethod.getType(),
             newDexMethod,
             newDexMethod.proto,
             invokeMethod.outValue(),
-            invokeMethod.inValues());
+            newInValues);
+
+    // Insert and reschedule all instructions.
+    iterator.previous();
+    for (Instruction parameterConversion : parameterConversions) {
+      parameterConversion.setPosition(invokeMethod.getPosition());
+      iterator.add(parameterConversion);
+    }
+    assert iterator.peekNext() == invokeMethod;
+    iterator.next();
     iterator.replaceCurrentInstruction(newInvokeMethod);
-    iterator.add(conversionInstruction);
+    if (returnConversion != null) {
+      returnConversion.setPosition(invokeMethod.getPosition());
+      iterator.add(returnConversion);
+    }
   }
 
-  private DexMethod dexMethodWithDifferentParameter(
-      DexMethod method, DexType newParameterType, int parameter) {
-    DexType[] newParameters = method.proto.parameters.values.clone();
-    newParameters[parameter] = newParameterType;
-    DexProto newProto = factory.createProto(method.proto.returnType, newParameters);
-    return factory.createMethod(method.holder, newProto, method.name);
+  private Instruction createParameterConversion(
+      IRCode code, DexType argType, DexType argVivifiedType, Value inValue) {
+    DexMethod conversionMethod = createConversionMethod(argType, argType, argVivifiedType);
+    // The value is null only if the input is null.
+    Value convertedValue =
+        createConversionValue(code, inValue.getTypeLattice().nullability(), argVivifiedType);
+    return new InvokeStatic(conversionMethod, convertedValue, Collections.singletonList(inValue));
   }
 
-  private DexMethod dexMethodWithDifferentReturn(DexMethod method, DexType newReturnType) {
-    DexProto newProto = factory.createProto(newReturnType, method.proto.parameters.values);
-    return factory.createMethod(method.holder, newProto, method.name);
+  private Instruction createReturnConversionAndReplaceUses(
+      IRCode code, InvokeMethod invokeMethod, DexType returnType, DexType returnVivifiedType) {
+    DexMethod conversionMethod = createConversionMethod(returnType, returnVivifiedType, returnType);
+    Value convertedValue = createConversionValue(code, Nullability.maybeNull(), returnType);
+    invokeMethod.outValue().replaceUsers(convertedValue);
+    return new InvokeStatic(
+        conversionMethod, convertedValue, Collections.singletonList(invokeMethod.outValue()));
+  }
+
+  private DexMethod createConversionMethod(DexType type, DexType srcType, DexType destType) {
+    // ConversionType holds the methods "rewrittenType convert(type)" and the other way around.
+    // But everything is going to be rewritten, so we need to use vivifiedType and type".
+    DexType conversionHolder =
+        appView.options().desugaredLibraryConfiguration.getCustomConversions().get(type);
+    return factory.createMethod(
+        conversionHolder, factory.createProto(destType, srcType), factory.convertMethodName);
+  }
+
+  private Value createConversionValue(IRCode code, Nullability nullability, DexType valueType) {
+    return code.createValue(TypeLatticeElement.fromDexType(valueType, nullability, appView));
   }
 }
diff --git a/src/test/desugaredLibraryConversions/stubs/Duration.java b/src/test/desugaredLibraryConversions/stubs/Duration.java
index e559a8f..e49693f 100644
--- a/src/test/desugaredLibraryConversions/stubs/Duration.java
+++ b/src/test/desugaredLibraryConversions/stubs/Duration.java
@@ -9,7 +9,7 @@
     return null;
   }
 
-  public int getSeconds() {
+  public long getSeconds() {
     return 0;
   }
 
diff --git a/src/test/java/com/android/tools/r8/desugar/corelib/conversionTests/AllTimeConversionTest.java b/src/test/java/com/android/tools/r8/desugar/corelib/conversionTests/AllTimeConversionTest.java
new file mode 100644
index 0000000..71e1a3f
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/desugar/corelib/conversionTests/AllTimeConversionTest.java
@@ -0,0 +1,125 @@
+// Copyright (c) 2019, 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.desugar.corelib.conversionTests;
+
+import com.android.tools.r8.TestRuntime.DexRuntime;
+import com.android.tools.r8.ToolHelper.DexVm;
+import com.android.tools.r8.utils.AndroidApiLevel;
+import com.android.tools.r8.utils.StringUtils;
+import java.nio.file.Path;
+import java.time.Duration;
+import java.time.Instant;
+import java.time.LocalDate;
+import java.time.MonthDay;
+import java.time.ZoneId;
+import java.time.ZonedDateTime;
+import org.junit.Test;
+
+public class AllTimeConversionTest extends APIConversionTestBase {
+
+  @Test
+  public void testRewrittenAPICalls() throws Exception {
+    Path customLib = testForD8().addProgramClasses(CustomLibClass.class).compile().writeToZip();
+    testForD8()
+        .setMinApi(AndroidApiLevel.B)
+        .addProgramClasses(Executor.class)
+        .addLibraryClasses(CustomLibClass.class)
+        .enableCoreLibraryDesugaring(AndroidApiLevel.B)
+        .compile()
+        .addDesugaredCoreLibraryRunClassPath(
+            this::buildDesugaredLibraryWithConversionExtension, AndroidApiLevel.B)
+        .addRunClasspathFiles(customLib)
+        .run(new DexRuntime(DexVm.ART_9_0_0_HOST), Executor.class)
+        .assertSuccessWithOutput(
+            StringUtils.lines(
+                "1970-01-02T00:00Z[GMT]",
+                "PT0.000012345S",
+                "GMT",
+                "--03-02",
+                "-1000000000-01-01T00:00:00.999999999Z",
+                "GMT",
+                "GMT"));
+  }
+
+  static class Executor {
+
+    private static final String ZONE_ID = "GMT";
+
+    public static void main(String[] args) {
+      returnValueUsed();
+      returnValueUnused();
+      virtualMethods();
+    }
+
+    public static void returnValueUsed() {
+      System.out.println(
+          CustomLibClass.mix(
+              ZonedDateTime.ofInstant(Instant.ofEpochSecond(0), ZoneId.of(ZONE_ID)),
+              ZonedDateTime.ofInstant(Instant.ofEpochSecond(0), ZoneId.of(ZONE_ID))));
+      CustomLibClass.mix(LocalDate.of(2000, 3, 13), LocalDate.of(1990, 5, 25));
+      System.out.println(CustomLibClass.mix(Duration.ZERO, Duration.ofNanos(12345)));
+      System.out.println(CustomLibClass.mix(ZoneId.of(ZONE_ID), ZoneId.of(ZONE_ID)));
+      System.out.println(CustomLibClass.mix(MonthDay.of(3, 4), MonthDay.of(1, 2)));
+      System.out.println(CustomLibClass.mix(Instant.MIN, Instant.MAX));
+    }
+
+    public static void returnValueUnused() {
+      CustomLibClass.mix(
+          ZonedDateTime.ofInstant(Instant.ofEpochSecond(0), ZoneId.of(ZONE_ID)),
+          ZonedDateTime.ofInstant(Instant.ofEpochSecond(0), ZoneId.of(ZONE_ID)));
+      CustomLibClass.mix(LocalDate.of(2000, 3, 13), LocalDate.of(1990, 5, 25));
+      CustomLibClass.mix(Duration.ZERO, Duration.ofNanos(12345));
+      CustomLibClass.mix(ZoneId.of(ZONE_ID), ZoneId.of(ZONE_ID));
+      CustomLibClass.mix(MonthDay.of(3, 4), MonthDay.of(1, 2));
+      CustomLibClass.mix(Instant.MIN, Instant.MAX);
+    }
+
+    public static void virtualMethods() {
+      ZoneId of = ZoneId.of(ZONE_ID);
+      CustomLibClass customLibClass = new CustomLibClass();
+      customLibClass.virtual(of);
+      customLibClass.virtualString(of);
+      System.out.println(customLibClass.virtual(of));
+      System.out.println(customLibClass.virtualString(of));
+    }
+  }
+
+  // This class will be put at compilation time as library and on the runtime class path.
+  // This class is convenient for easy testing. None of the methods make sense.
+  static class CustomLibClass {
+
+    public static ZonedDateTime mix(ZonedDateTime zonedDateTime1, ZonedDateTime zonedDateTime2) {
+      return zonedDateTime1.plusDays(zonedDateTime2.getDayOfMonth());
+    }
+
+    public static LocalDate mix(LocalDate localDate1, LocalDate localDate2) {
+      return localDate1.plusDays(localDate2.getDayOfYear());
+    }
+
+    public static Duration mix(Duration duration1, Duration duration2) {
+      return duration1.plus(duration2);
+    }
+
+    public static ZoneId mix(ZoneId zoneId1, ZoneId zoneId2) {
+      return zoneId1;
+    }
+
+    public static MonthDay mix(MonthDay monthDay1, MonthDay monthDay2) {
+      return monthDay1.withDayOfMonth(monthDay2.getDayOfMonth());
+    }
+
+    public static Instant mix(Instant instant1, Instant instant2) {
+      return instant1.plusNanos(instant2.getNano());
+    }
+
+    public ZoneId virtual(ZoneId zoneId) {
+      return zoneId;
+    }
+
+    public String virtualString(ZoneId zoneId) {
+      return zoneId.getId();
+    }
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/desugar/corelib/conversionTests/TimeConversionCompilationTest.java b/src/test/java/com/android/tools/r8/desugar/corelib/conversionTests/BasicTimeConversionTest.java
similarity index 97%
rename from src/test/java/com/android/tools/r8/desugar/corelib/conversionTests/TimeConversionCompilationTest.java
rename to src/test/java/com/android/tools/r8/desugar/corelib/conversionTests/BasicTimeConversionTest.java
index 2cdf6e7..e82ae21 100644
--- a/src/test/java/com/android/tools/r8/desugar/corelib/conversionTests/TimeConversionCompilationTest.java
+++ b/src/test/java/com/android/tools/r8/desugar/corelib/conversionTests/BasicTimeConversionTest.java
@@ -25,7 +25,7 @@
 import java.util.TimeZone;
 import org.junit.Test;
 
-public class TimeConversionCompilationTest extends APIConversionTestBase {
+public class BasicTimeConversionTest extends APIConversionTestBase {
 
   @Test
   public void testTimeGeneratedDex() throws Exception {
@@ -53,7 +53,7 @@
   public void testRewrittenAPICalls() throws Exception {
     testForD8()
         .setMinApi(AndroidApiLevel.B)
-        .addInnerClasses(TimeConversionCompilationTest.class)
+        .addInnerClasses(BasicTimeConversionTest.class)
         .enableCoreLibraryDesugaring(AndroidApiLevel.B)
         .compile()
         .inspect(this::checkAPIRewritten)