Warnings in wrapper generation

- Warning for large wrappers (Stream, ...)
- Warning for final methods (SummaryStatistics, ...)
- Error for final classes (Does not happen in practice AFAIK)
- Stream wrappers working (since final methods do not crash anymore)

Bug: 134732760
Change-Id: I9b8d7fe9aebb81a76c4cafac889f903eb0781c04
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 1f9c023..3bdaf06 100644
--- a/src/main/java/com/android/tools/r8/graph/DexItemFactory.java
+++ b/src/main/java/com/android/tools/r8/graph/DexItemFactory.java
@@ -185,6 +185,7 @@
   public final DexString invokeMethodName = createString("invoke");
   public final DexString invokeExactMethodName = createString("invokeExact");
 
+  public final DexString runtimeExceptionDescriptor = createString("Ljava/lang/RuntimeException;");
   public final DexString assertionErrorDescriptor = createString("Ljava/lang/AssertionError;");
   public final DexString charSequenceDescriptor = createString("Ljava/lang/CharSequence;");
   public final DexString charSequenceArrayDescriptor = createString("[Ljava/lang/CharSequence;");
@@ -325,6 +326,7 @@
   public final DexType runnableType = createType(runnableDescriptor);
   public final DexType optionalType = createType(optionalDescriptor);
 
+  public final DexType runtimeExceptionType = createType(runtimeExceptionDescriptor);
   public final DexType throwableType = createType(throwableDescriptor);
   public final DexType illegalAccessErrorType = createType(illegalAccessErrorDescriptor);
   public final DexType icceType = createType(icceDescriptor);
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 036e8b4..a9c70ca 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
@@ -26,6 +26,7 @@
 import com.android.tools.r8.graph.ParameterAnnotationsList;
 import com.android.tools.r8.ir.conversion.IRConverter;
 import com.android.tools.r8.ir.synthetic.DesugaredLibraryAPIConversionCfCodeProvider.APIConverterConstructorCfCodeProvider;
+import com.android.tools.r8.ir.synthetic.DesugaredLibraryAPIConversionCfCodeProvider.APIConverterThrowRuntimeExceptionCfCodeProvider;
 import com.android.tools.r8.ir.synthetic.DesugaredLibraryAPIConversionCfCodeProvider.APIConverterVivifiedWrapperCfCodeProvider;
 import com.android.tools.r8.ir.synthetic.DesugaredLibraryAPIConversionCfCodeProvider.APIConverterWrapperCfCodeProvider;
 import com.android.tools.r8.ir.synthetic.DesugaredLibraryAPIConversionCfCodeProvider.APIConverterWrapperConversionCfCodeProvider;
@@ -33,8 +34,10 @@
 import com.android.tools.r8.utils.Box;
 import com.android.tools.r8.utils.DescriptorUtils;
 import com.android.tools.r8.utils.Pair;
+import com.android.tools.r8.utils.StringDiagnostic;
 import com.google.common.collect.Sets;
 import java.util.ArrayList;
+import java.util.Arrays;
 import java.util.Collections;
 import java.util.LinkedList;
 import java.util.List;
@@ -99,6 +102,11 @@
       new ConcurrentHashMap<>();
   private final Map<DexType, Pair<DexType, DexProgramClass>> vivifiedTypeWrappers =
       new ConcurrentHashMap<>();
+  // The invalidWrappers are wrappers with incorrect behavior because of final methods that could
+  // not be overridden. Such wrappers are awful because the runtime behavior is undefined and does
+  // not raise explicit errors. So we register them here and conversion methods for such wrappers
+  // raise a runtime exception instead of generating the wrapper.
+  private final Set<DexType> invalidWrappers = Sets.newConcurrentHashSet();
   private final Set<DexType> generatedWrappers = Sets.newConcurrentHashSet();
   private final DexItemFactory factory;
   private final DesugaredLibraryAPIConverter converter;
