Desugared library API unwrap tests

- Fixed unwrapping
- tests for unwrapping
- tests for wrappers without reverse
  wrappers (was broken due to unwrapping)
- patch comments

Bug: 134732760
Change-Id: I52bcf700bb0d81380bf6a39aad577063c2aa1437
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/DesugaredLibraryWrapperSynthesizer.java b/src/main/java/com/android/tools/r8/ir/desugar/DesugaredLibraryWrapperSynthesizer.java
index c87b921..fe410fa 100644
--- a/src/main/java/com/android/tools/r8/ir/desugar/DesugaredLibraryWrapperSynthesizer.java
+++ b/src/main/java/com/android/tools/r8/ir/desugar/DesugaredLibraryWrapperSynthesizer.java
@@ -358,8 +358,9 @@
 
   private DexEncodedField synthetizeWrappedValueField(DexType holder, DexType fieldType) {
     DexField field = factory.createField(holder, fieldType, factory.wrapperFieldName);
+    // Field is package private to be accessible from convert methods without a getter.
     FieldAccessFlags fieldAccessFlags =
-        FieldAccessFlags.fromCfAccessFlags(Constants.ACC_FINAL | Constants.ACC_PRIVATE);
+        FieldAccessFlags.fromCfAccessFlags(Constants.ACC_FINAL | Constants.ACC_SYNTHETIC);
     return new DexEncodedField(field, fieldAccessFlags, DexAnnotationSet.empty(), null);
   }
 
@@ -412,6 +413,7 @@
       Map<DexType, Pair<DexType, DexProgramClass>> wrappers,
       BiConsumer<DexType, DexProgramClass> generateConversions)
       throws ExecutionException {
+    assert verifyAllClassesGenerated();
     for (DexType type : wrappers.keySet()) {
       DexProgramClass pgrmClass = wrappers.get(type).getSecond();
       assert pgrmClass != null;
@@ -421,6 +423,16 @@
     }
   }
 
