Move type element caches from DexItemFactory to AppView

Change-Id: I5e2758864a8314345a5ce552f7623dac352c9048
diff --git a/src/main/java/com/android/tools/r8/D8.java b/src/main/java/com/android/tools/r8/D8.java
index 2500f19..2393dab 100644
--- a/src/main/java/com/android/tools/r8/D8.java
+++ b/src/main/java/com/android/tools/r8/D8.java
@@ -296,7 +296,7 @@
       // which will construct a graph lens.
       if (options.isGeneratingDex() && !options.mainDexKeepRules.isEmpty()) {
         timing.begin("Generate main-dex list");
-        appView.dexItemFactory().clearTypeElementsCache();
+        appView.getTypeElementFactory().clearTypeElementsCache();
         MainDexInfo mainDexInfo =
             new GenerateMainDexList(options).traceMainDexForD8(appView, executor);
         appView.setAppInfo(appView.appInfo().rebuildWithMainDexInfo(mainDexInfo));
diff --git a/src/main/java/com/android/tools/r8/R8.java b/src/main/java/com/android/tools/r8/R8.java
index 6c77797..7d74e71 100644
--- a/src/main/java/com/android/tools/r8/R8.java
+++ b/src/main/java/com/android/tools/r8/R8.java
@@ -496,7 +496,7 @@
       // The class type lattice elements include information about the interfaces that a class
       // implements. This information can change as a result of vertical class merging, so we need
       // to clear the cache, so that we will recompute the type lattice elements.
-      appView.dexItemFactory().clearTypeElementsCache();
+      appView.getTypeElementFactory().clearTypeElementsCache();
 
       // This pass attempts to reduce the number of nests and nest size to allow further passes, and
       // should therefore be run after the publicizer.
@@ -534,7 +534,7 @@
       appView.appInfo().getMainDexInfo().clearTracedMethodRoots();
 
       // None of the optimizations above should lead to the creation of type lattice elements.
-      assert appView.dexItemFactory().verifyNoCachedTypeElements();
+      assert appView.getTypeElementFactory().verifyNoCachedTypeElements();
       assert appView.checkForTesting(() -> allReferencesAssignedApiLevel(appViewWithLiveness));
 
       // Collect switch maps and ordinals maps.
@@ -558,7 +558,7 @@
           appView, ALLOW_MISSING_ENUM_UNBOXING_UTILITY_METHODS);
 
       // Clear the reference type lattice element cache to reduce memory pressure.
-      appView.dexItemFactory().clearTypeElementsCache();
+      appView.getTypeElementFactory().clearTypeElementsCache();
 
       // At this point all code has been mapped according to the graph lens. We cannot remove the
       // graph lens entirely, though, since it is needed for mapping all field and method signatures
@@ -788,12 +788,12 @@
       }
 
       if (!appView.hasCfByteCodePassThroughMethods()) {
-        assert appView.dexItemFactory().verifyNoCachedTypeElements();
+        assert appView.getTypeElementFactory().verifyNoCachedTypeElements();
 
         if (appView.hasLiveness()) {
           VerticalClassMerger.createForIntermediateClassMerging(appView.withLiveness(), timing)
               .runIfNecessary(executorService, timing);
-          assert appView.dexItemFactory().verifyNoCachedTypeElements();
+          assert appView.getTypeElementFactory().verifyNoCachedTypeElements();
         }
 
         genericContextBuilderBeforeFinalMerging = GenericSignatureContextBuilder.create(appView);
@@ -807,12 +807,12 @@
                 finalRuntimeTypeCheckInfoBuilder != null
                     ? finalRuntimeTypeCheckInfoBuilder.build(appView.graphLens())
                     : null);
-        assert appView.dexItemFactory().verifyNoCachedTypeElements();
+        assert appView.getTypeElementFactory().verifyNoCachedTypeElements();
 
         if (appView.hasLiveness()) {
           VerticalClassMerger.createForFinalClassMerging(appView.withLiveness(), timing)
               .runIfNecessary(executorService, timing);
-          assert appView.dexItemFactory().verifyNoCachedTypeElements();
+          assert appView.getTypeElementFactory().verifyNoCachedTypeElements();
 
           new SingleCallerInliner(appViewWithLiveness).runIfNecessary(executorService, timing);
           new ProtoNormalizer(appViewWithLiveness).run(executorService, timing);
@@ -905,7 +905,7 @@
 
       LirConverter.finalizeLirToOutputFormat(appView, timing, executorService);
       LirConverter.finalizeClasspathLirToOutputFormat(appView, timing, executorService);
-      assert appView.dexItemFactory().verifyNoCachedTypeElements();
+      assert appView.getTypeElementFactory().verifyNoCachedTypeElements();
 
       // Generate the resulting application resources.
       writeKeepDeclarationsToConfigurationConsumer(keepDeclarations);
diff --git a/src/main/java/com/android/tools/r8/R8Partial.java b/src/main/java/com/android/tools/r8/R8Partial.java
index e48de4c..afbc968 100644
--- a/src/main/java/com/android/tools/r8/R8Partial.java
+++ b/src/main/java/com/android/tools/r8/R8Partial.java
@@ -223,8 +223,7 @@
     }
     r8Builder.validate();
     R8Command r8Command =
-        r8Builder.makeR8Command(
-            options.dexItemFactory().clearTypeElementsCache(), options.getProguardConfiguration());
+        r8Builder.makeR8Command(options.dexItemFactory(), options.getProguardConfiguration());
     AndroidApp r8App = r8Command.getInputApp();
     InternalOptions r8Options = r8Command.getInternalOptions();
     forwardOptions(r8Options);
diff --git a/src/main/java/com/android/tools/r8/graph/AppView.java b/src/main/java/com/android/tools/r8/graph/AppView.java
index 47b2e63..a8665ac 100644
--- a/src/main/java/com/android/tools/r8/graph/AppView.java
+++ b/src/main/java/com/android/tools/r8/graph/AppView.java
@@ -28,6 +28,7 @@
 import com.android.tools.r8.ir.analysis.proto.GeneratedMessageLiteBuilderShrinker;
 import com.android.tools.r8.ir.analysis.proto.GeneratedMessageLiteShrinker;
 import com.android.tools.r8.ir.analysis.proto.ProtoShrinker;
+import com.android.tools.r8.ir.analysis.type.TypeElementFactory;
 import com.android.tools.r8.ir.analysis.value.AbstractValueFactory;
 import com.android.tools.r8.ir.analysis.value.AbstractValueJoiner.AbstractValueConstantPropagationJoiner;
 import com.android.tools.r8.ir.analysis.value.AbstractValueJoiner.AbstractValueFieldJoiner;
@@ -155,6 +156,9 @@
   private final Map<DexType, DexValueString> sourceDebugExtensions = new IdentityHashMap<>();
   private final Map<DexType, String> sourceFileForPrunedTypes = new IdentityHashMap<>();
 
+  // Types.
+  private TypeElementFactory typeElementFactory = new TypeElementFactory();
+
   private SeedMapper applyMappingSeedMapper;
 
   private R8ResourceShrinkerState resourceShrinkerState = null;
@@ -352,6 +356,10 @@
     this.methodResolutionOptimizationInfoCollection = getMethodResolutionOptimizationInfoCollection;
   }
 
+  public TypeElementFactory getTypeElementFactory() {
+    return typeElementFactory;
+  }
+
   public InstanceFieldInitializationInfoFactory instanceFieldInitializationInfoFactory() {
     return instanceFieldInitializationInfoFactory;
   }
diff --git a/src/main/java/com/android/tools/r8/graph/DexItemFactory.java b/src/main/java/com/android/tools/r8/graph/DexItemFactory.java
index 545981b..9df4ea2 100644
--- a/src/main/java/com/android/tools/r8/graph/DexItemFactory.java
+++ b/src/main/java/com/android/tools/r8/graph/DexItemFactory.java
@@ -3,7 +3,6 @@
 // BSD-style license that can be found in the LICENSE file.
 package com.android.tools.r8.graph;
 
-import static com.android.tools.r8.ir.analysis.type.ClassTypeElement.computeLeastUpperBoundOfInterfaces;
 import static com.android.tools.r8.utils.ConsumerUtils.emptyConsumer;
 
 import com.android.tools.r8.dex.Constants;
@@ -19,11 +18,6 @@
 import com.android.tools.r8.graph.DexDebugEvent.SetPositionFrame;
 import com.android.tools.r8.graph.DexDebugEvent.SetPrologueEnd;
 import com.android.tools.r8.graph.DexMethodHandle.MethodHandleType;
-import com.android.tools.r8.ir.analysis.type.ArrayTypeElement;
-import com.android.tools.r8.ir.analysis.type.ClassTypeElement;
-import com.android.tools.r8.ir.analysis.type.InterfaceCollection;
-import com.android.tools.r8.ir.analysis.type.Nullability;
-import com.android.tools.r8.ir.analysis.type.ReferenceTypeElement;
 import com.android.tools.r8.ir.analysis.type.TypeElement;
 import com.android.tools.r8.ir.code.Position;
 import com.android.tools.r8.ir.code.Value;
@@ -35,7 +29,6 @@
 import com.android.tools.r8.synthesis.SyntheticNaming;
 import com.android.tools.r8.utils.ArrayUtils;
 import com.android.tools.r8.utils.DescriptorUtils;
-import com.android.tools.r8.utils.LRUCacheTable;
 import com.android.tools.r8.utils.ListUtils;
 import com.android.tools.r8.utils.SetUtils;
 import com.google.common.collect.BiMap;
@@ -119,14 +112,6 @@
   public final DexDebugEvent.Default zeroChangeDefaultEvent = createDefault(0, 0);
   public final DexDebugEvent.Default oneChangeDefaultEvent = createDefault(1, 1);
 
-  // ReferenceTypeElement canonicalization.
-  private final ConcurrentHashMap<DexType, ReferenceTypeElement> referenceTypes =
-      new ConcurrentHashMap<>();
-  private final ConcurrentHashMap<DexType, InterfaceCollection> classTypeInterfaces =
-      new ConcurrentHashMap<>();
-  public final LRUCacheTable<InterfaceCollection, InterfaceCollection, InterfaceCollection>
-      leastUpperBoundOfInterfacesTable = LRUCacheTable.create(8, 8);
-
   // Internal type containing only the null value.
   public static final DexType nullValueType = new DexType(new DexString("NULL"));
 
@@ -3642,92 +3627,6 @@
     return method.name == classConstructorMethodName;
   }
 