@@ -166,6 +174,16 @@
       DexClass dexClass = appView.definitionFor(type);
       // The dexClass should be a library class, so it cannot be null.
       assert dexClass != null && dexClass.isLibraryClass();
+      if (dexClass.accessFlags.isFinal()) {
+        throw appView
+            .options()
+            .reporter
+            .fatalError(
+                new StringDiagnostic(
+                    "Cannot generate a wrapper for final class "
+                        + dexClass.type
+                        + ". Add a custom conversion in the desugared library."));
+      }
       pair.setSecond(wrapperGenerator.apply(dexClass, pair.getFirst()));
     }
     return pair.getFirst();
@@ -239,6 +257,7 @@
     //   v2 <- convertTypeToVivifiedType(v0);
     //   v3 <- wrappedValue.foo(v2,v1);
     //   return v3;
+    Set<DexMethod> finalMethods = Sets.newIdentityHashSet();
     for (DexEncodedMethod dexEncodedMethod : dexMethods) {
       DexClass holderClass = appView.definitionFor(dexEncodedMethod.method.holder);
       assert holderClass != null;
@@ -247,19 +266,26 @@
               wrapperField.field.holder,
               dexEncodedMethod.method.proto,
               dexEncodedMethod.method.name);
-      CfCode cfCode =
-          new APIConverterVivifiedWrapperCfCodeProvider(
-                  appView,
-                  methodToInstall,
-                  wrapperField.field,
-                  converter,
-                  holderClass.isInterface())
-              .generateCfCode();
+      CfCode cfCode;
+      if (dexEncodedMethod.isFinal()) {
+        invalidWrappers.add(wrapperField.field.holder);
+        finalMethods.add(dexEncodedMethod.method);
+        continue;
+      } else {
+        cfCode =
+            new APIConverterVivifiedWrapperCfCodeProvider(
+                    appView,
+                    methodToInstall,
+                    wrapperField.field,
+                    converter,
+                    holderClass.isInterface())
+                .generateCfCode();
+      }
       DexEncodedMethod newDexEncodedMethod =
           newSynthesizedMethod(methodToInstall, dexEncodedMethod, cfCode);
       generatedMethods.add(newDexEncodedMethod);
     }
-    return generatedMethods.toArray(DexEncodedMethod.EMPTY_ARRAY);
+    return finalizeWrapperMethods(generatedMethods, finalMethods);
   }
 
   private DexEncodedMethod[] synthesizeVirtualMethodsForTypeWrapper(
@@ -276,26 +302,58 @@
     //   v2 <- convertVivifiedTypeToType(v0);
     //   v3 <- wrappedValue.foo(v2,v1);
     //   return v3;
+    Set<DexMethod> finalMethods = Sets.newIdentityHashSet();
     for (DexEncodedMethod dexEncodedMethod : dexMethods) {
       DexClass holderClass = appView.definitionFor(dexEncodedMethod.method.holder);
       assert holderClass != null;
       DexMethod methodToInstall =
           converter.methodWithVivifiedTypeInSignature(
               dexEncodedMethod.method, wrapperField.field.holder);
-      CfCode cfCode =
-          new APIConverterWrapperCfCodeProvider(
-                  appView,
-                  dexEncodedMethod.method,
-                  wrapperField.field,
-                  converter,
-                  holderClass.isInterface())
-              .generateCfCode();
-
+      CfCode cfCode;
+      if (dexEncodedMethod.isFinal()) {
+        invalidWrappers.add(wrapperField.field.holder);
+        finalMethods.add(dexEncodedMethod.method);
+        continue;
+      } else {
+        cfCode =
+            new APIConverterWrapperCfCodeProvider(
+                    appView,
+                    dexEncodedMethod.method,
+                    wrapperField.field,
+                    converter,
+                    holderClass.isInterface())
+                .generateCfCode();
+      }
       DexEncodedMethod newDexEncodedMethod =
           newSynthesizedMethod(methodToInstall, dexEncodedMethod, cfCode);
       generatedMethods.add(newDexEncodedMethod);
     }
-    return generatedMethods.toArray(DexEncodedMethod.EMPTY_ARRAY);
+    return finalizeWrapperMethods(generatedMethods, finalMethods);
+  }
+
+  private DexEncodedMethod[] finalizeWrapperMethods(
+      List<DexEncodedMethod> generatedMethods, Set<DexMethod> finalMethods) {
+    if (finalMethods.isEmpty()) {
+      return generatedMethods.toArray(DexEncodedMethod.EMPTY_ARRAY);
+    }
+    // Wrapper is invalid, no need to add the virtual methods.
+    reportFinalMethodsInWrapper(finalMethods);
+    return DexEncodedMethod.EMPTY_ARRAY;
+  }
+
+  private void reportFinalMethodsInWrapper(Set<DexMethod> methods) {
+    String[] methodArray =
+        methods.stream().map(method -> method.holder + "#" + method.name).toArray(String[]::new);
+    appView
+        .options()
+        .reporter
+        .warning(
+            new StringDiagnostic(
+                "Desugared library API conversion: cannot wrap final methods "
+                    + Arrays.toString(methodArray)
+                    + ". "
+                    + methods.iterator().next().holder
+                    + " is marked as invalid and will throw a runtime exception upon conversion."));
   }
 
   DexEncodedMethod newSynthesizedMethod(
@@ -346,6 +404,17 @@
         workList.add(superClass);
       }
     }
