Fix visiting interfaces for emulated intefaces

The original change aaeee3ceabbe7edf939e96b6a49fae55810cd45d
was supposed to only consider generic signatures for cf to cf
desugaring. However in some cases that was not the case.

This reverts commit bf64f4a66338b743fb47c4144d780f9cd8a86d40
with additional changes.

Bug: 181750323, 182702580
Change-Id: I7d675869d72fdbc9456f12b58319592bee83e1e2
diff --git a/src/main/java/com/android/tools/r8/graph/DexClass.java b/src/main/java/com/android/tools/r8/graph/DexClass.java
index 0b4b188..b2bc948 100644
--- a/src/main/java/com/android/tools/r8/graph/DexClass.java
+++ b/src/main/java/com/android/tools/r8/graph/DexClass.java
@@ -10,6 +10,9 @@
 import com.android.tools.r8.errors.CompilationError;
 import com.android.tools.r8.errors.Unreachable;
 import com.android.tools.r8.graph.GenericSignature.ClassSignature;
+import com.android.tools.r8.graph.GenericSignature.ClassTypeSignature;
+import com.android.tools.r8.graph.GenericSignature.FieldTypeSignature;
+import com.android.tools.r8.graph.GenericSignature.FormalTypeParameter;
 import com.android.tools.r8.kotlin.KotlinClassLevelInfo;
 import com.android.tools.r8.origin.Origin;
 import com.android.tools.r8.references.ClassReference;
@@ -20,6 +23,7 @@
 import com.android.tools.r8.utils.TraversalContinuation;
 import com.google.common.base.MoreObjects;
 import com.google.common.base.Predicates;
+import com.google.common.collect.ImmutableList;
 import com.google.common.collect.Iterables;
 import com.google.common.collect.Iterators;
 import com.google.common.collect.Sets;
@@ -31,6 +35,7 @@
 import java.util.List;
 import java.util.ListIterator;
 import java.util.Set;
+import java.util.function.BiConsumer;
 import java.util.function.Consumer;
 import java.util.function.Function;
 import java.util.function.Predicate;
@@ -780,13 +785,125 @@
       Predicate<DexType> ignore,
       Set<DexType> seen);
 
