Delegate wrapper conversion to subtypes

- Support OpenOption and BasicFileAttributes conversions
  without custom conversion
- Fix issues with wrapping/unwrapping and subtypes

Change-Id: I056809fc3192887830796b0ce0f379631b4a3af8
diff --git a/buildSrc/src/main/java/desugaredlibrary/CustomConversionAsmRewriteDescription.java b/buildSrc/src/main/java/desugaredlibrary/CustomConversionAsmRewriteDescription.java
index 137ac12..2b46e36 100644
--- a/buildSrc/src/main/java/desugaredlibrary/CustomConversionAsmRewriteDescription.java
+++ b/buildSrc/src/main/java/desugaredlibrary/CustomConversionAsmRewriteDescription.java
@@ -17,8 +17,6 @@
 
   private static final Set<String> ENUM_WRAP_CONVERT_OWNER =
       ImmutableSet.of(
-          "j$/nio/file/StandardOpenOption",
-          "j$/nio/file/LinkOption",
           "j$/nio/file/attribute/PosixFilePermission",
           "j$/util/stream/Collector$Characteristics");
   private static final Set<String> WRAP_CONVERT_OWNER =
@@ -27,18 +25,14 @@
           "j$/nio/file/spi/FileTypeDetector",
           "j$/nio/file/Path",
           "j$/nio/file/WatchEvent",
-          "j$/nio/file/attribute/BasicFileAttributes",
-          "j$/nio/file/attribute/BasicFileAttributeView",
-          "j$/nio/file/attribute/FileOwnerAttributeView",
-          "j$/nio/file/attribute/PosixFileAttributes",
-          "j$/nio/file/attribute/PosixFileAttributeView");
+          "j$/nio/file/OpenOption");
 
   static Map<String, String> getJavaWrapConvertOwnerMap() {
-    return computeConvertOwnerMap("$VivifiedWrapper");
+    return computeConvertOwnerMap("$Wrapper");
   }
 
   static Map<String, String> getJ$WrapConvertOwnerMap() {
-    return computeConvertOwnerMap("$Wrapper");
+    return computeConvertOwnerMap("$VivifiedWrapper");
   }
 
   private static HashMap<String, String> computeConvertOwnerMap(String suffix) {
diff --git a/src/library_desugar/java/j$/nio/file/OpenOption.java b/src/library_desugar/java/j$/nio/file/OpenOption.java
index 8b063d1..a22eb7c 100644
--- a/src/library_desugar/java/j$/nio/file/OpenOption.java
+++ b/src/library_desugar/java/j$/nio/file/OpenOption.java
@@ -4,4 +4,12 @@
 
 package j$.nio.file;
 
-public class OpenOption {}
+public class OpenOption {
+  public static java.nio.file.OpenOption wrap_convert(j$.nio.file.OpenOption option) {
+    return null;
+  }
+
+  public static j$.nio.file.OpenOption wrap_convert(java.nio.file.OpenOption option) {
+    return null;
+  }
+}
diff --git a/src/library_desugar/java/j$/nio/file/Path.java b/src/library_desugar/java/j$/nio/file/Path.java
index cb01ec0..781ab5f 100644
--- a/src/library_desugar/java/j$/nio/file/Path.java
+++ b/src/library_desugar/java/j$/nio/file/Path.java
@@ -6,7 +6,7 @@
 
 public class Path {
 
-  public static j$.nio.file.Path inverted_wrap_convert(java.nio.file.Path path) {
+  public static j$.nio.file.Path wrap_convert(java.nio.file.Path path) {
     return null;
   }
 }
diff --git a/src/library_desugar/java/j$/nio/file/StandardOpenOption.java b/src/library_desugar/java/j$/nio/file/StandardOpenOption.java
deleted file mode 100644
index a65da37..0000000
--- a/src/library_desugar/java/j$/nio/file/StandardOpenOption.java
+++ /dev/null
@@ -1,18 +0,0 @@
-// Copyright (c) 2022, 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 j$.nio.file;
-
-public class StandardOpenOption extends OpenOption {
-
-  public static java.nio.file.StandardOpenOption wrap_convert(
-      j$.nio.file.StandardOpenOption option) {
-    return null;
-  }
-
-  public static j$.nio.file.StandardOpenOption wrap_convert(
-      java.nio.file.StandardOpenOption option) {
-    return null;
-  }
-}
diff --git a/src/library_desugar/java/j$/nio/file/spi/FileSystemProvider.java b/src/library_desugar/java/j$/nio/file/spi/FileSystemProvider.java
index 38152b3..8db22b3 100644
--- a/src/library_desugar/java/j$/nio/file/spi/FileSystemProvider.java
+++ b/src/library_desugar/java/j$/nio/file/spi/FileSystemProvider.java
@@ -5,7 +5,8 @@
 package j$.nio.file.spi;
 
 public class FileSystemProvider {
-  public static java.nio.file.spi.FileSystemProvider wrap_convert(FileSystemProvider provider) {
+  public static java.nio.file.spi.FileSystemProvider inverted_wrap_convert(
+      j$.nio.file.spi.FileSystemProvider provider) {
     // Rewritten in ASM to the wrapper method.
     return null;
   }
diff --git a/src/library_desugar/java/java/adapter/HybridFileSystemProvider.java b/src/library_desugar/java/java/adapter/HybridFileSystemProvider.java
index d02344d..5909b09 100644
--- a/src/library_desugar/java/java/adapter/HybridFileSystemProvider.java
+++ b/src/library_desugar/java/java/adapter/HybridFileSystemProvider.java
@@ -27,7 +27,7 @@
       Class.forName("java.nio.file.FileSystems");
       j$.nio.file.FileSystem fileSystem = FileSystems.getDefault();
       j$.nio.file.spi.FileSystemProvider provider = fileSystem.provider();
-      return j$.nio.file.spi.FileSystemProvider.wrap_convert(provider);
+      return j$.nio.file.spi.FileSystemProvider.inverted_wrap_convert(provider);
     } catch (ClassNotFoundException ignored) {
       // We reach this path is API < 26.
     }
diff --git a/src/library_desugar/java/java/adapter/HybridFileTypeDetector.java b/src/library_desugar/java/java/adapter/HybridFileTypeDetector.java
index af2c8f7..da93736 100644
--- a/src/library_desugar/java/java/adapter/HybridFileTypeDetector.java
+++ b/src/library_desugar/java/java/adapter/HybridFileTypeDetector.java
@@ -29,7 +29,7 @@
   static class PlatformFileTypeDetector extends FileTypeDetector {
     @Override
     public String probeContentType(Path path) throws IOException {
-      return j$.nio.file.Files.probeContentType(j$.nio.file.Path.inverted_wrap_convert(path));
+      return j$.nio.file.Files.probeContentType(j$.nio.file.Path.wrap_convert(path));
     }
   }
 }
diff --git a/src/library_desugar/java/java/nio/file/FileApiFlips.java b/src/library_desugar/java/java/nio/file/FileApiFlips.java
index 71b172a..b68aeb2 100644
--- a/src/library_desugar/java/java/nio/file/FileApiFlips.java
+++ b/src/library_desugar/java/java/nio/file/FileApiFlips.java
@@ -78,7 +78,7 @@
         } catch (ClassCastException cce) {
           throw exceptionOpenOption(cce);
         }
-        convertedSet.add(OpenOptionConversions.convert(option));
+        convertedSet.add(j$.nio.file.OpenOption.wrap_convert(option));
       }
       return convertedSet;
     }
