Do not rewrite generic signatures in target of merged classes

When merging an interface into it's super type, we rewrite the generic
signature from the source to the target, making it look like a class
is implement itself as an interface. This CL will remove the entry in
the generic signature instead.

Bug: 147386014
Change-Id: I9532104f14b4099ddfea336d153bb996a7fc3cf6
diff --git a/src/main/java/com/android/tools/r8/naming/ClassNameMinifier.java b/src/main/java/com/android/tools/r8/naming/ClassNameMinifier.java
index d0240e9..d13cc3b 100644
--- a/src/main/java/com/android/tools/r8/naming/ClassNameMinifier.java
+++ b/src/main/java/com/android/tools/r8/naming/ClassNameMinifier.java
@@ -13,6 +13,7 @@
 import com.android.tools.r8.graph.DexClass;
 import com.android.tools.r8.graph.DexEncodedField;
 import com.android.tools.r8.graph.DexEncodedMethod;
+import com.android.tools.r8.graph.DexProgramClass;
 import com.android.tools.r8.graph.DexProto;
 import com.android.tools.r8.graph.DexString;
 import com.android.tools.r8.graph.DexType;
@@ -24,12 +25,14 @@
 import com.android.tools.r8.utils.DescriptorUtils;
 import com.android.tools.r8.utils.InternalOptions;
 import com.android.tools.r8.utils.InternalOptions.PackageObfuscationMode;
+import com.android.tools.r8.utils.IteratorUtils;
 import com.android.tools.r8.utils.Timing;
 import com.google.common.collect.ImmutableMap;
 import com.google.common.collect.Maps;
 import com.google.common.collect.Sets;
 import java.util.Collections;
 import java.util.HashMap;
+import java.util.Iterator;
 import java.util.Map;
 import java.util.Map.Entry;
 import java.util.Set;
@@ -132,7 +135,15 @@
     timing.end();
 
     timing.begin("rename-generic");
-    new GenericSignatureRewriter(appView, renaming).run(classes);
+    new GenericSignatureRewriter(appView, renaming)
+        .run(
+            new Iterable<DexProgramClass>() {
+              @Override
+              public Iterator<DexProgramClass> iterator() {
+                return IteratorUtils.<DexClass, DexProgramClass>filter(
+                    classes.iterator(), DexClass::isProgramClass);
+              }
+            });
     timing.end();
 
     timing.begin("rename-arrays");