+  public void forEachImmediateInterface(Consumer<DexType> fn) {
+    for (DexType iface : interfaces.values) {
+      fn.accept(iface);
+    }
+  }
+
   public void forEachImmediateSupertype(Consumer<DexType> fn) {
     if (superType != null) {
       fn.accept(superType);
     }
-    for (DexType iface : interfaces.values) {
-      fn.accept(iface);
+    forEachImmediateInterface(fn);
+  }
+
+  public boolean validInterfaceSignatures() {
+    return getClassSignature().superInterfaceSignatures().isEmpty()
+        || interfaces.values.length == getClassSignature().superInterfaceSignatures.size();
+  }
+
+  public void forEachImmediateInterface(BiConsumer<DexType, ClassTypeSignature> consumer) {
+    assert validInterfaceSignatures();
+
+    // If there is no generic signature information don't pass any type arguments.
+    if (getClassSignature().superInterfaceSignatures().isEmpty()) {
+      forEachImmediateInterface(
+          superInterface ->
+              consumer.accept(superInterface, new ClassTypeSignature(superInterface)));
+      return;
     }
+
+    Iterator<DexType> interfaceIterator = Arrays.asList(interfaces.values).iterator();
+    Iterator<ClassTypeSignature> interfaceSignatureIterator =
+        getClassSignature().superInterfaceSignatures().iterator();
+
+    while (interfaceIterator.hasNext()) {
+      assert interfaceSignatureIterator.hasNext();
+      DexType superInterface = interfaceIterator.next();
+      ClassTypeSignature superInterfaceSignatures = interfaceSignatureIterator.next();
+      consumer.accept(superInterface, superInterfaceSignatures);
+    }
+  }
+
+  public void forEachImmediateSupertype(BiConsumer<DexType, ClassTypeSignature> consumer) {
+    if (superType != null) {
+      consumer.accept(superType, classSignature.superClassSignature);
+    }
+    forEachImmediateInterface(consumer);
+  }
+
+  public void forEachImmediateInterfaceWithAppliedTypeArguments(
+      List<FieldTypeSignature> typeArguments,
+      BiConsumer<DexType, List<FieldTypeSignature>> consumer) {
+    assert validInterfaceSignatures();
+
+    // If there is no generic signature information don't pass any type arguments.
+    if (getClassSignature().superInterfaceSignatures().size() == 0) {
+      forEachImmediateInterface(
+          superInterface -> consumer.accept(superInterface, ImmutableList.of()));
+      return;
+    }
+
+    Iterator<DexType> interfaceIterator = Arrays.asList(interfaces.values).iterator();
+    Iterator<ClassTypeSignature> interfaceSignatureIterator =
+        getClassSignature().superInterfaceSignatures().iterator();
+
+    while (interfaceIterator.hasNext()) {
+      assert interfaceSignatureIterator.hasNext();
+      DexType superInterface = interfaceIterator.next();
+      ClassTypeSignature superInterfaceSignatures = interfaceSignatureIterator.next();
+
+      // With no type arguments erase the signatures.
+      if (typeArguments.isEmpty() && superInterfaceSignatures.hasTypeVariableArguments()) {
+        consumer.accept(superInterface, ImmutableList.of());
+        continue;
+      }
+
+      consumer.accept(superInterface, applyTypeArguments(superInterfaceSignatures, typeArguments));
+    }
+    assert !interfaceSignatureIterator.hasNext();
+  }
+
+  public void forEachImmediateSupertypeWithAppliedTypeArguments(
+      List<FieldTypeSignature> typeArguments,
+      BiConsumer<DexType, List<FieldTypeSignature>> consumer) {
+    if (superType != null) {
+      consumer.accept(
+          superType, applyTypeArguments(getClassSignature().superClassSignature, typeArguments));
+    }
+    forEachImmediateInterfaceWithAppliedTypeArguments(typeArguments, consumer);
+  }
+
+  private List<FieldTypeSignature> applyTypeArguments(
+      ClassTypeSignature superInterfaceSignatures, List<FieldTypeSignature> appliedTypeArguments) {
+    ImmutableList.Builder<FieldTypeSignature> superTypeArgumentsBuilder = ImmutableList.builder();
+    if (superInterfaceSignatures.type.toSourceString().equals("java.util.Map")) {
+      System.currentTimeMillis();
+    }
+    superInterfaceSignatures
+        .typeArguments()
+        .forEach(
+            typeArgument -> {
+              if (typeArgument.isTypeVariableSignature()) {
+                for (int i = 0; i < getClassSignature().getFormalTypeParameters().size(); i++) {
+                  FormalTypeParameter formalTypeParameter =
+                      getClassSignature().getFormalTypeParameters().get(i);
+                  if (formalTypeParameter
+                      .getName()
+                      .equals(typeArgument.asTypeVariableSignature().typeVariable())) {
+                    if (i >= appliedTypeArguments.size()) {
+                      assert false;
+                    } else {
+                      superTypeArgumentsBuilder.add(appliedTypeArguments.get(i));
+                    }
+                  }
+                }
+              } else {
+                superTypeArgumentsBuilder.add(typeArgument);
+              }
+            });
+    return superTypeArgumentsBuilder.build();
   }
 
   @Override
diff --git a/src/main/java/com/android/tools/r8/graph/GenericSignature.java b/src/main/java/com/android/tools/r8/graph/GenericSignature.java
index 1dc1f4f..2fa08c8 100644
--- a/src/main/java/com/android/tools/r8/graph/GenericSignature.java
+++ b/src/main/java/com/android/tools/r8/graph/GenericSignature.java
@@ -542,6 +542,15 @@
         visitor.visitSimpleClass(innerTypeSignature);
       }
     }
+
+    public boolean hasTypeVariableArguments() {
+      for (FieldTypeSignature typeArgument : typeArguments) {
+        if (typeArgument.isTypeVariableSignature()) {
+          return true;
+        }
+      }
+      return false;
+    }
   }
 
   public static class ArrayTypeSignature extends FieldTypeSignature {
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/ClassProcessor.java b/src/main/java/com/android/tools/r8/ir/desugar/ClassProcessor.java
index 3e665db..6ef420d 100644
--- a/src/main/java/com/android/tools/r8/ir/desugar/ClassProcessor.java
+++ b/src/main/java/com/android/tools/r8/ir/desugar/ClassProcessor.java
@@ -205,6 +205,40 @@
     }
   }
 