@@ -90,7 +90,7 @@
         } catch (ClassCastException cce) {
           throw exceptionOpenOption(cce);
         }
-        convertedSet.add(OpenOptionConversions.convert(option));
+        convertedSet.add(j$.nio.file.OpenOption.wrap_convert(option));
       }
       return convertedSet;
     }
diff --git a/src/library_desugar/java/java/nio/file/OpenOptionConversions.java b/src/library_desugar/java/java/nio/file/OpenOptionConversions.java
deleted file mode 100644
index bfe4968..0000000
--- a/src/library_desugar/java/java/nio/file/OpenOptionConversions.java
+++ /dev/null
@@ -1,35 +0,0 @@
-// Copyright (c) 2022, 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 java.nio.file;
-
-import static java.util.ConversionRuntimeException.exception;
-
-public class OpenOptionConversions {
-  public static java.nio.file.OpenOption convert(j$.nio.file.OpenOption option) {
-    if (option == null) {
-      return null;
-    }
-    if (option instanceof j$.nio.file.StandardOpenOption) {
-      return j$.nio.file.StandardOpenOption.wrap_convert((j$.nio.file.StandardOpenOption) option);
-    }
-    if (option instanceof j$.nio.file.LinkOption) {
-      return j$.nio.file.LinkOption.wrap_convert((j$.nio.file.LinkOption) option);
-    }
-    throw exception("java.nio.file.OpenOption", option);
-  }
-
-  public static j$.nio.file.OpenOption convert(java.nio.file.OpenOption option) {
-    if (option == null) {
-      return null;
-    }
-    if (option instanceof java.nio.file.StandardOpenOption) {
-      return j$.nio.file.StandardOpenOption.wrap_convert((java.nio.file.StandardOpenOption) option);
-    }
-    if (option instanceof java.nio.file.LinkOption) {
-      return j$.nio.file.LinkOption.wrap_convert((java.nio.file.LinkOption) option);
-    }
-    throw exception("java.nio.file.OpenOption", option);
-  }
-}
diff --git a/src/library_desugar/java/java/nio/file/attribute/FileAttributeConversions.java b/src/library_desugar/java/java/nio/file/attribute/FileAttributeConversions.java
index 332c89b..c895d52 100644
--- a/src/library_desugar/java/java/nio/file/attribute/FileAttributeConversions.java
+++ b/src/library_desugar/java/java/nio/file/attribute/FileAttributeConversions.java
@@ -4,8 +4,6 @@
 
 package java.nio.file.attribute;
 
-import static java.util.ConversionRuntimeException.exception;
-
 public class FileAttributeConversions {
 
   public static java.nio.file.attribute.FileTime convert(j$.nio.file.attribute.FileTime fileTime) {
@@ -22,67 +20,4 @@
     return j$.nio.file.attribute.FileTime.fromMillis(fileTime.toMillis());
   }
 
-  public static java.nio.file.attribute.FileAttributeView convert(
-      j$.nio.file.attribute.FileAttributeView fileAttributeView) {
-    if (fileAttributeView == null) {
-      return null;
-    }
-    if (fileAttributeView instanceof j$.nio.file.attribute.PosixFileAttributeView) {
-      return j$.nio.file.attribute.PosixFileAttributeView.wrap_convert(
-          (j$.nio.file.attribute.PosixFileAttributeView) fileAttributeView);
-    }
-    if (fileAttributeView instanceof j$.nio.file.attribute.FileOwnerAttributeView) {
-      return j$.nio.file.attribute.FileOwnerAttributeView.wrap_convert(
-          (j$.nio.file.attribute.FileOwnerAttributeView) fileAttributeView);
-    }
-    if (fileAttributeView instanceof j$.nio.file.attribute.BasicFileAttributeView) {
-      return j$.nio.file.attribute.BasicFileAttributeView.wrap_convert(
-          (j$.nio.file.attribute.BasicFileAttributeView) fileAttributeView);
-    }
-    throw exception("java.nio.file.attribute.FileAttributeView", fileAttributeView);
-  }
-
-  public static j$.nio.file.attribute.FileAttributeView convert(
-      java.nio.file.attribute.FileAttributeView fileAttributeView) {
-    if (fileAttributeView == null) {
-      return null;
-    }
-    if (fileAttributeView instanceof java.nio.file.attribute.PosixFileAttributeView) {
-      return j$.nio.file.attribute.PosixFileAttributeView.wrap_convert(
-          (java.nio.file.attribute.PosixFileAttributeView) fileAttributeView);
-    }
-    if (fileAttributeView instanceof java.nio.file.attribute.FileOwnerAttributeView) {
-      return j$.nio.file.attribute.FileOwnerAttributeView.wrap_convert(
-          (java.nio.file.attribute.FileOwnerAttributeView) fileAttributeView);
-    }
-    if (fileAttributeView instanceof java.nio.file.attribute.BasicFileAttributeView) {
-      return j$.nio.file.attribute.BasicFileAttributeView.wrap_convert(
-          (java.nio.file.attribute.BasicFileAttributeView) fileAttributeView);
-    }
-    throw exception("java.nio.file.attribute.FileAttributeView", fileAttributeView);
-  }
-
-  public static java.nio.file.attribute.BasicFileAttributes convert(
-      j$.nio.file.attribute.BasicFileAttributes fileAttributes) {
-    if (fileAttributes == null) {
-      return null;
-    }
-    if (fileAttributes instanceof j$.nio.file.attribute.PosixFileAttributes) {
-      return j$.nio.file.attribute.PosixFileAttributes.wrap_convert(
-          (j$.nio.file.attribute.PosixFileAttributes) fileAttributes);
-    }
-    return j$.nio.file.attribute.BasicFileAttributes.wrap_convert(fileAttributes);
-  }
-
-  public static j$.nio.file.attribute.BasicFileAttributes convert(
-      java.nio.file.attribute.BasicFileAttributes fileAttributes) {
-    if (fileAttributes == null) {
-      return null;
-    }
-    if (fileAttributes instanceof java.nio.file.attribute.PosixFileAttributes) {
-      return j$.nio.file.attribute.PosixFileAttributes.wrap_convert(
-          (java.nio.file.attribute.PosixFileAttributes) fileAttributes);
-    }
-    return j$.nio.file.attribute.BasicFileAttributes.wrap_convert(fileAttributes);
-  }
 }
diff --git a/src/library_desugar/jdk11/desugar_jdk_libs_path.json b/src/library_desugar/jdk11/desugar_jdk_libs_path.json
index 7b767e1..f2e565f 100644
--- a/src/library_desugar/jdk11/desugar_jdk_libs_path.json
+++ b/src/library_desugar/jdk11/desugar_jdk_libs_path.json
@@ -132,6 +132,7 @@
         "java.nio.file.spi.FileSystemProvider",
         "java.nio.file.spi.FileTypeDetector",
         "java.nio.file.AccessMode",
+        "java.nio.file.OpenOption",
         "java.nio.file.StandardOpenOption",
         "java.nio.file.LinkOption",
         "java.nio.file.CopyOption",
@@ -145,6 +146,7 @@
         "java.nio.file.attribute.PosixFilePermission",
         "java.nio.file.attribute.BasicFileAttributes",
         "java.nio.file.attribute.PosixFileAttributes",
+        "java.nio.file.attribute.FileAttributeView",
         "java.nio.file.attribute.FileOwnerAttributeView",
         "java.nio.file.attribute.PosixFileAttributeView",
         "java.nio.file.attribute.BasicFileAttributeView"
@@ -157,10 +159,7 @@
         ]
       },
       "custom_conversion": {
-        "java.nio.file.OpenOption": "java.nio.file.OpenOptionConversions",
-        "java.nio.file.attribute.FileTime": "java.nio.file.attribute.FileAttributeConversions",
-        "java.nio.file.attribute.BasicFileAttributes": "java.nio.file.attribute.FileAttributeConversions",
-        "java.nio.file.attribute.FileAttributeView": "java.nio.file.attribute.FileAttributeConversions"
+        "java.nio.file.attribute.FileTime": "java.nio.file.attribute.FileAttributeConversions"
       }
     },
     {
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/desugaredlibrary/apiconversion/DesugaredLibraryWrapperSynthesizer.java b/src/main/java/com/android/tools/r8/ir/desugar/desugaredlibrary/apiconversion/DesugaredLibraryWrapperSynthesizer.java
index d28ccaf..41ab157 100644
--- a/src/main/java/com/android/tools/r8/ir/desugar/desugaredlibrary/apiconversion/DesugaredLibraryWrapperSynthesizer.java
+++ b/src/main/java/com/android/tools/r8/ir/desugar/desugaredlibrary/apiconversion/DesugaredLibraryWrapperSynthesizer.java
@@ -46,6 +46,7 @@
 import java.util.Collection;
 import java.util.Collections;
 import java.util.IdentityHashMap;
+import java.util.Iterator;
 import java.util.List;
 import java.util.Map;
 import java.util.function.BiFunction;
@@ -363,7 +364,12 @@
     }
     assert context.isNotProgramClass();
     Iterable<DexMethod> methods =
-        appView.options().machineDesugaredLibrarySpecification.getWrappers().get(context.type);
+        appView
+            .options()
+            .machineDesugaredLibrarySpecification
+            .getWrappers()
+            .get(context.type)
+            .getMethods();
     assert methods != null;
     ClasspathOrLibraryClass classpathOrLibraryContext = context.asClasspathOrLibraryClass();
     DexType type = context.type;
@@ -474,13 +480,24 @@
             eventConsumer::acceptWrapperClasspathClass);
   }
 