diff --git a/src/main/java/com/android/tools/r8/naming/signature/GenericSignatureAction.java b/src/main/java/com/android/tools/r8/naming/signature/GenericSignatureAction.java
index 6753e06..947b6d4 100644
--- a/src/main/java/com/android/tools/r8/naming/signature/GenericSignatureAction.java
+++ b/src/main/java/com/android/tools/r8/naming/signature/GenericSignatureAction.java
@@ -9,11 +9,16 @@
  */
 public interface GenericSignatureAction<T> {
 
+  enum ParserPosition {
+    TOP_LEVEL,
+    INNER_ENCLOSING_OR_TYPE_ARGUMENT
+  }
+
   public void parsedSymbol(char symbol);
 
   public void parsedIdentifier(String identifier);
 
-  public T parsedTypeName(String name);
+  public T parsedTypeName(String name, ParserPosition isTopLevel);
 
   public T parsedInnerTypeName(T enclosingType, String name);
 
diff --git a/src/main/java/com/android/tools/r8/naming/signature/GenericSignatureParser.java b/src/main/java/com/android/tools/r8/naming/signature/GenericSignatureParser.java
index c1f6a7d..5ef9de9 100644
--- a/src/main/java/com/android/tools/r8/naming/signature/GenericSignatureParser.java
+++ b/src/main/java/com/android/tools/r8/naming/signature/GenericSignatureParser.java
@@ -4,6 +4,7 @@
 
 package com.android.tools.r8.naming.signature;
 
+import com.android.tools.r8.naming.signature.GenericSignatureAction.ParserPosition;
 import java.lang.reflect.GenericSignatureFormatError;
 import java.nio.CharBuffer;
 
@@ -50,7 +51,7 @@
  */
 public class GenericSignatureParser<T> {
 
-  private final GenericSignatureAction<T> actions;
+  private GenericSignatureAction<T> actions;
 
   /*
    * Parser:
@@ -110,7 +111,7 @@
     try {
       actions.start();
       setInput(signature);
-      parseFieldTypeSignature();
+      parseFieldTypeSignature(ParserPosition.TOP_LEVEL);
       actions.stop();
     } catch (GenericSignatureFormatError e) {
       throw e;
@@ -141,11 +142,11 @@
     parseOptFormalTypeParameters();
 
     // SuperclassSignature ::= ClassTypeSignature.
-    parseClassTypeSignature();
+    parseClassTypeSignature(ParserPosition.TOP_LEVEL);
 
     while (symbol > 0) {
       // SuperinterfaceSignature ::= ClassTypeSignature.
-      parseClassTypeSignature();
+      parseClassTypeSignature(ParserPosition.TOP_LEVEL);
     }
   }
 
@@ -178,28 +179,28 @@
     expect(':');
 
     if (symbol == 'L' || symbol == '[' || symbol == 'T') {
-      parseFieldTypeSignature();
+      parseFieldTypeSignature(ParserPosition.INNER_ENCLOSING_OR_TYPE_ARGUMENT);
     }
 
     while (symbol == ':') {
       // InterfaceBound ::= ":" FieldTypeSignature.
       actions.parsedSymbol(symbol);
       scanSymbol();
-      parseFieldTypeSignature();
+      parseFieldTypeSignature(ParserPosition.TOP_LEVEL);
     }
   }
 
-  private void parseFieldTypeSignature() {
+  private void parseFieldTypeSignature(ParserPosition parserPosition) {
     // FieldTypeSignature ::= ClassTypeSignature | ArrayTypeSignature | TypeVariableSignature.
     switch (symbol) {
       case 'L':
-        parseClassTypeSignature();
+        parseClassTypeSignature(parserPosition);
         break;
       case '[':
-        // ArrayTypeSignature ::= "[" TypSignature.
+        // ArrayTypeSignature ::= "[" TypeSignature.
         actions.parsedSymbol(symbol);
         scanSymbol();
-        updateTypeSignature();
+        updateTypeSignature(parserPosition);
         break;
       case 'T':
         updateTypeVariableSignature();
@@ -209,7 +210,7 @@
     }
   }
 
-  private void parseClassTypeSignature() {
+  private void parseClassTypeSignature(ParserPosition parserPosition) {
     // ClassTypeSignature ::= "L" {Ident "/"} Ident OptTypeArguments {"." Ident OptTypeArguments}
     //  ";".
     actions.parsedSymbol(symbol);
@@ -226,18 +227,22 @@
     }
 
     qualIdent.append(this.identifier);
-    T parsedEnclosingType = actions.parsedTypeName(qualIdent.toString());
+    T parsedEnclosingType = actions.parsedTypeName(qualIdent.toString(), parserPosition);
 
-    updateOptTypeArguments();
-
-    while (symbol == '.') {
-      // Deal with Member Classes:
-      actions.parsedSymbol(symbol);
-      scanSymbol();
-      scanIdentifier();
-      assert identifier != null;
-      parsedEnclosingType = actions.parsedInnerTypeName(parsedEnclosingType, identifier);
+    if (parsedEnclosingType != null) {
+      // We should only parse any optional type arguments and member classes if we have not merged
+      // the class into the current subtype.
       updateOptTypeArguments();
+
+      while (symbol == '.') {
+        // Deal with Member Classes.
+        actions.parsedSymbol(symbol);
+        scanSymbol();
+        scanIdentifier();
+        assert identifier != null;
+        parsedEnclosingType = actions.parsedInnerTypeName(parsedEnclosingType, identifier);
+        updateOptTypeArguments();
+      }
     }
 
     actions.parsedSymbol(symbol);
@@ -268,13 +273,13 @@
     } else if (symbol == '+') {
       actions.parsedSymbol(symbol);
       scanSymbol();
-      parseFieldTypeSignature();
+      parseFieldTypeSignature(ParserPosition.INNER_ENCLOSING_OR_TYPE_ARGUMENT);
     } else if (symbol == '-') {
       actions.parsedSymbol(symbol);
       scanSymbol();
-      parseFieldTypeSignature();
+      parseFieldTypeSignature(ParserPosition.INNER_ENCLOSING_OR_TYPE_ARGUMENT);
     } else {
-      parseFieldTypeSignature();
+      parseFieldTypeSignature(ParserPosition.INNER_ENCLOSING_OR_TYPE_ARGUMENT);
     }
   }
 
@@ -291,7 +296,7 @@
     expect(';');
   }
 
-  private void updateTypeSignature() {
+  private void updateTypeSignature(ParserPosition parserPosition) {
     switch (symbol) {
       case 'B':
       case 'C':
@@ -306,7 +311,7 @@
         break;
       default:
         // Not an elementary type, but a FieldTypeSignature.
-        parseFieldTypeSignature();
+        parseFieldTypeSignature(parserPosition);
     }
   }
 
@@ -319,7 +324,7 @@
     expect('(');
 
     while (symbol != ')' && (symbol > 0)) {
-      updateTypeSignature();
+      updateTypeSignature(ParserPosition.TOP_LEVEL);
     }
 
     actions.parsedSymbol(symbol);
@@ -336,7 +341,7 @@
         if (symbol == 'T') {
           updateTypeVariableSignature();
         } else {
-          parseClassTypeSignature();
+          parseClassTypeSignature(ParserPosition.TOP_LEVEL);
         }
       } while (symbol == '^');
     }
@@ -345,7 +350,7 @@
   private void updateReturnType() {
     // ReturnType ::= TypeSignature | "V".
     if (symbol != 'V') {
-      updateTypeSignature();
+      updateTypeSignature(ParserPosition.TOP_LEVEL);
     } else {
       actions.parsedSymbol(symbol);
       scanSymbol();
diff --git a/src/main/java/com/android/tools/r8/naming/signature/GenericSignatureRewriter.java b/src/main/java/com/android/tools/r8/naming/signature/GenericSignatureRewriter.java
index c3b7188..6a8d460 100644
--- a/src/main/java/com/android/tools/r8/naming/signature/GenericSignatureRewriter.java
+++ b/src/main/java/com/android/tools/r8/naming/signature/GenericSignatureRewriter.java
@@ -10,8 +10,8 @@
 import com.android.tools.r8.graph.AppView;
 import com.android.tools.r8.graph.DexAnnotation;
 import com.android.tools.r8.graph.DexAnnotationSet;
-import com.android.tools.r8.graph.DexClass;
 import com.android.tools.r8.graph.DexDefinition;
+import com.android.tools.r8.graph.DexProgramClass;
 import com.android.tools.r8.graph.DexString;
 import com.android.tools.r8.graph.DexType;
 import com.android.tools.r8.origin.Origin;
@@ -43,15 +43,16 @@
     this.reporter = appView.options().reporter;
   }
 
-  public void run(Iterable<? extends DexClass> classes) {
+  public void run(Iterable<? extends DexProgramClass> classes) {
     final GenericSignatureCollector genericSignatureCollector = new GenericSignatureCollector();
     final GenericSignatureParser<DexType> genericSignatureParser =
         new GenericSignatureParser<>(genericSignatureCollector);
-    // classes may not be the same as appInfo().classes() if applymapping is used on classpath
+    // Classes may not be the same as appInfo().classes() if applymapping is used on classpath
     // arguments. If that is the case, the ProguardMapMinifier will pass in all classes that is
     // either ProgramClass or has a mapping. This is then transitively called inside the
     // ClassNameMinifier.
-    for (DexClass clazz : classes) {
+    for (DexProgramClass clazz : classes) {
+      genericSignatureCollector.setCurrentClassContext(clazz);
       clazz.annotations =
           rewriteGenericSignatures(
               clazz.annotations,
@@ -152,13 +153,22 @@
 
   private class GenericSignatureCollector implements GenericSignatureAction<DexType> {
     private StringBuilder renamedSignature;
+    private DexProgramClass currentClassContext;
 
-    public String getRenamedSignature() {
+    String getRenamedSignature() {
       return renamedSignature.toString();
     }
 
+    void setCurrentClassContext(DexProgramClass clazz) {
+      currentClassContext = clazz;
+    }
+
     @Override
     public void parsedSymbol(char symbol) {
+      if (symbol == ';' && renamedSignature.charAt(renamedSignature.length() - 1) == ';') {
+        // The type was never written (maybe because it was merged with it's subtype)
+        return;
+      }
       renamedSignature.append(symbol);
     }
 
@@ -168,13 +178,20 @@
     }
 
     @Override
-    public DexType parsedTypeName(String name) {
+    public DexType parsedTypeName(String name, ParserPosition parserPosition) {
       DexType type = appView.dexItemFactory().createType(getDescriptorFromClassBinaryName(name));
       type = appView.graphLense().lookupType(type);
       if (appView.appInfo().wasPruned(type)) {
         type = appView.dexItemFactory().objectType;
       }
       DexString renamedDescriptor = renaming.getOrDefault(type, type.descriptor);
+      if (parserPosition == ParserPosition.TOP_LEVEL
+          && currentClassContext != null
+          && renamedDescriptor.equals(currentClassContext.type.descriptor)) {
+        // We are renaming an interface to the class where it was merged down.
+        renamedSignature.deleteCharAt(renamedSignature.length() - 1);
+        return null;
+      }
       renamedSignature.append(getClassBinaryNameFromDescriptor(renamedDescriptor.toString()));
       return type;
     }
@@ -197,6 +214,7 @@
       type = appView.graphLense().lookupType(type);
       DexString renamedDescriptor = renaming.get(type);
       if (renamedDescriptor != null) {
+        // TODO(b/147504070): If this is a merged class equal to the class context, do not add.
         // Pick the renamed inner class from the fully renamed binary name.
         String fullRenamedBinaryName =
             getClassBinaryNameFromDescriptor(renamedDescriptor.toString());
diff --git a/src/main/java/com/android/tools/r8/shaking/VerticalClassMerger.java b/src/main/java/com/android/tools/r8/shaking/VerticalClassMerger.java
index ae8eb7e..ab32bd5 100644
--- a/src/main/java/com/android/tools/r8/shaking/VerticalClassMerger.java
+++ b/src/main/java/com/android/tools/r8/shaking/VerticalClassMerger.java
@@ -383,7 +383,7 @@
       }
     }
     if (clazz.getEnclosingMethod() != null || !clazz.getInnerClasses().isEmpty()) {
-      // TODO(herhut): Consider supporting merging of enclosing-method and inner-class attributes.
+      // TODO(b/147504070): Consider merging of enclosing-method and inner-class attributes.
       if (Log.ENABLED) {
         AbortReason.UNSUPPORTED_ATTRIBUTES.printLogMessageForClass(clazz);
       }
@@ -440,7 +440,7 @@
       return false;
     }
     if (targetClass.getEnclosingMethod() != null || !targetClass.getInnerClasses().isEmpty()) {
-      // TODO(herhut): Consider supporting merging of enclosing-method and inner-class attributes.
+      // TODO(b/147504070): Consider merging of enclosing-method and inner-class attributes.
       if (Log.ENABLED) {
         AbortReason.UNSUPPORTED_ATTRIBUTES.printLogMessageForClass(clazz);
       }
diff --git a/src/main/java/com/android/tools/r8/utils/IteratorUtils.java b/src/main/java/com/android/tools/r8/utils/IteratorUtils.java
index 7ffce35..f38c02a 100644
--- a/src/main/java/com/android/tools/r8/utils/IteratorUtils.java
+++ b/src/main/java/com/android/tools/r8/utils/IteratorUtils.java
@@ -16,7 +16,8 @@
 
 public class IteratorUtils {
 
-  public static <T, S extends T> Iterator<S> filter(Iterator<T> iterator, Predicate<T> predicate) {
+  public static <T, S extends T> Iterator<S> filter(
+      Iterator<? extends T> iterator, Predicate<T> predicate) {
     return new Iterator<S>() {
 
       private S next = advance();
diff --git a/src/test/java/com/android/tools/r8/naming/GenericSignatureParserTest.java b/src/test/java/com/android/tools/r8/naming/GenericSignatureParserTest.java
index b264ef5..4489b75 100644
--- a/src/test/java/com/android/tools/r8/naming/GenericSignatureParserTest.java
+++ b/src/test/java/com/android/tools/r8/naming/GenericSignatureParserTest.java
@@ -22,6 +22,7 @@
 import org.junit.Test;
 
 public class GenericSignatureParserTest extends TestBase {
+
   private static class ReGenerateGenericSignatureRewriter
       implements GenericSignatureAction<String> {
 
@@ -42,7 +43,7 @@
     }
 
     @Override
-    public String parsedTypeName(String name) {
+    public String parsedTypeName(String name, ParserPosition parserPosition) {
       renamedSignature.append(name);
       return name;
     }
@@ -389,7 +390,7 @@
       }
 
       @Override
-      public String parsedTypeName(String name) {
+      public String parsedTypeName(String name, ParserPosition parserPosition) {
         throw exceptionSupplier.get();
       }
     }
diff --git a/src/test/java/com/android/tools/r8/naming/signature/SignatureOfMergedClassesTest.java b/src/test/java/com/android/tools/r8/naming/signature/SignatureOfMergedClassesTest.java
new file mode 100644
index 0000000..a9fc20f
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/naming/signature/SignatureOfMergedClassesTest.java
@@ -0,0 +1,122 @@
+// Copyright (c) 2020, 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.naming.signature;
+
+import static com.android.tools.r8.utils.codeinspector.Matchers.isPresent;
+import static org.hamcrest.CoreMatchers.not;
+import static org.hamcrest.MatcherAssert.assertThat;
+
+import com.android.tools.r8.CompilationFailedException;
+import com.android.tools.r8.TestBase;
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.TestParametersCollection;
+import com.android.tools.r8.naming.signature.merging.I;
+import com.android.tools.r8.naming.signature.merging.ImplI;
+import com.android.tools.r8.naming.signature.merging.ImplK;
+import com.android.tools.r8.naming.signature.merging.InterfaceToKeep;
+import com.android.tools.r8.naming.signature.merging.J;
+import com.android.tools.r8.naming.signature.merging.K;
+import java.io.IOException;
+import java.lang.invoke.LambdaConversionException;
+import java.lang.reflect.Type;
+import java.util.concurrent.ExecutionException;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+import org.junit.runners.Parameterized.Parameters;
+
+@RunWith(Parameterized.class)
+public class SignatureOfMergedClassesTest extends TestBase {
+
+  private final TestParameters parameters;
+
+  @Parameters(name = "{0}")
+  public static TestParametersCollection data() {
+    return getTestParameters().withAllRuntimesAndApiLevels().build();
+  }
+
+  public SignatureOfMergedClassesTest(TestParameters parameters) {
+    this.parameters = parameters;
+  }
+
+  @Test
+  public void testRemovalOfMergedInterfaceOnSameClass()
+      throws IOException, CompilationFailedException, ExecutionException,
+          LambdaConversionException {
+    testForR8(parameters.getBackend())
+        .addProgramClasses(
+            ImplI.class, ImplK.class, I.class, J.class, InterfaceToKeep.class, K.class, Main.class)
+        .addKeepMainRule(Main.class)
+        .addKeepClassRules(InterfaceToKeep.class)
+        .addKeepAttributes("Signature, InnerClasses, EnclosingMethod, *Annotation*")
+        .setMinApi(parameters.getApiLevel())
+        .noMinification()
+        .addOptionsModification(
+            internalOptions -> {
+              internalOptions.enableUnusedInterfaceRemoval = false;
+            })
+        .run(parameters.getRuntime(), Main.class)
+        .assertSuccessWithOutputLines(
+            "ImplI.foo",
+            "ImplI: com.android.tools.r8.naming.signature.merging.InterfaceToKeep<java.lang.Void>",
+            "K: com.android.tools.r8.naming.signature.merging.InterfaceToKeep<java.lang.Void>",
+            "ImplK.foo",
+            "ImplK.bar",
+            "ImplK: interface com.android.tools.r8.naming.signature.merging.K")
+        .inspect(
+            codeInspector -> {
+              assertThat(codeInspector.clazz(I.class), not(isPresent()));
+              assertThat(codeInspector.clazz(J.class), not(isPresent()));
+            });
+  }
+
+  @Test
+  public void testKeepingOneSelfOnInterface()
+      throws ExecutionException, CompilationFailedException, IOException {
+    testForR8(parameters.getBackend())
+        .addProgramClasses(Foo.class, InterfaceToKeep.class)
+        .addKeepMainRule(Foo.class)
+        .addKeepClassRules(InterfaceToKeep.class)
+        .addKeepAttributes("Signature, InnerClasses, EnclosingMethod, *Annotation*")
+        .setMinApi(parameters.getApiLevel())
+        .noMinification()
+        .addOptionsModification(
+            internalOptions -> {
+              internalOptions.enableUnusedInterfaceRemoval = false;
+            })
+        .run(parameters.getRuntime(), Foo.class)
+        .assertSuccessWithOutputLines(
+            "com.android.tools.r8.naming.signature.merging.InterfaceToKeep"
+                + "<com.android.tools.r8.naming.signature.SignatureOfMergedClassesTest$Foo>");
+  }
+
+  public static class Foo implements InterfaceToKeep<Foo> {
+
+    public static void main(String[] args) {
+      for (Type genericInterface : Foo.class.getGenericInterfaces()) {
+        System.out.println(genericInterface);
+      }
+    }
+  }
+
+  public static class Main {
+
+    public static void main(String[] args) {
+      new ImplI().foo();
+      for (Type genericInterface : ImplI.class.getGenericInterfaces()) {
+        System.out.println("ImplI: " + genericInterface);
+      }
+      for (Type genericInterface : K.class.getGenericInterfaces()) {
+        System.out.println("K: " + genericInterface);
+      }
+      K k = new ImplK();
+      k.foo();
+      k.bar();
+      for (Type genericInterface : ImplK.class.getGenericInterfaces()) {
+        System.out.println("ImplK: " + genericInterface);
+      }
+    }
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/naming/signature/merging/I.java b/src/test/java/com/android/tools/r8/naming/signature/merging/I.java
new file mode 100644
index 0000000..237786c
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/naming/signature/merging/I.java
@@ -0,0 +1,9 @@
+// Copyright (c) 2020, 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.naming.signature.merging;
+
+public interface I {
+  void foo();
+}
diff --git a/src/test/java/com/android/tools/r8/naming/signature/merging/ImplI.java b/src/test/java/com/android/tools/r8/naming/signature/merging/ImplI.java
new file mode 100644
index 0000000..b842386
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/naming/signature/merging/ImplI.java
@@ -0,0 +1,13 @@
+// Copyright (c) 2020, 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.naming.signature.merging;
+
+public class ImplI implements InterfaceToKeep<Void>, I {
+
+  @Override
+  public void foo() {
+    System.out.println("ImplI.foo");
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/naming/signature/merging/ImplK.java b/src/test/java/com/android/tools/r8/naming/signature/merging/ImplK.java
new file mode 100644
index 0000000..3c23d56
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/naming/signature/merging/ImplK.java
@@ -0,0 +1,18 @@
+// Copyright (c) 2020, 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.naming.signature.merging;
+
+public class ImplK implements K {
+
+  @Override
+  public void foo() {
+    System.out.println("ImplK.foo");
+  }
+
+  @Override
+  public void bar() {
+    System.out.println("ImplK.bar");
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/naming/signature/merging/InterfaceToKeep.java b/src/test/java/com/android/tools/r8/naming/signature/merging/InterfaceToKeep.java
new file mode 100644
index 0000000..6bebcd0
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/naming/signature/merging/InterfaceToKeep.java
@@ -0,0 +1,7 @@
+// Copyright (c) 2020, 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.naming.signature.merging;
+
+public interface InterfaceToKeep<T> {}
diff --git a/src/test/java/com/android/tools/r8/naming/signature/merging/J.java b/src/test/java/com/android/tools/r8/naming/signature/merging/J.java
new file mode 100644
index 0000000..444a5e8
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/naming/signature/merging/J.java
@@ -0,0 +1,9 @@
+// Copyright (c) 2020, 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.naming.signature.merging;
+
+public interface J {
+  void foo();
+}
diff --git a/src/test/java/com/android/tools/r8/naming/signature/merging/K.java b/src/test/java/com/android/tools/r8/naming/signature/merging/K.java
new file mode 100644
index 0000000..4b8ed50
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/naming/signature/merging/K.java
@@ -0,0 +1,9 @@
+// Copyright (c) 2020, 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.naming.signature.merging;
+
+public interface K extends InterfaceToKeep<Void>, J {
+  void bar();
+}
diff --git a/src/test/java/com/android/tools/r8/utils/codeinspector/CodeInspector.java b/src/test/java/com/android/tools/r8/utils/codeinspector/CodeInspector.java
index 4aec544..1a4dcc16 100644
--- a/src/test/java/com/android/tools/r8/utils/codeinspector/CodeInspector.java
+++ b/src/test/java/com/android/tools/r8/utils/codeinspector/CodeInspector.java
@@ -423,7 +423,7 @@
     }
 
     @Override
-    public String parsedTypeName(String name) {
+    public String parsedTypeName(String name, ParserPosition parserPosition) {
       String type = name;
       if (obfuscatedToOriginalMapping != null) {
         String original = mapType(obfuscatedToOriginalMapping, name);