+  // Emulated interfaces together with the generic signatures.
+  static class EmulatedInterfaces {
+    static EmulatedInterfaces EMPTY = new EmulatedInterfaces(ImmutableSet.of());
+
+    final Set<DexType> emulatedInterfaces;
+
+    EmulatedInterfaces(DexType emulatedInterface) {
+      this.emulatedInterfaces = ImmutableSet.of(emulatedInterface);
+    }
+
+    private EmulatedInterfaces(Set<DexType> emulatedInterfaces) {
+      this.emulatedInterfaces = emulatedInterfaces;
+    }
+
+    boolean isEmpty() {
+      return emulatedInterfaces.isEmpty();
+    }
+
+    boolean contains(DexType type) {
+      return emulatedInterfaces.contains(type);
+    }
+
+    Set<DexType> getEmulatedInterfaces() {
+      return emulatedInterfaces;
+    }
+
+    EmulatedInterfaces merge(EmulatedInterfaces other) {
+      ImmutableSet.Builder<DexType> newEmulatedInterfaces = ImmutableSet.builder();
+      newEmulatedInterfaces.addAll(emulatedInterfaces);
+      newEmulatedInterfaces.addAll(other.emulatedInterfaces);
+      return new EmulatedInterfaces(newEmulatedInterfaces.build());
+    }
+  }
+
   // List of emulated interfaces and corresponding signatures which may require forwarding methods.
   // If one of the signatures has an override, then the class holding the override is required to
   // add the forwarding methods for all signatures, and introduce the corresponding emulated
@@ -214,13 +248,13 @@
   private static class EmulatedInterfaceInfo {
 
     static final EmulatedInterfaceInfo EMPTY =
-        new EmulatedInterfaceInfo(MethodSignatures.EMPTY, ImmutableSet.of());
+        new EmulatedInterfaceInfo(MethodSignatures.EMPTY, EmulatedInterfaces.EMPTY);
 
     final MethodSignatures signatures;
-    final ImmutableSet<DexType> emulatedInterfaces;
+    final EmulatedInterfaces emulatedInterfaces;
 
     private EmulatedInterfaceInfo(
-        MethodSignatures methodsToForward, ImmutableSet<DexType> emulatedInterfaces) {
+        MethodSignatures methodsToForward, EmulatedInterfaces emulatedInterfaces) {
       this.signatures = methodsToForward;
       this.emulatedInterfaces = emulatedInterfaces;
     }
@@ -232,17 +266,18 @@
       if (other.isEmpty()) {
         return this;
       }
-      ImmutableSet.Builder<DexType> newEmulatedInterfaces = ImmutableSet.builder();
-      newEmulatedInterfaces.addAll(emulatedInterfaces);
-      newEmulatedInterfaces.addAll(other.emulatedInterfaces);
       return new EmulatedInterfaceInfo(
-          signatures.merge(other.signatures), newEmulatedInterfaces.build());
+          signatures.merge(other.signatures), emulatedInterfaces.merge(other.emulatedInterfaces));
     }
 
     public boolean isEmpty() {
       assert !emulatedInterfaces.isEmpty() || signatures.isEmpty();
       return emulatedInterfaces.isEmpty();
     }
+
+    boolean contains(DexType type) {
+      return emulatedInterfaces.contains(type);
+    }
   }
 
   // Helper to keep track of the direct active subclass and nearest program subclass for reporting.
@@ -376,7 +411,7 @@
     assert needsLibraryInfo();
     MethodSignatures signatures = getDefaultMethods(iface);
     EmulatedInterfaceInfo emulatedInterfaceInfo =
-        new EmulatedInterfaceInfo(signatures, ImmutableSet.of(iface.type));
+        new EmulatedInterfaceInfo(signatures, new EmulatedInterfaces(iface.type));
     return interfaceInfo.withEmulatedInterfaceInfo(emulatedInterfaceInfo);
   }
 