-  public DexItemFactory clearTypeElementsCache() {
-    referenceTypes.clear();
-    classTypeInterfaces.clear();
-    leastUpperBoundOfInterfacesTable.clear();
-    return this;
-  }
-
-  public boolean verifyNoCachedTypeElements() {
-    assert referenceTypes.isEmpty();
-    assert classTypeInterfaces.isEmpty();
-    assert leastUpperBoundOfInterfacesTable.isEmpty();
-    return true;
-  }
-
-  public ReferenceTypeElement createReferenceTypeElement(
-      DexType type, Nullability nullability, AppView<?> appView) {
-    // Class case:
-    // If two concurrent threads will try to create the same class-type the concurrent hash map will
-    // synchronize on the type in .computeIfAbsent and only a single class type is created.
-    //
-    // Array case:
-    // Arrays will create a lattice element for its base type thus we take special care here.
-    // Multiple threads may race recursively to create a base type. We have two cases:
-    // (i)  If base type is class type and the threads will race to create the class type but only a
-    //      single one will be created (Class case).
-    // (ii) If base is ArrayLattice case we can use our induction hypothesis to get that only one
-    //      element is created for us up to this case. Threads will now race to return from the
-    //      latest recursive call and fight to get access to .computeIfAbsent to add the
-    //      ArrayTypeElement but only one will enter. The property that only one
-    //      ArrayTypeElement is created per level therefore holds inductively.
-    TypeElement memberType = null;
-    if (type.isArrayType()) {
-      ReferenceTypeElement existing = referenceTypes.get(type);
-      if (existing != null) {
-        return existing.getOrCreateVariant(nullability);
-      }
-      memberType =
-          TypeElement.fromDexType(
-              type.toArrayElementType(this), Nullability.maybeNull(), appView, true);
-    }
-    TypeElement finalMemberType = memberType;
-    return referenceTypes
-        .computeIfAbsent(
-            type,
-            t -> {
-              if (type.isClassType()) {
-                if (!appView.enableWholeProgramOptimizations()) {
-                  // Don't reason at the level of interfaces in D8.
-                  return ClassTypeElement.createForD8(type, nullability);
-                }
-                assert appView.appInfo().hasClassHierarchy();
-                if (appView.isInterface(type).isTrue()) {
-                  return ClassTypeElement.create(
-                      objectType,
-                      nullability,
-                      appView.withClassHierarchy(),
-                      InterfaceCollection.singleton(type));
-                }
-                // In theory, `interfaces` is the least upper bound of implemented interfaces.
-                // It is expensive to walk through type hierarchy; collect implemented interfaces;
-                // and compute the least upper bound of two interface sets. Hence, lazy
-                // computations. Most likely during lattice join. See {@link
-                // ClassTypeElement#getInterfaces}.
-                return ClassTypeElement.create(type, nullability, appView.withClassHierarchy());
-              }
-              assert type.isArrayType();
-              return ArrayTypeElement.create(finalMemberType, nullability);
-            })
-        .getOrCreateVariant(nullability);
-  }
-
-  public InterfaceCollection getLeastUpperBoundOfImplementedInterfacesOrDefault(
-      DexType type, InterfaceCollection defaultValue) {
-    return classTypeInterfaces.getOrDefault(type, defaultValue);
-  }
-
-  public InterfaceCollection getOrComputeLeastUpperBoundOfImplementedInterfaces(
-      DexType type, AppView<? extends AppInfoWithClassHierarchy> appView) {
-    return classTypeInterfaces.computeIfAbsent(
-        type,
-        t -> {
-          InterfaceCollection itfs = appView.appInfo().implementedInterfaces(t);
-          return computeLeastUpperBoundOfInterfaces(appView, itfs, itfs);
-        });
-  }
-
   @Deprecated
   synchronized public void forAllTypes(Consumer<DexType> f) {
     new ArrayList<>(types.values()).forEach(f);
diff --git a/src/main/java/com/android/tools/r8/horizontalclassmerging/HorizontalClassMerger.java b/src/main/java/com/android/tools/r8/horizontalclassmerging/HorizontalClassMerger.java
index 40e2f1c..51c282f 100644
--- a/src/main/java/com/android/tools/r8/horizontalclassmerging/HorizontalClassMerger.java
+++ b/src/main/java/com/android/tools/r8/horizontalclassmerging/HorizontalClassMerger.java
@@ -73,7 +73,7 @@
       assert ArtProfileCompletenessChecker.verify(appView);
 
       // Clear type elements cache after IR building.
-      appView.dexItemFactory().clearTypeElementsCache();
+      appView.getTypeElementFactory().clearTypeElementsCache();
       appView.notifyOptimizationFinishedForTesting();
     } else {
       appView.setHorizontallyMergedClasses(HorizontallyMergedClasses.empty());
@@ -387,7 +387,7 @@
     for (ClassMerger.Builder classMergerBuilder : classMergerBuilders) {
       classMergers.add(classMergerBuilder.build(lensBuilder));
     }
-    appView.dexItemFactory().clearTypeElementsCache();
+    appView.getTypeElementFactory().clearTypeElementsCache();
     return classMergers;
   }
 
@@ -407,7 +407,7 @@
           syntheticInitializerConverterBuilder,
           virtuallyMergedMethodsKeepInfoConsumer);
     }
-    appView.dexItemFactory().clearTypeElementsCache();
+    appView.getTypeElementFactory().clearTypeElementsCache();
     return prunedItemsBuilder.build();
   }
 
diff --git a/src/main/java/com/android/tools/r8/horizontalclassmerging/UndoConstructorInlining.java b/src/main/java/com/android/tools/r8/horizontalclassmerging/UndoConstructorInlining.java
index af621ab..94e598d 100644
--- a/src/main/java/com/android/tools/r8/horizontalclassmerging/UndoConstructorInlining.java
+++ b/src/main/java/com/android/tools/r8/horizontalclassmerging/UndoConstructorInlining.java
@@ -123,7 +123,7 @@
         computeStronglyConnectedComponents();
     new LirRewriter(appView, ensureConstructorsOnClasses, stronglyConnectedComponents)
         .run(executorService);
-    appView.dexItemFactory().clearTypeElementsCache();
+    appView.getTypeElementFactory().clearTypeElementsCache();
   }
 
   private void ensureConstructorsOnSubclasses(
diff --git a/src/main/java/com/android/tools/r8/horizontalclassmerging/code/SyntheticInitializerConverter.java b/src/main/java/com/android/tools/r8/horizontalclassmerging/code/SyntheticInitializerConverter.java
index 4ec2c1b..84f0b5d 100644
--- a/src/main/java/com/android/tools/r8/horizontalclassmerging/code/SyntheticInitializerConverter.java
+++ b/src/main/java/com/android/tools/r8/horizontalclassmerging/code/SyntheticInitializerConverter.java
@@ -38,14 +38,14 @@
 
   public void convertClassInitializers(ExecutorService executorService) throws ExecutionException {
     if (!classInitializers.isEmpty()) {
-      assert appView.dexItemFactory().verifyNoCachedTypeElements();
+      assert appView.getTypeElementFactory().verifyNoCachedTypeElements();
       IRConverter converter = new IRConverter(appView);
       ThreadUtils.processItems(
           classInitializers,
           method -> processMethod(method, converter),
           appView.options().getThreadingModule(),
           executorService);
-      appView.dexItemFactory().clearTypeElementsCache();
+      appView.getTypeElementFactory().clearTypeElementsCache();
     }
   }
 
diff --git a/src/main/java/com/android/tools/r8/ir/analysis/type/ClassTypeElement.java b/src/main/java/com/android/tools/r8/ir/analysis/type/ClassTypeElement.java
index a9ee774..8447dee 100644
--- a/src/main/java/com/android/tools/r8/ir/analysis/type/ClassTypeElement.java
+++ b/src/main/java/com/android/tools/r8/ir/analysis/type/ClassTypeElement.java
@@ -100,7 +100,7 @@
       assert appView != null;
       assert appView.enableWholeProgramOptimizations();
       return appView
-          .dexItemFactory()
+          .getTypeElementFactory()
           .getOrComputeLeastUpperBoundOfImplementedInterfaces(type, appView);
     }
     return lazyInterfaces;
@@ -113,7 +113,7 @@
     }
     InterfaceCollection defaultImplementedInterfaces =
         appView
-            .dexItemFactory()
+            .getTypeElementFactory()
             .getLeastUpperBoundOfImplementedInterfacesOrDefault(getClassType(), null);
     if (ObjectUtils.identical(lazyInterfaces, defaultImplementedInterfaces)) {
       return true;
@@ -408,7 +408,7 @@
             : computeLeastUpperBoundOfInterfaces(appView, interfaces, otherInterfaces);
     InterfaceCollection lubInterfacesDefault =
         appView
-            .dexItemFactory()
+            .getTypeElementFactory()
             .getOrComputeLeastUpperBoundOfImplementedInterfaces(lubType, appView);
     Nullability lubNullability = nullability().join(nullability);
 
@@ -580,13 +580,13 @@
       return InterfaceCollection.empty();
     }
     // Synchronization is required, see b/242286733.