-  private void getExistingProgramConversionMethod(
+  private void synthesizeProgramConversionMethod(
       SyntheticKindSelector kindSelector,
       DexProgramClass context,
+      List<DexType> subwrappers,
       DexClass wrapper,
       DexClass reverseWrapper) {
     DexField wrapperField = getWrapperUniqueField(wrapper);
     DexField reverseWrapperField = getWrapperUniqueField(reverseWrapper);
+    List<DexMethod> subwrapperConvertList = new ArrayList<>();
+    for (DexType subwrapper : subwrappers) {
+      DexClass subwrapperClass = appView.definitionFor(subwrapper);
+      assert subwrapperClass != null;
+      DexProgramClass subwrapperWrapper =
+          getExistingProgramWrapper(
+              subwrapperClass,
+              subwrapperClass.isEnum() ? kinds -> kinds.ENUM_CONVERSION : kindSelector);
+      subwrapperConvertList.add(getConversion(subwrapperWrapper));
+    }
     DexProto proto = factory.createProto(reverseWrapperField.type, wrapperField.type);
     appView
         .getSyntheticItems()
@@ -496,14 +513,27 @@
                     methodBuilder,
                     proto,
                     computeProgramConversionMethodCode(
-                        wrapperField, reverseWrapperField, context)));
+                        wrapperField, reverseWrapperField, context, subwrapperConvertList)));
+  }
+
+  private DexMethod getConversion(DexProgramClass subwrapperWrapper) {
+    Iterator<DexEncodedMethod> iterator = subwrapperWrapper.directMethods().iterator();
+    DexEncodedMethod method;
+    do {
+      method = iterator.next();
+    } while (!method.isStatic());
+    assert method.getName() == factory.convertMethodName;
+    return method.getReference();
   }
 
   private CfCode computeProgramConversionMethodCode(
-      DexField wrapperField, DexField reverseWrapperField, DexClass context) {
+      DexField wrapperField,
+      DexField reverseWrapperField,
+      DexClass context,
+      List<DexMethod> subwrapperConvertList) {
     assert context.isProgramClass();
     return new NullableConversionCfCodeProvider.WrapperConversionCfCodeProvider(
-            appView, reverseWrapperField, wrapperField)
+            appView, reverseWrapperField, wrapperField, subwrapperConvertList)
         .generateCfCode();
   }
 
@@ -635,7 +665,7 @@
     librarySpecification
         .getWrappers()
         .forEach(
-            (type, methods) -> {
+            (type, descriptor) -> {
               DexClass validClassToWrap = getValidClassToWrap(type);
               // In broken set-ups we can end up having a json files containing wrappers of non
               // desugared classes. Such wrappers are not required since the class won't be
@@ -644,20 +674,25 @@
                 if (validClassToWrap.isEnum()) {
                   enumConverter.ensureProgramEnumConversionClass(validClassToWrap, eventConsumer);
                 } else {
-                  validClassesToWrap.put(validClassToWrap.asProgramClass(), methods);
-                  ensureProgramWrappersWithoutVirtualMethods(validClassToWrap, eventConsumer);
+                  validClassesToWrap.put(
+                      validClassToWrap.asProgramClass(), descriptor.getMethods());
+                  synthesizeProgramWrappersWithoutVirtualMethods(
+                      validClassToWrap, descriptor.getSubwrappers(), eventConsumer);
                 }
               }
             });
     validClassesToWrap.forEach(
         (clazz, methods) ->
-            ensureProgramWrappersVirtualMethods(clazz, methods, eventConsumer, processingContext));
+            synthesizeProgramWrappersVirtualMethods(
+                clazz, methods, eventConsumer, processingContext));
   }
 
   // We generate first the two wrappers with the constructor method and the fields, then we
   // the two conversion methods which requires the wrappers to know both fields.
-  private void ensureProgramWrappersWithoutVirtualMethods(
-      DexClass context, DesugaredLibraryL8ProgramWrapperSynthesizerEventConsumer eventConsumer) {
+  private void synthesizeProgramWrappersWithoutVirtualMethods(
+      DexClass context,
+      List<DexType> subwrappers,
+      DesugaredLibraryL8ProgramWrapperSynthesizerEventConsumer eventConsumer) {
     assert eventConsumer != null;
     assert context.isProgramClass();
     DexType type = context.type;
@@ -673,13 +708,13 @@
             vivifiedTypeFor(type),
             programContext,
             eventConsumer);
-    getExistingProgramConversionMethod(
-        kinds -> kinds.WRAPPER, programContext, wrapper, vivifiedWrapper);
-    getExistingProgramConversionMethod(
-        kinds -> kinds.VIVIFIED_WRAPPER, programContext, vivifiedWrapper, wrapper);
+    synthesizeProgramConversionMethod(
+        kinds -> kinds.WRAPPER, programContext, subwrappers, wrapper, vivifiedWrapper);
+    synthesizeProgramConversionMethod(
+        kinds -> kinds.VIVIFIED_WRAPPER, programContext, subwrappers, vivifiedWrapper, wrapper);
   }
 
-  private void ensureProgramWrappersVirtualMethods(
+  private void synthesizeProgramWrappersVirtualMethods(
       DexProgramClass context,
       Iterable<DexMethod> methods,
       CfClassSynthesizerDesugaringEventConsumer eventConsumer,
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/desugaredlibrary/humanspecification/HumanRewritingFlags.java b/src/main/java/com/android/tools/r8/ir/desugar/desugaredlibrary/humanspecification/HumanRewritingFlags.java
index f17235f..5c70c34 100644
--- a/src/main/java/com/android/tools/r8/ir/desugar/desugaredlibrary/humanspecification/HumanRewritingFlags.java
+++ b/src/main/java/com/android/tools/r8/ir/desugar/desugaredlibrary/humanspecification/HumanRewritingFlags.java
@@ -15,11 +15,13 @@
 import com.google.common.collect.ImmutableMap;
 import com.google.common.collect.ImmutableSet;
 import com.google.common.collect.Sets;
+import com.google.common.collect.Sets.SetView;
 import java.util.Collections;
 import java.util.HashMap;
 import java.util.IdentityHashMap;
 import java.util.Map;
 import java.util.Set;
+import java.util.stream.Collectors;
 
 public class HumanRewritingFlags {
 
@@ -443,6 +445,7 @@
     }
 
     public HumanRewritingFlags build() {
+      validate();
       return new HumanRewritingFlags(
           ImmutableMap.copyOf(rewritePrefix),
           ImmutableSet.copyOf(dontRewritePrefix),
@@ -462,5 +465,19 @@
           ImmutableMap.copyOf(amendLibraryMethod),
           ImmutableMap.copyOf(amendLibraryField));
     }
+
+    private void validate() {
+      SetView<DexType> dups =
+          Sets.intersection(customConversions.keySet(), wrapperConversions.keySet());
+      if (!dups.isEmpty()) {
+        throw reporter.fatalError(
+            new StringDiagnostic(
+                "Invalid desugared library configuration. "
+                    + "Duplicate types in custom conversions and wrapper conversions: "
+                    + String.join(
+                        ", ", dups.stream().map(DexType::toString).collect(Collectors.toSet())),
+                origin));
+      }
+    }
   }
 }
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/desugaredlibrary/machinespecification/MachineDesugaredLibrarySpecification.java b/src/main/java/com/android/tools/r8/ir/desugar/desugaredlibrary/machinespecification/MachineDesugaredLibrarySpecification.java
index 16b0b9f..4aa99b6 100644
--- a/src/main/java/com/android/tools/r8/ir/desugar/desugaredlibrary/machinespecification/MachineDesugaredLibrarySpecification.java
+++ b/src/main/java/com/android/tools/r8/ir/desugar/desugaredlibrary/machinespecification/MachineDesugaredLibrarySpecification.java
@@ -145,7 +145,7 @@
     return rewritingFlags.isEmulatedInterfaceRewrittenType(type);
   }
 
