Prune generic signatures from non-pinned items

This CL will now allow us to prune all generic signatures that are not
pinned. For all pruned generic signatures, all applied arguments will be
removed to ensure correctness.

Additionally, this CL also ensure proper substitution of type-parameters
for pruned enclosing classes.

Bug: 184927364
Bug: 185098797
Bug: 172999267
Change-Id: I61820f0f31cc7284a2daabcb46c5ebe0d3c3bd79
diff --git a/src/main/java/com/android/tools/r8/R8.java b/src/main/java/com/android/tools/r8/R8.java
index d814466..2404763 100644
--- a/src/main/java/com/android/tools/r8/R8.java
+++ b/src/main/java/com/android/tools/r8/R8.java
@@ -32,6 +32,7 @@
 import com.android.tools.r8.graph.DexReference;
 import com.android.tools.r8.graph.DexType;
 import com.android.tools.r8.graph.DirectMappedDexApplication;
+import com.android.tools.r8.graph.GenericSignatureTypeVariableRemover;
 import com.android.tools.r8.graph.GraphLens;
 import com.android.tools.r8.graph.InitClassLens;
 import com.android.tools.r8.graph.PrunedItems;
@@ -376,6 +377,10 @@
           appView.withGeneratedExtensionRegistryShrinker(
               shrinker -> shrinker.run(Mode.INITIAL_TREE_SHAKING));
 
+          // Build enclosing information and type-paramter information before pruning.
+          GenericSignatureTypeVariableRemover typeVariableRemover =
+              GenericSignatureTypeVariableRemover.create(appView, appView.appInfo().classes());
+
           TreePruner pruner = new TreePruner(appViewWithLiveness);
           DirectMappedDexApplication prunedApp = pruner.run(executorService);
 
@@ -402,6 +407,7 @@
           annotationRemover.ensureValid().run();
           classesToRetainInnerClassAttributeFor =
               annotationRemover.getClassesToRetainInnerClassAttributeFor();
+          typeVariableRemover.removeDeadGenericSignatureTypeVariables(appView);
           new GenericSignatureRewriter(appView, NamingLens.getIdentityLens())
               .run(appView.appInfo().classes(), executorService);
         }
@@ -611,6 +617,9 @@
                     shrinker -> shrinker.run(enqueuer.getMode()),
                     DefaultTreePrunerConfiguration.getInstance());
 
+            GenericSignatureTypeVariableRemover typeVariableRemover =
+                GenericSignatureTypeVariableRemover.create(appView, appView.appInfo().classes());
+
             TreePruner pruner = new TreePruner(appViewWithLiveness, treePrunerConfiguration);
             DirectMappedDexApplication application = pruner.run(executorService);
             Set<DexType> removedClasses = pruner.getRemovedClasses();
@@ -652,6 +661,7 @@
                 .setClassesToRetainInnerClassAttributeFor(classesToRetainInnerClassAttributeFor)
                 .build(appView.withLiveness(), removedClasses)
                 .run();
+            typeVariableRemover.removeDeadGenericSignatureTypeVariables(appView);
             // Synthesize fields for triggering class initializers.
             new ClassInitFieldSynthesizer(appViewWithLiveness).run(executorService);
           }
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 e38a163..9a34638 100644
--- a/src/main/java/com/android/tools/r8/graph/GenericSignature.java
+++ b/src/main/java/com/android/tools/r8/graph/GenericSignature.java
@@ -98,10 +98,26 @@
  */
 public class GenericSignature {
 
-  static final List<FormalTypeParameter> EMPTY_TYPE_PARAMS = ImmutableList.of();
-  static final List<FieldTypeSignature> EMPTY_TYPE_ARGUMENTS = ImmutableList.of();
-  static final List<ClassTypeSignature> EMPTY_SUPER_INTERFACES = ImmutableList.of();
-  static final List<TypeSignature> EMPTY_TYPE_SIGNATURES = ImmutableList.of();
+  private static final List<FormalTypeParameter> EMPTY_TYPE_PARAMS = ImmutableList.of();
+  private static final List<FieldTypeSignature> EMPTY_TYPE_ARGUMENTS = ImmutableList.of();
+  private static final List<ClassTypeSignature> EMPTY_SUPER_INTERFACES = ImmutableList.of();
+  private static final List<TypeSignature> EMPTY_TYPE_SIGNATURES = ImmutableList.of();
+
+  public static List<FormalTypeParameter> getEmptyTypeParams() {
+    return EMPTY_TYPE_PARAMS;
+  }
+
+  public static List<FieldTypeSignature> getEmptyTypeArguments() {
+    return EMPTY_TYPE_ARGUMENTS;
+  }
+
+  public static List<ClassTypeSignature> getEmptySuperInterfaces() {
+    return EMPTY_SUPER_INTERFACES;
+  }
+
+  public static List<TypeSignature> getEmptyTypeSignatures() {
+    return EMPTY_TYPE_SIGNATURES;
+  }
 
   interface DexDefinitionSignature<T extends DexDefinition> {
 
@@ -561,10 +577,11 @@
       if (visitedType == null) {
         return null;
       }
-      List<FieldTypeSignature> rewrittenArguments = visitor.visitTypeArguments(typeArguments);
+      List<FieldTypeSignature> rewrittenArguments =
+          visitor.visitTypeArguments(visitedType, typeArguments);
       ClassTypeSignature rewrittenOuter = null;
       if (enclosingTypeSignature != null) {
-        rewrittenOuter = visitor.visitSimpleClass(enclosingTypeSignature);
+        rewrittenOuter = visitor.visitEnclosing(enclosingTypeSignature, this);
       }
       if (type == visitedType
           && typeArguments == rewrittenArguments
diff --git a/src/main/java/com/android/tools/r8/graph/GenericSignaturePartialTypeArgumentApplier.java b/src/main/java/com/android/tools/r8/graph/GenericSignaturePartialTypeArgumentApplier.java
index dfa8a95..adb256f 100644
--- a/src/main/java/com/android/tools/r8/graph/GenericSignaturePartialTypeArgumentApplier.java
+++ b/src/main/java/com/android/tools/r8/graph/GenericSignaturePartialTypeArgumentApplier.java
@@ -4,6 +4,8 @@
 
 package com.android.tools.r8.graph;
 
+import static com.android.tools.r8.graph.GenericSignature.getEmptyTypeArguments;
+
 import com.android.tools.r8.graph.GenericSignature.ClassSignature;
 import com.android.tools.r8.graph.GenericSignature.ClassTypeSignature;
 import com.android.tools.r8.graph.GenericSignature.FieldTypeSignature;
@@ -14,31 +16,66 @@
 import com.android.tools.r8.graph.GenericSignature.TypeSignature;
 import com.android.tools.r8.graph.GenericSignature.WildcardIndicator;
 import com.android.tools.r8.utils.ListUtils;
-import java.util.HashSet;
+import com.google.common.collect.ImmutableSet;
+import java.util.Collections;
 import java.util.List;
 import java.util.Map;
 import java.util.Set;
+import java.util.function.BiPredicate;
+import java.util.function.Predicate;
 
 public class GenericSignaturePartialTypeArgumentApplier implements GenericSignatureVisitor {
 
   private final Map<String, DexType> substitutions;
+  private final Set<String> liveTypeVariables;
   private final DexType objectType;
-  private final Set<String> introducedClassTypeVariables = new HashSet<>();
-  private final Set<String> introducedMethodTypeVariables = new HashSet<>();
+  private final BiPredicate<DexType, DexType> enclosingPruned;
+  private final Predicate<DexType> hasGenericTypeParameters;
 
   private GenericSignaturePartialTypeArgumentApplier(
-      Map<String, DexType> substitutions, DexType objectType) {
+      Map<String, DexType> substitutions,
+      Set<String> liveTypeVariables,
+      DexType objectType,
+      BiPredicate<DexType, DexType> enclosingPruned,
+      Predicate<DexType> hasGenericTypeParameters) {
     this.substitutions = substitutions;
+    this.liveTypeVariables = liveTypeVariables;
     this.objectType = objectType;
+    this.enclosingPruned = enclosingPruned;
+    this.hasGenericTypeParameters = hasGenericTypeParameters;
   }
 
   public static GenericSignaturePartialTypeArgumentApplier build(
-      DexType objectType, ClassSignature classSignature, Map<String, DexType> substitutions) {
-    GenericSignaturePartialTypeArgumentApplier applier =
-        new GenericSignaturePartialTypeArgumentApplier(substitutions, objectType);
-    classSignature.formalTypeParameters.forEach(
-        parameter -> applier.introducedClassTypeVariables.add(parameter.name));
-    return applier;
+      DexType objectType,
+      BiPredicate<DexType, DexType> enclosingPruned,
+      Predicate<DexType> hasGenericTypeParameters) {
+    return new GenericSignaturePartialTypeArgumentApplier(
+        Collections.emptyMap(),
+        Collections.emptySet(),
+        objectType,
+        enclosingPruned,
+        hasGenericTypeParameters);
+  }
+
+  public GenericSignaturePartialTypeArgumentApplier addSubstitutionsAndVariables(
+      Map<String, DexType> substitutions, Set<String> liveTypeVariables) {
+    return new GenericSignaturePartialTypeArgumentApplier(
+        substitutions, liveTypeVariables, objectType, enclosingPruned, hasGenericTypeParameters);
+  }
+
+  public GenericSignaturePartialTypeArgumentApplier buildForMethod(
+      List<FormalTypeParameter> formals) {
+    if (formals.isEmpty()) {
+      return this;
+    }
+    ImmutableSet.Builder<String> liveVariablesBuilder = ImmutableSet.builder();
+    liveVariablesBuilder.addAll(liveTypeVariables);
+    formals.forEach(
+        formal -> {
+          liveVariablesBuilder.add(formal.name);
+        });
+    return new GenericSignaturePartialTypeArgumentApplier(
+        substitutions, liveTypeVariables, objectType, enclosingPruned, hasGenericTypeParameters);
   }
 
   @Override
@@ -48,12 +85,7 @@
 
   @Override
   public MethodTypeSignature visitMethodSignature(MethodTypeSignature methodSignature) {
-    assert introducedMethodTypeVariables.isEmpty();
-    methodSignature.formalTypeParameters.forEach(
-        parameter -> introducedMethodTypeVariables.add(parameter.name));
-    MethodTypeSignature rewritten = methodSignature.visit(this);
-    introducedMethodTypeVariables.clear();
-    return rewritten;
+    return methodSignature.visit(this);
   }
 
   @Override
@@ -96,9 +128,10 @@
   }
 
   @Override
-  public List<FieldTypeSignature> visitTypeArguments(List<FieldTypeSignature> typeArguments) {
-    if (typeArguments.isEmpty()) {
-      return typeArguments;
+  public List<FieldTypeSignature> visitTypeArguments(
+      DexType type, List<FieldTypeSignature> typeArguments) {
+    if (typeArguments.isEmpty() || !hasGenericTypeParameters.test(type)) {
+      return getEmptyTypeArguments();
     }
     // Wildcards can only be called be used in certain positions:
     // https://docs.oracle.com/javase/tutorial/java/generics/wildcards.html
@@ -121,8 +154,13 @@
   }
 
   @Override
-  public ClassTypeSignature visitSimpleClass(ClassTypeSignature classTypeSignature) {
-    return classTypeSignature.visit(this);
+  public ClassTypeSignature visitEnclosing(
+      ClassTypeSignature enclosingSignature, ClassTypeSignature enclosedSignature) {
+    if (enclosingPruned.test(enclosingSignature.type(), enclosedSignature.type())) {
+      return null;
+    } else {
+      return enclosingSignature.visit(this);
+    }
   }
 
   @Override
@@ -185,8 +223,7 @@
       assert fieldSignature.isTypeVariableSignature();
       String typeVariableName = fieldSignature.asTypeVariableSignature().typeVariable();
       if (substitutions.containsKey(typeVariableName)
-          && !introducedClassTypeVariables.contains(typeVariableName)
-          && !introducedMethodTypeVariables.contains(typeVariableName)) {
+          && !liveTypeVariables.contains(typeVariableName)) {
         DexType substitution = substitutions.get(typeVariableName);
         if (substitution == null) {
           substitution = objectType;
diff --git a/src/main/java/com/android/tools/r8/graph/GenericSignaturePrinter.java b/src/main/java/com/android/tools/r8/graph/GenericSignaturePrinter.java
index 739bc27..d04e3af 100644
--- a/src/main/java/com/android/tools/r8/graph/GenericSignaturePrinter.java
+++ b/src/main/java/com/android/tools/r8/graph/GenericSignaturePrinter.java
@@ -138,13 +138,15 @@
   }
 
   @Override
-  public ClassTypeSignature visitSimpleClass(ClassTypeSignature classTypeSignature) {
-    printFieldTypeSignature(classTypeSignature, true);
-    return classTypeSignature;
+  public ClassTypeSignature visitEnclosing(
+      ClassTypeSignature enclosingSignature, ClassTypeSignature enclosedSignature) {
+    printFieldTypeSignature(enclosingSignature, true);
+    return enclosingSignature;
   }
 
   @Override
-  public List<FieldTypeSignature> visitTypeArguments(List<FieldTypeSignature> typeArguments) {
+  public List<FieldTypeSignature> visitTypeArguments(
+      DexType type, List<FieldTypeSignature> typeArguments) {
     if (typeArguments.isEmpty()) {
       return typeArguments;
     }
@@ -185,7 +187,7 @@
       }
       // Visit enclosing before printing the type name to ensure we
       if (classTypeSignature.enclosingTypeSignature != null) {
-        visitSimpleClass(classTypeSignature.enclosingTypeSignature);
+        visitEnclosing(classTypeSignature.enclosingTypeSignature, classTypeSignature);
       }
       String renamedString = namingLens.lookupDescriptor(classTypeSignature.type).toString();
       if (classTypeSignature.enclosingTypeSignature == null) {
@@ -205,7 +207,7 @@
         }
         sb.append(".").append(innerClassName);
       }
-      visitTypeArguments(classTypeSignature.typeArguments);
+      visitTypeArguments(null, classTypeSignature.typeArguments);
       if (!printingOuter) {
         sb.append(";");
       }
diff --git a/src/main/java/com/android/tools/r8/graph/GenericSignatureTypeRewriter.java b/src/main/java/com/android/tools/r8/graph/GenericSignatureTypeRewriter.java
index 6df0d8b..956b076 100644
--- a/src/main/java/com/android/tools/r8/graph/GenericSignatureTypeRewriter.java
+++ b/src/main/java/com/android/tools/r8/graph/GenericSignatureTypeRewriter.java
@@ -4,7 +4,7 @@
 
 package com.android.tools.r8.graph;
 
-import static com.android.tools.r8.graph.GenericSignature.EMPTY_TYPE_ARGUMENTS;
+import static com.android.tools.r8.graph.GenericSignature.getEmptyTypeArguments;
 import static com.google.common.base.Predicates.alwaysFalse;
 
 import com.android.tools.r8.graph.GenericSignature.ClassSignature;
@@ -48,7 +48,7 @@
     this.wasPruned = wasPruned;
     this.lookupType = lookupType;
     this.context = context;
-    objectTypeSignature = new ClassTypeSignature(factory.objectType, EMPTY_TYPE_ARGUMENTS);
+    objectTypeSignature = new ClassTypeSignature(factory.objectType, getEmptyTypeArguments());
   }
 
   public ClassSignature rewrite(ClassSignature classSignature) {
@@ -138,9 +138,7 @@
     @Override
     public ClassTypeSignature visitSuperClass(ClassTypeSignature classTypeSignature) {
       ClassTypeSignature rewritten = classTypeSignature.visit(this);
-      return rewritten == null || rewritten.type() == context
-          ? new ClassTypeSignature(factory.objectType, EMPTY_TYPE_ARGUMENTS)
-          : rewritten;
+      return rewritten == null || rewritten.type() == context ? objectTypeSignature : rewritten;
     }
 
     @Override
@@ -216,12 +214,14 @@
     }
 
     @Override
-    public ClassTypeSignature visitSimpleClass(ClassTypeSignature classTypeSignature) {
-      return classTypeSignature.visit(this);
+    public ClassTypeSignature visitEnclosing(
+        ClassTypeSignature enclosingSignature, ClassTypeSignature enclosedSignature) {
+      return enclosingSignature.visit(this);
     }
 
     @Override
-    public List<FieldTypeSignature> visitTypeArguments(List<FieldTypeSignature> typeArguments) {
+    public List<FieldTypeSignature> visitTypeArguments(
+        DexType type, List<FieldTypeSignature> typeArguments) {
       if (typeArguments.isEmpty()) {
         return typeArguments;
       }
diff --git a/src/main/java/com/android/tools/r8/graph/GenericSignatureTypeVariableRemover.java b/src/main/java/com/android/tools/r8/graph/GenericSignatureTypeVariableRemover.java
index 23500e9..f2a359a 100644
--- a/src/main/java/com/android/tools/r8/graph/GenericSignatureTypeVariableRemover.java
+++ b/src/main/java/com/android/tools/r8/graph/GenericSignatureTypeVariableRemover.java
@@ -4,122 +4,299 @@
 
 package com.android.tools.r8.graph;
 
-import com.android.tools.r8.graph.GenericSignature.FieldTypeSignature;
+import static com.android.tools.r8.graph.GenericSignatureTypeVariableRemover.TypeParameterContext.empty;
+import static com.google.common.base.Predicates.alwaysFalse;
+
 import com.android.tools.r8.graph.GenericSignature.FormalTypeParameter;
+import com.android.tools.r8.graph.GenericSignature.MethodTypeSignature;
+import java.util.Collections;
 import java.util.HashMap;
+import java.util.HashSet;
+import java.util.IdentityHashMap;
 import java.util.List;
 import java.util.Map;
+import java.util.Set;
 import java.util.function.Predicate;
 
 public class GenericSignatureTypeVariableRemover {
 
-  private final AppView<?> appView;
-  private final Predicate<InnerClassAttribute> innerClassPruned;
-  private final Predicate<EnclosingMethodAttribute> enclosingClassOrMethodPruned;
+  private final DexType objectType;
+  private final Map<DexReference, TypeParameterSubstitutions> formalsInfo;
+  private final Map<DexReference, DexReference> enclosingInfo;
 
-  public GenericSignatureTypeVariableRemover(
-      AppView<?> appView,
-      Predicate<InnerClassAttribute> innerClassPruned,
-      Predicate<EnclosingMethodAttribute> enclosingClassOrMethodPruned) {
-    this.appView = appView;
-    this.innerClassPruned = innerClassPruned;
-    this.enclosingClassOrMethodPruned = enclosingClassOrMethodPruned;
+  private static class TypeParameterSubstitutions {
+
+    private final Map<String, DexType> parametersWithBounds;
+
+    private TypeParameterSubstitutions(Map<String, DexType> parametersWithBounds) {
+      this.parametersWithBounds = parametersWithBounds;
+    }
+
+    private static TypeParameterSubstitutions create(List<FormalTypeParameter> formals) {
+      Map<String, DexType> map = new IdentityHashMap<>();
+      formals.forEach(
+          formal -> {
+            DexType bound = null;
+            if (formal.getClassBound() != null
+                && formal.getClassBound().hasSignature()
+                && formal.getClassBound().isClassTypeSignature()) {
+              bound = formal.getClassBound().asClassTypeSignature().type;
+            } else if (!formal.getInterfaceBounds().isEmpty()
+                && formal.getInterfaceBounds().get(0).isClassTypeSignature()) {
+              bound = formal.getInterfaceBounds().get(0).asClassTypeSignature().type;
+            }
+            map.put(formal.getName(), bound);
+          });
+      return new TypeParameterSubstitutions(map);
+    }
   }
 
-  public void removeDeadGenericSignatureTypeVariables(DexProgramClass clazz) {
-    if (clazz.getClassSignature().hasNoSignature() || clazz.getClassSignature().isInvalid()) {
-      return;
+  static class TypeParameterContext {
+
+    private static final TypeParameterContext EMPTY =
+        new TypeParameterContext(Collections.emptyMap(), Collections.emptySet());
+
+    private final Map<String, DexType> prunedParametersWithBounds;
+    private final Set<String> liveParameters;
+
+    private TypeParameterContext(
+        Map<String, DexType> prunedParametersWithBounds, Set<String> liveParameters) {
+      this.prunedParametersWithBounds = prunedParametersWithBounds;
+      this.liveParameters = liveParameters;
     }
-    Map<String, DexType> substitutions = new HashMap<>();
-    getPrunedTypeParameters(clazz, substitutions, false);
-    if (substitutions.isEmpty()) {
-      return;
+
+    private TypeParameterContext combine(TypeParameterSubstitutions information, boolean dead) {
+      if (information == null) {
+        return this;
+      }
+      HashMap<String, DexType> newPruned = new HashMap<>(prunedParametersWithBounds);
+      HashSet<String> newLiveParameters = new HashSet<>(liveParameters);
+      information.parametersWithBounds.forEach(
+          (param, type) -> {
+            if (dead) {
+              newPruned.put(param, type);
+              newLiveParameters.remove(param);
+            } else {
+              newLiveParameters.add(param);
+              newPruned.remove(param);
+            }
+          });
+      return new TypeParameterContext(newPruned, newLiveParameters);
     }
-    GenericSignaturePartialTypeArgumentApplier genericSignatureTypeArgumentApplier =
-        GenericSignaturePartialTypeArgumentApplier.build(
-            appView.dexItemFactory().objectType, clazz.getClassSignature(), substitutions);
-    clazz.setClassSignature(
-        genericSignatureTypeArgumentApplier.visitClassSignature(clazz.getClassSignature()));
-    clazz
-        .methods()
-        .forEach(
-            method -> {
-              if (method.getGenericSignature().hasSignature()
-                  && method.getGenericSignature().isValid()
-                  && method.isVirtualMethod()) {
-                method.setGenericSignature(
-                    genericSignatureTypeArgumentApplier.visitMethodSignature(
-                        method.getGenericSignature()));
-              }
-            });
-    clazz
-        .instanceFields()
-        .forEach(
-            field -> {
-              if (field.getGenericSignature().hasSignature()
-                  && field.getGenericSignature().isValid()) {
-                field.setGenericSignature(
-                    genericSignatureTypeArgumentApplier.visitFieldTypeSignature(
-                        field.getGenericSignature()));
-              }
-            });
+
+    public static TypeParameterContext empty() {
+      return EMPTY;
+    }
   }
 
-  private void getPrunedTypeParameters(
-      DexClass clazz, Map<String, DexType> substitutions, boolean seenPruned) {
-    InnerClassAttribute innerClassAttribute = clazz.getInnerClassAttributeForThisClass();
-    if (innerClassAttribute != null
-        && innerClassAttribute.getOuter() != null
-        && (seenPruned || innerClassPruned.test(innerClassAttribute))) {
-      DexClass outerClass = appView.definitionFor(innerClassAttribute.getOuter());
-      if (outerClass != null && outerClass.getClassSignature().isValid()) {
-        updateMap(outerClass.getClassSignature().getFormalTypeParameters(), substitutions);
-        getPrunedTypeParameters(outerClass, substitutions, true);
-      }
-    }
-    if (clazz.getEnclosingMethodAttribute() != null
-        && (seenPruned || enclosingClassOrMethodPruned.test(clazz.getEnclosingMethodAttribute()))) {
-      DexClass outerClass =
-          appView.definitionFor(clazz.getEnclosingMethodAttribute().getEnclosingType());
-      if (outerClass == null) {
-        return;
-      }
-      if (clazz.getEnclosingMethodAttribute().getEnclosingMethod() != null) {
-        DexEncodedMethod enclosingMethod =
-            outerClass.lookupMethod(clazz.getEnclosingMethodAttribute().getEnclosingMethod());
-        if (enclosingMethod != null) {
-          updateMap(enclosingMethod.getGenericSignature().getFormalTypeParameters(), substitutions);
-          if (enclosingMethod.isStatic()) {
-            return;
+  private GenericSignatureTypeVariableRemover(
+      Map<DexReference, TypeParameterSubstitutions> formalsInfo,
+      Map<DexReference, DexReference> enclosingInfo,
+      DexType objectType) {
+    this.formalsInfo = formalsInfo;
+    this.enclosingInfo = enclosingInfo;
+    this.objectType = objectType;
+  }
+
+  public static GenericSignatureTypeVariableRemover create(
+      AppView<?> appView, List<DexProgramClass> programClasses) {
+    Map<DexReference, TypeParameterSubstitutions> formalsInfo = new IdentityHashMap<>();
+    Map<DexReference, DexReference> enclosingInfo = new IdentityHashMap<>();
+    programClasses.forEach(
+        clazz -> {
+          // Build up a map of type variables to bounds for every reference such that we can
+          // lookup the information even after we prune the generic signatures.
+          if (clazz.getClassSignature().isValid()) {
+            formalsInfo.put(
+                clazz.getReference(),
+                TypeParameterSubstitutions.create(clazz.classSignature.getFormalTypeParameters()));
+            clazz.forEachProgramMethod(
+                method -> {
+                  MethodTypeSignature methodSignature =
+                      method.getDefinition().getGenericSignature();
+                  if (methodSignature.isValid()) {
+                    formalsInfo.put(
+                        method.getReference(),
+                        TypeParameterSubstitutions.create(
+                            methodSignature.getFormalTypeParameters()));
+                  }
+                });
           }
+          // Build up an enclosing class context such that the enclosing class can be looked up
+          // even after inner class and enclosing method attribute attributes are removed.
+          InnerClassAttribute innerClassAttribute = clazz.getInnerClassAttributeForThisClass();
+          if (innerClassAttribute != null) {
+            enclosingInfo.put(clazz.getType(), innerClassAttribute.getOuter());
+          }
+          EnclosingMethodAttribute enclosingMethodAttribute = clazz.getEnclosingMethodAttribute();
+          if (enclosingMethodAttribute != null) {
+            enclosingInfo.put(
+                clazz.getType(),
+                enclosingMethodAttribute.getEnclosingMethod() != null
+                    ? enclosingMethodAttribute.getEnclosingMethod()
+                    : enclosingMethodAttribute.getEnclosingClass());
+          }
+        });
+    return new GenericSignatureTypeVariableRemover(
+        formalsInfo, enclosingInfo, appView.dexItemFactory().objectType);
+  }
+
+  private TypeParameterContext computeTypeParameterContext(
+      AppView<?> appView,
+      DexReference reference,
+      Predicate<DexType> wasPruned,
+      boolean seenPruned) {
+    if (reference == null) {
+      return empty();
+    }
+    DexType contextType = reference.getContextType();
+    // TODO(b/187035453): We should visit generic signatures in the enqueuer.
+    DexClass clazz =
+        appView.appInfo().definitionForWithoutExistenceAssert(reference.getContextType());
+    boolean prunedHere = seenPruned || clazz == null;
+    if (appView.hasLiveness()
+        && appView
+            .withLiveness()
+            .appInfo()
+            .getMissingClasses()
+            .contains(reference.getContextType())) {
+      prunedHere = seenPruned;
+    }
+    if (reference.isDexMethod()) {
+      TypeParameterSubstitutions typeParameterSubstitutions = formalsInfo.get(reference);
+      if (clazz != null) {
+        assert clazz.isProgramClass();
+        DexEncodedMethod method = clazz.lookupMethod(reference.asDexMethod());
+        if (method == null) {
+          prunedHere = true;
+        } else if (method.isStatic()) {
+          // Static methods define their own scope.
+          return empty().combine(typeParameterSubstitutions, seenPruned);
         }
       }
-      if (outerClass.getClassSignature().isValid()) {
-        updateMap(outerClass.getClassSignature().getFormalTypeParameters(), substitutions);
+      // Lookup the formals in the enclosing context.
+      return computeTypeParameterContext(
+              appView,
+              contextType,
+              wasPruned,
+              prunedHere
+                  || hasPrunedRelationship(
+                      appView, enclosingInfo.get(contextType), contextType, wasPruned))
+          // Add the formals for the class.
+          .combine(formalsInfo.get(contextType), prunedHere)
+          // Add the formals for the method.
+          .combine(formalsInfo.get(reference), prunedHere);
+    }
+    assert reference.isDexType();
+    return computeTypeParameterContext(
+            appView,
+            enclosingInfo.get(reference),
+            wasPruned,
+            prunedHere
+                || hasPrunedRelationship(
+                    appView, enclosingInfo.get(reference), contextType, wasPruned))
+        .combine(formalsInfo.get(reference), prunedHere);
+  }
+
+  private static boolean hasPrunedRelationship(
+      AppView<?> appView,
+      DexReference enclosingReference,
+      DexType enclosedClassType,
+      Predicate<DexType> wasPruned) {
+    assert enclosedClassType != null;
+    if (enclosingReference == null) {
+      // There is no relationship, so it does not really matter what we return since the
+      // algorithm will return the base case.
+      return true;
+    }
+    if (wasPruned.test(enclosingReference.getContextType()) || wasPruned.test(enclosedClassType)) {
+      return true;
+    }
+    DexClass enclosingClass = appView.definitionFor(enclosingReference.getContextType());
+    DexClass enclosedClass = appView.definitionFor(enclosedClassType);
+    if (enclosingClass == null || enclosedClass == null) {
+      return true;
+    }
+    if (enclosingReference.isDexMethod()) {
+      return enclosedClass.getEnclosingMethodAttribute() == null
+          || enclosedClass.getEnclosingMethodAttribute().getEnclosingMethod() != enclosingReference;
+    } else {
+      InnerClassAttribute innerClassAttribute = enclosedClass.getInnerClassAttributeForThisClass();
+      if (innerClassAttribute != null) {
+        return innerClassAttribute.getOuter() != enclosingReference;
       }
-      getPrunedTypeParameters(outerClass, substitutions, true);
+      return enclosedClass.getEnclosingMethodAttribute() == null
+          || enclosedClass.getEnclosingMethodAttribute().getEnclosingClass() != enclosingReference;
     }
   }
 
-  private void updateMap(
-      List<FormalTypeParameter> formalTypeParameters, Map<String, DexType> substitutions) {
-    // We are updating the map going from inner most to outer, thus the any overriding formal type
-    // parameters will be in the substitution map already.
-    formalTypeParameters.forEach(
-        parameter -> {
-          if (substitutions.containsKey(parameter.getName())) {
-            return;
-          }
-          // The null substitution will use the wildcard as argument, which is smaller than using
-          // Ljava/lang/Object;
-          DexType substitution = null;
-          FieldTypeSignature classBound = parameter.getClassBound();
-          if (classBound != null
-              && classBound.hasSignature()
-              && classBound.isClassTypeSignature()) {
-            substitution = classBound.asClassTypeSignature().type();
-          }
-          substitutions.put(parameter.getName(), substitution);
-        });
+  private static boolean hasGenericTypeVariables(
+      AppView<?> appView, DexType type, Predicate<DexType> wasPruned) {
+    if (wasPruned.test(type)) {
+      return false;
+    }
+    DexClass clazz = appView.definitionFor(type);
+    if (clazz == null || clazz.isNotProgramClass() || clazz.getClassSignature().isInvalid()) {
+      return true;
+    }
+    return !clazz.getClassSignature().getFormalTypeParameters().isEmpty();
+  }
+
+  public void removeDeadGenericSignatureTypeVariables(AppView<?> appView) {
+    Predicate<DexType> wasPruned =
+        appView.hasLiveness() ? appView.withLiveness().appInfo()::wasPruned : alwaysFalse();
+    GenericSignaturePartialTypeArgumentApplier baseArgumentApplier =
+        GenericSignaturePartialTypeArgumentApplier.build(
+            objectType,
+            (enclosing, enclosed) -> hasPrunedRelationship(appView, enclosing, enclosed, wasPruned),
+            type -> hasGenericTypeVariables(appView, type, wasPruned));
+    appView
+        .appInfo()
+        .classes()
+        .forEach(
+            clazz -> {
+              if (clazz.getClassSignature().isInvalid()) {
+                return;
+              }
+              TypeParameterContext computedClassFormals =
+                  computeTypeParameterContext(appView, clazz.getType(), wasPruned, false);
+              GenericSignaturePartialTypeArgumentApplier classArgumentApplier =
+                  baseArgumentApplier.addSubstitutionsAndVariables(
+                      computedClassFormals.prunedParametersWithBounds,
+                      computedClassFormals.liveParameters);
+              clazz.setClassSignature(
+                  classArgumentApplier.visitClassSignature(clazz.getClassSignature()));
+              clazz
+                  .methods()
+                  .forEach(
+                      method -> {
+                        MethodTypeSignature methodSignature = method.getGenericSignature();
+                        if (methodSignature.hasSignature()
+                            && method.getGenericSignature().isValid()) {
+                          if (method.isStatic()) {
+                            method.setGenericSignature(
+                                baseArgumentApplier
+                                    .buildForMethod(methodSignature.getFormalTypeParameters())
+                                    .visitMethodSignature(methodSignature));
+                          } else {
+                            method.setGenericSignature(
+                                classArgumentApplier
+                                    .buildForMethod(methodSignature.getFormalTypeParameters())
+                                    .visitMethodSignature(methodSignature));
+                          }
+                        }
+                      });
+              clazz
+                  .instanceFields()
+                  .forEach(
+                      field -> {
+                        if (field.getGenericSignature().hasSignature()
+                            && field.getGenericSignature().isValid()) {
+                          field.setGenericSignature(
+                              classArgumentApplier.visitFieldTypeSignature(
+                                  field.getGenericSignature()));
+                        }
+                      });
+            });
   }
 }
diff --git a/src/main/java/com/android/tools/r8/graph/GenericSignatureTypeVisitor.java b/src/main/java/com/android/tools/r8/graph/GenericSignatureTypeVisitor.java
index dff8d5d..c6d0e6a 100644
--- a/src/main/java/com/android/tools/r8/graph/GenericSignatureTypeVisitor.java
+++ b/src/main/java/com/android/tools/r8/graph/GenericSignatureTypeVisitor.java
@@ -116,8 +116,9 @@
   }
 
   @Override
-  public ClassTypeSignature visitSimpleClass(ClassTypeSignature classTypeSignature) {
-    return classTypeSignature.visit(this);
+  public ClassTypeSignature visitEnclosing(
+      ClassTypeSignature enclosingSignature, ClassTypeSignature enclosedSignature) {
+    return enclosingSignature.visit(this);
   }
 
   @Override
@@ -142,7 +143,8 @@
   }
 
   @Override
-  public List<FieldTypeSignature> visitTypeArguments(List<FieldTypeSignature> typeArguments) {
+  public List<FieldTypeSignature> visitTypeArguments(
+      DexType type, List<FieldTypeSignature> typeArguments) {
     typeArguments.forEach(this::visitFieldTypeSignature);
     return typeArguments;
   }
diff --git a/src/main/java/com/android/tools/r8/graph/GenericSignatureVisitor.java b/src/main/java/com/android/tools/r8/graph/GenericSignatureVisitor.java
index ba1d8f2..b321ad1 100644
--- a/src/main/java/com/android/tools/r8/graph/GenericSignatureVisitor.java
+++ b/src/main/java/com/android/tools/r8/graph/GenericSignatureVisitor.java
@@ -66,7 +66,8 @@
     throw new Unreachable("Implement if visited");
   }
 
-  default ClassTypeSignature visitSimpleClass(ClassTypeSignature classTypeSignature) {
+  default ClassTypeSignature visitEnclosing(
+      ClassTypeSignature enclosingSignature, ClassTypeSignature enclosedSignature) {
     throw new Unreachable("Implement if visited");
   }
 
@@ -82,7 +83,8 @@
     throw new Unreachable("Implement if visited");
   }
 
-  default List<FieldTypeSignature> visitTypeArguments(List<FieldTypeSignature> typeArguments) {
+  default List<FieldTypeSignature> visitTypeArguments(
+      DexType type, List<FieldTypeSignature> typeArguments) {
     throw new Unreachable("Implement if visited");
   }
 
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 3893e32..c44e4e7 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
@@ -8,7 +8,6 @@
 import com.android.tools.r8.graph.DexProgramClass;
 import com.android.tools.r8.graph.GenericSignatureTypeRewriter;
 import com.android.tools.r8.naming.NamingLens;
-import com.android.tools.r8.utils.InternalOptions;
 import com.android.tools.r8.utils.ThreadUtils;
 import java.util.concurrent.ExecutionException;
 import java.util.concurrent.ExecutorService;
@@ -18,12 +17,10 @@
 
   private final AppView<?> appView;
   private final NamingLens namingLens;
-  private final InternalOptions options;
 
   public GenericSignatureRewriter(AppView<?> appView, NamingLens namingLens) {
     this.appView = appView;
     this.namingLens = namingLens;
-    this.options = appView.options();
   }
 
   public void run(Iterable<? extends DexProgramClass> classes, ExecutorService executorService)
diff --git a/src/main/java/com/android/tools/r8/shaking/AnnotationRemover.java b/src/main/java/com/android/tools/r8/shaking/AnnotationRemover.java
index f17d49b..c5d7038 100644
--- a/src/main/java/com/android/tools/r8/shaking/AnnotationRemover.java
+++ b/src/main/java/com/android/tools/r8/shaking/AnnotationRemover.java
@@ -190,14 +190,17 @@
 
   public void run() {
     for (DexProgramClass clazz : appView.appInfo().classes()) {
-      stripAttributes(clazz);
+      boolean enclosingMethodPinned = enclosingMethodPinned(appView, clazz);
+      stripAttributes(clazz, enclosingMethodPinned);
       clazz.setAnnotations(
           clazz.annotations().rewrite(annotation -> rewriteAnnotation(clazz, annotation)));
       // Kotlin properties are split over fields and methods. Check if any is pinned before pruning
       // the information.
       Set<KotlinPropertyInfo> pinnedKotlinProperties = Sets.newIdentityHashSet();
-      clazz.forEachMethod(method -> processMethod(method, clazz, pinnedKotlinProperties));
-      clazz.forEachField(field -> processField(field, clazz, pinnedKotlinProperties));
+      clazz.forEachMethod(
+          method -> processMethod(method, clazz, pinnedKotlinProperties, enclosingMethodPinned));
+      clazz.forEachField(
+          field -> processField(field, clazz, pinnedKotlinProperties, enclosingMethodPinned));
       clazz.forEachProgramMember(
           member -> {
             KotlinMemberLevelInfo kotlinInfo = member.getKotlinInfo();
@@ -212,13 +215,14 @@
   private void processMethod(
       DexEncodedMethod method,
       DexProgramClass clazz,
-      Set<KotlinPropertyInfo> pinnedKotlinProperties) {
+      Set<KotlinPropertyInfo> pinnedKotlinProperties,
+      boolean enclosingMethodPinned) {
     method.setAnnotations(
         method.annotations().rewrite(annotation -> rewriteAnnotation(method, annotation)));
     method.parameterAnnotationsList =
         method.parameterAnnotationsList.keepIf(this::filterParameterAnnotations);
     KeepMethodInfo methodInfo = appView.getKeepInfo().getMethodInfo(method, clazz);
-    if (methodInfo.isAllowSignatureAttributeRemovalAllowed(options)) {
+    if (!enclosingMethodPinned && methodInfo.isAllowSignatureAttributeRemovalAllowed(options)) {
       method.clearGenericSignature();
     }
     if (!methodInfo.isPinned() && method.getKotlinInfo().isFunction()) {
@@ -232,11 +236,12 @@
   private void processField(
       DexEncodedField field,
       DexProgramClass clazz,
-      Set<KotlinPropertyInfo> pinnedKotlinProperties) {
+      Set<KotlinPropertyInfo> pinnedKotlinProperties,
+      boolean enclosingMethodPinned) {
     field.setAnnotations(
         field.annotations().rewrite(annotation -> rewriteAnnotation(field, annotation)));
     KeepFieldInfo fieldInfo = appView.getKeepInfo().getFieldInfo(field, clazz);
-    if (fieldInfo.isAllowSignatureAttributeRemovalAllowed(options)) {
+    if (!enclosingMethodPinned && fieldInfo.isAllowSignatureAttributeRemovalAllowed(options)) {
       field.clearGenericSignature();
     }
     if (fieldInfo.isPinned() && field.getKotlinInfo().isProperty()) {
@@ -302,7 +307,7 @@
     return false;
   }
 
-  private void stripAttributes(DexProgramClass clazz) {
+  private void stripAttributes(DexProgramClass clazz, boolean enclosingMethodPinned) {
     // If [clazz] is mentioned by a keep rule, it could be used for reflection, and we therefore
     // need to keep the enclosing method and inner classes attributes, if requested. In Proguard
     // compatibility mode we keep these attributes independent of whether the given class is kept.
@@ -311,7 +316,7 @@
     // is kept.
     boolean keptAnyway =
         appView.appInfo().isPinned(clazz.type)
-            || enclosingMethodPinned(appView, clazz)
+            || enclosingMethodPinned
             || appView.options().forceProguardCompatibility;
     boolean keepForThisInnerClass = false;
     boolean keepForThisEnclosingClass = false;
@@ -356,10 +361,12 @@
       clazz.clearEnclosingMethodAttribute();
       clazz.clearInnerClasses();
     }
-    if (appView
-        .getKeepInfo()
-        .getClassInfo(clazz)
-        .isAllowSignatureAttributeRemovalAllowed(options)) {
+    if (!enclosingMethodPinned
+        && clazz.getClassSignature().isValid()
+        && appView
+            .getKeepInfo()
+            .getClassInfo(clazz)
+            .isAllowSignatureAttributeRemovalAllowed(options)) {
       clazz.clearClassSignature();
     }
   }
diff --git a/src/main/java/com/android/tools/r8/shaking/KeepInfo.java b/src/main/java/com/android/tools/r8/shaking/KeepInfo.java
index 36f5cdd..1040ae2 100644
--- a/src/main/java/com/android/tools/r8/shaking/KeepInfo.java
+++ b/src/main/java/com/android/tools/r8/shaking/KeepInfo.java
@@ -84,9 +84,10 @@
 
   public boolean isAllowSignatureAttributeRemovalAllowed(
       GlobalKeepInfoConfiguration configuration) {
-    // TODO(b/172999267): For full mode we should be able to remove for not pinned items if
-    //  java reflect will not throw up.
-    return !configuration.isKeepAttributesSignatureEnabled();
+    if (!configuration.isKeepAttributesSignatureEnabled()) {
+      return true;
+    }
+    return !(configuration.isForceProguardCompatibilityEnabled() || isPinned());
   }
 
   public abstract boolean isTop();
diff --git a/src/main/java/com/android/tools/r8/shaking/TreePruner.java b/src/main/java/com/android/tools/r8/shaking/TreePruner.java
index 577ecc2..ef2f149 100644
--- a/src/main/java/com/android/tools/r8/shaking/TreePruner.java
+++ b/src/main/java/com/android/tools/r8/shaking/TreePruner.java
@@ -15,7 +15,6 @@
 import com.android.tools.r8.graph.DexTypeList;
 import com.android.tools.r8.graph.DirectMappedDexApplication;
 import com.android.tools.r8.graph.EnclosingMethodAttribute;
-import com.android.tools.r8.graph.GenericSignatureTypeVariableRemover;
 import com.android.tools.r8.graph.InnerClassAttribute;
 import com.android.tools.r8.graph.NestMemberClassAttribute;
 import com.android.tools.r8.ir.optimize.info.MutableFieldOptimizationInfo;
@@ -46,7 +45,6 @@
   private final UnusedItemsPrinter unusedItemsPrinter;
   private final Set<DexType> prunedTypes = Sets.newIdentityHashSet();
   private final Set<DexMethod> methodsToKeepForConfigurationDebugging = Sets.newIdentityHashSet();
-  private final GenericSignatureTypeVariableRemover typeVariableRemover;
 
   public TreePruner(AppView<AppInfoWithLiveness> appView) {
     this(appView, DefaultTreePrunerConfiguration.getInstance());
@@ -63,11 +61,6 @@
                     ExceptionUtils.withConsumeResourceHandler(
                         options.reporter, options.usageInformationConsumer, s))
             : UnusedItemsPrinter.DONT_PRINT;
-    this.typeVariableRemover =
-        new GenericSignatureTypeVariableRemover(
-            appView,
-            this::isAttributeReferencingMissingOrPrunedType,
-            this::isAttributeReferencingPrunedItem);
   }
 
   public DirectMappedDexApplication run(ExecutorService executorService) throws ExecutionException {
@@ -200,7 +193,6 @@
     if (reachableStaticFields != null) {
       clazz.setStaticFields(reachableStaticFields);
     }
-    typeVariableRemover.removeDeadGenericSignatureTypeVariables(clazz);
     clazz.removeInnerClasses(this::isAttributeReferencingMissingOrPrunedType);
     clazz.removeEnclosingMethodAttribute(this::isAttributeReferencingPrunedItem);
     rewriteNestAttributes(clazz);
diff --git a/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/gson/gson.cfg b/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/gson/gson.cfg
index e7560e1f..2b36e8a 100644
--- a/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/gson/gson.cfg
+++ b/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/gson/gson.cfg
@@ -30,6 +30,7 @@
 
 # Prevent R8 from removing the generic signature of TypeToken
 -keep,allowobfuscation class * extends com.google.gson.reflect.TypeToken
+-keep,allowobfuscation class com.google.gson.reflect.TypeToken
 
 # Prevent R8 from leaving Data object members always null
 -keepclassmembers,allowobfuscation class * {
diff --git a/src/test/java/com/android/tools/r8/graph/genericsignature/GenericSignatureKeepAttributesTest.java b/src/test/java/com/android/tools/r8/graph/genericsignature/GenericSignatureKeepAttributesTest.java
new file mode 100644
index 0000000..21cc62c
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/graph/genericsignature/GenericSignatureKeepAttributesTest.java
@@ -0,0 +1,151 @@
+// Copyright (c) 2021, 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.graph.genericsignature;
+
+import static com.android.tools.r8.utils.codeinspector.Matchers.isPresent;
+import static org.hamcrest.MatcherAssert.assertThat;
+import static org.junit.Assert.assertEquals;
+
+import com.android.tools.r8.TestBase;
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.graph.genericsignature.GenericSignatureKeepAttributesTest.Outer.Middle;
+import com.android.tools.r8.graph.genericsignature.GenericSignatureKeepAttributesTest.Outer.Middle.Inner;
+import com.android.tools.r8.utils.BooleanUtils;
+import com.android.tools.r8.utils.codeinspector.ClassSubject;
+import com.android.tools.r8.utils.codeinspector.CodeInspector;
+import com.android.tools.r8.utils.codeinspector.MethodSubject;
+import java.util.List;
+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 GenericSignatureKeepAttributesTest extends TestBase {
+
+  private final TestParameters parameters;
+  private final boolean isCompat;
+
+  private final String[] EXPECTED_JVM =
+      new String[] {
+        "Outer.Middle.Inner::test",
+        "public class com.android.tools.r8.graph.genericsignature"
+            + ".GenericSignatureKeepAttributesTest$Outer$Middle$Inner<I>"
+      };
+
+  private final String[] EXPECTED_DEX =
+      new String[] {
+        "Outer.Middle.Inner::test",
+        "class com.android.tools.r8.graph.genericsignature"
+            + ".GenericSignatureKeepAttributesTest$Outer$Middle$Inner"
+      };
+
+  @Parameters(name = "{0}, isCompat: {1}")
+  public static List<Object[]> data() {
+    return buildParameters(
+        getTestParameters().withAllRuntimesAndApiLevels().build(), BooleanUtils.values());
+  }
+
+  public GenericSignatureKeepAttributesTest(TestParameters parameters, boolean isCompat) {
+    this.parameters = parameters;
+    this.isCompat = isCompat;
+  }
+
+  @Test
+  public void testRuntime() throws Exception {
+    testForRuntime(parameters)
+        .addProgramClasses(Supplier.class, Predicate.class, Outer.class, Middle.class, Main.class)
+        .addProgramClassFileData(getClassFileData())
+        .run(parameters.getRuntime(), Main.class)
+        .assertSuccessWithOutputLines(parameters.isCfRuntime() ? EXPECTED_JVM : EXPECTED_DEX);
+  }
+
+  @Test
+  public void testR8() throws Exception {
+    (isCompat ? testForR8Compat(parameters.getBackend()) : testForR8(parameters.getBackend()))
+        .addProgramClasses(Supplier.class, Predicate.class, Outer.class, Middle.class, Main.class)
+        .addProgramClassFileData(getClassFileData())
+        .setMinApi(parameters.getApiLevel())
+        .addKeepAttributeSignature()
+        .addKeepAttributeInnerClassesAndEnclosingMethod()
+        .addKeepMainRule(Main.class)
+        .addKeepClassAndMembersRules(Outer.Middle.Inner.class, Supplier.class, Predicate.class)
+        .run(parameters.getRuntime(), Main.class)
+        .assertSuccessWithOutputLines(parameters.isCfRuntime() ? EXPECTED_JVM : EXPECTED_DEX)
+        .inspect(this::inspectSignatures);
+  }
+
+  private byte[] getClassFileData() throws Exception {
+    return transformer(Inner.class)
+        .transformMethodInsnInMethod(
+            "test",
+            ((opcode, owner, name, descriptor, isInterface, continuation) -> {
+              if (parameters.isCfRuntime() && name.equals("toString")) {
+                name = "toGenericString";
+              }
+              continuation.visitMethodInsn(opcode, owner, name, descriptor, isInterface);
+            }))
+        .transform();
+  }
+
+  private void inspectSignatures(CodeInspector inspector) {
+    ClassSubject outerClass = inspector.clazz(Outer.class);
+    assertThat(outerClass, isPresent());
+    assertEquals(
+        isCompat ? "<O::L" + binaryName(Supplier.class) + "<*>;>Ljava/lang/Object;" : null,
+        outerClass.getFinalSignatureAttribute());
+
+    ClassSubject middleClass = inspector.clazz(Middle.class);
+    assertThat(middleClass, isPresent());
+    assertEquals(
+        isCompat ? "<M::L" + binaryName(Predicate.class) + "<TO;>;>Ljava/lang/Object;" : null,
+        middleClass.getFinalSignatureAttribute());
+
+    ClassSubject innerClass = inspector.clazz(Inner.class);
+    assertThat(innerClass, isPresent());
+    MethodSubject testMethod = innerClass.uniqueMethodWithName("test");
+    assertThat(testMethod, isPresent());
+    // TODO(b/184927364): TO; should be replaced with Supplier
+    assertEquals("(TO;TM;)TI;", testMethod.getFinalSignatureAttribute());
+  }
+
+  public interface Supplier<T> {}
+
+  public interface Predicate<T> {}
+
+  public static class Outer<O extends Supplier<?>> {
+
+    public class Middle<M extends Predicate<O>> {
+
+      public class Inner<I> {
+
+        public I test(O o, M m) {
+          System.out.println("Outer.Middle.Inner::test");
+          System.out.println(this.getClass().toString()); // .toGenericString() for JVMs
+          return null;
+        }
+      }
+
+      private Outer<O>.Middle<M>.Inner<Object> createInner() {
+        return new Inner<>();
+      }
+    }
+
+    private Outer<O>.Middle<?> createMiddle() {
+      return new Outer<O>.Middle<>();
+    }
+
+    public static Outer<?>.Middle<?>.Inner<Object> create() {
+      return new Outer<>().createMiddle().createInner();
+    }
+  }
+
+  public static class Main {
+
+    public static void main(String[] args) {
+      Outer.create().test(null, null);
+    }
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/graph/genericsignature/GenericSignatureKeepReferencesPruneTest.java b/src/test/java/com/android/tools/r8/graph/genericsignature/GenericSignatureKeepReferencesPruneTest.java
index 1c3ddbb..177ea63 100644
--- a/src/test/java/com/android/tools/r8/graph/genericsignature/GenericSignatureKeepReferencesPruneTest.java
+++ b/src/test/java/com/android/tools/r8/graph/genericsignature/GenericSignatureKeepReferencesPruneTest.java
@@ -43,6 +43,14 @@
         "Hello world"
       };
 
+  private final String[] EXPECTED_FULL_MODE =
+      new String[] {
+        "class " + Foo.class.getTypeName(),
+        "interface " + I.class.getTypeName(),
+        "interface " + I.class.getTypeName(),
+        "Hello world"
+      };
+
   private final TestParameters parameters;
   private final boolean isCompat;
 
@@ -79,34 +87,37 @@
         .enableInliningAnnotations()
         .enableNeverClassInliningAnnotations()
         .noMinification()
+        .compile()
+        .inspect(this::inspectSignatures)
         .run(parameters.getRuntime(), Main.class)
-        // TODO(b/184927364): Should have different output due to pruning the inner class.
-        .assertSuccessWithOutputLines(EXPECTED)
-        .inspect(this::inspectSignatures);
+        .assertSuccessWithOutputLines(isCompat ? EXPECTED : EXPECTED_FULL_MODE);
   }
 
   private void inspectSignatures(CodeInspector inspector) {
     ClassSubject fooClass = inspector.clazz(Foo.class);
     assertThat(fooClass, isPresent());
-    // TODO(b/184927364): Fullmode should not keep the interface bound.
     assertEquals(
-        "<T::Ljava/lang/Comparable<TT;>;>Ljava/lang/Object;",
+        isCompat ? "<T::Ljava/lang/Comparable<TT;>;>Ljava/lang/Object;" : null,
         fooClass.getFinalSignatureAttribute());
     ClassSubject iClass = inspector.clazz(I.class);
     assertThat(iClass, isPresent());
-    // TODO(b/184927364): Fullmode should not keep the interface and class bound.
     assertEquals(
-        "<T::Ljava/lang/Comparable<TT;>;R:L" + binaryName(Foo.class) + "<TT;>;>Ljava/lang/Object;",
+        isCompat
+            ? "<T::Ljava/lang/Comparable<TT;>;R:L"
+                + binaryName(Foo.class)
+                + "<TT;>;>Ljava/lang/Object;"
+            : null,
         iClass.getFinalSignatureAttribute());
     ClassSubject fooInnerClass = inspector.clazz(Main.class.getTypeName() + "$1");
     assertThat(fooInnerClass, isPresent());
-    // TODO(b/184927364): Fullmode should completely remove this signature
     assertEquals(
-        "Ljava/lang/Object;L"
-            + binaryName(I.class)
-            + "<Ljava/lang/String;L"
-            + binaryName(Foo.class)
-            + "<Ljava/lang/String;>;>;",
+        isCompat
+            ? "Ljava/lang/Object;L"
+                + binaryName(I.class)
+                + "<Ljava/lang/String;L"
+                + binaryName(Foo.class)
+                + "<Ljava/lang/String;>;>;"
+            : null,
         fooInnerClass.getFinalSignatureAttribute());
   }
 }
diff --git a/src/test/java/com/android/tools/r8/graph/genericsignature/GenericSignaturePartialTypeArgumentApplierTest.java b/src/test/java/com/android/tools/r8/graph/genericsignature/GenericSignaturePartialTypeArgumentApplierTest.java
index f3cee7b..c1d8c5f 100644
--- a/src/test/java/com/android/tools/r8/graph/genericsignature/GenericSignaturePartialTypeArgumentApplierTest.java
+++ b/src/test/java/com/android/tools/r8/graph/genericsignature/GenericSignaturePartialTypeArgumentApplierTest.java
@@ -4,6 +4,8 @@
 
 package com.android.tools.r8.graph.genericsignature;
 
+import static com.google.common.base.Predicates.alwaysFalse;
+import static com.google.common.base.Predicates.alwaysTrue;
 import static org.junit.Assert.assertEquals;
 
 import com.android.tools.r8.TestBase;
@@ -14,12 +16,16 @@
 import com.android.tools.r8.graph.DexItemFactory;
 import com.android.tools.r8.graph.DexType;
 import com.android.tools.r8.graph.GenericSignature;
-import com.android.tools.r8.graph.GenericSignature.ClassSignature;
 import com.android.tools.r8.graph.GenericSignature.MethodTypeSignature;
 import com.android.tools.r8.graph.GenericSignaturePartialTypeArgumentApplier;
 import com.android.tools.r8.origin.Origin;
+import com.android.tools.r8.utils.BiPredicateUtils;
 import com.google.common.collect.ImmutableMap;
+import java.util.Collections;
 import java.util.Map;
+import java.util.Set;
+import java.util.function.BiPredicate;
+import java.util.function.Predicate;
 import org.junit.Test;
 import org.junit.runner.RunWith;
 import org.junit.runners.Parameterized;
@@ -45,6 +51,9 @@
   public void testVariablesInOuterPosition() {
     runTest(
             ImmutableMap.of("T", objectType, "R", objectType),
+            Collections.emptySet(),
+            BiPredicateUtils.alwaysFalse(),
+            alwaysTrue(),
             "(TT;)TR;",
             "(Ljava/lang/Object;)Ljava/lang/Object;")
         .assertNoMessages();
@@ -54,18 +63,50 @@
   public void testVariablesInInnerPosition() {
     runTest(
             ImmutableMap.of("T", objectType, "R", objectType),
+            Collections.emptySet(),
+            BiPredicateUtils.alwaysFalse(),
+            alwaysTrue(),
             "(LList<TT;>;)LList<TR;>;",
             "(LList<*>;)LList<*>;")
         .assertNoMessages();
   }
 
+  @Test
+  public void testRemovingPrunedLink() {
+    runTest(
+            Collections.emptyMap(),
+            Collections.emptySet(),
+            BiPredicateUtils.alwaysTrue(),
+            alwaysTrue(),
+            "(LFoo<Ljava/lang/String;>.Bar<Ljava/lang/Integer;>;)"
+                + "LFoo<Ljava/lang/String;>.Bar<Ljava/lang/Integer;>;",
+            "(LFoo$Bar<Ljava/lang/Integer;>;)LFoo$Bar<Ljava/lang/Integer;>;")
+        .assertNoMessages();
+  }
+
+  @Test
+  public void testRemovedGenericArguments() {
+    runTest(
+            Collections.emptyMap(),
+            Collections.emptySet(),
+            BiPredicateUtils.alwaysTrue(),
+            alwaysFalse(),
+            "(LFoo<Ljava/lang/String;>;)LFoo<Ljava/lang/String;>.Bar<Ljava/lang/Integer;>;",
+            "(LFoo;)LFoo$Bar;")
+        .assertNoMessages();
+  }
+
   private TestDiagnosticMessages runTest(
       Map<String, DexType> substitutions,
+      Set<String> liveVariables,
+      BiPredicate<DexType, DexType> removedLink,
+      Predicate<DexType> hasFormalTypeParameters,
       String initialSignature,
       String expectedRewrittenSignature) {
     GenericSignaturePartialTypeArgumentApplier argumentApplier =
         GenericSignaturePartialTypeArgumentApplier.build(
-            objectType, ClassSignature.noSignature(), substitutions);
+                objectType, removedLink, hasFormalTypeParameters)
+            .addSubstitutionsAndVariables(substitutions, liveVariables);
     TestDiagnosticMessagesImpl diagnosticsHandler = new TestDiagnosticMessagesImpl();
     MethodTypeSignature methodTypeSignature =
         argumentApplier.visitMethodSignature(
diff --git a/src/test/java/com/android/tools/r8/graph/genericsignature/GenericSignatureReflectiveInnerTest.java b/src/test/java/com/android/tools/r8/graph/genericsignature/GenericSignatureReflectiveInnerTest.java
index 0908f05..e34a36c 100644
--- a/src/test/java/com/android/tools/r8/graph/genericsignature/GenericSignatureReflectiveInnerTest.java
+++ b/src/test/java/com/android/tools/r8/graph/genericsignature/GenericSignatureReflectiveInnerTest.java
@@ -5,7 +5,6 @@
 package com.android.tools.r8.graph.genericsignature;
 
 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.TestBase;
@@ -48,12 +47,13 @@
         .addKeepMainRule(Main.class)
         .addKeepAttributeInnerClassesAndEnclosingMethod()
         .addKeepAttributeSignature()
+        .addKeepClassRules(Foo.Bar.class)
         .setMinApi(parameters.getApiLevel())
         .run(parameters.getRuntime(), Main.class)
         .assertSuccessWithOutputLines(EXPECTED)
         .inspect(
             inspector -> {
-              assertThat(inspector.clazz(Foo.class), not(isPresent()));
+              assertThat(inspector.clazz(Foo.class), isPresent());
               assertThat(inspector.clazz(Foo.Bar.class), isPresent());
             });
   }
diff --git a/src/test/java/com/android/tools/r8/naming/MinifierClassSignatureTest.java b/src/test/java/com/android/tools/r8/naming/MinifierClassSignatureTest.java
index e3e965c..dc3acee 100644
--- a/src/test/java/com/android/tools/r8/naming/MinifierClassSignatureTest.java
+++ b/src/test/java/com/android/tools/r8/naming/MinifierClassSignatureTest.java
@@ -69,6 +69,9 @@
   String extendsInnerSignature = "LOuter<TT;>.Inner;";
   String extendsInnerInnerSignature = "LOuter<TT;>.Inner.InnerInner;";
 
+  String extendsInnerSignatureInvalidOuter = "LOuter.Inner;";
+  String extendsInnerInnerSignatureInvalidOuter = "LOuter.Inner.InnerInner;";
+
   private final TestParameters parameters;
 
   @Parameters(name = "{0}")
@@ -80,13 +83,12 @@
     this.parameters = parameters;
   }
 
-  private byte[] dumpSimple(String classSignature) throws Exception {
+  private byte[] dumpSimple(String classSignature) {
 
     ClassWriter cw = new ClassWriter(0);
     MethodVisitor mv;
 
-    String signature = classSignature;
-    cw.visit(V1_8, ACC_SUPER, "Simple", signature, "java/lang/Object", null);
+    cw.visit(V1_8, ACC_SUPER, "Simple", classSignature, "java/lang/Object", null);
 
     {
       mv = cw.visitMethod(0, "<init>", "()V", null, null);
@@ -102,7 +104,7 @@
     return cw.toByteArray();
   }
 
-  private byte[] dumpBase(String classSignature) throws Exception {
+  private byte[] dumpBase(String classSignature) {
 
     final String javacClassSignature = baseSignature;
     ClassWriter cw = new ClassWriter(0);
@@ -190,7 +192,7 @@
     return cw.toByteArray();
   }
 
-  private byte[] dumpExtendsInner(String classSignature) throws Exception {
+  private byte[] dumpExtendsInner(String classSignature) {
 
     final String javacClassSignature = extendsInnerSignature;
     ClassWriter cw = new ClassWriter(0);
@@ -226,7 +228,7 @@
     return cw.toByteArray();
   }
 
-  private byte[] dumpInnerInner(String classSignature) throws Exception {
+  private byte[] dumpInnerInner(String classSignature) {
 
     final String javacClassSignature = null;
     ClassWriter cw = new ClassWriter(0);
@@ -261,7 +263,7 @@
     return cw.toByteArray();
   }
 
-  private byte[] dumpExtendsInnerInner(String classSignature) throws Exception {
+  private byte[] dumpExtendsInnerInner(String classSignature) {
 
     final String javacClassSignature = extendsInnerInnerSignature;
     ClassWriter cw = new ClassWriter(0);
@@ -304,7 +306,8 @@
   public void runTest(
       ImmutableMap<String, String> signatures,
       Consumer<TestDiagnosticMessages> diagnostics,
-      ThrowingConsumer<CodeInspector, Exception> inspect)
+      ThrowingConsumer<CodeInspector, Exception> inspect,
+      boolean noOuterFormals)
       throws Exception {
     R8TestCompileResult compileResult =
         testForR8(parameters.getBackend())
@@ -355,14 +358,16 @@
       assertNull(inspector.clazz("Outer$Inner").getOriginalSignatureAttribute());
     }
     if (!signatures.containsKey("Outer$ExtendsInner")) {
-      assertEquals(extendsInnerSignature,
+      assertEquals(
+          noOuterFormals ? extendsInnerSignatureInvalidOuter : extendsInnerSignature,
           inspector.clazz("Outer$ExtendsInner").getOriginalSignatureAttribute());
     }
     if (!signatures.containsKey("Outer$Inner$InnerInner")) {
       assertNull(inspector.clazz("Outer$Inner$InnerInner").getOriginalSignatureAttribute());
     }
     if (!signatures.containsKey("Outer$Inner$ExtendsInnerInner")) {
-      assertEquals(extendsInnerInnerSignature,
+      assertEquals(
+          noOuterFormals ? extendsInnerInnerSignatureInvalidOuter : extendsInnerInnerSignature,
           inspector.clazz("Outer$Inner$ExtendsInnerInner").getOriginalSignatureAttribute());
     }
 
@@ -374,10 +379,11 @@
       String name,
       String signature,
       Consumer<TestDiagnosticMessages> diagnostics,
-      ThrowingConsumer<CodeInspector, Exception> inspector)
+      ThrowingConsumer<CodeInspector, Exception> inspector,
+      boolean noOuterFormals)
       throws Exception {
     ImmutableMap<String, String> signatures = ImmutableMap.of(name, signature);
-    runTest(signatures, diagnostics, inspector);
+    runTest(signatures, diagnostics, inspector, noOuterFormals);
   }
 
   private void noInspection(CodeInspector inspector) {
@@ -391,7 +397,7 @@
   @Test
   public void originalJavacSignatures() throws Exception {
     // Test using the signatures generated by javac.
-    runTest(ImmutableMap.of(), TestDiagnosticMessages::assertNoWarnings, this::noInspection);
+    runTest(ImmutableMap.of(), TestDiagnosticMessages::assertNoWarnings, this::noInspection, false);
   }
 
   @Test
@@ -404,7 +410,8 @@
           ClassSubject outer = inspector.clazz("Outer");
           assertNull(outer.getFinalSignatureAttribute());
           assertNull(outer.getOriginalSignatureAttribute());
-        });
+        },
+        true);
   }
 
   @Test
@@ -425,7 +432,8 @@
               "<T:" + simple.getFinalDescriptor() + ">" + baseDescriptorWithoutSemicolon + "<TT;>;";
           assertEquals(minifiedSignature, outer.getFinalSignatureAttribute());
           assertEquals(signature, outer.getOriginalSignatureAttribute());
-        });
+        },
+        false);
   }
 
   @Test
@@ -447,7 +455,8 @@
           String minifiedSignature = outerDescriptorWithoutSemicolon + "<TT;>." + innerLastPart;
           assertEquals(minifiedSignature, extendsInner.getFinalSignatureAttribute());
           assertEquals(signature, extendsInner.getOriginalSignatureAttribute());
-        });
+        },
+        false);
   }
 
   @Test
@@ -461,7 +470,8 @@
           assertThat(inspector.clazz("NotFound"), not(isPresent()));
           ClassSubject outer = inspector.clazz("Outer");
           assertEquals(signature, outer.getOriginalSignatureAttribute());
-        });
+        },
+        false);
   }
 
   @Test
@@ -474,13 +484,15 @@
         inspector -> {
           assertThat(inspector.clazz("NotFound"), not(isPresent()));
           ClassSubject outer = inspector.clazz("Outer$ExtendsInner");
-          assertEquals(signature, outer.getOriginalSignatureAttribute());
-        });
+          // TODO(b/1867459990): What to do here.
+          assertEquals("LOuter$NotFound;", outer.getOriginalSignatureAttribute());
+        },
+        false);
   }
 
   @Test
   public void classSignatureExtendsInner_outerAndInnerClassNotFound() throws Exception {
-    String signature = "LNotFound<TT;>.AlsoNotFound;";
+    String signature = "LNotFound$AlsoNotFound;";
     testSingleClass(
         "Outer$ExtendsInner",
         signature,
@@ -489,7 +501,8 @@
           assertThat(inspector.clazz("NotFound"), not(isPresent()));
           ClassSubject outer = inspector.clazz("Outer$ExtendsInner");
           assertEquals(signature, outer.getOriginalSignatureAttribute());
-        });
+        },
+        false);
   }
 
   @Test
@@ -502,8 +515,10 @@
         inspector -> {
           assertThat(inspector.clazz("NotFound"), not(isPresent()));
           ClassSubject outer = inspector.clazz("Outer$ExtendsInner");
-          assertEquals(signature, outer.getOriginalSignatureAttribute());
-        });
+          // TODO(b/1867459990): What to do here.
+          assertEquals("LOuter$Inner$NotFound;", outer.getOriginalSignatureAttribute());
+        },
+        false);
   }
 
   @Test
@@ -516,8 +531,10 @@
         inspector -> {
           assertThat(inspector.clazz("NotFound"), not(isPresent()));
           ClassSubject outer = inspector.clazz("Outer$ExtendsInner");
-          assertEquals(signature, outer.getOriginalSignatureAttribute());
-        });
+          // TODO(b/1867459990): What to do here.
+          assertEquals("LOuter$NotFound$AlsoNotFound;", outer.getOriginalSignatureAttribute());
+        },
+        false);
   }
 
   @Test
@@ -533,7 +550,8 @@
                   diagnosticMessage(containsString("Expected L at position 1")),
                   diagnosticOrigin(Origin.unknown())));
         },
-        inspector -> noSignatureAttribute(inspector.clazz("Outer")));
+        inspector -> noSignatureAttribute(inspector.clazz("Outer")),
+        true);
   }
 
   @Test
@@ -549,7 +567,8 @@
                   diagnosticMessage(containsString("Unexpected end of signature at position 3")),
                   diagnosticOrigin(Origin.unknown())));
         },
-        inspector -> noSignatureAttribute(inspector.clazz("Outer")));
+        inspector -> noSignatureAttribute(inspector.clazz("Outer")),
+        true);
   }
 
   @Test
@@ -566,7 +585,8 @@
                   diagnosticMessage(containsString("Expected L at position 1")),
                   diagnosticOrigin(Origin.unknown())));
         },
-        inspector -> noSignatureAttribute(inspector.clazz("Outer$ExtendsInner")));
+        inspector -> noSignatureAttribute(inspector.clazz("Outer$ExtendsInner")),
+        false);
   }
 
   @Test
@@ -584,7 +604,8 @@
                   diagnosticMessage(containsString("Expected L at position 1")),
                   diagnosticOrigin(Origin.unknown())));
         },
-        inspector -> noSignatureAttribute(inspector.clazz("Outer$Inner$ExtendsInnerInner")));
+        inspector -> noSignatureAttribute(inspector.clazz("Outer$Inner$ExtendsInnerInner")),
+        false);
   }
 
   @Test