+    // 10 is large enough to avoid warnings on Clock/Function, but not on Stream.
+    if (implementedMethods.size() > 10) {
+      appView
+          .options()
+          .reporter
+          .warning(
+              new StringDiagnostic(
+                  "Desugared library API conversion: Generating a large wrapper for "
+                      + libraryClass.type
+                      + ". Is that the intended behavior?"));
+    }
     return implementedMethods;
   }
 
@@ -439,6 +508,7 @@
         synthesizeConversionMethod(
             synthesizedClass.type,
             type,
+            type,
             converter.vivifiedTypeFor(type),
             reverse == null ? null : reverse.getSecond()));
   }
@@ -448,28 +518,45 @@
     synthesizedClass.addDirectMethod(
         synthesizeConversionMethod(
             synthesizedClass.type,
+            type,
             converter.vivifiedTypeFor(type),
             type,
             reverse == null ? null : reverse.getSecond()));
   }
 
   private DexEncodedMethod synthesizeConversionMethod(
-      DexType holder, DexType argType, DexType returnType, DexClass reverseWrapperClassOrNull) {
+      DexType holder,
+      DexType type,
+      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,
-                uniqueFieldOrNull,
-                factory.createField(holder, returnType, factory.wrapperFieldName))
-            .generateCfCode();
+    CfCode cfCode;
+    if (invalidWrappers.contains(holder)) {
+      cfCode =
+          new APIConverterThrowRuntimeExceptionCfCodeProvider(
+                  appView,
+                  factory.createString(
+                      "Unsupported conversion for "
+                          + type
+                          + ". See compilation time warnings for more infos."),
+                  holder)
+              .generateCfCode();
+    } else {
+      DexField uniqueFieldOrNull =
+          reverseWrapperClassOrNull == null
+              ? null
+              : reverseWrapperClassOrNull.instanceFields().get(0).field;
+      cfCode =
+          new APIConverterWrapperConversionCfCodeProvider(
+                  appView,
+                  argType,
+                  uniqueFieldOrNull,
+                  factory.createField(holder, returnType, factory.wrapperFieldName))
+              .generateCfCode();
+    }
     return newSynthesizedMethod(
         method,
         Constants.ACC_SYNTHETIC | Constants.ACC_STATIC | Constants.ACC_PUBLIC,
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 2a1d196..60ca8ca 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
@@ -6,6 +6,7 @@
 
 import com.android.tools.r8.cf.code.CfCheckCast;
 import com.android.tools.r8.cf.code.CfConstNull;
+import com.android.tools.r8.cf.code.CfConstString;
 import com.android.tools.r8.cf.code.CfFieldInstruction;
 import com.android.tools.r8.cf.code.CfIf;
 import com.android.tools.r8.cf.code.CfInstanceOf;
@@ -17,6 +18,7 @@
 import com.android.tools.r8.cf.code.CfReturn;
 import com.android.tools.r8.cf.code.CfReturnVoid;
 import com.android.tools.r8.cf.code.CfStackInstruction;
+import com.android.tools.r8.cf.code.CfThrow;
 import com.android.tools.r8.graph.AppView;
 import com.android.tools.r8.graph.CfCode;
 import com.android.tools.r8.graph.DexField;
@@ -305,4 +307,34 @@
       return standardCfCodeFromInstructions(instructions);
     }
   }