+  private boolean verifyAllClassesGenerated() {
+    for (Pair<DexType, DexProgramClass> pair : vivifiedTypeWrappers.values()) {
+      assert pair.getSecond() != null;
+    }
+    for (Pair<DexType, DexProgramClass> pair : typeWrappers.values()) {
+      assert pair.getSecond() != null;
+    }
+    return true;
+  }
+
   private void registerSynthesizedClass(
       DexProgramClass synthesizedClass, DexApplication.Builder<?> builder) {
     builder.addSynthesizedClass(synthesizedClass, false);
@@ -428,33 +440,41 @@
   }
 
   private void generateTypeConversions(DexType type, DexProgramClass synthesizedClass) {
+    Pair<DexType, DexProgramClass> reverse = vivifiedTypeWrappers.get(type);
+    assert reverse == null || reverse.getSecond() != null;
     synthesizedClass.addDirectMethod(
         synthetizeConversionMethod(
             synthesizedClass.type,
             type,
             converter.vivifiedTypeFor(type),
-            typeWrappers.get(type).getFirst()));
+            reverse == null ? null : reverse.getSecond()));
   }
 
   private void generateVivifiedTypeConversions(DexType type, DexProgramClass synthesizedClass) {
+    Pair<DexType, DexProgramClass> reverse = typeWrappers.get(type);
     synthesizedClass.addDirectMethod(
         synthetizeConversionMethod(
             synthesizedClass.type,
             converter.vivifiedTypeFor(type),
             type,
-            vivifiedTypeWrappers.get(type).getFirst()));
+            reverse == null ? null : reverse.getSecond()));
   }
 
   private DexEncodedMethod synthetizeConversionMethod(
-      DexType holder, DexType argType, DexType returnType, DexType reverseWrapperType) {
+      DexType holder, DexType argType, DexType returnType, DexClass reverseWrapperClassOrNull) {
     DexMethod method =
         factory.createMethod(
             holder, factory.createProto(returnType, argType), factory.convertMethodName);
+
+    DexField uniqueFieldOrNull =
+        reverseWrapperClassOrNull == null
+            ? null
+            : reverseWrapperClassOrNull.instanceFields().get(0).field;
     CfCode cfCode =
         new APIConverterWrapperConversionCfCodeProvider(
                 appView,
                 argType,
-                reverseWrapperType,
+                uniqueFieldOrNull,
                 factory.createField(holder, returnType, factory.wrapperFieldName))
             .generateCfCode();
     return newSynthesizedMethod(
diff --git a/src/main/java/com/android/tools/r8/ir/synthetic/DesugaredLibraryAPIConversionCfCodeProvider.java b/src/main/java/com/android/tools/r8/ir/synthetic/DesugaredLibraryAPIConversionCfCodeProvider.java
index 00eb005..c98f1fa 100644
--- a/src/main/java/com/android/tools/r8/ir/synthetic/DesugaredLibraryAPIConversionCfCodeProvider.java
+++ b/src/main/java/com/android/tools/r8/ir/synthetic/DesugaredLibraryAPIConversionCfCodeProvider.java
@@ -198,14 +198,14 @@
   public static class APIConverterWrapperConversionCfCodeProvider extends SyntheticCfCodeProvider {
 
     DexType argType;
-    DexType reverseWrapperType;
+    DexField reverseWrapperField;
     DexField wrapperField;
 
     public APIConverterWrapperConversionCfCodeProvider(
-        AppView<?> appView, DexType argType, DexType reverseWrapperType, DexField wrapperField) {
+        AppView<?> appView, DexType argType, DexField reverseWrapperField, DexField wrapperField) {
       super(appView, wrapperField.holder);
       this.argType = argType;
-      this.reverseWrapperType = reverseWrapperType;
+      this.reverseWrapperField = reverseWrapperField;
       this.wrapperField = wrapperField;
     }
 
@@ -222,16 +222,18 @@
       instructions.add(new CfReturn(ValueType.OBJECT));
       instructions.add(nullDest);
 
+      // This part is skipped if there is no reverse wrapper.
       // if (arg instanceOf ReverseWrapper) { return ((ReverseWrapper) arg).wrapperField};
-      if (reverseWrapperType != null) {
+      if (reverseWrapperField != null) {
         CfLabel unwrapDest = new CfLabel();
         instructions.add(new CfLoad(ValueType.fromDexType(argType), 0));
-        instructions.add(new CfInstanceOf(reverseWrapperType));
+        instructions.add(new CfInstanceOf(reverseWrapperField.holder));
         instructions.add(new CfIf(If.Type.EQ, ValueType.INT, unwrapDest));
         instructions.add(new CfLoad(ValueType.fromDexType(argType), 0));
-        instructions.add(new CfCheckCast(reverseWrapperType));
-        instructions.add(new CfFieldInstruction(Opcodes.GETFIELD, wrapperField, wrapperField));
-        instructions.add(new CfReturn(ValueType.fromDexType(wrapperField.type)));
+        instructions.add(new CfCheckCast(reverseWrapperField.holder));
+        instructions.add(
+            new CfFieldInstruction(Opcodes.GETFIELD, reverseWrapperField, reverseWrapperField));
+        instructions.add(new CfReturn(ValueType.fromDexType(reverseWrapperField.type)));
         instructions.add(unwrapDest);
       }
 
diff --git a/src/test/java/com/android/tools/r8/desugar/corelib/conversionTests/AllOptionalConversionTest.java b/src/test/java/com/android/tools/r8/desugar/corelib/conversionTests/AllOptionalConversionTest.java
index 0b34b7f..6c069d9 100644
--- a/src/test/java/com/android/tools/r8/desugar/corelib/conversionTests/AllOptionalConversionTest.java
+++ b/src/test/java/com/android/tools/r8/desugar/corelib/conversionTests/AllOptionalConversionTest.java
@@ -75,7 +75,8 @@
   }
 
   // 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.
+  // This class is convenient for easy testing. Each method plays the role of methods in the
+  // platform APIs for which argument/return values need conversion.
   static class CustomLibClass {
 
     @SuppressWarnings("all")
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
index 71e1a3f..ed5ca26 100644
--- 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
@@ -87,7 +87,8 @@
   }
 
   // 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.
+  // This class is convenient for easy testing. Each method plays the role of methods in the
+  // platform APIs for which argument/return values need conversion.
   static class CustomLibClass {
 
     public static ZonedDateTime mix(ZonedDateTime zonedDateTime1, ZonedDateTime zonedDateTime2) {
diff --git a/src/test/java/com/android/tools/r8/desugar/corelib/conversionTests/BasicLongDoubleConversionTest.java b/src/test/java/com/android/tools/r8/desugar/corelib/conversionTests/BasicLongDoubleConversionTest.java
index 18480ee..cd6dd54 100644
--- a/src/test/java/com/android/tools/r8/desugar/corelib/conversionTests/BasicLongDoubleConversionTest.java
+++ b/src/test/java/com/android/tools/r8/desugar/corelib/conversionTests/BasicLongDoubleConversionTest.java
@@ -42,7 +42,8 @@
   }
 
   // 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.
+  // This class is convenient for easy testing. Each method plays the role of methods in the
+  // platform APIs for which argument/return values need conversion.
   static class CustomLibClass {
 
     public static MonthDay mix(
diff --git a/src/test/java/com/android/tools/r8/desugar/corelib/conversionTests/FunctionConversionsTest.java b/src/test/java/com/android/tools/r8/desugar/corelib/conversionTests/FunctionConversionTest.java
similarity index 69%
rename from src/test/java/com/android/tools/r8/desugar/corelib/conversionTests/FunctionConversionsTest.java
rename to src/test/java/com/android/tools/r8/desugar/corelib/conversionTests/FunctionConversionTest.java
index 1743557..702eb05 100644
--- a/src/test/java/com/android/tools/r8/desugar/corelib/conversionTests/FunctionConversionsTest.java
+++ b/src/test/java/com/android/tools/r8/desugar/corelib/conversionTests/FunctionConversionTest.java
@@ -4,21 +4,29 @@
 
 package com.android.tools.r8.desugar.corelib.conversionTests;
 
+import static junit.framework.TestCase.assertEquals;
+
 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 com.android.tools.r8.utils.codeinspector.CodeInspector;
+import com.android.tools.r8.utils.codeinspector.FoundClassSubject;
 import java.nio.file.Path;
+import java.util.List;
 import java.util.function.BiFunction;
 import java.util.function.BinaryOperator;
 import java.util.function.BooleanSupplier;
 import java.util.function.DoublePredicate;
+import java.util.function.DoubleSupplier;
 import java.util.function.Function;
+import java.util.function.IntSupplier;
 import java.util.function.LongConsumer;
 import java.util.function.LongSupplier;
+import java.util.stream.Collectors;
 import org.junit.Test;
 
-public class FunctionConversionsTest extends APIConversionTestBase {
+public class FunctionConversionTest extends APIConversionTestBase {
 
   @Test
   public void testFunctionComposition() throws Exception {
@@ -30,12 +38,33 @@
         .addLibraryClasses(CustomLibClass.class)
         .enableCoreLibraryDesugaring(AndroidApiLevel.B)
         .compile()
+        .inspect(this::assertSingleWrappers)
         .addDesugaredCoreLibraryRunClassPath(
             this::buildDesugaredLibraryWithConversionExtension, AndroidApiLevel.B)
         .addRunClasspathFiles(customLib)
         .run(new DexRuntime(DexVm.ART_9_0_0_HOST), Executor.class)
         .assertSuccessWithOutput(
-            StringUtils.lines("Object1 Object2 Object3", "2", "false", "3", "true"));
+            StringUtils.lines("Object1 Object2 Object3", "2", "false", "3", "true", "5", "42.0"));
+  }
+
+  private void assertSingleWrappers(CodeInspector i) {
+    List<FoundClassSubject> intSupplierWrapperClasses =
+        i.allClasses().stream()
+            .filter(c -> c.getOriginalName().contains("IntSupplier"))
+            .collect(Collectors.toList());
+    assertEquals(
+        "Expected 1 IntSupplier wrapper but got " + intSupplierWrapperClasses,
+        1,
+        intSupplierWrapperClasses.size());
+
+    List<FoundClassSubject> doubleSupplierWrapperClasses =
+        i.allClasses().stream()
+            .filter(c -> c.getOriginalName().contains("DoubleSupplier"))
+            .collect(Collectors.toList());
+    assertEquals(
+        "Expected 1 DoubleSupplier wrapper but got " + doubleSupplierWrapperClasses,
+        1,
+        doubleSupplierWrapperClasses.size());
   }
 
   static class Executor {
@@ -53,6 +82,9 @@
       DoublePredicate doublePredicate =
           CustomLibClass.mixPredicate(d -> d > 1.0, d -> d == 2.0, d -> d < 3.0);
       System.out.println(doublePredicate.test(2.0));
+      // Reverse wrapper should not exist.
+      System.out.println(CustomLibClass.extractInt(() -> 5));
+      System.out.println(CustomLibClass.getDoubleSupplier().getAsDouble());
     }
 
     static class Object1 {}
@@ -85,7 +117,8 @@
   }
 
   // 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.
+  // This class is convenient for easy testing. Each method plays the role of methods in the
+  // platform APIs for which argument/return values need conversion.
   static class CustomLibClass {
 
     public static <T, Q, R> Function<T, R> mixFunction(Function<T, Q> f1, Function<Q, R> f2) {
@@ -110,5 +143,13 @@
         DoublePredicate predicate1, DoublePredicate predicate2, DoublePredicate predicate3) {
       return predicate1.and(predicate2).and(predicate3);
     }
+
+    public static int extractInt(IntSupplier supplier) {
+      return supplier.getAsInt();
+    }
+
+    public static DoubleSupplier getDoubleSupplier() {
+      return () -> 42.0;
+    }
   }
 }
diff --git a/src/test/java/com/android/tools/r8/desugar/corelib/conversionTests/UnwrapConversionTest.java b/src/test/java/com/android/tools/r8/desugar/corelib/conversionTests/UnwrapConversionTest.java
new file mode 100644
index 0000000..8fe7504
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/desugar/corelib/conversionTests/UnwrapConversionTest.java
@@ -0,0 +1,70 @@
+// 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.util.function.DoubleConsumer;
+import java.util.function.IntConsumer;
+import org.junit.Test;
+
+public class UnwrapConversionTest extends APIConversionTestBase {
+
+  @Test
+  public void testUnwrap() 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("true", "true"));
+  }
+
+  static class Executor {
+
+    @SuppressWarnings("all")
+    public static void main(String[] args) {
+      // Type wrapper.
+      IntConsumer intConsumer = i -> {};
+      IntConsumer unwrappedIntConsumer = CustomLibClass.identity(intConsumer);
+      System.out.println(intConsumer == unwrappedIntConsumer);
+
+      // Vivified wrapper.
+      DoubleConsumer consumer = CustomLibClass.getConsumer();
+      System.out.println(CustomLibClass.testConsumer(consumer));
+    }
+  }
+
+  // This class will be put at compilation time as library and on the runtime class path.
+  // This class is convenient for easy testing. Each method plays the role of methods in the
+  // platform APIs for which argument/return values need conversion.
+  static class CustomLibClass {
+
+    private static DoubleConsumer consumer = d -> {};
+
+    @SuppressWarnings("WeakerAccess")
+    public static IntConsumer identity(IntConsumer intConsumer) {
+      return intConsumer;
+    }
+
+    public static DoubleConsumer getConsumer() {
+      return consumer;
+    }
+
+    @SuppressWarnings("WeakerAccess")
+    public static boolean testConsumer(DoubleConsumer doubleConsumer) {
+      return doubleConsumer == consumer;
+    }
+  }
+}