-  public Map<DexType, List<DexMethod>> getWrappers() {
+  public Map<DexType, WrapperDescriptor> getWrappers() {
     return rewritingFlags.getWrappers();
   }
 
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/desugaredlibrary/machinespecification/MachineRewritingFlags.java b/src/main/java/com/android/tools/r8/ir/desugar/desugaredlibrary/machinespecification/MachineRewritingFlags.java
index e125c53..5ad734b 100644
--- a/src/main/java/com/android/tools/r8/ir/desugar/desugaredlibrary/machinespecification/MachineRewritingFlags.java
+++ b/src/main/java/com/android/tools/r8/ir/desugar/desugaredlibrary/machinespecification/MachineRewritingFlags.java
@@ -10,13 +10,12 @@
 import com.android.tools.r8.graph.DexType;
 import com.android.tools.r8.graph.FieldAccessFlags;
 import com.android.tools.r8.graph.MethodAccessFlags;
-import com.google.common.collect.ImmutableList;
 import com.google.common.collect.ImmutableMap;
 import com.google.common.collect.ImmutableSet;
 import com.google.common.collect.Iterables;
 import java.util.ArrayList;
 import java.util.IdentityHashMap;
-import java.util.List;
+import java.util.LinkedHashMap;
 import java.util.Map;
 import java.util.Set;
 import java.util.function.Consumer;
@@ -39,7 +38,7 @@
       Map<DexMethod, DexMethod> emulatedVirtualRetargetThroughEmulatedInterface,
       Map<DexMethod, DexMethod[]> apiGenericTypesConversion,
       Map<DexType, EmulatedInterfaceDescriptor> emulatedInterfaces,
-      Map<DexType, List<DexMethod>> wrappers,
+      LinkedHashMap<DexType, WrapperDescriptor> wrappers,
       Map<DexType, DexType> legacyBackport,
       Set<DexType> dontRetarget,
       Map<DexType, CustomConversionDescriptor> customConversions,
@@ -101,8 +100,8 @@
   // Emulated interface descriptors.
   private final Map<DexType, EmulatedInterfaceDescriptor> emulatedInterfaces;
 
-  // Wrappers and the list of methods they implement.
-  private final Map<DexType, List<DexMethod>> wrappers;
+  // Wrapper descriptors.
+  private final LinkedHashMap<DexType, WrapperDescriptor> wrappers;
 
   private final Map<DexType, DexType> legacyBackport;
   private final Set<DexType> dontRetarget;
@@ -160,7 +159,7 @@
     return emulatedInterfaces;
   }
 
-  public Map<DexType, List<DexMethod>> getWrappers() {
+  public Map<DexType, WrapperDescriptor> getWrappers() {
     return wrappers;
   }
 
@@ -249,7 +248,7 @@
         ImmutableMap.builder();
     private final ImmutableMap.Builder<DexType, EmulatedInterfaceDescriptor> emulatedInterfaces =
         ImmutableMap.builder();
-    private final ImmutableMap.Builder<DexType, List<DexMethod>> wrappers = ImmutableMap.builder();
+    private final LinkedHashMap<DexType, WrapperDescriptor> wrappers = new LinkedHashMap<>();
     private final ImmutableMap.Builder<DexType, DexType> legacyBackport = ImmutableMap.builder();
     private final ImmutableSet.Builder<DexType> dontRetarget = ImmutableSet.builder();
     private final ImmutableMap.Builder<DexType, CustomConversionDescriptor> customConversions =
@@ -308,8 +307,8 @@
       apiGenericTypesConversion.put(method, conversions);
     }
 