+
+  public static class APIConverterThrowRuntimeExceptionCfCodeProvider
+      extends SyntheticCfCodeProvider {
+    DexString message;
+
+    public APIConverterThrowRuntimeExceptionCfCodeProvider(
+        AppView<?> appView, DexString message, DexType holder) {
+      super(appView, holder);
+      this.message = message;
+    }
+
+    @Override
+    public CfCode generateCfCode() {
+      DexItemFactory factory = appView.dexItemFactory();
+      List<CfInstruction> instructions = new ArrayList<>();
+      instructions.add(new CfNew(factory.runtimeExceptionType));
+      instructions.add(CfStackInstruction.fromAsm(Opcodes.DUP));
+      instructions.add(new CfConstString(message));
+      instructions.add(
+          new CfInvoke(
+              Opcodes.INVOKESPECIAL,
+              factory.createMethod(
+                  factory.runtimeExceptionType,
+                  factory.createProto(factory.voidType, factory.stringType),
+                  factory.initMethodName),
+              false));
+      instructions.add(new CfThrow());
+      return standardCfCodeFromInstructions(instructions);
+    }
+  }
 }
diff --git a/src/test/java/com/android/tools/r8/desugar/corelib/conversionTests/APIConversionFinalClassErrorTest.java b/src/test/java/com/android/tools/r8/desugar/corelib/conversionTests/APIConversionFinalClassErrorTest.java
new file mode 100644
index 0000000..c2297ad
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/desugar/corelib/conversionTests/APIConversionFinalClassErrorTest.java
@@ -0,0 +1,56 @@
+// 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 static junit.framework.TestCase.assertEquals;
+import static junit.framework.TestCase.fail;
+
+import com.android.tools.r8.CompilationFailedException;
+import com.android.tools.r8.TestDiagnosticMessages;
+import com.android.tools.r8.utils.AndroidApiLevel;
+import java.time.Year;
+import org.junit.Test;
+
+public class APIConversionFinalClassErrorTest extends APIConversionTestBase {
+
+  @Test
+  public void testFinalMethod() {
+    try {
+      testForD8()
+          .setMinApi(AndroidApiLevel.B)
+          .addProgramClasses(Executor.class)
+          .addLibraryClasses(CustomLibClass.class)
+          .enableCoreLibraryDesugaring(AndroidApiLevel.B)
+          .compileWithExpectedDiagnostics(this::assertDiagnosis);
+      fail("Expected compilation error");
+    } catch (CompilationFailedException ignored) {
+
+    }
+  }
+
+  private void assertDiagnosis(TestDiagnosticMessages d) {
+    assertEquals(
+        "Cannot generate a wrapper for final class java.time.Year."
+            + " Add a custom conversion in the desugared library.",
+        d.getErrors().get(0).getDiagnosticMessage());
+  }
+
+  static class Executor {
+
+    public static void main(String[] args) {
+      System.out.println(CustomLibClass.call(Year.now()));
+    }
+  }
+
+  // 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 {
+
+    public static long call(Year year) {
+      return 0L;
+    }
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/desugar/corelib/conversionTests/APIConversionFinalWarningTest.java b/src/test/java/com/android/tools/r8/desugar/corelib/conversionTests/APIConversionFinalWarningTest.java
new file mode 100644
index 0000000..10b6d75
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/desugar/corelib/conversionTests/APIConversionFinalWarningTest.java
@@ -0,0 +1,69 @@
+// 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 static org.hamcrest.CoreMatchers.startsWith;
+
+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.LongSummaryStatistics;
+import org.junit.Test;
+
+public class APIConversionFinalWarningTest extends APIConversionTestBase {
+
+  @Test
+  public void testFinalMethod() throws Exception {
+    Path customLib = testForD8().addProgramClasses(CustomLibClass.class).compile().writeToZip();
+    testForD8()
+        .setMinApi(AndroidApiLevel.B)
+        .addProgramClasses(Executor.class)
+        .addLibraryClasses(CustomLibClass.class)
+        .enableCoreLibraryDesugaring(AndroidApiLevel.B)
+        .compile()
+        .assertWarningMessageThatMatches(
+            startsWith(
+                "Desugared library API conversion: cannot wrap final methods"
+                    + " [java.util.LongSummaryStatistics"))
+        .addDesugaredCoreLibraryRunClassPath(
+            this::buildDesugaredLibraryWithConversionExtension, AndroidApiLevel.B)
+        .addRunClasspathFiles(customLib)
+        .run(new DexRuntime(DexVm.ART_9_0_0_HOST), Executor.class)
+        .assertSuccessWithOutput(
+            StringUtils.lines(
+                "Unsupported conversion for java.util.LongSummaryStatistics. See compilation time"
+                    + " warnings for more infos."));
+  }
+
+  static class Executor {
+
+    public static void main(String[] args) {
+      LongSummaryStatistics statistics = new LongSummaryStatistics();
+      statistics.accept(3L);
+      try {
+        makeCall(statistics);
+      } catch (RuntimeException e) {
+        System.out.println(e.getMessage());
+      }
+    }
+
+    @SuppressWarnings("ResultOfMethodCallIgnored")
+    static void makeCall(LongSummaryStatistics statistics) {
+      CustomLibClass.call(statistics);
+    }
+  }
+
+  // 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 {
+
+    public static long call(LongSummaryStatistics stats) {
+      return stats.getMax();
+    }
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/desugar/corelib/conversionTests/APIConversionLargeWarningTest.java b/src/test/java/com/android/tools/r8/desugar/corelib/conversionTests/APIConversionLargeWarningTest.java
new file mode 100644
index 0000000..efbe9b6
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/desugar/corelib/conversionTests/APIConversionLargeWarningTest.java
@@ -0,0 +1,60 @@
+// 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 static org.hamcrest.CoreMatchers.startsWith;
+
+import com.android.tools.r8.utils.AndroidApiLevel;
+import java.nio.file.Path;
+import java.time.Clock;
+import java.util.function.Function;
+import java.util.stream.Stream;
+import org.junit.Test;
+
+public class APIConversionLargeWarningTest extends APIConversionTestBase {
+
+  @Test
+  public void testFinalMethod() throws Exception {
+    Path customLib = testForD8().addProgramClasses(CustomLibClass.class).compile().writeToZip();
+    testForD8()
+        .setMinApi(AndroidApiLevel.B)
+        .addProgramClasses(Executor.class)
+        .addLibraryClasses(CustomLibClass.class)
+        .enableCoreLibraryDesugaring(AndroidApiLevel.B)
+        .compile()
+        .assertWarningMessageThatMatches(
+            startsWith(
+                "Desugared library API conversion: Generating a large wrapper for"
+                    + " java.util.stream.Stream"))
+        .assertNoWarningMessageThatMatches(
+            startsWith(
+                "Desugared library API conversion: Generating a large wrapper for java.time.Clock"))
+        .assertNoWarningMessageThatMatches(
+            startsWith(
+                "Desugared library API conversion: Generating a large wrapper for"
+                    + " java.util.function.Function"));
+  }
+
+  static class Executor {
+
+    public static void main(String[] args) {
+      CustomLibClass.callClock(Clock.systemUTC());
+      CustomLibClass.callStream(Stream.empty());
+      CustomLibClass.callFunction(x -> x);
+    }
+  }
+
+  // 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 {
+
+    public static void callStream(Stream stream) {}
+
+    public static void callClock(Clock clock) {}
+
+    public static void callFunction(Function<String, String> func) {}
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/desugar/corelib/conversionTests/APIConversionTest.java b/src/test/java/com/android/tools/r8/desugar/corelib/conversionTests/APIConversionTest.java
index 4fe9a9d..e3e8c77 100644
--- a/src/test/java/com/android/tools/r8/desugar/corelib/conversionTests/APIConversionTest.java
+++ b/src/test/java/com/android/tools/r8/desugar/corelib/conversionTests/APIConversionTest.java
@@ -1,7 +1,6 @@
 // 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 static org.hamcrest.CoreMatchers.endsWith;
@@ -14,7 +13,9 @@
 import com.android.tools.r8.utils.AndroidApiLevel;
 import com.android.tools.r8.utils.StringUtils;
 import java.util.Arrays;
+import java.util.Random;
 import java.util.function.IntUnaryOperator;
+import java.util.stream.IntStream;
 import org.junit.Test;
 import org.junit.runner.RunWith;
 import org.junit.runners.Parameterized;
@@ -47,12 +48,13 @@
         .assertNoWarningMessageThatMatches(containsString("java.util.Random#ints"))
         .assertNoWarningMessageThatMatches(endsWith("is a desugared type)."))
         .run(parameters.getRuntime(), Executor.class)
-        .assertSuccessWithOutput(StringUtils.lines("[5, 6, 7]"));
+        .assertSuccessWithOutput(
+            StringUtils.lines(
+                "[5, 6, 7]", "java.util.stream.IntPipeline$Head", "IntSummaryStatistics"));
   }
 
   @Test
   public void testAPIConversionDesugaring() throws Exception {
-    // TODO(b/134732760): Make library API work when library desugaring is on for Stream.
     testForD8()
         .addInnerClasses(APIConversionTest.class)
         .setMinApi(parameters.getApiLevel())
@@ -60,7 +62,12 @@
         .compile()
         .addDesugaredCoreLibraryRunClassPath(this::buildDesugaredLibrary, parameters.getApiLevel())
         .run(parameters.getRuntime(), Executor.class)
-        .assertSuccessWithOutput(StringUtils.lines("[5, 6, 7]"));
+        .assertSuccessWithOutput(
+            StringUtils.lines(
+                "[5, 6, 7]",
+                "j$.util.stream.IntStream$-V-WRP",
+                "Unsupported conversion for java.util.IntSummaryStatistics. See compilation time"
+                    + " warnings for more infos."));
   }
 
   static class Executor {
@@ -69,9 +76,31 @@
       int[] ints = new int[3];
       Arrays.setAll(ints, new MyFunction());
       System.out.println(Arrays.toString(ints));
-      // TODO(b/134732760): Support Stream wrappers.
-      // IntStream intStream = new Random().ints();
-      // System.out.println(intStream.getClass().getName());
+      IntStream intStream = new Random().ints();
+      System.out.println(intStream.getClass().getName());
+      CharSequence charSequence =
+          new CharSequence() {
+            @Override
+            public int length() {
+              return 1;
+            }
+
+            @Override
+            public char charAt(int index) {
+              return 42;
+            }
+
+            @Override
+            public CharSequence subSequence(int start, int end) {
+              return null;
+            }
+          };
+      IntStream fixedSizedIntStream = charSequence.codePoints();
+      try {
+        System.out.println(fixedSizedIntStream.summaryStatistics().getClass().getSimpleName());
+      } catch (RuntimeException e) {
+        System.out.println(e.getMessage());
+      }
     }
   }