@@ -423,14 +458,13 @@
   // implement the interface and the emulated one for correct emulated dispatch.
   // The class signature won't include the correct type parameters for the duplicated interfaces,
   // i.e., there will be foo.A instead of foo.A<K,V>, but such parameters are unused.
-  private void duplicateEmulatedInterfaces(
-      DexClass clazz, ImmutableSet<DexType> emulatedInterfaces) {
+  private void duplicateEmulatedInterfaces(DexClass clazz, EmulatedInterfaces emulatedInterfaces) {
     if (clazz.isNotProgramClass()) {
       return;
     }
-    Set<DexType> filtered = new HashSet<>(emulatedInterfaces);
+    Set<DexType> filtered = new HashSet<>(emulatedInterfaces.getEmulatedInterfaces());
     WorkList<DexType> workList = WorkList.newIdentityWorkList();
-    for (DexType emulatedInterface : emulatedInterfaces) {
+    for (DexType emulatedInterface : emulatedInterfaces.getEmulatedInterfaces()) {
       DexClass iface = appView.definitionFor(emulatedInterface);
       if (iface != null) {
         assert iface.isLibraryClass()
@@ -448,7 +482,7 @@
       workList.addIfNotSeen(iface.getInterfaces());
     }
 
-    for (DexType emulatedInterface : emulatedInterfaces) {
+    for (DexType emulatedInterface : emulatedInterfaces.getEmulatedInterfaces()) {
       DexClass s = appView.definitionFor(emulatedInterface);
       if (s != null) {
         s = appView.definitionFor(s.superType);
@@ -459,13 +493,17 @@
       }
     }
 
+    // Collect the signatures for the emulated interfaces to add.
+    Map<DexType, GenericSignature.ClassTypeSignature> signatures = new IdentityHashMap<>();
+    collectEmulatedInterfaces(clazz, filtered, signatures);
     // We need to introduce them in deterministic order for deterministic compilation.
     ArrayList<DexType> sortedEmulatedInterfaces = new ArrayList<>(filtered);
     Collections.sort(sortedEmulatedInterfaces);
     List<GenericSignature.ClassTypeSignature> extraInterfaceSignatures = new ArrayList<>();
     for (DexType extraInterface : sortedEmulatedInterfaces) {
-      extraInterfaceSignatures.add(
-          new GenericSignature.ClassTypeSignature(rewriter.getEmulatedInterface(extraInterface)));
+      GenericSignature.ClassTypeSignature signature = signatures.get(extraInterface);
+      assert signature != null;
+      extraInterfaceSignatures.add(signature);
     }
     // The emulated interface might already be implemented if the input class has gone through
     // library desugaring already.
@@ -490,6 +528,75 @@
     clazz.asProgramClass().addExtraInterfaces(extraInterfaceSignatures);
   }
 
+  private void collectEmulatedInterfaces(
+      DexClass clazz,
+      Set<DexType> emulatesInterfaces,
+      Map<DexType, GenericSignature.ClassTypeSignature> extraInterfaceSignatures) {
+    // TODO(b/182329331): Only handle type arguments for Cf to Cf desugar.
+    if (appView.options().cfToCfDesugar && clazz.validInterfaceSignatures()) {
+      clazz.forEachImmediateSupertype(
+          (type, signature) -> {
+            if (emulatesInterfaces.contains(type)) {
+              extraInterfaceSignatures.put(
+                  type,
+                  new GenericSignature.ClassTypeSignature(
+                      rewriter.getEmulatedInterface(type), signature.typeArguments()));
+            }
+            collectEmulatedInterfacesWithPropagatedTypeArguments(
+                type, signature.typeArguments(), emulatesInterfaces, extraInterfaceSignatures);
+          });
+    } else {
+      clazz.forEachImmediateSupertype(
+          (type) -> {
+            if (emulatesInterfaces.contains(type)) {
+              extraInterfaceSignatures.put(
+                  type,
+                  new GenericSignature.ClassTypeSignature(rewriter.getEmulatedInterface(type)));
+            }
+            collectEmulatedInterfacesWithPropagatedTypeArguments(
+                type, null, emulatesInterfaces, extraInterfaceSignatures);
+          });
+    }
+  }
+
+  private void collectEmulatedInterfacesWithPropagatedTypeArguments(
+      DexType type,
+      List<GenericSignature.FieldTypeSignature> typeArguments,
+      Set<DexType> emulatesInterfaces,
+      Map<DexType, GenericSignature.ClassTypeSignature> extraInterfaceSignatures) {
+    DexClass clazz = appView.definitionFor(type);
+    if (clazz == null) {
+      return;
+    }
+    // TODO(b/182329331): Only handle type arguments for Cf to Cf desugar.
+    if (appView.options().cfToCfDesugar && clazz.validInterfaceSignatures()) {
+      assert typeArguments != null;
+      clazz.forEachImmediateSupertypeWithAppliedTypeArguments(
+          typeArguments,
+          (iface, signature) -> {
+            if (emulatesInterfaces.contains(iface)) {
+              extraInterfaceSignatures.put(
+                  iface,
+                  new GenericSignature.ClassTypeSignature(
+                      rewriter.getEmulatedInterface(iface), signature));
+            }
+            collectEmulatedInterfacesWithPropagatedTypeArguments(
+                iface, signature, emulatesInterfaces, extraInterfaceSignatures);
+          });
+    } else {
+      assert typeArguments == null;
+      clazz.forEachImmediateSupertype(
+          iface -> {
+            if (emulatesInterfaces.contains(iface)) {
+              extraInterfaceSignatures.put(
+                  iface,
+                  new GenericSignature.ClassTypeSignature(rewriter.getEmulatedInterface(iface)));
+            }
+            collectEmulatedInterfacesWithPropagatedTypeArguments(
+                iface, null, emulatesInterfaces, extraInterfaceSignatures);
+          });
+    }
+  }
   // If any of the signature would lead to a different behavior than the default method on the
   // emulated interface, we need to resolve the forwarding methods.
   private boolean shouldResolveForwardingMethodsForEmulatedInterfaces(
@@ -502,7 +609,7 @@
       }
       DexClass resolvedHolder = resolutionResult.asSingleResolution().getResolvedHolder();
       if (!resolvedHolder.isLibraryClass()
-          && !emulatedInterfaceInfo.emulatedInterfaces.contains(resolvedHolder.type)) {
+          && !emulatedInterfaceInfo.contains(resolvedHolder.type)) {
         return true;
       }
     }
diff --git a/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/DesugaredGenericSignatureTest.java b/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/DesugaredGenericSignatureTest.java
index a3b768d..af6ca9e 100644
--- a/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/DesugaredGenericSignatureTest.java
+++ b/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/DesugaredGenericSignatureTest.java
@@ -9,15 +9,23 @@
 import static org.junit.Assert.assertEquals;
 
 import com.android.tools.r8.NeverInline;
+import com.android.tools.r8.TestBase;
 import com.android.tools.r8.TestParameters;
 import com.android.tools.r8.shaking.ProguardKeepAttributes;
+import com.android.tools.r8.utils.AndroidApiLevel;
 import com.android.tools.r8.utils.BooleanUtils;
 import com.android.tools.r8.utils.StringUtils;
 import com.android.tools.r8.utils.codeinspector.ClassSubject;
 import com.android.tools.r8.utils.codeinspector.CodeInspector;
+import java.io.Serializable;
+import java.lang.reflect.ParameterizedType;
+import java.lang.reflect.Type;
 import java.nio.file.Path;
 import java.time.LocalDate;
+import java.util.AbstractSequentialList;
+import java.util.LinkedHashMap;
 import java.util.List;
+import java.util.function.UnaryOperator;
 import org.junit.Assume;
 import org.junit.Test;
 import org.junit.runner.RunWith;
@@ -29,7 +37,6 @@
 
   private final TestParameters parameters;
   private final boolean shrinkDesugaredLibrary;
-  private static final String EXPECTED = StringUtils.lines("1970", "1", "2");
 
   @Parameters(name = "{1}, shrinkDesugaredLibrary: {0}")
   public static List<Object[]> data() {
@@ -60,7 +67,7 @@
             keepRuleConsumer.get(),
             shrinkDesugaredLibrary)
         .run(parameters.getRuntime(), Main.class)
-        .assertSuccessWithOutput(EXPECTED);
+        .assertSuccessWithOutput(expected(parameters, false));
   }
 
   @Test
@@ -96,13 +103,13 @@
               desugaredLibraryKeepRules,
               shrinkDesugaredLibrary)
           .run(parameters.getRuntime(), Main.class)
-          .assertSuccessWithOutput(EXPECTED);
+          .assertSuccessWithOutput(expected(parameters, true));
     } else {
       testForJvm()
           .addProgramFiles(jar)
           .addRunClasspathFiles(getDesugaredLibraryInCF(parameters, options -> {}))
           .run(parameters.getRuntime(), Main.class)
-          .assertSuccessWithOutput(EXPECTED);
+          .assertSuccessWithOutput(expected(parameters, true));
     }
   }
 