-    public void addWrapper(DexType wrapperConversion, List<DexMethod> methods) {
-      wrappers.put(wrapperConversion, ImmutableList.copyOf(methods));
+    public void addWrapper(DexType type, WrapperDescriptor descriptor) {
+      this.wrappers.put(type, descriptor);
     }
 
     public void putLegacyBackport(DexType src, DexType target) {
@@ -367,7 +366,7 @@
           emulatedVirtualRetargetThroughEmulatedInterface.build(),
           apiGenericTypesConversion.build(),
           emulatedInterfaces.build(),
-          wrappers.build(),
+          wrappers,
           legacyBackport.build(),
           dontRetarget.build(),
           customConversions.build(),
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/desugaredlibrary/machinespecification/WrapperDescriptor.java b/src/main/java/com/android/tools/r8/ir/desugar/desugaredlibrary/machinespecification/WrapperDescriptor.java
new file mode 100644
index 0000000..f4a158c
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/ir/desugar/desugaredlibrary/machinespecification/WrapperDescriptor.java
@@ -0,0 +1,27 @@
+// Copyright (c) 2022, 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.ir.desugar.desugaredlibrary.machinespecification;
+
+import com.android.tools.r8.graph.DexMethod;
+import com.android.tools.r8.graph.DexType;
+import java.util.List;
+
+public class WrapperDescriptor {
+  private final List<DexMethod> methods;
+  private final List<DexType> subwrappers;
+
+  public WrapperDescriptor(List<DexMethod> methods, List<DexType> directSubtypes) {
+    this.methods = methods;
+    this.subwrappers = directSubtypes;
+  }
+
+  public List<DexMethod> getMethods() {
+    return methods;
+  }
+
+  public List<DexType> getSubwrappers() {
+    return subwrappers;
+  }
+}
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/desugaredlibrary/specificationconversion/HumanToMachineWrapperConverter.java b/src/main/java/com/android/tools/r8/ir/desugar/desugaredlibrary/specificationconversion/HumanToMachineWrapperConverter.java
index c0d9157..a7ed82c 100644
--- a/src/main/java/com/android/tools/r8/ir/desugar/desugaredlibrary/specificationconversion/HumanToMachineWrapperConverter.java
+++ b/src/main/java/com/android/tools/r8/ir/desugar/desugaredlibrary/specificationconversion/HumanToMachineWrapperConverter.java
@@ -12,14 +12,19 @@
 import com.android.tools.r8.graph.DexType;
 import com.android.tools.r8.ir.desugar.desugaredlibrary.humanspecification.HumanRewritingFlags;
 import com.android.tools.r8.ir.desugar.desugaredlibrary.machinespecification.MachineRewritingFlags;
+import com.android.tools.r8.ir.desugar.desugaredlibrary.machinespecification.WrapperDescriptor;
 import com.android.tools.r8.utils.MethodSignatureEquivalence;
 import com.google.common.base.Equivalence.Wrapper;
 import com.google.common.collect.ImmutableList;
+import com.google.common.collect.Iterables;
 import com.google.common.collect.Sets;
 import java.util.ArrayList;
 import java.util.HashSet;
+import java.util.IdentityHashMap;
+import java.util.LinkedHashMap;
 import java.util.LinkedList;
 import java.util.List;
+import java.util.Map;
 import java.util.Set;
 import java.util.function.BiConsumer;
 
@@ -37,6 +42,53 @@
       HumanRewritingFlags rewritingFlags,
       MachineRewritingFlags.Builder builder,
       BiConsumer<String, Set<? extends DexReference>> warnConsumer) {
+    Map<DexType, WrapperDescriptorBuilder> descriptors = initializeDescriptors(rewritingFlags);
+    fillDescriptors(rewritingFlags, descriptors);
+    // The descriptors have to be ordered so that when processing a type, subtypes have been
+    // processed before.
+    LinkedHashMap<DexType, WrapperDescriptorBuilder> orderedDescriptors =
+        orderDescriptors(descriptors);
+    finalizeWrapperDescriptors(orderedDescriptors, builder);
+    warnConsumer.accept("The following types to wrap are missing: ", missingClasses);
+  }
+
+  private static class WrapperDescriptorBuilder {
+    private final List<DexMethod> methods = new ArrayList<>();
+    private final List<DexType> subwrappers = new ArrayList<>();
+
+    public WrapperDescriptorBuilder() {}
+
+    public List<DexMethod> getMethods() {
+      return methods;
+    }
+
+    public List<DexType> getSubwrappers() {
+      return subwrappers;
+    }
+
+    public void addSubwrapper(DexType type) {
+      subwrappers.add(type);
+    }
+
+    public WrapperDescriptor toWrapperDescriptor() {
+      methods.sort(DexMethod::compareTo);
+      subwrappers.sort(DexType::compareTo);
+      return new WrapperDescriptor(
+          ImmutableList.copyOf(methods), ImmutableList.copyOf(subwrappers));
+    }
+  }
+
+  private Map<DexType, WrapperDescriptorBuilder> initializeDescriptors(
+      HumanRewritingFlags rewritingFlags) {
+    Map<DexType, WrapperDescriptorBuilder> descriptors = new IdentityHashMap<>();
+    for (DexType wrapperType : rewritingFlags.getWrapperConversions().keySet()) {
+      descriptors.put(wrapperType, new WrapperDescriptorBuilder());
+    }
+    return descriptors;
+  }
+
+  private void fillDescriptors(
+      HumanRewritingFlags rewritingFlags, Map<DexType, WrapperDescriptorBuilder> descriptors) {
     rewritingFlags
         .getWrapperConversions()
         .forEach(
@@ -44,53 +96,91 @@
               DexClass wrapperClass = appInfo.definitionFor(wrapperType);
               if (wrapperClass == null) {
                 missingClasses.add(wrapperType);
+                descriptors.remove(wrapperType);
                 return;
               }
-              List<DexMethod> methods;
-              if (wrapperClass.isEnum()) {
-                methods = ImmutableList.of();
-              } else {
-                methods = allImplementedMethods(wrapperClass, excludedMethods);
-                methods.sort(DexMethod::compareTo);
-              }
-              builder.addWrapper(wrapperType, methods);
+              WrapperDescriptorBuilder descriptor = descriptors.get(wrapperType);
+              fillDescriptors(wrapperClass, excludedMethods, descriptor, descriptors);
             });
-    warnConsumer.accept("The following types to wrap are missing: ", missingClasses);
   }
 