-    synchronized (appView.dexItemFactory().leastUpperBoundOfInterfacesTable) {
+    synchronized (appView.getTypeElementFactory().leastUpperBoundOfInterfacesTable) {
       InterfaceCollection cached =
-          appView.dexItemFactory().leastUpperBoundOfInterfacesTable.get(s1, s2);
+          appView.getTypeElementFactory().leastUpperBoundOfInterfacesTable.get(s1, s2);
       if (cached != null) {
         return cached;
       }
-      cached = appView.dexItemFactory().leastUpperBoundOfInterfacesTable.get(s2, s1);
+      cached = appView.getTypeElementFactory().leastUpperBoundOfInterfacesTable.get(s2, s1);
       if (cached != null) {
         return cached;
       }
@@ -643,8 +643,8 @@
     InterfaceCollection lub = lubBuilder.build();
     // Cache the computation result only if the given two sets of interfaces are different.
     if (!s1.equals(s2)) {
-      synchronized (appView.dexItemFactory().leastUpperBoundOfInterfacesTable) {
-        appView.dexItemFactory().leastUpperBoundOfInterfacesTable.put(s1, s2, lub);
+      synchronized (appView.getTypeElementFactory().leastUpperBoundOfInterfacesTable) {
+        appView.getTypeElementFactory().leastUpperBoundOfInterfacesTable.put(s1, s2, lub);
       }
     }
     return lub;
diff --git a/src/main/java/com/android/tools/r8/ir/analysis/type/TypeElement.java b/src/main/java/com/android/tools/r8/ir/analysis/type/TypeElement.java
index 2b4ce3b..b9b357b 100644
--- a/src/main/java/com/android/tools/r8/ir/analysis/type/TypeElement.java
+++ b/src/main/java/com/android/tools/r8/ir/analysis/type/TypeElement.java
@@ -436,7 +436,7 @@
       DexType type, Nullability nullability, AppView<?> appView) {
     assert type.isClassType();
     return appView
-        .dexItemFactory()
+        .getTypeElementFactory()
         .createReferenceTypeElement(type, nullability, appView)
         .asClassType();
   }
@@ -455,7 +455,7 @@
     if (type.isPrimitiveType()) {
       return PrimitiveTypeElement.fromDexType(type, asArrayElementType);
     }
-    return appView.dexItemFactory().createReferenceTypeElement(type, nullability, appView);
+    return appView.getTypeElementFactory().createReferenceTypeElement(type, nullability, appView);
   }
 
   public boolean isValueTypeCompatible(TypeElement other) {
diff --git a/src/main/java/com/android/tools/r8/ir/analysis/type/TypeElementFactory.java b/src/main/java/com/android/tools/r8/ir/analysis/type/TypeElementFactory.java
new file mode 100644
index 0000000..29690b0
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/ir/analysis/type/TypeElementFactory.java
@@ -0,0 +1,111 @@
+// Copyright (c) 2025, 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.ir.analysis.type;
+
+import static com.android.tools.r8.ir.analysis.type.ClassTypeElement.computeLeastUpperBoundOfInterfaces;
+
+import com.android.tools.r8.graph.AppInfoWithClassHierarchy;
+import com.android.tools.r8.graph.AppView;
+import com.android.tools.r8.graph.DexType;
+import com.android.tools.r8.utils.LRUCacheTable;
+import java.util.concurrent.ConcurrentHashMap;
+
+public class TypeElementFactory {
+
+  // ReferenceTypeElement canonicalization.
+  private final ConcurrentHashMap<DexType, ReferenceTypeElement> referenceTypes =
+      new ConcurrentHashMap<>();
+  private final ConcurrentHashMap<DexType, InterfaceCollection> classTypeInterfaces =
+      new ConcurrentHashMap<>();
+  public final LRUCacheTable<InterfaceCollection, InterfaceCollection, InterfaceCollection>
+      leastUpperBoundOfInterfacesTable = LRUCacheTable.create(8, 8);
+
+  public void clearTypeElementsCache() {
+    referenceTypes.clear();
+    classTypeInterfaces.clear();
+    leastUpperBoundOfInterfacesTable.clear();
+  }
+
+  public boolean verifyNoCachedTypeElements() {
+    assert referenceTypes.isEmpty();
+    assert classTypeInterfaces.isEmpty();
+    assert leastUpperBoundOfInterfacesTable.isEmpty();
+    return true;
+  }
+
+  public ReferenceTypeElement createReferenceTypeElement(
+      DexType type, Nullability nullability, AppView<?> appView) {
+    // Class case:
+    // If two concurrent threads will try to create the same class-type the concurrent hash map will
+    // synchronize on the type in .computeIfAbsent and only a single class type is created.
+    //
+    // Array case:
+    // Arrays will create a lattice element for its base type thus we take special care here.
+    // Multiple threads may race recursively to create a base type. We have two cases:
+    // (i)  If base type is class type and the threads will race to create the class type but only a
+    //      single one will be created (Class case).
+    // (ii) If base is ArrayLattice case we can use our induction hypothesis to get that only one
+    //      element is created for us up to this case. Threads will now race to return from the
+    //      latest recursive call and fight to get access to .computeIfAbsent to add the
+    //      ArrayTypeElement but only one will enter. The property that only one
+    //      ArrayTypeElement is created per level therefore holds inductively.
+    TypeElement memberType = null;
+    if (type.isArrayType()) {
+      ReferenceTypeElement existing = referenceTypes.get(type);
+      if (existing != null) {
+        return existing.getOrCreateVariant(nullability);
+      }
+      memberType =
+          TypeElement.fromDexType(
+              type.toArrayElementType(appView.dexItemFactory()),
+              Nullability.maybeNull(),
+              appView,
+              true);
+    }
+    TypeElement finalMemberType = memberType;
+    return referenceTypes
+        .computeIfAbsent(
+            type,
+            t -> {
+              if (type.isClassType()) {
+                if (!appView.enableWholeProgramOptimizations()) {
+                  // Don't reason at the level of interfaces in D8.
+                  return ClassTypeElement.createForD8(type, nullability);
+                }
+                assert appView.appInfo().hasClassHierarchy();
+                if (appView.isInterface(type).isTrue()) {
+                  return ClassTypeElement.create(
+                      appView.dexItemFactory().objectType,
+                      nullability,
+                      appView.withClassHierarchy(),
+                      InterfaceCollection.singleton(type));
+                }
+                // In theory, `interfaces` is the least upper bound of implemented interfaces.
+                // It is expensive to walk through type hierarchy; collect implemented interfaces;
+                // and compute the least upper bound of two interface sets. Hence, lazy
+                // computations. Most likely during lattice join. See {@link
+                // ClassTypeElement#getInterfaces}.
+                return ClassTypeElement.create(type, nullability, appView.withClassHierarchy());
+              }
+              assert type.isArrayType();
+              return ArrayTypeElement.create(finalMemberType, nullability);
+            })
+        .getOrCreateVariant(nullability);
+  }
+
+  public InterfaceCollection getLeastUpperBoundOfImplementedInterfacesOrDefault(
+      DexType type, InterfaceCollection defaultValue) {
+    return classTypeInterfaces.getOrDefault(type, defaultValue);
+  }
+
+  public InterfaceCollection getOrComputeLeastUpperBoundOfImplementedInterfaces(
+      DexType type, AppView<? extends AppInfoWithClassHierarchy> appView) {
+    return classTypeInterfaces.computeIfAbsent(
+        type,
+        t -> {
+          InterfaceCollection itfs = appView.appInfo().implementedInterfaces(t);
+          return computeLeastUpperBoundOfInterfaces(appView, itfs, itfs);
+        });
+  }
+}
diff --git a/src/main/java/com/android/tools/r8/ir/conversion/CfToLirConverter.java b/src/main/java/com/android/tools/r8/ir/conversion/CfToLirConverter.java
index e60a217..e00941f 100644
--- a/src/main/java/com/android/tools/r8/ir/conversion/CfToLirConverter.java
+++ b/src/main/java/com/android/tools/r8/ir/conversion/CfToLirConverter.java
@@ -160,7 +160,7 @@
 
     // Conversion to LIR via IR will allocate type elements.
     // They are not needed after construction so remove them again.
-    appView.dexItemFactory().clearTypeElementsCache();
+    appView.getTypeElementFactory().clearTypeElementsCache();
   }
 
   private void processIdentifierNameStrings(
diff --git a/src/main/java/com/android/tools/r8/ir/conversion/LirConverter.java b/src/main/java/com/android/tools/r8/ir/conversion/LirConverter.java
index 22bb189..009008c 100644
--- a/src/main/java/com/android/tools/r8/ir/conversion/LirConverter.java
+++ b/src/main/java/com/android/tools/r8/ir/conversion/LirConverter.java
@@ -104,7 +104,7 @@
         executorService);
     // Conversion to LIR via IR will allocate type elements.
     // They are not needed after construction so remove them again.
-    appView.dexItemFactory().clearTypeElementsCache();
+    appView.getTypeElementFactory().clearTypeElementsCache();
   }
 
   public static void rewriteLirWithLens(
@@ -168,7 +168,7 @@
         executorService);
 
     // Clear the reference type cache after conversion to reduce memory pressure.
