Remove all uses of SubtypingInfo

Change-Id: I88bb14aa836d782c3e98aa37150889eac982a285
diff --git a/src/main/java/com/android/tools/r8/graph/ImmediateAppSubtypingInfo.java b/src/main/java/com/android/tools/r8/graph/ImmediateAppSubtypingInfo.java
index 84a589a..b313aa2 100644
--- a/src/main/java/com/android/tools/r8/graph/ImmediateAppSubtypingInfo.java
+++ b/src/main/java/com/android/tools/r8/graph/ImmediateAppSubtypingInfo.java
@@ -3,17 +3,15 @@
 // BSD-style license that can be found in the LICENSE file.
 package com.android.tools.r8.graph;
 
+import static com.android.tools.r8.graph.DexApplication.classesWithDeterministicOrder;
 import static com.android.tools.r8.utils.MapUtils.ignoreKey;
 
-import com.android.tools.r8.utils.TraversalContinuation;
 import com.google.common.collect.Iterables;
 import java.util.ArrayList;
 import java.util.List;
 import java.util.Map;
-import java.util.Set;
 import java.util.function.Consumer;
 import java.util.function.Function;
-import java.util.function.Predicate;
 
 public class ImmediateAppSubtypingInfo extends ImmediateSubtypingInfo<DexClass, DexClass> {
 
@@ -29,6 +27,11 @@
     DirectMappedDexApplication app = appView.app().asDirect();
     Iterable<DexClass> classes =
         Iterables.concat(app.programClasses(), app.classpathClasses(), app.libraryClasses());
+    return create(appView, classes);
+  }
+
+  public static ImmediateAppSubtypingInfo create(
+      AppView<? extends AppInfoWithClassHierarchy> appView, Iterable<DexClass> classes) {
     return internalCreate(
         appView,
         classes,
@@ -36,23 +39,21 @@
         immediateSubtypes -> new ImmediateAppSubtypingInfo(appView, immediateSubtypes));
   }
 
-  public void forEachTransitiveProgramSubclass(
-      DexClass clazz, Consumer<? super DexProgramClass> consumer) {
-    forEachTransitiveProgramSubclass(clazz, consumer, Function.identity());
+  @Override
+  DexClass toS(DexClass clazz) {
+    return clazz;
   }
 
-  public Set<DexProgramClass> getTransitiveProgramSubclasses(DexClass clazz) {
-    return getTransitiveProgramSubclasses(clazz, Function.identity());
+  // TODO(b/188395655): This is equivalent to just sorting all interfaces?
+  public List<DexClass> computeReachableInterfacesWithDeterministicOrder() {
+    List<DexClass> interfaces = new ArrayList<>();
+    forEachInterface(interfaces::add);
+    return classesWithDeterministicOrder(interfaces);
   }
 
-  public Set<DexProgramClass> getTransitiveProgramSubclassesMatching(
-      DexClass clazz, Predicate<DexProgramClass> predicate) {
-    return getTransitiveProgramSubclassesMatching(clazz, Function.identity(), predicate);
-  }
-
-  public <TB, TC> TraversalContinuation<TB, TC> traverseTransitiveSubclasses(
-      DexClass clazz, Function<DexClass, TraversalContinuation<TB, TC>> fn) {
-    return traverseTransitiveSubclasses(clazz, Function.identity(), fn);
+  private void forEachInterface(Consumer<DexClass> consumer) {
+    DexClass objectClass = appView.definitionFor(appView.dexItemFactory().objectType);
+    forEachImmediateSubClassMatching(objectClass, DexClass::isInterface, consumer);
   }
 
   public void update(AppView<? extends AppInfoWithClassHierarchy> appView) {
diff --git a/src/main/java/com/android/tools/r8/graph/ImmediateProgramSubtypingInfo.java b/src/main/java/com/android/tools/r8/graph/ImmediateProgramSubtypingInfo.java
index ffd3958..49dc125 100644
--- a/src/main/java/com/android/tools/r8/graph/ImmediateProgramSubtypingInfo.java
+++ b/src/main/java/com/android/tools/r8/graph/ImmediateProgramSubtypingInfo.java
@@ -33,4 +33,9 @@
         DexProgramClass::asProgramClassOrNull,
         immediateSubtypes -> new ImmediateProgramSubtypingInfo(appView, immediateSubtypes));
   }
+
+  @Override
+  DexProgramClass toS(DexClass clazz) {
+    return DexProgramClass.asProgramClassOrNull(clazz);
+  }
 }
diff --git a/src/main/java/com/android/tools/r8/graph/ImmediateSubtypingInfo.java b/src/main/java/com/android/tools/r8/graph/ImmediateSubtypingInfo.java
index 01b66d3..7313380 100644
--- a/src/main/java/com/android/tools/r8/graph/ImmediateSubtypingInfo.java
+++ b/src/main/java/com/android/tools/r8/graph/ImmediateSubtypingInfo.java
@@ -55,6 +55,8 @@
     return factory.apply(immediateSubtypes);
   }
 
+  abstract S toS(DexClass clazz);
+
   public void forEachImmediateSuperClass(DexClass clazz, Consumer<? super DexClass> consumer) {
     forEachImmediateSuperClassMatching(
         clazz,
@@ -120,32 +122,42 @@
             });
   }
 
-  void forEachTransitiveProgramSubclass(
-      S clazz, Consumer<? super DexProgramClass> consumer, Function<DexClass, S> cast) {
-    forEachTransitiveProgramSubclassMatching(clazz, consumer, cast, Predicates.alwaysTrue());
+  public void forEachTransitiveSubclass(S clazz, Consumer<? super T> consumer) {
+    forEachTransitiveSubclassMatching(clazz, Predicates.alwaysTrue(), consumer);
   }
 
-  void forEachTransitiveProgramSubclassMatching(
-      S clazz,
-      Consumer<? super DexProgramClass> consumer,
-      Function<DexClass, S> cast,
-      Predicate<DexProgramClass> predicate) {
-    WorkList<DexClass> worklist = WorkList.newIdentityWorkList(getSubclasses(clazz));
+  @SuppressWarnings("unchecked")
+  public <U extends DexClass> void forEachTransitiveSubclassMatching(
+      S clazz, Predicate<? super T> predicate, Consumer<? super U> consumer) {
+    WorkList<T> worklist = WorkList.newIdentityWorkList(getSubclasses(clazz));
     worklist.process(
         subclass -> {
-          if (subclass.isProgramClass()) {
-            DexProgramClass programSubclass = subclass.asProgramClass();
-            if (predicate.test(programSubclass)) {
-              consumer.accept(programSubclass);
-            }
+          if (predicate.test(subclass)) {
+            consumer.accept((U) subclass);
           }
-          S subclassOrNull = cast.apply(subclass);
+          S subclassOrNull = toS(subclass);
           if (subclassOrNull != null) {
             worklist.addIfNotSeen(getSubclasses(subclassOrNull));
           }
         });
   }
 
+  public void forEachTransitiveProgramSubclass(
+      S clazz, Consumer<? super DexProgramClass> consumer) {
+    forEachTransitiveProgramSubclassMatching(clazz, Predicates.alwaysTrue(), consumer);
+  }
+
+  @SuppressWarnings("unchecked")
+  public void forEachTransitiveProgramSubclassMatching(
+      S clazz,
+      Predicate<? super DexProgramClass> predicate,
+      Consumer<? super DexProgramClass> consumer) {
+    forEachTransitiveSubclassMatching(
+        clazz,
+        subclass -> subclass.isProgramClass() && predicate.test(subclass.asProgramClass()),
+        consumer);
+  }
+
   public List<T> getSubclasses(S clazz) {
     return immediateSubtypes.getOrDefault(clazz, Collections.emptyList());
   }
@@ -163,14 +175,14 @@
         Objects::nonNull);
   }
 
-  Set<DexProgramClass> getTransitiveProgramSubclasses(S clazz, Function<DexClass, S> cast) {
-    return getTransitiveProgramSubclassesMatching(clazz, cast, Predicates.alwaysTrue());
+  public Set<DexProgramClass> getTransitiveProgramSubclasses(S clazz) {
+    return getTransitiveProgramSubclassesMatching(clazz, Predicates.alwaysTrue());
   }
 
-  Set<DexProgramClass> getTransitiveProgramSubclassesMatching(
-      S clazz, Function<DexClass, S> cast, Predicate<DexProgramClass> predicate) {
+  public Set<DexProgramClass> getTransitiveProgramSubclassesMatching(
+      S clazz, Predicate<DexProgramClass> predicate) {
     Set<DexProgramClass> classes = Sets.newIdentityHashSet();
-    forEachTransitiveProgramSubclassMatching(clazz, classes::add, cast, predicate);
+    forEachTransitiveProgramSubclassMatching(clazz, predicate, classes::add);
     return classes;
   }
 
@@ -178,10 +190,8 @@
     return !getSubclasses(clazz).isEmpty();
   }
 
-  <TB, TC> TraversalContinuation<TB, TC> traverseTransitiveSubclasses(
-      S clazz,
-      Function<DexClass, S> cast,
-      Function<? super DexClass, TraversalContinuation<TB, TC>> fn) {
+  public <TB, TC> TraversalContinuation<TB, TC> traverseTransitiveSubclasses(
+      S clazz, Function<? super DexClass, TraversalContinuation<TB, TC>> fn) {
     TraversalContinuation<TB, TC> traversalContinuation = TraversalContinuation.doContinue();
     WorkList<DexClass> worklist = WorkList.newIdentityWorkList(getSubclasses(clazz));
     while (worklist.hasNext()) {
@@ -190,7 +200,7 @@
       if (traversalContinuation.shouldBreak()) {
         return traversalContinuation;
       }
-      S subclassOrNull = cast.apply(subclass);
+      S subclassOrNull = toS(subclass);
       if (subclassOrNull != null) {
         worklist.addIfNotSeen(getSubclasses(subclassOrNull));
       }
diff --git a/src/main/java/com/android/tools/r8/graph/SubtypingInfo.java b/src/main/java/com/android/tools/r8/graph/SubtypingInfo.java
deleted file mode 100644
index 523caf6..0000000
--- a/src/main/java/com/android/tools/r8/graph/SubtypingInfo.java
+++ /dev/null
@@ -1,377 +0,0 @@
-// 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.graph;
-
-import static com.android.tools.r8.graph.DexApplication.classesWithDeterministicOrder;
-import static com.android.tools.r8.utils.MapUtils.ignoreKey;
-
-import com.android.tools.r8.utils.WorkList;
-import com.android.tools.r8.utils.structural.StructuralItem;
-import com.google.common.collect.ImmutableSet;
-import com.google.common.collect.Iterables;
-import com.google.common.collect.Sets;
-import java.util.ArrayDeque;
-import java.util.ArrayList;
-import java.util.Deque;
-import java.util.HashSet;
-import java.util.IdentityHashMap;
-import java.util.List;
-import java.util.Map;
-import java.util.Objects;
-import java.util.Set;
-import java.util.concurrent.ConcurrentHashMap;
-import java.util.concurrent.ConcurrentSkipListSet;
-import java.util.function.Consumer;
-
-public class SubtypingInfo {
-
-  private static final int ROOT_LEVEL = 0;
-  private static final int UNKNOWN_LEVEL = -1;
-  private static final int INTERFACE_LEVEL = -2;
-  // Since most Java types has no sub-types, we can just share an empty immutable set until we
-  // need to add to it.
-  private static final Set<DexType> NO_DIRECT_SUBTYPE = ImmutableSet.of();
-  // Map from types to their subtypes.
-  private final Map<DexType, Set<DexType>> subtypeMap;
-
-  private Map<DexType, TypeInfo> typeInfo;
-
-  private final DexDefinitionSupplier definitionSupplier;
-  private final DexItemFactory factory;
-
-  private SubtypingInfo(
-      Map<DexType, TypeInfo> typeInfo,
-      Map<DexType, Set<DexType>> subtypeMap,
-      DexDefinitionSupplier definitionSupplier) {
-    this.typeInfo = typeInfo;
-    this.subtypeMap = subtypeMap;
-    this.definitionSupplier = definitionSupplier;
-    factory = definitionSupplier.dexItemFactory();
-  }
-
-  public void update(AppView<? extends AppInfoWithClassHierarchy> appView) {
-    assert typeInfo == null : "Extending typeInfo not implemented";
-    if (!appView.getSyntheticItems().hasPendingSyntheticClasses()) {
-      return;
-    }
-    WorkList<DexType> worklist = WorkList.newIdentityWorkList();
-    for (DexClass clazz : appView.getSyntheticItems().getAllPendingSyntheticClasses()) {
-      worklist.addIfNotSeen(clazz.allImmediateSupertypes());
-      worklist.process(
-          supertype -> {
-            DexClass superclass = appView.definitionFor(supertype);
-            if (superclass == null) {
-              return;
-            }
-            subtypeMap.computeIfAbsent(supertype, ignoreKey(HashSet::new)).add(clazz.getType());
-            worklist.addIfNotSeen(superclass.allImmediateSupertypes());
-          });
-      worklist.clearSeen();
-    }
-  }
-
-  public SubtypingInfo unsetTypeInfo() {
-    typeInfo = null;
-    return this;
-  }
-
-  public static SubtypingInfo create(AppView<? extends AppInfoWithClassHierarchy> appView) {
-    AppInfoWithClassHierarchy appInfo = appView.appInfo();
-    DirectMappedDexApplication app = appInfo.app().asDirect();
-    Iterable<DexClass> classes =
-        Iterables.concat(app.programClasses(), app.classpathClasses(), app.libraryClasses());
-    return create(classes, appInfo);
-  }
-
-  public static SubtypingInfo create(
-      Iterable<? extends DexClass> classes, DexDefinitionSupplier definitions) {
-    Map<DexType, TypeInfo> typeInfo = new ConcurrentHashMap<>();
-    Map<DexType, Set<DexType>> subtypeMap = new IdentityHashMap<>();
-    populateSubtypeMap(classes, subtypeMap, typeInfo, definitions);
-    return new SubtypingInfo(typeInfo, subtypeMap, definitions);
-  }
-
-  private static void populateSuperType(
-      Map<DexType, Set<DexType>> map,
-      Map<DexType, TypeInfo> typeInfo,
-      DexType superType,
-      DexClass baseClass,
-      DexDefinitionSupplier definitionSupplier) {
-    if (superType != null) {
-      Set<DexType> set = map.computeIfAbsent(superType, ignore -> new HashSet<>());
-      if (set.add(baseClass.type)) {
-        // Only continue recursion if type has been added to set.
-        populateAllSuperTypes(map, typeInfo, superType, baseClass, definitionSupplier);
-      }
-    }
-  }
-
-  private TypeInfo getTypeInfo(DexType type) {
-    return getTypeInfo(type, typeInfo);
-  }
-
-  private static TypeInfo getTypeInfo(DexType type, Map<DexType, TypeInfo> typeInfo) {
-    assert type != null;
-    return typeInfo.computeIfAbsent(type, TypeInfo::new);
-  }
-
-  @SuppressWarnings("ReferenceEquality")
-  private static void populateAllSuperTypes(
-      Map<DexType, Set<DexType>> map,
-      Map<DexType, TypeInfo> typeInfo,
-      DexType holder,
-      DexClass baseClass,
-      DexDefinitionSupplier definitionSupplier) {
-    DexClass holderClass = definitionSupplier.contextIndependentDefinitionFor(holder);
-    // Skip if no corresponding class is found.
-    TypeInfo typeInfoHere = getTypeInfo(holder, typeInfo);
-    if (holderClass != null) {
-      holderClass.forEachImmediateSupertype(
-          (superType, isInterface) -> {
-            populateSuperType(map, typeInfo, superType, baseClass, definitionSupplier);
-            TypeInfo superTypeInfo = getTypeInfo(superType, typeInfo);
-            if (isInterface) {
-              superTypeInfo.addInterfaceSubtype(holder);
-            } else {
-              superTypeInfo.addDirectSubtype(typeInfoHere);
-            }
-          });
-      if (holderClass.isInterface()) {
-        typeInfoHere.tagAsInterface();
-      }
-    } else {
-      // The subtype chain is broken, at least make this type a subtype of Object.
-      DexType objectType = definitionSupplier.dexItemFactory().objectType;
-      if (holder != objectType) {
-        getTypeInfo(objectType, typeInfo).addDirectSubtype(typeInfoHere);
-      }
-    }
-  }
-
-  private static void populateSubtypeMap(
-      Iterable<? extends DexClass> classes,
-      Map<DexType, Set<DexType>> map,
-      Map<DexType, TypeInfo> typeInfo,
-      DexDefinitionSupplier definitionSupplier) {
-    getTypeInfo(definitionSupplier.dexItemFactory().objectType, typeInfo).tagAsSubtypeRoot();
-    for (DexClass clazz : classes) {
-      populateAllSuperTypes(map, typeInfo, clazz.type, clazz, definitionSupplier);
-    }
-    assert validateLevelsAreCorrect(typeInfo, definitionSupplier);
-  }
-
-  @SuppressWarnings("ReferenceEquality")
-  private static boolean validateLevelsAreCorrect(
-      Map<DexType, TypeInfo> typeInfo, DexDefinitionSupplier definitionSupplier) {
-    Set<DexType> seenTypes = Sets.newIdentityHashSet();
-    Deque<DexType> worklist = new ArrayDeque<>();
-    DexType objectType = definitionSupplier.dexItemFactory().objectType;
-    worklist.add(objectType);
-    while (!worklist.isEmpty()) {
-      DexType next = worklist.pop();
-      DexClass nextHolder = definitionSupplier.contextIndependentDefinitionFor(next);
-      DexType superType;
-      if (nextHolder == null) {
-        // We might lack the definition of Object, so guard against that.
-        superType = next == objectType ? null : objectType;
-      } else {
-        superType = nextHolder.superType;
-      }
-      assert !seenTypes.contains(next);
-      seenTypes.add(next);
-      TypeInfo nextInfo = getTypeInfo(next, typeInfo);
-      if (superType == null) {
-        assert nextInfo.hierarchyLevel == ROOT_LEVEL;
-      } else {
-        TypeInfo superInfo = getTypeInfo(superType, typeInfo);
-        assert superInfo.hierarchyLevel == nextInfo.hierarchyLevel - 1
-            || (superInfo.hierarchyLevel == ROOT_LEVEL
-                && nextInfo.hierarchyLevel == INTERFACE_LEVEL);
-        assert superInfo.directSubtypes.contains(next);
-      }
-      if (nextInfo.hierarchyLevel != INTERFACE_LEVEL) {
-        // Only traverse the class hierarchy subtypes, not interfaces.
-        worklist.addAll(nextInfo.directSubtypes);
-      } else if (nextHolder != null) {
-        // Test that the interfaces of this class are interfaces and have this class as subtype.
-        for (DexType iface : nextHolder.interfaces.values) {
-          TypeInfo ifaceInfo = getTypeInfo(iface, typeInfo);
-          assert ifaceInfo.directSubtypes.contains(next);
-          assert ifaceInfo.hierarchyLevel == INTERFACE_LEVEL;
-        }
-      }
-    }
-    return true;
-  }
-
-  public Set<DexType> subtypes(DexType type) {
-    assert type.isClassType();
-    Set<DexType> subtypes = subtypeMap.get(type);
-    return subtypes == null ? ImmutableSet.of() : subtypes;
-  }
-
-  /**
-   * Apply the given function to all classes that directly extend this class.
-   *
-   * <p>If this class is an interface, then this method will visit all sub-interfaces. This deviates
-   * from the dex-file encoding, where subinterfaces "implement" their super interfaces. However, it
-   * is consistent with the source language.
-   */
-  public void forAllImmediateExtendsSubtypes(DexType type, Consumer<DexType> f) {
-    allImmediateExtendsSubtypes(type).forEach(f);
-  }
-
-  public Iterable<DexType> allImmediateExtendsSubtypes(DexType type) {
-    TypeInfo info = getTypeInfo(type);
-    assert info.hierarchyLevel != SubtypingInfo.UNKNOWN_LEVEL;
-    if (info.hierarchyLevel == SubtypingInfo.INTERFACE_LEVEL) {
-      return Iterables.filter(info.directSubtypes, t -> getTypeInfo(t).isInterface());
-    } else if (info.hierarchyLevel == SubtypingInfo.ROOT_LEVEL) {
-      // This is the object type. Filter out interfaces
-      return Iterables.filter(info.directSubtypes, t -> !getTypeInfo(t).isInterface());
-    } else {
-      return info.directSubtypes;
-    }
-  }
-
-  public Set<DexType> allImmediateSubtypes(DexType type) {
-    return getTypeInfo(type).directSubtypes;
-  }
-
-  public void forAllInterfaceRoots(Consumer<DexType> fn) {
-    Iterables.filter(
-            getTypeInfo(factory.objectType).directSubtypes,
-            subtype -> getTypeInfo(subtype).isInterface())
-        .forEach(fn);
-  }
-
-  public List<DexClass> computeReachableInterfacesWithDeterministicOrder() {
-    List<DexClass> interfaces = new ArrayList<>();
-    forAllInterfaceRoots(
-        type ->
-            definitionSupplier
-                .contextIndependentDefinitionForWithResolutionResult(type)
-                .forEachClassResolutionResult(interfaces::add));
-    return classesWithDeterministicOrder(interfaces);
-  }
-
-  public boolean verifyUpToDate(AppView<AppInfoWithClassHierarchy> appView) {
-    DirectMappedDexApplication app = appView.app().asDirect();
-    Iterable<DexClass> classes =
-        Iterables.concat(app.programClasses(), app.classpathClasses(), app.libraryClasses());
-    for (DexClass clazz : classes) {
-      assert verifyUpToDate(appView, clazz);
-    }
-    // This does not check that the `typeInfo` is up-to-date.
-    assert typeInfo == null;
-    return true;
-  }
-
-  private boolean verifyUpToDate(AppView<AppInfoWithClassHierarchy> appView, DexClass clazz) {
-    WorkList<DexType> worklist = WorkList.newIdentityWorkList(clazz.allImmediateSupertypes());
-    worklist.process(
-        supertype -> {
-          DexClass superclass = appView.definitionFor(supertype);
-          if (superclass == null) {
-            return;
-          }
-          assert subtypes(supertype).contains(clazz.getType())
-                  || (clazz.isLibraryClass()
-                      && appView.definitionFor(clazz.getType()).isProgramClass())
-              : "Expected subtypes("
-                  + supertype.getTypeName()
-                  + ") to include "
-                  + clazz.getTypeName();
-          worklist.addIfNotSeen(superclass.allImmediateSupertypes());
-        });
-    return true;
-  }
-
-  private static class TypeInfo {
-
-    private final DexType type;
-
-    private int hierarchyLevel = UNKNOWN_LEVEL;
-
-    /**
-     * Set of direct subtypes. This set has to remain sorted to ensure determinism. The actual
-     * sorting is not important but {@link DexType#compareTo(StructuralItem)} works well.
-     */
-    private Set<DexType> directSubtypes = NO_DIRECT_SUBTYPE;
-
-    TypeInfo(DexType type) {
-      this.type = type;
-    }
-
-    @Override
-    public int hashCode() {
-      return Objects.hash(type, directSubtypes);
-    }
-
-    @Override
-    @SuppressWarnings("ReferenceEquality")
-    public boolean equals(Object obj) {
-      if (!(obj instanceof TypeInfo)) {
-        return false;
-      }
-      TypeInfo other = (TypeInfo) obj;
-      return other.type == type && other.directSubtypes.equals(directSubtypes);
-    }
-
-    @Override
-    public String toString() {
-      return "TypeInfo{" + type + ", level:" + hierarchyLevel + "}";
-    }
-
-    private void ensureDirectSubTypeSet() {
-      if (directSubtypes == NO_DIRECT_SUBTYPE) {
-        directSubtypes = new ConcurrentSkipListSet<>(DexType::compareTo);
-      }
-    }
-
-    private void setLevel(int level) {
-      if (level == hierarchyLevel) {
-        return;
-      }
-      if (hierarchyLevel == INTERFACE_LEVEL) {
-        assert level == ROOT_LEVEL + 1;
-      } else if (level == INTERFACE_LEVEL) {
-        assert hierarchyLevel == ROOT_LEVEL + 1 || hierarchyLevel == UNKNOWN_LEVEL;
-        hierarchyLevel = INTERFACE_LEVEL;
-      } else {
-        assert hierarchyLevel == UNKNOWN_LEVEL;
-        hierarchyLevel = level;
-      }
-    }
-
-    private void addDirectSubtype(TypeInfo subtypeInfo) {
-      assert hierarchyLevel != UNKNOWN_LEVEL;
-      ensureDirectSubTypeSet();
-      directSubtypes.add(subtypeInfo.type);
-      subtypeInfo.setLevel(hierarchyLevel + 1);
-    }
-
-    private void tagAsSubtypeRoot() {
-      setLevel(ROOT_LEVEL);
-    }
-
-    private void tagAsInterface() {
-      setLevel(INTERFACE_LEVEL);
-    }
-
-    private boolean isInterface() {
-      assert hierarchyLevel != UNKNOWN_LEVEL : "Program class missing: " + this;
-      assert type.isClassType();
-      return hierarchyLevel == INTERFACE_LEVEL;
-    }
-
-    private void addInterfaceSubtype(DexType type) {
-      // Interfaces all inherit from java.lang.Object. However, we assign a special level to
-      // identify them later on.
-      setLevel(INTERFACE_LEVEL);
-      ensureDirectSubTypeSet();
-      directSubtypes.add(type);
-    }
-  }
-}
diff --git a/src/main/java/com/android/tools/r8/naming/FieldNameMinifier.java b/src/main/java/com/android/tools/r8/naming/FieldNameMinifier.java
index 634854a..cd8798d 100644
--- a/src/main/java/com/android/tools/r8/naming/FieldNameMinifier.java
+++ b/src/main/java/com/android/tools/r8/naming/FieldNameMinifier.java
@@ -10,8 +10,8 @@
 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.graph.ImmediateAppSubtypingInfo;
 import com.android.tools.r8.graph.ProgramField;
-import com.android.tools.r8.graph.SubtypingInfo;
 import com.android.tools.r8.graph.TopDownClassHierarchyTraversal;
 import com.android.tools.r8.shaking.AppInfoWithLiveness;
 import com.android.tools.r8.utils.SetUtils;
@@ -33,7 +33,7 @@
 class FieldNameMinifier {
 
   private final AppView<AppInfoWithLiveness> appView;
-  private final SubtypingInfo subtypingInfo;
+  private final ImmediateAppSubtypingInfo subtypingInfo;
   private final Map<DexField, DexString> renaming = new IdentityHashMap<>();
   private final Map<DexType, ReservedFieldNamingState> reservedNamingStates =
       new IdentityHashMap<>();
@@ -44,7 +44,7 @@
 
   FieldNameMinifier(
       AppView<AppInfoWithLiveness> appView,
-      SubtypingInfo subtypingInfo,
+      ImmediateAppSubtypingInfo subtypingInfo,
       MemberNamingStrategy strategy) {
     this.appView = appView;
     this.subtypingInfo = subtypingInfo;
@@ -321,18 +321,18 @@
         if (clazz.isInterface()) {
           partition.add(clazz);
 
-          for (DexType subtype : minifier.subtypingInfo.allImmediateSubtypes(type)) {
-            if (visited.add(subtype)) {
-              worklist.add(subtype);
+          for (DexClass subclass : minifier.subtypingInfo.getSubclasses(clazz)) {
+            if (visited.add(subclass.getType())) {
+              worklist.add(subclass.getType());
             }
           }
         } else if (clazz.type != appView.dexItemFactory().objectType) {
           if (visited.add(clazz.superType)) {
             worklist.add(clazz.superType);
           }
-          for (DexType subclass : minifier.subtypingInfo.allImmediateExtendsSubtypes(type)) {
-            if (visited.add(subclass)) {
-              worklist.add(subclass);
+          for (DexClass subclass : minifier.subtypingInfo.getSubclasses(clazz)) {
+            if (visited.add(subclass.getType())) {
+              worklist.add(subclass.getType());
             }
           }
         }
diff --git a/src/main/java/com/android/tools/r8/naming/InterfaceMethodNameMinifier.java b/src/main/java/com/android/tools/r8/naming/InterfaceMethodNameMinifier.java
index 54685b4..b611309 100644
--- a/src/main/java/com/android/tools/r8/naming/InterfaceMethodNameMinifier.java
+++ b/src/main/java/com/android/tools/r8/naming/InterfaceMethodNameMinifier.java
@@ -13,7 +13,7 @@
 import com.android.tools.r8.graph.DexMethod;
 import com.android.tools.r8.graph.DexString;
 import com.android.tools.r8.graph.DexType;
-import com.android.tools.r8.graph.SubtypingInfo;
+import com.android.tools.r8.graph.ImmediateAppSubtypingInfo;
 import com.android.tools.r8.ir.desugar.LambdaDescriptor;
 import com.android.tools.r8.naming.MethodNameMinifier.State;
 import com.android.tools.r8.shaking.AppInfoWithLiveness;
@@ -383,7 +383,7 @@
       };
 
   private final AppView<AppInfoWithLiveness> appView;
-  private final SubtypingInfo subtypingInfo;
+  private final ImmediateAppSubtypingInfo subtypingInfo;
   private final MethodNameMinifier.State minifierState;
 
   /** A map from DexMethods to all the states linked to interfaces they appear in. */
@@ -394,7 +394,9 @@
   private final Map<DexType, InterfaceReservationState> interfaceStateMap = new HashMap<>();
 
   InterfaceMethodNameMinifier(
-      AppView<AppInfoWithLiveness> appView, State minifierState, SubtypingInfo subtypingInfo) {
+      AppView<AppInfoWithLiveness> appView,
+      State minifierState,
+      ImmediateAppSubtypingInfo subtypingInfo) {
     this.appView = appView;
     this.minifierState = minifierState;
     this.subtypingInfo = subtypingInfo;
@@ -656,26 +658,24 @@
   private void computeReservationFrontiersForAllImplementingClasses(Iterable<DexClass> interfaces) {
     interfaces.forEach(
         iface ->
-            subtypingInfo
-                .subtypes(iface.getType())
-                .forEach(
-                    subType -> {
-                      DexClass subClass = appView.contextIndependentDefinitionFor(subType);
-                      if (subClass == null || subClass.isInterface()) {
-                        return;
-                      }
-                      DexType frontierType = minifierState.getFrontier(subType);
-                      if (minifierState.getReservationState(frontierType) == null) {
-                        // The reservation state should already be added. If it does not exist
-                        // it is because it is not reachable from the type hierarchy of program
-                        // classes and we can therefore disregard this interface.
-                        return;
-                      }
-                      InterfaceReservationState iState = interfaceStateMap.get(iface.getType());
-                      if (iState != null) {
-                        iState.addReservationType(frontierType);
-                      }
-                    }));
+            subtypingInfo.forEachTransitiveSubclass(
+                iface,
+                subclass -> {
+                  if (subclass.isInterface()) {
+                    return;
+                  }
+                  DexType frontierType = minifierState.getFrontier(subclass.getType());
+                  if (minifierState.getReservationState(frontierType) == null) {
+                    // The reservation state should already be added. If it does not exist
+                    // it is because it is not reachable from the type hierarchy of program
+                    // classes and we can therefore disregard this interface.
+                    return;
+                  }
+                  InterfaceReservationState iState = interfaceStateMap.get(iface.getType());
+                  if (iState != null) {
+                    iState.addReservationType(frontierType);
+                  }
+                }));
   }
 
   private boolean verifyAllCallSitesAreRepresentedIn(List<DexClassAndMethod> groups) {
diff --git a/src/main/java/com/android/tools/r8/naming/MethodNameMinifier.java b/src/main/java/com/android/tools/r8/naming/MethodNameMinifier.java
index 4669081..2d175ce 100644
--- a/src/main/java/com/android/tools/r8/naming/MethodNameMinifier.java
+++ b/src/main/java/com/android/tools/r8/naming/MethodNameMinifier.java
@@ -13,7 +13,7 @@
 import com.android.tools.r8.graph.DexMethod;
 import com.android.tools.r8.graph.DexString;
 import com.android.tools.r8.graph.DexType;
-import com.android.tools.r8.graph.SubtypingInfo;
+import com.android.tools.r8.graph.ImmediateAppSubtypingInfo;
 import com.android.tools.r8.graph.TopDownClassHierarchyTraversal;
 import com.android.tools.r8.shaking.AppInfoWithLiveness;
 import com.android.tools.r8.utils.InternalOptions;
@@ -182,7 +182,7 @@
   }
 
   MethodRenaming computeRenaming(
-      Iterable<DexClass> interfaces, SubtypingInfo subtypingInfo, Timing timing) {
+      Iterable<DexClass> interfaces, ImmediateAppSubtypingInfo subtypingInfo, Timing timing) {
     // Phase 1: Reserve all the names that need to be kept and allocate linked state in the
     //          library part.
     timing.begin("Phase 1");
diff --git a/src/main/java/com/android/tools/r8/naming/Minifier.java b/src/main/java/com/android/tools/r8/naming/Minifier.java
index 23f9f8d..c499d23 100644
--- a/src/main/java/com/android/tools/r8/naming/Minifier.java
+++ b/src/main/java/com/android/tools/r8/naming/Minifier.java
@@ -21,12 +21,12 @@
 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.graph.ImmediateAppSubtypingInfo;
 import com.android.tools.r8.graph.MethodResolutionResult;
 import com.android.tools.r8.graph.ProgramField;
 import com.android.tools.r8.graph.ProgramMethod;
 import com.android.tools.r8.graph.ReferencedMembersCollector;
 import com.android.tools.r8.graph.ReferencedMembersCollector.ReferencedMembersConsumer;
-import com.android.tools.r8.graph.SubtypingInfo;
 import com.android.tools.r8.naming.ClassNameMinifier.ClassNamingStrategy;
 import com.android.tools.r8.naming.ClassNameMinifier.ClassRenaming;
 import com.android.tools.r8.naming.FieldNameMinifier.FieldRenaming;
@@ -57,9 +57,10 @@
 
   public void run(ExecutorService executorService, Timing timing) throws ExecutionException {
     assert appView.options().isMinifying();
-    SubtypingInfo subtypingInfo = MinifierUtils.createSubtypingInfo(appView);
+    ImmediateAppSubtypingInfo immediateSubtypingInfo = ImmediateAppSubtypingInfo.create(appView);
     timing.begin("ComputeInterfaces");
-    List<DexClass> interfaces = subtypingInfo.computeReachableInterfacesWithDeterministicOrder();
+    List<DexClass> interfaces =
+        immediateSubtypingInfo.computeReachableInterfacesWithDeterministicOrder();
     timing.end();
     timing.begin("MinifyClasses");
     ClassNameMinifier classNameMinifier =
@@ -81,7 +82,7 @@
     timing.begin("MinifyMethods");
     MethodRenaming methodRenaming =
         new MethodNameMinifier(appView, minifyMembers)
-            .computeRenaming(interfaces, subtypingInfo, timing);
+            .computeRenaming(interfaces, immediateSubtypingInfo, timing);
     timing.end();
 
     assert new MinifiedRenaming(appView, classRenaming, methodRenaming, FieldRenaming.empty())
@@ -89,7 +90,7 @@
 
     timing.begin("MinifyFields");
     FieldRenaming fieldRenaming =
-        new FieldNameMinifier(appView, subtypingInfo, minifyMembers)
+        new FieldNameMinifier(appView, immediateSubtypingInfo, minifyMembers)
             .computeRenaming(interfaces, timing);
     timing.end();
 
diff --git a/src/main/java/com/android/tools/r8/naming/MinifierUtils.java b/src/main/java/com/android/tools/r8/naming/MinifierUtils.java
deleted file mode 100644
index e259a67..0000000
--- a/src/main/java/com/android/tools/r8/naming/MinifierUtils.java
+++ /dev/null
@@ -1,32 +0,0 @@
-// Copyright (c) 2022, the R8 project authors. Please see the AUTHORS file
-// for details. All rights reserved. Use of this source code is governed by a
-// BSD-style license that can be found in the LICENSE file.
-
-package com.android.tools.r8.naming;
-
-import com.android.tools.r8.graph.AppView;
-import com.android.tools.r8.graph.DexClass;
-import com.android.tools.r8.graph.SubtypingInfo;
-import com.android.tools.r8.shaking.AppInfoWithLiveness;
-import com.android.tools.r8.utils.SetUtils;
-import java.util.Set;
-
-public class MinifierUtils {
-
-  public static SubtypingInfo createSubtypingInfo(AppView<AppInfoWithLiveness> appView) {
-    Set<DexClass> classesToBuildSubtypeInformationFor =
-        SetUtils.newIdentityHashSet(appView.app().classes());
-    appView
-        .appInfo()
-        .getObjectAllocationInfoCollection()
-        .forEachInstantiatedLambdaInterfaces(
-            type -> {
-              DexClass lambdaInterface = appView.contextIndependentDefinitionFor(type);
-              if (lambdaInterface != null) {
-                classesToBuildSubtypeInformationFor.add(lambdaInterface);
-              }
-            });
-    appView.appInfo().forEachReferencedClasspathClass(classesToBuildSubtypeInformationFor::add);
-    return SubtypingInfo.create(classesToBuildSubtypeInformationFor, appView);
-  }
-}
diff --git a/src/main/java/com/android/tools/r8/naming/ProguardMapMinifier.java b/src/main/java/com/android/tools/r8/naming/ProguardMapMinifier.java
index aae09f7..78baeb5 100644
--- a/src/main/java/com/android/tools/r8/naming/ProguardMapMinifier.java
+++ b/src/main/java/com/android/tools/r8/naming/ProguardMapMinifier.java
@@ -27,9 +27,9 @@
 import com.android.tools.r8.graph.DexReference;
 import com.android.tools.r8.graph.DexString;
 import com.android.tools.r8.graph.DexType;
+import com.android.tools.r8.graph.ImmediateAppSubtypingInfo;
 import com.android.tools.r8.graph.ProgramField;
 import com.android.tools.r8.graph.ProgramOrClasspathClass;
-import com.android.tools.r8.graph.SubtypingInfo;
 import com.android.tools.r8.naming.ClassNameMinifier.ClassRenaming;
 import com.android.tools.r8.naming.FieldNameMinifier.FieldRenaming;
 import com.android.tools.r8.naming.MemberNaming.FieldSignature;
@@ -98,23 +98,20 @@
   public NamingLens run(ExecutorService executorService, Timing timing) throws ExecutionException {
     ArrayDeque<Map<DexReference, MemberNaming>> nonPrivateMembers = new ArrayDeque<>();
     Set<DexReference> notMappedReferences = new HashSet<>();
-    SubtypingInfo subtypingInfo = MinifierUtils.createSubtypingInfo(appView);
+    ImmediateAppSubtypingInfo subtypingInfo = ImmediateAppSubtypingInfo.create(appView);
     timing.begin("MappingInterfaces");
     List<DexClass> interfaces = subtypingInfo.computeReachableInterfacesWithDeterministicOrder();
     interfaces.forEach(
-        iface ->
-            computeMapping(iface.getType(), nonPrivateMembers, notMappedReferences, subtypingInfo));
+        iface -> computeMapping(iface, nonPrivateMembers, notMappedReferences, subtypingInfo));
     timing.end();
     timing.begin("MappingClasses");
     mappedClasses.addAll(appView.appInfo().classes());
-    subtypingInfo.forAllImmediateExtendsSubtypes(
-        factory.objectType,
-        subType -> {
-          DexClass dexClass = appView.definitionFor(subType);
-          if (dexClass != null && !dexClass.isInterface()) {
-            computeMapping(subType, nonPrivateMembers, notMappedReferences, subtypingInfo);
-          }
-        });
+    DexClass objectClass = appView.definitionFor(factory.objectType);
+    for (DexClass subclass : subtypingInfo.getSubclasses(objectClass)) {
+      if (!subclass.isInterface()) {
+        computeMapping(subclass, nonPrivateMembers, notMappedReferences, subtypingInfo);
+      }
+    }
     assert nonPrivateMembers.isEmpty();
     timing.end();
 
@@ -167,21 +164,21 @@
   }
 
   private void computeMapping(
-      DexType type,
+      DexClass clazz,
       Deque<Map<DexReference, MemberNaming>> buildUpNames,
       Set<DexReference> notMappedReferences,
-      SubtypingInfo subtypingInfo) {
+      ImmediateAppSubtypingInfo subtypingInfo) {
+    DexType type = clazz.getType();
     ClassNamingForMapApplier classNaming = seedMapper.getClassNaming(type);
-    DexClass clazz = appView.definitionFor(type);
 
     // Keep track of classpath classes that needs to get renamed.
-    if (clazz != null && clazz.isClasspathClass() && classNaming != null) {
+    if (clazz.isClasspathClass() && classNaming != null) {
       mappedClasses.add(clazz.asClasspathClass());
     }
 
     Map<DexReference, MemberNaming> nonPrivateMembers = new IdentityHashMap<>();
 
-    if (classNaming != null && (clazz == null || !clazz.isLibraryClass())) {
+    if (classNaming != null && !clazz.isLibraryClass()) {
       DexString mappedName = factory.createString(classNaming.renamedName);
       checkAndAddMappedNames(type, mappedName, classNaming.position);
       classNaming.forAllMemberNaming(
@@ -204,7 +201,7 @@
           if (!memberNames.containsKey(parentReferenceOnCurrentType)) {
             addMemberNaming(
                 parentReferenceOnCurrentType, parentMembers.get(key), additionalMethodNamings);
-          } else if (clazz != null) {
+          } else {
             DexEncodedMethod method = clazz.lookupMethod(parentReferenceOnCurrentType);
             assert method == null
                 || method.isStatic()
@@ -227,25 +224,25 @@
       }
     }
 
-    if (clazz != null) {
-      // If a class is marked as abstract it is allowed to not implement methods from interfaces
-      // thus the map will not contain a mapping. Also, if an interface is defined in the library
-      // and the class is in the program, we have to build up the correct names to reserve them.
-      if (clazz.isProgramClass() || clazz.isAbstract()) {
-        addNonPrivateInterfaceMappings(type, nonPrivateMembers, clazz.interfaces.values);
-      }
+    // If a class is marked as abstract it is allowed to not implement methods from interfaces
+    // thus the map will not contain a mapping. Also, if an interface is defined in the library
+    // and the class is in the program, we have to build up the correct names to reserve them.
+    if (clazz.isProgramClass() || clazz.isAbstract()) {
+      addNonPrivateInterfaceMappings(type, nonPrivateMembers, clazz.interfaces.values);
     }
 
-    if (nonPrivateMembers.size() > 0) {
+    if (!nonPrivateMembers.isEmpty()) {
       buildUpNames.addLast(nonPrivateMembers);
-      subtypingInfo.forAllImmediateExtendsSubtypes(
-          type,
-          subType -> computeMapping(subType, buildUpNames, notMappedReferences, subtypingInfo));
+      subtypingInfo.forEachImmediateSubClassMatching(
+          clazz,
+          subclass -> clazz.isInterface() == subclass.isInterface(),
+          subclass -> computeMapping(subclass, buildUpNames, notMappedReferences, subtypingInfo));
       buildUpNames.removeLast();
     } else {
-      subtypingInfo.forAllImmediateExtendsSubtypes(
-          type,
-          subType -> computeMapping(subType, buildUpNames, notMappedReferences, subtypingInfo));
+      subtypingInfo.forEachImmediateSubClassMatching(
+          clazz,
+          subclass -> clazz.isInterface() == subclass.isInterface(),
+          subclass -> computeMapping(subclass, buildUpNames, notMappedReferences, subtypingInfo));
     }
   }
 
diff --git a/src/test/java/com/android/tools/r8/internal/R8GMSCoreLookupTest.java b/src/test/java/com/android/tools/r8/internal/R8GMSCoreLookupTest.java
deleted file mode 100644
index 3b676f9..0000000
--- a/src/test/java/com/android/tools/r8/internal/R8GMSCoreLookupTest.java
+++ /dev/null
@@ -1,148 +0,0 @@
-// Copyright (c) 2016, 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.internal;
-
-import static org.junit.Assert.assertEquals;
-import static org.junit.Assert.assertFalse;
-import static org.junit.Assert.assertNotNull;
-import static org.junit.Assert.assertTrue;
-import static org.junit.Assert.fail;
-
-import com.android.tools.r8.StringResource;
-import com.android.tools.r8.TestBase;
-import com.android.tools.r8.ToolHelper;
-import com.android.tools.r8.dex.ApplicationReader;
-import com.android.tools.r8.graph.AppInfoWithClassHierarchy;
-import com.android.tools.r8.graph.AppServices;
-import com.android.tools.r8.graph.AppView;
-import com.android.tools.r8.graph.DexEncodedMethod;
-import com.android.tools.r8.graph.DexMethod;
-import com.android.tools.r8.graph.DexProgramClass;
-import com.android.tools.r8.graph.DirectMappedDexApplication;
-import com.android.tools.r8.graph.LookupResult;
-import com.android.tools.r8.graph.LookupResult.LookupResultSuccess;
-import com.android.tools.r8.graph.MethodResolutionResult;
-import com.android.tools.r8.graph.SubtypingInfo;
-import com.android.tools.r8.shaking.AppInfoWithLiveness;
-import com.android.tools.r8.utils.AndroidApp;
-import com.android.tools.r8.utils.InternalOptions;
-import com.android.tools.r8.utils.timing.Timing;
-import java.nio.file.Files;
-import java.nio.file.Path;
-import java.nio.file.Paths;
-import java.util.concurrent.ExecutorService;
-import java.util.concurrent.Executors;
-import org.junit.Before;
-import org.junit.Ignore;
-import org.junit.Test;
-
-public class R8GMSCoreLookupTest extends TestBase {
-
-  private static final String APP_DIR = "third_party/gmscore/v5/";
-  private DirectMappedDexApplication program;
-  private AppView<? extends AppInfoWithClassHierarchy> appView;
-  private SubtypingInfo subtypingInfo;
-
-  @Before
-  public void readGMSCore() throws Exception {
-    Path directory = Paths.get(APP_DIR);
-    AndroidApp app = ToolHelper.builderFromProgramDirectory(directory).build();
-    Path mapFile = directory.resolve(ToolHelper.DEFAULT_PROGUARD_MAP_FILE);
-    StringResource proguardMap = null;
-    if (Files.exists(mapFile)) {
-      proguardMap = StringResource.fromFile(mapFile);
-    }
-    ExecutorService executorService = Executors.newSingleThreadExecutor();
-    Timing timing = Timing.empty();
-    program =
-        new ApplicationReader(app, new InternalOptions(), timing)
-            .read(proguardMap, executorService)
-            .toDirect();
-    appView = AppView.createForR8(program);
-    appView.setAppServices(AppServices.builder(appView).build());
-    subtypingInfo = SubtypingInfo.create(appView);
-  }
-
-  private AppInfoWithClassHierarchy appInfo() {
-    return appView.appInfo();
-  }
-
-  private void testVirtualLookup(DexProgramClass clazz, DexEncodedMethod method) {
-    // Check lookup will produce the same result.
-    DexMethod id = method.getReference();
-    assertEquals(
-        appInfo().resolveMethodOnClassLegacy(id.holder, method.getReference()).getSingleTarget(),
-        method);
-
-    // Check lookup targets with include method.
-    MethodResolutionResult resolutionResult =
-        appInfo().resolveMethodOnClassLegacy(clazz, method.getReference());
-    AppInfoWithLiveness appInfo = null; // TODO(b/154881041): Remove or compute liveness.
-    LookupResult lookupResult =
-        resolutionResult.lookupVirtualDispatchTargets(
-            clazz, appView, appInfo, dexReference -> false);
-    assertTrue(lookupResult.isLookupResultSuccess());
-    assertTrue(lookupResult.asLookupResultSuccess().contains(method));
-  }
-
-  private static class Counter {
-    int count = 0;
-
-    void inc() {
-      count++;
-    }
-  }
-
-  private void testInterfaceLookup(DexProgramClass clazz, DexEncodedMethod method) {
-    AppInfoWithLiveness appInfo = null; // TODO(b/154881041): Remove or compute liveness.
-    LookupResultSuccess lookupResult =
-        appInfo()
-            .resolveMethodOnInterfaceLegacy(clazz, method.getReference())
-            .lookupVirtualDispatchTargets(clazz, appView, appInfo, dexReference -> false)
-            .asLookupResultSuccess();
-    assertNotNull(lookupResult);
-    assertFalse(lookupResult.hasLambdaTargets());
-    if (subtypingInfo.subtypes(method.getHolderType()).stream()
-        .allMatch(t -> appInfo().definitionFor(t).isInterface())) {
-      Counter counter = new Counter();
-      lookupResult.forEach(
-          target -> {
-            DexEncodedMethod m = target.getDefinition();
-            if (m.accessFlags.isAbstract() || !m.accessFlags.isBridge()) {
-              counter.inc();
-            }
-          },
-          l -> fail());
-      assertEquals(0, counter.count);
-    } else {
-      Counter counter = new Counter();
-      lookupResult.forEach(
-          target -> {
-            if (target.getDefinition().isAbstract()) {
-              counter.inc();
-            }
-          },
-          lambda -> fail());
-      assertEquals(0, counter.count);
-    }
-  }
-
-  private void testLookup(DexProgramClass clazz) {
-    if (clazz.isInterface()) {
-      for (DexEncodedMethod method : clazz.virtualMethods()) {
-        testInterfaceLookup(clazz, method);
-      }
-    } else {
-      for (DexEncodedMethod method : clazz.virtualMethods()) {
-        testVirtualLookup(clazz, method);
-      }
-    }
-  }
-
-  @Test
-  @Ignore("b/154881041: Does this test add value? If so it needs to compute a liveness app-info")
-  public void testLookup() {
-    program.classesWithDeterministicOrder().forEach(this::testLookup);
-  }
-}
diff --git a/src/test/java/com/android/tools/r8/shaking/R8Shaking2LookupTest.java b/src/test/java/com/android/tools/r8/shaking/R8Shaking2LookupTest.java
index ef19e65..8e6c718 100644
--- a/src/test/java/com/android/tools/r8/shaking/R8Shaking2LookupTest.java
+++ b/src/test/java/com/android/tools/r8/shaking/R8Shaking2LookupTest.java
@@ -5,63 +5,76 @@
 
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertNotEquals;
 import static org.junit.Assert.assertTrue;
 
+import com.android.tools.r8.TestBase;
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.TestParametersCollection;
 import com.android.tools.r8.ToolHelper;
 import com.android.tools.r8.graph.AppInfoWithClassHierarchy;
 import com.android.tools.r8.graph.AppView;
+import com.android.tools.r8.graph.DexClass;
 import com.android.tools.r8.graph.DexItemFactory;
-import com.android.tools.r8.graph.DexType;
 import com.android.tools.r8.graph.DirectMappedDexApplication;
-import com.android.tools.r8.graph.SubtypingInfo;
+import com.android.tools.r8.graph.ImmediateAppSubtypingInfo;
 import com.google.common.collect.ImmutableList;
 import java.io.IOException;
 import java.util.concurrent.ExecutionException;
 import org.junit.Before;
 import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+import org.junit.runners.Parameterized.Parameter;
+import org.junit.runners.Parameterized.Parameters;
 
-public class R8Shaking2LookupTest {
+@RunWith(Parameterized.class)
+public class R8Shaking2LookupTest extends TestBase {
 
-  private DirectMappedDexApplication program;
   private DexItemFactory dexItemFactory;
   private AppInfoWithClassHierarchy appInfo;
-  private SubtypingInfo subtypingInfo;
+  private ImmediateAppSubtypingInfo subtypingInfo;
+
+  @Parameter(0)
+  public TestParameters parameters;
+
+  @Parameters(name = "{0}")
+  public static TestParametersCollection data() {
+    return getTestParameters().withNoneRuntime().build();
+  }
 
   @Before
   public void readApp() throws IOException, ExecutionException {
-    program =
+    DirectMappedDexApplication program =
         ToolHelper.buildApplication(
             ImmutableList.of(ToolHelper.EXAMPLES_BUILD_DIR + "shaking2.jar"));
     dexItemFactory = program.dexItemFactory;
     AppView<AppInfoWithClassHierarchy> appView = AppView.createForR8(program);
     appInfo = appView.appInfo();
-    subtypingInfo = SubtypingInfo.create(appView);
+    subtypingInfo = ImmediateAppSubtypingInfo.create(appView);
   }
 
-  private void validateSubtype(DexType super_type, DexType sub_type) {
-    assertFalse(super_type.equals(sub_type));
-    assertTrue(subtypingInfo.subtypes(super_type).contains(sub_type));
+  private void validateSubtype(DexClass super_type, DexClass sub_type) {
+    assertNotEquals(super_type, sub_type);
+    assertTrue(subtypingInfo.getTransitiveProgramSubclasses(super_type).contains(sub_type));
     assertTrue(appInfo.isSubtype(sub_type, super_type));
-    assertFalse(subtypingInfo.subtypes(sub_type).contains(super_type));
+    assertFalse(subtypingInfo.getTransitiveProgramSubclasses(sub_type).contains(super_type));
     assertFalse(appInfo.isSubtype(super_type, sub_type));
   }
 
-  private void validateSubtypeSize(DexType type, int size) {
-    assertEquals(size, subtypingInfo.subtypes(type).size());
+  private void validateSubtypeSize(DexClass type, int size) {
+    assertEquals(size, subtypingInfo.getTransitiveProgramSubclasses(type).size());
   }
 
   @Test
   public void testLookup() {
-    DexType object_type = dexItemFactory.createType("Ljava/lang/Object;");
-
-    DexType interface_type = dexItemFactory.createType("Lshaking2/Interface;");
-    DexType superInterface1_type = dexItemFactory.createType("Lshaking2/SuperInterface1;");
-    DexType superInterface2_type = dexItemFactory.createType("Lshaking2/SuperInterface2;");
-
-    DexType superclass_type = dexItemFactory.createType("Lshaking2/SuperClass;");
-    DexType subClass1_type = dexItemFactory.createType("Lshaking2/SubClass1;");
-    DexType subClass2_type = dexItemFactory.createType("Lshaking2/SubClass2;");
-
+    DexClass object_type = definitionFor("Ljava/lang/Object;");
+    DexClass interface_type = definitionFor("Lshaking2/Interface;");
+    DexClass superInterface1_type = definitionFor("Lshaking2/SuperInterface1;");
+    DexClass superInterface2_type = definitionFor("Lshaking2/SuperInterface2;");
+    DexClass superclass_type = definitionFor("Lshaking2/SuperClass;");
+    DexClass subClass1_type = definitionFor("Lshaking2/SubClass1;");
+    DexClass subClass2_type = definitionFor("Lshaking2/SubClass2;");
     validateSubtype(object_type, interface_type);
     validateSubtypeSize(interface_type, 4);
     validateSubtype(interface_type, superclass_type);
@@ -72,4 +85,8 @@
     validateSubtype(superInterface2_type, interface_type);
     validateSubtypeSize(subClass2_type, 0);
   }
+
+  private DexClass definitionFor(String descriptor) {
+    return appInfo.definitionFor(dexItemFactory.createType(descriptor));
+  }
 }