Ensure handling of optional type args and inner if class was merged

Bug: 147800709
Change-Id: I71d7041df0d455fcf07287b50385181d78fdb50a
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 dc0aa8c..74b17d6 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
@@ -229,20 +229,16 @@
     qualIdent.append(this.identifier);
     T parsedEnclosingType = actions.parsedTypeName(qualIdent.toString(), parserPosition);
 
-    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();
+    updateOptTypeArguments();
 
-      while (symbol == '.') {
-        // Deal with Member Classes.
-        actions.parsedSymbol(symbol);
-        scanSymbol();
-        scanIdentifier();
-        assert identifier != null;
-        parsedEnclosingType = actions.parsedInnerTypeName(parsedEnclosingType, identifier);
-        updateOptTypeArguments();
-      }
+    while (symbol == '.') {
+      // Deal with Member Classes.
+      actions.parsedSymbol(symbol);
+      scanSymbol();
+      scanIdentifier();
+      assert identifier != null;
+      parsedEnclosingType = actions.parsedInnerTypeName(parsedEnclosingType, identifier);
+      updateOptTypeArguments();
     }
 
     actions.parsedSymbol(symbol);
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 13c2c43..45c59a8 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
@@ -24,6 +24,7 @@
 import java.util.Map;
 import java.util.function.BiConsumer;
 import java.util.function.Consumer;
+import java.util.function.Predicate;
 import java.util.function.Supplier;
 
 public class GenericSignatureRewriter {
@@ -154,6 +155,7 @@
   private class GenericSignatureCollector implements GenericSignatureAction<DexType> {
     private StringBuilder renamedSignature;
     private DexProgramClass currentClassContext;
+    private DexType lastWrittenType = null;
 
     String getRenamedSignature() {
       return renamedSignature.toString();
@@ -165,8 +167,13 @@
 
     @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)
+      if (symbol == ';' && lastWrittenType == null) {
+        // The type was never written (maybe because it was merged with it's subtype).
+        return;
+      }
+      // If the super-class or interface has been merged, we will stop writing out type
+      // arguments, resulting in a signature on the form '<>' if we do not remove it.
+      if (symbol == '>' && removeWrittenCharacter(c -> c == '<')) {
         return;
       }
       renamedSignature.append(symbol);
@@ -179,6 +186,12 @@
 
     @Override
     public DexType parsedTypeName(String name, ParserPosition parserPosition) {
+      if (parserPosition == ParserPosition.ENCLOSING_INNER_OR_TYPE_ANNOTATION
+          && lastWrittenType == null) {
+        // We are writing type-arguments for a merged class.
+        removeWrittenClassCharacter();
+        return null;
+      }
       String originalDescriptor = getDescriptorFromClassBinaryName(name);
       DexType type =
           appView.graphLense().lookupType(appView.dexItemFactory().createType(originalDescriptor));
@@ -192,16 +205,36 @@
         DexString classDescriptor = currentClassContext.type.descriptor;
         if (!originalDescriptor.equals(classDescriptor.toString())
             && renamedDescriptor.equals(classDescriptor)) {
-          renamedSignature.deleteCharAt(renamedSignature.length() - 1);
-          return null;
+          lastWrittenType = null;
+          removeWrittenClassCharacter();
+          return type;
         }
       }
       renamedSignature.append(getClassBinaryNameFromDescriptor(renamedDescriptor.toString()));
+      lastWrittenType = type;
       return type;
     }
 
+    private boolean removeWrittenCharacter(Predicate<Character> removeIf) {
+      int index = renamedSignature.length() - 1;
+      if (index < 0 || !removeIf.test(renamedSignature.charAt(index))) {
+        return false;
+      }
+      renamedSignature.deleteCharAt(index);
+      return true;
+    }
+
+    private void removeWrittenClassCharacter() {
+      removeWrittenCharacter(c -> c == 'L');
+    }
+
     @Override
     public DexType parsedInnerTypeName(DexType enclosingType, String name) {
+      if (enclosingType == null) {
+        // We are writing inner type names
+        removeWrittenClassCharacter();
+        return null;
+      }
       assert enclosingType.isClassType();
       String enclosingDescriptor = enclosingType.toDescriptorString();
       DexType type =
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
index a9fc20f..01a95ba 100644
--- a/src/test/java/com/android/tools/r8/naming/signature/SignatureOfMergedClassesTest.java
+++ b/src/test/java/com/android/tools/r8/naming/signature/SignatureOfMergedClassesTest.java
@@ -10,16 +10,18 @@
 
 import com.android.tools.r8.CompilationFailedException;
 import com.android.tools.r8.TestBase;
+import com.android.tools.r8.TestDiagnosticMessages;
 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.ImplL;
 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 com.android.tools.r8.naming.signature.merging.L;
 import java.io.IOException;
-import java.lang.invoke.LambdaConversionException;
 import java.lang.reflect.Type;
 import java.util.concurrent.ExecutionException;
 import org.junit.Test;
@@ -43,11 +45,18 @@
 
   @Test
   public void testRemovalOfMergedInterfaceOnSameClass()
-      throws IOException, CompilationFailedException, ExecutionException,
-          LambdaConversionException {
+      throws IOException, CompilationFailedException, ExecutionException {
     testForR8(parameters.getBackend())
         .addProgramClasses(
-            ImplI.class, ImplK.class, I.class, J.class, InterfaceToKeep.class, K.class, Main.class)
+            ImplI.class,
+            ImplK.class,
+            I.class,
+            J.class,
+            InterfaceToKeep.class,
+            K.class,
+            Main.class,
+            L.class,
+            ImplL.class)
         .addKeepMainRule(Main.class)
         .addKeepClassRules(InterfaceToKeep.class)
         .addKeepAttributes("Signature, InnerClasses, EnclosingMethod, *Annotation*")
@@ -57,6 +66,7 @@
             internalOptions -> {
               internalOptions.enableUnusedInterfaceRemoval = false;
             })
+        .compileWithExpectedDiagnostics(TestDiagnosticMessages::assertNoMessages)
         .run(parameters.getRuntime(), Main.class)
         .assertSuccessWithOutputLines(
             "ImplI.foo",
@@ -64,7 +74,8 @@
             "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")
+            "ImplK: interface com.android.tools.r8.naming.signature.merging.K",
+            "ImplL.print")
         .inspect(
             codeInspector -> {
               assertThat(codeInspector.clazz(I.class), not(isPresent()));
@@ -117,6 +128,11 @@
       for (Type genericInterface : ImplK.class.getGenericInterfaces()) {
         System.out.println("ImplK: " + genericInterface);
       }
+      L<ImplL> l = new ImplL();
+      l.print((ImplL) l);
+      for (Type genericInterface : ImplL.class.getGenericInterfaces()) {
+        System.out.println("ImplL: " + genericInterface);
+      }
     }
   }
 }
diff --git a/src/test/java/com/android/tools/r8/naming/signature/merging/ImplL.java b/src/test/java/com/android/tools/r8/naming/signature/merging/ImplL.java
new file mode 100644
index 0000000..e50bdb3
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/naming/signature/merging/ImplL.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 ImplL implements L<ImplL> {
+
+  @Override
+  public void print(ImplL implL) {
+    System.out.println("ImplL.print");
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/naming/signature/merging/L.java b/src/test/java/com/android/tools/r8/naming/signature/merging/L.java
new file mode 100644
index 0000000..65656dd
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/naming/signature/merging/L.java
@@ -0,0 +1,10 @@
+// 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 L<T> {
+
+  void print(T t);
+}