-    appView.dexItemFactory().clearTypeElementsCache();
+    appView.getTypeElementFactory().clearTypeElementsCache();
   }
 
   public static void rewriteLirMethodWithLens(
@@ -233,7 +233,7 @@
     merger.end();
     timing.end();
     // Clear the reference type cache after conversion to reduce memory pressure.
-    appView.dexItemFactory().clearTypeElementsCache();
+    appView.getTypeElementFactory().clearTypeElementsCache();
     // At this point all code has been mapped according to the graph lens.
     assert appView.graphLens().isMemberRebindingIdentityLens()
         && appView.graphLens().asMemberRebindingIdentityLens().getPrevious() == appView.codeLens();
@@ -322,7 +322,7 @@
     timing.end();
 
     // Clear the reference type cache after conversion to reduce memory pressure.
-    appView.dexItemFactory().clearTypeElementsCache();
+    appView.getTypeElementFactory().clearTypeElementsCache();
   }
 
   private static void finalizeClasspathLirMethodToOutputFormat(
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/desugaredlibrary/R8LibraryDesugaring.java b/src/main/java/com/android/tools/r8/ir/desugar/desugaredlibrary/R8LibraryDesugaring.java
index 878aa12..83e5c15 100644
--- a/src/main/java/com/android/tools/r8/ir/desugar/desugaredlibrary/R8LibraryDesugaring.java
+++ b/src/main/java/com/android/tools/r8/ir/desugar/desugaredlibrary/R8LibraryDesugaring.java
@@ -174,7 +174,7 @@
             executorService);
     timings.forEach(merger::add);
     merger.end();