@@ -601,7 +622,8 @@
           noSignatureAttribute(inspector.clazz("Outer"));
           noSignatureAttribute(inspector.clazz("Outer$ExtendsInner"));
           noSignatureAttribute(inspector.clazz("Outer$Inner$ExtendsInnerInner"));
-        });
+        },
+        false);
   }
 
   @Test
@@ -622,6 +644,7 @@
         },
         inspector -> {
           noSignatureAttribute(inspector.clazz("Outer$ExtendsInner"));
-        });
+        },
+        false);
   }
 }
diff --git a/src/test/java/com/android/tools/r8/naming/MinifierFieldSignatureTest.java b/src/test/java/com/android/tools/r8/naming/MinifierFieldSignatureTest.java
index 53e52b4..1e5b66c 100644
--- a/src/test/java/com/android/tools/r8/naming/MinifierFieldSignatureTest.java
+++ b/src/test/java/com/android/tools/r8/naming/MinifierFieldSignatureTest.java
@@ -296,7 +296,7 @@
 
   @Test
   public void classNotFound() throws Exception {
-    String signature = "LNotFound<TX;>.InnerNotFound.InnerAlsoNotFound;";
+    String signature = "LNotFound$InnerNotFound$InnerAlsoNotFound;";
     testSingleField(
         "anX",
         signature,
diff --git a/src/test/java/com/android/tools/r8/naming/MinifierMethodSignatureTest.java b/src/test/java/com/android/tools/r8/naming/MinifierMethodSignatureTest.java
index f88443d..9f693b0 100644
--- a/src/test/java/com/android/tools/r8/naming/MinifierMethodSignatureTest.java
+++ b/src/test/java/com/android/tools/r8/naming/MinifierMethodSignatureTest.java
@@ -103,7 +103,7 @@
 
   @Test
   public void classNotFound() throws Exception {
-    String signature = "<T:LNotFound;>(TT;LAlsoNotFound<TT;>.InnerNotFound.InnerAlsoNotFound;)TT;";
+    String signature = "<T:LNotFound;>(TT;LAlsoNotFound$InnerNotFound$InnerAlsoNotFound;)TT;";
     testSingleMethod(
         "generic",
         signature,
diff --git a/src/test/java/com/android/tools/r8/naming/b124357885/B124357885Test.java b/src/test/java/com/android/tools/r8/naming/b124357885/B124357885Test.java
index d56503c..bd10054 100644
--- a/src/test/java/com/android/tools/r8/naming/b124357885/B124357885Test.java
+++ b/src/test/java/com/android/tools/r8/naming/b124357885/B124357885Test.java
@@ -49,15 +49,7 @@
   private void checkSignature(CodeInspector inspector, String signature) {
     String fooImplFinalDescriptor =
         DescriptorUtils.javaTypeToDescriptor(inspector.clazz(FooImpl.class).getFinalName());
-    StringBuilder expected =
-        new StringBuilder()
-            .append("()")
-            // Remove the final ; from the descriptor to add the generic type.
-            .append(fooImplFinalDescriptor.substring(0, fooImplFinalDescriptor.length() - 1))
-            .append("<Ljava/lang/String;>")
-            // Add the ; after the generic type.
-            .append(";");
-    assertEquals(expected.toString(), signature);
+    assertEquals("()" + fooImplFinalDescriptor, signature);
   }
 
   @Test
@@ -98,9 +90,14 @@
   public static class Main {
     public static void main(String... args) throws Exception {
       Method method = Service.class.getMethod("fooList");
-      ParameterizedType type = (ParameterizedType) method.getGenericReturnType();
-      Class<?> rawType = (Class<?>) type.getRawType();
-      System.out.println(rawType.getName());
+
+      try {
+        ParameterizedType type = (ParameterizedType) method.getGenericReturnType();
+        Class<?> rawType = (Class<?>) type.getRawType();
+        System.out.println(rawType.getName());
+      } catch (ClassCastException e) {
+        System.out.println(((Class<?>) method.getGenericReturnType()).getName());
+      }
 
       // Convince R8 we only use subtypes to get class merging of Foo into FooImpl.
       Foo<String> foo = new FooImpl<>();
diff --git a/src/test/java/com/android/tools/r8/shaking/attributes/KeepSignatureTest.java b/src/test/java/com/android/tools/r8/shaking/attributes/KeepSignatureTest.java
index ef2e860..9dfff6e 100644
--- a/src/test/java/com/android/tools/r8/shaking/attributes/KeepSignatureTest.java
+++ b/src/test/java/com/android/tools/r8/shaking/attributes/KeepSignatureTest.java
@@ -56,8 +56,7 @@
 
   @Test
   public void testKeptClassFieldAndMethodFull() throws Exception {
-    // TODO(b/172999267): The below should be true
-    runTest(testForR8(parameters.getBackend()), true);
+    runTest(testForR8(parameters.getBackend()), false);
   }
 
   @Test