@@ -129,7 +136,7 @@
             keepRuleConsumer.get(),
             shrinkDesugaredLibrary)
         .run(parameters.getRuntime(), Main.class)
-        .assertSuccessWithOutput(EXPECTED);
+        .assertSuccessWithOutput(expected(parameters, false));
   }
 
   private void checkRewrittenSignature(CodeInspector inspector) {
@@ -149,6 +156,7 @@
   }
 
   public interface Box<T> {
+
     T addOne(T t);
   }
 
@@ -161,6 +169,61 @@
     }
   }
 
+  private static String expected(
+      TestParameters parameters, boolean genericSignaturesOnEmulatedInterfaces) {
+    final String EXPECTED = StringUtils.lines("Box", "1970", "1", "2");
+    final String STRING_KEY_HASH_MAP_EXPECTED =
+        StringUtils.lines(
+            "StringKeyHashMap", "1", "j$.util.Map<java.lang.String, T>", "2", "true", "true");
+    final String SAME_KEY_AND_VALUE_TYPE_HASH_MAP_EXPECTED =
+        StringUtils.lines(
+            "SameKeyAndValueTypeHashMap", "1", "j$.util.Map<T, T>", "2", "true", "true");
+    final String TRANSFORMING_SEQUENTIAL_LIST_EXPECTED =
+        StringUtils.lines("TransformingSequentialList", "2", "j$.util.List<T>");
+
+    final String EXPECTED_WITH_EMULATED_INTERFACE =
+        STRING_KEY_HASH_MAP_EXPECTED
+            + SAME_KEY_AND_VALUE_TYPE_HASH_MAP_EXPECTED
+            + TRANSFORMING_SEQUENTIAL_LIST_EXPECTED;
+    final String EXPECTED_WITHOUT_EMULATED_INTERFACE_ART_BEFORE_O =
+        StringUtils.lines(
+            "StringKeyHashMap",
+            "1",
+            "interface j$.util.Map",
+            "SameKeyAndValueTypeHashMap",
+            "1",
+            "interface j$.util.Map",
+            "TransformingSequentialList",
+            "2",
+            "interface j$.util.List");
+    final String EXPECTED_WITHOUT_EMULATED_INTERFACE_JVM_AND_ART_FROM_O =
+        StringUtils.lines(
+            "StringKeyHashMap",
+            "0",
+            "SameKeyAndValueTypeHashMap",
+            "0",
+            "TransformingSequentialList",
+            "1");
+
+    return EXPECTED
+        + (genericSignaturesOnEmulatedInterfaces
+                && !parameters
+                    .getApiLevel()
+                    .isGreaterThanOrEqualTo(TestBase.apiLevelWithDefaultInterfaceMethodsSupport())
+            ? EXPECTED_WITH_EMULATED_INTERFACE
+            : (parameters.isDexRuntime()
+                    && (parameters
+                            .getRuntime()
+                            .asDex()
+                            .getMinApiLevel()
+                            .isLessThan(AndroidApiLevel.N)
+                        || parameters
+                            .getApiLevel()
+                            .isLessThan(TestBase.apiLevelWithDefaultInterfaceMethodsSupport())))
+                ? EXPECTED_WITHOUT_EMULATED_INTERFACE_ART_BEFORE_O
+                : EXPECTED_WITHOUT_EMULATED_INTERFACE_JVM_AND_ART_FROM_O);
+  }
+
   public static class Main {
 
     public static Box<java.time.LocalDate> bar() {
@@ -168,10 +231,96 @@
     }
 
     public static void main(String[] args) {
+      testBox();
+      testEmulatedInterfaceGenericSignatureStringKeyHashMap();
+      testEmulatedInterfaceGenericSignatureSameKeyAndValueTypeHashMap();
+      testEmulatedInterfaceGenericSignatureTransformingSequentialList();
+    }
+
+    public static void testBox() {
+      System.out.println("Box");
       LocalDate localDate = bar().addOne(LocalDate.of(1970, 1, 1));
       System.out.println(localDate.getYear());
       System.out.println(localDate.getMonthValue());
       System.out.println(localDate.getDayOfMonth());
     }
+
+    public static void testEmulatedInterfaceGenericSignatureStringKeyHashMap() {
+      System.out.println("StringKeyHashMap");
+      Class<?> clazz = StringKeyHashMap.class;
+      System.out.println(clazz.getGenericInterfaces().length);
+      if (clazz.getGenericInterfaces().length == 0) {
+        return;
+      }
+      Type genericInterface = clazz.getGenericInterfaces()[0];
+      System.out.println(genericInterface);
+      if (genericInterface instanceof ParameterizedType) {
+        // The j$.util.Map emulated interface has the generic type arguments <String, T>.
+        Type[] actualTypeArguments =
+            ((ParameterizedType) genericInterface).getActualTypeArguments();
+        System.out.println(actualTypeArguments.length);
+        System.out.println(actualTypeArguments[0].equals(String.class));
+        System.out.println(actualTypeArguments[1].equals(clazz.getTypeParameters()[0]));
+      }
+    }
+
+    public static void testEmulatedInterfaceGenericSignatureSameKeyAndValueTypeHashMap() {
+      System.out.println("SameKeyAndValueTypeHashMap");
+      Class<?> clazz = SameKeyAndValueTypeHashMap.class;
+      System.out.println(clazz.getGenericInterfaces().length);
+      if (clazz.getGenericInterfaces().length == 0) {
+        return;
+      }
+      Type genericInterface = clazz.getGenericInterfaces()[0];
+      System.out.println(genericInterface);
+      if (genericInterface instanceof ParameterizedType) {
+        // The j$.util.Map emulated interface has the generic type arguments <T, T>.
+        Type[] actualTypeArguments =
+            ((ParameterizedType) genericInterface).getActualTypeArguments();
+        System.out.println(actualTypeArguments.length);
+        System.out.println(actualTypeArguments[0].equals(clazz.getTypeParameters()[0]));
+        System.out.println(actualTypeArguments[1].equals(clazz.getTypeParameters()[0]));
+      }
+    }
+
+    public static void testEmulatedInterfaceGenericSignatureTransformingSequentialList() {
+      System.out.println("TransformingSequentialList");
+      Class<?> clazz = TransformingSequentialList.class;
+      System.out.println(clazz.getGenericInterfaces().length);
+      if (clazz.getGenericInterfaces().length == 1) {
+        return;
+      }
+      // j$.util.List emulated interface has the generic type argument <T>.
+      System.out.println(clazz.getGenericInterfaces()[1]);
+    }
+  }
+
+  // LinkedHashMap implements Map.
+  abstract static class StringKeyHashMap<T> extends LinkedHashMap<String, T> {
+
+    // Need at least one overridden default method for emulated dispatch.
+    @Override
+    public T getOrDefault(Object key, T defaultValue) {
+      return super.getOrDefault(key, defaultValue);
+    }
+  }
+
+  // LinkedHashMap implements Map.
+  abstract static class SameKeyAndValueTypeHashMap<T> extends LinkedHashMap<T, T> {
+
+    // Need at least one overridden default method for emulated dispatch.
+    @Override
+    public T getOrDefault(Object key, T defaultValue) {
+      return super.getOrDefault(key, defaultValue);
+    }
+  }
+
+  // AbstractSequentialList implements List further up the hierarchy.
+  abstract static class TransformingSequentialList<F, T> extends AbstractSequentialList<T>
+      implements Serializable {
+
+    // Need at least one overridden default method for emulated dispatch.
+    @Override
+    public void replaceAll(UnaryOperator<T> operator) {}
   }
 }