-  private List<DexMethod> allImplementedMethods(
-      DexClass wrapperClass, Set<DexMethod> excludedMethods) {
+  private LinkedHashMap<DexType, WrapperDescriptorBuilder> orderDescriptors(
+      Map<DexType, WrapperDescriptorBuilder> descriptors) {
+    LinkedHashMap<DexType, WrapperDescriptorBuilder> orderedDescriptors = new LinkedHashMap<>();
+    List<DexType> preOrdered = new ArrayList<>(descriptors.keySet());
+    preOrdered.sort(DexType::compareTo);
+    LinkedList<DexType> workList = new LinkedList<>(preOrdered);
+    while (!workList.isEmpty()) {
+      DexType dexType = workList.removeFirst();
+      WrapperDescriptorBuilder descriptor = descriptors.get(dexType);
+      List<DexType> subwrappers = descriptor.getSubwrappers();
+      if (Iterables.all(subwrappers, orderedDescriptors::containsKey)) {
+        orderedDescriptors.put(dexType, descriptor);
+      } else {
+        workList.addLast(dexType);
+      }
+    }
+    return orderedDescriptors;
+  }
+
+  private void finalizeWrapperDescriptors(
+      LinkedHashMap<DexType, WrapperDescriptorBuilder> descriptors,
+      MachineRewritingFlags.Builder builder) {
+    descriptors.forEach(
+        (type, descriptor) -> {
+          LinkedList<DexType> workList = new LinkedList<>(descriptor.getSubwrappers());
+          while (!workList.isEmpty()) {
+            DexType dexType = workList.removeFirst();
+            List<DexType> subwrappers = descriptors.get(dexType).getSubwrappers();
+            descriptor.getSubwrappers().removeAll(subwrappers);
+            workList.addAll(subwrappers);
+          }
+          builder.addWrapper(type, descriptor.toWrapperDescriptor());
+        });
+  }
+
+  private void fillDescriptors(
+      DexClass wrapperClass,
+      Set<DexMethod> excludedMethods,
+      WrapperDescriptorBuilder descriptor,
+      Map<DexType, WrapperDescriptorBuilder> descriptors) {
     HashSet<Wrapper<DexMethod>> wrappers = new HashSet<>();
     for (DexMethod excludedMethod : excludedMethods) {
       wrappers.add(equivalence.wrap(excludedMethod));
     }
     LinkedList<DexClass> workList = new LinkedList<>();
-    List<DexMethod> implementedMethods = new ArrayList<>();
+    List<DexMethod> implementedMethods = descriptor.getMethods();
     workList.add(wrapperClass);
     while (!workList.isEmpty()) {
       DexClass dexClass = workList.removeFirst();
-      for (DexEncodedMethod virtualMethod : dexClass.virtualMethods()) {
-        if (!virtualMethod.isPrivateMethod()
-            // Don't include hashCode and equals overrides, as hashCode and equals are added to
-            // all wrappers regardless.
-            && (!appInfo.dexItemFactory().objectMembers.hashCode.match(virtualMethod))
-            && (!appInfo.dexItemFactory().objectMembers.equals.match(virtualMethod))) {
-          assert virtualMethod.isProtectedMethod() || virtualMethod.isPublicMethod();
-          boolean alreadyAdded = wrappers.contains(equivalence.wrap(virtualMethod.getReference()));
-          // This looks quadratic but given the size of the collections met in practice for
-          // desugared libraries (Max ~15) it does not matter.
-          if (!alreadyAdded) {
-            for (DexMethod alreadyImplementedMethod : implementedMethods) {
-              if (alreadyImplementedMethod.match(virtualMethod.getReference())) {
-                alreadyAdded = true;
-                break;
+      if (dexClass != wrapperClass && descriptors.containsKey(dexClass.type)) {
+        descriptors.get(dexClass.type).addSubwrapper(wrapperClass.type);
+      }
+      if (!wrapperClass.isEnum()) {
+        for (DexEncodedMethod virtualMethod : dexClass.virtualMethods()) {
+          if (!virtualMethod.isPrivateMethod()
+              // Don't include hashCode and equals overrides, as hashCode and equals are added to
+              // all wrappers regardless.
+              && (!appInfo.dexItemFactory().objectMembers.hashCode.match(virtualMethod))
+              && (!appInfo.dexItemFactory().objectMembers.equals.match(virtualMethod))) {
+            assert virtualMethod.isProtectedMethod() || virtualMethod.isPublicMethod();
+            boolean alreadyAdded =
+                wrappers.contains(equivalence.wrap(virtualMethod.getReference()));
+            // This looks quadratic but given the size of the collections met in practice for
+            // desugared libraries (Max ~15) it does not matter.
+            if (!alreadyAdded) {
+              for (DexMethod alreadyImplementedMethod : implementedMethods) {
+                if (alreadyImplementedMethod.match(virtualMethod.getReference())) {
+                  alreadyAdded = true;
+                  break;
+                }
               }
             }
-          }
-          if (!alreadyAdded) {
-            assert !virtualMethod.isFinal()
-                : "Cannot wrap final method " + virtualMethod + " while wrapping " + wrapperClass;
-            implementedMethods.add(virtualMethod.getReference());
+            if (!alreadyAdded) {
+              assert !virtualMethod.isFinal()
+                  : "Cannot wrap final method " + virtualMethod + " while wrapping " + wrapperClass;
+              implementedMethods.add(virtualMethod.getReference());
+            }
           }
         }
       }
@@ -107,6 +197,5 @@
         workList.add(superClass);
       }
     }
-    return implementedMethods;
   }
 }
diff --git a/src/main/java/com/android/tools/r8/ir/synthetic/apiconverter/NullableConversionCfCodeProvider.java b/src/main/java/com/android/tools/r8/ir/synthetic/apiconverter/NullableConversionCfCodeProvider.java
index a32d51e..8f0f2f4 100644
--- a/src/main/java/com/android/tools/r8/ir/synthetic/apiconverter/NullableConversionCfCodeProvider.java
+++ b/src/main/java/com/android/tools/r8/ir/synthetic/apiconverter/NullableConversionCfCodeProvider.java
@@ -193,14 +193,19 @@
 
   public static class WrapperConversionCfCodeProvider extends NullableConversionCfCodeProvider {
 
-    DexField reverseWrapperField;
-    DexField wrapperField;
+    private final DexField reverseWrapperField;
+    private final DexField wrapperField;
+    private final List<DexMethod> subwrapperConvertList;
 
     public WrapperConversionCfCodeProvider(
-        AppView<?> appView, DexField reverseWrapperField, DexField wrapperField) {
+        AppView<?> appView,
+        DexField reverseWrapperField,
+        DexField wrapperField,
+        List<DexMethod> subwrapperConvertList) {
       super(appView, wrapperField.holder);
       this.reverseWrapperField = reverseWrapperField;
       this.wrapperField = wrapperField;
+      this.subwrapperConvertList = subwrapperConvertList;
     }
 
     @Override
@@ -228,6 +233,23 @@
       instructions.add(unwrapDest);
       instructions.add(frame.clone());
 
+      // if (arg instanceOf Subtype) {
+      //     return SubtypeWrapper.convert((Subtype) arg)
+      // };
+      for (DexMethod convert : subwrapperConvertList) {
+        CfLabel dest = new CfLabel();
+        DexType convertArgType = convert.getArgumentType(0, true);
+        instructions.add(new CfLoad(ValueType.fromDexType(argType), 0));
+        instructions.add(new CfInstanceOf(convertArgType));
+        instructions.add(new CfIf(If.Type.EQ, ValueType.INT, dest));
+        instructions.add(new CfLoad(ValueType.fromDexType(argType), 0));
+        instructions.add(new CfCheckCast(convertArgType));
+        instructions.add(new CfInvoke(Opcodes.INVOKESTATIC, convert, false));
+        instructions.add(new CfReturn(ValueType.fromDexType(reverseWrapperField.type)));
+        instructions.add(dest);
+        instructions.add(frame.clone());
+      }
+
       // return new Wrapper(wrappedValue);
       instructions.add(new CfNew(wrapperField.holder));
       instructions.add(CfStackInstruction.fromAsm(Opcodes.DUP));
diff --git a/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/conversiontests/UnwrapConversionTest.java b/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/conversiontests/UnwrapConversionTest.java
index 278ec26..a61445b 100644
--- a/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/conversiontests/UnwrapConversionTest.java
+++ b/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/conversiontests/UnwrapConversionTest.java
@@ -14,9 +14,12 @@
 import com.android.tools.r8.desugar.desugaredlibrary.test.LibraryDesugaringSpecification;
 import com.android.tools.r8.utils.AndroidApiLevel;
 import com.android.tools.r8.utils.StringUtils;
+import java.util.Arrays;
 import java.util.List;
 import java.util.function.DoubleConsumer;
 import java.util.function.IntConsumer;
+import java.util.stream.BaseStream;
+import java.util.stream.IntStream;
 import org.junit.Test;
 import org.junit.runner.RunWith;
 import org.junit.runners.Parameterized;
@@ -30,7 +33,7 @@
   private final CompilationSpecification compilationSpecification;
 
   private static final AndroidApiLevel MIN_SUPPORTED = AndroidApiLevel.N;
-  private static final String EXPECTED_RESULT = StringUtils.lines("true", "true");
+  private static final String EXPECTED_RESULT = StringUtils.lines("true", "true", "true", "true");
 
   @Parameters(name = "{0}, spec: {1}, {2}")
   public static List<Object[]> data() {
@@ -64,6 +67,22 @@
 
     @SuppressWarnings("all")
     public static void main(String[] args) {
+      consumerTest();
+      streamTest();
+    }
+
+    private static void streamTest() {
+      // Type wrapper.
+      IntStream intStream = Arrays.stream(new int[] {1});
+      BaseStream<?, ?> unwrapped = CustomLibClass.identity(intStream);
+      System.out.println(unwrapped == intStream);
+
+      // Vivified wrapper.
+      IntStream consumer = CustomLibClass.getStream();
+      System.out.println(CustomLibClass.testStream(consumer));
+    }
+
+    private static void consumerTest() {
       // Type wrapper.
       IntConsumer intConsumer = i -> {};
       IntConsumer unwrappedIntConsumer = CustomLibClass.identity(intConsumer);
@@ -95,5 +114,21 @@
     public static boolean testConsumer(DoubleConsumer doubleConsumer) {
       return doubleConsumer == consumer;
     }
+
+    private static IntStream intStream = Arrays.stream(new int[] {0});
+
+    @SuppressWarnings("WeakerAccess")
+    public static BaseStream<Integer, IntStream> identity(IntStream arg) {
+      return arg;
+    }
+
+    public static IntStream getStream() {
+      return intStream;
+    }
+
+    @SuppressWarnings("WeakerAccess")
+    public static boolean testStream(BaseStream<?, ?> stream) {
+      return stream == intStream;
+    }
   }
 }
diff --git a/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/jdk11/InputStreamTransferToTest.java b/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/jdk11/InputStreamTransferToTest.java
index 54c88bf..7721551 100644
--- a/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/jdk11/InputStreamTransferToTest.java
+++ b/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/jdk11/InputStreamTransferToTest.java
@@ -4,7 +4,7 @@
 
 package com.android.tools.r8.desugar.desugaredlibrary.jdk11;
 
-import static com.android.tools.r8.desugar.desugaredlibrary.test.CompilationSpecification.DEFAULT_SPECIFICATIONS;
+import static com.android.tools.r8.desugar.desugaredlibrary.test.CompilationSpecification.SPECIFICATIONS_WITH_CF2CF;
 import static com.android.tools.r8.desugar.desugaredlibrary.test.LibraryDesugaringSpecification.JDK11_PATH;
 
 import com.android.tools.r8.TestParameters;
@@ -38,9 +38,9 @@
   @Parameters(name = "{0}, spec: {1}, {2}")
   public static List<Object[]> data() {
     return buildParameters(
-        getTestParameters().withAllRuntimesAndApiLevels().build(),
+        getTestParameters().withAllRuntimesAndApiLevels().enableApiLevelsForCf().build(),
         ImmutableList.of(JDK11_PATH),
-        DEFAULT_SPECIFICATIONS);
+        SPECIFICATIONS_WITH_CF2CF);
   }
 
   public InputStreamTransferToTest(