-    appView.dexItemFactory().clearTypeElementsCache();
+    appView.getTypeElementFactory().clearTypeElementsCache();
 
     // Move the pending methods and mark them live and ready for tracing.
     timing.begin("Postlude");
diff --git a/src/main/java/com/android/tools/r8/optimize/BridgeHoistingToSharedSyntheticSuperClass.java b/src/main/java/com/android/tools/r8/optimize/BridgeHoistingToSharedSyntheticSuperClass.java
index 70c47e9..1ce004a 100644
--- a/src/main/java/com/android/tools/r8/optimize/BridgeHoistingToSharedSyntheticSuperClass.java
+++ b/src/main/java/com/android/tools/r8/optimize/BridgeHoistingToSharedSyntheticSuperClass.java
@@ -84,7 +84,7 @@
       updateArtProfiles(groups);
       new BridgeHoisting(appView).run(executorService, timing);
     }
-    appView.dexItemFactory().clearTypeElementsCache();
+    appView.getTypeElementFactory().clearTypeElementsCache();
   }
 
   /** Returns the set of (non-singleton) groups that have the same superclass. */
diff --git a/src/main/java/com/android/tools/r8/optimize/MemberRebindingAnalysis.java b/src/main/java/com/android/tools/r8/optimize/MemberRebindingAnalysis.java
index 42adbc6..3824b63 100644
--- a/src/main/java/com/android/tools/r8/optimize/MemberRebindingAnalysis.java
+++ b/src/main/java/com/android/tools/r8/optimize/MemberRebindingAnalysis.java
@@ -387,7 +387,7 @@
     MemberRebindingLens memberRebindingLens = lensBuilder.build();
     appView.setGraphLens(memberRebindingLens);
     eventConsumer.finished(appView, memberRebindingLens);
-    appView.dexItemFactory().clearTypeElementsCache();
+    appView.getTypeElementFactory().clearTypeElementsCache();
     appView.notifyOptimizationFinishedForTesting();
   }
 
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 e807f0d..bd1b30f 100644
--- a/src/main/java/com/android/tools/r8/shaking/TreePruner.java
+++ b/src/main/java/com/android/tools/r8/shaking/TreePruner.java
@@ -447,7 +447,7 @@
       DirectMappedDexApplication application, ExecutorService executorService)
       throws ExecutionException {
     // Clear the type elements cache due to redundant interface removal.
-    appView.dexItemFactory().clearTypeElementsCache();
+    appView.getTypeElementFactory().clearTypeElementsCache();
 
     OptimizationFeedback feedback = OptimizationFeedbackSimple.getInstance();
     feedback.fixupOptimizationInfos(
@@ -468,7 +468,7 @@
         });
 
     // Verify that the fixup did not lead to the caching of any elements.
-    assert appView.dexItemFactory().verifyNoCachedTypeElements();
+    assert appView.getTypeElementFactory().verifyNoCachedTypeElements();
   }
 
   private boolean verifyNoDeadFields(DexProgramClass clazz) {
diff --git a/src/main/java/com/android/tools/r8/verticalclassmerging/VerticalClassMerger.java b/src/main/java/com/android/tools/r8/verticalclassmerging/VerticalClassMerger.java
index 72ce1bc..a3b4137 100644
--- a/src/main/java/com/android/tools/r8/verticalclassmerging/VerticalClassMerger.java
+++ b/src/main/java/com/android/tools/r8/verticalclassmerging/VerticalClassMerger.java
@@ -156,7 +156,7 @@
     // Finally update the code lens to signal that the code is fully up to date.
     markRewrittenWithLens(executorService, timing);
 
-    appView.dexItemFactory().clearTypeElementsCache();
+    appView.getTypeElementFactory().clearTypeElementsCache();
     appView.notifyOptimizationFinishedForTesting();
   }