Merge commit '003f5b447a7d850ac3bae1f499920fb5b63a476d' into dev-release
diff --git a/src/main/java/com/android/tools/r8/D8Command.java b/src/main/java/com/android/tools/r8/D8Command.java
index 92f194a..9673bc2 100644
--- a/src/main/java/com/android/tools/r8/D8Command.java
+++ b/src/main/java/com/android/tools/r8/D8Command.java
@@ -68,6 +68,7 @@
     private boolean intermediate = false;
     private DesugarGraphConsumer desugarGraphConsumer = null;
     private StringConsumer desugaredLibraryKeepRuleConsumer = null;
+    private String synthesizedClassPrefix = "";
 
     private Builder() {
       this(new DefaultD8DiagnosticsHandler());
@@ -168,6 +169,11 @@
       return CompilationMode.DEBUG;
     }
 
+    Builder setSynthesizedClassesPrefix(String prefix) {
+      synthesizedClassPrefix = prefix;
+      return self();
+    }
+
     @Override
     void validate() {
       Reporter reporter = getReporter();
@@ -227,6 +233,7 @@
           libraryConfiguration,
           getAssertionsConfiguration(),
           getOutputInspections(),
+          synthesizedClassPrefix,
           getThreadCount(),
           factory);
     }
@@ -238,6 +245,7 @@
   private final DesugarGraphConsumer desugarGraphConsumer;
   private final StringConsumer desugaredLibraryKeepRuleConsumer;
   private final DesugaredLibraryConfiguration libraryConfiguration;
+  private final String synthesizedClassPrefix;
   private final DexItemFactory factory;
 
   public static Builder builder() {
@@ -297,6 +305,7 @@
       DesugaredLibraryConfiguration libraryConfiguration,
       List<AssertionsConfiguration> assertionsConfiguration,
       List<Consumer<Inspector>> outputInspections,
+      String synthesizedClassPrefix,
       int threadCount,
       DexItemFactory factory) {
     super(
@@ -317,6 +326,7 @@
     this.desugarGraphConsumer = desugarGraphConsumer;
     this.desugaredLibraryKeepRuleConsumer = desugaredLibraryKeepRuleConsumer;
     this.libraryConfiguration = libraryConfiguration;
+    this.synthesizedClassPrefix = synthesizedClassPrefix;
     this.factory = factory;
   }
 
@@ -326,6 +336,7 @@
     desugarGraphConsumer = null;
     desugaredLibraryKeepRuleConsumer = null;
     libraryConfiguration = null;
+    synthesizedClassPrefix = null;
     factory = null;
   }
 
@@ -372,8 +383,8 @@
     internal.dexClassChecksumFilter = getDexClassChecksumFilter();
     internal.enableInheritanceClassInDexDistributor = isOptimizeMultidexForLinearAlloc();
 
-    // TODO(134732760): This is still work in progress.
     internal.desugaredLibraryConfiguration = libraryConfiguration;
+    internal.synthesizedClassPrefix = synthesizedClassPrefix;
     internal.desugaredLibraryKeepRuleConsumer = desugaredLibraryKeepRuleConsumer;
 
     // Default is to remove all javac generated assertion code when generating dex.
diff --git a/src/main/java/com/android/tools/r8/GenerateLintFiles.java b/src/main/java/com/android/tools/r8/GenerateLintFiles.java
index 830f6b6..a6d704b 100644
--- a/src/main/java/com/android/tools/r8/GenerateLintFiles.java
+++ b/src/main/java/com/android/tools/r8/GenerateLintFiles.java
@@ -145,7 +145,7 @@
               ParameterAnnotationsList.empty(),
               code,
               50);
-      if (method.accessFlags.isStatic()) {
+      if (method.isStatic() || method.isDirectMethod()) {
         directMethods.add(throwingMethod);
       } else {
         virtualMethods.add(throwingMethod);
diff --git a/src/main/java/com/android/tools/r8/L8Command.java b/src/main/java/com/android/tools/r8/L8Command.java
index c32fbb7..7741651 100644
--- a/src/main/java/com/android/tools/r8/L8Command.java
+++ b/src/main/java/com/android/tools/r8/L8Command.java
@@ -178,8 +178,10 @@
     assert internal.enableInheritanceClassInDexDistributor;
     internal.enableInheritanceClassInDexDistributor = false;
 
-    // TODO(134732760): This is still work in progress.
+    assert libraryConfiguration != null;
     internal.desugaredLibraryConfiguration = libraryConfiguration;
+    internal.synthesizedClassPrefix =
+        libraryConfiguration.getSynthesizedLibraryClassesPackagePrefix();
 
     // Default is to remove all javac generated assertion code when generating dex.
     assert internal.assertionsConfiguration == null;
@@ -289,6 +291,8 @@
         R8Command.Builder r8Builder =
             R8Command.builder(getReporter())
                 .addProgramResourceProvider(desugaredLibrary)
+                .setSynthesizedClassesPrefix(
+                    libraryConfiguration.getSynthesizedLibraryClassesPackagePrefix())
                 .setMinApiLevel(getMinApiLevel())
                 .setMode(getMode())
                 .setProgramConsumer(getProgramConsumer());
@@ -307,6 +311,8 @@
         D8Command.Builder d8Builder =
             D8Command.builder(getReporter())
                 .addProgramResourceProvider(desugaredLibrary)
+                .setSynthesizedClassesPrefix(
+                    libraryConfiguration.getSynthesizedLibraryClassesPackagePrefix())
                 .setMinApiLevel(getMinApiLevel())
                 .setMode(getMode())
                 .setProgramConsumer(getProgramConsumer());
diff --git a/src/main/java/com/android/tools/r8/PrintUses.java b/src/main/java/com/android/tools/r8/PrintUses.java
index 57f8395..4b310aa 100644
--- a/src/main/java/com/android/tools/r8/PrintUses.java
+++ b/src/main/java/com/android/tools/r8/PrintUses.java
@@ -18,7 +18,6 @@
 import com.android.tools.r8.graph.DexType;
 import com.android.tools.r8.graph.DexValue;
 import com.android.tools.r8.graph.DexValue.DexValueArray;
-import com.android.tools.r8.graph.DexValue.DexValueType;
 import com.android.tools.r8.graph.DirectMappedDexApplication;
 import com.android.tools.r8.graph.ResolutionResult;
 import com.android.tools.r8.graph.UseRegistry;
@@ -258,9 +257,9 @@
       }
       for (DexAnnotation annotation : method.annotations().annotations) {
         if (annotation.annotation.type == appInfo.dexItemFactory().annotationThrows) {
-          DexValueArray dexValues = (DexValueArray) annotation.annotation.elements[0].value;
+          DexValueArray dexValues = annotation.annotation.elements[0].value.asDexValueArray();
           for (DexValue dexValType : dexValues.getValues()) {
-            registerTypeReference(((DexValueType) dexValType).value);
+            registerTypeReference(dexValType.asDexValueType().value);
           }
         }
       }
diff --git a/src/main/java/com/android/tools/r8/R8.java b/src/main/java/com/android/tools/r8/R8.java
index 5223520..5bca79b1 100644
--- a/src/main/java/com/android/tools/r8/R8.java
+++ b/src/main/java/com/android/tools/r8/R8.java
@@ -34,7 +34,7 @@
 import com.android.tools.r8.inspector.internal.InspectorImpl;
 import com.android.tools.r8.ir.analysis.proto.GeneratedExtensionRegistryShrinker;
 import com.android.tools.r8.ir.conversion.IRConverter;
-import com.android.tools.r8.ir.desugar.BackportedMethodRewriter;
+import com.android.tools.r8.ir.desugar.DesugaredLibraryRetargeter;
 import com.android.tools.r8.ir.desugar.NestedPrivateMethodLense;
 import com.android.tools.r8.ir.desugar.R8NestBasedAccessDesugaring;
 import com.android.tools.r8.ir.optimize.AssertionsRewriter;
@@ -278,7 +278,7 @@
         MainDexListBuilder.checkForAssumedLibraryTypes(appView.appInfo());
       }
       if (!options.desugaredLibraryConfiguration.getRetargetCoreLibMember().isEmpty()) {
-        BackportedMethodRewriter.checkForAssumedLibraryTypes(appView);
+        DesugaredLibraryRetargeter.checkForAssumedLibraryTypes(appView);
       }
 
       List<ProguardConfigurationRule> synthesizedProguardRules = new ArrayList<>();
@@ -411,7 +411,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().clearTypeLatticeElementsCache();
+      appView.dexItemFactory().clearTypeElementsCache();
 
       if (options.getProguardConfiguration().isAccessModificationAllowed()) {
         GraphLense publicizedLense =
@@ -514,7 +514,7 @@
       }
 
       // None of the optimizations above should lead to the creation of type lattice elements.
-      assert appView.dexItemFactory().verifyNoCachedTypeLatticeElements();
+      assert appView.dexItemFactory().verifyNoCachedTypeElements();
 
       // Collect switch maps and ordinals maps.
       if (options.enableEnumSwitchMapRemoval) {
@@ -536,7 +536,7 @@
       }
 
       // Clear the reference type lattice element cache to reduce memory pressure.
-      appView.dexItemFactory().clearTypeLatticeElementsCache();
+      appView.dexItemFactory().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
@@ -717,7 +717,7 @@
           // TODO(b/112437944): Avoid iterating the entire application to post-process every
           //  dynamicMethod() method.
           appView.withGeneratedMessageLiteShrinker(
-              shrinker -> shrinker.postOptimizeDynamicMethods(converter));
+              shrinker -> shrinker.postOptimizeDynamicMethods(converter, timing));
 
           // If proto shrinking is enabled, we need to post-process every
           // findLiteExtensionByNumber() method. This ensures that there are no references to dead
@@ -725,7 +725,7 @@
           // TODO(b/112437944): Avoid iterating the entire application to post-process every
           //  findLiteExtensionByNumber() method.
           appView.withGeneratedExtensionRegistryShrinker(
-              shrinker -> shrinker.postOptimizeGeneratedExtensionRegistry(converter));
+              shrinker -> shrinker.postOptimizeGeneratedExtensionRegistry(converter, timing));
         }
       }
 
diff --git a/src/main/java/com/android/tools/r8/R8Command.java b/src/main/java/com/android/tools/r8/R8Command.java
index 611bc7a..fea04c7 100644
--- a/src/main/java/com/android/tools/r8/R8Command.java
+++ b/src/main/java/com/android/tools/r8/R8Command.java
@@ -104,6 +104,7 @@
     private GraphConsumer mainDexKeptGraphConsumer = null;
     private BiFunction<String, Long, Boolean> dexClassChecksumFilter = (name, checksum) -> true;
     private final List<FeatureSplit> featureSplits = new ArrayList<>();
+    private String synthesizedClassPrefix = "";
 
     private boolean allowPartiallyImplementedProguardOptions = false;
     private boolean allowTestProguardOptions =
@@ -146,6 +147,11 @@
       return CompilationMode.RELEASE;
     }
 
+    Builder setSynthesizedClassesPrefix(String prefix) {
+      synthesizedClassPrefix = prefix;
+      return self();
+    }
+
     /**
      * Disable tree shaking.
      *
@@ -572,6 +578,7 @@
               featureSplitConfiguration,
               getAssertionsConfiguration(),
               getOutputInspections(),
+              synthesizedClassPrefix,
               getThreadCount());
 
       return command;
@@ -654,6 +661,7 @@
   private final StringConsumer desugaredLibraryKeepRuleConsumer;
   private final DesugaredLibraryConfiguration libraryConfiguration;
   private final FeatureSplitConfiguration featureSplitConfiguration;
+  private final String synthesizedClassPrefix;
 
   /** Get a new {@link R8Command.Builder}. */
   public static Builder builder() {
@@ -731,6 +739,7 @@
       FeatureSplitConfiguration featureSplitConfiguration,
       List<AssertionsConfiguration> assertionsConfiguration,
       List<Consumer<Inspector>> outputInspections,
+      String synthesizedClassPrefix,
       int threadCount) {
     super(
         inputApp,
@@ -764,6 +773,7 @@
     this.desugaredLibraryKeepRuleConsumer = desugaredLibraryKeepRuleConsumer;
     this.libraryConfiguration = libraryConfiguration;
     this.featureSplitConfiguration = featureSplitConfiguration;
+    this.synthesizedClassPrefix = synthesizedClassPrefix;
   }
 
   private R8Command(boolean printHelp, boolean printVersion) {
@@ -784,6 +794,7 @@
     desugaredLibraryKeepRuleConsumer = null;
     libraryConfiguration = null;
     featureSplitConfiguration = null;
+    synthesizedClassPrefix = null;
   }
 
   /** Get the enable-tree-shaking state. */
@@ -910,8 +921,8 @@
 
     internal.enableInheritanceClassInDexDistributor = isOptimizeMultidexForLinearAlloc();
 
-    // TODO(134732760): This is still work in progress.
     internal.desugaredLibraryConfiguration = libraryConfiguration;
+    internal.synthesizedClassPrefix = synthesizedClassPrefix;
     internal.desugaredLibraryKeepRuleConsumer = desugaredLibraryKeepRuleConsumer;
 
     assert internal.threadCount == ThreadUtils.NOT_SPECIFIED;
diff --git a/src/main/java/com/android/tools/r8/ResourceShrinker.java b/src/main/java/com/android/tools/r8/ResourceShrinker.java
index efeccec..0affffd 100644
--- a/src/main/java/com/android/tools/r8/ResourceShrinker.java
+++ b/src/main/java/com/android/tools/r8/ResourceShrinker.java
@@ -174,19 +174,25 @@
     }
 
     private void processFieldValue(DexValue value) {
-      if (value instanceof DexValue.DexValueString) {
-        callback.referencedString(((DexValue.DexValueString) value).value.toString());
-      } else if (value instanceof DexValue.DexValueInt) {
-        int constantValue = ((DexValue.DexValueInt) value).getValue();
-        callback.referencedInt(constantValue);
-      } else if (value instanceof DexValue.DexValueArray) {
-        DexValue.DexValueArray arrayEncodedValue = (DexValue.DexValueArray) value;
-        for (DexValue encodedValue : arrayEncodedValue.getValues()) {
-          if (encodedValue instanceof DexValue.DexValueInt) {
-            int constantValue = ((DexValue.DexValueInt) encodedValue).getValue();
-            callback.referencedInt(constantValue);
+      switch (value.getValueKind()) {
+        case ARRAY:
+          for (DexValue elementValue : value.asDexValueArray().getValues()) {
+            if (elementValue.isDexValueInt()) {
+              callback.referencedInt(elementValue.asDexValueInt().getValue());
+            }
           }
-        }
+          break;
+
+        case INT:
+          callback.referencedInt(value.asDexValueInt().getValue());
+          break;
+
+        case STRING:
+          callback.referencedString(value.asDexValueString().value.toString());
+          break;
+
+        default:
+          // Intentionally empty.
       }
     }
 
@@ -286,22 +292,29 @@
     }
 
     private void processAnnotationValue(DexValue value) {
-      if (value instanceof DexValue.DexValueInt) {
-        DexValue.DexValueInt dexValueInt = (DexValue.DexValueInt) value;
-        callback.referencedInt(dexValueInt.value);
-      } else if (value instanceof DexValue.DexValueString) {
-        DexValue.DexValueString dexValueString = (DexValue.DexValueString) value;
-        callback.referencedString(dexValueString.value.toString());
-      } else if (value instanceof DexValue.DexValueArray) {
-        DexValue.DexValueArray dexValueArray = (DexValue.DexValueArray) value;
-        for (DexValue dexValue : dexValueArray.getValues()) {
-          processAnnotationValue(dexValue);
-        }
-      } else if (value instanceof DexValue.DexValueAnnotation) {
-        DexValue.DexValueAnnotation dexValueAnnotation = (DexValue.DexValueAnnotation) value;
-        for (DexAnnotationElement element : dexValueAnnotation.value.elements) {
-          processAnnotationValue(element.value);
-        }
+      switch (value.getValueKind()) {
+        case ANNOTATION:
+          for (DexAnnotationElement element : value.asDexValueAnnotation().value.elements) {
+            processAnnotationValue(element.value);
+          }
+          break;
+
+        case ARRAY:
+          for (DexValue elementValue : value.asDexValueArray().getValues()) {
+            processAnnotationValue(elementValue);
+          }
+          break;
+
+        case INT:
+          callback.referencedInt(value.asDexValueInt().value);
+          break;
+
+        case STRING:
+          callback.referencedString(value.asDexValueString().value.toString());
+          break;
+
+        default:
+          // Intentionally empty.
       }
     }
 
diff --git a/src/main/java/com/android/tools/r8/cf/FixedLocalValue.java b/src/main/java/com/android/tools/r8/cf/FixedLocalValue.java
index 1b68ef6..cc78305 100644
--- a/src/main/java/com/android/tools/r8/cf/FixedLocalValue.java
+++ b/src/main/java/com/android/tools/r8/cf/FixedLocalValue.java
@@ -18,7 +18,7 @@
   private final Phi phi;
 
   public FixedLocalValue(Phi phi) {
-    super(phi.getNumber(), phi.getTypeLattice(), phi.getLocalInfo());
+    super(phi.getNumber(), phi.getType(), phi.getLocalInfo());
     this.phi = phi;
   }
 
diff --git a/src/main/java/com/android/tools/r8/cf/TypeVerificationHelper.java b/src/main/java/com/android/tools/r8/cf/TypeVerificationHelper.java
index e77cba3..5b4d9f2 100644
--- a/src/main/java/com/android/tools/r8/cf/TypeVerificationHelper.java
+++ b/src/main/java/com/android/tools/r8/cf/TypeVerificationHelper.java
@@ -9,7 +9,7 @@
 import com.android.tools.r8.graph.DexItemFactory;
 import com.android.tools.r8.graph.DexType;
 import com.android.tools.r8.ir.analysis.type.Nullability;
-import com.android.tools.r8.ir.analysis.type.TypeLatticeElement;
+import com.android.tools.r8.ir.analysis.type.TypeElement;
 import com.android.tools.r8.ir.code.Argument;
 import com.android.tools.r8.ir.code.ConstNumber;
 import com.android.tools.r8.ir.code.IRCode;
@@ -182,15 +182,15 @@
       return types.iterator().next();
     }
     Iterator<DexType> iterator = types.iterator();
-    TypeLatticeElement result = getLatticeElement(iterator.next());
+    TypeElement result = toTypeElement(iterator.next());
     while (iterator.hasNext()) {
-      result = result.join(getLatticeElement(iterator.next()), appView);
+      result = result.join(toTypeElement(iterator.next()), appView);
     }
     // All types are reference types so the join is either a class or an array.
     if (result.isClassType()) {
-      return result.asClassTypeLatticeElement().getClassType();
+      return result.asClassType().getClassType();
     } else if (result.isArrayType()) {
-      return result.asArrayTypeLatticeElement().getArrayType(appView.dexItemFactory());
+      return result.asArrayType().toDexType(appView.dexItemFactory());
     }
     throw new CompilationError("Unexpected join " + result + " of types: " +
         String.join(", ",
@@ -215,8 +215,8 @@
     return createInitializedType(join(ImmutableSet.of(type1, type2)));
   }
 
-  private TypeLatticeElement getLatticeElement(DexType type) {
-    return TypeLatticeElement.fromDexType(type, Nullability.maybeNull(), appView);
+  private TypeElement toTypeElement(DexType type) {
+    return TypeElement.fromDexType(type, Nullability.maybeNull(), appView);
   }
 
   public Map<Value, TypeInfo> computeVerificationTypes() {
diff --git a/src/main/java/com/android/tools/r8/cf/code/CfConstNumber.java b/src/main/java/com/android/tools/r8/cf/code/CfConstNumber.java
index 8977303..da5d12a 100644
--- a/src/main/java/com/android/tools/r8/cf/code/CfConstNumber.java
+++ b/src/main/java/com/android/tools/r8/cf/code/CfConstNumber.java
@@ -128,7 +128,7 @@
 
   @Override
   public void buildIR(IRBuilder builder, CfState state, CfSourceCode code) {
-    builder.addConst(type.toPrimitiveTypeLattice(), state.push(type).register, value);
+    builder.addConst(type.toPrimitiveType(), state.push(type).register, value);
   }
 
   @Override
diff --git a/src/main/java/com/android/tools/r8/cf/code/CfInvokeDynamic.java b/src/main/java/com/android/tools/r8/cf/code/CfInvokeDynamic.java
index ef0ec1b..8f01356 100644
--- a/src/main/java/com/android/tools/r8/cf/code/CfInvokeDynamic.java
+++ b/src/main/java/com/android/tools/r8/cf/code/CfInvokeDynamic.java
@@ -10,14 +10,6 @@
 import com.android.tools.r8.graph.DexString;
 import com.android.tools.r8.graph.DexType;
 import com.android.tools.r8.graph.DexValue;
-import com.android.tools.r8.graph.DexValue.DexValueDouble;
-import com.android.tools.r8.graph.DexValue.DexValueFloat;
-import com.android.tools.r8.graph.DexValue.DexValueInt;
-import com.android.tools.r8.graph.DexValue.DexValueLong;
-import com.android.tools.r8.graph.DexValue.DexValueMethodHandle;
-import com.android.tools.r8.graph.DexValue.DexValueMethodType;
-import com.android.tools.r8.graph.DexValue.DexValueString;
-import com.android.tools.r8.graph.DexValue.DexValueType;
 import com.android.tools.r8.graph.InitClassLens;
 import com.android.tools.r8.graph.UseRegistry;
 import com.android.tools.r8.ir.code.ValueType;
@@ -55,26 +47,27 @@
         methodName.toString(), callSite.methodProto.toDescriptorString(lens), bsmHandle, bsmArgs);
   }
 
-  private Object decodeBootstrapArgument(DexValue dexValue, NamingLens lens) {
-    if (dexValue instanceof DexValueInt) {
-      return ((DexValueInt) dexValue).getValue();
-    } else if (dexValue instanceof DexValueLong) {
-      return ((DexValueLong) dexValue).getValue();
-    } else if (dexValue instanceof DexValueFloat) {
-      return ((DexValueFloat) dexValue).getValue();
-    } else if (dexValue instanceof DexValueDouble) {
-      return ((DexValueDouble) dexValue).getValue();
-    } else if (dexValue instanceof DexValueString) {
-      return ((DexValueString) dexValue).getValue();
-    } else if (dexValue instanceof DexValueType) {
-      return Type.getType(lens.lookupDescriptor(((DexValueType) dexValue).value).toString());
-    } else if (dexValue instanceof DexValueMethodType) {
-      return Type.getMethodType(((DexValueMethodType) dexValue).value.toDescriptorString(lens));
-    } else if (dexValue instanceof DexValueMethodHandle) {
-      return ((DexValueMethodHandle) dexValue).value.toAsmHandle(lens);
-    } else {
-      throw new Unreachable(
-          "Unsupported bootstrap argument of type " + dexValue.getClass().getSimpleName());
+  private Object decodeBootstrapArgument(DexValue value, NamingLens lens) {
+    switch (value.getValueKind()) {
+      case DOUBLE:
+        return value.asDexValueDouble().getValue();
+      case FLOAT:
+        return value.asDexValueFloat().getValue();
+      case INT:
+        return value.asDexValueInt().getValue();
+      case LONG:
+        return value.asDexValueLong().getValue();
+      case METHOD_HANDLE:
+        return value.asDexValueMethodHandle().getValue().toAsmHandle(lens);
+      case METHOD_TYPE:
+        return Type.getMethodType(value.asDexValueMethodType().getValue().toDescriptorString(lens));
+      case STRING:
+        return value.asDexValueString().getValue();
+      case TYPE:
+        return Type.getType(lens.lookupDescriptor(value.asDexValueType().value).toString());
+      default:
+        throw new Unreachable(
+            "Unsupported bootstrap argument of type " + value.getClass().getSimpleName());
     }
   }
 
diff --git a/src/main/java/com/android/tools/r8/code/Const.java b/src/main/java/com/android/tools/r8/code/Const.java
index 9026684..498f747 100644
--- a/src/main/java/com/android/tools/r8/code/Const.java
+++ b/src/main/java/com/android/tools/r8/code/Const.java
@@ -3,7 +3,7 @@
 // BSD-style license that can be found in the LICENSE file.
 package com.android.tools.r8.code;
 
-import com.android.tools.r8.ir.analysis.type.TypeLatticeElement;
+import com.android.tools.r8.ir.analysis.type.TypeElement;
 import com.android.tools.r8.ir.code.SingleConstant;
 import com.android.tools.r8.ir.conversion.IRBuilder;
 import com.android.tools.r8.naming.ClassNameMapper;
@@ -58,8 +58,7 @@
   @Override
   public void buildIR(IRBuilder builder) {
     int value = decodedValue();
-    TypeLatticeElement typeLattice =
-        value == 0 ? TypeLatticeElement.getTop() : TypeLatticeElement.getSingle();
-    builder.addConst(typeLattice, AA, value);
+    TypeElement type = value == 0 ? TypeElement.getTop() : TypeElement.getSingle();
+    builder.addConst(type, AA, value);
   }
 }
diff --git a/src/main/java/com/android/tools/r8/code/Const16.java b/src/main/java/com/android/tools/r8/code/Const16.java
index f859228..9d69f25 100644
--- a/src/main/java/com/android/tools/r8/code/Const16.java
+++ b/src/main/java/com/android/tools/r8/code/Const16.java
@@ -3,7 +3,7 @@
 // BSD-style license that can be found in the LICENSE file.
 package com.android.tools.r8.code;
 
-import com.android.tools.r8.ir.analysis.type.TypeLatticeElement;
+import com.android.tools.r8.ir.analysis.type.TypeElement;
 import com.android.tools.r8.ir.code.SingleConstant;
 import com.android.tools.r8.ir.conversion.IRBuilder;
 import com.android.tools.r8.naming.ClassNameMapper;
@@ -52,8 +52,7 @@
   @Override
   public void buildIR(IRBuilder builder) {
     int value = decodedValue();
-    TypeLatticeElement typeLattice =
-        value == 0 ? TypeLatticeElement.getTop() : TypeLatticeElement.getSingle();
-    builder.addConst(typeLattice, AA, value);
+    TypeElement type = value == 0 ? TypeElement.getTop() : TypeElement.getSingle();
+    builder.addConst(type, AA, value);
   }
 }
diff --git a/src/main/java/com/android/tools/r8/code/Const4.java b/src/main/java/com/android/tools/r8/code/Const4.java
index a036d57..b9fd59c 100644
--- a/src/main/java/com/android/tools/r8/code/Const4.java
+++ b/src/main/java/com/android/tools/r8/code/Const4.java
@@ -3,7 +3,7 @@
 // BSD-style license that can be found in the LICENSE file.
 package com.android.tools.r8.code;
 
-import com.android.tools.r8.ir.analysis.type.TypeLatticeElement;
+import com.android.tools.r8.ir.analysis.type.TypeElement;
 import com.android.tools.r8.ir.code.SingleConstant;
 import com.android.tools.r8.ir.conversion.IRBuilder;
 import com.android.tools.r8.naming.ClassNameMapper;
@@ -58,8 +58,7 @@
   @Override
   public void buildIR(IRBuilder builder) {
     int value = decodedValue();
-    TypeLatticeElement typeLattice =
-        value == 0 ? TypeLatticeElement.getTop() : TypeLatticeElement.getSingle();
-    builder.addConst(typeLattice, A, value);
+    TypeElement type = value == 0 ? TypeElement.getTop() : TypeElement.getSingle();
+    builder.addConst(type, A, value);
   }
 }
diff --git a/src/main/java/com/android/tools/r8/code/ConstHigh16.java b/src/main/java/com/android/tools/r8/code/ConstHigh16.java
index f3821a6..b16e45d 100644
--- a/src/main/java/com/android/tools/r8/code/ConstHigh16.java
+++ b/src/main/java/com/android/tools/r8/code/ConstHigh16.java
@@ -3,7 +3,7 @@
 // BSD-style license that can be found in the LICENSE file.
 package com.android.tools.r8.code;
 
-import com.android.tools.r8.ir.analysis.type.TypeLatticeElement;
+import com.android.tools.r8.ir.analysis.type.TypeElement;
 import com.android.tools.r8.ir.code.SingleConstant;
 import com.android.tools.r8.ir.conversion.IRBuilder;
 import com.android.tools.r8.naming.ClassNameMapper;
@@ -58,8 +58,7 @@
   @Override
   public void buildIR(IRBuilder builder) {
     int value = decodedValue();
-    TypeLatticeElement typeLattice =
-        value == 0 ? TypeLatticeElement.getTop() : TypeLatticeElement.getSingle();
-    builder.addConst(typeLattice, AA, value);
+    TypeElement type = value == 0 ? TypeElement.getTop() : TypeElement.getSingle();
+    builder.addConst(type, AA, value);
   }
 }
diff --git a/src/main/java/com/android/tools/r8/code/ConstWide.java b/src/main/java/com/android/tools/r8/code/ConstWide.java
index 31c1089..dd2e6e6 100644
--- a/src/main/java/com/android/tools/r8/code/ConstWide.java
+++ b/src/main/java/com/android/tools/r8/code/ConstWide.java
@@ -3,7 +3,7 @@
 // BSD-style license that can be found in the LICENSE file.
 package com.android.tools.r8.code;
 
-import com.android.tools.r8.ir.analysis.type.TypeLatticeElement;
+import com.android.tools.r8.ir.analysis.type.TypeElement;
 import com.android.tools.r8.ir.code.WideConstant;
 import com.android.tools.r8.ir.conversion.IRBuilder;
 import com.android.tools.r8.naming.ClassNameMapper;
@@ -57,6 +57,6 @@
 
   @Override
   public void buildIR(IRBuilder builder) {
-    builder.addConst(TypeLatticeElement.getWide(), AA, decodedValue());
+    builder.addConst(TypeElement.getWide(), AA, decodedValue());
   }
 }
diff --git a/src/main/java/com/android/tools/r8/code/ConstWide16.java b/src/main/java/com/android/tools/r8/code/ConstWide16.java
index 2f7634d..99c1810 100644
--- a/src/main/java/com/android/tools/r8/code/ConstWide16.java
+++ b/src/main/java/com/android/tools/r8/code/ConstWide16.java
@@ -3,7 +3,7 @@
 // BSD-style license that can be found in the LICENSE file.
 package com.android.tools.r8.code;
 
-import com.android.tools.r8.ir.analysis.type.TypeLatticeElement;
+import com.android.tools.r8.ir.analysis.type.TypeElement;
 import com.android.tools.r8.ir.code.WideConstant;
 import com.android.tools.r8.ir.conversion.IRBuilder;
 import com.android.tools.r8.naming.ClassNameMapper;
@@ -57,6 +57,6 @@
 
   @Override
   public void buildIR(IRBuilder builder) {
-    builder.addConst(TypeLatticeElement.getWide(), AA, decodedValue());
+    builder.addConst(TypeElement.getWide(), AA, decodedValue());
   }
 }
diff --git a/src/main/java/com/android/tools/r8/code/ConstWide32.java b/src/main/java/com/android/tools/r8/code/ConstWide32.java
index 51ef097..1e2c274 100644
--- a/src/main/java/com/android/tools/r8/code/ConstWide32.java
+++ b/src/main/java/com/android/tools/r8/code/ConstWide32.java
@@ -3,7 +3,7 @@
 // BSD-style license that can be found in the LICENSE file.
 package com.android.tools.r8.code;
 
-import com.android.tools.r8.ir.analysis.type.TypeLatticeElement;
+import com.android.tools.r8.ir.analysis.type.TypeElement;
 import com.android.tools.r8.ir.code.WideConstant;
 import com.android.tools.r8.ir.conversion.IRBuilder;
 import com.android.tools.r8.naming.ClassNameMapper;
@@ -57,6 +57,6 @@
 
   @Override
   public void buildIR(IRBuilder builder) {
-    builder.addConst(TypeLatticeElement.getWide(), AA, decodedValue());
+    builder.addConst(TypeElement.getWide(), AA, decodedValue());
   }
 }
diff --git a/src/main/java/com/android/tools/r8/code/ConstWideHigh16.java b/src/main/java/com/android/tools/r8/code/ConstWideHigh16.java
index 8437a35..26d5ea0 100644
--- a/src/main/java/com/android/tools/r8/code/ConstWideHigh16.java
+++ b/src/main/java/com/android/tools/r8/code/ConstWideHigh16.java
@@ -3,7 +3,7 @@
 // BSD-style license that can be found in the LICENSE file.
 package com.android.tools.r8.code;
 
-import com.android.tools.r8.ir.analysis.type.TypeLatticeElement;
+import com.android.tools.r8.ir.analysis.type.TypeElement;
 import com.android.tools.r8.ir.code.WideConstant;
 import com.android.tools.r8.ir.conversion.IRBuilder;
 import com.android.tools.r8.naming.ClassNameMapper;
@@ -57,6 +57,6 @@
 
   @Override
   public void buildIR(IRBuilder builder) {
-    builder.addConst(TypeLatticeElement.getWide(), AA, decodedValue());
+    builder.addConst(TypeElement.getWide(), AA, decodedValue());
   }
 }
diff --git a/src/main/java/com/android/tools/r8/dex/DexParser.java b/src/main/java/com/android/tools/r8/dex/DexParser.java
index f15e96e..a6eb1c3 100644
--- a/src/main/java/com/android/tools/r8/dex/DexParser.java
+++ b/src/main/java/com/android/tools/r8/dex/DexParser.java
@@ -48,10 +48,7 @@
 import com.android.tools.r8.graph.DexTypeList;
 import com.android.tools.r8.graph.DexValue;
 import com.android.tools.r8.graph.DexValue.DexValueKind;
-import com.android.tools.r8.graph.DexValue.DexValueMethodHandle;
-import com.android.tools.r8.graph.DexValue.DexValueMethodType;
 import com.android.tools.r8.graph.DexValue.DexValueNull;
-import com.android.tools.r8.graph.DexValue.DexValueString;
 import com.android.tools.r8.graph.EnclosingMethodAttribute;
 import com.android.tools.r8.graph.FieldAccessFlags;
 import com.android.tools.r8.graph.InnerClassAttribute;
@@ -1244,14 +1241,14 @@
         dexReader.getUint(dexSection.offset + (Constants.TYPE_CALL_SITE_ID_ITEM_SIZE * index));
     DexEncodedArray callSiteEncodedArray = encodedArrayAt(callSiteOffset);
     DexValue[] values = callSiteEncodedArray.values;
-    assert values[0] instanceof DexValueMethodHandle;
-    assert values[1] instanceof DexValueString;
-    assert values[2] instanceof DexValueMethodType;
+    assert values[0].isDexValueMethodHandle();
+    assert values[1].isDexValueString();
+    assert values[2].isDexValueMethodType();
 
     return dexItemFactory.createCallSite(
-        ((DexValueString) values[1]).value,
-        ((DexValueMethodType) values[2]).value,
-        ((DexValueMethodHandle) values[0]).value,
+        values[1].asDexValueString().value,
+        values[2].asDexValueMethodType().value,
+        values[0].asDexValueMethodHandle().value,
         // 3 means first extra args
         Arrays.asList(Arrays.copyOfRange(values, 3, values.length)));
   }
diff --git a/src/main/java/com/android/tools/r8/graph/AppInfo.java b/src/main/java/com/android/tools/r8/graph/AppInfo.java
index 2ddba3f..a41e119 100644
--- a/src/main/java/com/android/tools/r8/graph/AppInfo.java
+++ b/src/main/java/com/android/tools/r8/graph/AppInfo.java
@@ -31,11 +31,15 @@
 
   private final DexApplication app;
   private final DexItemFactory dexItemFactory;
-  private final ConcurrentHashMap<DexType, Map<DexMember<?, ?>, DexEncodedMember<?, ?>>>
-      definitions = new ConcurrentHashMap<>();
+
+  // TODO(b/151804585): Remove this cache.
+  private final ConcurrentHashMap<DexType, Map<DexField, DexEncodedField>> fieldDefinitionsCache =
+      new ConcurrentHashMap<>();
+
   // For some optimizations, e.g. optimizing synthetic classes, we may need to resolve the current
   // class being optimized.
-  final ConcurrentHashMap<DexType, DexProgramClass> synthesizedClasses = new ConcurrentHashMap<>();
+  private final ConcurrentHashMap<DexType, DexProgramClass> synthesizedClasses =
+      new ConcurrentHashMap<>();
 
   // Set when a new AppInfo replaces a previous one. All public methods should verify that the
   // current instance is not obsolete, to ensure that we almost use the most recent AppInfo.
@@ -50,7 +54,7 @@
     assert !previous.isObsolete();
     this.app = previous.app;
     this.dexItemFactory = app.dexItemFactory;
-    this.definitions.putAll(previous.definitions);
+    this.fieldDefinitionsCache.putAll(previous.fieldDefinitionsCache);
     copyMetadataFromPrevious(previous);
   }
 
@@ -94,7 +98,7 @@
     assert checkIfObsolete();
     assert clazz.type.isD8R8SynthesizedClassType();
     DexProgramClass previous = synthesizedClasses.put(clazz.type, clazz);
-    invalidateTypeCacheFor(clazz.type);
+    invalidateFieldCacheFor(clazz.type);
     assert previous == null || previous == clazz;
   }
 
@@ -103,12 +107,11 @@
     return Collections.unmodifiableCollection(synthesizedClasses.values());
   }
 
-  private Map<DexMember<?, ?>, DexEncodedMember<?, ?>> computeDefinitions(DexType type) {
-    Builder<DexMember<?, ?>, DexEncodedMember<?, ?>> builder = ImmutableMap.builder();
+  private Map<DexField, DexEncodedField> computeFieldDefinitions(DexType type) {
+    Builder<DexField, DexEncodedField> builder = ImmutableMap.builder();
     DexClass clazz = definitionFor(type);
     if (clazz != null) {
-      clazz.forEachMethod(method -> builder.put(method.toReference(), method));
-      clazz.forEachField(field -> builder.put(field.toReference(), field));
+      clazz.forEachField(field -> builder.put(field.field, field));
     }
     return builder.build();
   }
@@ -173,35 +176,29 @@
   @Override
   public DexEncodedMethod definitionFor(DexMethod method) {
     assert checkIfObsolete();
-    DexType holderType = method.holder;
-    DexEncodedMethod cached = (DexEncodedMethod) getDefinitions(holderType).get(method);
-    if (cached != null && cached.isObsolete()) {
-      definitions.remove(holderType);
-      cached = (DexEncodedMethod) getDefinitions(holderType).get(method);
+    assert method.holder.isClassType();
+    if (!method.holder.isClassType()) {
+      return null;
     }
-    return cached;
+    DexClass clazz = definitionFor(method.holder);
+    if (clazz == null) {
+      return null;
+    }
+    return clazz.getMethodCollection().getMethod(method);
   }
 
   @Override
   public DexEncodedField definitionFor(DexField field) {
     assert checkIfObsolete();
-    return (DexEncodedField) getDefinitions(field.holder).get(field);
+    return getFieldDefinitions(field.holder).get(field);
   }
 
-  private Map<DexMember<?, ?>, DexEncodedMember<?, ?>> getDefinitions(DexType type) {
-    Map<DexMember<?, ?>, DexEncodedMember<?, ?>> typeDefinitions = definitions.get(type);
-    if (typeDefinitions != null) {
-      return typeDefinitions;
-    }
-
-    typeDefinitions = computeDefinitions(type);
-    Map<DexMember<?, ?>, DexEncodedMember<?, ?>> existing =
-        definitions.putIfAbsent(type, typeDefinitions);
-    return existing != null ? existing : typeDefinitions;
+  private Map<DexField, DexEncodedField> getFieldDefinitions(DexType type) {
+    return fieldDefinitionsCache.computeIfAbsent(type, this::computeFieldDefinitions);
   }
 
-  public void invalidateTypeCacheFor(DexType type) {
-    definitions.remove(type);
+  public void invalidateFieldCacheFor(DexType type) {
+    fieldDefinitionsCache.remove(type);
   }
 
   // TODO(b/147578480): Temporary API since most of the code base use a type instead
diff --git a/src/main/java/com/android/tools/r8/graph/AppInfoWithSubtyping.java b/src/main/java/com/android/tools/r8/graph/AppInfoWithSubtyping.java
index 5f2b826..58611d4 100644
--- a/src/main/java/com/android/tools/r8/graph/AppInfoWithSubtyping.java
+++ b/src/main/java/com/android/tools/r8/graph/AppInfoWithSubtyping.java
@@ -5,7 +5,7 @@
 
 import static com.android.tools.r8.ir.desugar.LambdaRewriter.LAMBDA_GROUP_CLASS_NAME_PREFIX;
 
-import com.android.tools.r8.ir.analysis.type.ClassTypeLatticeElement;
+import com.android.tools.r8.ir.analysis.type.ClassTypeElement;
 import com.android.tools.r8.ir.desugar.LambdaDescriptor;
 import com.android.tools.r8.utils.SetUtils;
 import com.android.tools.r8.utils.WorkList;
@@ -480,7 +480,7 @@
     return !isSubtype(type1, type2) && !isSubtype(type2, type1);
   }
 
-  public boolean mayHaveFinalizeMethodDirectlyOrIndirectly(ClassTypeLatticeElement type) {
+  public boolean mayHaveFinalizeMethodDirectlyOrIndirectly(ClassTypeElement type) {
     if (type.getClassType() == dexItemFactory().objectType && !type.getInterfaces().isEmpty()) {
       for (DexType interfaceType : type.getInterfaces()) {
         if (computeMayHaveFinalizeMethodDirectlyOrIndirectlyIfAbsent(interfaceType, false)) {
diff --git a/src/main/java/com/android/tools/r8/graph/AssemblyWriter.java b/src/main/java/com/android/tools/r8/graph/AssemblyWriter.java
index 74735f4..4eb9a85 100644
--- a/src/main/java/com/android/tools/r8/graph/AssemblyWriter.java
+++ b/src/main/java/com/android/tools/r8/graph/AssemblyWriter.java
@@ -7,6 +7,9 @@
 import com.android.tools.r8.ir.conversion.IRConverter;
 import com.android.tools.r8.ir.conversion.OneTimeMethodProcessor;
 import com.android.tools.r8.ir.optimize.info.OptimizationFeedbackIgnore;
+import com.android.tools.r8.kotlin.Kotlin;
+import com.android.tools.r8.kotlin.KotlinClassMetadataReader;
+import com.android.tools.r8.kotlin.KotlinInfo;
 import com.android.tools.r8.naming.ClassNameMapper;
 import com.android.tools.r8.naming.MemberNaming.FieldSignature;
 import com.android.tools.r8.utils.CfgPrinter;
@@ -21,6 +24,7 @@
   private final boolean writeAnnotations;
   private final boolean writeIR;
   private final AppInfoWithSubtyping appInfo;
+  private final Kotlin kotlin;
   private final Timing timing = new Timing("AssemblyWriter");
 
   public AssemblyWriter(
@@ -41,6 +45,7 @@
     } else {
       this.appInfo = null;
     }
+    kotlin = new Kotlin(application.dexItemFactory);
   }
 
   @Override
@@ -59,7 +64,7 @@
     ps.println("# Bytecode for");
     ps.println("# Class: '" + clazzName + "'");
     if (writeAllClassInfo) {
-      writeAnnotations(clazz.annotations(), ps);
+      writeAnnotations(clazz, clazz.annotations(), ps);
       ps.println("# Flags: '" + clazz.accessFlags + "'");
       if (clazz.superType != application.dexItemFactory.objectType) {
         ps.println("# Extends: '" + clazz.superType.toSourceString() + "'");
@@ -87,7 +92,7 @@
       FieldSignature fieldSignature = naming != null
           ? naming.originalSignatureOf(field.field)
           : FieldSignature.fromDexField(field.field);
-      writeAnnotations(field.annotations(), ps);
+      writeAnnotations(null, field.annotations(), ps);
       ps.print(field.accessFlags + " ");
       ps.print(fieldSignature);
       if (field.isStatic() && field.hasExplicitStaticValue()) {
@@ -110,7 +115,7 @@
         : method.method.name.toString();
     ps.println("#");
     ps.println("# Method: '" + methodName + "':");
-    writeAnnotations(method.annotations(), ps);
+    writeAnnotations(null, method.annotations(), ps);
     ps.println("# " + method.accessFlags);
     ps.println("#");
     ps.println();
@@ -134,16 +139,17 @@
     ps.println(printer.toString());
   }
 
-  private void writeAnnotations(DexAnnotationSet annotations, PrintStream ps) {
+  private void writeAnnotations(
+      DexProgramClass clazz, DexAnnotationSet annotations, PrintStream ps) {
     if (writeAnnotations) {
       if (!annotations.isEmpty()) {
         ps.println("# Annotations:");
         for (DexAnnotation annotation : annotations.annotations) {
-          ps.print("#   ");
-          if (annotation.annotation.type
-              == application.dexItemFactory.createType("Lkotlin/Metadata;")) {
-            ps.println("<kotlin metadata>");
+          if (annotation.annotation.type == kotlin.metadata.kotlinMetadataType) {
+            assert clazz != null : "Kotlin metadata is a class annotation";
+            writeKotlinMetadata(clazz, annotation, ps);
           } else {
+            ps.print("#   ");
             ps.println(annotation);
           }
         }
@@ -151,6 +157,14 @@
     }
   }
 
+  private void writeKotlinMetadata(
+      DexProgramClass clazz, DexAnnotation annotation, PrintStream ps) {
+    assert annotation.annotation.type == kotlin.metadata.kotlinMetadataType;
+    KotlinInfo kotlinInfo =
+        KotlinClassMetadataReader.createKotlinInfo(kotlin, clazz, annotation.annotation);
+    ps.println(kotlinInfo.toString("#  "));
+  }
+
   @Override
   void writeClassFooter(DexProgramClass clazz, PrintStream ps) {
 
diff --git a/src/main/java/com/android/tools/r8/graph/DexAnnotation.java b/src/main/java/com/android/tools/r8/graph/DexAnnotation.java
index 50b4b2f..a409737 100644
--- a/src/main/java/com/android/tools/r8/graph/DexAnnotation.java
+++ b/src/main/java/com/android/tools/r8/graph/DexAnnotation.java
@@ -99,9 +99,11 @@
 
   public static DexType getEnclosingClassFromAnnotation(
       DexAnnotation annotation, DexItemFactory factory) {
-    DexValueType typeValue =
-        (DexValueType) getSystemValueAnnotationValue(factory.annotationEnclosingClass, annotation);
-    return typeValue == null ? null : typeValue.value;
+    DexValue value = getSystemValueAnnotationValue(factory.annotationEnclosingClass, annotation);
+    if (value == null) {
+      return null;
+    }
+    return value.asDexValueType().value;
   }
 
   public static DexAnnotation createEnclosingMethodAnnotation(DexMethod enclosingMethod,
@@ -112,10 +114,11 @@
 
   public static DexMethod getEnclosingMethodFromAnnotation(
       DexAnnotation annotation, DexItemFactory factory) {
-    DexValueMethod methodValue =
-        (DexValueMethod)
-            getSystemValueAnnotationValue(factory.annotationEnclosingMethod, annotation);
-    return methodValue == null ? null : methodValue.value;
+    DexValue value = getSystemValueAnnotationValue(factory.annotationEnclosingMethod, annotation);
+    if (value == null) {
+      return null;
+    }
+    return value.asDexValueMethod().value;
   }
 
   public static boolean isEnclosingClassAnnotation(DexAnnotation annotation,
@@ -159,12 +162,12 @@
     Pair<DexString, Integer> result = new Pair<>();
     for (DexAnnotationElement element : elements) {
       if (element.name == factory.createString("name")) {
-        if (element.value instanceof DexValueString) {
-          result.setFirst(((DexValueString) element.value).getValue());
+        if (element.value.isDexValueString()) {
+          result.setFirst(element.value.asDexValueString().getValue());
         }
       } else {
         assert element.name == factory.createString("accessFlags");
-        result.setSecond(((DexValueInt) element.value).getValue());
+        result.setSecond(element.value.asDexValueInt().getValue());
       }
     }
     return result;
@@ -182,14 +185,14 @@
 
   public static List<DexType> getMemberClassesFromAnnotation(
       DexAnnotation annotation, DexItemFactory factory) {
-    DexValueArray membersArray =
-        (DexValueArray) getSystemValueAnnotationValue(factory.annotationMemberClasses, annotation);
-    if (membersArray == null) {
+    DexValue value = getSystemValueAnnotationValue(factory.annotationMemberClasses, annotation);
+    if (value == null) {
       return null;
     }
+    DexValueArray membersArray = value.asDexValueArray();
     List<DexType> types = new ArrayList<>(membersArray.getValues().length);
-    for (DexValue value : membersArray.getValues()) {
-      types.add(((DexValueType) value).value);
+    for (DexValue elementValue : membersArray.getValues()) {
+      types.add(elementValue.asDexValueType().value);
     }
     return types;
   }
@@ -233,10 +236,10 @@
   }
 
   public static String getSignature(DexAnnotation signatureAnnotation) {
-    DexValueArray elements = (DexValueArray) signatureAnnotation.annotation.elements[0].value;
+    DexValueArray elements = signatureAnnotation.annotation.elements[0].value.asDexValueArray();
     StringBuilder signature = new StringBuilder();
     for (DexValue element : elements.getValues()) {
-      signature.append(((DexValueString) element).value.toString());
+      signature.append(element.asDexValueString().value.toString());
     }
     return signature.toString();
   }
@@ -349,16 +352,16 @@
       if (!value.name.toSourceString().equals("value")) {
         throw new CompilationError(getInvalidSynthesizedClassMapMessage(clazz, foundAnnotation));
       }
-      if (!(value.value instanceof DexValueArray)) {
+      DexValueArray existingList = value.value.asDexValueArray();
+      if (existingList == null) {
         throw new CompilationError(getInvalidSynthesizedClassMapMessage(clazz, foundAnnotation));
       }
-      DexValueArray existingList = (DexValueArray) value.value;
       Collection<DexType> synthesized = new ArrayList<>(existingList.values.length);
       for (DexValue element : existingList.getValues()) {
-        if (!(element instanceof DexValueType)) {
+        if (!element.isDexValueType()) {
           throw new CompilationError(getInvalidSynthesizedClassMapMessage(clazz, foundAnnotation));
         }
-        synthesized.add(((DexValueType) element).value);
+        synthesized.add(element.asDexValueType().value);
       }
       return synthesized;
     }
diff --git a/src/main/java/com/android/tools/r8/graph/DexCallSite.java b/src/main/java/com/android/tools/r8/graph/DexCallSite.java
index 382f4bb..3249abf 100644
--- a/src/main/java/com/android/tools/r8/graph/DexCallSite.java
+++ b/src/main/java/com/android/tools/r8/graph/DexCallSite.java
@@ -213,52 +213,35 @@
     private void write(List<DexValue> args) throws IOException {
       out.writeInt(args.size());
       for (DexValue arg : args) {
-        // String, Class, Integer, Long, Float, Double, MethodHandle, MethodType
-        if (arg instanceof DexValue.DexValueString) {
-          out.writeByte(0);
-          write(((DexValue.DexValueString) arg).value);
-          continue;
+        out.writeByte(arg.getValueKind().toByte());
+        switch (arg.getValueKind()) {
+          case DOUBLE:
+            out.writeDouble(arg.asDexValueDouble().value);
+            break;
+          case FLOAT:
+            out.writeFloat(arg.asDexValueFloat().value);
+            break;
+          case INT:
+            out.writeInt(arg.asDexValueInt().value);
+            break;
+          case LONG:
+            out.writeLong(arg.asDexValueLong().value);
+            break;
+          case METHOD_HANDLE:
+            write(arg.asDexValueMethodHandle().value);
+            break;
+          case METHOD_TYPE:
+            write(arg.asDexValueMethodType().value);
+            break;
+          case STRING:
+            write(arg.asDexValueString().value);
+            break;
+          case TYPE:
+            write(arg.asDexValueType().value);
+            break;
+          default:
+            assert false;
         }
-
-        if (arg instanceof DexValue.DexValueType) {
-          out.writeByte(1);
-          write(((DexValue.DexValueType) arg).value);
-          continue;
-        }
-
-        if (arg instanceof DexValue.DexValueInt) {
-          out.writeByte(2);
-          out.writeInt(((DexValue.DexValueInt) arg).value);
-          continue;
-        }
-
-        if (arg instanceof DexValue.DexValueLong) {
-          out.writeByte(3);
-          out.writeLong(((DexValue.DexValueLong) arg).value);
-          continue;
-        }
-
-        if (arg instanceof DexValue.DexValueFloat) {
-          out.writeByte(4);
-          out.writeFloat(((DexValue.DexValueFloat) arg).value);
-          continue;
-        }
-
-        if (arg instanceof DexValue.DexValueDouble) {
-          out.writeByte(5);
-          out.writeDouble(((DexValue.DexValueDouble) arg).value);
-          continue;
-        }
-
-        if (arg instanceof DexValue.DexValueMethodHandle) {
-          out.writeByte(6);
-          write(((DexValue.DexValueMethodHandle) arg).value);
-          continue;
-        }
-
-        assert arg instanceof DexValue.DexValueMethodType;
-        out.writeByte(7);
-        write(((DexValue.DexValueMethodType) arg).value);
       }
     }
 
diff --git a/src/main/java/com/android/tools/r8/graph/DexClass.java b/src/main/java/com/android/tools/r8/graph/DexClass.java
index 773a71a..85810ed 100644
--- a/src/main/java/com/android/tools/r8/graph/DexClass.java
+++ b/src/main/java/com/android/tools/r8/graph/DexClass.java
@@ -85,9 +85,7 @@
     this.type = type;
     setStaticFields(staticFields);
     setInstanceFields(instanceFields);
-    this.methodCollection = new MethodCollection(this);
-    setDirectMethods(directMethods);
-    setVirtualMethods(virtualMethods);
+    this.methodCollection = new MethodCollection(this, directMethods, virtualMethods);
     this.nestHost = nestHost;
     this.nestMembers = nestMembers;
     assert nestMembers != null;
@@ -148,12 +146,8 @@
     return Iterables.filter(directMethods(), predicate::test);
   }
 
-  public void appendDirectMethod(DexEncodedMethod method) {
-    methodCollection.appendDirectMethod(method);
-  }
-
-  public void appendDirectMethods(Collection<DexEncodedMethod> methods) {
-    methodCollection.appendDirectMethods(methods);
+  public void addDirectMethods(Collection<DexEncodedMethod> methods) {
+    methodCollection.addDirectMethods(methods);
   }
 
   public void removeDirectMethod(DexMethod method) {
@@ -172,12 +166,8 @@
     return Iterables.filter(virtualMethods(), predicate::test);
   }
 
-  public void appendVirtualMethod(DexEncodedMethod method) {
-    methodCollection.appendVirtualMethod(method);
-  }
-
-  public void appendVirtualMethods(Collection<DexEncodedMethod> methods) {
-    methodCollection.appendVirtualMethods(methods);
+  public void addVirtualMethods(Collection<DexEncodedMethod> methods) {
+    methodCollection.addVirtualMethods(methods);
   }
 
   public void setVirtualMethods(DexEncodedMethod[] methods) {
diff --git a/src/main/java/com/android/tools/r8/graph/DexEncodedField.java b/src/main/java/com/android/tools/r8/graph/DexEncodedField.java
index 8a21e7b..10427a3 100644
--- a/src/main/java/com/android/tools/r8/graph/DexEncodedField.java
+++ b/src/main/java/com/android/tools/r8/graph/DexEncodedField.java
@@ -7,7 +7,7 @@
 
 import com.android.tools.r8.dex.IndexedItemCollection;
 import com.android.tools.r8.dex.MixedSectionCollection;
-import com.android.tools.r8.ir.analysis.type.TypeLatticeElement;
+import com.android.tools.r8.ir.analysis.type.TypeElement;
 import com.android.tools.r8.ir.analysis.value.AbstractValue;
 import com.android.tools.r8.ir.analysis.value.SingleValue;
 import com.android.tools.r8.ir.code.IRCode;
@@ -207,7 +207,7 @@
         return null;
       }
       if (singleValue.isMaterializableInContext(appView, code.method.method.holder)) {
-        TypeLatticeElement type = TypeLatticeElement.fromDexType(field.type, maybeNull(), appView);
+        TypeElement type = TypeElement.fromDexType(field.type, maybeNull(), appView);
         return singleValue.createMaterializingInstruction(
             appView, code, TypeAndLocalInfoSupplier.create(type, local));
       }
diff --git a/src/main/java/com/android/tools/r8/graph/DexEncodedMethod.java b/src/main/java/com/android/tools/r8/graph/DexEncodedMethod.java
index 342a3ed..01b0e84 100644
--- a/src/main/java/com/android/tools/r8/graph/DexEncodedMethod.java
+++ b/src/main/java/com/android/tools/r8/graph/DexEncodedMethod.java
@@ -1108,6 +1108,7 @@
     MethodAccessFlags newFlags = target.accessFlags.copy();
     // Some debuggers (like IntelliJ) automatically skip synthetic methods on single step.
     newFlags.setSynthetic();
+    newFlags.unsetAbstract();
     ForwardMethodSourceCode.Builder forwardSourceCodeBuilder =
         ForwardMethodSourceCode.builder(newMethod);
     forwardSourceCodeBuilder
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 bfaf19c..30d9369 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,7 @@
 // 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.ClassTypeLatticeElement.computeLeastUpperBoundOfInterfaces;
+import static com.android.tools.r8.ir.analysis.type.ClassTypeElement.computeLeastUpperBoundOfInterfaces;
 import static com.google.common.base.Predicates.alwaysTrue;
 
 import com.android.tools.r8.dex.Constants;
@@ -18,11 +18,11 @@
 import com.android.tools.r8.graph.DexDebugEvent.SetInlineFrame;
 import com.android.tools.r8.graph.DexDebugEvent.SetPrologueEnd;
 import com.android.tools.r8.graph.DexMethodHandle.MethodHandleType;
-import com.android.tools.r8.ir.analysis.type.ArrayTypeLatticeElement;
-import com.android.tools.r8.ir.analysis.type.ClassTypeLatticeElement;
+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.Nullability;
-import com.android.tools.r8.ir.analysis.type.ReferenceTypeLatticeElement;
-import com.android.tools.r8.ir.analysis.type.TypeLatticeElement;
+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.InvokeMethod;
 import com.android.tools.r8.ir.code.Position;
 import com.android.tools.r8.ir.code.Value;
@@ -82,11 +82,11 @@
   private final Map<DexString, SetFile> setFiles = new HashMap<>();
   private final Map<SetInlineFrame, SetInlineFrame> setInlineFrames = new HashMap<>();
 
-  // ReferenceTypeLattice canonicalization.
-  private final ConcurrentHashMap<DexType, ReferenceTypeLatticeElement>
-      referenceTypeLatticeElements = new ConcurrentHashMap<>();
-  private final ConcurrentHashMap<DexType, Set<DexType>>
-      classTypeLatticeInterfaces = new ConcurrentHashMap<>();
+  // ReferenceTypeElement canonicalization.
+  private final ConcurrentHashMap<DexType, ReferenceTypeElement> referenceTypes =
+      new ConcurrentHashMap<>();
+  private final ConcurrentHashMap<DexType, Set<DexType>> classTypeInterfaces =
+      new ConcurrentHashMap<>();
   public final LRUCacheTable<Set<DexType>, Set<DexType>, Set<DexType>>
       leastUpperBoundOfInterfacesTable = LRUCacheTable.create(8, 8);
 
@@ -1201,7 +1201,7 @@
       if (invokedMethod == charSequenceConstructor) {
         // NullPointerException - if seq is null.
         Value seqValue = invoke.inValues().get(1);
-        return !seqValue.getTypeLattice().isNullable();
+        return !seqValue.getType().isNullable();
       }
 
       if (invokedMethod == defaultConstructor) {
@@ -1220,7 +1220,7 @@
       if (invokedMethod == stringConstructor) {
         // NullPointerException - if str is null.
         Value strValue = invoke.inValues().get(1);
-        return !strValue.getTypeLattice().isNullable();
+        return !strValue.getType().isNullable();
       }
 
       assert false : "Unexpected invoke targeting `" + invokedMethod.toSourceString() +  "`";
@@ -1644,20 +1644,20 @@
     return method.name == classConstructorMethodName;
   }
 
-  public void clearTypeLatticeElementsCache() {
-    referenceTypeLatticeElements.clear();
-    classTypeLatticeInterfaces.clear();
+  public void clearTypeElementsCache() {
+    referenceTypes.clear();
+    classTypeInterfaces.clear();
     leastUpperBoundOfInterfacesTable.clear();
   }
 
-  public boolean verifyNoCachedTypeLatticeElements() {
-    assert referenceTypeLatticeElements.isEmpty();
-    assert classTypeLatticeInterfaces.isEmpty();
+  public boolean verifyNoCachedTypeElements() {
+    assert referenceTypes.isEmpty();
+    assert classTypeInterfaces.isEmpty();
     assert leastUpperBoundOfInterfacesTable.isEmpty();
     return true;
   }
 
-  public ReferenceTypeLatticeElement createReferenceTypeLatticeElement(
+  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
@@ -1671,56 +1671,54 @@
     // (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
-    //      ArrayTypeLatticeElement but only one will enter. The property that only one
-    //      ArrayTypeLatticeElement is created per level therefore holds inductively.
-    TypeLatticeElement memberType = null;
+    //      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()) {
-      ReferenceTypeLatticeElement existing = referenceTypeLatticeElements.get(type);
+      ReferenceTypeElement existing = referenceTypes.get(type);
       if (existing != null) {
         return existing.getOrCreateVariant(nullability);
       }
       memberType =
-          TypeLatticeElement.fromDexType(
+          TypeElement.fromDexType(
               type.toArrayElementType(this), Nullability.maybeNull(), appView, true);
     }
-    TypeLatticeElement finalMemberType = memberType;
-    return referenceTypeLatticeElements
+    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 ClassTypeLatticeElement.create(type, nullability, Collections.emptySet());
+                  return ClassTypeElement.create(type, nullability, Collections.emptySet());
                 }
                 assert appView.appInfo().hasSubtyping();
                 if (appView.isInterface(type).isTrue()) {
-                  return ClassTypeLatticeElement.create(
+                  return ClassTypeElement.create(
                       objectType, nullability, Collections.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
-                // ClassTypeLatticeElement#getInterfaces}.
-                return ClassTypeLatticeElement.create(type, nullability, appView.withSubtyping());
+                // ClassTypeElement#getInterfaces}.
+                return ClassTypeElement.create(type, nullability, appView.withSubtyping());
               }
               assert type.isArrayType();
-              return ArrayTypeLatticeElement.create(finalMemberType, nullability);
+              return ArrayTypeElement.create(finalMemberType, nullability);
             })
         .getOrCreateVariant(nullability);
   }
 
   public Set<DexType> getOrComputeLeastUpperBoundOfImplementedInterfaces(
       DexType type, AppView<? extends AppInfoWithSubtyping> appView) {
-    return classTypeLatticeInterfaces
-        .computeIfAbsent(
-            type,
-            t -> {
-              Set<DexType> itfs = appView.appInfo().implementedInterfaces(t);
-              return computeLeastUpperBoundOfInterfaces(appView, itfs, itfs);
-            }
-        );
+    return classTypeInterfaces.computeIfAbsent(
+        type,
+        t -> {
+          Set<DexType> itfs = appView.appInfo().implementedInterfaces(t);
+          return computeLeastUpperBoundOfInterfaces(appView, itfs, itfs);
+        });
   }
 
   synchronized public void forAllTypes(Consumer<DexType> f) {
diff --git a/src/main/java/com/android/tools/r8/graph/DexType.java b/src/main/java/com/android/tools/r8/graph/DexType.java
index 4b8c6d4..01c5298 100644
--- a/src/main/java/com/android/tools/r8/graph/DexType.java
+++ b/src/main/java/com/android/tools/r8/graph/DexType.java
@@ -16,6 +16,7 @@
 import com.android.tools.r8.dex.IndexedItemCollection;
 import com.android.tools.r8.errors.Unreachable;
 import com.android.tools.r8.ir.desugar.BackportedMethodRewriter;
+import com.android.tools.r8.ir.desugar.DesugaredLibraryRetargeter;
 import com.android.tools.r8.ir.desugar.NestBasedAccessDesugaring;
 import com.android.tools.r8.ir.desugar.TwrCloseResourceRewriter;
 import com.android.tools.r8.ir.optimize.ServiceLoaderRewriter;
@@ -44,8 +45,7 @@
   private String toStringCache = null;
 
   DexType(DexString descriptor) {
-    assert !descriptor.toString().contains(".")
-        : "Malformed descriptor: " + descriptor.toString();
+    assert !descriptor.toString().contains(".") : "Malformed descriptor: " + descriptor.toString();
     this.descriptor = descriptor;
   }
 
@@ -145,11 +145,12 @@
   }
 
   @Override
-  public void collectIndexedItems(IndexedItemCollection collection,
-      DexMethod method, int instructionOffset) {
+  public void collectIndexedItems(
+      IndexedItemCollection collection, DexMethod method, int instructionOffset) {
     if (collection.addType(this)) {
-      collection.getRenamedDescriptor(this).collectIndexedItems(collection, method,
-          instructionOffset);
+      collection
+          .getRenamedDescriptor(this)
+          .collectIndexedItems(collection, method, instructionOffset);
     }
   }
 
@@ -272,6 +273,7 @@
         || name.contains(TwrCloseResourceRewriter.UTILITY_CLASS_NAME)
         || name.contains(NestBasedAccessDesugaring.NEST_CONSTRUCTOR_NAME)
         || name.contains(BackportedMethodRewriter.UTILITY_CLASS_NAME_PREFIX)
+        || name.contains(DesugaredLibraryRetargeter.DESUGAR_LIB_RETARGET_CLASS_NAME_PREFIX)
         || name.contains(ServiceLoaderRewriter.SERVICE_LOADER_CLASS_NAME)
         || oldSynthesizedName(name);
   }
@@ -298,17 +300,17 @@
   public int elementSizeForPrimitiveArrayType() {
     assert isPrimitiveArrayType();
     switch (descriptor.content[1]) {
-      case 'Z':  // boolean
-      case 'B':  // byte
+      case 'Z': // boolean
+      case 'B': // byte
         return 1;
-      case 'S':  // short
-      case 'C':  // char
+      case 'S': // short
+      case 'C': // char
         return 2;
-      case 'I':  // int
-      case 'F':  // float
+      case 'I': // int
+      case 'F': // float
         return 4;
-      case 'J':  // long
-      case 'D':  // double
+      case 'J': // long
+      case 'D': // double
         return 8;
       default:
         throw new Unreachable("Not array of primitives '" + descriptor + "'");
@@ -328,8 +330,11 @@
     if (leadingSquareBrackets == 0) {
       return this;
     }
-    DexString newDesc = dexItemFactory.createString(descriptor.size - leadingSquareBrackets,
-        Arrays.copyOfRange(descriptor.content, leadingSquareBrackets, descriptor.content.length));
+    DexString newDesc =
+        dexItemFactory.createString(
+            descriptor.size - leadingSquareBrackets,
+            Arrays.copyOfRange(
+                descriptor.content, leadingSquareBrackets, descriptor.content.length));
     return dexItemFactory.createType(newDesc);
   }
 
@@ -364,8 +369,10 @@
 
   public DexType toArrayElementType(DexItemFactory dexItemFactory) {
     assert this.isArrayType();
-    DexString newDesc = dexItemFactory.createString(descriptor.size - 1,
-        Arrays.copyOfRange(descriptor.content, 1, descriptor.content.length));
+    DexString newDesc =
+        dexItemFactory.createString(
+            descriptor.size - 1,
+            Arrays.copyOfRange(descriptor.content, 1, descriptor.content.length));
     return dexItemFactory.createType(newDesc);
   }
 
@@ -376,7 +383,8 @@
     if (lastSeparator == -1) {
       return packagePart ? "" : descriptor.substring(1, descriptor.length() - 1);
     } else {
-      return packagePart ? descriptor.substring(1, lastSeparator)
+      return packagePart
+          ? descriptor.substring(1, lastSeparator)
           : descriptor.substring(lastSeparator + 1, descriptor.length() - 1);
     }
   }
diff --git a/src/main/java/com/android/tools/r8/graph/DexValue.java b/src/main/java/com/android/tools/r8/graph/DexValue.java
index be0cf37..bab3c75 100644
--- a/src/main/java/com/android/tools/r8/graph/DexValue.java
+++ b/src/main/java/com/android/tools/r8/graph/DexValue.java
@@ -10,7 +10,7 @@
 import com.android.tools.r8.dex.IndexedItemCollection;
 import com.android.tools.r8.dex.MixedSectionCollection;
 import com.android.tools.r8.errors.Unreachable;
-import com.android.tools.r8.ir.analysis.type.TypeLatticeElement;
+import com.android.tools.r8.ir.analysis.type.TypeElement;
 import com.android.tools.r8.ir.code.BasicBlock.ThrowingInfo;
 import com.android.tools.r8.ir.code.ConstInstruction;
 import com.android.tools.r8.ir.code.ConstString;
@@ -102,6 +102,8 @@
 
   public static final DexValue[] EMPTY_ARRAY = {};
 
+  public abstract DexValueKind getValueKind();
+
   public boolean isDexItemBasedValueString() {
     return false;
   }
@@ -110,14 +112,30 @@
     return null;
   }
 
+  public boolean isDexValueMethodHandle() {
+    return false;
+  }
+
   public DexValueMethodHandle asDexValueMethodHandle() {
     return null;
   }
 
+  public boolean isDexValueMethodType() {
+    return false;
+  }
+
   public DexValueMethodType asDexValueMethodType() {
     return null;
   }
 
+  public boolean isDexValueAnnotation() {
+    return false;
+  }
+
+  public DexValueAnnotation asDexValueAnnotation() {
+    return null;
+  }
+
   public boolean isDexValueArray() {
     return false;
   }
@@ -158,6 +176,22 @@
     return null;
   }
 
+  public boolean isDexValueEnum() {
+    return false;
+  }
+
+  public DexValueEnum asDexValueEnum() {
+    return null;
+  }
+
+  public boolean isDexValueField() {
+    return false;
+  }
+
+  public DexValueField asDexValueField() {
+    return null;
+  }
+
   public boolean isDexValueFloat() {
     return false;
   }
@@ -182,6 +216,14 @@
     return null;
   }
 
+  public boolean isDexValueMethod() {
+    return false;
+  }
+
+  public DexValueMethod asDexValueMethod() {
+    return null;
+  }
+
   public boolean isDexValueNull() {
     return false;
   }
@@ -392,6 +434,11 @@
     }
 
     @Override
+    public DexValueKind getValueKind() {
+      return DexValueKind.BYTE;
+    }
+
+    @Override
     public DexType getType(DexItemFactory factory) {
       return factory.byteType;
     }
@@ -470,6 +517,11 @@
     }
 
     @Override
+    public DexValueKind getValueKind() {
+      return DexValueKind.SHORT;
+    }
+
+    @Override
     public DexType getType(DexItemFactory factory) {
       return factory.shortType;
     }
@@ -547,6 +599,11 @@
     }
 
     @Override
+    public DexValueKind getValueKind() {
+      return DexValueKind.CHAR;
+    }
+
+    @Override
     public DexType getType(DexItemFactory factory) {
       return factory.charType;
     }
@@ -628,6 +685,11 @@
     }
 
     @Override
+    public DexValueKind getValueKind() {
+      return DexValueKind.INT;
+    }
+
+    @Override
     public DexType getType(DexItemFactory factory) {
       return factory.intType;
     }
@@ -705,6 +767,11 @@
     }
 
     @Override
+    public DexValueKind getValueKind() {
+      return DexValueKind.LONG;
+    }
+
+    @Override
     public DexType getType(DexItemFactory factory) {
       return factory.longType;
     }
@@ -782,6 +849,11 @@
     }
 
     @Override
+    public DexValueKind getValueKind() {
+      return DexValueKind.FLOAT;
+    }
+
+    @Override
     public DexType getType(DexItemFactory factory) {
       return factory.floatType;
     }
@@ -836,8 +908,8 @@
       if (other == this) {
         return true;
       }
-      return (other instanceof DexValueFloat) &&
-          (Float.compare(value, ((DexValueFloat) other).value) == 0);
+      return other instanceof DexValueFloat
+          && Float.compare(value, ((DexValueFloat) other).value) == 0;
     }
 
     @Override
@@ -865,6 +937,11 @@
     }
 
     @Override
+    public DexValueKind getValueKind() {
+      return DexValueKind.DOUBLE;
+    }
+
+    @Override
     public DexType getType(DexItemFactory factory) {
       return factory.doubleType;
     }
@@ -937,8 +1014,6 @@
       this.value = value;
     }
 
-    protected abstract DexValueKind getValueKind();
-
     @Override
     public DexType getType(DexItemFactory factory) {
       throw new Unreachable();
@@ -1024,7 +1099,7 @@
     }
 
     @Override
-    protected DexValueKind getValueKind() {
+    public DexValueKind getValueKind() {
       return DexValueKind.STRING;
     }
 
@@ -1036,7 +1111,7 @@
     @Override
     public ConstInstruction asConstInstruction(
         AppView<? extends AppInfoWithSubtyping> appView, IRCode code, DebugLocalInfo local) {
-      TypeLatticeElement type = TypeLatticeElement.stringClassType(appView, definitelyNotNull());
+      TypeElement type = TypeElement.stringClassType(appView, definitelyNotNull());
       Value outValue = code.createValue(type, local);
       ConstString instruction =
           new ConstString(outValue, value, ThrowingInfo.defaultForConstString(appView.options()));
@@ -1082,7 +1157,7 @@
     }
 
     @Override
-    protected DexValueKind getValueKind() {
+    public DexValueKind getValueKind() {
       return DexValueKind.STRING;
     }
 
@@ -1094,7 +1169,7 @@
     @Override
     public ConstInstruction asConstInstruction(
         AppView<? extends AppInfoWithSubtyping> appView, IRCode code, DebugLocalInfo local) {
-      TypeLatticeElement type = TypeLatticeElement.stringClassType(appView, definitelyNotNull());
+      TypeElement type = TypeElement.stringClassType(appView, definitelyNotNull());
       Value outValue = code.createValue(type, local);
       DexItemBasedConstString instruction =
           new DexItemBasedConstString(
@@ -1121,7 +1196,7 @@
     }
 
     @Override
-    protected DexValueKind getValueKind() {
+    public DexValueKind getValueKind() {
       return DexValueKind.TYPE;
     }
 
@@ -1149,7 +1224,7 @@
     }
 
     @Override
-    protected DexValueKind getValueKind() {
+    public DexValueKind getValueKind() {
       return DexValueKind.FIELD;
     }
 
@@ -1158,6 +1233,16 @@
         IndexedItemCollection indexedItems, DexMethod method, int instructionOffset) {
       value.collectIndexedItems(indexedItems, method, instructionOffset);
     }
+
+    @Override
+    public boolean isDexValueField() {
+      return true;
+    }
+
+    @Override
+    public DexValueField asDexValueField() {
+      return this;
+    }
   }
 
   static public class DexValueMethod extends NestedDexValue<DexMethod> {
@@ -1167,7 +1252,7 @@
     }
 
     @Override
-    protected DexValueKind getValueKind() {
+    public DexValueKind getValueKind() {
       return DexValueKind.METHOD;
     }
 
@@ -1176,6 +1261,16 @@
         IndexedItemCollection indexedItems, DexMethod method, int instructionOffset) {
       value.collectIndexedItems(indexedItems, method, instructionOffset);
     }
+
+    @Override
+    public boolean isDexValueMethod() {
+      return true;
+    }
+
+    @Override
+    public DexValueMethod asDexValueMethod() {
+      return this;
+    }
   }
 
   static public class DexValueEnum extends NestedDexValue<DexField> {
@@ -1185,7 +1280,7 @@
     }
 
     @Override
-    protected DexValueKind getValueKind() {
+    public DexValueKind getValueKind() {
       return DexValueKind.ENUM;
     }
 
@@ -1194,6 +1289,16 @@
         IndexedItemCollection indexedItems, DexMethod method, int instructionOffset) {
       value.collectIndexedItems(indexedItems, method, instructionOffset);
     }
+
+    @Override
+    public boolean isDexValueEnum() {
+      return true;
+    }
+
+    @Override
+    public DexValueEnum asDexValueEnum() {
+      return this;
+    }
   }
 
   static public class DexValueMethodType extends NestedDexValue<DexProto> {
@@ -1203,12 +1308,17 @@
     }
 
     @Override
+    public boolean isDexValueMethodType() {
+      return true;
+    }
+
+    @Override
     public DexValueMethodType asDexValueMethodType() {
       return this;
     }
 
     @Override
-    protected DexValueKind getValueKind() {
+    public DexValueKind getValueKind() {
       return DexValueKind.METHOD_TYPE;
     }
 
@@ -1232,6 +1342,11 @@
     }
 
     @Override
+    public DexValueKind getValueKind() {
+      return DexValueKind.ARRAY;
+    }
+
+    @Override
     public void collectIndexedItems(IndexedItemCollection indexedItems,
         DexMethod method, int instructionOffset) {
       collectAll(indexedItems, values);
@@ -1310,6 +1425,21 @@
     }
 
     @Override
+    public DexValueKind getValueKind() {
+      return DexValueKind.ANNOTATION;
+    }
+
+    @Override
+    public boolean isDexValueAnnotation() {
+      return true;
+    }
+
+    @Override
+    public DexValueAnnotation asDexValueAnnotation() {
+      return this;
+    }
+
+    @Override
     public void collectIndexedItems(IndexedItemCollection indexedItems,
         DexMethod method, int instructionOffset) {
       value.collectIndexedItems(indexedItems, method, instructionOffset);
@@ -1377,6 +1507,11 @@
     }
 
     @Override
+    public DexValueKind getValueKind() {
+      return DexValueKind.NULL;
+    }
+
+    @Override
     public DexType getType(DexItemFactory factory) {
       throw new Unreachable();
     }
@@ -1421,7 +1556,7 @@
       if (other == this) {
         return true;
       }
-      return (other instanceof DexValueNull);
+      return other instanceof DexValueNull;
     }
 
     @Override
@@ -1458,6 +1593,11 @@
     }
 
     @Override
+    public DexValueKind getValueKind() {
+      return DexValueKind.BOOLEAN;
+    }
+
+    @Override
     public DexType getType(DexItemFactory factory) {
       return factory.booleanType;
     }
@@ -1502,7 +1642,7 @@
       if (other == this) {
         return true;
       }
-      return (other instanceof DexValueBoolean) && ((DexValueBoolean) other).value == value;
+      return other instanceof DexValueBoolean && ((DexValueBoolean) other).value == value;
     }
 
     @Override
@@ -1524,12 +1664,17 @@
     }
 
     @Override
+    public boolean isDexValueMethodHandle() {
+      return true;
+    }
+
+    @Override
     public DexValueMethodHandle asDexValueMethodHandle() {
       return this;
     }
 
     @Override
-    protected DexValueKind getValueKind() {
+    public DexValueKind getValueKind() {
       return DexValueKind.METHOD_HANDLE;
     }
 
diff --git a/src/main/java/com/android/tools/r8/graph/DirectMappedDexApplication.java b/src/main/java/com/android/tools/r8/graph/DirectMappedDexApplication.java
index b8210c8..03c329c 100644
--- a/src/main/java/com/android/tools/r8/graph/DirectMappedDexApplication.java
+++ b/src/main/java/com/android/tools/r8/graph/DirectMappedDexApplication.java
@@ -243,7 +243,7 @@
       return self();
     }
 
-    public Builder addClasspathClasses(List<DexClasspathClass> classes) {
+    public Builder addClasspathClasses(Collection<DexClasspathClass> classes) {
       classpathClasses =
           ImmutableList.<DexClasspathClass>builder()
               .addAll(classpathClasses)
diff --git a/src/main/java/com/android/tools/r8/graph/MethodArrayBacking.java b/src/main/java/com/android/tools/r8/graph/MethodArrayBacking.java
index 7800d81a..4eda679 100644
--- a/src/main/java/com/android/tools/r8/graph/MethodArrayBacking.java
+++ b/src/main/java/com/android/tools/r8/graph/MethodArrayBacking.java
@@ -3,7 +3,6 @@
 // BSD-style license that can be found in the LICENSE file.
 package com.android.tools.r8.graph;
 
-import com.android.tools.r8.utils.InternalOptions;
 import com.android.tools.r8.utils.PredicateUtils;
 import com.android.tools.r8.utils.TraversalContinuation;
 import com.google.common.base.MoreObjects;
@@ -11,7 +10,6 @@
 import com.google.common.collect.Sets;
 import java.util.Arrays;
 import java.util.Collection;
-import java.util.Collections;
 import java.util.List;
 import java.util.Set;
 import java.util.function.Function;
@@ -22,16 +20,23 @@
   private DexEncodedMethod[] directMethods = DexEncodedMethod.EMPTY_ARRAY;
   private DexEncodedMethod[] virtualMethods = DexEncodedMethod.EMPTY_ARRAY;
 
-  private boolean belongsToDirectPool(DexEncodedMethod method) {
-    return method.accessFlags.isStatic()
-        || method.accessFlags.isPrivate()
-        || method.accessFlags.isConstructor();
+  private boolean verifyNoDuplicateMethods() {
+    Set<DexMethod> unique = Sets.newIdentityHashSet();
+    forEachMethod(
+        method -> {
+          boolean changed = unique.add(method.method);
+          assert changed : "Duplicate method `" + method.method.toSourceString() + "`";
+        });
+    return true;
   }
 
-  private boolean belongsToVirtualPool(DexEncodedMethod method) {
-    return !belongsToDirectPool(method);
+  @Override
+  boolean verify() {
+    assert verifyNoDuplicateMethods();
+    return true;
   }
 
+  @Override
   int size() {
     return directMethods.length + virtualMethods.length;
   }
@@ -53,27 +58,19 @@
     return TraversalContinuation.CONTINUE;
   }
 
+  @Override
   public Iterable<DexEncodedMethod> methods() {
     return Iterables.concat(Arrays.asList(directMethods), Arrays.asList(virtualMethods));
   }
 
+  @Override
   List<DexEncodedMethod> directMethods() {
     assert directMethods != null;
-    if (InternalOptions.assertionsEnabled()) {
-      return Collections.unmodifiableList(Arrays.asList(directMethods));
-    }
     return Arrays.asList(directMethods);
   }
 
-  void appendDirectMethod(DexEncodedMethod method) {
-    DexEncodedMethod[] newMethods = new DexEncodedMethod[directMethods.length + 1];
-    System.arraycopy(directMethods, 0, newMethods, 0, directMethods.length);
-    newMethods[directMethods.length] = method;
-    directMethods = newMethods;
-    assert verifyNoDuplicateMethods();
-  }
-
-  void appendDirectMethods(Collection<DexEncodedMethod> methods) {
+  @Override
+  void addDirectMethods(Collection<DexEncodedMethod> methods) {
     DexEncodedMethod[] newMethods = new DexEncodedMethod[directMethods.length + methods.size()];
     System.arraycopy(directMethods, 0, newMethods, 0, directMethods.length);
     int i = directMethods.length;
@@ -92,6 +89,7 @@
     directMethods = newMethods;
   }
 
+  @Override
   void removeDirectMethod(DexMethod method) {
     int index = -1;
     for (int i = 0; i < directMethods.length; i++) {
@@ -105,33 +103,20 @@
     }
   }
 
-  void setDirectMethod(int index, DexEncodedMethod method) {
-    directMethods[index] = method;
-    assert verifyNoDuplicateMethods();
-  }
-
+  @Override
   void setDirectMethods(DexEncodedMethod[] methods) {
     directMethods = MoreObjects.firstNonNull(methods, DexEncodedMethod.EMPTY_ARRAY);
     assert verifyNoDuplicateMethods();
   }
 
+  @Override
   List<DexEncodedMethod> virtualMethods() {
     assert virtualMethods != null;
-    if (InternalOptions.assertionsEnabled()) {
-      return Collections.unmodifiableList(Arrays.asList(virtualMethods));
-    }
     return Arrays.asList(virtualMethods);
   }
 
-  void appendVirtualMethod(DexEncodedMethod method) {
-    DexEncodedMethod[] newMethods = new DexEncodedMethod[virtualMethods.length + 1];
-    System.arraycopy(virtualMethods, 0, newMethods, 0, virtualMethods.length);
-    newMethods[virtualMethods.length] = method;
-    virtualMethods = newMethods;
-    assert verifyNoDuplicateMethods();
-  }
-
-  void appendVirtualMethods(Collection<DexEncodedMethod> methods) {
+  @Override
+  void addVirtualMethods(Collection<DexEncodedMethod> methods) {
     DexEncodedMethod[] newMethods = new DexEncodedMethod[virtualMethods.length + methods.size()];
     System.arraycopy(virtualMethods, 0, newMethods, 0, virtualMethods.length);
     int i = virtualMethods.length;
@@ -143,11 +128,13 @@
     assert verifyNoDuplicateMethods();
   }
 
+  @Override
   void setVirtualMethods(DexEncodedMethod[] methods) {
     virtualMethods = MoreObjects.firstNonNull(methods, DexEncodedMethod.EMPTY_ARRAY);
     assert verifyNoDuplicateMethods();
   }
 
+  @Override
   void virtualizeMethods(Set<DexEncodedMethod> privateInstanceMethods) {
     int vLen = virtualMethods.length;
     int dLen = directMethods.length;
@@ -174,6 +161,7 @@
     setVirtualMethods(newVirtualMethods);
   }
 
+  @Override
   DexEncodedMethod getDirectMethod(DexMethod method) {
     for (DexEncodedMethod directMethod : directMethods) {
       if (method.match(directMethod)) {
@@ -183,10 +171,12 @@
     return null;
   }
 
+  @Override
   DexEncodedMethod getDirectMethod(Predicate<DexEncodedMethod> predicate) {
     return PredicateUtils.findFirst(directMethods, predicate);
   }
 
+  @Override
   DexEncodedMethod getVirtualMethod(DexMethod method) {
     for (DexEncodedMethod virtualMethod : virtualMethods) {
       if (method.match(virtualMethod)) {
@@ -196,51 +186,41 @@
     return null;
   }
 
+  @Override
   DexEncodedMethod getVirtualMethod(Predicate<DexEncodedMethod> predicate) {
     return PredicateUtils.findFirst(virtualMethods, predicate);
   }
 
+  @Override
   DexEncodedMethod getMethod(DexMethod method) {
     DexEncodedMethod result = getDirectMethod(method);
     return result == null ? getVirtualMethod(method) : result;
   }
 
+  @Override
   void addMethod(DexEncodedMethod method) {
-    if (method.accessFlags.isStatic()
-        || method.accessFlags.isPrivate()
-        || method.accessFlags.isConstructor()) {
+    if (belongsToDirectPool(method)) {
       addDirectMethod(method);
     } else {
       addVirtualMethod(method);
     }
   }
 
+  @Override
   void addVirtualMethod(DexEncodedMethod virtualMethod) {
-    assert !virtualMethod.accessFlags.isStatic();
-    assert !virtualMethod.accessFlags.isPrivate();
-    assert !virtualMethod.accessFlags.isConstructor();
+    assert belongsToVirtualPool(virtualMethod);
     virtualMethods = Arrays.copyOf(virtualMethods, virtualMethods.length + 1);
     virtualMethods[virtualMethods.length - 1] = virtualMethod;
   }
 
-  void addDirectMethod(DexEncodedMethod staticMethod) {
-    assert staticMethod.accessFlags.isStatic()
-        || staticMethod.accessFlags.isPrivate()
-        || staticMethod.accessFlags.isConstructor();
+  @Override
+  void addDirectMethod(DexEncodedMethod directMethod) {
+    assert belongsToDirectPool(directMethod);
     directMethods = Arrays.copyOf(directMethods, directMethods.length + 1);
-    directMethods[directMethods.length - 1] = staticMethod;
+    directMethods[directMethods.length - 1] = directMethod;
   }
 
-  boolean verifyNoDuplicateMethods() {
-    Set<DexMethod> unique = Sets.newIdentityHashSet();
-    forEachMethod(
-        method -> {
-          boolean changed = unique.add(method.method);
-          assert changed : "Duplicate method `" + method.method.toSourceString() + "`";
-        });
-    return true;
-  }
-
+  @Override
   public DexEncodedMethod replaceDirectMethod(
       DexMethod method, Function<DexEncodedMethod, DexEncodedMethod> replacement) {
     for (int i = 0; i < directMethods.length; i++) {
@@ -255,6 +235,7 @@
     return null;
   }
 
+  @Override
   public DexEncodedMethod replaceDirectMethodWithVirtualMethod(
       DexMethod method, Function<DexEncodedMethod, DexEncodedMethod> replacement) {
     for (int i = 0; i < directMethods.length; i++) {
@@ -263,18 +244,20 @@
         DexEncodedMethod newMethod = replacement.apply(directMethod);
         assert belongsToVirtualPool(newMethod);
         removeDirectMethod(i);
-        appendVirtualMethod(newMethod);
+        addVirtualMethod(newMethod);
         return newMethod;
       }
     }
     return null;
   }
 
+  @Override
   public void replaceMethods(Function<DexEncodedMethod, DexEncodedMethod> replacement) {
     replaceDirectMethods(replacement);
     replaceVirtualMethods(replacement);
   }
 
+  @Override
   public void replaceDirectMethods(Function<DexEncodedMethod, DexEncodedMethod> replacement) {
     for (int i = 0; i < directMethods.length; i++) {
       DexEncodedMethod method = directMethods[i];
@@ -287,6 +270,7 @@
     }
   }
 
+  @Override
   public void replaceVirtualMethods(Function<DexEncodedMethod, DexEncodedMethod> replacement) {
     for (int i = 0; i < virtualMethods.length; i++) {
       DexEncodedMethod method = virtualMethods[i];
diff --git a/src/main/java/com/android/tools/r8/graph/MethodCollection.java b/src/main/java/com/android/tools/r8/graph/MethodCollection.java
index a4f3d5a..269b36b 100644
--- a/src/main/java/com/android/tools/r8/graph/MethodCollection.java
+++ b/src/main/java/com/android/tools/r8/graph/MethodCollection.java
@@ -1,12 +1,13 @@
 package com.android.tools.r8.graph;
 
+import com.android.tools.r8.utils.InternalOptions;
 import com.android.tools.r8.utils.IterableUtils;
 import com.android.tools.r8.utils.TraversalContinuation;
 import java.util.ArrayList;
 import java.util.Arrays;
 import java.util.Collection;
+import java.util.Collections;
 import java.util.List;
-import java.util.Optional;
 import java.util.Set;
 import java.util.function.Consumer;
 import java.util.function.Function;
@@ -14,12 +15,37 @@
 
 public class MethodCollection {
 
-  private final DexClass holder;
-  private final MethodArrayBacking backing = new MethodArrayBacking();
-  private Optional<DexEncodedMethod> cachedClassInitializer = null;
+  // Threshold between using an array and a map for the backing store.
+  // Compiling R8 plus library shows classes with up to 30 methods account for about 95% of classes.
+  private static final int ARRAY_BACKING_THRESHOLD = 30;
 
-  public MethodCollection(DexClass holder) {
+  private final DexClass holder;
+  private final MethodCollectionBacking backing;
+  private DexEncodedMethod cachedClassInitializer = DexEncodedMethod.SENTINEL;
+
+  public MethodCollection(
+      DexClass holder, DexEncodedMethod[] directMethods, DexEncodedMethod[] virtualMethods) {
     this.holder = holder;
+    if (directMethods.length + virtualMethods.length > ARRAY_BACKING_THRESHOLD) {
+      backing = new MethodMapBacking();
+    } else {
+      backing = new MethodArrayBacking();
+    }
+    backing.setDirectMethods(directMethods);
+    backing.setVirtualMethods(virtualMethods);
+  }
+
+  private void resetCaches() {
+    resetDirectMethodCaches();
+    resetVirtualMethodCaches();
+  }
+
+  private void resetDirectMethodCaches() {
+    resetClassInitializerCache();
+  }
+
+  private void resetVirtualMethodCaches() {
+    // Nothing to do.
   }
 
   public int size() {
@@ -50,10 +76,16 @@
   }
 
   public List<DexEncodedMethod> directMethods() {
+    if (InternalOptions.assertionsEnabled()) {
+      return Collections.unmodifiableList(backing.directMethods());
+    }
     return backing.directMethods();
   }
 
   public List<DexEncodedMethod> virtualMethods() {
+    if (InternalOptions.assertionsEnabled()) {
+      return Collections.unmodifiableList(backing.virtualMethods());
+    }
     return backing.virtualMethods();
   }
 
@@ -77,48 +109,56 @@
     return backing.getVirtualMethod(predicate);
   }
 
+  private void resetClassInitializerCache() {
+    cachedClassInitializer = DexEncodedMethod.SENTINEL;
+  }
+
   public DexEncodedMethod getClassInitializer() {
-    if (cachedClassInitializer == null) {
-      cachedClassInitializer = Optional.empty();
+    if (cachedClassInitializer == DexEncodedMethod.SENTINEL) {
+      cachedClassInitializer = null;
       for (DexEncodedMethod directMethod : directMethods()) {
         if (directMethod.isClassInitializer()) {
-          cachedClassInitializer = Optional.of(directMethod);
+          cachedClassInitializer = directMethod;
           break;
         }
       }
     }
-    return cachedClassInitializer.orElse(null);
+    return cachedClassInitializer;
   }
 
   public void addMethod(DexEncodedMethod method) {
+    resetCaches();
     backing.addMethod(method);
   }
 
   public void addVirtualMethod(DexEncodedMethod virtualMethod) {
+    resetVirtualMethodCaches();
     backing.addVirtualMethod(virtualMethod);
   }
 
   public void addDirectMethod(DexEncodedMethod directMethod) {
-    cachedClassInitializer = null;
+    resetDirectMethodCaches();
     backing.addDirectMethod(directMethod);
   }
 
   public DexEncodedMethod replaceDirectMethod(
       DexMethod method, Function<DexEncodedMethod, DexEncodedMethod> replacement) {
-    cachedClassInitializer = null;
+    resetDirectMethodCaches();
     return backing.replaceDirectMethod(method, replacement);
   }
 
   public void replaceMethods(Function<DexEncodedMethod, DexEncodedMethod> replacement) {
+    resetCaches();
     backing.replaceMethods(replacement);
   }
 
   public void replaceVirtualMethods(Function<DexEncodedMethod, DexEncodedMethod> replacement) {
+    resetVirtualMethodCaches();
     backing.replaceVirtualMethods(replacement);
   }
 
   public void replaceDirectMethods(Function<DexEncodedMethod, DexEncodedMethod> replacement) {
-    cachedClassInitializer = null;
+    resetDirectMethodCaches();
     backing.replaceDirectMethods(replacement);
   }
 
@@ -131,49 +171,41 @@
    */
   public DexEncodedMethod replaceDirectMethodWithVirtualMethod(
       DexMethod method, Function<DexEncodedMethod, DexEncodedMethod> replacement) {
-    // The class initializer can never by converted to a virtual.
+    resetCaches();
     return backing.replaceDirectMethodWithVirtualMethod(method, replacement);
   }
 
-  public void appendDirectMethod(DexEncodedMethod method) {
-    assert verifyCorrectnessOfMethodHolder(method);
-    cachedClassInitializer = null;
-    backing.appendDirectMethod(method);
-  }
-
-  public void appendDirectMethods(Collection<DexEncodedMethod> methods) {
+  public void addDirectMethods(Collection<DexEncodedMethod> methods) {
     assert verifyCorrectnessOfMethodHolders(methods);
-    cachedClassInitializer = null;
-    backing.appendDirectMethods(methods);
+    resetDirectMethodCaches();
+    backing.addDirectMethods(methods);
   }
 
   public void removeDirectMethod(DexMethod method) {
-    cachedClassInitializer = null;
+    resetDirectMethodCaches();
     backing.removeDirectMethod(method);
   }
 
   public void setDirectMethods(DexEncodedMethod[] methods) {
     assert verifyCorrectnessOfMethodHolders(methods);
-    cachedClassInitializer = null;
+    resetDirectMethodCaches();
     backing.setDirectMethods(methods);
   }
 
-  public void appendVirtualMethod(DexEncodedMethod method) {
-    assert verifyCorrectnessOfMethodHolder(method);
-    backing.appendVirtualMethod(method);
-  }
-
-  public void appendVirtualMethods(Collection<DexEncodedMethod> methods) {
+  public void addVirtualMethods(Collection<DexEncodedMethod> methods) {
     assert verifyCorrectnessOfMethodHolders(methods);
-    backing.appendVirtualMethods(methods);
+    resetVirtualMethodCaches();
+    backing.addVirtualMethods(methods);
   }
 
   public void setVirtualMethods(DexEncodedMethod[] methods) {
     assert verifyCorrectnessOfMethodHolders(methods);
+    resetVirtualMethodCaches();
     backing.setVirtualMethods(methods);
   }
 
   public void virtualizeMethods(Set<DexEncodedMethod> privateInstanceMethods) {
+    resetVirtualMethodCaches();
     backing.virtualizeMethods(privateInstanceMethods);
   }
 
@@ -191,7 +223,7 @@
         method -> {
           assert verifyCorrectnessOfMethodHolder(method);
         });
-    assert backing.verifyNoDuplicateMethods();
+    assert backing.verify();
     return true;
   }
 
diff --git a/src/main/java/com/android/tools/r8/graph/MethodCollectionBacking.java b/src/main/java/com/android/tools/r8/graph/MethodCollectionBacking.java
index b50adf6..427f022 100644
--- a/src/main/java/com/android/tools/r8/graph/MethodCollectionBacking.java
+++ b/src/main/java/com/android/tools/r8/graph/MethodCollectionBacking.java
@@ -4,11 +4,35 @@
 package com.android.tools.r8.graph;
 
 import com.android.tools.r8.utils.TraversalContinuation;
+import java.util.Collection;
+import java.util.List;
+import java.util.Set;
 import java.util.function.Consumer;
 import java.util.function.Function;
+import java.util.function.Predicate;
 
 public abstract class MethodCollectionBacking {
 
+  // Internal consistency.
+
+  abstract boolean verify();
+
+  boolean belongsToDirectPool(DexEncodedMethod method) {
+    return method.accessFlags.isStatic()
+        || method.accessFlags.isPrivate()
+        || method.accessFlags.isConstructor();
+  }
+
+  boolean belongsToVirtualPool(DexEncodedMethod method) {
+    return !belongsToDirectPool(method);
+  }
+
+  // Collection methods.
+
+  abstract int size();
+
+  // Traversal methods.
+
   abstract TraversalContinuation traverse(Function<DexEncodedMethod, TraversalContinuation> fn);
 
   void forEachMethod(Consumer<DexEncodedMethod> fn) {
@@ -18,4 +42,58 @@
           return TraversalContinuation.CONTINUE;
         });
   }
+
+  abstract Iterable<DexEncodedMethod> methods();
+
+  abstract List<DexEncodedMethod> directMethods();
+
+  abstract List<DexEncodedMethod> virtualMethods();
+
+  // Lookup methods.
+
+  abstract DexEncodedMethod getMethod(DexMethod method);
+
+  abstract DexEncodedMethod getDirectMethod(DexMethod method);
+
+  abstract DexEncodedMethod getDirectMethod(Predicate<DexEncodedMethod> predicate);
+
+  abstract DexEncodedMethod getVirtualMethod(DexMethod method);
+
+  abstract DexEncodedMethod getVirtualMethod(Predicate<DexEncodedMethod> predicate);
+
+  // Amendment methods.
+
+  abstract void addMethod(DexEncodedMethod method);
+
+  abstract void addDirectMethod(DexEncodedMethod method);
+
+  abstract void addVirtualMethod(DexEncodedMethod method);
+
+  abstract void addDirectMethods(Collection<DexEncodedMethod> methods);
+
+  abstract void addVirtualMethods(Collection<DexEncodedMethod> methods);
+
+  // Removal methods.
+
+  abstract void removeDirectMethod(DexMethod method);
+
+  // Replacement/mutation methods.
+
+  abstract void setDirectMethods(DexEncodedMethod[] methods);
+
+  abstract void setVirtualMethods(DexEncodedMethod[] methods);
+
+  abstract void replaceMethods(Function<DexEncodedMethod, DexEncodedMethod> replacement);
+
+  abstract void replaceDirectMethods(Function<DexEncodedMethod, DexEncodedMethod> replacement);
+
+  abstract void replaceVirtualMethods(Function<DexEncodedMethod, DexEncodedMethod> replacement);
+
+  abstract DexEncodedMethod replaceDirectMethod(
+      DexMethod method, Function<DexEncodedMethod, DexEncodedMethod> replacement);
+
+  abstract DexEncodedMethod replaceDirectMethodWithVirtualMethod(
+      DexMethod method, Function<DexEncodedMethod, DexEncodedMethod> replacement);
+
+  abstract void virtualizeMethods(Set<DexEncodedMethod> privateInstanceMethods);
 }
diff --git a/src/main/java/com/android/tools/r8/graph/MethodMapBacking.java b/src/main/java/com/android/tools/r8/graph/MethodMapBacking.java
new file mode 100644
index 0000000..8afffc6
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/graph/MethodMapBacking.java
@@ -0,0 +1,306 @@
+// 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 com.android.tools.r8.utils.Box;
+import com.android.tools.r8.utils.MethodSignatureEquivalence;
+import com.android.tools.r8.utils.TraversalContinuation;
+import com.google.common.base.Equivalence.Wrapper;
+import it.unimi.dsi.fastutil.objects.Object2ReferenceLinkedOpenHashMap;
+import it.unimi.dsi.fastutil.objects.Object2ReferenceMap;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.List;
+import java.util.Map.Entry;
+import java.util.Set;
+import java.util.function.Function;
+import java.util.function.Predicate;
+
+public class MethodMapBacking extends MethodCollectionBacking {
+
+  private Object2ReferenceMap<Wrapper<DexMethod>, DexEncodedMethod> methodMap;
+
+  public MethodMapBacking() {
+    this.methodMap = createMap();
+  }
+
+  private static Object2ReferenceMap<Wrapper<DexMethod>, DexEncodedMethod> createMap() {
+    // Maintain a linked map so the output order remains a deterministic function of the input.
+    return new Object2ReferenceLinkedOpenHashMap<>();
+  }
+
+  private static Object2ReferenceMap<Wrapper<DexMethod>, DexEncodedMethod> createMap(int capacity) {
+    // Maintain a linked map so the output order remains a deterministic function of the input.
+    return new Object2ReferenceLinkedOpenHashMap<>(capacity);
+  }
+
+  private Wrapper<DexMethod> wrap(DexMethod method) {
+    return MethodSignatureEquivalence.get().wrap(method);
+  }
+
+  private void replace(Wrapper<DexMethod> existingKey, DexEncodedMethod method) {
+    if (existingKey.get().match(method)) {
+      methodMap.put(existingKey, method);
+    } else {
+      methodMap.remove(existingKey);
+      methodMap.put(wrap(method.method), method);
+    }
+  }
+
+  private void rehash() {
+    Object2ReferenceMap<Wrapper<DexMethod>, DexEncodedMethod> newMap = createMap(methodMap.size());
+    for (DexEncodedMethod method : methodMap.values()) {
+      newMap.put(wrap(method.method), method);
+    }
+    methodMap = newMap;
+  }
+
+  @Override
+  boolean verify() {
+    methodMap.forEach(
+        (key, method) -> {
+          assert key.get().match(method);
+        });
+    return true;
+  }
+
+  @Override
+  int size() {
+    return methodMap.size();
+  }
+
+  @Override
+  TraversalContinuation traverse(Function<DexEncodedMethod, TraversalContinuation> fn) {
+    for (Entry<Wrapper<DexMethod>, DexEncodedMethod> entry : methodMap.object2ReferenceEntrySet()) {
+      TraversalContinuation result = fn.apply(entry.getValue());
+      if (result.shouldBreak()) {
+        return result;
+      }
+    }
+    return TraversalContinuation.CONTINUE;
+  }
+
+  @Override
+  Iterable<DexEncodedMethod> methods() {
+    return methodMap.values();
+  }
+
+  @Override
+  List<DexEncodedMethod> directMethods() {
+    List<DexEncodedMethod> methods = new ArrayList<>(size());
+    forEachMethod(
+        method -> {
+          if (belongsToDirectPool(method)) {
+            methods.add(method);
+          }
+        });
+    return methods;
+  }
+
+  @Override
+  List<DexEncodedMethod> virtualMethods() {
+    List<DexEncodedMethod> methods = new ArrayList<>(size());
+    forEachMethod(
+        method -> {
+          if (belongsToVirtualPool(method)) {
+            methods.add(method);
+          }
+        });
+    return methods;
+  }
+
+  @Override
+  DexEncodedMethod getMethod(DexMethod method) {
+    return methodMap.get(wrap(method));
+  }
+
+  private DexEncodedMethod getMethod(Predicate<DexEncodedMethod> predicate) {
+    Box<DexEncodedMethod> found = new Box<>();
+    traverse(
+        method -> {
+          if (predicate.test(method)) {
+            found.set(method);
+            return TraversalContinuation.BREAK;
+          }
+          return TraversalContinuation.CONTINUE;
+        });
+    return found.get();
+  }
+
+  @Override
+  DexEncodedMethod getDirectMethod(DexMethod method) {
+    DexEncodedMethod definition = getMethod(method);
+    return definition != null && belongsToDirectPool(definition) ? definition : null;
+  }
+
+  @Override
+  DexEncodedMethod getDirectMethod(Predicate<DexEncodedMethod> predicate) {
+    Predicate<DexEncodedMethod> isDirect = this::belongsToDirectPool;
+    return getMethod(isDirect.and(predicate));
+  }
+
+  @Override
+  DexEncodedMethod getVirtualMethod(DexMethod method) {
+    DexEncodedMethod definition = getMethod(method);
+    return definition != null && belongsToVirtualPool(definition) ? definition : null;
+  }
+
+  @Override
+  DexEncodedMethod getVirtualMethod(Predicate<DexEncodedMethod> predicate) {
+    Predicate<DexEncodedMethod> isVirtual = this::belongsToVirtualPool;
+    return getMethod(isVirtual.and(predicate));
+  }
+
+  @Override
+  void addMethod(DexEncodedMethod method) {
+    Wrapper<DexMethod> key = wrap(method.method);
+    DexEncodedMethod old = methodMap.put(key, method);
+    assert old == null;
+  }
+
+  @Override
+  void addDirectMethod(DexEncodedMethod method) {
+    assert belongsToDirectPool(method);
+    addMethod(method);
+  }
+
+  @Override
+  void addVirtualMethod(DexEncodedMethod method) {
+    assert belongsToVirtualPool(method);
+    addMethod(method);
+  }
+
+  @Override
+  void addDirectMethods(Collection<DexEncodedMethod> methods) {
+    for (DexEncodedMethod method : methods) {
+      addDirectMethod(method);
+    }
+  }
+
+  @Override
+  void addVirtualMethods(Collection<DexEncodedMethod> methods) {
+    for (DexEncodedMethod method : methods) {
+      addVirtualMethod(method);
+    }
+  }
+
+  @Override
+  void removeDirectMethod(DexMethod method) {
+    methodMap.remove(wrap(method));
+  }
+
+  @Override
+  void setDirectMethods(DexEncodedMethod[] methods) {
+    if ((methods == null || methods.length == 0) && methodMap.isEmpty()) {
+      return;
+    }
+    if (methods == null) {
+      methods = DexEncodedMethod.EMPTY_ARRAY;
+    }
+    Object2ReferenceMap<Wrapper<DexMethod>, DexEncodedMethod> newMap =
+        createMap(size() + methods.length);
+    forEachMethod(
+        method -> {
+          if (belongsToVirtualPool(method)) {
+            newMap.put(wrap(method.method), method);
+          }
+        });
+    for (DexEncodedMethod method : methods) {
+      assert belongsToDirectPool(method);
+      newMap.put(wrap(method.method), method);
+    }
+    methodMap = newMap;
+  }
+
+  @Override
+  void setVirtualMethods(DexEncodedMethod[] methods) {
+    if ((methods == null || methods.length == 0) && methodMap.isEmpty()) {
+      return;
+    }
+    if (methods == null) {
+      methods = DexEncodedMethod.EMPTY_ARRAY;
+    }
+    Object2ReferenceMap<Wrapper<DexMethod>, DexEncodedMethod> newMap =
+        createMap(size() + methods.length);
+    forEachMethod(
+        method -> {
+          if (belongsToDirectPool(method)) {
+            newMap.put(wrap(method.method), method);
+          }
+        });
+    for (DexEncodedMethod method : methods) {
+      assert belongsToVirtualPool(method);
+      newMap.put(wrap(method.method), method);
+    }
+    methodMap = newMap;
+  }
+
+  @Override
+  void replaceMethods(Function<DexEncodedMethod, DexEncodedMethod> replacement) {
+    boolean rehash = false;
+    for (Object2ReferenceMap.Entry<Wrapper<DexMethod>, DexEncodedMethod> entry :
+        methodMap.object2ReferenceEntrySet()) {
+      DexEncodedMethod newMethod = replacement.apply(entry.getValue());
+      if (newMethod != entry.getValue()) {
+        rehash = rehash || newMethod.method != entry.getKey().get();
+        entry.setValue(newMethod);
+      }
+    }
+    if (rehash) {
+      rehash();
+    }
+  }
+
+  @Override
+  void replaceDirectMethods(Function<DexEncodedMethod, DexEncodedMethod> replacement) {
+    replaceMethods(method -> belongsToDirectPool(method) ? replacement.apply(method) : method);
+  }
+
+  @Override
+  void replaceVirtualMethods(Function<DexEncodedMethod, DexEncodedMethod> replacement) {
+    replaceMethods(method -> belongsToVirtualPool(method) ? replacement.apply(method) : method);
+  }
+
+  @Override
+  DexEncodedMethod replaceDirectMethod(
+      DexMethod method, Function<DexEncodedMethod, DexEncodedMethod> replacement) {
+    Wrapper<DexMethod> key = wrap(method);
+    DexEncodedMethod existing = methodMap.get(key);
+    if (existing == null || belongsToVirtualPool(existing)) {
+      return null;
+    }
+    DexEncodedMethod newMethod = replacement.apply(existing);
+    assert belongsToDirectPool(newMethod);
+    replace(key, newMethod);
+    return newMethod;
+  }
+
+  @Override
+  DexEncodedMethod replaceDirectMethodWithVirtualMethod(
+      DexMethod method, Function<DexEncodedMethod, DexEncodedMethod> replacement) {
+    Wrapper<DexMethod> key = wrap(method);
+    DexEncodedMethod existing = methodMap.get(key);
+    if (existing == null || belongsToVirtualPool(existing)) {
+      return null;
+    }
+    DexEncodedMethod newMethod = replacement.apply(existing);
+    assert belongsToVirtualPool(newMethod);
+    replace(key, newMethod);
+    return newMethod;
+  }
+
+  @Override
+  void virtualizeMethods(Set<DexEncodedMethod> privateInstanceMethods) {
+    // This is a no-op as the virtualizer has modified the encoded method bits.
+    assert verifyVirtualizedMethods(privateInstanceMethods);
+  }
+
+  private boolean verifyVirtualizedMethods(Set<DexEncodedMethod> methods) {
+    for (DexEncodedMethod method : methods) {
+      assert belongsToVirtualPool(method);
+      assert methodMap.get(wrap(method.method)) == method;
+    }
+    return true;
+  }
+}
diff --git a/src/main/java/com/android/tools/r8/graph/ResolutionResult.java b/src/main/java/com/android/tools/r8/graph/ResolutionResult.java
index 5c1611d..e8cae8d 100644
--- a/src/main/java/com/android/tools/r8/graph/ResolutionResult.java
+++ b/src/main/java/com/android/tools/r8/graph/ResolutionResult.java
@@ -135,6 +135,10 @@
       return resolvedMethod;
     }
 
+    public DexClassAndMethod getResolutionPair() {
+      return DexClassAndMethod.create(resolvedHolder, resolvedMethod);
+    }
+
     @Override
     public boolean isSingleResolution() {
       return true;
diff --git a/src/main/java/com/android/tools/r8/graph/UseRegistry.java b/src/main/java/com/android/tools/r8/graph/UseRegistry.java
index 2c27763..1e89e13 100644
--- a/src/main/java/com/android/tools/r8/graph/UseRegistry.java
+++ b/src/main/java/com/android/tools/r8/graph/UseRegistry.java
@@ -3,14 +3,6 @@
 // BSD-style license that can be found in the LICENSE file.
 package com.android.tools.r8.graph;
 
-import com.android.tools.r8.graph.DexValue.DexValueDouble;
-import com.android.tools.r8.graph.DexValue.DexValueFloat;
-import com.android.tools.r8.graph.DexValue.DexValueInt;
-import com.android.tools.r8.graph.DexValue.DexValueLong;
-import com.android.tools.r8.graph.DexValue.DexValueMethodHandle;
-import com.android.tools.r8.graph.DexValue.DexValueMethodType;
-import com.android.tools.r8.graph.DexValue.DexValueString;
-import com.android.tools.r8.graph.DexValue.DexValueType;
 
 public abstract class UseRegistry {
 
@@ -128,22 +120,27 @@
     // Register bootstrap method arguments.
     // Only Type, MethodHandle, and MethodType need to be registered.
     for (DexValue arg : callSite.bootstrapArgs) {
-      if (arg instanceof DexValueType) {
-        registerTypeReference(((DexValueType) arg).value);
-      } else if (arg instanceof DexValueMethodHandle) {
-        DexMethodHandle handle = ((DexValueMethodHandle) arg).value;
-        MethodHandleUse use = isLambdaMetaFactory
-            ? MethodHandleUse.ARGUMENT_TO_LAMBDA_METAFACTORY
-            : MethodHandleUse.NOT_ARGUMENT_TO_LAMBDA_METAFACTORY;
-        registerMethodHandle(handle, use);
-      } else if (arg instanceof DexValueMethodType) {
-        registerProto(((DexValueMethodType) arg).value);
-      } else {
-        assert (arg instanceof DexValueInt)
-            || (arg instanceof DexValueLong)
-            || (arg instanceof DexValueFloat)
-            || (arg instanceof DexValueDouble)
-            || (arg instanceof DexValueString);
+      switch (arg.getValueKind()) {
+        case METHOD_HANDLE:
+          DexMethodHandle handle = arg.asDexValueMethodHandle().value;
+          MethodHandleUse use =
+              isLambdaMetaFactory
+                  ? MethodHandleUse.ARGUMENT_TO_LAMBDA_METAFACTORY
+                  : MethodHandleUse.NOT_ARGUMENT_TO_LAMBDA_METAFACTORY;
+          registerMethodHandle(handle, use);
+          break;
+        case METHOD_TYPE:
+          registerProto(arg.asDexValueMethodType().value);
+          break;
+        case TYPE:
+          registerTypeReference(arg.asDexValueType().value);
+          break;
+        default:
+          assert arg.isDexValueInt()
+              || arg.isDexValueLong()
+              || arg.isDexValueFloat()
+              || arg.isDexValueDouble()
+              || arg.isDexValueString();
       }
     }
   }
diff --git a/src/main/java/com/android/tools/r8/graph/analysis/DesugaredLibraryConversionWrapperAnalysis.java b/src/main/java/com/android/tools/r8/graph/analysis/DesugaredLibraryConversionWrapperAnalysis.java
index d8d48a2..8d61294 100644
--- a/src/main/java/com/android/tools/r8/graph/analysis/DesugaredLibraryConversionWrapperAnalysis.java
+++ b/src/main/java/com/android/tools/r8/graph/analysis/DesugaredLibraryConversionWrapperAnalysis.java
@@ -15,18 +15,16 @@
 import com.android.tools.r8.ir.desugar.DesugaredLibraryAPIConverter.Mode;
 import com.android.tools.r8.utils.OptionalBool;
 import java.util.ArrayList;
+import java.util.IdentityHashMap;
 import java.util.List;
 import java.util.Map;
-import java.util.Set;
-import java.util.function.Consumer;
 
 public class DesugaredLibraryConversionWrapperAnalysis extends EnqueuerAnalysis
     implements EnqueuerInvokeAnalysis {
 
   private final AppView<?> appView;
   private final DesugaredLibraryAPIConverter converter;
-  private boolean callbackGenerated = false;
-  private Map<DexProgramClass, DexProgramClass> wrappersToReverseMap = null;
+  private Map<DexType, DexProgramClass> synthesizedWrappers = new IdentityHashMap<>();
 
   public DesugaredLibraryConversionWrapperAnalysis(AppView<?> appView) {
     this.appView = appView;
@@ -69,22 +67,18 @@
   }
 
   public List<DexEncodedMethod> generateCallbackMethods() {
-    assert !callbackGenerated;
-    callbackGenerated = true;
     return converter.generateCallbackMethods();
   }
 
-  public Set<DexProgramClass> generateWrappers() {
-    assert wrappersToReverseMap == null;
-    wrappersToReverseMap = converter.synthesizeWrappersAndMapToReverse();
-    return wrappersToReverseMap.keySet();
+  public List<DexProgramClass> generateWrappers() {
+    return converter.synthesizeWrappers(synthesizedWrappers);
   }
 
   // Generate a mock classpath class for all vivified types.
   // Types will be available at runtime in the desugared library dex file.
-  public List<DexClasspathClass> generateWrappersSuperTypeMock() {
+  public List<DexClasspathClass> generateWrappersSuperTypeMock(List<DexProgramClass> wrappers) {
     List<DexClasspathClass> classpathClasses = new ArrayList<>();
-    for (DexProgramClass wrapper : wrappersToReverseMap.keySet()) {
+    for (DexProgramClass wrapper : wrappers) {
       boolean mockIsInterface = wrapper.interfaces.size() == 1;
       DexType mockType = mockIsInterface ? wrapper.interfaces.values[0] : wrapper.superType;
       if (appView.definitionFor(mockType) == null) {
@@ -106,37 +100,4 @@
     }
     return classpathClasses;
   }
-
-  public DesugaredLibraryConversionWrapperAnalysis registerWrite(
-      DexProgramClass wrapper, Consumer<DexEncodedMethod> registration) {
-    registration.accept(getInitializer(wrapper));
-    return this;
-  }
-
-  public DesugaredLibraryConversionWrapperAnalysis registerReads(
-      DexProgramClass wrapper, Consumer<DexEncodedMethod> registration) {
-    // The field of each wrapper is read exclusively in all virtual methods and the reverse wrapper
-    // convert method.
-    for (DexEncodedMethod virtualMethod : wrapper.virtualMethods()) {
-      registration.accept(virtualMethod);
-    }
-    DexProgramClass reverseWrapper = wrappersToReverseMap.get(wrapper);
-    assert reverseWrapper != null;
-    registration.accept(getConvertMethod(reverseWrapper));
-    return this;
-  }
-
-  private DexEncodedMethod getInitializer(DexProgramClass wrapper) {
-    DexEncodedMethod initializer =
-        wrapper.lookupDirectMethod(DexEncodedMethod::isInstanceInitializer);
-    assert initializer != null;
-    return initializer;
-  }
-
-  private DexEncodedMethod getConvertMethod(DexProgramClass wrapper) {
-    DexEncodedMethod convertMethod = wrapper.lookupDirectMethod(DexEncodedMethod::isStatic);
-    assert convertMethod != null;
-    assert convertMethod.method.name == appView.dexItemFactory().convertMethodName;
-    return convertMethod;
-  }
 }
diff --git a/src/main/java/com/android/tools/r8/graph/analysis/EnqueuerAnalysis.java b/src/main/java/com/android/tools/r8/graph/analysis/EnqueuerAnalysis.java
index 8374410..29d7dcc 100644
--- a/src/main/java/com/android/tools/r8/graph/analysis/EnqueuerAnalysis.java
+++ b/src/main/java/com/android/tools/r8/graph/analysis/EnqueuerAnalysis.java
@@ -9,6 +9,7 @@
 import com.android.tools.r8.graph.DexProgramClass;
 import com.android.tools.r8.shaking.Enqueuer;
 import com.android.tools.r8.shaking.EnqueuerWorklist;
+import com.android.tools.r8.utils.Timing;
 
 public abstract class EnqueuerAnalysis {
 
@@ -28,7 +29,7 @@
    * Called when the Enqueuer reaches a fixpoint. This may happen multiple times, since each
    * analysis may enqueue items into the worklist upon the fixpoint using {@param worklist}.
    */
-  public void notifyFixpoint(Enqueuer enqueuer, EnqueuerWorklist worklist) {}
+  public void notifyFixpoint(Enqueuer enqueuer, EnqueuerWorklist worklist, Timing timing) {}
 
   /**
    * Called when the Enqueuer has reached the final fixpoint. Each analysis may use this callback to
diff --git a/src/main/java/com/android/tools/r8/graph/analysis/InitializedClassesInInstanceMethodsAnalysis.java b/src/main/java/com/android/tools/r8/graph/analysis/InitializedClassesInInstanceMethodsAnalysis.java
index 7df903b..f32143d 100644
--- a/src/main/java/com/android/tools/r8/graph/analysis/InitializedClassesInInstanceMethodsAnalysis.java
+++ b/src/main/java/com/android/tools/r8/graph/analysis/InitializedClassesInInstanceMethodsAnalysis.java
@@ -10,7 +10,7 @@
 import com.android.tools.r8.graph.DexEncodedMethod;
 import com.android.tools.r8.graph.DexProgramClass;
 import com.android.tools.r8.graph.DexType;
-import com.android.tools.r8.ir.analysis.type.ClassTypeLatticeElement;
+import com.android.tools.r8.ir.analysis.type.ClassTypeElement;
 import java.util.IdentityHashMap;
 import java.util.Map;
 
@@ -79,7 +79,7 @@
         mapping.getOrDefault(key, guaranteedToBeInitialized);
     mapping.put(
         key,
-        ClassTypeLatticeElement.computeLeastUpperBoundOfClasses(
+        ClassTypeElement.computeLeastUpperBoundOfClasses(
             appInfo, guaranteedToBeInitialized, existingGuaranteedToBeInitialized));
   }
 
diff --git a/src/main/java/com/android/tools/r8/inspector/BooleanValueInspector.java b/src/main/java/com/android/tools/r8/inspector/BooleanValueInspector.java
index 1fba365..105f577 100644
--- a/src/main/java/com/android/tools/r8/inspector/BooleanValueInspector.java
+++ b/src/main/java/com/android/tools/r8/inspector/BooleanValueInspector.java
@@ -3,6 +3,9 @@
 // BSD-style license that can be found in the LICENSE file.
 package com.android.tools.r8.inspector;
 
+import com.android.tools.r8.Keep;
+
+@Keep
 public interface BooleanValueInspector extends ValueInspector {
   boolean getBooleanValue();
 }
diff --git a/src/main/java/com/android/tools/r8/inspector/ByteValueInspector.java b/src/main/java/com/android/tools/r8/inspector/ByteValueInspector.java
index 718183e..045476d 100644
--- a/src/main/java/com/android/tools/r8/inspector/ByteValueInspector.java
+++ b/src/main/java/com/android/tools/r8/inspector/ByteValueInspector.java
@@ -3,6 +3,9 @@
 // BSD-style license that can be found in the LICENSE file.
 package com.android.tools.r8.inspector;
 
+import com.android.tools.r8.Keep;
+
+@Keep
 public interface ByteValueInspector extends ValueInspector {
   byte getByteValue();
 }
diff --git a/src/main/java/com/android/tools/r8/inspector/CharValueInspector.java b/src/main/java/com/android/tools/r8/inspector/CharValueInspector.java
index d79ee9c..05d5c2b 100644
--- a/src/main/java/com/android/tools/r8/inspector/CharValueInspector.java
+++ b/src/main/java/com/android/tools/r8/inspector/CharValueInspector.java
@@ -3,6 +3,9 @@
 // BSD-style license that can be found in the LICENSE file.
 package com.android.tools.r8.inspector;
 
+import com.android.tools.r8.Keep;
+
+@Keep
 public interface CharValueInspector extends ValueInspector {
   char getCharValue();
 }
diff --git a/src/main/java/com/android/tools/r8/inspector/DoubleValueInspector.java b/src/main/java/com/android/tools/r8/inspector/DoubleValueInspector.java
index 334f7ac..e9ac7e1 100644
--- a/src/main/java/com/android/tools/r8/inspector/DoubleValueInspector.java
+++ b/src/main/java/com/android/tools/r8/inspector/DoubleValueInspector.java
@@ -3,6 +3,9 @@
 // BSD-style license that can be found in the LICENSE file.
 package com.android.tools.r8.inspector;
 
+import com.android.tools.r8.Keep;
+
+@Keep
 public interface DoubleValueInspector extends ValueInspector {
   double getDoubleValue();
 }
diff --git a/src/main/java/com/android/tools/r8/inspector/FloatValueInspector.java b/src/main/java/com/android/tools/r8/inspector/FloatValueInspector.java
index cb4c12d..a7be463 100644
--- a/src/main/java/com/android/tools/r8/inspector/FloatValueInspector.java
+++ b/src/main/java/com/android/tools/r8/inspector/FloatValueInspector.java
@@ -3,6 +3,9 @@
 // BSD-style license that can be found in the LICENSE file.
 package com.android.tools.r8.inspector;
 
+import com.android.tools.r8.Keep;
+
+@Keep
 public interface FloatValueInspector extends ValueInspector {
   float getFloatValue();
 }
diff --git a/src/main/java/com/android/tools/r8/inspector/IntValueInspector.java b/src/main/java/com/android/tools/r8/inspector/IntValueInspector.java
index 38376c2..6b43ac8 100644
--- a/src/main/java/com/android/tools/r8/inspector/IntValueInspector.java
+++ b/src/main/java/com/android/tools/r8/inspector/IntValueInspector.java
@@ -3,6 +3,9 @@
 // BSD-style license that can be found in the LICENSE file.
 package com.android.tools.r8.inspector;
 
+import com.android.tools.r8.Keep;
+
+@Keep
 public interface IntValueInspector extends ValueInspector {
   int getIntValue();
 }
diff --git a/src/main/java/com/android/tools/r8/inspector/LongValueInspector.java b/src/main/java/com/android/tools/r8/inspector/LongValueInspector.java
index 177a885..987721e 100644
--- a/src/main/java/com/android/tools/r8/inspector/LongValueInspector.java
+++ b/src/main/java/com/android/tools/r8/inspector/LongValueInspector.java
@@ -3,6 +3,9 @@
 // BSD-style license that can be found in the LICENSE file.
 package com.android.tools.r8.inspector;
 
+import com.android.tools.r8.Keep;
+
+@Keep
 public interface LongValueInspector extends ValueInspector {
   long getLongValue();
 }
diff --git a/src/main/java/com/android/tools/r8/inspector/ShortValueInspector.java b/src/main/java/com/android/tools/r8/inspector/ShortValueInspector.java
index fb1d823..6b01df3 100644
--- a/src/main/java/com/android/tools/r8/inspector/ShortValueInspector.java
+++ b/src/main/java/com/android/tools/r8/inspector/ShortValueInspector.java
@@ -3,6 +3,9 @@
 // BSD-style license that can be found in the LICENSE file.
 package com.android.tools.r8.inspector;
 
+import com.android.tools.r8.Keep;
+
+@Keep
 public interface ShortValueInspector extends ValueInspector {
   short getShortValue();
 }
diff --git a/src/main/java/com/android/tools/r8/inspector/StringValueInspector.java b/src/main/java/com/android/tools/r8/inspector/StringValueInspector.java
index c0b78c4..5336948 100644
--- a/src/main/java/com/android/tools/r8/inspector/StringValueInspector.java
+++ b/src/main/java/com/android/tools/r8/inspector/StringValueInspector.java
@@ -3,6 +3,9 @@
 // BSD-style license that can be found in the LICENSE file.
 package com.android.tools.r8.inspector;
 
+import com.android.tools.r8.Keep;
+
+@Keep
 public interface StringValueInspector extends ValueInspector {
   String getStringValue();
 }
diff --git a/src/main/java/com/android/tools/r8/ir/analysis/ClassInitializationAnalysis.java b/src/main/java/com/android/tools/r8/ir/analysis/ClassInitializationAnalysis.java
index 7270d42..7167e72 100644
--- a/src/main/java/com/android/tools/r8/ir/analysis/ClassInitializationAnalysis.java
+++ b/src/main/java/com/android/tools/r8/ir/analysis/ClassInitializationAnalysis.java
@@ -14,7 +14,7 @@
 import com.android.tools.r8.graph.DexMethod;
 import com.android.tools.r8.graph.DexType;
 import com.android.tools.r8.graph.ResolutionResult;
-import com.android.tools.r8.ir.analysis.type.TypeLatticeElement;
+import com.android.tools.r8.ir.analysis.type.TypeElement;
 import com.android.tools.r8.ir.code.BasicBlock;
 import com.android.tools.r8.ir.code.CatchHandlers.CatchHandler;
 import com.android.tools.r8.ir.code.DominatorTree;
@@ -278,7 +278,7 @@
             instruction.isInstanceGet()
                 ? instruction.asInstanceGet().object()
                 : instruction.asInstancePut().object();
-        if (object.getTypeLattice().isNullable()) {
+        if (object.getType().isNullable()) {
           // If the receiver is null we cannot be sure that the holder has been initialized.
           return false;
         }
@@ -294,7 +294,7 @@
         Query mode,
         AnalysisAssumption assumption) {
       if (assumption == AnalysisAssumption.NONE) {
-        if (instruction.getReceiver().getTypeLattice().isNullable()) {
+        if (instruction.getReceiver().getType().isNullable()) {
           // If the receiver is null we cannot be sure that the holder has been initialized.
           return false;
         }
@@ -311,7 +311,7 @@
         Query mode,
         AnalysisAssumption assumption) {
       if (assumption == AnalysisAssumption.NONE) {
-        if (instruction.getReceiver().getTypeLattice().isNullable()) {
+        if (instruction.getReceiver().getType().isNullable()) {
           // If the receiver is null we cannot be sure that the holder has been initialized.
           return false;
         }
@@ -362,7 +362,7 @@
         Query mode,
         AnalysisAssumption assumption) {
       if (assumption == AnalysisAssumption.NONE) {
-        if (instruction.getReceiver().getTypeLattice().isNullable()) {
+        if (instruction.getReceiver().getType().isNullable()) {
           // If the receiver is null we cannot be sure that the holder has been initialized.
           return false;
         }
@@ -406,7 +406,7 @@
         Query mode,
         AnalysisAssumption assumption) {
       if (assumption == AnalysisAssumption.NONE) {
-        if (instruction.getReceiver().getTypeLattice().isNullable()) {
+        if (instruction.getReceiver().getType().isNullable()) {
           // If the receiver is null we cannot be sure that the holder has been initialized.
           return false;
         }
@@ -554,9 +554,9 @@
       // implies that the type of the receiver must be initialized.
       if (!method.isStatic()) {
         assert arguments.size() > 0;
-        TypeLatticeElement type = arguments.get(0).getTypeLattice();
+        TypeElement type = arguments.get(0).getType();
         if (type.isClassType()) {
-          enqueue(type.asClassTypeLatticeElement().getClassType(), visited, worklist);
+          enqueue(type.asClassType().getClassType(), visited, worklist);
         }
       }
       // If an invoke to a method succeeds, and the method would have thrown and exception if the
@@ -566,9 +566,9 @@
       if (nonNullParamOrThrowFacts != null) {
         for (int i = 0; i < arguments.size(); i++) {
           if (nonNullParamOrThrowFacts.get(i)) {
-            TypeLatticeElement type = arguments.get(i).getTypeLattice();
+            TypeElement type = arguments.get(i).getType();
             if (type.isClassType()) {
-              enqueue(type.asClassTypeLatticeElement().getClassType(), visited, worklist);
+              enqueue(type.asClassType().getClassType(), visited, worklist);
             }
           }
         }
diff --git a/src/main/java/com/android/tools/r8/ir/analysis/TypeChecker.java b/src/main/java/com/android/tools/r8/ir/analysis/TypeChecker.java
index 01a7e3d..f8a621d 100644
--- a/src/main/java/com/android/tools/r8/ir/analysis/TypeChecker.java
+++ b/src/main/java/com/android/tools/r8/ir/analysis/TypeChecker.java
@@ -9,7 +9,7 @@
 import com.android.tools.r8.graph.DexClass;
 import com.android.tools.r8.graph.DexEncodedMethod;
 import com.android.tools.r8.ir.analysis.type.Nullability;
-import com.android.tools.r8.ir.analysis.type.TypeLatticeElement;
+import com.android.tools.r8.ir.analysis.type.TypeElement;
 import com.android.tools.r8.ir.code.FieldInstruction;
 import com.android.tools.r8.ir.code.IRCode;
 import com.android.tools.r8.ir.code.InstancePut;
@@ -67,15 +67,14 @@
     if (instruction.isReturnVoid()) {
       return true;
     }
-    TypeLatticeElement valueType = instruction.returnValue().getTypeLattice();
-    TypeLatticeElement returnType =
-        TypeLatticeElement.fromDexType(
-            method.method.proto.returnType, Nullability.maybeNull(), appView);
+    TypeElement valueType = instruction.returnValue().getType();
+    TypeElement returnType =
+        TypeElement.fromDexType(method.method.proto.returnType, Nullability.maybeNull(), appView);
     if (isSubtypeOf(valueType, returnType)) {
       return true;
     }
 
-    if (returnType.isClassType() && valueType.isReference()) {
+    if (returnType.isClassType() && valueType.isReferenceType()) {
       // Interface types are treated like Object according to the JVM spec.
       // https://docs.oracle.com/javase/specs/jvms/se9/html/jvms-4.html#jvms-4.10.1.2-100
       DexClass clazz = appView.definitionFor(method.method.proto.returnType);
@@ -91,15 +90,14 @@
 
   public boolean checkFieldPut(FieldInstruction instruction) {
     assert instruction.isFieldPut();
-    TypeLatticeElement valueType = instruction.value().getTypeLattice();
-    TypeLatticeElement fieldType =
-        TypeLatticeElement.fromDexType(
-            instruction.getField().type, valueType.nullability(), appView);
+    TypeElement valueType = instruction.value().getType();
+    TypeElement fieldType =
+        TypeElement.fromDexType(instruction.getField().type, valueType.nullability(), appView);
     if (isSubtypeOf(valueType, fieldType)) {
       return true;
     }
 
-    if (fieldType.isClassType() && valueType.isReference()) {
+    if (fieldType.isClassType() && valueType.isReferenceType()) {
       // Interface types are treated like Object according to the JVM spec.
       // https://docs.oracle.com/javase/specs/jvms/se9/html/jvms-4.html#jvms-4.10.1.2-100
       DexClass clazz = appView.definitionFor(instruction.getField().type);
@@ -110,16 +108,15 @@
   }
 
   public boolean check(Throw instruction) {
-    TypeLatticeElement valueType = instruction.exception().getTypeLattice();
-    TypeLatticeElement throwableType =
-        TypeLatticeElement.fromDexType(
+    TypeElement valueType = instruction.exception().getType();
+    TypeElement throwableType =
+        TypeElement.fromDexType(
             appView.dexItemFactory().throwableType, valueType.nullability(), appView);
     return isSubtypeOf(valueType, throwableType);
   }
 
-  private boolean isSubtypeOf(
-      TypeLatticeElement expectedSubtype, TypeLatticeElement expectedSupertype) {
-    return (expectedSubtype.isNullType() && expectedSupertype.isReference())
+  private boolean isSubtypeOf(TypeElement expectedSubtype, TypeElement expectedSupertype) {
+    return (expectedSubtype.isNullType() && expectedSupertype.isReferenceType())
         || expectedSubtype.lessThanOrEqual(expectedSupertype, appView)
         || expectedSubtype.isBasedOnMissingClass(appView);
   }
diff --git a/src/main/java/com/android/tools/r8/ir/analysis/equivalence/BasicBlockBehavioralSubsumption.java b/src/main/java/com/android/tools/r8/ir/analysis/equivalence/BasicBlockBehavioralSubsumption.java
index 7422417..27c4e19 100644
--- a/src/main/java/com/android/tools/r8/ir/analysis/equivalence/BasicBlockBehavioralSubsumption.java
+++ b/src/main/java/com/android/tools/r8/ir/analysis/equivalence/BasicBlockBehavioralSubsumption.java
@@ -192,7 +192,7 @@
 
     Value outValue = instruction.outValue();
     Value otherOutValue = other.outValue();
-    if (!outValue.getTypeLattice().equals(otherOutValue.getTypeLattice())) {
+    if (!outValue.getType().equals(otherOutValue.getType())) {
       return false;
     }
 
diff --git a/src/main/java/com/android/tools/r8/ir/analysis/escape/EscapeAnalysis.java b/src/main/java/com/android/tools/r8/ir/analysis/escape/EscapeAnalysis.java
index 466edeb..43e484d 100644
--- a/src/main/java/com/android/tools/r8/ir/analysis/escape/EscapeAnalysis.java
+++ b/src/main/java/com/android/tools/r8/ir/analysis/escape/EscapeAnalysis.java
@@ -83,7 +83,7 @@
 
   // Returns the set of instructions where the value of interest can escape from the code.
   private void run(IRCode code, Value valueOfInterest, Predicate<Instruction> stoppingCriterion) {
-    assert valueOfInterest.getTypeLattice().isReference();
+    assert valueOfInterest.getType().isReferenceType();
     assert trackedValues.isEmpty();
     assert valuesToTrack.isEmpty();
 
@@ -153,13 +153,13 @@
       }
       if (couldIntroduceTrackedValueAlias) {
         Value outValue = user.outValue();
-        assert outValue != null && outValue.getTypeLattice().isReference();
+        assert outValue != null && outValue.getType().isReferenceType();
         addToWorklist(outValue);
       }
       // Track propagated values through which the value of interest can escape indirectly.
       Value propagatedValue = getPropagatedSubject(alias, user);
       if (propagatedValue != null && propagatedValue != alias) {
-        assert propagatedValue.getTypeLattice().isReference();
+        assert propagatedValue.getType().isReferenceType();
         addToWorklist(propagatedValue);
       }
     }
diff --git a/src/main/java/com/android/tools/r8/ir/analysis/fieldvalueanalysis/InstanceFieldValueAnalysis.java b/src/main/java/com/android/tools/r8/ir/analysis/fieldvalueanalysis/InstanceFieldValueAnalysis.java
index e540a06..3aec803 100644
--- a/src/main/java/com/android/tools/r8/ir/analysis/fieldvalueanalysis/InstanceFieldValueAnalysis.java
+++ b/src/main/java/com/android/tools/r8/ir/analysis/fieldvalueanalysis/InstanceFieldValueAnalysis.java
@@ -12,8 +12,8 @@
 import com.android.tools.r8.graph.DexEncodedMethod;
 import com.android.tools.r8.graph.DexProgramClass;
 import com.android.tools.r8.graph.DexType;
-import com.android.tools.r8.ir.analysis.type.ClassTypeLatticeElement;
-import com.android.tools.r8.ir.analysis.type.TypeLatticeElement;
+import com.android.tools.r8.ir.analysis.type.ClassTypeElement;
+import com.android.tools.r8.ir.analysis.type.TypeElement;
 import com.android.tools.r8.ir.analysis.value.AbstractValue;
 import com.android.tools.r8.ir.code.Argument;
 import com.android.tools.r8.ir.code.BasicBlock;
@@ -30,6 +30,7 @@
 import com.android.tools.r8.ir.optimize.info.field.InstanceFieldInitializationInfoCollection;
 import com.android.tools.r8.ir.optimize.info.field.InstanceFieldInitializationInfoFactory;
 import com.android.tools.r8.shaking.AppInfoWithLiveness;
+import com.android.tools.r8.utils.Timing;
 
 public class InstanceFieldValueAnalysis extends FieldValueAnalysis {
 
@@ -66,6 +67,20 @@
       IRCode code,
       ClassInitializerDefaultsResult classInitializerDefaultsResult,
       OptimizationFeedback feedback,
+      DexEncodedMethod method,
+      Timing timing) {
+    timing.begin("Analyze instance initializer");
+    InstanceFieldInitializationInfoCollection result =
+        run(appView, code, classInitializerDefaultsResult, feedback, method);
+    timing.end();
+    return result;
+  }
+
+  private static InstanceFieldInitializationInfoCollection run(
+      AppView<?> appView,
+      IRCode code,
+      ClassInitializerDefaultsResult classInitializerDefaultsResult,
+      OptimizationFeedback feedback,
       DexEncodedMethod method) {
     assert appView.appInfo().hasLiveness();
     assert appView.enableWholeProgramOptimizations();
@@ -153,10 +168,9 @@
 
     DexType fieldType = field.field.type;
     if (fieldType.isClassType()) {
-      ClassTypeLatticeElement dynamicLowerBoundType = value.getDynamicLowerBoundType(appView);
-      TypeLatticeElement dynamicUpperBoundType = value.getDynamicUpperBoundType(appView);
-      TypeLatticeElement staticFieldType =
-          TypeLatticeElement.fromDexType(fieldType, maybeNull(), appView);
+      ClassTypeElement dynamicLowerBoundType = value.getDynamicLowerBoundType(appView);
+      TypeElement dynamicUpperBoundType = value.getDynamicUpperBoundType(appView);
+      TypeElement staticFieldType = TypeElement.fromDexType(fieldType, maybeNull(), appView);
       if (dynamicLowerBoundType != null || !dynamicUpperBoundType.equals(staticFieldType)) {
         builder.recordInitializationInfo(
             field,
diff --git a/src/main/java/com/android/tools/r8/ir/analysis/fieldvalueanalysis/StaticFieldValueAnalysis.java b/src/main/java/com/android/tools/r8/ir/analysis/fieldvalueanalysis/StaticFieldValueAnalysis.java
index 10dd287..4d57a5e 100644
--- a/src/main/java/com/android/tools/r8/ir/analysis/fieldvalueanalysis/StaticFieldValueAnalysis.java
+++ b/src/main/java/com/android/tools/r8/ir/analysis/fieldvalueanalysis/StaticFieldValueAnalysis.java
@@ -12,9 +12,9 @@
 import com.android.tools.r8.graph.DexField;
 import com.android.tools.r8.graph.DexItemFactory;
 import com.android.tools.r8.graph.DexProgramClass;
-import com.android.tools.r8.ir.analysis.type.ClassTypeLatticeElement;
+import com.android.tools.r8.ir.analysis.type.ClassTypeElement;
 import com.android.tools.r8.ir.analysis.type.Nullability;
-import com.android.tools.r8.ir.analysis.type.TypeLatticeElement;
+import com.android.tools.r8.ir.analysis.type.TypeElement;
 import com.android.tools.r8.ir.analysis.value.AbstractValue;
 import com.android.tools.r8.ir.analysis.value.AbstractValueFactory;
 import com.android.tools.r8.ir.analysis.value.SingleEnumValue;
@@ -29,6 +29,7 @@
 import com.android.tools.r8.ir.optimize.ClassInitializerDefaultsOptimization.ClassInitializerDefaultsResult;
 import com.android.tools.r8.ir.optimize.info.OptimizationFeedback;
 import com.android.tools.r8.shaking.AppInfoWithLiveness;
+import com.android.tools.r8.utils.Timing;
 
 public class StaticFieldValueAnalysis extends FieldValueAnalysis {
 
@@ -46,13 +47,16 @@
       IRCode code,
       ClassInitializerDefaultsResult classInitializerDefaultsResult,
       OptimizationFeedback feedback,
-      DexEncodedMethod method) {
+      DexEncodedMethod method,
+      Timing timing) {
     assert appView.appInfo().hasLiveness();
     assert appView.enableWholeProgramOptimizations();
     assert method.isClassInitializer();
+    timing.begin("Analyze class initializer");
     DexProgramClass clazz = appView.definitionFor(method.method.holder).asProgramClass();
     new StaticFieldValueAnalysis(appView.withLiveness(), code, feedback, clazz, method)
         .computeFieldOptimizationInfo(classInitializerDefaultsResult);
+    timing.end();
   }
 
   @Override
@@ -105,15 +109,15 @@
     }
 
     // Dynamic upper bound type.
-    TypeLatticeElement fieldType =
-        TypeLatticeElement.fromDexType(field.field.type, Nullability.maybeNull(), appView);
-    TypeLatticeElement dynamicUpperBoundType = value.getDynamicUpperBoundType(appView);
+    TypeElement fieldType =
+        TypeElement.fromDexType(field.field.type, Nullability.maybeNull(), appView);
+    TypeElement dynamicUpperBoundType = value.getDynamicUpperBoundType(appView);
     if (dynamicUpperBoundType.strictlyLessThan(fieldType, appView)) {
       feedback.markFieldHasDynamicUpperBoundType(field, dynamicUpperBoundType);
     }
 
     // Dynamic lower bound type.
-    ClassTypeLatticeElement dynamicLowerBoundType = value.getDynamicLowerBoundType(appView);
+    ClassTypeElement dynamicLowerBoundType = value.getDynamicLowerBoundType(appView);
     if (dynamicLowerBoundType != null) {
       assert dynamicLowerBoundType.lessThanOrEqual(dynamicUpperBoundType, appView);
       feedback.markFieldHasDynamicLowerBoundType(field, dynamicLowerBoundType);
diff --git a/src/main/java/com/android/tools/r8/ir/analysis/proto/GeneratedExtensionRegistryShrinker.java b/src/main/java/com/android/tools/r8/ir/analysis/proto/GeneratedExtensionRegistryShrinker.java
index 4de5c39..2a70440 100644
--- a/src/main/java/com/android/tools/r8/ir/analysis/proto/GeneratedExtensionRegistryShrinker.java
+++ b/src/main/java/com/android/tools/r8/ir/analysis/proto/GeneratedExtensionRegistryShrinker.java
@@ -32,6 +32,7 @@
 import com.android.tools.r8.shaking.TreePrunerConfiguration;
 import com.android.tools.r8.utils.DescriptorUtils;
 import com.android.tools.r8.utils.FileUtils;
+import com.android.tools.r8.utils.Timing;
 import com.google.common.base.Predicates;
 import com.google.common.collect.Sets;
 import java.io.IOException;
@@ -155,13 +156,15 @@
     return removedExtensionFields.contains(field);
   }
 
-  public void postOptimizeGeneratedExtensionRegistry(IRConverter converter) {
+  public void postOptimizeGeneratedExtensionRegistry(IRConverter converter, Timing timing) {
+    timing.begin("[Proto] Post optimize generated extension registry");
     forEachFindLiteExtensionByNumberMethod(
         method ->
             converter.processMethod(
                 method,
                 OptimizationFeedbackIgnore.getInstance(),
                 OneTimeMethodProcessor.getInstance()));
+    timing.end(); // [Proto] Post optimize generated extension registry
   }
 
   private void forEachFindLiteExtensionByNumberMethod(Consumer<DexEncodedMethod> consumer) {
diff --git a/src/main/java/com/android/tools/r8/ir/analysis/proto/GeneratedMessageLiteBuilderShrinker.java b/src/main/java/com/android/tools/r8/ir/analysis/proto/GeneratedMessageLiteBuilderShrinker.java
index 8da13ed..feca06c 100644
--- a/src/main/java/com/android/tools/r8/ir/analysis/proto/GeneratedMessageLiteBuilderShrinker.java
+++ b/src/main/java/com/android/tools/r8/ir/analysis/proto/GeneratedMessageLiteBuilderShrinker.java
@@ -13,9 +13,9 @@
 import com.android.tools.r8.graph.DexType;
 import com.android.tools.r8.graph.EnumValueInfoMapCollection.EnumValueInfo;
 import com.android.tools.r8.graph.EnumValueInfoMapCollection.EnumValueInfoMap;
-import com.android.tools.r8.ir.analysis.type.ClassTypeLatticeElement;
+import com.android.tools.r8.ir.analysis.type.ClassTypeElement;
 import com.android.tools.r8.ir.analysis.type.TypeAnalysis;
-import com.android.tools.r8.ir.analysis.type.TypeLatticeElement;
+import com.android.tools.r8.ir.analysis.type.TypeElement;
 import com.android.tools.r8.ir.code.CheckCast;
 import com.android.tools.r8.ir.code.IRCode;
 import com.android.tools.r8.ir.code.Instruction;
@@ -217,8 +217,8 @@
       if (!references.methodToInvokeMembers.isNewMutableInstanceEnum(methodToInvokeValue)) {
         continue;
       }
-      ClassTypeLatticeElement receiverType =
-          invoke.getReceiver().getDynamicUpperBoundType(appView).asClassTypeLatticeElement();
+      ClassTypeElement receiverType =
+          invoke.getReceiver().getDynamicUpperBoundType(appView).asClassType();
       if (receiverType != null) {
         AppInfoWithClassHierarchy appInfo = appView.appInfo();
         DexType rawReceiverType = receiverType.getClassType();
@@ -269,9 +269,9 @@
       if (definition.getInvokedMethod() != appView.dexItemFactory().enumMethods.ordinal) {
         return false;
       }
-      TypeLatticeElement enumType = definition.getReceiver().getTypeLattice();
+      TypeElement enumType = definition.getReceiver().getType();
       return enumType.isClassType()
-          && enumType.asClassTypeLatticeElement().getClassType() == references.methodToInvokeType;
+          && enumType.asClassType().getClassType() == references.methodToInvokeType;
     }
   }
 
diff --git a/src/main/java/com/android/tools/r8/ir/analysis/proto/GeneratedMessageLiteShrinker.java b/src/main/java/com/android/tools/r8/ir/analysis/proto/GeneratedMessageLiteShrinker.java
index cc7307f..390f2b2 100644
--- a/src/main/java/com/android/tools/r8/ir/analysis/proto/GeneratedMessageLiteShrinker.java
+++ b/src/main/java/com/android/tools/r8/ir/analysis/proto/GeneratedMessageLiteShrinker.java
@@ -15,7 +15,7 @@
 import com.android.tools.r8.ir.analysis.proto.schema.ProtoMessageInfo;
 import com.android.tools.r8.ir.analysis.proto.schema.ProtoObject;
 import com.android.tools.r8.ir.analysis.type.Nullability;
-import com.android.tools.r8.ir.analysis.type.TypeLatticeElement;
+import com.android.tools.r8.ir.analysis.type.TypeElement;
 import com.android.tools.r8.ir.code.ArrayPut;
 import com.android.tools.r8.ir.code.BasicBlock;
 import com.android.tools.r8.ir.code.BasicBlock.ThrowingInfo;
@@ -32,6 +32,7 @@
 import com.android.tools.r8.ir.conversion.OneTimeMethodProcessor;
 import com.android.tools.r8.ir.optimize.info.OptimizationFeedbackIgnore;
 import com.android.tools.r8.shaking.AppInfoWithLiveness;
+import com.android.tools.r8.utils.Timing;
 import java.util.List;
 import java.util.function.Consumer;
 
@@ -43,8 +44,8 @@
   private final ProtoReferences references;
   private final ThrowingInfo throwingInfo;
 
-  private final TypeLatticeElement objectArrayType;
-  private final TypeLatticeElement stringType;
+  private final TypeElement objectArrayType;
+  private final TypeElement stringType;
 
   public GeneratedMessageLiteShrinker(
       AppView<AppInfoWithLiveness> appView,
@@ -58,9 +59,9 @@
 
     // Types.
     this.objectArrayType =
-        TypeLatticeElement.fromDexType(
+        TypeElement.fromDexType(
             appView.dexItemFactory().objectArrayType, Nullability.definitelyNotNull(), appView);
-    this.stringType = TypeLatticeElement.stringClassType(appView, Nullability.definitelyNotNull());
+    this.stringType = TypeElement.stringClassType(appView, Nullability.definitelyNotNull());
   }
 
   public void run(DexEncodedMethod method, IRCode code) {
@@ -69,13 +70,15 @@
     }
   }
 
-  public void postOptimizeDynamicMethods(IRConverter converter) {
+  public void postOptimizeDynamicMethods(IRConverter converter, Timing timing) {
+    timing.begin("[Proto] Post optimize dynamic methods");
     forEachDynamicMethod(
         method ->
             converter.processMethod(
                 method,
                 OptimizationFeedbackIgnore.getInstance(),
                 OneTimeMethodProcessor.getInstance()));
+    timing.end();
   }
 
   private void forEachDynamicMethod(Consumer<DexEncodedMethod> consumer) {
diff --git a/src/main/java/com/android/tools/r8/ir/analysis/proto/schema/LiveProtoFieldObject.java b/src/main/java/com/android/tools/r8/ir/analysis/proto/schema/LiveProtoFieldObject.java
index 568b822..308ea07 100644
--- a/src/main/java/com/android/tools/r8/ir/analysis/proto/schema/LiveProtoFieldObject.java
+++ b/src/main/java/com/android/tools/r8/ir/analysis/proto/schema/LiveProtoFieldObject.java
@@ -7,7 +7,7 @@
 import com.android.tools.r8.graph.AppView;
 import com.android.tools.r8.graph.DexField;
 import com.android.tools.r8.ir.analysis.type.Nullability;
-import com.android.tools.r8.ir.analysis.type.TypeLatticeElement;
+import com.android.tools.r8.ir.analysis.type.TypeElement;
 import com.android.tools.r8.ir.code.BasicBlock.ThrowingInfo;
 import com.android.tools.r8.ir.code.ConstString;
 import com.android.tools.r8.ir.code.DexItemBasedConstString;
@@ -31,8 +31,7 @@
   @Override
   public Instruction buildIR(AppView<?> appView, IRCode code) {
     Value value =
-        code.createValue(
-            TypeLatticeElement.stringClassType(appView, Nullability.definitelyNotNull()));
+        code.createValue(TypeElement.stringClassType(appView, Nullability.definitelyNotNull()));
     ThrowingInfo throwingInfo = ThrowingInfo.defaultForConstString(appView.options());
     if (appView.options().isMinifying()) {
       return new DexItemBasedConstString(
diff --git a/src/main/java/com/android/tools/r8/ir/analysis/proto/schema/ProtoEnqueuerExtension.java b/src/main/java/com/android/tools/r8/ir/analysis/proto/schema/ProtoEnqueuerExtension.java
index bf6c8b1..e414f23 100644
--- a/src/main/java/com/android/tools/r8/ir/analysis/proto/schema/ProtoEnqueuerExtension.java
+++ b/src/main/java/com/android/tools/r8/ir/analysis/proto/schema/ProtoEnqueuerExtension.java
@@ -21,7 +21,7 @@
 import com.android.tools.r8.ir.analysis.proto.ProtoReferences;
 import com.android.tools.r8.ir.analysis.proto.ProtoShrinker;
 import com.android.tools.r8.ir.analysis.proto.RawMessageInfoDecoder;
-import com.android.tools.r8.ir.analysis.type.TypeLatticeElement;
+import com.android.tools.r8.ir.analysis.type.TypeElement;
 import com.android.tools.r8.ir.code.BasicBlock;
 import com.android.tools.r8.ir.code.IRCode;
 import com.android.tools.r8.ir.code.IRCodeUtils;
@@ -37,6 +37,7 @@
 import com.android.tools.r8.shaking.KeepReason;
 import com.android.tools.r8.utils.BitUtils;
 import com.android.tools.r8.utils.OptionalBool;
+import com.android.tools.r8.utils.Timing;
 import com.google.common.collect.ImmutableSet;
 import com.google.common.collect.Sets;
 import java.util.IdentityHashMap;
@@ -171,7 +172,8 @@
   }
 
   @Override
-  public void notifyFixpoint(Enqueuer enqueuer, EnqueuerWorklist worklist) {
+  public void notifyFixpoint(Enqueuer enqueuer, EnqueuerWorklist worklist, Timing timing) {
+    timing.begin("[Proto] Extend fixpoint");
     populateExtensionGraph(enqueuer);
 
     markMapOrRequiredFieldsAsReachable(enqueuer, worklist);
@@ -187,6 +189,7 @@
         tracePendingInstructionsInDynamicMethods(enqueuer, worklist);
       }
     }
+    timing.end();
   }
 
   /**
@@ -317,17 +320,17 @@
       InvokeMethod invoke = extensionFactory.asInvokeMethod();
       DexMethod invokedMethod = invoke.getInvokedMethod();
 
-      TypeLatticeElement containerType, extensionType;
+      TypeElement containerType, extensionType;
       if (invokedMethod == references.generatedMessageLiteMethods.newRepeatedGeneratedExtension) {
-        containerType = invoke.arguments().get(0).getTypeLattice();
-        extensionType = invoke.arguments().get(1).getTypeLattice();
+        containerType = invoke.arguments().get(0).getType();
+        extensionType = invoke.arguments().get(1).getType();
       } else if (invokedMethod
           == references.generatedMessageLiteMethods.newSingularGeneratedExtension) {
-        containerType = invoke.arguments().get(0).getTypeLattice();
-        extensionType = invoke.arguments().get(2).getTypeLattice();
+        containerType = invoke.arguments().get(0).getType();
+        extensionType = invoke.arguments().get(2).getType();
       } else if (references.generatedExtensionMethods.isConstructor(invokedMethod)) {
-        containerType = invoke.arguments().get(1).getTypeLattice();
-        extensionType = invoke.arguments().get(3).getTypeLattice();
+        containerType = invoke.arguments().get(1).getType();
+        extensionType = invoke.arguments().get(3).getType();
       } else {
         return;
       }
@@ -343,9 +346,8 @@
 
       extensionGraph
           .computeIfAbsent(
-              containerType.asClassTypeLatticeElement().getClassType(),
-              ignore -> Sets.newIdentityHashSet())
-          .add(extensionType.asClassTypeLatticeElement().getClassType());
+              containerType.asClassType().getClassType(), ignore -> Sets.newIdentityHashSet())
+          .add(extensionType.asClassType().getClassType());
     }
   }
 
diff --git a/src/main/java/com/android/tools/r8/ir/analysis/proto/schema/ProtoObjectFromInvokeStatic.java b/src/main/java/com/android/tools/r8/ir/analysis/proto/schema/ProtoObjectFromInvokeStatic.java
index 0812098..8c9bc00 100644
--- a/src/main/java/com/android/tools/r8/ir/analysis/proto/schema/ProtoObjectFromInvokeStatic.java
+++ b/src/main/java/com/android/tools/r8/ir/analysis/proto/schema/ProtoObjectFromInvokeStatic.java
@@ -7,7 +7,7 @@
 import com.android.tools.r8.graph.AppView;
 import com.android.tools.r8.graph.DexMethod;
 import com.android.tools.r8.ir.analysis.type.Nullability;
-import com.android.tools.r8.ir.analysis.type.TypeLatticeElement;
+import com.android.tools.r8.ir.analysis.type.TypeElement;
 import com.android.tools.r8.ir.code.IRCode;
 import com.android.tools.r8.ir.code.Instruction;
 import com.android.tools.r8.ir.code.InvokeStatic;
@@ -26,8 +26,7 @@
   public Instruction buildIR(AppView<?> appView, IRCode code) {
     Value value =
         code.createValue(
-            TypeLatticeElement.fromDexType(
-                method.proto.returnType, Nullability.maybeNull(), appView));
+            TypeElement.fromDexType(method.proto.returnType, Nullability.maybeNull(), appView));
     return new InvokeStatic(method, value, ImmutableList.of());
   }
 }
diff --git a/src/main/java/com/android/tools/r8/ir/analysis/proto/schema/ProtoObjectFromStaticGet.java b/src/main/java/com/android/tools/r8/ir/analysis/proto/schema/ProtoObjectFromStaticGet.java
index 551466f..874ab9d 100644
--- a/src/main/java/com/android/tools/r8/ir/analysis/proto/schema/ProtoObjectFromStaticGet.java
+++ b/src/main/java/com/android/tools/r8/ir/analysis/proto/schema/ProtoObjectFromStaticGet.java
@@ -7,7 +7,7 @@
 import com.android.tools.r8.graph.AppView;
 import com.android.tools.r8.graph.DexField;
 import com.android.tools.r8.ir.analysis.type.Nullability;
-import com.android.tools.r8.ir.analysis.type.TypeLatticeElement;
+import com.android.tools.r8.ir.analysis.type.TypeElement;
 import com.android.tools.r8.ir.code.IRCode;
 import com.android.tools.r8.ir.code.Instruction;
 import com.android.tools.r8.ir.code.StaticGet;
@@ -28,8 +28,7 @@
   @Override
   public Instruction buildIR(AppView<?> appView, IRCode code) {
     Value value =
-        code.createValue(
-            TypeLatticeElement.fromDexType(field.type, Nullability.maybeNull(), appView));
+        code.createValue(TypeElement.fromDexType(field.type, Nullability.maybeNull(), appView));
     return new StaticGet(value, field);
   }
 
diff --git a/src/main/java/com/android/tools/r8/ir/analysis/type/ArrayTypeElement.java b/src/main/java/com/android/tools/r8/ir/analysis/type/ArrayTypeElement.java
new file mode 100644
index 0000000..4af45c6
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/ir/analysis/type/ArrayTypeElement.java
@@ -0,0 +1,184 @@
+// Copyright (c) 2017, 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.Nullability.maybeNull;
+
+import com.android.tools.r8.graph.AppInfoWithSubtyping;
+import com.android.tools.r8.graph.AppView;
+import com.android.tools.r8.graph.DexItemFactory;
+import com.android.tools.r8.graph.DexType;
+import java.util.Objects;
+import java.util.function.Function;
+
+public class ArrayTypeElement extends ReferenceTypeElement {
+
+  private final TypeElement memberTypeLattice;
+
+  // On-demand link between other nullability-variants.
+  private final NullabilityVariants<ArrayTypeElement> variants;
+
+  public static ArrayTypeElement create(TypeElement memberTypeLattice, Nullability nullability) {
+    return NullabilityVariants.create(
+        nullability, (variants) -> new ArrayTypeElement(memberTypeLattice, nullability, variants));
+  }
+
+  private ArrayTypeElement(
+      TypeElement memberTypeLattice,
+      Nullability nullability,
+      NullabilityVariants<ArrayTypeElement> variants) {
+    super(nullability);
+    assert memberTypeLattice.isPrimitiveType() || memberTypeLattice.nullability().isMaybeNull();
+    this.memberTypeLattice = memberTypeLattice;
+    this.variants = variants;
+  }
+
+  public DexType toDexType(DexItemFactory factory) {
+    TypeElement baseTypeLattice = getBaseType();
+    DexType baseType;
+    if (baseTypeLattice.isPrimitiveType()) {
+      baseType = baseTypeLattice.asPrimitiveType().toDexType(factory);
+    } else {
+      assert baseTypeLattice.isClassType();
+      baseType = baseTypeLattice.asClassType().getClassType();
+    }
+    return factory.createArrayType(getNesting(), baseType);
+  }
+
+  public int getNesting() {
+    int nesting = 1;
+    TypeElement member = getMemberType();
+    while (member.isArrayType()) {
+      ++nesting;
+      member = member.asArrayType().getMemberType();
+    }
+    return nesting;
+  }
+
+  public TypeElement getMemberType() {
+    return memberTypeLattice;
+  }
+
+  public TypeElement getMemberTypeAsValueType() {
+    return memberTypeLattice.isFineGrainedType() ? getInt() : memberTypeLattice;
+  }
+
+  public TypeElement getBaseType() {
+    TypeElement base = getMemberType();
+    while (base.isArrayType()) {
+      base = base.asArrayType().getMemberType();
+    }
+    return base;
+  }
+
+  private ArrayTypeElement createVariant(
+      Nullability nullability, NullabilityVariants<ArrayTypeElement> variants) {
+    assert this.nullability != nullability;
+    return new ArrayTypeElement(memberTypeLattice, nullability, variants);
+  }
+
+  @Override
+  public ReferenceTypeElement getOrCreateVariant(Nullability nullability) {
+    ArrayTypeElement variant = variants.get(nullability);
+    if (variant != null) {
+      return variant;
+    }
+    return variants.getOrCreateElement(nullability, this::createVariant);
+  }
+
+  @Override
+  public boolean isBasedOnMissingClass(AppView<? extends AppInfoWithSubtyping> appView) {
+    return memberTypeLattice.isBasedOnMissingClass(appView);
+  }
+
+  @Override
+  public boolean isArrayType() {
+    return true;
+  }
+
+  @Override
+  public ArrayTypeElement asArrayType() {
+    return this;
+  }
+
+  @Override
+  public String toString() {
+    return nullability.toString() + " (" + memberTypeLattice.toString() + "[])";
+  }
+
+  @Override
+  public boolean equals(Object o) {
+    if (this == o) {
+      return true;
+    }
+    if (!(o instanceof ArrayTypeElement)) {
+      return false;
+    }
+    ArrayTypeElement other = (ArrayTypeElement) o;
+    if (nullability() != other.nullability()) {
+      return false;
+    }
+    return memberTypeLattice.equals(other.memberTypeLattice);
+  }
+
+  @Override
+  public int hashCode() {
+    return Objects.hash(nullability, memberTypeLattice);
+  }
+
+  @Override
+  public ArrayTypeElement fixupClassTypeReferences(
+      Function<DexType, DexType> mapping, AppView<? extends AppInfoWithSubtyping> appView) {
+    if (memberTypeLattice.isReferenceType()) {
+      TypeElement substitutedMemberType =
+          memberTypeLattice.fixupClassTypeReferences(mapping, appView);
+      if (substitutedMemberType != memberTypeLattice) {
+        return ArrayTypeElement.create(substitutedMemberType, nullability);
+      }
+    }
+    return this;
+  }
+
+  ReferenceTypeElement join(ArrayTypeElement other, AppView<?> appView) {
+    Nullability nullability = nullability().join(other.nullability());
+    ReferenceTypeElement join =
+        joinMember(this.memberTypeLattice, other.memberTypeLattice, appView, nullability);
+    if (join == null) {
+      // Check if other has the right nullability before creating it.
+      if (other.nullability == nullability) {
+        return other;
+      } else {
+        return getOrCreateVariant(nullability);
+      }
+    } else {
+      assert join.nullability == nullability;
+      return join;
+    }
+  }
+
+  private static ReferenceTypeElement joinMember(
+      TypeElement aMember, TypeElement bMember, AppView<?> appView, Nullability nullability) {
+    if (aMember.equals(bMember)) {
+      // Return null indicating the join is the same as the member to avoid object allocation.
+      return null;
+    }
+    if (aMember.isArrayType() && bMember.isArrayType()) {
+      TypeElement join =
+          joinMember(
+              aMember.asArrayType().memberTypeLattice,
+              bMember.asArrayType().memberTypeLattice,
+              appView,
+              maybeNull());
+      return join == null ? null : ArrayTypeElement.create(join, nullability);
+    }
+    if (aMember.isClassType() && bMember.isClassType()) {
+      ReferenceTypeElement join = aMember.asClassType().join(bMember.asClassType(), appView);
+      return ArrayTypeElement.create(join, nullability);
+    }
+    if (aMember.isPrimitiveType() || bMember.isPrimitiveType()) {
+      return objectClassType(appView, nullability);
+    }
+    return objectArrayType(appView, nullability);
+  }
+}
diff --git a/src/main/java/com/android/tools/r8/ir/analysis/type/ArrayTypeLatticeElement.java b/src/main/java/com/android/tools/r8/ir/analysis/type/ArrayTypeLatticeElement.java
deleted file mode 100644
index 39ebff8..0000000
--- a/src/main/java/com/android/tools/r8/ir/analysis/type/ArrayTypeLatticeElement.java
+++ /dev/null
@@ -1,190 +0,0 @@
-// Copyright (c) 2017, 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.Nullability.maybeNull;
-
-import com.android.tools.r8.graph.AppInfoWithSubtyping;
-import com.android.tools.r8.graph.AppView;
-import com.android.tools.r8.graph.DexItemFactory;
-import com.android.tools.r8.graph.DexType;
-import java.util.Objects;
-import java.util.function.Function;
-
-public class ArrayTypeLatticeElement extends ReferenceTypeLatticeElement {
-
-  private final TypeLatticeElement memberTypeLattice;
-
-  // On-demand link between other nullability-variants.
-  private final NullabilityVariants<ArrayTypeLatticeElement> variants;
-
-  public static ArrayTypeLatticeElement create(
-      TypeLatticeElement memberTypeLattice, Nullability nullability) {
-    return NullabilityVariants.create(
-        nullability,
-        (variants) -> new ArrayTypeLatticeElement(memberTypeLattice, nullability, variants));
-  }
-
-  private ArrayTypeLatticeElement(
-      TypeLatticeElement memberTypeLattice,
-      Nullability nullability,
-      NullabilityVariants<ArrayTypeLatticeElement> variants) {
-    super(nullability);
-    assert memberTypeLattice.isPrimitive() || memberTypeLattice.nullability().isMaybeNull();
-    this.memberTypeLattice = memberTypeLattice;
-    this.variants = variants;
-  }
-
-  public DexType getArrayType(DexItemFactory factory) {
-    TypeLatticeElement baseTypeLattice = getArrayBaseTypeLattice();
-    DexType baseType;
-    if (baseTypeLattice.isPrimitive()) {
-      baseType = baseTypeLattice.asPrimitiveTypeLatticeElement().toDexType(factory);
-    } else {
-      assert baseTypeLattice.isClassType();
-      baseType = baseTypeLattice.asClassTypeLatticeElement().getClassType();
-    }
-    return factory.createArrayType(getNesting(), baseType);
-  }
-
-  int getNesting() {
-    int nesting = 1;
-    TypeLatticeElement member = getArrayMemberTypeAsMemberType();
-    while (member.isArrayType()) {
-      ++nesting;
-      member = member.asArrayTypeLatticeElement().getArrayMemberTypeAsMemberType();
-    }
-    return nesting;
-  }
-
-  public TypeLatticeElement getArrayMemberTypeAsMemberType() {
-    return memberTypeLattice;
-  }
-
-  public TypeLatticeElement getArrayMemberTypeAsValueType() {
-    return memberTypeLattice.isFineGrainedType() ? getInt() : memberTypeLattice;
-  }
-
-  public TypeLatticeElement getArrayBaseTypeLattice() {
-    TypeLatticeElement base = getArrayMemberTypeAsMemberType();
-    while (base.isArrayType()) {
-      base = base.asArrayTypeLatticeElement().getArrayMemberTypeAsMemberType();
-    }
-    return base;
-  }
-
-  private ArrayTypeLatticeElement createVariant(
-      Nullability nullability, NullabilityVariants<ArrayTypeLatticeElement> variants) {
-    assert this.nullability != nullability;
-    return new ArrayTypeLatticeElement(memberTypeLattice, nullability, variants);
-  }
-
-  @Override
-  public ReferenceTypeLatticeElement getOrCreateVariant(Nullability nullability) {
-    ArrayTypeLatticeElement variant = variants.get(nullability);
-    if (variant != null) {
-      return variant;
-    }
-    return variants.getOrCreateElement(nullability, this::createVariant);
-  }
-
-  @Override
-  public boolean isBasedOnMissingClass(AppView<? extends AppInfoWithSubtyping> appView) {
-    return memberTypeLattice.isBasedOnMissingClass(appView);
-  }
-
-  @Override
-  public boolean isArrayType() {
-    return true;
-  }
-
-  @Override
-  public ArrayTypeLatticeElement asArrayTypeLatticeElement() {
-    return this;
-  }
-
-  @Override
-  public String toString() {
-    return nullability.toString() + " (" + memberTypeLattice.toString() + "[])";
-  }
-
-  @Override
-  public boolean equals(Object o) {
-    if (this == o) {
-      return true;
-    }
-    if (!(o instanceof ArrayTypeLatticeElement)) {
-      return false;
-    }
-    ArrayTypeLatticeElement other = (ArrayTypeLatticeElement) o;
-    if (nullability() != other.nullability()) {
-      return false;
-    }
-    return memberTypeLattice.equals(other.memberTypeLattice);
-  }
-
-  @Override
-  public int hashCode() {
-    return Objects.hash(nullability, memberTypeLattice);
-  }
-
-  @Override
-  public ArrayTypeLatticeElement fixupClassTypeReferences(
-      Function<DexType, DexType> mapping, AppView<? extends AppInfoWithSubtyping> appView) {
-    if (memberTypeLattice.isReference()) {
-      TypeLatticeElement substitutedMemberType =
-          memberTypeLattice.fixupClassTypeReferences(mapping, appView);
-      if (substitutedMemberType != memberTypeLattice) {
-        return ArrayTypeLatticeElement.create(substitutedMemberType, nullability);
-      }
-    }
-    return this;
-  }
-
-  ReferenceTypeLatticeElement join(ArrayTypeLatticeElement other, AppView<?> appView) {
-    Nullability nullability = nullability().join(other.nullability());
-    ReferenceTypeLatticeElement join =
-        joinMember(this.memberTypeLattice, other.memberTypeLattice, appView, nullability);
-    if (join == null) {
-      // Check if other has the right nullability before creating it.
-      if (other.nullability == nullability) {
-        return other;
-      } else {
-        return getOrCreateVariant(nullability);
-      }
-    } else {
-      assert join.nullability == nullability;
-      return join;
-    }
-  }
-
-  private static ReferenceTypeLatticeElement joinMember(
-      TypeLatticeElement aMember,
-      TypeLatticeElement bMember,
-      AppView<?> appView,
-      Nullability nullability) {
-    if (aMember.equals(bMember)) {
-      // Return null indicating the join is the same as the member to avoid object allocation.
-      return null;
-    }
-    if (aMember.isArrayType() && bMember.isArrayType()) {
-      TypeLatticeElement join =
-          joinMember(
-              aMember.asArrayTypeLatticeElement().memberTypeLattice,
-              bMember.asArrayTypeLatticeElement().memberTypeLattice,
-              appView,
-              maybeNull());
-      return join == null ? null : ArrayTypeLatticeElement.create(join, nullability);
-    }
-    if (aMember.isClassType() && bMember.isClassType()) {
-      ReferenceTypeLatticeElement join =
-          aMember.asClassTypeLatticeElement().join(bMember.asClassTypeLatticeElement(), appView);
-      return ArrayTypeLatticeElement.create(join, nullability);
-    }
-    if (aMember.isPrimitive() || bMember.isPrimitive()) {
-      return objectClassType(appView, nullability);
-    }
-    return objectArrayType(appView, nullability);
-  }
-}
diff --git a/src/main/java/com/android/tools/r8/ir/analysis/type/BooleanTypeLatticeElement.java b/src/main/java/com/android/tools/r8/ir/analysis/type/BooleanTypeElement.java
similarity index 71%
rename from src/main/java/com/android/tools/r8/ir/analysis/type/BooleanTypeLatticeElement.java
rename to src/main/java/com/android/tools/r8/ir/analysis/type/BooleanTypeElement.java
index 07e3503..eea19ef 100644
--- a/src/main/java/com/android/tools/r8/ir/analysis/type/BooleanTypeLatticeElement.java
+++ b/src/main/java/com/android/tools/r8/ir/analysis/type/BooleanTypeElement.java
@@ -3,11 +3,11 @@
 // BSD-style license that can be found in the LICENSE file.
 package com.android.tools.r8.ir.analysis.type;
 
-public class BooleanTypeLatticeElement extends SinglePrimitiveTypeLatticeElement {
+public class BooleanTypeElement extends SinglePrimitiveTypeElement {
 
-  private static final BooleanTypeLatticeElement INSTANCE = new BooleanTypeLatticeElement();
+  private static final BooleanTypeElement INSTANCE = new BooleanTypeElement();
 
-  static BooleanTypeLatticeElement getInstance() {
+  static BooleanTypeElement getInstance() {
     return INSTANCE;
   }
 
diff --git a/src/main/java/com/android/tools/r8/ir/analysis/type/BottomTypeLatticeElement.java b/src/main/java/com/android/tools/r8/ir/analysis/type/BottomTypeElement.java
similarity index 76%
rename from src/main/java/com/android/tools/r8/ir/analysis/type/BottomTypeLatticeElement.java
rename to src/main/java/com/android/tools/r8/ir/analysis/type/BottomTypeElement.java
index 1583714..5ded712 100644
--- a/src/main/java/com/android/tools/r8/ir/analysis/type/BottomTypeLatticeElement.java
+++ b/src/main/java/com/android/tools/r8/ir/analysis/type/BottomTypeElement.java
@@ -3,15 +3,15 @@
 // BSD-style license that can be found in the LICENSE file.
 package com.android.tools.r8.ir.analysis.type;
 
-public class BottomTypeLatticeElement extends TypeLatticeElement {
-  private static final BottomTypeLatticeElement INSTANCE = new BottomTypeLatticeElement();
+public class BottomTypeElement extends TypeElement {
+  private static final BottomTypeElement INSTANCE = new BottomTypeElement();
 
   @Override
   public Nullability nullability() {
     return Nullability.bottom();
   }
 
-  static BottomTypeLatticeElement getInstance() {
+  static BottomTypeElement getInstance() {
     return INSTANCE;
   }
 
diff --git a/src/main/java/com/android/tools/r8/ir/analysis/type/ByteTypeLatticeElement.java b/src/main/java/com/android/tools/r8/ir/analysis/type/ByteTypeElement.java
similarity index 72%
rename from src/main/java/com/android/tools/r8/ir/analysis/type/ByteTypeLatticeElement.java
rename to src/main/java/com/android/tools/r8/ir/analysis/type/ByteTypeElement.java
index b2df89c..c9e5e9f 100644
--- a/src/main/java/com/android/tools/r8/ir/analysis/type/ByteTypeLatticeElement.java
+++ b/src/main/java/com/android/tools/r8/ir/analysis/type/ByteTypeElement.java
@@ -3,11 +3,11 @@
 // BSD-style license that can be found in the LICENSE file.
 package com.android.tools.r8.ir.analysis.type;
 
-public class ByteTypeLatticeElement extends SinglePrimitiveTypeLatticeElement {
+public class ByteTypeElement extends SinglePrimitiveTypeElement {
 
-  private static final ByteTypeLatticeElement INSTANCE = new ByteTypeLatticeElement();
+  private static final ByteTypeElement INSTANCE = new ByteTypeElement();
 
-  static ByteTypeLatticeElement getInstance() {
+  static ByteTypeElement getInstance() {
     return INSTANCE;
   }
 
diff --git a/src/main/java/com/android/tools/r8/ir/analysis/type/CharTypeLatticeElement.java b/src/main/java/com/android/tools/r8/ir/analysis/type/CharTypeElement.java
similarity index 72%
rename from src/main/java/com/android/tools/r8/ir/analysis/type/CharTypeLatticeElement.java
rename to src/main/java/com/android/tools/r8/ir/analysis/type/CharTypeElement.java
index 3ff532f..0d33bfd 100644
--- a/src/main/java/com/android/tools/r8/ir/analysis/type/CharTypeLatticeElement.java
+++ b/src/main/java/com/android/tools/r8/ir/analysis/type/CharTypeElement.java
@@ -3,11 +3,11 @@
 // BSD-style license that can be found in the LICENSE file.
 package com.android.tools.r8.ir.analysis.type;
 
-public class CharTypeLatticeElement extends SinglePrimitiveTypeLatticeElement {
+public class CharTypeElement extends SinglePrimitiveTypeElement {
 
-  private static final CharTypeLatticeElement INSTANCE = new CharTypeLatticeElement();
+  private static final CharTypeElement INSTANCE = new CharTypeElement();
 
-  static CharTypeLatticeElement getInstance() {
+  static CharTypeElement getInstance() {
     return INSTANCE;
   }
 
diff --git a/src/main/java/com/android/tools/r8/ir/analysis/type/ClassTypeLatticeElement.java b/src/main/java/com/android/tools/r8/ir/analysis/type/ClassTypeElement.java
similarity index 88%
rename from src/main/java/com/android/tools/r8/ir/analysis/type/ClassTypeLatticeElement.java
rename to src/main/java/com/android/tools/r8/ir/analysis/type/ClassTypeElement.java
index 0499222..8a47047 100644
--- a/src/main/java/com/android/tools/r8/ir/analysis/type/ClassTypeLatticeElement.java
+++ b/src/main/java/com/android/tools/r8/ir/analysis/type/ClassTypeElement.java
@@ -23,38 +23,37 @@
 import java.util.function.Function;
 import java.util.stream.Collectors;
 
-public class ClassTypeLatticeElement extends ReferenceTypeLatticeElement {
+public class ClassTypeElement extends ReferenceTypeElement {
 
   // Least upper bound of interfaces that this class type is implementing.
   // Lazily computed on demand via DexItemFactory, where the canonicalized set will be maintained.
   private Set<DexType> lazyInterfaces;
   private AppView<? extends AppInfoWithSubtyping> appView;
   // On-demand link between other nullability-variants.
-  private final NullabilityVariants<ClassTypeLatticeElement> variants;
+  private final NullabilityVariants<ClassTypeElement> variants;
   private final DexType type;
 
-  public static ClassTypeLatticeElement create(
+  public static ClassTypeElement create(
       DexType classType, Nullability nullability, Set<DexType> interfaces) {
     assert interfaces != null;
     return NullabilityVariants.create(
         nullability,
-        (variants) ->
-            new ClassTypeLatticeElement(classType, nullability, interfaces, variants, null));
+        (variants) -> new ClassTypeElement(classType, nullability, interfaces, variants, null));
   }
 
-  public static ClassTypeLatticeElement create(
+  public static ClassTypeElement create(
       DexType classType, Nullability nullability, AppView<? extends AppInfoWithSubtyping> appView) {
     assert appView != null;
     return NullabilityVariants.create(
         nullability,
-        (variants) -> new ClassTypeLatticeElement(classType, nullability, null, variants, appView));
+        (variants) -> new ClassTypeElement(classType, nullability, null, variants, appView));
   }
 
-  private ClassTypeLatticeElement(
+  private ClassTypeElement(
       DexType classType,
       Nullability nullability,
       Set<DexType> interfaces,
-      NullabilityVariants<ClassTypeLatticeElement> variants,
+      NullabilityVariants<ClassTypeElement> variants,
       AppView<? extends AppInfoWithSubtyping> appView) {
     super(nullability);
     assert classType.isClassType();
@@ -80,20 +79,20 @@
     return lazyInterfaces;
   }
 
-  private ClassTypeLatticeElement createVariant(
-      Nullability nullability, NullabilityVariants<ClassTypeLatticeElement> variants) {
+  private ClassTypeElement createVariant(
+      Nullability nullability, NullabilityVariants<ClassTypeElement> variants) {
     assert this.nullability != nullability;
-    return new ClassTypeLatticeElement(type, nullability, lazyInterfaces, variants, appView);
+    return new ClassTypeElement(type, nullability, lazyInterfaces, variants, appView);
   }
 
-  public boolean isRelatedTo(ClassTypeLatticeElement other, AppView<?> appView) {
+  public boolean isRelatedTo(ClassTypeElement other, AppView<?> appView) {
     return lessThanOrEqualUpToNullability(other, appView)
         || other.lessThanOrEqualUpToNullability(this, appView);
   }
 
   @Override
-  public ClassTypeLatticeElement getOrCreateVariant(Nullability nullability) {
-    ClassTypeLatticeElement variant = variants.get(nullability);
+  public ClassTypeElement getOrCreateVariant(Nullability nullability) {
+    ClassTypeElement variant = variants.get(nullability);
     if (variant != null) {
       return variant;
     }
@@ -114,12 +113,12 @@
   }
 
   @Override
-  public ClassTypeLatticeElement asClassTypeLatticeElement() {
+  public ClassTypeElement asClassType() {
     return this;
   }
 
   @Override
-  public ClassTypeLatticeElement asMeetWithNotNull() {
+  public ClassTypeElement asMeetWithNotNull() {
     return getOrCreateVariant(nullability.meet(Nullability.definitelyNotNull()));
   }
 
@@ -145,11 +144,11 @@
   }
 
   @Override
-  public TypeLatticeElement fixupClassTypeReferences(
+  public TypeElement fixupClassTypeReferences(
       Function<DexType, DexType> mapping, AppView<? extends AppInfoWithSubtyping> appView) {
     DexType mappedType = mapping.apply(type);
     if (mappedType.isPrimitiveType()) {
-      return PrimitiveTypeLatticeElement.fromDexType(mappedType, false);
+      return PrimitiveTypeElement.fromDexType(mappedType, false);
     }
     if (mappedType != type) {
       return create(mappedType, nullability, appView);
@@ -196,12 +195,12 @@
     return this;
   }
 
-  ClassTypeLatticeElement join(ClassTypeLatticeElement other, AppView<?> appView) {
+  ClassTypeElement join(ClassTypeElement other, AppView<?> appView) {
     Nullability nullability = nullability().join(other.nullability());
     if (!appView.appInfo().hasSubtyping()) {
       assert lazyInterfaces != null && lazyInterfaces.isEmpty();
       assert other.lazyInterfaces != null && other.lazyInterfaces.isEmpty();
-      return ClassTypeLatticeElement.create(
+      return ClassTypeElement.create(
           getClassType() == other.getClassType()
               ? getClassType()
               : appView.dexItemFactory().objectType,
@@ -220,7 +219,7 @@
     if (lubItfs == null) {
       lubItfs = computeLeastUpperBoundOfInterfaces(appView.withSubtyping(), c1lubItfs, c2lubItfs);
     }
-    return ClassTypeLatticeElement.create(lubType, nullability, lubItfs);
+    return ClassTypeElement.create(lubType, nullability, lubItfs);
   }
 
   private enum InterfaceMarker {
@@ -371,10 +370,10 @@
     if (this == o) {
       return true;
     }
-    if (!(o instanceof ClassTypeLatticeElement)) {
+    if (!(o instanceof ClassTypeElement)) {
       return false;
     }
-    ClassTypeLatticeElement other = (ClassTypeLatticeElement) o;
+    ClassTypeElement other = (ClassTypeElement) o;
     if (nullability() != other.nullability()) {
       return false;
     }
diff --git a/src/main/java/com/android/tools/r8/ir/analysis/type/DestructivePhiTypeUpdater.java b/src/main/java/com/android/tools/r8/ir/analysis/type/DestructivePhiTypeUpdater.java
index 6449294..3e34256 100644
--- a/src/main/java/com/android/tools/r8/ir/analysis/type/DestructivePhiTypeUpdater.java
+++ b/src/main/java/com/android/tools/r8/ir/analysis/type/DestructivePhiTypeUpdater.java
@@ -40,7 +40,7 @@
     Deque<Phi> worklist = new ArrayDeque<>(affectedPhis);
     while (!worklist.isEmpty()) {
       Phi phi = worklist.poll();
-      phi.setTypeLattice(TypeLatticeElement.getBottom());
+      phi.setType(TypeElement.getBottom());
       for (Phi affectedPhi : phi.uniquePhiUsers()) {
         if (affectedPhis.add(affectedPhi)) {
           worklist.add(affectedPhi);
@@ -56,10 +56,10 @@
     worklist.addAll(affectedPhis);
     while (!worklist.isEmpty()) {
       Phi phi = worklist.poll();
-      TypeLatticeElement newType = phi.computePhiType(appView);
-      if (!phi.getTypeLattice().equals(newType)) {
+      TypeElement newType = phi.computePhiType(appView);
+      if (!phi.getType().equals(newType)) {
         assert !newType.isBottom();
-        phi.setTypeLattice(newType);
+        phi.setType(newType);
         worklist.addAll(phi.uniquePhiUsers());
         affectedValues.addAll(phi.affectedValues());
       }
@@ -80,12 +80,12 @@
       for (Value operand : phi.getOperands()) {
         if (operand.isPhi()) {
           Phi operandPhi = operand.asPhi();
-          TypeLatticeElement operandType = operandPhi.getTypeLattice();
+          TypeElement operandType = operandPhi.getType();
           assert !affectedPhis.contains(operandPhi) || operandType.isBottom();
           assert affectedPhis.contains(operandPhi)
-              || operandType.isPrimitive()
+              || operandType.isPrimitiveType()
               || operandType.isNullType()
-              || (operandType.isReference()
+              || (operandType.isReferenceType()
                   && operandType.fixupClassTypeReferences(mapping, appView) == operandType);
         }
       }
@@ -98,9 +98,9 @@
     while (blocks.hasNext()) {
       BasicBlock block = blocks.next();
       for (Phi phi : block.getPhis()) {
-        TypeLatticeElement phiTypeLattice = phi.getTypeLattice();
-        TypeLatticeElement substituted = phiTypeLattice.fixupClassTypeReferences(mapping, appView);
-        assert substituted == phiTypeLattice || affectedPhis.contains(phi);
+        TypeElement phiType = phi.getType();
+        TypeElement substituted = phiType.fixupClassTypeReferences(mapping, appView);
+        assert substituted == phiType || affectedPhis.contains(phi);
       }
     }
     return true;
diff --git a/src/main/java/com/android/tools/r8/ir/analysis/type/DoubleTypeLatticeElement.java b/src/main/java/com/android/tools/r8/ir/analysis/type/DoubleTypeElement.java
similarity index 69%
rename from src/main/java/com/android/tools/r8/ir/analysis/type/DoubleTypeLatticeElement.java
rename to src/main/java/com/android/tools/r8/ir/analysis/type/DoubleTypeElement.java
index 3f521c2..e9632a8 100644
--- a/src/main/java/com/android/tools/r8/ir/analysis/type/DoubleTypeLatticeElement.java
+++ b/src/main/java/com/android/tools/r8/ir/analysis/type/DoubleTypeElement.java
@@ -3,11 +3,11 @@
 // BSD-style license that can be found in the LICENSE file.
 package com.android.tools.r8.ir.analysis.type;
 
-public class DoubleTypeLatticeElement extends WidePrimitiveTypeLatticeElement {
+public class DoubleTypeElement extends WidePrimitiveTypeElement {
 
-  private static final DoubleTypeLatticeElement INSTANCE = new DoubleTypeLatticeElement();
+  private static final DoubleTypeElement INSTANCE = new DoubleTypeElement();
 
-  static DoubleTypeLatticeElement getInstance() {
+  static DoubleTypeElement getInstance() {
     return INSTANCE;
   }
 
diff --git a/src/main/java/com/android/tools/r8/ir/analysis/type/FloatTypeLatticeElement.java b/src/main/java/com/android/tools/r8/ir/analysis/type/FloatTypeElement.java
similarity index 69%
rename from src/main/java/com/android/tools/r8/ir/analysis/type/FloatTypeLatticeElement.java
rename to src/main/java/com/android/tools/r8/ir/analysis/type/FloatTypeElement.java
index 69933a0..386981d 100644
--- a/src/main/java/com/android/tools/r8/ir/analysis/type/FloatTypeLatticeElement.java
+++ b/src/main/java/com/android/tools/r8/ir/analysis/type/FloatTypeElement.java
@@ -3,10 +3,10 @@
 // BSD-style license that can be found in the LICENSE file.
 package com.android.tools.r8.ir.analysis.type;
 
-public class FloatTypeLatticeElement extends SinglePrimitiveTypeLatticeElement {
-  private static final FloatTypeLatticeElement INSTANCE = new FloatTypeLatticeElement();
+public class FloatTypeElement extends SinglePrimitiveTypeElement {
+  private static final FloatTypeElement INSTANCE = new FloatTypeElement();
 
-  static FloatTypeLatticeElement getInstance() {
+  static FloatTypeElement getInstance() {
     return INSTANCE;
   }
 
diff --git a/src/main/java/com/android/tools/r8/ir/analysis/type/IntTypeLatticeElement.java b/src/main/java/com/android/tools/r8/ir/analysis/type/IntTypeElement.java
similarity index 70%
rename from src/main/java/com/android/tools/r8/ir/analysis/type/IntTypeLatticeElement.java
rename to src/main/java/com/android/tools/r8/ir/analysis/type/IntTypeElement.java
index 2d7e5b3..5ecb417 100644
--- a/src/main/java/com/android/tools/r8/ir/analysis/type/IntTypeLatticeElement.java
+++ b/src/main/java/com/android/tools/r8/ir/analysis/type/IntTypeElement.java
@@ -3,10 +3,10 @@
 // BSD-style license that can be found in the LICENSE file.
 package com.android.tools.r8.ir.analysis.type;
 
-public class IntTypeLatticeElement extends SinglePrimitiveTypeLatticeElement {
-  private static final IntTypeLatticeElement INSTANCE = new IntTypeLatticeElement();
+public class IntTypeElement extends SinglePrimitiveTypeElement {
+  private static final IntTypeElement INSTANCE = new IntTypeElement();
 
-  static IntTypeLatticeElement getInstance() {
+  static IntTypeElement getInstance() {
     return INSTANCE;
   }
 
diff --git a/src/main/java/com/android/tools/r8/ir/analysis/type/LongTypeLatticeElement.java b/src/main/java/com/android/tools/r8/ir/analysis/type/LongTypeElement.java
similarity index 70%
rename from src/main/java/com/android/tools/r8/ir/analysis/type/LongTypeLatticeElement.java
rename to src/main/java/com/android/tools/r8/ir/analysis/type/LongTypeElement.java
index db3ef45..caf2ed3 100644
--- a/src/main/java/com/android/tools/r8/ir/analysis/type/LongTypeLatticeElement.java
+++ b/src/main/java/com/android/tools/r8/ir/analysis/type/LongTypeElement.java
@@ -3,11 +3,11 @@
 // BSD-style license that can be found in the LICENSE file.
 package com.android.tools.r8.ir.analysis.type;
 
-public class LongTypeLatticeElement extends WidePrimitiveTypeLatticeElement {
+public class LongTypeElement extends WidePrimitiveTypeElement {
 
-  private static final LongTypeLatticeElement INSTANCE = new LongTypeLatticeElement();
+  private static final LongTypeElement INSTANCE = new LongTypeElement();
 
-  static LongTypeLatticeElement getInstance() {
+  static LongTypeElement getInstance() {
     return INSTANCE;
   }
 
diff --git a/src/main/java/com/android/tools/r8/ir/analysis/type/NullabilityVariants.java b/src/main/java/com/android/tools/r8/ir/analysis/type/NullabilityVariants.java
index 9a0f95a..2c76787 100644
--- a/src/main/java/com/android/tools/r8/ir/analysis/type/NullabilityVariants.java
+++ b/src/main/java/com/android/tools/r8/ir/analysis/type/NullabilityVariants.java
@@ -7,14 +7,14 @@
 import java.util.function.BiFunction;
 import java.util.function.Function;
 
-public class NullabilityVariants<T extends ReferenceTypeLatticeElement> {
+public class NullabilityVariants<T extends ReferenceTypeElement> {
 
   private T maybeNullVariant;
   private T definitelyNullVariant;
   private T definitelyNotNullVariant;
   private T bottomVariant;
 
-  public static <T extends ReferenceTypeLatticeElement> T create(
+  public static <T extends ReferenceTypeElement> T create(
       Nullability nullability, Function<NullabilityVariants<T>, T> callback) {
     NullabilityVariants<T> variants = new NullabilityVariants<>();
     T newElement = callback.apply(variants);
diff --git a/src/main/java/com/android/tools/r8/ir/analysis/type/PrimitiveTypeLatticeElement.java b/src/main/java/com/android/tools/r8/ir/analysis/type/PrimitiveTypeElement.java
similarity index 68%
rename from src/main/java/com/android/tools/r8/ir/analysis/type/PrimitiveTypeLatticeElement.java
rename to src/main/java/com/android/tools/r8/ir/analysis/type/PrimitiveTypeElement.java
index 6a16fdc..4e9b5f1a 100644
--- a/src/main/java/com/android/tools/r8/ir/analysis/type/PrimitiveTypeLatticeElement.java
+++ b/src/main/java/com/android/tools/r8/ir/analysis/type/PrimitiveTypeElement.java
@@ -9,10 +9,8 @@
 import com.android.tools.r8.graph.DexType;
 import com.android.tools.r8.ir.code.NumericType;
 
-/**
- * A {@link TypeLatticeElement} that abstracts primitive types.
- */
-public abstract class PrimitiveTypeLatticeElement extends TypeLatticeElement {
+/** A {@link TypeElement} that abstracts primitive types. */
+public abstract class PrimitiveTypeElement extends TypeElement {
 
   @Override
   public Nullability nullability() {
@@ -20,16 +18,16 @@
   }
 
   @Override
-  public boolean isPrimitive() {
+  public boolean isPrimitiveType() {
     return true;
   }
 
   @Override
-  public PrimitiveTypeLatticeElement asPrimitiveTypeLatticeElement() {
+  public PrimitiveTypeElement asPrimitiveType() {
     return this;
   }
 
-  static PrimitiveTypeLatticeElement fromDexType(DexType type, boolean asArrayElementType) {
+  static PrimitiveTypeElement fromDexType(DexType type, boolean asArrayElementType) {
     assert type.isPrimitiveType();
     return fromTypeDescriptorChar((char) type.descriptor.content[0], asArrayElementType);
   }
@@ -73,37 +71,37 @@
         || isDouble();
   }
 
-  private static PrimitiveTypeLatticeElement fromTypeDescriptorChar(
+  private static PrimitiveTypeElement fromTypeDescriptorChar(
       char descriptor, boolean asArrayElementType) {
     switch (descriptor) {
       case 'Z':
         if (asArrayElementType) {
-          return TypeLatticeElement.getBoolean();
+          return TypeElement.getBoolean();
         }
         // fall through
       case 'B':
         if (asArrayElementType) {
-          return TypeLatticeElement.getByte();
+          return TypeElement.getByte();
         }
         // fall through
       case 'S':
         if (asArrayElementType) {
-          return TypeLatticeElement.getShort();
+          return TypeElement.getShort();
         }
         // fall through
       case 'C':
         if (asArrayElementType) {
-          return TypeLatticeElement.getChar();
+          return TypeElement.getChar();
         }
         // fall through
       case 'I':
-        return TypeLatticeElement.getInt();
+        return TypeElement.getInt();
       case 'F':
-        return TypeLatticeElement.getFloat();
+        return TypeElement.getFloat();
       case 'J':
-        return TypeLatticeElement.getLong();
+        return TypeElement.getLong();
       case 'D':
-        return TypeLatticeElement.getDouble();
+        return TypeElement.getDouble();
       case 'V':
         throw new InternalCompilerError("No value type for void type.");
       default:
@@ -111,40 +109,40 @@
     }
   }
 
-  public static PrimitiveTypeLatticeElement fromNumericType(NumericType numericType) {
+  public static PrimitiveTypeElement fromNumericType(NumericType numericType) {
     switch(numericType) {
       case BYTE:
       case CHAR:
       case SHORT:
       case INT:
-        return TypeLatticeElement.getInt();
+        return TypeElement.getInt();
       case FLOAT:
-        return TypeLatticeElement.getFloat();
+        return TypeElement.getFloat();
       case LONG:
-        return TypeLatticeElement.getLong();
+        return TypeElement.getLong();
       case DOUBLE:
-        return TypeLatticeElement.getDouble();
+        return TypeElement.getDouble();
       default:
         throw new Unreachable("Invalid numeric type '" + numericType + "'");
     }
   }
 
-  TypeLatticeElement join(PrimitiveTypeLatticeElement other) {
+  TypeElement join(PrimitiveTypeElement other) {
     if (this == other) {
       return this;
     }
     if (isSinglePrimitive()) {
       if (other.isSinglePrimitive()) {
-        return TypeLatticeElement.getSingle();
+        return TypeElement.getSingle();
       }
       assert other.isWidePrimitive();
-      return TypeLatticeElement.getTop();
+      return TypeElement.getTop();
     }
     assert isWidePrimitive();
     if (other.isWidePrimitive()) {
-      return TypeLatticeElement.getWide();
+      return TypeElement.getWide();
     }
     assert other.isSinglePrimitive();
-    return TypeLatticeElement.getTop();
+    return TypeElement.getTop();
   }
 }
diff --git a/src/main/java/com/android/tools/r8/ir/analysis/type/ReferenceTypeElement.java b/src/main/java/com/android/tools/r8/ir/analysis/type/ReferenceTypeElement.java
new file mode 100644
index 0000000..c150cfd
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/ir/analysis/type/ReferenceTypeElement.java
@@ -0,0 +1,108 @@
+// Copyright (c) 2018, 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 com.android.tools.r8.errors.Unreachable;
+import com.android.tools.r8.graph.DexItemFactory;
+
+public abstract class ReferenceTypeElement extends TypeElement {
+
+  private static class NullElement extends ReferenceTypeElement {
+
+    NullElement(Nullability nullability) {
+      super(nullability);
+    }
+
+    @Override
+    public ReferenceTypeElement getOrCreateVariant(Nullability nullability) {
+      return nullability.isNullable() ? NULL_INSTANCE : NULL_BOTTOM_INSTANCE;
+    }
+
+    private static NullElement create() {
+      return new NullElement(Nullability.definitelyNull());
+    }
+
+    private static NullElement createBottom() {
+      return new NullElement(Nullability.bottom());
+    }
+
+    @Override
+    public boolean isNullType() {
+      return true;
+    }
+
+    @Override
+    public String toString() {
+      return nullability.toString() + " " + DexItemFactory.nullValueType.toString();
+    }
+
+    @Override
+    public int hashCode() {
+      return System.identityHashCode(this);
+    }
+
+    @Override
+    public boolean equals(Object o) {
+      if (this == o) {
+        return true;
+      }
+      if (!(o instanceof NullElement)) {
+        return false;
+      }
+      return true;
+    }
+  }
+
+  private static final ReferenceTypeElement NULL_INSTANCE = NullElement.create();
+  private static final ReferenceTypeElement NULL_BOTTOM_INSTANCE = NullElement.createBottom();
+
+  final Nullability nullability;
+
+  ReferenceTypeElement(Nullability nullability) {
+    this.nullability = nullability;
+  }
+
+  @Override
+  public Nullability nullability() {
+    return nullability;
+  }
+
+  static ReferenceTypeElement getNullType() {
+    return NULL_INSTANCE;
+  }
+
+  public abstract ReferenceTypeElement getOrCreateVariant(Nullability nullability);
+
+  public TypeElement asMeetWithNotNull() {
+    return getOrCreateVariant(nullability.meet(Nullability.definitelyNotNull()));
+  }
+
+  public TypeElement asDefinitelyNotNull() {
+    return getOrCreateVariant(Nullability.definitelyNotNull());
+  }
+
+  public TypeElement asMaybeNull() {
+    return getOrCreateVariant(Nullability.maybeNull());
+  }
+
+  @Override
+  public boolean isReferenceType() {
+    return true;
+  }
+
+  @Override
+  public ReferenceTypeElement asReferenceType() {
+    return this;
+  }
+
+  @Override
+  public boolean equals(Object o) {
+    throw new Unreachable("Should be implemented on each sub type");
+  }
+
+  @Override
+  public int hashCode() {
+    throw new Unreachable("Should be implemented on each sub type");
+  }
+}
diff --git a/src/main/java/com/android/tools/r8/ir/analysis/type/ReferenceTypeLatticeElement.java b/src/main/java/com/android/tools/r8/ir/analysis/type/ReferenceTypeLatticeElement.java
deleted file mode 100644
index b888ae5..0000000
--- a/src/main/java/com/android/tools/r8/ir/analysis/type/ReferenceTypeLatticeElement.java
+++ /dev/null
@@ -1,109 +0,0 @@
-// Copyright (c) 2018, 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 com.android.tools.r8.errors.Unreachable;
-import com.android.tools.r8.graph.DexItemFactory;
-
-public abstract class ReferenceTypeLatticeElement extends TypeLatticeElement {
-
-  private static class NullLatticeElement extends ReferenceTypeLatticeElement {
-
-    NullLatticeElement(Nullability nullability) {
-      super(nullability);
-    }
-
-    @Override
-    public ReferenceTypeLatticeElement getOrCreateVariant(Nullability nullability) {
-      return nullability.isNullable() ? NULL_INSTANCE : NULL_BOTTOM_INSTANCE;
-    }
-
-    private static NullLatticeElement create() {
-      return new NullLatticeElement(Nullability.definitelyNull());
-    }
-
-    private static NullLatticeElement createBottom() {
-      return new NullLatticeElement(Nullability.bottom());
-    }
-
-    @Override
-    public boolean isNullType() {
-      return true;
-    }
-
-    @Override
-    public String toString() {
-      return nullability.toString() + " " + DexItemFactory.nullValueType.toString();
-    }
-
-    @Override
-    public int hashCode() {
-      return System.identityHashCode(this);
-    }
-
-    @Override
-    public boolean equals(Object o) {
-      if (this == o) {
-        return true;
-      }
-      if (!(o instanceof NullLatticeElement)) {
-        return false;
-      }
-      return true;
-    }
-  }
-
-  private static final ReferenceTypeLatticeElement NULL_INSTANCE = NullLatticeElement.create();
-  private static final ReferenceTypeLatticeElement NULL_BOTTOM_INSTANCE =
-      NullLatticeElement.createBottom();
-
-  final Nullability nullability;
-
-  ReferenceTypeLatticeElement(Nullability nullability) {
-    this.nullability = nullability;
-  }
-
-  @Override
-  public Nullability nullability() {
-    return nullability;
-  }
-
-  static ReferenceTypeLatticeElement getNullTypeLatticeElement() {
-    return NULL_INSTANCE;
-  }
-
-  public abstract ReferenceTypeLatticeElement getOrCreateVariant(Nullability nullability);
-
-  public TypeLatticeElement asMeetWithNotNull() {
-    return getOrCreateVariant(nullability.meet(Nullability.definitelyNotNull()));
-  }
-
-  public TypeLatticeElement asDefinitelyNotNull() {
-    return getOrCreateVariant(Nullability.definitelyNotNull());
-  }
-
-  public TypeLatticeElement asMaybeNull() {
-    return getOrCreateVariant(Nullability.maybeNull());
-  }
-
-  @Override
-  public boolean isReference() {
-    return true;
-  }
-
-  @Override
-  public ReferenceTypeLatticeElement asReferenceTypeLatticeElement() {
-    return this;
-  }
-
-  @Override
-  public boolean equals(Object o) {
-    throw new Unreachable("Should be implemented on each sub type");
-  }
-
-  @Override
-  public int hashCode() {
-    throw new Unreachable("Should be implemented on each sub type");
-  }
-}
diff --git a/src/main/java/com/android/tools/r8/ir/analysis/type/ShortTypeLatticeElement.java b/src/main/java/com/android/tools/r8/ir/analysis/type/ShortTypeElement.java
similarity index 72%
rename from src/main/java/com/android/tools/r8/ir/analysis/type/ShortTypeLatticeElement.java
rename to src/main/java/com/android/tools/r8/ir/analysis/type/ShortTypeElement.java
index 85bd6fc..61b8514 100644
--- a/src/main/java/com/android/tools/r8/ir/analysis/type/ShortTypeLatticeElement.java
+++ b/src/main/java/com/android/tools/r8/ir/analysis/type/ShortTypeElement.java
@@ -3,11 +3,11 @@
 // BSD-style license that can be found in the LICENSE file.
 package com.android.tools.r8.ir.analysis.type;
 
-public class ShortTypeLatticeElement extends SinglePrimitiveTypeLatticeElement {
+public class ShortTypeElement extends SinglePrimitiveTypeElement {
 
-  private static final ShortTypeLatticeElement INSTANCE = new ShortTypeLatticeElement();
+  private static final ShortTypeElement INSTANCE = new ShortTypeElement();
 
-  static ShortTypeLatticeElement getInstance() {
+  static ShortTypeElement getInstance() {
     return INSTANCE;
   }
 
diff --git a/src/main/java/com/android/tools/r8/ir/analysis/type/SinglePrimitiveTypeLatticeElement.java b/src/main/java/com/android/tools/r8/ir/analysis/type/SinglePrimitiveTypeElement.java
similarity index 61%
rename from src/main/java/com/android/tools/r8/ir/analysis/type/SinglePrimitiveTypeLatticeElement.java
rename to src/main/java/com/android/tools/r8/ir/analysis/type/SinglePrimitiveTypeElement.java
index 2a94fb5..40436d1 100644
--- a/src/main/java/com/android/tools/r8/ir/analysis/type/SinglePrimitiveTypeLatticeElement.java
+++ b/src/main/java/com/android/tools/r8/ir/analysis/type/SinglePrimitiveTypeElement.java
@@ -3,17 +3,16 @@
 // BSD-style license that can be found in the LICENSE file.
 package com.android.tools.r8.ir.analysis.type;
 
-/** A {@link TypeLatticeElement} that abstracts primitive types, which fit in 32 bits. */
-public class SinglePrimitiveTypeLatticeElement extends PrimitiveTypeLatticeElement {
+/** A {@link TypeElement} that abstracts primitive types, which fit in 32 bits. */
+public class SinglePrimitiveTypeElement extends PrimitiveTypeElement {
 
-  private static final SinglePrimitiveTypeLatticeElement INSTANCE =
-      new SinglePrimitiveTypeLatticeElement();
+  private static final SinglePrimitiveTypeElement INSTANCE = new SinglePrimitiveTypeElement();
 
-  SinglePrimitiveTypeLatticeElement() {
+  SinglePrimitiveTypeElement() {
     super();
   }
 
-  static SinglePrimitiveTypeLatticeElement getInstance() {
+  static SinglePrimitiveTypeElement getInstance() {
     return INSTANCE;
   }
 
diff --git a/src/main/java/com/android/tools/r8/ir/analysis/type/TopTypeLatticeElement.java b/src/main/java/com/android/tools/r8/ir/analysis/type/TopTypeElement.java
similarity index 77%
rename from src/main/java/com/android/tools/r8/ir/analysis/type/TopTypeLatticeElement.java
rename to src/main/java/com/android/tools/r8/ir/analysis/type/TopTypeElement.java
index 1d483e8..6c7fec2 100644
--- a/src/main/java/com/android/tools/r8/ir/analysis/type/TopTypeLatticeElement.java
+++ b/src/main/java/com/android/tools/r8/ir/analysis/type/TopTypeElement.java
@@ -3,15 +3,15 @@
 // BSD-style license that can be found in the LICENSE file.
 package com.android.tools.r8.ir.analysis.type;
 
-public class TopTypeLatticeElement extends TypeLatticeElement {
-  private static final TopTypeLatticeElement INSTANCE = new TopTypeLatticeElement();
+public class TopTypeElement extends TypeElement {
+  private static final TopTypeElement INSTANCE = new TopTypeElement();
 
   @Override
   public Nullability nullability() {
     return Nullability.maybeNull();
   }
 
-  static TopTypeLatticeElement getInstance() {
+  static TopTypeElement getInstance() {
     return INSTANCE;
   }
 
diff --git a/src/main/java/com/android/tools/r8/ir/analysis/type/TypeAnalysis.java b/src/main/java/com/android/tools/r8/ir/analysis/type/TypeAnalysis.java
index 0596d7b..3d1c2b5 100644
--- a/src/main/java/com/android/tools/r8/ir/analysis/type/TypeAnalysis.java
+++ b/src/main/java/com/android/tools/r8/ir/analysis/type/TypeAnalysis.java
@@ -101,7 +101,7 @@
         // The type for Argument, a quasi instruction is already set correctly during IR building.
         // Note that we don't need to enqueue the out value of arguments here because it's constant.
       } else if (instruction.hasInvariantOutType()) {
-        TypeLatticeElement derived = instruction.evaluate(appView);
+        TypeElement derived = instruction.evaluate(appView);
         updateTypeOfValue(outValue, derived);
       } else {
         enqueue(outValue);
@@ -113,18 +113,18 @@
   }
 
   private void analyzeValue(Value value) {
-    TypeLatticeElement previous = value.getTypeLattice();
-    TypeLatticeElement derived =
+    TypeElement previous = value.getType();
+    TypeElement derived =
         value.isPhi() ? value.asPhi().computePhiType(appView) : value.definition.evaluate(appView);
     assert mayHaveImpreciseTypes || derived.isPreciseType();
     assert !previous.isPreciseType() || derived.isPreciseType();
     updateTypeOfValue(value, derived);
   }
 
-  private void updateTypeOfValue(Value value, TypeLatticeElement type) {
+  private void updateTypeOfValue(Value value, TypeElement type) {
     assert mode != Mode.UNSET;
 
-    TypeLatticeElement current = value.getTypeLattice();
+    TypeElement current = value.getType();
     if (current.equals(type)) {
       return;
     }
@@ -158,10 +158,10 @@
   public static DexType getRefinedReceiverType(
       AppView<? extends AppInfoWithSubtyping> appView, InvokeMethodWithReceiver invoke) {
     Value receiver = invoke.getReceiver();
-    TypeLatticeElement lattice = receiver.getDynamicUpperBoundType(appView);
+    TypeElement lattice = receiver.getDynamicUpperBoundType(appView);
     DexType staticReceiverType = invoke.getInvokedMethod().holder;
     if (lattice.isClassType()) {
-      ClassTypeLatticeElement classType = lattice.asClassTypeLatticeElement();
+      ClassTypeElement classType = lattice.asClassType();
       DexType refinedType = classType.getClassType();
       if (refinedType == appView.dexItemFactory().objectType) {
         Set<DexType> interfaces = classType.getInterfaces();
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
new file mode 100644
index 0000000..ecc7340
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/ir/analysis/type/TypeElement.java
@@ -0,0 +1,422 @@
+// Copyright (c) 2017, 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 com.android.tools.r8.errors.Unreachable;
+import com.android.tools.r8.graph.AppInfoWithSubtyping;
+import com.android.tools.r8.graph.AppView;
+import com.android.tools.r8.graph.DexDefinitionSupplier;
+import com.android.tools.r8.graph.DexItemFactory;
+import com.android.tools.r8.graph.DexType;
+import com.android.tools.r8.ir.code.Value;
+import java.util.function.Function;
+
+/** The base abstraction of lattice elements for local type analysis. */
+public abstract class TypeElement {
+
+  public static BottomTypeElement getBottom() {
+    return BottomTypeElement.getInstance();
+  }
+
+  public static TopTypeElement getTop() {
+    return TopTypeElement.getInstance();
+  }
+
+  public static BooleanTypeElement getBoolean() {
+    return BooleanTypeElement.getInstance();
+  }
+
+  public static ByteTypeElement getByte() {
+    return ByteTypeElement.getInstance();
+  }
+
+  public static ShortTypeElement getShort() {
+    return ShortTypeElement.getInstance();
+  }
+
+  public static CharTypeElement getChar() {
+    return CharTypeElement.getInstance();
+  }
+
+  public static IntTypeElement getInt() {
+    return IntTypeElement.getInstance();
+  }
+
+  public static FloatTypeElement getFloat() {
+    return FloatTypeElement.getInstance();
+  }
+
+  public static SinglePrimitiveTypeElement getSingle() {
+    return SinglePrimitiveTypeElement.getInstance();
+  }
+
+  public static LongTypeElement getLong() {
+    return LongTypeElement.getInstance();
+  }
+
+  public static DoubleTypeElement getDouble() {
+    return DoubleTypeElement.getInstance();
+  }
+
+  public static WidePrimitiveTypeElement getWide() {
+    return WidePrimitiveTypeElement.getInstance();
+  }
+
+  public static ReferenceTypeElement getNull() {
+    return ReferenceTypeElement.getNullType();
+  }
+
+  public TypeElement fixupClassTypeReferences(
+      Function<DexType, DexType> mapping, AppView<? extends AppInfoWithSubtyping> appView) {
+    return this;
+  }
+
+  public boolean isNullable() {
+    return nullability().isNullable();
+  }
+
+  public abstract Nullability nullability();
+
+  /**
+   * Computes the least upper bound of the current and the other elements.
+   *
+   * @param other {@link TypeElement} to join.
+   * @param appView {@link DexDefinitionSupplier}.
+   * @return {@link TypeElement}, a least upper bound of {@param this} and {@param other}.
+   */
+  public TypeElement join(TypeElement other, AppView<?> appView) {
+    if (this == other) {
+      return this;
+    }
+    if (isBottom()) {
+      return other;
+    }
+    if (other.isBottom()) {
+      return this;
+    }
+    if (isTop() || other.isTop()) {
+      return getTop();
+    }
+    if (isPrimitiveType()) {
+      return other.isPrimitiveType() ? asPrimitiveType().join(other.asPrimitiveType()) : getTop();
+    }
+    if (other.isPrimitiveType()) {
+      // By the above case, !(isPrimitive())
+      return getTop();
+    }
+    // From now on, this and other are precise reference types, i.e., either ArrayType or ClassType.
+    assert isReferenceType() && other.isReferenceType();
+    assert isPreciseType() && other.isPreciseType();
+    Nullability nullabilityJoin = nullability().join(other.nullability());
+    if (isNullType()) {
+      return other.asReferenceType().getOrCreateVariant(nullabilityJoin);
+    }
+    if (other.isNullType()) {
+      return this.asReferenceType().getOrCreateVariant(nullabilityJoin);
+    }
+    if (getClass() != other.getClass()) {
+      return objectClassType(appView, nullabilityJoin);
+    }
+    // From now on, getClass() == other.getClass()
+    if (isArrayType()) {
+      assert other.isArrayType();
+      return asArrayType().join(other.asArrayType(), appView);
+    }
+    if (isClassType()) {
+      assert other.isClassType();
+      return asClassType().join(other.asClassType(), appView);
+    }
+    throw new Unreachable("unless a new type lattice is introduced.");
+  }
+
+  public static TypeElement join(Iterable<TypeElement> typeLattices, AppView<?> appView) {
+    TypeElement result = getBottom();
+    for (TypeElement other : typeLattices) {
+      result = result.join(other, appView);
+    }
+    return result;
+  }
+
+  /**
+   * Determines the strict partial order of the given {@link TypeElement}s.
+   *
+   * @param other expected to be *strictly* bigger than {@param this}
+   * @param appView {@link DexDefinitionSupplier} to compute the least upper bound of {@link
+   *     TypeElement}
+   * @return {@code true} if {@param this} is strictly less than {@param other}.
+   */
+  public boolean strictlyLessThan(TypeElement other, AppView<?> appView) {
+    if (equals(other)) {
+      return false;
+    }
+    TypeElement lub = join(other, appView);
+    return !equals(lub) && other.equals(lub);
+  }
+
+  /**
+   * Determines the partial order of the given {@link TypeElement}s.
+   *
+   * @param other expected to be bigger than or equal to {@param this}
+   * @param appView {@link DexDefinitionSupplier} to compute the least upper bound of {@link
+   *     TypeElement}
+   * @return {@code true} if {@param this} is less than or equal to {@param other}.
+   */
+  public boolean lessThanOrEqual(TypeElement other, AppView<?> appView) {
+    return equals(other) || strictlyLessThan(other, appView);
+  }
+
+  /**
+   * Determines if this {@link TypeElement} is less than or equal to the given {@link TypeElement}
+   * up to nullability.
+   *
+   * @param other to check for equality with this
+   * @return {@code true} if {@param this} is equal up to nullability with {@param other}.
+   */
+  public boolean lessThanOrEqualUpToNullability(TypeElement other, AppView<?> appView) {
+    if (this == other) {
+      return true;
+    }
+    if (this.isTop()) {
+      return other.isTop();
+    }
+    if (other.isTop()) {
+      return true;
+    }
+    if (this.isBottom()) {
+      return true;
+    }
+    if (other.isBottom()) {
+      return false;
+    }
+    if (isPrimitiveType()) {
+      // Primitives cannot be nullable.
+      return lessThanOrEqual(other, appView);
+    }
+    assert isReferenceType() && other.isReferenceType();
+    ReferenceTypeElement otherAsNullable =
+        other.isNullable()
+            ? other.asReferenceType()
+            : other.asReferenceType().getOrCreateVariant(Nullability.maybeNull());
+    return lessThanOrEqual(otherAsNullable, appView);
+  }
+
+  /**
+   * Determines if the {@link TypeElement}s are equal up to nullability.
+   *
+   * @param other to check for equality with this
+   * @return {@code true} if {@param this} is equal up to nullability with {@param other}.
+   */
+  public boolean equalUpToNullability(TypeElement other) {
+    if (this == other) {
+      return true;
+    }
+    if (isPrimitiveType() || other.isPrimitiveType()) {
+      return false;
+    }
+    assert isReferenceType() && other.isReferenceType();
+    ReferenceTypeElement thisAsMaybeNull =
+        this.asReferenceType().getOrCreateVariant(Nullability.maybeNull());
+    ReferenceTypeElement otherAsMaybeNull =
+        other.asReferenceType().getOrCreateVariant(Nullability.maybeNull());
+    return thisAsMaybeNull.equals(otherAsMaybeNull);
+  }
+
+  /**
+   * Determines if this type is based on a missing class, directly or indirectly.
+   *
+   * @return {@code} true if this type is based on a missing class.
+   * @param appView
+   */
+  public boolean isBasedOnMissingClass(AppView<? extends AppInfoWithSubtyping> appView) {
+    return false;
+  }
+
+  /**
+   * Represents a type that can be everything.
+   *
+   * @return {@code true} if the corresponding {@link Value} could be any kinds.
+   */
+  public boolean isTop() {
+    return false;
+  }
+
+  /**
+   * Represents an empty type.
+   *
+   * @return {@code true} if the type of corresponding {@link Value} is not determined yet.
+   */
+  public boolean isBottom() {
+    return false;
+  }
+
+  public boolean isReferenceType() {
+    return false;
+  }
+
+  public ReferenceTypeElement asReferenceType() {
+    return null;
+  }
+
+  public boolean isArrayType() {
+    return false;
+  }
+
+  public ArrayTypeElement asArrayType() {
+    return null;
+  }
+
+  public boolean isClassType() {
+    return false;
+  }
+
+  public ClassTypeElement asClassType() {
+    return null;
+  }
+
+  public boolean isPrimitiveType() {
+    return false;
+  }
+
+  public PrimitiveTypeElement asPrimitiveType() {
+    return null;
+  }
+
+  public boolean isSinglePrimitive() {
+    return false;
+  }
+
+  public boolean isWidePrimitive() {
+    return false;
+  }
+
+  boolean isBoolean() {
+    return false;
+  }
+
+  boolean isByte() {
+    return false;
+  }
+
+  boolean isShort() {
+    return false;
+  }
+
+  boolean isChar() {
+    return false;
+  }
+
+  public boolean isInt() {
+    return false;
+  }
+
+  public boolean isFloat() {
+    return false;
+  }
+
+  public boolean isLong() {
+    return false;
+  }
+
+  public boolean isDouble() {
+    return false;
+  }
+
+  public boolean isPreciseType() {
+    return isArrayType()
+        || isClassType()
+        || isNullType()
+        || isInt()
+        || isFloat()
+        || isLong()
+        || isDouble()
+        || isBottom();
+  }
+
+  public boolean isFineGrainedType() {
+    return isBoolean()
+        || isByte()
+        || isShort()
+        || isChar();
+  }
+
+  /**
+   * Determines if this type only includes null values that are defined by a const-number
+   * instruction in the same enclosing method.
+   *
+   * These null values can be assigned to any type.
+   */
+  public boolean isNullType() {
+    return false;
+  }
+
+  /**
+   * Determines if this type only includes null values.
+   *
+   * These null values cannot be assigned to any type. For example, it is a type error to "throw v"
+   * where the value `v` satisfies isDefinitelyNull(), because the static type of `v` may not be a
+   * subtype of Throwable.
+   */
+  public boolean isDefinitelyNull() {
+    return nullability().isDefinitelyNull();
+  }
+
+  public boolean isDefinitelyNotNull() {
+    return nullability().isDefinitelyNotNull();
+  }
+
+  public int requiredRegisters() {
+    assert !isBottom() && !isTop();
+    return 1;
+  }
+
+  public static ClassTypeElement objectClassType(AppView<?> appView, Nullability nullability) {
+    return fromDexType(appView.dexItemFactory().objectType, nullability, appView).asClassType();
+  }
+
+  static ArrayTypeElement objectArrayType(AppView<?> appView, Nullability nullability) {
+    DexItemFactory dexItemFactory = appView.dexItemFactory();
+    return fromDexType(
+            dexItemFactory.createArrayType(1, dexItemFactory.objectType), nullability, appView)
+        .asArrayType();
+  }
+
+  public static ClassTypeElement classClassType(AppView<?> appView, Nullability nullability) {
+    return fromDexType(appView.dexItemFactory().classType, nullability, appView).asClassType();
+  }
+
+  public static ClassTypeElement stringClassType(AppView<?> appView, Nullability nullability) {
+    return fromDexType(appView.dexItemFactory().stringType, nullability, appView).asClassType();
+  }
+
+  public static TypeElement fromDexType(DexType type, Nullability nullability, AppView<?> appView) {
+    return fromDexType(type, nullability, appView, false);
+  }
+
+  public static TypeElement fromDexType(
+      DexType type, Nullability nullability, AppView<?> appView, boolean asArrayElementType) {
+    if (type == DexItemFactory.nullValueType) {
+      assert !nullability.isDefinitelyNotNull();
+      return getNull();
+    }
+    if (type.isPrimitiveType()) {
+      return PrimitiveTypeElement.fromDexType(type, asArrayElementType);
+    }
+    return appView.dexItemFactory().createReferenceTypeElement(type, nullability, appView);
+  }
+
+  public boolean isValueTypeCompatible(TypeElement other) {
+    return (isReferenceType() && other.isReferenceType())
+        || (isSinglePrimitive() && other.isSinglePrimitive())
+        || (isWidePrimitive() && other.isWidePrimitive());
+  }
+
+  @Override
+  public abstract String toString();
+
+  @Override
+  public abstract boolean equals(Object o);
+
+  @Override
+  public abstract int hashCode();
+}
diff --git a/src/main/java/com/android/tools/r8/ir/analysis/type/TypeLatticeElement.java b/src/main/java/com/android/tools/r8/ir/analysis/type/TypeLatticeElement.java
deleted file mode 100644
index de7b894..0000000
--- a/src/main/java/com/android/tools/r8/ir/analysis/type/TypeLatticeElement.java
+++ /dev/null
@@ -1,434 +0,0 @@
-// Copyright (c) 2017, 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 com.android.tools.r8.errors.Unreachable;
-import com.android.tools.r8.graph.AppInfoWithSubtyping;
-import com.android.tools.r8.graph.AppView;
-import com.android.tools.r8.graph.DexDefinitionSupplier;
-import com.android.tools.r8.graph.DexItemFactory;
-import com.android.tools.r8.graph.DexType;
-import com.android.tools.r8.ir.code.Value;
-import java.util.function.Function;
-
-/**
- * The base abstraction of lattice elements for local type analysis.
- */
-public abstract class TypeLatticeElement {
-
-  public static BottomTypeLatticeElement getBottom() {
-    return BottomTypeLatticeElement.getInstance();
-  }
-
-  public static TopTypeLatticeElement getTop() {
-    return TopTypeLatticeElement.getInstance();
-  }
-
-  public static BooleanTypeLatticeElement getBoolean() {
-    return BooleanTypeLatticeElement.getInstance();
-  }
-
-  public static ByteTypeLatticeElement getByte() {
-    return ByteTypeLatticeElement.getInstance();
-  }
-
-  public static ShortTypeLatticeElement getShort() {
-    return ShortTypeLatticeElement.getInstance();
-  }
-
-  public static CharTypeLatticeElement getChar() {
-    return CharTypeLatticeElement.getInstance();
-  }
-
-  public static IntTypeLatticeElement getInt() {
-    return IntTypeLatticeElement.getInstance();
-  }
-
-  public static FloatTypeLatticeElement getFloat() {
-    return FloatTypeLatticeElement.getInstance();
-  }
-
-  public static SinglePrimitiveTypeLatticeElement getSingle() {
-    return SinglePrimitiveTypeLatticeElement.getInstance();
-  }
-
-  public static LongTypeLatticeElement getLong() {
-    return LongTypeLatticeElement.getInstance();
-  }
-
-  public static DoubleTypeLatticeElement getDouble() {
-    return DoubleTypeLatticeElement.getInstance();
-  }
-
-  public static WidePrimitiveTypeLatticeElement getWide() {
-    return WidePrimitiveTypeLatticeElement.getInstance();
-  }
-
-  public static ReferenceTypeLatticeElement getNull() {
-    return ReferenceTypeLatticeElement.getNullTypeLatticeElement();
-  }
-
-  public TypeLatticeElement fixupClassTypeReferences(
-      Function<DexType, DexType> mapping, AppView<? extends AppInfoWithSubtyping> appView) {
-    return this;
-  }
-
-  public boolean isNullable() {
-    return nullability().isNullable();
-  }
-
-  public abstract Nullability nullability();
-
-  /**
-   * Computes the least upper bound of the current and the other elements.
-   *
-   * @param other {@link TypeLatticeElement} to join.
-   * @param appView {@link DexDefinitionSupplier}.
-   * @return {@link TypeLatticeElement}, a least upper bound of {@param this} and {@param other}.
-   */
-  public TypeLatticeElement join(TypeLatticeElement other, AppView<?> appView) {
-    if (this == other) {
-      return this;
-    }
-    if (isBottom()) {
-      return other;
-    }
-    if (other.isBottom()) {
-      return this;
-    }
-    if (isTop() || other.isTop()) {
-      return getTop();
-    }
-    if (isPrimitive()) {
-      return other.isPrimitive()
-          ? asPrimitiveTypeLatticeElement().join(other.asPrimitiveTypeLatticeElement())
-          : getTop();
-    }
-    if (other.isPrimitive()) {
-      // By the above case, !(isPrimitive())
-      return getTop();
-    }
-    // From now on, this and other are precise reference types, i.e., either ArrayType or ClassType.
-    assert isReference() && other.isReference();
-    assert isPreciseType() && other.isPreciseType();
-    Nullability nullabilityJoin = nullability().join(other.nullability());
-    if (isNullType()) {
-      return other.asReferenceTypeLatticeElement().getOrCreateVariant(nullabilityJoin);
-    }
-    if (other.isNullType()) {
-      return this.asReferenceTypeLatticeElement().getOrCreateVariant(nullabilityJoin);
-    }
-    if (getClass() != other.getClass()) {
-      return objectClassType(appView, nullabilityJoin);
-    }
-    // From now on, getClass() == other.getClass()
-    if (isArrayType()) {
-      assert other.isArrayType();
-      return asArrayTypeLatticeElement().join(other.asArrayTypeLatticeElement(), appView);
-    }
-    if (isClassType()) {
-      assert other.isClassType();
-      return asClassTypeLatticeElement().join(other.asClassTypeLatticeElement(), appView);
-    }
-    throw new Unreachable("unless a new type lattice is introduced.");
-  }
-
-  public static TypeLatticeElement join(
-      Iterable<TypeLatticeElement> typeLattices, AppView<?> appView) {
-    TypeLatticeElement result = getBottom();
-    for (TypeLatticeElement other : typeLattices) {
-      result = result.join(other, appView);
-    }
-    return result;
-  }
-
-  /**
-   * Determines the strict partial order of the given {@link TypeLatticeElement}s.
-   *
-   * @param other expected to be *strictly* bigger than {@param this}
-   * @param appView {@link DexDefinitionSupplier} to compute the least upper bound of {@link
-   *     TypeLatticeElement}
-   * @return {@code true} if {@param this} is strictly less than {@param other}.
-   */
-  public boolean strictlyLessThan(TypeLatticeElement other, AppView<?> appView) {
-    if (equals(other)) {
-      return false;
-    }
-    TypeLatticeElement lub = join(other, appView);
-    return !equals(lub) && other.equals(lub);
-  }
-
-  /**
-   * Determines the partial order of the given {@link TypeLatticeElement}s.
-   *
-   * @param other expected to be bigger than or equal to {@param this}
-   * @param appView {@link DexDefinitionSupplier} to compute the least upper bound of {@link
-   *     TypeLatticeElement}
-   * @return {@code true} if {@param this} is less than or equal to {@param other}.
-   */
-  public boolean lessThanOrEqual(TypeLatticeElement other, AppView<?> appView) {
-    return equals(other) || strictlyLessThan(other, appView);
-  }
-
-  /**
-   * Determines if this {@link TypeLatticeElement} is less than or equal to the given {@link
-   * TypeLatticeElement} up to nullability.
-   *
-   * @param other to check for equality with this
-   * @return {@code true} if {@param this} is equal up to nullability with {@param other}.
-   */
-  public boolean lessThanOrEqualUpToNullability(TypeLatticeElement other, AppView<?> appView) {
-    if (this == other) {
-      return true;
-    }
-    if (this.isTop()) {
-      return other.isTop();
-    }
-    if (other.isTop()) {
-      return true;
-    }
-    if (this.isBottom()) {
-      return true;
-    }
-    if (other.isBottom()) {
-      return false;
-    }
-    if (isPrimitive()) {
-      // Primitives cannot be nullable.
-      return lessThanOrEqual(other, appView);
-    }
-    assert isReference() && other.isReference();
-    ReferenceTypeLatticeElement otherAsNullable =
-        other.isNullable()
-            ? other.asReferenceTypeLatticeElement()
-            : other.asReferenceTypeLatticeElement().getOrCreateVariant(Nullability.maybeNull());
-    return lessThanOrEqual(otherAsNullable, appView);
-  }
-
-  /**
-   * Determines if the {@link TypeLatticeElement}s are equal up to nullability.
-   *
-   * @param other to check for equality with this
-   * @return {@code true} if {@param this} is equal up to nullability with {@param other}.
-   */
-  public boolean equalUpToNullability(TypeLatticeElement other) {
-    if (this == other) {
-      return true;
-    }
-    if (isPrimitive() || other.isPrimitive()) {
-      return false;
-    }
-    assert isReference() && other.isReference();
-    ReferenceTypeLatticeElement thisAsMaybeNull =
-        this.asReferenceTypeLatticeElement().getOrCreateVariant(Nullability.maybeNull());
-    ReferenceTypeLatticeElement otherAsMaybeNull =
-        other.asReferenceTypeLatticeElement().getOrCreateVariant(Nullability.maybeNull());
-    return thisAsMaybeNull.equals(otherAsMaybeNull);
-  }
-
-  /**
-   * Determines if this type is based on a missing class, directly or indirectly.
-   *
-   * @return {@code} true if this type is based on a missing class.
-   * @param appView
-   */
-  public boolean isBasedOnMissingClass(AppView<? extends AppInfoWithSubtyping> appView) {
-    return false;
-  }
-
-  /**
-   * Represents a type that can be everything.
-   *
-   * @return {@code true} if the corresponding {@link Value} could be any kinds.
-   */
-  public boolean isTop() {
-    return false;
-  }
-
-  /**
-   * Represents an empty type.
-   *
-   * @return {@code true} if the type of corresponding {@link Value} is not determined yet.
-   */
-  public boolean isBottom() {
-    return false;
-  }
-
-  public boolean isReference() {
-    return false;
-  }
-
-  public ReferenceTypeLatticeElement asReferenceTypeLatticeElement() {
-    return null;
-  }
-
-  public boolean isArrayType() {
-    return false;
-  }
-
-  public ArrayTypeLatticeElement asArrayTypeLatticeElement() {
-    return null;
-  }
-
-  public boolean isClassType() {
-    return false;
-  }
-
-  public ClassTypeLatticeElement asClassTypeLatticeElement() {
-    return null;
-  }
-
-  public boolean isPrimitive() {
-    return false;
-  }
-
-  public PrimitiveTypeLatticeElement asPrimitiveTypeLatticeElement() {
-    return null;
-  }
-
-  public boolean isSinglePrimitive() {
-    return false;
-  }
-
-  public boolean isWidePrimitive() {
-    return false;
-  }
-
-  boolean isBoolean() {
-    return false;
-  }
-
-  boolean isByte() {
-    return false;
-  }
-
-  boolean isShort() {
-    return false;
-  }
-
-  boolean isChar() {
-    return false;
-  }
-
-  public boolean isInt() {
-    return false;
-  }
-
-  public boolean isFloat() {
-    return false;
-  }
-
-  public boolean isLong() {
-    return false;
-  }
-
-  public boolean isDouble() {
-    return false;
-  }
-
-  public boolean isPreciseType() {
-    return isArrayType()
-        || isClassType()
-        || isNullType()
-        || isInt()
-        || isFloat()
-        || isLong()
-        || isDouble()
-        || isBottom();
-  }
-
-  public boolean isFineGrainedType() {
-    return isBoolean()
-        || isByte()
-        || isShort()
-        || isChar();
-  }
-
-  /**
-   * Determines if this type only includes null values that are defined by a const-number
-   * instruction in the same enclosing method.
-   *
-   * These null values can be assigned to any type.
-   */
-  public boolean isNullType() {
-    return false;
-  }
-
-  /**
-   * Determines if this type only includes null values.
-   *
-   * These null values cannot be assigned to any type. For example, it is a type error to "throw v"
-   * where the value `v` satisfies isDefinitelyNull(), because the static type of `v` may not be a
-   * subtype of Throwable.
-   */
-  public boolean isDefinitelyNull() {
-    return nullability().isDefinitelyNull();
-  }
-
-  public boolean isDefinitelyNotNull() {
-    return nullability().isDefinitelyNotNull();
-  }
-
-  public int requiredRegisters() {
-    assert !isBottom() && !isTop();
-    return 1;
-  }
-
-  public static ClassTypeLatticeElement objectClassType(
-      AppView<?> appView, Nullability nullability) {
-    return fromDexType(appView.dexItemFactory().objectType, nullability, appView)
-        .asClassTypeLatticeElement();
-  }
-
-  static ArrayTypeLatticeElement objectArrayType(AppView<?> appView, Nullability nullability) {
-    DexItemFactory dexItemFactory = appView.dexItemFactory();
-    return fromDexType(
-            dexItemFactory.createArrayType(1, dexItemFactory.objectType), nullability, appView)
-        .asArrayTypeLatticeElement();
-  }
-
-  public static ClassTypeLatticeElement classClassType(
-      AppView<?> appView, Nullability nullability) {
-    return fromDexType(appView.dexItemFactory().classType, nullability, appView)
-        .asClassTypeLatticeElement();
-  }
-
-  public static ClassTypeLatticeElement stringClassType(
-      AppView<?> appView, Nullability nullability) {
-    return fromDexType(appView.dexItemFactory().stringType, nullability, appView)
-        .asClassTypeLatticeElement();
-  }
-
-  public static TypeLatticeElement fromDexType(
-      DexType type, Nullability nullability, AppView<?> appView) {
-    return fromDexType(type, nullability, appView, false);
-  }
-
-  public static TypeLatticeElement fromDexType(
-      DexType type, Nullability nullability, AppView<?> appView, boolean asArrayElementType) {
-    if (type == DexItemFactory.nullValueType) {
-      assert !nullability.isDefinitelyNotNull();
-      return getNull();
-    }
-    if (type.isPrimitiveType()) {
-      return PrimitiveTypeLatticeElement.fromDexType(type, asArrayElementType);
-    }
-    return appView.dexItemFactory().createReferenceTypeLatticeElement(type, nullability, appView);
-  }
-
-  public boolean isValueTypeCompatible(TypeLatticeElement other) {
-    return (isReference() && other.isReference())
-        || (isSinglePrimitive() && other.isSinglePrimitive())
-        || (isWidePrimitive() && other.isWidePrimitive());
-  }
-
-  @Override
-  public abstract String toString();
-
-  @Override
-  public abstract boolean equals(Object o);
-
-  @Override
-  public abstract int hashCode();
-}
diff --git a/src/main/java/com/android/tools/r8/ir/analysis/type/TypeUtils.java b/src/main/java/com/android/tools/r8/ir/analysis/type/TypeUtils.java
index 912b379..02459a7 100644
--- a/src/main/java/com/android/tools/r8/ir/analysis/type/TypeUtils.java
+++ b/src/main/java/com/android/tools/r8/ir/analysis/type/TypeUtils.java
@@ -8,8 +8,8 @@
 
 public class TypeUtils {
 
-  public static boolean isNullPointerException(TypeLatticeElement type, AppView<?> appView) {
+  public static boolean isNullPointerException(TypeElement type, AppView<?> appView) {
     return type.isClassType()
-        && type.asClassTypeLatticeElement().getClassType() == appView.dexItemFactory().npeType;
+        && type.asClassType().getClassType() == appView.dexItemFactory().npeType;
   }
 }
diff --git a/src/main/java/com/android/tools/r8/ir/analysis/type/WidePrimitiveTypeLatticeElement.java b/src/main/java/com/android/tools/r8/ir/analysis/type/WidePrimitiveTypeElement.java
similarity index 64%
rename from src/main/java/com/android/tools/r8/ir/analysis/type/WidePrimitiveTypeLatticeElement.java
rename to src/main/java/com/android/tools/r8/ir/analysis/type/WidePrimitiveTypeElement.java
index 3e07e54..0ae7426 100644
--- a/src/main/java/com/android/tools/r8/ir/analysis/type/WidePrimitiveTypeLatticeElement.java
+++ b/src/main/java/com/android/tools/r8/ir/analysis/type/WidePrimitiveTypeElement.java
@@ -3,17 +3,16 @@
 // BSD-style license that can be found in the LICENSE file.
 package com.android.tools.r8.ir.analysis.type;
 
-/** A {@link TypeLatticeElement} that abstracts primitive types, which fit in 64 bits. */
-public class WidePrimitiveTypeLatticeElement extends PrimitiveTypeLatticeElement {
+/** A {@link TypeElement} that abstracts primitive types, which fit in 64 bits. */
+public class WidePrimitiveTypeElement extends PrimitiveTypeElement {
 
-  private static final WidePrimitiveTypeLatticeElement INSTANCE =
-      new WidePrimitiveTypeLatticeElement();
+  private static final WidePrimitiveTypeElement INSTANCE = new WidePrimitiveTypeElement();
 
-  WidePrimitiveTypeLatticeElement() {
+  WidePrimitiveTypeElement() {
     super();
   }
 
-  static WidePrimitiveTypeLatticeElement getInstance() {
+  static WidePrimitiveTypeElement getInstance() {
     return INSTANCE;
   }
 
diff --git a/src/main/java/com/android/tools/r8/ir/analysis/value/SingleConstClassValue.java b/src/main/java/com/android/tools/r8/ir/analysis/value/SingleConstClassValue.java
index 897b31e..34a83d1 100644
--- a/src/main/java/com/android/tools/r8/ir/analysis/value/SingleConstClassValue.java
+++ b/src/main/java/com/android/tools/r8/ir/analysis/value/SingleConstClassValue.java
@@ -5,7 +5,7 @@
 package com.android.tools.r8.ir.analysis.value;
 
 import static com.android.tools.r8.ir.analysis.type.Nullability.definitelyNotNull;
-import static com.android.tools.r8.ir.analysis.type.TypeLatticeElement.classClassType;
+import static com.android.tools.r8.ir.analysis.type.TypeElement.classClassType;
 import static com.android.tools.r8.optimize.MemberRebindingAnalysis.isClassTypeVisibleFromContext;
 
 import com.android.tools.r8.graph.AppInfoWithSubtyping;
@@ -14,7 +14,7 @@
 import com.android.tools.r8.graph.DexClass;
 import com.android.tools.r8.graph.DexType;
 import com.android.tools.r8.graph.GraphLense;
-import com.android.tools.r8.ir.analysis.type.TypeLatticeElement;
+import com.android.tools.r8.ir.analysis.type.TypeElement;
 import com.android.tools.r8.ir.code.ConstClass;
 import com.android.tools.r8.ir.code.IRCode;
 import com.android.tools.r8.ir.code.Instruction;
@@ -63,13 +63,11 @@
   @Override
   public Instruction createMaterializingInstruction(
       AppView<? extends AppInfoWithSubtyping> appView, IRCode code, TypeAndLocalInfoSupplier info) {
-    TypeLatticeElement typeLattice = info.getTypeLattice();
+    TypeElement typeLattice = info.getOutType();
     DebugLocalInfo debugLocalInfo = info.getLocalInfo();
     assert typeLattice.isClassType();
     assert appView
-        .isSubtype(
-            appView.dexItemFactory().classType,
-            typeLattice.asClassTypeLatticeElement().getClassType())
+        .isSubtype(appView.dexItemFactory().classType, typeLattice.asClassType().getClassType())
         .isTrue();
     Value returnedValue =
         code.createValue(classClassType(appView, definitelyNotNull()), debugLocalInfo);
diff --git a/src/main/java/com/android/tools/r8/ir/analysis/value/SingleFieldValue.java b/src/main/java/com/android/tools/r8/ir/analysis/value/SingleFieldValue.java
index 5479737..ef38879 100644
--- a/src/main/java/com/android/tools/r8/ir/analysis/value/SingleFieldValue.java
+++ b/src/main/java/com/android/tools/r8/ir/analysis/value/SingleFieldValue.java
@@ -14,8 +14,8 @@
 import com.android.tools.r8.graph.DexField;
 import com.android.tools.r8.graph.DexType;
 import com.android.tools.r8.graph.GraphLense;
-import com.android.tools.r8.ir.analysis.type.ClassTypeLatticeElement;
-import com.android.tools.r8.ir.analysis.type.TypeLatticeElement;
+import com.android.tools.r8.ir.analysis.type.ClassTypeElement;
+import com.android.tools.r8.ir.analysis.type.TypeElement;
 import com.android.tools.r8.ir.code.IRCode;
 import com.android.tools.r8.ir.code.Instruction;
 import com.android.tools.r8.ir.code.StaticGet;
@@ -39,9 +39,8 @@
   public boolean mayHaveFinalizeMethodDirectlyOrIndirectly(AppView<AppInfoWithLiveness> appView) {
     DexType fieldType = field.type;
     if (fieldType.isClassType()) {
-      ClassTypeLatticeElement fieldClassType =
-          TypeLatticeElement.fromDexType(fieldType, maybeNull(), appView)
-              .asClassTypeLatticeElement();
+      ClassTypeElement fieldClassType =
+          TypeElement.fromDexType(fieldType, maybeNull(), appView).asClassType();
       return appView.appInfo().mayHaveFinalizeMethodDirectlyOrIndirectly(fieldClassType);
     }
     assert fieldType.isArrayType() || fieldType.isPrimitiveType();
@@ -76,8 +75,8 @@
   @Override
   public Instruction createMaterializingInstruction(
       AppView<? extends AppInfoWithSubtyping> appView, IRCode code, TypeAndLocalInfoSupplier info) {
-    TypeLatticeElement type = TypeLatticeElement.fromDexType(field.type, maybeNull(), appView);
-    assert type.lessThanOrEqual(info.getTypeLattice(), appView);
+    TypeElement type = TypeElement.fromDexType(field.type, maybeNull(), appView);
+    assert type.lessThanOrEqual(info.getOutType(), appView);
     Value outValue = code.createValue(type, info.getLocalInfo());
     return new StaticGet(outValue, field);
   }
diff --git a/src/main/java/com/android/tools/r8/ir/analysis/value/SingleNumberValue.java b/src/main/java/com/android/tools/r8/ir/analysis/value/SingleNumberValue.java
index b408c90..e2f12fe 100644
--- a/src/main/java/com/android/tools/r8/ir/analysis/value/SingleNumberValue.java
+++ b/src/main/java/com/android/tools/r8/ir/analysis/value/SingleNumberValue.java
@@ -9,7 +9,7 @@
 import com.android.tools.r8.graph.DebugLocalInfo;
 import com.android.tools.r8.graph.DexType;
 import com.android.tools.r8.graph.GraphLense;
-import com.android.tools.r8.ir.analysis.type.TypeLatticeElement;
+import com.android.tools.r8.ir.analysis.type.TypeElement;
 import com.android.tools.r8.ir.code.ConstNumber;
 import com.android.tools.r8.ir.code.IRCode;
 import com.android.tools.r8.ir.code.Instruction;
@@ -68,12 +68,12 @@
   @Override
   public Instruction createMaterializingInstruction(
       AppView<? extends AppInfoWithSubtyping> appView, IRCode code, TypeAndLocalInfoSupplier info) {
-    TypeLatticeElement typeLattice = info.getTypeLattice();
+    TypeElement typeLattice = info.getOutType();
     DebugLocalInfo debugLocalInfo = info.getLocalInfo();
-    assert !typeLattice.isReference() || value == 0;
+    assert !typeLattice.isReferenceType() || value == 0;
     Value returnedValue =
         code.createValue(
-            typeLattice.isReference() ? TypeLatticeElement.getNull() : typeLattice, debugLocalInfo);
+            typeLattice.isReferenceType() ? TypeElement.getNull() : typeLattice, debugLocalInfo);
     return new ConstNumber(returnedValue, value);
   }
 
diff --git a/src/main/java/com/android/tools/r8/ir/analysis/value/SingleStringValue.java b/src/main/java/com/android/tools/r8/ir/analysis/value/SingleStringValue.java
index a7439c0..1280264 100644
--- a/src/main/java/com/android/tools/r8/ir/analysis/value/SingleStringValue.java
+++ b/src/main/java/com/android/tools/r8/ir/analysis/value/SingleStringValue.java
@@ -5,7 +5,7 @@
 package com.android.tools.r8.ir.analysis.value;
 
 import static com.android.tools.r8.ir.analysis.type.Nullability.definitelyNotNull;
-import static com.android.tools.r8.ir.analysis.type.TypeLatticeElement.stringClassType;
+import static com.android.tools.r8.ir.analysis.type.TypeElement.stringClassType;
 
 import com.android.tools.r8.graph.AppInfoWithSubtyping;
 import com.android.tools.r8.graph.AppView;
@@ -13,7 +13,7 @@
 import com.android.tools.r8.graph.DexString;
 import com.android.tools.r8.graph.DexType;
 import com.android.tools.r8.graph.GraphLense;
-import com.android.tools.r8.ir.analysis.type.TypeLatticeElement;
+import com.android.tools.r8.ir.analysis.type.TypeElement;
 import com.android.tools.r8.ir.code.BasicBlock.ThrowingInfo;
 import com.android.tools.r8.ir.code.ConstString;
 import com.android.tools.r8.ir.code.IRCode;
@@ -65,13 +65,11 @@
       AppView<? extends AppInfoWithSubtyping> appView,
       IRCode code,
       TypeAndLocalInfoSupplier info) {
-    TypeLatticeElement typeLattice = info.getTypeLattice();
+    TypeElement typeLattice = info.getOutType();
     DebugLocalInfo debugLocalInfo = info.getLocalInfo();
     assert typeLattice.isClassType();
     assert appView
-        .isSubtype(
-            appView.dexItemFactory().stringType,
-            typeLattice.asClassTypeLatticeElement().getClassType())
+        .isSubtype(appView.dexItemFactory().stringType, typeLattice.asClassType().getClassType())
         .isTrue();
     Value returnedValue =
         code.createValue(stringClassType(appView, definitelyNotNull()), debugLocalInfo);
diff --git a/src/main/java/com/android/tools/r8/ir/code/Argument.java b/src/main/java/com/android/tools/r8/ir/code/Argument.java
index 1db2ba6..2ace686 100644
--- a/src/main/java/com/android/tools/r8/ir/code/Argument.java
+++ b/src/main/java/com/android/tools/r8/ir/code/Argument.java
@@ -9,7 +9,7 @@
 import com.android.tools.r8.errors.Unreachable;
 import com.android.tools.r8.graph.AppView;
 import com.android.tools.r8.graph.DexType;
-import com.android.tools.r8.ir.analysis.type.TypeLatticeElement;
+import com.android.tools.r8.ir.analysis.type.TypeElement;
 import com.android.tools.r8.ir.conversion.CfBuilder;
 import com.android.tools.r8.ir.conversion.DexBuilder;
 import com.android.tools.r8.ir.optimize.Inliner.ConstraintWithTarget;
@@ -121,8 +121,8 @@
   }
 
   @Override
-  public TypeLatticeElement evaluate(AppView<?> appView) {
-    return outValue.getTypeLattice();
+  public TypeElement evaluate(AppView<?> appView) {
+    return outValue.getType();
   }
 
   @Override
diff --git a/src/main/java/com/android/tools/r8/ir/code/ArithmeticBinop.java b/src/main/java/com/android/tools/r8/ir/code/ArithmeticBinop.java
index 5d97e99..bf545a0 100644
--- a/src/main/java/com/android/tools/r8/ir/code/ArithmeticBinop.java
+++ b/src/main/java/com/android/tools/r8/ir/code/ArithmeticBinop.java
@@ -9,7 +9,7 @@
 import com.android.tools.r8.ir.analysis.constant.Bottom;
 import com.android.tools.r8.ir.analysis.constant.ConstLatticeElement;
 import com.android.tools.r8.ir.analysis.constant.LatticeElement;
-import com.android.tools.r8.ir.analysis.type.TypeLatticeElement;
+import com.android.tools.r8.ir.analysis.type.TypeElement;
 import com.android.tools.r8.ir.conversion.CfBuilder;
 import com.android.tools.r8.ir.conversion.DexBuilder;
 import java.util.function.Function;
@@ -140,20 +140,20 @@
       ConstNumber newConst;
       if (type == NumericType.INT) {
         int result = foldIntegers(leftConst.getIntValue(), rightConst.getIntValue());
-        Value value = code.createValue(TypeLatticeElement.getInt(), getLocalInfo());
+        Value value = code.createValue(TypeElement.getInt(), getLocalInfo());
         newConst = new ConstNumber(value, result);
       } else if (type == NumericType.LONG) {
         long result = foldLongs(leftConst.getLongValue(), rightConst.getLongValue());
-        Value value = code.createValue(TypeLatticeElement.getLong(), getLocalInfo());
+        Value value = code.createValue(TypeElement.getLong(), getLocalInfo());
         newConst = new ConstNumber(value, result);
       } else if (type == NumericType.FLOAT) {
         float result = foldFloat(leftConst.getFloatValue(), rightConst.getFloatValue());
-        Value value = code.createValue(TypeLatticeElement.getFloat(), getLocalInfo());
+        Value value = code.createValue(TypeElement.getFloat(), getLocalInfo());
         newConst = new ConstNumber(value, Float.floatToIntBits(result));
       } else {
         assert type == NumericType.DOUBLE;
         double result = foldDouble(leftConst.getDoubleValue(), rightConst.getDoubleValue());
-        Value value = code.createValue(TypeLatticeElement.getDouble(), getLocalInfo());
+        Value value = code.createValue(TypeElement.getDouble(), getLocalInfo());
         newConst = new ConstNumber(value, Double.doubleToLongBits(result));
       }
       return new ConstLatticeElement(newConst);
diff --git a/src/main/java/com/android/tools/r8/ir/code/ArrayAccess.java b/src/main/java/com/android/tools/r8/ir/code/ArrayAccess.java
new file mode 100644
index 0000000..14ac116
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/ir/code/ArrayAccess.java
@@ -0,0 +1,38 @@
+// 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.ir.code;
+
+import java.util.List;
+
+public abstract class ArrayAccess extends Instruction implements ImpreciseMemberTypeInstruction {
+
+  // Input values are ordered according to the stack order of the Java bytecodes.
+  private static final int ARRAY_INDEX = 0;
+  private static final int INDEX_INDEX = 1;
+
+  ArrayAccess(Value outValue, List<? extends Value> inValues) {
+    super(outValue, inValues);
+  }
+
+  public Value array() {
+    return inValues.get(ARRAY_INDEX);
+  }
+
+  public Value index() {
+    return inValues.get(INDEX_INDEX);
+  }
+
+  @Override
+  public boolean isArrayAccess() {
+    return true;
+  }
+
+  @Override
+  public ArrayAccess asArrayAccess() {
+    return this;
+  }
+
+  public abstract ArrayAccess withMemberType(MemberType newMemberType);
+}
diff --git a/src/main/java/com/android/tools/r8/ir/code/ArrayGet.java b/src/main/java/com/android/tools/r8/ir/code/ArrayGet.java
index 17dcc5e..432cf11 100644
--- a/src/main/java/com/android/tools/r8/ir/code/ArrayGet.java
+++ b/src/main/java/com/android/tools/r8/ir/code/ArrayGet.java
@@ -19,8 +19,8 @@
 import com.android.tools.r8.graph.AppView;
 import com.android.tools.r8.graph.DexItemFactory;
 import com.android.tools.r8.graph.DexType;
-import com.android.tools.r8.ir.analysis.type.ArrayTypeLatticeElement;
-import com.android.tools.r8.ir.analysis.type.TypeLatticeElement;
+import com.android.tools.r8.ir.analysis.type.ArrayTypeElement;
+import com.android.tools.r8.ir.analysis.type.TypeElement;
 import com.android.tools.r8.ir.conversion.CfBuilder;
 import com.android.tools.r8.ir.conversion.DexBuilder;
 import com.android.tools.r8.ir.conversion.TypeConstraintResolver;
@@ -30,7 +30,7 @@
 import java.util.Arrays;
 import java.util.Set;
 
-public class ArrayGet extends Instruction implements ImpreciseMemberTypeInstruction {
+public class ArrayGet extends ArrayAccess {
 
   private MemberType type;
 
@@ -53,14 +53,6 @@
     return outValue;
   }
 
-  public Value array() {
-    return inValues.get(0);
-  }
-
-  public Value index() {
-    return inValues.get(1);
-  }
-
   @Override
   public MemberType getMemberType() {
     return type;
@@ -68,9 +60,9 @@
 
   @Override
   public boolean couldIntroduceAnAlias(AppView<?> appView, Value root) {
-    assert root != null && root.getTypeLattice().isReference();
+    assert root != null && root.getType().isReferenceType();
     assert outValue != null;
-    return outValue.getTypeLattice().isReference();
+    return outValue.getType().isReferenceType();
   }
 
   @Override
@@ -94,13 +86,12 @@
         instruction = new AgetObject(dest, array, index);
         break;
       case BOOLEAN_OR_BYTE:
-        ArrayTypeLatticeElement arrayType = array().getTypeLattice().asArrayTypeLatticeElement();
-        if (arrayType != null
-            && arrayType.getArrayMemberTypeAsMemberType() == TypeLatticeElement.getBoolean()) {
+        ArrayTypeElement arrayType = array().getType().asArrayType();
+        if (arrayType != null && arrayType.getMemberType() == TypeElement.getBoolean()) {
           instruction = new AgetBoolean(dest, array, index);
         } else {
-          assert array().getTypeLattice().isDefinitelyNull()
-              || arrayType.getArrayMemberTypeAsMemberType() == TypeLatticeElement.getByte();
+          assert array().getType().isDefinitelyNull()
+              || arrayType.getMemberType() == TypeElement.getByte();
           instruction = new AgetByte(dest, array, index);
         }
         break;
@@ -172,7 +163,7 @@
   @Override
   public DexType computeVerificationType(AppView<?> appView, TypeVerificationHelper helper) {
     // This method is not called for ArrayGet on primitive array.
-    assert this.outValue.getTypeLattice().isReference();
+    assert this.outValue.getType().isReferenceType();
     DexType arrayType = helper.getDexType(array());
     if (arrayType == DexItemFactory.nullValueType) {
       // JVM 8 §4.10.1.9.aaload: Array component type of null is null.
@@ -193,56 +184,51 @@
   }
 
   @Override
-  public TypeLatticeElement evaluate(AppView<?> appView) {
-    ArrayTypeLatticeElement arrayTypeLattice = array().getTypeLattice().isArrayType()
-        ? array().getTypeLattice().asArrayTypeLatticeElement()
-        : null;
+  public TypeElement evaluate(AppView<?> appView) {
+    ArrayTypeElement arrayTypeLattice =
+        array().getType().isArrayType() ? array().getType().asArrayType() : null;
     switch (getMemberType()) {
       case OBJECT:
         // If the out-type of the array is bottom (the input array must be definitely null), then
         // the instruction cannot return. For now we return NULL as the type to ensure we have a
         // type consistent witness for the out-value type. We could consider returning bottom in
         // this case as the value is indeed empty, i.e., the instruction will always fail.
-        TypeLatticeElement valueType =
+        TypeElement valueType =
             arrayTypeLattice == null
-                ? TypeLatticeElement.getNull()
-                : arrayTypeLattice.getArrayMemberTypeAsValueType();
-        assert valueType.isReference();
+                ? TypeElement.getNull()
+                : arrayTypeLattice.getMemberTypeAsValueType();
+        assert valueType.isReferenceType();
         return valueType;
       case BOOLEAN_OR_BYTE:
       case CHAR:
       case SHORT:
       case INT:
-        assert arrayTypeLattice == null
-            || arrayTypeLattice.getArrayMemberTypeAsValueType().isInt();
-        return TypeLatticeElement.getInt();
+        assert arrayTypeLattice == null || arrayTypeLattice.getMemberTypeAsValueType().isInt();
+        return TypeElement.getInt();
       case FLOAT:
-        assert arrayTypeLattice == null
-            || arrayTypeLattice.getArrayMemberTypeAsValueType().isFloat();
-        return TypeLatticeElement.getFloat();
+        assert arrayTypeLattice == null || arrayTypeLattice.getMemberTypeAsValueType().isFloat();
+        return TypeElement.getFloat();
       case LONG:
-        assert arrayTypeLattice == null
-            || arrayTypeLattice.getArrayMemberTypeAsValueType().isLong();
-        return TypeLatticeElement.getLong();
+        assert arrayTypeLattice == null || arrayTypeLattice.getMemberTypeAsValueType().isLong();
+        return TypeElement.getLong();
       case DOUBLE:
-        assert arrayTypeLattice == null
-            || arrayTypeLattice.getArrayMemberTypeAsValueType().isDouble();
-        return TypeLatticeElement.getDouble();
+        assert arrayTypeLattice == null || arrayTypeLattice.getMemberTypeAsValueType().isDouble();
+        return TypeElement.getDouble();
       case INT_OR_FLOAT:
         assert arrayTypeLattice == null
-            || arrayTypeLattice.getArrayMemberTypeAsValueType().isSinglePrimitive();
+            || arrayTypeLattice.getMemberTypeAsValueType().isSinglePrimitive();
         return checkConstraint(dest(), ValueTypeConstraint.INT_OR_FLOAT);
       case LONG_OR_DOUBLE:
         assert arrayTypeLattice == null
-            || arrayTypeLattice.getArrayMemberTypeAsValueType().isWidePrimitive();
+            || arrayTypeLattice.getMemberTypeAsValueType().isWidePrimitive();
         return checkConstraint(dest(), ValueTypeConstraint.LONG_OR_DOUBLE);
       default:
         throw new Unreachable("Unexpected member type: " + getMemberType());
     }
   }
 
-  private static TypeLatticeElement checkConstraint(Value value, ValueTypeConstraint constraint) {
-    TypeLatticeElement latticeElement = value.constrainedType(constraint);
+  private static TypeElement checkConstraint(Value value, ValueTypeConstraint constraint) {
+    TypeElement latticeElement = value.constrainedType(constraint);
     if (latticeElement != null) {
       return latticeElement;
     }
@@ -267,9 +253,8 @@
 
   @Override
   public boolean outTypeKnownToBeBoolean(Set<Phi> seen) {
-    return array().getTypeLattice().isArrayType()
-        && array().getTypeLattice().asArrayTypeLatticeElement().getArrayMemberTypeAsMemberType()
-            == TypeLatticeElement.getBoolean();
+    return array().getType().isArrayType()
+        && array().getType().asArrayType().getMemberType() == TypeElement.getBoolean();
   }
 
   @Override
@@ -281,4 +266,9 @@
   public boolean instructionMayTriggerMethodInvocation(AppView<?> appView, DexType context) {
     return false;
   }
+
+  @Override
+  public ArrayAccess withMemberType(MemberType newMemberType) {
+    return new ArrayGet(newMemberType, outValue(), array(), index());
+  }
 }
diff --git a/src/main/java/com/android/tools/r8/ir/code/ArrayLength.java b/src/main/java/com/android/tools/r8/ir/code/ArrayLength.java
index da92bf3..dc0b133 100644
--- a/src/main/java/com/android/tools/r8/ir/code/ArrayLength.java
+++ b/src/main/java/com/android/tools/r8/ir/code/ArrayLength.java
@@ -10,7 +10,7 @@
 import com.android.tools.r8.graph.AppView;
 import com.android.tools.r8.graph.DexType;
 import com.android.tools.r8.ir.analysis.AbstractError;
-import com.android.tools.r8.ir.analysis.type.TypeLatticeElement;
+import com.android.tools.r8.ir.analysis.type.TypeElement;
 import com.android.tools.r8.ir.conversion.CfBuilder;
 import com.android.tools.r8.ir.conversion.DexBuilder;
 import com.android.tools.r8.ir.optimize.Inliner.ConstraintWithTarget;
@@ -75,7 +75,7 @@
 
   @Override
   public AbstractError instructionInstanceCanThrow(AppView<?> appView, DexType context) {
-    if (array().typeLattice.isNullable()) {
+    if (array().type.isNullable()) {
       return AbstractError.specific(appView.dexItemFactory().npeType);
     }
 
@@ -130,8 +130,8 @@
   }
 
   @Override
-  public TypeLatticeElement evaluate(AppView<?> appView) {
-    return TypeLatticeElement.getInt();
+  public TypeElement evaluate(AppView<?> appView) {
+    return TypeElement.getInt();
   }
 
   @Override
diff --git a/src/main/java/com/android/tools/r8/ir/code/ArrayPut.java b/src/main/java/com/android/tools/r8/ir/code/ArrayPut.java
index 589710f..f4a8a14 100644
--- a/src/main/java/com/android/tools/r8/ir/code/ArrayPut.java
+++ b/src/main/java/com/android/tools/r8/ir/code/ArrayPut.java
@@ -16,8 +16,8 @@
 import com.android.tools.r8.errors.Unreachable;
 import com.android.tools.r8.graph.AppView;
 import com.android.tools.r8.graph.DexType;
-import com.android.tools.r8.ir.analysis.type.ArrayTypeLatticeElement;
-import com.android.tools.r8.ir.analysis.type.TypeLatticeElement;
+import com.android.tools.r8.ir.analysis.type.ArrayTypeElement;
+import com.android.tools.r8.ir.analysis.type.TypeElement;
 import com.android.tools.r8.ir.conversion.CfBuilder;
 import com.android.tools.r8.ir.conversion.DexBuilder;
 import com.android.tools.r8.ir.conversion.TypeConstraintResolver;
@@ -26,11 +26,9 @@
 import com.android.tools.r8.ir.regalloc.RegisterAllocator;
 import java.util.Arrays;
 
-public class ArrayPut extends Instruction implements ImpreciseMemberTypeInstruction {
+public class ArrayPut extends ArrayAccess {
 
   // Input values are ordered according to the stack order of the Java bytecode astore.
-  private static final int ARRAY_INDEX = 0;
-  private static final int INDEX_INDEX = 1;
   private static final int VALUE_INDEX = 2;
 
   private MemberType type;
@@ -53,14 +51,6 @@
     return visitor.visit(this);
   }
 
-  public Value array() {
-    return inValues.get(ARRAY_INDEX);
-  }
-
-  public Value index() {
-    return inValues.get(INDEX_INDEX);
-  }
-
   public Value value() {
     return inValues.get(VALUE_INDEX);
   }
@@ -89,13 +79,12 @@
         instruction = new AputObject(value, array, index);
         break;
       case BOOLEAN_OR_BYTE:
-        ArrayTypeLatticeElement arrayType = array().getTypeLattice().asArrayTypeLatticeElement();
-        if (arrayType != null
-            && arrayType.getArrayMemberTypeAsMemberType() == TypeLatticeElement.getBoolean()) {
+        ArrayTypeElement arrayType = array().getType().asArrayType();
+        if (arrayType != null && arrayType.getMemberType() == TypeElement.getBoolean()) {
           instruction = new AputBoolean(value, array, index);
         } else {
-          assert array().getTypeLattice().isDefinitelyNull()
-              || arrayType.getArrayMemberTypeAsMemberType() == TypeLatticeElement.getByte();
+          assert array().getType().isDefinitelyNull()
+              || arrayType.getMemberType() == TypeElement.getByte();
           instruction = new AputByte(value, array, index);
         }
         break;
@@ -176,13 +165,12 @@
     }
 
     // Check for type errors.
-    TypeLatticeElement arrayType = array.getTypeLattice();
-    TypeLatticeElement valueType = value().getTypeLattice();
+    TypeElement arrayType = array.getType();
+    TypeElement valueType = value().getType();
     if (!arrayType.isArrayType()) {
       return true;
     }
-    TypeLatticeElement memberType =
-        arrayType.asArrayTypeLatticeElement().getArrayMemberTypeAsValueType();
+    TypeElement memberType = arrayType.asArrayType().getMemberTypeAsValueType();
     if (!valueType.lessThanOrEqualUpToNullability(memberType, appView)) {
       return true;
     }
@@ -275,4 +263,9 @@
   public boolean instructionMayTriggerMethodInvocation(AppView<?> appView, DexType context) {
     return false;
   }
+
+  @Override
+  public ArrayAccess withMemberType(MemberType newMemberType) {
+    return new ArrayPut(newMemberType, array(), index(), value());
+  }
 }
diff --git a/src/main/java/com/android/tools/r8/ir/code/Assume.java b/src/main/java/com/android/tools/r8/ir/code/Assume.java
index 37de422..6611bc7 100644
--- a/src/main/java/com/android/tools/r8/ir/code/Assume.java
+++ b/src/main/java/com/android/tools/r8/ir/code/Assume.java
@@ -9,8 +9,8 @@
 import com.android.tools.r8.errors.Unreachable;
 import com.android.tools.r8.graph.AppView;
 import com.android.tools.r8.graph.DexType;
-import com.android.tools.r8.ir.analysis.type.ClassTypeLatticeElement;
-import com.android.tools.r8.ir.analysis.type.TypeLatticeElement;
+import com.android.tools.r8.ir.analysis.type.ClassTypeElement;
+import com.android.tools.r8.ir.analysis.type.TypeElement;
 import com.android.tools.r8.ir.code.Assume.Assumption;
 import com.android.tools.r8.ir.conversion.CfBuilder;
 import com.android.tools.r8.ir.conversion.DexBuilder;
@@ -47,8 +47,8 @@
   }
 
   public static Assume<DynamicTypeAssumption> createAssumeDynamicTypeInstruction(
-      TypeLatticeElement dynamicUpperBoundType,
-      ClassTypeLatticeElement dynamicLowerBoundType,
+      TypeElement dynamicUpperBoundType,
+      ClassTypeElement dynamicLowerBoundType,
       Value dest,
       Value src,
       Instruction origin,
@@ -164,10 +164,10 @@
 
   @Override
   public boolean couldIntroduceAnAlias(AppView<?> appView, Value root) {
-    assert root != null && root.getTypeLattice().isReference();
+    assert root != null && root.getType().isReferenceType();
     assert outValue != null;
-    TypeLatticeElement outType = outValue.getTypeLattice();
-    if (outType.isPrimitive()) {
+    TypeElement outType = outValue.getType();
+    if (outType.isPrimitiveType()) {
       return false;
     }
     if (assumption.isAssumeNone()) {
@@ -179,14 +179,17 @@
     }
     if (appView.appInfo().hasSubtyping()) {
       if (outType.isClassType()
-          && root.getTypeLattice().isClassType()
-          && appView.appInfo().withSubtyping().inDifferentHierarchy(
-              outType.asClassTypeLatticeElement().getClassType(),
-              root.getTypeLattice().asClassTypeLatticeElement().getClassType())) {
+          && root.getType().isClassType()
+          && appView
+              .appInfo()
+              .withSubtyping()
+              .inDifferentHierarchy(
+                  outType.asClassType().getClassType(),
+                  root.getType().asClassType().getClassType())) {
         return false;
       }
     }
-    return outType.isReference();
+    return outType.isReferenceType();
   }
 
   @Override
@@ -240,13 +243,13 @@
   }
 
   @Override
-  public TypeLatticeElement evaluate(AppView<?> appView) {
+  public TypeElement evaluate(AppView<?> appView) {
     if (assumption.isAssumeNone() || assumption.isAssumeDynamicType()) {
-      return src().getTypeLattice();
+      return src().getType();
     }
     if (assumption.isAssumeNonNull()) {
-      assert src().getTypeLattice().isReference();
-      return src().getTypeLattice().asReferenceTypeLatticeElement().asMeetWithNotNull();
+      assert src().getType().isReferenceType();
+      return src().getType().asReferenceType().asMeetWithNotNull();
     }
     throw new Unimplemented();
   }
@@ -275,18 +278,17 @@
   public boolean verifyTypes(AppView<?> appView) {
     assert super.verifyTypes(appView);
 
-    TypeLatticeElement inType = src().getTypeLattice();
-    TypeLatticeElement outType = outValue().getTypeLattice();
+    TypeElement inType = src().getType();
+    TypeElement outType = outValue().getType();
     if (isAssumeNone() || isAssumeDynamicType()) {
-      assert inType.isReference() : inType;
+      assert inType.isReferenceType() : inType;
       assert outType.equals(inType)
           : "At " + this + System.lineSeparator() + outType + " != " + inType;
     } else {
       assert isAssumeNonNull() : this;
-      assert inType.isReference() : inType;
-      assert inType.isNullType()
-          || outType.equals(inType.asReferenceTypeLatticeElement().asMeetWithNotNull())
-              : "At " + this + System.lineSeparator() + outType + " != " + inType;
+      assert inType.isReferenceType() : inType;
+      assert inType.isNullType() || outType.equals(inType.asReferenceType().asMeetWithNotNull())
+          : "At " + this + System.lineSeparator() + outType + " != " + inType;
     }
     return true;
   }
@@ -351,27 +353,27 @@
 
     @Override
     public boolean verifyCorrectnessOfValues(Value dest, Value src, AppView<?> appView) {
-      assert dest.getTypeLattice() == src.getTypeLattice();
+      assert dest.getType() == src.getType();
       return true;
     }
   }
 
   public static class DynamicTypeAssumption extends Assumption {
 
-    private final TypeLatticeElement dynamicUpperBoundType;
-    private final ClassTypeLatticeElement dynamicLowerBoundType;
+    private final TypeElement dynamicUpperBoundType;
+    private final ClassTypeElement dynamicLowerBoundType;
 
     private DynamicTypeAssumption(
-        TypeLatticeElement dynamicUpperBoundType, ClassTypeLatticeElement dynamicLowerBoundType) {
+        TypeElement dynamicUpperBoundType, ClassTypeElement dynamicLowerBoundType) {
       this.dynamicUpperBoundType = dynamicUpperBoundType;
       this.dynamicLowerBoundType = dynamicLowerBoundType;
     }
 
-    public TypeLatticeElement getDynamicUpperBoundType() {
+    public TypeElement getDynamicUpperBoundType() {
       return dynamicUpperBoundType;
     }
 
-    public ClassTypeLatticeElement getDynamicLowerBoundType() {
+    public ClassTypeElement getDynamicLowerBoundType() {
       return dynamicLowerBoundType;
     }
 
@@ -382,7 +384,7 @@
 
     @Override
     public boolean verifyCorrectnessOfValues(Value dest, Value src, AppView<?> appView) {
-      assert dynamicUpperBoundType.lessThanOrEqualUpToNullability(src.getTypeLattice(), appView);
+      assert dynamicUpperBoundType.lessThanOrEqualUpToNullability(src.getType(), appView);
       return true;
     }
 
diff --git a/src/main/java/com/android/tools/r8/ir/code/BasicBlock.java b/src/main/java/com/android/tools/r8/ir/code/BasicBlock.java
index 584ea2c..37d9b9e 100644
--- a/src/main/java/com/android/tools/r8/ir/code/BasicBlock.java
+++ b/src/main/java/com/android/tools/r8/ir/code/BasicBlock.java
@@ -13,7 +13,7 @@
 import com.android.tools.r8.graph.DexType;
 import com.android.tools.r8.graph.GraphLense;
 import com.android.tools.r8.ir.analysis.type.Nullability;
-import com.android.tools.r8.ir.analysis.type.TypeLatticeElement;
+import com.android.tools.r8.ir.analysis.type.TypeElement;
 import com.android.tools.r8.ir.code.Phi.RegisterReadType;
 import com.android.tools.r8.ir.conversion.DexBuilder;
 import com.android.tools.r8.ir.conversion.IRBuilder;
@@ -1445,8 +1445,8 @@
 
   public static BasicBlock createRethrowBlock(
       IRCode code, Position position, DexType guard, AppView<?> appView) {
-    TypeLatticeElement guardTypeLattice =
-        TypeLatticeElement.fromDexType(guard, Nullability.definitelyNotNull(), appView);
+    TypeElement guardTypeLattice =
+        TypeElement.fromDexType(guard, Nullability.definitelyNotNull(), appView);
     BasicBlock block = new BasicBlock();
     MoveException moveException =
         new MoveException(
@@ -1743,14 +1743,14 @@
     int nextBlockNumber = code.getHighestBlockNumber() + 1;
     List<BasicBlock> predecessors = getMutablePredecessors();
     boolean hasMoveException = entry().isMoveException();
-    TypeLatticeElement exceptionTypeLattice = null;
+    TypeElement exceptionTypeLattice = null;
     DexType exceptionType = null;
     MoveException move = null;
     Position position = entry().getPosition();
     if (hasMoveException) {
       // Remove the move-exception instruction.
       move = entry().asMoveException();
-      exceptionTypeLattice = move.outValue().getTypeLattice();
+      exceptionTypeLattice = move.outValue().getType();
       exceptionType = move.getExceptionType();
       assert move.getDebugValues().isEmpty();
       getInstructions().remove(0);
diff --git a/src/main/java/com/android/tools/r8/ir/code/BasicBlockInstructionListIterator.java b/src/main/java/com/android/tools/r8/ir/code/BasicBlockInstructionListIterator.java
index 98f4c97..4e5c923 100644
--- a/src/main/java/com/android/tools/r8/ir/code/BasicBlockInstructionListIterator.java
+++ b/src/main/java/com/android/tools/r8/ir/code/BasicBlockInstructionListIterator.java
@@ -12,7 +12,7 @@
 import com.android.tools.r8.graph.DexField;
 import com.android.tools.r8.graph.DexType;
 import com.android.tools.r8.ir.analysis.type.TypeAnalysis;
-import com.android.tools.r8.ir.analysis.type.TypeLatticeElement;
+import com.android.tools.r8.ir.analysis.type.TypeElement;
 import com.android.tools.r8.ir.code.Phi.RegisterReadType;
 import com.android.tools.r8.ir.optimize.NestUtils;
 import com.android.tools.r8.utils.InternalOptions;
@@ -228,7 +228,7 @@
       throw new IllegalStateException();
     }
 
-    assert !current.hasOutValue() || current.outValue().getTypeLattice().isInt();
+    assert !current.hasOutValue() || current.outValue().getType().isInt();
 
     // Replace the instruction by const-number.
     ConstNumber constNumber = code.createIntConstant(value, current.getLocalInfo());
@@ -249,8 +249,8 @@
     }
 
     // Replace the instruction by static-get.
-    TypeLatticeElement newType = TypeLatticeElement.fromDexType(field.type, maybeNull(), appView);
-    TypeLatticeElement oldType = current.hasOutValue() ? current.outValue().getTypeLattice() : null;
+    TypeElement newType = TypeElement.fromDexType(field.type, maybeNull(), appView);
+    TypeElement oldType = current.hasOutValue() ? current.outValue().getType() : null;
     Value value = code.createValue(newType, current.getLocalInfo());
     StaticGet staticGet = new StaticGet(value, field);
     for (Value inValue : current.inValues()) {
@@ -534,9 +534,8 @@
       // TODO(b/120257211): Even if the receiver is used, we can avoid inserting a check-cast
       //  instruction if the program still type checks without the cast.
       Value receiver = invoke.inValues().get(0);
-      TypeLatticeElement castTypeLattice =
-          TypeLatticeElement.fromDexType(
-              downcast, receiver.getTypeLattice().nullability(), appView);
+      TypeElement castTypeLattice =
+          TypeElement.fromDexType(downcast, receiver.getType().nullability(), appView);
       CheckCast castInstruction =
           new CheckCast(code.createValue(castTypeLattice), receiver, downcast);
       castInstruction.setPosition(invoke.getPosition());
@@ -715,7 +714,7 @@
             new Phi(
                 code.valueNumberGenerator.next(),
                 newExitBlock,
-                TypeLatticeElement.getBottom(),
+                TypeElement.getBottom(),
                 null,
                 RegisterReadType.NORMAL);
         phi.addOperands(operands);
diff --git a/src/main/java/com/android/tools/r8/ir/code/Binop.java b/src/main/java/com/android/tools/r8/ir/code/Binop.java
index 70187db..f0d4eff 100644
--- a/src/main/java/com/android/tools/r8/ir/code/Binop.java
+++ b/src/main/java/com/android/tools/r8/ir/code/Binop.java
@@ -10,8 +10,8 @@
 import com.android.tools.r8.errors.Unreachable;
 import com.android.tools.r8.graph.AppView;
 import com.android.tools.r8.graph.DexType;
-import com.android.tools.r8.ir.analysis.type.PrimitiveTypeLatticeElement;
-import com.android.tools.r8.ir.analysis.type.TypeLatticeElement;
+import com.android.tools.r8.ir.analysis.type.PrimitiveTypeElement;
+import com.android.tools.r8.ir.analysis.type.TypeElement;
 import com.android.tools.r8.ir.optimize.Inliner.ConstraintWithTarget;
 import com.android.tools.r8.ir.optimize.InliningConstraints;
 import com.android.tools.r8.ir.regalloc.RegisterAllocator;
@@ -134,8 +134,8 @@
   }
 
   @Override
-  public TypeLatticeElement evaluate(AppView<?> appView) {
-    return PrimitiveTypeLatticeElement.fromNumericType(type);
+  public TypeElement evaluate(AppView<?> appView) {
+    return PrimitiveTypeElement.fromNumericType(type);
   }
 
   @Override
diff --git a/src/main/java/com/android/tools/r8/ir/code/CheckCast.java b/src/main/java/com/android/tools/r8/ir/code/CheckCast.java
index a084a0f..2d7e409 100644
--- a/src/main/java/com/android/tools/r8/ir/code/CheckCast.java
+++ b/src/main/java/com/android/tools/r8/ir/code/CheckCast.java
@@ -17,7 +17,7 @@
 import com.android.tools.r8.graph.DexClass;
 import com.android.tools.r8.graph.DexType;
 import com.android.tools.r8.ir.analysis.AbstractError;
-import com.android.tools.r8.ir.analysis.type.TypeLatticeElement;
+import com.android.tools.r8.ir.analysis.type.TypeElement;
 import com.android.tools.r8.ir.conversion.CfBuilder;
 import com.android.tools.r8.ir.conversion.DexBuilder;
 import com.android.tools.r8.ir.optimize.Inliner.ConstraintWithTarget;
@@ -121,8 +121,7 @@
       }
     }
     AppView<? extends AppInfoWithLiveness> appViewWithLiveness = appView.withLiveness();
-    TypeLatticeElement castType =
-        TypeLatticeElement.fromDexType(type, definitelyNotNull(), appView);
+    TypeElement castType = TypeElement.fromDexType(type, definitelyNotNull(), appView);
     if (object()
         .getDynamicUpperBoundType(appViewWithLiveness)
         .lessThanOrEqualUpToNullability(castType, appView)) {
@@ -160,21 +159,20 @@
   }
 
   @Override
-  public TypeLatticeElement evaluate(AppView<?> appView) {
-    return TypeLatticeElement.fromDexType(type, object().getTypeLattice().nullability(), appView);
+  public TypeElement evaluate(AppView<?> appView) {
+    return TypeElement.fromDexType(type, object().getType().nullability(), appView);
   }
 
   @Override
   public boolean verifyTypes(AppView<?> appView) {
     assert super.verifyTypes(appView);
 
-    TypeLatticeElement inType = object().getTypeLattice();
+    TypeElement inType = object().getType();
 
     assert inType.isPreciseType();
 
-    TypeLatticeElement outType = outValue().getTypeLattice();
-    TypeLatticeElement castType =
-        TypeLatticeElement.fromDexType(getType(), inType.nullability(), appView);
+    TypeElement outType = outValue().getType();
+    TypeElement castType = TypeElement.fromDexType(getType(), inType.nullability(), appView);
 
     if (inType.lessThanOrEqual(castType, appView)) {
       // Cast can be removed. Check that it is sound to replace all users of the out-value by the
diff --git a/src/main/java/com/android/tools/r8/ir/code/Cmp.java b/src/main/java/com/android/tools/r8/ir/code/Cmp.java
index acccf31..2b44f93 100644
--- a/src/main/java/com/android/tools/r8/ir/code/Cmp.java
+++ b/src/main/java/com/android/tools/r8/ir/code/Cmp.java
@@ -15,7 +15,7 @@
 import com.android.tools.r8.ir.analysis.constant.Bottom;
 import com.android.tools.r8.ir.analysis.constant.ConstLatticeElement;
 import com.android.tools.r8.ir.analysis.constant.LatticeElement;
-import com.android.tools.r8.ir.analysis.type.TypeLatticeElement;
+import com.android.tools.r8.ir.analysis.type.TypeElement;
 import com.android.tools.r8.ir.conversion.CfBuilder;
 import com.android.tools.r8.ir.conversion.DexBuilder;
 import com.android.tools.r8.utils.LongInterval;
@@ -186,7 +186,7 @@
           result = (int) Math.signum(left - right);
         }
       }
-      Value value = code.createValue(TypeLatticeElement.getInt(), getLocalInfo());
+      Value value = code.createValue(TypeElement.getInt(), getLocalInfo());
       ConstNumber newConst = new ConstNumber(value, result);
       return new ConstLatticeElement(newConst);
     } else if (leftLattice.isValueRange() && rightLattice.isConst()) {
@@ -214,7 +214,7 @@
       return Bottom.getInstance();
     }
     int result = Integer.signum(Long.compare(leftRange.getMin(), rightRange.getMin()));
-    Value value = code.createValue(TypeLatticeElement.getInt(), getLocalInfo());
+    Value value = code.createValue(TypeElement.getInt(), getLocalInfo());
     ConstNumber newConst = new ConstNumber(value, result);
     return new ConstLatticeElement(newConst);
   }
@@ -235,8 +235,8 @@
   }
 
   @Override
-  public TypeLatticeElement evaluate(AppView<?> appView) {
-    return TypeLatticeElement.getInt();
+  public TypeElement evaluate(AppView<?> appView) {
+    return TypeElement.getInt();
   }
 
 }
diff --git a/src/main/java/com/android/tools/r8/ir/code/ConstClass.java b/src/main/java/com/android/tools/r8/ir/code/ConstClass.java
index a9089f8..e77f315 100644
--- a/src/main/java/com/android/tools/r8/ir/code/ConstClass.java
+++ b/src/main/java/com/android/tools/r8/ir/code/ConstClass.java
@@ -15,7 +15,7 @@
 import com.android.tools.r8.graph.DexType;
 import com.android.tools.r8.ir.analysis.AbstractError;
 import com.android.tools.r8.ir.analysis.type.Nullability;
-import com.android.tools.r8.ir.analysis.type.TypeLatticeElement;
+import com.android.tools.r8.ir.analysis.type.TypeElement;
 import com.android.tools.r8.ir.analysis.value.AbstractValue;
 import com.android.tools.r8.ir.analysis.value.UnknownValue;
 import com.android.tools.r8.ir.conversion.CfBuilder;
@@ -46,7 +46,7 @@
     Value newValue =
         new Value(
             code.valueNumberGenerator.next(),
-            original.outValue().getTypeLattice(),
+            original.outValue().getType(),
             original.getLocalInfo());
     return copyOf(newValue, original);
   }
@@ -172,8 +172,8 @@
   }
 
   @Override
-  public TypeLatticeElement evaluate(AppView<?> appView) {
-    return TypeLatticeElement.classClassType(appView, Nullability.definitelyNotNull());
+  public TypeElement evaluate(AppView<?> appView) {
+    return TypeElement.classClassType(appView, Nullability.definitelyNotNull());
   }
 
   @Override
diff --git a/src/main/java/com/android/tools/r8/ir/code/ConstMethodHandle.java b/src/main/java/com/android/tools/r8/ir/code/ConstMethodHandle.java
index 446601c..996bf38 100644
--- a/src/main/java/com/android/tools/r8/ir/code/ConstMethodHandle.java
+++ b/src/main/java/com/android/tools/r8/ir/code/ConstMethodHandle.java
@@ -11,7 +11,7 @@
 import com.android.tools.r8.graph.DexMethodHandle;
 import com.android.tools.r8.graph.DexType;
 import com.android.tools.r8.ir.analysis.type.Nullability;
-import com.android.tools.r8.ir.analysis.type.TypeLatticeElement;
+import com.android.tools.r8.ir.analysis.type.TypeElement;
 import com.android.tools.r8.ir.conversion.CfBuilder;
 import com.android.tools.r8.ir.conversion.DexBuilder;
 import com.android.tools.r8.ir.optimize.Inliner.ConstraintWithTarget;
@@ -40,7 +40,7 @@
     Value newValue =
         new Value(
             code.valueNumberGenerator.next(),
-            original.outValue().getTypeLattice(),
+            original.outValue().getType(),
             original.getLocalInfo());
     return copyOf(newValue, original);
   }
@@ -116,8 +116,8 @@
   }
 
   @Override
-  public TypeLatticeElement evaluate(AppView<?> appView) {
-    return TypeLatticeElement.fromDexType(
+  public TypeElement evaluate(AppView<?> appView) {
+    return TypeElement.fromDexType(
         appView.dexItemFactory().methodHandleType, Nullability.definitelyNotNull(), appView);
   }
 
diff --git a/src/main/java/com/android/tools/r8/ir/code/ConstMethodType.java b/src/main/java/com/android/tools/r8/ir/code/ConstMethodType.java
index bb3f574..6cd4d0b 100644
--- a/src/main/java/com/android/tools/r8/ir/code/ConstMethodType.java
+++ b/src/main/java/com/android/tools/r8/ir/code/ConstMethodType.java
@@ -11,7 +11,7 @@
 import com.android.tools.r8.graph.DexProto;
 import com.android.tools.r8.graph.DexType;
 import com.android.tools.r8.ir.analysis.type.Nullability;
-import com.android.tools.r8.ir.analysis.type.TypeLatticeElement;
+import com.android.tools.r8.ir.analysis.type.TypeElement;
 import com.android.tools.r8.ir.conversion.CfBuilder;
 import com.android.tools.r8.ir.conversion.DexBuilder;
 import com.android.tools.r8.ir.optimize.Inliner.ConstraintWithTarget;
@@ -40,7 +40,7 @@
     Value newValue =
         new Value(
             code.valueNumberGenerator.next(),
-            original.outValue().getTypeLattice(),
+            original.outValue().getType(),
             original.getLocalInfo());
     return copyOf(newValue, original);
   }
@@ -110,8 +110,8 @@
   }
 
   @Override
-  public TypeLatticeElement evaluate(AppView<?> appView) {
-    return TypeLatticeElement.fromDexType(
+  public TypeElement evaluate(AppView<?> appView) {
+    return TypeElement.fromDexType(
         appView.dexItemFactory().methodTypeType, Nullability.definitelyNotNull(), appView);
   }
 
diff --git a/src/main/java/com/android/tools/r8/ir/code/ConstNumber.java b/src/main/java/com/android/tools/r8/ir/code/ConstNumber.java
index 84f33f3..2683b66 100644
--- a/src/main/java/com/android/tools/r8/ir/code/ConstNumber.java
+++ b/src/main/java/com/android/tools/r8/ir/code/ConstNumber.java
@@ -21,7 +21,7 @@
 import com.android.tools.r8.ir.analysis.constant.Bottom;
 import com.android.tools.r8.ir.analysis.constant.ConstLatticeElement;
 import com.android.tools.r8.ir.analysis.constant.LatticeElement;
-import com.android.tools.r8.ir.analysis.type.TypeLatticeElement;
+import com.android.tools.r8.ir.analysis.type.TypeElement;
 import com.android.tools.r8.ir.analysis.value.AbstractValue;
 import com.android.tools.r8.ir.conversion.CfBuilder;
 import com.android.tools.r8.ir.conversion.DexBuilder;
@@ -54,10 +54,11 @@
   }
 
   public static ConstNumber copyOf(IRCode code, ConstNumber original) {
-    Value newValue = new Value(
-        code.valueNumberGenerator.next(),
-        original.outValue().getTypeLattice(),
-        original.getLocalInfo());
+    Value newValue =
+        new Value(
+            code.valueNumberGenerator.next(),
+            original.outValue().getType(),
+            original.getLocalInfo());
     return copyOf(newValue, original);
   }
 
@@ -250,7 +251,7 @@
   @Override
   public String toString() {
     if (outValue != null) {
-      return super.toString() + " " + value + " (" + outValue().getTypeLattice() + ")";
+      return super.toString() + " " + value + " (" + outValue().getType() + ")";
     } else {
       return super.toString() + " " + value + " (dead)";
     }
@@ -314,16 +315,14 @@
   }
 
   @Override
-  public TypeLatticeElement evaluate(AppView<?> appView) {
-    return outValue().getTypeLattice();
+  public TypeElement evaluate(AppView<?> appView) {
+    return outValue().getType();
   }
 
   @Override
   public boolean verifyTypes(AppView<?> appView) {
     assert super.verifyTypes(appView);
-    assert !isZero()
-        || outValue().getTypeLattice().isPrimitive()
-        || outValue().getTypeLattice().isNullType();
+    assert !isZero() || outValue().getType().isPrimitiveType() || outValue().getType().isNullType();
     return true;
   }
 
diff --git a/src/main/java/com/android/tools/r8/ir/code/ConstString.java b/src/main/java/com/android/tools/r8/ir/code/ConstString.java
index a80bc66..c98d8b7 100644
--- a/src/main/java/com/android/tools/r8/ir/code/ConstString.java
+++ b/src/main/java/com/android/tools/r8/ir/code/ConstString.java
@@ -11,7 +11,7 @@
 import com.android.tools.r8.graph.DexString;
 import com.android.tools.r8.graph.DexType;
 import com.android.tools.r8.ir.analysis.type.Nullability;
-import com.android.tools.r8.ir.analysis.type.TypeLatticeElement;
+import com.android.tools.r8.ir.analysis.type.TypeElement;
 import com.android.tools.r8.ir.analysis.value.AbstractValue;
 import com.android.tools.r8.ir.analysis.value.UnknownValue;
 import com.android.tools.r8.ir.code.BasicBlock.ThrowingInfo;
@@ -42,8 +42,9 @@
 
   public static ConstString copyOf(IRCode code, ConstString original) {
     Value newValue =
-        new Value(code.valueNumberGenerator.next(),
-            original.outValue().getTypeLattice(),
+        new Value(
+            code.valueNumberGenerator.next(),
+            original.outValue().getType(),
             original.getLocalInfo());
     return copyOf(newValue, original);
   }
@@ -153,8 +154,8 @@
   }
 
   @Override
-  public TypeLatticeElement evaluate(AppView<?> appView) {
-    return TypeLatticeElement.stringClassType(appView, Nullability.definitelyNotNull());
+  public TypeElement evaluate(AppView<?> appView) {
+    return TypeElement.stringClassType(appView, Nullability.definitelyNotNull());
   }
 
   @Override
diff --git a/src/main/java/com/android/tools/r8/ir/code/DebugLocalWrite.java b/src/main/java/com/android/tools/r8/ir/code/DebugLocalWrite.java
index 453a564..5884660 100644
--- a/src/main/java/com/android/tools/r8/ir/code/DebugLocalWrite.java
+++ b/src/main/java/com/android/tools/r8/ir/code/DebugLocalWrite.java
@@ -79,7 +79,7 @@
   @Override
   public boolean verifyTypes(AppView<?> appView) {
     super.verifyTypes(appView);
-    assert src().getTypeLattice().lessThanOrEqual(outValue().getTypeLattice(), appView);
+    assert src().getType().lessThanOrEqual(outValue().getType(), appView);
     return true;
   }
 }
diff --git a/src/main/java/com/android/tools/r8/ir/code/DexItemBasedConstString.java b/src/main/java/com/android/tools/r8/ir/code/DexItemBasedConstString.java
index 979f172..70ebd80 100644
--- a/src/main/java/com/android/tools/r8/ir/code/DexItemBasedConstString.java
+++ b/src/main/java/com/android/tools/r8/ir/code/DexItemBasedConstString.java
@@ -11,7 +11,7 @@
 import com.android.tools.r8.graph.DexReference;
 import com.android.tools.r8.graph.DexType;
 import com.android.tools.r8.ir.analysis.type.Nullability;
-import com.android.tools.r8.ir.analysis.type.TypeLatticeElement;
+import com.android.tools.r8.ir.analysis.type.TypeElement;
 import com.android.tools.r8.ir.code.BasicBlock.ThrowingInfo;
 import com.android.tools.r8.ir.conversion.CfBuilder;
 import com.android.tools.r8.ir.conversion.DexBuilder;
@@ -48,8 +48,9 @@
 
   public static DexItemBasedConstString copyOf(IRCode code, DexItemBasedConstString original) {
     Value newValue =
-        new Value(code.valueNumberGenerator.next(),
-            original.outValue().getTypeLattice(),
+        new Value(
+            code.valueNumberGenerator.next(),
+            original.outValue().getType(),
             original.getLocalInfo());
     return copyOf(newValue, original);
   }
@@ -153,8 +154,8 @@
   }
 
   @Override
-  public TypeLatticeElement evaluate(AppView<?> appView) {
-    return TypeLatticeElement.stringClassType(appView, Nullability.definitelyNotNull());
+  public TypeElement evaluate(AppView<?> appView) {
+    return TypeElement.stringClassType(appView, Nullability.definitelyNotNull());
   }
 
   @Override
diff --git a/src/main/java/com/android/tools/r8/ir/code/Dup.java b/src/main/java/com/android/tools/r8/ir/code/Dup.java
index 79fe507..cddc41e 100644
--- a/src/main/java/com/android/tools/r8/ir/code/Dup.java
+++ b/src/main/java/com/android/tools/r8/ir/code/Dup.java
@@ -70,7 +70,7 @@
 
   @Override
   public void buildCf(CfBuilder builder) {
-    if (this.inValues.get(0).getTypeLattice().isWidePrimitive()) {
+    if (this.inValues.get(0).getType().isWidePrimitive()) {
       builder.add(new CfStackInstruction(Opcode.Dup2));
     } else {
       builder.add(new CfStackInstruction(Opcode.Dup));
diff --git a/src/main/java/com/android/tools/r8/ir/code/Dup2.java b/src/main/java/com/android/tools/r8/ir/code/Dup2.java
index 2c24e04..465c408 100644
--- a/src/main/java/com/android/tools/r8/ir/code/Dup2.java
+++ b/src/main/java/com/android/tools/r8/ir/code/Dup2.java
@@ -33,8 +33,8 @@
 
   private Dup2(StackValues dest, Value srcBottom, Value srcTop) {
     super(dest, ImmutableList.of(srcBottom, srcTop));
-    assert !srcBottom.getTypeLattice().isWidePrimitive();
-    assert !srcTop.getTypeLattice().isWidePrimitive();
+    assert !srcBottom.getType().isWidePrimitive();
+    assert !srcTop.getType().isWidePrimitive();
     assert dest.getStackValues().length == 4;
     assert srcBottom.isValueOnStack() && !(srcBottom instanceof StackValues);
     assert srcTop.isValueOnStack() && !(srcTop instanceof StackValues);
diff --git a/src/main/java/com/android/tools/r8/ir/code/FieldInstruction.java b/src/main/java/com/android/tools/r8/ir/code/FieldInstruction.java
index 507ae25..d7e0067 100644
--- a/src/main/java/com/android/tools/r8/ir/code/FieldInstruction.java
+++ b/src/main/java/com/android/tools/r8/ir/code/FieldInstruction.java
@@ -17,7 +17,7 @@
 import com.android.tools.r8.ir.analysis.fieldvalueanalysis.ConcreteMutableFieldSet;
 import com.android.tools.r8.ir.analysis.fieldvalueanalysis.EmptyFieldSet;
 import com.android.tools.r8.ir.analysis.fieldvalueanalysis.UnknownFieldSet;
-import com.android.tools.r8.ir.analysis.type.TypeLatticeElement;
+import com.android.tools.r8.ir.analysis.type.TypeElement;
 import com.android.tools.r8.ir.analysis.value.AbstractValue;
 import com.android.tools.r8.ir.analysis.value.SingleFieldValue;
 import com.android.tools.r8.ir.analysis.value.UnknownValue;
@@ -114,7 +114,7 @@
     if (isInstanceGet() || isInstancePut()) {
       if (!assumption.canAssumeReceiverIsNotNull()) {
         Value receiver = inValues.get(0);
-        if (receiver.isAlwaysNull(appView) || receiver.typeLattice.isNullable()) {
+        if (receiver.isAlwaysNull(appView) || receiver.type.isNullable()) {
           return AbstractError.specific(appView.dexItemFactory().npeType);
         }
       }
@@ -189,9 +189,8 @@
       AppView<AppInfoWithLiveness> appView, DexEncodedField field) {
     assert isFieldPut();
 
-    TypeLatticeElement type = value().getTypeLattice();
-    TypeLatticeElement baseType =
-        type.isArrayType() ? type.asArrayTypeLatticeElement().getArrayBaseTypeLattice() : type;
+    TypeElement type = value().getType();
+    TypeElement baseType = type.isArrayType() ? type.asArrayType().getBaseType() : type;
     if (!baseType.isClassType()) {
       return false;
     }
@@ -225,7 +224,7 @@
       return resolutionResult != null && resolutionResult.isProgramMethod(appView);
     }
 
-    return appInfo.mayHaveFinalizeMethodDirectlyOrIndirectly(baseType.asClassTypeLatticeElement());
+    return appInfo.mayHaveFinalizeMethodDirectlyOrIndirectly(baseType.asClassType());
   }
 
   @Override
diff --git a/src/main/java/com/android/tools/r8/ir/code/FixedRegisterValue.java b/src/main/java/com/android/tools/r8/ir/code/FixedRegisterValue.java
index b074e44..c1424b7 100644
--- a/src/main/java/com/android/tools/r8/ir/code/FixedRegisterValue.java
+++ b/src/main/java/com/android/tools/r8/ir/code/FixedRegisterValue.java
@@ -4,7 +4,7 @@
 package com.android.tools.r8.ir.code;
 
 import com.android.tools.r8.errors.Unreachable;
-import com.android.tools.r8.ir.analysis.type.TypeLatticeElement;
+import com.android.tools.r8.ir.analysis.type.TypeElement;
 import java.util.function.Predicate;
 
 // Value that has a fixed register allocated. These are used for inserting spill, restore, and phi
@@ -12,7 +12,7 @@
 public class FixedRegisterValue extends Value {
   private final int register;
 
-  public FixedRegisterValue(TypeLatticeElement moveType, int register) {
+  public FixedRegisterValue(TypeElement moveType, int register) {
     // Set local info to null since these values are never representatives of live-ranges.
     super(-1, moveType, null);
     setNeedsRegister(true);
@@ -21,8 +21,8 @@
 
   @Override
   public ValueType outType() {
-    TypeLatticeElement type = getTypeLattice();
-    if (type.isPrimitive()) {
+    TypeElement type = getType();
+    if (type.isPrimitiveType()) {
       if (type.isSinglePrimitive()) {
         if (type.isInt()) {
           return ValueType.INT;
@@ -40,7 +40,7 @@
         }
       }
     } else {
-      assert type.isReference();
+      assert type.isReferenceType();
       return ValueType.OBJECT;
     }
     throw new Unreachable("Unexpected imprecise type: " + type);
diff --git a/src/main/java/com/android/tools/r8/ir/code/IRCode.java b/src/main/java/com/android/tools/r8/ir/code/IRCode.java
index 883d49f..53573ac 100644
--- a/src/main/java/com/android/tools/r8/ir/code/IRCode.java
+++ b/src/main/java/com/android/tools/r8/ir/code/IRCode.java
@@ -12,9 +12,9 @@
 import com.android.tools.r8.graph.classmerging.VerticallyMergedClasses;
 import com.android.tools.r8.ir.analysis.TypeChecker;
 import com.android.tools.r8.ir.analysis.ValueMayDependOnEnvironmentAnalysis;
-import com.android.tools.r8.ir.analysis.type.ClassTypeLatticeElement;
+import com.android.tools.r8.ir.analysis.type.ClassTypeElement;
 import com.android.tools.r8.ir.analysis.type.Nullability;
-import com.android.tools.r8.ir.analysis.type.TypeLatticeElement;
+import com.android.tools.r8.ir.analysis.type.TypeElement;
 import com.android.tools.r8.ir.code.Phi.RegisterReadType;
 import com.android.tools.r8.ir.conversion.IRBuilder;
 import com.android.tools.r8.origin.Origin;
@@ -565,9 +565,8 @@
       return true;
     }
     for (Instruction instruction : instructions()) {
-      if (instruction.outValue != null && instruction.outValue.getTypeLattice().isClassType()) {
-        ClassTypeLatticeElement classTypeLattice =
-            instruction.outValue.getTypeLattice().asClassTypeLatticeElement();
+      if (instruction.outValue != null && instruction.outValue.getType().isClassType()) {
+        ClassTypeElement classTypeLattice = instruction.outValue.getType().asClassType();
         assert !verticallyMergedClasses.hasBeenMergedIntoSubtype(classTypeLattice.getClassType());
         for (DexType itf : classTypeLattice.getInterfaces()) {
           assert !verticallyMergedClasses.hasBeenMergedIntoSubtype(itf);
@@ -893,11 +892,11 @@
   public boolean verifyNoImpreciseOrBottomTypes() {
     Predicate<Value> verifyValue =
         v -> {
-          assert v.getTypeLattice().isPreciseType();
-          assert !v.getTypeLattice().isFineGrainedType();
+          assert v.getType().isPreciseType();
+          assert !v.getType().isFineGrainedType();
           // For now we assume no bottom types on IR values. We may want to reconsider this for
           // representing unreachable code.
-          assert !v.getTypeLattice().isBottom();
+          assert !v.getType().isBottom();
           assert !(v.definition instanceof ImpreciseMemberTypeInstruction)
               || ((ImpreciseMemberTypeInstruction) v.definition).getMemberType().isPrecise();
           return true;
@@ -908,9 +907,8 @@
   public boolean verifyNoNullabilityBottomTypes() {
     Predicate<Value> verifyValue =
         v -> {
-          assert v.getTypeLattice().isPrimitive()
-              || v.getTypeLattice().asReferenceTypeLatticeElement().nullability()
-                  != Nullability.bottom();
+          assert v.getType().isPrimitiveType()
+              || v.getType().asReferenceType().nullability() != Nullability.bottom();
           return true;
         };
     return verifySSATypeLattice(wrapSSAVerifierWithStackValueHandling(verifyValue));
@@ -1049,21 +1047,21 @@
     return thisValue;
   }
 
-  public Value createValue(TypeLatticeElement typeLattice, DebugLocalInfo local) {
+  public Value createValue(TypeElement typeLattice, DebugLocalInfo local) {
     return new Value(valueNumberGenerator.next(), typeLattice, local);
   }
 
-  public Value createValue(TypeLatticeElement typeLattice) {
+  public Value createValue(TypeElement typeLattice) {
     return createValue(typeLattice, null);
   }
 
   public ConstNumber createDoubleConstant(double value, DebugLocalInfo local) {
-    Value out = createValue(TypeLatticeElement.getDouble(), local);
+    Value out = createValue(TypeElement.getDouble(), local);
     return new ConstNumber(out, Double.doubleToLongBits(value));
   }
 
   public ConstNumber createFloatConstant(float value, DebugLocalInfo local) {
-    Value out = createValue(TypeLatticeElement.getFloat(), local);
+    Value out = createValue(TypeElement.getFloat(), local);
     return new ConstNumber(out, Float.floatToIntBits(value));
   }
 
@@ -1072,16 +1070,16 @@
   }
 
   public ConstNumber createIntConstant(int value, DebugLocalInfo local) {
-    Value out = createValue(TypeLatticeElement.getInt(), local);
+    Value out = createValue(TypeElement.getInt(), local);
     return new ConstNumber(out, value);
   }
 
   public ConstNumber createLongConstant(long value, DebugLocalInfo local) {
-    Value out = createValue(TypeLatticeElement.getLong(), local);
+    Value out = createValue(TypeElement.getLong(), local);
     return new ConstNumber(out, value);
   }
 
-  public Phi createPhi(BasicBlock block, TypeLatticeElement type) {
+  public Phi createPhi(BasicBlock block, TypeElement type) {
     return new Phi(valueNumberGenerator.next(), block, type, null, RegisterReadType.NORMAL);
   }
 
@@ -1091,17 +1089,17 @@
 
   public ConstClass createConstClass(AppView<?> appView, DexType type) {
     Value out =
-        createValue(TypeLatticeElement.fromDexType(type, Nullability.definitelyNotNull(), appView));
+        createValue(TypeElement.fromDexType(type, Nullability.definitelyNotNull(), appView));
     return new ConstClass(out, type);
   }
 
   public ConstNumber createConstNull() {
-    Value out = createValue(TypeLatticeElement.getNull());
+    Value out = createValue(TypeElement.getNull());
     return new ConstNumber(out, 0);
   }
 
   public ConstNumber createConstNull(DebugLocalInfo local) {
-    Value out = createValue(TypeLatticeElement.getNull(), local);
+    Value out = createValue(TypeElement.getNull(), local);
     return new ConstNumber(out, 0);
   }
 
diff --git a/src/main/java/com/android/tools/r8/ir/code/If.java b/src/main/java/com/android/tools/r8/ir/code/If.java
index 611ebcc..da0592d 100644
--- a/src/main/java/com/android/tools/r8/ir/code/If.java
+++ b/src/main/java/com/android/tools/r8/ir/code/If.java
@@ -10,7 +10,7 @@
 import com.android.tools.r8.cf.code.CfIf;
 import com.android.tools.r8.cf.code.CfIfCmp;
 import com.android.tools.r8.errors.Unreachable;
-import com.android.tools.r8.ir.analysis.type.TypeLatticeElement;
+import com.android.tools.r8.ir.analysis.type.TypeElement;
 import com.android.tools.r8.ir.conversion.CfBuilder;
 import com.android.tools.r8.ir.conversion.DexBuilder;
 import com.android.tools.r8.utils.CfgPrinter;
@@ -61,10 +61,10 @@
     }
   }
 
-  private static boolean verifyTypeCompatible(TypeLatticeElement valueType, If.Type ifType) {
+  private static boolean verifyTypeCompatible(TypeElement valueType, If.Type ifType) {
     return valueType.isInt()
         || (valueType.isFloat() && (ifType == Type.EQ || ifType == Type.NE))
-        || (valueType.isReference() && (ifType == Type.EQ || ifType == Type.NE));
+        || (valueType.isReferenceType() && (ifType == Type.EQ || ifType == Type.NE));
   }
 
   private Type type;
@@ -199,14 +199,14 @@
 
   public BasicBlock targetFromCondition(ConstNumber value) {
     assert isZeroTest();
-    assert verifyTypeCompatible(value.outValue().getTypeLattice(), type);
+    assert verifyTypeCompatible(value.outValue().getType(), type);
     return targetFromCondition(Long.signum(value.getRawValue()));
   }
 
   public BasicBlock targetFromCondition(ConstNumber left, ConstNumber right) {
     assert !isZeroTest();
     assert left.outType() == right.outType();
-    assert verifyTypeCompatible(left.outValue().getTypeLattice(), type);
+    assert verifyTypeCompatible(left.outValue().getType(), type);
     return targetFromCondition(Long.signum(left.getRawValue() - right.getRawValue()));
   }
 
diff --git a/src/main/java/com/android/tools/r8/ir/code/ImpreciseMemberTypeInstruction.java b/src/main/java/com/android/tools/r8/ir/code/ImpreciseMemberTypeInstruction.java
index 2b5bc02..ff21fbd 100644
--- a/src/main/java/com/android/tools/r8/ir/code/ImpreciseMemberTypeInstruction.java
+++ b/src/main/java/com/android/tools/r8/ir/code/ImpreciseMemberTypeInstruction.java
@@ -6,6 +6,7 @@
 import com.android.tools.r8.ir.conversion.TypeConstraintResolver;
 
 public interface ImpreciseMemberTypeInstruction {
+
   MemberType getMemberType();
 
   void constrainType(TypeConstraintResolver constraintResolver);
diff --git a/src/main/java/com/android/tools/r8/ir/code/InitClass.java b/src/main/java/com/android/tools/r8/ir/code/InitClass.java
index 8dd260c..02ee0eb 100644
--- a/src/main/java/com/android/tools/r8/ir/code/InitClass.java
+++ b/src/main/java/com/android/tools/r8/ir/code/InitClass.java
@@ -16,7 +16,7 @@
 import com.android.tools.r8.ir.analysis.ClassInitializationAnalysis;
 import com.android.tools.r8.ir.analysis.ClassInitializationAnalysis.AnalysisAssumption;
 import com.android.tools.r8.ir.analysis.ClassInitializationAnalysis.Query;
-import com.android.tools.r8.ir.analysis.type.TypeLatticeElement;
+import com.android.tools.r8.ir.analysis.type.TypeElement;
 import com.android.tools.r8.ir.conversion.CfBuilder;
 import com.android.tools.r8.ir.conversion.DexBuilder;
 import com.android.tools.r8.ir.optimize.Inliner.ConstraintWithTarget;
@@ -30,7 +30,7 @@
   public InitClass(Value outValue, DexType clazz) {
     super(outValue);
     assert hasOutValue();
-    assert outValue.getTypeLattice().isInt();
+    assert outValue.getType().isInt();
     assert clazz.isClassType();
     this.clazz = clazz;
   }
@@ -50,8 +50,8 @@
   }
 
   @Override
-  public TypeLatticeElement evaluate(AppView<?> appView) {
-    return TypeLatticeElement.getInt();
+  public TypeElement evaluate(AppView<?> appView) {
+    return TypeElement.getInt();
   }
 
   @Override
diff --git a/src/main/java/com/android/tools/r8/ir/code/InstanceGet.java b/src/main/java/com/android/tools/r8/ir/code/InstanceGet.java
index 6d9b8e9..6edc6da 100644
--- a/src/main/java/com/android/tools/r8/ir/code/InstanceGet.java
+++ b/src/main/java/com/android/tools/r8/ir/code/InstanceGet.java
@@ -23,7 +23,7 @@
 import com.android.tools.r8.ir.analysis.ClassInitializationAnalysis.AnalysisAssumption;
 import com.android.tools.r8.ir.analysis.ClassInitializationAnalysis.Query;
 import com.android.tools.r8.ir.analysis.type.Nullability;
-import com.android.tools.r8.ir.analysis.type.TypeLatticeElement;
+import com.android.tools.r8.ir.analysis.type.TypeElement;
 import com.android.tools.r8.ir.conversion.CfBuilder;
 import com.android.tools.r8.ir.conversion.DexBuilder;
 import com.android.tools.r8.ir.optimize.Inliner.ConstraintWithTarget;
@@ -68,9 +68,9 @@
 
   @Override
   public boolean couldIntroduceAnAlias(AppView<?> appView, Value root) {
-    assert root != null && root.getTypeLattice().isReference();
+    assert root != null && root.getType().isReferenceType();
     assert outValue != null;
-    return outValue.getTypeLattice().isReference();
+    return outValue.getType().isReferenceType();
   }
 
   @Override
@@ -181,8 +181,8 @@
   }
 
   @Override
-  public TypeLatticeElement evaluate(AppView<?> appView) {
-    return TypeLatticeElement.fromDexType(getField().type, Nullability.maybeNull(), appView);
+  public TypeElement evaluate(AppView<?> appView) {
+    return TypeElement.fromDexType(getField().type, Nullability.maybeNull(), appView);
   }
 
   @Override
diff --git a/src/main/java/com/android/tools/r8/ir/code/InstanceOf.java b/src/main/java/com/android/tools/r8/ir/code/InstanceOf.java
index de4fa66..c18e3c5 100644
--- a/src/main/java/com/android/tools/r8/ir/code/InstanceOf.java
+++ b/src/main/java/com/android/tools/r8/ir/code/InstanceOf.java
@@ -9,7 +9,7 @@
 import com.android.tools.r8.dex.Constants;
 import com.android.tools.r8.graph.AppView;
 import com.android.tools.r8.graph.DexType;
-import com.android.tools.r8.ir.analysis.type.TypeLatticeElement;
+import com.android.tools.r8.ir.analysis.type.TypeElement;
 import com.android.tools.r8.ir.conversion.CfBuilder;
 import com.android.tools.r8.ir.conversion.DexBuilder;
 import com.android.tools.r8.ir.optimize.Inliner.ConstraintWithTarget;
@@ -91,8 +91,8 @@
   }
 
   @Override
-  public TypeLatticeElement evaluate(AppView<?> appView) {
-    return TypeLatticeElement.getInt();
+  public TypeElement evaluate(AppView<?> appView) {
+    return TypeElement.getInt();
   }
 
   @Override
diff --git a/src/main/java/com/android/tools/r8/ir/code/InstancePut.java b/src/main/java/com/android/tools/r8/ir/code/InstancePut.java
index 5680260..116a3e6 100644
--- a/src/main/java/com/android/tools/r8/ir/code/InstancePut.java
+++ b/src/main/java/com/android/tools/r8/ir/code/InstancePut.java
@@ -160,7 +160,7 @@
       // If the value being written by this instruction is an array, then make sure that the value
       // being written by the other instruction is the exact same value. Otherwise, the verifier
       // may incorrectly join the types of these arrays to Object[].
-      if (value().getTypeLattice().isArrayType() && value() != instancePut.value()) {
+      if (value().getType().isArrayType() && value() != instancePut.value()) {
         return false;
       }
     }
diff --git a/src/main/java/com/android/tools/r8/ir/code/Instruction.java b/src/main/java/com/android/tools/r8/ir/code/Instruction.java
index a56f0a2..654c2e8 100644
--- a/src/main/java/com/android/tools/r8/ir/code/Instruction.java
+++ b/src/main/java/com/android/tools/r8/ir/code/Instruction.java
@@ -19,7 +19,7 @@
 import com.android.tools.r8.ir.analysis.fieldvalueanalysis.AbstractFieldSet;
 import com.android.tools.r8.ir.analysis.fieldvalueanalysis.EmptyFieldSet;
 import com.android.tools.r8.ir.analysis.fieldvalueanalysis.UnknownFieldSet;
-import com.android.tools.r8.ir.analysis.type.TypeLatticeElement;
+import com.android.tools.r8.ir.analysis.type.TypeElement;
 import com.android.tools.r8.ir.analysis.value.AbstractValue;
 import com.android.tools.r8.ir.analysis.value.UnknownValue;
 import com.android.tools.r8.ir.code.Assume.DynamicTypeAssumption;
@@ -145,9 +145,9 @@
   }
 
   @Override
-  public TypeLatticeElement getTypeLattice() {
+  public TypeElement getOutType() {
     if (hasOutValue()) {
-      return outValue().getTypeLattice();
+      return outValue().getType();
     }
     return null;
   }
@@ -661,6 +661,14 @@
     return this;
   }
 
+  public boolean isArrayAccess() {
+    return false;
+  }
+
+  public ArrayAccess asArrayAccess() {
+    return null;
+  }
+
   public boolean isArrayGet() {
     return false;
   }
@@ -1375,7 +1383,7 @@
   public abstract void insertLoadAndStores(InstructionListIterator it, LoadStoreHelper helper);
 
   public DexType computeVerificationType(AppView<?> appView, TypeVerificationHelper helper) {
-    assert outValue == null || !outValue.getTypeLattice().isReference();
+    assert outValue == null || !outValue.getType().isReferenceType();
     throw new Unreachable("Instruction without object outValue cannot compute verification type");
   }
 
@@ -1389,7 +1397,7 @@
   }
 
   // TODO(b/72693244): maybe rename to computeOutType once TypeVerificationHelper is gone?
-  public TypeLatticeElement evaluate(AppView<?> appView) {
+  public TypeElement evaluate(AppView<?> appView) {
     assert outValue == null;
     throw new Unimplemented(
         "Implement type lattice evaluation for: " + getInstructionName());
@@ -1398,16 +1406,16 @@
   public boolean verifyTypes(AppView<?> appView) {
     // TODO(b/72693244): for instructions with invariant out type, we can verify type directly here.
     if (outValue != null) {
-      TypeLatticeElement outTypeLatticeElement = outValue.getTypeLattice();
-      if (outTypeLatticeElement.isArrayType()) {
+      TypeElement outTypeElement = outValue.getType();
+      if (outTypeElement.isArrayType()) {
         DexType outBaseType =
-            outTypeLatticeElement
-                .asArrayTypeLatticeElement()
-                .getArrayType(appView.dexItemFactory())
+            outTypeElement
+                .asArrayType()
+                .toDexType(appView.dexItemFactory())
                 .toBaseType(appView.dexItemFactory());
         assert appView.graphLense().lookupType(outBaseType) == outBaseType;
-      } else if (outTypeLatticeElement.isClassType()) {
-        DexType outType = outTypeLatticeElement.asClassTypeLatticeElement().getClassType();
+      } else if (outTypeElement.isClassType()) {
+        DexType outType = outTypeElement.asClassType().getClassType();
         assert appView.graphLense().lookupType(outType) == outType;
       }
     }
diff --git a/src/main/java/com/android/tools/r8/ir/code/Invoke.java b/src/main/java/com/android/tools/r8/ir/code/Invoke.java
index 15834d9..05e80d1 100644
--- a/src/main/java/com/android/tools/r8/ir/code/Invoke.java
+++ b/src/main/java/com/android/tools/r8/ir/code/Invoke.java
@@ -16,7 +16,7 @@
 import com.android.tools.r8.graph.DexProto;
 import com.android.tools.r8.graph.DexType;
 import com.android.tools.r8.ir.analysis.type.Nullability;
-import com.android.tools.r8.ir.analysis.type.TypeLatticeElement;
+import com.android.tools.r8.ir.analysis.type.TypeElement;
 import com.android.tools.r8.ir.conversion.DexBuilder;
 import java.util.List;
 import java.util.Set;
@@ -167,14 +167,14 @@
   protected void addInvokeAndMoveResult(
       com.android.tools.r8.code.Instruction instruction, DexBuilder builder) {
     if (outValue != null && outValue.needsRegister()) {
-      TypeLatticeElement moveType = outValue.getTypeLattice();
+      TypeElement moveType = outValue.getType();
       int register = builder.allocatedRegister(outValue, getNumber());
       com.android.tools.r8.code.Instruction moveResult;
       if (moveType.isSinglePrimitive()) {
         moveResult = new MoveResult(register);
       } else if (moveType.isWidePrimitive()) {
         moveResult = new MoveResultWide(register);
-      } else if (moveType.isReference()) {
+      } else if (moveType.isReferenceType()) {
         moveResult = new MoveResultObject(register);
       } else {
         throw new Unreachable("Unexpected result type " + outType());
@@ -187,24 +187,27 @@
 
   @Override
   public boolean couldIntroduceAnAlias(AppView<?> appView, Value root) {
-    assert root != null && root.getTypeLattice().isReference();
+    assert root != null && root.getType().isReferenceType();
     if (outValue == null) {
       return false;
     }
-    TypeLatticeElement outType = outValue.getTypeLattice();
-    if (outType.isPrimitive()) {
+    TypeElement outType = outValue.getType();
+    if (outType.isPrimitiveType()) {
       return false;
     }
     if (appView.appInfo().hasSubtyping()) {
       if (outType.isClassType()
-          && root.getTypeLattice().isClassType()
-          && appView.appInfo().withSubtyping().inDifferentHierarchy(
-              outType.asClassTypeLatticeElement().getClassType(),
-              root.getTypeLattice().asClassTypeLatticeElement().getClassType())) {
+          && root.getType().isClassType()
+          && appView
+              .appInfo()
+              .withSubtyping()
+              .inDifferentHierarchy(
+                  outType.asClassType().getClassType(),
+                  root.getType().asClassType().getClassType())) {
         return false;
       }
     }
-    return outType.isReference();
+    return outType.isReferenceType();
   }
 
   @Override
@@ -284,12 +287,12 @@
   }
 
   @Override
-  public TypeLatticeElement evaluate(AppView<?> appView) {
+  public TypeElement evaluate(AppView<?> appView) {
     DexType returnType = getReturnType();
     if (returnType.isVoidType()) {
       throw new Unreachable("void methods have no type.");
     }
-    return TypeLatticeElement.fromDexType(returnType, Nullability.maybeNull(), appView);
+    return TypeElement.fromDexType(returnType, Nullability.maybeNull(), appView);
   }
 
   @Override
diff --git a/src/main/java/com/android/tools/r8/ir/code/InvokeCustom.java b/src/main/java/com/android/tools/r8/ir/code/InvokeCustom.java
index 2c7dcfb..9ee1988 100644
--- a/src/main/java/com/android/tools/r8/ir/code/InvokeCustom.java
+++ b/src/main/java/com/android/tools/r8/ir/code/InvokeCustom.java
@@ -10,9 +10,9 @@
 import com.android.tools.r8.graph.AppView;
 import com.android.tools.r8.graph.DexCallSite;
 import com.android.tools.r8.graph.DexType;
-import com.android.tools.r8.ir.analysis.type.ClassTypeLatticeElement;
+import com.android.tools.r8.ir.analysis.type.ClassTypeElement;
 import com.android.tools.r8.ir.analysis.type.Nullability;
-import com.android.tools.r8.ir.analysis.type.TypeLatticeElement;
+import com.android.tools.r8.ir.analysis.type.TypeElement;
 import com.android.tools.r8.ir.conversion.CfBuilder;
 import com.android.tools.r8.ir.conversion.DexBuilder;
 import com.android.tools.r8.ir.desugar.LambdaDescriptor;
@@ -43,9 +43,9 @@
   }
 
   private static boolean verifyLambdaInterfaces(
-      TypeLatticeElement returnTypeLattice, Set<DexType> lambdaInterfaceSet, DexType objectType) {
-    Set<DexType> primaryInterfaces = returnTypeLattice.asClassTypeLatticeElement().getInterfaces();
-    if (returnTypeLattice.asClassTypeLatticeElement().getClassType() == objectType) {
+      TypeElement returnType, Set<DexType> lambdaInterfaceSet, DexType objectType) {
+    Set<DexType> primaryInterfaces = returnType.asClassType().getInterfaces();
+    if (returnType.asClassType().getClassType() == objectType) {
       assert primaryInterfaces.size() == 1;
       // The interfaces returned by the LambdaDescriptor assumed to already contain the primary
       // interface. If they're both singleton lists they must be identical and we can return the
@@ -53,47 +53,46 @@
       assert lambdaInterfaceSet.contains(primaryInterfaces.iterator().next());
     } else {
       // We arrive here if the primary interface is a missing class. In that case the
-      // returnTypeLattice will be the missing type as the class type.
+      // returnType will be the missing type as the class type.
       assert primaryInterfaces.isEmpty();
-      assert lambdaInterfaceSet.contains(
-          returnTypeLattice.asClassTypeLatticeElement().getClassType());
+      assert lambdaInterfaceSet.contains(returnType.asClassType().getClassType());
     }
     return true;
   }
 
   @Override
-  public TypeLatticeElement evaluate(AppView<?> appView) {
-    TypeLatticeElement returnTypeLattice = super.evaluate(appView);
+  public TypeElement evaluate(AppView<?> appView) {
+    TypeElement returnType = super.evaluate(appView);
     if (!appView.appInfo().hasSubtyping()) {
-      return returnTypeLattice;
+      return returnType;
     }
     List<DexType> lambdaInterfaces =
         LambdaDescriptor.getInterfaces(callSite, appView.appInfo().withSubtyping());
     if (lambdaInterfaces == null || lambdaInterfaces.isEmpty()) {
-      return returnTypeLattice;
+      return returnType;
     }
 
     // The primary return type is either an interface or a missing type.
-    assert returnTypeLattice instanceof ClassTypeLatticeElement;
+    assert returnType instanceof ClassTypeElement;
 
-    Set<DexType> primaryInterfaces = returnTypeLattice.asClassTypeLatticeElement().getInterfaces();
+    Set<DexType> primaryInterfaces = returnType.asClassType().getInterfaces();
     DexType objectType = appView.dexItemFactory().objectType;
 
-    if (returnTypeLattice.asClassTypeLatticeElement().getClassType() == objectType) {
+    if (returnType.asClassType().getClassType() == objectType) {
       assert primaryInterfaces.size() == 1;
       // Shortcut for the common case: single interface. Save creating a new lattice type.
       if (lambdaInterfaces.size() == 1) {
         assert lambdaInterfaces.get(0) == primaryInterfaces.iterator().next();
-        return returnTypeLattice;
+        return returnType;
       }
     }
 
     Set<DexType> lambdaInterfaceSet =
         ImmutableSet.<DexType>builder().addAll(lambdaInterfaces).build();
 
-    assert verifyLambdaInterfaces(returnTypeLattice, lambdaInterfaceSet, objectType);
+    assert verifyLambdaInterfaces(returnType, lambdaInterfaceSet, objectType);
 
-    return ClassTypeLatticeElement.create(objectType, Nullability.maybeNull(), lambdaInterfaceSet);
+    return ClassTypeElement.create(objectType, Nullability.maybeNull(), lambdaInterfaceSet);
   }
 
   @Override
diff --git a/src/main/java/com/android/tools/r8/ir/code/InvokeDirect.java b/src/main/java/com/android/tools/r8/ir/code/InvokeDirect.java
index b4531c3..ddc8952 100644
--- a/src/main/java/com/android/tools/r8/ir/code/InvokeDirect.java
+++ b/src/main/java/com/android/tools/r8/ir/code/InvokeDirect.java
@@ -166,7 +166,7 @@
 
     // Check if it could throw a NullPointerException as a result of the receiver being null.
     Value receiver = getReceiver();
-    if (!assumption.canAssumeReceiverIsNotNull() && receiver.getTypeLattice().isNullable()) {
+    if (!assumption.canAssumeReceiverIsNotNull() && receiver.getType().isNullable()) {
       return true;
     }
 
diff --git a/src/main/java/com/android/tools/r8/ir/code/InvokeMethod.java b/src/main/java/com/android/tools/r8/ir/code/InvokeMethod.java
index 401b4df..9b708bc 100644
--- a/src/main/java/com/android/tools/r8/ir/code/InvokeMethod.java
+++ b/src/main/java/com/android/tools/r8/ir/code/InvokeMethod.java
@@ -161,7 +161,7 @@
       // incorrectly join the types of these arrays to Object[].
       for (int i = 0; i < arguments().size(); ++i) {
         Value argument = arguments().get(i);
-        if (argument.getTypeLattice().isArrayType() && argument != invoke.arguments().get(i)) {
+        if (argument.getType().isArrayType() && argument != invoke.arguments().get(i)) {
           return false;
         }
       }
diff --git a/src/main/java/com/android/tools/r8/ir/code/InvokeMethodWithReceiver.java b/src/main/java/com/android/tools/r8/ir/code/InvokeMethodWithReceiver.java
index e15a387..689bf8f 100644
--- a/src/main/java/com/android/tools/r8/ir/code/InvokeMethodWithReceiver.java
+++ b/src/main/java/com/android/tools/r8/ir/code/InvokeMethodWithReceiver.java
@@ -8,9 +8,9 @@
 import com.android.tools.r8.graph.DexMethod;
 import com.android.tools.r8.graph.DexType;
 import com.android.tools.r8.ir.analysis.ClassInitializationAnalysis;
-import com.android.tools.r8.ir.analysis.type.ClassTypeLatticeElement;
+import com.android.tools.r8.ir.analysis.type.ClassTypeElement;
 import com.android.tools.r8.ir.analysis.type.TypeAnalysis;
-import com.android.tools.r8.ir.analysis.type.TypeLatticeElement;
+import com.android.tools.r8.ir.analysis.type.TypeElement;
 import com.android.tools.r8.ir.optimize.DefaultInliningOracle;
 import com.android.tools.r8.ir.optimize.Inliner.InlineAction;
 import com.android.tools.r8.ir.optimize.Inliner.Reason;
@@ -70,12 +70,12 @@
     assert super.verifyTypes(appView);
 
     Value receiver = getReceiver();
-    TypeLatticeElement receiverType = receiver.getTypeLattice();
+    TypeElement receiverType = receiver.getType();
     assert receiverType.isPreciseType();
 
     if (appView.appInfo().hasLiveness()) {
       AppView<AppInfoWithLiveness> appViewWithLiveness = appView.withLiveness();
-      ClassTypeLatticeElement receiverLowerBoundType =
+      ClassTypeElement receiverLowerBoundType =
           receiver.getDynamicLowerBoundType(appViewWithLiveness);
       if (receiverLowerBoundType != null) {
         DexType refinedReceiverType =
diff --git a/src/main/java/com/android/tools/r8/ir/code/InvokeMultiNewArray.java b/src/main/java/com/android/tools/r8/ir/code/InvokeMultiNewArray.java
index 02e45ef..a71d185 100644
--- a/src/main/java/com/android/tools/r8/ir/code/InvokeMultiNewArray.java
+++ b/src/main/java/com/android/tools/r8/ir/code/InvokeMultiNewArray.java
@@ -14,7 +14,7 @@
 import com.android.tools.r8.graph.DexType;
 import com.android.tools.r8.ir.analysis.AbstractError;
 import com.android.tools.r8.ir.analysis.type.Nullability;
-import com.android.tools.r8.ir.analysis.type.TypeLatticeElement;
+import com.android.tools.r8.ir.analysis.type.TypeElement;
 import com.android.tools.r8.ir.conversion.CfBuilder;
 import com.android.tools.r8.ir.conversion.DexBuilder;
 import com.android.tools.r8.ir.optimize.Inliner.ConstraintWithTarget;
@@ -82,8 +82,8 @@
   }
 
   @Override
-  public TypeLatticeElement evaluate(AppView<?> appView) {
-    return TypeLatticeElement.fromDexType(type, Nullability.definitelyNotNull(), appView);
+  public TypeElement evaluate(AppView<?> appView) {
+    return TypeElement.fromDexType(type, Nullability.definitelyNotNull(), appView);
   }
 
   @Override
diff --git a/src/main/java/com/android/tools/r8/ir/code/InvokeNewArray.java b/src/main/java/com/android/tools/r8/ir/code/InvokeNewArray.java
index ce75c7c..7992a1c 100644
--- a/src/main/java/com/android/tools/r8/ir/code/InvokeNewArray.java
+++ b/src/main/java/com/android/tools/r8/ir/code/InvokeNewArray.java
@@ -15,7 +15,7 @@
 import com.android.tools.r8.graph.DexType;
 import com.android.tools.r8.ir.analysis.AbstractError;
 import com.android.tools.r8.ir.analysis.type.Nullability;
-import com.android.tools.r8.ir.analysis.type.TypeLatticeElement;
+import com.android.tools.r8.ir.analysis.type.TypeElement;
 import com.android.tools.r8.ir.conversion.CfBuilder;
 import com.android.tools.r8.ir.conversion.DexBuilder;
 import com.android.tools.r8.ir.optimize.Inliner.ConstraintWithTarget;
@@ -111,8 +111,8 @@
   }
 
   @Override
-  public TypeLatticeElement evaluate(AppView<?> appView) {
-    return TypeLatticeElement.fromDexType(type, Nullability.definitelyNotNull(), appView);
+  public TypeElement evaluate(AppView<?> appView) {
+    return TypeElement.fromDexType(type, Nullability.definitelyNotNull(), appView);
   }
 
   @Override
diff --git a/src/main/java/com/android/tools/r8/ir/code/InvokeVirtual.java b/src/main/java/com/android/tools/r8/ir/code/InvokeVirtual.java
index 1218f08..711c911 100644
--- a/src/main/java/com/android/tools/r8/ir/code/InvokeVirtual.java
+++ b/src/main/java/com/android/tools/r8/ir/code/InvokeVirtual.java
@@ -154,7 +154,7 @@
 
     // Check if it could throw a NullPointerException as a result of the receiver being null.
     Value receiver = getReceiver();
-    if (!assumption.canAssumeReceiverIsNotNull() && receiver.getTypeLattice().isNullable()) {
+    if (!assumption.canAssumeReceiverIsNotNull() && receiver.getType().isNullable()) {
       return true;
     }
 
diff --git a/src/main/java/com/android/tools/r8/ir/code/Load.java b/src/main/java/com/android/tools/r8/ir/code/Load.java
index e1885b0..9886690 100644
--- a/src/main/java/com/android/tools/r8/ir/code/Load.java
+++ b/src/main/java/com/android/tools/r8/ir/code/Load.java
@@ -10,7 +10,7 @@
 import com.android.tools.r8.errors.Unreachable;
 import com.android.tools.r8.graph.AppView;
 import com.android.tools.r8.graph.DexType;
-import com.android.tools.r8.ir.analysis.type.TypeLatticeElement;
+import com.android.tools.r8.ir.analysis.type.TypeElement;
 import com.android.tools.r8.ir.conversion.CfBuilder;
 import com.android.tools.r8.ir.conversion.DexBuilder;
 import com.android.tools.r8.ir.optimize.Inliner.ConstraintWithTarget;
@@ -89,8 +89,8 @@
   }
 
   @Override
-  public TypeLatticeElement evaluate(AppView<?> appView) {
-    return src().getTypeLattice();
+  public TypeElement evaluate(AppView<?> appView) {
+    return src().getType();
   }
 
   @Override
diff --git a/src/main/java/com/android/tools/r8/ir/code/LogicalBinop.java b/src/main/java/com/android/tools/r8/ir/code/LogicalBinop.java
index 7f87253..267db9b 100644
--- a/src/main/java/com/android/tools/r8/ir/code/LogicalBinop.java
+++ b/src/main/java/com/android/tools/r8/ir/code/LogicalBinop.java
@@ -9,7 +9,7 @@
 import com.android.tools.r8.ir.analysis.constant.Bottom;
 import com.android.tools.r8.ir.analysis.constant.ConstLatticeElement;
 import com.android.tools.r8.ir.analysis.constant.LatticeElement;
-import com.android.tools.r8.ir.analysis.type.TypeLatticeElement;
+import com.android.tools.r8.ir.analysis.type.TypeElement;
 import com.android.tools.r8.ir.conversion.CfBuilder;
 import com.android.tools.r8.ir.conversion.DexBuilder;
 import java.util.function.Function;
@@ -117,7 +117,7 @@
       ConstNumber newConst;
       if (type == NumericType.INT) {
         int result = foldIntegers(leftConst.getIntValue(), rightConst.getIntValue());
-        Value value = code.createValue(TypeLatticeElement.getInt(), getLocalInfo());
+        Value value = code.createValue(TypeElement.getInt(), getLocalInfo());
         newConst = new ConstNumber(value, result);
       } else {
         assert type == NumericType.LONG;
@@ -129,7 +129,7 @@
           right = rightConst.getLongValue();
         }
         long result = foldLongs(leftConst.getLongValue(), right);
-        Value value = code.createValue(TypeLatticeElement.getLong(), getLocalInfo());
+        Value value = code.createValue(TypeElement.getLong(), getLocalInfo());
         newConst = new ConstNumber(value, result);
       }
       return new ConstLatticeElement(newConst);
diff --git a/src/main/java/com/android/tools/r8/ir/code/Move.java b/src/main/java/com/android/tools/r8/ir/code/Move.java
index 3478645..adcc848 100644
--- a/src/main/java/com/android/tools/r8/ir/code/Move.java
+++ b/src/main/java/com/android/tools/r8/ir/code/Move.java
@@ -9,7 +9,7 @@
 import com.android.tools.r8.errors.Unreachable;
 import com.android.tools.r8.graph.AppView;
 import com.android.tools.r8.graph.DexType;
-import com.android.tools.r8.ir.analysis.type.TypeLatticeElement;
+import com.android.tools.r8.ir.analysis.type.TypeElement;
 import com.android.tools.r8.ir.conversion.CfBuilder;
 import com.android.tools.r8.ir.conversion.DexBuilder;
 import com.android.tools.r8.ir.optimize.Inliner.ConstraintWithTarget;
@@ -73,7 +73,7 @@
 
   @Override
   public String toString() {
-    return super.toString() + " (" + outValue().getTypeLattice() + ")";
+    return super.toString() + " (" + outValue().getType() + ")";
   }
 
   @Override
@@ -104,8 +104,8 @@
   }
 
   @Override
-  public TypeLatticeElement evaluate(AppView<?> appView) {
-    return src().getTypeLattice();
+  public TypeElement evaluate(AppView<?> appView) {
+    return src().getType();
   }
 
   @Override
@@ -128,7 +128,7 @@
     super.verifyTypes(appView);
     // DebugLocalWrite defines it's own verification of types but should be allowed to call super.
     if (!this.isDebugLocalWrite()) {
-      assert src().getTypeLattice().equals(outValue().getTypeLattice());
+      assert src().getType().equals(outValue().getType());
     }
     return true;
   }
diff --git a/src/main/java/com/android/tools/r8/ir/code/MoveException.java b/src/main/java/com/android/tools/r8/ir/code/MoveException.java
index 143227a..e9cd95f 100644
--- a/src/main/java/com/android/tools/r8/ir/code/MoveException.java
+++ b/src/main/java/com/android/tools/r8/ir/code/MoveException.java
@@ -9,7 +9,7 @@
 import com.android.tools.r8.graph.AppView;
 import com.android.tools.r8.graph.DexType;
 import com.android.tools.r8.ir.analysis.type.Nullability;
-import com.android.tools.r8.ir.analysis.type.TypeLatticeElement;
+import com.android.tools.r8.ir.analysis.type.TypeElement;
 import com.android.tools.r8.ir.conversion.CfBuilder;
 import com.android.tools.r8.ir.conversion.DexBuilder;
 import com.android.tools.r8.ir.optimize.Inliner.ConstraintWithTarget;
@@ -111,8 +111,8 @@
   }
 
   @Override
-  public TypeLatticeElement evaluate(AppView<?> appView) {
-    return TypeLatticeElement.fromDexType(exceptionType, Nullability.definitelyNotNull(), appView);
+  public TypeElement evaluate(AppView<?> appView) {
+    return TypeElement.fromDexType(exceptionType, Nullability.definitelyNotNull(), appView);
   }
 
   public DexType getExceptionType() {
diff --git a/src/main/java/com/android/tools/r8/ir/code/Neg.java b/src/main/java/com/android/tools/r8/ir/code/Neg.java
index 41ec4f1..ad0886a 100644
--- a/src/main/java/com/android/tools/r8/ir/code/Neg.java
+++ b/src/main/java/com/android/tools/r8/ir/code/Neg.java
@@ -12,8 +12,8 @@
 import com.android.tools.r8.ir.analysis.constant.Bottom;
 import com.android.tools.r8.ir.analysis.constant.ConstLatticeElement;
 import com.android.tools.r8.ir.analysis.constant.LatticeElement;
-import com.android.tools.r8.ir.analysis.type.PrimitiveTypeLatticeElement;
-import com.android.tools.r8.ir.analysis.type.TypeLatticeElement;
+import com.android.tools.r8.ir.analysis.type.PrimitiveTypeElement;
+import com.android.tools.r8.ir.analysis.type.TypeElement;
 import com.android.tools.r8.ir.conversion.CfBuilder;
 import com.android.tools.r8.ir.conversion.DexBuilder;
 import java.util.function.Function;
@@ -88,7 +88,7 @@
     LatticeElement sourceLattice = getLatticeElement.apply(source());
     if (sourceLattice.isConst()) {
       ConstNumber sourceConst = sourceLattice.asConst().getConstNumber();
-      TypeLatticeElement typeLattice = PrimitiveTypeLatticeElement.fromNumericType(type);
+      TypeElement typeLattice = PrimitiveTypeElement.fromNumericType(type);
       Value value = code.createValue(typeLattice, getLocalInfo());
       ConstNumber newConst;
       if (type == NumericType.INT) {
diff --git a/src/main/java/com/android/tools/r8/ir/code/NewArrayEmpty.java b/src/main/java/com/android/tools/r8/ir/code/NewArrayEmpty.java
index 6d2d5df..bd5c34f 100644
--- a/src/main/java/com/android/tools/r8/ir/code/NewArrayEmpty.java
+++ b/src/main/java/com/android/tools/r8/ir/code/NewArrayEmpty.java
@@ -11,7 +11,7 @@
 import com.android.tools.r8.graph.AppView;
 import com.android.tools.r8.graph.DexType;
 import com.android.tools.r8.ir.analysis.type.Nullability;
-import com.android.tools.r8.ir.analysis.type.TypeLatticeElement;
+import com.android.tools.r8.ir.analysis.type.TypeElement;
 import com.android.tools.r8.ir.conversion.CfBuilder;
 import com.android.tools.r8.ir.conversion.DexBuilder;
 import com.android.tools.r8.ir.optimize.Inliner.ConstraintWithTarget;
@@ -134,8 +134,8 @@
   }
 
   @Override
-  public TypeLatticeElement evaluate(AppView<?> appView) {
-    return TypeLatticeElement.fromDexType(type, Nullability.definitelyNotNull(), appView);
+  public TypeElement evaluate(AppView<?> appView) {
+    return TypeElement.fromDexType(type, Nullability.definitelyNotNull(), appView);
   }
 
   @Override
diff --git a/src/main/java/com/android/tools/r8/ir/code/NewArrayFilledData.java b/src/main/java/com/android/tools/r8/ir/code/NewArrayFilledData.java
index afd87ff..2211a59 100644
--- a/src/main/java/com/android/tools/r8/ir/code/NewArrayFilledData.java
+++ b/src/main/java/com/android/tools/r8/ir/code/NewArrayFilledData.java
@@ -121,7 +121,7 @@
       return AbstractError.top();
     }
 
-    if (src().getTypeLattice().isNullable()) {
+    if (src().getType().isNullable()) {
       return AbstractError.top();
     }
 
diff --git a/src/main/java/com/android/tools/r8/ir/code/NewInstance.java b/src/main/java/com/android/tools/r8/ir/code/NewInstance.java
index 384a27a..d8acd0b 100644
--- a/src/main/java/com/android/tools/r8/ir/code/NewInstance.java
+++ b/src/main/java/com/android/tools/r8/ir/code/NewInstance.java
@@ -19,7 +19,7 @@
 import com.android.tools.r8.ir.analysis.ClassInitializationAnalysis.AnalysisAssumption;
 import com.android.tools.r8.ir.analysis.ClassInitializationAnalysis.Query;
 import com.android.tools.r8.ir.analysis.type.Nullability;
-import com.android.tools.r8.ir.analysis.type.TypeLatticeElement;
+import com.android.tools.r8.ir.analysis.type.TypeElement;
 import com.android.tools.r8.ir.conversion.CfBuilder;
 import com.android.tools.r8.ir.conversion.DexBuilder;
 import com.android.tools.r8.ir.optimize.Inliner.ConstraintWithTarget;
@@ -125,8 +125,8 @@
   }
 
   @Override
-  public TypeLatticeElement evaluate(AppView<?> appView) {
-    return TypeLatticeElement.fromDexType(clazz, Nullability.definitelyNotNull(), appView);
+  public TypeElement evaluate(AppView<?> appView) {
+    return TypeElement.fromDexType(clazz, Nullability.definitelyNotNull(), appView);
   }
 
   @Override
@@ -226,10 +226,9 @@
 
   @Override
   public boolean verifyTypes(AppView<?> appView) {
-    TypeLatticeElement type = outValue().getTypeLattice();
+    TypeElement type = outValue().getType();
     assert type.isClassType();
-    assert type.asClassTypeLatticeElement().getClassType() == clazz
-        || appView.options().testing.allowTypeErrors;
+    assert type.asClassType().getClassType() == clazz || appView.options().testing.allowTypeErrors;
     assert type.isDefinitelyNotNull();
     return true;
   }
diff --git a/src/main/java/com/android/tools/r8/ir/code/Not.java b/src/main/java/com/android/tools/r8/ir/code/Not.java
index 0ab5c78..d000623 100644
--- a/src/main/java/com/android/tools/r8/ir/code/Not.java
+++ b/src/main/java/com/android/tools/r8/ir/code/Not.java
@@ -10,8 +10,8 @@
 import com.android.tools.r8.ir.analysis.constant.Bottom;
 import com.android.tools.r8.ir.analysis.constant.ConstLatticeElement;
 import com.android.tools.r8.ir.analysis.constant.LatticeElement;
-import com.android.tools.r8.ir.analysis.type.PrimitiveTypeLatticeElement;
-import com.android.tools.r8.ir.analysis.type.TypeLatticeElement;
+import com.android.tools.r8.ir.analysis.type.PrimitiveTypeElement;
+import com.android.tools.r8.ir.analysis.type.TypeElement;
 import com.android.tools.r8.ir.conversion.CfBuilder;
 import com.android.tools.r8.ir.conversion.DexBuilder;
 import java.util.function.Function;
@@ -45,13 +45,13 @@
     LatticeElement sourceLattice = getLatticeElement.apply(source());
     if (sourceLattice.isConst()) {
       ConstNumber sourceConst = sourceLattice.asConst().getConstNumber();
-      TypeLatticeElement typeLattice  = PrimitiveTypeLatticeElement.fromNumericType(type);
-      Value value = code.createValue(typeLattice, getLocalInfo());
+      TypeElement type = PrimitiveTypeElement.fromNumericType(this.type);
+      Value value = code.createValue(type, getLocalInfo());
       ConstNumber newConst;
-      if (type == NumericType.INT) {
+      if (this.type == NumericType.INT) {
         newConst = new ConstNumber(value, ~sourceConst.getIntValue());
       } else {
-        assert type == NumericType.LONG;
+        assert this.type == NumericType.LONG;
         newConst = new ConstNumber(value, ~sourceConst.getLongValue());
       }
       return new ConstLatticeElement(newConst);
diff --git a/src/main/java/com/android/tools/r8/ir/code/NumberConversion.java b/src/main/java/com/android/tools/r8/ir/code/NumberConversion.java
index 99adf0c..e6b6f8d 100644
--- a/src/main/java/com/android/tools/r8/ir/code/NumberConversion.java
+++ b/src/main/java/com/android/tools/r8/ir/code/NumberConversion.java
@@ -21,8 +21,8 @@
 import com.android.tools.r8.code.LongToInt;
 import com.android.tools.r8.errors.Unreachable;
 import com.android.tools.r8.graph.AppView;
-import com.android.tools.r8.ir.analysis.type.PrimitiveTypeLatticeElement;
-import com.android.tools.r8.ir.analysis.type.TypeLatticeElement;
+import com.android.tools.r8.ir.analysis.type.PrimitiveTypeElement;
+import com.android.tools.r8.ir.analysis.type.TypeElement;
 import com.android.tools.r8.ir.conversion.CfBuilder;
 import com.android.tools.r8.ir.conversion.DexBuilder;
 import java.util.Set;
@@ -153,8 +153,8 @@
   }
 
   @Override
-  public TypeLatticeElement evaluate(AppView<?> appView) {
-    return PrimitiveTypeLatticeElement.fromNumericType(to);
+  public TypeElement evaluate(AppView<?> appView) {
+    return PrimitiveTypeElement.fromNumericType(to);
   }
 
   @Override
diff --git a/src/main/java/com/android/tools/r8/ir/code/Phi.java b/src/main/java/com/android/tools/r8/ir/code/Phi.java
index 95c2608..49779aa 100644
--- a/src/main/java/com/android/tools/r8/ir/code/Phi.java
+++ b/src/main/java/com/android/tools/r8/ir/code/Phi.java
@@ -11,7 +11,7 @@
 import com.android.tools.r8.graph.DebugLocalInfo;
 import com.android.tools.r8.graph.DexMethod;
 import com.android.tools.r8.graph.DexType;
-import com.android.tools.r8.ir.analysis.type.TypeLatticeElement;
+import com.android.tools.r8.ir.analysis.type.TypeElement;
 import com.android.tools.r8.ir.code.BasicBlock.EdgeType;
 import com.android.tools.r8.ir.conversion.IRBuilder;
 import com.android.tools.r8.ir.conversion.TypeConstraintResolver;
@@ -53,10 +53,10 @@
   public Phi(
       int number,
       BasicBlock block,
-      TypeLatticeElement typeLattice,
+      TypeElement type,
       DebugLocalInfo local,
       RegisterReadType readType) {
-    super(number, typeLattice, local);
+    super(number, type, local);
     this.block = block;
     this.readType = readType;
     block.addPhi(this);
@@ -97,7 +97,7 @@
       throw new InvalidDebugInfoException(
           "Type information in locals-table is inconsistent."
               + " Cannot constrain type: "
-              + typeLattice
+              + type
               + " for value: "
               + this
               + " by constraint "
@@ -115,7 +115,7 @@
       throwUndefinedValueError();
     }
 
-    ValueTypeConstraint readConstraint = TypeConstraintResolver.constraintForType(typeLattice);
+    ValueTypeConstraint readConstraint = TypeConstraintResolver.constraintForType(type);
     List<Value> operands = new ArrayList<>(block.getPredecessors().size());
     for (BasicBlock pred : block.getPredecessors()) {
       EdgeType edgeType = pred.getEdgeType(block);
@@ -125,7 +125,7 @@
 
     if (readType != RegisterReadType.NORMAL) {
       for (Value operand : operands) {
-        TypeLatticeElement type = operand.getTypeLattice();
+        TypeElement type = operand.getType();
         ValueTypeConstraint constraint = TypeConstraintResolver.constraintForType(type);
         abortOnInvalidDebugInfo(constraint);
       }
@@ -261,8 +261,8 @@
       return false;
     }
     // Ensure that the value that replaces this phi is constrained to the type of this phi.
-    if (builder != null && typeLattice.isPreciseType() && !typeLattice.isBottom()) {
-      builder.constrainType(same, ValueTypeConstraint.fromTypeLattice(typeLattice));
+    if (builder != null && type.isPreciseType() && !type.isBottom()) {
+      builder.constrainType(same, ValueTypeConstraint.fromTypeLattice(type));
     }
     if (affectedValues != null) {
       affectedValues.addAll(this.affectedValues());
@@ -320,7 +320,7 @@
     }
     builder.append(" <- phi");
     StringUtils.append(builder, ListUtils.map(operands, Value::toString));
-    builder.append(" : ").append(getTypeLattice());
+    builder.append(" : ").append(getType());
     return builder.toString();
   }
 
@@ -398,17 +398,16 @@
   }
 
   // Type of phi(v1, v2, ..., vn) is the least upper bound of all those n operands.
-  public TypeLatticeElement computePhiType(AppView<?> appView) {
-    TypeLatticeElement result = TypeLatticeElement.getBottom();
+  public TypeElement computePhiType(AppView<?> appView) {
+    TypeElement result = TypeElement.getBottom();
     for (Value operand : getOperands()) {
-      result = result.join(operand.getTypeLattice(), appView);
+      result = result.join(operand.getType(), appView);
     }
     return result;
   }
 
   @Override
-  public TypeLatticeElement getDynamicUpperBoundType(
-      AppView<? extends AppInfoWithSubtyping> appView) {
+  public TypeElement getDynamicUpperBoundType(AppView<? extends AppInfoWithSubtyping> appView) {
     Set<Phi> reachablePhis = SetUtils.newIdentityHashSet(this);
     Deque<Phi> worklist = DequeUtils.newArrayDeque(this);
     while (!worklist.isEmpty()) {
@@ -422,7 +421,7 @@
       }
     }
     Set<Value> visitedOperands = Sets.newIdentityHashSet();
-    TypeLatticeElement result = TypeLatticeElement.getBottom();
+    TypeElement result = TypeElement.getBottom();
     for (Phi phi : reachablePhis) {
       for (Value operand : phi.getOperands()) {
         if (!operand.getAliasedValue().isPhi() && visitedOperands.add(operand)) {
diff --git a/src/main/java/com/android/tools/r8/ir/code/Return.java b/src/main/java/com/android/tools/r8/ir/code/Return.java
index 037f856..0adf292 100644
--- a/src/main/java/com/android/tools/r8/ir/code/Return.java
+++ b/src/main/java/com/android/tools/r8/ir/code/Return.java
@@ -12,7 +12,7 @@
 import com.android.tools.r8.dex.Constants;
 import com.android.tools.r8.errors.Unreachable;
 import com.android.tools.r8.graph.DexType;
-import com.android.tools.r8.ir.analysis.type.TypeLatticeElement;
+import com.android.tools.r8.ir.analysis.type.TypeElement;
 import com.android.tools.r8.ir.conversion.CfBuilder;
 import com.android.tools.r8.ir.conversion.DexBuilder;
 import com.android.tools.r8.ir.optimize.Inliner.ConstraintWithTarget;
@@ -42,9 +42,9 @@
     return inValues.size() == 0;
   }
 
-  public TypeLatticeElement getReturnType() {
+  public TypeElement getReturnType() {
     assert !isReturnVoid();
-    return returnValue().getTypeLattice();
+    return returnValue().getType();
   }
 
   public Value returnValue() {
@@ -57,8 +57,8 @@
       return new ReturnVoid();
     }
     int register = builder.allocatedRegister(returnValue(), getNumber());
-    TypeLatticeElement returnType = getReturnType();
-    if (returnType.isReference()) {
+    TypeElement returnType = getReturnType();
+    if (returnType.isReferenceType()) {
       return new ReturnObject(register);
     }
     if (returnType.isSinglePrimitive()) {
@@ -124,8 +124,6 @@
   @Override
   public void buildCf(CfBuilder builder) {
     builder.add(
-        isReturnVoid()
-            ? new CfReturnVoid()
-            : new CfReturn(ValueType.fromTypeLattice(getReturnType())));
+        isReturnVoid() ? new CfReturnVoid() : new CfReturn(ValueType.fromType(getReturnType())));
   }
 }
diff --git a/src/main/java/com/android/tools/r8/ir/code/StackValue.java b/src/main/java/com/android/tools/r8/ir/code/StackValue.java
index 430f290..0d13811 100644
--- a/src/main/java/com/android/tools/r8/ir/code/StackValue.java
+++ b/src/main/java/com/android/tools/r8/ir/code/StackValue.java
@@ -6,15 +6,15 @@
 import com.android.tools.r8.cf.TypeVerificationHelper.TypeInfo;
 import com.android.tools.r8.graph.AppView;
 import com.android.tools.r8.ir.analysis.type.Nullability;
-import com.android.tools.r8.ir.analysis.type.TypeLatticeElement;
+import com.android.tools.r8.ir.analysis.type.TypeElement;
 
 public class StackValue extends Value {
 
   private final int height;
   private final TypeInfo typeInfo;
 
-  private StackValue(TypeInfo typeInfo, TypeLatticeElement typeLattice, int height) {
-    super(Value.UNDEFINED_NUMBER, typeLattice, null);
+  private StackValue(TypeInfo typeInfo, TypeElement type, int height) {
+    super(Value.UNDEFINED_NUMBER, type, null);
     this.height = height;
     this.typeInfo = typeInfo;
     assert height >= 0;
@@ -23,7 +23,7 @@
   public static StackValue create(TypeInfo typeInfo, int height, AppView<?> appView) {
     return new StackValue(
         typeInfo,
-        TypeLatticeElement.fromDexType(typeInfo.getDexType(), Nullability.maybeNull(), appView),
+        TypeElement.fromDexType(typeInfo.getDexType(), Nullability.maybeNull(), appView),
         height);
   }
 
@@ -36,7 +36,7 @@
   }
 
   public StackValue duplicate(int height) {
-    return new StackValue(typeInfo, getTypeLattice(), height);
+    return new StackValue(typeInfo, getType(), height);
   }
 
   @Override
diff --git a/src/main/java/com/android/tools/r8/ir/code/StackValues.java b/src/main/java/com/android/tools/r8/ir/code/StackValues.java
index 528cddd..d0c56eb 100644
--- a/src/main/java/com/android/tools/r8/ir/code/StackValues.java
+++ b/src/main/java/com/android/tools/r8/ir/code/StackValues.java
@@ -5,7 +5,7 @@
 package com.android.tools.r8.ir.code;
 
 import com.android.tools.r8.errors.Unreachable;
-import com.android.tools.r8.ir.analysis.type.TypeLatticeElement;
+import com.android.tools.r8.ir.analysis.type.TypeElement;
 
 /**
  * {@link StackValues} allow us to represent stack operations that produces two or more elements on
@@ -16,7 +16,7 @@
   private final StackValue[] stackValues;
 
   public StackValues(StackValue... stackValues) {
-    super(Value.UNDEFINED_NUMBER, TypeLatticeElement.getBottom(), null);
+    super(Value.UNDEFINED_NUMBER, TypeElement.getBottom(), null);
     this.stackValues = stackValues;
     assert stackValues.length >= 2;
   }
@@ -55,7 +55,7 @@
   }
 
   @Override
-  public TypeLatticeElement getTypeLattice() {
+  public TypeElement getType() {
     throw new Unreachable();
   }
 }
diff --git a/src/main/java/com/android/tools/r8/ir/code/StaticGet.java b/src/main/java/com/android/tools/r8/ir/code/StaticGet.java
index a3c202b..2c246f92 100644
--- a/src/main/java/com/android/tools/r8/ir/code/StaticGet.java
+++ b/src/main/java/com/android/tools/r8/ir/code/StaticGet.java
@@ -22,7 +22,7 @@
 import com.android.tools.r8.ir.analysis.ClassInitializationAnalysis.AnalysisAssumption;
 import com.android.tools.r8.ir.analysis.ClassInitializationAnalysis.Query;
 import com.android.tools.r8.ir.analysis.type.Nullability;
-import com.android.tools.r8.ir.analysis.type.TypeLatticeElement;
+import com.android.tools.r8.ir.analysis.type.TypeElement;
 import com.android.tools.r8.ir.conversion.CfBuilder;
 import com.android.tools.r8.ir.conversion.DexBuilder;
 import com.android.tools.r8.ir.optimize.Inliner.ConstraintWithTarget;
@@ -40,7 +40,7 @@
     Value newValue =
         new Value(
             code.valueNumberGenerator.next(),
-            original.outValue().getTypeLattice(),
+            original.outValue().getType(),
             original.getLocalInfo());
     return copyOf(newValue, original);
   }
@@ -71,22 +71,25 @@
 
   @Override
   public boolean couldIntroduceAnAlias(AppView<?> appView, Value root) {
-    assert root != null && root.getTypeLattice().isReference();
+    assert root != null && root.getType().isReferenceType();
     assert outValue != null;
-    TypeLatticeElement outType = outValue.getTypeLattice();
-    if (outType.isPrimitive()) {
+    TypeElement outType = outValue.getType();
+    if (outType.isPrimitiveType()) {
       return false;
     }
     if (appView.appInfo().hasSubtyping()) {
       if (outType.isClassType()
-          && root.getTypeLattice().isClassType()
-          && appView.appInfo().withSubtyping().inDifferentHierarchy(
-              outType.asClassTypeLatticeElement().getClassType(),
-              root.getTypeLattice().asClassTypeLatticeElement().getClassType())) {
+          && root.getType().isClassType()
+          && appView
+              .appInfo()
+              .withSubtyping()
+              .inDifferentHierarchy(
+                  outType.asClassType().getClassType(),
+                  root.getType().asClassType().getClassType())) {
         return false;
       }
     }
-    return outType.isReference();
+    return outType.isReferenceType();
   }
 
   @Override
@@ -219,8 +222,8 @@
   }
 
   @Override
-  public TypeLatticeElement evaluate(AppView<?> appView) {
-    return TypeLatticeElement.fromDexType(getField().type, Nullability.maybeNull(), appView);
+  public TypeElement evaluate(AppView<?> appView) {
+    return TypeElement.fromDexType(getField().type, Nullability.maybeNull(), appView);
   }
 
   @Override
diff --git a/src/main/java/com/android/tools/r8/ir/code/StaticPut.java b/src/main/java/com/android/tools/r8/ir/code/StaticPut.java
index e7d2bdc..16c102b 100644
--- a/src/main/java/com/android/tools/r8/ir/code/StaticPut.java
+++ b/src/main/java/com/android/tools/r8/ir/code/StaticPut.java
@@ -168,7 +168,7 @@
       // If the value being written by this instruction is an array, then make sure that the value
       // being written by the other instruction is the exact same value. Otherwise, the verifier
       // may incorrectly join the types of these arrays to Object[].
-      if (value().getTypeLattice().isArrayType() && value() != staticPut.value()) {
+      if (value().getType().isArrayType() && value() != staticPut.value()) {
         return false;
       }
     }
diff --git a/src/main/java/com/android/tools/r8/ir/code/Store.java b/src/main/java/com/android/tools/r8/ir/code/Store.java
index 72eb683..4fe63c0 100644
--- a/src/main/java/com/android/tools/r8/ir/code/Store.java
+++ b/src/main/java/com/android/tools/r8/ir/code/Store.java
@@ -11,7 +11,7 @@
 import com.android.tools.r8.errors.Unreachable;
 import com.android.tools.r8.graph.AppView;
 import com.android.tools.r8.graph.DexType;
-import com.android.tools.r8.ir.analysis.type.TypeLatticeElement;
+import com.android.tools.r8.ir.analysis.type.TypeElement;
 import com.android.tools.r8.ir.conversion.CfBuilder;
 import com.android.tools.r8.ir.conversion.DexBuilder;
 import com.android.tools.r8.ir.optimize.Inliner.ConstraintWithTarget;
@@ -89,8 +89,8 @@
   }
 
   @Override
-  public TypeLatticeElement evaluate(AppView<?> appView) {
-    return src().getTypeLattice();
+  public TypeElement evaluate(AppView<?> appView) {
+    return src().getType();
   }
 
   @Override
diff --git a/src/main/java/com/android/tools/r8/ir/code/Swap.java b/src/main/java/com/android/tools/r8/ir/code/Swap.java
index 8cdaa77..2515934 100644
--- a/src/main/java/com/android/tools/r8/ir/code/Swap.java
+++ b/src/main/java/com/android/tools/r8/ir/code/Swap.java
@@ -26,7 +26,7 @@
     super(dest, ImmutableList.of(src1, src2));
     assert src1.isValueOnStack() && !(src1 instanceof StackValues);
     assert src2.isValueOnStack() && !(src2 instanceof StackValues);
-    assert !src1.getTypeLattice().isWidePrimitive() && !src2.getTypeLattice().isWidePrimitive();
+    assert !src1.getType().isWidePrimitive() && !src2.getType().isWidePrimitive();
   }
 
   @Override
diff --git a/src/main/java/com/android/tools/r8/ir/code/Throw.java b/src/main/java/com/android/tools/r8/ir/code/Throw.java
index 115e9fd..2b6b80d 100644
--- a/src/main/java/com/android/tools/r8/ir/code/Throw.java
+++ b/src/main/java/com/android/tools/r8/ir/code/Throw.java
@@ -8,7 +8,7 @@
 import com.android.tools.r8.dex.Constants;
 import com.android.tools.r8.graph.AppView;
 import com.android.tools.r8.graph.DexType;
-import com.android.tools.r8.ir.analysis.type.TypeLatticeElement;
+import com.android.tools.r8.ir.analysis.type.TypeElement;
 import com.android.tools.r8.ir.conversion.CfBuilder;
 import com.android.tools.r8.ir.conversion.DexBuilder;
 import com.android.tools.r8.ir.optimize.Inliner.ConstraintWithTarget;
@@ -91,7 +91,7 @@
     if (exception() == value) {
       return true;
     }
-    TypeLatticeElement exceptionType = exception().getTypeLattice();
+    TypeElement exceptionType = exception().getType();
     if (exceptionType.isNullType()) {
       // throw null
       return true;
diff --git a/src/main/java/com/android/tools/r8/ir/code/TypeAndLocalInfoSupplier.java b/src/main/java/com/android/tools/r8/ir/code/TypeAndLocalInfoSupplier.java
index 0d5cfd6..d28407f 100644
--- a/src/main/java/com/android/tools/r8/ir/code/TypeAndLocalInfoSupplier.java
+++ b/src/main/java/com/android/tools/r8/ir/code/TypeAndLocalInfoSupplier.java
@@ -5,13 +5,14 @@
 package com.android.tools.r8.ir.code;
 
 import com.android.tools.r8.graph.DebugLocalInfo;
-import com.android.tools.r8.ir.analysis.type.TypeLatticeElement;
+import com.android.tools.r8.ir.analysis.type.TypeElement;
 
 public interface TypeAndLocalInfoSupplier {
   DebugLocalInfo getLocalInfo();
-  TypeLatticeElement getTypeLattice();
 
-  static TypeAndLocalInfoSupplier create(TypeLatticeElement type, DebugLocalInfo local) {
+  TypeElement getOutType();
+
+  static TypeAndLocalInfoSupplier create(TypeElement type, DebugLocalInfo local) {
     return new TypeAndLocalInfoSupplier() {
 
       @Override
@@ -20,7 +21,7 @@
       }
 
       @Override
-      public TypeLatticeElement getTypeLattice() {
+      public TypeElement getOutType() {
         return type;
       }
     };
diff --git a/src/main/java/com/android/tools/r8/ir/code/Unop.java b/src/main/java/com/android/tools/r8/ir/code/Unop.java
index 57fad2d..1765626 100644
--- a/src/main/java/com/android/tools/r8/ir/code/Unop.java
+++ b/src/main/java/com/android/tools/r8/ir/code/Unop.java
@@ -7,7 +7,7 @@
 import com.android.tools.r8.dex.Constants;
 import com.android.tools.r8.graph.AppView;
 import com.android.tools.r8.graph.DexType;
-import com.android.tools.r8.ir.analysis.type.TypeLatticeElement;
+import com.android.tools.r8.ir.analysis.type.TypeElement;
 import com.android.tools.r8.ir.optimize.Inliner.ConstraintWithTarget;
 import com.android.tools.r8.ir.optimize.InliningConstraints;
 
@@ -58,8 +58,8 @@
   }
 
   @Override
-  public TypeLatticeElement evaluate(AppView<?> appView) {
-    return source().getTypeLattice();
+  public TypeElement evaluate(AppView<?> appView) {
+    return source().getType();
   }
 
   @Override
diff --git a/src/main/java/com/android/tools/r8/ir/code/Value.java b/src/main/java/com/android/tools/r8/ir/code/Value.java
index 25ed39f..e84f64a 100644
--- a/src/main/java/com/android/tools/r8/ir/code/Value.java
+++ b/src/main/java/com/android/tools/r8/ir/code/Value.java
@@ -20,8 +20,8 @@
 import com.android.tools.r8.graph.DexClass;
 import com.android.tools.r8.graph.DexMethod;
 import com.android.tools.r8.graph.DexType;
-import com.android.tools.r8.ir.analysis.type.ClassTypeLatticeElement;
-import com.android.tools.r8.ir.analysis.type.TypeLatticeElement;
+import com.android.tools.r8.ir.analysis.type.ClassTypeElement;
+import com.android.tools.r8.ir.analysis.type.TypeElement;
 import com.android.tools.r8.ir.analysis.value.AbstractValue;
 import com.android.tools.r8.ir.analysis.value.UnknownValue;
 import com.android.tools.r8.ir.regalloc.LiveIntervals;
@@ -52,81 +52,80 @@
 
   public void constrainType(
       ValueTypeConstraint constraint, DexMethod method, Origin origin, Reporter reporter) {
-    TypeLatticeElement constrainedType = constrainedType(constraint);
+    TypeElement constrainedType = constrainedType(constraint);
     if (constrainedType == null) {
       throw reporter.fatalError(
           new StringDiagnostic(
               "Cannot constrain type: "
-                  + typeLattice
+                  + type
                   + " for value: "
                   + this
                   + " by constraint: "
                   + constraint,
               origin,
               new MethodPosition(method)));
-    } else if (constrainedType != typeLattice) {
-      setTypeLattice(constrainedType);
+    } else if (constrainedType != type) {
+      setType(constrainedType);
     }
   }
 
-  public TypeLatticeElement constrainedType(ValueTypeConstraint constraint) {
-    if (constraint == ValueTypeConstraint.INT_OR_FLOAT_OR_OBJECT
-        && !typeLattice.isWidePrimitive()) {
-      return typeLattice;
+  public TypeElement constrainedType(ValueTypeConstraint constraint) {
+    if (constraint == ValueTypeConstraint.INT_OR_FLOAT_OR_OBJECT && !type.isWidePrimitive()) {
+      return type;
     }
     switch (constraint) {
       case OBJECT:
-        if (typeLattice.isTop()) {
+        if (type.isTop()) {
           if (definition != null && definition.isConstNumber()) {
             assert definition.asConstNumber().isZero();
-            return TypeLatticeElement.getNull();
+            return TypeElement.getNull();
           } else {
-            return TypeLatticeElement.getBottom();
+            return TypeElement.getBottom();
           }
         }
-        if (typeLattice.isReference()) {
-          return typeLattice;
+        if (type.isReferenceType()) {
+          return type;
         }
-        if (typeLattice.isBottom()) {
+        if (type.isBottom()) {
           // Only a few instructions may propagate a bottom input to a bottom output.
           assert isPhi()
               || definition.isDebugLocalWrite()
               || (definition.isArrayGet()
                   && definition.asArrayGet().getMemberType() == MemberType.OBJECT);
-          return typeLattice;
+          return type;
         }
         break;
       case INT:
-        if (typeLattice.isTop() || (typeLattice.isSinglePrimitive() && !typeLattice.isFloat())) {
-          return TypeLatticeElement.getInt();
+        if (type.isTop() || (type.isSinglePrimitive() && !type.isFloat())) {
+          return TypeElement.getInt();
         }
         break;
       case FLOAT:
-        if (typeLattice.isTop() || (typeLattice.isSinglePrimitive() && !typeLattice.isInt())) {
-          return TypeLatticeElement.getFloat();
+        if (type.isTop() || (type.isSinglePrimitive() && !type.isInt())) {
+          return TypeElement.getFloat();
         }
         break;
       case INT_OR_FLOAT:
-        if (typeLattice.isTop()) {
-          return TypeLatticeElement.getSingle();
+        if (type.isTop()) {
+          return TypeElement.getSingle();
         }
-        if (typeLattice.isSinglePrimitive()) {
-          return typeLattice;
+        if (type.isSinglePrimitive()) {
+          return type;
         }
         break;
       case LONG:
-        if (typeLattice.isWidePrimitive() && !typeLattice.isDouble()) {
-          return TypeLatticeElement.getLong();
+        if (type.isWidePrimitive() && !type.isDouble()) {
+          return TypeElement.getLong();
         }
         break;
       case DOUBLE:
-        if (typeLattice.isWidePrimitive() && !typeLattice.isLong()) {
-          return TypeLatticeElement.getDouble();
+        if (type.isWidePrimitive() && !type.isLong()) {
+          return TypeElement.getDouble();
         }
         break;
       case LONG_OR_DOUBLE:
-        if (typeLattice.isWidePrimitive()) {
-          return typeLattice;
+        if (type.isWidePrimitive()) {
+          return type;
         }
         break;
       default:
@@ -212,8 +211,7 @@
 
   public static final int UNDEFINED_NUMBER = -1;
 
-  public static final Value UNDEFINED =
-      new Value(UNDEFINED_NUMBER, TypeLatticeElement.getBottom(), null);
+  public static final Value UNDEFINED = new Value(UNDEFINED_NUMBER, TypeElement.getBottom(), null);
 
   protected final int number;
   public Instruction definition = null;
@@ -228,13 +226,13 @@
   private boolean isThis = false;
   private LongInterval valueRange;
   private DebugData debugData;
-  protected TypeLatticeElement typeLattice;
+  protected TypeElement type;
 
-  public Value(int number, TypeLatticeElement typeLattice, DebugLocalInfo local) {
-    assert typeLattice != null;
+  public Value(int number, TypeElement type, DebugLocalInfo local) {
+    assert type != null;
     this.number = number;
     this.debugData = local == null ? null : new DebugData(local);
-    this.typeLattice = typeLattice;
+    this.type = type;
   }
 
   public boolean isFixedRegisterValue() {
@@ -310,7 +308,7 @@
   }
 
   public int requiredRegisters() {
-    return typeLattice.requiredRegisters();
+    return type.requiredRegisters();
   }
 
   public DebugLocalInfo getLocalInfo() {
@@ -553,14 +551,11 @@
       // Not always null as the value can be changed via the debugger.
       return false;
     }
-    if (typeLattice.isDefinitelyNull()) {
+    if (type.isDefinitelyNull()) {
       return true;
     }
-    if (typeLattice.isClassType() && appView.appInfo().hasLiveness()) {
-      return typeLattice
-          .asClassTypeLatticeElement()
-          .getClassType()
-          .isAlwaysNull(appView.withLiveness());
+    if (type.isClassType() && appView.appInfo().hasLiveness()) {
+      return type.asClassType().getClassType().isAlwaysNull(appView.withLiveness());
     }
     return false;
   }
@@ -831,7 +826,7 @@
       builder.append("(");
       if (isConstant && definition.asConstNumber().outValue != null) {
         ConstNumber constNumber = definition.asConstNumber();
-        if (constNumber.outValue().getTypeLattice().isSinglePrimitive()) {
+        if (constNumber.outValue().getType().isSinglePrimitive()) {
           builder.append((int) constNumber.getRawValue());
         } else {
           builder.append(constNumber.getRawValue());
@@ -852,7 +847,7 @@
   }
 
   public ValueType outType() {
-    return ValueType.fromTypeLattice(typeLattice);
+    return ValueType.fromType(type);
   }
 
   public ConstInstruction getConstInstruction() {
@@ -921,9 +916,9 @@
    * Returns whether this value is known to never be <code>null</code>.
    */
   public boolean isNeverNull() {
-    assert typeLattice.isReference();
+    assert type.isReferenceType();
     return (definition != null && definition.isAssumeNonNull())
-        || typeLattice.nullability().isDefinitelyNotNull();
+        || type.nullability().isDefinitelyNotNull();
   }
 
   public boolean isArgument() {
@@ -956,7 +951,7 @@
   }
 
   public boolean knownToBeBoolean(Set<Phi> seen) {
-    if (!getTypeLattice().isInt()) {
+    if (!getType().isInt()) {
       return false;
     }
 
@@ -1014,11 +1009,11 @@
 
   public LongInterval getValueRange() {
     if (isConstNumber()) {
-      if (typeLattice.isSinglePrimitive()) {
+      if (type.isSinglePrimitive()) {
         int value = getConstInstruction().asConstNumber().getIntValue();
         return new LongInterval(value, value);
       } else {
-        assert typeLattice.isWidePrimitive();
+        assert type.isWidePrimitive();
         long value = getConstInstruction().asConstNumber().getLongValue();
         return new LongInterval(value, value);
       }
@@ -1096,45 +1091,44 @@
    *
    * @param newType The new type lattice element
    */
-  public void setTypeLattice(TypeLatticeElement newType) {
+  public void setType(TypeElement newType) {
     assert newType != null;
-    typeLattice = newType;
+    type = newType;
   }
 
-  public void widening(AppView<?> appView, TypeLatticeElement newType) {
+  public void widening(AppView<?> appView, TypeElement newType) {
     // During WIDENING (due to fix-point iteration), type update is monotonically upwards,
     //   i.e., towards something wider.
-    assert this.typeLattice.lessThanOrEqual(newType, appView)
+    assert this.type.lessThanOrEqual(newType, appView)
         : "During WIDENING, "
             + newType
             + " < "
-            + typeLattice
+            + type
             + " at "
             + (isPhi() ? asPhi().printPhi() : definition.toString());
-    setTypeLattice(newType);
+    setType(newType);
   }
 
-  public void narrowing(AppView<?> appView, TypeLatticeElement newType) {
+  public void narrowing(AppView<?> appView, TypeElement newType) {
     // During NARROWING (e.g., after inlining), type update is monotonically downwards,
     //   i.e., towards something narrower, with more specific type info.
     assert (!appView.options().testing.enableNarrowingChecksInD8
                 && !appView.enableWholeProgramOptimizations())
-            || !this.typeLattice.strictlyLessThan(newType, appView)
+            || !this.type.strictlyLessThan(newType, appView)
         : "During NARROWING, "
-            + typeLattice
+            + type
             + " < "
             + newType
             + " at "
             + (isPhi() ? asPhi().printPhi() : definition.toString());
-    setTypeLattice(newType);
+    setType(newType);
   }
 
-  public TypeLatticeElement getTypeLattice() {
-    return typeLattice;
+  public TypeElement getType() {
+    return type;
   }
 
-  public TypeLatticeElement getDynamicUpperBoundType(
-      AppView<? extends AppInfoWithSubtyping> appView) {
+  public TypeElement getDynamicUpperBoundType(AppView<? extends AppInfoWithSubtyping> appView) {
     Value root = getAliasedValue();
     if (root.isPhi()) {
       assert getSpecificAliasedValue(
@@ -1147,7 +1141,7 @@
     // Assume<DynamicTypeAssumption>.
     Value aliasedValue =
         getSpecificAliasedValue(value -> !value.isPhi() && value.definition.isAssumeDynamicType());
-    TypeLatticeElement lattice;
+    TypeElement lattice;
     if (aliasedValue != null) {
       // If there is an alias of the receiver, which is defined by an Assume<DynamicTypeAssumption>
       // instruction, then use the dynamic type as the refined receiver type.
@@ -1155,25 +1149,24 @@
           aliasedValue.definition.asAssumeDynamicType().getAssumption().getDynamicUpperBoundType();
 
       // For precision, verify that the dynamic type is at least as precise as the static type.
-      assert lattice.lessThanOrEqualUpToNullability(typeLattice, appView)
-          : typeLattice + " < " + lattice;
+      assert lattice.lessThanOrEqualUpToNullability(type, appView) : type + " < " + lattice;
     } else {
       // Otherwise, simply use the static type.
-      lattice = typeLattice;
+      lattice = type;
     }
 
     // Account for nullability, which could be flown from non-null assumption in between dynamic
     // type assumption or simply from array/object creation.
-    if (typeLattice.isDefinitelyNotNull() && lattice.isNullable()) {
+    if (type.isDefinitelyNotNull() && lattice.isNullable()) {
       // Having non-null assumption means it is a reference type.
-      assert lattice.isReference();
+      assert lattice.isReferenceType();
       // Then, we can return the non-null variant of dynamic type if both assumptions are aliased.
-      return lattice.asReferenceTypeLatticeElement().asMeetWithNotNull();
+      return lattice.asReferenceType().asMeetWithNotNull();
     }
     return lattice;
   }
 
-  public ClassTypeLatticeElement getDynamicLowerBoundType(AppView<AppInfoWithLiveness> appView) {
+  public ClassTypeElement getDynamicLowerBoundType(AppView<AppInfoWithLiveness> appView) {
     Value root = getAliasedValue();
     if (root.isPhi()) {
       return null;
@@ -1184,7 +1177,7 @@
       DexType type = definition.asNewInstance().clazz;
       DexClass clazz = appView.definitionFor(type);
       if (clazz != null && !clazz.isInterface()) {
-        return ClassTypeLatticeElement.create(type, definitelyNotNull(), appView);
+        return ClassTypeElement.create(type, definitelyNotNull(), appView);
       }
       return null;
     }
@@ -1193,16 +1186,16 @@
     // Assume<DynamicTypeAssumption>.
     Value aliasedValue = getSpecificAliasedValue(value -> value.definition.isAssumeDynamicType());
     if (aliasedValue != null) {
-      ClassTypeLatticeElement lattice =
+      ClassTypeElement lattice =
           aliasedValue.definition.asAssumeDynamicType().getAssumption().getDynamicLowerBoundType();
-      return lattice != null && typeLattice.isDefinitelyNotNull() && lattice.isNullable()
+      return lattice != null && type.isDefinitelyNotNull() && lattice.isNullable()
           ? lattice.asMeetWithNotNull()
           : lattice;
     }
 
     // If it is a final or effectively-final class type, then we know the lower bound.
-    if (getTypeLattice().isClassType()) {
-      ClassTypeLatticeElement classType = getTypeLattice().asClassTypeLatticeElement();
+    if (getType().isClassType()) {
+      ClassTypeElement classType = getType().asClassType();
       DexType type = classType.getClassType();
       DexClass clazz = appView.definitionFor(type);
       if (clazz != null && clazz.isEffectivelyFinal(appView)) {
diff --git a/src/main/java/com/android/tools/r8/ir/code/ValueType.java b/src/main/java/com/android/tools/r8/ir/code/ValueType.java
index 865c7d4..729cdcf 100644
--- a/src/main/java/com/android/tools/r8/ir/code/ValueType.java
+++ b/src/main/java/com/android/tools/r8/ir/code/ValueType.java
@@ -7,8 +7,8 @@
 import com.android.tools.r8.errors.InternalCompilerError;
 import com.android.tools.r8.errors.Unreachable;
 import com.android.tools.r8.graph.DexType;
-import com.android.tools.r8.ir.analysis.type.PrimitiveTypeLatticeElement;
-import com.android.tools.r8.ir.analysis.type.TypeLatticeElement;
+import com.android.tools.r8.ir.analysis.type.PrimitiveTypeElement;
+import com.android.tools.r8.ir.analysis.type.TypeElement;
 
 public enum ValueType {
   OBJECT,
@@ -102,35 +102,35 @@
     }
   }
 
-  public static ValueType fromTypeLattice(TypeLatticeElement typeLatticeElement) {
-    if (typeLatticeElement.isReference()) {
+  public static ValueType fromType(TypeElement type) {
+    if (type.isReferenceType()) {
       return OBJECT;
     }
-    if (typeLatticeElement.isInt()) {
+    if (type.isInt()) {
       return INT;
     }
-    if (typeLatticeElement.isFloat()) {
+    if (type.isFloat()) {
       return FLOAT;
     }
-    if (typeLatticeElement.isLong()) {
+    if (type.isLong()) {
       return LONG;
     }
-    if (typeLatticeElement.isDouble()) {
+    if (type.isDouble()) {
       return DOUBLE;
     }
-    throw new Unreachable("Unexpected conversion of imprecise type: " + typeLatticeElement);
+    throw new Unreachable("Unexpected conversion of imprecise type: " + type);
   }
 
-  public PrimitiveTypeLatticeElement toPrimitiveTypeLattice() {
+  public PrimitiveTypeElement toPrimitiveType() {
     switch (this) {
       case INT:
-        return TypeLatticeElement.getInt();
+        return TypeElement.getInt();
       case FLOAT:
-        return TypeLatticeElement.getFloat();
+        return TypeElement.getFloat();
       case LONG:
-        return TypeLatticeElement.getLong();
+        return TypeElement.getLong();
       case DOUBLE:
-        return TypeLatticeElement.getDouble();
+        return TypeElement.getDouble();
       default:
         throw new Unreachable("Unexpected type in conversion to primitive: " + this);
     }
diff --git a/src/main/java/com/android/tools/r8/ir/code/ValueTypeConstraint.java b/src/main/java/com/android/tools/r8/ir/code/ValueTypeConstraint.java
index 1f7f97b..13cd1ed 100644
--- a/src/main/java/com/android/tools/r8/ir/code/ValueTypeConstraint.java
+++ b/src/main/java/com/android/tools/r8/ir/code/ValueTypeConstraint.java
@@ -7,8 +7,8 @@
 import com.android.tools.r8.errors.InternalCompilerError;
 import com.android.tools.r8.errors.Unreachable;
 import com.android.tools.r8.graph.DexType;
-import com.android.tools.r8.ir.analysis.type.PrimitiveTypeLatticeElement;
-import com.android.tools.r8.ir.analysis.type.TypeLatticeElement;
+import com.android.tools.r8.ir.analysis.type.PrimitiveTypeElement;
+import com.android.tools.r8.ir.analysis.type.TypeElement;
 
 public enum ValueTypeConstraint {
   OBJECT,
@@ -129,44 +129,44 @@
     }
   }
 
-  public static ValueTypeConstraint fromTypeLattice(TypeLatticeElement typeLatticeElement) {
-    if (typeLatticeElement.isReference()) {
+  public static ValueTypeConstraint fromTypeLattice(TypeElement typeElement) {
+    if (typeElement.isReferenceType()) {
       return OBJECT;
     }
-    if (typeLatticeElement.isFineGrainedType() || typeLatticeElement.isInt()) {
+    if (typeElement.isFineGrainedType() || typeElement.isInt()) {
       return INT;
     }
-    if (typeLatticeElement.isFloat()) {
+    if (typeElement.isFloat()) {
       return FLOAT;
     }
-    if (typeLatticeElement.isLong()) {
+    if (typeElement.isLong()) {
       return LONG;
     }
-    if (typeLatticeElement.isDouble()) {
+    if (typeElement.isDouble()) {
       return DOUBLE;
     }
-    if (typeLatticeElement.isSinglePrimitive()) {
+    if (typeElement.isSinglePrimitive()) {
       return INT_OR_FLOAT;
     }
-    if (typeLatticeElement.isWidePrimitive()) {
+    if (typeElement.isWidePrimitive()) {
       return LONG_OR_DOUBLE;
     }
-    if (typeLatticeElement.isTop()) {
+    if (typeElement.isTop()) {
       return INT_OR_FLOAT_OR_OBJECT;
     }
-    throw new Unreachable("Unexpected conversion of type: " + typeLatticeElement);
+    throw new Unreachable("Unexpected conversion of type: " + typeElement);
   }
 
-  public PrimitiveTypeLatticeElement toPrimitiveTypeLattice() {
+  public PrimitiveTypeElement toPrimitiveType() {
     switch (this) {
       case INT:
-        return TypeLatticeElement.getInt();
+        return TypeElement.getInt();
       case FLOAT:
-        return TypeLatticeElement.getFloat();
+        return TypeElement.getFloat();
       case LONG:
-        return TypeLatticeElement.getLong();
+        return TypeElement.getLong();
       case DOUBLE:
-        return TypeLatticeElement.getDouble();
+        return TypeElement.getDouble();
       default:
         throw new Unreachable("Unexpected type in conversion to primitive: " + this);
     }
diff --git a/src/main/java/com/android/tools/r8/ir/conversion/CfBuilder.java b/src/main/java/com/android/tools/r8/ir/conversion/CfBuilder.java
index a16d39d..ae3e7cd 100644
--- a/src/main/java/com/android/tools/r8/ir/conversion/CfBuilder.java
+++ b/src/main/java/com/android/tools/r8/ir/conversion/CfBuilder.java
@@ -27,7 +27,7 @@
 import com.android.tools.r8.graph.DexEncodedMethod;
 import com.android.tools.r8.graph.DexField;
 import com.android.tools.r8.graph.DexType;
-import com.android.tools.r8.ir.analysis.type.TypeLatticeElement;
+import com.android.tools.r8.ir.analysis.type.TypeElement;
 import com.android.tools.r8.ir.code.Argument;
 import com.android.tools.r8.ir.code.BasicBlock;
 import com.android.tools.r8.ir.code.CatchHandlers;
@@ -234,7 +234,7 @@
 
         // Insert ConstNumber(v, -1) before Not.
         it.previous();
-        Value constValue = code.createValue(inValue.getTypeLattice());
+        Value constValue = code.createValue(inValue.getType());
         Instruction newInstruction = new ConstNumber(constValue, -1);
         newInstruction.setBlock(block);
         newInstruction.setPosition(current.getPosition());
@@ -386,7 +386,7 @@
             || constNumber == null
             || add == null
             || store == null
-            || constNumber.outValue().getTypeLattice() != TypeLatticeElement.getInt()) {
+            || constNumber.outValue().getType() != TypeElement.getInt()) {
           it.next();
           continue;
         }
diff --git a/src/main/java/com/android/tools/r8/ir/conversion/DexBuilder.java b/src/main/java/com/android/tools/r8/ir/conversion/DexBuilder.java
index 8af9977..a4ca85a 100644
--- a/src/main/java/com/android/tools/r8/ir/conversion/DexBuilder.java
+++ b/src/main/java/com/android/tools/r8/ir/conversion/DexBuilder.java
@@ -42,7 +42,7 @@
 import com.android.tools.r8.graph.DexCode.TryHandler.TypeAddrPair;
 import com.android.tools.r8.graph.DexDebugEventBuilder;
 import com.android.tools.r8.graph.DexType;
-import com.android.tools.r8.ir.analysis.type.TypeLatticeElement;
+import com.android.tools.r8.ir.analysis.type.TypeElement;
 import com.android.tools.r8.ir.code.Argument;
 import com.android.tools.r8.ir.code.BasicBlock;
 import com.android.tools.r8.ir.code.CatchHandlers;
@@ -1404,7 +1404,7 @@
     @Override
     public void addInstructions(DexBuilder builder, List<Instruction> instructions) {
       Move move = getMove();
-      TypeLatticeElement moveType = move.outValue().getTypeLattice();
+      TypeElement moveType = move.outValue().getType();
       int src = srcRegister(builder);
       int dest = destRegister(builder);
       Instruction instruction;
@@ -1418,7 +1418,7 @@
             instruction = new com.android.tools.r8.code.Move(dest, src);
           } else if (moveType.isWidePrimitive()) {
             instruction = new MoveWide(dest, src);
-          } else if (moveType.isReference()) {
+          } else if (moveType.isReferenceType()) {
             instruction = new MoveObject(dest, src);
           } else {
             throw new Unreachable("Unexpected type: " + move.outType());
@@ -1429,7 +1429,7 @@
             instruction = new MoveFrom16(dest, src);
           } else if (moveType.isWidePrimitive()) {
             instruction = new MoveWideFrom16(dest, src);
-          } else if (moveType.isReference()) {
+          } else if (moveType.isReferenceType()) {
             instruction = new MoveObjectFrom16(dest, src);
           } else {
             throw new Unreachable("Unexpected type: " + move.outType());
@@ -1440,7 +1440,7 @@
             instruction = new Move16(dest, src);
           } else if (moveType.isWidePrimitive()) {
             instruction = new MoveWide16(dest, src);
-          } else if (moveType.isReference()) {
+          } else if (moveType.isReferenceType()) {
             instruction = new MoveObject16(dest, src);
           } else {
             throw new Unreachable("Unexpected type: " + move.outType());
diff --git a/src/main/java/com/android/tools/r8/ir/conversion/FieldOptimizationFeedback.java b/src/main/java/com/android/tools/r8/ir/conversion/FieldOptimizationFeedback.java
index a6d6267..c6070c5 100644
--- a/src/main/java/com/android/tools/r8/ir/conversion/FieldOptimizationFeedback.java
+++ b/src/main/java/com/android/tools/r8/ir/conversion/FieldOptimizationFeedback.java
@@ -6,8 +6,8 @@
 
 import com.android.tools.r8.graph.AppView;
 import com.android.tools.r8.graph.DexEncodedField;
-import com.android.tools.r8.ir.analysis.type.ClassTypeLatticeElement;
-import com.android.tools.r8.ir.analysis.type.TypeLatticeElement;
+import com.android.tools.r8.ir.analysis.type.ClassTypeElement;
+import com.android.tools.r8.ir.analysis.type.TypeElement;
 import com.android.tools.r8.ir.analysis.value.AbstractValue;
 import com.android.tools.r8.shaking.AppInfoWithLiveness;
 
@@ -19,9 +19,9 @@
 
   void markFieldAsPropagated(DexEncodedField field);
 
-  void markFieldHasDynamicLowerBoundType(DexEncodedField field, ClassTypeLatticeElement type);
+  void markFieldHasDynamicLowerBoundType(DexEncodedField field, ClassTypeElement type);
 
-  void markFieldHasDynamicUpperBoundType(DexEncodedField field, TypeLatticeElement type);
+  void markFieldHasDynamicUpperBoundType(DexEncodedField field, TypeElement type);
 
   void markFieldBitsRead(DexEncodedField field, int bitsRead);
 
diff --git a/src/main/java/com/android/tools/r8/ir/conversion/IRBuilder.java b/src/main/java/com/android/tools/r8/ir/conversion/IRBuilder.java
index e87f619..993a69c 100644
--- a/src/main/java/com/android/tools/r8/ir/conversion/IRBuilder.java
+++ b/src/main/java/com/android/tools/r8/ir/conversion/IRBuilder.java
@@ -6,14 +6,14 @@
 
 import static com.android.tools.r8.ir.analysis.type.Nullability.definitelyNotNull;
 import static com.android.tools.r8.ir.analysis.type.Nullability.maybeNull;
-import static com.android.tools.r8.ir.analysis.type.TypeLatticeElement.getBottom;
-import static com.android.tools.r8.ir.analysis.type.TypeLatticeElement.getDouble;
-import static com.android.tools.r8.ir.analysis.type.TypeLatticeElement.getFloat;
-import static com.android.tools.r8.ir.analysis.type.TypeLatticeElement.getInt;
-import static com.android.tools.r8.ir.analysis.type.TypeLatticeElement.getLong;
-import static com.android.tools.r8.ir.analysis.type.TypeLatticeElement.getNull;
-import static com.android.tools.r8.ir.analysis.type.TypeLatticeElement.getSingle;
-import static com.android.tools.r8.ir.analysis.type.TypeLatticeElement.getWide;
+import static com.android.tools.r8.ir.analysis.type.TypeElement.getBottom;
+import static com.android.tools.r8.ir.analysis.type.TypeElement.getDouble;
+import static com.android.tools.r8.ir.analysis.type.TypeElement.getFloat;
+import static com.android.tools.r8.ir.analysis.type.TypeElement.getInt;
+import static com.android.tools.r8.ir.analysis.type.TypeElement.getLong;
+import static com.android.tools.r8.ir.analysis.type.TypeElement.getNull;
+import static com.android.tools.r8.ir.analysis.type.TypeElement.getSingle;
+import static com.android.tools.r8.ir.analysis.type.TypeElement.getWide;
 
 import com.android.tools.r8.ApiLevelException;
 import com.android.tools.r8.errors.CompilationError;
@@ -39,9 +39,9 @@
 import com.android.tools.r8.graph.RewrittenPrototypeDescription.RemovedArgumentInfo;
 import com.android.tools.r8.graph.RewrittenPrototypeDescription.RewrittenTypeInfo;
 import com.android.tools.r8.ir.analysis.type.Nullability;
-import com.android.tools.r8.ir.analysis.type.PrimitiveTypeLatticeElement;
+import com.android.tools.r8.ir.analysis.type.PrimitiveTypeElement;
 import com.android.tools.r8.ir.analysis.type.TypeAnalysis;
-import com.android.tools.r8.ir.analysis.type.TypeLatticeElement;
+import com.android.tools.r8.ir.analysis.type.TypeElement;
 import com.android.tools.r8.ir.code.Add;
 import com.android.tools.r8.ir.code.And;
 import com.android.tools.r8.ir.code.Argument;
@@ -151,7 +151,7 @@
 
   public static final int INITIAL_BLOCK_OFFSET = -1;
 
-  private static TypeLatticeElement fromMemberType(MemberType type) {
+  private static TypeElement fromMemberType(MemberType type) {
     switch (type) {
       case BOOLEAN_OR_BYTE:
       case CHAR:
@@ -176,8 +176,8 @@
     }
   }
 
-  public TypeLatticeElement getTypeLattice(DexType type, Nullability nullability) {
-    return TypeLatticeElement.fromDexType(type, nullability, appView);
+  public TypeElement getTypeLattice(DexType type, Nullability nullability) {
+    return TypeElement.fromDexType(type, nullability, appView);
   }
 
   // SSA construction uses a worklist of basic blocks reachable from the entry and their
@@ -543,13 +543,13 @@
 
     int usedArgumentIndex = 0;
     while (argumentIndex < numberOfArguments) {
-      TypeLatticeElement type;
+      TypeElement type;
       ArgumentInfo argumentInfo = argumentsInfo.getArgumentInfo(argumentIndex);
       if (argumentInfo.isRemovedArgumentInfo()) {
         RemovedArgumentInfo removedArgumentInfo = argumentInfo.asRemovedArgumentInfo();
         writeCallback.accept(register, removedArgumentInfo.getType());
         type =
-            TypeLatticeElement.fromDexType(
+            TypeElement.fromDexType(
                 removedArgumentInfo.getType(), Nullability.maybeNull(), appView);
         addConstantOrUnusedArgument(register, removedArgumentInfo);
       } else {
@@ -567,7 +567,7 @@
         }
         usedArgumentIndex++;
         writeCallback.accept(register, argType);
-        type = TypeLatticeElement.fromDexType(argType, Nullability.maybeNull(), appView);
+        type = TypeElement.fromDexType(argType, Nullability.maybeNull(), appView);
         if (argType.isBooleanType()) {
           addBooleanNonThisArgument(register);
         } else {
@@ -882,8 +882,8 @@
     int moveExceptionDest = source.getMoveExceptionRegister(targetIndex);
     Position position = source.getCanonicalDebugPositionAtOffset(moveExceptionItem.targetOffset);
     if (moveExceptionDest >= 0) {
-      TypeLatticeElement typeLattice =
-          TypeLatticeElement.fromDexType(moveExceptionItem.guard, definitelyNotNull(), appView);
+      TypeElement typeLattice =
+          TypeElement.fromDexType(moveExceptionItem.guard, definitelyNotNull(), appView);
       Value out = writeRegister(moveExceptionDest, typeLattice, ThrowingInfo.NO_THROW, null);
       MoveException moveException =
           new MoveException(out, moveExceptionItem.guard, appView.options());
@@ -932,12 +932,11 @@
   void addThisArgument(int register) {
     boolean receiverCouldBeNull = context != null && context != method;
     Nullability nullability = receiverCouldBeNull ? maybeNull() : definitelyNotNull();
-    TypeLatticeElement receiverType =
-        TypeLatticeElement.fromDexType(method.method.holder, nullability, appView);
+    TypeElement receiverType = TypeElement.fromDexType(method.method.holder, nullability, appView);
     addThisArgument(register, receiverType);
   }
 
-  public void addThisArgument(int register, TypeLatticeElement receiverType) {
+  public void addThisArgument(int register, TypeElement receiverType) {
     DebugLocalInfo local = getOutgoingLocal(register);
     Value value = writeRegister(register, receiverType, ThrowingInfo.NO_THROW, local);
     addInstruction(new Argument(value, currentBlock.size(), false));
@@ -945,7 +944,7 @@
     value.markAsThis();
   }
 
-  public void addNonThisArgument(int register, TypeLatticeElement typeLattice) {
+  public void addNonThisArgument(int register, TypeElement typeLattice) {
     DebugLocalInfo local = getOutgoingLocal(register);
     Value value = writeRegister(register, typeLattice, ThrowingInfo.NO_THROW, local);
     addNonThisArgument(new Argument(value, currentBlock.size(), false));
@@ -1010,8 +1009,7 @@
       // Note that the write register must not lookup outgoing local information and the local is
       // never considered clobbered by a start (if the in value has local info it must have been
       // marked ended elsewhere).
-      Value out = writeRegister(
-          register, incomingValue.getTypeLattice(), ThrowingInfo.NO_THROW, local);
+      Value out = writeRegister(register, incomingValue.getType(), ThrowingInfo.NO_THROW, local);
       DebugLocalWrite write = new DebugLocalWrite(out, incomingValue);
       addInstruction(write);
     }
@@ -1092,7 +1090,7 @@
   public void addArrayGet(MemberType type, int dest, int array, int index) {
     Value in1 = readRegister(array, ValueTypeConstraint.OBJECT);
     Value in2 = readRegister(index, ValueTypeConstraint.INT);
-    TypeLatticeElement typeLattice = fromMemberType(type);
+    TypeElement typeLattice = fromMemberType(type);
     Value out = writeRegister(dest, typeLattice, ThrowingInfo.CAN_THROW);
     ArrayGet instruction = new ArrayGet(type, out, in1, in2);
     assert instruction.instructionTypeCanThrow();
@@ -1123,8 +1121,8 @@
 
   public void addCheckCast(int value, DexType type) {
     Value in = readRegister(value, ValueTypeConstraint.OBJECT);
-    TypeLatticeElement castTypeLattice =
-        TypeLatticeElement.fromDexType(type, in.getTypeLattice().nullability(), appView);
+    TypeElement castTypeLattice =
+        TypeElement.fromDexType(type, in.getType().nullability(), appView);
     Value out = writeRegister(value, castTypeLattice, ThrowingInfo.CAN_THROW);
     CheckCast instruction = new CheckCast(out, in, type);
     assert instruction.instructionTypeCanThrow();
@@ -1140,7 +1138,7 @@
     add(instruction);
   }
 
-  public void addConst(TypeLatticeElement typeLattice, int dest, long value) {
+  public void addConst(TypeElement typeLattice, int dest, long value) {
     Value out = writeRegister(dest, typeLattice, ThrowingInfo.NO_THROW);
     ConstNumber instruction = new ConstNumber(out, value);
     assert !instruction.instructionTypeCanThrow();
@@ -1168,8 +1166,7 @@
   }
 
   public void addConstClass(int dest, DexType type) {
-    TypeLatticeElement typeLattice =
-        TypeLatticeElement.classClassType(appView, definitelyNotNull());
+    TypeElement typeLattice = TypeElement.classClassType(appView, definitelyNotNull());
     Value out = writeRegister(dest, typeLattice, ThrowingInfo.CAN_THROW);
     ConstClass instruction = new ConstClass(out, type);
     assert instruction.instructionTypeCanThrow();
@@ -1183,8 +1180,8 @@
           "Const-method-handle",
           null /* sourceString */);
     }
-    TypeLatticeElement typeLattice =
-        TypeLatticeElement.fromDexType(
+    TypeElement typeLattice =
+        TypeElement.fromDexType(
             appView.dexItemFactory().methodHandleType, definitelyNotNull(), appView);
     Value out = writeRegister(dest, typeLattice, ThrowingInfo.CAN_THROW);
     ConstMethodHandle instruction = new ConstMethodHandle(out, methodHandle);
@@ -1198,8 +1195,8 @@
           "Const-method-type",
           null /* sourceString */);
     }
-    TypeLatticeElement typeLattice =
-        TypeLatticeElement.fromDexType(
+    TypeElement typeLattice =
+        TypeElement.fromDexType(
             appView.dexItemFactory().methodTypeType, definitelyNotNull(), appView);
     Value out = writeRegister(dest, typeLattice, ThrowingInfo.CAN_THROW);
     ConstMethodType instruction = new ConstMethodType(out, methodType);
@@ -1213,16 +1210,14 @@
   }
 
   public void addConstString(int dest, DexString string) {
-    TypeLatticeElement typeLattice =
-        TypeLatticeElement.stringClassType(appView, definitelyNotNull());
+    TypeElement typeLattice = TypeElement.stringClassType(appView, definitelyNotNull());
     ThrowingInfo throwingInfo = throwingInfoForConstStrings();
     add(new ConstString(writeRegister(dest, typeLattice, throwingInfo), string, throwingInfo));
   }
 
   public void addDexItemBasedConstString(
       int dest, DexReference item, NameComputationInfo<?> nameComputationInfo) {
-    TypeLatticeElement typeLattice =
-        TypeLatticeElement.stringClassType(appView, definitelyNotNull());
+    TypeElement typeLattice = TypeElement.stringClassType(appView, definitelyNotNull());
     ThrowingInfo throwingInfo = throwingInfoForConstStrings();
     Value out = writeRegister(dest, typeLattice, throwingInfo);
     add(new DexItemBasedConstString(out, item, nameComputationInfo, throwingInfo));
@@ -1268,7 +1263,7 @@
       // If the move is writing to a different local we must construct a new value.
       DebugLocalInfo destLocal = getOutgoingLocal(dest);
       if (destLocal != null && destLocal != in.getLocalInfo()) {
-        Value out = writeRegister(dest, in.getTypeLattice(), ThrowingInfo.NO_THROW);
+        Value out = writeRegister(dest, in.getType(), ThrowingInfo.NO_THROW);
         addInstruction(new DebugLocalWrite(out, in));
         return;
       }
@@ -1423,7 +1418,7 @@
     Value out =
         writeRegister(
             dest,
-            TypeLatticeElement.fromDexType(field.type, maybeNull(), appView),
+            TypeElement.fromDexType(field.type, maybeNull(), appView),
             ThrowingInfo.CAN_THROW);
     InstanceGet instruction = new InstanceGet(out, in, field);
     assert instruction.instructionTypeCanThrow();
@@ -1712,11 +1707,11 @@
             : maybeNull();
     // InvokeCustom.evaluate will look into the metadata of the callsite which will provide more
     // information than just looking at the type.
-    TypeLatticeElement typeLatticeElement =
+    TypeElement typeElement =
         invoke.isInvokeCustom()
             ? invoke.evaluate(appView)
-            : TypeLatticeElement.fromDexType(outType, nullability, appView);
-    Value outValue = writeRegister(dest, typeLatticeElement, ThrowingInfo.CAN_THROW);
+            : TypeElement.fromDexType(outType, nullability, appView);
+    Value outValue = writeRegister(dest, typeElement, ThrowingInfo.CAN_THROW);
     invoke.setOutValue(outValue);
   }
 
@@ -1745,8 +1740,7 @@
   public void addNewArrayEmpty(int dest, int size, DexType type) {
     assert type.isArrayType();
     Value in = readRegister(size, ValueTypeConstraint.INT);
-    TypeLatticeElement arrayTypeLattice =
-        TypeLatticeElement.fromDexType(type, definitelyNotNull(), appView);
+    TypeElement arrayTypeLattice = TypeElement.fromDexType(type, definitelyNotNull(), appView);
     Value out = writeRegister(dest, arrayTypeLattice, ThrowingInfo.CAN_THROW);
     NewArrayEmpty instruction = new NewArrayEmpty(out, in, type);
     assert instruction.instructionTypeCanThrow();
@@ -1762,8 +1756,7 @@
   }
 
   public void addNewInstance(int dest, DexType type) {
-    TypeLatticeElement instanceType =
-        TypeLatticeElement.fromDexType(type, definitelyNotNull(), appView);
+    TypeElement instanceType = TypeElement.fromDexType(type, definitelyNotNull(), appView);
     Value out = writeRegister(dest, instanceType, ThrowingInfo.CAN_THROW);
     NewInstance instruction = new NewInstance(type, out);
     assert instruction.instructionTypeCanThrow();
@@ -1797,7 +1790,7 @@
   }
 
   public void addInitClass(int dest, DexType clazz) {
-    Value out = writeRegister(dest, TypeLatticeElement.getInt(), ThrowingInfo.CAN_THROW);
+    Value out = writeRegister(dest, TypeElement.getInt(), ThrowingInfo.CAN_THROW);
     InitClass instruction = new InitClass(out, clazz);
     assert instruction.instructionTypeCanThrow();
     addInstruction(instruction);
@@ -1807,7 +1800,7 @@
     Value out =
         writeRegister(
             dest,
-            TypeLatticeElement.fromDexType(field.type, maybeNull(), appView),
+            TypeElement.fromDexType(field.type, maybeNull(), appView),
             ThrowingInfo.CAN_THROW);
     StaticGet instruction = new StaticGet(out, field);
     assert instruction.instructionTypeCanThrow();
@@ -2142,7 +2135,7 @@
         value = getUninitializedDebugLocalValue(register, constraint);
       } else {
         DebugLocalInfo local = getIncomingLocalAtBlock(register, block);
-        TypeLatticeElement phiType = TypeConstraintResolver.typeForConstraint(constraint);
+        TypeElement phiType = TypeConstraintResolver.typeForConstraint(constraint);
         hasImpreciseValues |= !phiType.isPreciseType();
         Phi phi = new Phi(valueNumberGenerator.next(), block, phiType, local, readType);
         if (!block.isSealed()) {
@@ -2181,15 +2174,14 @@
   private Value getUninitializedDebugLocalValue(int register, ValueTypeConstraint typeConstraint) {
     // A debug initiated value must have a precise type constraint.
     assert typeConstraint.isPrecise();
-    TypeLatticeElement type =
-        typeConstraint.isObject() ? getNull() : typeConstraint.toPrimitiveTypeLattice();
+    TypeElement type = typeConstraint.isObject() ? getNull() : typeConstraint.toPrimitiveType();
     if (uninitializedDebugLocalValues == null) {
       uninitializedDebugLocalValues = new Int2ReferenceOpenHashMap<>();
     }
     List<Value> values = uninitializedDebugLocalValues.get(register);
     if (values != null) {
       for (Value value : values) {
-        if (value.getTypeLattice() == type) {
+        if (value.getType() == type) {
           return value;
         }
       }
@@ -2235,14 +2227,14 @@
   // This special write register is needed when changing the scoping of a local variable.
   // See addDebugLocalStart and addDebugLocalEnd.
   private Value writeRegister(
-      int register, TypeLatticeElement typeLattice, ThrowingInfo throwing, DebugLocalInfo local) {
+      int register, TypeElement typeLattice, ThrowingInfo throwing, DebugLocalInfo local) {
     checkRegister(register);
     Value value = new Value(valueNumberGenerator.next(), typeLattice, local);
     currentBlock.writeCurrentDefinition(register, value, throwing);
     return value;
   }
 
-  public Value writeRegister(int register, TypeLatticeElement typeLattice, ThrowingInfo throwing) {
+  public Value writeRegister(int register, TypeElement typeLattice, ThrowingInfo throwing) {
     DebugLocalInfo incomingLocal = getIncomingLocal(register);
     DebugLocalInfo outgoingLocal = getOutgoingLocal(register);
     // If the local info does not change at the current instruction, we need to ensure
@@ -2261,7 +2253,7 @@
   }
 
   public Value writeNumericRegister(int register, NumericType type, ThrowingInfo throwing) {
-    return writeRegister(register, PrimitiveTypeLatticeElement.fromNumericType(type), throwing);
+    return writeRegister(register, PrimitiveTypeElement.fromNumericType(type), throwing);
   }
 
   private DebugLocalInfo getIncomingLocal(int register) {
@@ -2340,7 +2332,7 @@
 
   private void addInstruction(Instruction ir, Position position) {
     assert verifyOutValueType(ir);
-    hasImpreciseValues |= ir.outValue() != null && !ir.outValue().getTypeLattice().isPreciseType();
+    hasImpreciseValues |= ir.outValue() != null && !ir.outValue().getType().isPreciseType();
     ir.setPosition(position);
     attachLocalValues(ir);
     currentBlock.add(ir, metadata);
@@ -2373,11 +2365,11 @@
   private boolean verifyOutValueType(Instruction ir) {
     assert ir.outValue() == null
         || ir.isArrayGet()
-        || ir.evaluate(appView) == ir.outValue().getTypeLattice();
+        || ir.evaluate(appView) == ir.outValue().getType();
     assert ir.outValue() == null
         || !ir.isArrayGet()
-        || ir.evaluate(appView) == ir.outValue().getTypeLattice()
-        || (ir.outValue().getTypeLattice().isBottom() && ir.evaluate(appView).isReference());
+        || ir.evaluate(appView) == ir.outValue().getType()
+        || (ir.outValue().getType().isBottom() && ir.evaluate(appView).isReferenceType());
     return true;
   }
 
diff --git a/src/main/java/com/android/tools/r8/ir/conversion/IRConverter.java b/src/main/java/com/android/tools/r8/ir/conversion/IRConverter.java
index d968d31..cae0c84 100644
--- a/src/main/java/com/android/tools/r8/ir/conversion/IRConverter.java
+++ b/src/main/java/com/android/tools/r8/ir/conversion/IRConverter.java
@@ -30,7 +30,7 @@
 import com.android.tools.r8.ir.analysis.fieldaccess.TrivialFieldAccessReprocessor;
 import com.android.tools.r8.ir.analysis.fieldvalueanalysis.InstanceFieldValueAnalysis;
 import com.android.tools.r8.ir.analysis.fieldvalueanalysis.StaticFieldValueAnalysis;
-import com.android.tools.r8.ir.analysis.type.TypeLatticeElement;
+import com.android.tools.r8.ir.analysis.type.TypeElement;
 import com.android.tools.r8.ir.code.AlwaysMaterializingDefinition;
 import com.android.tools.r8.ir.code.AlwaysMaterializingUser;
 import com.android.tools.r8.ir.code.BasicBlock;
@@ -45,6 +45,7 @@
 import com.android.tools.r8.ir.desugar.D8NestBasedAccessDesugaring;
 import com.android.tools.r8.ir.desugar.DesugaredLibraryAPIConverter;
 import com.android.tools.r8.ir.desugar.DesugaredLibraryAPIConverter.Mode;
+import com.android.tools.r8.ir.desugar.DesugaredLibraryRetargeter;
 import com.android.tools.r8.ir.desugar.InterfaceMethodRewriter;
 import com.android.tools.r8.ir.desugar.InterfaceMethodRewriter.Flavor;
 import com.android.tools.r8.ir.desugar.LambdaRewriter;
@@ -143,6 +144,7 @@
   private final InterfaceMethodRewriter interfaceMethodRewriter;
   private final TwrCloseResourceRewriter twrCloseResourceRewriter;
   private final BackportedMethodRewriter backportedMethodRewriter;
+  private final DesugaredLibraryRetargeter desugaredLibraryRetargeter;
   private final LambdaMerger lambdaMerger;
   private final ClassInliner classInliner;
   private final ClassStaticizer classStaticizer;
@@ -217,13 +219,16 @@
             .collect(Collectors.toList());
     if (options.isDesugaredLibraryCompilation()) {
       // Specific L8 Settings.
-      // BackportedMethodRewriter is needed for retarget core library members and backports.
+      // DesugaredLibraryRetargeter is needed for retarget core library members and backports.
       // InterfaceMethodRewriter is needed for emulated interfaces.
       // LambdaRewriter is needed because if it is missing there are invoke custom on
       // default/static interface methods, and this is not supported by the compiler.
       // DesugaredLibraryAPIConverter is here to duplicate APIs.
       // The rest is nulled out. In addition the rewriting logic fails without lambda rewriting.
-      this.backportedMethodRewriter = new BackportedMethodRewriter(appView, this);
+      this.desugaredLibraryRetargeter =
+          options.desugaredLibraryConfiguration.getRetargetCoreLibMember().isEmpty()
+              ? null
+              : new DesugaredLibraryRetargeter(appView);
       this.interfaceMethodRewriter =
           options.desugaredLibraryConfiguration.getEmulateLibraryInterface().isEmpty()
               ? null
@@ -231,6 +236,10 @@
       this.lambdaRewriter = new LambdaRewriter(appView);
       this.desugaredLibraryAPIConverter =
           new DesugaredLibraryAPIConverter(appView, Mode.GENERATE_CALLBACKS_AND_WRAPPERS);
+      this.backportedMethodRewriter =
+          options.testing.forceLibBackportsInL8CfToCf
+              ? new BackportedMethodRewriter(appView, this)
+              : null;
       this.twrCloseResourceRewriter = null;
       this.lambdaMerger = null;
       this.covariantReturnTypeAnnotationTransformer = null;
@@ -268,6 +277,10 @@
             ? new TwrCloseResourceRewriter(appView, this)
             : null;
     this.backportedMethodRewriter = new BackportedMethodRewriter(appView, this);
+    this.desugaredLibraryRetargeter =
+        options.desugaredLibraryConfiguration.getRetargetCoreLibMember().isEmpty()
+            ? null
+            : new DesugaredLibraryRetargeter(appView);
     this.covariantReturnTypeAnnotationTransformer =
         options.processCovariantReturnTypeAnnotations
             ? new CovariantReturnTypeAnnotationTransformer(this, appView.dexItemFactory())
@@ -450,7 +463,16 @@
 
   private void synthesizeJava8UtilityClass(
       Builder<?> builder, ExecutorService executorService) throws ExecutionException {
-    backportedMethodRewriter.synthesizeUtilityClasses(builder, executorService);
+    if (backportedMethodRewriter != null) {
+      backportedMethodRewriter.synthesizeUtilityClasses(builder, executorService);
+    }
+  }
+
+  private void synthesizeRetargetClass(Builder<?> builder, ExecutorService executorService)
+      throws ExecutionException {
+    if (desugaredLibraryRetargeter != null) {
+      desugaredLibraryRetargeter.synthesizeRetargetClasses(builder, executorService, this);
+    }
   }
 
   private void synthesizeEnumUnboxingUtilityClass(
@@ -482,6 +504,8 @@
     desugarInterfaceMethods(builder, ExcludeDexResources, executor);
     synthesizeTwrCloseResourceUtilityClass(builder, executor);
     synthesizeJava8UtilityClass(builder, executor);
+    synthesizeRetargetClass(builder, executor);
+
     processCovariantReturnTypeAnnotations(builder);
     generateDesugaredLibraryAPIWrappers(builder, executor);
 
@@ -747,6 +771,7 @@
     printPhase("Utility classes synthesis");
     synthesizeTwrCloseResourceUtilityClass(builder, executorService);
     synthesizeJava8UtilityClass(builder, executorService);
+    synthesizeRetargetClass(builder, executorService);
     handleSynthesizedClassMapping(builder);
     synthesizeEnumUnboxingUtilityClass(builder, executorService);
 
@@ -974,12 +999,7 @@
   private DexType computeOutlineClassType() {
     DexType result;
     int count = 0;
-    String tempPrefix =
-        appView
-            .options()
-            .desugaredLibraryConfiguration
-            .getSynthesizedLibraryClassesPackagePrefix(appView);
-    String prefix = tempPrefix.replace('/', '.');
+    String prefix = appView.options().synthesizedClassPrefix.replace('/', '.');
     do {
       String name =
           prefix + OutlineOptions.CLASS_NAME + (count == 0 ? "" : Integer.toString(count));
@@ -1375,9 +1395,20 @@
       codeRewriter.rewriteThrowableAddAndGetSuppressed(code);
       timing.end();
     }
-    timing.begin("Rewrite backport methods");
-    backportedMethodRewriter.desugar(code);
-    timing.end();
+
+    if (desugaredLibraryRetargeter != null) {
+      // The desugaredLibraryRetargeter should run before backportedMethodRewriter to be able to
+      // perform backport rewriting before the methods can be retargeted.
+      timing.begin("Retarget library methods");
+      desugaredLibraryRetargeter.desugar(code);
+      timing.end();
+    }
+
+    if (backportedMethodRewriter != null) {
+      timing.begin("Rewrite backport methods");
+      backportedMethodRewriter.desugar(code);
+      timing.end();
+    }
 
     timing.begin("Desugar string concat");
     stringConcatRewriter.desugarStringConcats(method.method, code);
@@ -1599,15 +1630,20 @@
     if (method.isInitializer()) {
       if (method.isClassInitializer()) {
         StaticFieldValueAnalysis.run(
-            appView, code, classInitializerDefaultsResult, feedback, code.method);
+            appView, code, classInitializerDefaultsResult, feedback, code.method, timing);
       } else {
         instanceFieldInitializationInfos =
             InstanceFieldValueAnalysis.run(
-                appView, code, classInitializerDefaultsResult, feedback, code.method);
+                appView, code, classInitializerDefaultsResult, feedback, code.method, timing);
       }
     }
     methodOptimizationInfoCollector.collectMethodOptimizationInfo(
-        code.method, code, feedback, dynamicTypeOptimization, instanceFieldInitializationInfos);
+        code.method,
+        code,
+        feedback,
+        dynamicTypeOptimization,
+        instanceFieldInitializationInfos,
+        timing);
   }
 
   public void removeDeadCodeAndFinalizeIR(
@@ -1884,7 +1920,7 @@
     Instruction check = it.previous();
     assert addBefore == check;
     // Forced definition of const-zero
-    Value fixitValue = code.createValue(TypeLatticeElement.getInt());
+    Value fixitValue = code.createValue(TypeElement.getInt());
     Instruction fixitDefinition = new AlwaysMaterializingDefinition(fixitValue);
     fixitDefinition.setBlock(addBefore.getBlock());
     fixitDefinition.setPosition(addBefore.getPosition());
diff --git a/src/main/java/com/android/tools/r8/ir/conversion/LensCodeRewriter.java b/src/main/java/com/android/tools/r8/ir/conversion/LensCodeRewriter.java
index 06c4bdf..bb541cf 100644
--- a/src/main/java/com/android/tools/r8/ir/conversion/LensCodeRewriter.java
+++ b/src/main/java/com/android/tools/r8/ir/conversion/LensCodeRewriter.java
@@ -34,7 +34,7 @@
 import com.android.tools.r8.graph.UseRegistry.MethodHandleUse;
 import com.android.tools.r8.graph.classmerging.VerticallyMergedClasses;
 import com.android.tools.r8.ir.analysis.type.DestructivePhiTypeUpdater;
-import com.android.tools.r8.ir.analysis.type.TypeLatticeElement;
+import com.android.tools.r8.ir.analysis.type.TypeElement;
 import com.android.tools.r8.ir.code.BasicBlock;
 import com.android.tools.r8.ir.code.CatchHandlers;
 import com.android.tools.r8.ir.code.CheckCast;
@@ -86,8 +86,8 @@
 
   private Value makeOutValue(Instruction insn, IRCode code) {
     if (insn.outValue() != null) {
-      TypeLatticeElement oldType = insn.outValue().getTypeLattice();
-      TypeLatticeElement newType =
+      TypeElement oldType = insn.outValue().getType();
+      TypeElement newType =
           oldType.fixupClassTypeReferences(appView.graphLense()::lookupType, appView);
       return code.createValue(newType, insn.getLocalInfo());
     }
@@ -123,8 +123,7 @@
             InvokeCustom newInvokeCustom =
                 new InvokeCustom(newCallSite, newOutValue, invokeCustom.inValues());
             iterator.replaceCurrentInstruction(newInvokeCustom);
-            if (newOutValue != null
-                && newOutValue.getTypeLattice() != invokeCustom.outValue().getTypeLattice()) {
+            if (newOutValue != null && newOutValue.getType() != invokeCustom.outValue().getType()) {
               affectedPhis.addAll(newOutValue.uniquePhiUsers());
             }
           }
@@ -135,8 +134,7 @@
           if (newHandle != handle) {
             Value newOutValue = makeOutValue(current, code);
             iterator.replaceCurrentInstruction(new ConstMethodHandle(newOutValue, newHandle));
-            if (newOutValue != null
-                && newOutValue.getTypeLattice() != current.outValue().getTypeLattice()) {
+            if (newOutValue != null && newOutValue.getType() != current.outValue().getType()) {
               affectedPhis.addAll(newOutValue.uniquePhiUsers());
             }
           }
@@ -250,8 +248,7 @@
             Invoke newInvoke =
                 Invoke.create(actualInvokeType, actualTarget, null, newOutValue, newInValues);
             iterator.replaceCurrentInstruction(newInvoke);
-            if (newOutValue != null
-                && newOutValue.getTypeLattice() != current.outValue().getTypeLattice()) {
+            if (newOutValue != null && newOutValue.getType() != current.outValue().getType()) {
               affectedPhis.addAll(newOutValue.uniquePhiUsers());
             }
 
@@ -287,16 +284,14 @@
             Value newOutValue = makeOutValue(current, code);
             iterator.replaceCurrentInstruction(
                 new InvokeStatic(replacementMethod, newOutValue, current.inValues()));
-            if (newOutValue != null
-                && newOutValue.getTypeLattice() != current.outValue().getTypeLattice()) {
+            if (newOutValue != null && newOutValue.getType() != current.outValue().getType()) {
               affectedPhis.addAll(current.outValue().uniquePhiUsers());
             }
           } else if (actualField != field) {
             Value newOutValue = makeOutValue(instanceGet, code);
             iterator.replaceCurrentInstruction(
                 new InstanceGet(newOutValue, instanceGet.object(), actualField));
-            if (newOutValue != null
-                && newOutValue.getTypeLattice() != current.outValue().getTypeLattice()) {
+            if (newOutValue != null && newOutValue.getType() != current.outValue().getType()) {
               affectedPhis.addAll(newOutValue.uniquePhiUsers());
             }
           }
@@ -328,15 +323,13 @@
             Value newOutValue = makeOutValue(current, code);
             iterator.replaceCurrentInstruction(
                 new InvokeStatic(replacementMethod, newOutValue, current.inValues()));
-            if (newOutValue != null
-                && newOutValue.getTypeLattice() != current.outValue().getTypeLattice()) {
+            if (newOutValue != null && newOutValue.getType() != current.outValue().getType()) {
               affectedPhis.addAll(newOutValue.uniquePhiUsers());
             }
           } else if (actualField != field) {
             Value newOutValue = makeOutValue(staticGet, code);
             iterator.replaceCurrentInstruction(new StaticGet(newOutValue, actualField));
-            if (newOutValue != null
-                && newOutValue.getTypeLattice() != current.outValue().getTypeLattice()) {
+            if (newOutValue != null && newOutValue.getType() != current.outValue().getType()) {
               affectedPhis.addAll(newOutValue.uniquePhiUsers());
             }
           }
@@ -399,11 +392,11 @@
               .replaceInstructionIfTypeChanged(type, NewInstance::new);
         } else if (current.outValue() != null) {
           // For all other instructions, substitute any changed type.
-          TypeLatticeElement typeLattice = current.outValue().getTypeLattice();
-          TypeLatticeElement substituted =
+          TypeElement typeLattice = current.outValue().getType();
+          TypeElement substituted =
               typeLattice.fixupClassTypeReferences(graphLense::lookupType, appView);
           if (substituted != typeLattice) {
-            current.outValue().setTypeLattice(substituted);
+            current.outValue().setType(substituted);
             affectedPhis.addAll(current.outValue().uniquePhiUsers());
           }
         }
@@ -434,7 +427,7 @@
       // TODO(b/150188380): Add API to insert a const instruction with a type lattice.
       Value rewrittenDefaultValue = iterator.insertConstIntInstruction(code, appView.options(), 0);
       iterator.next();
-      rewrittenDefaultValue.setTypeLattice(defaultValueLatticeElement(newType));
+      rewrittenDefaultValue.setType(defaultValueLatticeElement(newType));
       return rewrittenDefaultValue;
     }
     return initialValue;
@@ -455,11 +448,11 @@
     return false;
   }
 
-  private TypeLatticeElement defaultValueLatticeElement(DexType type) {
+  private TypeElement defaultValueLatticeElement(DexType type) {
     if (type.isPrimitiveType()) {
-      return TypeLatticeElement.fromDexType(type, null, appView);
+      return TypeElement.fromDexType(type, null, appView);
     }
-    return TypeLatticeElement.getNull();
+    return TypeElement.getNull();
   }
 
   public DexCallSite rewriteCallSite(DexCallSite callSite, DexEncodedMethod context) {
@@ -568,16 +561,22 @@
     for (int i = 0; i < bootstrapArgs.size(); i++) {
       DexValue argument = bootstrapArgs.get(i);
       DexValue newArgument = null;
-      if (argument instanceof DexValueMethodHandle) {
-        newArgument = rewriteDexValueMethodHandle(argument.asDexValueMethodHandle(), method, use);
-      } else if (argument instanceof DexValueMethodType) {
-        newArgument = rewriteDexMethodType(argument.asDexValueMethodType());
-      } else if (argument instanceof DexValueType) {
-        DexType oldType = ((DexValueType) argument).value;
-        DexType newType = appView.graphLense().lookupType(oldType);
-        if (newType != oldType) {
-          newArgument = new DexValueType(newType);
-        }
+      switch (argument.getValueKind()) {
+        case METHOD_HANDLE:
+          newArgument = rewriteDexValueMethodHandle(argument.asDexValueMethodHandle(), method, use);
+          break;
+        case METHOD_TYPE:
+          newArgument = rewriteDexMethodType(argument.asDexValueMethodType());
+          break;
+        case TYPE:
+          DexType oldType = argument.asDexValueType().value;
+          DexType newType = appView.graphLense().lookupType(oldType);
+          if (newType != oldType) {
+            newArgument = new DexValueType(newType);
+          }
+          break;
+        default:
+          // Intentionally empty.
       }
       if (newArgument != null) {
         if (newBootstrapArgs == null) {
@@ -686,7 +685,7 @@
         Instruction newInstruction = constructor.apply(newType, newOutValue);
         iterator.replaceCurrentInstruction(newInstruction);
         if (newOutValue != null) {
-          if (newOutValue.getTypeLattice() != current.outValue().getTypeLattice()) {
+          if (newOutValue.getType() != current.outValue().getType()) {
             affectedPhis.addAll(newOutValue.uniquePhiUsers());
           } else {
             assert current.hasInvariantOutType();
diff --git a/src/main/java/com/android/tools/r8/ir/conversion/MethodOptimizationFeedback.java b/src/main/java/com/android/tools/r8/ir/conversion/MethodOptimizationFeedback.java
index ca144f7..8982b5a 100644
--- a/src/main/java/com/android/tools/r8/ir/conversion/MethodOptimizationFeedback.java
+++ b/src/main/java/com/android/tools/r8/ir/conversion/MethodOptimizationFeedback.java
@@ -7,8 +7,8 @@
 import com.android.tools.r8.graph.AppView;
 import com.android.tools.r8.graph.DexEncodedMethod;
 import com.android.tools.r8.graph.DexType;
-import com.android.tools.r8.ir.analysis.type.ClassTypeLatticeElement;
-import com.android.tools.r8.ir.analysis.type.TypeLatticeElement;
+import com.android.tools.r8.ir.analysis.type.ClassTypeElement;
+import com.android.tools.r8.ir.analysis.type.TypeElement;
 import com.android.tools.r8.ir.analysis.value.AbstractValue;
 import com.android.tools.r8.ir.optimize.Inliner.ConstraintWithTarget;
 import com.android.tools.r8.ir.optimize.classinliner.ClassInlinerEligibilityInfo;
@@ -35,9 +35,9 @@
       DexEncodedMethod method, AppView<AppInfoWithLiveness> appView, AbstractValue abstractValue);
 
   void methodReturnsObjectWithUpperBoundType(
-      DexEncodedMethod method, AppView<?> appView, TypeLatticeElement type);
+      DexEncodedMethod method, AppView<?> appView, TypeElement type);
 
-  void methodReturnsObjectWithLowerBoundType(DexEncodedMethod method, ClassTypeLatticeElement type);
+  void methodReturnsObjectWithLowerBoundType(DexEncodedMethod method, ClassTypeElement type);
 
   void methodMayNotHaveSideEffects(DexEncodedMethod method);
 
diff --git a/src/main/java/com/android/tools/r8/ir/conversion/StringSwitchRemover.java b/src/main/java/com/android/tools/r8/ir/conversion/StringSwitchRemover.java
index 2e8bf77..b1f53e1 100644
--- a/src/main/java/com/android/tools/r8/ir/conversion/StringSwitchRemover.java
+++ b/src/main/java/com/android/tools/r8/ir/conversion/StringSwitchRemover.java
@@ -7,10 +7,10 @@
 import com.android.tools.r8.graph.AppView;
 import com.android.tools.r8.graph.DexEncodedMethod;
 import com.android.tools.r8.graph.DexString;
-import com.android.tools.r8.ir.analysis.type.ClassTypeLatticeElement;
+import com.android.tools.r8.ir.analysis.type.ClassTypeElement;
 import com.android.tools.r8.ir.analysis.type.Nullability;
-import com.android.tools.r8.ir.analysis.type.PrimitiveTypeLatticeElement;
-import com.android.tools.r8.ir.analysis.type.TypeLatticeElement;
+import com.android.tools.r8.ir.analysis.type.PrimitiveTypeElement;
+import com.android.tools.r8.ir.analysis.type.TypeElement;
 import com.android.tools.r8.ir.code.BasicBlock;
 import com.android.tools.r8.ir.code.BasicBlock.ThrowingInfo;
 import com.android.tools.r8.ir.code.ConstString;
@@ -37,13 +37,13 @@
 
   private final AppView<?> appView;
   private final IdentifierNameStringMarker identifierNameStringMarker;
-  private final ClassTypeLatticeElement stringType;
+  private final ClassTypeElement stringType;
   private final ThrowingInfo throwingInfo;
 
   StringSwitchRemover(AppView<?> appView, IdentifierNameStringMarker identifierNameStringMarker) {
     this.appView = appView;
     this.identifierNameStringMarker = identifierNameStringMarker;
-    this.stringType = TypeLatticeElement.stringClassType(appView, Nullability.definitelyNotNull());
+    this.stringType = TypeElement.stringClassType(appView, Nullability.definitelyNotNull());
     this.throwingInfo = ThrowingInfo.defaultForConstString(appView.options());
   }
 
@@ -109,7 +109,7 @@
       InvokeVirtual invokeInstruction =
           new InvokeVirtual(
               appView.dexItemFactory().stringMethods.equals,
-              code.createValue(PrimitiveTypeLatticeElement.getInt()),
+              code.createValue(PrimitiveTypeElement.getInt()),
               ImmutableList.of(theSwitch.value(), constStringInstruction.outValue()));
       invokeInstruction.setPosition(Position.syntheticNone());
 
diff --git a/src/main/java/com/android/tools/r8/ir/conversion/TypeConstraintResolver.java b/src/main/java/com/android/tools/r8/ir/conversion/TypeConstraintResolver.java
index 7257d49..d7edc8a 100644
--- a/src/main/java/com/android/tools/r8/ir/conversion/TypeConstraintResolver.java
+++ b/src/main/java/com/android/tools/r8/ir/conversion/TypeConstraintResolver.java
@@ -6,9 +6,9 @@
 import com.android.tools.r8.errors.CompilationError;
 import com.android.tools.r8.errors.Unreachable;
 import com.android.tools.r8.graph.AppView;
-import com.android.tools.r8.ir.analysis.type.ArrayTypeLatticeElement;
+import com.android.tools.r8.ir.analysis.type.ArrayTypeElement;
 import com.android.tools.r8.ir.analysis.type.TypeAnalysis;
-import com.android.tools.r8.ir.analysis.type.TypeLatticeElement;
+import com.android.tools.r8.ir.analysis.type.TypeElement;
 import com.android.tools.r8.ir.code.ArrayPut;
 import com.android.tools.r8.ir.code.BasicBlock;
 import com.android.tools.r8.ir.code.IRCode;
@@ -59,33 +59,33 @@
     this.builder = builder;
   }
 
-  public static ValueTypeConstraint constraintForType(TypeLatticeElement type) {
+  public static ValueTypeConstraint constraintForType(TypeElement type) {
     // During constraint resolution the type bottom denotes references of not-yet-computed types.
     return type.isBottom() ? ValueTypeConstraint.OBJECT : ValueTypeConstraint.fromTypeLattice(type);
   }
 
-  public static TypeLatticeElement typeForConstraint(ValueTypeConstraint constraint) {
+  public static TypeElement typeForConstraint(ValueTypeConstraint constraint) {
     switch (constraint) {
       case INT_OR_FLOAT_OR_OBJECT:
-        return TypeLatticeElement.getTop();
+        return TypeElement.getTop();
       case OBJECT:
         // If the constraint is object the concrete lattice type will need to be computed.
         // We mark the object type as bottom for now, with the implication that it is of type
         // reference but that it should not contribute to the computation of its join
         // (in potentially self-referencing phis).
-        return TypeLatticeElement.getBottom();
+        return TypeElement.getBottom();
       case INT:
-        return TypeLatticeElement.getInt();
+        return TypeElement.getInt();
       case FLOAT:
-        return TypeLatticeElement.getFloat();
+        return TypeElement.getFloat();
       case INT_OR_FLOAT:
-        return TypeLatticeElement.getSingle();
+        return TypeElement.getSingle();
       case LONG:
-        return TypeLatticeElement.getLong();
+        return TypeElement.getLong();
       case DOUBLE:
-        return TypeLatticeElement.getDouble();
+        return TypeElement.getDouble();
       case LONG_OR_DOUBLE:
-        return TypeLatticeElement.getWide();
+        return TypeElement.getWide();
       default:
         throw new Unreachable("Unexpected constraint type: " + constraint);
     }
@@ -106,7 +106,7 @@
     List<Value> impreciseValues = new ArrayList<>();
     for (BasicBlock block : code.blocks) {
       for (Phi phi : block.getPhis()) {
-        if (!phi.getTypeLattice().isPreciseType()) {
+        if (!phi.getType().isPreciseType()) {
           impreciseValues.add(phi);
         }
         for (Value value : phi.getOperands()) {
@@ -114,8 +114,7 @@
         }
       }
       for (Instruction instruction : block.getInstructions()) {
-        if (instruction.outValue() != null
-            && !instruction.outValue().getTypeLattice().isPreciseType()) {
+        if (instruction.outValue() != null && !instruction.outValue().getType().isPreciseType()) {
           impreciseValues.add(instruction.outValue());
         }
 
@@ -151,7 +150,7 @@
                   "Cannot determine precise type for value: "
                       + stillImprecise.get(0)
                       + ", its imprecise type is: "
-                      + stillImprecise.get(0).getTypeLattice(),
+                      + stillImprecise.get(0).getType(),
                   code.origin,
                   new MethodPosition(code.method.method)));
     }
@@ -161,7 +160,7 @@
     ArrayList<Value> stillImprecise = new ArrayList<>(impreciseValues.size());
     for (Value value : impreciseValues) {
       builder.constrainType(value, getCanonicalTypeConstraint(value, finished));
-      if (!value.getTypeLattice().isPreciseType()) {
+      if (!value.getType().isPreciseType()) {
         stillImprecise.add(value);
       }
     }
@@ -173,10 +172,10 @@
     assert !type.isPrecise();
     Value canonical = canonical(value);
     ValueTypeConstraint constraint;
-    if (array.getTypeLattice().isArrayType()) {
+    if (array.getType().isArrayType()) {
       // If the array type is known it uniquely defines the actual member type.
-      ArrayTypeLatticeElement arrayType = array.getTypeLattice().asArrayTypeLatticeElement();
-      constraint = ValueTypeConstraint.fromTypeLattice(arrayType.getArrayMemberTypeAsValueType());
+      ArrayTypeElement arrayType = array.getType().asArrayType();
+      constraint = ValueTypeConstraint.fromTypeLattice(arrayType.getMemberTypeAsValueType());
     } else {
       // If not, e.g., the array input is null, the canonical value determines the final type.
       constraint = getCanonicalTypeConstraint(canonical, true);
@@ -192,7 +191,7 @@
   }
 
   private ValueTypeConstraint getCanonicalTypeConstraint(Value value, boolean finished) {
-    ValueTypeConstraint type = constraintForType(canonical(value).getTypeLattice());
+    ValueTypeConstraint type = constraintForType(canonical(value).getType());
     switch (type) {
       case INT_OR_FLOAT_OR_OBJECT:
         // There is never a second round for resolving object vs single.
@@ -231,7 +230,7 @@
         ArrayPut put = user.asArrayPut();
         assert value == put.value();
         assert !put.getMemberType().isPrecise();
-        assert put.array().getTypeLattice().isDefinitelyNull();
+        assert put.array().getType().isDefinitelyNull();
       } else {
         assert false;
       }
@@ -244,8 +243,8 @@
     if (canonical1 == canonical2) {
       return;
     }
-    TypeLatticeElement type1 = canonical1.getTypeLattice();
-    TypeLatticeElement type2 = canonical2.getTypeLattice();
+    TypeElement type1 = canonical1.getType();
+    TypeElement type2 = canonical2.getType();
     if (type1.isPreciseType() && type2.isPreciseType()) {
       if (type1 != type2 && constraintForType(type1) != constraintForType(type2)) {
         throw new CompilationError(
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/BackportedMethodRewriter.java b/src/main/java/com/android/tools/r8/ir/desugar/BackportedMethodRewriter.java
index fd0e452..43db91d 100644
--- a/src/main/java/com/android/tools/r8/ir/desugar/BackportedMethodRewriter.java
+++ b/src/main/java/com/android/tools/r8/ir/desugar/BackportedMethodRewriter.java
@@ -21,7 +21,6 @@
 import com.android.tools.r8.graph.DexEncodedField;
 import com.android.tools.r8.graph.DexEncodedMethod;
 import com.android.tools.r8.graph.DexItemFactory;
-import com.android.tools.r8.graph.DexLibraryClass;
 import com.android.tools.r8.graph.DexMethod;
 import com.android.tools.r8.graph.DexProgramClass;
 import com.android.tools.r8.graph.DexProgramClass.ChecksumSupplier;
@@ -31,7 +30,6 @@
 import com.android.tools.r8.graph.DexTypeList;
 import com.android.tools.r8.graph.MethodAccessFlags;
 import com.android.tools.r8.graph.ParameterAnnotationsList;
-import com.android.tools.r8.graph.ResolutionResult;
 import com.android.tools.r8.ir.analysis.type.TypeAnalysis;
 import com.android.tools.r8.ir.code.IRCode;
 import com.android.tools.r8.ir.code.Instruction;
@@ -54,13 +52,10 @@
 import com.android.tools.r8.utils.AndroidApp;
 import com.android.tools.r8.utils.InternalOptions;
 import com.android.tools.r8.utils.InternalOptions.DesugarState;
-import com.android.tools.r8.utils.StringDiagnostic;
 import com.android.tools.r8.utils.Timing;
-import com.google.common.collect.Maps;
 import com.google.common.collect.Sets;
 import java.io.IOException;
 import java.util.ArrayList;
-import java.util.Arrays;
 import java.util.Collection;
 import java.util.Collections;
 import java.util.IdentityHashMap;
@@ -113,15 +108,17 @@
       if (androidApp != null) {
         DexApplication app =
             new ApplicationReader(androidApp, options, Timing.empty()).read(executor);
-        appInfo =
-            options.desugaredLibraryConfiguration.getRewritePrefix().isEmpty()
-                ? new AppInfo(app)
-                : new AppInfoWithClassHierarchy(app);
+        appInfo = new AppInfoWithClassHierarchy(app);
       }
       AppView<?> appView = AppView.createForD8(appInfo, options, rewritePrefix);
       BackportedMethodRewriter.RewritableMethods rewritableMethods =
           new BackportedMethodRewriter.RewritableMethods(options, appView);
       rewritableMethods.visit(methods::add);
+      if (appInfo != null) {
+        DesugaredLibraryRetargeter desugaredLibraryRetargeter =
+            new DesugaredLibraryRetargeter(appView);
+        desugaredLibraryRetargeter.visit(methods::add);
+      }
       return methods;
     } catch (ExecutionException e) {
       throw unwrapExecutionException(e);
@@ -146,49 +143,10 @@
           && !invoke.isInvokeStatic()) {
         continue;
       }
+
       MethodProvider provider = getMethodProviderOrNull(invoke.getInvokedMethod());
       if (provider == null) {
-        if (!rewritableMethods.matchesVirtualRewrite(invoke.getInvokedMethod())) {
-          continue;
-        }
-        // We need to force resolution, even on d8, to know if the invoke has to be rewritten.
-        ResolutionResult resolutionResult =
-            appView
-                .appInfo()
-                .resolveMethod(invoke.getInvokedMethod().holder, invoke.getInvokedMethod());
-        if (resolutionResult.isFailedResolution()) {
-          continue;
-        }
-        DexEncodedMethod singleTarget = resolutionResult.getSingleTarget();
-        assert singleTarget != null;
-        provider = getMethodProviderOrNull(singleTarget.method);
-        if (provider == null) {
-          continue;
-        }
-      }
-
-      // Due to emulated dispatch, we have to rewrite invoke-super differently or we end up in
-      // infinite loops. We do direct resolution. This is a very uncommon case.
-      if (invoke.isInvokeSuper()
-          && rewritableMethods.matchesVirtualRewrite(invoke.getInvokedMethod())) {
-        DexEncodedMethod dexEncodedMethod =
-            appView
-                .appInfo()
-                .withClassHierarchy()
-                .lookupSuperTarget(invoke.getInvokedMethod(), code.method.method.holder);
-        // Final methods can be rewritten as a normal invoke.
-        if (dexEncodedMethod != null && !dexEncodedMethod.isFinal()) {
-          DexMethod retargetMethod =
-              appView
-                  .options()
-                  .desugaredLibraryConfiguration
-                  .retargetMethod(dexEncodedMethod.method, appView);
-          if (retargetMethod != null) {
-            iterator.replaceCurrentInstruction(
-                new InvokeStatic(retargetMethod, invoke.outValue(), invoke.arguments()));
-          }
-          continue;
-        }
+        continue;
       }
 
       provider.rewriteInvoke(invoke, iterator, code, appView, affectedValues);
@@ -222,11 +180,6 @@
     if (!enabled) {
       return;
     }
-    if (appView.options().isDesugaredLibraryCompilation()) {
-      synthesizeEmulatedDispatchMethods(builder);
-    } else {
-      addInterfacesAndForwardingMethods(executorService);
-    }
     if (holders.isEmpty()) {
       return;
     }
@@ -243,8 +196,9 @@
       }
     }
 
-    MethodAccessFlags flags = MethodAccessFlags.fromSharedAccessFlags(
-        Constants.ACC_PUBLIC | Constants.ACC_STATIC | Constants.ACC_SYNTHETIC, false);
+    MethodAccessFlags flags =
+        MethodAccessFlags.fromSharedAccessFlags(
+            Constants.ACC_PUBLIC | Constants.ACC_STATIC | Constants.ACC_SYNTHETIC, false);
     ClassAccessFlags classAccessFlags =
         ClassAccessFlags.fromSharedAccessFlags(Constants.ACC_PUBLIC | Constants.ACC_SYNTHETIC);
     // Generate the utility classes in a loop since utility classes can require the
@@ -262,7 +216,12 @@
       Code code = provider.generateTemplateMethod(appView.options(), method);
       DexEncodedMethod dexEncodedMethod =
           new DexEncodedMethod(
-              method, flags, DexAnnotationSet.empty(), ParameterAnnotationsList.empty(), code, true);
+              method,
+              flags,
+              DexAnnotationSet.empty(),
+              ParameterAnnotationsList.empty(),
+              code,
+              true);
       boolean addToMainDexList =
           referencingClasses.stream()
               .anyMatch(clazz -> appView.appInfo().isInMainDexList(clazz.type));
@@ -273,140 +232,27 @@
               method.holder,
               dexEncodedMethod,
               "java8 methods utility class",
-              addToMainDexList);
+              addToMainDexList,
+              appView);
       // The following may add elements to methodsProviders.
       converter.optimizeSynthesizedClass(utilityClass, executorService);
     }
   }
 
-  private void addInterfacesAndForwardingMethods(ExecutorService executorService)
-      throws ExecutionException {
-    assert !appView.options().isDesugaredLibraryCompilation();
-    Map<DexType, List<DexMethod>> map = Maps.newIdentityHashMap();
-    for (DexMethod emulatedDispatchMethod : rewritableMethods.getEmulatedDispatchMethods()) {
-      map.putIfAbsent(emulatedDispatchMethod.holder, new ArrayList<>(1));
-      map.get(emulatedDispatchMethod.holder).add(emulatedDispatchMethod);
-    }
-    List<DexEncodedMethod> addedMethods = new ArrayList<>();
-    for (DexProgramClass clazz : appView.appInfo().classes()) {
-      if (clazz.superType == null) {
-        assert clazz.type == appView.dexItemFactory().objectType : clazz.type.toSourceString();
-        continue;
-      }
-      DexClass dexClass = appView.definitionFor(clazz.superType);
-      // Only performs computation if superclass is a library class, but not object to filter out
-      // the most common case.
-      if (dexClass != null
-          && dexClass.isLibraryClass()
-          && dexClass.type != appView.dexItemFactory().objectType) {
-        for (DexType dexType : map.keySet()) {
-          if (inherit(dexClass.asLibraryClass(), dexType)) {
-            addedMethods.addAll(addInterfacesAndForwardingMethods(clazz, map.get(dexType)));
-          }
-        }
-      }
-    }
-    if (addedMethods.isEmpty()) {
-      return;
-    }
-    converter.processMethodsConcurrently(addedMethods, executorService);
-  }
-
-  private boolean inherit(DexLibraryClass clazz, DexType typeToInherit) {
-    DexLibraryClass current = clazz;
-    while (current.type != appView.dexItemFactory().objectType) {
-      if (current.type == typeToInherit) {
-        return true;
-      }
-      current = appView.definitionFor(current.superType).asLibraryClass();
-    }
-    return false;
-  }
-
-  private List<DexEncodedMethod> addInterfacesAndForwardingMethods(
-      DexProgramClass clazz, List<DexMethod> dexMethods) {
-    // BackportedMethodRewriter emulate dispatch: insertion of a marker interface & forwarding
-    // methods.
-    // We cannot use the ClassProcessor since this applies up to 26, while the ClassProcessor
-    // applies up to 24.
-    List<DexEncodedMethod> newForwardingMethods = new ArrayList<>();
-    for (DexMethod dexMethod : dexMethods) {
-      DexType[] newInterfaces = Arrays.copyOf(clazz.interfaces.values, clazz.interfaces.size() + 1);
-      newInterfaces[newInterfaces.length - 1] =
-          BackportedMethodRewriter.dispatchInterfaceTypeFor(appView, dexMethod);
-      clazz.interfaces = new DexTypeList(newInterfaces);
-      DexEncodedMethod dexEncodedMethod = clazz.lookupVirtualMethod(dexMethod);
-      if (dexEncodedMethod == null) {
-        DexEncodedMethod newMethod = createForwardingMethod(dexMethod, clazz);
-        clazz.addVirtualMethod(newMethod);
-        newForwardingMethods.add(newMethod);
-      }
-    }
-    return newForwardingMethods;
-  }
-
-  private DexEncodedMethod createForwardingMethod(DexMethod target, DexClass clazz) {
-    // NOTE: Never add a forwarding method to methods of classes unknown or coming from android.jar
-    // even if this results in invalid code, these classes are never desugared.
-    // In desugared library, emulated interface methods can be overridden by retarget lib members.
-    DexMethod forwardMethod =
-        appView.options().desugaredLibraryConfiguration.retargetMethod(target, appView);
-    assert forwardMethod != null && forwardMethod != target;
-    return DexEncodedMethod.createDesugaringForwardingMethod(
-        appView.definitionFor(target), clazz, forwardMethod, appView.dexItemFactory());
-  }
-
-  private void synthesizeEmulatedDispatchMethods(Builder<?> builder) {
-    assert appView.options().isDesugaredLibraryCompilation();
-    if (rewritableMethods.getEmulatedDispatchMethods().isEmpty()) {
-      return;
-    }
-    ClassAccessFlags itfAccessFlags =
-        ClassAccessFlags.fromSharedAccessFlags(
-            Constants.ACC_PUBLIC
-                | Constants.ACC_SYNTHETIC
-                | Constants.ACC_ABSTRACT
-                | Constants.ACC_INTERFACE);
-    ClassAccessFlags holderAccessFlags =
-        ClassAccessFlags.fromSharedAccessFlags(Constants.ACC_PUBLIC | Constants.ACC_SYNTHETIC);
-    for (DexMethod emulatedDispatchMethod : rewritableMethods.getEmulatedDispatchMethods()) {
-      // Dispatch interface.
-      DexType interfaceType = dispatchInterfaceTypeFor(appView, emulatedDispatchMethod);
-      DexEncodedMethod itfMethod =
-          generateInterfaceDispatchMethod(emulatedDispatchMethod, interfaceType);
-      synthesizeClassWithUniqueMethod(
-          builder,
-          itfAccessFlags,
-          interfaceType,
-          itfMethod,
-          "desugared library dispatch interface",
-          false);
-      // Dispatch holder.
-      DexType holderType = dispatchHolderTypeFor(appView, emulatedDispatchMethod);
-      DexEncodedMethod dispatchMethod =
-          generateHolderDispatchMethod(emulatedDispatchMethod, holderType, itfMethod.method);
-      synthesizeClassWithUniqueMethod(
-          builder,
-          holderAccessFlags,
-          holderType,
-          dispatchMethod,
-          "desugared library dispatch class",
-          false);
-    }
-  }
-
-  private DexProgramClass synthesizeClassWithUniqueMethod(
+  static DexProgramClass synthesizeClassWithUniqueMethod(
       Builder<?> builder,
       ClassAccessFlags accessFlags,
       DexType type,
       DexEncodedMethod uniqueMethod,
       String origin,
-      boolean addToMainDexList) {
+      boolean addToMainDexList,
+      AppView<?> appView) {
+    DexItemFactory factory = appView.dexItemFactory();
     DexProgramClass newClass =
         new DexProgramClass(
             type,
             null,
-            new SynthesizedOrigin(origin, getClass()),
+            new SynthesizedOrigin(origin, BackportedMethodRewriter.class),
             accessFlags,
             factory.objectType,
             DexTypeList.empty(),
@@ -425,133 +271,45 @@
                 ? DexEncodedMethod.EMPTY_ARRAY
                 : new DexEncodedMethod[] {uniqueMethod},
             factory.getSkipNameValidationForTesting(),
-            getChecksumSupplier(uniqueMethod));
+            getChecksumSupplier(uniqueMethod, appView));
     appView.appInfo().addSynthesizedClass(newClass);
     builder.addSynthesizedClass(newClass, addToMainDexList);
     return newClass;
   }
 
-  private DexEncodedMethod generateInterfaceDispatchMethod(
-      DexMethod emulatedDispatchMethod, DexType interfaceType) {
-    MethodAccessFlags flags =
-        MethodAccessFlags.fromSharedAccessFlags(
-            Constants.ACC_PUBLIC | Constants.ACC_ABSTRACT | Constants.ACC_SYNTHETIC, false);
-    DexMethod newMethod =
-        factory.createMethod(
-            interfaceType, emulatedDispatchMethod.proto, emulatedDispatchMethod.name);
-    return new DexEncodedMethod(
-        newMethod, flags, DexAnnotationSet.empty(), ParameterAnnotationsList.empty(), null, true);
-  }
-
-  private DexEncodedMethod generateHolderDispatchMethod(
-      DexMethod emulatedDispatchMethod, DexType dispatchHolder, DexMethod itfMethod) {
-    // The method should look like:
-    // static foo(rcvr, arg0, arg1) {
-    //    if (rcvr instanceof interfaceType) {
-    //      return invoke-interface receiver.foo(arg0, arg1);
-    //    } else {
-    //      return DesugarX.foo(rcvr, arg0, arg1)
-    //    }
-    // We do not deal with complex cases (multiple retargeting of the same signature in the
-    // same inheritance tree, etc., since they do not happen in the most common desugared library.
-    DexMethod desugarMethod =
-        appView
-            .options()
-            .desugaredLibraryConfiguration
-            .retargetMethod(emulatedDispatchMethod, appView);
-    assert desugarMethod != null; // This method is reached only for retarget core lib members.
-    DexMethod newMethod =
-        appView
-            .dexItemFactory()
-            .createMethod(dispatchHolder, desugarMethod.proto, emulatedDispatchMethod.name);
-    return DexEncodedMethod.toEmulateDispatchLibraryMethod(
-        emulatedDispatchMethod.holder,
-        newMethod,
-        desugarMethod,
-        itfMethod,
-        Collections.emptyList(),
-        appView);
-  }
-
-  private ChecksumSupplier getChecksumSupplier(DexEncodedMethod method) {
+  private static ChecksumSupplier getChecksumSupplier(DexEncodedMethod method, AppView<?> appView) {
     if (!appView.options().encodeChecksums) {
       return DexProgramClass::invalidChecksumRequest;
     }
     return c -> method.method.hashCode();
   }
-
-  public static DexType dispatchInterfaceTypeFor(AppView<?> appView, DexMethod method) {
-    return dispatchTypeFor(appView, method, "dispatchInterface");
-  }
-
-  static DexType dispatchHolderTypeFor(AppView<?> appView, DexMethod method) {
-    return dispatchTypeFor(appView, method, "dispatchHolder");
-  }
-
-  private static DexType dispatchTypeFor(AppView<?> appView, DexMethod method, String suffix) {
-    String desugaredLibPrefix =
-        appView.options().desugaredLibraryConfiguration.getSynthesizedLibraryClassesPackagePrefix();
-    String descriptor =
-        "L"
-            + desugaredLibPrefix
-            + UTILITY_CLASS_NAME_PREFIX
-            + '$'
-            + method.holder.getName()
-            + '$'
-            + method.name
-            + '$'
-            + suffix
-            + ';';
-    return appView.dexItemFactory().createType(descriptor);
-  }
-
+  
   private MethodProvider getMethodProviderOrNull(DexMethod method) {
     DexMethod original = appView.graphLense().getOriginalMethodSignature(method);
     assert original != null;
-    Map<DexType, DexType> backportCoreLibraryMembers =
-        appView.options().desugaredLibraryConfiguration.getBackportCoreLibraryMember();
-    if (backportCoreLibraryMembers.containsKey(original.holder)) {
-      DexType newHolder = backportCoreLibraryMembers.get(original.holder);
-      DexMethod newMethod =
-          appView.dexItemFactory().createMethod(newHolder, original.proto, original.name);
-      MethodProvider provider = rewritableMethods.getProvider(newMethod);
-      if (provider != null) {
-        return provider;
-      }
-      RetargetCoreLibraryMethodProvider extraProvider =
-          new RetargetCoreLibraryMethodProvider(newHolder, original, true);
-      // TODO(b/139788786): cache this entry, but without writing into a lock free structure.
-      // rewritableMethods.addProvider(extraProvider);
-      return extraProvider;
-    }
     return rewritableMethods.getProvider(original);
   }
 
-  public static void checkForAssumedLibraryTypes(AppView<?> appView) {
-    Map<DexString, Map<DexType, DexType>> retargetCoreLibMember =
-        appView.options().desugaredLibraryConfiguration.getRetargetCoreLibMember();
-    for (DexString methodName : retargetCoreLibMember.keySet()) {
-      for (DexType inType : retargetCoreLibMember.get(methodName).keySet()) {
-        DexClass typeClass = appView.definitionFor(inType);
-        if (typeClass == null) {
-          RewritableMethods.warnMissingRetargetCoreLibraryMember(inType, appView);
-        }
-      }
-    }
-  }
-
   private static final class RewritableMethods {
 
     // Map backported method to a provider for creating the actual target method (with code).
     private final Map<DexMethod, MethodProvider> rewritable = new IdentityHashMap<>();
-    // Map virtualRewrites hold a methodName->method mapping for virtual methods which are
-    // rewritten while the holder is non final but no superclass implement the method. In this case
-    // d8 needs to force resolution of given methods to see if the invoke needs to be rewritten.
-    private final Map<DexString, List<DexMethod>> virtualRewrites = new IdentityHashMap<>();
-    // non final virtual library methods requiring generation of emulated dispatch.
-    private final Set<DexMethod> emulatedDispatchMethods = Sets.newHashSet();
 
     RewritableMethods(InternalOptions options, AppView<?> appView) {
+
+      if (options.testing.forceLibBackportsInL8CfToCf) {
+        DexItemFactory factory = options.itemFactory;
+        initializeJava9OptionalMethodProviders(factory);
+        initializeJava10OptionalMethodProviders(factory);
+        initializeJava11OptionalMethodProviders(factory);
+        initializeStreamMethodProviders(factory);
+        return;
+      }
+
+      if (!options.shouldBackportMethods()) {
+        return;
+      }
+
       DexItemFactory factory = options.itemFactory;
 
       if (options.minApiLevel < AndroidApiLevel.K.getLevel()) {
@@ -565,9 +323,9 @@
       }
 
       // The following providers are currently not implemented at any API level in Android.
-      // They however require the Optional/Stream class to be present, either through
-      // desugared libraries or natively. If Optional/Stream class is not present,
-      // we do not desugar to avoid confusion in error messages.
+      // They however require the Optional/Stream class to be present, either through desugared
+      // libraries or natively. If Optional/Stream class is not present, we do not desugar to
+      // avoid confusion in error messages.
       if (appView.rewritePrefix.hasRewrittenType(factory.optionalType, appView)
           || options.minApiLevel >= AndroidApiLevel.N.getLevel()) {
         initializeJava9OptionalMethodProviders(factory);
@@ -583,27 +341,6 @@
       initializeJava9MethodProviders(factory);
       initializeJava10MethodProviders(factory);
       initializeJava11MethodProviders(factory);
-
-      if (!options.desugaredLibraryConfiguration.getRetargetCoreLibMember().isEmpty()) {
-        initializeRetargetCoreLibraryMembers(appView);
-      }
-    }
-
-    boolean matchesVirtualRewrite(DexMethod method) {
-      List<DexMethod> dexMethods = virtualRewrites.get(method.name);
-      if (dexMethods == null) {
-        return false;
-      }
-      for (DexMethod dexMethod : dexMethods) {
-        if (method.match(dexMethod)) {
-          return true;
-        }
-      }
-      return false;
-    }
-
-    public Set<DexMethod> getEmulatedDispatchMethods() {
-      return emulatedDispatchMethods;
     }
 
     boolean isEmpty() {
@@ -668,8 +405,9 @@
 
       // int Objects.compare(T a, T b, Comparator<? super T> c)
       name = factory.createString("compare");
-      proto = factory.createProto(factory.intType, factory.objectType, factory.objectType,
-          factory.comparatorType);
+      proto =
+          factory.createProto(
+              factory.intType, factory.objectType, factory.objectType, factory.comparatorType);
       method = factory.createMethod(type, proto, name);
       addProvider(new MethodGenerator(method, BackportedMethods::ObjectsMethods_compare));
 
@@ -1242,8 +980,9 @@
 
       // String String.join(CharSequence, CharSequence...)
       name = factory.createString("join");
-      proto = factory.createProto(factory.stringType, factory.charSequenceType,
-          factory.charSequenceArrayType);
+      proto =
+          factory.createProto(
+              factory.stringType, factory.charSequenceType, factory.charSequenceArrayType);
       method = factory.createMethod(type, proto, name);
       addProvider(
           new MethodGenerator(method, BackportedMethods::StringMethods_joinArray, "joinArray"));
@@ -1278,19 +1017,13 @@
         name = factory.createString("multiplyFull");
         proto = factory.createProto(factory.longType, factory.intType, factory.intType);
         method = factory.createMethod(type, proto, name);
-        addProvider(
-            new MethodGenerator(
-                method,
-                BackportedMethods::MathMethods_multiplyFull));
+        addProvider(new MethodGenerator(method, BackportedMethods::MathMethods_multiplyFull));
 
         // long {Math,StrictMath}.multiplyHigh(long, long)
         name = factory.createString("multiplyHigh");
         proto = factory.createProto(factory.longType, factory.longType, factory.longType);
         method = factory.createMethod(type, proto, name);
-        addProvider(
-            new MethodGenerator(
-                method,
-                BackportedMethods::MathMethods_multiplyHigh));
+        addProvider(new MethodGenerator(method, BackportedMethods::MathMethods_multiplyHigh));
 
         // long {Math,StrictMath}.floorDiv(long, int)
         name = factory.createString("floorDiv");
@@ -1384,8 +1117,7 @@
       proto = factory.createProto(type, factory.objectArrayType);
       method = factory.createMethod(type, proto, name);
       addProvider(
-          new MethodGenerator(
-              method, BackportedMethods::CollectionMethods_listOfArray, "ofArray"));
+          new MethodGenerator(method, BackportedMethods::CollectionMethods_listOfArray, "ofArray"));
 
       // Set<E> Set.of(<args>) for 0 to 10 arguments and Set.of(E[])
       type = factory.setType;
@@ -1405,8 +1137,7 @@
       proto = factory.createProto(type, factory.objectArrayType);
       method = factory.createMethod(type, proto, name);
       addProvider(
-          new MethodGenerator(
-              method, BackportedMethods::CollectionMethods_setOfArray, "ofArray"));
+          new MethodGenerator(method, BackportedMethods::CollectionMethods_setOfArray, "ofArray"));
 
       // Map<K, V> Map.of(<K, V args>) for 0 to 10 pairs and Map.ofEntries(Map.Entry<K, V>[])
       type = factory.mapType;
@@ -1553,24 +1284,24 @@
       // Optional{void,Int,Long,Double}.stream()
       DexType[] optionalTypes =
           new DexType[] {
-              optionalType,
-              factory.optionalDoubleType,
-              factory.optionalLongType,
-              factory.optionalIntType,
+            optionalType,
+            factory.optionalDoubleType,
+            factory.optionalLongType,
+            factory.optionalIntType,
           };
       DexType[] streamReturnTypes =
           new DexType[] {
-              factory.streamType,
-              factory.createType(factory.createString("Ljava/util/stream/DoubleStream;")),
-              factory.createType(factory.createString("Ljava/util/stream/LongStream;")),
-              factory.createType(factory.createString("Ljava/util/stream/IntStream;")),
+            factory.streamType,
+            factory.createType(factory.createString("Ljava/util/stream/DoubleStream;")),
+            factory.createType(factory.createString("Ljava/util/stream/LongStream;")),
+            factory.createType(factory.createString("Ljava/util/stream/IntStream;")),
           };
       TemplateMethodFactory[] streamMethodFactories =
           new TemplateMethodFactory[] {
-              BackportedMethods::OptionalMethods_stream,
-              BackportedMethods::OptionalMethods_streamDouble,
-              BackportedMethods::OptionalMethods_streamLong,
-              BackportedMethods::OptionalMethods_streamInt,
+            BackportedMethods::OptionalMethods_stream,
+            BackportedMethods::OptionalMethods_streamDouble,
+            BackportedMethods::OptionalMethods_streamLong,
+            BackportedMethods::OptionalMethods_streamInt,
           };
       name = factory.createString("stream");
       for (int i = 0; i < optionalTypes.length; i++) {
@@ -1579,24 +1310,20 @@
         proto = factory.createProto(streamReturnType);
         method = factory.createMethod(optional, proto, name);
         addProvider(
-            new StatifyingMethodGenerator(
-                method, streamMethodFactories[i], "stream", optional));
+            new StatifyingMethodGenerator(method, streamMethodFactories[i], "stream", optional));
       }
 
       // Optional{void,Int,Long,Double}.ifPresentOrElse(consumer,runnable)
       DexType[] consumerTypes =
-          new DexType[]{
-              factory.consumerType,
-              factory.doubleConsumer,
-              factory.longConsumer,
-              factory.intConsumer
+          new DexType[] {
+            factory.consumerType, factory.doubleConsumer, factory.longConsumer, factory.intConsumer
           };
       TemplateMethodFactory[] methodFactories =
-          new TemplateMethodFactory[]{
-              BackportedMethods::OptionalMethods_ifPresentOrElse,
-              BackportedMethods::OptionalMethods_ifPresentOrElseDouble,
-              BackportedMethods::OptionalMethods_ifPresentOrElseLong,
-              BackportedMethods::OptionalMethods_ifPresentOrElseInt
+          new TemplateMethodFactory[] {
+            BackportedMethods::OptionalMethods_ifPresentOrElse,
+            BackportedMethods::OptionalMethods_ifPresentOrElseDouble,
+            BackportedMethods::OptionalMethods_ifPresentOrElseLong,
+            BackportedMethods::OptionalMethods_ifPresentOrElseInt
           };
       for (int i = 0; i < optionalTypes.length; i++) {
         DexType optional = optionalTypes[i];
@@ -1613,17 +1340,14 @@
       // Optional{void,Int,Long,Double}.orElseThrow()
       DexType[] optionalTypes =
           new DexType[] {
-              factory.optionalType,
-              factory.optionalDoubleType,
-              factory.optionalLongType,
-              factory.optionalIntType,
+            factory.optionalType,
+            factory.optionalDoubleType,
+            factory.optionalLongType,
+            factory.optionalIntType,
           };
       DexType[] returnTypes =
           new DexType[] {
-              factory.objectType,
-              factory.doubleType,
-              factory.longType,
-              factory.intType,
+            factory.objectType, factory.doubleType, factory.longType, factory.intType,
           };
       MethodInvokeRewriter[] rewriters =
           new MethodInvokeRewriter[] {
@@ -1644,17 +1368,17 @@
       // Optional{void,Int,Long,Double}.isEmpty()
       DexType[] optionalTypes =
           new DexType[] {
-              factory.optionalType,
-              factory.optionalDoubleType,
-              factory.optionalLongType,
-              factory.optionalIntType,
+            factory.optionalType,
+            factory.optionalDoubleType,
+            factory.optionalLongType,
+            factory.optionalIntType,
           };
       TemplateMethodFactory[] methodFactories =
-          new TemplateMethodFactory[]{
-              BackportedMethods::OptionalMethods_isEmpty,
-              BackportedMethods::OptionalMethods_isEmptyDouble,
-              BackportedMethods::OptionalMethods_isEmptyLong,
-              BackportedMethods::OptionalMethods_isEmptyInt
+          new TemplateMethodFactory[] {
+            BackportedMethods::OptionalMethods_isEmpty,
+            BackportedMethods::OptionalMethods_isEmptyDouble,
+            BackportedMethods::OptionalMethods_isEmptyLong,
+            BackportedMethods::OptionalMethods_isEmptyInt
           };
       DexString name = factory.createString("isEmpty");
       for (int i = 0; i < optionalTypes.length; i++) {
@@ -1675,76 +1399,7 @@
       DexProto proto = factory.createProto(factory.streamType, factory.objectType);
       DexMethod method = factory.createMethod(streamType, proto, name);
       addProvider(
-          new MethodGenerator(
-              method, BackportedMethods::StreamMethods_ofNullable, "ofNullable"));
-    }
-
-    private static void warnMissingRetargetCoreLibraryMember(DexType type, AppView<?> appView) {
-      StringDiagnostic warning =
-          new StringDiagnostic(
-              "Cannot retarget core library member "
-                  + type.getName()
-                  + " because the class is missing.");
-      appView.options().reporter.warning(warning);
-    }
-
-    private void initializeRetargetCoreLibraryMembers(AppView<?> appView) {
-      assert appView.appInfo().hasClassHierarchy()
-          : "Class hierarchy required for desugared library.";
-      Map<DexString, Map<DexType, DexType>> retargetCoreLibMember =
-          appView.options().desugaredLibraryConfiguration.getRetargetCoreLibMember();
-      for (DexString methodName : retargetCoreLibMember.keySet()) {
-        for (DexType inType : retargetCoreLibMember.get(methodName).keySet()) {
-          DexClass typeClass = appView.definitionFor(inType);
-          if (typeClass != null) {
-            DexType newHolder = retargetCoreLibMember.get(methodName).get(inType);
-            List<DexEncodedMethod> found = findDexEncodedMethodsWithName(methodName, typeClass);
-            for (DexEncodedMethod encodedMethod : found) {
-              if (!encodedMethod.isStatic()) {
-                virtualRewrites.putIfAbsent(encodedMethod.method.name, new ArrayList<>());
-                virtualRewrites.get(encodedMethod.method.name).add(encodedMethod.method);
-                if (InterfaceMethodRewriter.isEmulatedInterfaceDispatch(appView, encodedMethod)) {
-                  // In this case interface method rewriter takes care of it.
-                  continue;
-                } else if (!encodedMethod.isFinal()) {
-                  // Virtual rewrites require emulated dispatch for inheritance.
-                  // The call is rewritten to the dispatch holder class instead.
-                  handleEmulateDispatch(appView, encodedMethod.method);
-                  newHolder = dispatchHolderTypeFor(appView, encodedMethod.method);
-                }
-              }
-              DexProto proto = encodedMethod.method.proto;
-              DexMethod method = appView.dexItemFactory().createMethod(inType, proto, methodName);
-              addProvider(
-                  new RetargetCoreLibraryMethodProvider(
-                      newHolder, method, encodedMethod.isStatic()));
-            }
-          }
-        }
-      }
-    }
-
-    private List<DexEncodedMethod> findDexEncodedMethodsWithName(
-        DexString methodName, DexClass clazz) {
-      List<DexEncodedMethod> found = new ArrayList<>();
-      for (DexEncodedMethod encodedMethod : clazz.methods()) {
-        if (encodedMethod.method.name == methodName) {
-          found.add(encodedMethod);
-        }
-      }
-      assert found.size() > 0 : "Should have found a method (library specifications).";
-      return found;
-    }
-
-    private void handleEmulateDispatch(AppView<?> appView, DexMethod method) {
-      emulatedDispatchMethods.add(method);
-      if (!appView.options().isDesugaredLibraryCompilation()) {
-        // Add rewrite rules so keeps rules are correctly generated in the program.
-        DexType dispatchInterfaceType = dispatchInterfaceTypeFor(appView, method);
-        appView.rewritePrefix.rewriteType(dispatchInterfaceType, dispatchInterfaceType);
-        DexType dispatchHolderType = dispatchHolderTypeFor(appView, method);
-        appView.rewritePrefix.rewriteType(dispatchHolderType, dispatchHolderType);
-      }
+          new MethodGenerator(method, BackportedMethods::StreamMethods_ofNullable, "ofNullable"));
     }
 
     private void addProvider(MethodProvider generator) {
@@ -1779,52 +1434,6 @@
     public abstract boolean requiresGenerationOfCode();
   }
 
-  private static class RetargetCoreLibraryMethodProvider extends MethodProvider {
-
-    private final DexType newHolder;
-    private DexMethod targetMethod;
-    private boolean isStatic;
-
-    RetargetCoreLibraryMethodProvider(DexType newHolder, DexMethod method, boolean isStatic) {
-      super(method);
-      this.newHolder = newHolder;
-      this.isStatic = isStatic;
-    }
-
-    @Override
-    public void rewriteInvoke(
-        InvokeMethod invoke,
-        InstructionListIterator iterator,
-        IRCode code,
-        AppView<?> appView,
-        Set<Value> affectedValues) {
-      iterator.replaceCurrentInstruction(
-          new InvokeStatic(provideMethod(appView), invoke.outValue(), invoke.inValues()));
-    }
-
-    @Override
-    public DexMethod provideMethod(AppView<?> appView) {
-      if (targetMethod != null) {
-        return targetMethod;
-      }
-      DexItemFactory factory = appView.dexItemFactory();
-      DexProto newProto =
-          isStatic ? method.proto : factory.prependTypeToProto(method.holder, method.proto);
-      targetMethod = factory.createMethod(newHolder, newProto, method.name);
-      return targetMethod;
-    }
-
-    @Override
-    public Code generateTemplateMethod(InternalOptions options, DexMethod method) {
-      throw new Unreachable("Does not generate any method.");
-    }
-
-    @Override
-    public boolean requiresGenerationOfCode() {
-      return false;
-    }
-  }
-
   private static final class InvokeRewriter extends MethodProvider {
 
     private final MethodInvokeRewriter rewriter;
@@ -1896,14 +1505,9 @@
       DexItemFactory factory = appView.dexItemFactory();
       String unqualifiedName = method.holder.getName();
       // Avoid duplicate class names between core lib dex file and program dex files.
-      String desugaredLibPrefix =
-          appView
-              .options()
-              .desugaredLibraryConfiguration
-              .getSynthesizedLibraryClassesPackagePrefix(appView);
       String descriptor =
           "L"
-              + desugaredLibPrefix
+              + appView.options().synthesizedClassPrefix
               + UTILITY_CLASS_NAME_PREFIX
               + '$'
               + unqualifiedName
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/ClassProcessor.java b/src/main/java/com/android/tools/r8/ir/desugar/ClassProcessor.java
index 76e97df..3d16d19 100644
--- a/src/main/java/com/android/tools/r8/ir/desugar/ClassProcessor.java
+++ b/src/main/java/com/android/tools/r8/ir/desugar/ClassProcessor.java
@@ -229,7 +229,7 @@
     for (DexProgramClass clazz : newSyntheticMethods.keySet()) {
       List<DexEncodedMethod> newForwardingMethods = newSyntheticMethods.get(clazz);
       if (newForwardingMethods != null) {
-        clazz.appendVirtualMethods(newForwardingMethods);
+        clazz.addVirtualMethods(newForwardingMethods);
         newForwardingMethods.forEach(newSynthesizedMethodConsumer);
       }
     }
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/CovariantReturnTypeAnnotationTransformer.java b/src/main/java/com/android/tools/r8/ir/desugar/CovariantReturnTypeAnnotationTransformer.java
index 3cadabf..2be1832 100644
--- a/src/main/java/com/android/tools/r8/ir/desugar/CovariantReturnTypeAnnotationTransformer.java
+++ b/src/main/java/com/android/tools/r8/ir/desugar/CovariantReturnTypeAnnotationTransformer.java
@@ -16,6 +16,9 @@
 import com.android.tools.r8.graph.DexProto;
 import com.android.tools.r8.graph.DexType;
 import com.android.tools.r8.graph.DexValue;
+import com.android.tools.r8.graph.DexValue.DexValueAnnotation;
+import com.android.tools.r8.graph.DexValue.DexValueArray;
+import com.android.tools.r8.graph.DexValue.DexValueType;
 import com.android.tools.r8.graph.MethodAccessFlags;
 import com.android.tools.r8.ir.code.Invoke;
 import com.android.tools.r8.ir.conversion.IRConverter;
@@ -96,7 +99,7 @@
           method.annotations().keepIf(x -> !isCovariantReturnTypeAnnotation(x.annotation)));
     }
     // Add the newly constructed methods to the class.
-    clazz.appendVirtualMethods(covariantReturnTypeMethods);
+    clazz.addVirtualMethods(covariantReturnTypeMethods);
   }
 
   // Processes all the dalvik.annotation.codegen.CovariantReturnType and dalvik.annotation.codegen.
@@ -201,22 +204,22 @@
       String name = element.name.toString();
       if (annotation.type == factory.annotationCovariantReturnType) {
         if (name.equals("returnType")) {
-          if (!(element.value instanceof DexValue.DexValueType)) {
+          DexValueType dexValueType = element.value.asDexValueType();
+          if (dexValueType == null) {
             throw new CompilationError(
                 String.format(
                     "Expected element \"returnType\" of CovariantReturnType annotation to "
                         + "reference a type (method: \"%s\", was: %s)",
                     method.toSourceString(), element.value.getClass().getCanonicalName()));
           }
-
-          DexValue.DexValueType dexValueType = (DexValue.DexValueType) element.value;
           covariantReturnTypes.add(dexValueType.value);
         } else if (name.equals("presentAfter")) {
           hasPresentAfterElement = true;
         }
       } else {
         if (name.equals("value")) {
-          if (!(element.value instanceof DexValue.DexValueArray)) {
+          DexValueArray array = element.value.asDexValueArray();
+          if (array == null) {
             throw new CompilationError(
                 String.format(
                     "Expected element \"value\" of CovariantReturnTypes annotation to "
@@ -224,11 +227,10 @@
                     method.toSourceString(), element.value.getClass().getCanonicalName()));
           }
 
-          DexValue.DexValueArray array = (DexValue.DexValueArray) element.value;
           // Handle the inner dalvik.annotation.codegen.CovariantReturnType annotations recursively.
           for (DexValue value : array.getValues()) {
-            assert value instanceof DexValue.DexValueAnnotation;
-            DexValue.DexValueAnnotation innerAnnotation = (DexValue.DexValueAnnotation) value;
+            assert value.isDexValueAnnotation();
+            DexValueAnnotation innerAnnotation = value.asDexValueAnnotation();
             getCovariantReturnTypesFromAnnotation(
                 clazz, method, innerAnnotation.value, covariantReturnTypes);
           }
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/DesugaredLibraryAPIConverter.java b/src/main/java/com/android/tools/r8/ir/desugar/DesugaredLibraryAPIConverter.java
index ceed9ef..c81eedb 100644
--- a/src/main/java/com/android/tools/r8/ir/desugar/DesugaredLibraryAPIConverter.java
+++ b/src/main/java/com/android/tools/r8/ir/desugar/DesugaredLibraryAPIConverter.java
@@ -16,7 +16,7 @@
 import com.android.tools.r8.graph.DexProto;
 import com.android.tools.r8.graph.DexType;
 import com.android.tools.r8.ir.analysis.type.Nullability;
-import com.android.tools.r8.ir.analysis.type.TypeLatticeElement;
+import com.android.tools.r8.ir.analysis.type.TypeElement;
 import com.android.tools.r8.ir.code.BasicBlock;
 import com.android.tools.r8.ir.code.IRCode;
 import com.android.tools.r8.ir.code.Instruction;
@@ -29,14 +29,15 @@
 import com.android.tools.r8.ir.synthetic.DesugaredLibraryAPIConversionCfCodeProvider.APIConverterWrapperCfCodeProvider;
 import com.android.tools.r8.utils.BooleanUtils;
 import com.android.tools.r8.utils.DescriptorUtils;
+import com.android.tools.r8.utils.ListUtils;
+import com.android.tools.r8.utils.OptionalBool;
 import com.android.tools.r8.utils.StringDiagnostic;
+import com.android.tools.r8.utils.WorkList;
 import com.google.common.collect.Sets;
 import java.util.ArrayList;
-import java.util.Arrays;
 import java.util.Collections;
-import java.util.HashMap;
 import java.util.HashSet;
-import java.util.LinkedList;
+import java.util.IdentityHashMap;
 import java.util.List;
 import java.util.ListIterator;
 import java.util.Map;
@@ -70,7 +71,9 @@
   // Enqueuer and not during IR processing.
   private final Mode mode;
   private final DesugaredLibraryWrapperSynthesizer wrapperSynthesizor;
-  private final Map<DexClass, Set<DexEncodedMethod>> callBackMethods = new HashMap<>();
+  private final Map<DexClass, Set<DexEncodedMethod>> callBackMethods = new IdentityHashMap<>();
+  private final Map<DexClass, List<DexEncodedMethod>> pendingCallBackMethods =
+      new IdentityHashMap<>();
   private final Set<DexMethod> trackedCallBackAPIs;
   private final Set<DexMethod> trackedAPIs;
 
@@ -172,7 +175,9 @@
     // library type), but the enqueuer cannot see that.
     // To avoid too much computation we first look if the method would need to be rewritten if
     // it would override a library method, then check if it overrides a library method.
-    if (encodedMethod.isPrivateMethod() || encodedMethod.isStatic()) {
+    if (encodedMethod.isPrivateMethod()
+        || encodedMethod.isStatic()
+        || encodedMethod.isLibraryMethodOverride().isFalse()) {
       return false;
     }
     DexMethod method = encodedMethod.method;
@@ -195,22 +200,22 @@
 
   private boolean overridesLibraryMethod(DexClass theClass, DexMethod method) {
     // We look up everywhere to see if there is a supertype/interface implementing the method...
-    LinkedList<DexType> workList = new LinkedList<>();
-    Collections.addAll(workList, theClass.interfaces.values);
+    WorkList<DexType> workList = WorkList.newIdentityWorkList();
+    workList.addIfNotSeen(theClass.interfaces.values);
     boolean foundOverrideToRewrite = false;
     // There is no methods with desugared types on Object.
     if (theClass.superType != factory.objectType) {
-      workList.add(theClass.superType);
+      workList.addIfNotSeen(theClass.superType);
     }
-    while (!workList.isEmpty()) {
-      DexType current = workList.removeFirst();
+    while (workList.hasNext()) {
+      DexType current = workList.next();
       DexClass dexClass = appView.definitionFor(current);
       if (dexClass == null) {
         continue;
       }
-      workList.addAll(Arrays.asList(dexClass.interfaces.values));
+      workList.addIfNotSeen(dexClass.interfaces.values);
       if (dexClass.superType != factory.objectType) {
-        workList.add(dexClass.superType);
+        workList.addIfNotSeen(dexClass.superType);
       }
       if (!dexClass.isLibraryClass() && !appView.options().isDesugaredLibraryCompilation()) {
         continue;
@@ -249,8 +254,9 @@
 
   private synchronized void addCallBackSignature(DexClass dexClass, DexEncodedMethod method) {
     assert dexClass.type == method.method.holder;
-    callBackMethods.putIfAbsent(dexClass, new HashSet<>());
-    callBackMethods.get(dexClass).add(method);
+    if (callBackMethods.computeIfAbsent(dexClass, key -> new HashSet<>()).add(method)) {
+      pendingCallBackMethods.computeIfAbsent(dexClass, key -> new ArrayList<>()).add(method);
+    }
   }
 
   public static DexMethod methodWithVivifiedTypeInSignature(
@@ -289,19 +295,24 @@
     if (appView.options().testing.trackDesugaredAPIConversions) {
       generateTrackDesugaredAPIWarnings(trackedAPIs, "");
       generateTrackDesugaredAPIWarnings(trackedCallBackAPIs, "callback ");
+      trackedAPIs.clear();
+      trackedCallBackAPIs.clear();
     }
     List<DexEncodedMethod> result = new ArrayList<>();
-    for (DexClass dexClass : callBackMethods.keySet()) {
-      List<DexEncodedMethod> dexEncodedMethods =
-          generateCallbackMethods(callBackMethods.get(dexClass), dexClass);
-      dexClass.appendVirtualMethods(dexEncodedMethods);
-      result.addAll(dexEncodedMethods);
-    }
+    pendingCallBackMethods.forEach(
+        (clazz, callbacks) -> {
+          List<DexEncodedMethod> generated =
+              ListUtils.map(callbacks, callback -> generateCallbackMethod(callback, clazz));
+          clazz.addVirtualMethods(generated);
+          result.addAll(generated);
+        });
+    pendingCallBackMethods.clear();
     return result;
   }
 
-  public Map<DexProgramClass, DexProgramClass> synthesizeWrappersAndMapToReverse() {
-    return wrapperSynthesizor.synthesizeWrappersAndMapToReverse();
+  public List<DexProgramClass> synthesizeWrappers(
+      Map<DexType, DexProgramClass> synthesizedWrappers) {
+    return wrapperSynthesizor.synthesizeWrappers(synthesizedWrappers);
   }
 
   public DexClasspathClass synthesizeClasspathMock(
@@ -309,23 +320,21 @@
     return wrapperSynthesizor.synthesizeClasspathMock(classToMock, mockType, mockIsInterface);
   }
 
-  private List<DexEncodedMethod> generateCallbackMethods(
-      Set<DexEncodedMethod> originalMethods, DexClass dexClass) {
-    List<DexEncodedMethod> newDexEncodedMethods = new ArrayList<>();
-    for (DexEncodedMethod originalMethod : originalMethods) {
-      DexMethod methodToInstall =
-          methodWithVivifiedTypeInSignature(originalMethod.method, dexClass.type, appView);
-      CfCode cfCode =
-          new APIConverterWrapperCfCodeProvider(
-                  appView, originalMethod.method, null, this, dexClass.isInterface())
-              .generateCfCode();
-      DexEncodedMethod newDexEncodedMethod =
-          wrapperSynthesizor.newSynthesizedMethod(methodToInstall, originalMethod, cfCode);
-      newDexEncodedMethod.setCode(cfCode, appView);
-      newDexEncodedMethods.add(newDexEncodedMethod);
+  private DexEncodedMethod generateCallbackMethod(
+      DexEncodedMethod originalMethod, DexClass dexClass) {
+    DexMethod methodToInstall =
+        methodWithVivifiedTypeInSignature(originalMethod.method, dexClass.type, appView);
+    CfCode cfCode =
+        new APIConverterWrapperCfCodeProvider(
+                appView, originalMethod.method, null, this, dexClass.isInterface())
+            .generateCfCode();
+    DexEncodedMethod newDexEncodedMethod =
+        wrapperSynthesizor.newSynthesizedMethod(methodToInstall, originalMethod, cfCode);
+    newDexEncodedMethod.setCode(cfCode, appView);
+    if (originalMethod.isLibraryMethodOverride().isTrue()) {
+      newDexEncodedMethod.setLibraryMethodOverride(OptionalBool.TRUE);
     }
-    assert Sets.newHashSet(newDexEncodedMethods).size() == newDexEncodedMethods.size();
-    return newDexEncodedMethods;
+    return newDexEncodedMethod;
   }
 
   private void generateTrackDesugaredAPIWarnings(Set<DexMethod> tracked, String inner) {
@@ -513,7 +522,7 @@
     DexMethod conversionMethod = createConversionMethod(argType, argType, argVivifiedType);
     // The value is null only if the input is null.
     Value convertedValue =
-        createConversionValue(code, inValue.getTypeLattice().nullability(), argVivifiedType);
+        createConversionValue(code, inValue.getType().nullability(), argVivifiedType);
     return new InvokeStatic(conversionMethod, convertedValue, Collections.singletonList(inValue));
   }
 
@@ -524,9 +533,8 @@
     Value outValue = invokeMethod.outValue();
     outValue.replaceUsers(convertedValue);
     // The only user of out value is now the new invoke static, so no type propagation is required.
-    outValue.setTypeLattice(
-        TypeLatticeElement.fromDexType(
-            returnVivifiedType, outValue.getTypeLattice().nullability(), appView));
+    outValue.setType(
+        TypeElement.fromDexType(returnVivifiedType, outValue.getType().nullability(), appView));
     return new InvokeStatic(conversionMethod, convertedValue, Collections.singletonList(outValue));
   }
 
@@ -557,7 +565,7 @@
   }
 
   private Value createConversionValue(IRCode code, Nullability nullability, DexType valueType) {
-    return code.createValue(TypeLatticeElement.fromDexType(valueType, nullability, appView));
+    return code.createValue(TypeElement.fromDexType(valueType, nullability, appView));
   }
 
   public boolean canConvert(DexType type) {
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/DesugaredLibraryConfiguration.java b/src/main/java/com/android/tools/r8/ir/desugar/DesugaredLibraryConfiguration.java
index bdb4cd9..baa6317 100644
--- a/src/main/java/com/android/tools/r8/ir/desugar/DesugaredLibraryConfiguration.java
+++ b/src/main/java/com/android/tools/r8/ir/desugar/DesugaredLibraryConfiguration.java
@@ -110,12 +110,6 @@
     return libraryCompilation;
   }
 
-  public String getSynthesizedLibraryClassesPackagePrefix(AppView<?> appView) {
-    return appView.options().isDesugaredLibraryCompilation()
-        ? synthesizedLibraryClassesPackagePrefix
-        : "";
-  }
-
   public String getSynthesizedLibraryClassesPackagePrefix() {
     return synthesizedLibraryClassesPackagePrefix;
   }
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/DesugaredLibraryRetargeter.java b/src/main/java/com/android/tools/r8/ir/desugar/DesugaredLibraryRetargeter.java
new file mode 100644
index 0000000..3949767
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/ir/desugar/DesugaredLibraryRetargeter.java
@@ -0,0 +1,441 @@
+// 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.ir.desugar;
+
+import com.android.tools.r8.dex.Constants;
+import com.android.tools.r8.graph.*;
+import com.android.tools.r8.ir.code.IRCode;
+import com.android.tools.r8.ir.code.Instruction;
+import com.android.tools.r8.ir.code.InstructionListIterator;
+import com.android.tools.r8.ir.code.InvokeMethod;
+import com.android.tools.r8.ir.code.InvokeStatic;
+import com.android.tools.r8.ir.conversion.IRConverter;
+import com.android.tools.r8.utils.StringDiagnostic;
+import com.google.common.collect.Maps;
+import com.google.common.collect.Sets;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.IdentityHashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+import java.util.concurrent.ExecutionException;
+import java.util.concurrent.ExecutorService;
+import java.util.function.Consumer;
+
+public class DesugaredLibraryRetargeter {
+
+  public static final String DESUGAR_LIB_RETARGET_CLASS_NAME_PREFIX =
+      "$r8$retargetLibraryMember$virtualDispatch";
+
+  private final AppView<AppInfoWithClassHierarchy> appView;
+  private final Map<DexMethod, DexMethod> retargetLibraryMember = new IdentityHashMap<>();
+  // Map virtualRewrites hold a methodName->method mapping for virtual methods which are
+  // rewritten while the holder is non final but no superclass implement the method. In this case
+  // d8 needs to force resolution of given methods to see if the invoke needs to be rewritten.
+  private final Map<DexString, List<DexMethod>> virtualRewrites = new IdentityHashMap<>();
+  // Non final virtual library methods requiring generation of emulated dispatch.
+  private final Set<DexMethod> emulatedDispatchMethods = Sets.newHashSet();
+
+  public DesugaredLibraryRetargeter(AppView<?> appView) {
+    assert appView.appInfo().hasClassHierarchy()
+        : "Class hierarchy required for desugared library.";
+    this.appView = appView.withClassHierarchy();
+    if (appView.options().desugaredLibraryConfiguration.getRetargetCoreLibMember().isEmpty()) {
+      return;
+    }
+    new RetargetingSetup().setUpRetargeting();
+  }
+
+  public static void checkForAssumedLibraryTypes(AppView<?> appView) {
+    Map<DexString, Map<DexType, DexType>> retargetCoreLibMember =
+        appView.options().desugaredLibraryConfiguration.getRetargetCoreLibMember();
+    for (DexString methodName : retargetCoreLibMember.keySet()) {
+      for (DexType inType : retargetCoreLibMember.get(methodName).keySet()) {
+        DexClass typeClass = appView.definitionFor(inType);
+        if (typeClass == null) {
+          warnMissingRetargetCoreLibraryMember(inType, appView);
+        }
+      }
+    }
+  }
+
+  private static void warnMissingRetargetCoreLibraryMember(DexType type, AppView<?> appView) {
+    StringDiagnostic warning =
+        new StringDiagnostic(
+            "Cannot retarget core library member "
+                + type.getName()
+                + " because the class is missing.");
+    appView.options().reporter.warning(warning);
+  }
+
+  // Used by the ListOfBackportedMethods utility.
+  void visit(Consumer<DexMethod> consumer) {
+    retargetLibraryMember.keySet().forEach(consumer);
+  }
+
+  public void desugar(IRCode code) {
+    if (retargetLibraryMember.isEmpty()) {
+      return;
+    }
+
+    InstructionListIterator iterator = code.instructionListIterator();
+    while (iterator.hasNext()) {
+      Instruction instruction = iterator.next();
+      if (!instruction.isInvokeMethod()) {
+        continue;
+      }
+
+      InvokeMethod invoke = instruction.asInvokeMethod();
+      DexMethod retarget = getRetargetLibraryMember(invoke.getInvokedMethod());
+      if (retarget == null) {
+        if (!matchesVirtualRewrite(invoke.getInvokedMethod())) {
+          continue;
+        }
+        // We need to force resolution, even on d8, to know if the invoke has to be rewritten.
+        ResolutionResult resolutionResult =
+            appView
+                .appInfo()
+                .resolveMethod(invoke.getInvokedMethod().holder, invoke.getInvokedMethod());
+        if (resolutionResult.isFailedResolution()) {
+          continue;
+        }
+        DexEncodedMethod singleTarget = resolutionResult.getSingleTarget();
+        assert singleTarget != null;
+        retarget = getRetargetLibraryMember(singleTarget.method);
+        if (retarget == null) {
+          continue;
+        }
+      }
+
+      // Due to emulated dispatch, we have to rewrite invoke-super differently or we end up in
+      // infinite loops. We do direct resolution. This is a very uncommon case.
+      if (invoke.isInvokeSuper() && matchesVirtualRewrite(invoke.getInvokedMethod())) {
+        DexEncodedMethod dexEncodedMethod =
+            appView
+                .appInfo()
+                .withClassHierarchy()
+                .lookupSuperTarget(invoke.getInvokedMethod(), code.method.method.holder);
+        // Final methods can be rewritten as a normal invoke.
+        if (dexEncodedMethod != null && !dexEncodedMethod.isFinal()) {
+          DexMethod retargetMethod =
+              appView
+                  .options()
+                  .desugaredLibraryConfiguration
+                  .retargetMethod(dexEncodedMethod.method, appView);
+          if (retargetMethod != null) {
+            iterator.replaceCurrentInstruction(
+                new InvokeStatic(retargetMethod, invoke.outValue(), invoke.arguments()));
+          }
+          continue;
+        }
+      }
+
+      iterator.replaceCurrentInstruction(
+          new InvokeStatic(retarget, invoke.outValue(), invoke.inValues()));
+    }
+  }
+
+  private DexMethod getRetargetLibraryMember(DexMethod method) {
+    Map<DexType, DexType> backportCoreLibraryMembers =
+        appView.options().desugaredLibraryConfiguration.getBackportCoreLibraryMember();
+    if (backportCoreLibraryMembers.containsKey(method.holder)) {
+      DexType newHolder = backportCoreLibraryMembers.get(method.holder);
+      return appView.dexItemFactory().createMethod(newHolder, method.proto, method.name);
+    }
+    return retargetLibraryMember.get(method);
+  }
+
+  private boolean matchesVirtualRewrite(DexMethod method) {
+    List<DexMethod> dexMethods = virtualRewrites.get(method.name);
+    if (dexMethods == null) {
+      return false;
+    }
+    for (DexMethod dexMethod : dexMethods) {
+      if (method.match(dexMethod)) {
+        return true;
+      }
+    }
+    return false;
+  }
+
+  private class RetargetingSetup {
+
+    private void setUpRetargeting() {
+      Map<DexString, Map<DexType, DexType>> retargetCoreLibMember =
+          appView.options().desugaredLibraryConfiguration.getRetargetCoreLibMember();
+      for (DexString methodName : retargetCoreLibMember.keySet()) {
+        for (DexType inType : retargetCoreLibMember.get(methodName).keySet()) {
+          DexClass typeClass = appView.definitionFor(inType);
+          if (typeClass != null) {
+            DexType newHolder = retargetCoreLibMember.get(methodName).get(inType);
+            List<DexEncodedMethod> found = findDexEncodedMethodsWithName(methodName, typeClass);
+            for (DexEncodedMethod encodedMethod : found) {
+              if (!encodedMethod.isStatic()) {
+                virtualRewrites.putIfAbsent(encodedMethod.method.name, new ArrayList<>());
+                virtualRewrites.get(encodedMethod.method.name).add(encodedMethod.method);
+                if (InterfaceMethodRewriter.isEmulatedInterfaceDispatch(appView, encodedMethod)) {
+                  // In this case interface method rewriter takes care of it.
+                  continue;
+                } else if (!encodedMethod.isFinal()) {
+                  // Virtual rewrites require emulated dispatch for inheritance.
+                  // The call is rewritten to the dispatch holder class instead.
+                  handleEmulateDispatch(appView, encodedMethod.method);
+                  newHolder = dispatchHolderTypeFor(encodedMethod.method);
+                }
+              }
+              DexProto proto = encodedMethod.method.proto;
+              DexMethod method = appView.dexItemFactory().createMethod(inType, proto, methodName);
+              retargetLibraryMember.put(
+                  method, computeRetargetMethod(method, encodedMethod.isStatic(), newHolder));
+            }
+          }
+        }
+      }
+    }
+
+    private DexMethod computeRetargetMethod(DexMethod method, boolean isStatic, DexType newHolder) {
+      DexItemFactory factory = appView.dexItemFactory();
+      DexProto newProto =
+          isStatic ? method.proto : factory.prependTypeToProto(method.holder, method.proto);
+      return factory.createMethod(newHolder, newProto, method.name);
+    }
+
+    private List<DexEncodedMethod> findDexEncodedMethodsWithName(
+        DexString methodName, DexClass clazz) {
+      List<DexEncodedMethod> found = new ArrayList<>();
+      for (DexEncodedMethod encodedMethod : clazz.methods()) {
+        if (encodedMethod.method.name == methodName) {
+          found.add(encodedMethod);
+        }
+      }
+      assert found.size() > 0 : "Should have found a method (library specifications).";
+      return found;
+    }
+
+    private void handleEmulateDispatch(AppView<?> appView, DexMethod method) {
+      emulatedDispatchMethods.add(method);
+      if (!appView.options().isDesugaredLibraryCompilation()) {
+        // Add rewrite rules so keeps rules are correctly generated in the program.
+        DexType dispatchInterfaceType = dispatchInterfaceTypeFor(method);
+        appView.rewritePrefix.rewriteType(dispatchInterfaceType, dispatchInterfaceType);
+        DexType dispatchHolderType = dispatchHolderTypeFor(method);
+        appView.rewritePrefix.rewriteType(dispatchHolderType, dispatchHolderType);
+      }
+    }
+  }
+
+  public void synthesizeRetargetClasses(
+      DexApplication.Builder<?> builder, ExecutorService executorService, IRConverter converter)
+      throws ExecutionException {
+    new EmulatedDispatchTreeFixer().fixApp(builder, executorService, converter);
+  }
+
+  // The rewrite of virtual calls requires to go through emulate dispatch. This class is responsible
+  // for inserting interfaces on library boundaries and forwarding methods in the program, and to
+  // synthesize the interfaces and emulated dispatch classes in the desugared library.
+  class EmulatedDispatchTreeFixer {
+
+    void fixApp(
+        DexApplication.Builder<?> builder, ExecutorService executorService, IRConverter converter)
+        throws ExecutionException {
+      if (appView.options().isDesugaredLibraryCompilation()) {
+        synthesizeEmulatedDispatchMethods(builder);
+      } else {
+        addInterfacesAndForwardingMethods(executorService, converter);
+      }
+    }
+
+    private void addInterfacesAndForwardingMethods(
+        ExecutorService executorService, IRConverter converter) throws ExecutionException {
+      assert !appView.options().isDesugaredLibraryCompilation();
+      Map<DexType, List<DexMethod>> map = Maps.newIdentityHashMap();
+      for (DexMethod emulatedDispatchMethod : emulatedDispatchMethods) {
+        map.putIfAbsent(emulatedDispatchMethod.holder, new ArrayList<>(1));
+        map.get(emulatedDispatchMethod.holder).add(emulatedDispatchMethod);
+      }
+      List<DexEncodedMethod> addedMethods = new ArrayList<>();
+      for (DexProgramClass clazz : appView.appInfo().classes()) {
+        if (clazz.superType == null) {
+          assert clazz.type == appView.dexItemFactory().objectType : clazz.type.toSourceString();
+          continue;
+        }
+        DexClass dexClass = appView.definitionFor(clazz.superType);
+        // Only performs computation if superclass is a library class, but not object to filter out
+        // the most common case.
+        if (dexClass != null
+            && dexClass.isLibraryClass()
+            && dexClass.type != appView.dexItemFactory().objectType) {
+          for (DexType dexType : map.keySet()) {
+            if (inherit(dexClass.asLibraryClass(), dexType)) {
+              addedMethods.addAll(addInterfacesAndForwardingMethods(clazz, map.get(dexType)));
+            }
+          }
+        }
+      }
+      if (addedMethods.isEmpty()) {
+        return;
+      }
+      converter.processMethodsConcurrently(addedMethods, executorService);
+    }
+
+    private boolean inherit(DexLibraryClass clazz, DexType typeToInherit) {
+      DexLibraryClass current = clazz;
+      while (current.type != appView.dexItemFactory().objectType) {
+        if (current.type == typeToInherit) {
+          return true;
+        }
+        current = appView.definitionFor(current.superType).asLibraryClass();
+      }
+      return false;
+    }
+
+    private List<DexEncodedMethod> addInterfacesAndForwardingMethods(
+        DexProgramClass clazz, List<DexMethod> dexMethods) {
+      // DesugaredLibraryRetargeter emulate dispatch: insertion of a marker interface & forwarding
+      // methods.
+      // We cannot use the ClassProcessor since this applies up to 26, while the ClassProcessor
+      // applies up to 24.
+      List<DexEncodedMethod> newForwardingMethods = new ArrayList<>();
+      for (DexMethod dexMethod : dexMethods) {
+        DexType[] newInterfaces =
+            Arrays.copyOf(clazz.interfaces.values, clazz.interfaces.size() + 1);
+        newInterfaces[newInterfaces.length - 1] = dispatchInterfaceTypeFor(dexMethod);
+        clazz.interfaces = new DexTypeList(newInterfaces);
+        DexEncodedMethod dexEncodedMethod = clazz.lookupVirtualMethod(dexMethod);
+        if (dexEncodedMethod == null) {
+          DexEncodedMethod newMethod = createForwardingMethod(dexMethod, clazz);
+          clazz.addVirtualMethod(newMethod);
+          newForwardingMethods.add(newMethod);
+        }
+      }
+      return newForwardingMethods;
+    }
+
+    private DexEncodedMethod createForwardingMethod(DexMethod target, DexClass clazz) {
+      // NOTE: Never add a forwarding method to methods of classes unknown or coming from
+      // android.jar
+      // even if this results in invalid code, these classes are never desugared.
+      // In desugared library, emulated interface methods can be overridden by retarget lib members.
+      DexMethod forwardMethod =
+          appView.options().desugaredLibraryConfiguration.retargetMethod(target, appView);
+      assert forwardMethod != null && forwardMethod != target;
+      return DexEncodedMethod.createDesugaringForwardingMethod(
+          appView.definitionFor(target), clazz, forwardMethod, appView.dexItemFactory());
+    }
+
+    private void synthesizeEmulatedDispatchMethods(DexApplication.Builder<?> builder) {
+      assert appView.options().isDesugaredLibraryCompilation();
+      if (emulatedDispatchMethods.isEmpty()) {
+        return;
+      }
+      ClassAccessFlags itfAccessFlags =
+          ClassAccessFlags.fromSharedAccessFlags(
+              Constants.ACC_PUBLIC
+                  | Constants.ACC_SYNTHETIC
+                  | Constants.ACC_ABSTRACT
+                  | Constants.ACC_INTERFACE);
+      ClassAccessFlags holderAccessFlags =
+          ClassAccessFlags.fromSharedAccessFlags(Constants.ACC_PUBLIC | Constants.ACC_SYNTHETIC);
+      for (DexMethod emulatedDispatchMethod : emulatedDispatchMethods) {
+        // Dispatch interface.
+        DexType interfaceType = dispatchInterfaceTypeFor(emulatedDispatchMethod);
+        DexEncodedMethod itfMethod =
+            generateInterfaceDispatchMethod(emulatedDispatchMethod, interfaceType);
+        BackportedMethodRewriter.synthesizeClassWithUniqueMethod(
+            builder,
+            itfAccessFlags,
+            interfaceType,
+            itfMethod,
+            "desugared library dispatch interface",
+            false,
+            appView);
+        // Dispatch holder.
+        DexType holderType = dispatchHolderTypeFor(emulatedDispatchMethod);
+        DexEncodedMethod dispatchMethod =
+            generateHolderDispatchMethod(emulatedDispatchMethod, holderType, itfMethod.method);
+        BackportedMethodRewriter.synthesizeClassWithUniqueMethod(
+            builder,
+            holderAccessFlags,
+            holderType,
+            dispatchMethod,
+            "desugared library dispatch class",
+            false,
+            appView);
+      }
+    }
+
+    private DexEncodedMethod generateInterfaceDispatchMethod(
+        DexMethod emulatedDispatchMethod, DexType interfaceType) {
+      MethodAccessFlags flags =
+          MethodAccessFlags.fromSharedAccessFlags(
+              Constants.ACC_PUBLIC | Constants.ACC_ABSTRACT | Constants.ACC_SYNTHETIC, false);
+      DexMethod newMethod =
+          appView
+              .dexItemFactory()
+              .createMethod(
+                  interfaceType, emulatedDispatchMethod.proto, emulatedDispatchMethod.name);
+      return new DexEncodedMethod(
+          newMethod, flags, DexAnnotationSet.empty(), ParameterAnnotationsList.empty(), null, true);
+    }
+
+    private DexEncodedMethod generateHolderDispatchMethod(
+        DexMethod emulatedDispatchMethod, DexType dispatchHolder, DexMethod itfMethod) {
+      // The method should look like:
+      // static foo(rcvr, arg0, arg1) {
+      //    if (rcvr instanceof interfaceType) {
+      //      return invoke-interface receiver.foo(arg0, arg1);
+      //    } else {
+      //      return DesugarX.foo(rcvr, arg0, arg1)
+      //    }
+      // We do not deal with complex cases (multiple retargeting of the same signature in the
+      // same inheritance tree, etc., since they do not happen in the most common desugared library.
+      DexMethod desugarMethod =
+          appView
+              .options()
+              .desugaredLibraryConfiguration
+              .retargetMethod(emulatedDispatchMethod, appView);
+      assert desugarMethod != null; // This method is reached only for retarget core lib members.
+      DexMethod newMethod =
+          appView
+              .dexItemFactory()
+              .createMethod(dispatchHolder, desugarMethod.proto, emulatedDispatchMethod.name);
+      return DexEncodedMethod.toEmulateDispatchLibraryMethod(
+          emulatedDispatchMethod.holder,
+          newMethod,
+          desugarMethod,
+          itfMethod,
+          Collections.emptyList(),
+          appView);
+    }
+  }
+
+  private DexType dispatchInterfaceTypeFor(DexMethod method) {
+    return dispatchTypeFor(method, "dispatchInterface");
+  }
+
+  private DexType dispatchHolderTypeFor(DexMethod method) {
+    return dispatchTypeFor(method, "dispatchHolder");
+  }
+
+  private DexType dispatchTypeFor(DexMethod method, String suffix) {
+    String descriptor =
+        "L"
+            + appView
+                .options()
+                .desugaredLibraryConfiguration
+                .getSynthesizedLibraryClassesPackagePrefix()
+            + DESUGAR_LIB_RETARGET_CLASS_NAME_PREFIX
+            + '$'
+            + method.holder.getName()
+            + '$'
+            + method.name
+            + '$'
+            + suffix
+            + ';';
+    return appView.dexItemFactory().createType(descriptor);
+  }
+}
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/DesugaredLibraryWrapperSynthesizer.java b/src/main/java/com/android/tools/r8/ir/desugar/DesugaredLibraryWrapperSynthesizer.java
index 3527f62..30bc78e 100644
--- a/src/main/java/com/android/tools/r8/ir/desugar/DesugaredLibraryWrapperSynthesizer.java
+++ b/src/main/java/com/android/tools/r8/ir/desugar/DesugaredLibraryWrapperSynthesizer.java
@@ -144,14 +144,9 @@
   }
 
   private DexType createWrapperType(DexType type, String suffix) {
-    String desugaredLibPrefix =
-        appView
-            .options()
-            .desugaredLibraryConfiguration
-            .getSynthesizedLibraryClassesPackagePrefix(appView);
     return factory.createType(
         "L"
-            + desugaredLibPrefix
+            + appView.options().synthesizedClassPrefix
             + WRAPPER_PREFIX
             + type.toString().replace('.', '$')
             + suffix
@@ -511,52 +506,33 @@
   void finalizeWrappersForD8(
       DexApplication.Builder<?> builder, IRConverter irConverter, ExecutorService executorService)
       throws ExecutionException {
-    Map<DexType, DexProgramClass> synthesizedWrappers = synthesizeWrappers();
-    registerAndProcessWrappers(builder, irConverter, executorService, synthesizedWrappers.values());
+    List<DexProgramClass> synthesizedWrappers = synthesizeWrappers(new IdentityHashMap<>());
+    registerAndProcessWrappers(builder, irConverter, executorService, synthesizedWrappers);
   }
 
-  private Map<DexType, DexProgramClass> synthesizeWrappers() {
-    Map<DexType, DexProgramClass> synthesizedWrappers = new IdentityHashMap<>();
+  List<DexProgramClass> synthesizeWrappers(Map<DexType, DexProgramClass> synthesizedWrappers) {
+    List<DexProgramClass> additions = new ArrayList<>();
     // Generating a wrapper may require other wrappers to be generated, iterate until fix point.
     while (synthesizedWrappers.size() != typeWrappers.size() + vivifiedTypeWrappers.size()) {
       for (DexType type : typeWrappers.keySet()) {
         DexType typeWrapperType = typeWrappers.get(type);
         if (!synthesizedWrappers.containsKey(typeWrapperType)) {
-          synthesizedWrappers.put(
-              typeWrapperType, generateTypeWrapper(getValidClassToWrap(type), typeWrapperType));
+          DexProgramClass wrapper = generateTypeWrapper(getValidClassToWrap(type), typeWrapperType);
+          synthesizedWrappers.put(typeWrapperType, wrapper);
+          additions.add(wrapper);
         }
       }
       for (DexType type : vivifiedTypeWrappers.keySet()) {
         DexType vivifiedTypeWrapperType = vivifiedTypeWrappers.get(type);
         if (!synthesizedWrappers.containsKey(vivifiedTypeWrapperType)) {
-          synthesizedWrappers.put(
-              vivifiedTypeWrapperType,
-              generateVivifiedTypeWrapper(getValidClassToWrap(type), vivifiedTypeWrapperType));
+          DexProgramClass wrapper =
+              generateVivifiedTypeWrapper(getValidClassToWrap(type), vivifiedTypeWrapperType);
+          synthesizedWrappers.put(vivifiedTypeWrapperType, wrapper);
+          additions.add(wrapper);
         }
       }
     }
-    return synthesizedWrappers;
-  }
-
-  private Map<DexType, DexType> reverseWrapperMap() {
-    Map<DexType, DexType> reverseWrapperMap = new IdentityHashMap<>();
-    for (DexType type : typeWrappers.keySet()) {
-      reverseWrapperMap.put(typeWrappers.get(type), vivifiedTypeWrappers.get(type));
-    }
-    for (DexType type : vivifiedTypeWrappers.keySet()) {
-      reverseWrapperMap.put(vivifiedTypeWrappers.get(type), typeWrappers.get(type));
-    }
-    return reverseWrapperMap;
-  }
-
-  Map<DexProgramClass, DexProgramClass> synthesizeWrappersAndMapToReverse() {
-    Map<DexType, DexProgramClass> synthesizedWrappers = synthesizeWrappers();
-    Map<DexType, DexType> reverseMap = reverseWrapperMap();
-    Map<DexProgramClass, DexProgramClass> wrappersAndReverse = new IdentityHashMap<>();
-    for (DexProgramClass wrapper : synthesizedWrappers.values()) {
-      wrappersAndReverse.put(wrapper, synthesizedWrappers.get(reverseMap.get(wrapper.type)));
-    }
-    return wrappersAndReverse;
+    return additions;
   }
 
   private void registerAndProcessWrappers(
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/InterfaceMethodRewriter.java b/src/main/java/com/android/tools/r8/ir/desugar/InterfaceMethodRewriter.java
index 403f2f0..1a4c416 100644
--- a/src/main/java/com/android/tools/r8/ir/desugar/InterfaceMethodRewriter.java
+++ b/src/main/java/com/android/tools/r8/ir/desugar/InterfaceMethodRewriter.java
@@ -196,9 +196,9 @@
           DexCallSite callSite = instruction.asInvokeCustom().getCallSite();
           reportStaticInterfaceMethodHandle(encodedMethod.method, callSite.bootstrapMethod);
           for (DexValue arg : callSite.bootstrapArgs) {
-            if (arg instanceof DexValue.DexValueMethodHandle) {
-              reportStaticInterfaceMethodHandle(encodedMethod.method,
-                  ((DexValue.DexValueMethodHandle) arg).value);
+            if (arg.isDexValueMethodHandle()) {
+              reportStaticInterfaceMethodHandle(
+                  encodedMethod.method, arg.asDexValueMethodHandle().value);
             }
           }
           continue;
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/LambdaClass.java b/src/main/java/com/android/tools/r8/ir/desugar/LambdaClass.java
index ec1b05f..87c3424 100644
--- a/src/main/java/com/android/tools/r8/ir/desugar/LambdaClass.java
+++ b/src/main/java/com/android/tools/r8/ir/desugar/LambdaClass.java
@@ -667,14 +667,14 @@
       // For all instantiation points for which the compiler creates lambda$
       // methods, it creates these methods in the same class/interface.
       DexMethod implMethod = descriptor.implHandle.asMethod();
-      DexClass implMethodHolder = definitionFor(implMethod.holder);
+      DexProgramClass implMethodHolder = definitionFor(implMethod.holder).asProgramClass();
       return allowMethodModification
           ? modifyLambdaImplementationMethod(implMethod, implMethodHolder)
           : createSyntheticAccessor(implMethod, implMethodHolder);
     }
 
     private DexEncodedMethod modifyLambdaImplementationMethod(
-        DexMethod implMethod, DexClass implMethodHolder) {
+        DexMethod implMethod, DexProgramClass implMethodHolder) {
       return implMethodHolder
           .getMethodCollection()
           .replaceDirectMethodWithVirtualMethod(
@@ -701,7 +701,7 @@
     }
 
     private DexEncodedMethod createSyntheticAccessor(
-        DexMethod implMethod, DexClass implMethodHolder) {
+        DexMethod implMethod, DexProgramClass implMethodHolder) {
       MethodAccessFlags accessorFlags =
           MethodAccessFlags.fromSharedAccessFlags(
               Constants.ACC_SYNTHETIC | Constants.ACC_PUBLIC, false);
@@ -725,7 +725,7 @@
                   registry -> registry.registerInvokeDirect(implMethod)),
               true);
 
-      implMethodHolder.appendVirtualMethod(accessorEncodedMethod);
+      implMethodHolder.addVirtualMethod(accessorEncodedMethod);
       return accessorEncodedMethod;
     }
   }
@@ -761,7 +761,7 @@
 
       // We may arrive here concurrently so we need must update the methods of the class atomically.
       synchronized (accessorClass) {
-        accessorClass.appendDirectMethod(accessorEncodedMethod);
+        accessorClass.addDirectMethod(accessorEncodedMethod);
       }
 
       return accessorEncodedMethod;
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/LambdaRewriter.java b/src/main/java/com/android/tools/r8/ir/desugar/LambdaRewriter.java
index 770f170..4ee35d6 100644
--- a/src/main/java/com/android/tools/r8/ir/desugar/LambdaRewriter.java
+++ b/src/main/java/com/android/tools/r8/ir/desugar/LambdaRewriter.java
@@ -17,7 +17,7 @@
 import com.android.tools.r8.graph.DexType;
 import com.android.tools.r8.ir.analysis.type.Nullability;
 import com.android.tools.r8.ir.analysis.type.TypeAnalysis;
-import com.android.tools.r8.ir.analysis.type.TypeLatticeElement;
+import com.android.tools.r8.ir.analysis.type.TypeElement;
 import com.android.tools.r8.ir.code.BasicBlock;
 import com.android.tools.r8.ir.code.IRCode;
 import com.android.tools.r8.ir.code.Instruction;
@@ -323,8 +323,7 @@
       // The out value might be empty in case it was optimized out.
       lambdaInstanceValue =
           code.createValue(
-              TypeLatticeElement.fromDexType(
-                  lambdaClass.type, Nullability.maybeNull(), getAppView()));
+              TypeElement.fromDexType(lambdaClass.type, Nullability.maybeNull(), getAppView()));
     } else {
       affectedValues.add(lambdaInstanceValue);
     }
@@ -352,8 +351,8 @@
     //    result:
     //      NewInstance   rResult <-  LambdaClass
     //      Invoke-Direct { rResult, rArg0, rArg1, ... }; method: void LambdaClass.<init>(...)
-    lambdaInstanceValue.setTypeLattice(
-        lambdaInstanceValue.getTypeLattice().asReferenceTypeLatticeElement().asDefinitelyNotNull());
+    lambdaInstanceValue.setType(
+        lambdaInstanceValue.getType().asReferenceType().asDefinitelyNotNull());
     NewInstance newInstance = new NewInstance(lambdaClass.type, lambdaInstanceValue);
     instructions.replaceCurrentInstruction(newInstance);
 
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/StringConcatRewriter.java b/src/main/java/com/android/tools/r8/ir/desugar/StringConcatRewriter.java
index 834a492..4b33d2f 100644
--- a/src/main/java/com/android/tools/r8/ir/desugar/StringConcatRewriter.java
+++ b/src/main/java/com/android/tools/r8/ir/desugar/StringConcatRewriter.java
@@ -15,7 +15,8 @@
 import com.android.tools.r8.graph.DexProto;
 import com.android.tools.r8.graph.DexType;
 import com.android.tools.r8.graph.DexValue;
-import com.android.tools.r8.ir.analysis.type.TypeLatticeElement;
+import com.android.tools.r8.graph.DexValue.DexValueString;
+import com.android.tools.r8.ir.analysis.type.TypeElement;
 import com.android.tools.r8.ir.code.BasicBlock;
 import com.android.tools.r8.ir.code.BasicBlock.ThrowingInfo;
 import com.android.tools.r8.ir.code.ConstString;
@@ -212,11 +213,11 @@
     }
 
     // Extract recipe.
-    DexValue recipeValue = bootstrapArgs.get(0);
-    if (!(recipeValue instanceof DexValue.DexValueString)) {
+    DexValueString recipeValue = bootstrapArgs.get(0).asDexValueString();
+    if (recipeValue == null) {
       throw error(method, "bootstrap method argument `recipe` must be a string");
     }
-    String recipe = ((DexValue.DexValueString) recipeValue).getValue().toString();
+    String recipe = recipeValue.getValue().toString();
 
     // Collect chunks and patch the instruction.
     ConcatBuilder builder = new ConcatBuilder(appView, code, blocks, instructions);
@@ -273,8 +274,8 @@
   }
 
   private static String convertToString(DexMethod method, DexValue value) {
-    if (value instanceof DexValue.DexValueString) {
-      return ((DexValue.DexValueString) value).getValue().toString();
+    if (value.isDexValueString()) {
+      return value.asDexValueString().getValue().toString();
     }
     throw error(method,
         "const arg referenced from `recipe` is not supported: " + value.getClass().getName());
@@ -338,8 +339,8 @@
       instructions.previous();
 
       // new-instance v0, StringBuilder
-      TypeLatticeElement stringBuilderTypeLattice =
-          TypeLatticeElement.fromDexType(factory.stringBuilderType, definitelyNotNull(), appView);
+      TypeElement stringBuilderTypeLattice =
+          TypeElement.fromDexType(factory.stringBuilderType, definitelyNotNull(), appView);
       Value sbInstance = code.createValue(stringBuilderTypeLattice);
       appendInstruction(new NewInstance(factory.stringBuilderType, sbInstance));
 
@@ -361,8 +362,7 @@
       Value concatValue = invokeCustom.outValue();
       if (concatValue == null) {
         // The out value might be empty in case it was optimized out.
-        concatValue =
-            code.createValue(TypeLatticeElement.stringClassType(appView, definitelyNotNull()));
+        concatValue = code.createValue(TypeElement.stringClassType(appView, definitelyNotNull()));
       }
 
       // Replace the instruction.
@@ -440,8 +440,7 @@
 
       @Override
       Value getOrCreateValue() {
-        Value value =
-            code.createValue(TypeLatticeElement.stringClassType(appView, definitelyNotNull()));
+        Value value = code.createValue(TypeElement.stringClassType(appView, definitelyNotNull()));
         appendInstruction(
             new ConstString(
                 value,
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/AliasIntroducer.java b/src/main/java/com/android/tools/r8/ir/optimize/AliasIntroducer.java
index cc97597..7219eb0 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/AliasIntroducer.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/AliasIntroducer.java
@@ -46,15 +46,14 @@
       }
       Value outValue = current.outValue();
       // TODO(b/129859039): We may need similar concept when adding/testing assume-range
-      if (outValue.getTypeLattice().isPrimitive()
-          || outValue.getTypeLattice().isNullType()) {
+      if (outValue.getType().isPrimitiveType() || outValue.getType().isNullType()) {
         continue;
       }
       // Split block if needed
       BasicBlock insertionBlock =
           block.hasCatchHandlers() ? instructionIterator.split(code, blockIterator) : block;
       // Replace usages of out-value by the out-value of the AssumeNone instruction.
-      Value aliasedValue = code.createValue(outValue.getTypeLattice(), outValue.getLocalInfo());
+      Value aliasedValue = code.createValue(outValue.getType(), outValue.getLocalInfo());
       outValue.replaceUsers(aliasedValue);
       // Insert AssumeNone instruction.
       Assume<NoAssumption> assumeNone =
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/AssumeDynamicTypeRemover.java b/src/main/java/com/android/tools/r8/ir/optimize/AssumeDynamicTypeRemover.java
index 6bb9b03..9102f53 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/AssumeDynamicTypeRemover.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/AssumeDynamicTypeRemover.java
@@ -68,7 +68,7 @@
       Value outValue = assumeDynamicTypeInstruction.outValue();
 
       // Check if we need to run the type analysis for the affected values of the out-value.
-      if (!outValue.getTypeLattice().equals(inValue.getTypeLattice())) {
+      if (!outValue.getType().equals(inValue.getType())) {
         affectedValues.addAll(outValue.affectedValues());
       }
 
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/CallSiteOptimizationInfoPropagator.java b/src/main/java/com/android/tools/r8/ir/optimize/CallSiteOptimizationInfoPropagator.java
index 627aec5..368b58a 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/CallSiteOptimizationInfoPropagator.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/CallSiteOptimizationInfoPropagator.java
@@ -9,7 +9,7 @@
 import com.android.tools.r8.graph.DexProgramClass;
 import com.android.tools.r8.graph.ResolutionResult;
 import com.android.tools.r8.ir.analysis.type.TypeAnalysis;
-import com.android.tools.r8.ir.analysis.type.TypeLatticeElement;
+import com.android.tools.r8.ir.analysis.type.TypeElement;
 import com.android.tools.r8.ir.analysis.value.AbstractValue;
 import com.android.tools.r8.ir.analysis.value.SingleValue;
 import com.android.tools.r8.ir.code.Assume;
@@ -223,7 +223,7 @@
       }
       argumentsSeen++;
       Value originalArg = instr.asArgument().outValue();
-      if (originalArg.hasLocalInfo() || !originalArg.getTypeLattice().isReference()) {
+      if (originalArg.hasLocalInfo() || !originalArg.getType().isReferenceType()) {
         continue;
       }
       int argIndex = argumentsSeen - 1;
@@ -241,7 +241,7 @@
           continue;
         }
       }
-      TypeLatticeElement dynamicUpperBoundType =
+      TypeElement dynamicUpperBoundType =
           callSiteOptimizationInfo.getDynamicUpperBoundType(argIndex);
       if (dynamicUpperBoundType == null) {
         continue;
@@ -255,8 +255,8 @@
         continue;
       }
       Value specializedArg;
-      if (dynamicUpperBoundType.strictlyLessThan(originalArg.getTypeLattice(), appView)) {
-        specializedArg = code.createValue(originalArg.getTypeLattice());
+      if (dynamicUpperBoundType.strictlyLessThan(originalArg.getType(), appView)) {
+        specializedArg = code.createValue(originalArg.getType());
         affectedValues.addAll(originalArg.affectedValues());
         originalArg.replaceUsers(specializedArg);
         Assume<DynamicTypeAssumption> assumeType =
@@ -267,12 +267,12 @@
       } else {
         specializedArg = originalArg;
       }
-      assert specializedArg != null && specializedArg.getTypeLattice().isReference();
+      assert specializedArg != null && specializedArg.getType().isReferenceType();
       if (dynamicUpperBoundType.isDefinitelyNotNull()) {
         // If we already knew `arg` is never null, e.g., receiver, skip adding non-null.
-        if (!specializedArg.getTypeLattice().isDefinitelyNotNull()) {
-          Value nonNullArg = code.createValue(
-              specializedArg.getTypeLattice().asReferenceTypeLatticeElement().asMeetWithNotNull());
+        if (!specializedArg.getType().isDefinitelyNotNull()) {
+          Value nonNullArg =
+              code.createValue(specializedArg.getType().asReferenceType().asMeetWithNotNull());
           affectedValues.addAll(specializedArg.affectedValues());
           specializedArg.replaceUsers(nonNullArg);
           Assume<NonNullAssumption> assumeNotNull =
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/ClassInitializerDefaultsOptimization.java b/src/main/java/com/android/tools/r8/ir/optimize/ClassInitializerDefaultsOptimization.java
index 1e02c2b..025c178 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/ClassInitializerDefaultsOptimization.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/ClassInitializerDefaultsOptimization.java
@@ -30,7 +30,7 @@
 import com.android.tools.r8.graph.DexValue.DexValueShort;
 import com.android.tools.r8.graph.DexValue.DexValueString;
 import com.android.tools.r8.ir.analysis.ValueMayDependOnEnvironmentAnalysis;
-import com.android.tools.r8.ir.analysis.type.TypeLatticeElement;
+import com.android.tools.r8.ir.analysis.type.TypeElement;
 import com.android.tools.r8.ir.code.ArrayPut;
 import com.android.tools.r8.ir.code.BasicBlock;
 import com.android.tools.r8.ir.code.ConstNumber;
@@ -395,7 +395,7 @@
             }
             DexField field = put.getField();
             Value value = put.value();
-            TypeLatticeElement valueType = value.getTypeLattice();
+            TypeElement valueType = value.getType();
             if (clazz.definesStaticField(field)) {
               if (isReadBefore.contains(field)) {
                 // Promoting this put to a class constant would cause a previous static-get
@@ -439,7 +439,7 @@
                   isWrittenBefore.remove(field);
                 }
                 continue;
-              } else if (valueType.isReference() && valueType.isDefinitelyNotNull()) {
+              } else if (valueType.isReferenceType() && valueType.isDefinitelyNotNull()) {
                 finalFieldPuts.put(field, put);
                 continue;
               }
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/CodeRewriter.java b/src/main/java/com/android/tools/r8/ir/optimize/CodeRewriter.java
index f7755d4..5b78d8a 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/CodeRewriter.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/CodeRewriter.java
@@ -25,7 +25,7 @@
 import com.android.tools.r8.graph.DexType;
 import com.android.tools.r8.ir.analysis.equivalence.BasicBlockBehavioralSubsumption;
 import com.android.tools.r8.ir.analysis.type.TypeAnalysis;
-import com.android.tools.r8.ir.analysis.type.TypeLatticeElement;
+import com.android.tools.r8.ir.analysis.type.TypeElement;
 import com.android.tools.r8.ir.analysis.value.AbstractValue;
 import com.android.tools.r8.ir.code.AlwaysMaterializingNop;
 import com.android.tools.r8.ir.code.ArrayLength;
@@ -260,8 +260,8 @@
           }
 
           Value value = ifInstruction.lhs();
-          if (!value.getTypeLattice().isReference()) {
-            assert value.getTypeLattice().isPrimitive();
+          if (!value.getType().isReferenceType()) {
+            assert value.getType().isPrimitiveType();
             continue;
           }
 
@@ -1186,12 +1186,10 @@
 
   private boolean checkArgumentType(InvokeMethod invoke, int argumentIndex) {
     // TODO(sgjesse): Insert cast if required.
-    TypeLatticeElement returnType =
-        TypeLatticeElement.fromDexType(
-            invoke.getInvokedMethod().proto.returnType, maybeNull(), appView);
-    TypeLatticeElement argumentType =
-        TypeLatticeElement.fromDexType(
-            getArgumentType(invoke, argumentIndex), maybeNull(), appView);
+    TypeElement returnType =
+        TypeElement.fromDexType(invoke.getInvokedMethod().proto.returnType, maybeNull(), appView);
+    TypeElement argumentType =
+        TypeElement.fromDexType(getArgumentType(invoke, argumentIndex), maybeNull(), appView);
     return appView.enableWholeProgramOptimizations()
         ? argumentType.lessThanOrEqual(returnType, appView)
         : argumentType.equals(returnType);
@@ -1244,7 +1242,7 @@
             // Make sure that we are only narrowing information here. Note, in cases where
             // we cannot find the definition of types, computing lessThanOrEqual will
             // return false unless it is object.
-            if (argument.getTypeLattice().lessThanOrEqual(outValue.getTypeLattice(), appView)) {
+            if (argument.getType().lessThanOrEqual(outValue.getType(), appView)) {
               affectedValues.addAll(outValue.affectedValues());
               assumeDynamicTypeRemover.markUsersForRemoval(outValue);
               mayHaveRemovedTrivialPhi |= outValue.numberOfPhiUsers() > 0;
@@ -1362,17 +1360,17 @@
     // If the in-value is `null` and the cast-type is a float-array type, then trivial check-cast
     // elimination may lead to verification errors. See b/123269162.
     if (options.canHaveArtCheckCastVerifierBug()) {
-      if (inValue.getTypeLattice().isNullType()
+      if (inValue.getType().isNullType()
           && castType.isArrayType()
           && castType.toBaseType(dexItemFactory).isFloatType()) {
         return RemoveCheckCastInstructionIfTrivialResult.NO_REMOVALS;
       }
     }
 
-    TypeLatticeElement inTypeLattice = inValue.getTypeLattice();
-    TypeLatticeElement outTypeLattice = outValue.getTypeLattice();
-    TypeLatticeElement castTypeLattice =
-        TypeLatticeElement.fromDexType(castType, inTypeLattice.nullability(), appView);
+    TypeElement inTypeLattice = inValue.getType();
+    TypeElement outTypeLattice = outValue.getType();
+    TypeElement castTypeLattice =
+        TypeElement.fromDexType(castType, inTypeLattice.nullability(), appView);
 
     assert inTypeLattice.nullability().lessThanOrEqual(outTypeLattice.nullability());
 
@@ -1417,9 +1415,9 @@
     }
 
     Value inValue = instanceOf.value();
-    TypeLatticeElement inType = inValue.getTypeLattice();
-    TypeLatticeElement instanceOfType =
-        TypeLatticeElement.fromDexType(instanceOf.type(), inType.nullability(), appView);
+    TypeElement inType = inValue.getType();
+    TypeElement instanceOfType =
+        TypeElement.fromDexType(instanceOf.type(), inType.nullability(), appView);
     Value aliasValue = inValue.getAliasedValue();
 
     InstanceOfResult result = InstanceOfResult.UNKNOWN;
@@ -1442,8 +1440,7 @@
 
       if (result == InstanceOfResult.UNKNOWN) {
         if (inType.isClassType()
-            && isNeverInstantiatedDirectlyOrIndirectly(
-                inType.asClassTypeLatticeElement().getClassType())) {
+            && isNeverInstantiatedDirectlyOrIndirectly(inType.asClassType().getClassType())) {
           // The type of the in-value is a program class, and is never instantiated directly or
           // indirectly. This, the in-value must be null, meaning that the instance-of instruction
           // will always evaluate to false.
@@ -1456,7 +1453,7 @@
             inValue.getSpecificAliasedValue(
                 value -> !value.isPhi() && value.definition.isAssumeDynamicType());
         if (aliasedValue != null) {
-          TypeLatticeElement dynamicType =
+          TypeElement dynamicType =
               aliasedValue
                   .definition
                   .asAssumeDynamicType()
@@ -1476,7 +1473,7 @@
           new ConstNumber(
               new Value(
                   code.valueNumberGenerator.next(),
-                  TypeLatticeElement.getInt(),
+                  TypeElement.getInt(),
                   instanceOf.outValue().getLocalInfo()),
               result == InstanceOfResult.TRUE ? 1 : 0);
       it.replaceCurrentInstruction(newInstruction);
@@ -2053,8 +2050,8 @@
           for (ConstInstruction value : values) {
             stringValues.add(value.outValue());
           }
-          Value invokeValue = code.createValue(
-              newArray.outValue().getTypeLattice(), newArray.getLocalInfo());
+          Value invokeValue =
+              code.createValue(newArray.outValue().getType(), newArray.getLocalInfo());
           InvokeNewArray invoke =
               new InvokeNewArray(dexItemFactory.stringArrayType, invokeValue, stringValues);
           for (Value value : newArray.inValues()) {
@@ -2462,11 +2459,11 @@
         } else if (theIf.getType() == Type.EQ || theIf.getType() == Type.NE) {
           if (theIf.isZeroTest()) {
             if (!lhs.isConstNumber()) {
-              TypeLatticeElement l = lhs.getTypeLattice();
-              if (l.isReference() && lhs.isNeverNull()) {
+              TypeElement l = lhs.getType();
+              if (l.isReferenceType() && lhs.isNeverNull()) {
                 simplifyIfWithKnownCondition(code, block, theIf, 1);
               } else {
-                if (!l.isPrimitive() && !l.isNullable()) {
+                if (!l.isPrimitiveType() && !l.isNullable()) {
                   simplifyIfWithKnownCondition(code, block, theIf, 1);
                 }
               }
@@ -2721,12 +2718,12 @@
       }
 
       if (dominatorTree.get().dominatedBy(block, dominator)) {
-        if (newValue.getTypeLattice().lessThanOrEqual(value.getTypeLattice(), appView)) {
+        if (newValue.getType().lessThanOrEqual(value.getType(), appView)) {
           value.replaceUsers(newValue);
           block.listIterator(code, constNumber).removeOrReplaceByDebugLocalRead();
           constantWithValueIterator.remove();
           changed = true;
-        } else if (value.getTypeLattice().isNullType()) {
+        } else if (value.getType().isNullType()) {
           // TODO(b/120257211): Need a mechanism to determine if `newValue` can be used at all of
           // the use sites of `value` without introducing a type error.
         }
@@ -2875,7 +2872,7 @@
                          (theIf.getType() == Type.EQ &&
                            trueNumber.isIntegerOne() &&
                            falseNumber.isIntegerZero())) {
-                Value newOutValue = code.createValue(phi.getTypeLattice(), phi.getLocalInfo());
+                Value newOutValue = code.createValue(phi.getType(), phi.getLocalInfo());
                 ConstNumber cstToUse = trueNumber.isIntegerOne() ? trueNumber : falseNumber;
                 BasicBlock phiBlock = phi.getBlock();
                 Position phiPosition = phiBlock.getPosition();
@@ -3107,7 +3104,7 @@
                   new InvokeVirtual(
                       dexItemFactory.throwableMethods.initCause,
                       code.createValue(
-                          TypeLatticeElement.fromDexType(
+                          TypeElement.fromDexType(
                               dexItemFactory.throwableType, maybeNull(), appView)),
                       initCauseArguments);
               initCause.setPosition(current.getPosition());
@@ -3316,8 +3313,7 @@
   }
 
   private Value addConstString(IRCode code, InstructionListIterator iterator, String s) {
-    TypeLatticeElement typeLattice =
-        TypeLatticeElement.stringClassType(appView, definitelyNotNull());
+    TypeElement typeLattice = TypeElement.stringClassType(appView, definitelyNotNull());
     Value value = code.createValue(typeLattice);
     ThrowingInfo throwingInfo =
         options.isGeneratingClassFiles() ? ThrowingInfo.NO_THROW : ThrowingInfo.CAN_THROW;
@@ -3351,7 +3347,7 @@
     DexType javaIoPrintStreamType = dexItemFactory.javaIoPrintStreamType;
     Value out =
         code.createValue(
-            TypeLatticeElement.fromDexType(javaIoPrintStreamType, definitelyNotNull(), appView));
+            TypeElement.fromDexType(javaIoPrintStreamType, definitelyNotNull(), appView));
 
     DexProto proto = dexItemFactory.createProto(dexItemFactory.voidType, dexItemFactory.objectType);
     DexMethod print = dexItemFactory.createMethod(javaIoPrintStreamType, proto, "print");
@@ -3388,7 +3384,7 @@
       eol.link(successor);
 
       Value argument = arguments.get(i);
-      if (!argument.getTypeLattice().isReference()) {
+      if (!argument.getType().isReferenceType()) {
         iterator.add(new InvokeVirtual(print, null, ImmutableList.of(out, primitive)));
       } else {
         // Insert "if (argument != null) ...".
@@ -3418,7 +3414,7 @@
         iterator.add(new InvokeVirtual(print, null, ImmutableList.of(out, nul)));
         iterator = isNotNullBlock.listIterator(code);
         iterator.setInsertionPosition(position);
-        value = code.createValue(TypeLatticeElement.classClassType(appView, definitelyNotNull()));
+        value = code.createValue(TypeElement.classClassType(appView, definitelyNotNull()));
         iterator.add(
             new InvokeVirtual(
                 dexItemFactory.objectMembers.getClass, value, ImmutableList.of(arguments.get(i))));
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/DefaultInliningOracle.java b/src/main/java/com/android/tools/r8/ir/optimize/DefaultInliningOracle.java
index 463bfce..d225220 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/DefaultInliningOracle.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/DefaultInliningOracle.java
@@ -250,7 +250,7 @@
       for (int index = 0; index < arguments.size(); index++) {
         Value argument = arguments.get(index);
         if ((argument.isArgument()
-            || (argument.getTypeLattice().isReference() && argument.isNeverNull()))
+                || (argument.getType().isReferenceType() && argument.isNeverNull()))
             && hints.get(index)) {
           // 5-4 instructions per parameter check are expected to be removed.
           instructionLimit += 4;
@@ -304,15 +304,15 @@
       Reason reason,
       WhyAreYouNotInliningReporter whyAreYouNotInliningReporter) {
     Value receiver = invoke.getReceiver();
-    if (receiver.getTypeLattice().isDefinitelyNull()) {
+    if (receiver.getType().isDefinitelyNull()) {
       // A definitely null receiver will throw an error on call site.
       whyAreYouNotInliningReporter.reportReceiverDefinitelyNull();
       return null;
     }
 
     InlineAction action = new InlineAction(singleTarget, invoke, reason);
-    if (receiver.getTypeLattice().isNullable()) {
-      assert !receiver.getTypeLattice().isDefinitelyNull();
+    if (receiver.getType().isNullable()) {
+      assert !receiver.getType().isDefinitelyNull();
       // When inlining an instance method call, we need to preserve the null check for the
       // receiver. Therefore, if the receiver may be null and the candidate inlinee does not
       // throw if the receiver is null before any other side effect, then we must synthesize a
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/Devirtualizer.java b/src/main/java/com/android/tools/r8/ir/optimize/Devirtualizer.java
index 0c53a4c..82ea8ab 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/Devirtualizer.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/Devirtualizer.java
@@ -10,7 +10,7 @@
 import com.android.tools.r8.graph.DexType;
 import com.android.tools.r8.graph.ResolutionResult;
 import com.android.tools.r8.ir.analysis.type.TypeAnalysis;
-import com.android.tools.r8.ir.analysis.type.TypeLatticeElement;
+import com.android.tools.r8.ir.analysis.type.TypeElement;
 import com.android.tools.r8.ir.code.Assume;
 import com.android.tools.r8.ir.code.Assume.NonNullAssumption;
 import com.android.tools.r8.ir.code.BasicBlock;
@@ -101,8 +101,8 @@
               // the out-value of the cast instruction is a more precise type than the in-value,
               // otherwise we could introduce type errors.
               Value oldReceiver = newCheckCast.object();
-              TypeLatticeElement oldReceiverType = oldReceiver.getTypeLattice();
-              TypeLatticeElement newReceiverType = newReceiver.getTypeLattice();
+              TypeElement oldReceiverType = oldReceiver.getType();
+              TypeElement newReceiverType = newReceiver.getType();
               if (newReceiverType.lessThanOrEqual(oldReceiverType, appView)
                   && dominatorTree.dominatedBy(block, devirtualizedInvoke.getBlock())) {
                 assert nonNull.src() == oldReceiver;
@@ -166,10 +166,9 @@
         // (out <-) invoke-virtual a, ... A#foo
         if (holderType != invoke.getInvokedMethod().holder) {
           Value receiver = invoke.getReceiver();
-          TypeLatticeElement receiverTypeLattice = receiver.getTypeLattice();
-          TypeLatticeElement castTypeLattice =
-              TypeLatticeElement.fromDexType(
-                  holderType, receiverTypeLattice.nullability(), appView);
+          TypeElement receiverTypeLattice = receiver.getType();
+          TypeElement castTypeLattice =
+              TypeElement.fromDexType(holderType, receiverTypeLattice.nullability(), appView);
           // Avoid adding trivial cast and up-cast.
           // We should not use strictlyLessThan(castType, receiverType), which detects downcast,
           // due to side-casts, e.g., A (unused) < I, B < I, and cast from A to B.
@@ -262,7 +261,7 @@
    */
   private DexMethod rebindVirtualInvokeToMostSpecific(
       DexMethod target, Value receiver, DexType context) {
-    if (!receiver.getTypeLattice().isClassType()) {
+    if (!receiver.getType().isClassType()) {
       return target;
     }
     DexEncodedMethod encodedTarget = appView.definitionFor(target);
@@ -273,9 +272,7 @@
       return target;
     }
     DexType receiverType =
-        appView
-            .graphLense()
-            .lookupType(receiver.getTypeLattice().asClassTypeLatticeElement().getClassType());
+        appView.graphLense().lookupType(receiver.getType().asClassType().getClassType());
     if (receiverType == target.holder) {
       // Virtual invoke is already as specific as it can get.
       return target;
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/DynamicTypeOptimization.java b/src/main/java/com/android/tools/r8/ir/optimize/DynamicTypeOptimization.java
index 0a314aa..8048e3a 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/DynamicTypeOptimization.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/DynamicTypeOptimization.java
@@ -11,8 +11,8 @@
 import com.android.tools.r8.graph.DexEncodedMethod;
 import com.android.tools.r8.graph.DexMethod;
 import com.android.tools.r8.graph.DexType;
-import com.android.tools.r8.ir.analysis.type.ClassTypeLatticeElement;
-import com.android.tools.r8.ir.analysis.type.TypeLatticeElement;
+import com.android.tools.r8.ir.analysis.type.ClassTypeElement;
+import com.android.tools.r8.ir.analysis.type.TypeElement;
 import com.android.tools.r8.ir.code.Assume;
 import com.android.tools.r8.ir.code.Assume.DynamicTypeAssumption;
 import com.android.tools.r8.ir.code.BasicBlock;
@@ -61,8 +61,8 @@
         continue;
       }
 
-      TypeLatticeElement dynamicUpperBoundType;
-      ClassTypeLatticeElement dynamicLowerBoundType;
+      TypeElement dynamicUpperBoundType;
+      ClassTypeElement dynamicLowerBoundType;
       if (current.isInvokeMethod()) {
         InvokeMethod invoke = current.asInvokeMethod();
         DexMethod invokedMethod = invoke.getInvokedMethod();
@@ -75,7 +75,7 @@
         if (invokedMethod.holder.isArrayType()
             && invokedMethod.match(appView.dexItemFactory().objectMembers.clone)) {
           dynamicUpperBoundType =
-              TypeLatticeElement.fromDexType(invokedMethod.holder, definitelyNotNull(), appView);
+              TypeElement.fromDexType(invokedMethod.holder, definitelyNotNull(), appView);
           dynamicLowerBoundType = null;
         } else {
           DexEncodedMethod singleTarget =
@@ -110,14 +110,14 @@
       Value outValue = current.outValue();
       boolean isTrivial =
           (dynamicUpperBoundType == null
-                  || !dynamicUpperBoundType.strictlyLessThan(outValue.getTypeLattice(), appView))
+                  || !dynamicUpperBoundType.strictlyLessThan(outValue.getType(), appView))
               && dynamicLowerBoundType == null;
       if (isTrivial) {
         continue;
       }
 
       if (dynamicUpperBoundType == null) {
-        dynamicUpperBoundType = outValue.getTypeLattice();
+        dynamicUpperBoundType = outValue.getType();
       }
 
       // Split block if needed (only debug instructions are allowed after the throwing
@@ -126,8 +126,7 @@
           block.hasCatchHandlers() ? instructionIterator.split(code, blockIterator) : block;
 
       // Replace usages of out-value by the out-value of the AssumeDynamicType instruction.
-      Value specializedOutValue =
-          code.createValue(outValue.getTypeLattice(), outValue.getLocalInfo());
+      Value specializedOutValue = code.createValue(outValue.getType(), outValue.getLocalInfo());
       outValue.replaceUsers(specializedOutValue);
 
       // Insert AssumeDynamicType instruction.
@@ -154,9 +153,9 @@
    *
    * <p>If the method has no normal exits, then null is returned.
    */
-  public TypeLatticeElement computeDynamicReturnType(DexEncodedMethod method, IRCode code) {
+  public TypeElement computeDynamicReturnType(DexEncodedMethod method, IRCode code) {
     assert method.method.proto.returnType.isReferenceType();
-    List<TypeLatticeElement> returnedTypes = new ArrayList<>();
+    List<TypeElement> returnedTypes = new ArrayList<>();
     for (BasicBlock block : code.blocks) {
       JumpInstruction exitInstruction = block.exit();
       if (exitInstruction.isReturn()) {
@@ -164,19 +163,17 @@
         returnedTypes.add(returnValue.getDynamicUpperBoundType(appView));
       }
     }
-    return returnedTypes.isEmpty() ? null : TypeLatticeElement.join(returnedTypes, appView);
+    return returnedTypes.isEmpty() ? null : TypeElement.join(returnedTypes, appView);
   }
 
-  public ClassTypeLatticeElement computeDynamicLowerBoundType(
-      DexEncodedMethod method, IRCode code) {
+  public ClassTypeElement computeDynamicLowerBoundType(DexEncodedMethod method, IRCode code) {
     assert method.method.proto.returnType.isReferenceType();
-    ClassTypeLatticeElement result = null;
+    ClassTypeElement result = null;
     for (BasicBlock block : code.blocks) {
       JumpInstruction exitInstruction = block.exit();
       if (exitInstruction.isReturn()) {
         Value returnValue = exitInstruction.asReturn().returnValue();
-        ClassTypeLatticeElement dynamicLowerBoundType =
-            returnValue.getDynamicLowerBoundType(appView);
+        ClassTypeElement dynamicLowerBoundType = returnValue.getDynamicLowerBoundType(appView);
         if (dynamicLowerBoundType == null) {
           return null;
         }
@@ -184,7 +181,7 @@
           result = dynamicLowerBoundType;
         } else if (dynamicLowerBoundType.equalUpToNullability(result)) {
           if (dynamicLowerBoundType.nullability() != result.nullability()) {
-            result = dynamicLowerBoundType.join(result, appView).asClassTypeLatticeElement();
+            result = dynamicLowerBoundType.join(result, appView).asClassType();
           }
         } else {
           return null;
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/IdempotentFunctionCallCanonicalizer.java b/src/main/java/com/android/tools/r8/ir/optimize/IdempotentFunctionCallCanonicalizer.java
index ebf6e57..5dba772 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/IdempotentFunctionCallCanonicalizer.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/IdempotentFunctionCallCanonicalizer.java
@@ -149,7 +149,7 @@
           // Check if the call could throw a NPE as a result of the receiver being null.
           if (current.isInvokeMethodWithReceiver()) {
             Value receiver = current.asInvokeMethodWithReceiver().getReceiver().getAliasedValue();
-            if (receiver.getTypeLattice().isNullable()) {
+            if (receiver.getType().isNullable()) {
               continue;
             }
           }
@@ -195,39 +195,42 @@
         .filter(a -> a.getValue().size() > 1)
         .sorted((a, b) -> Integer.compare(b.getValue().size(), a.getValue().size()))
         .limit(MAX_CANONICALIZED_CALL)
-        .forEach((entry) -> {
-          InvokeMethod invoke = entry.getKey();
-          if (Log.ENABLED) {
-            if (factory.libraryMethodsWithReturnValueDependingOnlyOnArguments
-                .contains(invoke.getInvokedMethod())) {
-              numberOfLibraryCallCanonicalization += entry.getValue().size() - 1;
-            } else {
-              numberOfProgramCallCanonicalization += entry.getValue().size() - 1;
-            }
-          }
-          Value canonicalizedValue = code.createValue(
-              invoke.outValue().getTypeLattice(), invoke.outValue().getLocalInfo());
-          Invoke canonicalizedInvoke =
-              Invoke.create(
-                  invoke.getType(),
-                  invoke.getInvokedMethod(),
-                  null,
-                  canonicalizedValue,
-                  invoke.inValues());
-          // Note that it is fine to use any position, since the invoke has no side effects, which
-          // is guaranteed not to throw. That is, we will never have a stack trace with this call.
-          // Nonetheless, here we pick the position of the very first invocation.
-          Position firstInvocationPosition = entry.getValue().get(0).definition.getPosition();
-          canonicalizedInvoke.setPosition(firstInvocationPosition);
-          if (invoke.inValues().size() > 0) {
-            insertCanonicalizedInvokeWithInValues(code, canonicalizedInvoke);
-          } else {
-            insertCanonicalizedInvokeWithoutInValues(code, canonicalizedInvoke);
-          }
-          for (Value oldOutValue : entry.getValue()) {
-            deadInvocations.put(oldOutValue.definition.asInvokeMethod(), canonicalizedValue);
-          }
-        });
+        .forEach(
+            (entry) -> {
+              InvokeMethod invoke = entry.getKey();
+              if (Log.ENABLED) {
+                if (factory.libraryMethodsWithReturnValueDependingOnlyOnArguments.contains(
+                    invoke.getInvokedMethod())) {
+                  numberOfLibraryCallCanonicalization += entry.getValue().size() - 1;
+                } else {
+                  numberOfProgramCallCanonicalization += entry.getValue().size() - 1;
+                }
+              }
+              Value canonicalizedValue =
+                  code.createValue(invoke.outValue().getType(), invoke.outValue().getLocalInfo());
+              Invoke canonicalizedInvoke =
+                  Invoke.create(
+                      invoke.getType(),
+                      invoke.getInvokedMethod(),
+                      null,
+                      canonicalizedValue,
+                      invoke.inValues());
+              // Note that it is fine to use any position, since the invoke has no side effects,
+              // which
+              // is guaranteed not to throw. That is, we will never have a stack trace with this
+              // call.
+              // Nonetheless, here we pick the position of the very first invocation.
+              Position firstInvocationPosition = entry.getValue().get(0).definition.getPosition();
+              canonicalizedInvoke.setPosition(firstInvocationPosition);
+              if (invoke.inValues().size() > 0) {
+                insertCanonicalizedInvokeWithInValues(code, canonicalizedInvoke);
+              } else {
+                insertCanonicalizedInvokeWithoutInValues(code, canonicalizedInvoke);
+              }
+              for (Value oldOutValue : entry.getValue()) {
+                deadInvocations.put(oldOutValue.definition.asInvokeMethod(), canonicalizedValue);
+              }
+            });
 
     if (!deadInvocations.isEmpty()) {
       for (BasicBlock block : code.blocks) {
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/Inliner.java b/src/main/java/com/android/tools/r8/ir/optimize/Inliner.java
index d45bbb0..b56dece 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/Inliner.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/Inliner.java
@@ -21,7 +21,7 @@
 import com.android.tools.r8.ir.analysis.ClassInitializationAnalysis;
 import com.android.tools.r8.ir.analysis.proto.ProtoInliningReasonStrategy;
 import com.android.tools.r8.ir.analysis.type.Nullability;
-import com.android.tools.r8.ir.analysis.type.TypeLatticeElement;
+import com.android.tools.r8.ir.analysis.type.TypeElement;
 import com.android.tools.r8.ir.code.BasicBlock;
 import com.android.tools.r8.ir.code.CatchHandlers.CatchHandler;
 import com.android.tools.r8.ir.code.ConstClass;
@@ -631,8 +631,8 @@
 
       // Insert monitor-enter and monitor-exit instructions if the method is synchronized.
       if (shouldSynthesizeMonitorEnterExit) {
-        TypeLatticeElement throwableType =
-            TypeLatticeElement.fromDexType(
+        TypeElement throwableType =
+            TypeElement.fromDexType(
                 dexItemFactory.throwableType, Nullability.definitelyNotNull(), appView);
 
         code.prepareBlocksForCatchHandlers();
@@ -709,8 +709,7 @@
         if (target.isStatic()) {
           lockValue =
               code.createValue(
-                  TypeLatticeElement.fromDexType(
-                      dexItemFactory.objectType, definitelyNotNull(), appView));
+                  TypeElement.fromDexType(dexItemFactory.objectType, definitelyNotNull(), appView));
           monitorEnterBlockIterator.add(new ConstClass(lockValue, target.method.holder));
         } else {
           lockValue = entryBlock.getInstructions().getFirst().asArgument().outValue();
@@ -763,7 +762,7 @@
 
       InstructionListIterator iterator = initClassBlock.listIterator(code);
       iterator.setInsertionPosition(entryBlock.exit().getPosition());
-      iterator.add(new InitClass(code.createValue(TypeLatticeElement.getInt()), target.holder()));
+      iterator.add(new InitClass(code.createValue(TypeElement.getInt()), target.holder()));
     }
 
     private void synthesizeNullCheckForReceiver(AppView<?> appView, IRCode code) {
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/MemberValuePropagation.java b/src/main/java/com/android/tools/r8/ir/optimize/MemberValuePropagation.java
index 7e25c8a..4bf7a6d 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/MemberValuePropagation.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/MemberValuePropagation.java
@@ -16,7 +16,7 @@
 import com.android.tools.r8.graph.DexType;
 import com.android.tools.r8.ir.analysis.type.Nullability;
 import com.android.tools.r8.ir.analysis.type.TypeAnalysis;
-import com.android.tools.r8.ir.analysis.type.TypeLatticeElement;
+import com.android.tools.r8.ir.analysis.type.TypeElement;
 import com.android.tools.r8.ir.analysis.value.AbstractValue;
 import com.android.tools.r8.ir.analysis.value.SingleValue;
 import com.android.tools.r8.ir.code.BasicBlock;
@@ -139,11 +139,10 @@
           .createMaterializingInstruction(appView, code, instruction);
     }
 
-    TypeLatticeElement typeLattice = instruction.outValue().getTypeLattice();
+    TypeElement typeLattice = instruction.outValue().getType();
     if (returnValueRule.isField()) {
       DexField field = returnValueRule.getField();
-      assert typeLattice
-          == TypeLatticeElement.fromDexType(field.type, Nullability.maybeNull(), appView);
+      assert typeLattice == TypeElement.fromDexType(field.type, Nullability.maybeNull(), appView);
 
       DexEncodedField staticField = appView.appInfo().lookupStaticTarget(field.holder, field);
       if (staticField == null) {
@@ -446,7 +445,7 @@
     }
     DexProgramClass clazz = asProgramClassOrNull(appView.definitionFor(holder));
     if (clazz != null) {
-      Value dest = code.createValue(TypeLatticeElement.getInt());
+      Value dest = code.createValue(TypeElement.getInt());
       iterator.replaceCurrentInstruction(new InitClass(dest, clazz.type));
     }
   }
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/NonNullTracker.java b/src/main/java/com/android/tools/r8/ir/optimize/NonNullTracker.java
index 4fa0312..15904c5 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/NonNullTracker.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/NonNullTracker.java
@@ -12,7 +12,7 @@
 import com.android.tools.r8.graph.DexItemFactory;
 import com.android.tools.r8.graph.DexMethod;
 import com.android.tools.r8.ir.analysis.type.TypeAnalysis;
-import com.android.tools.r8.ir.analysis.type.TypeLatticeElement;
+import com.android.tools.r8.ir.analysis.type.TypeElement;
 import com.android.tools.r8.ir.code.Assume;
 import com.android.tools.r8.ir.code.Assume.NonNullAssumption;
 import com.android.tools.r8.ir.code.BasicBlock;
@@ -214,10 +214,10 @@
               if (knownToBeNonNullValue.isArgument()
                   || !dominatedUsers.isEmpty()
                   || !dominatedPhiUsersWithPositions.isEmpty()) {
-                TypeLatticeElement typeLattice = knownToBeNonNullValue.getTypeLattice();
+                TypeElement typeLattice = knownToBeNonNullValue.getType();
                 Value nonNullValue =
                     code.createValue(
-                        typeLattice.asReferenceTypeLatticeElement().asMeetWithNotNull(),
+                        typeLattice.asReferenceType().asMeetWithNotNull(),
                         knownToBeNonNullValue.getLocalInfo());
                 affectedValues.addAll(knownToBeNonNullValue.affectedValues());
                 Assume<NonNullAssumption> nonNull =
@@ -316,11 +316,11 @@
         // ...
         // A: non_null_rcv <- non-null(rcv)
         // ...y
-        TypeLatticeElement typeLattice = knownToBeNonNullValue.getTypeLattice();
-        assert typeLattice.isReference();
+        TypeElement typeLattice = knownToBeNonNullValue.getType();
+        assert typeLattice.isReferenceType();
         Value nonNullValue =
             code.createValue(
-                typeLattice.asReferenceTypeLatticeElement().asMeetWithNotNull(),
+                typeLattice.asReferenceType().asMeetWithNotNull(),
                 knownToBeNonNullValue.getLocalInfo());
         affectedValues.addAll(knownToBeNonNullValue.affectedValues());
         Assume<NonNullAssumption> nonNull =
@@ -369,9 +369,9 @@
   }
 
   private static boolean isNullableReferenceTypeWithUsers(Value value) {
-    TypeLatticeElement type = value.getTypeLattice();
-    return type.isReference()
-        && type.asReferenceTypeLatticeElement().isNullable()
+    TypeElement type = value.getType();
+    return type.isReferenceType()
+        && type.asReferenceType().isNullable()
         && value.numberOfAllUsers() > 0;
   }
 }
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/Outliner.java b/src/main/java/com/android/tools/r8/ir/optimize/Outliner.java
index 4ce7ede..ab9fa71 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/Outliner.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/Outliner.java
@@ -4,9 +4,9 @@
 
 package com.android.tools.r8.ir.optimize;
 
+import static com.android.tools.r8.graph.DexProgramClass.asProgramClassOrNull;
 import static com.android.tools.r8.ir.analysis.type.Nullability.definitelyNotNull;
 import static com.android.tools.r8.ir.analysis.type.Nullability.maybeNull;
-import static com.android.tools.r8.graph.DexProgramClass.asProgramClassOrNull;
 
 import com.android.tools.r8.dex.Constants;
 import com.android.tools.r8.errors.Unreachable;
@@ -30,10 +30,10 @@
 import com.android.tools.r8.graph.MethodAccessFlags;
 import com.android.tools.r8.graph.ParameterAnnotationsList;
 import com.android.tools.r8.graph.UseRegistry;
-import com.android.tools.r8.ir.analysis.type.ArrayTypeLatticeElement;
-import com.android.tools.r8.ir.analysis.type.ClassTypeLatticeElement;
-import com.android.tools.r8.ir.analysis.type.PrimitiveTypeLatticeElement;
-import com.android.tools.r8.ir.analysis.type.TypeLatticeElement;
+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.PrimitiveTypeElement;
+import com.android.tools.r8.ir.analysis.type.TypeElement;
 import com.android.tools.r8.ir.code.Add;
 import com.android.tools.r8.ir.code.BasicBlock;
 import com.android.tools.r8.ir.code.BasicBlock.ThrowingInfo;
@@ -277,7 +277,7 @@
         inValues.add(
             builder.readRegister(register, ValueTypeConstraint.fromNumericType(numericType)));
       }
-      TypeLatticeElement latticeElement = PrimitiveTypeLatticeElement.fromNumericType(numericType);
+      TypeElement latticeElement = PrimitiveTypeElement.fromNumericType(numericType);
       Value outValue =
           builder.writeRegister(outline.argumentCount(), latticeElement, ThrowingInfo.CAN_THROW);
       Instruction newInstruction = null;
@@ -359,8 +359,8 @@
 
     @Override
     public int createInstruction(IRBuilder builder, Outline outline, int argumentMapIndex) {
-      TypeLatticeElement latticeElement =
-          TypeLatticeElement.fromDexType(clazz, definitelyNotNull(), builder.appView);
+      TypeElement latticeElement =
+          TypeElement.fromDexType(clazz, definitelyNotNull(), builder.appView);
       Value outValue =
           builder.writeRegister(outline.argumentCount(), latticeElement, ThrowingInfo.CAN_THROW);
       Instruction newInstruction = new NewInstance(clazz, outValue);
@@ -495,8 +495,8 @@
       }
       Value outValue = null;
       if (hasOutValue) {
-        TypeLatticeElement latticeElement =
-            TypeLatticeElement.fromDexType(method.proto.returnType, maybeNull(), builder.appView);
+        TypeElement latticeElement =
+            TypeElement.fromDexType(method.proto.returnType, maybeNull(), builder.appView);
         outValue =
             builder.writeRegister(outline.argumentCount(), latticeElement, ThrowingInfo.CAN_THROW);
       }
@@ -933,7 +933,7 @@
 
     private boolean supportedArgumentType(Value value) {
       // All non array types are supported.
-      if (!value.getTypeLattice().isArrayType()) {
+      if (!value.getType().isArrayType()) {
         return true;
       }
       // Avoid array type elements which have interfaces, as Art does not have the same semantics
@@ -942,14 +942,13 @@
           && appView.options().isGeneratingClassFiles()) {
         return true;
       }
-      ArrayTypeLatticeElement arrayTypeLatticeElement =
-          value.getTypeLattice().asArrayTypeLatticeElement();
-      TypeLatticeElement arrayBaseType = arrayTypeLatticeElement.getArrayBaseTypeLattice();
-      if (arrayBaseType.isPrimitive()) {
+      ArrayTypeElement arrayType = value.getType().asArrayType();
+      TypeElement arrayBaseType = arrayType.getBaseType();
+      if (arrayBaseType.isPrimitiveType()) {
         return true;
       }
       if (arrayBaseType.isClassType()) {
-        return arrayBaseType.asClassTypeLatticeElement().getInterfaces().size() == 0;
+        return arrayBaseType.asClassType().getInterfaces().size() == 0;
       }
       return false;
     }
@@ -958,30 +957,29 @@
       assert supportedArgumentType(value);
       DexItemFactory itemFactory = appView.options().itemFactory;
       DexType objectType = itemFactory.objectType;
-      TypeLatticeElement valueLatticeElement = value.getTypeLattice();
-      if (valueLatticeElement.isClassType()) {
-        ClassTypeLatticeElement valueClassTypeLatticeElement =
-            value.getTypeLattice().asClassTypeLatticeElement();
+      TypeElement valueType = value.getType();
+      if (valueType.isClassType()) {
+        ClassTypeElement valueClassType = value.getType().asClassType();
         // For values of lattice type java.lang.Object and only one interface use the interface as
         // the type of the outline argument. If there are several interfaces these interfaces don't
         // have a common super interface nor are they implemented by a common superclass so the
         // argument type of the outline will be java.lang.Object.
-        if (valueClassTypeLatticeElement.getClassType() == objectType
-            && valueClassTypeLatticeElement.getInterfaces().size() == 1) {
-          return valueClassTypeLatticeElement.getInterfaces().iterator().next();
+        if (valueClassType.getClassType() == objectType
+            && valueClassType.getInterfaces().size() == 1) {
+          return valueClassType.getInterfaces().iterator().next();
         } else {
-          return valueClassTypeLatticeElement.getClassType();
+          return valueClassType.getClassType();
         }
-      } else if (valueLatticeElement.isArrayType()) {
-        return value.getTypeLattice().asArrayTypeLatticeElement().getArrayType(itemFactory);
-      } else if (valueLatticeElement.isNullType()) {
+      } else if (valueType.isArrayType()) {
+        return value.getType().asArrayType().toDexType(itemFactory);
+      } else if (valueType.isNullType()) {
         // For values which are always null use the actual type at the call site.
         return argumentTypeFromInvoke(invoke, argumentIndex);
       } else {
-        assert valueLatticeElement.isPrimitive();
-        assert valueLatticeElement.asPrimitiveTypeLatticeElement().hasDexType();
-        DexType type = valueLatticeElement.asPrimitiveTypeLatticeElement().toDexType(itemFactory);
-        if (valueLatticeElement.isInt()) {
+        assert valueType.isPrimitiveType();
+        assert valueType.asPrimitiveType().hasDexType();
+        DexType type = valueType.asPrimitiveType().toDexType(itemFactory);
+        if (valueType.isInt()) {
           // In the type lattice boolean, byte, short and char are all int. However, as the
           // outline argument type use the actual primitive type at the call site.
           assert type == itemFactory.intType;
@@ -1488,8 +1486,8 @@
         if (outline.argumentTypes.get(i).isBooleanType()) {
           builder.addBooleanNonThisArgument(i);
         } else {
-          TypeLatticeElement typeLattice =
-              TypeLatticeElement.fromDexType(outline.argumentTypes.get(i), maybeNull(), appView);
+          TypeElement typeLattice =
+              TypeElement.fromDexType(outline.argumentTypes.get(i), maybeNull(), appView);
           builder.addNonThisArgument(i, typeLattice);
         }
       }
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/ReflectionOptimizer.java b/src/main/java/com/android/tools/r8/ir/optimize/ReflectionOptimizer.java
index 055eeca..1b56e8c 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/ReflectionOptimizer.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/ReflectionOptimizer.java
@@ -14,7 +14,7 @@
 import com.android.tools.r8.graph.DexType;
 import com.android.tools.r8.ir.analysis.ClassInitializationAnalysis;
 import com.android.tools.r8.ir.analysis.type.TypeAnalysis;
-import com.android.tools.r8.ir.analysis.type.TypeLatticeElement;
+import com.android.tools.r8.ir.analysis.type.TypeElement;
 import com.android.tools.r8.ir.code.BasicBlock;
 import com.android.tools.r8.ir.code.ConstClass;
 import com.android.tools.r8.ir.code.IRCode;
@@ -60,8 +60,7 @@
         }
         if (type != null) {
           affectedValues.addAll(current.outValue().affectedValues());
-          TypeLatticeElement typeLattice =
-              TypeLatticeElement.classClassType(appView, definitelyNotNull());
+          TypeElement typeLattice = TypeElement.classClassType(appView, definitelyNotNull());
           Value value = code.createValue(typeLattice, current.getLocalInfo());
           ConstClass constClass = new ConstClass(value, type);
           it.replaceCurrentInstruction(constClass);
@@ -90,7 +89,7 @@
     if (in.hasLocalInfo()) {
       return null;
     }
-    TypeLatticeElement inType = in.getTypeLattice();
+    TypeElement inType = in.getType();
     // Check the receiver is either class type or array type. Also make sure it is not
     // nullable.
     if (!(inType.isClassType() || inType.isArrayType())
@@ -99,8 +98,8 @@
     }
     DexType type =
         inType.isClassType()
-            ? inType.asClassTypeLatticeElement().getClassType()
-            : inType.asArrayTypeLatticeElement().getArrayType(dexItemFactory);
+            ? inType.asClassType().getClassType()
+            : inType.asArrayType().toDexType(dexItemFactory);
     DexType baseType = type.toBaseType(dexItemFactory);
     // Make sure base type is a class type.
     if (!baseType.isClassType()) {
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/ServiceLoaderRewriter.java b/src/main/java/com/android/tools/r8/ir/optimize/ServiceLoaderRewriter.java
index e8799da..d626168 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/ServiceLoaderRewriter.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/ServiceLoaderRewriter.java
@@ -135,7 +135,7 @@
       InvokeVirtual classLoaderInvoke =
           serviceLoaderLoad.inValues().get(1).definition.asInvokeVirtual();
       boolean isGetClassLoaderOnConstClassOrNull =
-          serviceLoaderLoad.inValues().get(1).getTypeLattice().isNullType()
+          serviceLoaderLoad.inValues().get(1).getType().isNullType()
               || (classLoaderInvoke != null
                   && classLoaderInvoke.inValues().size() == 1
                   && classLoaderInvoke.getReceiver().getAliasedValue().isConstClass()
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/classinliner/ClassInlinerReceiverAnalysis.java b/src/main/java/com/android/tools/r8/ir/optimize/classinliner/ClassInlinerReceiverAnalysis.java
index f6e6e70..e0f991d 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/classinliner/ClassInlinerReceiverAnalysis.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/classinliner/ClassInlinerReceiverAnalysis.java
@@ -6,7 +6,7 @@
 
 import com.android.tools.r8.graph.AppView;
 import com.android.tools.r8.graph.DexEncodedMethod;
-import com.android.tools.r8.ir.analysis.type.ClassTypeLatticeElement;
+import com.android.tools.r8.ir.analysis.type.ClassTypeElement;
 import com.android.tools.r8.ir.code.BasicBlock;
 import com.android.tools.r8.ir.code.IRCode;
 import com.android.tools.r8.ir.code.Instruction;
@@ -47,7 +47,7 @@
     this.code = code;
     this.receiver = code.getThis();
     assert !receiver.hasAliasedValue();
-    assert receiver.getTypeLattice().isClassType();
+    assert receiver.getType().isClassType();
   }
 
   public OptionalBool computeReturnsReceiver() {
@@ -87,12 +87,12 @@
       return OptionalBool.TRUE;
     }
 
-    ClassTypeLatticeElement valueType = value.getTypeLattice().asClassTypeLatticeElement();
+    ClassTypeElement valueType = value.getType().asClassType();
     if (valueType == null) {
       return OptionalBool.FALSE;
     }
 
-    ClassTypeLatticeElement receiverType = receiver.getTypeLattice().asClassTypeLatticeElement();
+    ClassTypeElement receiverType = receiver.getType().asClassType();
     if (!valueType.isRelatedTo(receiverType, appView)) {
       // Guaranteed not to return the receiver.
       return OptionalBool.FALSE;
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/classinliner/FieldValueHelper.java b/src/main/java/com/android/tools/r8/ir/optimize/classinliner/FieldValueHelper.java
index 832e8b9..833ceaa 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/classinliner/FieldValueHelper.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/classinliner/FieldValueHelper.java
@@ -9,7 +9,7 @@
 
 import com.android.tools.r8.graph.AppView;
 import com.android.tools.r8.graph.DexField;
-import com.android.tools.r8.ir.analysis.type.TypeLatticeElement;
+import com.android.tools.r8.ir.analysis.type.TypeElement;
 import com.android.tools.r8.ir.code.BasicBlock;
 import com.android.tools.r8.ir.code.ConstNumber;
 import com.android.tools.r8.ir.code.IRCode;
@@ -98,7 +98,7 @@
           new Phi(
               code.valueNumberGenerator.next(),
               block,
-              TypeLatticeElement.fromDexType(field.type, maybeNull(), appView),
+              TypeElement.fromDexType(field.type, maybeNull(), appView),
               null,
               RegisterReadType.NORMAL);
       ins.put(block, phi);
@@ -112,9 +112,9 @@
       // phis until we are done with replacing fields reads.
       phi.addOperands(operands, false);
 
-      TypeLatticeElement phiType = phi.computePhiType(appView);
-      assert phiType.lessThanOrEqual(phi.getTypeLattice(), appView);
-      phi.setTypeLattice(phiType);
+      TypeElement phiType = phi.computePhiType(appView);
+      assert phiType.lessThanOrEqual(phi.getType(), appView);
+      phi.setType(phiType);
 
       value = phi;
     }
@@ -153,8 +153,8 @@
       InstructionListIterator it = block.listIterator(code, root);
       // If we met newInstance it means that default value is supposed to be used.
       if (field.type.isPrimitiveType()) {
-        defaultValue = code.createValue(
-            TypeLatticeElement.fromDexType(field.type, definitelyNotNull(), appView));
+        defaultValue =
+            code.createValue(TypeElement.fromDexType(field.type, definitelyNotNull(), appView));
         ConstNumber defaultValueInsn = new ConstNumber(defaultValue, 0);
         defaultValueInsn.setPosition(root.getPosition());
         it.add(defaultValueInsn);
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/classinliner/InlineCandidateProcessor.java b/src/main/java/com/android/tools/r8/ir/optimize/classinliner/InlineCandidateProcessor.java
index 22344de..81f68de 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/classinliner/InlineCandidateProcessor.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/classinliner/InlineCandidateProcessor.java
@@ -19,7 +19,7 @@
 import com.android.tools.r8.graph.DexType;
 import com.android.tools.r8.graph.ResolutionResult;
 import com.android.tools.r8.ir.analysis.ClassInitializationAnalysis;
-import com.android.tools.r8.ir.analysis.type.ClassTypeLatticeElement;
+import com.android.tools.r8.ir.analysis.type.ClassTypeElement;
 import com.android.tools.r8.ir.analysis.type.TypeAnalysis;
 import com.android.tools.r8.ir.code.AliasedValueConfiguration;
 import com.android.tools.r8.ir.code.AssumeAndCheckCastAliasedValueConfiguration;
@@ -175,7 +175,7 @@
     }
     DexEncodedField field = appView.appInfo().resolveField(staticGet.getField());
     FieldOptimizationInfo optimizationInfo = field.getOptimizationInfo();
-    ClassTypeLatticeElement dynamicLowerBoundType = optimizationInfo.getDynamicLowerBoundType();
+    ClassTypeElement dynamicLowerBoundType = optimizationInfo.getDynamicLowerBoundType();
     if (dynamicLowerBoundType == null
         || !dynamicLowerBoundType.equals(optimizationInfo.getDynamicUpperBoundType())) {
       return EligibilityStatus.NOT_A_SINGLETON_FIELD;
@@ -1031,7 +1031,7 @@
       }
 
       // A definitely null receiver will throw an error on call site.
-      if (receiver.getTypeLattice().nullability().isDefinitelyNull()) {
+      if (receiver.getType().nullability().isDefinitelyNull()) {
         return false;
       }
     }
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/enums/EnumUnboxer.java b/src/main/java/com/android/tools/r8/ir/optimize/enums/EnumUnboxer.java
index 5e8b552..84b952b 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/enums/EnumUnboxer.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/enums/EnumUnboxer.java
@@ -18,14 +18,15 @@
 import com.android.tools.r8.graph.DexType;
 import com.android.tools.r8.graph.DexValue.DexValueInt;
 import com.android.tools.r8.graph.DexValue.DexValueNull;
-import com.android.tools.r8.graph.EnumValueInfoMapCollection.EnumValueInfoMap;
 import com.android.tools.r8.graph.GraphLense;
 import com.android.tools.r8.graph.GraphLense.NestedGraphLense;
 import com.android.tools.r8.graph.RewrittenPrototypeDescription;
 import com.android.tools.r8.graph.RewrittenPrototypeDescription.ArgumentInfoCollection;
 import com.android.tools.r8.graph.RewrittenPrototypeDescription.RewrittenTypeInfo;
-import com.android.tools.r8.ir.analysis.type.ArrayTypeLatticeElement;
-import com.android.tools.r8.ir.analysis.type.TypeLatticeElement;
+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.TypeElement;
+import com.android.tools.r8.ir.code.ArrayPut;
 import com.android.tools.r8.ir.code.BasicBlock;
 import com.android.tools.r8.ir.code.ConstClass;
 import com.android.tools.r8.ir.code.FieldInstruction;
@@ -33,6 +34,7 @@
 import com.android.tools.r8.ir.code.If;
 import com.android.tools.r8.ir.code.Instruction;
 import com.android.tools.r8.ir.code.InvokeMethod;
+import com.android.tools.r8.ir.code.MemberType;
 import com.android.tools.r8.ir.code.Phi;
 import com.android.tools.r8.ir.code.Value;
 import com.android.tools.r8.ir.conversion.CodeOptimization;
@@ -106,16 +108,15 @@
     enumsUnboxingCandidates.remove(enumClass.type);
   }
 
-  private DexProgramClass getEnumUnboxingCandidateOrNull(TypeLatticeElement lattice) {
+  private DexProgramClass getEnumUnboxingCandidateOrNull(TypeElement lattice) {
     if (lattice.isClassType()) {
-      DexType classType = lattice.asClassTypeLatticeElement().getClassType();
+      DexType classType = lattice.asClassType().getClassType();
       return getEnumUnboxingCandidateOrNull(classType);
     }
     if (lattice.isArrayType()) {
-      ArrayTypeLatticeElement arrayLattice = lattice.asArrayTypeLatticeElement();
-      if (arrayLattice.getArrayBaseTypeLattice().isClassType()) {
-        DexType classType =
-            arrayLattice.getArrayBaseTypeLattice().asClassTypeLatticeElement().getClassType();
+      ArrayTypeElement arrayLattice = lattice.asArrayType();
+      if (arrayLattice.getBaseType().isClassType()) {
+        DexType classType = arrayLattice.getBaseType().asClassType().getClassType();
         return getEnumUnboxingCandidateOrNull(classType);
       }
     }
@@ -135,7 +136,7 @@
       for (Instruction instruction : block.getInstructions()) {
         Value outValue = instruction.outValue();
         if (outValue != null) {
-          DexProgramClass enumClass = getEnumUnboxingCandidateOrNull(outValue.getTypeLattice());
+          DexProgramClass enumClass = getEnumUnboxingCandidateOrNull(outValue.getType());
           if (enumClass != null) {
             Reason reason = validateEnumUsages(code, outValue, enumClass);
             if (reason == Reason.ELIGIBLE) {
@@ -147,7 +148,7 @@
               eligibleEnums.add(enumClass.type);
             }
           }
-          if (outValue.getTypeLattice().isNullType()) {
+          if (outValue.getType().isNullType()) {
             addNullDependencies(outValue.uniqueUsers(), eligibleEnums);
           }
         }
@@ -160,17 +161,33 @@
             markEnumAsUnboxable(
                 Reason.CONST_CLASS, appView.definitionForProgramType(constClass.getValue()));
           }
+        } else if (instruction.isInvokeStatic()) {
+          // TODO(b/150370354): Since we temporary allow enum unboxing on enums with values and
+          // valueOf static methods only if such methods are unused, such methods cannot be
+          // called. the long term solution is to simply move called methods to a companion class,
+          // as any static helper method, and remove these checks.
+          DexMethod invokedMethod = instruction.asInvokeStatic().getInvokedMethod();
+          DexProgramClass enumClass = getEnumUnboxingCandidateOrNull(invokedMethod.holder);
+          if (enumClass != null) {
+            if (factory.enumMethods.isValueOfMethod(invokedMethod, enumClass)) {
+              markEnumAsUnboxable(Reason.VALUE_OF_INVOKE, enumClass);
+            } else if (factory.enumMethods.isValuesMethod(invokedMethod, enumClass)) {
+              markEnumAsUnboxable(Reason.VALUES_INVOKE, enumClass);
+            } else {
+              assert false; // We do not allow any other static call in unboxing candidates.
+            }
+          }
         }
       }
       for (Phi phi : block.getPhis()) {
-        DexProgramClass enumClass = getEnumUnboxingCandidateOrNull(phi.getTypeLattice());
+        DexProgramClass enumClass = getEnumUnboxingCandidateOrNull(phi.getType());
         if (enumClass != null) {
           Reason reason = validateEnumUsages(code, phi, enumClass);
           if (reason == Reason.ELIGIBLE) {
             eligibleEnums.add(enumClass.type);
           }
         }
-        if (phi.getTypeLattice().isNullType()) {
+        if (phi.getType().isNullType()) {
           addNullDependencies(phi.uniqueUsers(), eligibleEnums);
         }
       }
@@ -223,7 +240,7 @@
     }
     for (Phi phi : value.uniquePhiUsers()) {
       for (Value operand : phi.getOperands()) {
-        if (getEnumUnboxingCandidateOrNull(operand.getTypeLattice()) != enumClass) {
+        if (getEnumUnboxingCandidateOrNull(operand.getType()) != enumClass) {
           markEnumAsUnboxable(Reason.INVALID_PHI, enumClass);
           return Reason.INVALID_PHI;
         }
@@ -263,6 +280,7 @@
               if (optimizationInfo.isMutableFieldOptimizationInfo()) {
                 optimizationInfo
                     .asMutableFieldOptimizationInfo()
+                    .fixupClassTypeReferences(appView.graphLense()::lookupType, appView)
                     .fixupAbstractValue(appView, appView.graphLense());
               } else {
                 assert optimizationInfo.isDefaultFieldOptimizationInfo();
@@ -275,6 +293,7 @@
               if (optimizationInfo.isUpdatableMethodOptimizationInfo()) {
                 optimizationInfo
                     .asUpdatableMethodOptimizationInfo()
+                    .fixupClassTypeReferences(appView.graphLense()::lookupType, appView)
                     .fixupAbstractReturnValue(appView, appView.graphLense())
                     .fixupInstanceInitializerInfo(appView, appView.graphLense());
               } else {
@@ -292,6 +311,8 @@
       DexProgramClass enumClass = appView.definitionForProgramType(toUnbox);
       assert enumClass != null;
 
+      // Enum candidates have necessarily only one constructor matching enumMethods.constructor
+      // signature.
       DexEncodedMethod initializer = enumClass.lookupDirectMethod(factory.enumMethods.constructor);
       if (initializer == null) {
         // This case typically happens when a programmer uses EnumSet/EnumMap without using the
@@ -307,17 +328,6 @@
 
       if (enumClass.classInitializationMayHaveSideEffects(appView)) {
         markEnumAsUnboxable(Reason.INVALID_CLINIT, enumClass);
-        continue;
-      }
-
-      EnumValueInfoMap enumValueInfoMap =
-          appView.appInfo().withLiveness().getEnumValueInfoMap(enumClass.type);
-      if (enumValueInfoMap == null) {
-        markEnumAsUnboxable(Reason.MISSING_INFO_MAP, enumClass);
-        continue;
-      }
-      if (enumValueInfoMap.size() != enumClass.staticFields().size() - 1) {
-        markEnumAsUnboxable(Reason.UNEXPECTED_STATIC_FIELD, enumClass);
       }
     }
     if (debugLogEnabled) {
@@ -360,7 +370,7 @@
         int offset = BooleanUtils.intValue(!encodedSingleTarget.isStatic());
         for (int i = 0; i < singleTarget.proto.parameters.size(); i++) {
           if (invokeMethod.inValues().get(offset + i) == enumValue) {
-            if (singleTarget.proto.parameters.values[i] != enumClass.type) {
+            if (singleTarget.proto.parameters.values[i].toBaseType(factory) != enumClass.type) {
               return Reason.GENERIC_INVOKE;
             }
           }
@@ -426,16 +436,50 @@
         return Reason.ELIGIBLE;
       }
       // e == MyEnum.X
-      TypeLatticeElement leftType = anIf.lhs().getTypeLattice();
-      TypeLatticeElement rightType = anIf.rhs().getTypeLattice();
+      TypeElement leftType = anIf.lhs().getType();
+      TypeElement rightType = anIf.rhs().getType();
       if (leftType.equalUpToNullability(rightType)) {
         assert leftType.isClassType();
-        assert leftType.asClassTypeLatticeElement().getClassType() == enumClass.type;
+        assert leftType.asClassType().getClassType() == enumClass.type;
         return Reason.ELIGIBLE;
       }
       return Reason.INVALID_IF_TYPES;
     }
 
+    if (instruction.isArrayLength()) {
+      // MyEnum[] array = ...; array.length; is valid.
+      return Reason.ELIGIBLE;
+    }
+
+    if (instruction.isArrayGet()) {
+      // MyEnum[] array = ...; array[0]; is valid.
+      return Reason.ELIGIBLE;
+    }
+
+    if (instruction.isArrayPut()) {
+      // MyEnum[] array; array[0] = MyEnum.A; is valid.
+      // MyEnum[][] array2d; MyEnum[] array; array2d[0] = array; is valid.
+      // MyEnum[]^N array; MyEnum[]^(N-1) element; array[0] = element; is valid.
+      // We need to prove that the value to put in and the array have correct types.
+      ArrayPut arrayPut = instruction.asArrayPut();
+      assert arrayPut.getMemberType() == MemberType.OBJECT;
+      TypeElement arrayType = arrayPut.array().getType();
+      assert arrayType.isArrayType();
+      assert arrayType.asArrayType().getBaseType().isClassType();
+      ClassTypeElement arrayBaseType = arrayType.asArrayType().getBaseType().asClassType();
+      TypeElement valueBaseType = arrayPut.value().getType();
+      if (valueBaseType.isArrayType()) {
+        assert valueBaseType.asArrayType().getBaseType().isClassType();
+        assert valueBaseType.asArrayType().getNesting() == arrayType.asArrayType().getNesting() - 1;
+        valueBaseType = valueBaseType.asArrayType().getBaseType();
+      }
+      if (arrayBaseType.equalUpToNullability(valueBaseType)
+          && arrayBaseType.getClassType() == enumClass.type) {
+        return Reason.ELIGIBLE;
+      }
+      return Reason.INVALID_ARRAY_PUT;
+    }
+
     if (instruction.isAssume()) {
       Value outValue = instruction.outValue();
       return validateEnumUsages(code, outValue, enumClass);
@@ -537,6 +581,7 @@
     UNSUPPORTED_LIBRARY_CALL,
     MISSING_INFO_MAP,
     INVALID_FIELD_PUT,
+    INVALID_ARRAY_PUT,
     FIELD_PUT_ON_ENUM,
     TYPE_MISSMATCH_FIELD_PUT,
     INVALID_IF_TYPES,
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/enums/EnumUnboxingCandidateAnalysis.java b/src/main/java/com/android/tools/r8/ir/optimize/enums/EnumUnboxingCandidateAnalysis.java
index 3d18f6a..0b39506 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/enums/EnumUnboxingCandidateAnalysis.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/enums/EnumUnboxingCandidateAnalysis.java
@@ -15,6 +15,7 @@
 import com.android.tools.r8.graph.DexProto;
 import com.android.tools.r8.graph.DexReference;
 import com.android.tools.r8.graph.DexType;
+import com.android.tools.r8.graph.EnumValueInfoMapCollection.EnumValueInfoMap;
 import com.android.tools.r8.ir.optimize.enums.EnumUnboxer.Reason;
 import com.android.tools.r8.shaking.AppInfoWithLiveness;
 import com.google.common.collect.Sets;
@@ -71,6 +72,12 @@
       enumUnboxer.reportFailure(clazz.type, Reason.VIRTUAL_METHOD);
       return false;
     }
+    EnumValueInfoMap enumValueInfoMap =
+        appView.appInfo().withLiveness().getEnumValueInfoMap(clazz.type);
+    if (enumValueInfoMap == null) {
+      enumUnboxer.reportFailure(clazz.type, Reason.MISSING_INFO_MAP);
+      return false;
+    }
     // Methods values, valueOf, init, clinit are present on each enum.
     // Methods init and clinit are required if the enum is used.
     // Methods valueOf and values are normally kept by the commonly used/recommended enum keep rule
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/enums/EnumUnboxingRewriter.java b/src/main/java/com/android/tools/r8/ir/optimize/enums/EnumUnboxingRewriter.java
index cbd3824..0d065ff 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/enums/EnumUnboxingRewriter.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/enums/EnumUnboxingRewriter.java
@@ -22,15 +22,18 @@
 import com.android.tools.r8.graph.EnumValueInfoMapCollection.EnumValueInfoMap;
 import com.android.tools.r8.graph.MethodAccessFlags;
 import com.android.tools.r8.graph.ParameterAnnotationsList;
+import com.android.tools.r8.ir.analysis.type.ArrayTypeElement;
 import com.android.tools.r8.ir.analysis.type.DestructivePhiTypeUpdater;
-import com.android.tools.r8.ir.analysis.type.PrimitiveTypeLatticeElement;
-import com.android.tools.r8.ir.analysis.type.TypeLatticeElement;
+import com.android.tools.r8.ir.analysis.type.PrimitiveTypeElement;
+import com.android.tools.r8.ir.analysis.type.TypeElement;
+import com.android.tools.r8.ir.code.ArrayAccess;
 import com.android.tools.r8.ir.code.ConstNumber;
 import com.android.tools.r8.ir.code.IRCode;
 import com.android.tools.r8.ir.code.Instruction;
 import com.android.tools.r8.ir.code.InstructionListIterator;
 import com.android.tools.r8.ir.code.InvokeMethodWithReceiver;
 import com.android.tools.r8.ir.code.InvokeStatic;
+import com.android.tools.r8.ir.code.MemberType;
 import com.android.tools.r8.ir.code.NumericType;
 import com.android.tools.r8.ir.code.Phi;
 import com.android.tools.r8.ir.code.StaticGet;
@@ -96,7 +99,7 @@
         InvokeMethodWithReceiver invokeMethod = instruction.asInvokeMethodWithReceiver();
         DexMethod invokedMethod = invokeMethod.getInvokedMethod();
         if (invokedMethod == factory.enumMethods.ordinal
-            && invokeMethod.getReceiver().getTypeLattice().isInt()) {
+            && invokeMethod.getReceiver().getType().isInt()) {
           instruction =
               new InvokeStatic(
                   ordinalUtilityMethod, invokeMethod.outValue(), invokeMethod.inValues());
@@ -122,13 +125,19 @@
           assert enumValueInfo != null
               : "Invalid read to " + staticGet.getField().name + ", error during enum analysis";
           instruction = new ConstNumber(staticGet.outValue(), enumValueInfo.convertToInt());
-          staticGet
-              .outValue()
-              .setTypeLattice(PrimitiveTypeLatticeElement.fromNumericType(NumericType.INT));
+          staticGet.outValue().setType(PrimitiveTypeElement.fromNumericType(NumericType.INT));
           iterator.replaceCurrentInstruction(instruction);
           affectedPhis.addAll(staticGet.outValue().uniquePhiUsers());
         }
       }
+      // Rewrite array accesses from MyEnum[] (OBJECT) to int[] (INT).
+      if (instruction.isArrayAccess()) {
+        ArrayAccess arrayAccess = instruction.asArrayAccess();
+        if (shouldRewriteArrayAccess(arrayAccess)) {
+          instruction = arrayAccess.withMemberType(MemberType.INT);
+          iterator.replaceCurrentInstruction(instruction);
+        }
+      }
       assert validateEnumToUnboxRemoved(instruction);
     }
     if (!affectedPhis.isEmpty()) {
@@ -137,19 +146,31 @@
     assert code.isConsistentSSABeforeTypesAreCorrect();
   }
 
+  private boolean shouldRewriteArrayAccess(ArrayAccess arrayAccess) {
+    ArrayTypeElement arrayType = arrayAccess.array().getType().asArrayType();
+    return arrayAccess.getMemberType() == MemberType.OBJECT
+        && arrayType.getNesting() == 1
+        && arrayType.getBaseType().isInt();
+  }
+
   private boolean validateEnumToUnboxRemoved(Instruction instruction) {
+    if (instruction.isArrayAccess()) {
+      ArrayAccess arrayAccess = instruction.asArrayAccess();
+      ArrayTypeElement arrayType = arrayAccess.array().getType().asArrayType();
+      assert arrayAccess.getMemberType() != MemberType.OBJECT
+          || arrayType.getNesting() > 1
+          || arrayType.getBaseType().isReferenceType();
+    }
     if (instruction.outValue() == null) {
       return true;
     }
-    TypeLatticeElement typeLattice = instruction.outValue().getTypeLattice();
+    TypeElement typeLattice = instruction.outValue().getType();
     assert !typeLattice.isClassType()
-        || !enumsToUnbox.containsEnum(typeLattice.asClassTypeLatticeElement().getClassType());
+        || !enumsToUnbox.containsEnum(typeLattice.asClassType().getClassType());
     if (typeLattice.isArrayType()) {
-      TypeLatticeElement arrayBaseTypeLattice =
-          typeLattice.asArrayTypeLatticeElement().getArrayBaseTypeLattice();
+      TypeElement arrayBaseTypeLattice = typeLattice.asArrayType().getBaseType();
       assert !arrayBaseTypeLattice.isClassType()
-          || !enumsToUnbox.containsEnum(
-              arrayBaseTypeLattice.asClassTypeLatticeElement().getClassType());
+          || !enumsToUnbox.containsEnum(arrayBaseTypeLattice.asClassType().getClassType());
     }
     return true;
   }
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/info/CallSiteOptimizationInfo.java b/src/main/java/com/android/tools/r8/ir/optimize/info/CallSiteOptimizationInfo.java
index 18c709c..e85cf9e 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/info/CallSiteOptimizationInfo.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/info/CallSiteOptimizationInfo.java
@@ -5,7 +5,7 @@
 
 import com.android.tools.r8.graph.AppView;
 import com.android.tools.r8.graph.DexEncodedMethod;
-import com.android.tools.r8.ir.analysis.type.TypeLatticeElement;
+import com.android.tools.r8.ir.analysis.type.TypeElement;
 import com.android.tools.r8.ir.analysis.value.AbstractValue;
 import com.android.tools.r8.ir.analysis.value.UnknownValue;
 import com.android.tools.r8.ir.optimize.CallSiteOptimizationInfoPropagator;
@@ -59,7 +59,7 @@
   }
 
   // The index exactly matches with in values of invocation, i.e., even including receiver.
-  public TypeLatticeElement getDynamicUpperBoundType(int argIndex) {
+  public TypeElement getDynamicUpperBoundType(int argIndex) {
     return null;
   }
 
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/info/ConcreteCallSiteOptimizationInfo.java b/src/main/java/com/android/tools/r8/ir/optimize/info/ConcreteCallSiteOptimizationInfo.java
index 702074a..ba24e19 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/info/ConcreteCallSiteOptimizationInfo.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/info/ConcreteCallSiteOptimizationInfo.java
@@ -10,7 +10,7 @@
 import com.android.tools.r8.graph.AppView;
 import com.android.tools.r8.graph.DexEncodedMethod;
 import com.android.tools.r8.ir.analysis.type.Nullability;
-import com.android.tools.r8.ir.analysis.type.TypeLatticeElement;
+import com.android.tools.r8.ir.analysis.type.TypeElement;
 import com.android.tools.r8.ir.analysis.value.AbstractValue;
 import com.android.tools.r8.ir.analysis.value.UnknownValue;
 import com.android.tools.r8.ir.code.Value;
@@ -26,7 +26,7 @@
   // inValues() size == DexMethod.arity + (isStatic ? 0 : 1) // receiver
   // That is, this information takes into account the receiver as well.
   private final int size;
-  private final Int2ReferenceMap<TypeLatticeElement> dynamicUpperBoundTypes;
+  private final Int2ReferenceMap<TypeElement> dynamicUpperBoundTypes;
   private final Int2ReferenceMap<AbstractValue> constants;
 
   private ConcreteCallSiteOptimizationInfo(
@@ -59,15 +59,15 @@
         }
       }
 
-      TypeLatticeElement thisUpperBoundType = getDynamicUpperBoundType(i);
+      TypeElement thisUpperBoundType = getDynamicUpperBoundType(i);
       if (thisUpperBoundType == null) {
         // This means the corresponding argument is primitive. The counterpart should be too.
         assert other.getDynamicUpperBoundType(i) == null;
         continue;
       }
-      assert thisUpperBoundType.isReference();
-      TypeLatticeElement otherUpperBoundType = other.getDynamicUpperBoundType(i);
-      assert otherUpperBoundType != null && otherUpperBoundType.isReference();
+      assert thisUpperBoundType.isReferenceType();
+      TypeElement otherUpperBoundType = other.getDynamicUpperBoundType(i);
+      assert otherUpperBoundType != null && otherUpperBoundType.isReferenceType();
       result.dynamicUpperBoundTypes.put(
           i, thisUpperBoundType.join(otherUpperBoundType, appView));
     }
@@ -79,18 +79,17 @@
     return TOP;
   }
 
-  private TypeLatticeElement[] getStaticTypes(AppView<?> appView, DexEncodedMethod encodedMethod) {
+  private TypeElement[] getStaticTypes(AppView<?> appView, DexEncodedMethod encodedMethod) {
     int argOffset = encodedMethod.isStatic() ? 0 : 1;
     int size = encodedMethod.method.getArity() + argOffset;
-    TypeLatticeElement[] staticTypes = new TypeLatticeElement[size];
+    TypeElement[] staticTypes = new TypeElement[size];
     if (!encodedMethod.isStatic()) {
       staticTypes[0] =
-          TypeLatticeElement.fromDexType(
-              encodedMethod.method.holder, definitelyNotNull(), appView);
+          TypeElement.fromDexType(encodedMethod.method.holder, definitelyNotNull(), appView);
     }
     for (int i = 0; i < encodedMethod.method.getArity(); i++) {
       staticTypes[i + argOffset] =
-          TypeLatticeElement.fromDexType(
+          TypeElement.fromDexType(
               encodedMethod.method.proto.parameters.values[i], maybeNull(), appView);
     }
     return staticTypes;
@@ -98,7 +97,7 @@
 
   @Override
   public boolean hasUsefulOptimizationInfo(AppView<?> appView, DexEncodedMethod encodedMethod) {
-    TypeLatticeElement[] staticTypes = getStaticTypes(appView, encodedMethod);
+    TypeElement[] staticTypes = getStaticTypes(appView, encodedMethod);
     for (int i = 0; i < size; i++) {
       ParameterUsage parameterUsage = encodedMethod.getOptimizationInfo().getParameterUsages(i);
       // If the parameter is not used, passing accurate argument info doesn't matter.
@@ -111,10 +110,10 @@
         return true;
       }
 
-      if (!staticTypes[i].isReference()) {
+      if (!staticTypes[i].isReferenceType()) {
         continue;
       }
-      TypeLatticeElement dynamicUpperBoundType = getDynamicUpperBoundType(i);
+      TypeElement dynamicUpperBoundType = getDynamicUpperBoundType(i);
       if (dynamicUpperBoundType == null) {
         continue;
       }
@@ -137,7 +136,7 @@
   }
 
   @Override
-  public TypeLatticeElement getDynamicUpperBoundType(int argIndex) {
+  public TypeElement getDynamicUpperBoundType(int argIndex) {
     assert 0 <= argIndex && argIndex < size;
     assert dynamicUpperBoundTypes != null;
     return dynamicUpperBoundTypes.getOrDefault(argIndex, null);
@@ -176,10 +175,10 @@
         }
       }
 
-      if (arg.getTypeLattice().isPrimitive()) {
+      if (arg.getType().isPrimitiveType()) {
         continue;
       }
-      assert arg.getTypeLattice().isReference();
+      assert arg.getType().isReferenceType();
       newCallSiteInfo.dynamicUpperBoundTypes.put(i, arg.getDynamicUpperBoundType(appView));
     }
     if (newCallSiteInfo.hasUsefulOptimizationInfo(appView, method)) {
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/info/DefaultFieldOptimizationInfo.java b/src/main/java/com/android/tools/r8/ir/optimize/info/DefaultFieldOptimizationInfo.java
index 8248e52..80fbd54 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/info/DefaultFieldOptimizationInfo.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/info/DefaultFieldOptimizationInfo.java
@@ -4,8 +4,8 @@
 
 package com.android.tools.r8.ir.optimize.info;
 
-import com.android.tools.r8.ir.analysis.type.ClassTypeLatticeElement;
-import com.android.tools.r8.ir.analysis.type.TypeLatticeElement;
+import com.android.tools.r8.ir.analysis.type.ClassTypeElement;
+import com.android.tools.r8.ir.analysis.type.TypeElement;
 import com.android.tools.r8.ir.analysis.value.AbstractValue;
 import com.android.tools.r8.ir.analysis.value.UnknownValue;
 
@@ -40,12 +40,12 @@
   }
 
   @Override
-  public ClassTypeLatticeElement getDynamicLowerBoundType() {
+  public ClassTypeElement getDynamicLowerBoundType() {
     return null;
   }
 
   @Override
-  public TypeLatticeElement getDynamicUpperBoundType() {
+  public TypeElement getDynamicUpperBoundType() {
     return null;
   }
 
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/info/DefaultMethodOptimizationInfo.java b/src/main/java/com/android/tools/r8/ir/optimize/info/DefaultMethodOptimizationInfo.java
index 29fcf04..414e55f 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/info/DefaultMethodOptimizationInfo.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/info/DefaultMethodOptimizationInfo.java
@@ -5,8 +5,8 @@
 package com.android.tools.r8.ir.optimize.info;
 
 import com.android.tools.r8.graph.DexType;
-import com.android.tools.r8.ir.analysis.type.ClassTypeLatticeElement;
-import com.android.tools.r8.ir.analysis.type.TypeLatticeElement;
+import com.android.tools.r8.ir.analysis.type.ClassTypeElement;
+import com.android.tools.r8.ir.analysis.type.TypeElement;
 import com.android.tools.r8.ir.analysis.value.AbstractValue;
 import com.android.tools.r8.ir.analysis.value.UnknownValue;
 import com.android.tools.r8.ir.optimize.classinliner.ClassInlinerEligibilityInfo;
@@ -26,8 +26,8 @@
   static boolean UNKNOWN_NEVER_RETURNS_NULL = false;
   static boolean UNKNOWN_NEVER_RETURNS_NORMALLY = false;
   static AbstractValue UNKNOWN_ABSTRACT_RETURN_VALUE = UnknownValue.getInstance();
-  static TypeLatticeElement UNKNOWN_TYPE = null;
-  static ClassTypeLatticeElement UNKNOWN_CLASS_TYPE = null;
+  static TypeElement UNKNOWN_TYPE = null;
+  static ClassTypeElement UNKNOWN_CLASS_TYPE = null;
   static boolean UNKNOWN_CHECKS_NULL_RECEIVER_BEFORE_ANY_SIDE_EFFECT = false;
   static boolean UNKNOWN_TRIGGERS_CLASS_INIT_BEFORE_ANY_SIDE_EFFECT = false;
   static ClassInlinerEligibilityInfo UNKNOWN_CLASS_INLINER_ELIGIBILITY = null;
@@ -66,12 +66,12 @@
   }
 
   @Override
-  public TypeLatticeElement getDynamicUpperBoundType() {
+  public TypeElement getDynamicUpperBoundType() {
     return UNKNOWN_TYPE;
   }
 
   @Override
-  public ClassTypeLatticeElement getDynamicLowerBoundType() {
+  public ClassTypeElement getDynamicLowerBoundType() {
     return UNKNOWN_CLASS_TYPE;
   }
 
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/info/FieldOptimizationInfo.java b/src/main/java/com/android/tools/r8/ir/optimize/info/FieldOptimizationInfo.java
index 33a6e75..09ad5e5 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/info/FieldOptimizationInfo.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/info/FieldOptimizationInfo.java
@@ -4,8 +4,8 @@
 
 package com.android.tools.r8.ir.optimize.info;
 
-import com.android.tools.r8.ir.analysis.type.ClassTypeLatticeElement;
-import com.android.tools.r8.ir.analysis.type.TypeLatticeElement;
+import com.android.tools.r8.ir.analysis.type.ClassTypeElement;
+import com.android.tools.r8.ir.analysis.type.TypeElement;
 import com.android.tools.r8.ir.analysis.value.AbstractValue;
 
 public abstract class FieldOptimizationInfo {
@@ -22,9 +22,9 @@
    */
   public abstract int getReadBits();
 
-  public abstract ClassTypeLatticeElement getDynamicLowerBoundType();
+  public abstract ClassTypeElement getDynamicLowerBoundType();
 
-  public abstract TypeLatticeElement getDynamicUpperBoundType();
+  public abstract TypeElement getDynamicUpperBoundType();
 
   public abstract boolean isDead();
 
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/info/MethodOptimizationInfo.java b/src/main/java/com/android/tools/r8/ir/optimize/info/MethodOptimizationInfo.java
index b7b4973..c74bc0a 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/info/MethodOptimizationInfo.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/info/MethodOptimizationInfo.java
@@ -5,8 +5,8 @@
 package com.android.tools.r8.ir.optimize.info;
 
 import com.android.tools.r8.graph.DexType;
-import com.android.tools.r8.ir.analysis.type.ClassTypeLatticeElement;
-import com.android.tools.r8.ir.analysis.type.TypeLatticeElement;
+import com.android.tools.r8.ir.analysis.type.ClassTypeElement;
+import com.android.tools.r8.ir.analysis.type.TypeElement;
 import com.android.tools.r8.ir.analysis.value.AbstractValue;
 import com.android.tools.r8.ir.optimize.classinliner.ClassInlinerEligibilityInfo;
 import com.android.tools.r8.ir.optimize.info.ParameterUsagesInfo.ParameterUsage;
@@ -32,9 +32,9 @@
 
   boolean classInitializerMayBePostponed();
 
-  TypeLatticeElement getDynamicUpperBoundType();
+  TypeElement getDynamicUpperBoundType();
 
-  ClassTypeLatticeElement getDynamicLowerBoundType();
+  ClassTypeElement getDynamicLowerBoundType();
 
   ParameterUsage getParameterUsages(int parameter);
 
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/info/MethodOptimizationInfoCollector.java b/src/main/java/com/android/tools/r8/ir/optimize/info/MethodOptimizationInfoCollector.java
index bbf2f22..19c897f 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/info/MethodOptimizationInfoCollector.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/info/MethodOptimizationInfoCollector.java
@@ -57,9 +57,9 @@
 import com.android.tools.r8.ir.analysis.InitializedClassesOnNormalExitAnalysis;
 import com.android.tools.r8.ir.analysis.sideeffect.ClassInitializerSideEffectAnalysis;
 import com.android.tools.r8.ir.analysis.sideeffect.ClassInitializerSideEffectAnalysis.ClassInitializerSideEffect;
-import com.android.tools.r8.ir.analysis.type.ClassTypeLatticeElement;
+import com.android.tools.r8.ir.analysis.type.ClassTypeElement;
 import com.android.tools.r8.ir.analysis.type.Nullability;
-import com.android.tools.r8.ir.analysis.type.TypeLatticeElement;
+import com.android.tools.r8.ir.analysis.type.TypeElement;
 import com.android.tools.r8.ir.analysis.value.AbstractValue;
 import com.android.tools.r8.ir.code.AliasedValueConfiguration;
 import com.android.tools.r8.ir.code.AssumeAndCheckCastAliasedValueConfiguration;
@@ -95,6 +95,7 @@
 import com.android.tools.r8.utils.ListUtils;
 import com.android.tools.r8.utils.MethodSignatureEquivalence;
 import com.android.tools.r8.utils.Pair;
+import com.android.tools.r8.utils.Timing;
 import com.android.tools.r8.utils.WorkList;
 import com.google.common.base.Equivalence.Wrapper;
 import com.google.common.collect.Sets;
@@ -123,20 +124,29 @@
       IRCode code,
       OptimizationFeedback feedback,
       DynamicTypeOptimization dynamicTypeOptimization,
-      InstanceFieldInitializationInfoCollection instanceFieldInitializationInfos) {
-    identifyClassInlinerEligibility(method, code, feedback);
-    identifyParameterUsages(method, code, feedback);
-    identifyReturnsArgument(method, code, feedback);
+      InstanceFieldInitializationInfoCollection instanceFieldInitializationInfos,
+      Timing timing) {
+    identifyClassInlinerEligibility(method, code, feedback, timing);
+    identifyParameterUsages(method, code, feedback, timing);
+    identifyReturnsArgument(method, code, feedback, timing);
     if (options.enableInlining) {
-      identifyInvokeSemanticsForInlining(method, code, appView, feedback);
+      identifyInvokeSemanticsForInlining(method, code, feedback, timing);
     }
-    computeDynamicReturnType(dynamicTypeOptimization, feedback, method, code);
-    computeInitializedClassesOnNormalExit(feedback, method, code);
-    computeInstanceInitializerInfo(method, code, feedback, instanceFieldInitializationInfos);
-    computeMayHaveSideEffects(feedback, method, code);
-    computeReturnValueOnlyDependsOnArguments(feedback, method, code);
-    computeNonNullParamOrThrow(feedback, method, code);
-    computeNonNullParamOnNormalExits(feedback, code);
+    computeDynamicReturnType(dynamicTypeOptimization, feedback, method, code, timing);
+    computeInitializedClassesOnNormalExit(feedback, method, code, timing);
+    computeInstanceInitializerInfo(
+        method, code, feedback, instanceFieldInitializationInfos, timing);
+    computeMayHaveSideEffects(feedback, method, code, timing);
+    computeReturnValueOnlyDependsOnArguments(feedback, method, code, timing);
+    computeNonNullParamOrThrow(feedback, method, code, timing);
+    computeNonNullParamOnNormalExits(feedback, code, timing);
+  }
+
+  private void identifyClassInlinerEligibility(
+      DexEncodedMethod method, IRCode code, OptimizationFeedback feedback, Timing timing) {
+    timing.begin("Identify class inliner eligibility");
+    identifyClassInlinerEligibility(method, code, feedback);
+    timing.end();
   }
 
   private void identifyClassInlinerEligibility(
@@ -283,6 +293,13 @@
   }
 
   private void identifyParameterUsages(
+      DexEncodedMethod method, IRCode code, OptimizationFeedback feedback, Timing timing) {
+    timing.begin("Identify parameter usages");
+    identifyParameterUsages(method, code, feedback);
+    timing.end();
+  }
+
+  private void identifyParameterUsages(
       DexEncodedMethod method, IRCode code, OptimizationFeedback feedback) {
     List<ParameterUsage> usages = new ArrayList<>();
     List<Value> values = code.collectArguments();
@@ -322,6 +339,13 @@
   }
 
   private void identifyReturnsArgument(
+      DexEncodedMethod method, IRCode code, OptimizationFeedback feedback, Timing timing) {
+    timing.begin("Identify returns argument");
+    identifyReturnsArgument(method, code, feedback);
+    timing.end();
+  }
+
+  private void identifyReturnsArgument(
       DexEncodedMethod method, IRCode code, OptimizationFeedback feedback) {
     List<BasicBlock> normalExits = code.computeNormalExitBlocks();
     if (normalExits.isEmpty()) {
@@ -333,14 +357,14 @@
       return;
     }
     Value returnValue = firstExit.returnValue();
-    boolean isNeverNull = returnValue.getTypeLattice().isReference() && returnValue.isNeverNull();
+    boolean isNeverNull = returnValue.getType().isReferenceType() && returnValue.isNeverNull();
     for (int i = 1; i < normalExits.size(); i++) {
       Return exit = normalExits.get(i).exit().asReturn();
       Value value = exit.returnValue();
       if (value != returnValue) {
         returnValue = null;
       }
-      isNeverNull &= value.getTypeLattice().isReference() && value.isNeverNull();
+      isNeverNull &= value.getType().isReferenceType() && value.isNeverNull();
     }
     if (returnValue != null) {
       Value aliasedValue = returnValue.getAliasedValue();
@@ -365,6 +389,17 @@
       DexEncodedMethod method,
       IRCode code,
       OptimizationFeedback feedback,
+      InstanceFieldInitializationInfoCollection instanceFieldInitializationInfos,
+      Timing timing) {
+    timing.begin("Compute instance initializer info");
+    computeInstanceInitializerInfo(method, code, feedback, instanceFieldInitializationInfos);
+    timing.end();
+  }
+
+  private void computeInstanceInitializerInfo(
+      DexEncodedMethod method,
+      IRCode code,
+      OptimizationFeedback feedback,
       InstanceFieldInitializationInfoCollection instanceFieldInitializationInfos) {
     assert !appView.appInfo().isPinned(method.method);
 
@@ -641,7 +676,14 @@
   }
 
   private void identifyInvokeSemanticsForInlining(
-      DexEncodedMethod method, IRCode code, AppView<?> appView, OptimizationFeedback feedback) {
+      DexEncodedMethod method, IRCode code, OptimizationFeedback feedback, Timing timing) {
+    timing.begin("Identify invoke semantics for inlining");
+    identifyInvokeSemanticsForInlining(method, code, feedback);
+    timing.end();
+  }
+
+  private void identifyInvokeSemanticsForInlining(
+      DexEncodedMethod method, IRCode code, OptimizationFeedback feedback) {
     if (method.isStatic()) {
       // Identifies if the method preserves class initialization after inlining.
       feedback.markTriggerClassInitBeforeAnySideEffect(
@@ -899,24 +941,35 @@
       DynamicTypeOptimization dynamicTypeOptimization,
       OptimizationFeedback feedback,
       DexEncodedMethod method,
+      IRCode code,
+      Timing timing) {
+    timing.begin("Compute dynamic return type");
+    computeDynamicReturnType(dynamicTypeOptimization, feedback, method, code);
+    timing.end();
+  }
+
+  private void computeDynamicReturnType(
+      DynamicTypeOptimization dynamicTypeOptimization,
+      OptimizationFeedback feedback,
+      DexEncodedMethod method,
       IRCode code) {
     if (dynamicTypeOptimization != null) {
       DexType staticReturnTypeRaw = method.method.proto.returnType;
       if (!staticReturnTypeRaw.isReferenceType()) {
         return;
       }
-      TypeLatticeElement dynamicReturnType =
+      TypeElement dynamicReturnType =
           dynamicTypeOptimization.computeDynamicReturnType(method, code);
       if (dynamicReturnType != null) {
-        TypeLatticeElement staticReturnType =
-            TypeLatticeElement.fromDexType(staticReturnTypeRaw, Nullability.maybeNull(), appView);
+        TypeElement staticReturnType =
+            TypeElement.fromDexType(staticReturnTypeRaw, Nullability.maybeNull(), appView);
         // If the dynamic return type is not more precise than the static return type there is no
         // need to record it.
         if (dynamicReturnType.strictlyLessThan(staticReturnType, appView)) {
           feedback.methodReturnsObjectWithUpperBoundType(method, appView, dynamicReturnType);
         }
       }
-      ClassTypeLatticeElement exactReturnType =
+      ClassTypeElement exactReturnType =
           dynamicTypeOptimization.computeDynamicLowerBoundType(method, code);
       if (exactReturnType != null) {
         feedback.methodReturnsObjectWithLowerBoundType(method, exactReturnType);
@@ -925,6 +978,13 @@
   }
 
   private void computeInitializedClassesOnNormalExit(
+      OptimizationFeedback feedback, DexEncodedMethod method, IRCode code, Timing timing) {
+    timing.begin("Compute initialized classes on normal exits");
+    computeInitializedClassesOnNormalExit(feedback, method, code);
+    timing.end();
+  }
+
+  private void computeInitializedClassesOnNormalExit(
       OptimizationFeedback feedback, DexEncodedMethod method, IRCode code) {
     if (options.enableInitializedClassesAnalysis && appView.appInfo().hasLiveness()) {
       AppView<AppInfoWithLiveness> appViewWithLiveness = appView.withLiveness();
@@ -938,6 +998,13 @@
   }
 
   private void computeMayHaveSideEffects(
+      OptimizationFeedback feedback, DexEncodedMethod method, IRCode code, Timing timing) {
+    timing.begin("Compute may have side effects");
+    computeMayHaveSideEffects(feedback, method, code);
+    timing.end();
+  }
+
+  private void computeMayHaveSideEffects(
       OptimizationFeedback feedback, DexEncodedMethod method, IRCode code) {
     // If the method is native, we don't know what could happen.
     assert !method.accessFlags.isNative();
@@ -1018,6 +1085,13 @@
   }
 
   private void computeReturnValueOnlyDependsOnArguments(
+      OptimizationFeedback feedback, DexEncodedMethod method, IRCode code, Timing timing) {
+    timing.begin("Return value only depends on argument");
+    computeReturnValueOnlyDependsOnArguments(feedback, method, code);
+    timing.end();
+  }
+
+  private void computeReturnValueOnlyDependsOnArguments(
       OptimizationFeedback feedback, DexEncodedMethod method, IRCode code) {
     if (!options.enableDeterminismAnalysis) {
       return;
@@ -1029,6 +1103,13 @@
     }
   }
 
+  private void computeNonNullParamOrThrow(
+      OptimizationFeedback feedback, DexEncodedMethod method, IRCode code, Timing timing) {
+    timing.begin("Compute non-null-param-or-throw");
+    computeNonNullParamOrThrow(feedback, method, code);
+    timing.end();
+  }
+
   // Track usage of parameters and compute their nullability and possibility of NPE.
   private void computeNonNullParamOrThrow(
       OptimizationFeedback feedback, DexEncodedMethod method, IRCode code) {
@@ -1058,6 +1139,13 @@
     }
   }
 
+  private void computeNonNullParamOnNormalExits(
+      OptimizationFeedback feedback, IRCode code, Timing timing) {
+    timing.begin("Compute non-null-param-on-normal-exits");
+    computeNonNullParamOnNormalExits(feedback, code);
+    timing.end();
+  }
+
   private void computeNonNullParamOnNormalExits(OptimizationFeedback feedback, IRCode code) {
     Set<BasicBlock> normalExits = Sets.newIdentityHashSet();
     normalExits.addAll(code.computeNormalExitBlocks());
@@ -1068,7 +1156,7 @@
     for (int index = 0; index < arguments.size(); index++) {
       Value argument = arguments.get(index);
       // Consider reference-type parameter only.
-      if (!argument.getTypeLattice().isReference()) {
+      if (!argument.getType().isReferenceType()) {
         continue;
       }
       // The receiver is always non-null on normal exits.
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/info/MutableFieldOptimizationInfo.java b/src/main/java/com/android/tools/r8/ir/optimize/info/MutableFieldOptimizationInfo.java
index a4917f2..118c285 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/info/MutableFieldOptimizationInfo.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/info/MutableFieldOptimizationInfo.java
@@ -8,8 +8,8 @@
 import com.android.tools.r8.graph.AppView;
 import com.android.tools.r8.graph.DexType;
 import com.android.tools.r8.graph.GraphLense;
-import com.android.tools.r8.ir.analysis.type.ClassTypeLatticeElement;
-import com.android.tools.r8.ir.analysis.type.TypeLatticeElement;
+import com.android.tools.r8.ir.analysis.type.ClassTypeElement;
+import com.android.tools.r8.ir.analysis.type.TypeElement;
 import com.android.tools.r8.ir.analysis.value.AbstractValue;
 import com.android.tools.r8.ir.analysis.value.UnknownValue;
 import com.android.tools.r8.shaking.AppInfoWithLiveness;
@@ -31,25 +31,26 @@
   private AbstractValue abstractValue = UnknownValue.getInstance();
   private int flags;
   private int readBits = 0;
-  private ClassTypeLatticeElement dynamicLowerBoundType = null;
-  private TypeLatticeElement dynamicUpperBoundType = null;
+  private ClassTypeElement dynamicLowerBoundType = null;
+  private TypeElement dynamicUpperBoundType = null;
 
-  public void fixupClassTypeReferences(
+  public MutableFieldOptimizationInfo fixupClassTypeReferences(
       Function<DexType, DexType> mapping, AppView<? extends AppInfoWithSubtyping> appView) {
     if (dynamicUpperBoundType != null) {
       dynamicUpperBoundType = dynamicUpperBoundType.fixupClassTypeReferences(mapping, appView);
     }
     if (dynamicLowerBoundType != null) {
-      TypeLatticeElement dynamicLowerBoundType =
+      TypeElement dynamicLowerBoundType =
           this.dynamicLowerBoundType.fixupClassTypeReferences(mapping, appView);
       if (dynamicLowerBoundType.isClassType()) {
-        this.dynamicLowerBoundType = dynamicLowerBoundType.asClassTypeLatticeElement();
+        this.dynamicLowerBoundType = dynamicLowerBoundType.asClassType();
       } else {
-        assert dynamicLowerBoundType.isPrimitive();
+        assert dynamicLowerBoundType.isPrimitiveType();
         this.dynamicLowerBoundType = null;
         this.dynamicUpperBoundType = null;
       }
     }
+    return this;
   }
 
   @Override
@@ -91,20 +92,20 @@
   }
 
   @Override
-  public ClassTypeLatticeElement getDynamicLowerBoundType() {
+  public ClassTypeElement getDynamicLowerBoundType() {
     return dynamicLowerBoundType;
   }
 
-  void setDynamicLowerBoundType(ClassTypeLatticeElement type) {
+  void setDynamicLowerBoundType(ClassTypeElement type) {
     dynamicLowerBoundType = type;
   }
 
   @Override
-  public TypeLatticeElement getDynamicUpperBoundType() {
+  public TypeElement getDynamicUpperBoundType() {
     return dynamicUpperBoundType;
   }
 
-  void setDynamicUpperBoundType(TypeLatticeElement type) {
+  void setDynamicUpperBoundType(TypeElement type) {
     dynamicUpperBoundType = type;
   }
 
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/info/OptimizationFeedbackDelayed.java b/src/main/java/com/android/tools/r8/ir/optimize/info/OptimizationFeedbackDelayed.java
index 81d573b..212a044 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/info/OptimizationFeedbackDelayed.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/info/OptimizationFeedbackDelayed.java
@@ -8,8 +8,8 @@
 import com.android.tools.r8.graph.DexEncodedField;
 import com.android.tools.r8.graph.DexEncodedMethod;
 import com.android.tools.r8.graph.DexType;
-import com.android.tools.r8.ir.analysis.type.ClassTypeLatticeElement;
-import com.android.tools.r8.ir.analysis.type.TypeLatticeElement;
+import com.android.tools.r8.ir.analysis.type.ClassTypeElement;
+import com.android.tools.r8.ir.analysis.type.TypeElement;
 import com.android.tools.r8.ir.analysis.value.AbstractValue;
 import com.android.tools.r8.ir.optimize.Inliner.ConstraintWithTarget;
 import com.android.tools.r8.ir.optimize.classinliner.ClassInlinerEligibilityInfo;
@@ -126,13 +126,12 @@
   }
 
   @Override
-  public void markFieldHasDynamicLowerBoundType(
-      DexEncodedField field, ClassTypeLatticeElement type) {
+  public void markFieldHasDynamicLowerBoundType(DexEncodedField field, ClassTypeElement type) {
     getFieldOptimizationInfoForUpdating(field).setDynamicLowerBoundType(type);
   }
 
   @Override
-  public void markFieldHasDynamicUpperBoundType(DexEncodedField field, TypeLatticeElement type) {
+  public void markFieldHasDynamicUpperBoundType(DexEncodedField field, TypeElement type) {
     getFieldOptimizationInfoForUpdating(field).setDynamicUpperBoundType(type);
   }
 
@@ -190,13 +189,13 @@
 
   @Override
   public synchronized void methodReturnsObjectWithUpperBoundType(
-      DexEncodedMethod method, AppView<?> appView, TypeLatticeElement type) {
+      DexEncodedMethod method, AppView<?> appView, TypeElement type) {
     getMethodOptimizationInfoForUpdating(method).markReturnsObjectWithUpperBoundType(appView, type);
   }
 
   @Override
   public synchronized void methodReturnsObjectWithLowerBoundType(
-      DexEncodedMethod method, ClassTypeLatticeElement type) {
+      DexEncodedMethod method, ClassTypeElement type) {
     getMethodOptimizationInfoForUpdating(method).markReturnsObjectWithLowerBoundType(type);
   }
 
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/info/OptimizationFeedbackIgnore.java b/src/main/java/com/android/tools/r8/ir/optimize/info/OptimizationFeedbackIgnore.java
index 66aefb4..2f20a39 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/info/OptimizationFeedbackIgnore.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/info/OptimizationFeedbackIgnore.java
@@ -8,8 +8,8 @@
 import com.android.tools.r8.graph.DexEncodedField;
 import com.android.tools.r8.graph.DexEncodedMethod;
 import com.android.tools.r8.graph.DexType;
-import com.android.tools.r8.ir.analysis.type.ClassTypeLatticeElement;
-import com.android.tools.r8.ir.analysis.type.TypeLatticeElement;
+import com.android.tools.r8.ir.analysis.type.ClassTypeElement;
+import com.android.tools.r8.ir.analysis.type.TypeElement;
 import com.android.tools.r8.ir.analysis.value.AbstractValue;
 import com.android.tools.r8.ir.optimize.Inliner.ConstraintWithTarget;
 import com.android.tools.r8.ir.optimize.classinliner.ClassInlinerEligibilityInfo;
@@ -40,11 +40,10 @@
   public void markFieldAsPropagated(DexEncodedField field) {}
 
   @Override
-  public void markFieldHasDynamicLowerBoundType(
-      DexEncodedField field, ClassTypeLatticeElement type) {}
+  public void markFieldHasDynamicLowerBoundType(DexEncodedField field, ClassTypeElement type) {}
 
   @Override
-  public void markFieldHasDynamicUpperBoundType(DexEncodedField field, TypeLatticeElement type) {}
+  public void markFieldHasDynamicUpperBoundType(DexEncodedField field, TypeElement type) {}
 
   @Override
   public void markFieldBitsRead(DexEncodedField field, int bitsRead) {}
@@ -77,11 +76,11 @@
 
   @Override
   public void methodReturnsObjectWithUpperBoundType(
-      DexEncodedMethod method, AppView<?> appView, TypeLatticeElement type) {}
+      DexEncodedMethod method, AppView<?> appView, TypeElement type) {}
 
   @Override
   public void methodReturnsObjectWithLowerBoundType(
-      DexEncodedMethod method, ClassTypeLatticeElement type) {}
+      DexEncodedMethod method, ClassTypeElement type) {}
 
   @Override
   public void methodMayNotHaveSideEffects(DexEncodedMethod method) {}
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/info/OptimizationFeedbackSimple.java b/src/main/java/com/android/tools/r8/ir/optimize/info/OptimizationFeedbackSimple.java
index b258bdf..0bf4d8b 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/info/OptimizationFeedbackSimple.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/info/OptimizationFeedbackSimple.java
@@ -8,8 +8,8 @@
 import com.android.tools.r8.graph.DexEncodedField;
 import com.android.tools.r8.graph.DexEncodedMethod;
 import com.android.tools.r8.graph.DexType;
-import com.android.tools.r8.ir.analysis.type.ClassTypeLatticeElement;
-import com.android.tools.r8.ir.analysis.type.TypeLatticeElement;
+import com.android.tools.r8.ir.analysis.type.ClassTypeElement;
+import com.android.tools.r8.ir.analysis.type.TypeElement;
 import com.android.tools.r8.ir.analysis.value.AbstractValue;
 import com.android.tools.r8.ir.optimize.Inliner.ConstraintWithTarget;
 import com.android.tools.r8.ir.optimize.classinliner.ClassInlinerEligibilityInfo;
@@ -46,13 +46,12 @@
   }
 
   @Override
-  public void markFieldHasDynamicLowerBoundType(
-      DexEncodedField field, ClassTypeLatticeElement type) {
+  public void markFieldHasDynamicLowerBoundType(DexEncodedField field, ClassTypeElement type) {
     // Ignored.
   }
 
   @Override
-  public void markFieldHasDynamicUpperBoundType(DexEncodedField field, TypeLatticeElement type) {
+  public void markFieldHasDynamicUpperBoundType(DexEncodedField field, TypeElement type) {
     // Ignored.
   }
 
@@ -103,13 +102,13 @@
 
   @Override
   public void methodReturnsObjectWithUpperBoundType(
-      DexEncodedMethod method, AppView<?> appView, TypeLatticeElement type) {
+      DexEncodedMethod method, AppView<?> appView, TypeElement type) {
     // Ignored.
   }
 
   @Override
   public void methodReturnsObjectWithLowerBoundType(
-      DexEncodedMethod method, ClassTypeLatticeElement type) {
+      DexEncodedMethod method, ClassTypeElement type) {
     // Ignored.
   }
 
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/info/UpdatableMethodOptimizationInfo.java b/src/main/java/com/android/tools/r8/ir/optimize/info/UpdatableMethodOptimizationInfo.java
index 366b4d2..ed6233f 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/info/UpdatableMethodOptimizationInfo.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/info/UpdatableMethodOptimizationInfo.java
@@ -8,8 +8,8 @@
 import com.android.tools.r8.graph.AppView;
 import com.android.tools.r8.graph.DexType;
 import com.android.tools.r8.graph.GraphLense;
-import com.android.tools.r8.ir.analysis.type.ClassTypeLatticeElement;
-import com.android.tools.r8.ir.analysis.type.TypeLatticeElement;
+import com.android.tools.r8.ir.analysis.type.ClassTypeElement;
+import com.android.tools.r8.ir.analysis.type.TypeElement;
 import com.android.tools.r8.ir.analysis.value.AbstractValue;
 import com.android.tools.r8.ir.optimize.classinliner.ClassInlinerEligibilityInfo;
 import com.android.tools.r8.ir.optimize.info.ParameterUsagesInfo.ParameterUsage;
@@ -28,9 +28,8 @@
   private int returnedArgument = DefaultMethodOptimizationInfo.UNKNOWN_RETURNED_ARGUMENT;
   private AbstractValue abstractReturnValue =
       DefaultMethodOptimizationInfo.UNKNOWN_ABSTRACT_RETURN_VALUE;
-  private TypeLatticeElement returnsObjectWithUpperBoundType =
-      DefaultMethodOptimizationInfo.UNKNOWN_TYPE;
-  private ClassTypeLatticeElement returnsObjectWithLowerBoundType =
+  private TypeElement returnsObjectWithUpperBoundType = DefaultMethodOptimizationInfo.UNKNOWN_TYPE;
+  private ClassTypeElement returnsObjectWithLowerBoundType =
       DefaultMethodOptimizationInfo.UNKNOWN_CLASS_TYPE;
   private InlinePreference inlining = InlinePreference.Default;
   // Stores information about instance methods and constructors for
@@ -142,24 +141,24 @@
     nonNullParamOnNormalExits = template.nonNullParamOnNormalExits;
   }
 
-  public void fixupClassTypeReferences(
+  public UpdatableMethodOptimizationInfo fixupClassTypeReferences(
       Function<DexType, DexType> mapping, AppView<? extends AppInfoWithSubtyping> appView) {
     if (returnsObjectWithUpperBoundType != null) {
       returnsObjectWithUpperBoundType =
           returnsObjectWithUpperBoundType.fixupClassTypeReferences(mapping, appView);
     }
     if (returnsObjectWithLowerBoundType != null) {
-      TypeLatticeElement returnsObjectWithLowerBoundType =
+      TypeElement returnsObjectWithLowerBoundType =
           this.returnsObjectWithLowerBoundType.fixupClassTypeReferences(mapping, appView);
       if (returnsObjectWithLowerBoundType.isClassType()) {
-        this.returnsObjectWithLowerBoundType =
-            returnsObjectWithLowerBoundType.asClassTypeLatticeElement();
+        this.returnsObjectWithLowerBoundType = returnsObjectWithLowerBoundType.asClassType();
       } else {
-        assert returnsObjectWithLowerBoundType.isPrimitive();
+        assert returnsObjectWithLowerBoundType.isPrimitiveType();
         this.returnsObjectWithUpperBoundType = DefaultMethodOptimizationInfo.UNKNOWN_TYPE;
         this.returnsObjectWithLowerBoundType = DefaultMethodOptimizationInfo.UNKNOWN_CLASS_TYPE;
       }
     }
+    return this;
   }
 
   public UpdatableMethodOptimizationInfo fixupAbstractReturnValue(
@@ -231,12 +230,12 @@
   }
 
   @Override
-  public TypeLatticeElement getDynamicUpperBoundType() {
+  public TypeElement getDynamicUpperBoundType() {
     return returnsObjectWithUpperBoundType;
   }
 
   @Override
-  public ClassTypeLatticeElement getDynamicLowerBoundType() {
+  public ClassTypeElement getDynamicLowerBoundType() {
     return returnsObjectWithLowerBoundType;
   }
 
@@ -405,7 +404,7 @@
     abstractReturnValue = value;
   }
 
-  void markReturnsObjectWithUpperBoundType(AppView<?> appView, TypeLatticeElement type) {
+  void markReturnsObjectWithUpperBoundType(AppView<?> appView, TypeElement type) {
     assert type != null;
     // We may get more precise type information if the method is reprocessed (e.g., due to
     // optimization info collected from all call sites), and hence the
@@ -420,7 +419,7 @@
     returnsObjectWithUpperBoundType = type;
   }
 
-  void markReturnsObjectWithLowerBoundType(ClassTypeLatticeElement type) {
+  void markReturnsObjectWithLowerBoundType(ClassTypeElement type) {
     assert type != null;
     // Currently, we only have a lower bound type when we have _exact_ runtime type information.
     // Thus, the type should never become more precise (although the nullability could).
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/info/field/InstanceFieldInitializationInfoFactory.java b/src/main/java/com/android/tools/r8/ir/optimize/info/field/InstanceFieldInitializationInfoFactory.java
index eb7cdda..87f4c2a 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/info/field/InstanceFieldInitializationInfoFactory.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/info/field/InstanceFieldInitializationInfoFactory.java
@@ -4,8 +4,8 @@
 
 package com.android.tools.r8.ir.optimize.info.field;
 
-import com.android.tools.r8.ir.analysis.type.ClassTypeLatticeElement;
-import com.android.tools.r8.ir.analysis.type.TypeLatticeElement;
+import com.android.tools.r8.ir.analysis.type.ClassTypeElement;
+import com.android.tools.r8.ir.analysis.type.TypeElement;
 import java.util.concurrent.ConcurrentHashMap;
 
 public class InstanceFieldInitializationInfoFactory {
@@ -20,7 +20,7 @@
   }
 
   public InstanceFieldTypeInitializationInfo createTypeInitializationInfo(
-      ClassTypeLatticeElement dynamicLowerBoundType, TypeLatticeElement dynamicUpperBoundType) {
+      ClassTypeElement dynamicLowerBoundType, TypeElement dynamicUpperBoundType) {
     return new InstanceFieldTypeInitializationInfo(dynamicLowerBoundType, dynamicUpperBoundType);
   }
 }
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/info/field/InstanceFieldTypeInitializationInfo.java b/src/main/java/com/android/tools/r8/ir/optimize/info/field/InstanceFieldTypeInitializationInfo.java
index 91d1aef..ec77b22 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/info/field/InstanceFieldTypeInitializationInfo.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/info/field/InstanceFieldTypeInitializationInfo.java
@@ -7,8 +7,8 @@
 import com.android.tools.r8.graph.AppView;
 import com.android.tools.r8.graph.EnumValueInfoMapCollection;
 import com.android.tools.r8.graph.GraphLense;
-import com.android.tools.r8.ir.analysis.type.ClassTypeLatticeElement;
-import com.android.tools.r8.ir.analysis.type.TypeLatticeElement;
+import com.android.tools.r8.ir.analysis.type.ClassTypeElement;
+import com.android.tools.r8.ir.analysis.type.TypeElement;
 import com.android.tools.r8.shaking.AppInfoWithLiveness;
 import java.util.Objects;
 
@@ -18,21 +18,21 @@
  */
 public class InstanceFieldTypeInitializationInfo implements InstanceFieldInitializationInfo {
 
-  private final ClassTypeLatticeElement dynamicLowerBoundType;
-  private final TypeLatticeElement dynamicUpperBoundType;
+  private final ClassTypeElement dynamicLowerBoundType;
+  private final TypeElement dynamicUpperBoundType;
 
   /** Intentionally package private, use {@link InstanceFieldInitializationInfoFactory} instead. */
   InstanceFieldTypeInitializationInfo(
-      ClassTypeLatticeElement dynamicLowerBoundType, TypeLatticeElement dynamicUpperBoundType) {
+      ClassTypeElement dynamicLowerBoundType, TypeElement dynamicUpperBoundType) {
     this.dynamicLowerBoundType = dynamicLowerBoundType;
     this.dynamicUpperBoundType = dynamicUpperBoundType;
   }
 
-  public ClassTypeLatticeElement getDynamicLowerBoundType() {
+  public ClassTypeElement getDynamicLowerBoundType() {
     return dynamicLowerBoundType;
   }
 
-  public TypeLatticeElement getDynamicUpperBoundType() {
+  public TypeElement getDynamicUpperBoundType() {
     return dynamicUpperBoundType;
   }
 
@@ -56,8 +56,7 @@
       return UnknownInstanceFieldInitializationInfo.getInstance();
     }
     if (dynamicUpperBoundType.isClassType()
-        && unboxedEnums.containsEnum(
-            dynamicUpperBoundType.asClassTypeLatticeElement().getClassType())) {
+        && unboxedEnums.containsEnum(dynamicUpperBoundType.asClassType().getClassType())) {
       // No point in tracking the type of primitives.
       return UnknownInstanceFieldInitializationInfo.getInstance();
     }
@@ -65,7 +64,7 @@
         dynamicLowerBoundType != null
             ? dynamicLowerBoundType
                 .fixupClassTypeReferences(lens::lookupType, appView.withSubtyping())
-                .asClassTypeLatticeElement()
+                .asClassType()
             : null,
         dynamicUpperBoundType.fixupClassTypeReferences(lens::lookupType, appView.withSubtyping()));
   }
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/lambda/LambdaMerger.java b/src/main/java/com/android/tools/r8/ir/optimize/lambda/LambdaMerger.java
index 510c122..ce2394c 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/lambda/LambdaMerger.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/lambda/LambdaMerger.java
@@ -594,8 +594,7 @@
       // able to propagate the type information correctly, since lambda merging is neither a
       // narrowing nor a widening.
       for (Value value : transitivelyTypeAffectedValues) {
-        value.setTypeLattice(
-            value.getTypeLattice().fixupClassTypeReferences(optimizationInfoFixer, appView));
+        value.setType(value.getType().fixupClassTypeReferences(optimizationInfoFixer, appView));
       }
 
       // Filter out the type affected phis and destructively update the type of the phis. This is
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/lambda/LambdaTypeVisitor.java b/src/main/java/com/android/tools/r8/ir/optimize/lambda/LambdaTypeVisitor.java
index 7422bc0..9dce489 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/lambda/LambdaTypeVisitor.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/lambda/LambdaTypeVisitor.java
@@ -17,12 +17,6 @@
 import com.android.tools.r8.graph.DexType;
 import com.android.tools.r8.graph.DexTypeList;
 import com.android.tools.r8.graph.DexValue;
-import com.android.tools.r8.graph.DexValue.DexValueArray;
-import com.android.tools.r8.graph.DexValue.DexValueField;
-import com.android.tools.r8.graph.DexValue.DexValueMethod;
-import com.android.tools.r8.graph.DexValue.DexValueMethodHandle;
-import com.android.tools.r8.graph.DexValue.DexValueMethodType;
-import com.android.tools.r8.graph.DexValue.DexValueType;
 import com.android.tools.r8.graph.ParameterAnnotationsList;
 import java.util.function.Consumer;
 import java.util.function.Predicate;
@@ -49,30 +43,29 @@
   }
 
   private void accept(DexValue value) {
-    if (value instanceof DexValueType) {
-      accept(((DexValueType) value).value);
-      return;
-    }
-    if (value instanceof DexValueArray) {
-      for (DexValue subValue : ((DexValueArray) value).getValues()) {
-        accept(subValue);
-      }
-      return;
-    }
-    if (value instanceof DexValueMethod) {
-      accept(((DexValueMethod) value).value, null);
-      return;
-    }
-    if (value instanceof DexValueMethodHandle) {
-      accept(((DexValueMethodHandle) value).value);
-      return;
-    }
-    if (value instanceof DexValueMethodType) {
-      accept(((DexValueMethodType) value).value);
-      return;
-    }
-    if (value instanceof DexValueField) {
-      accept(((DexValueField) value).value, null);
+    switch (value.getValueKind()) {
+      case ARRAY:
+        for (DexValue elementValue : value.asDexValueArray().getValues()) {
+          accept(elementValue);
+        }
+        break;
+      case FIELD:
+        accept(value.asDexValueField().value, null);
+        break;
+      case METHOD:
+        accept(value.asDexValueMethod().value, null);
+        break;
+      case METHOD_HANDLE:
+        accept(value.asDexValueMethodHandle().value);
+        break;
+      case METHOD_TYPE:
+        accept(value.asDexValueMethodType().value);
+        break;
+      case TYPE:
+        accept(value.asDexValueType().value);
+        break;
+      default:
+        // Intentionally empty.
     }
   }
 
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/lambda/kotlin/ClassInitializerSourceCode.java b/src/main/java/com/android/tools/r8/ir/optimize/lambda/kotlin/ClassInitializerSourceCode.java
index b679383..0eec9af 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/lambda/kotlin/ClassInitializerSourceCode.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/lambda/kotlin/ClassInitializerSourceCode.java
@@ -8,7 +8,7 @@
 import com.android.tools.r8.graph.DexMethod;
 import com.android.tools.r8.graph.DexType;
 import com.android.tools.r8.graph.DexTypeList;
-import com.android.tools.r8.ir.analysis.type.TypeLatticeElement;
+import com.android.tools.r8.ir.analysis.type.TypeElement;
 import com.android.tools.r8.ir.code.Invoke.Type;
 import com.android.tools.r8.ir.code.Position;
 import com.android.tools.r8.ir.code.ValueType;
@@ -47,7 +47,7 @@
           if (group.isSingletonLambda(lambda)) {
             int id = group.lambdaId(lambda);
             add(builder -> builder.addNewInstance(instance, groupClassType));
-            add(builder -> builder.addConst(TypeLatticeElement.getInt(), lambdaId, id));
+            add(builder -> builder.addConst(TypeElement.getInt(), lambdaId, id));
             add(
                 builder ->
                     builder.addInvoke(
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/lambda/kotlin/KStyleLambdaGroup.java b/src/main/java/com/android/tools/r8/ir/optimize/lambda/kotlin/KStyleLambdaGroup.java
index b510e88..0295106 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/lambda/kotlin/KStyleLambdaGroup.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/lambda/kotlin/KStyleLambdaGroup.java
@@ -18,7 +18,7 @@
 import com.android.tools.r8.graph.DexType;
 import com.android.tools.r8.graph.EnclosingMethodAttribute;
 import com.android.tools.r8.graph.InnerClassAttribute;
-import com.android.tools.r8.ir.analysis.type.TypeLatticeElement;
+import com.android.tools.r8.ir.analysis.type.TypeElement;
 import com.android.tools.r8.ir.code.Invoke.Type;
 import com.android.tools.r8.ir.code.Position;
 import com.android.tools.r8.ir.code.ValueType;
@@ -240,7 +240,7 @@
     @Override
     void prepareSuperConstructorCall(int receiverRegister) {
       int arityRegister = nextRegister(ValueType.INT);
-      add(builder -> builder.addConst(TypeLatticeElement.getInt(), arityRegister, arity));
+      add(builder -> builder.addConst(TypeElement.getInt(), arityRegister, arity));
       add(
           builder ->
               builder.addInvoke(
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/lambda/kotlin/KotlinLambdaGroupClassBuilder.java b/src/main/java/com/android/tools/r8/ir/optimize/lambda/kotlin/KotlinLambdaGroupClassBuilder.java
index 6bc95f2..9f14329 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/lambda/kotlin/KotlinLambdaGroupClassBuilder.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/lambda/kotlin/KotlinLambdaGroupClassBuilder.java
@@ -25,7 +25,7 @@
 import com.android.tools.r8.graph.InnerClassAttribute;
 import com.android.tools.r8.graph.MethodAccessFlags;
 import com.android.tools.r8.graph.ParameterAnnotationsList;
-import com.android.tools.r8.ir.analysis.type.ClassTypeLatticeElement;
+import com.android.tools.r8.ir.analysis.type.ClassTypeElement;
 import com.android.tools.r8.ir.code.Position;
 import com.android.tools.r8.ir.optimize.info.OptimizationFeedback;
 import com.android.tools.r8.ir.optimize.lambda.LambdaGroupClassBuilder;
@@ -240,8 +240,8 @@
 
             // Record that the field is definitely not null. It is guaranteed to be assigned in the
             // class initializer of the enclosing class before it is read.
-            ClassTypeLatticeElement exactType =
-                ClassTypeLatticeElement.create(field.type, definitelyNotNull(), appView);
+            ClassTypeElement exactType =
+                ClassTypeElement.create(field.type, definitelyNotNull(), appView);
             feedback.markFieldHasDynamicLowerBoundType(encodedField, exactType);
             feedback.markFieldHasDynamicUpperBoundType(encodedField, exactType);
           }
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/lambda/kotlin/KotlinLambdaGroupCodeStrategy.java b/src/main/java/com/android/tools/r8/ir/optimize/lambda/kotlin/KotlinLambdaGroupCodeStrategy.java
index 81480d6..3e602b7 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/lambda/kotlin/KotlinLambdaGroupCodeStrategy.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/lambda/kotlin/KotlinLambdaGroupCodeStrategy.java
@@ -12,7 +12,7 @@
 import com.android.tools.r8.graph.DexMethod;
 import com.android.tools.r8.graph.DexProto;
 import com.android.tools.r8.graph.DexType;
-import com.android.tools.r8.ir.analysis.type.TypeLatticeElement;
+import com.android.tools.r8.ir.analysis.type.TypeElement;
 import com.android.tools.r8.ir.code.CheckCast;
 import com.android.tools.r8.ir.code.ConstNumber;
 import com.android.tools.r8.ir.code.InitClass;
@@ -128,7 +128,7 @@
         new NewInstance(
             newType,
             context.code.createValue(
-                TypeLatticeElement.fromDexType(newType, definitelyNotNull(), context.appView)));
+                TypeElement.fromDexType(newType, definitelyNotNull(), context.appView)));
     context.instructions().replaceCurrentInstruction(patchedNewInstance);
 
     assert newType != oldType;
@@ -178,8 +178,8 @@
 
     // Since all captured values of non-primitive types are stored in fields of type
     // java.lang.Object, we need to cast them to appropriate type to satisfy the verifier.
-    TypeLatticeElement castTypeLattice =
-        TypeLatticeElement.fromDexType(oldFieldType, maybeNull(), context.appView);
+    TypeElement castTypeLattice =
+        TypeElement.fromDexType(oldFieldType, maybeNull(), context.appView);
     Value newValue = context.code.createValue(castTypeLattice, newInstanceGet.getLocalInfo());
     newInstanceGet.outValue().replaceUsers(newValue);
     CheckCast cast = new CheckCast(newValue, newInstanceGet.outValue(), oldFieldType);
@@ -202,7 +202,7 @@
     StaticGet patchedStaticGet =
         new StaticGet(
             context.code.createValue(
-                TypeLatticeElement.fromDexType(newField.type, maybeNull(), context.appView)),
+                TypeElement.fromDexType(newField.type, maybeNull(), context.appView)),
             newField);
     context.instructions().replaceCurrentInstruction(patchedStaticGet);
 
@@ -213,8 +213,7 @@
   @Override
   public void patch(ApplyStrategy context, InitClass initClass) {
     InitClass pachedInitClass =
-        new InitClass(
-            context.code.createValue(TypeLatticeElement.getInt()), group.getGroupClassType());
+        new InitClass(context.code.createValue(TypeElement.getInt()), group.getGroupClassType());
     context.instructions().replaceCurrentInstruction(pachedInitClass);
   }
 
@@ -229,7 +228,7 @@
     DexType lambda = method.holder;
 
     // Create constant with lambda id.
-    Value lambdaIdValue = context.code.createValue(TypeLatticeElement.getInt());
+    Value lambdaIdValue = context.code.createValue(TypeElement.getInt());
     ConstNumber lambdaId = new ConstNumber(lambdaIdValue, group.lambdaId(lambda));
     lambdaId.setPosition(invoke.getPosition());
     context.instructions().previous();
@@ -250,7 +249,7 @@
     return returnType == context.factory.voidType
         ? null
         : context.code.createValue(
-            TypeLatticeElement.fromDexType(returnType, maybeNull(), context.appView));
+            TypeElement.fromDexType(returnType, maybeNull(), context.appView));
   }
 
   private List<Value> mapInitializerArgs(
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/library/ObjectsMethodOptimizer.java b/src/main/java/com/android/tools/r8/ir/optimize/library/ObjectsMethodOptimizer.java
index 975aca8..f76aeae 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/library/ObjectsMethodOptimizer.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/library/ObjectsMethodOptimizer.java
@@ -42,7 +42,7 @@
   private void optimizeRequireNonNull(
       InstructionListIterator instructionIterator, InvokeMethod invoke, Set<Value> affectedValues) {
     Value inValue = invoke.inValues().get(0);
-    if (inValue.getTypeLattice().isDefinitelyNotNull()) {
+    if (inValue.getType().isDefinitelyNotNull()) {
       Value outValue = invoke.outValue();
       if (outValue != null) {
         affectedValues.addAll(outValue.affectedValues());
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/peepholes/DupDupDupPeephole.java b/src/main/java/com/android/tools/r8/ir/optimize/peepholes/DupDupDupPeephole.java
index 8d84870..b1b2eb54 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/peepholes/DupDupDupPeephole.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/peepholes/DupDupDupPeephole.java
@@ -28,11 +28,11 @@
 public class DupDupDupPeephole implements BasicBlockPeephole {
 
   private final Point dup1Exp =
-      new Point((i) -> i.isDup() && !i.inValues().get(0).getTypeLattice().isWidePrimitive());
+      new Point((i) -> i.isDup() && !i.inValues().get(0).getType().isWidePrimitive());
   private final Point dup2Exp =
-      new Point((i) -> i.isDup() && !i.inValues().get(0).getTypeLattice().isWidePrimitive());
+      new Point((i) -> i.isDup() && !i.inValues().get(0).getType().isWidePrimitive());
   private final Point dup3Exp =
-      new Point((i) -> i.isDup() && !i.inValues().get(0).getTypeLattice().isWidePrimitive());
+      new Point((i) -> i.isDup() && !i.inValues().get(0).getType().isWidePrimitive());
 
   private final PeepholeLayout layout = PeepholeLayout.lookBackward(dup1Exp, dup2Exp, dup3Exp);
 
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/peepholes/MoveLoadUpPeephole.java b/src/main/java/com/android/tools/r8/ir/optimize/peepholes/MoveLoadUpPeephole.java
index 49a74d1..127b144 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/peepholes/MoveLoadUpPeephole.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/peepholes/MoveLoadUpPeephole.java
@@ -4,7 +4,7 @@
 
 package com.android.tools.r8.ir.optimize.peepholes;
 
-import com.android.tools.r8.ir.analysis.type.TypeLatticeElement;
+import com.android.tools.r8.ir.analysis.type.TypeElement;
 import com.android.tools.r8.ir.code.Instruction;
 import com.android.tools.r8.ir.code.InstructionListIterator;
 import com.android.tools.r8.ir.code.Load;
@@ -113,7 +113,7 @@
     Instruction current = it.next();
     if (position != current.getPosition()
         || !current.isConstNumber()
-        || current.outValue().getTypeLattice() != TypeLatticeElement.getInt()
+        || current.outValue().getType() != TypeElement.getInt()
         || current.asConstNumber().getIntValue() < -128
         || current.asConstNumber().getIntValue() > 127
         || !it.hasNext()) {
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/peepholes/StoreSequenceLoadPeephole.java b/src/main/java/com/android/tools/r8/ir/optimize/peepholes/StoreSequenceLoadPeephole.java
index 421bdbf..1ff49d0 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/peepholes/StoreSequenceLoadPeephole.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/peepholes/StoreSequenceLoadPeephole.java
@@ -40,7 +40,7 @@
       new Point(
           (i) -> {
             if (!i.isStore()
-                || i.asStore().src().getTypeLattice().isWidePrimitive()
+                || i.asStore().src().getType().isWidePrimitive()
                 || i.outValue().hasLocalInfo()
                 || i.asStore().outValue().numberOfAllUsers() != 1) {
               return false;
@@ -102,7 +102,7 @@
       lastOut = stackValues[stackValues.length - 1];
     }
 
-    if (lastOut == null || lastOut.getTypeLattice().isWidePrimitive()) {
+    if (lastOut == null || lastOut.getType().isWidePrimitive()) {
       return false;
     }
 
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/staticizer/StaticizingProcessor.java b/src/main/java/com/android/tools/r8/ir/optimize/staticizer/StaticizingProcessor.java
index e0ca9093..f79593b 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/staticizer/StaticizingProcessor.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/staticizer/StaticizingProcessor.java
@@ -14,7 +14,7 @@
 import com.android.tools.r8.graph.DexMethod;
 import com.android.tools.r8.graph.DexProgramClass;
 import com.android.tools.r8.graph.DexType;
-import com.android.tools.r8.ir.analysis.type.TypeLatticeElement;
+import com.android.tools.r8.ir.analysis.type.TypeElement;
 import com.android.tools.r8.ir.code.IRCode;
 import com.android.tools.r8.ir.code.Instruction;
 import com.android.tools.r8.ir.code.InstructionListIterator;
@@ -536,7 +536,7 @@
       Value newValue = null;
       Value outValue = invoke.outValue();
       if (outValue != null) {
-        newValue = code.createValue(outValue.getTypeLattice());
+        newValue = code.createValue(outValue.getType());
         DebugLocalInfo localInfo = outValue.getLocalInfo();
         if (localInfo != null) {
           newValue.setLocalInfo(localInfo);
@@ -563,8 +563,8 @@
           it.replaceCurrentInstruction(
               new StaticGet(
                   code.createValue(
-                      TypeLatticeElement.fromDexType(
-                          field.type, outValue.getTypeLattice().nullability(), appView),
+                      TypeElement.fromDexType(
+                          field.type, outValue.getType().nullability(), appView),
                       outValue.getLocalInfo()),
                   field));
         }
@@ -592,8 +592,8 @@
               returnType.isVoidType() || outValue == null
                   ? null
                   : code.createValue(
-                      TypeLatticeElement.fromDexType(
-                          returnType, outValue.getTypeLattice().nullability(), appView),
+                      TypeElement.fromDexType(
+                          returnType, outValue.getType().nullability(), appView),
                       outValue.getLocalInfo());
           it.replaceCurrentInstruction(new InvokeStatic(newMethod, newOutValue, invoke.inValues()));
         }
@@ -724,7 +724,7 @@
           methodMapping.put(originalMethod, newMethod.method);
         }
       }
-      hostClass.appendDirectMethods(newMethods);
+      hostClass.addDirectMethods(newMethods);
     }
   }
 
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/string/StringBuilderOptimizer.java b/src/main/java/com/android/tools/r8/ir/optimize/string/StringBuilderOptimizer.java
index 6c714c9..20ea5df 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/string/StringBuilderOptimizer.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/string/StringBuilderOptimizer.java
@@ -14,7 +14,7 @@
 import com.android.tools.r8.ir.analysis.escape.EscapeAnalysis;
 import com.android.tools.r8.ir.analysis.escape.EscapeAnalysisConfiguration;
 import com.android.tools.r8.ir.analysis.type.TypeAnalysis;
-import com.android.tools.r8.ir.analysis.type.TypeLatticeElement;
+import com.android.tools.r8.ir.analysis.type.TypeElement;
 import com.android.tools.r8.ir.code.Assume;
 import com.android.tools.r8.ir.code.BasicBlock;
 import com.android.tools.r8.ir.code.BasicBlock.ThrowingInfo;
@@ -294,8 +294,8 @@
       }
       // Make sure builder is neither phi nor coming from outside of the method.
       assert !builder.isPhi() && builder.definition.isNewInstance();
-      assert builder.getTypeLattice().isClassType();
-      DexType builderType = builder.getTypeLattice().asClassTypeLatticeElement().getClassType();
+      assert builder.getType().isClassType();
+      DexType builderType = builder.getType().asClassType().getClassType();
       assert optimizationConfiguration.isBuilderType(builderType);
       EscapeAnalysis escapeAnalysis =
           new EscapeAnalysis(
@@ -442,7 +442,7 @@
         if (number == null) {
           return addition;
         }
-        if (arg.getTypeLattice().isPrimitive()) {
+        if (arg.getType().isPrimitiveType()) {
           if (argType == factory.booleanType) {
             addition = String.valueOf(number.intValue() != 0);
           } else if (argType == factory.byteType) {
@@ -460,7 +460,7 @@
           } else if (argType == factory.doubleType) {
             addition = String.valueOf(number.doubleValue());
           }
-        } else if (arg.getTypeLattice().isNullType()) {
+        } else if (arg.getType().isNullType()) {
           assert number.intValue() == 0;
           addition = "null";
         }
@@ -591,7 +591,7 @@
           if (outValue != null && outValue.isUsed()) {
             Value dummy =
                 code.createValue(
-                    TypeLatticeElement.stringClassType(appView, definitelyNotNull()),
+                    TypeElement.stringClassType(appView, definitelyNotNull()),
                     invoke.getLocalInfo());
             it.replaceCurrentInstruction(
                 new ConstString(dummy, factory.createString(DUMMY), throwingInfo));
@@ -612,8 +612,7 @@
         assert element != null;
         Value stringValue =
             code.createValue(
-                TypeLatticeElement.stringClassType(appView, definitelyNotNull()),
-                invoke.getLocalInfo());
+                TypeElement.stringClassType(appView, definitelyNotNull()), invoke.getLocalInfo());
         affectedValues.addAll(outValue.affectedValues());
         it.replaceCurrentInstruction(
             new ConstString(stringValue, factory.createString(element), throwingInfo));
@@ -815,7 +814,7 @@
     public boolean isBuilderInitWithInitialValue(InvokeMethod invoke) {
       return isBuilderInit(invoke.getInvokedMethod())
           && invoke.inValues().size() == 2
-          && !invoke.inValues().get(1).getTypeLattice().isPrimitive();
+          && !invoke.inValues().get(1).getType().isPrimitiveType();
     }
 
     @Override
@@ -838,13 +837,13 @@
         return false;
       }
       assert invoke.inValues().size() == 2;
-      TypeLatticeElement argType = invoke.inValues().get(1).getTypeLattice();
-      if (!argType.isPrimitive() && !argType.isClassType() && !argType.isNullType()) {
+      TypeElement argType = invoke.inValues().get(1).getType();
+      if (!argType.isPrimitiveType() && !argType.isClassType() && !argType.isNullType()) {
         numberOfBuildersWithUnsupportedArg++;
         return false;
       }
       if (argType.isClassType()) {
-        DexType argClassType = argType.asClassTypeLatticeElement().getClassType();
+        DexType argClassType = argType.asClassType().getClassType();
         return canHandleArgumentType(argClassType);
       }
       return true;
@@ -868,8 +867,8 @@
 
     private StringBuilderOptimizerEscapeAnalysisConfiguration(Value builder) {
       this.builder = builder;
-      assert builder.getTypeLattice().isClassType();
-      builderType = builder.getTypeLattice().asClassTypeLatticeElement().getClassType();
+      assert builder.getType().isClassType();
+      builderType = builder.getType().asClassType().getClassType();
     }
 
     private void logEscapingRoute(boolean legitimate) {
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/string/StringOptimizer.java b/src/main/java/com/android/tools/r8/ir/optimize/string/StringOptimizer.java
index 072f762..a2b8eb5 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/string/StringOptimizer.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/string/StringOptimizer.java
@@ -20,7 +20,7 @@
 import com.android.tools.r8.ir.analysis.escape.EscapeAnalysis;
 import com.android.tools.r8.ir.analysis.escape.EscapeAnalysisConfiguration;
 import com.android.tools.r8.ir.analysis.type.TypeAnalysis;
-import com.android.tools.r8.ir.analysis.type.TypeLatticeElement;
+import com.android.tools.r8.ir.analysis.type.TypeElement;
 import com.android.tools.r8.ir.code.BasicBlock.ThrowingInfo;
 import com.android.tools.r8.ir.code.ConstClass;
 import com.android.tools.r8.ir.code.ConstNumber;
@@ -183,8 +183,7 @@
         String sub = rcvString.substring(beginIndexValue, endIndexValue);
         Value stringValue =
             code.createValue(
-                TypeLatticeElement.stringClassType(appView, definitelyNotNull()),
-                invoke.getLocalInfo());
+                TypeElement.stringClassType(appView, definitelyNotNull()), invoke.getLocalInfo());
         affectedValues.addAll(invoke.outValue().affectedValues());
         it.replaceCurrentInstruction(
             new ConstString(stringValue, factory.createString(sub), throwingInfo));
@@ -201,8 +200,7 @@
             factory.createString(receiver.definition.asConstString().getValue().toString().trim());
         Value newOutValue =
             code.createValue(
-                TypeLatticeElement.stringClassType(appView, definitelyNotNull()),
-                invoke.getLocalInfo());
+                TypeElement.stringClassType(appView, definitelyNotNull()), invoke.getLocalInfo());
         affectedValues.addAll(invoke.outValue().affectedValues());
         it.replaceCurrentInstruction(new ConstString(newOutValue, resultString, throwingInfo));
         numberOfSimplifiedOperations++;
@@ -443,8 +441,7 @@
         affectedValues.addAll(invoke.outValue().affectedValues());
         Value stringValue =
             code.createValue(
-                TypeLatticeElement.stringClassType(appView, definitelyNotNull()),
-                invoke.getLocalInfo());
+                TypeElement.stringClassType(appView, definitelyNotNull()), invoke.getLocalInfo());
         ConstString constString = new ConstString(stringValue, name, throwingInfo);
         it.replaceCurrentInstruction(constString);
         logHistogramOfNames(name);
@@ -525,20 +522,19 @@
           continue;
         }
         Value out = invoke.outValue();
-        TypeLatticeElement inType = in.getTypeLattice();
+        TypeElement inType = in.getType();
         if (out != null && in.isAlwaysNull(appView)) {
           affectedValues.addAll(out.affectedValues());
           Value nullStringValue =
               code.createValue(
-                  TypeLatticeElement.stringClassType(appView, definitelyNotNull()),
-                  invoke.getLocalInfo());
+                  TypeElement.stringClassType(appView, definitelyNotNull()), invoke.getLocalInfo());
           ConstString nullString =
               new ConstString(nullStringValue, factory.createString("null"), throwingInfo);
           it.replaceCurrentInstruction(nullString);
           numberOfSimplifiedConversions++;
         } else if (inType.nullability().isDefinitelyNotNull()
             && inType.isClassType()
-            && inType.asClassTypeLatticeElement().getClassType().equals(factory.stringType)) {
+            && inType.asClassType().getClassType().equals(factory.stringType)) {
           if (out != null) {
             affectedValues.addAll(out.affectedValues());
             removeOrReplaceByDebugLocalWrite(invoke, it, in, out);
@@ -555,10 +551,10 @@
         }
         assert invoke.inValues().size() == 1;
         Value in = invoke.getReceiver();
-        TypeLatticeElement inType = in.getTypeLattice();
+        TypeElement inType = in.getType();
         if (inType.nullability().isDefinitelyNotNull()
             && inType.isClassType()
-            && inType.asClassTypeLatticeElement().getClassType().equals(factory.stringType)) {
+            && inType.asClassType().getClassType().equals(factory.stringType)) {
           Value out = invoke.outValue();
           if (out != null) {
             affectedValues.addAll(out.affectedValues());
diff --git a/src/main/java/com/android/tools/r8/ir/regalloc/LinearScanRegisterAllocator.java b/src/main/java/com/android/tools/r8/ir/regalloc/LinearScanRegisterAllocator.java
index 5dcdbd8..0f6f6c9 100644
--- a/src/main/java/com/android/tools/r8/ir/regalloc/LinearScanRegisterAllocator.java
+++ b/src/main/java/com/android/tools/r8/ir/regalloc/LinearScanRegisterAllocator.java
@@ -12,7 +12,7 @@
 import com.android.tools.r8.errors.CompilationError;
 import com.android.tools.r8.graph.AppView;
 import com.android.tools.r8.graph.DebugLocalInfo;
-import com.android.tools.r8.ir.analysis.type.TypeLatticeElement;
+import com.android.tools.r8.ir.analysis.type.TypeElement;
 import com.android.tools.r8.ir.code.Add;
 import com.android.tools.r8.ir.code.And;
 import com.android.tools.r8.ir.code.ArithmeticBinop;
@@ -2747,7 +2747,7 @@
     return true;
   }
 
-  private Value createValue(TypeLatticeElement typeLattice) {
+  private Value createValue(TypeElement typeLattice) {
     Value value = code.createValue(typeLattice, null);
     value.setNeedsRegister(true);
     return value;
@@ -2800,7 +2800,7 @@
             argument.isLinked() ||
             argument == previous ||
             argument.hasRegisterConstraint()) {
-          newArgument = createValue(argument.getTypeLattice());
+          newArgument = createValue(argument.getType());
           Move move = new Move(newArgument, argument);
           move.setBlock(invoke.getBlock());
           replaceArgument(invoke, i, newArgument);
diff --git a/src/main/java/com/android/tools/r8/ir/regalloc/RegisterMove.java b/src/main/java/com/android/tools/r8/ir/regalloc/RegisterMove.java
index dda59e6..60c7c49 100644
--- a/src/main/java/com/android/tools/r8/ir/regalloc/RegisterMove.java
+++ b/src/main/java/com/android/tools/r8/ir/regalloc/RegisterMove.java
@@ -3,7 +3,7 @@
 // BSD-style license that can be found in the LICENSE file.
 package com.android.tools.r8.ir.regalloc;
 
-import com.android.tools.r8.ir.analysis.type.TypeLatticeElement;
+import com.android.tools.r8.ir.analysis.type.TypeElement;
 import com.android.tools.r8.ir.code.Instruction;
 import java.util.Map;
 import java.util.Set;
@@ -12,19 +12,19 @@
 // for phi moves and they are moves between actual registers represented by their register number.
 public class RegisterMove implements Comparable<RegisterMove> {
 
-  final TypeLatticeElement type;
+  final TypeElement type;
   final int dst;
   final int src;
   final Instruction definition;
 
-  public RegisterMove(int dst, int src, TypeLatticeElement type) {
+  public RegisterMove(int dst, int src, TypeElement type) {
     this.dst = dst;
     this.src = src;
     this.definition = null;
     this.type = type;
   }
 
-  public RegisterMove(int dst, TypeLatticeElement type, Instruction definition) {
+  public RegisterMove(int dst, TypeElement type, Instruction definition) {
     assert definition.isOutConstant();
     this.dst = dst;
     this.src = LiveIntervals.NO_REGISTER;
@@ -82,14 +82,14 @@
     if (dstDiff != 0) {
       return dstDiff;
     }
-    if (type.isPrimitive() != o.type.isPrimitive()) {
-      return Boolean.compare(type.isPrimitive(), o.type.isPrimitive());
+    if (type.isPrimitiveType() != o.type.isPrimitiveType()) {
+      return Boolean.compare(type.isPrimitiveType(), o.type.isPrimitiveType());
     }
     if (type.isWidePrimitive() != o.type.isWidePrimitive()) {
       return Boolean.compare(type.isWidePrimitive(), o.type.isWidePrimitive());
     }
-    if (type.isReference() != o.type.isReference()) {
-      return Boolean.compare(type.isReference(), o.type.isReference());
+    if (type.isReferenceType() != o.type.isReferenceType()) {
+      return Boolean.compare(type.isReferenceType(), o.type.isReferenceType());
     }
     if (definition == null) {
       if (o.definition != null) {
diff --git a/src/main/java/com/android/tools/r8/ir/regalloc/RegisterMoveScheduler.java b/src/main/java/com/android/tools/r8/ir/regalloc/RegisterMoveScheduler.java
index 08afab7..1cb6482 100644
--- a/src/main/java/com/android/tools/r8/ir/regalloc/RegisterMoveScheduler.java
+++ b/src/main/java/com/android/tools/r8/ir/regalloc/RegisterMoveScheduler.java
@@ -4,7 +4,7 @@
 package com.android.tools.r8.ir.regalloc;
 
 import com.android.tools.r8.errors.Unreachable;
-import com.android.tools.r8.ir.analysis.type.TypeLatticeElement;
+import com.android.tools.r8.ir.analysis.type.TypeElement;
 import com.android.tools.r8.ir.code.Argument;
 import com.android.tools.r8.ir.code.ConstInstruction;
 import com.android.tools.r8.ir.code.ConstNumber;
@@ -114,7 +114,7 @@
     return usedTempRegisters;
   }
 
-  private List<RegisterMove> findMovesWithSrc(int src, TypeLatticeElement type) {
+  private List<RegisterMove> findMovesWithSrc(int src, TypeElement type) {
     List<RegisterMove> result = new ArrayList<>();
     assert src != LiveIntervals.NO_REGISTER;
     for (RegisterMove move : moveSet) {
@@ -139,17 +139,14 @@
       if (move.definition.isArgument()) {
         Argument argument = move.definition.asArgument();
         int argumentRegister = argument.outValue().getLiveIntervals().getRegister();
-        Value to =
-            new FixedRegisterValue(argument.outValue().getTypeLattice(), move.dst);
-        Value from =
-            new FixedRegisterValue(argument.outValue().getTypeLattice(), argumentRegister);
+        Value to = new FixedRegisterValue(argument.outValue().getType(), move.dst);
+        Value from = new FixedRegisterValue(argument.outValue().getType(), argumentRegister);
         instruction = new Move(to, from);
       } else {
         assert move.definition.isOutConstant();
         ConstInstruction definition = move.definition.getOutConstantConstInstruction();
         if (definition.isConstNumber()) {
-          Value to =
-              new FixedRegisterValue(move.definition.outValue().getTypeLattice(), move.dst);
+          Value to = new FixedRegisterValue(move.definition.outValue().getType(), move.dst);
           instruction = new ConstNumber(to, definition.asConstNumber().getRawValue());
         } else {
           throw new Unreachable("Unexpected definition");
diff --git a/src/main/java/com/android/tools/r8/ir/regalloc/SpillMove.java b/src/main/java/com/android/tools/r8/ir/regalloc/SpillMove.java
index 84f51f1..b33743e 100644
--- a/src/main/java/com/android/tools/r8/ir/regalloc/SpillMove.java
+++ b/src/main/java/com/android/tools/r8/ir/regalloc/SpillMove.java
@@ -4,7 +4,7 @@
 
 package com.android.tools.r8.ir.regalloc;
 
-import com.android.tools.r8.ir.analysis.type.TypeLatticeElement;
+import com.android.tools.r8.ir.analysis.type.TypeElement;
 
 /**
  * A SpillMove represents either a phi move that transfers an SSA value to the SSA phi value or
@@ -12,11 +12,11 @@
  * of spilling.
  */
 class SpillMove {
-  final TypeLatticeElement type;
+  final TypeElement type;
   LiveIntervals from;
   final LiveIntervals to;
 
-  public SpillMove(TypeLatticeElement type, LiveIntervals to, LiveIntervals from) {
+  public SpillMove(TypeElement type, LiveIntervals to, LiveIntervals from) {
     this.type = type;
     this.to = to;
     this.from = from;
diff --git a/src/main/java/com/android/tools/r8/ir/regalloc/SpillMoveSet.java b/src/main/java/com/android/tools/r8/ir/regalloc/SpillMoveSet.java
index a5506d9..1c13ae6 100644
--- a/src/main/java/com/android/tools/r8/ir/regalloc/SpillMoveSet.java
+++ b/src/main/java/com/android/tools/r8/ir/regalloc/SpillMoveSet.java
@@ -6,7 +6,7 @@
 
 import com.android.tools.r8.graph.AppView;
 import com.android.tools.r8.ir.analysis.type.Nullability;
-import com.android.tools.r8.ir.analysis.type.TypeLatticeElement;
+import com.android.tools.r8.ir.analysis.type.TypeElement;
 import com.android.tools.r8.ir.code.BasicBlock;
 import com.android.tools.r8.ir.code.IRCode;
 import com.android.tools.r8.ir.code.Instruction;
@@ -36,7 +36,7 @@
   // The register allocator generating moves.
   private final LinearScanRegisterAllocator allocator;
   // Reference to the object type.
-  private final TypeLatticeElement objectType;
+  private final TypeElement objectType;
   // Mapping from instruction numbers to the block that start with that instruction if any.
   private final Map<Integer, BasicBlock> blockStartMap = new HashMap<>();
   // The number of temporary registers used for parallel moves when scheduling the moves.
@@ -45,7 +45,7 @@
   public SpillMoveSet(LinearScanRegisterAllocator allocator, IRCode code, AppView<?> appView) {
     this.allocator = allocator;
     this.code = code;
-    this.objectType = TypeLatticeElement.objectClassType(appView, Nullability.maybeNull());
+    this.objectType = TypeElement.objectClassType(appView, Nullability.maybeNull());
     for (BasicBlock block : code.blocks) {
       blockStartMap.put(block.entry().getNumber(), block);
     }
@@ -197,12 +197,12 @@
     return usedTempRegisters;
   }
 
-  private TypeLatticeElement moveTypeForIntervals(LiveIntervals to, LiveIntervals from) {
-    TypeLatticeElement toType = to.getValue().getTypeLattice();
-    TypeLatticeElement fromType = from.getValue().getTypeLattice();
-    if (toType.isReference() || fromType.isReference()) {
-      assert fromType.isReference() || fromType.isSinglePrimitive();
-      assert toType.isReference() || toType.isSinglePrimitive();
+  private TypeElement moveTypeForIntervals(LiveIntervals to, LiveIntervals from) {
+    TypeElement toType = to.getValue().getType();
+    TypeElement fromType = from.getValue().getType();
+    if (toType.isReferenceType() || fromType.isReferenceType()) {
+      assert fromType.isReferenceType() || fromType.isSinglePrimitive();
+      assert toType.isReferenceType() || toType.isSinglePrimitive();
       return objectType;
     }
     assert toType == fromType;
diff --git a/src/main/java/com/android/tools/r8/jar/CfApplicationWriter.java b/src/main/java/com/android/tools/r8/jar/CfApplicationWriter.java
index 7822316..688d59b 100644
--- a/src/main/java/com/android/tools/r8/jar/CfApplicationWriter.java
+++ b/src/main/java/com/android/tools/r8/jar/CfApplicationWriter.java
@@ -26,13 +26,8 @@
 import com.android.tools.r8.graph.DexValue.DexValueAnnotation;
 import com.android.tools.r8.graph.DexValue.DexValueArray;
 import com.android.tools.r8.graph.DexValue.DexValueEnum;
-import com.android.tools.r8.graph.DexValue.DexValueField;
 import com.android.tools.r8.graph.DexValue.DexValueInt;
-import com.android.tools.r8.graph.DexValue.DexValueMethod;
-import com.android.tools.r8.graph.DexValue.DexValueMethodHandle;
-import com.android.tools.r8.graph.DexValue.DexValueMethodType;
 import com.android.tools.r8.graph.DexValue.DexValueString;
-import com.android.tools.r8.graph.DexValue.DexValueType;
 import com.android.tools.r8.graph.GraphLense;
 import com.android.tools.r8.graph.InnerClassAttribute;
 import com.android.tools.r8.graph.NestMemberClassAttribute;
@@ -234,29 +229,26 @@
   }
 
   private String getSignature(DexAnnotationSet annotations) {
-    DexValueArray value =
-        (DexValueArray)
-            getSystemAnnotationValue(annotations, application.dexItemFactory.annotationSignature);
+    DexValue value =
+        getSystemAnnotationValue(annotations, application.dexItemFactory.annotationSignature);
     if (value == null) {
       return null;
     }
     // Signature has already been minified by ClassNameMinifier.renameTypesInGenericSignatures().
-    DexValue[] parts = value.getValues();
     StringBuilder res = new StringBuilder();
-    for (DexValue part : parts) {
-      res.append(((DexValueString) part).getValue().toString());
+    for (DexValue part : value.asDexValueArray().getValues()) {
+      res.append(part.asDexValueString().getValue().toString());
     }
     return res.toString();
   }
 
   private ImmutableMap<DexString, DexValue> getAnnotationDefaults(DexAnnotationSet annotations) {
-    DexValueAnnotation value =
-        (DexValueAnnotation)
-            getSystemAnnotationValue(annotations, application.dexItemFactory.annotationDefault);
+    DexValue value =
+        getSystemAnnotationValue(annotations, application.dexItemFactory.annotationDefault);
     if (value == null) {
       return ImmutableMap.of();
     }
-    DexEncodedAnnotation annotation = value.value;
+    DexEncodedAnnotation annotation = value.asDexValueAnnotation().value;
     Builder<DexString, DexValue> builder = ImmutableMap.builder();
     for (DexAnnotationElement element : annotation.elements) {
       builder.put(element.name, element.value);
@@ -265,16 +257,15 @@
   }
 
   private String[] getExceptions(DexAnnotationSet annotations) {
-    DexValueArray value =
-        (DexValueArray)
-            getSystemAnnotationValue(annotations, application.dexItemFactory.annotationThrows);
+    DexValue value =
+        getSystemAnnotationValue(annotations, application.dexItemFactory.annotationThrows);
     if (value == null) {
       return null;
     }
-    DexValue[] values = value.getValues();
+    DexValue[] values = value.asDexValueArray().getValues();
     String[] res = new String[values.length];
     for (int i = 0; i < values.length; i++) {
-      res[i] = namingLens.lookupInternalName(((DexValueType) values[i]).value);
+      res[i] = namingLens.lookupInternalName(values[i].asDexValueType().value);
     }
     return res;
   }
@@ -331,13 +322,13 @@
         assert annotation.annotation.elements.length == 2;
         assert annotation.annotation.elements[0].name.toString().equals("names");
         assert annotation.annotation.elements[1].name.toString().equals("accessFlags");
-        DexValueArray names = (DexValueArray) annotation.annotation.elements[0].value;
-        DexValueArray accessFlags = (DexValueArray) annotation.annotation.elements[1].value;
+        DexValueArray names = annotation.annotation.elements[0].value.asDexValueArray();
+        DexValueArray accessFlags = annotation.annotation.elements[1].value.asDexValueArray();
         assert names != null && accessFlags != null;
         assert names.getValues().length == accessFlags.getValues().length;
         for (int i = 0; i < names.getValues().length; i++) {
-          DexValueString name = (DexValueString) names.getValues()[i];
-          DexValueInt access = (DexValueInt) accessFlags.getValues()[i];
+          DexValueString name = names.getValues()[i].asDexValueString();
+          DexValueInt access = accessFlags.getValues()[i].asDexValueInt();
           visitor.visitParameter(name.value.toString(), access.value);
         }
       }
@@ -392,44 +383,64 @@
   }
 
   private void writeAnnotationElement(AnnotationVisitor visitor, String name, DexValue value) {
-    if (value instanceof DexValueAnnotation) {
-      DexValueAnnotation valueAnnotation = (DexValueAnnotation) value;
-      AnnotationVisitor innerVisitor =
-          visitor.visitAnnotation(
-              name, namingLens.lookupDescriptor(valueAnnotation.value.type).toString());
-      if (innerVisitor != null) {
-        writeAnnotation(innerVisitor, valueAnnotation.value);
-        innerVisitor.visitEnd();
-      }
-    } else if (value instanceof DexValueArray) {
-      DexValue[] values = ((DexValueArray) value).getValues();
-      AnnotationVisitor innerVisitor = visitor.visitArray(name);
-      if (innerVisitor != null) {
-        for (DexValue arrayValue : values) {
-          writeAnnotationElement(innerVisitor, null, arrayValue);
+    switch (value.getValueKind()) {
+      case ANNOTATION:
+        {
+          DexValueAnnotation valueAnnotation = value.asDexValueAnnotation();
+          AnnotationVisitor innerVisitor =
+              visitor.visitAnnotation(
+                  name, namingLens.lookupDescriptor(valueAnnotation.value.type).toString());
+          if (innerVisitor != null) {
+            writeAnnotation(innerVisitor, valueAnnotation.value);
+            innerVisitor.visitEnd();
+          }
         }
-        innerVisitor.visitEnd();
-      }
-    } else if (value instanceof DexValueEnum) {
-      DexValueEnum en = (DexValueEnum) value;
-      visitor.visitEnum(
-          name, namingLens.lookupDescriptor(en.value.type).toString(), en.value.name.toString());
-    } else if (value instanceof DexValueField) {
-      throw new Unreachable("writeAnnotationElement of DexValueField");
-    } else if (value instanceof DexValueMethod) {
-      throw new Unreachable("writeAnnotationElement of DexValueMethod");
-    } else if (value instanceof DexValueMethodHandle) {
-      throw new Unreachable("writeAnnotationElement of DexValueMethodHandle");
-    } else if (value instanceof DexValueMethodType) {
-      throw new Unreachable("writeAnnotationElement of DexValueMethodType");
-    } else if (value instanceof DexValueString) {
-      DexValueString str = (DexValueString) value;
-      visitor.visit(name, str.getValue().toString());
-    } else if (value instanceof DexValueType) {
-      DexValueType ty = (DexValueType) value;
-      visitor.visit(name, Type.getType(namingLens.lookupDescriptor(ty.value).toString()));
-    } else {
-      visitor.visit(name, value.getBoxedValue());
+        break;
+
+      case ARRAY:
+        {
+          DexValue[] values = value.asDexValueArray().getValues();
+          AnnotationVisitor innerVisitor = visitor.visitArray(name);
+          if (innerVisitor != null) {
+            for (DexValue elementValue : values) {
+              writeAnnotationElement(innerVisitor, null, elementValue);
+            }
+            innerVisitor.visitEnd();
+          }
+        }
+        break;
+
+      case ENUM:
+        DexValueEnum en = value.asDexValueEnum();
+        visitor.visitEnum(
+            name, namingLens.lookupDescriptor(en.value.type).toString(), en.value.name.toString());
+        break;
+
+      case FIELD:
+        throw new Unreachable("writeAnnotationElement of DexValueField");
+
+      case METHOD:
+        throw new Unreachable("writeAnnotationElement of DexValueMethod");
+
+      case METHOD_HANDLE:
+        throw new Unreachable("writeAnnotationElement of DexValueMethodHandle");
+
+      case METHOD_TYPE:
+        throw new Unreachable("writeAnnotationElement of DexValueMethodType");
+
+      case STRING:
+        visitor.visit(name, value.asDexValueString().getValue().toString());
+        break;
+
+      case TYPE:
+        visitor.visit(
+            name,
+            Type.getType(namingLens.lookupDescriptor(value.asDexValueType().value).toString()));
+        break;
+
+      default:
+        visitor.visit(name, value.getBoxedValue());
+        break;
     }
   }
 
diff --git a/src/main/java/com/android/tools/r8/kotlin/KotlinClass.java b/src/main/java/com/android/tools/r8/kotlin/KotlinClass.java
index a37a42b..136947b 100644
--- a/src/main/java/com/android/tools/r8/kotlin/KotlinClass.java
+++ b/src/main/java/com/android/tools/r8/kotlin/KotlinClass.java
@@ -22,6 +22,7 @@
 import com.android.tools.r8.graph.InnerClassAttribute;
 import com.android.tools.r8.naming.NamingLens;
 import com.android.tools.r8.shaking.AppInfoWithLiveness;
+import com.android.tools.r8.utils.StringUtils;
 import java.util.List;
 import kotlinx.metadata.KmClass;
 import kotlinx.metadata.KmConstructor;
@@ -187,7 +188,13 @@
   }
 
   @Override
-  public String toString() {
-    return clazz.toString() + ": " + kmClass.toString();
+  public String toString(String indent) {
+    StringBuilder sb = new StringBuilder(indent);
+    sb.append("Metadata.Class {");
+    sb.append(StringUtils.LINE_SEPARATOR);
+    sb.append(kmDeclarationContainerToString(indent + INDENT));
+    sb.append(indent);
+    sb.append("}");
+    return sb.toString();
   }
 }
diff --git a/src/main/java/com/android/tools/r8/kotlin/KotlinClassFacade.java b/src/main/java/com/android/tools/r8/kotlin/KotlinClassFacade.java
index cc1cd14..627ac9d 100644
--- a/src/main/java/com/android/tools/r8/kotlin/KotlinClassFacade.java
+++ b/src/main/java/com/android/tools/r8/kotlin/KotlinClassFacade.java
@@ -81,8 +81,7 @@
   }
 
   @Override
-  public String toString() {
-    return clazz.toString()
-        + ": MultiFileClassFacade(" + StringUtils.join(partClassNames, ", ") + ")";
+  public String toString(String indent) {
+    return indent + "MultiFileClassFacade(" + StringUtils.join(partClassNames, ", ") + ")";
   }
 }
diff --git a/src/main/java/com/android/tools/r8/kotlin/KotlinClassMetadataReader.java b/src/main/java/com/android/tools/r8/kotlin/KotlinClassMetadataReader.java
index fec6a34..e49d99d 100644
--- a/src/main/java/com/android/tools/r8/kotlin/KotlinClassMetadataReader.java
+++ b/src/main/java/com/android/tools/r8/kotlin/KotlinClassMetadataReader.java
@@ -11,7 +11,6 @@
 import com.android.tools.r8.graph.DexString;
 import com.android.tools.r8.graph.DexValue;
 import com.android.tools.r8.graph.DexValue.DexValueArray;
-import com.android.tools.r8.graph.DexValue.DexValueString;
 import com.android.tools.r8.utils.StringDiagnostic;
 import java.util.IdentityHashMap;
 import java.util.Map;
@@ -19,7 +18,7 @@
 import kotlinx.metadata.jvm.KotlinClassHeader;
 import kotlinx.metadata.jvm.KotlinClassMetadata;
 
-final class KotlinClassMetadataReader {
+public final class KotlinClassMetadataReader {
 
   static KotlinInfo getKotlinInfo(
       Kotlin kotlin,
@@ -73,7 +72,7 @@
     return KotlinClassMetadata.read(header);
   }
 
-  private static KotlinInfo createKotlinInfo(
+  public static KotlinInfo createKotlinInfo(
       Kotlin kotlin, DexClass clazz, DexEncodedAnnotation metadataAnnotation) {
     KotlinClassMetadata kMetadata = toKotlinClassMetadata(kotlin, metadataAnnotation);
 
@@ -96,10 +95,10 @@
   }
 
   private static int[] getUnboxedIntArray(DexValue v, String elementName) {
-    if (!(v instanceof DexValueArray)) {
+    if (!v.isDexValueArray()) {
       throw new MetadataError("invalid '" + elementName + "' value: " + v.toSourceString());
     }
-    DexValueArray intArrayValue = (DexValueArray) v;
+    DexValueArray intArrayValue = v.asDexValueArray();
     DexValue[] values = intArrayValue.getValues();
     int[] result = new int [values.length];
     for (int i = 0; i < values.length; i++) {
@@ -109,10 +108,10 @@
   }
 
   private static String[] getUnboxedStringArray(DexValue v, String elementName) {
-    if (!(v instanceof DexValueArray)) {
+    if (!v.isDexValueArray()) {
       throw new MetadataError("invalid '" + elementName + "' value: " + v.toSourceString());
     }
-    DexValueArray stringArrayValue = (DexValueArray) v;
+    DexValueArray stringArrayValue = v.asDexValueArray();
     DexValue[] values = stringArrayValue.getValues();
     String[] result = new String [values.length];
     for (int i = 0; i < values.length; i++) {
@@ -122,10 +121,10 @@
   }
 
   private static String getUnboxedString(DexValue v, String elementName) {
-    if (!(v instanceof DexValueString)) {
+    if (!v.isDexValueString()) {
       throw new MetadataError("invalid '" + elementName + "' value: " + v.toSourceString());
     }
-    return ((DexValueString) v).getValue().toString();
+    return v.asDexValueString().getValue().toString();
   }
 
   private static class MetadataError extends RuntimeException {
diff --git a/src/main/java/com/android/tools/r8/kotlin/KotlinClassPart.java b/src/main/java/com/android/tools/r8/kotlin/KotlinClassPart.java
index 16223bb..e67cd05 100644
--- a/src/main/java/com/android/tools/r8/kotlin/KotlinClassPart.java
+++ b/src/main/java/com/android/tools/r8/kotlin/KotlinClassPart.java
@@ -12,6 +12,7 @@
 import com.android.tools.r8.naming.NamingLens;
 import com.android.tools.r8.shaking.AppInfoWithLiveness;
 import com.android.tools.r8.utils.DescriptorUtils;
+import com.android.tools.r8.utils.StringUtils;
 import kotlinx.metadata.KmPackage;
 import kotlinx.metadata.jvm.KotlinClassHeader;
 import kotlinx.metadata.jvm.KotlinClassMetadata;
@@ -82,8 +83,14 @@
   }
 
   @Override
-  public String toString() {
-    return clazz.toString() + ": " + kmPackage.toString()
-        + ": MultiFileClassPart(" + facadeClassName + ")";
+  public String toString(String indent) {
+    StringBuilder sb = new StringBuilder(indent);
+    sb.append("Metadata.MultiFileClassPart {");
+    sb.append(StringUtils.LINE_SEPARATOR);
+    sb.append(kmDeclarationContainerToString(indent + INDENT));
+    appendKeyValue(indent + INDENT, "facadeClassName", facadeClassName, sb);
+    sb.append(indent);
+    sb.append("}");
+    return sb.toString();
   }
 }
diff --git a/src/main/java/com/android/tools/r8/kotlin/KotlinFile.java b/src/main/java/com/android/tools/r8/kotlin/KotlinFile.java
index 27c9230..08fc8ef 100644
--- a/src/main/java/com/android/tools/r8/kotlin/KotlinFile.java
+++ b/src/main/java/com/android/tools/r8/kotlin/KotlinFile.java
@@ -64,7 +64,13 @@
   }
 
   @Override
-  public String toString() {
-    return clazz.toString() + ": " + kmPackage.toString();
+  public String toString(String indent) {
+    StringBuilder sb = new StringBuilder(indent);
+    sb.append("Metadata.MultiFileClassPart {\n");
+    sb.append(kmDeclarationContainerToString(indent + INDENT));
+    appendKeyValue(indent + INDENT, "package", kmPackage.toString(), sb);
+    sb.append(indent);
+    sb.append("}");
+    return sb.toString();
   }
 }
diff --git a/src/main/java/com/android/tools/r8/kotlin/KotlinInfo.java b/src/main/java/com/android/tools/r8/kotlin/KotlinInfo.java
index 7c53cba..967cf05 100644
--- a/src/main/java/com/android/tools/r8/kotlin/KotlinInfo.java
+++ b/src/main/java/com/android/tools/r8/kotlin/KotlinInfo.java
@@ -15,13 +15,20 @@
 import com.android.tools.r8.kotlin.KotlinMetadataSynthesizer.KmPropertyGroup;
 import com.android.tools.r8.naming.NamingLens;
 import com.android.tools.r8.shaking.AppInfoWithLiveness;
+import com.android.tools.r8.utils.DescriptorUtils;
+import com.android.tools.r8.utils.StringUtils;
 import java.util.HashMap;
 import java.util.List;
 import java.util.Map;
+import java.util.function.BiFunction;
 import java.util.function.Predicate;
+import java.util.stream.Collectors;
 import kotlinx.metadata.KmDeclarationContainer;
 import kotlinx.metadata.KmFunction;
 import kotlinx.metadata.KmProperty;
+import kotlinx.metadata.KmType;
+import kotlinx.metadata.KmTypeAlias;
+import kotlinx.metadata.KmTypeParameter;
 import kotlinx.metadata.jvm.KotlinClassHeader;
 import kotlinx.metadata.jvm.KotlinClassMetadata;
 
@@ -202,4 +209,131 @@
       }
     }
   }
+
+  public abstract String toString(String indent);
+
+  String kmDeclarationContainerToString(String indent) {
+    StringBuilder sb = new StringBuilder();
+    KmDeclarationContainer declarations = getDeclarations();
+    appendKmSection(indent, "functions", declarations.getFunctions(), this::kmFunctionToString, sb);
+    appendKmSection(
+        indent, "properties", declarations.getProperties(), this::kmPropertyToString, sb);
+    appendKmSection(
+        indent, "typeAliases", declarations.getTypeAliases(), this::kmTypeAliasToString, sb);
+    return sb.toString();
+  }
+
+  final String INDENT = "  ";
+
+  private <T> void appendKmSection(
+      String indent,
+      String header,
+      List<T> items,
+      BiFunction<String, T, String> stringify,
+      StringBuilder sb) {
+    if (items.size() > 0) {
+      sb.append(indent);
+      sb.append(header);
+      sb.append(": [");
+      sb.append(StringUtils.LINE_SEPARATOR);
+    }
+    for (T item : items) {
+      sb.append(stringify.apply(indent + INDENT, item));
+      sb.append(",");
+      sb.append(StringUtils.LINE_SEPARATOR);
+    }
+    if (items.size() > 0) {
+      sb.append(indent);
+      sb.append("]");
+      sb.append(StringUtils.LINE_SEPARATOR);
+    }
+  }
+
+  private String kmFunctionToString(String indent, KmFunction function) {
+    assert function != null;
+    StringBuilder sb = new StringBuilder();
+    sb.append(indent);
+    sb.append("KmFunction {");
+    sb.append(StringUtils.LINE_SEPARATOR);
+    String newIndent = indent + INDENT;
+    KmType receiverParameterType = function.getReceiverParameterType();
+    appendKeyValue(
+        newIndent,
+        "receiverParameterType",
+        receiverParameterType == null ? "null" : kmTypeToString(receiverParameterType),
+        sb);
+    appendKeyValue(newIndent, "returnType", kmTypeToString(function.returnType), sb);
+    appendKeyValue(newIndent, "name", function.getName(), sb);
+    // TODO(b/148581822): Print flags, generic signature etc.
+    sb.append(indent);
+    sb.append("}");
+    return sb.toString();
+  }
+
+  private String kmPropertyToString(String indent, KmProperty property) {
+    // TODO(b/148581822): Add information.
+    return indent + "KmProperty { " + property + "}";
+  }
+
+  private String kmTypeAliasToString(String indent, KmTypeAlias alias) {
+    assert alias != null;
+    StringBuilder sb = new StringBuilder(indent);
+    sb.append("KmTypeAlias {");
+    sb.append(StringUtils.LINE_SEPARATOR);
+    String newIndent = indent + INDENT;
+    appendKeyValue(newIndent, "name", alias.getName(), sb);
+    if (!alias.getTypeParameters().isEmpty()) {
+      appendKeyValue(
+          newIndent,
+          "typeParameters",
+          alias.getTypeParameters().stream()
+              .map(KmTypeParameter::getName)
+              .collect(Collectors.joining(",")),
+          sb);
+    }
+    appendType(newIndent, "underlyingType", alias.underlyingType, sb);
+    appendType(newIndent, "expandedType", alias.expandedType, sb);
+    // TODO(b/151783973): Extend with annotations.
+    sb.append(indent);
+    sb.append("}");
+    return sb.toString();
+  }
+
+  void appendType(String indent, String key, KmType kmType, StringBuilder sb) {
+    sb.append(indent);
+    sb.append(key);
+    sb.append(" {");
+    sb.append(StringUtils.LINE_SEPARATOR);
+    String newIndent = indent + INDENT;
+    appendKeyValue(newIndent, "classifier", kmType.classifier.toString(), sb);
+    if (!kmType.getArguments().isEmpty()) {
+      appendKeyValue(
+          newIndent,
+          "arguments",
+          kmType.getArguments().stream()
+              .map(arg -> arg.getType().classifier.toString())
+              .collect(Collectors.joining(",")),
+          sb);
+    }
+    sb.append(indent);
+    sb.append("}");
+    sb.append(StringUtils.LINE_SEPARATOR);
+  }
+
+  void appendKeyValue(String indent, String key, String value, StringBuilder sb) {
+    sb.append(indent);
+    sb.append(key);
+    sb.append(": ");
+    sb.append(value);
+    sb.append(StringUtils.LINE_SEPARATOR);
+  }
+
+  private String kmTypeToString(KmType type) {
+    return DescriptorUtils.getDescriptorFromKmType(type);
+  }
+
+  @Override
+  public String toString() {
+    return toString("");
+  }
 }
diff --git a/src/main/java/com/android/tools/r8/kotlin/KotlinSyntheticClass.java b/src/main/java/com/android/tools/r8/kotlin/KotlinSyntheticClass.java
index 1bb17ea..8c4ccaf 100644
--- a/src/main/java/com/android/tools/r8/kotlin/KotlinSyntheticClass.java
+++ b/src/main/java/com/android/tools/r8/kotlin/KotlinSyntheticClass.java
@@ -119,7 +119,7 @@
   }
 
   @Override
-  public String toString() {
-    return clazz.toString() + ": " + metadata.toString();
+  public String toString(String indent) {
+    return indent + "Metadata.SyntheticClass { function: " + metadata.toString() + "}";
   }
 }
diff --git a/src/main/java/com/android/tools/r8/naming/IdentifierMinifier.java b/src/main/java/com/android/tools/r8/naming/IdentifierMinifier.java
index 72d5558..a0b1328 100644
--- a/src/main/java/com/android/tools/r8/naming/IdentifierMinifier.java
+++ b/src/main/java/com/android/tools/r8/naming/IdentifierMinifier.java
@@ -69,8 +69,8 @@
   private void adaptClassStringsInStaticField(DexEncodedField encodedField) {
     assert encodedField.accessFlags.isStatic();
     DexValue staticValue = encodedField.getStaticValue();
-    if (staticValue instanceof DexValueString) {
-      DexString original = ((DexValueString) staticValue).getValue();
+    if (staticValue.isDexValueString()) {
+      DexString original = staticValue.asDexValueString().getValue();
       encodedField.setStaticValue(new DexValueString(getRenamedStringLiteral(original)));
     }
   }
diff --git a/src/main/java/com/android/tools/r8/naming/IdentifierNameStringMarker.java b/src/main/java/com/android/tools/r8/naming/IdentifierNameStringMarker.java
index 47d6431..d5873a9 100644
--- a/src/main/java/com/android/tools/r8/naming/IdentifierNameStringMarker.java
+++ b/src/main/java/com/android/tools/r8/naming/IdentifierNameStringMarker.java
@@ -18,7 +18,6 @@
 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.DexValue;
 import com.android.tools.r8.graph.DexValue.DexItemBasedValueString;
 import com.android.tools.r8.graph.DexValue.DexValueString;
 import com.android.tools.r8.ir.code.BasicBlock;
@@ -81,11 +80,11 @@
     if (!identifierNameStrings.containsKey(encodedField.field)) {
       return;
     }
-    DexValue staticValue = encodedField.getStaticValue();
-    if (!(staticValue instanceof DexValueString)) {
+    DexValueString staticValue = encodedField.getStaticValue().asDexValueString();
+    if (staticValue == null) {
       return;
     }
-    DexString original = ((DexValueString) staticValue).getValue();
+    DexString original = staticValue.getValue();
     DexReference itemBasedString = inferMemberOrTypeFromNameString(appView, original);
     if (itemBasedString != null) {
       encodedField.setStaticValue(
@@ -162,7 +161,7 @@
     assert iterator.peekPrevious() == fieldPut;
     iterator.previous();
     // Prepare $decoupled just before $fieldPut
-    Value newIn = code.createValue(in.getTypeLattice(), in.getLocalInfo());
+    Value newIn = code.createValue(in.getType(), in.getLocalInfo());
     DexItemBasedConstString decoupled =
         new DexItemBasedConstString(
             newIn, itemBasedString, ClassNameComputationInfo.none(), throwingInfo);
@@ -228,7 +227,7 @@
       }
 
       // Prepare $decoupled just before $invoke
-      Value newIn = code.createValue(in.getTypeLattice(), in.getLocalInfo());
+      Value newIn = code.createValue(in.getType(), in.getLocalInfo());
       DexItemBasedConstString decoupled =
           new DexItemBasedConstString(
               newIn, itemBasedString, ClassNameComputationInfo.none(), throwingInfo);
@@ -295,7 +294,7 @@
         assert iterator.peekPrevious() == invoke;
         iterator.previous();
         // Prepare $decoupled just before $invoke
-        Value newIn = code.createValue(in.getTypeLattice(), in.getLocalInfo());
+        Value newIn = code.createValue(in.getType(), in.getLocalInfo());
         DexItemBasedConstString decoupled =
             new DexItemBasedConstString(
                 newIn, itemBasedString, ClassNameComputationInfo.none(), throwingInfo);
diff --git a/src/main/java/com/android/tools/r8/shaking/AppInfoWithLiveness.java b/src/main/java/com/android/tools/r8/shaking/AppInfoWithLiveness.java
index 7df2fbc..fdd1274 100644
--- a/src/main/java/com/android/tools/r8/shaking/AppInfoWithLiveness.java
+++ b/src/main/java/com/android/tools/r8/shaking/AppInfoWithLiveness.java
@@ -37,7 +37,7 @@
 import com.android.tools.r8.graph.PresortedComparable;
 import com.android.tools.r8.graph.ResolutionResult;
 import com.android.tools.r8.graph.ResolutionResult.SingleResolutionResult;
-import com.android.tools.r8.ir.analysis.type.ClassTypeLatticeElement;
+import com.android.tools.r8.ir.analysis.type.ClassTypeElement;
 import com.android.tools.r8.ir.code.Invoke.Type;
 import com.android.tools.r8.ir.desugar.LambdaDescriptor;
 import com.android.tools.r8.utils.CollectionUtils;
@@ -1149,7 +1149,7 @@
       boolean isInterface,
       LibraryModeledPredicate modeledPredicate,
       DexType refinedReceiverType,
-      ClassTypeLatticeElement receiverLowerBoundType) {
+      ClassTypeElement receiverLowerBoundType) {
     assert checkIfObsolete();
     assert refinedReceiverType != null;
 
@@ -1239,7 +1239,7 @@
 
   private DexEncodedMethod getMethodTargetFromExactRuntimeInformation(
       DexType refinedReceiverType,
-      ClassTypeLatticeElement receiverLowerBoundType,
+      ClassTypeElement receiverLowerBoundType,
       SingleResolutionResult resolution,
       DexClass refinedReceiverClass) {
     // If the lower-bound on the receiver type is the same as the upper-bound, then we have exact
diff --git a/src/main/java/com/android/tools/r8/shaking/Enqueuer.java b/src/main/java/com/android/tools/r8/shaking/Enqueuer.java
index 2bc230c..20dde1b 100644
--- a/src/main/java/com/android/tools/r8/shaking/Enqueuer.java
+++ b/src/main/java/com/android/tools/r8/shaking/Enqueuer.java
@@ -23,6 +23,7 @@
 import com.android.tools.r8.dex.IndexedItemCollection;
 import com.android.tools.r8.errors.Unreachable;
 import com.android.tools.r8.experimental.graphinfo.GraphConsumer;
+import com.android.tools.r8.graph.AppInfoWithClassHierarchy;
 import com.android.tools.r8.graph.AppInfoWithSubtyping;
 import com.android.tools.r8.graph.AppView;
 import com.android.tools.r8.graph.CfCode;
@@ -93,7 +94,9 @@
 import com.android.tools.r8.utils.DequeUtils;
 import com.android.tools.r8.utils.InternalOptions;
 import com.android.tools.r8.utils.InternalOptions.DesugarState;
+import com.android.tools.r8.utils.MethodSignatureEquivalence;
 import com.android.tools.r8.utils.OptionalBool;
+import com.android.tools.r8.utils.Pair;
 import com.android.tools.r8.utils.SetUtils;
 import com.android.tools.r8.utils.StringDiagnostic;
 import com.android.tools.r8.utils.Timing;
@@ -169,8 +172,10 @@
 
   private Set<EnqueuerAnalysis> analyses = Sets.newIdentityHashSet();
   private Set<EnqueuerInvokeAnalysis> invokeAnalyses = Sets.newIdentityHashSet();
-  private final AppInfoWithSubtyping appInfo;
-  private final AppView<? extends AppInfoWithSubtyping> appView;
+
+  // Don't hold a direct pointer to app info (use appView).
+  private AppInfoWithSubtyping appInfo;
+  private final AppView<AppInfoWithSubtyping> appView;
   private final InternalOptions options;
   private RootSet rootSet;
   private ProguardClassFilter dontWarnPatterns;
@@ -271,16 +276,8 @@
    */
   private final Set<DexEncodedMethod> pendingReflectiveUses = Sets.newLinkedHashSet();
 
-  /**
-   * Mapping of types to the reachable method resolutions.
-   *
-   * <p>Primary map key is the initial/static/symbolic type specified by a live invoke.
-   *
-   * <p>The keys of the reachable resolutions are again the static reference, thus methodKey.holder
-   * will be the same as the classKey.type. The value of the reachable resolution is the resolution
-   * target.
-   */
-  private final Map<DexProgramClass, Map<DexMethod, ProgramMethod>> reachableVirtualResolutions =
+  /** Mapping of types to the methods reachable at that type. */
+  private final Map<DexProgramClass, Set<DexMethod>> reachableVirtualTargets =
       new IdentityHashMap<>();
 
   /**
@@ -327,7 +324,8 @@
 
   private final LambdaRewriter lambdaRewriter;
   private final DesugaredLibraryConversionWrapperAnalysis desugaredLibraryWrapperAnalysis;
-  private final Map<DexType, LambdaClass> lambdaClasses = new IdentityHashMap<>();
+  private final Map<DexType, Pair<LambdaClass, DexEncodedMethod>> lambdaClasses =
+      new IdentityHashMap<>();
   private final Map<DexEncodedMethod, Map<DexCallSite, LambdaClass>> lambdaCallSites =
       new IdentityHashMap<>();
   private final Set<DexProgramClass> classesWithSerializableLambdas = Sets.newIdentityHashSet();
@@ -339,7 +337,7 @@
     assert appView.appServices() != null;
     InternalOptions options = appView.options();
     this.appInfo = appView.appInfo();
-    this.appView = appView;
+    this.appView = appView.withSubtyping();
     this.forceProguardCompatibility = options.forceProguardCompatibility;
     this.graphReporter = new GraphReporter(appView, keptGraphConsumer);
     this.mode = mode;
@@ -375,6 +373,10 @@
     }
   }
 
+  private AppInfoWithClassHierarchy appInfo() {
+    return appView.appInfo();
+  }
+
   public Mode getMode() {
     return mode;
   }
@@ -656,7 +658,7 @@
       if (code != null) {
         LambdaClass lambdaClass =
             lambdaRewriter.getOrCreateLambdaClass(descriptor, contextMethod.method.holder);
-        lambdaClasses.put(lambdaClass.type, lambdaClass);
+        lambdaClasses.put(lambdaClass.type, new Pair<>(lambdaClass, contextMethod));
         lambdaCallSites
             .computeIfAbsent(contextMethod, k -> new IdentityHashMap<>())
             .put(callSite, lambdaClass);
@@ -1855,7 +1857,7 @@
    */
   private void transitionMethodsForInstantiatedObject(
       InstantiatedObject instantiation, DexClass clazz, List<DexType> interfaces) {
-    ScopedDexMethodSet seen = new ScopedDexMethodSet();
+    Set<Wrapper<DexMethod>> seen = new HashSet<>();
     WorkList<DexType> worklist = WorkList.newIdentityWorkList();
     worklist.addIfNotSeen(interfaces);
     // First we lookup and mark all targets on the instantiated class for each reachable method in
@@ -1889,22 +1891,27 @@
     }
   }
 
-  private Map<DexMethod, ProgramMethod> getReachableVirtualResolutions(DexProgramClass clazz) {
-    return reachableVirtualResolutions.getOrDefault(clazz, Collections.emptyMap());
+  private Set<DexMethod> getReachableVirtualTargets(DexProgramClass clazz) {
+    return reachableVirtualTargets.getOrDefault(clazz, Collections.emptySet());
   }
 
   private void markProgramMethodOverridesAsLive(
       InstantiatedObject instantiation,
       DexProgramClass superClass,
-      ScopedDexMethodSet seenMethods) {
-    Map<DexMethod, ProgramMethod> reachableResolution = getReachableVirtualResolutions(superClass);
-    reachableResolution.forEach(
-        (method, resolution) -> {
-          assert method.holder == superClass.type;
-          if (seenMethods.addMethod(resolution.getMethod())) {
-            markLiveOverrides(instantiation, superClass, resolution);
-          }
-        });
+      Set<Wrapper<DexMethod>> seenMethods) {
+    for (DexMethod method : getReachableVirtualTargets(superClass)) {
+      assert method.holder == superClass.type;
+      if (seenMethods.add(MethodSignatureEquivalence.get().wrap(method))) {
+        SingleResolutionResult resolution =
+            appInfo.resolveMethod(superClass, method).asSingleResolution();
+        assert resolution != null;
+        assert resolution.getResolvedHolder().isProgramClass();
+        if (resolution != null && resolution.getResolvedHolder().isProgramClass()) {
+          markLiveOverrides(
+              instantiation, superClass, resolution.getResolutionPair().asProgramMethod());
+        }
+      }
+    }
   }
 
   private void markLiveOverrides(
@@ -2281,18 +2288,6 @@
       return;
     }
 
-    ProgramMethod resolutionMethod = getReachableVirtualResolutions(holder).get(method);
-
-    // If the method has already been marked, just report the new reason for the resolved target.
-    if (resolutionMethod != null) {
-      graphReporter.registerMethod(resolutionMethod.getMethod(), reason);
-      return;
-    }
-
-    if (Log.ENABLED) {
-      Log.verbose(getClass(), "Marking virtual method `%s` as reachable.", method);
-    }
-
     SingleResolutionResult resolution = resolveMethod(method, reason, interfaceInvoke);
     if (resolution == null) {
       return;
@@ -2306,6 +2301,16 @@
       return;
     }
 
+    // If the method has already been marked, just report the new reason for the resolved target.
+    if (getReachableVirtualTargets(holder).contains(method)) {
+      graphReporter.registerMethod(resolution.getResolvedMethod(), reason);
+      return;
+    }
+
+    if (Log.ENABLED) {
+      Log.verbose(getClass(), "Marking virtual method `%s` as reachable.", method);
+    }
+
     // We have to mark the resolution targeted, even if it does not become live, we
     // need at least an abstract version of it so that it can be targeted.
     DexProgramClass resolvedHolder = resolution.getResolvedHolder().asProgramClass();
@@ -2326,9 +2331,7 @@
     }
 
     // The method resolved and is accessible, so currently live overrides become live.
-    reachableVirtualResolutions
-        .computeIfAbsent(holder, k -> new IdentityHashMap<>())
-        .put(method, new ProgramMethod(resolvedHolder, resolvedMethod));
+    reachableVirtualTargets.computeIfAbsent(holder, k -> Sets.newIdentityHashSet()).add(method);
 
     resolution
         .lookupVirtualDispatchTargets(
@@ -2504,6 +2507,163 @@
     return appInfoWithLiveness;
   }
 
+  private static class SyntheticAdditions {
+
+    Map<DexType, Pair<DexProgramClass, DexEncodedMethod>> syntheticInstantiations =
+        new IdentityHashMap<>();
+
+    Map<DexMethod, ProgramMethod> liveMethods = new IdentityHashMap<>();
+
+    Map<DexType, DexClasspathClass> syntheticClasspathClasses = new IdentityHashMap<>();
+
+    // Subset of live methods that need to be pinned.
+    Set<DexMethod> pinnedMethods = Sets.newIdentityHashSet();
+
+    // Subset of synthesized classes that need to be added to the main-dex file.
+    Set<DexType> mainDexTypes = Sets.newIdentityHashSet();
+
+    boolean isEmpty() {
+      boolean empty = syntheticInstantiations.isEmpty() && liveMethods.isEmpty();
+      assert !empty || (pinnedMethods.isEmpty() && mainDexTypes.isEmpty());
+      return empty;
+    }
+
+    void addInstantiatedClass(
+        DexProgramClass clazz, DexEncodedMethod context, boolean isMainDexClass) {
+      assert !syntheticInstantiations.containsKey(clazz.type);
+      syntheticInstantiations.put(clazz.type, new Pair<>(clazz, context));
+      if (isMainDexClass) {
+        mainDexTypes.add(clazz.type);
+      }
+    }
+
+    void addClasspathClass(DexClasspathClass clazz) {
+      DexClasspathClass old = syntheticClasspathClasses.put(clazz.type, clazz);
+      assert old == null;
+    }
+
+    void addLiveMethod(ProgramMethod method) {
+      DexMethod signature = method.getMethod().method;
+      assert !liveMethods.containsKey(signature);
+      liveMethods.put(signature, method);
+    }
+
+    void addLiveAndPinnedMethod(ProgramMethod method) {
+      addLiveMethod(method);
+      pinnedMethods.add(method.getMethod().method);
+    }
+
+    void amendApplication(Builder appBuilder) {
+      assert !isEmpty();
+      for (Pair<DexProgramClass, DexEncodedMethod> clazzAndContext :
+          syntheticInstantiations.values()) {
+        appBuilder.addProgramClass(clazzAndContext.getFirst());
+      }
+      appBuilder.addClasspathClasses(syntheticClasspathClasses.values());
+      appBuilder.addToMainDexList(mainDexTypes);
+    }
+
+    void enqueueWorkItems(Enqueuer enqueuer) {
+      assert !isEmpty();
+      assert enqueuer.mode.isInitialTreeShaking();
+      // All synthetic additions are initial tree shaking only. No need to track keep reasons.
+      KeepReasonWitness fakeReason = enqueuer.graphReporter.fakeReportShouldNotBeUsed();
+
+      enqueuer.pinnedItems.addAll(pinnedMethods);
+      for (Pair<DexProgramClass, DexEncodedMethod> clazzAndContext :
+          syntheticInstantiations.values()) {
+        enqueuer.workList.enqueueMarkInstantiatedAction(
+            clazzAndContext.getFirst(),
+            clazzAndContext.getSecond(),
+            InstantiationReason.SYNTHESIZED_CLASS,
+            fakeReason);
+      }
+      for (ProgramMethod liveMethod : liveMethods.values()) {
+        assert !enqueuer.targetedMethods.contains(liveMethod.getMethod());
+        DexProgramClass holder = liveMethod.getHolder();
+        DexEncodedMethod method = liveMethod.getMethod();
+        enqueuer.markMethodAsTargeted(holder, method, fakeReason);
+        enqueuer.enqueueMarkMethodLiveAction(holder, method, fakeReason);
+      }
+    }
+  }
+
+  private void synthesize() {
+    if (!mode.isInitialTreeShaking()) {
+      return;
+    }
+    // First part of synthesis is to create and register all reachable synthetic additions.
+    // In particular these additions are order independent, i.e., it does not matter which are
+    // registered first and no dependencies may exist among them.
+    SyntheticAdditions additions = new SyntheticAdditions();
+    synthesizeInterfaceMethodBridges(additions);
+    synthesizeLambdas(additions);
+    synthesizeLibraryConversionWrappers(additions);
+    if (additions.isEmpty()) {
+      return;
+    }
+
+    // Now all additions are computed, the application is atomically extended with those additions.
+    Builder appBuilder = appInfo.app().asDirect().builder();
+    additions.amendApplication(appBuilder);
+    appInfo = new AppInfoWithSubtyping(appBuilder.build());
+    appView.setAppInfo(appInfo);
+
+    // Finally once all synthesized items "exist" it is now safe to continue tracing. The new work
+    // items are enqueued and the fixed point will continue once this subroutine returns.
+    additions.enqueueWorkItems(this);
+  }
+
+  private void synthesizeInterfaceMethodBridges(SyntheticAdditions additions) {
+    for (ProgramMethod bridge : syntheticInterfaceMethodBridges.values()) {
+      DexProgramClass holder = bridge.getHolder();
+      DexEncodedMethod method = bridge.getMethod();
+      holder.addVirtualMethod(method);
+      additions.addLiveAndPinnedMethod(bridge);
+    }
+    syntheticInterfaceMethodBridges.clear();
+  }
+
+  private void synthesizeLambdas(SyntheticAdditions additions) {
+    if (lambdaRewriter == null || lambdaClasses.isEmpty()) {
+      assert lambdaCallSites.isEmpty();
+      assert classesWithSerializableLambdas.isEmpty();
+      return;
+    }
+    for (Pair<LambdaClass, DexEncodedMethod> lambdaClassAndContext : lambdaClasses.values()) {
+      // Add all desugared classes to the application, main-dex list, and mark them instantiated.
+      LambdaClass lambdaClass = lambdaClassAndContext.getFirst();
+      DexEncodedMethod context = lambdaClassAndContext.getSecond();
+      DexProgramClass programClass = lambdaClass.getOrCreateLambdaClass();
+      additions.addInstantiatedClass(programClass, context, lambdaClass.addToMainDexList.get());
+
+      // Mark all methods on the desugared lambda classes as live.
+      for (DexEncodedMethod method : programClass.methods()) {
+        additions.addLiveMethod(new ProgramMethod(programClass, method));
+      }
+
+      // Ensure accessors if needed and mark them live too.
+      DexEncodedMethod accessor = lambdaClass.target.ensureAccessibilityIfNeeded(false);
+      if (accessor != null) {
+        DexProgramClass clazz = getProgramClassOrNull(accessor.method.holder);
+        additions.addLiveMethod(new ProgramMethod(clazz, accessor));
+      }
+    }
+
+    // Rewrite all of the invoke-dynamic instructions to lambda class instantiations.
+    lambdaCallSites.forEach(this::rewriteLambdaCallSites);
+
+    // Remove all '$deserializeLambda$' methods which are not supported by desugaring.
+    for (DexProgramClass clazz : classesWithSerializableLambdas) {
+      clazz.removeDirectMethod(appView.dexItemFactory().deserializeLambdaMethod);
+    }
+
+    // Clear state before next fixed point iteration.
+    lambdaClasses.clear();
+    lambdaCallSites.clear();
+    classesWithSerializableLambdas.clear();
+  }
+
   private void finalizeLibraryMethodOverrideInformation() {
     for (DexProgramClass liveType : liveTypes.getItems()) {
       for (DexEncodedMethod method : liveType.virtualMethods()) {
@@ -2530,16 +2690,6 @@
         (field, info) -> field != info.getField() || info == MISSING_FIELD_ACCESS_INFO);
     assert fieldAccessInfoCollection.verifyMappingIsOneToOne();
 
-    for (ProgramMethod bridge : syntheticInterfaceMethodBridges.values()) {
-      DexProgramClass holder = bridge.getHolder();
-      DexEncodedMethod method = bridge.getMethod();
-      appView.appInfo().invalidateTypeCacheFor(holder.type);
-      holder.appendVirtualMethod(method);
-      targetedMethods.add(method, graphReporter.fakeReportShouldNotBeUsed());
-      liveMethods.add(holder, method, graphReporter.fakeReportShouldNotBeUsed());
-      pinnedItems.add(method.method);
-    }
-
     // Ensure references from various root set collections.
     rootSet
         .noSideEffects
@@ -2573,8 +2723,6 @@
     appBuilder.replaceClasspathClasses(classpathClasses);
     // Can't replace the program classes at this point as they are needed in tree pruning.
     // Post process the app to add synthetic content.
-    postProcessLambdaDesugaring(appBuilder);
-    postProcessLibraryConversionWrappers(appBuilder);
     DirectMappedDexApplication app = appBuilder.build();
 
     AppInfoWithLiveness appInfoWithLiveness =
@@ -2670,7 +2818,7 @@
     }
   }
 
-  private void postProcessLibraryConversionWrappers(DirectMappedDexApplication.Builder appBuilder) {
+  private void synthesizeLibraryConversionWrappers(SyntheticAdditions additions) {
     if (desugaredLibraryWrapperAnalysis == null) {
       return;
     }
@@ -2679,95 +2827,24 @@
     List<DexEncodedMethod> callbacks = desugaredLibraryWrapperAnalysis.generateCallbackMethods();
     for (DexEncodedMethod callback : callbacks) {
       DexProgramClass clazz = getProgramClassOrNull(callback.method.holder);
-      targetedMethods.add(callback, graphReporter.fakeReportShouldNotBeUsed());
-      liveMethods.add(clazz, callback, graphReporter.fakeReportShouldNotBeUsed());
+      additions.addLiveMethod(new ProgramMethod(clazz, callback));
     }
 
     // Generate the wrappers.
-    Set<DexProgramClass> wrappers = desugaredLibraryWrapperAnalysis.generateWrappers();
+    List<DexProgramClass> wrappers = desugaredLibraryWrapperAnalysis.generateWrappers();
     for (DexProgramClass wrapper : wrappers) {
-      appBuilder.addProgramClass(wrapper);
-      liveTypes.add(wrapper, graphReporter.fakeReportShouldNotBeUsed());
-      objectAllocationInfoCollection.recordDirectAllocationSite(
-          wrapper,
-          null,
-          InstantiationReason.SYNTHESIZED_CLASS,
-          graphReporter.fakeReportShouldNotBeUsed(),
-          appInfo);
+      additions.addInstantiatedClass(wrapper, null, false);
       // Mark all methods on the wrapper as live and targeted.
       for (DexEncodedMethod method : wrapper.methods()) {
-        targetedMethods.add(method, graphReporter.fakeReportShouldNotBeUsed());
-        liveMethods.add(wrapper, method, graphReporter.fakeReportShouldNotBeUsed());
+        additions.addLiveMethod(new ProgramMethod(wrapper, method));
       }
-      // Register wrapper unique field reads and unique write.
-      assert wrapper.instanceFields().size() == 1;
-      DexField field = wrapper.instanceFields().get(0).field;
-      FieldAccessInfoImpl info = new FieldAccessInfoImpl(field);
-      fieldAccessInfoCollection.extend(field, info);
-      desugaredLibraryWrapperAnalysis
-          .registerWrite(wrapper, writeContext -> info.recordWrite(field, writeContext))
-          .registerReads(wrapper, readContext -> info.recordRead(field, readContext));
     }
 
     // Add all vivified types as classpath classes.
     // They will be available at runtime in the desugared library dex file.
-    List<DexClasspathClass> mockVivifiedClasses =
-        desugaredLibraryWrapperAnalysis.generateWrappersSuperTypeMock();
-    appBuilder.addClasspathClasses(mockVivifiedClasses);
-  }
-
-  private void postProcessLambdaDesugaring(DirectMappedDexApplication.Builder appBuilder) {
-    if (lambdaRewriter == null || lambdaClasses.isEmpty()) {
-      return;
-    }
-    for (LambdaClass lambdaClass : lambdaClasses.values()) {
-      // Add all desugared classes to the application, main-dex list, and mark them instantiated.
-      DexProgramClass programClass = lambdaClass.getOrCreateLambdaClass();
-      appBuilder.addProgramClass(programClass);
-      if (lambdaClass.addToMainDexList.get()) {
-        appBuilder.addToMainDexList(Collections.singletonList(programClass.type));
-      }
-      liveTypes.add(programClass, graphReporter.fakeReportShouldNotBeUsed());
-      objectAllocationInfoCollection.recordDirectAllocationSite(
-          programClass,
-          null,
-          InstantiationReason.SYNTHESIZED_CLASS,
-          graphReporter.fakeReportShouldNotBeUsed(),
-          appInfo);
-
-      // Register all of the field writes in the lambda constructors.
-      // This is needed to ensure that the initializers can be optimized.
-      Map<DexEncodedField, Set<DexEncodedMethod>> writes =
-          lambdaRewriter.getWritesWithContexts(programClass);
-      writes.forEach(
-          (field, contexts) -> {
-            for (DexEncodedMethod context : contexts) {
-              registerFieldWrite(field.field, context);
-            }
-          });
-
-      // Mark all methods on the desugared lambda classes as live.
-      for (DexEncodedMethod method : programClass.methods()) {
-        targetedMethods.add(method, graphReporter.fakeReportShouldNotBeUsed());
-        liveMethods.add(programClass, method, graphReporter.fakeReportShouldNotBeUsed());
-      }
-
-      // Ensure accessors if needed and mark them live too.
-      DexEncodedMethod accessor = lambdaClass.target.ensureAccessibilityIfNeeded(false);
-      if (accessor != null) {
-        DexProgramClass clazz = getProgramClassOrNull(accessor.method.holder);
-        targetedMethods.add(accessor, graphReporter.fakeReportShouldNotBeUsed());
-        liveMethods.add(clazz, accessor, graphReporter.fakeReportShouldNotBeUsed());
-      }
-    }
-
-    // Rewrite all of the invoke-dynamic instructions to lambda class instantiations.
-    lambdaCallSites.forEach(this::rewriteLambdaCallSites);
-
-    // Remove all '$deserializeLambda$' methods which are not supported by desugaring.
-    for (DexProgramClass clazz : classesWithSerializableLambdas) {
-      clazz.removeDirectMethod(appView.dexItemFactory().deserializeLambdaMethod);
-    }
+    desugaredLibraryWrapperAnalysis
+        .generateWrappersSuperTypeMock(wrappers)
+        .forEach(additions::addClasspathClass);
   }
 
   private void rewriteLambdaCallSites(
@@ -2893,7 +2970,7 @@
 
         // Notify each analysis that a fixpoint has been reached, and give each analysis an
         // opportunity to add items to the worklist.
-        analyses.forEach(analysis -> analysis.notifyFixpoint(this, workList));
+        analyses.forEach(analysis -> analysis.notifyFixpoint(this, workList, timing));
         if (!workList.isEmpty()) {
           continue;
         }
@@ -2905,6 +2982,11 @@
           continue;
         }
 
+        synthesize();
+        if (!workList.isEmpty()) {
+          continue;
+        }
+
         // Reached the fixpoint.
         break;
       }
diff --git a/src/main/java/com/android/tools/r8/shaking/StaticClassMerger.java b/src/main/java/com/android/tools/r8/shaking/StaticClassMerger.java
index b89678e..5031d82 100644
--- a/src/main/java/com/android/tools/r8/shaking/StaticClassMerger.java
+++ b/src/main/java/com/android/tools/r8/shaking/StaticClassMerger.java
@@ -471,9 +471,9 @@
     numberOfMergedClasses++;
 
     // Move members from source to target.
-    targetClass.appendDirectMethods(
+    targetClass.addDirectMethods(
         mergeMethods(sourceClass.directMethods(), targetClass.directMethods(), targetClass));
-    targetClass.appendVirtualMethods(
+    targetClass.addVirtualMethods(
         mergeMethods(sourceClass.virtualMethods(), targetClass.virtualMethods(), targetClass));
     targetClass.setStaticFields(
         mergeFields(sourceClass.staticFields(), targetClass.staticFields(), targetClass));
diff --git a/src/main/java/com/android/tools/r8/shaking/VerticalClassMerger.java b/src/main/java/com/android/tools/r8/shaking/VerticalClassMerger.java
index ef0ce63..92fa339 100644
--- a/src/main/java/com/android/tools/r8/shaking/VerticalClassMerger.java
+++ b/src/main/java/com/android/tools/r8/shaking/VerticalClassMerger.java
@@ -50,7 +50,6 @@
 import com.android.tools.r8.utils.Timing;
 import com.google.common.base.Equivalence;
 import com.google.common.base.Equivalence.Wrapper;
-import com.google.common.collect.ImmutableSet;
 import com.google.common.collect.Iterables;
 import com.google.common.collect.Sets;
 import com.google.common.collect.Streams;
@@ -238,10 +237,21 @@
   }
 
   private void initializeMergeCandidates(Iterable<DexProgramClass> classes) {
-    for (DexProgramClass clazz : classes) {
-      if (isMergeCandidate(clazz, pinnedTypes) && isStillMergeCandidate(clazz)) {
-        mergeCandidates.add(clazz);
+    for (DexProgramClass sourceClass : classes) {
+      DexProgramClass targetClass = appInfo.getSingleDirectSubtype(sourceClass);
+      if (targetClass == null) {
+        continue;
       }
+      if (!isMergeCandidate(sourceClass, targetClass, pinnedTypes)) {
+        continue;
+      }
+      if (!isStillMergeCandidate(sourceClass, targetClass)) {
+        continue;
+      }
+      if (mergeMayLeadToIllegalAccesses(sourceClass, targetClass)) {
+        continue;
+      }
+      mergeCandidates.add(sourceClass);
     }
   }
 
@@ -313,54 +323,41 @@
 
     DexClass clazz = appInfo.definitionFor(baseType);
     if (clazz != null && clazz.isProgramClass()) {
-      boolean changed = pinnedTypes.add(baseType);
-
-      if (Log.ENABLED) {
-        if (changed && isMergeCandidate(clazz.asProgramClass(), ImmutableSet.of())) {
-          reason.printLogMessageForClass(clazz);
-        }
-      }
+      pinnedTypes.add(baseType);
     }
   }
 
   // Returns true if [clazz] is a merge candidate. Note that the result of the checks in this
   // method do not change in response to any class merges.
-  private boolean isMergeCandidate(DexProgramClass clazz, Set<DexType> pinnedTypes) {
-    if (appInfo.getObjectAllocationInfoCollection().isInstantiatedDirectly(clazz)
-        || appInfo.instantiatedLambdas.contains(clazz.type)
-        || appInfo.isPinned(clazz.type)
-        || pinnedTypes.contains(clazz.type)
-        || appInfo.neverMerge.contains(clazz.type)) {
+  private boolean isMergeCandidate(
+      DexProgramClass sourceClass, DexProgramClass targetClass, Set<DexType> pinnedTypes) {
+    assert targetClass != null;
+
+    if (appInfo.getObjectAllocationInfoCollection().isInstantiatedDirectly(sourceClass)
+        || appInfo.instantiatedLambdas.contains(sourceClass.type)
+        || appInfo.isPinned(sourceClass.type)
+        || pinnedTypes.contains(sourceClass.type)
+        || appInfo.neverMerge.contains(sourceClass.type)) {
       return false;
     }
 
-    assert Streams.stream(Iterables.concat(clazz.fields(), clazz.methods()))
+    assert Streams.stream(Iterables.concat(sourceClass.fields(), sourceClass.methods()))
         .map(DexEncodedMember::toReference)
         .noneMatch(appInfo::isPinned);
 
-    if (appView.options().featureSplitConfiguration != null &&
-        appView.options().featureSplitConfiguration.isInFeature(clazz)) {
+    if (appView.options().featureSplitConfiguration != null
+        && appView.options().featureSplitConfiguration.isInFeature(sourceClass)) {
       // TODO(b/141452765): Allow class merging between classes in features.
       return false;
     }
-
-    // Note that the property "singleSubtype == null" cannot change during merging, since we visit
-    // classes in a top-down order.
-    DexProgramClass singleSubtype = appInfo.getSingleDirectSubtype(clazz);
-    if (singleSubtype == null) {
-      // TODO(christofferqa): Even if [clazz] has multiple subtypes, we could still merge it into
-      // its subclass if [clazz] is not live. This should only be done, though, if it does not
-      // lead to members being duplicated.
-      return false;
-    }
-    if (appView.appServices().allServiceTypes().contains(clazz.type)
-        && appInfo.isPinned(singleSubtype.type)) {
+    if (appView.appServices().allServiceTypes().contains(sourceClass.type)
+        && appInfo.isPinned(targetClass.type)) {
       if (Log.ENABLED) {
-        AbortReason.SERVICE_LOADER.printLogMessageForClass(clazz);
+        AbortReason.SERVICE_LOADER.printLogMessageForClass(sourceClass);
       }
       return false;
     }
-    if (singleSubtype.isSerializable(appView) && !appInfo.isSerializable(clazz.type)) {
+    if (targetClass.isSerializable(appView) && !appInfo.isSerializable(sourceClass.type)) {
       // https://docs.oracle.com/javase/8/docs/platform/serialization/spec/serial-arch.html
       //   1.10 The Serializable Interface
       //   ...
@@ -369,32 +366,32 @@
       //     * Have access to the no-arg constructor of its first non-serializable superclass
       return false;
     }
-    for (DexEncodedMethod method : clazz.directMethods()) {
+    for (DexEncodedMethod method : sourceClass.directMethods()) {
       // We rename constructors to private methods and mark them to be forced-inlined, so we have to
       // check if we can force-inline all constructors.
       if (method.isInstanceInitializer()) {
-        AbortReason reason = disallowInlining(method, singleSubtype.type);
+        AbortReason reason = disallowInlining(method, targetClass.type);
         if (reason != null) {
           // Cannot guarantee that markForceInline() will work.
           if (Log.ENABLED) {
-            reason.printLogMessageForClass(clazz);
+            reason.printLogMessageForClass(sourceClass);
           }
           return false;
         }
       }
     }
-    if (clazz.getEnclosingMethod() != null || !clazz.getInnerClasses().isEmpty()) {
+    if (sourceClass.getEnclosingMethod() != null || !sourceClass.getInnerClasses().isEmpty()) {
       // TODO(b/147504070): Consider merging of enclosing-method and inner-class attributes.
       if (Log.ENABLED) {
-        AbortReason.UNSUPPORTED_ATTRIBUTES.printLogMessageForClass(clazz);
+        AbortReason.UNSUPPORTED_ATTRIBUTES.printLogMessageForClass(sourceClass);
       }
       return false;
     }
     // We abort class merging when merging across nests or from a nest to non-nest.
     // Without nest this checks null == null.
-    if (singleSubtype.getNestHost() != clazz.getNestHost()) {
+    if (targetClass.getNestHost() != sourceClass.getNestHost()) {
       if (Log.ENABLED) {
-        AbortReason.MERGE_ACROSS_NESTS.printLogMessageForClass(clazz);
+        AbortReason.MERGE_ACROSS_NESTS.printLogMessageForClass(sourceClass);
       }
       return false;
     }
@@ -404,66 +401,61 @@
   // Returns true if [clazz] is a merge candidate. Note that the result of the checks in this
   // method may change in response to class merges. Therefore, this method should always be called
   // before merging [clazz] into its subtype.
-  private boolean isStillMergeCandidate(DexProgramClass clazz) {
-    assert isMergeCandidate(clazz, pinnedTypes);
-    if (mergedClassesInverse.containsKey(clazz.type)) {
+  private boolean isStillMergeCandidate(DexProgramClass sourceClass, DexProgramClass targetClass) {
+    assert isMergeCandidate(sourceClass, targetClass, pinnedTypes);
+    if (mergedClassesInverse.containsKey(sourceClass.type)) {
       // Do not allow merging the resulting class into its subclass.
       // TODO(christofferqa): Get rid of this limitation.
       if (Log.ENABLED) {
-        AbortReason.ALREADY_MERGED.printLogMessageForClass(clazz);
+        AbortReason.ALREADY_MERGED.printLogMessageForClass(sourceClass);
       }
       return false;
     }
-    DexProgramClass targetClass = appInfo.getSingleDirectSubtype(clazz);
     // For interface types, this is more complicated, see:
     // https://docs.oracle.com/javase/specs/jvms/se9/html/jvms-5.html#jvms-5.5
     // We basically can't move the clinit, since it is not called when implementing classes have
     // their clinit called - except when the interface has a default method.
-    if ((clazz.hasClassInitializer() && targetClass.hasClassInitializer())
+    if ((sourceClass.hasClassInitializer() && targetClass.hasClassInitializer())
         || targetClass.classInitializationMayHaveSideEffects(
-            appView, type -> type == clazz.type, Sets.newIdentityHashSet())
-        || (clazz.isInterface() && clazz.classInitializationMayHaveSideEffects(appView))) {
+            appView, type -> type == sourceClass.type, Sets.newIdentityHashSet())
+        || (sourceClass.isInterface()
+            && sourceClass.classInitializationMayHaveSideEffects(appView))) {
       // TODO(herhut): Handle class initializers.
       if (Log.ENABLED) {
-        AbortReason.STATIC_INITIALIZERS.printLogMessageForClass(clazz);
+        AbortReason.STATIC_INITIALIZERS.printLogMessageForClass(sourceClass);
       }
       return false;
     }
     boolean sourceCanBeSynchronizedOn =
-        appView.appInfo().isLockCandidate(clazz.type) || clazz.hasStaticSynchronizedMethods();
+        appView.appInfo().isLockCandidate(sourceClass.type)
+            || sourceClass.hasStaticSynchronizedMethods();
     boolean targetCanBeSynchronizedOn =
         appView.appInfo().isLockCandidate(targetClass.type)
             || targetClass.hasStaticSynchronizedMethods();
     if (sourceCanBeSynchronizedOn && targetCanBeSynchronizedOn) {
       if (Log.ENABLED) {
-        AbortReason.SOURCE_AND_TARGET_LOCK_CANDIDATES.printLogMessageForClass(clazz);
+        AbortReason.SOURCE_AND_TARGET_LOCK_CANDIDATES.printLogMessageForClass(sourceClass);
       }
       return false;
     }
     if (targetClass.getEnclosingMethod() != null || !targetClass.getInnerClasses().isEmpty()) {
       // TODO(b/147504070): Consider merging of enclosing-method and inner-class attributes.
       if (Log.ENABLED) {
-        AbortReason.UNSUPPORTED_ATTRIBUTES.printLogMessageForClass(clazz);
+        AbortReason.UNSUPPORTED_ATTRIBUTES.printLogMessageForClass(sourceClass);
       }
       return false;
     }
-    if (mergeMayLeadToIllegalAccesses(clazz, targetClass)) {
+    if (methodResolutionMayChange(sourceClass, targetClass)) {
       if (Log.ENABLED) {
-        AbortReason.ILLEGAL_ACCESS.printLogMessageForClass(clazz);
-      }
-      return false;
-    }
-    if (methodResolutionMayChange(clazz, targetClass)) {
-      if (Log.ENABLED) {
-        AbortReason.RESOLUTION_FOR_METHODS_MAY_CHANGE.printLogMessageForClass(clazz);
+        AbortReason.RESOLUTION_FOR_METHODS_MAY_CHANGE.printLogMessageForClass(sourceClass);
       }
       return false;
     }
     // Field resolution first considers the direct interfaces of [targetClass] before it proceeds
     // to the super class.
-    if (fieldResolutionMayChange(clazz, targetClass)) {
+    if (fieldResolutionMayChange(sourceClass, targetClass)) {
       if (Log.ENABLED) {
-        AbortReason.RESOLUTION_FOR_FIELDS_MAY_CHANGE.printLogMessageForClass(clazz);
+        AbortReason.RESOLUTION_FOR_FIELDS_MAY_CHANGE.printLogMessageForClass(sourceClass);
       }
       return false;
     }
@@ -782,20 +774,19 @@
       return;
     }
 
-    assert isMergeCandidate(clazz, pinnedTypes);
-
     DexProgramClass targetClass = appInfo.getSingleDirectSubtype(clazz);
+    assert isMergeCandidate(clazz, targetClass, pinnedTypes);
     assert !mergedClasses.containsKey(targetClass.type);
 
     boolean clazzOrTargetClassHasBeenMerged =
         mergedClassesInverse.containsKey(clazz.type)
             || mergedClassesInverse.containsKey(targetClass.type);
     if (clazzOrTargetClassHasBeenMerged) {
-      if (!isStillMergeCandidate(clazz)) {
+      if (!isStillMergeCandidate(clazz, targetClass)) {
         return;
       }
     } else {
-      assert isStillMergeCandidate(clazz);
+      assert isStillMergeCandidate(clazz, targetClass);
     }
 
     // Guard against the case where we have two methods that may get the same signature
@@ -1085,8 +1076,8 @@
               ? DexTypeList.empty()
               : new DexTypeList(interfaces.toArray(DexType.EMPTY_ARRAY));
       // Step 2: replace fields and methods.
-      target.appendDirectMethods(directMethods.values());
-      target.appendVirtualMethods(virtualMethods.values());
+      target.addDirectMethods(directMethods.values());
+      target.addVirtualMethods(virtualMethods.values());
       target.setInstanceFields(mergedInstanceFields);
       target.setStaticFields(mergedStaticFields);
       target.forEachField(feedback::markFieldCannotBeKept);
diff --git a/src/main/java/com/android/tools/r8/utils/InternalOptions.java b/src/main/java/com/android/tools/r8/utils/InternalOptions.java
index 37b38e9..f9f955c 100644
--- a/src/main/java/com/android/tools/r8/utils/InternalOptions.java
+++ b/src/main/java/com/android/tools/r8/utils/InternalOptions.java
@@ -287,6 +287,8 @@
 
   public boolean enablePcDebugInfoOutput = false;
 
+  public String synthesizedClassPrefix = "";
+
   // Number of threads to use while processing the dex files.
   public int threadCount = DETERMINISTIC_DEBUGGING ? 1 : ThreadUtils.NOT_SPECIFIED;
   // Print smali disassembly.
@@ -360,6 +362,10 @@
     return desugaredLibraryConfiguration.isLibraryCompilation();
   }
 
+  public boolean shouldBackportMethods() {
+    return !hasConsumer() || isGeneratingDex();
+  }
+
   public boolean shouldKeepStackMapTable() {
     return isDesugaredLibraryCompilation()
         || getProguardConfiguration().getKeepAttributes().stackMapTable;
@@ -1091,6 +1097,7 @@
     public PrintStream whyAreYouNotInliningConsumer = System.out;
     public boolean trackDesugaredAPIConversions =
         System.getProperty("com.android.tools.r8.trackDesugaredAPIConversions") != null;
+    public boolean forceLibBackportsInL8CfToCf = false;
 
     // TODO(b/144781417): This is disabled by default as some test apps appear to have such classes.
     public boolean allowNonAbstractClassesWithAbstractMethods = true;
@@ -1143,6 +1150,12 @@
     enableNameReflectionOptimization = false;
   }
 
+  @VisibleForTesting
+  public void enableEnumUnboxing() {
+    assert !enableEnumUnboxing;
+    enableEnumUnboxing = true;
+  }
+
   // TODO(b/69963623): Remove this once enabled.
   @VisibleForTesting
   public void enablePropagationOfConstantsAtCallSites() {
diff --git a/src/test/java/com/android/tools/r8/R8RunExamplesAndroidOTest.java b/src/test/java/com/android/tools/r8/R8RunExamplesAndroidOTest.java
index a642002..b8256fe 100644
--- a/src/test/java/com/android/tools/r8/R8RunExamplesAndroidOTest.java
+++ b/src/test/java/com/android/tools/r8/R8RunExamplesAndroidOTest.java
@@ -102,7 +102,7 @@
         .withOptionConsumer(opts -> opts.enableClassInlining = false)
         .withBuilderTransformation(
             b -> b.addProguardConfiguration(PROGUARD_OPTIONS, Origin.unknown()))
-        .withDexCheck(inspector -> checkLambdaCount(inspector, 115, "lambdadesugaring"))
+        .withDexCheck(inspector -> checkLambdaCount(inspector, 116, "lambdadesugaring"))
         .run();
 
     test("lambdadesugaring", "lambdadesugaring", "LambdaDesugaring")
@@ -142,7 +142,7 @@
         .withOptionConsumer(opts -> opts.enableClassInlining = false)
         .withBuilderTransformation(
             b -> b.addProguardConfiguration(PROGUARD_OPTIONS, Origin.unknown()))
-        .withDexCheck(inspector -> checkLambdaCount(inspector, 115, "lambdadesugaring"))
+        .withDexCheck(inspector -> checkLambdaCount(inspector, 116, "lambdadesugaring"))
         .run();
 
     test("lambdadesugaring", "lambdadesugaring", "LambdaDesugaring")
diff --git a/src/test/java/com/android/tools/r8/TestCompileResult.java b/src/test/java/com/android/tools/r8/TestCompileResult.java
index 87cc650..7f777ed 100644
--- a/src/test/java/com/android/tools/r8/TestCompileResult.java
+++ b/src/test/java/com/android/tools/r8/TestCompileResult.java
@@ -208,10 +208,18 @@
     return self();
   }
 
+  public CR disableVerifer() {
+    assert getBackend() == Backend.CF;
+    if (!vmArguments.contains("-noverify")) {
+      vmArguments.add("-noverify");
+    }
+    return self();
+  }
+
   public CR enableRuntimeAssertions() {
     assert getBackend() == Backend.CF;
-    if (!this.vmArguments.contains("-ea")) {
-      this.vmArguments.add("-ea");
+    if (!vmArguments.contains("-ea")) {
+      vmArguments.add("-ea");
     }
     return self();
   }
diff --git a/src/test/java/com/android/tools/r8/TestParameters.java b/src/test/java/com/android/tools/r8/TestParameters.java
index d494ea2..d886efb 100644
--- a/src/test/java/com/android/tools/r8/TestParameters.java
+++ b/src/test/java/com/android/tools/r8/TestParameters.java
@@ -23,6 +23,11 @@
     this.apiLevel = apiLevel;
   }
 
+  public boolean canUseDefaultAndStaticInterfaceMethods() {
+    assert isCfRuntime() || isDexRuntime();
+    return isCfRuntime() || getApiLevel().isGreaterThanOrEqualTo(AndroidApiLevel.N);
+  }
+
   // Convenience predicates.
   public boolean isDexRuntime() {
     return runtime.isDex();
diff --git a/src/test/java/com/android/tools/r8/desugar/DesugarInstanceLambdaWithReadsTest.java b/src/test/java/com/android/tools/r8/desugar/DesugarInstanceLambdaWithReadsTest.java
index 07e4f62..bf507de 100644
--- a/src/test/java/com/android/tools/r8/desugar/DesugarInstanceLambdaWithReadsTest.java
+++ b/src/test/java/com/android/tools/r8/desugar/DesugarInstanceLambdaWithReadsTest.java
@@ -87,8 +87,8 @@
   }
 
   static class Main {
-    // Field that is read from the lambda$ method.
-    A filter;
+    // Field that is read from the lambda$ method (private ensures the method can't be inlined).
+    private A filter;
 
     public Main(A filter) {
       this.filter = filter;
diff --git a/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/ConcurrentHashMapSubclassTest.java b/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/ConcurrentHashMapSubclassTest.java
new file mode 100644
index 0000000..f13eddb
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/ConcurrentHashMapSubclassTest.java
@@ -0,0 +1,130 @@
+// 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.desugar.desugaredlibrary;
+
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.utils.BooleanUtils;
+import java.util.IdentityHashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.concurrent.ConcurrentHashMap;
+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 ConcurrentHashMapSubclassTest extends DesugaredLibraryTestBase {
+
+  private final TestParameters parameters;
+  private final boolean shrinkDesugaredLibrary;
+
+  @Parameters(name = "{1}, shrinkDesugaredLibrary: {0}")
+  public static List<Object[]> data() {
+    return buildParameters(
+        BooleanUtils.values(), getTestParameters().withDexRuntimes().withAllApiLevels().build());
+  }
+
+  public ConcurrentHashMapSubclassTest(boolean shrinkDesugaredLibrary, TestParameters parameters) {
+    this.shrinkDesugaredLibrary = shrinkDesugaredLibrary;
+    this.parameters = parameters;
+  }
+
+  @Test
+  public void testCustomCollectionD8() throws Exception {
+    KeepRuleConsumer keepRuleConsumer = createKeepRuleConsumer(parameters);
+    testForD8()
+        .addInnerClasses(ConcurrentHashMapSubclassTest.class)
+        .setMinApi(parameters.getApiLevel())
+        .enableCoreLibraryDesugaring(parameters.getApiLevel(), keepRuleConsumer)
+        .compile()
+        .addDesugaredCoreLibraryRunClassPath(
+            this::buildDesugaredLibrary,
+            parameters.getApiLevel(),
+            keepRuleConsumer.get(),
+            shrinkDesugaredLibrary)
+        .run(parameters.getRuntime(), Executor.class)
+        .assertSuccessWithOutputLines("1.0", "10.0", "1.0", "10.0", "1.0", "10.0");
+  }
+
+  @Test
+  public void testCustomCollectionR8() throws Exception {
+    KeepRuleConsumer keepRuleConsumer = createKeepRuleConsumer(parameters);
+    testForR8(Backend.DEX)
+        .addInnerClasses(ConcurrentHashMapSubclassTest.class)
+        .setMinApi(parameters.getApiLevel())
+        .addKeepClassAndMembersRules(Executor.class)
+        .enableCoreLibraryDesugaring(parameters.getApiLevel(), keepRuleConsumer)
+        .compile()
+        .addDesugaredCoreLibraryRunClassPath(
+            this::buildDesugaredLibrary,
+            parameters.getApiLevel(),
+            keepRuleConsumer.get(),
+            shrinkDesugaredLibrary)
+        .run(parameters.getRuntime(), Executor.class)
+        .assertSuccessWithOutputLines("1.0", "10.0", "1.0", "10.0", "1.0", "10.0");
+  }
+
+  @SuppressWarnings("unchecked")
+  static class Executor {
+    public static void main(String[] args) {
+      directType();
+      classType();
+      itfType();
+    }
+
+    static void itfType() {
+      Map map = new NullableConcurrentHashMap<Integer, Double>();
+      map.put(1, 1.0);
+      map.putAll(example());
+      System.out.println(map.get(1));
+      System.out.println(map.get(10));
+    }
+
+    static void classType() {
+      ConcurrentHashMap map = new NullableConcurrentHashMap<Integer, Double>();
+      map.put(1, 1.0);
+      map.putAll(example());
+      System.out.println(map.get(1));
+      System.out.println(map.get(10));
+    }
+
+    static void directType() {
+      NullableConcurrentHashMap map = new NullableConcurrentHashMap<Integer, Double>();
+      map.put(1, 1.0);
+      map.putAll(example());
+      System.out.println(map.get(1));
+      System.out.println(map.get(10));
+    }
+
+    static Map<Integer, Double> example() {
+      IdentityHashMap<Integer, Double> example = new IdentityHashMap<>();
+      example.put(10, 10.0);
+      return example;
+    }
+  }
+
+  static class NullableConcurrentHashMap<K, V> extends ConcurrentHashMap<K, V> {
+    NullableConcurrentHashMap() {
+      super();
+    }
+
+    @SuppressWarnings("NullableProblems")
+    @Override
+    public V put(K key, V value) {
+      if (key == null || value == null) {
+        return null;
+      }
+      return super.put(key, value);
+    }
+
+    @Override
+    public void putAll(Map<? extends K, ? extends V> m) {
+      for (Entry<? extends K, ? extends V> entry : m.entrySet()) {
+        put(entry.getKey(), entry.getValue());
+      }
+    }
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/DesugaredLibraryTestBase.java b/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/DesugaredLibraryTestBase.java
index 628fbfe..b68997a 100644
--- a/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/DesugaredLibraryTestBase.java
+++ b/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/DesugaredLibraryTestBase.java
@@ -82,7 +82,7 @@
       // If we compile extended library here, it means we use TestNG.
       // TestNG requires annotations, hence we disable AnnotationRemoval.
       // This implies that extra warning are generated if this is set.
-      boolean disableL8AnnotationRemovalForTesting = !additionalProgramFiles.isEmpty();
+      boolean extraFiles = !additionalProgramFiles.isEmpty();
       ArrayList<Path> extraPaths = new ArrayList<>(additionalProgramFiles);
       TestDiagnosticMessagesImpl diagnosticsHandler = new TestDiagnosticMessagesImpl();
       Path desugaredLib = temp.newFolder().toPath().resolve("desugar_jdk_libs_dex.zip");
@@ -104,11 +104,12 @@
       ToolHelper.runL8(
           l8Builder.build(),
           options -> {
-            if (disableL8AnnotationRemovalForTesting) {
+            if (extraFiles) {
               options.testing.disableL8AnnotationRemoval = true;
+              options.testing.forceLibBackportsInL8CfToCf = true;
             }
           });
-      if (!disableL8AnnotationRemovalForTesting) {
+      if (!extraFiles) {
         assertTrue(
             diagnosticsHandler.getInfos().stream()
                 .noneMatch(
diff --git a/src/test/java/com/android/tools/r8/desugaring/interfacemethods/DefaultInterfaceMethodDesugaringWithStaticResolutionTest.java b/src/test/java/com/android/tools/r8/desugaring/interfacemethods/DefaultInterfaceMethodDesugaringWithStaticResolutionTest.java
new file mode 100644
index 0000000..bdd7960
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/desugaring/interfacemethods/DefaultInterfaceMethodDesugaringWithStaticResolutionTest.java
@@ -0,0 +1,97 @@
+package com.android.tools.r8.desugaring.interfacemethods;
+
+import static org.hamcrest.CoreMatchers.containsString;
+import static org.junit.Assume.assumeTrue;
+
+import com.android.tools.r8.D8TestRunResult;
+import com.android.tools.r8.R8TestRunResult;
+import com.android.tools.r8.TestBase;
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.TestParametersCollection;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+
+@RunWith(Parameterized.class)
+public class DefaultInterfaceMethodDesugaringWithStaticResolutionTest extends TestBase {
+
+  private final TestParameters parameters;
+
+  @Parameterized.Parameters(name = "{0}")
+  public static TestParametersCollection data() {
+    return getTestParameters().withAllRuntimesAndApiLevels().build();
+  }
+
+  public DefaultInterfaceMethodDesugaringWithStaticResolutionTest(TestParameters parameters) {
+    this.parameters = parameters;
+  }
+
+  @Test
+  public void testJVM() throws Exception {
+    assumeTrue(parameters.isCfRuntime());
+    testForJvm()
+        .addTestClasspath()
+        .run(parameters.getRuntime(), TestClass.class)
+        .assertSuccessWithOutputLines("I.m()");
+  }
+
+  @Test
+  public void testD8() throws Exception {
+    assumeTrue(parameters.isDexRuntime());
+    D8TestRunResult result =
+        testForD8()
+            .addInnerClasses(DefaultInterfaceMethodDesugaringWithStaticResolutionTest.class)
+            .setMinApi(parameters.getApiLevel())
+            .compile()
+            .run(parameters.getRuntime(), TestClass.class);
+    // TODO(b/152163087): Should always succeed with "I.m()".
+    if (parameters.canUseDefaultAndStaticInterfaceMethods()) {
+      result.assertSuccessWithOutputLines("I.m()");
+    } else {
+      result.assertFailureWithErrorThatMatches(
+          containsString(AbstractMethodError.class.getTypeName()));
+    }
+  }
+
+  @Test
+  public void testR8() throws Exception {
+    R8TestRunResult result =
+        testForR8(parameters.getBackend())
+            .addInnerClasses(DefaultInterfaceMethodDesugaringWithStaticResolutionTest.class)
+            .addKeepAllClassesRule()
+            .setMinApi(parameters.getApiLevel())
+            .compile()
+            .run(parameters.getRuntime(), TestClass.class);
+    // TODO(b/152163087): Should always succeed with "I.m()".
+    if (parameters.canUseDefaultAndStaticInterfaceMethods()) {
+      result.assertSuccessWithOutputLines("I.m()");
+    } else {
+      result.assertFailureWithErrorThatMatches(
+          containsString(AbstractMethodError.class.getTypeName()));
+    }
+  }
+
+  static class TestClass {
+
+    public static void main(String[] args) {
+      I b = new B();
+      b.m();
+    }
+  }
+
+  interface I {
+
+    default void m() {
+      System.out.println("I.m()");
+    }
+  }
+
+  static class A {
+
+    private static void m() {
+      System.out.println("A.m()");
+    }
+  }
+
+  static class B extends A implements I {}
+}
diff --git a/src/test/java/com/android/tools/r8/enumunboxing/EnumClinitWithSideEffectsUnboxingTest.java b/src/test/java/com/android/tools/r8/enumunboxing/EnumClinitWithSideEffectsUnboxingTest.java
new file mode 100644
index 0000000..2fbd235
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/enumunboxing/EnumClinitWithSideEffectsUnboxingTest.java
@@ -0,0 +1,55 @@
+package com.android.tools.r8.enumunboxing;
+
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.utils.InternalOptions;
+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 EnumClinitWithSideEffectsUnboxingTest extends EnumUnboxingTestBase {
+
+  private final KeepRule enumKeepRule;
+  private final TestParameters parameters;
+
+  @Parameters(name = "{1}, enum keep rule: {0}")
+  public static List<Object[]> data() {
+    return buildParameters(
+        getAllEnumKeepRules(), getTestParameters().withAllRuntimesAndApiLevels().build());
+  }
+
+  public EnumClinitWithSideEffectsUnboxingTest(KeepRule enumKeepRule, TestParameters parameters) {
+    this.enumKeepRule = enumKeepRule;
+    this.parameters = parameters;
+  }
+
+  @Test
+  public void test() throws Exception {
+    testForR8(parameters.getBackend())
+        .addInnerClasses(EnumClinitWithSideEffectsUnboxingTest.class)
+        .addKeepMainRule(TestClass.class)
+        .addKeepRules(enumKeepRule.getKeepRule())
+        .addOptionsModification(InternalOptions::enableEnumUnboxing)
+        .setMinApi(parameters.getApiLevel())
+        .compile()
+        .run(parameters.getRuntime(), TestClass.class)
+        .assertSuccessWithOutputLines("Hello world!");
+  }
+
+  static class TestClass {
+
+    static MyEnum f = MyEnum.A;
+
+    public static void main(String[] args) {}
+  }
+
+  enum MyEnum {
+    A;
+
+    static {
+      System.out.println("Hello world!");
+    }
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/enumunboxing/EnumInitWithSideEffectsUnboxingTest.java b/src/test/java/com/android/tools/r8/enumunboxing/EnumInitWithSideEffectsUnboxingTest.java
new file mode 100644
index 0000000..d353692
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/enumunboxing/EnumInitWithSideEffectsUnboxingTest.java
@@ -0,0 +1,55 @@
+package com.android.tools.r8.enumunboxing;
+
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.utils.InternalOptions;
+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 EnumInitWithSideEffectsUnboxingTest extends EnumUnboxingTestBase {
+
+  private final KeepRule enumKeepRule;
+  private final TestParameters parameters;
+
+  @Parameters(name = "{1}, enum keep rule: {0}")
+  public static List<Object[]> data() {
+    return buildParameters(
+        getAllEnumKeepRules(), getTestParameters().withAllRuntimesAndApiLevels().build());
+  }
+
+  public EnumInitWithSideEffectsUnboxingTest(KeepRule enumKeepRule, TestParameters parameters) {
+    this.enumKeepRule = enumKeepRule;
+    this.parameters = parameters;
+  }
+
+  @Test
+  public void test() throws Exception {
+    testForR8(parameters.getBackend())
+        .addInnerClasses(EnumInitWithSideEffectsUnboxingTest.class)
+        .addKeepMainRule(TestClass.class)
+        .addKeepRules(enumKeepRule.getKeepRule())
+        .addOptionsModification(InternalOptions::enableEnumUnboxing)
+        .setMinApi(parameters.getApiLevel())
+        .compile()
+        .run(parameters.getRuntime(), TestClass.class)
+        .assertSuccessWithOutputLines("Hello world!");
+  }
+
+  static class TestClass {
+
+    static MyEnum f = MyEnum.A;
+
+    public static void main(String[] args) {}
+  }
+
+  enum MyEnum {
+    A;
+
+    MyEnum() {
+      System.out.println("Hello world!");
+    }
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/enumunboxing/EnumUnboxingArrayTest.java b/src/test/java/com/android/tools/r8/enumunboxing/EnumUnboxingArrayTest.java
index 7d6f2ac..c97b7a7 100644
--- a/src/test/java/com/android/tools/r8/enumunboxing/EnumUnboxingArrayTest.java
+++ b/src/test/java/com/android/tools/r8/enumunboxing/EnumUnboxingArrayTest.java
@@ -18,7 +18,7 @@
 @RunWith(Parameterized.class)
 public class EnumUnboxingArrayTest extends EnumUnboxingTestBase {
 
-  private static final Class<?>[] FAILURES = {
+  private static final Class<?>[] SUCCESSES = {
     EnumVarArgs.class,
     EnumArrayReadWriteNoEscape.class,
     EnumArrayReadWrite.class,
@@ -42,11 +42,11 @@
   }
 
   @Test
-  public void testEnumUnboxingFailure() throws Exception {
+  public void testEnumUnboxing() throws Exception {
     R8TestCompileResult compile =
         testForR8(parameters.getBackend())
             .addInnerClasses(EnumUnboxingArrayTest.class)
-            .addKeepMainRules(FAILURES)
+            .addKeepMainRules(SUCCESSES)
             .enableInliningAnnotations()
             .enableNeverClassInliningAnnotations()
             .addKeepRules(enumKeepRules.getKeepRule())
@@ -54,14 +54,14 @@
             .allowDiagnosticInfoMessages()
             .setMinApi(parameters.getApiLevel())
             .compile();
-    for (Class<?> failure : FAILURES) {
+    for (Class<?> success : SUCCESSES) {
       R8TestRunResult run =
           compile
               .inspectDiagnosticMessages(
                   m ->
-                      assertEnumIsBoxed(
-                          failure.getDeclaredClasses()[0], failure.getSimpleName(), m))
-              .run(parameters.getRuntime(), failure)
+                      assertEnumIsUnboxed(
+                          success.getDeclaredClasses()[0], success.getSimpleName(), m))
+              .run(parameters.getRuntime(), success)
               .assertSuccess();
       assertLines2By2Correct(run.getStdOut());
     }
@@ -96,8 +96,8 @@
       myEnums[1] = MyEnum.C;
       System.out.println(myEnums[1].ordinal());
       System.out.println(2);
-      System.out.println(myEnums[0]);
-      System.out.println("null");
+      System.out.println(myEnums[0] == null);
+      System.out.println("true");
       myEnums[0] = MyEnum.B;
       System.out.println(myEnums.length);
       System.out.println(2);
@@ -117,8 +117,8 @@
       MyEnum[] myEnums = getArray();
       System.out.println(myEnums[1].ordinal());
       System.out.println(2);
-      System.out.println(myEnums[0]);
-      System.out.println("null");
+      System.out.println(myEnums[0] == null);
+      System.out.println("true");
       myEnums[0] = MyEnum.B;
       System.out.println(sum(myEnums));
       System.out.println(2);
@@ -152,6 +152,8 @@
       System.out.println(2);
       System.out.println(myEnums[0][0].ordinal());
       System.out.println(1);
+      System.out.println(myEnums[0].length);
+      System.out.println(2);
     }
 
     @NeverInline
diff --git a/src/test/java/com/android/tools/r8/enumunboxing/EnumUnboxingTestBase.java b/src/test/java/com/android/tools/r8/enumunboxing/EnumUnboxingTestBase.java
index e03d352..e367418 100644
--- a/src/test/java/com/android/tools/r8/enumunboxing/EnumUnboxingTestBase.java
+++ b/src/test/java/com/android/tools/r8/enumunboxing/EnumUnboxingTestBase.java
@@ -96,4 +96,8 @@
         BooleanUtils.values(),
         KEEP_ENUM);
   }
+
+  static List<KeepRule> getAllEnumKeepRules() {
+    return KEEP_ENUM;
+  }
 }
diff --git a/src/test/java/com/android/tools/r8/enumunboxing/SwitchEnumUnboxingTest.java b/src/test/java/com/android/tools/r8/enumunboxing/SwitchEnumUnboxingTest.java
index b7c7005..3ccdd37 100644
--- a/src/test/java/com/android/tools/r8/enumunboxing/SwitchEnumUnboxingTest.java
+++ b/src/test/java/com/android/tools/r8/enumunboxing/SwitchEnumUnboxingTest.java
@@ -51,7 +51,9 @@
             .compile()
             .inspectDiagnosticMessages(
                 m -> {
-                  if (enumValueOptimization) {
+                  // TODO(b/150370354): We need to allow static helper method to re-enable unboxing
+                  // with switches.
+                  if (enumValueOptimization && enumKeepRules.getKeepRule().equals("")) {
                     assertEnumIsUnboxed(ENUM_CLASS, classToTest.getSimpleName(), m);
                   } else {
                     assertEnumIsBoxed(ENUM_CLASS, classToTest.getSimpleName(), m);
diff --git a/src/test/java/com/android/tools/r8/inspection/InspectionApiTest.java b/src/test/java/com/android/tools/r8/inspection/InspectionApiTest.java
index 5c5a6e3..9f413d5 100644
--- a/src/test/java/com/android/tools/r8/inspection/InspectionApiTest.java
+++ b/src/test/java/com/android/tools/r8/inspection/InspectionApiTest.java
@@ -4,6 +4,7 @@
 package com.android.tools.r8.inspection;
 
 import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNull;
 
 import com.android.tools.r8.TestBase;
 import com.android.tools.r8.TestParameters;
@@ -14,6 +15,9 @@
 import com.android.tools.r8.references.MethodReference;
 import com.android.tools.r8.references.Reference;
 import com.android.tools.r8.utils.StringUtils;
+import java.util.Collections;
+import java.util.HashSet;
+import java.util.Set;
 import org.junit.Test;
 import org.junit.runner.RunWith;
 import org.junit.runners.Parameterized;
@@ -49,7 +53,7 @@
         .apply(b -> b.getBuilder().addOutputInspection(this::inspection))
         .run(parameters.getRuntime(), TestClass.class)
         .assertSuccessWithOutput(EXPECTED);
-    assertFound();
+    assertFound(false);
   }
 
   @Test
@@ -61,37 +65,47 @@
         .apply(b -> b.getBuilder().addOutputInspection(this::inspection))
         .run(parameters.getRuntime(), TestClass.class)
         .assertSuccessWithOutput(EXPECTED);
-    assertFound();
+    assertFound(true);
   }
 
   ClassReference foundClass = null;
   FieldReference foundField = null;
-  MethodReference foundMethod = null;
+  Set<MethodReference> foundMethods = new HashSet<>();
 
   private void inspection(Inspector inspector) {
     inspector.forEachClass(
         classInspector -> {
+          assertNull(foundClass);
           foundClass = classInspector.getClassReference();
           classInspector.forEachField(
               fieldInspector -> {
+                assertNull(foundField);
                 foundField = fieldInspector.getFieldReference();
               });
           classInspector.forEachMethod(
               methodInspector -> {
-                // Ignore clinit (which is removed in R8).
-                if (!methodInspector.getMethodReference().getMethodName().equals("<clinit>")) {
-                  foundMethod = methodInspector.getMethodReference();
-                }
+                foundMethods.add(methodInspector.getMethodReference());
               });
         });
   }
 
-  private void assertFound() throws Exception {
+  private void assertFound(boolean isR8) throws Exception {
     assertEquals(Reference.classFromClass(TestClass.class), foundClass);
     assertEquals(Reference.fieldFromField(TestClass.class.getDeclaredField("foo")), foundField);
-    assertEquals(
-        Reference.methodFromMethod(TestClass.class.getDeclaredMethod("main", String[].class)),
-        foundMethod);
+
+    Set<MethodReference> expectedMethods = new HashSet<>();
+    expectedMethods.add(
+        Reference.methodFromMethod(TestClass.class.getDeclaredMethod("main", String[].class)));
+    expectedMethods.add(Reference.methodFromMethod(TestClass.class.getDeclaredConstructor()));
+    if (!isR8) {
+      expectedMethods.add(
+          Reference.method(
+              Reference.classFromClass(TestClass.class),
+              "<clinit>",
+              Collections.emptyList(),
+              null));
+    }
+    assertEquals(expectedMethods, foundMethods);
   }
 
   static class TestClass {
diff --git a/src/test/java/com/android/tools/r8/ir/LinearFlowIteratorTest.java b/src/test/java/com/android/tools/r8/ir/LinearFlowIteratorTest.java
index 88785c4..0d31dca 100644
--- a/src/test/java/com/android/tools/r8/ir/LinearFlowIteratorTest.java
+++ b/src/test/java/com/android/tools/r8/ir/LinearFlowIteratorTest.java
@@ -153,7 +153,7 @@
     IRCode code = simpleCode();
     InstructionListIterator it = new LinearFlowInstructionListIterator(code, code.blocks.get(1));
     Instruction current = it.previous();
-    assertTrue(current.isConstNumber() && current.outValue().getTypeLattice().isReference());
+    assertTrue(current.isConstNumber() && current.outValue().getType().isReferenceType());
     it.next();
     current = it.next();
     assertTrue(current.isArrayGet());
diff --git a/src/test/java/com/android/tools/r8/ir/SplitBlockTest.java b/src/test/java/com/android/tools/r8/ir/SplitBlockTest.java
index e53db5e..2b3e44d 100644
--- a/src/test/java/com/android/tools/r8/ir/SplitBlockTest.java
+++ b/src/test/java/com/android/tools/r8/ir/SplitBlockTest.java
@@ -11,7 +11,7 @@
 import com.android.tools.r8.graph.AppInfoWithClassHierarchy;
 import com.android.tools.r8.graph.AppView;
 import com.android.tools.r8.graph.DexApplication;
-import com.android.tools.r8.ir.analysis.type.TypeLatticeElement;
+import com.android.tools.r8.ir.analysis.type.TypeElement;
 import com.android.tools.r8.ir.code.Add;
 import com.android.tools.r8.ir.code.BasicBlock;
 import com.android.tools.r8.ir.code.ConstNumber;
@@ -363,10 +363,9 @@
       BasicBlock newReturnBlock = iterator.split(code);
       // Modify the code to make the inserted block add the constant 10 to the original return
       // value.
-      Value newConstValue =
-          new Value(test.valueNumberGenerator.next(), TypeLatticeElement.getInt(), null);
+      Value newConstValue = new Value(test.valueNumberGenerator.next(), TypeElement.getInt(), null);
       Value newReturnValue =
-          new Value(test.valueNumberGenerator.next(), TypeLatticeElement.getInt(), null);
+          new Value(test.valueNumberGenerator.next(), TypeElement.getInt(), null);
       Value oldReturnValue = newReturnBlock.iterator().next().asReturn().returnValue();
       newReturnBlock.iterator().next().asReturn().returnValue().replaceUsers(newReturnValue);
       Instruction constInstruction = new ConstNumber(newConstValue, 10);
diff --git a/src/test/java/com/android/tools/r8/ir/analysis/type/ArrayTypeTest.java b/src/test/java/com/android/tools/r8/ir/analysis/type/ArrayTypeTest.java
index 74a49ed..a644df9 100644
--- a/src/test/java/com/android/tools/r8/ir/analysis/type/ArrayTypeTest.java
+++ b/src/test/java/com/android/tools/r8/ir/analysis/type/ArrayTypeTest.java
@@ -4,8 +4,8 @@
 
 package com.android.tools.r8.ir.analysis.type;
 
-import static com.android.tools.r8.ir.analysis.type.TypeLatticeElement.getFloat;
-import static com.android.tools.r8.ir.analysis.type.TypeLatticeElement.getInt;
+import static com.android.tools.r8.ir.analysis.type.TypeElement.getFloat;
+import static com.android.tools.r8.ir.analysis.type.TypeElement.getInt;
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertTrue;
 
@@ -69,13 +69,13 @@
             value = arrayPutInstruction.value();
           }
 
-          assertTrue(array.getTypeLattice().isArrayType());
+          assertTrue(array.getType().isArrayType());
 
-          ArrayTypeLatticeElement arrayType = array.getTypeLattice().asArrayTypeLatticeElement();
-          TypeLatticeElement elementType = arrayType.getArrayMemberTypeAsMemberType();
+          ArrayTypeElement arrayType = array.getType().asArrayType();
+          TypeElement elementType = arrayType.getMemberType();
 
           assertEquals(getFloat(), elementType);
-          assertEquals(getFloat(), value.getTypeLattice());
+          assertEquals(getFloat(), value.getType());
         }
       }
     };
@@ -89,13 +89,13 @@
         Value array = arrayPutInstruction.array();
         Value value = arrayPutInstruction.value();
 
-        assertTrue(array.getTypeLattice().isArrayType());
+        assertTrue(array.getType().isArrayType());
 
-        ArrayTypeLatticeElement arrayType = array.getTypeLattice().asArrayTypeLatticeElement();
-        TypeLatticeElement elementType = arrayType.getArrayMemberTypeAsMemberType();
+        ArrayTypeElement arrayType = array.getType().asArrayType();
+        TypeElement elementType = arrayType.getMemberType();
 
         assertEquals(getFloat(), elementType);
-        assertEquals(getFloat(), value.getTypeLattice());
+        assertEquals(getFloat(), value.getType());
       }
 
       {
@@ -104,7 +104,7 @@
                 code,
                 instruction ->
                     instruction.isConstNumber() && instruction.asConstNumber().getRawValue() != 0);
-        assertEquals(getFloat(), constNumberInstruction.outValue().getTypeLattice());
+        assertEquals(getFloat(), constNumberInstruction.outValue().getType());
       }
     };
   }
@@ -115,7 +115,7 @@
       for (BasicBlock block : code.blocks) {
         for (Phi phi : block.getPhis()) {
           phiCount++;
-          assertEquals(getInt(), phi.getTypeLattice());
+          assertEquals(getInt(), phi.getType());
         }
       }
       assertEquals(2, phiCount);
diff --git a/src/test/java/com/android/tools/r8/ir/analysis/type/ConstrainedPrimitiveTypeTest.java b/src/test/java/com/android/tools/r8/ir/analysis/type/ConstrainedPrimitiveTypeTest.java
index 9471d46..e3b40cb 100644
--- a/src/test/java/com/android/tools/r8/ir/analysis/type/ConstrainedPrimitiveTypeTest.java
+++ b/src/test/java/com/android/tools/r8/ir/analysis/type/ConstrainedPrimitiveTypeTest.java
@@ -4,10 +4,10 @@
 
 package com.android.tools.r8.ir.analysis.type;
 
-import static com.android.tools.r8.ir.analysis.type.TypeLatticeElement.getDouble;
-import static com.android.tools.r8.ir.analysis.type.TypeLatticeElement.getFloat;
-import static com.android.tools.r8.ir.analysis.type.TypeLatticeElement.getInt;
-import static com.android.tools.r8.ir.analysis.type.TypeLatticeElement.getLong;
+import static com.android.tools.r8.ir.analysis.type.TypeElement.getDouble;
+import static com.android.tools.r8.ir.analysis.type.TypeElement.getFloat;
+import static com.android.tools.r8.ir.analysis.type.TypeElement.getInt;
+import static com.android.tools.r8.ir.analysis.type.TypeElement.getLong;
 import static org.junit.Assert.assertEquals;
 
 import com.android.tools.r8.NeverInline;
@@ -93,12 +93,12 @@
   }
 
   private static Consumer<IRCode> testInspector(
-      TypeLatticeElement expectedType, int expectedNumberOfConstNumberInstructions) {
+      TypeElement expectedType, int expectedNumberOfConstNumberInstructions) {
     return code -> {
       for (Instruction instruction : code.instructions()) {
         if (instruction.isConstNumber()) {
           ConstNumber constNumberInstruction = instruction.asConstNumber();
-          assertEquals(expectedType, constNumberInstruction.outValue().getTypeLattice());
+          assertEquals(expectedType, constNumberInstruction.outValue().getType());
         }
       }
 
diff --git a/src/test/java/com/android/tools/r8/ir/analysis/type/NullabilityTest.java b/src/test/java/com/android/tools/r8/ir/analysis/type/NullabilityTest.java
index e4fb7b1..2590f1b 100644
--- a/src/test/java/com/android/tools/r8/ir/analysis/type/NullabilityTest.java
+++ b/src/test/java/com/android/tools/r8/ir/analysis/type/NullabilityTest.java
@@ -5,8 +5,8 @@
 
 import static com.android.tools.r8.ir.analysis.type.Nullability.definitelyNotNull;
 import static com.android.tools.r8.ir.analysis.type.Nullability.maybeNull;
-import static com.android.tools.r8.ir.analysis.type.TypeLatticeElement.fromDexType;
-import static com.android.tools.r8.ir.analysis.type.TypeLatticeElement.stringClassType;
+import static com.android.tools.r8.ir.analysis.type.TypeElement.fromDexType;
+import static com.android.tools.r8.ir.analysis.type.TypeElement.stringClassType;
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertFalse;
 import static org.junit.Assert.assertTrue;
@@ -55,24 +55,24 @@
   }
 
   private static void verifyClassTypeLattice(
-      Map<Class<? extends Instruction>, TypeLatticeElement> expectedLattices,
+      Map<Class<? extends Instruction>, TypeElement> expectedLattices,
       DexType receiverType,
       Value v,
-      TypeLatticeElement l) {
+      TypeElement l) {
     // Due to the last invocation that will check nullability of the argument,
     // there is one exceptional mapping to PRIMITIVE.
-    if (l.isPrimitive()) {
+    if (l.isPrimitiveType()) {
       return;
     }
     assertTrue(l.isClassType());
-    ClassTypeLatticeElement lattice = l.asClassTypeLatticeElement();
+    ClassTypeElement lattice = l.asClassType();
     // Receiver
     if (lattice.getClassType().equals(receiverType)) {
       assertFalse(l.isNullable());
     } else {
       Instruction definition = v.definition;
       if (definition != null) {
-        TypeLatticeElement expected = expectedLattices.get(v.definition.getClass());
+        TypeElement expected = expectedLattices.get(v.definition.getClass());
         if (expected != null) {
           assertEquals(expected, l);
         }
@@ -87,7 +87,7 @@
         InvokeMethodWithReceiver invoke = instruction.asInvokeMethodWithReceiver();
         if (invoke.getInvokedMethod().name.toString().contains("hash")) {
           metInvokeVirtual = true;
-          TypeLatticeElement l = invoke.getReceiver().getTypeLattice();
+          TypeElement l = invoke.getReceiver().getType();
           assertEquals(npeCaught, l.isNullable());
         }
       }
@@ -95,14 +95,17 @@
     assertTrue(metInvokeVirtual);
   }
 
-  private void forEachOutValue(IRCode irCode, BiConsumer<Value, TypeLatticeElement> consumer) {
-    irCode.instructionIterator().forEachRemaining(instruction -> {
-      Value outValue = instruction.outValue();
-      if (outValue != null) {
-        TypeLatticeElement element = outValue.getTypeLattice();
-        consumer.accept(outValue, element);
-      }
-    });
+  private void forEachOutValue(IRCode irCode, BiConsumer<Value, TypeElement> consumer) {
+    irCode
+        .instructionIterator()
+        .forEachRemaining(
+            instruction -> {
+              Value outValue = instruction.outValue();
+              if (outValue != null) {
+                TypeElement element = outValue.getType();
+                consumer.accept(outValue, element);
+              }
+            });
   }
 
   @Test
@@ -122,7 +125,7 @@
                   .createType(
                       DescriptorUtils.javaTypeToDescriptor(
                           NonNullAfterInvoke.class.getCanonicalName()));
-          Map<Class<? extends Instruction>, TypeLatticeElement> expectedLattices =
+          Map<Class<? extends Instruction>, TypeElement> expectedLattices =
               ImmutableMap.of(
                   InvokeVirtual.class, stringClassType(appInfo, maybeNull()),
                   Assume.class, stringClassType(appInfo, definitelyNotNull()),
@@ -149,7 +152,7 @@
                   .createType(
                       DescriptorUtils.javaTypeToDescriptor(
                           NonNullAfterInvoke.class.getCanonicalName()));
-          Map<Class<? extends Instruction>, TypeLatticeElement> expectedLattices =
+          Map<Class<? extends Instruction>, TypeElement> expectedLattices =
               ImmutableMap.of(
                   InvokeVirtual.class, stringClassType(appInfo, maybeNull()),
                   Assume.class, stringClassType(appInfo, definitelyNotNull()),
@@ -176,7 +179,7 @@
                   .createType(
                       DescriptorUtils.javaTypeToDescriptor(
                           NonNullAfterArrayAccess.class.getCanonicalName()));
-          Map<Class<? extends Instruction>, TypeLatticeElement> expectedLattices =
+          Map<Class<? extends Instruction>, TypeElement> expectedLattices =
               ImmutableMap.of(
                   // An element inside a non-null array could be null.
                   ArrayGet.class,
@@ -186,13 +189,13 @@
               irCode,
               (v, l) -> {
                 if (l.isArrayType()) {
-                  ArrayTypeLatticeElement lattice = l.asArrayTypeLatticeElement();
+                  ArrayTypeElement lattice = l.asArrayType();
                   assertEquals(1, lattice.getNesting());
-                  TypeLatticeElement elementTypeLattice = lattice.getArrayMemberTypeAsMemberType();
-                  assertTrue(elementTypeLattice.isClassType());
+                  TypeElement elementType = lattice.getMemberType();
+                  assertTrue(elementType.isClassType());
                   assertEquals(
                       appInfo.dexItemFactory().stringType,
-                      elementTypeLattice.asClassTypeLatticeElement().getClassType());
+                      elementType.asClassType().getClassType());
                   assertEquals(v.definition.isArgument(), l.isNullable());
                 } else if (l.isClassType()) {
                   verifyClassTypeLattice(expectedLattices, mainClass, v, l);
@@ -218,7 +221,7 @@
                   .createType(
                       DescriptorUtils.javaTypeToDescriptor(
                           NonNullAfterArrayAccess.class.getCanonicalName()));
-          Map<Class<? extends Instruction>, TypeLatticeElement> expectedLattices =
+          Map<Class<? extends Instruction>, TypeElement> expectedLattices =
               ImmutableMap.of(
                   // An element inside a non-null array could be null.
                   ArrayGet.class,
@@ -228,13 +231,13 @@
               irCode,
               (v, l) -> {
                 if (l.isArrayType()) {
-                  ArrayTypeLatticeElement lattice = l.asArrayTypeLatticeElement();
+                  ArrayTypeElement lattice = l.asArrayType();
                   assertEquals(1, lattice.getNesting());
-                  TypeLatticeElement elementTypeLattice = lattice.getArrayMemberTypeAsMemberType();
+                  TypeElement elementTypeLattice = lattice.getMemberType();
                   assertTrue(elementTypeLattice.isClassType());
                   assertEquals(
                       appInfo.dexItemFactory().stringType,
-                      elementTypeLattice.asClassTypeLatticeElement().getClassType());
+                      elementTypeLattice.asClassType().getClassType());
                   assertEquals(v.definition.isArgument(), l.isNullable());
                 } else if (l.isClassType()) {
                   verifyClassTypeLattice(expectedLattices, mainClass, v, l);
@@ -266,7 +269,7 @@
                   .createType(
                       DescriptorUtils.javaTypeToDescriptor(
                           FieldAccessTest.class.getCanonicalName()));
-          Map<Class<? extends Instruction>, TypeLatticeElement> expectedLattices =
+          Map<Class<? extends Instruction>, TypeElement> expectedLattices =
               ImmutableMap.of(
                   Argument.class, fromDexType(testClass, maybeNull(), appInfo),
                   Assume.class, fromDexType(testClass, definitelyNotNull(), appInfo),
@@ -302,7 +305,7 @@
                   .createType(
                       DescriptorUtils.javaTypeToDescriptor(
                           FieldAccessTest.class.getCanonicalName()));
-          Map<Class<? extends Instruction>, TypeLatticeElement> expectedLattices =
+          Map<Class<? extends Instruction>, TypeElement> expectedLattices =
               ImmutableMap.of(
                   Argument.class, fromDexType(testClass, maybeNull(), appInfo),
                   Assume.class, fromDexType(testClass, definitelyNotNull(), appInfo),
diff --git a/src/test/java/com/android/tools/r8/ir/analysis/type/TypeAnalysisTest.java b/src/test/java/com/android/tools/r8/ir/analysis/type/TypeAnalysisTest.java
index 198ffa3..d0cf699 100644
--- a/src/test/java/com/android/tools/r8/ir/analysis/type/TypeAnalysisTest.java
+++ b/src/test/java/com/android/tools/r8/ir/analysis/type/TypeAnalysisTest.java
@@ -58,10 +58,10 @@
 @RunWith(Parameterized.class)
 public class TypeAnalysisTest extends SmaliTestBase {
   private static final InternalOptions TEST_OPTIONS = new InternalOptions();
-  private static final TypeLatticeElement NULL = TypeLatticeElement.getNull();
-  private static final TypeLatticeElement SINGLE = TypeLatticeElement.getSingle();
-  private static final TypeLatticeElement INT = TypeLatticeElement.getInt();
-  private static final TypeLatticeElement LONG = TypeLatticeElement.getLong();
+  private static final TypeElement NULL = TypeElement.getNull();
+  private static final TypeElement SINGLE = TypeElement.getSingle();
+  private static final TypeElement INT = TypeElement.getInt();
+  private static final TypeElement LONG = TypeElement.getLong();
 
   private final String dirName;
   private final String smaliFileName;
@@ -119,15 +119,17 @@
         new CodeInspector(dexApplication));
   }
 
-  private static void forEachOutValue(
-      IRCode irCode, BiConsumer<Value, TypeLatticeElement> consumer) {
-    irCode.instructionIterator().forEachRemaining(instruction -> {
-      Value outValue = instruction.outValue();
-      if (outValue != null) {
-        TypeLatticeElement element = outValue.getTypeLattice();
-        consumer.accept(outValue, element);
-      }
-    });
+  private static void forEachOutValue(IRCode irCode, BiConsumer<Value, TypeElement> consumer) {
+    irCode
+        .instructionIterator()
+        .forEachRemaining(
+            instruction -> {
+              Value outValue = instruction.outValue();
+              if (outValue != null) {
+                TypeElement element = outValue.getType();
+                consumer.accept(outValue, element);
+              }
+            });
   }
 
   // Simple one path with a lot of arithmetic operations.
@@ -173,8 +175,8 @@
         (v, l) -> {
           if (v == finalArray) {
             assertTrue(l.isArrayType());
-            ArrayTypeLatticeElement lattice = l.asArrayTypeLatticeElement();
-            assertTrue(lattice.getArrayMemberTypeAsMemberType().isPrimitive());
+            ArrayTypeElement lattice = l.asArrayType();
+            assertTrue(lattice.getMemberType().isPrimitiveType());
             assertEquals(1, lattice.getNesting());
             assertFalse(lattice.isNullable());
           }
@@ -200,8 +202,8 @@
         (v, l) -> {
           if (v == finalArray) {
             assertTrue(l.isArrayType());
-            ArrayTypeLatticeElement lattice = l.asArrayTypeLatticeElement();
-            assertTrue(lattice.getArrayMemberTypeAsMemberType().isPrimitive());
+            ArrayTypeElement lattice = l.asArrayType();
+            assertTrue(lattice.getMemberType().isPrimitiveType());
             assertEquals(1, lattice.getNesting());
             assertFalse(lattice.isNullable());
           }
@@ -213,14 +215,16 @@
     MethodSubject loop2Subject =
         inspector.clazz("Test").method(new MethodSignature("loop2", "void", ImmutableList.of()));
     IRCode irCode = loop2Subject.buildIR();
-    forEachOutValue(irCode, (v, l) -> {
-      if (l.isClassType()) {
-        ClassTypeLatticeElement lattice = l.asClassTypeLatticeElement();
-        assertEquals("Ljava/io/PrintStream;", lattice.getClassType().toDescriptorString());
-        // TODO(b/70795205): Can be refined by using control-flow info.
-        assertTrue(l.isNullable());
-      }
-    });
+    forEachOutValue(
+        irCode,
+        (v, l) -> {
+          if (l.isClassType()) {
+            ClassTypeElement lattice = l.asClassType();
+            assertEquals("Ljava/io/PrintStream;", lattice.getClassType().toDescriptorString());
+            // TODO(b/70795205): Can be refined by using control-flow info.
+            assertTrue(l.isNullable());
+          }
+        });
   }
 
   // move-exception
@@ -230,13 +234,15 @@
             .clazz("Test")
             .method(new MethodSignature("test2_throw", "int", ImmutableList.of()));
     IRCode irCode = test2Subject.buildIR();
-    forEachOutValue(irCode, (v, l) -> {
-      if (l.isClassType()) {
-        ClassTypeLatticeElement lattice = l.asClassTypeLatticeElement();
-        assertEquals("Ljava/lang/Throwable;", lattice.getClassType().toDescriptorString());
-        assertFalse(l.isNullable());
-      }
-    });
+    forEachOutValue(
+        irCode,
+        (v, l) -> {
+          if (l.isClassType()) {
+            ClassTypeElement lattice = l.asClassType();
+            assertEquals("Ljava/lang/Throwable;", lattice.getClassType().toDescriptorString());
+            assertFalse(l.isNullable());
+          }
+        });
   }
 
   // One very complicated example.
@@ -247,12 +253,12 @@
             .method(
                 new MethodSignature("a", "void", ImmutableList.of("Test", "Test", "Test", "Test")));
     DexType test = appView.dexItemFactory().createType("LTest;");
-    Map<Class<? extends Instruction>, TypeLatticeElement> expectedLattices =
+    Map<Class<? extends Instruction>, TypeElement> expectedLattices =
         ImmutableMap.of(
             ArrayLength.class, INT,
-            ConstString.class, TypeLatticeElement.stringClassType(appView, definitelyNotNull()),
-            CheckCast.class, TypeLatticeElement.fromDexType(test, maybeNull(), appView),
-            NewInstance.class, TypeLatticeElement.fromDexType(test, definitelyNotNull(), appView));
+            ConstString.class, TypeElement.stringClassType(appView, definitelyNotNull()),
+            CheckCast.class, TypeElement.fromDexType(test, maybeNull(), appView),
+            NewInstance.class, TypeElement.fromDexType(test, definitelyNotNull(), appView));
     IRCode irCode = methodSubject.buildIR();
     forEachOutValue(irCode, (v, l) -> {
       verifyTypeEnvironment(expectedLattices, v, l);
@@ -275,27 +281,25 @@
             .clazz("TestObject")
             .method(new MethodSignature("onClick", "void", ImmutableList.of("Test")));
     DexType test = appView.dexItemFactory().createType("LTest;");
-    Map<Class<? extends Instruction>, TypeLatticeElement> expectedLattices =
+    Map<Class<? extends Instruction>, TypeElement> expectedLattices =
         ImmutableMap.of(
-            ConstString.class, TypeLatticeElement.stringClassType(appView, definitelyNotNull()),
+            ConstString.class, TypeElement.stringClassType(appView, definitelyNotNull()),
             InstanceOf.class, INT,
-            StaticGet.class, TypeLatticeElement.fromDexType(test, maybeNull(), appView));
+            StaticGet.class, TypeElement.fromDexType(test, maybeNull(), appView));
     IRCode irCode = methodSubject.buildIR();
     forEachOutValue(irCode, (v, l) -> verifyTypeEnvironment(expectedLattices, v, l));
   }
 
-  private static void assertEither(TypeLatticeElement actual, TypeLatticeElement... expected) {
+  private static void assertEither(TypeElement actual, TypeElement... expected) {
     assertTrue(Arrays.stream(expected).anyMatch(e -> e == actual));
   }
 
   private static void verifyTypeEnvironment(
-      Map<Class<? extends Instruction>, TypeLatticeElement> expectedLattices,
-      Value v,
-      TypeLatticeElement l) {
+      Map<Class<? extends Instruction>, TypeElement> expectedLattices, Value v, TypeElement l) {
     if (v.definition == null) {
       return;
     }
-    TypeLatticeElement expected = expectedLattices.get(v.definition.getClass());
+    TypeElement expected = expectedLattices.get(v.definition.getClass());
     if (expected != null) {
       assertEquals(expected, l);
     }
diff --git a/src/test/java/com/android/tools/r8/ir/analysis/type/TypeConstraintOnTrivialPhiTest.java b/src/test/java/com/android/tools/r8/ir/analysis/type/TypeConstraintOnTrivialPhiTest.java
index 80d752e..0f40b4b 100644
--- a/src/test/java/com/android/tools/r8/ir/analysis/type/TypeConstraintOnTrivialPhiTest.java
+++ b/src/test/java/com/android/tools/r8/ir/analysis/type/TypeConstraintOnTrivialPhiTest.java
@@ -90,30 +90,28 @@
 
   @Test
   public void testIntConstraintOnTrivialPhi() throws Exception {
-    buildAndCheckIR("intConstraintOnTrivialPhiTest", testInspector(TypeLatticeElement.getInt()));
+    buildAndCheckIR("intConstraintOnTrivialPhiTest", testInspector(TypeElement.getInt()));
   }
 
   @Test
   public void testFloatConstraintOnTrivialPhi() throws Exception {
-    buildAndCheckIR(
-        "floatConstraintOnTrivialPhiTest", testInspector(TypeLatticeElement.getFloat()));
+    buildAndCheckIR("floatConstraintOnTrivialPhiTest", testInspector(TypeElement.getFloat()));
   }
 
   @Test
   public void testLongConstraintOnTrivialPhi() throws Exception {
-    buildAndCheckIR("longConstraintOnTrivialPhiTest", testInspector(TypeLatticeElement.getLong()));
+    buildAndCheckIR("longConstraintOnTrivialPhiTest", testInspector(TypeElement.getLong()));
   }
 
   @Test
   public void testDoubleConstraintOnTrivialPhi() throws Exception {
-    buildAndCheckIR(
-        "doubleConstraintOnTrivialPhiTest", testInspector(TypeLatticeElement.getDouble()));
+    buildAndCheckIR("doubleConstraintOnTrivialPhiTest", testInspector(TypeElement.getDouble()));
   }
 
-  private static Consumer<IRCode> testInspector(TypeLatticeElement expectedType) {
+  private static Consumer<IRCode> testInspector(TypeElement expectedType) {
     return code -> {
       ConstNumber constNumberInstruction = getMatchingInstruction(code, Instruction::isConstNumber);
-      assertEquals(expectedType, constNumberInstruction.outValue().getTypeLattice());
+      assertEquals(expectedType, constNumberInstruction.outValue().getType());
     };
   }
 }
diff --git a/src/test/java/com/android/tools/r8/ir/analysis/type/TypeElementWidthTest.java b/src/test/java/com/android/tools/r8/ir/analysis/type/TypeElementWidthTest.java
new file mode 100644
index 0000000..304d344
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/ir/analysis/type/TypeElementWidthTest.java
@@ -0,0 +1,93 @@
+// Copyright (c) 2019, 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 org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertTrue;
+
+import com.android.tools.r8.TestBase;
+import com.android.tools.r8.graph.DexItemFactory;
+import com.google.common.collect.ImmutableSet;
+import org.junit.Test;
+
+public class TypeElementWidthTest extends TestBase {
+
+  @Test
+  public void testArrayWidth() {
+    ArrayTypeElement arrayType =
+        ArrayTypeElement.create(TypeElement.getInt(), Nullability.maybeNull());
+    assertFalse(arrayType.isSinglePrimitive());
+    assertFalse(arrayType.isWidePrimitive());
+    assertEquals(1, arrayType.requiredRegisters());
+  }
+
+  @Test
+  public void testBooleanWidth() {
+    assertTrue(TypeElement.getBoolean().isSinglePrimitive());
+    assertFalse(TypeElement.getBoolean().isWidePrimitive());
+    assertEquals(1, TypeElement.getBoolean().requiredRegisters());
+  }
+
+  @Test
+  public void testByteWidth() {
+    assertTrue(TypeElement.getByte().isSinglePrimitive());
+    assertFalse(TypeElement.getByte().isWidePrimitive());
+    assertEquals(1, TypeElement.getByte().requiredRegisters());
+  }
+
+  @Test
+  public void testCharWidth() {
+    assertTrue(TypeElement.getChar().isSinglePrimitive());
+    assertFalse(TypeElement.getChar().isWidePrimitive());
+    assertEquals(1, TypeElement.getChar().requiredRegisters());
+  }
+
+  @Test
+  public void testDoubleWidth() {
+    assertTrue(TypeElement.getDouble().isWidePrimitive());
+    assertFalse(TypeElement.getDouble().isSinglePrimitive());
+    assertEquals(2, TypeElement.getDouble().requiredRegisters());
+  }
+
+  @Test
+  public void testFloatWidth() {
+    assertTrue(TypeElement.getFloat().isSinglePrimitive());
+    assertFalse(TypeElement.getFloat().isWidePrimitive());
+    assertEquals(1, TypeElement.getFloat().requiredRegisters());
+  }
+
+  @Test
+  public void testIntWidth() {
+    assertTrue(TypeElement.getInt().isSinglePrimitive());
+    assertFalse(TypeElement.getInt().isWidePrimitive());
+    assertEquals(1, TypeElement.getInt().requiredRegisters());
+  }
+
+  @Test
+  public void testLongWidth() {
+    assertTrue(TypeElement.getLong().isWidePrimitive());
+    assertFalse(TypeElement.getLong().isSinglePrimitive());
+    assertEquals(2, TypeElement.getLong().requiredRegisters());
+  }
+
+  @Test
+  public void testReferenceWidth() {
+    DexItemFactory dexItemFactory = new DexItemFactory();
+    ClassTypeElement referenceType =
+        ClassTypeElement.create(
+            dexItemFactory.objectType, Nullability.maybeNull(), ImmutableSet.of());
+    assertFalse(referenceType.isSinglePrimitive());
+    assertFalse(referenceType.isWidePrimitive());
+    assertEquals(1, referenceType.requiredRegisters());
+  }
+
+  @Test
+  public void testShortWidth() {
+    assertTrue(TypeElement.getShort().isSinglePrimitive());
+    assertFalse(TypeElement.getShort().isWidePrimitive());
+    assertEquals(1, TypeElement.getShort().requiredRegisters());
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/ir/analysis/type/TypeLatticeElementWidthTest.java b/src/test/java/com/android/tools/r8/ir/analysis/type/TypeLatticeElementWidthTest.java
deleted file mode 100644
index f2a20a6..0000000
--- a/src/test/java/com/android/tools/r8/ir/analysis/type/TypeLatticeElementWidthTest.java
+++ /dev/null
@@ -1,93 +0,0 @@
-// Copyright (c) 2019, 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 org.junit.Assert.assertEquals;
-import static org.junit.Assert.assertFalse;
-import static org.junit.Assert.assertTrue;
-
-import com.android.tools.r8.TestBase;
-import com.android.tools.r8.graph.DexItemFactory;
-import com.google.common.collect.ImmutableSet;
-import org.junit.Test;
-
-public class TypeLatticeElementWidthTest extends TestBase {
-
-  @Test
-  public void testArrayWidth() {
-    ArrayTypeLatticeElement arrayType =
-        ArrayTypeLatticeElement.create(TypeLatticeElement.getInt(), Nullability.maybeNull());
-    assertFalse(arrayType.isSinglePrimitive());
-    assertFalse(arrayType.isWidePrimitive());
-    assertEquals(1, arrayType.requiredRegisters());
-  }
-
-  @Test
-  public void testBooleanWidth() {
-    assertTrue(TypeLatticeElement.getBoolean().isSinglePrimitive());
-    assertFalse(TypeLatticeElement.getBoolean().isWidePrimitive());
-    assertEquals(1, TypeLatticeElement.getBoolean().requiredRegisters());
-  }
-
-  @Test
-  public void testByteWidth() {
-    assertTrue(TypeLatticeElement.getByte().isSinglePrimitive());
-    assertFalse(TypeLatticeElement.getByte().isWidePrimitive());
-    assertEquals(1, TypeLatticeElement.getByte().requiredRegisters());
-  }
-
-  @Test
-  public void testCharWidth() {
-    assertTrue(TypeLatticeElement.getChar().isSinglePrimitive());
-    assertFalse(TypeLatticeElement.getChar().isWidePrimitive());
-    assertEquals(1, TypeLatticeElement.getChar().requiredRegisters());
-  }
-
-  @Test
-  public void testDoubleWidth() {
-    assertTrue(TypeLatticeElement.getDouble().isWidePrimitive());
-    assertFalse(TypeLatticeElement.getDouble().isSinglePrimitive());
-    assertEquals(2, TypeLatticeElement.getDouble().requiredRegisters());
-  }
-
-  @Test
-  public void testFloatWidth() {
-    assertTrue(TypeLatticeElement.getFloat().isSinglePrimitive());
-    assertFalse(TypeLatticeElement.getFloat().isWidePrimitive());
-    assertEquals(1, TypeLatticeElement.getFloat().requiredRegisters());
-  }
-
-  @Test
-  public void testIntWidth() {
-    assertTrue(TypeLatticeElement.getInt().isSinglePrimitive());
-    assertFalse(TypeLatticeElement.getInt().isWidePrimitive());
-    assertEquals(1, TypeLatticeElement.getInt().requiredRegisters());
-  }
-
-  @Test
-  public void testLongWidth() {
-    assertTrue(TypeLatticeElement.getLong().isWidePrimitive());
-    assertFalse(TypeLatticeElement.getLong().isSinglePrimitive());
-    assertEquals(2, TypeLatticeElement.getLong().requiredRegisters());
-  }
-
-  @Test
-  public void testReferenceWidth() {
-    DexItemFactory dexItemFactory = new DexItemFactory();
-    ClassTypeLatticeElement referenceType =
-        ClassTypeLatticeElement.create(
-            dexItemFactory.objectType, Nullability.maybeNull(), ImmutableSet.of());
-    assertFalse(referenceType.isSinglePrimitive());
-    assertFalse(referenceType.isWidePrimitive());
-    assertEquals(1, referenceType.requiredRegisters());
-  }
-
-  @Test
-  public void testShortWidth() {
-    assertTrue(TypeLatticeElement.getShort().isSinglePrimitive());
-    assertFalse(TypeLatticeElement.getShort().isWidePrimitive());
-    assertEquals(1, TypeLatticeElement.getShort().requiredRegisters());
-  }
-}
diff --git a/src/test/java/com/android/tools/r8/ir/analysis/type/TypeLatticeTest.java b/src/test/java/com/android/tools/r8/ir/analysis/type/TypeLatticeTest.java
index cac6ef5..5a8a80e 100644
--- a/src/test/java/com/android/tools/r8/ir/analysis/type/TypeLatticeTest.java
+++ b/src/test/java/com/android/tools/r8/ir/analysis/type/TypeLatticeTest.java
@@ -3,10 +3,10 @@
 // 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.ClassTypeLatticeElement.computeLeastUpperBoundOfInterfaces;
-import static com.android.tools.r8.ir.analysis.type.TypeLatticeElement.fromDexType;
-import static com.android.tools.r8.ir.analysis.type.TypeLatticeElement.getBottom;
-import static com.android.tools.r8.ir.analysis.type.TypeLatticeElement.getTop;
+import static com.android.tools.r8.ir.analysis.type.ClassTypeElement.computeLeastUpperBoundOfInterfaces;
+import static com.android.tools.r8.ir.analysis.type.TypeElement.fromDexType;
+import static com.android.tools.r8.ir.analysis.type.TypeElement.getBottom;
+import static com.android.tools.r8.ir.analysis.type.TypeElement.getTop;
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertFalse;
 import static org.junit.Assert.assertTrue;
@@ -63,48 +63,48 @@
     appView = AppView.createForR8(new AppInfoWithSubtyping(application), options);
   }
 
-  private TopTypeLatticeElement top() {
+  private TopTypeElement top() {
     return getTop();
   }
 
-  private BottomTypeLatticeElement bottom() {
+  private BottomTypeElement bottom() {
     return getBottom();
   }
 
-  private SinglePrimitiveTypeLatticeElement single() {
-    return TypeLatticeElement.getSingle();
+  private SinglePrimitiveTypeElement single() {
+    return TypeElement.getSingle();
   }
 
-  private WidePrimitiveTypeLatticeElement wide() {
-    return TypeLatticeElement.getWide();
+  private WidePrimitiveTypeElement wide() {
+    return TypeElement.getWide();
   }
 
-  private TypeLatticeElement element(DexType type) {
+  private TypeElement element(DexType type) {
     return element(type, Nullability.maybeNull());
   }
 
-  private TypeLatticeElement element(DexType type, Nullability nullability) {
-    return TypeLatticeElement.fromDexType(type, nullability, appView);
+  private TypeElement element(DexType type, Nullability nullability) {
+    return TypeElement.fromDexType(type, nullability, appView);
   }
 
-  private ArrayTypeLatticeElement array(int nesting, DexType base) {
-    return (ArrayTypeLatticeElement) element(factory.createArrayType(nesting, base));
+  private ArrayTypeElement array(int nesting, DexType base) {
+    return (ArrayTypeElement) element(factory.createArrayType(nesting, base));
   }
 
-  private TypeLatticeElement join(TypeLatticeElement... elements) {
+  private TypeElement join(TypeElement... elements) {
     assertTrue(elements.length > 1);
-    return TypeLatticeElement.join(Arrays.asList(elements), appView);
+    return TypeElement.join(Arrays.asList(elements), appView);
   }
 
-  private boolean strictlyLessThan(TypeLatticeElement l1, TypeLatticeElement l2) {
+  private boolean strictlyLessThan(TypeElement l1, TypeElement l2) {
     return l1.strictlyLessThan(l2, appView);
   }
 
-  private boolean lessThanOrEqual(TypeLatticeElement l1, TypeLatticeElement l2) {
+  private boolean lessThanOrEqual(TypeElement l1, TypeElement l2) {
     return l1.lessThanOrEqual(l2, appView);
   }
 
-  private boolean lessThanOrEqualUpToNullability(TypeLatticeElement l1, TypeLatticeElement l2) {
+  private boolean lessThanOrEqualUpToNullability(TypeElement l1, TypeElement l2) {
     return l1.lessThanOrEqualUpToNullability(l2, appView);
   }
 
@@ -510,7 +510,7 @@
     assertTrue(strictlyLessThan(
         array(2, factory.objectType),
         array(1, factory.objectType)));
-    assertTrue(strictlyLessThan(TypeLatticeElement.getNull(), array(1, factory.classType)));
+    assertTrue(strictlyLessThan(TypeElement.getNull(), array(1, factory.classType)));
   }
 
   @Test
@@ -535,26 +535,24 @@
   public void testSelfOrderWithoutSubtypingInfo() {
     DexType type = factory.createType("Lmy/Type;");
     appView.withSubtyping().appInfo().registerNewTypeForTesting(type, factory.objectType);
-    TypeLatticeElement nonNullType = fromDexType(type, Nullability.definitelyNotNull(), appView);
-    ReferenceTypeLatticeElement nullableType =
-        nonNullType.asReferenceTypeLatticeElement().getOrCreateVariant(Nullability.maybeNull());
+    TypeElement nonNullType = fromDexType(type, Nullability.definitelyNotNull(), appView);
+    ReferenceTypeElement nullableType =
+        nonNullType.asReferenceType().getOrCreateVariant(Nullability.maybeNull());
     assertTrue(strictlyLessThan(nonNullType, nullableType));
     assertTrue(lessThanOrEqual(nonNullType, nullableType));
     assertFalse(lessThanOrEqual(nullableType, nonNullType));
 
     // Check that the class-type null is also more specific than nullableType.
-    assertTrue(strictlyLessThan(TypeLatticeElement.getNull(), nullableType));
+    assertTrue(strictlyLessThan(TypeElement.getNull(), nullableType));
     assertTrue(
         strictlyLessThan(
-            TypeLatticeElement.getNull(),
-            nullableType.getOrCreateVariant(Nullability.definitelyNull())));
+            TypeElement.getNull(), nullableType.getOrCreateVariant(Nullability.definitelyNull())));
   }
 
   @Test
   public void testNotNullOfNullGivesBottom() {
     assertEquals(
-        Nullability.bottom(),
-        ReferenceTypeLatticeElement.getNull().asMeetWithNotNull().nullability());
+        Nullability.bottom(), ReferenceTypeElement.getNull().asMeetWithNotNull().nullability());
   }
 
   @Test
diff --git a/src/test/java/com/android/tools/r8/ir/analysis/type/UnconstrainedPrimitiveTypeTest.java b/src/test/java/com/android/tools/r8/ir/analysis/type/UnconstrainedPrimitiveTypeTest.java
index d08b323..f6ad83a 100644
--- a/src/test/java/com/android/tools/r8/ir/analysis/type/UnconstrainedPrimitiveTypeTest.java
+++ b/src/test/java/com/android/tools/r8/ir/analysis/type/UnconstrainedPrimitiveTypeTest.java
@@ -86,41 +86,37 @@
 
   @Test
   public void testUnconstrainedSingleWithNoUsers() throws Exception {
-    buildAndCheckIR(
-        "unconstrainedSingleWithNoUsersTest", testInspector(TypeLatticeElement.getInt(), 1));
+    buildAndCheckIR("unconstrainedSingleWithNoUsersTest", testInspector(TypeElement.getInt(), 1));
   }
 
   @Test
   public void testUnconstrainedSingleWithIfUser() throws Exception {
-    buildAndCheckIR(
-        "unconstrainedSingleWithIfUserTest", testInspector(TypeLatticeElement.getInt(), 2));
+    buildAndCheckIR("unconstrainedSingleWithIfUserTest", testInspector(TypeElement.getInt(), 2));
   }
 
   @Test
   public void testUnconstrainedSingleWithIfZeroUser() throws Exception {
     buildAndCheckIR(
-        "unconstrainedSingleWithIfZeroUserTest", testInspector(IntTypeLatticeElement.getInt(), 1));
+        "unconstrainedSingleWithIfZeroUserTest", testInspector(IntTypeElement.getInt(), 1));
   }
 
   @Test
   public void testUnconstrainedWideWithNoUsers() throws Exception {
-    buildAndCheckIR(
-        "unconstrainedWideWithNoUsersTest", testInspector(TypeLatticeElement.getLong(), 1));
+    buildAndCheckIR("unconstrainedWideWithNoUsersTest", testInspector(TypeElement.getLong(), 1));
   }
 
   @Test
   public void testUnconstrainedWideWithIfUser() throws Exception {
-    buildAndCheckIR(
-        "unconstrainedWideWithIfUserTest", testInspector(TypeLatticeElement.getLong(), 2));
+    buildAndCheckIR("unconstrainedWideWithIfUserTest", testInspector(TypeElement.getLong(), 2));
   }
 
   private static Consumer<IRCode> testInspector(
-      TypeLatticeElement expectedType, int expectedNumberOfConstNumberInstructions) {
+      TypeElement expectedType, int expectedNumberOfConstNumberInstructions) {
     return code -> {
       for (Instruction instruction : code.instructions()) {
         if (instruction.isConstNumber()) {
           ConstNumber constNumberInstruction = instruction.asConstNumber();
-          assertEquals(expectedType, constNumberInstruction.outValue().getTypeLattice());
+          assertEquals(expectedType, constNumberInstruction.outValue().getType());
         }
       }
 
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/ConstantRemovalTest.java b/src/test/java/com/android/tools/r8/ir/optimize/ConstantRemovalTest.java
index 607839e..fa6597f 100644
--- a/src/test/java/com/android/tools/r8/ir/optimize/ConstantRemovalTest.java
+++ b/src/test/java/com/android/tools/r8/ir/optimize/ConstantRemovalTest.java
@@ -8,7 +8,7 @@
 import com.android.tools.r8.graph.AppInfo;
 import com.android.tools.r8.graph.AppView;
 import com.android.tools.r8.graph.DexApplication;
-import com.android.tools.r8.ir.analysis.type.TypeLatticeElement;
+import com.android.tools.r8.ir.analysis.type.TypeElement;
 import com.android.tools.r8.ir.code.BasicBlock;
 import com.android.tools.r8.ir.code.ConstNumber;
 import com.android.tools.r8.ir.code.Div;
@@ -76,14 +76,14 @@
     IRMetadata metadata = IRMetadata.unknown();
     Position position = Position.testingPosition();
 
-    Value v3 = new Value(3, TypeLatticeElement.getLong(), null);
+    Value v3 = new Value(3, TypeElement.getLong(), null);
     v3.setNeedsRegister(true);
     new MockLiveIntervals(v3);
     Instruction instruction = new ConstNumber(v3, 0);
     instruction.setPosition(position);
     block.add(instruction, metadata);
 
-    Value v0 = new Value(0, TypeLatticeElement.getLong(), null);
+    Value v0 = new Value(0, TypeElement.getLong(), null);
     v0.setNeedsRegister(true);
     new MockLiveIntervals(v0);
     instruction = new ConstNumber(v0, 10);
@@ -94,14 +94,14 @@
     instruction.setPosition(position);
     block.add(instruction, metadata);
 
-    Value v2 = new Value(2, TypeLatticeElement.getInt(), null);
+    Value v2 = new Value(2, TypeElement.getInt(), null);
     v2.setNeedsRegister(true);
     new MockLiveIntervals(v2);
     instruction = new ConstNumber(v2, 10);
     instruction.setPosition(position);
     block.add(instruction, metadata);
 
-    Value v1 = new Value(1, TypeLatticeElement.getInt(), null);
+    Value v1 = new Value(1, TypeElement.getInt(), null);
     v1.setNeedsRegister(true);
     new MockLiveIntervals(v1);
     instruction = new Move(v1 ,v2);
@@ -112,7 +112,7 @@
     instruction.setPosition(position);
     block.add(instruction, metadata);
 
-    Value v0_2 = new Value(0, TypeLatticeElement.getLong(), null);
+    Value v0_2 = new Value(0, TypeElement.getLong(), null);
     v0_2.setNeedsRegister(true);
     new MockLiveIntervals(v0_2);
     instruction = new ConstNumber(v0_2, 10);
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/TrivialGotoEliminationTest.java b/src/test/java/com/android/tools/r8/ir/optimize/TrivialGotoEliminationTest.java
index 6104883..95557b6 100644
--- a/src/test/java/com/android/tools/r8/ir/optimize/TrivialGotoEliminationTest.java
+++ b/src/test/java/com/android/tools/r8/ir/optimize/TrivialGotoEliminationTest.java
@@ -10,7 +10,7 @@
 import com.android.tools.r8.graph.AppView;
 import com.android.tools.r8.graph.DexApplication;
 import com.android.tools.r8.ir.analysis.type.Nullability;
-import com.android.tools.r8.ir.analysis.type.TypeLatticeElement;
+import com.android.tools.r8.ir.analysis.type.TypeElement;
 import com.android.tools.r8.ir.code.Argument;
 import com.android.tools.r8.ir.code.BasicBlock;
 import com.android.tools.r8.ir.code.ConstNumber;
@@ -59,7 +59,7 @@
     block2.setFilledForTesting();
     BasicBlock block1 = new BasicBlock();
     block1.setNumber(1);
-    Value value = new Value(0, TypeLatticeElement.getInt(), null);
+    Value value = new Value(0, TypeElement.getInt(), null);
     Instruction number = new ConstNumber(value, 0);
     number.setPosition(position);
     block1.add(number, metadata);
@@ -134,7 +134,7 @@
     Value value =
         new Value(
             0,
-            TypeLatticeElement.fromDexType(
+            TypeElement.fromDexType(
                 app.dexItemFactory.throwableType, Nullability.definitelyNotNull(), appView),
             null);
     instruction = new Argument(value, 0, false);
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/callsites/dynamicupperboundtype/InvokeDirectNegativeTest.java b/src/test/java/com/android/tools/r8/ir/optimize/callsites/dynamicupperboundtype/InvokeDirectNegativeTest.java
index 27db8be..349c613 100644
--- a/src/test/java/com/android/tools/r8/ir/optimize/callsites/dynamicupperboundtype/InvokeDirectNegativeTest.java
+++ b/src/test/java/com/android/tools/r8/ir/optimize/callsites/dynamicupperboundtype/InvokeDirectNegativeTest.java
@@ -13,7 +13,7 @@
 import com.android.tools.r8.TestParameters;
 import com.android.tools.r8.TestParametersCollection;
 import com.android.tools.r8.graph.DexEncodedMethod;
-import com.android.tools.r8.ir.analysis.type.TypeLatticeElement;
+import com.android.tools.r8.ir.analysis.type.TypeElement;
 import com.android.tools.r8.ir.optimize.info.CallSiteOptimizationInfo;
 import com.android.tools.r8.utils.codeinspector.ClassSubject;
 import com.android.tools.r8.utils.codeinspector.CodeInspector;
@@ -59,11 +59,10 @@
     assert encodedMethod.method.name.toString().equals("test")
         : "Unexpected revisit: " + encodedMethod.toSourceString();
     CallSiteOptimizationInfo callSiteOptimizationInfo = encodedMethod.getCallSiteOptimizationInfo();
-    TypeLatticeElement upperBoundType = callSiteOptimizationInfo.getDynamicUpperBoundType(1);
+    TypeElement upperBoundType = callSiteOptimizationInfo.getDynamicUpperBoundType(1);
     assert upperBoundType.isDefinitelyNotNull();
     assert upperBoundType.isClassType()
-        && upperBoundType.asClassTypeLatticeElement()
-        .getClassType().toSourceString().endsWith("$Base");
+        && upperBoundType.asClassType().getClassType().toSourceString().endsWith("$Base");
   }
 
   private void inspect(CodeInspector inspector) {
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/callsites/dynamicupperboundtype/InvokeDirectPositiveTest.java b/src/test/java/com/android/tools/r8/ir/optimize/callsites/dynamicupperboundtype/InvokeDirectPositiveTest.java
index 67bb7db..9119e88 100644
--- a/src/test/java/com/android/tools/r8/ir/optimize/callsites/dynamicupperboundtype/InvokeDirectPositiveTest.java
+++ b/src/test/java/com/android/tools/r8/ir/optimize/callsites/dynamicupperboundtype/InvokeDirectPositiveTest.java
@@ -14,7 +14,7 @@
 import com.android.tools.r8.TestParameters;
 import com.android.tools.r8.TestParametersCollection;
 import com.android.tools.r8.graph.DexEncodedMethod;
-import com.android.tools.r8.ir.analysis.type.TypeLatticeElement;
+import com.android.tools.r8.ir.analysis.type.TypeElement;
 import com.android.tools.r8.ir.optimize.info.CallSiteOptimizationInfo;
 import com.android.tools.r8.utils.codeinspector.ClassSubject;
 import com.android.tools.r8.utils.codeinspector.CodeInspector;
@@ -62,7 +62,7 @@
     assert methodName.equals("<init>") || methodName.equals("test")
         : "Unexpected revisit: " + encodedMethod.toSourceString();
     CallSiteOptimizationInfo callSiteOptimizationInfo = encodedMethod.getCallSiteOptimizationInfo();
-    TypeLatticeElement upperBoundType;
+    TypeElement upperBoundType;
     if (methodName.equals("test")) {
       upperBoundType = callSiteOptimizationInfo.getDynamicUpperBoundType(1);
     } else {
@@ -73,8 +73,7 @@
     }
     assert upperBoundType.isDefinitelyNotNull();
     assert upperBoundType.isClassType()
-        && upperBoundType.asClassTypeLatticeElement()
-            .getClassType().toSourceString().endsWith("$Sub1");
+        && upperBoundType.asClassType().getClassType().toSourceString().endsWith("$Sub1");
   }
 
   private void inspect(CodeInspector inspector) {
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/callsites/dynamicupperboundtype/InvokeInterfaceNegativeTest.java b/src/test/java/com/android/tools/r8/ir/optimize/callsites/dynamicupperboundtype/InvokeInterfaceNegativeTest.java
index 1493d73..b341d62 100644
--- a/src/test/java/com/android/tools/r8/ir/optimize/callsites/dynamicupperboundtype/InvokeInterfaceNegativeTest.java
+++ b/src/test/java/com/android/tools/r8/ir/optimize/callsites/dynamicupperboundtype/InvokeInterfaceNegativeTest.java
@@ -14,7 +14,7 @@
 import com.android.tools.r8.TestParameters;
 import com.android.tools.r8.TestParametersCollection;
 import com.android.tools.r8.graph.DexEncodedMethod;
-import com.android.tools.r8.ir.analysis.type.TypeLatticeElement;
+import com.android.tools.r8.ir.analysis.type.TypeElement;
 import com.android.tools.r8.ir.optimize.info.CallSiteOptimizationInfo;
 import com.android.tools.r8.utils.codeinspector.ClassSubject;
 import com.android.tools.r8.utils.codeinspector.CodeInspector;
@@ -64,11 +64,10 @@
     assert encodedMethod.method.name.toString().equals("m")
         : "Unexpected revisit: " + encodedMethod.toSourceString();
     CallSiteOptimizationInfo callSiteOptimizationInfo = encodedMethod.getCallSiteOptimizationInfo();
-    TypeLatticeElement upperBoundType = callSiteOptimizationInfo.getDynamicUpperBoundType(1);
+    TypeElement upperBoundType = callSiteOptimizationInfo.getDynamicUpperBoundType(1);
     assert upperBoundType.isDefinitelyNotNull();
     assert upperBoundType.isClassType()
-        && upperBoundType.asClassTypeLatticeElement()
-            .getClassType().toSourceString().endsWith("$Base");
+        && upperBoundType.asClassType().getClassType().toSourceString().endsWith("$Base");
   }
 
   private void inspect(CodeInspector inspector) {
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/callsites/dynamicupperboundtype/InvokeInterfacePositiveTest.java b/src/test/java/com/android/tools/r8/ir/optimize/callsites/dynamicupperboundtype/InvokeInterfacePositiveTest.java
index 24cad5b..8cc1e12 100644
--- a/src/test/java/com/android/tools/r8/ir/optimize/callsites/dynamicupperboundtype/InvokeInterfacePositiveTest.java
+++ b/src/test/java/com/android/tools/r8/ir/optimize/callsites/dynamicupperboundtype/InvokeInterfacePositiveTest.java
@@ -14,7 +14,7 @@
 import com.android.tools.r8.TestParameters;
 import com.android.tools.r8.TestParametersCollection;
 import com.android.tools.r8.graph.DexEncodedMethod;
-import com.android.tools.r8.ir.analysis.type.TypeLatticeElement;
+import com.android.tools.r8.ir.analysis.type.TypeElement;
 import com.android.tools.r8.ir.optimize.info.CallSiteOptimizationInfo;
 import com.android.tools.r8.utils.codeinspector.ClassSubject;
 import com.android.tools.r8.utils.codeinspector.CodeInspector;
@@ -64,16 +64,14 @@
     assert encodedMethod.method.name.toString().equals("m")
         : "Unexpected revisit: " + encodedMethod.toSourceString();
     CallSiteOptimizationInfo callSiteOptimizationInfo = encodedMethod.getCallSiteOptimizationInfo();
-    TypeLatticeElement upperBoundType = callSiteOptimizationInfo.getDynamicUpperBoundType(1);
+    TypeElement upperBoundType = callSiteOptimizationInfo.getDynamicUpperBoundType(1);
     assert upperBoundType.isDefinitelyNotNull();
     if (encodedMethod.method.holder.toSourceString().endsWith("$A")) {
       assert upperBoundType.isClassType()
-          && upperBoundType.asClassTypeLatticeElement()
-          .getClassType().toSourceString().endsWith("$Sub1");
+          && upperBoundType.asClassType().getClassType().toSourceString().endsWith("$Sub1");
     } else {
       assert upperBoundType.isClassType()
-          && upperBoundType.asClassTypeLatticeElement()
-              .getClassType().toSourceString().endsWith("$Base");
+          && upperBoundType.asClassType().getClassType().toSourceString().endsWith("$Base");
     }
   }
 
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/callsites/dynamicupperboundtype/InvokeStaticNegativeTest.java b/src/test/java/com/android/tools/r8/ir/optimize/callsites/dynamicupperboundtype/InvokeStaticNegativeTest.java
index 6291f4d..d98e911 100644
--- a/src/test/java/com/android/tools/r8/ir/optimize/callsites/dynamicupperboundtype/InvokeStaticNegativeTest.java
+++ b/src/test/java/com/android/tools/r8/ir/optimize/callsites/dynamicupperboundtype/InvokeStaticNegativeTest.java
@@ -12,7 +12,7 @@
 import com.android.tools.r8.TestParameters;
 import com.android.tools.r8.TestParametersCollection;
 import com.android.tools.r8.graph.DexEncodedMethod;
-import com.android.tools.r8.ir.analysis.type.TypeLatticeElement;
+import com.android.tools.r8.ir.analysis.type.TypeElement;
 import com.android.tools.r8.ir.optimize.info.CallSiteOptimizationInfo;
 import com.android.tools.r8.utils.codeinspector.ClassSubject;
 import com.android.tools.r8.utils.codeinspector.CodeInspector;
@@ -56,11 +56,10 @@
     assert encodedMethod.method.name.toString().equals("test")
         : "Unexpected revisit: " + encodedMethod.toSourceString();
     CallSiteOptimizationInfo callSiteOptimizationInfo = encodedMethod.getCallSiteOptimizationInfo();
-    TypeLatticeElement upperBoundType = callSiteOptimizationInfo.getDynamicUpperBoundType(0);
+    TypeElement upperBoundType = callSiteOptimizationInfo.getDynamicUpperBoundType(0);
     assert upperBoundType.isDefinitelyNotNull();
     assert upperBoundType.isClassType()
-        && upperBoundType.asClassTypeLatticeElement()
-            .getClassType().toSourceString().endsWith("$Base");
+        && upperBoundType.asClassType().getClassType().toSourceString().endsWith("$Base");
   }
 
   private void inspect(CodeInspector inspector) {
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/callsites/dynamicupperboundtype/InvokeStaticPositiveTest.java b/src/test/java/com/android/tools/r8/ir/optimize/callsites/dynamicupperboundtype/InvokeStaticPositiveTest.java
index f31919a..d014d5a 100644
--- a/src/test/java/com/android/tools/r8/ir/optimize/callsites/dynamicupperboundtype/InvokeStaticPositiveTest.java
+++ b/src/test/java/com/android/tools/r8/ir/optimize/callsites/dynamicupperboundtype/InvokeStaticPositiveTest.java
@@ -13,7 +13,7 @@
 import com.android.tools.r8.TestParameters;
 import com.android.tools.r8.TestParametersCollection;
 import com.android.tools.r8.graph.DexEncodedMethod;
-import com.android.tools.r8.ir.analysis.type.TypeLatticeElement;
+import com.android.tools.r8.ir.analysis.type.TypeElement;
 import com.android.tools.r8.ir.optimize.info.CallSiteOptimizationInfo;
 import com.android.tools.r8.utils.codeinspector.ClassSubject;
 import com.android.tools.r8.utils.codeinspector.CodeInspector;
@@ -63,11 +63,10 @@
     // TODO(b/139246447): should avoid visiting <init>, which is trivial, default init!
     // For testing purpose, `Base` is not merged and kept. The system correctly caught that, when
     // the default initializer is invoked, the receiver had a refined type, `Sub1`.
-    TypeLatticeElement upperBoundType = callSiteOptimizationInfo.getDynamicUpperBoundType(0);
+    TypeElement upperBoundType = callSiteOptimizationInfo.getDynamicUpperBoundType(0);
     assert upperBoundType.isDefinitelyNotNull();
     assert upperBoundType.isClassType()
-        && upperBoundType.asClassTypeLatticeElement()
-            .getClassType().toSourceString().endsWith("$Sub1");
+        && upperBoundType.asClassType().getClassType().toSourceString().endsWith("$Sub1");
   }
 
   private void inspect(CodeInspector inspector) {
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/callsites/dynamicupperboundtype/InvokeVirtualNegativeTest.java b/src/test/java/com/android/tools/r8/ir/optimize/callsites/dynamicupperboundtype/InvokeVirtualNegativeTest.java
index cc0a69f..c2fffc1 100644
--- a/src/test/java/com/android/tools/r8/ir/optimize/callsites/dynamicupperboundtype/InvokeVirtualNegativeTest.java
+++ b/src/test/java/com/android/tools/r8/ir/optimize/callsites/dynamicupperboundtype/InvokeVirtualNegativeTest.java
@@ -14,7 +14,7 @@
 import com.android.tools.r8.TestParameters;
 import com.android.tools.r8.TestParametersCollection;
 import com.android.tools.r8.graph.DexEncodedMethod;
-import com.android.tools.r8.ir.analysis.type.TypeLatticeElement;
+import com.android.tools.r8.ir.analysis.type.TypeElement;
 import com.android.tools.r8.ir.optimize.info.CallSiteOptimizationInfo;
 import com.android.tools.r8.utils.codeinspector.ClassSubject;
 import com.android.tools.r8.utils.codeinspector.CodeInspector;
@@ -63,11 +63,10 @@
         : "Unexpected revisit: " + encodedMethod.toSourceString();
     CallSiteOptimizationInfo callSiteOptimizationInfo = encodedMethod.getCallSiteOptimizationInfo();
     if (methodName.equals("m")) {
-      TypeLatticeElement upperBoundType = callSiteOptimizationInfo.getDynamicUpperBoundType(1);
+      TypeElement upperBoundType = callSiteOptimizationInfo.getDynamicUpperBoundType(1);
       assert upperBoundType.isDefinitelyNotNull();
       assert upperBoundType.isClassType()
-          && upperBoundType.asClassTypeLatticeElement()
-              .getClassType().toSourceString().endsWith("$Base");
+          && upperBoundType.asClassType().getClassType().toSourceString().endsWith("$Base");
     } else {
       assert methodName.equals("test");
       assert callSiteOptimizationInfo.getDynamicUpperBoundType(0).isDefinitelyNotNull();
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/callsites/dynamicupperboundtype/InvokeVirtualPositiveTest.java b/src/test/java/com/android/tools/r8/ir/optimize/callsites/dynamicupperboundtype/InvokeVirtualPositiveTest.java
index f49d16e..22a4faa 100644
--- a/src/test/java/com/android/tools/r8/ir/optimize/callsites/dynamicupperboundtype/InvokeVirtualPositiveTest.java
+++ b/src/test/java/com/android/tools/r8/ir/optimize/callsites/dynamicupperboundtype/InvokeVirtualPositiveTest.java
@@ -14,7 +14,7 @@
 import com.android.tools.r8.TestParameters;
 import com.android.tools.r8.TestParametersCollection;
 import com.android.tools.r8.graph.DexEncodedMethod;
-import com.android.tools.r8.ir.analysis.type.TypeLatticeElement;
+import com.android.tools.r8.ir.analysis.type.TypeElement;
 import com.android.tools.r8.ir.optimize.info.CallSiteOptimizationInfo;
 import com.android.tools.r8.utils.codeinspector.ClassSubject;
 import com.android.tools.r8.utils.codeinspector.CodeInspector;
@@ -62,7 +62,7 @@
     assert methodName.equals("<init>") || methodName.equals("m")
         : "Unexpected revisit: " + encodedMethod.toSourceString();
     CallSiteOptimizationInfo callSiteOptimizationInfo = encodedMethod.getCallSiteOptimizationInfo();
-    TypeLatticeElement upperBoundType;
+    TypeElement upperBoundType;
     if (methodName.equals("m")) {
       upperBoundType = callSiteOptimizationInfo.getDynamicUpperBoundType(1);
     } else {
@@ -73,8 +73,7 @@
     }
     assert upperBoundType.isDefinitelyNotNull();
     assert upperBoundType.isClassType()
-        && upperBoundType.asClassTypeLatticeElement()
-            .getClassType().toSourceString().endsWith("$Sub1");
+        && upperBoundType.asClassType().getClassType().toSourceString().endsWith("$Sub1");
   }
 
   private void inspect(CodeInspector inspector) {
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/callsites/nullability/InvokeInterfaceNegativeTest.java b/src/test/java/com/android/tools/r8/ir/optimize/callsites/nullability/InvokeInterfaceNegativeTest.java
index 3fde0f1..e7c5597 100644
--- a/src/test/java/com/android/tools/r8/ir/optimize/callsites/nullability/InvokeInterfaceNegativeTest.java
+++ b/src/test/java/com/android/tools/r8/ir/optimize/callsites/nullability/InvokeInterfaceNegativeTest.java
@@ -14,7 +14,7 @@
 import com.android.tools.r8.TestParameters;
 import com.android.tools.r8.TestParametersCollection;
 import com.android.tools.r8.graph.DexEncodedMethod;
-import com.android.tools.r8.ir.analysis.type.TypeLatticeElement;
+import com.android.tools.r8.ir.analysis.type.TypeElement;
 import com.android.tools.r8.ir.optimize.info.CallSiteOptimizationInfo;
 import com.android.tools.r8.utils.codeinspector.ClassSubject;
 import com.android.tools.r8.utils.codeinspector.CodeInspector;
@@ -64,11 +64,10 @@
     assert encodedMethod.method.name.toString().equals("m")
         : "Unexpected revisit: " + encodedMethod.toSourceString();
     CallSiteOptimizationInfo callSiteOptimizationInfo = encodedMethod.getCallSiteOptimizationInfo();
-    TypeLatticeElement upperBoundType = callSiteOptimizationInfo.getDynamicUpperBoundType(1);
+    TypeElement upperBoundType = callSiteOptimizationInfo.getDynamicUpperBoundType(1);
     assert upperBoundType.isNullable();
     assert upperBoundType.isClassType()
-        && upperBoundType.asClassTypeLatticeElement()
-            .getClassType().toSourceString().endsWith("$A");
+        && upperBoundType.asClassType().getClassType().toSourceString().endsWith("$A");
   }
 
   private void inspect(CodeInspector inspector) {
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/callsites/nullability/InvokeVirtualNegativeTest.java b/src/test/java/com/android/tools/r8/ir/optimize/callsites/nullability/InvokeVirtualNegativeTest.java
index e36d2e9..ad7b3ae 100644
--- a/src/test/java/com/android/tools/r8/ir/optimize/callsites/nullability/InvokeVirtualNegativeTest.java
+++ b/src/test/java/com/android/tools/r8/ir/optimize/callsites/nullability/InvokeVirtualNegativeTest.java
@@ -14,7 +14,7 @@
 import com.android.tools.r8.TestParameters;
 import com.android.tools.r8.TestParametersCollection;
 import com.android.tools.r8.graph.DexEncodedMethod;
-import com.android.tools.r8.ir.analysis.type.TypeLatticeElement;
+import com.android.tools.r8.ir.analysis.type.TypeElement;
 import com.android.tools.r8.ir.optimize.info.CallSiteOptimizationInfo;
 import com.android.tools.r8.utils.codeinspector.ClassSubject;
 import com.android.tools.r8.utils.codeinspector.CodeInspector;
@@ -63,11 +63,10 @@
         : "Unexpected revisit: " + encodedMethod.toSourceString();
     CallSiteOptimizationInfo callSiteOptimizationInfo = encodedMethod.getCallSiteOptimizationInfo();
     if (methodName.equals("m")) {
-      TypeLatticeElement upperBoundType = callSiteOptimizationInfo.getDynamicUpperBoundType(1);
+      TypeElement upperBoundType = callSiteOptimizationInfo.getDynamicUpperBoundType(1);
       assert upperBoundType.isNullable();
       assert upperBoundType.isClassType()
-          && upperBoundType.asClassTypeLatticeElement()
-              .getClassType().equals(encodedMethod.method.holder);
+          && upperBoundType.asClassType().getClassType().equals(encodedMethod.method.holder);
     } else {
       assert methodName.equals("test");
       assert callSiteOptimizationInfo.getDynamicUpperBoundType(0).isDefinitelyNotNull();
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/callsites/nullability/InvokeVirtualPositiveTest.java b/src/test/java/com/android/tools/r8/ir/optimize/callsites/nullability/InvokeVirtualPositiveTest.java
index e05b279..0551286 100644
--- a/src/test/java/com/android/tools/r8/ir/optimize/callsites/nullability/InvokeVirtualPositiveTest.java
+++ b/src/test/java/com/android/tools/r8/ir/optimize/callsites/nullability/InvokeVirtualPositiveTest.java
@@ -14,7 +14,7 @@
 import com.android.tools.r8.TestParameters;
 import com.android.tools.r8.TestParametersCollection;
 import com.android.tools.r8.graph.DexEncodedMethod;
-import com.android.tools.r8.ir.analysis.type.TypeLatticeElement;
+import com.android.tools.r8.ir.analysis.type.TypeElement;
 import com.android.tools.r8.ir.optimize.info.CallSiteOptimizationInfo;
 import com.android.tools.r8.utils.codeinspector.ClassSubject;
 import com.android.tools.r8.utils.codeinspector.CodeInspector;
@@ -61,10 +61,9 @@
     assert encodedMethod.method.name.toString().equals("m")
         : "Unexpected revisit: " + encodedMethod.toSourceString();
     CallSiteOptimizationInfo callSiteOptimizationInfo = encodedMethod.getCallSiteOptimizationInfo();
-    TypeLatticeElement upperBoundType = callSiteOptimizationInfo.getDynamicUpperBoundType(1);
+    TypeElement upperBoundType = callSiteOptimizationInfo.getDynamicUpperBoundType(1);
     assert upperBoundType.isClassType()
-        && upperBoundType.asClassTypeLatticeElement()
-            .getClassType().toSourceString().endsWith("$A");
+        && upperBoundType.asClassType().getClassType().toSourceString().endsWith("$A");
     if (encodedMethod.method.holder.toSourceString().endsWith("$A")) {
       assert upperBoundType.isDefinitelyNotNull();
     } else {
diff --git a/src/test/java/com/android/tools/r8/ir/regalloc/IdenticalAfterRegisterAllocationTest.java b/src/test/java/com/android/tools/r8/ir/regalloc/IdenticalAfterRegisterAllocationTest.java
index 00ee4e7..9e9b1c4 100644
--- a/src/test/java/com/android/tools/r8/ir/regalloc/IdenticalAfterRegisterAllocationTest.java
+++ b/src/test/java/com/android/tools/r8/ir/regalloc/IdenticalAfterRegisterAllocationTest.java
@@ -6,7 +6,7 @@
 import static org.junit.Assert.assertFalse;
 import static org.junit.Assert.assertTrue;
 
-import com.android.tools.r8.ir.analysis.type.TypeLatticeElement;
+import com.android.tools.r8.ir.analysis.type.TypeElement;
 import com.android.tools.r8.ir.code.Add;
 import com.android.tools.r8.ir.code.BasicBlock;
 import com.android.tools.r8.ir.code.ConstNumber;
@@ -65,13 +65,13 @@
   @Test
   public void equalityOfConstantOperands() {
     RegisterAllocator allocator = new MockRegisterAllocator();
-    Value value0 = new Value(0, TypeLatticeElement.getInt(), null);
+    Value value0 = new Value(0, TypeElement.getInt(), null);
     ConstNumber const0 = new ConstNumber(value0, 0);
-    Value value1 = new Value(1, TypeLatticeElement.getInt(), null);
+    Value value1 = new Value(1, TypeElement.getInt(), null);
     ConstNumber const1 = new ConstNumber(value1, 1);
-    Value value2 = new Value(2, TypeLatticeElement.getInt(), null);
+    Value value2 = new Value(2, TypeElement.getInt(), null);
     ConstNumber const2 = new ConstNumber(value2, 2);
-    Value value3 = new Value(2, TypeLatticeElement.getInt(), null);
+    Value value3 = new Value(2, TypeElement.getInt(), null);
     Add add0 = new Add(NumericType.INT, value3, value0, value1);
     add0.setPosition(Position.none());
     Add add1 = new Add(NumericType.INT, value3, value0, value2);
diff --git a/src/test/java/com/android/tools/r8/ir/regalloc/RegisterMoveSchedulerTest.java b/src/test/java/com/android/tools/r8/ir/regalloc/RegisterMoveSchedulerTest.java
index 911479e..e570b8c 100644
--- a/src/test/java/com/android/tools/r8/ir/regalloc/RegisterMoveSchedulerTest.java
+++ b/src/test/java/com/android/tools/r8/ir/regalloc/RegisterMoveSchedulerTest.java
@@ -13,7 +13,7 @@
 import com.android.tools.r8.graph.DexField;
 import com.android.tools.r8.graph.DexType;
 import com.android.tools.r8.ir.analysis.type.Nullability;
-import com.android.tools.r8.ir.analysis.type.TypeLatticeElement;
+import com.android.tools.r8.ir.analysis.type.TypeElement;
 import com.android.tools.r8.ir.code.BasicBlock;
 import com.android.tools.r8.ir.code.IRCode;
 import com.android.tools.r8.ir.code.Instruction;
@@ -157,8 +157,8 @@
     CollectMovesIterator moves = new CollectMovesIterator();
     int temp = 42;
     RegisterMoveScheduler scheduler = new RegisterMoveScheduler(moves, temp);
-    scheduler.addMove(new RegisterMove(0, 1, TypeLatticeElement.getInt()));
-    scheduler.addMove(new RegisterMove(1, 0, TypeLatticeElement.getInt()));
+    scheduler.addMove(new RegisterMove(0, 1, TypeElement.getInt()));
+    scheduler.addMove(new RegisterMove(1, 0, TypeElement.getInt()));
     scheduler.schedule();
     assertEquals(3, moves.size());
     Move tempMove = moves.get(0);
@@ -182,8 +182,8 @@
     CollectMovesIterator moves = new CollectMovesIterator();
     int temp = 42;
     RegisterMoveScheduler scheduler = new RegisterMoveScheduler(moves, temp);
-    scheduler.addMove(new RegisterMove(0, 2, TypeLatticeElement.getLong()));
-    scheduler.addMove(new RegisterMove(2, 0, TypeLatticeElement.getLong()));
+    scheduler.addMove(new RegisterMove(0, 2, TypeElement.getLong()));
+    scheduler.addMove(new RegisterMove(2, 0, TypeElement.getLong()));
     scheduler.schedule();
     assertEquals(3, moves.size());
     Move tempMove = moves.get(0);
@@ -207,8 +207,8 @@
     CollectMovesIterator moves = new CollectMovesIterator();
     int temp = 42;
     RegisterMoveScheduler scheduler = new RegisterMoveScheduler(moves, temp);
-    scheduler.addMove(new RegisterMove(1, 0, TypeLatticeElement.getLong()));
-    scheduler.addMove(new RegisterMove(0, 1, TypeLatticeElement.getInt()));
+    scheduler.addMove(new RegisterMove(1, 0, TypeElement.getLong()));
+    scheduler.addMove(new RegisterMove(0, 1, TypeElement.getInt()));
     scheduler.schedule();
     assertEquals(3, moves.size());
     Move tempMove = moves.get(0).asMove();
@@ -232,8 +232,8 @@
     CollectMovesIterator moves = new CollectMovesIterator();
     int temp = 42;
     RegisterMoveScheduler scheduler = new RegisterMoveScheduler(moves, temp);
-    scheduler.addMove(new RegisterMove(0, 1, TypeLatticeElement.getInt()));
-    scheduler.addMove(new RegisterMove(1, 0, TypeLatticeElement.getLong()));
+    scheduler.addMove(new RegisterMove(0, 1, TypeElement.getInt()));
+    scheduler.addMove(new RegisterMove(1, 0, TypeElement.getLong()));
     scheduler.schedule();
     assertEquals(3, moves.size());
     Move tempMove = moves.get(0).asMove();
@@ -257,8 +257,8 @@
     CollectMovesIterator moves = new CollectMovesIterator();
     int temp = 42;
     RegisterMoveScheduler scheduler = new RegisterMoveScheduler(moves, temp);
-    scheduler.addMove(new RegisterMove(0, 1, TypeLatticeElement.getLong()));
-    scheduler.addMove(new RegisterMove(2, 3, TypeLatticeElement.getLong()));
+    scheduler.addMove(new RegisterMove(0, 1, TypeElement.getLong()));
+    scheduler.addMove(new RegisterMove(2, 3, TypeElement.getLong()));
     scheduler.schedule();
     assertEquals(2, moves.size());
     Move firstMove = moves.get(0).asMove();
@@ -276,8 +276,8 @@
     CollectMovesIterator moves = new CollectMovesIterator();
     int temp = 42;
     RegisterMoveScheduler scheduler = new RegisterMoveScheduler(moves, temp);
-    scheduler.addMove(new RegisterMove(2, 1, TypeLatticeElement.getLong()));
-    scheduler.addMove(new RegisterMove(0, 3, TypeLatticeElement.getLong()));
+    scheduler.addMove(new RegisterMove(2, 1, TypeElement.getLong()));
+    scheduler.addMove(new RegisterMove(0, 3, TypeElement.getLong()));
     scheduler.schedule();
     assertEquals(3, moves.size());
     Move firstMove = moves.get(0).asMove();
@@ -299,9 +299,9 @@
     CollectMovesIterator moves = new CollectMovesIterator();
     int temp = 42;
     RegisterMoveScheduler scheduler = new RegisterMoveScheduler(moves, temp);
-    scheduler.addMove(new RegisterMove(2, 0, TypeLatticeElement.getLong()));
-    scheduler.addMove(new RegisterMove(0, 2, TypeLatticeElement.getInt()));
-    scheduler.addMove(new RegisterMove(1, 3, TypeLatticeElement.getInt()));
+    scheduler.addMove(new RegisterMove(2, 0, TypeElement.getLong()));
+    scheduler.addMove(new RegisterMove(0, 2, TypeElement.getInt()));
+    scheduler.addMove(new RegisterMove(1, 3, TypeElement.getInt()));
     scheduler.schedule();
     assertEquals(4, moves.size());
     Move firstMove = moves.get(0).asMove();
@@ -323,8 +323,8 @@
     CollectMovesIterator moves = new CollectMovesIterator();
     int temp = 42;
     RegisterMoveScheduler scheduler = new RegisterMoveScheduler(moves, temp);
-    scheduler.addMove(new RegisterMove(0, 2, TypeLatticeElement.getLong()));
-    scheduler.addMove(new RegisterMove(3, 0, TypeLatticeElement.getInt()));
+    scheduler.addMove(new RegisterMove(0, 2, TypeElement.getLong()));
+    scheduler.addMove(new RegisterMove(3, 0, TypeElement.getInt()));
     scheduler.schedule();
     assertEquals(3, moves.size());
     Move firstMove = moves.get(0).asMove();
@@ -346,10 +346,10 @@
     CollectMovesIterator moves = new CollectMovesIterator();
     int temp = 42;
     RegisterMoveScheduler scheduler = new RegisterMoveScheduler(moves, temp);
-    scheduler.addMove(new RegisterMove(14, 11, TypeLatticeElement.getLong()));
-    scheduler.addMove(new RegisterMove(16, 13, TypeLatticeElement.getLong()));
-    scheduler.addMove(new RegisterMove(10, 17, TypeLatticeElement.getLong()));
-    scheduler.addMove(new RegisterMove(12, 19, TypeLatticeElement.getLong()));
+    scheduler.addMove(new RegisterMove(14, 11, TypeElement.getLong()));
+    scheduler.addMove(new RegisterMove(16, 13, TypeElement.getLong()));
+    scheduler.addMove(new RegisterMove(10, 17, TypeElement.getLong()));
+    scheduler.addMove(new RegisterMove(12, 19, TypeElement.getLong()));
     scheduler.schedule();
     // In order to resolve these moves, we need to use two temporary register pairs.
     assertEquals(6, moves.size());
@@ -365,16 +365,15 @@
     InternalOptions options = new InternalOptions();
     AppView<AppInfo> appInfo =
         AppView.createForD8(new AppInfo(DexApplication.builder(options, null).build()), options);
-    TypeLatticeElement objectType =
-        TypeLatticeElement.fromDexType(
-            options.itemFactory.objectType, Nullability.maybeNull(), appInfo);
+    TypeElement objectType =
+        TypeElement.fromDexType(options.itemFactory.objectType, Nullability.maybeNull(), appInfo);
     CollectMovesIterator moves = new CollectMovesIterator();
     int temp = 42;
     RegisterMoveScheduler scheduler = new RegisterMoveScheduler(moves, temp);
-    scheduler.addMove(new RegisterMove(26, 22, TypeLatticeElement.getInt()));
-    scheduler.addMove(new RegisterMove(29, 24, TypeLatticeElement.getLong()));
+    scheduler.addMove(new RegisterMove(26, 22, TypeElement.getInt()));
+    scheduler.addMove(new RegisterMove(29, 24, TypeElement.getLong()));
     scheduler.addMove(new RegisterMove(28, 26, objectType));
-    scheduler.addMove(new RegisterMove(23, 28, TypeLatticeElement.getLong()));
+    scheduler.addMove(new RegisterMove(23, 28, TypeElement.getLong()));
     scheduler.schedule();
     // For this example we need recursive unblocking.
     assertEquals(6, moves.size());
diff --git a/src/test/java/com/android/tools/r8/ir/regalloc/Regress68656641.java b/src/test/java/com/android/tools/r8/ir/regalloc/Regress68656641.java
index 4700a44..2c1d228 100644
--- a/src/test/java/com/android/tools/r8/ir/regalloc/Regress68656641.java
+++ b/src/test/java/com/android/tools/r8/ir/regalloc/Regress68656641.java
@@ -6,7 +6,7 @@
 import com.android.tools.r8.graph.AppInfo;
 import com.android.tools.r8.graph.AppView;
 import com.android.tools.r8.graph.DexApplication;
-import com.android.tools.r8.ir.analysis.type.TypeLatticeElement;
+import com.android.tools.r8.ir.analysis.type.TypeElement;
 import com.android.tools.r8.ir.code.IRCode;
 import com.android.tools.r8.ir.code.Value;
 import com.android.tools.r8.smali.SmaliBuilder;
@@ -62,20 +62,18 @@
     MyRegisterAllocator allocator = new MyRegisterAllocator(appView, code);
     // Setup live an inactive live interval with ranges [0, 10[ and [20, 30[ with only
     // uses in the first interval and which is linked to another interval.
-    LiveIntervals inactiveIntervals =
-        new LiveIntervals(new Value(0, TypeLatticeElement.getInt(), null));
+    LiveIntervals inactiveIntervals = new LiveIntervals(new Value(0, TypeElement.getInt(), null));
     inactiveIntervals.addRange(new LiveRange(0, 10));
     inactiveIntervals.addUse(new LiveIntervalsUse(0, 10));
     inactiveIntervals.addUse(new LiveIntervalsUse(4, 10));
     inactiveIntervals.addRange(new LiveRange(20, 30));
     inactiveIntervals.setRegister(0);
-    LiveIntervals linked = new LiveIntervals(new Value(1, TypeLatticeElement.getInt(), null));
+    LiveIntervals linked = new LiveIntervals(new Value(1, TypeElement.getInt(), null));
     linked.setRegister(1);
     inactiveIntervals.link(linked);
     allocator.addInactiveIntervals(inactiveIntervals);
     // Setup an unhandled interval that overlaps the inactive interval.
-    LiveIntervals unhandledIntervals =
-        new LiveIntervals(new Value(2, TypeLatticeElement.getInt(), null));
+    LiveIntervals unhandledIntervals = new LiveIntervals(new Value(2, TypeElement.getInt(), null));
     unhandledIntervals.addRange(new LiveRange(12, 24));
     // Split the overlapping inactive intervals and check that after the split, the second
     // part of the inactive interval is unhandled and will therefore get a new register
diff --git a/src/test/java/com/android/tools/r8/jsr45/JSR45Tests.java b/src/test/java/com/android/tools/r8/jsr45/JSR45Tests.java
index a6fd390..08a4ae9 100644
--- a/src/test/java/com/android/tools/r8/jsr45/JSR45Tests.java
+++ b/src/test/java/com/android/tools/r8/jsr45/JSR45Tests.java
@@ -10,7 +10,6 @@
 import com.android.tools.r8.R8Command;
 import com.android.tools.r8.ToolHelper;
 import com.android.tools.r8.graph.DexAnnotationElement;
-import com.android.tools.r8.graph.DexValue.DexValueString;
 import com.android.tools.r8.utils.AndroidApiLevel;
 import com.android.tools.r8.utils.AndroidApp;
 import com.android.tools.r8.utils.AndroidAppConsumers;
@@ -30,7 +29,6 @@
 import org.junit.rules.TemporaryFolder;
 import org.objectweb.asm.ClassReader;
 import org.objectweb.asm.ClassVisitor;
-import org.objectweb.asm.Opcodes;
 
 public class JSR45Tests {
 
@@ -158,10 +156,10 @@
     Assert.assertNotNull(annotationElement);
     Assert.assertTrue(annotationElement.length == 1);
     Assert.assertEquals("value", annotationElement[0].name.toString());
-    Assert.assertTrue(annotationElement[0].value instanceof DexValueString);
+    Assert.assertTrue(annotationElement[0].value.isDexValueString());
     Assert.assertEquals(
         sourceDebugExtensionReader.debugSourceExtension,
-        ((DexValueString) annotationElement[0].value).value.toSourceString());
+        annotationElement[0].value.asDexValueString().value.toSourceString());
   }
 
   private String getGeneratedProguardMap() throws IOException {
diff --git a/src/test/java/com/android/tools/r8/kotlin/metadata/MetadataRewriteInTypeAliasTest.java b/src/test/java/com/android/tools/r8/kotlin/metadata/MetadataRewriteInTypeAliasTest.java
index 094362c..734ce50 100644
--- a/src/test/java/com/android/tools/r8/kotlin/metadata/MetadataRewriteInTypeAliasTest.java
+++ b/src/test/java/com/android/tools/r8/kotlin/metadata/MetadataRewriteInTypeAliasTest.java
@@ -33,7 +33,24 @@
 @RunWith(Parameterized.class)
 public class MetadataRewriteInTypeAliasTest extends KotlinMetadataTestBase {
   private static final String EXPECTED =
-      StringUtils.lines("Impl::foo", "Program::foo", "true", "42");
+      StringUtils.lines(
+          "Impl::foo",
+          "Program::foo",
+          "true",
+          "42",
+          "42",
+          "42",
+          "42",
+          "42",
+          "42",
+          "42",
+          "true",
+          "42",
+          "1",
+          "42",
+          "42",
+          "1",
+          "Hello World!");
 
   private final TestParameters parameters;
 
@@ -57,7 +74,9 @@
     for (KotlinTargetVersion targetVersion : KotlinTargetVersion.values()) {
       Path typeAliasLibJar =
           kotlinc(KOTLINC, targetVersion)
-              .addSourceFiles(getKotlinFileInTest(typeAliasLibFolder, "lib"))
+              .addSourceFiles(
+                  getKotlinFileInTest(typeAliasLibFolder, "lib"),
+                  getKotlinFileInTest(typeAliasLibFolder, "lib_ext"))
               .compile();
       typeAliasLibJarMap.put(targetVersion, typeAliasLibJar);
     }
diff --git a/src/test/java/com/android/tools/r8/kotlin/metadata/MetadataRewriteInTypeArgumentsTest.java b/src/test/java/com/android/tools/r8/kotlin/metadata/MetadataRewriteInTypeArgumentsTest.java
new file mode 100644
index 0000000..07fbdd9
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/kotlin/metadata/MetadataRewriteInTypeArgumentsTest.java
@@ -0,0 +1,99 @@
+// 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.kotlin.metadata;
+
+import static com.android.tools.r8.KotlinCompilerTool.KOTLINC;
+import static org.hamcrest.CoreMatchers.containsString;
+import static org.hamcrest.MatcherAssert.assertThat;
+import static org.junit.Assert.assertNotEquals;
+
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.ToolHelper;
+import com.android.tools.r8.ToolHelper.KotlinTargetVersion;
+import com.android.tools.r8.ToolHelper.ProcessResult;
+import com.android.tools.r8.utils.StringUtils;
+import java.nio.file.Path;
+import java.util.Collection;
+import java.util.HashMap;
+import java.util.Map;
+import org.junit.BeforeClass;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+
+@RunWith(Parameterized.class)
+public class MetadataRewriteInTypeArgumentsTest extends KotlinMetadataTestBase {
+  private static final String EXPECTED =
+      StringUtils.lines("42", "1", "42", "42", "1", "42", "42", "42", "1", "42");
+
+  private final TestParameters parameters;
+
+  @Parameterized.Parameters(name = "{0} target: {1}")
+  public static Collection<Object[]> data() {
+    return buildParameters(
+        getTestParameters().withCfRuntimes().build(), KotlinTargetVersion.values());
+  }
+
+  public MetadataRewriteInTypeArgumentsTest(
+      TestParameters parameters, KotlinTargetVersion targetVersion) {
+    super(targetVersion);
+    this.parameters = parameters;
+  }
+
+  private static final Map<KotlinTargetVersion, Path> jarMap = new HashMap<>();
+
+  @BeforeClass
+  public static void createLibJar() throws Exception {
+    String typeAliasLibFolder = PKG_PREFIX + "/typeargument_lib";
+    for (KotlinTargetVersion targetVersion : KotlinTargetVersion.values()) {
+      Path typeAliasLibJar =
+          kotlinc(KOTLINC, targetVersion)
+              .addSourceFiles(getKotlinFileInTest(typeAliasLibFolder, "lib"))
+              .compile();
+      jarMap.put(targetVersion, typeAliasLibJar);
+    }
+  }
+
+  @Test
+  public void smokeTest() throws Exception {
+    Path libJar = jarMap.get(targetVersion);
+
+    Path output =
+        kotlinc(parameters.getRuntime().asCf(), KOTLINC, targetVersion)
+            .addClasspathFiles(libJar)
+            .addSourceFiles(getKotlinFileInTest(PKG_PREFIX + "/typeargument_app", "main"))
+            .setOutputPath(temp.newFolder().toPath())
+            .compile();
+
+    testForJvm()
+        .addRunClasspathFiles(ToolHelper.getKotlinStdlibJar(), libJar)
+        .addClasspath(output)
+        .run(parameters.getRuntime(), PKG + ".typeargument_app.MainKt")
+        .assertSuccessWithOutput(EXPECTED);
+  }
+
+  @Test
+  public void testMetadataInTypeAlias_renamed() throws Exception {
+    Path libJar =
+        testForR8(parameters.getBackend())
+            .addProgramFiles(jarMap.get(targetVersion))
+            .addKeepAllClassesRule()
+            .compile()
+            // TODO(b/151925520): Add inspections when program compiles
+            .writeToZip();
+
+    ProcessResult kotlinTestCompileResult =
+        kotlinc(parameters.getRuntime().asCf(), KOTLINC, targetVersion)
+            .addClasspathFiles(libJar)
+            .addSourceFiles(getKotlinFileInTest(PKG_PREFIX + "/typeargument_app", "main"))
+            .setOutputPath(temp.newFolder().toPath())
+            // TODO(b/151925520): update to just .compile() once fixed.
+            .compileRaw();
+    // TODO(b/151925520): should be able to compile!
+    assertNotEquals(0, kotlinTestCompileResult.exitCode);
+    assertThat(
+        kotlinTestCompileResult.stderr,
+        containsString("no type arguments expected for constructor Invariant()"));
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/kotlin/metadata/typealias_app/main.kt b/src/test/java/com/android/tools/r8/kotlin/metadata/typealias_app/main.kt
index f0ddbf7..396c2e4 100644
--- a/src/test/java/com/android/tools/r8/kotlin/metadata/typealias_app/main.kt
+++ b/src/test/java/com/android/tools/r8/kotlin/metadata/typealias_app/main.kt
@@ -4,8 +4,29 @@
 package com.android.tools.r8.kotlin.metadata.typealias_app
 
 import com.android.tools.r8.kotlin.metadata.typealias_lib.API
+import com.android.tools.r8.kotlin.metadata.typealias_lib.AlphaNaming
+import com.android.tools.r8.kotlin.metadata.typealias_lib.Arr
+import com.android.tools.r8.kotlin.metadata.typealias_lib.Arr2D
+import com.android.tools.r8.kotlin.metadata.typealias_lib.Arr2DTester
+import com.android.tools.r8.kotlin.metadata.typealias_lib.CWithConstructor
+import com.android.tools.r8.kotlin.metadata.typealias_lib.CWithConstructorTester
+import com.android.tools.r8.kotlin.metadata.typealias_lib.ClassWithCompanionC
+import com.android.tools.r8.kotlin.metadata.typealias_lib.FunctionTester
 import com.android.tools.r8.kotlin.metadata.typealias_lib.Impl
+import com.android.tools.r8.kotlin.metadata.typealias_lib.InterfaceTester
+import com.android.tools.r8.kotlin.metadata.typealias_lib.MyAdvancedMap
+import com.android.tools.r8.kotlin.metadata.typealias_lib.MyI
+import com.android.tools.r8.kotlin.metadata.typealias_lib.MyMapToSetOfInt
+import com.android.tools.r8.kotlin.metadata.typealias_lib.OuterNested
+import com.android.tools.r8.kotlin.metadata.typealias_lib.OuterNestedInner
+import com.android.tools.r8.kotlin.metadata.typealias_lib.OuterTester
+import com.android.tools.r8.kotlin.metadata.typealias_lib.SimpleClassTester
+import com.android.tools.r8.kotlin.metadata.typealias_lib.StillCWithConstructor
+import com.android.tools.r8.kotlin.metadata.typealias_lib.UnderlyingTypeTest
+import com.android.tools.r8.kotlin.metadata.typealias_lib.UnusedTypeArgument
+import com.android.tools.r8.kotlin.metadata.typealias_lib.VerticalClassMergingTester
 import com.android.tools.r8.kotlin.metadata.typealias_lib.seq
+import com.android.tools.r8.kotlin.metadata.typealias_lib.IntSet as IntSet1
 
 class ProgramClass : Impl() {
   override fun foo(): API {
@@ -15,6 +36,77 @@
   }
 }
 
+fun testUnusedArgument() {
+  val u = UnusedTypeArgument<Int>();
+}
+
+fun testSimpleClass() {
+  val simple = object : AlphaNaming() { }
+  println(SimpleClassTester.f(SimpleClassTester.g(simple)).y)
+}
+
+fun testArr2D() {
+  val arr1d : Arr<Int> = Arr(42);
+  val arr2d : Arr2D<Int> = Arr2D(arr1d);
+  println(Arr2DTester.f(Arr2DTester.g(arr2d)).x.x);
+}
+
+fun testInterface() {
+  val myInstance = object : MyI {
+    override fun f() {
+      println("42");
+    }
+  }
+  InterfaceTester.f(InterfaceTester.g(myInstance)).f()
+
+  val map : MyMapToSetOfInt<Int> = HashMap();
+  val set : IntSet1 = mutableSetOf(42);
+  map.put(1, set);
+  println(InterfaceTester.i(InterfaceTester.h(map))[1]?.iterator()?.next() ?: "");
+}
+
+fun testFunctionTypes() {
+  FunctionTester.f(FunctionTester.g({ i : Int, a : Any ->
+    println(i)
+    println(a)
+  }))(42, "42")
+  FunctionTester.h(FunctionTester.i({ b ->
+    println(b)
+    false
+  }))(true)
+}
+
+fun testNestedClasses() {
+  val nested = OuterNested(42);
+  val myInner : OuterNestedInner = nested.Inner(1)
+  println(OuterTester.g(nested).y)
+  println(OuterTester.f(myInner).x)
+}
+
+fun testCompanion() {
+  ClassWithCompanionC.foo;
+}
+
+fun testConstructor() {
+  println(CWithConstructorTester.f(CWithConstructorTester.g(CWithConstructor(42))).x);
+}
+
+fun testUnderlyingType() {
+  val cWithConstructor = StillCWithConstructor(42)
+  println(UnderlyingTypeTest.f(UnderlyingTypeTest.g(cWithConstructor)).x)
+  val advancedMap : MyAdvancedMap = HashMap();
+  val nested = OuterNested(42);
+  val myInner : OuterNestedInner = nested.Inner(1)
+  advancedMap.put(nested, myInner);
+  val sameMap = UnderlyingTypeTest.h(UnderlyingTypeTest.i(advancedMap))
+  println(sameMap.get(nested)?.x)
+}
+
+fun testVerticalClassMerging() {
+  val apiImpl = VerticalClassMergingTester.produce()
+  VerticalClassMergingTester.passThrough(apiImpl).foo()
+}
+
 fun main() {
   val instance = ProgramClass()
   val l = seq(instance)
@@ -22,4 +114,14 @@
     println(api == api.foo())
     println(api.hey())
   }
+  testUnusedArgument()
+  testSimpleClass()
+  testArr2D()
+  testInterface()
+  testFunctionTypes()
+  testNestedClasses()
+  testCompanion()
+  testConstructor()
+  testUnderlyingType();
+  testVerticalClassMerging();
 }
diff --git a/src/test/java/com/android/tools/r8/kotlin/metadata/typealias_lib/lib.kt b/src/test/java/com/android/tools/r8/kotlin/metadata/typealias_lib/lib.kt
index 82ba761..15771dd 100644
--- a/src/test/java/com/android/tools/r8/kotlin/metadata/typealias_lib/lib.kt
+++ b/src/test/java/com/android/tools/r8/kotlin/metadata/typealias_lib/lib.kt
@@ -3,12 +3,15 @@
 // BSD-style license that can be found in the LICENSE file.
 package com.android.tools.r8.kotlin.metadata.typealias_lib
 
-// TypeAlias {
-//   name: "Jude"
+// KmTypeAlias {
+//   name: Jude
 //   underlyingType {
-//     classifier: "kotlin/Long"
+//     classifier: Class(name=kotlin/Long)
 //   }
-// }
+//   expandedType {
+//     classifier: Class(name=kotlin/Long)
+//   }
+// },
 typealias Jude = Long
 
 interface Itf {
@@ -16,36 +19,42 @@
   fun hey() : Jude
 }
 
-// TypeAlias {
-//   name: "API"
+// KmTypeAlias {
+//   name: API
 //   underlyingType {
-//     classifier: ".../Itf"
-//   }
-// }
-typealias API = Itf
-// TypeAlias {
-//   typeParameters { KmTypeParameter { name = "T" ... } }
-//   name: "myAliasedList"
-//   underlyingType {
-//     classifier: "kotlin/Array"
-//   }
-//   expandedType == underlyingType
-// }
-typealias myAliasedArray<T> = Array<T>
-// TypeAlias {
-//   underlyingType {
-//     classifier: ".../myAliasedArray"
-//     arguments {
-//       KmTypeProjection { ... type = ".../API" }
-//     }
+//     classifier: Class(name=com/android/tools/r8/kotlin/metadata/typealias_lib/Itf)
 //   }
 //   expandedType {
-//     classifier: "kotlin/Array"
-//     arguments {
-//       KmTypeProjection { ... type = ".../Itf" }
-//     }
+//     classifier: Class(name=com/android/tools/r8/kotlin/metadata/typealias_lib/Itf)
 //   }
-// }
+// },
+typealias API = Itf
+
+// KmTypeAlias {
+//   name: myAliasedArray
+//   typeParameters: T
+//   underlyingType {
+//     classifier: Class(name=kotlin/Array)
+//     arguments: TypeParameter(id=0)
+//   }
+//   expandedType {
+//     classifier: Class(name=kotlin/Array)
+//     arguments: TypeParameter(id=0)
+//   }
+// },
+typealias myAliasedArray<T> = Array<T>
+
+// KmTypeAlias {
+//   name: APIs
+//   underlyingType {
+//     classifier: TypeAlias(name=com/android/tools/r8/kotlin/metadata/typealias_lib/myAliasedArray)
+//     arguments: TypeAlias(name=com/android/tools/r8/kotlin/metadata/typealias_lib/API)
+//   }
+//   expandedType {
+//     classifier: Class(name=kotlin/Array)
+//     arguments: Class(name=com/android/tools/r8/kotlin/metadata/typealias_lib/Itf)
+//   }
+// },
 typealias APIs = myAliasedArray<API>
 
 open class Impl : API {
diff --git a/src/test/java/com/android/tools/r8/kotlin/metadata/typealias_lib/lib_ext.kt b/src/test/java/com/android/tools/r8/kotlin/metadata/typealias_lib/lib_ext.kt
new file mode 100644
index 0000000..ba63667
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/kotlin/metadata/typealias_lib/lib_ext.kt
@@ -0,0 +1,415 @@
+// 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.kotlin.metadata.typealias_lib
+
+// Unused type aliases
+
+open class LibraryClass { }
+
+// KmTypeAlias {
+//   name: Unused
+//   underlyingType {
+//     classifier: Class(name=com/android/tools/r8/kotlin/metadata/typealias_lib/LibraryClass)
+//   }
+//   expandedType {
+//     classifier: Class(name=com/android/tools/r8/kotlin/metadata/typealias_lib/LibraryClass)
+//   }
+// },
+typealias Unused = LibraryClass
+
+// Will give a warning, but is is not an error to have an unused argument.
+// KmTypeAlias {
+//   name: UnusedTypeArgument
+//   typeParameters: T
+//   underlyingType {
+//     classifier: TypeAlias(name=com/android/tools/r8/kotlin/metadata/typealias_lib/Unused)
+//   }
+//   expandedType {
+//     classifier: Class(name=com/android/tools/r8/kotlin/metadata/typealias_lib/LibraryClass)
+//   }
+// },
+typealias UnusedTypeArgument<T> = Unused
+
+// Expansion to simple class
+
+open class SimpleClass {
+  val y : Int = 42;
+}
+
+// KmTypeAlias {
+//   name: AlphaNaming
+//   underlyingType {
+//     classifier: Class(name=com/android/tools/r8/kotlin/metadata/typealias_lib/SimpleClass)
+//   }
+//   expandedType {
+//     classifier: Class(name=com/android/tools/r8/kotlin/metadata/typealias_lib/SimpleClass)
+//   }
+// },
+typealias AlphaNaming = SimpleClass
+
+class SimpleClassTester {
+
+  companion object {
+    fun f(a : SimpleClass) : AlphaNaming {
+      return a;
+    }
+
+    fun g(a : AlphaNaming) : SimpleClass {
+      return a;
+    }
+  }
+}
+
+// Vertical class merging
+
+interface Api {
+
+  fun foo();
+}
+
+class ApiImpl : Api {
+  override fun foo() {
+    println("Hello World!")
+  }
+}
+
+// KmTypeAlias {
+//   name: ApiAlias
+//   underlyingType {
+//     classifier: Class(name=com/android/tools/r8/kotlin/metadata/typealias_lib/Api)
+//   }
+//   expandedType {
+//     classifier: Class(name=com/android/tools/r8/kotlin/metadata/typealias_lib/Api)
+//   }
+// },
+typealias ApiAlias = Api
+
+class VerticalClassMergingTester {
+
+  companion object {
+
+    fun produce(): ApiAlias {
+      return ApiImpl()
+    }
+
+    fun passThrough(a : ApiAlias) : ApiAlias {
+      return a;
+    }
+  }
+}
+
+// Multiple expansions
+
+class Arr<K>(val x : K)
+
+// KmTypeAlias {
+//   name: Arr1D
+//   typeParameters: K
+//   underlyingType {
+//     classifier: Class(name=com/android/tools/r8/kotlin/metadata/typealias_lib/Arr)
+//     arguments: TypeParameter(id=0)
+//   }
+//   expandedType {
+//     classifier: Class(name=com/android/tools/r8/kotlin/metadata/typealias_lib/Arr)
+//     arguments: TypeParameter(id=0)
+//   }
+// },
+typealias Arr1D<K> = Arr<K>
+
+// KmTypeAlias {
+//   name: Arr2D
+//   typeParameters: K
+//   underlyingType {
+//     classifier: TypeAlias(name=com/android/tools/r8/kotlin/metadata/typealias_lib/Arr1D)
+//     arguments: TypeAlias(name=com/android/tools/r8/kotlin/metadata/typealias_lib/Arr1D)
+//   }
+//   expandedType {
+//     classifier: Class(name=com/android/tools/r8/kotlin/metadata/typealias_lib/Arr)
+//     arguments: Class(name=com/android/tools/r8/kotlin/metadata/typealias_lib/Arr)
+//   }
+// },
+typealias Arr2D<K> = Arr1D<Arr1D<K>>
+
+class Arr2DTester {
+
+  companion object {
+
+    fun <K> f(a : Arr1D<Arr1D<K>>) : Arr2D<K> {
+      return a;
+    }
+
+    fun <K> g(a : Arr2D<K>) : Arr1D<Arr1D<K>> {
+      return a;
+    }
+
+  }
+}
+
+// Expansion to interfaces
+
+interface I<T : Any> {
+  fun f()
+}
+
+// KmTypeAlias {
+//   name: MyI
+//   underlyingType {
+//     classifier: Class(name=com/android/tools/r8/kotlin/metadata/typealias_lib/I)
+//     arguments: Class(name=kotlin/Int)
+//   }
+//   expandedType {
+//     classifier: Class(name=com/android/tools/r8/kotlin/metadata/typealias_lib/I)
+//     arguments: Class(name=kotlin/Int)
+//   }
+// },
+typealias MyI = I<Int>
+
+// KmTypeAlias {
+//   name: IntSet
+//   underlyingType {
+//     classifier: Class(name=kotlin/collections/Set)
+//     arguments: Class(name=kotlin/Int)
+//   }
+//   expandedType {
+//     classifier: Class(name=kotlin/collections/Set)
+//     arguments: Class(name=kotlin/Int)
+//   }
+// },
+typealias IntSet = Set<Int>
+
+// KmTypeAlias {
+//   name: MyMapToSetOfInt
+//   typeParameters: K
+//   underlyingType {
+//     classifier: Class(name=kotlin/collections/MutableMap)
+//     arguments: TypeParameter(id=0),TypeAlias(name=com/android/tools/r8/kotlin/metadata/typealias_lib/IntSet)
+//   }
+//   expandedType {
+//     classifier: Class(name=kotlin/collections/MutableMap)
+//     arguments: TypeParameter(id=0),Class(name=kotlin/collections/Set)
+//   }
+// },
+typealias MyMapToSetOfInt<K> = MutableMap<K, IntSet>
+
+class InterfaceTester {
+
+  companion object {
+
+    fun f(i : I<Int>) : MyI {
+      return i;
+    }
+
+    fun g(myI : MyI) : I<Int> {
+      return myI;
+    }
+
+    fun h(k : MyMapToSetOfInt<Int>) : MutableMap<Int, IntSet> {
+      return k;
+    }
+
+    fun i(myMap : MutableMap<Int, IntSet>) : MyMapToSetOfInt<Int> {
+      return myMap;
+    }
+  }
+}
+
+// Expansion to function types
+
+// KmTypeAlias {
+//   name: MyHandler
+//   underlyingType {
+//     classifier: Class(name=kotlin/Function2)
+//     arguments: Class(name=kotlin/Int),Class(name=kotlin/Any),Class(name=kotlin/Unit)
+//   }
+//   expandedType {
+//     classifier: Class(name=kotlin/Function2)
+//     arguments: Class(name=kotlin/Int),Class(name=kotlin/Any),Class(name=kotlin/Unit)
+//   }
+// },
+typealias MyHandler = (Int, Any) -> Unit
+
+// KmTypeAlias {
+//   name: MyGenericPredicate
+//   typeParameters: T
+//   underlyingType {
+//     classifier: Class(name=kotlin/Function1)
+//     arguments: TypeParameter(id=0),Class(name=kotlin/Boolean)
+//   }
+//   expandedType {
+//     classifier: Class(name=kotlin/Function1)
+//     arguments: TypeParameter(id=0),Class(name=kotlin/Boolean)
+//   }
+// },
+typealias MyGenericPredicate<T> = (T) -> Boolean
+
+class FunctionTester {
+
+  companion object {
+
+    fun f(a : (Int, Any) -> Unit) : MyHandler {
+      return a;
+    }
+
+    fun g(a : MyHandler) : (Int, Any) -> Unit {
+      return a;
+    }
+
+    fun h(a : (Boolean) -> Boolean) : MyGenericPredicate<Boolean> {
+      return a;
+    }
+
+    fun i(a : MyGenericPredicate<Boolean>) : (Boolean) -> Boolean {
+      return a;
+    }
+  }
+}
+
+// Expansion to nested classes
+
+class Outer {
+  class Nested(val y : Int) {
+    inner class Inner(val x : Int) {
+
+    }
+  }
+}
+
+// KmTypeAlias {
+//   name: OuterNested
+//   underlyingType {
+//     classifier: Class(name=com/android/tools/r8/kotlin/metadata/typealias_lib/Outer.Nested)
+//   }
+//   expandedType {
+//     classifier: Class(name=com/android/tools/r8/kotlin/metadata/typealias_lib/Outer.Nested)
+//   }
+// },
+typealias OuterNested = Outer.Nested
+
+// KmTypeAlias {
+//   name: OuterNestedInner
+//   underlyingType {
+//     classifier: Class(name=com/android/tools/r8/kotlin/metadata/typealias_lib/Outer.Nested.Inner)
+//   }
+//   expandedType {
+//     classifier: Class(name=com/android/tools/r8/kotlin/metadata/typealias_lib/Outer.Nested.Inner)
+//   }
+// },
+typealias OuterNestedInner = Outer.Nested.Inner
+
+class OuterTester {
+
+  companion object {
+
+    fun f(a : Outer.Nested.Inner) : OuterNestedInner {
+      return a;
+    }
+
+    fun g(a : OuterNested) : Outer.Nested {
+      return a;
+    }
+
+  }
+}
+
+// Expansion to companion class
+
+class ClassWithCompanion {
+
+  companion object {
+    val foo: String
+      get() = "A.Companion::foo"
+  }
+}
+
+// KmTypeAlias {
+//   name: ClassWithCompanionC
+//   underlyingType {
+//     classifier: Class(name=com/android/tools/r8/kotlin/metadata/typealias_lib/ClassWithCompanion.Companion)
+//   }
+//   expandedType {
+//     classifier: Class(name=com/android/tools/r8/kotlin/metadata/typealias_lib/ClassWithCompanion.Companion)
+//   }
+// },
+typealias ClassWithCompanionC = ClassWithCompanion.Companion
+
+// Expansion to constructor
+
+class C(val x : Int) {
+
+  private constructor() : this(0)
+}
+
+// KmTypeAlias {
+//   name: CWithConstructor
+//   underlyingType {
+//     classifier: Class(name=com/android/tools/r8/kotlin/metadata/typealias_lib/C)
+//   }
+//   expandedType {
+//     classifier: Class(name=com/android/tools/r8/kotlin/metadata/typealias_lib/C)
+//   }
+// },
+typealias CWithConstructor = C
+
+class CWithConstructorTester {
+
+  companion object {
+
+    fun f(a : C) : CWithConstructor {
+      return a;
+    }
+
+    fun g(a : CWithConstructor) : C {
+      return a;
+    }
+  }
+}
+
+// Underlying type having type alias
+
+// KmTypeAlias {
+//   name: StillCWithConstructor
+//   underlyingType {
+//     classifier: TypeAlias(name=com/android/tools/r8/kotlin/metadata/typealias_lib/CWithConstructor)
+//   }
+//   expandedType {
+//     classifier: Class(name=com/android/tools/r8/kotlin/metadata/typealias_lib/C)
+//   }
+// },
+typealias StillCWithConstructor = CWithConstructor
+
+// KmTypeAlias {
+//   name: MyAdvancedMap
+//   underlyingType {
+//     classifier: Class(name=kotlin/collections/MutableMap)
+//     arguments: TypeAlias(name=com/android/tools/r8/kotlin/metadata/typealias_lib/OuterNested),TypeAlias(name=com/android/tools/r8/kotlin/metadata/typealias_lib/OuterNestedInner)
+//   }
+//   expandedType {
+//     classifier: Class(name=kotlin/collections/MutableMap)
+//     arguments: Class(name=com/android/tools/r8/kotlin/metadata/typealias_lib/Outer.Nested),Class(name=com/android/tools/r8/kotlin/metadata/typealias_lib/Outer.Nested.Inner)
+//   }
+// },
+typealias MyAdvancedMap = MutableMap<OuterNested, OuterNestedInner>
+
+class UnderlyingTypeTest {
+
+  companion object {
+
+    fun f(a : StillCWithConstructor) : CWithConstructor {
+      return a;
+    }
+
+    fun g(a : C) : StillCWithConstructor {
+      return a;
+    }
+
+    fun h(a : MutableMap<OuterNested, OuterNestedInner>) : MyAdvancedMap {
+      return a;
+    }
+
+    fun i(a : MyAdvancedMap) : MutableMap<OuterNested, OuterNestedInner> {
+      return a;
+    }
+
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/kotlin/metadata/typeargument_app/main.kt b/src/test/java/com/android/tools/r8/kotlin/metadata/typeargument_app/main.kt
new file mode 100644
index 0000000..7fc2b69
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/kotlin/metadata/typeargument_app/main.kt
@@ -0,0 +1,53 @@
+// 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.kotlin.metadata.typeargument_app
+
+import com.android.tools.r8.kotlin.metadata.typeargument_lib.CoVariant
+import com.android.tools.r8.kotlin.metadata.typeargument_lib.ContraVariant
+import com.android.tools.r8.kotlin.metadata.typeargument_lib.Invariant
+import com.android.tools.r8.kotlin.metadata.typeargument_lib.SomeClass
+import com.android.tools.r8.kotlin.metadata.typeargument_lib.unBoxAndBox
+import com.android.tools.r8.kotlin.metadata.typeargument_lib.update
+
+class SomeSubClass(val x : Int) : SomeClass(), Comparable<SomeClass> {
+
+  override fun compareTo(other: SomeClass): Int {
+    if (other !is SomeSubClass) {
+      return -1;
+    }
+    return x.compareTo(other.x);
+  }
+}
+
+fun testInvariant() {
+  val subtype1 = SomeSubClass(42)
+  val subtype2 = SomeSubClass(1)
+  val inv = Invariant<SomeSubClass>()
+  println(inv.classGenerics(subtype1).x)
+  println(inv.funGenerics(subtype2).x)
+  println(inv.funGenericsWithUpperBound(subtype1).x)
+  println(inv.funGenericsWithUpperBounds(subtype1, subtype2).x)
+  println(inv.mixedGenerics(subtype2).x)
+}
+
+fun testCoVariant() {
+ println(CoVariant(42).classGenerics().t)
+}
+
+fun testContraVariant() {
+  ContraVariant<Number>().classGenerics(42)
+}
+
+fun testExtension() {
+  println(CoVariant(42).unBoxAndBox().t)
+  println(CoVariant(1).update(42).t)
+}
+
+fun main() {
+  testInvariant()
+  testCoVariant()
+  testContraVariant();
+  testExtension()
+}
\ No newline at end of file
diff --git a/src/test/java/com/android/tools/r8/kotlin/metadata/typeargument_lib/lib.kt b/src/test/java/com/android/tools/r8/kotlin/metadata/typeargument_lib/lib.kt
new file mode 100644
index 0000000..7c8b6cb
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/kotlin/metadata/typeargument_lib/lib.kt
@@ -0,0 +1,63 @@
+// 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.kotlin.metadata.typeargument_lib
+
+open class SomeClass
+
+class Invariant<T> {
+
+  fun classGenerics(t : T) : T {
+    return t
+  }
+
+  fun <R> funGenerics(r : R) : R {
+    return r
+  }
+
+  fun <R : SomeClass> funGenericsWithUpperBound(r : R) : R {
+    return r;
+  }
+
+  fun <R> funGenericsWithUpperBounds(r1 : R, r2 : R) : R
+    where R : SomeClass,
+          R : Comparable<SomeClass> {
+    return when {
+      r1 > r2 -> {
+        r1;
+      }
+      else -> {
+        r2;
+      }
+    }
+  }
+
+  fun <R : T> mixedGenerics(r : R) : T {
+    return r;
+  }
+}
+
+class CoVariant<out T>(val t : T) {
+
+  fun classGenerics() : CoVariant<T> {
+    return CoVariant(t);
+  }
+}
+
+class ContraVariant<in T> {
+
+  fun classGenerics(t : T) {
+    println(t)
+  }
+}
+
+
+fun <T> CoVariant<T>.unBoxAndBox() : CoVariant<T> {
+  return CoVariant(this.t)
+}
+
+fun <T, R> CoVariant<R>.update(t : T) : CoVariant<T> {
+  println(this.t)
+  return CoVariant(t)
+}
diff --git a/src/test/java/com/android/tools/r8/memberrebinding/MemberRebindingConflictTest.java b/src/test/java/com/android/tools/r8/memberrebinding/MemberRebindingConflictTest.java
new file mode 100644
index 0000000..e081a44
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/memberrebinding/MemberRebindingConflictTest.java
@@ -0,0 +1,99 @@
+// 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.memberrebinding;
+
+import static org.hamcrest.core.StringContains.containsString;
+
+import com.android.tools.r8.NeverInline;
+import com.android.tools.r8.NeverMerge;
+import com.android.tools.r8.R8TestRunResult;
+import com.android.tools.r8.TestBase;
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.TestParametersCollection;
+import com.android.tools.r8.graph.MethodAccessFlags;
+import com.android.tools.r8.memberrebinding.testclasses.MemberRebindingConflictTestClasses;
+import com.android.tools.r8.memberrebinding.testclasses.MemberRebindingConflictTestClasses.C;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+
+@RunWith(Parameterized.class)
+public class MemberRebindingConflictTest extends TestBase {
+
+  private final TestParameters parameters;
+
+  @Parameterized.Parameters(name = "{0}")
+  public static TestParametersCollection data() {
+    return getTestParameters().withAllRuntimesAndApiLevels().build();
+  }
+
+  public MemberRebindingConflictTest(TestParameters parameters) {
+    this.parameters = parameters;
+  }
+
+  @Test
+  public void test() throws Exception {
+    R8TestRunResult result =
+        testForR8(parameters.getBackend())
+            .addProgramClasses(A.class, TestClass.class)
+            .addProgramClassFileData(
+                transformer(B.class)
+                    .removeMethods(
+                        (access, name, descriptor, signature, exceptions) -> {
+                          if (name.equals("foo")) {
+                            assert MethodAccessFlags.fromCfAccessFlags(access, false).isSynthetic();
+                            return true;
+                          }
+                          return false;
+                        })
+                    .transform())
+            .addInnerClasses(MemberRebindingConflictTestClasses.class)
+            .addKeepMainRule(TestClass.class)
+            .enableInliningAnnotations()
+            .enableMergeAnnotations()
+            .enableNeverClassInliningAnnotations()
+            .setMinApi(parameters.getApiLevel())
+            .compile()
+            .run(parameters.getRuntime(), TestClass.class);
+
+    if (parameters.isDexRuntime()
+        && parameters.getRuntime().asDex().getVm().getVersion().isDalvik()) {
+      result.assertSuccessWithOutputLines("foo", "bar", "foo", "baz");
+    } else {
+      result.assertFailureWithErrorThatMatches(
+          containsString(IllegalAccessError.class.getTypeName()));
+    }
+  }
+
+  static class TestClass {
+
+    public static void main(String[] args) {
+      C c = new C();
+      c.bar();
+      c.baz();
+    }
+  }
+
+  @NeverMerge
+  static class A {
+
+    @NeverInline
+    public void foo() {
+      System.out.println("foo");
+    }
+  }
+
+  @NeverMerge
+  public static class B extends A {
+
+    // public synthetic void foo() { super.foo(); }
+
+    @NeverInline
+    public void bar() {
+      foo();
+      System.out.println("bar");
+    }
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/memberrebinding/testclasses/MemberRebindingConflictTestClasses.java b/src/test/java/com/android/tools/r8/memberrebinding/testclasses/MemberRebindingConflictTestClasses.java
new file mode 100644
index 0000000..79f7dd6
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/memberrebinding/testclasses/MemberRebindingConflictTestClasses.java
@@ -0,0 +1,22 @@
+// 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.memberrebinding.testclasses;
+
+import com.android.tools.r8.NeverClassInline;
+import com.android.tools.r8.NeverInline;
+import com.android.tools.r8.memberrebinding.MemberRebindingConflictTest;
+
+public class MemberRebindingConflictTestClasses {
+
+  @NeverClassInline
+  public static class C extends MemberRebindingConflictTest.B {
+
+    @NeverInline
+    public void baz() {
+      super.foo();
+      System.out.println("baz");
+    }
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/naming/IdentifierMinifierTest.java b/src/test/java/com/android/tools/r8/naming/IdentifierMinifierTest.java
index 1c7c5bc..627a059 100644
--- a/src/test/java/com/android/tools/r8/naming/IdentifierMinifierTest.java
+++ b/src/test/java/com/android/tools/r8/naming/IdentifierMinifierTest.java
@@ -15,7 +15,6 @@
 import com.android.tools.r8.TestParameters;
 import com.android.tools.r8.ToolHelper;
 import com.android.tools.r8.graph.DexEncodedField;
-import com.android.tools.r8.graph.DexValue.DexValueString;
 import com.android.tools.r8.utils.FileUtils;
 import com.android.tools.r8.utils.ListUtils;
 import com.android.tools.r8.utils.codeinspector.ClassSubject;
@@ -284,19 +283,22 @@
   private static int countRenamedClassIdentifier(
       CodeInspector inspector, List<DexEncodedField> fields) {
     return fields.stream()
-        .filter(encodedField -> encodedField.getStaticValue() instanceof DexValueString)
-        .reduce(0, (cnt, encodedField) -> {
-          String cnstString =
-              ((DexValueString) encodedField.getStaticValue()).getValue().toString();
-          if (isValidJavaType(cnstString)) {
-            ClassSubject classSubject = inspector.clazz(cnstString);
-            if (classSubject.isRenamed()
-                && descriptorToJavaType(classSubject.getFinalDescriptor()).equals(cnstString)) {
-              return cnt + 1;
-            }
-          }
-          return cnt;
-        }, Integer::sum);
+        .filter(encodedField -> encodedField.getStaticValue().isDexValueString())
+        .reduce(
+            0,
+            (cnt, encodedField) -> {
+              String cnstString =
+                  encodedField.getStaticValue().asDexValueString().getValue().toString();
+              if (isValidJavaType(cnstString)) {
+                ClassSubject classSubject = inspector.clazz(cnstString);
+                if (classSubject.isRenamed()
+                    && descriptorToJavaType(classSubject.getFinalDescriptor()).equals(cnstString)) {
+                  return cnt + 1;
+                }
+              }
+              return cnt;
+            },
+            Integer::sum);
   }
 
   private static Set<InstructionSubject> getRenamedMemberIdentifierConstStrings(
diff --git a/src/test/java/com/android/tools/r8/naming/IdentifierNameStringMarkerTest.java b/src/test/java/com/android/tools/r8/naming/IdentifierNameStringMarkerTest.java
index e9ce1d8..116ce4a 100644
--- a/src/test/java/com/android/tools/r8/naming/IdentifierNameStringMarkerTest.java
+++ b/src/test/java/com/android/tools/r8/naming/IdentifierNameStringMarkerTest.java
@@ -26,7 +26,6 @@
 import com.android.tools.r8.code.SputObject;
 import com.android.tools.r8.graph.DexCode;
 import com.android.tools.r8.graph.DexEncodedMethod;
-import com.android.tools.r8.graph.DexValue.DexValueString;
 import com.android.tools.r8.smali.SmaliBuilder;
 import com.android.tools.r8.smali.SmaliBuilder.MethodSignature;
 import com.android.tools.r8.smali.SmaliTestBase;
@@ -278,8 +277,8 @@
     assertTrue(clazz.isPresent());
     FieldSubject field = clazz.field("java.lang.String", "sClassName");
     assertTrue(field.isPresent());
-    assertTrue(field.getStaticValue() instanceof DexValueString);
-    String defaultValue = ((DexValueString) field.getStaticValue()).getValue().toString();
+    assertTrue(field.getStaticValue().isDexValueString());
+    String defaultValue = field.getStaticValue().asDexValueString().getValue().toString();
     assertEquals(BOO, defaultValue);
   }
 
@@ -300,8 +299,8 @@
     assertTrue(clazz.isPresent());
     FieldSubject field = clazz.field("java.lang.String", "sClassName");
     assertTrue(field.isPresent());
-    assertTrue(field.getStaticValue() instanceof DexValueString);
-    String defaultValue = ((DexValueString) field.getStaticValue()).getValue().toString();
+    assertTrue(field.getStaticValue().isDexValueString());
+    String defaultValue = field.getStaticValue().asDexValueString().getValue().toString();
     assertNotEquals(BOO, defaultValue);
   }
 
@@ -324,8 +323,8 @@
     assertTrue(clazz.isPresent());
     FieldSubject field = clazz.field("java.lang.String", "sFieldName");
     assertTrue(field.isPresent());
-    assertTrue(field.getStaticValue() instanceof DexValueString);
-    String defaultValue = ((DexValueString) field.getStaticValue()).getValue().toString();
+    assertTrue(field.getStaticValue().isDexValueString());
+    String defaultValue = field.getStaticValue().asDexValueString().getValue().toString();
     assertNotEquals(fooInBoo, defaultValue);
   }
 
@@ -348,8 +347,8 @@
     assertTrue(clazz.isPresent());
     FieldSubject field = clazz.field("java.lang.String", "sMethodName");
     assertTrue(field.isPresent());
-    assertTrue(field.getStaticValue() instanceof DexValueString);
-    String defaultValue = ((DexValueString) field.getStaticValue()).getValue().toString();
+    assertTrue(field.getStaticValue().isDexValueString());
+    String defaultValue = field.getStaticValue().asDexValueString().getValue().toString();
     assertNotEquals(fooInBoo, defaultValue);
   }
 
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 1dabc11..e1cd05a 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
@@ -17,7 +17,6 @@
 import com.android.tools.r8.graph.DexAnnotationElement;
 import com.android.tools.r8.graph.DexValue;
 import com.android.tools.r8.graph.DexValue.DexValueArray;
-import com.android.tools.r8.graph.DexValue.DexValueString;
 import com.android.tools.r8.utils.BooleanUtils;
 import com.android.tools.r8.utils.DescriptorUtils;
 import com.android.tools.r8.utils.StringUtils;
@@ -47,12 +46,12 @@
     DexAnnotationElement[] elements = signature.getAnnotation().elements;
     assertEquals(1, elements.length);
     assertEquals("value", elements[0].name.toString());
-    assertTrue(elements[0].value instanceof DexValueArray);
-    DexValueArray array = (DexValueArray) elements[0].value;
+    assertTrue(elements[0].value.isDexValueArray());
+    DexValueArray array = elements[0].value.asDexValueArray();
     StringBuilder builder = new StringBuilder();
     for (DexValue value : array.getValues()) {
-      assertTrue(value instanceof DexValueString);
-      builder.append(((DexValueString) value).value);
+      assertTrue(value.isDexValueString());
+      builder.append(value.asDexValueString().value);
     }
     String fooImplFinalDescriptor =
         DescriptorUtils.javaTypeToDescriptor(inspector.clazz(FooImpl.class).getFinalName());
diff --git a/src/test/java/com/android/tools/r8/resolution/singletarget/InstantiatedLowerBoundTest.java b/src/test/java/com/android/tools/r8/resolution/singletarget/InstantiatedLowerBoundTest.java
index deec1e3..8a35389 100644
--- a/src/test/java/com/android/tools/r8/resolution/singletarget/InstantiatedLowerBoundTest.java
+++ b/src/test/java/com/android/tools/r8/resolution/singletarget/InstantiatedLowerBoundTest.java
@@ -19,7 +19,7 @@
 import com.android.tools.r8.graph.DexType;
 import com.android.tools.r8.graph.LookupResult;
 import com.android.tools.r8.graph.ResolutionResult;
-import com.android.tools.r8.ir.analysis.type.ClassTypeLatticeElement;
+import com.android.tools.r8.ir.analysis.type.ClassTypeElement;
 import com.android.tools.r8.ir.analysis.type.Nullability;
 import com.android.tools.r8.shaking.AppInfoWithLiveness;
 import com.google.common.collect.Sets;
@@ -53,8 +53,8 @@
     DexType typeB = buildType(B.class, appInfo.dexItemFactory());
     DexType typeMain = buildType(Main.class, appInfo.dexItemFactory());
     DexMethod fooA = buildNullaryVoidMethod(A.class, "foo", appInfo.dexItemFactory());
-    ClassTypeLatticeElement latticeB =
-        ClassTypeLatticeElement.create(typeB, Nullability.definitelyNotNull(), appView);
+    ClassTypeElement latticeB =
+        ClassTypeElement.create(typeB, Nullability.definitelyNotNull(), appView);
     DexEncodedMethod singleTarget =
         appInfo.lookupSingleVirtualTarget(fooA, typeMain, false, t -> false, typeA, latticeB);
     assertNotNull(singleTarget);
@@ -73,8 +73,8 @@
     DexType typeB = buildType(B.class, appInfo.dexItemFactory());
     DexType typeMain = buildType(Main.class, appInfo.dexItemFactory());
     DexMethod fooA = buildNullaryVoidMethod(A.class, "foo", appInfo.dexItemFactory());
-    ClassTypeLatticeElement latticeB =
-        ClassTypeLatticeElement.create(typeB, Nullability.definitelyNotNull(), appView);
+    ClassTypeElement latticeB =
+        ClassTypeElement.create(typeB, Nullability.definitelyNotNull(), appView);
     DexEncodedMethod singleTarget =
         appInfo.lookupSingleVirtualTarget(fooA, typeMain, false, t -> false, typeA, latticeB);
     assertNotNull(singleTarget);
@@ -117,8 +117,8 @@
               assert false;
             });
     assertEquals(expected, actual);
-    ClassTypeLatticeElement latticeC =
-        ClassTypeLatticeElement.create(typeC, Nullability.definitelyNotNull(), appView);
+    ClassTypeElement latticeC =
+        ClassTypeElement.create(typeC, Nullability.definitelyNotNull(), appView);
     DexEncodedMethod singleTarget =
         appInfo.lookupSingleVirtualTarget(fooA, typeMain, false, t -> false, typeA, latticeC);
     assertNull(singleTarget);
diff --git a/src/test/java/com/android/tools/r8/rewrite/staticvalues/StaticValuesTest.java b/src/test/java/com/android/tools/r8/rewrite/staticvalues/StaticValuesTest.java
index f0270f1..662b22e 100644
--- a/src/test/java/com/android/tools/r8/rewrite/staticvalues/StaticValuesTest.java
+++ b/src/test/java/com/android/tools/r8/rewrite/staticvalues/StaticValuesTest.java
@@ -13,15 +13,6 @@
 import com.android.tools.r8.code.SgetBoolean;
 import com.android.tools.r8.graph.DexCode;
 import com.android.tools.r8.graph.DexValue;
-import com.android.tools.r8.graph.DexValue.DexValueBoolean;
-import com.android.tools.r8.graph.DexValue.DexValueByte;
-import com.android.tools.r8.graph.DexValue.DexValueChar;
-import com.android.tools.r8.graph.DexValue.DexValueDouble;
-import com.android.tools.r8.graph.DexValue.DexValueFloat;
-import com.android.tools.r8.graph.DexValue.DexValueInt;
-import com.android.tools.r8.graph.DexValue.DexValueLong;
-import com.android.tools.r8.graph.DexValue.DexValueShort;
-import com.android.tools.r8.graph.DexValue.DexValueString;
 import com.android.tools.r8.smali.SmaliBuilder;
 import com.android.tools.r8.smali.SmaliTestBase;
 import com.android.tools.r8.utils.AndroidApp;
@@ -102,48 +93,49 @@
     DexValue value;
     assertTrue(inspector.clazz("Test").field("boolean", "booleanField").hasExplicitStaticValue());
     value = inspector.clazz("Test").field("boolean", "booleanField").getStaticValue();
-    assertTrue(value instanceof DexValueBoolean);
-    assertTrue(((DexValueBoolean) value).getValue());
+    assertTrue(value.isDexValueBoolean());
+    assertTrue(value.asDexValueBoolean().getValue());
 
     assertTrue(inspector.clazz("Test").field("byte", "byteField").hasExplicitStaticValue());
     value = inspector.clazz("Test").field("byte", "byteField").getStaticValue();
-    assertTrue(value instanceof DexValueByte);
-    assertEquals(1, ((DexValueByte) value).getValue());
+    assertTrue(value.isDexValueByte());
+    assertEquals(1, value.asDexValueByte().getValue());
 
     assertTrue(inspector.clazz("Test").field("short", "shortField").hasExplicitStaticValue());
     value = inspector.clazz("Test").field("short", "shortField").getStaticValue();
-    assertTrue(value instanceof DexValueShort);
-    assertEquals(2, ((DexValueShort) value).getValue());
+    assertTrue(value.isDexValueShort());
+    assertEquals(2, value.asDexValueShort().getValue());
 
     assertTrue(inspector.clazz("Test").field("int", "intField").hasExplicitStaticValue());
     value = inspector.clazz("Test").field("int", "intField").getStaticValue();
-    assertTrue(value instanceof DexValueInt);
-    assertEquals(3, ((DexValueInt) value).getValue());
+    assertTrue(value.isDexValueInt());
+    assertEquals(3, value.asDexValueInt().getValue());
 
     assertTrue(inspector.clazz("Test").field("long", "longField").hasExplicitStaticValue());
     value = inspector.clazz("Test").field("long", "longField").getStaticValue();
-    assertTrue(value instanceof DexValueLong);
-    assertEquals(4, ((DexValueLong) value).getValue());
+    assertTrue(value.isDexValueLong());
+    assertEquals(4, value.asDexValueLong().getValue());
 
     assertTrue(inspector.clazz("Test").field("float", "floatField").hasExplicitStaticValue());
     value = inspector.clazz("Test").field("float", "floatField").getStaticValue();
-    assertTrue(value instanceof DexValueFloat);
-    assertEquals(5.0f, ((DexValueFloat) value).getValue(), 0.0);
+    assertTrue(value.isDexValueFloat());
+    assertEquals(5.0f, value.asDexValueFloat().getValue(), 0.0);
 
     assertTrue(inspector.clazz("Test").field("double", "doubleField").hasExplicitStaticValue());
     value = inspector.clazz("Test").field("double", "doubleField").getStaticValue();
-    assertTrue(value instanceof DexValueDouble);
-    assertEquals(6.0f, ((DexValueDouble) value).getValue(), 0.0);
+    assertTrue(value.isDexValueDouble());
+    assertEquals(6.0f, value.asDexValueDouble().getValue(), 0.0);
 
     assertTrue(inspector.clazz("Test").field("char", "charField").hasExplicitStaticValue());
     value = inspector.clazz("Test").field("char", "charField").getStaticValue();
-    assertTrue(value instanceof DexValueChar);
-    assertEquals(0x30 + 7, ((DexValueChar) value).getValue());
+    assertTrue(value.isDexValueChar());
+    assertEquals(0x30 + 7, value.asDexValueChar().getValue());
 
-    assertTrue(inspector.clazz("Test").field("java.lang.String", "stringField").hasExplicitStaticValue());
+    assertTrue(
+        inspector.clazz("Test").field("java.lang.String", "stringField").hasExplicitStaticValue());
     value = inspector.clazz("Test").field("java.lang.String", "stringField").getStaticValue();
-    assertTrue(value instanceof DexValueString);
-    assertEquals(("8"), ((DexValueString) value).getValue().toString());
+    assertTrue(value.isDexValueString());
+    assertEquals(("8"), value.asDexValueString().getValue().toString());
 
     String result = runArt(processedApplication);
 
@@ -319,13 +311,14 @@
     DexValue value;
     assertTrue(inspector.clazz("Test").field("int", "intField").hasExplicitStaticValue());
     value = inspector.clazz("Test").field("int", "intField").getStaticValue();
-    assertTrue(value instanceof DexValueInt);
-    assertEquals(3, ((DexValueInt) value).getValue());
+    assertTrue(value.isDexValueInt());
+    assertEquals(3, value.asDexValueInt().getValue());
 
-    assertTrue(inspector.clazz("Test").field("java.lang.String", "stringField").hasExplicitStaticValue());
+    assertTrue(
+        inspector.clazz("Test").field("java.lang.String", "stringField").hasExplicitStaticValue());
     value = inspector.clazz("Test").field("java.lang.String", "stringField").getStaticValue();
-    assertTrue(value instanceof DexValueString);
-    assertEquals(("7"), ((DexValueString) value).getValue().toString());
+    assertTrue(value.isDexValueString());
+    assertEquals(("7"), value.asDexValueString().getValue().toString());
 
     String result = runArt(processedApplication);
 
@@ -389,14 +382,15 @@
 
     assertTrue(inspector.clazz("Test").field("int", "intField").hasExplicitStaticValue());
     DexValue value = inspector.clazz("Test").field("int", "intField").getStaticValue();
-    assertTrue(value instanceof DexValueInt);
-    assertEquals(1, ((DexValueInt) value).getValue());
+    assertTrue(value.isDexValueInt());
+    assertEquals(1, value.asDexValueInt().getValue());
 
-    assertTrue(inspector.clazz("Test").field("java.lang.String", "stringField").hasExplicitStaticValue());
+    assertTrue(
+        inspector.clazz("Test").field("java.lang.String", "stringField").hasExplicitStaticValue());
     value = inspector.clazz("Test").field("java.lang.String", "stringField").getStaticValue();
-    assertTrue(value instanceof DexValueString);
+    assertTrue(value.isDexValueString());
     // We stop at control-flow and therefore the initial value for the string field will be 5.
-    assertEquals(("5"), ((DexValueString) value).getValue().toString());
+    assertEquals(("5"), value.asDexValueString().getValue().toString());
 
     DexCode code = inspector.clazz("Test").clinit().getMethod().getCode().asDexCode();
     assertTrue(code.instructions[0] instanceof SgetBoolean);
diff --git a/src/test/java/com/android/tools/r8/shaking/EventuallyNonTargetedMethodTest.java b/src/test/java/com/android/tools/r8/shaking/EventuallyNonTargetedMethodTest.java
new file mode 100644
index 0000000..741496d
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/shaking/EventuallyNonTargetedMethodTest.java
@@ -0,0 +1,101 @@
+// 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.shaking;
+
+import static com.android.tools.r8.utils.codeinspector.Matchers.isPresent;
+import static org.hamcrest.MatcherAssert.assertThat;
+
+import com.android.tools.r8.NeverClassInline;
+import com.android.tools.r8.NeverInline;
+import com.android.tools.r8.NeverMerge;
+import com.android.tools.r8.TestBase;
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.TestParametersCollection;
+import com.android.tools.r8.utils.StringUtils;
+import com.android.tools.r8.utils.codeinspector.ClassSubject;
+import com.android.tools.r8.utils.codeinspector.CodeInspector;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+
+@RunWith(Parameterized.class)
+public class EventuallyNonTargetedMethodTest extends TestBase {
+
+  static final String EXPECTED = StringUtils.lines("A::foo", "C::bar");
+
+  private final TestParameters parameters;
+
+  @Parameterized.Parameters(name = "{0}")
+  public static TestParametersCollection data() {
+    return getTestParameters().withAllRuntimesAndApiLevels().build();
+  }
+
+  public EventuallyNonTargetedMethodTest(TestParameters parameters) {
+    this.parameters = parameters;
+  }
+
+  @Test
+  public void test() throws Exception {
+    testForR8(parameters.getBackend())
+        .enableInliningAnnotations()
+        .enableMergeAnnotations()
+        .enableNeverClassInliningAnnotations()
+        .addInnerClasses(EventuallyNonTargetedMethodTest.class)
+        .addKeepMainRule(Main.class)
+        .setMinApi(parameters.getApiLevel())
+        .run(parameters.getRuntime(), Main.class)
+        .assertSuccessWithOutput(EXPECTED)
+        .inspect(this::checkIsFooPresent);
+  }
+
+  private void checkIsFooPresent(CodeInspector inspector) {
+    ClassSubject classSubject = inspector.clazz(C.class);
+    assertThat(classSubject, isPresent());
+    // TODO(b/150445487): Member rebinding will rewrite B::foo to A::foo causing C::foo to remain.
+    assertThat(classSubject.uniqueMethodWithName("foo"), isPresent());
+  }
+
+  @NeverMerge
+  private static class A {
+    @NeverInline
+    public void foo() {
+      System.out.println("A::foo");
+    }
+  }
+
+  @NeverMerge
+  @NeverClassInline
+  private static class B extends A {
+    // No override of foo, but B::foo will be the only target.
+  }
+
+  @NeverClassInline
+  private static class C extends A {
+
+    // Non-targeted override.
+    @Override
+    public void foo() {
+      System.out.println("C::foo");
+    }
+
+    @NeverInline
+    public void bar() {
+      System.out.println("C::bar");
+    }
+  }
+
+  private static class Main {
+    static boolean effectivelyFinalFalse = false;
+
+    public static void main(String[] args) {
+      new B().foo();
+      new C().bar();
+      if (effectivelyFinalFalse) {
+        // First round of tree shaking the below reference to A::foo will keep it live.
+        // This branch will then be removed and it should be possible to conclude C::foo dead.
+        new A().foo();
+      }
+    }
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/shaking/LibraryMethodOverrideInLambdaMarkingTest.java b/src/test/java/com/android/tools/r8/shaking/LibraryMethodOverrideInLambdaMarkingTest.java
index e3925ca..9d16769 100644
--- a/src/test/java/com/android/tools/r8/shaking/LibraryMethodOverrideInLambdaMarkingTest.java
+++ b/src/test/java/com/android/tools/r8/shaking/LibraryMethodOverrideInLambdaMarkingTest.java
@@ -63,7 +63,11 @@
     DexEncodedMethod method =
         clazz.lookupVirtualMethod(m -> m.method.name.toString().equals("iterator"));
     // TODO(b/149976493): Mark library overrides from lambda instances.
-    assertTrue(method.isLibraryMethodOverride().isFalse());
+    if (parameters.isCfRuntime()) {
+      assertTrue(method.isLibraryMethodOverride().isFalse());
+    } else {
+      assertTrue(method.isLibraryMethodOverride().isTrue());
+    }
   }
 
   static class TestClass {
diff --git a/src/test/java/com/android/tools/r8/utils/codeinspector/CodeInspector.java b/src/test/java/com/android/tools/r8/utils/codeinspector/CodeInspector.java
index 63c85d5..1f25605 100644
--- a/src/test/java/com/android/tools/r8/utils/codeinspector/CodeInspector.java
+++ b/src/test/java/com/android/tools/r8/utils/codeinspector/CodeInspector.java
@@ -26,7 +26,6 @@
 import com.android.tools.r8.graph.DexType;
 import com.android.tools.r8.graph.DexValue;
 import com.android.tools.r8.graph.DexValue.DexValueArray;
-import com.android.tools.r8.graph.DexValue.DexValueString;
 import com.android.tools.r8.ir.desugar.InterfaceMethodRewriter;
 import com.android.tools.r8.naming.ClassNameMapper;
 import com.android.tools.r8.naming.ClassNamingForNameMapper;
@@ -228,13 +227,12 @@
     }
     assert annotation.annotation.elements.length == 1;
     DexAnnotationElement element = annotation.annotation.elements[0];
-    assert element.value instanceof DexValueArray;
+    assert element.value.isDexValueArray();
     StringBuilder builder = new StringBuilder();
-    DexValueArray valueArray = (DexValueArray) element.value;
+    DexValueArray valueArray = element.value.asDexValueArray();
     for (DexValue value : valueArray.getValues()) {
-      assertTrue(value instanceof DexValueString);
-      DexValueString s = (DexValueString) value;
-      builder.append(s.getValue());
+      assertTrue(value.isDexValueString());
+      builder.append(value.asDexValueString().getValue());
     }
     return builder.toString();
   }
diff --git a/src/test/java/com/android/tools/r8/utils/codeinspector/KotlinClassMetadataReader.java b/src/test/java/com/android/tools/r8/utils/codeinspector/KotlinClassMetadataReader.java
index b3a0143..73972f4 100644
--- a/src/test/java/com/android/tools/r8/utils/codeinspector/KotlinClassMetadataReader.java
+++ b/src/test/java/com/android/tools/r8/utils/codeinspector/KotlinClassMetadataReader.java
@@ -8,7 +8,6 @@
 import com.android.tools.r8.graph.DexString;
 import com.android.tools.r8.graph.DexValue;
 import com.android.tools.r8.graph.DexValue.DexValueArray;
-import com.android.tools.r8.graph.DexValue.DexValueString;
 import com.android.tools.r8.kotlin.Kotlin;
 import java.util.IdentityHashMap;
 import java.util.Map;
@@ -51,10 +50,10 @@
   }
 
   private static int[] getUnboxedIntArray(DexValue v, String elementName) {
-    if (!(v instanceof DexValueArray)) {
+    if (!v.isDexValueArray()) {
       throw new MetadataError("invalid '" + elementName + "' value: " + v.toSourceString());
     }
-    DexValueArray intArrayValue = (DexValueArray) v;
+    DexValueArray intArrayValue = v.asDexValueArray();
     DexValue[] values = intArrayValue.getValues();
     int[] result = new int [values.length];
     for (int i = 0; i < values.length; i++) {
@@ -64,10 +63,10 @@
   }
 
   private static String[] getUnboxedStringArray(DexValue v, String elementName) {
-    if (!(v instanceof DexValueArray)) {
+    if (!v.isDexValueArray()) {
       throw new MetadataError("invalid '" + elementName + "' value: " + v.toSourceString());
     }
-    DexValueArray stringArrayValue = (DexValueArray) v;
+    DexValueArray stringArrayValue = v.asDexValueArray();
     DexValue[] values = stringArrayValue.getValues();
     String[] result = new String [values.length];
     for (int i = 0; i < values.length; i++) {
@@ -77,10 +76,10 @@
   }
 
   private static String getUnboxedString(DexValue v, String elementName) {
-    if (!(v instanceof DexValueString)) {
+    if (!v.isDexValueString()) {
       throw new MetadataError("invalid '" + elementName + "' value: " + v.toSourceString());
     }
-    return ((DexValueString) v).getValue().toString();
+    return v.asDexValueString().getValue().toString();
   }
 
   private static class MetadataError extends RuntimeException {
diff --git a/src/test/java/com/android/tools/r8/utils/codeinspector/MemberSubject.java b/src/test/java/com/android/tools/r8/utils/codeinspector/MemberSubject.java
index a5cca23..e2561b0 100644
--- a/src/test/java/com/android/tools/r8/utils/codeinspector/MemberSubject.java
+++ b/src/test/java/com/android/tools/r8/utils/codeinspector/MemberSubject.java
@@ -6,7 +6,7 @@
 
 import com.android.tools.r8.graph.DexAnnotationElement;
 import com.android.tools.r8.graph.DexValue;
-import com.android.tools.r8.graph.DexValue.DexValueString;
+import com.android.tools.r8.graph.DexValue.DexValueArray;
 import com.android.tools.r8.naming.MemberNaming.Signature;
 
 public abstract class MemberSubject extends Subject {
@@ -66,12 +66,12 @@
     assert annotation.getAnnotation().elements.length == 1;
     DexAnnotationElement element = annotation.getAnnotation().elements[0];
     assert element.name.toString().equals("value");
-    assert element.value instanceof DexValue.DexValueArray;
-    DexValue.DexValueArray array = (DexValue.DexValueArray) element.value;
+    assert element.value.isDexValueArray();
+    DexValueArray array = element.value.asDexValueArray();
     StringBuilder builder = new StringBuilder();
     for (DexValue value : array.getValues()) {
-      if (value instanceof DexValueString) {
-        builder.append(((DexValueString) value).value);
+      if (value.isDexValueString()) {
+        builder.append(value.asDexValueString().value);
       } else {
         builder.append(value.toString());
       }
diff --git a/tools/fmt-diff.py b/tools/fmt-diff.py
new file mode 100755
index 0000000..4e97f3a
--- /dev/null
+++ b/tools/fmt-diff.py
@@ -0,0 +1,31 @@
+#!/usr/bin/env python
+# 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.
+
+import os
+import subprocess
+import sys
+
+import utils
+
+from subprocess import Popen, PIPE
+
+GOOGLE_JAVA_FORMAT_DIFF = os.path.join(
+    utils.THIRD_PARTY,
+    'google-java-format',
+    'google-java-format-google-java-format-1.7',
+    'scripts',
+    'google-java-format-diff.py')
+
+def main():
+  upstream = subprocess.check_output(['git', 'cl', 'upstream']).strip()
+  git_diff_process = Popen(['git', 'diff', '-U0', upstream], stdout=PIPE)
+  fmt_process = Popen(
+      ['python', GOOGLE_JAVA_FORMAT_DIFF, '-p1', '-i'],
+      stdin=git_diff_process.stdout)
+  git_diff_process.stdout.close()
+  fmt_process.communicate()
+
+if __name__ == '__main__':
+  sys.exit(main())
diff --git a/tools/jdk.py b/tools/jdk.py
index 5791e50..bc40873 100755
--- a/tools/jdk.py
+++ b/tools/jdk.py
@@ -20,6 +20,15 @@
   else:
     return os.environ['JAVA_HOME']
 
+def GetJdk8Home():
+  root = os.path.join(JDK_DIR, 'jdk8')
+  if defines.IsLinux():
+    return os.path.join(root, 'linux-x86')
+  elif defines.IsOsX():
+    return os.path.join(root, 'darwin-x86')
+  else:
+    return os.environ['JAVA_HOME']
+
 def GetJavaExecutable(jdkHome=None):
   jdkHome = jdkHome if jdkHome else GetJdkHome()
   executable = 'java.exe' if defines.IsWindows() else 'java'
diff --git a/tools/run_on_app.py b/tools/run_on_app.py
index 07de70c..1687797 100755
--- a/tools/run_on_app.py
+++ b/tools/run_on_app.py
@@ -109,6 +109,10 @@
                     help='Run without debug asserts.',
                     default=False,
                     action='store_true')
+  result.add_option('--debug-agent',
+                    help='Run with debug agent.',
+                    default=False,
+                    action='store_true')
   result.add_option('--version',
                     help='The version of the app to run')
   result.add_option('-k',
@@ -519,6 +523,12 @@
     if 'allow-type-errors' in values:
       extra_args.append('-Dcom.android.tools.r8.allowTypeErrors=1')
 
+  if options.debug_agent:
+    if not options.compiler_build == 'full':
+      print('WARNING: Running debugging agent on r8lib is questionable...')
+    extra_args.append(
+      '-agentlib:jdwp=transport=dt_socket,server=y,suspend=y,address=*:5005')
+
   if not options.no_libraries:
     for lib in libraries:
       args.extend(['--lib', lib])
diff --git a/tools/run_on_as_app.py b/tools/run_on_as_app.py
index abd0f71..6d6f0fe 100755
--- a/tools/run_on_as_app.py
+++ b/tools/run_on_as_app.py
@@ -60,7 +60,8 @@
       'name': None,
       'releaseTarget': None,
       'signed_apk_name': None,
-      'skip': False
+      'skip': False,
+      'has_lint_task': True
     }
     self.__dict__ = dict(defaults.items() + fields.items())
 
@@ -98,7 +99,8 @@
               'id': 'com.numix.calculator',
               'dir': 'Calculator',
               'name': 'numix-calculator',
-              'has_instrumentation_tests': True
+              'has_instrumentation_tests': True,
+              'has_lint_task': False
           })
       ]
   }),
@@ -157,7 +159,8 @@
       'revision': '6e53458f167b6d78398da60c20fd0da01a232617',
       'apps': [
           App({
-              'id': 'com.chanapps.four.activity'
+              'id': 'com.chanapps.four.activity',
+              'has_lint_task': False
           })
       ]
   }),
@@ -203,7 +206,8 @@
               'id': 'com.google.samples.apps.iosched',
               'module': 'mobile',
               'min_sdk': 21,
-              'compile_sdk': 29
+              'compile_sdk': 29,
+              'has_lint_task': False
           })
       ]
   }),
@@ -256,7 +260,8 @@
       'revision': 'ed543099c7823be00f15d9340f94bdb7cb37d1e6',
       'apps': [
           App({
-              'id': 'org.schabi.newpipe'
+              'id': 'org.schabi.newpipe',
+              'has_lint_task': False
           })
       ]
   }),
@@ -305,7 +310,8 @@
       'apps': [
           App({
               'id': 'com.simplemobiletools.calendar.pro',
-              'signed_apk_name': 'calendar-release.apk'
+              'signed_apk_name': 'calendar-release.apk',
+              'has_lint_task': False
           })
       ]
   }),
@@ -316,7 +322,8 @@
       'apps': [
           App({
               'id': 'com.simplemobiletools.camera.pro',
-              'signed_apk_name': 'camera-release.apk'
+              'signed_apk_name': 'camera-release.apk',
+              'has_lint_task': False
           })
       ]
   }),
@@ -327,7 +334,8 @@
       'apps': [
           App({
               'id': 'com.simplemobiletools.filemanager.pro',
-              'signed_apk_name': 'file-manager-release.apk'
+              'signed_apk_name': 'file-manager-release.apk',
+              'has_lint_task': False
           })
       ]
   }),
@@ -338,7 +346,8 @@
       'apps': [
           App({
               'id': 'com.simplemobiletools.gallery.pro',
-              'signed_apk_name': 'gallery-release.apk'
+              'signed_apk_name': 'gallery-release.apk',
+              'has_lint_task': False
           })
       ]
   }),
@@ -364,7 +373,8 @@
           App({
               'id': 'eu.kanade.tachiyomi',
               'flavor': 'dev',
-              'min_sdk': 16
+              'min_sdk': 16,
+              'has_lint_task': False
           })
       ]
   }),
@@ -377,6 +387,7 @@
               'id': 'app.tivi',
               'min_sdk': 23,
               'compile_sdk': 28,
+              'has_lint_task': False
           })
       ]
   }),
@@ -708,20 +719,24 @@
       app, checkout_dir, proguard_config_dest)
 
   env_vars = {}
+  env_vars['JAVA_HOME'] = jdk.GetJdk8Home()
   env_vars['ANDROID_HOME'] = utils.getAndroidHome()
   if not options.disable_assertions:
     env_vars['JAVA_OPTS'] = '-ea:com.android.tools.r8...'
 
   release_target = app.releaseTarget
   if not release_target:
-    release_target = app.module.replace('/', ':') + ':' + 'assemble' + (
-        app.flavor.capitalize() if app.flavor else '') + 'Release'
+    app_module = app.module.replace('/', ':')
+    app_flavor = (app.flavor.capitalize() if app.flavor else '') + 'Release'
+    release_target = app_module + ':' + 'assemble' + app_flavor
 
   # Build using gradle.
   args = [release_target,
           '-g=' + os.path.join(checkout_dir, GRADLE_USER_HOME),
           '-Pandroid.enableR8=' + str(IsR8(shrinker)).lower(),
           '-Pandroid.enableR8.fullMode=' + str(IsR8FullMode(shrinker)).lower()]
+  if app.has_lint_task:
+    args.extend(['-x', app_module + ':lintVital' + app_flavor])
 
   # Warm up gradle if pre_runs > 0. For posterity we generate the same sequence
   # as the benchmarking at https://github.com/madsager/santa-tracker-android.
diff --git a/tools/toolhelper.py b/tools/toolhelper.py
index cc1a9cb..1eee1f7 100644
--- a/tools/toolhelper.py
+++ b/tools/toolhelper.py
@@ -14,9 +14,10 @@
 def run(tool, args, build=None, debug=True,
         profile=False, track_memory_file=None, extra_args=None,
         stderr=None, stdout=None, return_stdout=False, timeout=0, quiet=False,
-        cmd_prefix=[]):
+        cmd_prefix=None):
   cmd = []
-  cmd.extend(cmd_prefix)
+  if cmd_prefix:
+    cmd.extend(cmd_prefix)
   if build is None:
     build, args = extract_build_from_args(args)
   if build: