Merge commit '34e27f5ef1e7849efd311fd649791a0024ef686a' into dev-release
diff --git a/PRESUBMIT.py b/PRESUBMIT.py
index cc80809..7837f26 100644
--- a/PRESUBMIT.py
+++ b/PRESUBMIT.py
@@ -47,10 +47,26 @@
   """ % FMT_CMD))
   return results
 
+def CheckDeterministicDebuggingChanged(input_api, output_api):
+  for f in input_api.AffectedFiles():
+    path = f.LocalPath()
+    if not path.endswith('InternalOptions.java'):
+      continue
+    branch = (
+        check_output(['git', 'cl', 'upstream'])
+            .strip()
+            .replace('refs/heads/', ''))
+    diff = check_output(
+        ['git', 'diff', '--no-prefix', '-U0', branch, '--', path])
+    if 'DETERMINISTIC_DEBUGGING' in diff:
+      return [output_api.PresubmitError(diff)]
+  return []
+
 def CheckChange(input_api, output_api):
   results = []
   results.extend(CheckFormatting(input_api, output_api))
   results.extend(CheckDoNotMerge(input_api, output_api))
+  results.extend(CheckDeterministicDebuggingChanged(input_api, output_api))
   return results
 
 def CheckChangeOnCommit(input_api, output_api):
diff --git a/build.gradle b/build.gradle
index fd47c97..77b5865 100644
--- a/build.gradle
+++ b/build.gradle
@@ -35,7 +35,7 @@
 
 ext {
     androidSupportVersion = '25.4.0'
-    asmVersion = '7.1'
+    asmVersion = '7.2'
     espressoVersion = '3.0.0'
     fastutilVersion = '7.2.0'
     guavaVersion = '23.0'
diff --git a/src/main/java/com/android/tools/r8/GenerateLintFiles.java b/src/main/java/com/android/tools/r8/GenerateLintFiles.java
index a6d704b..bf55656 100644
--- a/src/main/java/com/android/tools/r8/GenerateLintFiles.java
+++ b/src/main/java/com/android/tools/r8/GenerateLintFiles.java
@@ -132,7 +132,7 @@
     List<DexEncodedMethod> directMethods = new ArrayList<>();
     List<DexEncodedMethod> virtualMethods = new ArrayList<>();
     for (DexEncodedMethod method : methods) {
-      assert method.method.holder == clazz.type;
+      assert method.holder() == clazz.type;
       CfCode code = null;
       if (!method.accessFlags.isAbstract() /*&& !method.accessFlags.isNative()*/) {
         code = buildEmptyThrowingCfCode(method.method);
diff --git a/src/main/java/com/android/tools/r8/PrintUses.java b/src/main/java/com/android/tools/r8/PrintUses.java
index 4b310aa..51b6ed7 100644
--- a/src/main/java/com/android/tools/r8/PrintUses.java
+++ b/src/main/java/com/android/tools/r8/PrintUses.java
@@ -203,7 +203,7 @@
           isStatic
               ? appInfo.lookupStaticTarget(field.holder, field)
               : appInfo.lookupInstanceTarget(field.holder, field);
-      if (baseField != null && baseField.field.holder != field.holder) {
+      if (baseField != null && baseField.holder() != field.holder) {
         field = baseField.field;
       }
       addType(field.holder);
@@ -214,7 +214,7 @@
           noObfuscationTypes.add(field.holder);
         }
         if (baseField.accessFlags.isVisibilityDependingOnPackage()) {
-          keepPackageNames.add(baseField.field.holder.getPackageName());
+          keepPackageNames.add(baseField.holder().getPackageName());
         }
         typeFields.add(field);
       }
@@ -234,7 +234,7 @@
           noObfuscationTypes.add(method.holder);
         }
         if (encodedMethod.accessFlags.isVisibilityDependingOnPackage()) {
-          keepPackageNames.add(encodedMethod.method.holder.getPackageName());
+          keepPackageNames.add(encodedMethod.holder().getPackageName());
         }
         typeMethods.add(method);
       }
@@ -247,7 +247,7 @@
     private void registerMethod(DexEncodedMethod method) {
       DexEncodedMethod superTarget =
           appInfo
-              .resolveMethod(method.method.holder, method.method)
+              .resolveMethod(method.holder(), method.method)
               .lookupInvokeSpecialTarget(context, appInfo);
       if (superTarget != null) {
         addMethod(superTarget.method);
@@ -477,7 +477,7 @@
       if (encodedMethod.accessFlags.isStatic()) {
         append("<clinit>");
       } else {
-        String holderName = encodedMethod.method.holder.toSourceString();
+        String holderName = encodedMethod.holder().toSourceString();
         String constructorName = holderName.substring(holderName.lastIndexOf('.') + 1);
         append(constructorName);
       }
diff --git a/src/main/java/com/android/tools/r8/R8.java b/src/main/java/com/android/tools/r8/R8.java
index 5bca79b1..15af102 100644
--- a/src/main/java/com/android/tools/r8/R8.java
+++ b/src/main/java/com/android/tools/r8/R8.java
@@ -34,7 +34,9 @@
 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.InterfaceMethodRewriter;
 import com.android.tools.r8.ir.desugar.NestedPrivateMethodLense;
 import com.android.tools.r8.ir.desugar.R8NestBasedAccessDesugaring;
 import com.android.tools.r8.ir.optimize.AssertionsRewriter;
@@ -45,6 +47,7 @@
 import com.android.tools.r8.ir.optimize.UninstantiatedTypeOptimization.UninstantiatedTypeOptimizationGraphLense;
 import com.android.tools.r8.ir.optimize.UnusedArgumentsCollector;
 import com.android.tools.r8.ir.optimize.UnusedArgumentsCollector.UnusedArgumentsGraphLense;
+import com.android.tools.r8.ir.optimize.enums.EnumUnboxingCfMethods;
 import com.android.tools.r8.ir.optimize.enums.EnumValueInfoMapCollector;
 import com.android.tools.r8.ir.optimize.info.OptimizationFeedbackSimple;
 import com.android.tools.r8.jar.CfApplicationWriter;
@@ -280,6 +283,11 @@
       if (!options.desugaredLibraryConfiguration.getRetargetCoreLibMember().isEmpty()) {
         DesugaredLibraryRetargeter.checkForAssumedLibraryTypes(appView);
       }
+      InterfaceMethodRewriter.checkForAssumedLibraryTypes(appView.appInfo(), options);
+      BackportedMethodRewriter.registerAssumedLibraryTypes(options);
+      if (options.enableEnumUnboxing) {
+        EnumUnboxingCfMethods.registerSynthesizedCodeReferences(options.itemFactory);
+      }
 
       List<ProguardConfigurationRule> synthesizedProguardRules = new ArrayList<>();
       timing.begin("Strip unused code");
@@ -364,21 +372,22 @@
           application = pruner.run(application);
 
           // Recompute the subtyping information.
+          Set<DexType> removedClasses = pruner.getRemovedClasses();
           appView.setAppInfo(
               appView
                   .appInfo()
                   .withLiveness()
                   .prunedCopyFrom(
                       application,
-                      pruner.getRemovedClasses(),
+                      removedClasses,
                       pruner.getMethodsToKeepForConfigurationDebugging()));
-          appView.setAppServices(appView.appServices().prunedCopy(pruner.getRemovedClasses()));
+          appView.setAppServices(appView.appServices().prunedCopy(removedClasses));
           new AbstractMethodRemover(appView.appInfo().withLiveness()).run();
 
           AnnotationRemover annotationRemover =
               annotationRemoverBuilder
                   .computeClassesToRetainInnerClassAttributeFor(appViewWithLiveness)
-                  .build(appViewWithLiveness);
+                  .build(appViewWithLiveness, removedClasses);
           annotationRemover.ensureValid().run();
           classesToRetainInnerClassAttributeFor =
               annotationRemover.getClassesToRetainInnerClassAttributeFor();
@@ -659,6 +668,7 @@
 
             TreePruner pruner = new TreePruner(appViewWithLiveness, treePrunerConfiguration);
             application = pruner.run(application);
+            Set<DexType> removedClasses = pruner.getRemovedClasses();
 
             if (options.usageInformationConsumer != null) {
               ExceptionUtils.withFinishedResourceHandler(
@@ -669,9 +679,9 @@
                     .appInfo()
                     .prunedCopyFrom(
                         application,
-                        CollectionUtils.mergeSets(prunedTypes, pruner.getRemovedClasses()),
+                        CollectionUtils.mergeSets(prunedTypes, removedClasses),
                         pruner.getMethodsToKeepForConfigurationDebugging()));
-            appView.setAppServices(appView.appServices().prunedCopy(pruner.getRemovedClasses()));
+            appView.setAppServices(appView.appServices().prunedCopy(removedClasses));
 
             // TODO(b/130721661): Enable this assert.
             // assert Inliner.verifyNoMethodsInlinedDueToSingleCallSite(appView);
@@ -694,7 +704,7 @@
             assert classesToRetainInnerClassAttributeFor != null;
             AnnotationRemover.builder()
                 .setClassesToRetainInnerClassAttributeFor(classesToRetainInnerClassAttributeFor)
-                .build(appView.withLiveness())
+                .build(appView.withLiveness(), removedClasses)
                 .run();
             if (!mainDexClasses.isEmpty()) {
               // Remove types that no longer exists from the computed main dex list.
diff --git a/src/main/java/com/android/tools/r8/R8Command.java b/src/main/java/com/android/tools/r8/R8Command.java
index fea04c7..3e9ae7e 100644
--- a/src/main/java/com/android/tools/r8/R8Command.java
+++ b/src/main/java/com/android/tools/r8/R8Command.java
@@ -3,6 +3,8 @@
 // BSD-style license that can be found in the LICENSE file.
 package com.android.tools.r8;
 
+import static com.android.tools.r8.utils.InternalOptions.DETERMINISTIC_DEBUGGING;
+
 import com.android.tools.r8.AssertionsConfiguration.AssertionTransformation;
 import com.android.tools.r8.ProgramResource.Kind;
 import com.android.tools.r8.errors.DexFileOverflowDiagnostic;
@@ -854,6 +856,7 @@
       internal.enableVerticalClassMerging = false;
       internal.enableClassStaticizer = false;
       internal.outline.enabled = false;
+      internal.enableEnumUnboxing = false;
     }
 
     // Amend the proguard-map consumer with options from the proguard configuration.
@@ -925,8 +928,10 @@
     internal.synthesizedClassPrefix = synthesizedClassPrefix;
     internal.desugaredLibraryKeepRuleConsumer = desugaredLibraryKeepRuleConsumer;
 
-    assert internal.threadCount == ThreadUtils.NOT_SPECIFIED;
-    internal.threadCount = getThreadCount();
+    if (!DETERMINISTIC_DEBUGGING) {
+      assert internal.threadCount == ThreadUtils.NOT_SPECIFIED;
+      internal.threadCount = getThreadCount();
+    }
 
     return internal;
   }
diff --git a/src/main/java/com/android/tools/r8/cf/CfCodePrinter.java b/src/main/java/com/android/tools/r8/cf/CfCodePrinter.java
index 93ee213..21a5392 100644
--- a/src/main/java/com/android/tools/r8/cf/CfCodePrinter.java
+++ b/src/main/java/com/android/tools/r8/cf/CfCodePrinter.java
@@ -75,6 +75,7 @@
   private Set<String> imports = new HashSet<>();
   private List<String> methods = new ArrayList<>();
   private Set<String> methodNames = new HashSet<>();
+  private Set<String> synthesizedTypes = new HashSet<>();
 
   // Per method structures.
 
@@ -261,7 +262,8 @@
     if (field != null) {
       return "options.itemFactory." + field;
     }
-    return "options.itemFactory.createSynthesizedType(" + quote(descriptor) + ")";
+    synthesizedTypes.add(descriptor);
+    return "options.itemFactory.createType(" + quote(descriptor) + ")";
   }
 
   private String dexProto(DexProto proto) {
@@ -523,4 +525,8 @@
   public void print(CfConstMethodType type) {
     throw new Unimplemented(type.getClass().getSimpleName());
   }
+
+  public Set<String> getSynthesizedTypes() {
+    return synthesizedTypes;
+  }
 }
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 5b4d9f2..93973c3 100644
--- a/src/main/java/com/android/tools/r8/cf/TypeVerificationHelper.java
+++ b/src/main/java/com/android/tools/r8/cf/TypeVerificationHelper.java
@@ -238,8 +238,8 @@
         if (argumentIndex < 0) {
           argumentType =
               code.method.isInstanceInitializer()
-                  ? new ThisInstanceInfo(instruction.asArgument(), code.method.method.holder)
-                  : createInitializedType(code.method.method.holder);
+                  ? new ThisInstanceInfo(instruction.asArgument(), code.method.holder())
+                  : createInitializedType(code.method.holder());
         } else {
           argumentType =
               createInitializedType(code.method.method.proto.parameters.values[argumentIndex]);
diff --git a/src/main/java/com/android/tools/r8/dex/FileWriter.java b/src/main/java/com/android/tools/r8/dex/FileWriter.java
index a7c3ec0..bd4c6da 100644
--- a/src/main/java/com/android/tools/r8/dex/FileWriter.java
+++ b/src/main/java/com/android/tools/r8/dex/FileWriter.java
@@ -351,10 +351,10 @@
     String originalClassName;
     if (proguardMap != null) {
       signature = proguardMap.originalSignatureOf(method.method);
-      originalClassName = proguardMap.originalNameOf(method.method.holder);
+      originalClassName = proguardMap.originalNameOf(method.holder());
     } else {
       signature = MethodSignature.fromDexMethod(method.method);
-      originalClassName = method.method.holder.toSourceString();
+      originalClassName = method.holder().toSourceString();
     }
     codeToSignatureMap.put(code, originalClassName + signature);
   }
diff --git a/src/main/java/com/android/tools/r8/errors/InvalidLibrarySuperclassDiagnostic.java b/src/main/java/com/android/tools/r8/errors/InvalidLibrarySuperclassDiagnostic.java
new file mode 100644
index 0000000..2392f0a
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/errors/InvalidLibrarySuperclassDiagnostic.java
@@ -0,0 +1,71 @@
+// 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.errors;
+
+import com.android.tools.r8.Keep;
+import com.android.tools.r8.origin.Origin;
+import com.android.tools.r8.position.Position;
+import com.android.tools.r8.references.ClassReference;
+import com.android.tools.r8.references.MethodReference;
+import com.android.tools.r8.utils.StringUtils;
+import java.util.List;
+
+/**
+ * Diagnostic for super types of library classes which are not library classes but required for
+ * desugaring.
+ */
+@Keep
+public class InvalidLibrarySuperclassDiagnostic implements DesugarDiagnostic {
+
+  private final Origin origin;
+  private final List<MethodReference> methods;
+  private final ClassReference libraryType;
+  private final ClassReference invalidSuperType;
+  private final String message;
+
+  public InvalidLibrarySuperclassDiagnostic(
+      Origin origin,
+      ClassReference libraryType,
+      ClassReference invalidSuperType,
+      String message,
+      List<MethodReference> methods) {
+    assert origin != null;
+    assert libraryType != null;
+    assert invalidSuperType != null;
+    assert message != null;
+    this.origin = origin;
+    this.libraryType = libraryType;
+    this.invalidSuperType = invalidSuperType;
+    this.message = message;
+    this.methods = methods;
+  }
+
+  @Override
+  public Origin getOrigin() {
+    return origin;
+  }
+
+  @Override
+  public Position getPosition() {
+    return Position.UNKNOWN;
+  }
+
+  @Override
+  public String getDiagnosticMessage() {
+    StringBuilder builder =
+        new StringBuilder()
+            .append("Superclass `")
+            .append(invalidSuperType.getTypeName())
+            .append("` of library class `")
+            .append(libraryType.getTypeName())
+            .append("` is ")
+            .append(message)
+            .append(
+                ". A superclass of a library class should be a library class. This is required for"
+                    + " the desugaring of ");
+    StringUtils.append(builder, methods, ", ", StringUtils.BraceType.NONE);
+    return builder.toString();
+  }
+}
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 a41e119..8b63cbf 100644
--- a/src/main/java/com/android/tools/r8/graph/AppInfo.java
+++ b/src/main/java/com/android/tools/r8/graph/AppInfo.java
@@ -141,6 +141,10 @@
 
   @Override
   public DexClass definitionFor(DexType type) {
+    return definitionForWithoutExistenceAssert(type);
+  }
+
+  public final DexClass definitionForWithoutExistenceAssert(DexType type) {
     assert checkIfObsolete();
     DexProgramClass cached = synthesizedClasses.get(type);
     if (cached != null) {
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 58611d4..4edc070 100644
--- a/src/main/java/com/android/tools/r8/graph/AppInfoWithSubtyping.java
+++ b/src/main/java/com/android/tools/r8/graph/AppInfoWithSubtyping.java
@@ -357,7 +357,7 @@
     return true;
   }
 
-  public boolean hasAnyInstantiatedLambdas(DexProgramClass clazz) {
+  public boolean isInstantiatedInterface(DexProgramClass clazz) {
     assert checkIfObsolete();
     return true; // Don't know, there might be.
   }
diff --git a/src/main/java/com/android/tools/r8/graph/CfCode.java b/src/main/java/com/android/tools/r8/graph/CfCode.java
index 0c4267c..897763a 100644
--- a/src/main/java/com/android/tools/r8/graph/CfCode.java
+++ b/src/main/java/com/android/tools/r8/graph/CfCode.java
@@ -400,7 +400,7 @@
   @Override
   public void registerCodeReferences(DexEncodedMethod method, UseRegistry registry) {
     for (CfInstruction instruction : instructions) {
-      instruction.registerUse(registry, method.method.holder);
+      instruction.registerUse(registry, method.holder());
     }
     for (CfTryCatch tryCatch : tryCatchRanges) {
       for (DexType guard : tryCatch.guards) {
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 85810ed..9dd0398 100644
--- a/src/main/java/com/android/tools/r8/graph/DexClass.java
+++ b/src/main/java/com/android/tools/r8/graph/DexClass.java
@@ -332,7 +332,7 @@
   }
 
   private boolean verifyCorrectnessOfFieldHolder(DexEncodedField field) {
-    assert field.field.holder == type
+    assert field.holder() == type
         : "Expected field `"
             + field.field.toSourceString()
             + "` to have holder `"
@@ -435,8 +435,7 @@
   }
 
   private boolean isSignaturePolymorphicMethod(DexEncodedMethod method, DexItemFactory factory) {
-    assert method.method.holder == factory.methodHandleType
-        || method.method.holder == factory.varHandleType;
+    assert method.holder() == factory.methodHandleType || method.holder() == factory.varHandleType;
     return method.accessFlags.isVarargs()
         && method.accessFlags.isNative()
         && method.method.proto.parameters.size() == 1
diff --git a/src/main/java/com/android/tools/r8/graph/DexClassAndMethod.java b/src/main/java/com/android/tools/r8/graph/DexClassAndMethod.java
index 37f8853..6c05f12 100644
--- a/src/main/java/com/android/tools/r8/graph/DexClassAndMethod.java
+++ b/src/main/java/com/android/tools/r8/graph/DexClassAndMethod.java
@@ -12,7 +12,7 @@
   private final DexEncodedMethod method;
 
   DexClassAndMethod(DexClass holder, DexEncodedMethod method) {
-    assert holder.type == method.method.holder;
+    assert holder.type == method.holder();
     this.holder = holder;
     this.method = method;
   }
diff --git a/src/main/java/com/android/tools/r8/graph/DexDebugEntryBuilder.java b/src/main/java/com/android/tools/r8/graph/DexDebugEntryBuilder.java
index 49611c6..e73c267 100644
--- a/src/main/java/com/android/tools/r8/graph/DexDebugEntryBuilder.java
+++ b/src/main/java/com/android/tools/r8/graph/DexDebugEntryBuilder.java
@@ -73,7 +73,7 @@
     int argumentRegister = code.registerSize - code.incomingRegisterSize;
     if (!method.accessFlags.isStatic()) {
       DexString name = factory.thisName;
-      DexType type = method.method.holder;
+      DexType type = method.holder();
       startArgument(argumentRegister, name, type);
       argumentRegister += ValueType.fromDexType(type).requiredRegisters();
     }
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 10427a3..0d5c50d 100644
--- a/src/main/java/com/android/tools/r8/graph/DexEncodedField.java
+++ b/src/main/java/com/android/tools/r8/graph/DexEncodedField.java
@@ -159,6 +159,10 @@
     return isStatic();
   }
 
+  public boolean isVolatile() {
+    return accessFlags.isVolatile();
+  }
+
   public boolean hasAnnotation() {
     return !annotations().isEmpty();
   }
@@ -206,7 +210,7 @@
           && singleValue.asSingleFieldValue().getField() == field) {
         return null;
       }
-      if (singleValue.isMaterializableInContext(appView, code.method.method.holder)) {
+      if (singleValue.isMaterializableInContext(appView, code.method.holder())) {
         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 01b0e84..bb1ee15 100644
--- a/src/main/java/com/android/tools/r8/graph/DexEncodedMethod.java
+++ b/src/main/java/com/android/tools/r8/graph/DexEncodedMethod.java
@@ -450,7 +450,7 @@
       WhyAreYouNotInliningReporter whyAreYouNotInliningReporter) {
     checkIfObsolete();
     return isInliningCandidate(
-        container.method.holder, inliningReason, appInfo, whyAreYouNotInliningReporter);
+        container.holder(), inliningReason, appInfo, whyAreYouNotInliningReporter);
   }
 
   public boolean isInliningCandidate(
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 30d9369..b0a86c6 100644
--- a/src/main/java/com/android/tools/r8/graph/DexItemFactory.java
+++ b/src/main/java/com/android/tools/r8/graph/DexItemFactory.java
@@ -26,6 +26,8 @@
 import com.android.tools.r8.ir.code.InvokeMethod;
 import com.android.tools.r8.ir.code.Position;
 import com.android.tools.r8.ir.code.Value;
+import com.android.tools.r8.ir.desugar.NestBasedAccessDesugaring;
+import com.android.tools.r8.ir.optimize.enums.EnumUnboxingRewriter;
 import com.android.tools.r8.kotlin.Kotlin;
 import com.android.tools.r8.utils.ArrayUtils;
 import com.android.tools.r8.utils.LRUCacheTable;
@@ -156,6 +158,7 @@
   public final DexString isEmptyMethodName = createString("isEmpty");
   public final DexString lengthMethodName = createString("length");
 
+  public final DexString concatMethodName = createString("concat");
   public final DexString containsMethodName = createString("contains");
   public final DexString startsWithMethodName = createString("startsWith");
   public final DexString endsWithMethodName = createString("endsWith");
@@ -254,6 +257,8 @@
   public final DexString throwableDescriptor = createString(throwableDescriptorString);
   public final DexString illegalAccessErrorDescriptor =
       createString("Ljava/lang/IllegalAccessError;");
+  public final DexString illegalArgumentExceptionDescriptor =
+      createString("Ljava/lang/IllegalArgumentException;");
   public final DexString icceDescriptor = createString("Ljava/lang/IncompatibleClassChangeError;");
   public final DexString exceptionInInitializerErrorDescriptor =
       createString("Ljava/lang/ExceptionInInitializerError;");
@@ -381,6 +386,8 @@
   public final DexType throwableType = createStaticallyKnownType(throwableDescriptor);
   public final DexType illegalAccessErrorType =
       createStaticallyKnownType(illegalAccessErrorDescriptor);
+  public final DexType illegalArgumentExceptionType =
+      createStaticallyKnownType(illegalArgumentExceptionDescriptor);
   public final DexType icceType = createStaticallyKnownType(icceDescriptor);
   public final DexType exceptionInInitializerErrorType =
       createStaticallyKnownType(exceptionInInitializerErrorDescriptor);
@@ -398,6 +405,15 @@
   public final DexType androidOsBuildVersionType =
       createStaticallyKnownType("Landroid/os/Build$VERSION;");
 
+  public final DexString nestConstructorDescriptor =
+      createString("L" + NestBasedAccessDesugaring.NEST_CONSTRUCTOR_NAME + ";");
+  public final DexType nestConstructorType = createStaticallyKnownType(nestConstructorDescriptor);
+
+  public final DexString enumUnboxingUtilityDescriptor =
+      createString("L" + EnumUnboxingRewriter.ENUM_UNBOXING_UTILITY_CLASS_NAME + ";");
+  public final DexType enumUnboxingUtilityType =
+      createStaticallyKnownType(enumUnboxingUtilityDescriptor);
+
   public final StringBuildingMethods stringBuilderMethods =
       new StringBuildingMethods(stringBuilderType);
   public final StringBuildingMethods stringBufferMethods =
@@ -415,6 +431,8 @@
   public final ConstructorMethods constructorMethods = new ConstructorMethods();
   public final EnumMethods enumMethods = new EnumMethods();
   public final NullPointerExceptionMethods npeMethods = new NullPointerExceptionMethods();
+  public final IllegalArgumentExceptionMethods illegalArgumentExceptionMethods =
+      new IllegalArgumentExceptionMethods();
   public final PrimitiveTypesBoxedTypeFields primitiveTypesBoxedTypeFields =
       new PrimitiveTypesBoxedTypeFields();
   public final AtomicFieldUpdaterMethods atomicFieldUpdaterMethods =
@@ -965,6 +983,13 @@
         createMethod(npeType, createProto(voidType, stringType), constructorMethodName);
   }
 
+  public class IllegalArgumentExceptionMethods {
+
+    public final DexMethod initWithMessage =
+        createMethod(
+            illegalArgumentExceptionType, createProto(voidType, stringType), initMethodName);
+  }
+
   /**
    * All boxed types (Boolean, Byte, ...) have a field named TYPE which contains the Class object
    * for the primitive type.
@@ -1051,6 +1076,7 @@
     public final DexMethod isEmpty;
     public final DexMethod length;
 
+    public final DexMethod concat;
     public final DexMethod contains;
     public final DexMethod startsWith;
     public final DexMethod endsWith;
@@ -1083,6 +1109,7 @@
       DexString[] needsOneObject = { objectDescriptor };
       DexString[] needsOneInt = { intDescriptor };
 
+      concat = createMethod(stringDescriptor, concatMethodName, stringDescriptor, needsOneString);
       contains = createMethod(
           stringDescriptor, containsMethodName, booleanDescriptor, needsOneCharSequence);
       startsWith = createMethod(
@@ -1414,6 +1441,12 @@
     return type;
   }
 
+  // Registration of a type that is only dynamically known (eg, in the desugared lib spec), but
+  // will be referenced during desugaring.
+  public void registerTypeNeededForDesugaring(DexType type) {
+    addPossiblySynthesizedType(type);
+  }
+
   private void addPossiblySynthesizedType(DexType type) {
     if (type.isArrayType()) {
       type = type.toBaseType(this);
diff --git a/src/main/java/com/android/tools/r8/graph/DexMethod.java b/src/main/java/com/android/tools/r8/graph/DexMethod.java
index 80605e5..5236191 100644
--- a/src/main/java/com/android/tools/r8/graph/DexMethod.java
+++ b/src/main/java/com/android/tools/r8/graph/DexMethod.java
@@ -6,6 +6,11 @@
 import com.android.tools.r8.dex.IndexedItemCollection;
 import com.android.tools.r8.errors.CompilationError;
 import com.android.tools.r8.naming.NamingLens;
+import com.android.tools.r8.references.MethodReference;
+import com.android.tools.r8.references.Reference;
+import com.android.tools.r8.references.TypeReference;
+import java.util.ArrayList;
+import java.util.List;
 
 public class DexMethod extends DexMember<DexEncodedMethod, DexMethod> {
 
@@ -28,6 +33,22 @@
     return "Method " + holder + "." + name + " " + proto.toString();
   }
 
+  public MethodReference asMethodReference(AppView<?> appView) {
+    List<TypeReference> parameters = new ArrayList<>();
+    for (DexType value : proto.parameters.values) {
+      parameters.add(Reference.typeFromDescriptor(value.toDescriptorString()));
+    }
+    TypeReference returnType =
+        proto.returnType == appView.dexItemFactory().voidType
+            ? null
+            : Reference.typeFromDescriptor(proto.returnType.toDescriptorString());
+    return Reference.method(
+        Reference.classFromDescriptor(holder.toDescriptorString()),
+        name.toString(),
+        parameters,
+        returnType);
+  }
+
   public int getArity() {
     return proto.parameters.size();
   }
diff --git a/src/main/java/com/android/tools/r8/graph/EnumValueInfoMapCollection.java b/src/main/java/com/android/tools/r8/graph/EnumValueInfoMapCollection.java
index 4fd96f7..23bfb99 100644
--- a/src/main/java/com/android/tools/r8/graph/EnumValueInfoMapCollection.java
+++ b/src/main/java/com/android/tools/r8/graph/EnumValueInfoMapCollection.java
@@ -7,6 +7,7 @@
 import com.google.common.collect.ImmutableMap;
 import java.util.Map;
 import java.util.Set;
+import java.util.function.BiConsumer;
 
 public class EnumValueInfoMapCollection {
 
@@ -94,6 +95,10 @@
       return map.get(field);
     }
 
+    public void forEach(BiConsumer<DexField, EnumValueInfo> consumer) {
+      map.forEach(consumer);
+    }
+
     EnumValueInfoMap rewrittenWithLens(GraphLense lens) {
       ImmutableMap.Builder<DexField, EnumValueInfo> builder = ImmutableMap.builder();
       map.forEach(
diff --git a/src/main/java/com/android/tools/r8/graph/GenericSignature.java b/src/main/java/com/android/tools/r8/graph/GenericSignature.java
index 049cb7a..97d49a5 100644
--- a/src/main/java/com/android/tools/r8/graph/GenericSignature.java
+++ b/src/main/java/com/android/tools/r8/graph/GenericSignature.java
@@ -96,6 +96,8 @@
  */
 public class GenericSignature {
 
+  private static final List<FormalTypeParameter> EMPTY_TYPE_PARAMS = ImmutableList.of();
+
   interface DexDefinitionSignature<T extends DexDefinition> {
     default boolean isClassSignature() {
       return false;
@@ -122,19 +124,51 @@
     }
   }
 
+  public static class FormalTypeParameter {
+
+    final String name;
+    final FieldTypeSignature classBound;
+    final List<FieldTypeSignature> interfaceBounds;
+
+    FormalTypeParameter(
+        String name, FieldTypeSignature classBound, List<FieldTypeSignature> interfaceBounds) {
+      this.name = name;
+      this.classBound = classBound;
+      this.interfaceBounds = interfaceBounds;
+    }
+
+    public String getName() {
+      return name;
+    }
+
+    public FieldTypeSignature getClassBound() {
+      return classBound;
+    }
+
+    public List<FieldTypeSignature> getInterfaceBounds() {
+      return interfaceBounds;
+    }
+  }
+
   public static class ClassSignature implements DexDefinitionSignature<DexClass> {
     static final ClassSignature UNKNOWN_CLASS_SIGNATURE =
-        new ClassSignature(ClassTypeSignature.UNKNOWN_CLASS_TYPE_SIGNATURE, ImmutableList.of());
+        new ClassSignature(
+            ImmutableList.of(),
+            ClassTypeSignature.UNKNOWN_CLASS_TYPE_SIGNATURE,
+            ImmutableList.of());
 
-    // TODO(b/129925954): encoding formal type parameters
+    final List<FormalTypeParameter> formalTypeParameters;
     final ClassTypeSignature superClassSignature;
     final List<ClassTypeSignature> superInterfaceSignatures;
 
     ClassSignature(
+        List<FormalTypeParameter> formalTypeParameters,
         ClassTypeSignature superClassSignature,
         List<ClassTypeSignature> superInterfaceSignatures) {
+      assert formalTypeParameters != null;
       assert superClassSignature != null;
       assert superInterfaceSignatures != null;
+      this.formalTypeParameters = formalTypeParameters;
       this.superClassSignature = superClassSignature;
       this.superInterfaceSignatures = superInterfaceSignatures;
     }
@@ -221,6 +255,10 @@
     public TypeVariableSignature asTypeVariableSignature() {
       return null;
     }
+
+    public boolean isUnknown() {
+      return this == ClassTypeSignature.UNKNOWN_CLASS_TYPE_SIGNATURE;
+    }
   }
 
   public static class ClassTypeSignature extends FieldTypeSignature {
@@ -331,7 +369,7 @@
   public static class TypeVariableSignature extends FieldTypeSignature {
     final String typeVariable;
 
-    TypeVariableSignature(String typeVariable) {
+    private TypeVariableSignature(String typeVariable) {
       assert typeVariable != null;
       this.typeVariable = typeVariable;
     }
@@ -350,6 +388,10 @@
     public ArrayTypeSignature toArrayTypeSignature(AppView<?> appView) {
       return new ArrayTypeSignature(this);
     }
+
+    public String getTypeVariable() {
+      return typeVariable;
+    }
   }
 
   // TODO(b/129925954): Canonicalization?
@@ -400,20 +442,24 @@
 
   public static class MethodTypeSignature implements DexDefinitionSignature<DexEncodedMethod> {
     static final MethodTypeSignature UNKNOWN_METHOD_TYPE_SIGNATURE =
-        new MethodTypeSignature(ImmutableList.of(), ReturnType.VOID, ImmutableList.of());
+        new MethodTypeSignature(
+            ImmutableList.of(), ImmutableList.of(), ReturnType.VOID, ImmutableList.of());
 
-    // TODO(b/129925954): encoding formal type parameters
+    final List<FormalTypeParameter> formalTypeParameters;
     final List<TypeSignature> typeSignatures;
     final ReturnType returnType;
     final List<TypeSignature> throwsSignatures;
 
     MethodTypeSignature(
+        final List<FormalTypeParameter> formalTypeParameters,
         List<TypeSignature> typeSignatures,
         ReturnType returnType,
         List<TypeSignature> throwsSignatures) {
+      assert formalTypeParameters != null;
       assert typeSignatures != null;
       assert returnType != null;
       assert throwsSignatures != null;
+      this.formalTypeParameters = formalTypeParameters;
       this.typeSignatures = typeSignatures;
       this.returnType = returnType;
       this.throwsSignatures = throwsSignatures;
@@ -443,6 +489,10 @@
     public MethodTypeSignature asMethodTypeSignature() {
       return this;
     }
+
+    public List<FormalTypeParameter> getFormalTypeParameters() {
+      return formalTypeParameters;
+    }
   }
 
   enum Kind {
@@ -518,7 +568,7 @@
 
     public static FieldTypeSignature toFieldTypeSignature(
         DexEncodedField field, AppView<AppInfoWithLiveness> appView) {
-      DexClass currentClassContext = appView.definitionFor(field.field.holder);
+      DexClass currentClassContext = appView.definitionFor(field.holder());
       DexDefinitionSignature<?> signature =
           toGenericSignature(currentClassContext, field, appView);
       if (signature != null) {
@@ -530,7 +580,7 @@
 
     public static MethodTypeSignature toMethodTypeSignature(
         DexEncodedMethod method, AppView<AppInfoWithLiveness> appView) {
-      DexClass currentClassContext = appView.definitionFor(method.method.holder);
+      DexClass currentClassContext = appView.definitionFor(method.holder());
       DexDefinitionSignature<?> signature =
           toGenericSignature(currentClassContext, method, appView);
       if (signature != null) {
@@ -688,7 +738,7 @@
     private ClassSignature parseClassSignature() {
       // ClassSignature ::= FormalTypeParameters? SuperclassSignature SuperinterfaceSignature*.
 
-      parseOptFormalTypeParameters();
+      List<FormalTypeParameter> formalTypeParameters = parseOptFormalTypeParameters();
 
       // SuperclassSignature ::= ClassTypeSignature.
       ClassTypeSignature superClassSignature =
@@ -700,42 +750,53 @@
         builder.add(parseClassTypeSignature(ParserPosition.CLASS_SUPER_OR_INTERFACE_ANNOTATION));
       }
 
-      return new ClassSignature(superClassSignature, builder.build());
+      return new ClassSignature(formalTypeParameters, superClassSignature, builder.build());
     }
 
-    private void parseOptFormalTypeParameters() {
+    private List<FormalTypeParameter> parseOptFormalTypeParameters() {
       // FormalTypeParameters ::= "<" FormalTypeParameter+ ">".
-
-      if (symbol == '<') {
-        scanSymbol();
-
-        updateFormalTypeParameter();
-
-        while ((symbol != '>') && (symbol > 0)) {
-          updateFormalTypeParameter();
-        }
-
-        expect('>');
+      if (symbol != '<') {
+        return EMPTY_TYPE_PARAMS;
       }
+      scanSymbol();
+
+      ImmutableList.Builder<FormalTypeParameter> builder = ImmutableList.builder();
+      while ((symbol != '>') && (symbol > 0)) {
+        builder.add(updateFormalTypeParameter());
+      }
+      expect('>');
+      return builder.build();
     }
 
-    private void updateFormalTypeParameter() {
+    private FormalTypeParameter updateFormalTypeParameter() {
       // FormalTypeParameter ::= Identifier ClassBound InterfaceBound*.
       scanIdentifier();
       assert identifier != null;
 
+      String typeParameterIdentifier = identifier;
+
       // ClassBound ::= ":" FieldTypeSignature?.
       expect(':');
 
+      FieldTypeSignature classBound = ClassTypeSignature.UNKNOWN_CLASS_TYPE_SIGNATURE;
       if (symbol == 'L' || symbol == '[' || symbol == 'T') {
-        parseFieldTypeSignature(ParserPosition.MEMBER_ANNOTATION);
+        classBound = parseFieldTypeSignature(ParserPosition.MEMBER_ANNOTATION);
       }
 
+      // Only build the interfacebound builder, which is uncommon, if we actually see an interface.
+      ImmutableList.Builder<FieldTypeSignature> builder = null;
       while (symbol == ':') {
         // InterfaceBound ::= ":" FieldTypeSignature.
+        if (builder == null) {
+          builder = ImmutableList.builder();
+        }
         scanSymbol();
-        parseFieldTypeSignature(ParserPosition.MEMBER_ANNOTATION);
+        builder.add(parseFieldTypeSignature(ParserPosition.MEMBER_ANNOTATION));
       }
+      if (builder == null) {
+        return new FormalTypeParameter(typeParameterIdentifier, classBound, null);
+      }
+      return new FormalTypeParameter(typeParameterIdentifier, classBound, builder.build());
     }
 
     private FieldTypeSignature parseFieldTypeSignature(ParserPosition parserPosition) {
@@ -862,7 +923,7 @@
     private MethodTypeSignature parseMethodTypeSignature() {
       // MethodTypeSignature ::=
       //     FormalTypeParameters? "(" TypeSignature* ")" ReturnType ThrowsSignature*.
-      parseOptFormalTypeParameters();
+      List<FormalTypeParameter> formalTypeParameters = parseOptFormalTypeParameters();
 
       expect('(');
 
@@ -890,7 +951,10 @@
       }
 
       return new MethodTypeSignature(
-          parameterSignatureBuilder.build(), returnType, throwsSignatureBuilder.build());
+          formalTypeParameters,
+          parameterSignatureBuilder.build(),
+          returnType,
+          throwsSignatureBuilder.build());
     }
 
     private ReturnType updateReturnType() {
diff --git a/src/main/java/com/android/tools/r8/graph/GraphLense.java b/src/main/java/com/android/tools/r8/graph/GraphLense.java
index 7d2d844..e5de7ff 100644
--- a/src/main/java/com/android/tools/r8/graph/GraphLense.java
+++ b/src/main/java/com/android/tools/r8/graph/GraphLense.java
@@ -15,7 +15,6 @@
 import java.util.ArrayDeque;
 import java.util.Collections;
 import java.util.Deque;
-import java.util.HashSet;
 import java.util.IdentityHashMap;
 import java.util.Map;
 import java.util.Set;
@@ -710,7 +709,7 @@
 
     @Override
     public Set<DexMethod> lookupMethodInAllContexts(DexMethod method) {
-      Set<DexMethod> result = new HashSet<>();
+      Set<DexMethod> result = Sets.newIdentityHashSet();
       for (DexMethod previous : previousLense.lookupMethodInAllContexts(method)) {
         result.add(methodMap.getOrDefault(previous, previous));
       }
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 269b36b..6fecde6 100644
--- a/src/main/java/com/android/tools/r8/graph/MethodCollection.java
+++ b/src/main/java/com/android/tools/r8/graph/MethodCollection.java
@@ -228,7 +228,7 @@
   }
 
   private boolean verifyCorrectnessOfMethodHolder(DexEncodedMethod method) {
-    assert method.method.holder == holder.type
+    assert method.holder() == holder.type
         : "Expected method `"
             + method.method.toSourceString()
             + "` to have holder `"
diff --git a/src/main/java/com/android/tools/r8/graph/ObjectAllocationInfoCollection.java b/src/main/java/com/android/tools/r8/graph/ObjectAllocationInfoCollection.java
index 2ed505e..546bf90 100644
--- a/src/main/java/com/android/tools/r8/graph/ObjectAllocationInfoCollection.java
+++ b/src/main/java/com/android/tools/r8/graph/ObjectAllocationInfoCollection.java
@@ -20,6 +20,12 @@
 
   boolean isInstantiatedDirectly(DexProgramClass clazz);
 
+  boolean isInstantiatedDirectlyOrHasInstantiatedSubtype(DexProgramClass clazz);
+
+  boolean isInterfaceWithUnknownSubtypeHierarchy(DexProgramClass clazz);
+
+  boolean isImmediateInterfaceOfInstantiatedLambda(DexProgramClass clazz);
+
   ObjectAllocationInfoCollection rewrittenWithLens(
       DexDefinitionSupplier definitions, GraphLense lens);
 }
diff --git a/src/main/java/com/android/tools/r8/graph/ObjectAllocationInfoCollectionImpl.java b/src/main/java/com/android/tools/r8/graph/ObjectAllocationInfoCollectionImpl.java
index 3869d23..5999bbf 100644
--- a/src/main/java/com/android/tools/r8/graph/ObjectAllocationInfoCollectionImpl.java
+++ b/src/main/java/com/android/tools/r8/graph/ObjectAllocationInfoCollectionImpl.java
@@ -21,28 +21,101 @@
 import java.util.Set;
 import java.util.function.BiConsumer;
 import java.util.function.Consumer;
-import java.util.function.Predicate;
 
-/** Stores the set of instantiated classes along with their allocation sites. */
-public class ObjectAllocationInfoCollectionImpl implements ObjectAllocationInfoCollection {
+/**
+ * Provides information about all possibly instantiated classes and lambdas, their allocation sites,
+ * if known, as well as the full subtyping hierarchy of types above them.
+ */
+public abstract class ObjectAllocationInfoCollectionImpl implements ObjectAllocationInfoCollection {
 
-  private final Map<DexProgramClass, Set<DexEncodedMethod>> classesWithAllocationSiteTracking;
-  private final Set<DexProgramClass> classesWithoutAllocationSiteTracking;
+  /** Instantiated classes with the contexts of the instantiations. */
+  final Map<DexProgramClass, Set<DexEncodedMethod>> classesWithAllocationSiteTracking =
+      new IdentityHashMap<>();
 
-  private ObjectAllocationInfoCollectionImpl(
-      Map<DexProgramClass, Set<DexEncodedMethod>> classesWithAllocationSiteTracking,
-      Set<DexProgramClass> classesWithoutAllocationSiteTracking) {
-    this.classesWithAllocationSiteTracking = classesWithAllocationSiteTracking;
-    this.classesWithoutAllocationSiteTracking = classesWithoutAllocationSiteTracking;
+  /** Instantiated classes without contexts. */
+  final Set<DexProgramClass> classesWithoutAllocationSiteTracking = Sets.newIdentityHashSet();
+
+  /**
+   * Set of interface types for which the subtype hierarchy is unknown from that type.
+   *
+   * <p>E.g., the type is kept thus there could be instantiations of subtypes.
+   *
+   * <p>TODO(b/145344105): Generalize this to typesWithUnknownSubtypeHierarchy.
+   */
+  final Set<DexProgramClass> interfacesWithUnknownSubtypeHierarchy = Sets.newIdentityHashSet();
+
+  /** Map of types directly implemented by lambdas to those lambdas. */
+  final Map<DexType, List<LambdaDescriptor>> instantiatedLambdas = new IdentityHashMap<>();
+
+  /**
+   * Hierarchy for instantiated types mapping a type to the set of immediate subtypes for which some
+   * subtype is either an instantiated class, kept interface or is implemented by an instantiated
+   * lambda.
+   */
+  Map<DexType, Set<DexClass>> instantiatedHierarchy = new IdentityHashMap<>();
+
+  private ObjectAllocationInfoCollectionImpl() {
+    // Only builder can allocate an instance.
   }
 
   public static Builder builder(boolean trackAllocationSites, GraphReporter reporter) {
     return new Builder(trackAllocationSites, reporter);
   }
 
-  public void markNoLongerInstantiated(DexProgramClass clazz) {
-    classesWithAllocationSiteTracking.remove(clazz);
-    classesWithoutAllocationSiteTracking.remove(clazz);
+  public abstract void mutate(Consumer<Builder> mutator, AppInfo appInfo);
+
+  /**
+   * True if a class type might be instantiated directly at the given type.
+   *
+   * <p>Should not be called on interface types.
+   *
+   * <p>TODO(b/145344105): Extend this to not be called on any abstract types.
+   */
+  @Override
+  public boolean isInstantiatedDirectly(DexProgramClass clazz) {
+    if (clazz.isInterface()) {
+      return false;
+    }
+    if (classesWithAllocationSiteTracking.containsKey(clazz)) {
+      assert !classesWithAllocationSiteTracking.get(clazz).isEmpty();
+      return true;
+    }
+    return classesWithoutAllocationSiteTracking.contains(clazz);
+  }
+
+  /** True if the type or subtype of it might be instantiated. */
+  @Override
+  public boolean isInstantiatedDirectlyOrHasInstantiatedSubtype(DexProgramClass clazz) {
+    return (!clazz.isInterface() && isInstantiatedDirectly(clazz))
+        || hasInstantiatedStrictSubtype(clazz);
+  }
+
+  /** True if there might exist an instantiated (strict) subtype of the given type. */
+  public boolean hasInstantiatedStrictSubtype(DexProgramClass clazz) {
+    if (instantiatedHierarchy.get(clazz.type) != null) {
+      return true;
+    }
+    if (!clazz.isInterface()) {
+      return false;
+    }
+    return interfacesWithUnknownSubtypeHierarchy.contains(clazz)
+        || isImmediateInterfaceOfInstantiatedLambda(clazz);
+  }
+
+  /** True if the type is an interface that has unknown instantiations, eg, by being kept. */
+  @Override
+  public boolean isInterfaceWithUnknownSubtypeHierarchy(DexProgramClass clazz) {
+    return clazz.isInterface() && interfacesWithUnknownSubtypeHierarchy.contains(clazz);
+  }
+
+  /** Returns true if the type is an immediate interface of an instantiated lambda. */
+  @Override
+  public boolean isImmediateInterfaceOfInstantiatedLambda(DexProgramClass iface) {
+    return iface.isInterface() && instantiatedLambdas.get(iface.type) != null;
+  }
+
+  public Set<DexClass> getImmediateSubtypesInInstantiatedHierarchy(DexType type) {
+    return instantiatedHierarchy.get(type);
   }
 
   @Override
@@ -57,238 +130,15 @@
   }
 
   @Override
-  public boolean isInstantiatedDirectly(DexProgramClass clazz) {
-    if (classesWithAllocationSiteTracking.containsKey(clazz)) {
-      assert !classesWithAllocationSiteTracking.get(clazz).isEmpty();
-      return true;
-    }
-    return classesWithoutAllocationSiteTracking.contains(clazz);
-  }
-
-  @Override
   public ObjectAllocationInfoCollectionImpl rewrittenWithLens(
       DexDefinitionSupplier definitions, GraphLense lens) {
-    return builder(true, null).rewrittenWithLens(this, definitions, lens).build();
+    return builder(true, null).rewrittenWithLens(this, definitions, lens).build(definitions);
   }
 
-  public static class Builder {
-
-    private final boolean trackAllocationSites;
-
-    /** Instantiated classes with the contexts of the instantiations. */
-    private final Map<DexProgramClass, Set<DexEncodedMethod>> classesWithAllocationSiteTracking =
-        new IdentityHashMap<>();
-
-    /** Instantiated classes without contexts. */
-    private final Set<DexProgramClass> classesWithoutAllocationSiteTracking =
-        Sets.newIdentityHashSet();
-
-    /** Set of types directly implemented by a lambda. */
-    private final Map<DexType, List<LambdaDescriptor>> instantiatedLambdas =
-        new IdentityHashMap<>();
-
-    /**
-     * Hierarchy for instantiated types mapping a type to the set of immediate subtypes for which
-     * some subtype is either instantiated or is implemented by an instantiated lambda.
-     */
-    private final Map<DexType, Set<DexClass>> instantiatedHierarchy = new IdentityHashMap<>();
-
-    /**
-     * Set of interface types for which there may be instantiations, such as lambda expressions or
-     * explicit keep rules.
-     */
-    private final Set<DexProgramClass> instantiatedInterfaceTypes = Sets.newIdentityHashSet();
-
-    /** Subset of the above that are marked instantiated by usages that are not lambdas. */
-    public final Set<DexProgramClass> unknownInstantiatedInterfaceTypes = Sets.newIdentityHashSet();
-
-    private GraphReporter reporter;
-
-    private Builder(boolean trackAllocationSites, GraphReporter reporter) {
-      this.trackAllocationSites = trackAllocationSites;
-      this.reporter = reporter;
-    }
-
-    private boolean shouldTrackAllocationSitesForClass(
-        DexProgramClass clazz, InstantiationReason instantiationReason) {
-      if (!trackAllocationSites) {
-        return false;
-      }
-      if (instantiationReason != InstantiationReason.NEW_INSTANCE_INSTRUCTION) {
-        // There is an allocation site which is not a new-instance instruction.
-        return false;
-      }
-      if (classesWithoutAllocationSiteTracking.contains(clazz)) {
-        // We already gave up on tracking the allocation sites for `clazz` previously.
-        return false;
-      }
-      // We currently only use allocation site information for instance field value propagation.
-      return !clazz.instanceFields().isEmpty();
-    }
-
-    public boolean isInstantiatedDirectlyOrIsInstantiationLeaf(DexProgramClass clazz) {
-      if (clazz.isInterface()) {
-        return instantiatedInterfaceTypes.contains(clazz);
-      }
-      return isInstantiatedDirectly(clazz);
-    }
-
-    public boolean isInstantiatedDirectly(DexProgramClass clazz) {
-      assert !clazz.isInterface();
-      if (classesWithAllocationSiteTracking.containsKey(clazz)) {
-        assert !classesWithAllocationSiteTracking.get(clazz).isEmpty();
-        return true;
-      }
-      return classesWithoutAllocationSiteTracking.contains(clazz);
-    }
-
-    public boolean isInstantiatedDirectlyOrHasInstantiatedSubtype(DexProgramClass clazz) {
-      return isInstantiatedDirectlyOrIsInstantiationLeaf(clazz)
-          || instantiatedHierarchy.containsKey(clazz.type);
-    }
-
-    public void forEachInstantiatedSubType(
-        DexType type,
-        Consumer<DexProgramClass> onClass,
-        Consumer<LambdaDescriptor> onLambda,
-        AppInfo appInfo) {
-      internalForEachInstantiatedSubType(
-          type,
-          onClass,
-          onLambda,
-          instantiatedHierarchy,
-          instantiatedLambdas,
-          this::isInstantiatedDirectlyOrIsInstantiationLeaf,
-          appInfo);
-    }
-
-    public Set<DexClass> getImmediateSubtypesInInstantiatedHierarchy(DexType type) {
-      return instantiatedHierarchy.get(type);
-    }
-
-    /**
-     * Records that {@param clazz} is instantiated in {@param context}.
-     *
-     * @return true if {@param clazz} was not instantiated before.
-     */
-    public boolean recordDirectAllocationSite(
-        DexProgramClass clazz,
-        DexEncodedMethod context,
-        InstantiationReason instantiationReason,
-        KeepReason keepReason,
-        AppInfo appInfo) {
-      assert !clazz.isInterface();
-      if (reporter != null) {
-        reporter.registerClass(clazz, keepReason);
-      }
-      populateInstantiatedHierarchy(appInfo, clazz);
-      if (shouldTrackAllocationSitesForClass(clazz, instantiationReason)) {
-        assert context != null;
-        Set<DexEncodedMethod> allocationSitesForClass =
-            classesWithAllocationSiteTracking.computeIfAbsent(
-                clazz, ignore -> Sets.newIdentityHashSet());
-        allocationSitesForClass.add(context);
-        return allocationSitesForClass.size() == 1;
-      }
-      if (classesWithoutAllocationSiteTracking.add(clazz)) {
-        Set<DexEncodedMethod> allocationSitesForClass =
-            classesWithAllocationSiteTracking.remove(clazz);
-        return allocationSitesForClass == null;
-      }
-      return false;
-    }
-
-    public boolean recordInstantiatedInterface(DexProgramClass iface) {
-      assert iface.isInterface();
-      assert !iface.isAnnotation();
-      unknownInstantiatedInterfaceTypes.add(iface);
-      return instantiatedInterfaceTypes.add(iface);
-    }
-
-    public void recordInstantiatedLambdaInterface(
-        DexType iface, LambdaDescriptor lambda, AppInfo appInfo) {
-      instantiatedLambdas.computeIfAbsent(iface, key -> new ArrayList<>()).add(lambda);
-      populateInstantiatedHierarchy(appInfo, iface);
-    }
-
-    private void populateInstantiatedHierarchy(AppInfo appInfo, DexType type) {
-      DexClass clazz = appInfo.definitionFor(type);
-      if (clazz != null) {
-        populateInstantiatedHierarchy(appInfo, clazz);
-      }
-    }
-
-    private void populateInstantiatedHierarchy(AppInfo appInfo, DexClass clazz) {
-      if (clazz.superType != null) {
-        populateInstantiatedHierarchy(appInfo, clazz.superType, clazz);
-      }
-      for (DexType iface : clazz.interfaces.values) {
-        populateInstantiatedHierarchy(appInfo, iface, clazz);
-      }
-    }
-
-    private void populateInstantiatedHierarchy(AppInfo appInfo, DexType type, DexClass subtype) {
-      if (type == appInfo.dexItemFactory().objectType) {
-        return;
-      }
-      Set<DexClass> subtypes = instantiatedHierarchy.get(type);
-      if (subtypes != null) {
-        subtypes.add(subtype);
-        return;
-      }
-      // This is the first time an instantiation appears below 'type', recursively populate.
-      subtypes = Sets.newIdentityHashSet();
-      subtypes.add(subtype);
-      instantiatedHierarchy.put(type, subtypes);
-      populateInstantiatedHierarchy(appInfo, type);
-    }
-
-    Builder rewrittenWithLens(
-        ObjectAllocationInfoCollectionImpl objectAllocationInfos,
-        DexDefinitionSupplier definitions,
-        GraphLense lens) {
-      objectAllocationInfos.classesWithAllocationSiteTracking.forEach(
-          (clazz, allocationSitesForClass) -> {
-            DexType type = lens.lookupType(clazz.type);
-            if (type.isPrimitiveType()) {
-              return;
-            }
-            DexProgramClass rewrittenClass = asProgramClassOrNull(definitions.definitionFor(type));
-            assert rewrittenClass != null;
-            assert !classesWithAllocationSiteTracking.containsKey(rewrittenClass);
-            classesWithAllocationSiteTracking.put(
-                rewrittenClass,
-                LensUtils.rewrittenWithRenamedSignature(
-                    allocationSitesForClass, definitions, lens));
-          });
-      objectAllocationInfos.classesWithoutAllocationSiteTracking.forEach(
-          clazz -> {
-            DexType type = lens.lookupType(clazz.type);
-            if (type.isPrimitiveType()) {
-              return;
-            }
-            DexProgramClass rewrittenClass = asProgramClassOrNull(definitions.definitionFor(type));
-            assert rewrittenClass != null;
-            assert !classesWithAllocationSiteTracking.containsKey(rewrittenClass);
-            assert !classesWithoutAllocationSiteTracking.contains(rewrittenClass);
-            classesWithoutAllocationSiteTracking.add(rewrittenClass);
-          });
-      return this;
-    }
-
-    public ObjectAllocationInfoCollectionImpl build() {
-      return new ObjectAllocationInfoCollectionImpl(
-          classesWithAllocationSiteTracking, classesWithoutAllocationSiteTracking);
-    }
-  }
-
-  private static void internalForEachInstantiatedSubType(
+  public void forEachInstantiatedSubType(
       DexType type,
-      Consumer<DexProgramClass> subTypeConsumer,
-      Consumer<LambdaDescriptor> lambdaConsumer,
-      Map<DexType, Set<DexClass>> instantiatedHierarchy,
-      Map<DexType, List<LambdaDescriptor>> instantiatedLambdas,
-      Predicate<DexProgramClass> isInstantiatedDirectly,
+      Consumer<DexProgramClass> onClass,
+      Consumer<LambdaDescriptor> onLambda,
       AppInfo appInfo) {
     WorkList<DexClass> worklist = WorkList.newIdentityWorkList();
     if (type == appInfo.dexItemFactory().objectType) {
@@ -307,7 +157,7 @@
         // If no definition for the type is found, populate the worklist with any
         // instantiated subtypes and callback with any lambda instance.
         worklist.addIfNotSeen(instantiatedHierarchy.getOrDefault(type, Collections.emptySet()));
-        instantiatedLambdas.getOrDefault(type, Collections.emptyList()).forEach(lambdaConsumer);
+        instantiatedLambdas.getOrDefault(type, Collections.emptyList()).forEach(onLambda);
       } else {
         worklist.addIfNotSeen(initialClass);
       }
@@ -317,12 +167,287 @@
       DexClass clazz = worklist.next();
       if (clazz.isProgramClass()) {
         DexProgramClass programClass = clazz.asProgramClass();
-        if (isInstantiatedDirectly.test(programClass)) {
-          subTypeConsumer.accept(programClass);
+        if (isInstantiatedDirectly(programClass)
+            || isInterfaceWithUnknownSubtypeHierarchy(programClass)) {
+          onClass.accept(programClass);
         }
       }
       worklist.addIfNotSeen(instantiatedHierarchy.getOrDefault(clazz.type, Collections.emptySet()));
-      instantiatedLambdas.getOrDefault(clazz.type, Collections.emptyList()).forEach(lambdaConsumer);
+      instantiatedLambdas.getOrDefault(clazz.type, Collections.emptyList()).forEach(onLambda);
+    }
+  }
+
+  public static class Builder extends ObjectAllocationInfoCollectionImpl {
+
+    private static class Data {
+
+      private final boolean trackAllocationSites;
+      private final GraphReporter reporter;
+
+      private Data(boolean trackAllocationSites, GraphReporter reporter) {
+        this.trackAllocationSites = trackAllocationSites;
+        this.reporter = reporter;
+      }
+    }
+
+    // Pointer to data valid during the duration of the builder.
+    private Data data;
+
+    private Builder(boolean trackAllocationSites, GraphReporter reporter) {
+      data = new Data(trackAllocationSites, reporter);
+    }
+
+    public ObjectAllocationInfoCollectionImpl build(DexDefinitionSupplier definitions) {
+      assert data != null;
+      if (instantiatedHierarchy == null) {
+        repopulateInstantiatedHierarchy(definitions);
+      }
+      assert validate(definitions);
+      data = null;
+      return this;
+    }
+
+    // Consider a mutation interface that has just the mutation methods.
+    @Override
+    public void mutate(Consumer<Builder> mutator, AppInfo appInfo) {
+      mutator.accept(this);
+      repopulateInstantiatedHierarchy(appInfo);
+    }
+
+    private boolean shouldTrackAllocationSitesForClass(
+        DexProgramClass clazz, InstantiationReason instantiationReason) {
+      if (!data.trackAllocationSites) {
+        return false;
+      }
+      if (instantiationReason != InstantiationReason.NEW_INSTANCE_INSTRUCTION) {
+        // There is an allocation site which is not a new-instance instruction.
+        return false;
+      }
+      if (classesWithoutAllocationSiteTracking.contains(clazz)) {
+        // We already gave up on tracking the allocation sites for `clazz` previously.
+        return false;
+      }
+      // We currently only use allocation site information for instance field value propagation.
+      return !clazz.instanceFields().isEmpty();
+    }
+
+    /**
+     * Records that {@param clazz} is instantiated in {@param context}.
+     *
+     * @return true if {@param clazz} was not instantiated before.
+     */
+    public boolean recordDirectAllocationSite(
+        DexProgramClass clazz,
+        DexEncodedMethod context,
+        InstantiationReason instantiationReason,
+        KeepReason keepReason,
+        AppInfo appInfo) {
+      assert !clazz.isInterface();
+      if (data.reporter != null) {
+        data.reporter.registerClass(clazz, keepReason);
+      }
+      populateInstantiatedHierarchy(appInfo, clazz);
+      if (shouldTrackAllocationSitesForClass(clazz, instantiationReason)) {
+        assert context != null;
+        Set<DexEncodedMethod> allocationSitesForClass =
+            classesWithAllocationSiteTracking.computeIfAbsent(
+                clazz, ignore -> Sets.newIdentityHashSet());
+        allocationSitesForClass.add(context);
+        return allocationSitesForClass.size() == 1;
+      }
+      if (classesWithoutAllocationSiteTracking.add(clazz)) {
+        Set<DexEncodedMethod> allocationSitesForClass =
+            classesWithAllocationSiteTracking.remove(clazz);
+        return allocationSitesForClass == null;
+      }
+      return false;
+    }
+
+    public boolean recordInstantiatedInterface(DexProgramClass iface, AppInfo appInfo) {
+      assert iface.isInterface();
+      assert !iface.isAnnotation();
+      if (interfacesWithUnknownSubtypeHierarchy.add(iface)) {
+        populateInstantiatedHierarchy(appInfo, iface);
+        return true;
+      }
+      return false;
+    }
+
+    public void recordInstantiatedLambdaInterface(
+        DexType iface, LambdaDescriptor lambda, AppInfo appInfo) {
+      instantiatedLambdas.computeIfAbsent(iface, key -> new ArrayList<>()).add(lambda);
+      populateInstantiatedHierarchy(appInfo, iface);
+    }
+
+    private void repopulateInstantiatedHierarchy(DexDefinitionSupplier definitions) {
+      instantiatedHierarchy = new IdentityHashMap<>();
+      classesWithAllocationSiteTracking
+          .keySet()
+          .forEach(clazz -> populateInstantiatedHierarchy(definitions, clazz));
+      classesWithoutAllocationSiteTracking.forEach(
+          clazz -> populateInstantiatedHierarchy(definitions, clazz));
+      interfacesWithUnknownSubtypeHierarchy.forEach(
+          clazz -> populateInstantiatedHierarchy(definitions, clazz));
+      instantiatedLambdas
+          .keySet()
+          .forEach(type -> populateInstantiatedHierarchy(definitions, type));
+    }
+
+    private void populateInstantiatedHierarchy(DexDefinitionSupplier definitions, DexType type) {
+      DexClass clazz = definitions.definitionFor(type);
+      if (clazz != null) {
+        populateInstantiatedHierarchy(definitions, clazz);
+      }
+    }
+
+    private void populateInstantiatedHierarchy(DexDefinitionSupplier definitions, DexClass clazz) {
+      if (clazz.superType != null) {
+        populateInstantiatedHierarchy(definitions, clazz.superType, clazz);
+      }
+      for (DexType iface : clazz.interfaces.values) {
+        populateInstantiatedHierarchy(definitions, iface, clazz);
+      }
+    }
+
+    private void populateInstantiatedHierarchy(
+        DexDefinitionSupplier definitions, DexType type, DexClass subtype) {
+      if (type == definitions.dexItemFactory().objectType) {
+        return;
+      }
+      Set<DexClass> subtypes = instantiatedHierarchy.get(type);
+      if (subtypes != null) {
+        subtypes.add(subtype);
+        return;
+      }
+      // This is the first time an instantiation appears below 'type', recursively populate.
+      subtypes = Sets.newIdentityHashSet();
+      subtypes.add(subtype);
+      instantiatedHierarchy.put(type, subtypes);
+      populateInstantiatedHierarchy(definitions, type);
+    }
+
+    public void markNoLongerInstantiated(DexProgramClass clazz) {
+      classesWithAllocationSiteTracking.remove(clazz);
+      classesWithoutAllocationSiteTracking.remove(clazz);
+      instantiatedHierarchy = null;
+    }
+
+    Builder rewrittenWithLens(
+        ObjectAllocationInfoCollectionImpl objectAllocationInfos,
+        DexDefinitionSupplier definitions,
+        GraphLense lens) {
+      instantiatedHierarchy = null;
+      objectAllocationInfos.classesWithAllocationSiteTracking.forEach(
+          (clazz, allocationSitesForClass) -> {
+            DexType type = lens.lookupType(clazz.type);
+            if (type.isPrimitiveType()) {
+              assert !objectAllocationInfos.hasInstantiatedStrictSubtype(clazz);
+              return;
+            }
+            DexProgramClass rewrittenClass = asProgramClassOrNull(definitions.definitionFor(type));
+            assert rewrittenClass != null;
+            assert !classesWithAllocationSiteTracking.containsKey(rewrittenClass);
+            classesWithAllocationSiteTracking.put(
+                rewrittenClass,
+                LensUtils.rewrittenWithRenamedSignature(
+                    allocationSitesForClass, definitions, lens));
+          });
+      objectAllocationInfos.classesWithoutAllocationSiteTracking.forEach(
+          clazz -> {
+            DexType type = lens.lookupType(clazz.type);
+            if (type.isPrimitiveType()) {
+              assert !objectAllocationInfos.hasInstantiatedStrictSubtype(clazz);
+              return;
+            }
+            DexProgramClass rewrittenClass = asProgramClassOrNull(definitions.definitionFor(type));
+            assert rewrittenClass != null;
+            assert !classesWithAllocationSiteTracking.containsKey(rewrittenClass);
+            assert !classesWithoutAllocationSiteTracking.contains(rewrittenClass);
+            classesWithoutAllocationSiteTracking.add(rewrittenClass);
+          });
+      for (DexProgramClass abstractType :
+          objectAllocationInfos.interfacesWithUnknownSubtypeHierarchy) {
+        DexType type = lens.lookupType(abstractType.type);
+        if (type.isPrimitiveType()) {
+          assert false;
+          continue;
+        }
+        DexProgramClass rewrittenClass = asProgramClassOrNull(definitions.definitionFor(type));
+        assert rewrittenClass != null;
+        assert !interfacesWithUnknownSubtypeHierarchy.contains(rewrittenClass);
+        interfacesWithUnknownSubtypeHierarchy.add(rewrittenClass);
+      }
+      objectAllocationInfos.instantiatedLambdas.forEach(
+          (iface, lambdas) -> {
+            DexType type = lens.lookupType(iface);
+            if (type.isPrimitiveType()) {
+              assert false;
+              return;
+            }
+            assert !instantiatedLambdas.containsKey(type);
+            // TODO(b/150277553): Rewrite lambda descriptor.
+            instantiatedLambdas.put(type, lambdas);
+          });
+      return this;
+    }
+
+    // Validation that all types are linked in the instantiated hierarchy map.
+    boolean validate(DexDefinitionSupplier definitions) {
+      classesWithAllocationSiteTracking.forEach(
+          (clazz, contexts) -> {
+            assert !clazz.isInterface();
+            assert !classesWithoutAllocationSiteTracking.contains(clazz);
+            assert verifyAllSuperTypesAreInHierarchy(definitions, clazz.allImmediateSupertypes());
+          });
+      classesWithoutAllocationSiteTracking.forEach(
+          clazz -> {
+            assert !clazz.isInterface();
+            assert !classesWithAllocationSiteTracking.containsKey(clazz);
+            assert verifyAllSuperTypesAreInHierarchy(definitions, clazz.allImmediateSupertypes());
+          });
+      instantiatedLambdas.forEach(
+          (iface, lambdas) -> {
+            assert !lambdas.isEmpty();
+            DexClass definition = definitions.definitionFor(iface);
+            if (definition != null) {
+              assert definition.isInterface();
+              assert verifyAllSuperTypesAreInHierarchy(
+                  definitions, definition.allImmediateSupertypes());
+            }
+          });
+      for (DexProgramClass iface : interfacesWithUnknownSubtypeHierarchy) {
+        verifyAllSuperTypesAreInHierarchy(definitions, iface.allImmediateSupertypes());
+      }
+      instantiatedHierarchy.forEach(
+          (type, subtypes) -> {
+            assert !subtypes.isEmpty();
+            for (DexClass subtype : subtypes) {
+              assert isImmediateSuperType(type, subtype);
+            }
+          });
+      return true;
+    }
+
+    private boolean verifyAllSuperTypesAreInHierarchy(
+        DexDefinitionSupplier definitions, Iterable<DexType> dexTypes) {
+      for (DexType supertype : dexTypes) {
+        assert typeIsInHierarchy(definitions, supertype);
+      }
+      return true;
+    }
+
+    private boolean typeIsInHierarchy(DexDefinitionSupplier definitions, DexType type) {
+      return type == definitions.dexItemFactory().objectType
+          || instantiatedHierarchy.containsKey(type);
+    }
+
+    private boolean isImmediateSuperType(DexType type, DexClass subtype) {
+      for (DexType supertype : subtype.allImmediateSupertypes()) {
+        if (type == supertype) {
+          return true;
+        }
+      }
+      return false;
     }
   }
 }
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 e8cae8d..283777f 100644
--- a/src/main/java/com/android/tools/r8/graph/ResolutionResult.java
+++ b/src/main/java/com/android/tools/r8/graph/ResolutionResult.java
@@ -45,6 +45,10 @@
     return false;
   }
 
+  public boolean isIncompatibleClassChangeErrorResult() {
+    return false;
+  }
+
   /** Returns non-null if isFailedResolution() is true, otherwise null. */
   public FailedResolutionResult asFailedResolution() {
     return null;
@@ -101,7 +105,7 @@
       InstantiatedObject instance, AppInfoWithClassHierarchy appInfo);
 
   public abstract DexClassAndMethod lookupVirtualDispatchTarget(
-      DexProgramClass dynamicInstance, AppInfoWithClassHierarchy appInfo);
+      DexClass dynamicInstance, AppInfoWithClassHierarchy appInfo);
 
   public abstract LookupTarget lookupVirtualDispatchTarget(
       LambdaDescriptor lambdaInstance, AppInfoWithClassHierarchy appInfo);
@@ -119,12 +123,12 @@
       assert initialResolutionHolder != null;
       assert resolvedHolder != null;
       assert resolvedMethod != null;
-      assert resolvedHolder.type == resolvedMethod.method.holder;
+      assert resolvedHolder.type == resolvedMethod.holder();
       this.resolvedHolder = resolvedHolder;
       this.resolvedMethod = resolvedMethod;
       this.initialResolutionHolder = initialResolutionHolder;
       assert !resolvedMethod.isPrivateMethod()
-          || initialResolutionHolder.type == resolvedMethod.method.holder;
+          || initialResolutionHolder.type == resolvedMethod.holder();
     }
 
     public DexClass getResolvedHolder() {
@@ -324,7 +328,7 @@
       // It appears as if this check is also in place for non-initializer methods too.
       // See NestInvokeSpecialMethodAccessWithIntermediateTest.
       if ((target.isInstanceInitializer() || target.isPrivateMethod())
-          && target.method.holder != symbolicReference.type) {
+          && target.holder() != symbolicReference.type) {
         return null;
       }
       // Runtime exceptions:
@@ -440,7 +444,7 @@
         Consumer<DexProgramClass> lambdaInstantiatedConsumer =
             subType -> {
               subTypeConsumer.accept(subType);
-              if (appInfo.hasAnyInstantiatedLambdas(subType)) {
+              if (appInfo.isInstantiatedInterface(subType)) {
                 hasInstantiatedLambdas.set(true);
               }
             };
@@ -516,7 +520,7 @@
 
     @Override
     public DexClassAndMethod lookupVirtualDispatchTarget(
-        DexProgramClass dynamicInstance, AppInfoWithClassHierarchy appInfo) {
+        DexClass dynamicInstance, AppInfoWithClassHierarchy appInfo) {
       return lookupVirtualDispatchTarget(dynamicInstance, appInfo, initialResolutionHolder.type);
     }
 
@@ -542,9 +546,7 @@
     }
 
     private DexClassAndMethod lookupVirtualDispatchTarget(
-        DexProgramClass dynamicInstance,
-        AppInfoWithClassHierarchy appInfo,
-        DexType resolutionHolder) {
+        DexClass dynamicInstance, AppInfoWithClassHierarchy appInfo, DexType resolutionHolder) {
       assert appInfo.isSubtype(dynamicInstance.type, resolutionHolder)
           : dynamicInstance.type + " is not a subtype of " + resolutionHolder;
       // TODO(b/148591377): Enable this assertion.
@@ -567,7 +569,7 @@
         }
         if (candidate == null || candidate == DexEncodedMethod.SENTINEL) {
           // We cannot find a target above the resolved method.
-          if (current.type == overrideTarget.method.holder) {
+          if (current.type == overrideTarget.holder()) {
             return null;
           }
           current = current.superType == null ? null : appInfo.definitionFor(current.superType);
@@ -584,7 +586,7 @@
     }
 
     private DexClassAndMethod lookupMaximallySpecificDispatchTarget(
-        DexProgramClass dynamicInstance, AppInfoWithClassHierarchy appInfo) {
+        DexClass dynamicInstance, AppInfoWithClassHierarchy appInfo) {
       return appInfo.lookupMaximallySpecificMethod(dynamicInstance, resolvedMethod.method);
     }
 
@@ -646,7 +648,7 @@
       }
       // For package private methods, a valid override has to be inside the package.
       assert resolvedMethod.accessFlags.isPackagePrivate();
-      return resolvedMethod.method.holder.isSamePackage(candidate.method.holder);
+      return resolvedMethod.holder().isSamePackage(candidate.holder());
     }
   }
 
@@ -702,7 +704,7 @@
 
     @Override
     public DexClassAndMethod lookupVirtualDispatchTarget(
-        DexProgramClass dynamicInstance, AppInfoWithClassHierarchy appInfo) {
+        DexClass dynamicInstance, AppInfoWithClassHierarchy appInfo) {
       return null;
     }
 
@@ -808,6 +810,11 @@
           ? INSTANCE
           : new IncompatibleClassResult(methodsCausingError);
     }
+
+    @Override
+    public boolean isIncompatibleClassChangeErrorResult() {
+      return true;
+    }
   }
 
   public static class NoSuchMethodResult extends FailedResolutionResult {
diff --git a/src/main/java/com/android/tools/r8/graph/analysis/ClassInitializerAssertionEnablingAnalysis.java b/src/main/java/com/android/tools/r8/graph/analysis/ClassInitializerAssertionEnablingAnalysis.java
index fdb4dc1..061cbcf 100644
--- a/src/main/java/com/android/tools/r8/graph/analysis/ClassInitializerAssertionEnablingAnalysis.java
+++ b/src/main/java/com/android/tools/r8/graph/analysis/ClassInitializerAssertionEnablingAnalysis.java
@@ -127,7 +127,7 @@
   }
 
   private boolean hasKotlincClinitAssertionCode(DexEncodedMethod method) {
-    if (method.method.holder == dexItemFactory.kotlin.kotlinAssertions) {
+    if (method.holder() == dexItemFactory.kotlin.kotlinAssertions) {
       CfCode code = method.getCode().asCfCode();
       for (int i = 1; i < code.instructions.size(); i++) {
         CfInstruction instruction = code.instructions.get(i - 1);
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 f32143d..7ecd527 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
@@ -74,7 +74,7 @@
 
     // Record that the enclosing class is guaranteed to be initialized at the allocation site.
     AppInfoWithSubtyping appInfo = appView.appInfo();
-    DexType guaranteedToBeInitialized = context.method.holder;
+    DexType guaranteedToBeInitialized = context.holder();
     DexType existingGuaranteedToBeInitialized =
         mapping.getOrDefault(key, guaranteedToBeInitialized);
     mapping.put(
diff --git a/src/main/java/com/android/tools/r8/graph/classmerging/VerticallyMergedClasses.java b/src/main/java/com/android/tools/r8/graph/classmerging/VerticallyMergedClasses.java
index 237f9ab..944d680 100644
--- a/src/main/java/com/android/tools/r8/graph/classmerging/VerticallyMergedClasses.java
+++ b/src/main/java/com/android/tools/r8/graph/classmerging/VerticallyMergedClasses.java
@@ -39,6 +39,10 @@
     return mergedClasses.containsKey(type);
   }
 
+  public boolean isTarget(DexType type) {
+    return !getSourcesFor(type).isEmpty();
+  }
+
   @Override
   public boolean verifyAllSourcesPruned(AppView<AppInfoWithLiveness> appView) {
     for (List<DexType> sourcesForTarget : sources.values()) {
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 7167e72..94ee9cf 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
@@ -97,7 +97,7 @@
   }
 
   public boolean isClassDefinitelyLoadedBeforeInstruction(DexType type, Instruction instruction) {
-    DexType context = code.method.method.holder;
+    DexType context = code.method.holder();
     BasicBlock block = instruction.getBlock();
 
     // Visit the instructions in `block` prior to `instruction`.
@@ -335,7 +335,7 @@
       if (!resolutionResult.isSingleResolution()) {
         return false;
       }
-      DexType holder = resolutionResult.getSingleTarget().method.holder;
+      DexType holder = resolutionResult.getSingleTarget().holder();
       return appView.isSubtype(holder, type).isTrue();
     }
 
@@ -394,7 +394,7 @@
       if (!resolutionResult.isSingleResolution()) {
         return false;
       }
-      DexType holder = resolutionResult.getSingleTarget().method.holder;
+      DexType holder = resolutionResult.getSingleTarget().holder();
       return appView.isSubtype(holder, type).isTrue();
     }
 
@@ -430,7 +430,7 @@
       if (!resolutionResult.isSingleResolution()) {
         return false;
       }
-      DexType holder = resolutionResult.getSingleTarget().method.holder;
+      DexType holder = resolutionResult.getSingleTarget().holder();
       return appView.isSubtype(holder, type).isTrue();
     }
 
@@ -505,11 +505,11 @@
         enqueue(clazz.type, visited, worklist);
       } else if (definition.isDexEncodedField()) {
         DexEncodedField field = definition.asDexEncodedField();
-        enqueue(field.field.holder, visited, worklist);
+        enqueue(field.holder(), visited, worklist);
       } else if (definition.isDexEncodedMethod()) {
         assert instruction.isInvokeMethod();
         DexEncodedMethod method = definition.asDexEncodedMethod();
-        enqueue(method.method.holder, visited, worklist);
+        enqueue(method.holder(), visited, worklist);
         enqueueInitializedClassesOnNormalExit(method, instruction.inValues(), visited, worklist);
       } else {
         assert false;
diff --git a/src/main/java/com/android/tools/r8/ir/analysis/DeterminismAnalysis.java b/src/main/java/com/android/tools/r8/ir/analysis/DeterminismAnalysis.java
index 0fc8bd0..9ae6c86 100644
--- a/src/main/java/com/android/tools/r8/ir/analysis/DeterminismAnalysis.java
+++ b/src/main/java/com/android/tools/r8/ir/analysis/DeterminismAnalysis.java
@@ -37,7 +37,7 @@
       }
       if (instr.isInvokeMethod()) {
         DexEncodedMethod target =
-            instr.asInvokeMethod().lookupSingleTarget(appView, code.method.method.holder);
+            instr.asInvokeMethod().lookupSingleTarget(appView, code.method.holder());
         if (target != null && target.getOptimizationInfo().returnValueOnlyDependsOnArguments()) {
           continue;
         }
diff --git a/src/main/java/com/android/tools/r8/ir/analysis/InitializedClassesOnNormalExitAnalysis.java b/src/main/java/com/android/tools/r8/ir/analysis/InitializedClassesOnNormalExitAnalysis.java
index f5cd48d..406c9e6 100644
--- a/src/main/java/com/android/tools/r8/ir/analysis/InitializedClassesOnNormalExitAnalysis.java
+++ b/src/main/java/com/android/tools/r8/ir/analysis/InitializedClassesOnNormalExitAnalysis.java
@@ -36,7 +36,7 @@
   public static Set<DexType> computeInitializedClassesOnNormalExit(
       AppView<AppInfoWithLiveness> appView, IRCode code) {
     DominatorTree dominatorTree = new DominatorTree(code, Assumption.MAY_HAVE_UNREACHABLE_BLOCKS);
-    Visitor visitor = new Visitor(appView, code.method.method.holder);
+    Visitor visitor = new Visitor(appView, code.method.holder());
     for (BasicBlock dominator : dominatorTree.normalExitDominatorBlocks()) {
       if (dominator.hasCatchHandlers()) {
         // When determining which classes that are guaranteed to be initialized from a given
@@ -115,8 +115,8 @@
     public Void handleFieldInstruction(FieldInstruction instruction) {
       DexEncodedField field = appView.appInfo().resolveField(instruction.getField());
       if (field != null) {
-        if (field.field.holder.isClassType()) {
-          markInitializedOnNormalExit(field.field.holder);
+        if (field.holder().isClassType()) {
+          markInitializedOnNormalExit(field.holder());
         } else {
           assert false : "Expected holder of field type to be a class type";
         }
@@ -132,7 +132,7 @@
         if (method.holder.isClassType()) {
           DexEncodedMethod singleTarget = invoke.lookupSingleTarget(appView, context);
           if (singleTarget != null) {
-            markInitializedOnNormalExit(singleTarget.method.holder);
+            markInitializedOnNormalExit(singleTarget.holder());
             markInitializedOnNormalExit(
                 singleTarget.getOptimizationInfo().getInitializedClassesOnNormalExit());
           } else {
diff --git a/src/main/java/com/android/tools/r8/ir/analysis/ValueMayDependOnEnvironmentAnalysis.java b/src/main/java/com/android/tools/r8/ir/analysis/ValueMayDependOnEnvironmentAnalysis.java
index 9fcee4a..05416df 100644
--- a/src/main/java/com/android/tools/r8/ir/analysis/ValueMayDependOnEnvironmentAnalysis.java
+++ b/src/main/java/com/android/tools/r8/ir/analysis/ValueMayDependOnEnvironmentAnalysis.java
@@ -84,7 +84,7 @@
   public ValueMayDependOnEnvironmentAnalysis(AppView<?> appView, IRCode code) {
     this.appView = appView;
     this.code = code;
-    this.context = code.method.method.holder;
+    this.context = code.method.holder();
   }
 
   public boolean valueMayDependOnEnvironment(Value value) {
@@ -433,7 +433,7 @@
       if (definition.isStaticGet()) {
         StaticGet staticGet = definition.asStaticGet();
         DexEncodedField field = appView.appInfo().resolveField(staticGet.getField());
-        if (field != null && field.field.holder == context) {
+        if (field != null && field.holder() == context) {
           List<StaticPut> finalFieldPuts = computeFinalFieldPuts().get(field);
           if (finalFieldPuts == null || finalFieldPuts.size() != 1) {
             return false;
@@ -452,7 +452,7 @@
       finalFieldPuts = new IdentityHashMap<>();
       for (StaticPut staticPut : code.<StaticPut>instructions(Instruction::isStaticPut)) {
         DexEncodedField field = appView.appInfo().resolveField(staticPut.getField());
-        if (field != null && field.field.holder == context && field.isFinal()) {
+        if (field != null && field.holder() == context && field.isFinal()) {
           finalFieldPuts.computeIfAbsent(field, ignore -> new ArrayList<>()).add(staticPut);
         }
       }
diff --git a/src/main/java/com/android/tools/r8/ir/analysis/fieldaccess/FieldAssignmentTracker.java b/src/main/java/com/android/tools/r8/ir/analysis/fieldaccess/FieldAssignmentTracker.java
index d5bd10a..75d5423 100644
--- a/src/main/java/com/android/tools/r8/ir/analysis/fieldaccess/FieldAssignmentTracker.java
+++ b/src/main/java/com/android/tools/r8/ir/analysis/fieldaccess/FieldAssignmentTracker.java
@@ -148,7 +148,7 @@
       return;
     }
 
-    DexEncodedMethod singleTarget = invoke.lookupSingleTarget(appView, context.method.holder);
+    DexEncodedMethod singleTarget = invoke.lookupSingleTarget(appView, context.holder());
     if (singleTarget == null) {
       // We just lost track.
       abstractInstanceFieldValues.remove(clazz);
@@ -173,7 +173,7 @@
               initializationInfo.asArgumentInitializationInfo();
           Value argument = invoke.arguments().get(argumentInitializationInfo.getArgumentIndex());
           AbstractValue abstractValue =
-              entry.getValue().join(argument.getAbstractValue(appView, context.method.holder));
+              entry.getValue().join(argument.getAbstractValue(appView, context.holder()));
           assert !abstractValue.isBottom();
           if (!abstractValue.isUnknown()) {
             entry.setValue(abstractValue);
@@ -294,7 +294,7 @@
     if (abstractValue.isUnknown()) {
       return true;
     }
-    assert abstractValue == value.getAbstractValue(appView, context.method.holder);
+    assert abstractValue == value.getAbstractValue(appView, context.holder());
     return true;
   }
 
diff --git a/src/main/java/com/android/tools/r8/ir/analysis/fieldvalueanalysis/FieldValueAnalysis.java b/src/main/java/com/android/tools/r8/ir/analysis/fieldvalueanalysis/FieldValueAnalysis.java
index af990b1..08989c0 100644
--- a/src/main/java/com/android/tools/r8/ir/analysis/fieldvalueanalysis/FieldValueAnalysis.java
+++ b/src/main/java/com/android/tools/r8/ir/analysis/fieldvalueanalysis/FieldValueAnalysis.java
@@ -49,7 +49,7 @@
       DexProgramClass clazz,
       DexEncodedMethod method) {
     assert clazz != null;
-    assert clazz.type == method.method.holder;
+    assert clazz.type == method.holder();
     this.appView = appView;
     this.clazz = clazz;
     this.code = code;
@@ -129,7 +129,7 @@
 
     // Then check if any of the instructions that precede the given instruction in the current block
     // may read the field.
-    DexType context = method.method.holder;
+    DexType context = method.holder();
     InstructionIterator instructionIterator = block.iterator();
     while (instructionIterator.hasNext()) {
       Instruction current = instructionIterator.next();
@@ -164,7 +164,7 @@
    * and its transitive predecessors.
    */
   private Map<BasicBlock, AbstractFieldSet> createFieldsMaybeReadBeforeBlockInclusive() {
-    DexType context = method.method.holder;
+    DexType context = method.holder();
     Map<BasicBlock, AbstractFieldSet> result = new IdentityHashMap<>();
     Deque<BasicBlock> worklist = DequeUtils.newArrayDeque(code.entryBlock());
     while (!worklist.isEmpty()) {
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 3aec803..58b4194 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
@@ -86,7 +86,7 @@
     assert appView.enableWholeProgramOptimizations();
     assert method.isInstanceInitializer();
 
-    DexProgramClass clazz = appView.definitionFor(method.method.holder).asProgramClass();
+    DexProgramClass clazz = appView.definitionFor(method.holder()).asProgramClass();
     if (!appView.options().enableValuePropagationForInstanceFields) {
       return EmptyInstanceFieldInitializationInfoCollection.getInstance();
     }
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 4d57a5e..e2ab677 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
@@ -53,7 +53,7 @@
     assert appView.enableWholeProgramOptimizations();
     assert method.isClassInitializer();
     timing.begin("Analyze class initializer");
-    DexProgramClass clazz = appView.definitionFor(method.method.holder).asProgramClass();
+    DexProgramClass clazz = appView.definitionFor(method.holder()).asProgramClass();
     new StaticFieldValueAnalysis(appView.withLiveness(), code, feedback, clazz, method)
         .computeFieldOptimizationInfo(classInitializerDefaultsResult);
     timing.end();
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 2a70440..08b29ce 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
@@ -130,7 +130,7 @@
    */
   public void rewriteCode(DexEncodedMethod method, IRCode code) {
     if (method.isClassInitializer()
-        && classesWithRemovedExtensionFields.contains(method.method.holder)
+        && classesWithRemovedExtensionFields.contains(method.holder())
         && code.metadata().mayHaveStaticPut()) {
       rewriteClassInitializer(code);
     }
@@ -204,7 +204,7 @@
       return false;
     }
 
-    DexClass clazz = appView.definitionFor(encodedField.field.holder);
+    DexClass clazz = appView.definitionFor(encodedField.holder());
     if (clazz == null || !clazz.isProgramClass()) {
       return false;
     }
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 feca06c..0801f16 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
@@ -36,6 +36,7 @@
 import com.android.tools.r8.ir.optimize.inliner.FixedInliningReasonStrategy;
 import com.android.tools.r8.origin.Origin;
 import com.android.tools.r8.shaking.AppInfoWithLiveness;
+import com.android.tools.r8.shaking.Enqueuer;
 import com.android.tools.r8.utils.PredicateSet;
 import com.android.tools.r8.utils.ThreadUtils;
 import com.android.tools.r8.utils.Timing;
@@ -64,11 +65,16 @@
 
   /** Returns true if an action was deferred. */
   public boolean deferDeadProtoBuilders(
-      DexProgramClass clazz, DexEncodedMethod context, BooleanSupplier register) {
+      DexProgramClass clazz,
+      DexEncodedMethod context,
+      BooleanSupplier register,
+      Enqueuer enqueuer) {
     if (references.isDynamicMethod(context) && references.isGeneratedMessageLiteBuilder(clazz)) {
       if (register.getAsBoolean()) {
-        assert builders.getOrDefault(clazz, context) == context;
-        builders.put(clazz, context);
+        if (enqueuer.getMode().isFinalTreeShaking()) {
+          assert builders.getOrDefault(clazz, context) == context;
+          builders.put(clazz, context);
+        }
         return true;
       }
     }
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 390f2b2..b2d813a 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
@@ -99,7 +99,7 @@
    * newMessageInfo is still pending.
    */
   private void rewriteDynamicMethod(DexEncodedMethod method, IRCode code) {
-    DexClass context = appView.definitionFor(method.method.holder);
+    DexClass context = appView.definitionFor(method.holder());
     if (context == null || !context.isProgramClass()) {
       return;
     }
diff --git a/src/main/java/com/android/tools/r8/ir/analysis/proto/ProtoEnqueuerUseRegistry.java b/src/main/java/com/android/tools/r8/ir/analysis/proto/ProtoEnqueuerUseRegistry.java
index 71750f1..79dea35 100644
--- a/src/main/java/com/android/tools/r8/ir/analysis/proto/ProtoEnqueuerUseRegistry.java
+++ b/src/main/java/com/android/tools/r8/ir/analysis/proto/ProtoEnqueuerUseRegistry.java
@@ -43,6 +43,7 @@
   @Override
   public boolean registerConstClass(DexType type) {
     if (references.isDynamicMethod(getContextMethod())) {
+      enqueuer.addDeadProtoTypeCandidate(type);
       return false;
     }
     return super.registerConstClass(type);
@@ -58,6 +59,7 @@
   @Override
   public boolean registerStaticFieldRead(DexField field) {
     if (references.isDynamicMethod(getContextMethod())) {
+      enqueuer.addDeadProtoTypeCandidate(field.holder);
       return false;
     }
     return super.registerStaticFieldRead(field);
diff --git a/src/main/java/com/android/tools/r8/ir/analysis/proto/ProtoInliningReasonStrategy.java b/src/main/java/com/android/tools/r8/ir/analysis/proto/ProtoInliningReasonStrategy.java
index de37f38..1321984 100644
--- a/src/main/java/com/android/tools/r8/ir/analysis/proto/ProtoInliningReasonStrategy.java
+++ b/src/main/java/com/android/tools/r8/ir/analysis/proto/ProtoInliningReasonStrategy.java
@@ -36,7 +36,7 @@
   @Override
   public Reason computeInliningReason(
       InvokeMethod invoke, DexEncodedMethod target, DexEncodedMethod context) {
-    DexProgramClass enclosingClass = appView.definitionFor(context.method.holder).asProgramClass();
+    DexProgramClass enclosingClass = appView.definitionFor(context.holder()).asProgramClass();
     if (references.isAbstractGeneratedMessageLiteBuilder(enclosingClass)
         && invoke.isInvokeSuper()) {
       // Aggressively inline invoke-super calls inside the GeneratedMessageLite builders. Such
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 e414f23..d6d2990 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
@@ -135,7 +135,7 @@
       return;
     }
 
-    DexType holder = encodedMethod.method.holder;
+    DexType holder = encodedMethod.holder();
     if (seenButNotLiveProtos.containsKey(holder)) {
       // The proto is now live instead of dead.
       liveProtos.put(holder, seenButNotLiveProtos.remove(holder));
@@ -150,7 +150,7 @@
 
   private void createProtoMessageInfoFromDynamicMethod(
       DexEncodedMethod dynamicMethod, Map<DexType, ProtoMessageInfo> protos) {
-    DexType holder = dynamicMethod.method.holder;
+    DexType holder = dynamicMethod.holder();
     assert !protos.containsKey(holder);
 
     DexClass context = appView.definitionFor(holder);
@@ -254,7 +254,7 @@
     for (DexEncodedMethod findLiteExtensionByNumberMethod : findLiteExtensionByNumberMethods) {
       IRCode code =
           findLiteExtensionByNumberMethod.buildIR(
-              appView, appView.appInfo().originFor(findLiteExtensionByNumberMethod.method.holder));
+              appView, appView.appInfo().originFor(findLiteExtensionByNumberMethod.holder()));
       for (BasicBlock block : code.blocks(BasicBlock::isReturnBlock)) {
         Value returnValue = block.exit().asReturn().returnValue().getAliasedValue();
         if (returnValue.isPhi()) {
@@ -275,7 +275,7 @@
             continue;
           }
 
-          DexProgramClass holder = asProgramClassOrNull(appView.definitionFor(field.field.holder));
+          DexProgramClass holder = asProgramClassOrNull(appView.definitionFor(field.holder()));
           if (holder == null) {
             assert false;
             continue;
@@ -365,7 +365,7 @@
       }
 
       DexEncodedMethod dynamicMethod = protoMessageInfo.getDynamicMethod();
-      DexProgramClass clazz = appView.definitionFor(dynamicMethod.method.holder).asProgramClass();
+      DexProgramClass clazz = appView.definitionFor(dynamicMethod.holder()).asProgramClass();
 
       for (ProtoFieldInfo protoFieldInfo : protoMessageInfo.getFields()) {
         DexEncodedField valueStorage = protoFieldInfo.getValueStorage(appView, protoMessageInfo);
@@ -526,7 +526,7 @@
       return;
     }
 
-    DexClass clazz = appView.definitionFor(encodedOneOfCaseField.field.holder);
+    DexClass clazz = appView.definitionFor(encodedOneOfCaseField.holder());
     if (clazz == null || !clazz.isProgramClass()) {
       assert false;
       return;
@@ -555,7 +555,7 @@
       return;
     }
 
-    if (encodedOneOfField.field.holder != encodedOneOfCaseField.field.holder) {
+    if (encodedOneOfField.holder() != encodedOneOfCaseField.holder()) {
       assert false;
       return;
     }
diff --git a/src/main/java/com/android/tools/r8/ir/analysis/proto/schema/ProtoMessageInfo.java b/src/main/java/com/android/tools/r8/ir/analysis/proto/schema/ProtoMessageInfo.java
index ac88305..f026295 100644
--- a/src/main/java/com/android/tools/r8/ir/analysis/proto/schema/ProtoMessageInfo.java
+++ b/src/main/java/com/android/tools/r8/ir/analysis/proto/schema/ProtoMessageInfo.java
@@ -220,7 +220,7 @@
   }
 
   public DexType getType() {
-    return dynamicMethod.method.holder;
+    return dynamicMethod.holder();
   }
 
   public boolean hasFields() {
diff --git a/src/main/java/com/android/tools/r8/ir/analysis/sideeffect/ClassInitializerSideEffectAnalysis.java b/src/main/java/com/android/tools/r8/ir/analysis/sideeffect/ClassInitializerSideEffectAnalysis.java
index 3449c30..0d500e9 100644
--- a/src/main/java/com/android/tools/r8/ir/analysis/sideeffect/ClassInitializerSideEffectAnalysis.java
+++ b/src/main/java/com/android/tools/r8/ir/analysis/sideeffect/ClassInitializerSideEffectAnalysis.java
@@ -39,7 +39,7 @@
    */
   public static ClassInitializerSideEffect classInitializerCanBePostponed(
       AppView<?> appView, IRCode code) {
-    DexType context = code.method.method.holder;
+    DexType context = code.method.holder();
     OptionalBool controlFlowMayDependOnEnvironment = OptionalBool.unknown();
     boolean mayHaveSideEffects = false;
 
@@ -113,7 +113,7 @@
         StaticPut staticPut = instruction.asStaticPut();
         DexEncodedField field = appView.appInfo().resolveField(staticPut.getField());
         if (field == null
-            || field.field.holder != context
+            || field.holder() != context
             || environmentAnalysis.valueMayDependOnEnvironment(staticPut.value())
             || instruction.instructionInstanceCanThrow(appView, context).isThrowing()) {
           return ClassInitializerSideEffect.SIDE_EFFECTS_THAT_CANNOT_BE_POSTPONED;
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 34a83d1..3f5640d 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
@@ -72,7 +72,7 @@
     Value returnedValue =
         code.createValue(classClassType(appView, definitelyNotNull()), debugLocalInfo);
     ConstClass instruction = new ConstClass(returnedValue, type);
-    assert !instruction.instructionMayHaveSideEffects(appView, code.method.method.holder);
+    assert !instruction.instructionMayHaveSideEffects(appView, code.method.holder());
     return instruction;
   }
 
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 ef38879..855694a 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
@@ -85,7 +85,7 @@
   public boolean isMaterializableInContext(AppView<?> appView, DexType context) {
     DexEncodedField encodedField = appView.appInfo().resolveField(field);
     return isMemberVisibleFromOriginalContext(
-        appView, context, encodedField.field.holder, encodedField.accessFlags);
+        appView, context, encodedField.holder(), encodedField.accessFlags);
   }
 
   @Override
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 dc0b133..0994dc0 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
@@ -90,7 +90,7 @@
 
   @Override
   public boolean canBeDeadCode(AppView<?> appView, IRCode code) {
-    return !instructionMayHaveSideEffects(appView, code.method.method.holder);
+    return !instructionMayHaveSideEffects(appView, code.method.holder());
   }
 
   @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 f4a8a14..cd29ec9 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
@@ -192,7 +192,7 @@
 
   @Override
   public boolean canBeDeadCode(AppView<?> appView, IRCode code) {
-    return !instructionMayHaveSideEffects(appView, code.method.method.holder);
+    return !instructionMayHaveSideEffects(appView, code.method.holder());
   }
 
   @Override
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 6611bc7..9fb9f27 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
@@ -279,7 +279,7 @@
     assert super.verifyTypes(appView);
 
     TypeElement inType = src().getType();
-    TypeElement outType = outValue().getType();
+    TypeElement outType = getOutType();
     if (isAssumeNone() || isAssumeDynamicType()) {
       assert inType.isReferenceType() : inType;
       assert outType.equals(inType)
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 37d9b9e..52aed30 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
@@ -540,6 +540,10 @@
     return phis;
   }
 
+  public boolean isEntry() {
+    return getPredecessors().isEmpty();
+  }
+
   public boolean isFilled() {
     return filled;
   }
@@ -1750,7 +1754,7 @@
     if (hasMoveException) {
       // Remove the move-exception instruction.
       move = entry().asMoveException();
-      exceptionTypeLattice = move.outValue().getType();
+      exceptionTypeLattice = move.getOutType();
       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 4e5c923..1537f85 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
@@ -228,7 +228,7 @@
       throw new IllegalStateException();
     }
 
-    assert !current.hasOutValue() || current.outValue().getType().isInt();
+    assert !current.hasOutValue() || current.getOutType().isInt();
 
     // Replace the instruction by const-number.
     ConstNumber constNumber = code.createIntConstant(value, current.getLocalInfo());
@@ -250,7 +250,7 @@
 
     // Replace the instruction by static-get.
     TypeElement newType = TypeElement.fromDexType(field.type, maybeNull(), appView);
-    TypeElement oldType = current.hasOutValue() ? current.outValue().getType() : null;
+    TypeElement oldType = current.getOutType();
     Value value = code.createValue(newType, current.getLocalInfo());
     StaticGet staticGet = new StaticGet(value, field);
     for (Value inValue : current.inValues()) {
@@ -495,8 +495,8 @@
       Set<BasicBlock> blocksToRemove,
       DexType downcast) {
     assert blocksToRemove != null;
-    DexType codeHolder = code.method.method.holder;
-    DexType inlineeHolder = inlinee.method.method.holder;
+    DexType codeHolder = code.method.holder();
+    DexType inlineeHolder = inlinee.method.holder();
     if (codeHolder != inlineeHolder && inlinee.method.isOnlyInlinedIntoNestMembers()) {
       // Should rewrite private calls to virtual calls.
       assert NestUtils.sameNest(codeHolder, inlineeHolder, appView);
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 2d7e409..da952bd 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
@@ -171,7 +171,7 @@
 
     assert inType.isPreciseType();
 
-    TypeElement outType = outValue().getType();
+    TypeElement outType = getOutType();
     TypeElement castType = TypeElement.fromDexType(getType(), inType.nullability(), appView);
 
     if (inType.lessThanOrEqual(castType, appView)) {
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 e77f315..bb5788b 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
@@ -44,10 +44,7 @@
 
   public static ConstClass copyOf(IRCode code, ConstClass original) {
     Value newValue =
-        new Value(
-            code.valueNumberGenerator.next(),
-            original.outValue().getType(),
-            original.getLocalInfo());
+        new Value(code.valueNumberGenerator.next(), original.getOutType(), original.getLocalInfo());
     return copyOf(newValue, original);
   }
 
@@ -142,7 +139,7 @@
 
   @Override
   public boolean canBeDeadCode(AppView<?> appView, IRCode code) {
-    return !instructionMayHaveSideEffects(appView, code.method.method.holder);
+    return !instructionMayHaveSideEffects(appView, code.method.holder());
   }
 
   @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 996bf38..a390b3f 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
@@ -38,10 +38,7 @@
 
   public static ConstMethodHandle copyOf(IRCode code, ConstMethodHandle original) {
     Value newValue =
-        new Value(
-            code.valueNumberGenerator.next(),
-            original.outValue().getType(),
-            original.getLocalInfo());
+        new Value(code.valueNumberGenerator.next(), original.getOutType(), original.getLocalInfo());
     return copyOf(newValue, original);
   }
 
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 6cd4d0b..774475d 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
@@ -38,10 +38,7 @@
 
   public static ConstMethodType copyOf(IRCode code, ConstMethodType original) {
     Value newValue =
-        new Value(
-            code.valueNumberGenerator.next(),
-            original.outValue().getType(),
-            original.getLocalInfo());
+        new Value(code.valueNumberGenerator.next(), original.getOutType(), original.getLocalInfo());
     return copyOf(newValue, original);
   }
 
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 2683b66..06aef0a 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
@@ -55,10 +55,7 @@
 
   public static ConstNumber copyOf(IRCode code, ConstNumber original) {
     Value newValue =
-        new Value(
-            code.valueNumberGenerator.next(),
-            original.outValue().getType(),
-            original.getLocalInfo());
+        new Value(code.valueNumberGenerator.next(), original.getOutType(), original.getLocalInfo());
     return copyOf(newValue, original);
   }
 
@@ -251,7 +248,7 @@
   @Override
   public String toString() {
     if (outValue != null) {
-      return super.toString() + " " + value + " (" + outValue().getType() + ")";
+      return super.toString() + " " + value + " (" + getOutType() + ")";
     } else {
       return super.toString() + " " + value + " (dead)";
     }
@@ -316,13 +313,13 @@
 
   @Override
   public TypeElement evaluate(AppView<?> appView) {
-    return outValue().getType();
+    return getOutType();
   }
 
   @Override
   public boolean verifyTypes(AppView<?> appView) {
     assert super.verifyTypes(appView);
-    assert !isZero() || outValue().getType().isPrimitiveType() || outValue().getType().isNullType();
+    assert !isZero() || getOutType().isPrimitiveType() || getOutType().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 c98d8b7..a90a67e 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
@@ -42,10 +42,7 @@
 
   public static ConstString copyOf(IRCode code, ConstString original) {
     Value newValue =
-        new Value(
-            code.valueNumberGenerator.next(),
-            original.outValue().getType(),
-            original.getLocalInfo());
+        new Value(code.valueNumberGenerator.next(), original.getOutType(), original.getLocalInfo());
     return copyOf(newValue, original);
   }
 
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 5884660..f8a36db 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().getType().lessThanOrEqual(outValue().getType(), appView);
+    assert src().getType().lessThanOrEqual(getOutType(), 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 70ebd80..bc29749 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
@@ -48,10 +48,7 @@
 
   public static DexItemBasedConstString copyOf(IRCode code, DexItemBasedConstString original) {
     Value newValue =
-        new Value(
-            code.valueNumberGenerator.next(),
-            original.outValue().getType(),
-            original.getLocalInfo());
+        new Value(code.valueNumberGenerator.next(), original.getOutType(), original.getLocalInfo());
     return copyOf(newValue, original);
   }
 
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 d7e0067..18f8326 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
@@ -232,7 +232,7 @@
     assert isFieldGet();
     DexEncodedField field = appView.appInfo().resolveField(getField());
     if (field != null) {
-      DexClass holder = appView.definitionFor(field.field.holder);
+      DexClass holder = appView.definitionFor(field.holder());
       if (holder != null && holder.isLibraryClass() && field.isStatic() && field.isFinal()) {
         return appView.abstractValueFactory().createSingleFieldValue(field.field);
       }
diff --git a/src/main/java/com/android/tools/r8/ir/code/IRMetadata.java b/src/main/java/com/android/tools/r8/ir/code/IRMetadata.java
index 5c488c4..e46af2c 100644
--- a/src/main/java/com/android/tools/r8/ir/code/IRMetadata.java
+++ b/src/main/java/com/android/tools/r8/ir/code/IRMetadata.java
@@ -116,6 +116,10 @@
     return result;
   }
 
+  public boolean mayHaveInitClass() {
+    return get(Opcodes.INIT_CLASS);
+  }
+
   public boolean mayHaveInstanceGet() {
     return get(Opcodes.INSTANCE_GET);
   }
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 da0592d..42068de 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
@@ -199,14 +199,14 @@
 
   public BasicBlock targetFromCondition(ConstNumber value) {
     assert isZeroTest();
-    assert verifyTypeCompatible(value.outValue().getType(), type);
+    assert verifyTypeCompatible(value.getOutType(), 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().getType(), type);
+    assert verifyTypeCompatible(left.getOutType(), type);
     return targetFromCondition(Long.signum(left.getRawValue() - right.getRawValue()));
   }
 
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 6edc6da..0740db4 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
@@ -127,7 +127,7 @@
     // * IncompatibleClassChangeError (instance-* instruction for static fields)
     // * IllegalAccessError (not visible from the access context)
     // * NullPointerException (null receiver)
-    return !instructionMayHaveSideEffects(appView, code.method.method.holder);
+    return !instructionMayHaveSideEffects(appView, code.method.holder());
   }
 
   @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 116a3e6..58bcd28 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
@@ -142,7 +142,7 @@
     // * IllegalAccessError (not visible from the access context)
     // * NullPointerException (null receiver)
     // * not read at all
-    boolean haveSideEffects = instructionMayHaveSideEffects(appView, code.method.method.holder);
+    boolean haveSideEffects = instructionMayHaveSideEffects(appView, code.method.holder());
     assert appView.enableWholeProgramOptimizations() || haveSideEffects
         : "Expected instance-put instruction to have side effects in D8";
     return !haveSideEffects;
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 654c2e8..b280728 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
@@ -593,7 +593,7 @@
   /** Returns true is this instruction can be treated as dead code if its outputs are not used. */
   public boolean canBeDeadCode(AppView<?> appView, IRCode code) {
     // TODO(b/129530569): instructions with fine-grained side effect analysis may use:
-    // return !instructionMayHaveSideEffects(appView, code.method.method.holder);
+    // return !instructionMayHaveSideEffects(appView, code.method.holder());
     return !instructionInstanceCanThrow();
   }
 
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 ddc8952..d6aaf36 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
@@ -190,12 +190,12 @@
 
     // Verify that the target method is accessible in the current context.
     if (!isMemberVisibleFromOriginalContext(
-        appView, context, target.method.holder, target.accessFlags)) {
+        appView, context, target.holder(), target.accessFlags)) {
       return true;
     }
 
     // Verify that the target method does not have side-effects.
-    DexClass clazz = appView.definitionFor(target.method.holder);
+    DexClass clazz = appView.definitionFor(target.holder());
     if (clazz == null) {
       assert false : "Expected to be able to find the enclosing class of a method definition";
       return true;
@@ -218,7 +218,7 @@
   @Override
   public boolean canBeDeadCode(AppView<?> appView, IRCode code) {
     DexEncodedMethod method = code.method;
-    if (instructionMayHaveSideEffects(appView, method.method.holder)) {
+    if (instructionMayHaveSideEffects(appView, method.holder())) {
       return false;
     }
 
@@ -236,7 +236,7 @@
           if (appView.dexItemFactory().isConstructor(invoke.getInvokedMethod())
               && invoke.getReceiver() == getReceiver()) {
             // If another constructor call than `this` is found, then it must not have side effects.
-            if (invoke.instructionMayHaveSideEffects(appView, method.method.holder)) {
+            if (invoke.instructionMayHaveSideEffects(appView, method.holder())) {
               return false;
             }
             if (otherInitCalls == null) {
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 9b708bc..63f6a89 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
@@ -118,9 +118,7 @@
             methodTarget -> {
               DexEncodedMethod target = methodTarget.getMethod();
               if (target == refinedTarget
-                  || appView
-                      .isSubtype(target.method.holder, refinedReceiverType)
-                      .isPossiblyTrue()) {
+                  || appView.isSubtype(target.holder(), refinedReceiverType).isPossiblyTrue()) {
                 result.add(target);
               }
             },
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 a71d185..41fac8c 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
@@ -184,7 +184,7 @@
 
   @Override
   public boolean canBeDeadCode(AppView<?> appView, IRCode code) {
-    return !instructionMayHaveSideEffects(appView, code.method.method.holder);
+    return !instructionMayHaveSideEffects(appView, code.method.holder());
   }
 
   @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 7992a1c..b285dd6 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
@@ -197,7 +197,7 @@
 
   @Override
   public boolean canBeDeadCode(AppView<?> appView, IRCode code) {
-    return !instructionMayHaveSideEffects(appView, code.method.method.holder);
+    return !instructionMayHaveSideEffects(appView, code.method.holder());
   }
 
   @Override
diff --git a/src/main/java/com/android/tools/r8/ir/code/InvokeStatic.java b/src/main/java/com/android/tools/r8/ir/code/InvokeStatic.java
index 2d29972..01a6a76 100644
--- a/src/main/java/com/android/tools/r8/ir/code/InvokeStatic.java
+++ b/src/main/java/com/android/tools/r8/ir/code/InvokeStatic.java
@@ -183,7 +183,7 @@
 
       // Verify that the target method is accessible in the current context.
       if (!isMemberVisibleFromOriginalContext(
-          appView, context, target.method.holder, target.accessFlags)) {
+          appView, context, target.holder(), target.accessFlags)) {
         return true;
       }
 
@@ -200,12 +200,14 @@
         return false;
       }
 
-      return target.method.holder.classInitializationMayHaveSideEffects(
-          appView,
-          // Types that are a super type of `context` are guaranteed to be initialized
-          // already.
-          type -> appView.isSubtype(context, type).isTrue(),
-          Sets.newIdentityHashSet());
+      return target
+          .holder()
+          .classInitializationMayHaveSideEffects(
+              appView,
+              // Types that are a super type of `context` are guaranteed to be initialized
+              // already.
+              type -> appView.isSubtype(context, type).isTrue(),
+              Sets.newIdentityHashSet());
     }
 
     return true;
@@ -213,6 +215,6 @@
 
   @Override
   public boolean canBeDeadCode(AppView<?> appView, IRCode code) {
-    return !instructionMayHaveSideEffects(appView, code.method.method.holder);
+    return !instructionMayHaveSideEffects(appView, code.method.holder());
   }
 }
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 711c911..c30a8cb 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
@@ -180,7 +180,7 @@
 
       // Verify that the target method is accessible in the current context.
       if (!isMemberVisibleFromOriginalContext(
-          appView, context, target.method.holder, target.accessFlags)) {
+          appView, context, target.holder(), target.accessFlags)) {
         return true;
       }
 
@@ -200,6 +200,6 @@
 
   @Override
   public boolean canBeDeadCode(AppView<?> appView, IRCode code) {
-    return !instructionMayHaveSideEffects(appView, code.method.method.holder);
+    return !instructionMayHaveSideEffects(appView, code.method.holder());
   }
 }
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 adcc848..24e831c 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
@@ -73,7 +73,7 @@
 
   @Override
   public String toString() {
-    return super.toString() + " (" + outValue().getType() + ")";
+    return super.toString() + " (" + getOutType() + ")";
   }
 
   @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().getType().equals(outValue().getType());
+      assert src().getType().equals(getOutType());
     }
     return true;
   }
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 2211a59..87643df 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
@@ -151,6 +151,6 @@
 
   @Override
   public boolean canBeDeadCode(AppView<?> appView, IRCode code) {
-    return !instructionMayHaveSideEffects(appView, code.method.method.holder);
+    return !instructionMayHaveSideEffects(appView, code.method.holder());
   }
 }
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 d8acd0b..1090554 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
@@ -196,7 +196,7 @@
 
   @Override
   public boolean canBeDeadCode(AppView<?> appView, IRCode code) {
-    return !instructionMayHaveSideEffects(appView, code.method.method.holder);
+    return !instructionMayHaveSideEffects(appView, code.method.holder());
   }
 
   public void markNoSpilling() {
@@ -226,7 +226,7 @@
 
   @Override
   public boolean verifyTypes(AppView<?> appView) {
-    TypeElement type = outValue().getType();
+    TypeElement type = getOutType();
     assert type.isClassType();
     assert type.asClassType().getClassType() == clazz || appView.options().testing.allowTypeErrors;
     assert type.isDefinitelyNotNull();
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 2c246f92..84a86f2 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
@@ -38,10 +38,7 @@
 
   public static StaticGet copyOf(IRCode code, StaticGet original) {
     Value newValue =
-        new Value(
-            code.valueNumberGenerator.next(),
-            original.outValue().getType(),
-            original.getLocalInfo());
+        new Value(code.valueNumberGenerator.next(), original.getOutType(), original.getLocalInfo());
     return copyOf(newValue, original);
   }
 
@@ -156,7 +153,7 @@
     // * IncompatibleClassChangeError (static-* instruction for instance fields)
     // * IllegalAccessError (not visible from the access context)
     // * side-effects in <clinit>
-    return !instructionMayHaveSideEffects(appView, code.method.method.holder);
+    return !instructionMayHaveSideEffects(appView, code.method.holder());
   }
 
   @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 16c102b..7249d94 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
@@ -139,7 +139,7 @@
     // * IllegalAccessError (not visible from the access context)
     // * side-effects in <clinit>
     // * not read _globally_
-    boolean haveSideEffects = instructionMayHaveSideEffects(appView, code.method.method.holder);
+    boolean haveSideEffects = instructionMayHaveSideEffects(appView, code.method.holder());
     assert appView.enableWholeProgramOptimizations() || haveSideEffects
         : "Expected static-put instruction to have side effects in D8";
     return !haveSideEffects;
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 e84f64a..57c923c 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
@@ -826,7 +826,7 @@
       builder.append("(");
       if (isConstant && definition.asConstNumber().outValue != null) {
         ConstNumber constNumber = definition.asConstNumber();
-        if (constNumber.outValue().getType().isSinglePrimitive()) {
+        if (constNumber.getOutType().isSinglePrimitive()) {
           builder.append((int) constNumber.getRawValue());
         } else {
           builder.append(constNumber.getRawValue());
diff --git a/src/main/java/com/android/tools/r8/ir/conversion/CallGraphBuilderBase.java b/src/main/java/com/android/tools/r8/ir/conversion/CallGraphBuilderBase.java
index 6ff7e2f..18b62e6 100644
--- a/src/main/java/com/android/tools/r8/ir/conversion/CallGraphBuilderBase.java
+++ b/src/main/java/com/android/tools/r8/ir/conversion/CallGraphBuilderBase.java
@@ -178,7 +178,7 @@
         if (singleTarget != null) {
           assert !source.accessFlags.isBridge() || singleTarget != currentMethod.method;
           DexProgramClass clazz =
-              asProgramClassOrNull(appView.definitionFor(singleTarget.method.holder));
+              asProgramClassOrNull(appView.definitionFor(singleTarget.holder()));
           if (clazz != null) {
             // For static invokes, the class could be initialized.
             if (type == Invoke.Type.STATIC) {
@@ -253,8 +253,7 @@
         return;
       }
 
-      DexProgramClass clazz =
-          asProgramClassOrNull(appView.definitionFor(encodedField.field.holder));
+      DexProgramClass clazz = asProgramClassOrNull(appView.definitionFor(encodedField.holder()));
       if (clazz == null) {
         return;
       }
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 ae3e7cd..74f24c8 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
@@ -328,7 +328,7 @@
       }
     }
     return new CfCode(
-        method.method.holder,
+        method.holder(),
         stackHeightTracker.maxHeight,
         registerAllocator.registersUsed(),
         instructions,
@@ -386,7 +386,7 @@
             || constNumber == null
             || add == null
             || store == null
-            || constNumber.outValue().getType() != TypeElement.getInt()) {
+            || constNumber.getOutType() != TypeElement.getInt()) {
           it.next();
           continue;
         }
diff --git a/src/main/java/com/android/tools/r8/ir/conversion/CfSourceCode.java b/src/main/java/com/android/tools/r8/ir/conversion/CfSourceCode.java
index 1f1d2e5..f13653a 100644
--- a/src/main/java/com/android/tools/r8/ir/conversion/CfSourceCode.java
+++ b/src/main/java/com/android/tools/r8/ir/conversion/CfSourceCode.java
@@ -427,7 +427,7 @@
   private void buildMethodEnterSynchronization(IRBuilder builder) {
     assert needsGeneratedMethodSynchronization;
     currentlyGeneratingMethodSynchronization = true;
-    DexType type = method.method.holder;
+    DexType type = method.holder();
     int monitorRegister;
     if (isStatic()) {
       monitorRegister = state.push(type).register;
@@ -633,7 +633,7 @@
       return ((CfNew) instruction).getType();
     }
     if (type.isUninitializedThis()) {
-      return method.method.holder;
+      return method.holder();
     }
     assert type.isTop();
     return null;
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 a4ca85a..6e47281 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
@@ -1404,7 +1404,7 @@
     @Override
     public void addInstructions(DexBuilder builder, List<Instruction> instructions) {
       Move move = getMove();
-      TypeElement moveType = move.outValue().getType();
+      TypeElement moveType = move.getOutType();
       int src = srcRegister(builder);
       int dest = destRegister(builder);
       Instruction instruction;
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 993a69c..35250d1 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
@@ -530,7 +530,7 @@
     int argumentIndex = 0;
 
     if (!method.isStatic()) {
-      writeCallback.accept(register, method.method.holder);
+      writeCallback.accept(register, method.holder());
       addThisArgument(register);
       argumentIndex++;
       register++;
@@ -932,7 +932,7 @@
   void addThisArgument(int register) {
     boolean receiverCouldBeNull = context != null && context != method;
     Nullability nullability = receiverCouldBeNull ? maybeNull() : definitelyNotNull();
-    TypeElement receiverType = TypeElement.fromDexType(method.method.holder, nullability, appView);
+    TypeElement receiverType = TypeElement.fromDexType(method.holder(), nullability, appView);
     addThisArgument(register, receiverType);
   }
 
@@ -1463,14 +1463,14 @@
       // therefore we use an invoke-direct instead. We need to do this as the Android Runtime
       // will not allow invoke-virtual of a private method.
       DexMethod invocationMethod = (DexMethod) item;
-      DexType holderType = method.method.holder;
+      DexType holderType = method.holder();
       if (invocationMethod.holder == holderType) {
         DexClass holderClass = appView.definitionFor(holderType);
         assert holderClass != null && holderClass.isProgramClass();
         if (holderClass != null) {
           DexEncodedMethod directTarget = holderClass.lookupDirectMethod(invocationMethod);
           if (directTarget != null && !directTarget.isStatic()) {
-            assert invocationMethod.holder == directTarget.method.holder;
+            assert invocationMethod.holder == directTarget.holder();
             type = Type.DIRECT;
           }
         }
@@ -2332,7 +2332,7 @@
 
   private void addInstruction(Instruction ir, Position position) {
     assert verifyOutValueType(ir);
-    hasImpreciseValues |= ir.outValue() != null && !ir.outValue().getType().isPreciseType();
+    hasImpreciseValues |= ir.outValue() != null && !ir.getOutType().isPreciseType();
     ir.setPosition(position);
     attachLocalValues(ir);
     currentBlock.add(ir, metadata);
@@ -2363,13 +2363,11 @@
   }
 
   private boolean verifyOutValueType(Instruction ir) {
-    assert ir.outValue() == null
-        || ir.isArrayGet()
-        || ir.evaluate(appView) == ir.outValue().getType();
+    assert ir.outValue() == null || ir.isArrayGet() || ir.evaluate(appView) == ir.getOutType();
     assert ir.outValue() == null
         || !ir.isArrayGet()
-        || ir.evaluate(appView) == ir.outValue().getType()
-        || (ir.outValue().getType().isBottom() && ir.evaluate(appView).isReferenceType());
+        || ir.evaluate(appView) == ir.getOutType()
+        || (ir.getOutType().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 cae0c84..e29a1c1 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
@@ -94,6 +94,7 @@
 import com.android.tools.r8.shaking.AppInfoWithLiveness;
 import com.android.tools.r8.shaking.LibraryMethodOverrideAnalysis;
 import com.android.tools.r8.shaking.MainDexClasses;
+import com.android.tools.r8.utils.Action;
 import com.android.tools.r8.utils.CfgPrinter;
 import com.android.tools.r8.utils.DescriptorUtils;
 import com.android.tools.r8.utils.ExceptionUtils;
@@ -181,7 +182,7 @@
       OptimizationFeedbackSimple.getInstance();
   private DexString highestSortingString;
 
-  private List<com.android.tools.r8.utils.Action> onWaveDoneActions = null;
+  private List<Action> onWaveDoneActions = null;
 
   private final List<DexString> neverMergePrefixes;
   boolean seenNotNeverMergePrefix = false;
@@ -609,10 +610,10 @@
         if (appView.options().enableNeverMergePrefixes) {
           for (DexString neverMergePrefix : neverMergePrefixes) {
             // Synthetic classes will always be merged.
-            if (method.method.holder.isD8R8SynthesizedClassType()) {
+            if (method.holder().isD8R8SynthesizedClassType()) {
               continue;
             }
-            if (method.method.holder.descriptor.startsWith(neverMergePrefix)) {
+            if (method.holder().descriptor.startsWith(neverMergePrefix)) {
               seenNeverMergePrefix = true;
             } else {
               seenNotNeverMergePrefix = true;
@@ -904,7 +905,7 @@
     ThreadUtils.processItems(
         methods,
         method -> {
-          IRCode code = method.buildIR(appView, appView.appInfo().originFor(method.method.holder));
+          IRCode code = method.buildIR(appView, appView.appInfo().originFor(method.holder()));
           assert code != null;
           assert !method.getCode().isOutlineCode();
           // Instead of repeating all the optimizations of rewriteCode(), only run the
@@ -928,7 +929,7 @@
   }
 
   private void forEachSynthesizedServiceLoaderMethod(DexEncodedMethod method) {
-    IRCode code = method.buildIR(appView, appView.appInfo().originFor(method.method.holder));
+    IRCode code = method.buildIR(appView, appView.appInfo().originFor(method.holder()));
     assert code != null;
     codeRewriter.rewriteMoveResult(code);
     removeDeadCodeAndFinalizeIR(
@@ -1006,7 +1007,7 @@
       count++;
       result = appView.dexItemFactory().createType(DescriptorUtils.javaTypeToDescriptor(name));
 
-    } while (appView.definitionFor(result) != null);
+    } while (appView.appInfo().definitionForWithoutExistenceAssert(result) != null);
     return result;
   }
 
@@ -1085,7 +1086,7 @@
 
   private Timing rewriteCode(
       DexEncodedMethod method, OptimizationFeedback feedback, MethodProcessor methodProcessor) {
-    Origin origin = appView.appInfo().originFor(method.method.holder);
+    Origin origin = appView.appInfo().originFor(method.holder());
     return ExceptionUtils.withOriginAttachmentHandler(
         origin,
         new MethodPosition(method.method),
@@ -1147,6 +1148,10 @@
       codeRewriter.simplifyDebugLocals(code);
     }
 
+    if (enumUnboxer != null && methodProcessor.isPost()) {
+      enumUnboxer.rewriteCode(code);
+    }
+
     if (appView.graphLense().hasCodeRewritings()) {
       assert lensCodeRewriter != null;
       timing.begin("Lens rewrite");
@@ -1154,10 +1159,6 @@
       timing.end();
     }
 
-    if (enumUnboxer != null && methodProcessor.isPost()) {
-      enumUnboxer.rewriteCode(code);
-    }
-
     if (method.isProcessed()) {
       assert !appView.enableWholeProgramOptimizations()
           || !appView.appInfo().withLiveness().neverReprocess.contains(method.method);
@@ -1215,7 +1216,7 @@
 
     if (memberValuePropagation != null) {
       timing.begin("Propagate member values");
-      memberValuePropagation.rewriteWithConstantValues(code, method.method.holder);
+      memberValuePropagation.rewriteWithConstantValues(code, method.holder());
       timing.end();
     }
 
@@ -1284,7 +1285,7 @@
     if (devirtualizer != null) {
       assert code.verifyTypes(appView);
       timing.begin("Devirtualize invoke interface");
-      devirtualizer.devirtualizeInvokeInterface(code, method.method.holder);
+      devirtualizer.devirtualizeInvokeInterface(code, method.holder());
       timing.end();
     }
 
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 bb541cf..7daa4a1 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
@@ -7,6 +7,27 @@
 import static com.android.tools.r8.graph.UseRegistry.MethodHandleUse.NOT_ARGUMENT_TO_LAMBDA_METAFACTORY;
 import static com.android.tools.r8.ir.code.Invoke.Type.STATIC;
 import static com.android.tools.r8.ir.code.Invoke.Type.VIRTUAL;
+import static com.android.tools.r8.ir.code.Opcodes.CHECK_CAST;
+import static com.android.tools.r8.ir.code.Opcodes.CONST_CLASS;
+import static com.android.tools.r8.ir.code.Opcodes.CONST_METHOD_HANDLE;
+import static com.android.tools.r8.ir.code.Opcodes.INIT_CLASS;
+import static com.android.tools.r8.ir.code.Opcodes.INSTANCE_GET;
+import static com.android.tools.r8.ir.code.Opcodes.INSTANCE_OF;
+import static com.android.tools.r8.ir.code.Opcodes.INSTANCE_PUT;
+import static com.android.tools.r8.ir.code.Opcodes.INVOKE_CUSTOM;
+import static com.android.tools.r8.ir.code.Opcodes.INVOKE_DIRECT;
+import static com.android.tools.r8.ir.code.Opcodes.INVOKE_INTERFACE;
+import static com.android.tools.r8.ir.code.Opcodes.INVOKE_MULTI_NEW_ARRAY;
+import static com.android.tools.r8.ir.code.Opcodes.INVOKE_NEW_ARRAY;
+import static com.android.tools.r8.ir.code.Opcodes.INVOKE_POLYMORPHIC;
+import static com.android.tools.r8.ir.code.Opcodes.INVOKE_STATIC;
+import static com.android.tools.r8.ir.code.Opcodes.INVOKE_SUPER;
+import static com.android.tools.r8.ir.code.Opcodes.INVOKE_VIRTUAL;
+import static com.android.tools.r8.ir.code.Opcodes.MOVE_EXCEPTION;
+import static com.android.tools.r8.ir.code.Opcodes.NEW_ARRAY_EMPTY;
+import static com.android.tools.r8.ir.code.Opcodes.NEW_INSTANCE;
+import static com.android.tools.r8.ir.code.Opcodes.STATIC_GET;
+import static com.android.tools.r8.ir.code.Opcodes.STATIC_PUT;
 
 import com.android.tools.r8.errors.Unreachable;
 import com.android.tools.r8.graph.AppInfoWithSubtyping;
@@ -86,7 +107,7 @@
 
   private Value makeOutValue(Instruction insn, IRCode code) {
     if (insn.outValue() != null) {
-      TypeElement oldType = insn.outValue().getType();
+      TypeElement oldType = insn.getOutType();
       TypeElement newType =
           oldType.fixupClassTypeReferences(appView.graphLense()::lookupType, appView);
       return code.createValue(newType, insn.getLocalInfo());
@@ -114,291 +135,370 @@
       InstructionListIterator iterator = block.listIterator(code);
       while (iterator.hasNext()) {
         Instruction current = iterator.next();
-        if (current.isInvokeCustom()) {
-          InvokeCustom invokeCustom = current.asInvokeCustom();
-          DexCallSite callSite = invokeCustom.getCallSite();
-          DexCallSite newCallSite = rewriteCallSite(callSite, method);
-          if (newCallSite != callSite) {
-            Value newOutValue = makeOutValue(invokeCustom, code);
-            InvokeCustom newInvokeCustom =
-                new InvokeCustom(newCallSite, newOutValue, invokeCustom.inValues());
-            iterator.replaceCurrentInstruction(newInvokeCustom);
-            if (newOutValue != null && newOutValue.getType() != invokeCustom.outValue().getType()) {
-              affectedPhis.addAll(newOutValue.uniquePhiUsers());
-            }
-          }
-        } else if (current.isConstMethodHandle()) {
-          DexMethodHandle handle = current.asConstMethodHandle().getValue();
-          DexMethodHandle newHandle = rewriteDexMethodHandle(
-              handle, method, NOT_ARGUMENT_TO_LAMBDA_METAFACTORY);
-          if (newHandle != handle) {
-            Value newOutValue = makeOutValue(current, code);
-            iterator.replaceCurrentInstruction(new ConstMethodHandle(newOutValue, newHandle));
-            if (newOutValue != null && newOutValue.getType() != current.outValue().getType()) {
-              affectedPhis.addAll(newOutValue.uniquePhiUsers());
-            }
-          }
-        } else if (current.isInitClass()) {
-          InitClass initClass = current.asInitClass();
-          new InstructionReplacer(code, current, iterator, affectedPhis)
-              .replaceInstructionIfTypeChanged(
-                  initClass.getClassValue(), (t, v) -> new InitClass(v, t));
-        } else if (current.isInvokeMethod()) {
-          InvokeMethod invoke = current.asInvokeMethod();
-          DexMethod invokedMethod = invoke.getInvokedMethod();
-          DexType invokedHolder = invokedMethod.holder;
-          if (invokedHolder.isArrayType()) {
-            DexType baseType = invokedHolder.toBaseType(factory);
-            new InstructionReplacer(code, current, iterator, affectedPhis)
-                .replaceInstructionIfTypeChanged(
-                    baseType,
-                    (t, v) -> {
-                      DexType mappedHolder = invokedHolder.replaceBaseType(t, factory);
-                      // Just reuse proto and name, as no methods on array types cant be renamed nor
-                      // change signature.
-                      DexMethod actualTarget =
-                          factory.createMethod(
-                              mappedHolder, invokedMethod.proto, invokedMethod.name);
-                      return Invoke.create(VIRTUAL, actualTarget, null, v, invoke.inValues());
-                    });
-            continue;
-          }
-          if (!invokedHolder.isClassType()) {
-            assert false;
-            continue;
-          }
-          if (invoke.isInvokeDirect()) {
-            checkInvokeDirect(method.method, invoke.asInvokeDirect());
-          }
-          GraphLenseLookupResult lenseLookup =
-              graphLense.lookupMethod(invokedMethod, method.method, invoke.getType());
-          DexMethod actualTarget = lenseLookup.getMethod();
-          Invoke.Type actualInvokeType = lenseLookup.getType();
-          if (actualTarget != invokedMethod || invoke.getType() != actualInvokeType) {
-            RewrittenPrototypeDescription prototypeChanges =
-                graphLense.lookupPrototypeChanges(actualTarget);
-
-            List<Value> newInValues;
-            ArgumentInfoCollection argumentInfoCollection =
-                prototypeChanges.getArgumentInfoCollection();
-            if (argumentInfoCollection.isEmpty()) {
-              newInValues = invoke.inValues();
-            } else {
-              if (argumentInfoCollection.hasRemovedArguments()) {
-                if (Log.ENABLED) {
-                  Log.info(
-                      getClass(),
-                      "Invoked method "
-                          + invokedMethod.toSourceString()
-                          + " with "
-                          + argumentInfoCollection.numberOfRemovedArguments()
-                          + " arguments removed");
-                }
-              }
-              newInValues = new ArrayList<>(actualTarget.proto.parameters.size());
-              for (int i = 0; i < invoke.inValues().size(); i++) {
-                ArgumentInfo argumentInfo = argumentInfoCollection.getArgumentInfo(i);
-                if (argumentInfo.isRewrittenTypeInfo()) {
-                  RewrittenTypeInfo argInfo = argumentInfo.asRewrittenTypeInfo();
-                  Value rewrittenValue =
-                      rewriteValueIfDefault(
-                          code,
-                          iterator,
-                          argInfo.getOldType(),
-                          argInfo.getNewType(),
-                          invoke.inValues().get(i));
-                  newInValues.add(rewrittenValue);
-                } else if (!argumentInfo.isRemovedArgumentInfo()) {
-                  newInValues.add(invoke.inValues().get(i));
+        switch (current.opcode()) {
+          case INVOKE_CUSTOM:
+            {
+              InvokeCustom invokeCustom = current.asInvokeCustom();
+              DexCallSite callSite = invokeCustom.getCallSite();
+              DexCallSite newCallSite = rewriteCallSite(callSite, method);
+              if (newCallSite != callSite) {
+                Value newOutValue = makeOutValue(invokeCustom, code);
+                InvokeCustom newInvokeCustom =
+                    new InvokeCustom(newCallSite, newOutValue, invokeCustom.inValues());
+                iterator.replaceCurrentInstruction(newInvokeCustom);
+                if (newOutValue != null && newOutValue.getType() != invokeCustom.getOutType()) {
+                  affectedPhis.addAll(newOutValue.uniquePhiUsers());
                 }
               }
             }
+            break;
 
-            ConstInstruction constantReturnMaterializingInstruction = null;
-            if (prototypeChanges.hasBeenChangedToReturnVoid(appView) && invoke.outValue() != null) {
-              constantReturnMaterializingInstruction =
-                  prototypeChanges.getConstantReturn(code, invoke.getPosition());
-              if (invoke.outValue().hasLocalInfo()) {
-                constantReturnMaterializingInstruction
-                    .outValue()
-                    .setLocalInfo(invoke.outValue().getLocalInfo());
-              }
-              invoke.outValue().replaceUsers(constantReturnMaterializingInstruction.outValue());
-              if (graphLense.lookupType(invoke.getReturnType()) != invoke.getReturnType()) {
-                affectedPhis.addAll(
-                    constantReturnMaterializingInstruction.outValue().uniquePhiUsers());
+          case CONST_METHOD_HANDLE:
+            {
+              DexMethodHandle handle = current.asConstMethodHandle().getValue();
+              DexMethodHandle newHandle =
+                  rewriteDexMethodHandle(handle, method, NOT_ARGUMENT_TO_LAMBDA_METAFACTORY);
+              if (newHandle != handle) {
+                Value newOutValue = makeOutValue(current, code);
+                iterator.replaceCurrentInstruction(new ConstMethodHandle(newOutValue, newHandle));
+                if (newOutValue != null && newOutValue.getType() != current.getOutType()) {
+                  affectedPhis.addAll(newOutValue.uniquePhiUsers());
+                }
               }
             }
+            break;
 
-            Value newOutValue =
-                prototypeChanges.hasBeenChangedToReturnVoid(appView)
-                    ? null
-                    : makeOutValue(invoke, code);
-
-            if (prototypeChanges.hasExtraNullParameter()) {
-              iterator.previous();
-              Value extraNullValue = iterator.insertConstNullInstruction(code, appView.options());
-              iterator.next();
-              newInValues.add(extraNullValue);
+          case INIT_CLASS:
+            {
+              InitClass initClass = current.asInitClass();
+              new InstructionReplacer(code, current, iterator, affectedPhis)
+                  .replaceInstructionIfTypeChanged(
+                      initClass.getClassValue(), (t, v) -> new InitClass(v, t));
             }
+            break;
 
-            assert newInValues.size()
-                == actualTarget.proto.parameters.size() + (actualInvokeType == STATIC ? 0 : 1);
+          case INVOKE_DIRECT:
+          case INVOKE_INTERFACE:
+          case INVOKE_POLYMORPHIC:
+          case INVOKE_STATIC:
+          case INVOKE_SUPER:
+          case INVOKE_VIRTUAL:
+            {
+              InvokeMethod invoke = current.asInvokeMethod();
+              DexMethod invokedMethod = invoke.getInvokedMethod();
+              DexType invokedHolder = invokedMethod.holder;
+              if (invokedHolder.isArrayType()) {
+                DexType baseType = invokedHolder.toBaseType(factory);
+                new InstructionReplacer(code, current, iterator, affectedPhis)
+                    .replaceInstructionIfTypeChanged(
+                        baseType,
+                        (t, v) -> {
+                          DexType mappedHolder = invokedHolder.replaceBaseType(t, factory);
+                          // Just reuse proto and name, as no methods on array types cant be renamed
+                          // nor change signature.
+                          DexMethod actualTarget =
+                              factory.createMethod(
+                                  mappedHolder, invokedMethod.proto, invokedMethod.name);
+                          return Invoke.create(VIRTUAL, actualTarget, null, v, invoke.inValues());
+                        });
+                continue;
+              }
+              if (!invokedHolder.isClassType()) {
+                assert false;
+                continue;
+              }
+              if (invoke.isInvokeDirect()) {
+                checkInvokeDirect(method.method, invoke.asInvokeDirect());
+              }
+              GraphLenseLookupResult lenseLookup =
+                  graphLense.lookupMethod(invokedMethod, method.method, invoke.getType());
+              DexMethod actualTarget = lenseLookup.getMethod();
+              Invoke.Type actualInvokeType = lenseLookup.getType();
+              if (actualTarget != invokedMethod || invoke.getType() != actualInvokeType) {
+                RewrittenPrototypeDescription prototypeChanges =
+                    graphLense.lookupPrototypeChanges(actualTarget);
 
-            Invoke newInvoke =
-                Invoke.create(actualInvokeType, actualTarget, null, newOutValue, newInValues);
-            iterator.replaceCurrentInstruction(newInvoke);
-            if (newOutValue != null && newOutValue.getType() != current.outValue().getType()) {
-              affectedPhis.addAll(newOutValue.uniquePhiUsers());
-            }
+                List<Value> newInValues;
+                ArgumentInfoCollection argumentInfoCollection =
+                    prototypeChanges.getArgumentInfoCollection();
+                if (argumentInfoCollection.isEmpty()) {
+                  newInValues = invoke.inValues();
+                } else {
+                  if (argumentInfoCollection.hasRemovedArguments()) {
+                    if (Log.ENABLED) {
+                      Log.info(
+                          getClass(),
+                          "Invoked method "
+                              + invokedMethod.toSourceString()
+                              + " with "
+                              + argumentInfoCollection.numberOfRemovedArguments()
+                              + " arguments removed");
+                    }
+                  }
+                  newInValues = new ArrayList<>(actualTarget.proto.parameters.size());
+                  for (int i = 0; i < invoke.inValues().size(); i++) {
+                    ArgumentInfo argumentInfo = argumentInfoCollection.getArgumentInfo(i);
+                    if (argumentInfo.isRewrittenTypeInfo()) {
+                      RewrittenTypeInfo argInfo = argumentInfo.asRewrittenTypeInfo();
+                      Value rewrittenValue =
+                          rewriteValueIfDefault(
+                              code,
+                              iterator,
+                              argInfo.getOldType(),
+                              argInfo.getNewType(),
+                              invoke.inValues().get(i));
+                      newInValues.add(rewrittenValue);
+                    } else if (!argumentInfo.isRemovedArgumentInfo()) {
+                      newInValues.add(invoke.inValues().get(i));
+                    }
+                  }
+                }
 
-            if (constantReturnMaterializingInstruction != null) {
-              if (block.hasCatchHandlers()) {
-                // Split the block to ensure no instructions after throwing instructions.
-                iterator
-                    .split(code, blocks)
-                    .listIterator(code)
-                    .add(constantReturnMaterializingInstruction);
-              } else {
-                iterator.add(constantReturnMaterializingInstruction);
+                ConstInstruction constantReturnMaterializingInstruction = null;
+                if (prototypeChanges.hasBeenChangedToReturnVoid(appView)
+                    && invoke.outValue() != null) {
+                  constantReturnMaterializingInstruction =
+                      prototypeChanges.getConstantReturn(code, invoke.getPosition());
+                  if (invoke.outValue().hasLocalInfo()) {
+                    constantReturnMaterializingInstruction
+                        .outValue()
+                        .setLocalInfo(invoke.outValue().getLocalInfo());
+                  }
+                  invoke.outValue().replaceUsers(constantReturnMaterializingInstruction.outValue());
+                  if (graphLense.lookupType(invoke.getReturnType()) != invoke.getReturnType()) {
+                    affectedPhis.addAll(
+                        constantReturnMaterializingInstruction.outValue().uniquePhiUsers());
+                  }
+                }
+
+                Value newOutValue =
+                    prototypeChanges.hasBeenChangedToReturnVoid(appView)
+                        ? null
+                        : makeOutValue(invoke, code);
+
+                if (prototypeChanges.hasExtraNullParameter()) {
+                  iterator.previous();
+                  Value extraNullValue =
+                      iterator.insertConstNullInstruction(code, appView.options());
+                  iterator.next();
+                  newInValues.add(extraNullValue);
+                }
+
+                assert newInValues.size()
+                    == actualTarget.proto.parameters.size() + (actualInvokeType == STATIC ? 0 : 1);
+
+                Invoke newInvoke =
+                    Invoke.create(actualInvokeType, actualTarget, null, newOutValue, newInValues);
+                iterator.replaceCurrentInstruction(newInvoke);
+                if (newOutValue != null && newOutValue.getType() != current.getOutType()) {
+                  affectedPhis.addAll(newOutValue.uniquePhiUsers());
+                }
+
+                if (constantReturnMaterializingInstruction != null) {
+                  if (block.hasCatchHandlers()) {
+                    // Split the block to ensure no instructions after throwing instructions.
+                    iterator
+                        .split(code, blocks)
+                        .listIterator(code)
+                        .add(constantReturnMaterializingInstruction);
+                  } else {
+                    iterator.add(constantReturnMaterializingInstruction);
+                  }
+                }
+
+                DexType actualReturnType = actualTarget.proto.returnType;
+                DexType expectedReturnType = graphLense.lookupType(invokedMethod.proto.returnType);
+                if (newInvoke.outValue() != null && actualReturnType != expectedReturnType) {
+                  throw new Unreachable(
+                      "Unexpected need to insert a cast. Possibly related to resolving"
+                          + " b/79143143.\n"
+                          + invokedMethod
+                          + " type changed from "
+                          + expectedReturnType
+                          + " to "
+                          + actualReturnType);
+                }
               }
             }
+            break;
 
-            DexType actualReturnType = actualTarget.proto.returnType;
-            DexType expectedReturnType = graphLense.lookupType(invokedMethod.proto.returnType);
-            if (newInvoke.outValue() != null && actualReturnType != expectedReturnType) {
-              throw new Unreachable(
-                  "Unexpected need to insert a cast. Possibly related to resolving b/79143143.\n"
-                      + invokedMethod
-                      + " type changed from " + expectedReturnType
-                      + " to " + actualReturnType);
+          case INSTANCE_GET:
+            {
+              InstanceGet instanceGet = current.asInstanceGet();
+              DexField field = instanceGet.getField();
+              DexField actualField = graphLense.lookupField(field);
+              DexMethod replacementMethod =
+                  graphLense.lookupGetFieldForMethod(actualField, method.method);
+              if (replacementMethod != null) {
+                Value newOutValue = makeOutValue(current, code);
+                iterator.replaceCurrentInstruction(
+                    new InvokeStatic(replacementMethod, newOutValue, current.inValues()));
+                if (newOutValue != null && newOutValue.getType() != current.getOutType()) {
+                  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.getType() != current.getOutType()) {
+                  affectedPhis.addAll(newOutValue.uniquePhiUsers());
+                }
+              }
             }
-          }
-        } else if (current.isInstanceGet()) {
-          InstanceGet instanceGet = current.asInstanceGet();
-          DexField field = instanceGet.getField();
-          DexField actualField = graphLense.lookupField(field);
-          DexMethod replacementMethod =
-              graphLense.lookupGetFieldForMethod(actualField, method.method);
-          if (replacementMethod != null) {
-            Value newOutValue = makeOutValue(current, code);
-            iterator.replaceCurrentInstruction(
-                new InvokeStatic(replacementMethod, newOutValue, current.inValues()));
-            if (newOutValue != null && newOutValue.getType() != current.outValue().getType()) {
-              affectedPhis.addAll(current.outValue().uniquePhiUsers());
+            break;
+
+          case INSTANCE_PUT:
+            {
+              InstancePut instancePut = current.asInstancePut();
+              DexField field = instancePut.getField();
+              DexField actualField = graphLense.lookupField(field);
+              DexMethod replacementMethod =
+                  graphLense.lookupPutFieldForMethod(actualField, method.method);
+              if (replacementMethod != null) {
+                iterator.replaceCurrentInstruction(
+                    new InvokeStatic(replacementMethod, null, current.inValues()));
+              } else if (actualField != field) {
+                Value rewrittenValue =
+                    rewriteValueIfDefault(
+                        code, iterator, field.type, actualField.type, instancePut.value());
+                InstancePut newInstancePut =
+                    InstancePut.createPotentiallyInvalid(
+                        actualField, instancePut.object(), rewrittenValue);
+                iterator.replaceCurrentInstruction(newInstancePut);
+              }
             }
-          } else if (actualField != field) {
-            Value newOutValue = makeOutValue(instanceGet, code);
-            iterator.replaceCurrentInstruction(
-                new InstanceGet(newOutValue, instanceGet.object(), actualField));
-            if (newOutValue != null && newOutValue.getType() != current.outValue().getType()) {
-              affectedPhis.addAll(newOutValue.uniquePhiUsers());
+            break;
+
+          case STATIC_GET:
+            {
+              StaticGet staticGet = current.asStaticGet();
+              DexField field = staticGet.getField();
+              DexField actualField = graphLense.lookupField(field);
+              DexMethod replacementMethod =
+                  graphLense.lookupGetFieldForMethod(actualField, method.method);
+              if (replacementMethod != null) {
+                Value newOutValue = makeOutValue(current, code);
+                iterator.replaceCurrentInstruction(
+                    new InvokeStatic(replacementMethod, newOutValue, current.inValues()));
+                if (newOutValue != null && newOutValue.getType() != current.getOutType()) {
+                  affectedPhis.addAll(newOutValue.uniquePhiUsers());
+                }
+              } else if (actualField != field) {
+                Value newOutValue = makeOutValue(staticGet, code);
+                iterator.replaceCurrentInstruction(new StaticGet(newOutValue, actualField));
+                if (newOutValue != null && newOutValue.getType() != current.getOutType()) {
+                  affectedPhis.addAll(newOutValue.uniquePhiUsers());
+                }
+              }
             }
-          }
-        } else if (current.isInstancePut()) {
-          InstancePut instancePut = current.asInstancePut();
-          DexField field = instancePut.getField();
-          DexField actualField = graphLense.lookupField(field);
-          DexMethod replacementMethod =
-              graphLense.lookupPutFieldForMethod(actualField, method.method);
-          if (replacementMethod != null) {
-            iterator.replaceCurrentInstruction(
-                new InvokeStatic(replacementMethod, null, current.inValues()));
-          } else if (actualField != field) {
-            Value rewrittenValue =
-                rewriteValueIfDefault(
-                    code, iterator, field.type, actualField.type, instancePut.value());
-            InstancePut newInstancePut =
-                InstancePut.createPotentiallyInvalid(
-                    actualField, instancePut.object(), rewrittenValue);
-            iterator.replaceCurrentInstruction(newInstancePut);
-          }
-        } else if (current.isStaticGet()) {
-          StaticGet staticGet = current.asStaticGet();
-          DexField field = staticGet.getField();
-          DexField actualField = graphLense.lookupField(field);
-          DexMethod replacementMethod =
-              graphLense.lookupGetFieldForMethod(actualField, method.method);
-          if (replacementMethod != null) {
-            Value newOutValue = makeOutValue(current, code);
-            iterator.replaceCurrentInstruction(
-                new InvokeStatic(replacementMethod, newOutValue, current.inValues()));
-            if (newOutValue != null && newOutValue.getType() != current.outValue().getType()) {
-              affectedPhis.addAll(newOutValue.uniquePhiUsers());
+            break;
+
+          case STATIC_PUT:
+            {
+              StaticPut staticPut = current.asStaticPut();
+              DexField field = staticPut.getField();
+              DexField actualField = graphLense.lookupField(field);
+              DexMethod replacementMethod =
+                  graphLense.lookupPutFieldForMethod(actualField, method.method);
+              if (replacementMethod != null) {
+                iterator.replaceCurrentInstruction(
+                    new InvokeStatic(replacementMethod, current.outValue(), current.inValues()));
+              } else if (actualField != field) {
+                Value rewrittenValue =
+                    rewriteValueIfDefault(
+                        code, iterator, field.type, actualField.type, staticPut.value());
+                StaticPut newStaticPut = new StaticPut(rewrittenValue, actualField);
+                iterator.replaceCurrentInstruction(newStaticPut);
+              }
             }
-          } else if (actualField != field) {
-            Value newOutValue = makeOutValue(staticGet, code);
-            iterator.replaceCurrentInstruction(new StaticGet(newOutValue, actualField));
-            if (newOutValue != null && newOutValue.getType() != current.outValue().getType()) {
-              affectedPhis.addAll(newOutValue.uniquePhiUsers());
+            break;
+
+          case CHECK_CAST:
+            {
+              CheckCast checkCast = current.asCheckCast();
+              new InstructionReplacer(code, current, iterator, affectedPhis)
+                  .replaceInstructionIfTypeChanged(
+                      checkCast.getType(), (t, v) -> new CheckCast(v, checkCast.object(), t));
             }
-          }
-        } else if (current.isStaticPut()) {
-          StaticPut staticPut = current.asStaticPut();
-          DexField field = staticPut.getField();
-          DexField actualField = graphLense.lookupField(field);
-          DexMethod replacementMethod =
-              graphLense.lookupPutFieldForMethod(actualField, method.method);
-          if (replacementMethod != null) {
-            iterator.replaceCurrentInstruction(
-                new InvokeStatic(replacementMethod, current.outValue(), current.inValues()));
-          } else if (actualField != field) {
-            Value rewrittenValue =
-                rewriteValueIfDefault(
-                    code, iterator, field.type, actualField.type, staticPut.value());
-            StaticPut newStaticPut = new StaticPut(rewrittenValue, actualField);
-            iterator.replaceCurrentInstruction(newStaticPut);
-          }
-        } else if (current.isCheckCast()) {
-          CheckCast checkCast = current.asCheckCast();
-          new InstructionReplacer(code, current, iterator, affectedPhis)
-              .replaceInstructionIfTypeChanged(
-                  checkCast.getType(), (t, v) -> new CheckCast(v, checkCast.object(), t));
-        } else if (current.isConstClass()) {
-          ConstClass constClass = current.asConstClass();
-          new InstructionReplacer(code, current, iterator, affectedPhis)
-              .replaceInstructionIfTypeChanged(
-                  constClass.getValue(), (t, v) -> new ConstClass(v, t));
-        } else if (current.isInstanceOf()) {
-          InstanceOf instanceOf = current.asInstanceOf();
-          new InstructionReplacer(code, current, iterator, affectedPhis)
-              .replaceInstructionIfTypeChanged(
-                  instanceOf.type(), (t, v) -> new InstanceOf(v, instanceOf.value(), t));
-        } else if (current.isInvokeMultiNewArray()) {
-          InvokeMultiNewArray multiNewArray = current.asInvokeMultiNewArray();
-          new InstructionReplacer(code, current, iterator, affectedPhis)
-              .replaceInstructionIfTypeChanged(
-                  multiNewArray.getArrayType(),
-                  (t, v) -> new InvokeMultiNewArray(t, v, multiNewArray.inValues()));
-        } else if (current.isInvokeNewArray()) {
-          InvokeNewArray newArray = current.asInvokeNewArray();
-          new InstructionReplacer(code, current, iterator, affectedPhis)
-              .replaceInstructionIfTypeChanged(
-                  newArray.getArrayType(), (t, v) -> new InvokeNewArray(t, v, newArray.inValues()));
-        } else if (current.isMoveException()) {
-          MoveException moveException = current.asMoveException();
-          new InstructionReplacer(code, current, iterator, affectedPhis)
-              .replaceInstructionIfTypeChanged(
-                  moveException.getExceptionType(),
-                  (t, v) -> new MoveException(v, t, appView.options()));
-        } else if (current.isNewArrayEmpty()) {
-          NewArrayEmpty newArrayEmpty = current.asNewArrayEmpty();
-          new InstructionReplacer(code, current, iterator, affectedPhis)
-              .replaceInstructionIfTypeChanged(
-                  newArrayEmpty.type, (t, v) -> new NewArrayEmpty(v, newArrayEmpty.size(), t));
-        } else if (current.isNewInstance()) {
-          DexType type = current.asNewInstance().clazz;
-          new InstructionReplacer(code, current, iterator, affectedPhis)
-              .replaceInstructionIfTypeChanged(type, NewInstance::new);
-        } else if (current.outValue() != null) {
-          // For all other instructions, substitute any changed type.
-          TypeElement typeLattice = current.outValue().getType();
-          TypeElement substituted =
-              typeLattice.fixupClassTypeReferences(graphLense::lookupType, appView);
-          if (substituted != typeLattice) {
-            current.outValue().setType(substituted);
-            affectedPhis.addAll(current.outValue().uniquePhiUsers());
-          }
+            break;
+
+          case CONST_CLASS:
+            {
+              ConstClass constClass = current.asConstClass();
+              new InstructionReplacer(code, current, iterator, affectedPhis)
+                  .replaceInstructionIfTypeChanged(
+                      constClass.getValue(), (t, v) -> new ConstClass(v, t));
+            }
+            break;
+
+          case INSTANCE_OF:
+            {
+              InstanceOf instanceOf = current.asInstanceOf();
+              new InstructionReplacer(code, current, iterator, affectedPhis)
+                  .replaceInstructionIfTypeChanged(
+                      instanceOf.type(), (t, v) -> new InstanceOf(v, instanceOf.value(), t));
+            }
+            break;
+
+          case INVOKE_MULTI_NEW_ARRAY:
+            {
+              InvokeMultiNewArray multiNewArray = current.asInvokeMultiNewArray();
+              new InstructionReplacer(code, current, iterator, affectedPhis)
+                  .replaceInstructionIfTypeChanged(
+                      multiNewArray.getArrayType(),
+                      (t, v) -> new InvokeMultiNewArray(t, v, multiNewArray.inValues()));
+            }
+            break;
+
+          case INVOKE_NEW_ARRAY:
+            {
+              InvokeNewArray newArray = current.asInvokeNewArray();
+              new InstructionReplacer(code, current, iterator, affectedPhis)
+                  .replaceInstructionIfTypeChanged(
+                      newArray.getArrayType(),
+                      (t, v) -> new InvokeNewArray(t, v, newArray.inValues()));
+            }
+            break;
+
+          case MOVE_EXCEPTION:
+            {
+              MoveException moveException = current.asMoveException();
+              new InstructionReplacer(code, current, iterator, affectedPhis)
+                  .replaceInstructionIfTypeChanged(
+                      moveException.getExceptionType(),
+                      (t, v) -> new MoveException(v, t, appView.options()));
+            }
+            break;
+
+          case NEW_ARRAY_EMPTY:
+            {
+              NewArrayEmpty newArrayEmpty = current.asNewArrayEmpty();
+              new InstructionReplacer(code, current, iterator, affectedPhis)
+                  .replaceInstructionIfTypeChanged(
+                      newArrayEmpty.type, (t, v) -> new NewArrayEmpty(v, newArrayEmpty.size(), t));
+            }
+            break;
+
+          case NEW_INSTANCE:
+            {
+              DexType type = current.asNewInstance().clazz;
+              new InstructionReplacer(code, current, iterator, affectedPhis)
+                  .replaceInstructionIfTypeChanged(type, NewInstance::new);
+            }
+            break;
+
+          default:
+            if (current.hasOutValue()) {
+              // For all other instructions, substitute any changed type.
+              TypeElement type = current.getOutType();
+              TypeElement substituted =
+                  type.fixupClassTypeReferences(graphLense::lookupType, appView);
+              if (substituted != type) {
+                current.outValue().setType(substituted);
+                affectedPhis.addAll(current.outValue().uniquePhiUsers());
+              }
+            }
+            break;
         }
       }
     }
@@ -685,7 +785,7 @@
         Instruction newInstruction = constructor.apply(newType, newOutValue);
         iterator.replaceCurrentInstruction(newInstruction);
         if (newOutValue != null) {
-          if (newOutValue.getType() != current.outValue().getType()) {
+          if (newOutValue.getType() != current.getOutType()) {
             affectedPhis.addAll(newOutValue.uniquePhiUsers());
           } else {
             assert current.hasInvariantOutType();
diff --git a/src/main/java/com/android/tools/r8/ir/conversion/PostMethodProcessor.java b/src/main/java/com/android/tools/r8/ir/conversion/PostMethodProcessor.java
index e742528..328365e 100644
--- a/src/main/java/com/android/tools/r8/ir/conversion/PostMethodProcessor.java
+++ b/src/main/java/com/android/tools/r8/ir/conversion/PostMethodProcessor.java
@@ -175,7 +175,7 @@
       OptimizationFeedback feedback) {
     // TODO(b/140766440): Make IRConverter#process receive a list of CodeOptimization to conduct.
     //   Then, we can share IRCode creation there.
-    Origin origin = appView.appInfo().originFor(method.method.holder);
+    Origin origin = appView.appInfo().originFor(method.holder());
     if (appView.options().skipIR) {
       feedback.markProcessed(method, ConstraintWithTarget.NEVER);
       return;
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 d7edc8a..58f7155 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
@@ -114,7 +114,7 @@
         }
       }
       for (Instruction instruction : block.getInstructions()) {
-        if (instruction.outValue() != null && !instruction.outValue().getType().isPreciseType()) {
+        if (instruction.outValue() != null && !instruction.getOutType().isPreciseType()) {
           impreciseValues.add(instruction.outValue());
         }
 
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 43db91d..49c8000 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
@@ -125,6 +125,11 @@
     }
   }
 
+  public static void registerAssumedLibraryTypes(InternalOptions options) {
+    // TODO(b/150693139): Remove the pre-registration once fixed.
+    BackportedMethods.registerSynthesizedCodeReferences(options.itemFactory);
+  }
+
   public void desugar(IRCode code) {
     if (!enabled) {
       return; // Nothing to do!
@@ -154,7 +159,7 @@
       if (provider.requiresGenerationOfCode()) {
         DexMethod newMethod = provider.provideMethod(appView);
         methodProviders.putIfAbsent(newMethod, provider);
-        holders.add(code.method.method.holder);
+        holders.add(code.method.holder());
       }
     }
     if (!affectedValues.isEmpty()) {
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 3d16d19..82c7c6a 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
@@ -4,6 +4,7 @@
 
 package com.android.tools.r8.ir.desugar;
 
+import com.android.tools.r8.errors.CompilationError;
 import com.android.tools.r8.graph.AppInfoWithClassHierarchy;
 import com.android.tools.r8.graph.AppView;
 import com.android.tools.r8.graph.DexAnnotationSet;
@@ -18,9 +19,9 @@
 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.graph.ResolutionResult.IncompatibleClassResult;
 import com.android.tools.r8.ir.synthetic.ExceptionThrowingSourceCode;
 import com.android.tools.r8.ir.synthetic.SynthesizedCode;
+import com.android.tools.r8.position.MethodPosition;
 import com.android.tools.r8.utils.MethodSignatureEquivalence;
 import com.google.common.base.Equivalence.Wrapper;
 import com.google.common.collect.ImmutableList;
@@ -74,14 +75,6 @@
       return signatures.size() == merged.size() ? this : new MethodSignatures(merged);
     }
 
-    MethodSignatures merge(List<MethodSignatures> others) {
-      MethodSignatures merged = this;
-      for (MethodSignatures other : others) {
-        merged = merged.merge(others);
-      }
-      return merged;
-    }
-
     boolean isEmpty() {
       return signatures.isEmpty();
     }
@@ -151,7 +144,7 @@
     }
   }
 
-  // Specialized context to disable reporting when traversing the library strucure.
+  // Specialized context to disable reporting when traversing the library structure.
   private static class LibraryReportingContext extends ReportingContext {
 
     static final LibraryReportingContext LIBRARY_CONTEXT = new LibraryReportingContext();
@@ -281,22 +274,36 @@
     return ClassInfo.create(superInfo, additionalForwards.build());
   }
 
-  // Resolves a method signature from the point of 'clazz', if it must target a default method
+  // Looks up a method signature from the point of 'clazz', if it can dispatch to a default method
   // the 'addForward' call-back is called with the target of the forward.
   private void resolveForwardForSignature(
       DexClass clazz, DexMethod method, BiConsumer<DexClass, DexEncodedMethod> addForward) {
-    ResolutionResult resolution = appView.appInfo().resolveMethod(clazz, method);
-    // If resolution fails, install a method throwing IncompatibleClassChangeError.
-    if (resolution.isFailedResolution()) {
-      if (resolution instanceof IncompatibleClassResult) {
+    // Resolve the default method at base type as the symbolic holder at call sites is not known.
+    // The dispatch target is then looked up from the possible "instance" class.
+    // Doing so can cause an invalid invoke to become valid (at runtime resolution at a subtype
+    // might have failed which is hidden by the insertion of the forward method). However, not doing
+    // so could cause valid dispatches to become invalid by resolving to private overrides.
+    DexClassAndMethod virtualDispatchTarget =
+        appView
+            .appInfo()
+            .resolveMethodOnInterface(method.holder, method)
+            .lookupVirtualDispatchTarget(clazz, appView.appInfo());
+    if (virtualDispatchTarget == null) {
+      // If no target is found due to multiple default method targets, preserve ICCE behavior.
+      ResolutionResult resolutionFromSubclass = appView.appInfo().resolveMethod(clazz, method);
+      if (resolutionFromSubclass.isIncompatibleClassChangeErrorResult()) {
         addICCEThrowingMethod(method, clazz);
+        return;
       }
+      assert resolutionFromSubclass.isFailedResolution()
+          || resolutionFromSubclass.getSingleTarget().isPrivateMethod();
       return;
     }
-    DexEncodedMethod target = resolution.getSingleTarget();
-    DexClass targetHolder = appView.definitionFor(target.method.holder);
+
+    DexEncodedMethod target = virtualDispatchTarget.getMethod();
+    DexClass targetHolder = virtualDispatchTarget.getHolder();
     // Don-t forward if the target is explicitly marked as 'dont-rewrite'
-    if (targetHolder == null || dontRewrite(targetHolder, target)) {
+    if (dontRewrite(targetHolder, target)) {
       return;
     }
 
@@ -330,7 +337,7 @@
 
   private boolean isRetargetMethod(DexLibraryClass holder, DexEncodedMethod method) {
     assert needsLibraryInfo();
-    assert holder.type == method.method.holder;
+    assert holder.type == method.holder();
     assert method.isNonPrivateVirtualMethod();
     if (method.isFinal()) {
       return false;
@@ -375,6 +382,16 @@
     if (!clazz.isProgramClass()) {
       return;
     }
+
+    DexEncodedMethod methodOnSelf = clazz.lookupMethod(target.method);
+    if (methodOnSelf != null) {
+      throw new CompilationError(
+          "Attempt to add forwarding method that conflicts with existing method.",
+          null,
+          clazz.getOrigin(),
+          new MethodPosition(methodOnSelf.method));
+    }
+
     DexMethod method = target.method;
     // 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.
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 2be1832..b5c3f3e 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
@@ -152,12 +152,12 @@
     MethodAccessFlags newAccessFlags = method.accessFlags.copy();
     newAccessFlags.setBridge();
     newAccessFlags.setSynthetic();
-    DexMethod newMethod = factory.createMethod(method.method.holder, newProto, method.method.name);
+    DexMethod newMethod = factory.createMethod(method.holder(), newProto, method.method.name);
     ForwardMethodSourceCode.Builder forwardSourceCodeBuilder =
         ForwardMethodSourceCode.builder(newMethod);
     forwardSourceCodeBuilder
         .setReceiver(clazz.type)
-        .setTargetReceiver(method.method.holder)
+        .setTargetReceiver(method.holder())
         .setTarget(method.method)
         .setInvokeType(Invoke.Type.VIRTUAL)
         .setCastResult();
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/D8NestBasedAccessDesugaring.java b/src/main/java/com/android/tools/r8/ir/desugar/D8NestBasedAccessDesugaring.java
index 7b53a35..738f4aa 100644
--- a/src/main/java/com/android/tools/r8/ir/desugar/D8NestBasedAccessDesugaring.java
+++ b/src/main/java/com/android/tools/r8/ir/desugar/D8NestBasedAccessDesugaring.java
@@ -49,7 +49,7 @@
 
   public void rewriteNestBasedAccesses(
       DexEncodedMethod encodedMethod, IRCode code, AppView<?> appView) {
-    DexClass currentClass = appView.definitionFor(encodedMethod.method.holder);
+    DexClass currentClass = appView.definitionFor(encodedMethod.holder());
     assert currentClass != null;
     if (!currentClass.isInANest()) {
       return;
@@ -120,7 +120,7 @@
 
   private void addDeferredBridges(Collection<DexEncodedMethod> bridges) {
     for (DexEncodedMethod bridge : bridges) {
-      DexClass holder = definitionFor(bridge.method.holder);
+      DexClass holder = definitionFor(bridge.holder());
       assert holder != null && holder.isProgramClass();
       holder.asProgramClass().addMethod(bridge);
     }
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 c81eedb..11180c4 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
@@ -106,7 +106,7 @@
 
   public void desugar(IRCode code) {
 
-    if (wrapperSynthesizor.hasSynthesized(code.method.method.holder)) {
+    if (wrapperSynthesizor.hasSynthesized(code.method.holder())) {
       return;
     }
 
@@ -140,7 +140,7 @@
     if (!shouldRegisterCallback(encodedMethod)) {
       return true;
     }
-    DexProgramClass holderClass = appView.definitionForProgramType(encodedMethod.method.holder);
+    DexProgramClass holderClass = appView.definitionForProgramType(encodedMethod.holder());
     DexMethod installedCallback =
         methodWithVivifiedTypeInSignature(encodedMethod.method, holderClass.type, appView);
     assert holderClass.lookupMethod(installedCallback) != null;
@@ -161,7 +161,7 @@
 
   public void registerCallbackIfRequired(DexEncodedMethod encodedMethod) {
     if (shouldRegisterCallback(encodedMethod)) {
-      DexClass dexClass = appView.definitionFor(encodedMethod.method.holder);
+      DexClass dexClass = appView.definitionFor(encodedMethod.holder());
       assert dexClass != null;
       registerCallback(dexClass, encodedMethod);
     }
@@ -197,7 +197,6 @@
     return overridesLibraryMethod(dexClass, method);
   }
 
-
   private boolean overridesLibraryMethod(DexClass theClass, DexMethod method) {
     // We look up everywhere to see if there is a supertype/interface implementing the method...
     WorkList<DexType> workList = WorkList.newIdentityWorkList();
@@ -253,7 +252,7 @@
   }
 
   private synchronized void addCallBackSignature(DexClass dexClass, DexEncodedMethod method) {
-    assert dexClass.type == method.method.holder;
+    assert dexClass.type == method.holder();
     if (callBackMethods.computeIfAbsent(dexClass, key -> new HashSet<>()).add(method)) {
       pendingCallBackMethods.computeIfAbsent(dexClass, key -> new ArrayList<>()).add(method);
     }
@@ -347,22 +346,22 @@
     appView.options().reporter.warning(new StringDiagnostic(sb.toString()));
   }
 
-  private void warnInvalidInvoke(DexType type, DexMethod invokedMethod, String debugString) {
+  public void reportInvalidInvoke(DexType type, DexMethod invokedMethod, String debugString) {
     DexType desugaredType = appView.rewritePrefix.rewrittenType(type, appView);
     appView
         .options()
         .reporter
-        .warning(
+        .info(
             new StringDiagnostic(
                 "Invoke to "
                     + invokedMethod.holder
                     + "#"
                     + invokedMethod.name
-                    + " may not work correctly at runtime ("
+                    + " may not work correctly at runtime (Cannot convert "
                     + debugString
-                    + " type "
+                    + "type "
                     + desugaredType
-                    + " is a desugared type)."));
+                    + ")."));
   }
 
   public static DexType vivifiedTypeFor(DexType type, AppView<?> appView) {
@@ -398,6 +397,7 @@
       InstructionListIterator iterator,
       ListIterator<BasicBlock> blockIterator) {
     DexMethod invokedMethod = invokeMethod.getInvokedMethod();
+    boolean invalidConversion = false;
     if (trackedAPIs != null) {
       trackedAPIs.add(invokedMethod);
     }
@@ -412,12 +412,13 @@
         // Return conversion added only if return value is used.
         if (invokeMethod.outValue() != null
             && invokeMethod.outValue().numberOfUsers() + invokeMethod.outValue().numberOfPhiUsers()
-            > 0) {
+                > 0) {
           returnConversion =
               createReturnConversionAndReplaceUses(code, invokeMethod, returnType, newReturnType);
         }
       } else {
-        warnInvalidInvoke(returnType, invokeMethod.getInvokedMethod(), "return");
+        reportInvalidInvoke(returnType, invokeMethod.getInvokedMethod(), "return ");
+        invalidConversion = true;
         newReturnType = returnType;
       }
     } else {
@@ -445,7 +446,8 @@
               createParameterConversion(code, argType, argVivifiedType, inValue));
           newInValues.add(parameterConversions.get(parameterConversions.size() - 1).outValue());
         } else {
-          warnInvalidInvoke(argType, invokeMethod.getInvokedMethod(), "parameter");
+          reportInvalidInvoke(argType, invokeMethod.getInvokedMethod(), "parameter ");
+          invalidConversion = true;
           newInValues.add(invokeMethod.inValues().get(i + receiverShift));
         }
       } else {
@@ -465,7 +467,8 @@
             invokeMethod.outValue(),
             newInValues);
     assert newDexMethod
-        == methodWithVivifiedTypeInSignature(invokedMethod, invokedMethod.holder, appView);
+            == methodWithVivifiedTypeInSignature(invokedMethod, invokedMethod.holder, appView)
+        || invalidConversion;
 
     // Insert and reschedule all instructions.
     iterator.previous();
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
index 3949767..0e52152 100644
--- a/src/main/java/com/android/tools/r8/ir/desugar/DesugaredLibraryRetargeter.java
+++ b/src/main/java/com/android/tools/r8/ir/desugar/DesugaredLibraryRetargeter.java
@@ -5,7 +5,24 @@
 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.graph.AppInfoWithClassHierarchy;
+import com.android.tools.r8.graph.AppView;
+import com.android.tools.r8.graph.ClassAccessFlags;
+import com.android.tools.r8.graph.DexAnnotationSet;
+import com.android.tools.r8.graph.DexApplication;
+import com.android.tools.r8.graph.DexClass;
+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.DexProto;
+import com.android.tools.r8.graph.DexString;
+import com.android.tools.r8.graph.DexType;
+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.code.IRCode;
 import com.android.tools.r8.ir.code.Instruction;
 import com.android.tools.r8.ir.code.InstructionListIterator;
@@ -118,7 +135,7 @@
             appView
                 .appInfo()
                 .withClassHierarchy()
-                .lookupSuperTarget(invoke.getInvokedMethod(), code.method.method.holder);
+                .lookupSuperTarget(invoke.getInvokedMethod(), code.method.holder());
         // Final methods can be rewritten as a normal invoke.
         if (dexEncodedMethod != null && !dexEncodedMethod.isFinal()) {
           DexMethod retargetMethod =
@@ -270,7 +287,7 @@
             && dexClass.isLibraryClass()
             && dexClass.type != appView.dexItemFactory().objectType) {
           for (DexType dexType : map.keySet()) {
-            if (inherit(dexClass.asLibraryClass(), dexType)) {
+            if (inherit(dexClass.asLibraryClass(), dexType, emulatedDispatchMethods)) {
               addedMethods.addAll(addInterfacesAndForwardingMethods(clazz, map.get(dexType)));
             }
           }
@@ -282,13 +299,21 @@
       converter.processMethodsConcurrently(addedMethods, executorService);
     }
 
-    private boolean inherit(DexLibraryClass clazz, DexType typeToInherit) {
+    private boolean inherit(DexLibraryClass clazz, DexType typeToInherit, Set<DexMethod> retarget) {
       DexLibraryClass current = clazz;
       while (current.type != appView.dexItemFactory().objectType) {
         if (current.type == typeToInherit) {
           return true;
         }
-        current = appView.definitionFor(current.superType).asLibraryClass();
+        DexClass dexClass = appView.definitionFor(current.superType);
+        if (dexClass == null || dexClass.isClasspathClass()) {
+          reportInvalidLibrarySupertype(current, retarget);
+          return false;
+        } else if (dexClass.isProgramClass()) {
+          // If dexClass is a program class, then it is already correctly desugared.
+          return false;
+        }
+        current = dexClass.asLibraryClass();
       }
       return false;
     }
@@ -413,6 +438,29 @@
     }
   }
 
+  private void reportInvalidLibrarySupertype(
+      DexLibraryClass libraryClass, Set<DexMethod> retarget) {
+    DexClass dexClass = appView.definitionFor(libraryClass.superType);
+    String message;
+    if (dexClass == null) {
+      message = "missing";
+    } else if (dexClass.isClasspathClass()) {
+      message = "a classpath class";
+    } else {
+      message = "INVALID";
+      assert false;
+    }
+    appView
+        .options()
+        .warningInvalidLibrarySuperclassForDesugar(
+            dexClass == null ? libraryClass.getOrigin() : dexClass.getOrigin(),
+            libraryClass.type,
+            libraryClass.superType,
+            message,
+            retarget,
+            appView);
+  }
+
   private DexType dispatchInterfaceTypeFor(DexMethod method) {
     return dispatchTypeFor(method, "dispatchInterface");
   }
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 30bc78e..e3c15ae 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
@@ -125,7 +125,7 @@
 
   boolean canGenerateWrapper(DexType type) {
     DexClass dexClass = appView.definitionFor(type);
-    if (dexClass == null) {
+    if (dexClass == null || dexClass.accessFlags.isFinal()) {
       return false;
     }
     return dexClass.isLibraryClass() || appView.options().isDesugaredLibraryCompilation();
@@ -169,18 +169,9 @@
   private DexClass getValidClassToWrap(DexType type) {
     DexClass dexClass = appView.definitionFor(type);
     // The dexClass should be a library class, so it cannot be null.
-    assert dexClass != null
-        && (dexClass.isLibraryClass() || appView.options().isDesugaredLibraryCompilation());
-    if (dexClass.accessFlags.isFinal()) {
-      throw appView
-          .options()
-          .reporter
-          .fatalError(
-              new StringDiagnostic(
-                  "Cannot generate a wrapper for final class "
-                      + dexClass.type
-                      + ". Add a custom conversion in the desugared library."));
-    }
+    assert dexClass != null;
+    assert dexClass.isLibraryClass() || appView.options().isDesugaredLibraryCompilation();
+    assert !dexClass.accessFlags.isFinal();
     return dexClass;
   }
 
@@ -223,7 +214,7 @@
     DexTypeList interfaces =
         isItf ? new DexTypeList(new DexType[] {wrappingType}) : DexTypeList.empty();
     return new DexProgramClass(
-        wrapperField.field.holder,
+        wrapperField.holder(),
         null,
         new SynthesizedOrigin("Desugared library API Converter", getClass()),
         ClassAccessFlags.fromSharedAccessFlags(
@@ -260,23 +251,24 @@
     //   return v3;
     Set<DexMethod> finalMethods = Sets.newIdentityHashSet();
     for (DexEncodedMethod dexEncodedMethod : dexMethods) {
-      DexClass holderClass = appView.definitionFor(dexEncodedMethod.method.holder);
+      DexClass holderClass = appView.definitionFor(dexEncodedMethod.holder());
       boolean isInterface;
       if (holderClass == null) {
-        assert appView.options().desugaredLibraryConfiguration.getEmulateLibraryInterface()
-            .containsValue(dexEncodedMethod.method.holder);
+        assert appView
+            .options()
+            .desugaredLibraryConfiguration
+            .getEmulateLibraryInterface()
+            .containsValue(dexEncodedMethod.holder());
         isInterface = true;
       } else {
         isInterface = holderClass.isInterface();
       }
       DexMethod methodToInstall =
           factory.createMethod(
-              wrapperField.field.holder,
-              dexEncodedMethod.method.proto,
-              dexEncodedMethod.method.name);
+              wrapperField.holder(), dexEncodedMethod.method.proto, dexEncodedMethod.method.name);
       CfCode cfCode;
       if (dexEncodedMethod.isFinal()) {
-        invalidWrappers.add(wrapperField.field.holder);
+        invalidWrappers.add(wrapperField.holder());
         finalMethods.add(dexEncodedMethod.method);
         continue;
       } else {
@@ -312,15 +304,15 @@
     //   return v3;
     Set<DexMethod> finalMethods = Sets.newIdentityHashSet();
     for (DexEncodedMethod dexEncodedMethod : dexMethods) {
-      DexClass holderClass = appView.definitionFor(dexEncodedMethod.method.holder);
+      DexClass holderClass = appView.definitionFor(dexEncodedMethod.holder());
       assert holderClass != null || appView.options().isDesugaredLibraryCompilation();
       boolean isInterface = holderClass == null || holderClass.isInterface();
       DexMethod methodToInstall =
           DesugaredLibraryAPIConverter.methodWithVivifiedTypeInSignature(
-              dexEncodedMethod.method, wrapperField.field.holder, appView);
+              dexEncodedMethod.method, wrapperField.holder(), appView);
       CfCode cfCode;
       if (dexEncodedMethod.isFinal()) {
-        invalidWrappers.add(wrapperField.field.holder);
+        invalidWrappers.add(wrapperField.holder());
         finalMethods.add(dexEncodedMethod.method);
         continue;
       } else {
@@ -342,7 +334,7 @@
     List<DexEncodedMethod> generatedMethods = new ArrayList<>();
     // Generate only abstract methods for library override detection.
     for (DexEncodedMethod dexEncodedMethod : dexMethods) {
-      DexClass holderClass = appView.definitionFor(dexEncodedMethod.method.holder);
+      DexClass holderClass = appView.definitionFor(dexEncodedMethod.holder());
       assert holderClass != null || appView.options().isDesugaredLibraryCompilation();
       if (!dexEncodedMethod.isFinal()) {
         DexMethod methodToInstall =
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 1a4c416..e9b9b7e 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
@@ -62,6 +62,7 @@
 import java.util.concurrent.ConcurrentMap;
 import java.util.concurrent.ExecutionException;
 import java.util.concurrent.ExecutorService;
+import java.util.function.BiConsumer;
 import java.util.function.Consumer;
 
 //
@@ -143,6 +144,37 @@
     initializeEmulatedInterfaceVariables();
   }
 
+  public static void checkForAssumedLibraryTypes(AppInfo appInfo, InternalOptions options) {
+    DesugaredLibraryConfiguration config = options.desugaredLibraryConfiguration;
+    BiConsumer<DexType, DexType> registerEntry = registerMapEntry(appInfo);
+    config.getEmulateLibraryInterface().forEach(registerEntry);
+    config.getCustomConversions().forEach(registerEntry);
+    config.getRetargetCoreLibMember().forEach((method, types) -> types.forEach(registerEntry));
+  }
+
+  private static BiConsumer<DexType, DexType> registerMapEntry(AppInfo appInfo) {
+    return (key, value) -> {
+      registerType(appInfo, key);
+      registerType(appInfo, value);
+    };
+  }
+
+  private static void registerType(AppInfo appInfo, DexType type) {
+    appInfo.dexItemFactory().registerTypeNeededForDesugaring(type);
+    DexClass clazz = appInfo.definitionFor(type);
+    if (clazz != null && clazz.isLibraryClass() && clazz.isInterface()) {
+      clazz.forEachMethod(
+          m -> {
+            if (m.isDefaultMethod()) {
+              appInfo.dexItemFactory().registerTypeNeededForDesugaring(m.method.proto.returnType);
+              for (DexType param : m.method.proto.parameters.values) {
+                appInfo.dexItemFactory().registerTypeNeededForDesugaring(param);
+              }
+            }
+          });
+    }
+  }
+
   private void initializeEmulatedInterfaceVariables() {
     Map<DexType, DexType> emulateLibraryInterface =
         options.desugaredLibraryConfiguration.getEmulateLibraryInterface();
@@ -238,7 +270,7 @@
                         invokeStatic.outValue(), invokeStatic.arguments()));
                 requiredDispatchClasses
                     .computeIfAbsent(clazz.asLibraryClass(), k -> Sets.newConcurrentHashSet())
-                    .add(appInfo.definitionFor(encodedMethod.method.holder).asProgramClass());
+                    .add(appInfo.definitionFor(encodedMethod.holder()).asProgramClass());
               }
             } else {
               instructions.replaceCurrentInstruction(
@@ -269,8 +301,7 @@
             // WARNING: This may result in incorrect code on older platforms!
             // Retarget call to an appropriate method of companion class.
             DexMethod amendedMethod =
-                amendDefaultMethod(
-                    appInfo.definitionFor(encodedMethod.method.holder), invokedMethod);
+                amendDefaultMethod(appInfo.definitionFor(encodedMethod.holder()), invokedMethod);
             instructions.replaceCurrentInstruction(
                 new InvokeStatic(defaultAsMethodOfCompanionClass(amendedMethod),
                     invokeSuper.outValue(), invokeSuper.arguments()));
@@ -284,9 +315,9 @@
               DexEncodedMethod dexEncodedMethod =
                   appView
                       .appInfo()
-                      .lookupSuperTarget(invokeSuper.getInvokedMethod(), code.method.method.holder);
+                      .lookupSuperTarget(invokeSuper.getInvokedMethod(), code.method.holder());
               if (dexEncodedMethod != null) {
-                DexClass dexClass = appView.definitionFor(dexEncodedMethod.method.holder);
+                DexClass dexClass = appView.definitionFor(dexEncodedMethod.holder());
                 if (dexClass != null && dexClass.isLibraryClass()) {
                   // Rewriting is required because the super invoke resolves into a missing
                   // method (method is on desugared library). Find out if it needs to be
@@ -418,8 +449,8 @@
       // interfaces.
       return null;
     }
-    if (!singleTarget.isAbstract() && isEmulatedInterface(singleTarget.method.holder)) {
-      return singleTarget.method.holder;
+    if (!singleTarget.isAbstract() && isEmulatedInterface(singleTarget.holder())) {
+      return singleTarget.holder();
     }
     return null;
   }
@@ -616,8 +647,8 @@
         }
         emulationMethods.add(
             DexEncodedMethod.toEmulateDispatchLibraryMethod(
-                method.method.holder,
-                emulateInterfaceLibraryMethod(method.method, method.method.holder, factory),
+                method.holder(),
+                emulateInterfaceLibraryMethod(method.method, method.holder(), factory),
                 companionMethod,
                 libraryMethod,
                 extraDispatchCases,
@@ -719,6 +750,10 @@
     return type.descriptor.toString().endsWith(COMPANION_CLASS_NAME_SUFFIX + ";");
   }
 
+  public static boolean isEmulatedLibraryClassType(DexType type) {
+    return type.descriptor.toString().endsWith(EMULATE_LIBRARY_CLASS_NAME_SUFFIX + ";");
+  }
+
   // Gets the interface class for a companion class `type`.
   private DexType getInterfaceClassType(DexType type) {
     return getInterfaceClassType(type, factory);
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/InterfaceProcessor.java b/src/main/java/com/android/tools/r8/ir/desugar/InterfaceProcessor.java
index 4409c30..ab5a9cd 100644
--- a/src/main/java/com/android/tools/r8/ir/desugar/InterfaceProcessor.java
+++ b/src/main/java/com/android/tools/r8/ir/desugar/InterfaceProcessor.java
@@ -320,7 +320,7 @@
     } else {
       assert code.isCfCode();
       for (CfInstruction insn : code.asCfCode().getInstructions()) {
-        if (insn instanceof CfInvoke && ((CfInvoke) insn).isInvokeSuper(method.method.holder)) {
+        if (insn instanceof CfInvoke && ((CfInvoke) insn).isInvokeSuper(method.holder())) {
           return false;
         }
       }
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/LambdaDescriptor.java b/src/main/java/com/android/tools/r8/ir/desugar/LambdaDescriptor.java
index d440b01..2ac43f0 100644
--- a/src/main/java/com/android/tools/r8/ir/desugar/LambdaDescriptor.java
+++ b/src/main/java/com/android/tools/r8/ir/desugar/LambdaDescriptor.java
@@ -96,7 +96,7 @@
         invocationContext == null ? null : lookupTargetMethod(appInfo, invocationContext);
     if (targetMethod != null) {
       targetAccessFlags = targetMethod.accessFlags.copy();
-      targetHolder = targetMethod.method.holder;
+      targetHolder = targetMethod.holder();
     } else {
       targetAccessFlags = null;
       targetHolder = null;
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 4ee35d6..d6d698e 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
@@ -135,7 +135,7 @@
    */
   public void desugarLambdas(DexEncodedMethod encodedMethod, IRCode code) {
     Set<Value> affectedValues = Sets.newIdentityHashSet();
-    DexType currentType = encodedMethod.method.holder;
+    DexType currentType = encodedMethod.holder();
     ListIterator<BasicBlock> blocks = code.listIterator();
     while (blocks.hasNext()) {
       BasicBlock block = blocks.next();
@@ -145,7 +145,7 @@
         if (instruction.isInvokeCustom()) {
           InvokeCustom invoke = instruction.asInvokeCustom();
           LambdaDescriptor descriptor =
-              inferLambdaDescriptor(invoke.getCallSite(), encodedMethod.method.holder);
+              inferLambdaDescriptor(invoke.getCallSite(), encodedMethod.holder());
           if (descriptor == LambdaDescriptor.MATCH_FAILED) {
             continue;
           }
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/NestBasedAccessDesugaring.java b/src/main/java/com/android/tools/r8/ir/desugar/NestBasedAccessDesugaring.java
index b78757d..f4e1f4a 100644
--- a/src/main/java/com/android/tools/r8/ir/desugar/NestBasedAccessDesugaring.java
+++ b/src/main/java/com/android/tools/r8/ir/desugar/NestBasedAccessDesugaring.java
@@ -51,7 +51,6 @@
   private static final String NEST_ACCESS_STATIC_PUT_FIELD_NAME_PREFIX =
       NEST_ACCESS_NAME_PREFIX + "sfput";
   public static final String NEST_CONSTRUCTOR_NAME = NEST_ACCESS_NAME_PREFIX + "Constructor";
-  private static final String FULL_NEST_CONTRUCTOR_NAME = "L" + NEST_CONSTRUCTOR_NAME + ";";
 
   protected final AppView<?> appView;
   // Following maps are there to avoid creating the bridges multiple times
@@ -161,7 +160,7 @@
 
   private DexProgramClass createNestAccessConstructor() {
     return new DexProgramClass(
-        appView.dexItemFactory().createType(FULL_NEST_CONTRUCTOR_NAME),
+        appView.dexItemFactory().nestConstructorType,
         null,
         new SynthesizedOrigin("Nest based access desugaring", getClass()),
         // Make the synthesized class public since shared in the whole program.
@@ -238,7 +237,7 @@
   }
 
   private DexMethod computeFieldBridge(DexEncodedField field, boolean isGet) {
-    DexType holderType = field.field.holder;
+    DexType holderType = field.holder();
     DexType fieldType = field.field.type;
     int bridgeParameterCount =
         BooleanUtils.intValue(!field.isStatic()) + BooleanUtils.intValue(!isGet);
@@ -259,10 +258,10 @@
   boolean invokeRequiresRewriting(DexEncodedMethod method, DexClass contextClass) {
     assert method != null;
     // Rewrite only when targeting other nest members private fields.
-    if (!method.accessFlags.isPrivate() || method.method.holder == contextClass.type) {
+    if (!method.accessFlags.isPrivate() || method.holder() == contextClass.type) {
       return false;
     }
-    DexClass methodHolder = definitionFor(method.method.holder);
+    DexClass methodHolder = definitionFor(method.holder());
     assert methodHolder != null; // from encodedMethod
     return methodHolder.getNestHost() == contextClass.getNestHost();
   }
@@ -270,10 +269,10 @@
   boolean fieldAccessRequiresRewriting(DexEncodedField field, DexClass contextClass) {
     assert field != null;
     // Rewrite only when targeting other nest members private fields.
-    if (!field.accessFlags.isPrivate() || field.field.holder == contextClass.type) {
+    if (!field.accessFlags.isPrivate() || field.holder() == contextClass.type) {
       return false;
     }
-    DexClass fieldHolder = definitionFor(field.field.holder);
+    DexClass fieldHolder = definitionFor(field.holder());
     assert fieldHolder != null; // from encodedField
     return fieldHolder.getNestHost() == contextClass.getNestHost();
   }
@@ -295,7 +294,7 @@
   }
 
   DexMethod ensureFieldAccessBridge(DexEncodedField field, boolean isGet) {
-    DexClass holder = definitionFor(field.field.holder);
+    DexClass holder = definitionFor(field.holder());
     assert holder != null;
     DexMethod bridgeMethod = computeFieldBridge(field, isGet);
     if (holderRequiresBridge(holder)) {
@@ -314,7 +313,7 @@
 
   DexMethod ensureInvokeBridge(DexEncodedMethod method) {
     // We add bridges only when targeting other nest members.
-    DexClass holder = definitionFor(method.method.holder);
+    DexClass holder = definitionFor(method.holder());
     assert holder != null;
     DexMethod bridgeMethod;
     if (method.isInstanceInitializer()) {
@@ -508,7 +507,7 @@
     }
 
     public DexType getHolder() {
-      return field.field.holder;
+      return field.holder();
     }
 
     public DexField getField() {
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/PrefixRewritingMapper.java b/src/main/java/com/android/tools/r8/ir/desugar/PrefixRewritingMapper.java
index 1c3b6a2..6f5c3f2 100644
--- a/src/main/java/com/android/tools/r8/ir/desugar/PrefixRewritingMapper.java
+++ b/src/main/java/com/android/tools/r8/ir/desugar/PrefixRewritingMapper.java
@@ -18,6 +18,7 @@
 import java.util.Map;
 import java.util.Set;
 import java.util.concurrent.ConcurrentHashMap;
+import java.util.function.Consumer;
 
 public abstract class PrefixRewritingMapper {
 
@@ -47,6 +48,8 @@
 
   public abstract boolean isRewriting();
 
+  public abstract void forAllRewrittenTypes(Consumer<DexType> consumer);
+
   public static class DesugarPrefixRewritingMapper extends PrefixRewritingMapper {
 
     private final Set<DexType> notRewritten = Sets.newConcurrentHashSet();
@@ -70,6 +73,11 @@
       return factory.createString("L" + DescriptorUtils.getBinaryNameFromJavaType(prefix));
     }
 
+    @Override
+    public void forAllRewrittenTypes(Consumer<DexType> consumer) {
+      rewritten.keySet().forEach(consumer);
+    }
+
     private void validatePrefixes(Map<String, String> initialPrefixes) {
       String[] prefixes = initialPrefixes.keySet().toArray(new String[0]);
       for (int i = 0; i < prefixes.length; i++) {
@@ -192,5 +200,8 @@
     public boolean isRewriting() {
       return false;
     }
+
+    @Override
+    public void forAllRewrittenTypes(Consumer<DexType> consumer) {}
   }
 }
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/R8NestBasedAccessDesugaring.java b/src/main/java/com/android/tools/r8/ir/desugar/R8NestBasedAccessDesugaring.java
index c4c9bd7..ca10fe6 100644
--- a/src/main/java/com/android/tools/r8/ir/desugar/R8NestBasedAccessDesugaring.java
+++ b/src/main/java/com/android/tools/r8/ir/desugar/R8NestBasedAccessDesugaring.java
@@ -56,7 +56,7 @@
   private <E> void addDeferredBridgesAndMapMethods(
       Map<E, DexEncodedMethod> bridges, BiConsumer<E, DexMethod> lensInserter) {
     for (Map.Entry<E, DexEncodedMethod> entry : bridges.entrySet()) {
-      DexClass holder = definitionFor(entry.getValue().method.holder);
+      DexClass holder = definitionFor(entry.getValue().holder());
       assert holder != null && holder.isProgramClass();
       holder.asProgramClass().addMethod(entry.getValue());
       lensInserter.accept(entry.getKey(), entry.getValue().method);
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/TwrCloseResourceRewriter.java b/src/main/java/com/android/tools/r8/ir/desugar/TwrCloseResourceRewriter.java
index 17250cf..e477fe3 100644
--- a/src/main/java/com/android/tools/r8/ir/desugar/TwrCloseResourceRewriter.java
+++ b/src/main/java/com/android/tools/r8/ir/desugar/TwrCloseResourceRewriter.java
@@ -99,7 +99,7 @@
           new InvokeStatic(twrCloseResourceMethod, null, invoke.inValues()));
 
       // Mark as a class referencing utility class.
-      referencingClasses.add(appInfo.definitionFor(code.method.method.holder).asProgramClass());
+      referencingClasses.add(appInfo.definitionFor(code.method.holder()).asProgramClass());
     }
   }
 
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/backports/BackportedMethods.java b/src/main/java/com/android/tools/r8/ir/desugar/backports/BackportedMethods.java
index 023ef1c..bc3c320 100644
--- a/src/main/java/com/android/tools/r8/ir/desugar/backports/BackportedMethods.java
+++ b/src/main/java/com/android/tools/r8/ir/desugar/backports/BackportedMethods.java
@@ -36,6 +36,7 @@
 import com.android.tools.r8.cf.code.CfThrow;
 import com.android.tools.r8.cf.code.CfTryCatch;
 import com.android.tools.r8.graph.CfCode;
+import com.android.tools.r8.graph.DexItemFactory;
 import com.android.tools.r8.graph.DexMethod;
 import com.android.tools.r8.ir.code.Cmp;
 import com.android.tools.r8.ir.code.If;
@@ -47,6 +48,59 @@
 
 public final class BackportedMethods {
 
+  public static void registerSynthesizedCodeReferences(DexItemFactory factory) {
+    factory.createSynthesizedType("Ljava/lang/ArithmeticException;");
+    factory.createSynthesizedType("Ljava/lang/AssertionError;");
+    factory.createSynthesizedType("Ljava/lang/Double;");
+    factory.createSynthesizedType("Ljava/lang/ExceptionInInitializerError;");
+    factory.createSynthesizedType("Ljava/lang/Float;");
+    factory.createSynthesizedType("Ljava/lang/IllegalAccessException;");
+    factory.createSynthesizedType("Ljava/lang/IllegalArgumentException;");
+    factory.createSynthesizedType("Ljava/lang/IndexOutOfBoundsException;");
+    factory.createSynthesizedType("Ljava/lang/Integer;");
+    factory.createSynthesizedType("Ljava/lang/Iterable;");
+    factory.createSynthesizedType("Ljava/lang/Long;");
+    factory.createSynthesizedType("Ljava/lang/Math;");
+    factory.createSynthesizedType("Ljava/lang/NoSuchMethodException;");
+    factory.createSynthesizedType("Ljava/lang/NullPointerException;");
+    factory.createSynthesizedType("Ljava/lang/NumberFormatException;");
+    factory.createSynthesizedType("Ljava/lang/Runnable;");
+    factory.createSynthesizedType("Ljava/lang/SecurityException;");
+    factory.createSynthesizedType("Ljava/lang/reflect/InvocationTargetException;");
+    factory.createSynthesizedType("Ljava/lang/reflect/Method;");
+    factory.createSynthesizedType("Ljava/util/AbstractMap$SimpleImmutableEntry;");
+    factory.createSynthesizedType("Ljava/util/ArrayList;");
+    factory.createSynthesizedType("Ljava/util/Arrays;");
+    factory.createSynthesizedType("Ljava/util/Collection;");
+    factory.createSynthesizedType("Ljava/util/Collections;");
+    factory.createSynthesizedType("Ljava/util/Comparator;");
+    factory.createSynthesizedType("Ljava/util/Enumeration;");
+    factory.createSynthesizedType("Ljava/util/HashMap;");
+    factory.createSynthesizedType("Ljava/util/HashSet;");
+    factory.createSynthesizedType("Ljava/util/Iterator;");
+    factory.createSynthesizedType("Ljava/util/List;");
+    factory.createSynthesizedType("Ljava/util/ListIterator;");
+    factory.createSynthesizedType("Ljava/util/Map$Entry;");
+    factory.createSynthesizedType("Ljava/util/Map;");
+    factory.createSynthesizedType("Ljava/util/Objects;");
+    factory.createSynthesizedType("Ljava/util/Optional;");
+    factory.createSynthesizedType("Ljava/util/OptionalDouble;");
+    factory.createSynthesizedType("Ljava/util/OptionalInt;");
+    factory.createSynthesizedType("Ljava/util/OptionalLong;");
+    factory.createSynthesizedType("Ljava/util/Set;");
+    factory.createSynthesizedType("Ljava/util/function/Consumer;");
+    factory.createSynthesizedType("Ljava/util/function/DoubleConsumer;");
+    factory.createSynthesizedType("Ljava/util/function/IntConsumer;");
+    factory.createSynthesizedType("Ljava/util/function/LongConsumer;");
+    factory.createSynthesizedType("Ljava/util/function/Supplier;");
+    factory.createSynthesizedType("Ljava/util/stream/DoubleStream;");
+    factory.createSynthesizedType("Ljava/util/stream/IntStream;");
+    factory.createSynthesizedType("Ljava/util/stream/LongStream;");
+    factory.createSynthesizedType("Ljava/util/stream/Stream;");
+    factory.createSynthesizedType("[Ljava/lang/Class;");
+    factory.createSynthesizedType("[Ljava/lang/Object;");
+  }
+
   public static CfCode BooleanMethods_compare(InternalOptions options, DexMethod method) {
     CfLabel label0 = new CfLabel();
     CfLabel label1 = new CfLabel();
@@ -235,7 +289,7 @@
             new CfInvoke(
                 184,
                 options.itemFactory.createMethod(
-                    options.itemFactory.createSynthesizedType("Ljava/lang/Math;"),
+                    options.itemFactory.createType("Ljava/lang/Math;"),
                     options.itemFactory.createProto(
                         options.itemFactory.intType,
                         options.itemFactory.intType,
@@ -397,15 +451,15 @@
                 false),
             new CfConstString(options.itemFactory.createString("close")),
             new CfConstNumber(0, ValueType.INT),
-            new CfNewArray(options.itemFactory.createSynthesizedType("[Ljava/lang/Class;")),
+            new CfNewArray(options.itemFactory.createType("[Ljava/lang/Class;")),
             new CfInvoke(
                 182,
                 options.itemFactory.createMethod(
                     options.itemFactory.classType,
                     options.itemFactory.createProto(
-                        options.itemFactory.createSynthesizedType("Ljava/lang/reflect/Method;"),
+                        options.itemFactory.createType("Ljava/lang/reflect/Method;"),
                         options.itemFactory.stringType,
-                        options.itemFactory.createSynthesizedType("[Ljava/lang/Class;")),
+                        options.itemFactory.createType("[Ljava/lang/Class;")),
                     options.itemFactory.createString("getMethod")),
                 false),
             new CfStore(ValueType.OBJECT, 2),
@@ -413,15 +467,15 @@
             new CfLoad(ValueType.OBJECT, 2),
             new CfLoad(ValueType.OBJECT, 1),
             new CfConstNumber(0, ValueType.INT),
-            new CfNewArray(options.itemFactory.createSynthesizedType("[Ljava/lang/Object;")),
+            new CfNewArray(options.itemFactory.createType("[Ljava/lang/Object;")),
             new CfInvoke(
                 182,
                 options.itemFactory.createMethod(
-                    options.itemFactory.createSynthesizedType("Ljava/lang/reflect/Method;"),
+                    options.itemFactory.createType("Ljava/lang/reflect/Method;"),
                     options.itemFactory.createProto(
                         options.itemFactory.objectType,
                         options.itemFactory.objectType,
-                        options.itemFactory.createSynthesizedType("[Ljava/lang/Object;")),
+                        options.itemFactory.createType("[Ljava/lang/Object;")),
                     options.itemFactory.createString("invoke")),
                 false),
             new CfStackInstruction(CfStackInstruction.Opcode.Pop),
@@ -430,7 +484,7 @@
             label5,
             new CfStore(ValueType.OBJECT, 2),
             label6,
-            new CfNew(options.itemFactory.createSynthesizedType("Ljava/lang/AssertionError;")),
+            new CfNew(options.itemFactory.createType("Ljava/lang/AssertionError;")),
             new CfStackInstruction(CfStackInstruction.Opcode.Dup),
             new CfNew(options.itemFactory.stringBuilderType),
             new CfStackInstruction(CfStackInstruction.Opcode.Dup),
@@ -477,7 +531,7 @@
             new CfInvoke(
                 183,
                 options.itemFactory.createMethod(
-                    options.itemFactory.createSynthesizedType("Ljava/lang/AssertionError;"),
+                    options.itemFactory.createType("Ljava/lang/AssertionError;"),
                     options.itemFactory.createProto(
                         options.itemFactory.voidType,
                         options.itemFactory.stringType,
@@ -488,7 +542,7 @@
             label7,
             new CfStore(ValueType.OBJECT, 2),
             label8,
-            new CfNew(options.itemFactory.createSynthesizedType("Ljava/lang/AssertionError;")),
+            new CfNew(options.itemFactory.createType("Ljava/lang/AssertionError;")),
             new CfStackInstruction(CfStackInstruction.Opcode.Dup),
             new CfNew(options.itemFactory.stringBuilderType),
             new CfStackInstruction(CfStackInstruction.Opcode.Dup),
@@ -535,7 +589,7 @@
             new CfInvoke(
                 183,
                 options.itemFactory.createMethod(
-                    options.itemFactory.createSynthesizedType("Ljava/lang/AssertionError;"),
+                    options.itemFactory.createType("Ljava/lang/AssertionError;"),
                     options.itemFactory.createProto(
                         options.itemFactory.voidType,
                         options.itemFactory.stringType,
@@ -550,8 +604,7 @@
             new CfInvoke(
                 182,
                 options.itemFactory.createMethod(
-                    options.itemFactory.createSynthesizedType(
-                        "Ljava/lang/reflect/InvocationTargetException;"),
+                    options.itemFactory.createType("Ljava/lang/reflect/InvocationTargetException;"),
                     options.itemFactory.createProto(options.itemFactory.throwableType),
                     options.itemFactory.createString("getCause")),
                 false),
@@ -577,40 +630,36 @@
                 label2,
                 label4,
                 ImmutableList.of(
-                    options.itemFactory.createSynthesizedType("Ljava/lang/NoSuchMethodException;")),
+                    options.itemFactory.createType("Ljava/lang/NoSuchMethodException;")),
+                ImmutableList.of(label5)),
+            new CfTryCatch(
+                label2,
+                label4,
+                ImmutableList.of(options.itemFactory.createType("Ljava/lang/SecurityException;")),
                 ImmutableList.of(label5)),
             new CfTryCatch(
                 label2,
                 label4,
                 ImmutableList.of(
-                    options.itemFactory.createSynthesizedType("Ljava/lang/SecurityException;")),
-                ImmutableList.of(label5)),
-            new CfTryCatch(
-                label2,
-                label4,
-                ImmutableList.of(
-                    options.itemFactory.createSynthesizedType(
-                        "Ljava/lang/IllegalAccessException;")),
+                    options.itemFactory.createType("Ljava/lang/IllegalAccessException;")),
                 ImmutableList.of(label7)),
             new CfTryCatch(
                 label2,
                 label4,
                 ImmutableList.of(
-                    options.itemFactory.createSynthesizedType(
-                        "Ljava/lang/IllegalArgumentException;")),
+                    options.itemFactory.createType("Ljava/lang/IllegalArgumentException;")),
                 ImmutableList.of(label7)),
             new CfTryCatch(
                 label2,
                 label4,
                 ImmutableList.of(
-                    options.itemFactory.createSynthesizedType(
-                        "Ljava/lang/ExceptionInInitializerError;")),
+                    options.itemFactory.createType("Ljava/lang/ExceptionInInitializerError;")),
                 ImmutableList.of(label7)),
             new CfTryCatch(
                 label2,
                 label4,
                 ImmutableList.of(
-                    options.itemFactory.createSynthesizedType(
+                    options.itemFactory.createType(
                         "Ljava/lang/reflect/InvocationTargetException;")),
                 ImmutableList.of(label9)),
             new CfTryCatch(
@@ -635,14 +684,14 @@
         6,
         ImmutableList.of(
             label0,
-            new CfNew(options.itemFactory.createSynthesizedType("Ljava/util/ArrayList;")),
+            new CfNew(options.itemFactory.createType("Ljava/util/ArrayList;")),
             new CfStackInstruction(CfStackInstruction.Opcode.Dup),
             new CfLoad(ValueType.OBJECT, 0),
             new CfArrayLength(),
             new CfInvoke(
                 183,
                 options.itemFactory.createMethod(
-                    options.itemFactory.createSynthesizedType("Ljava/util/ArrayList;"),
+                    options.itemFactory.createType("Ljava/util/ArrayList;"),
                     options.itemFactory.createProto(
                         options.itemFactory.voidType, options.itemFactory.intType),
                     options.itemFactory.createString("<init>")),
@@ -670,7 +719,7 @@
             new CfInvoke(
                 184,
                 options.itemFactory.createMethod(
-                    options.itemFactory.createSynthesizedType("Ljava/util/Objects;"),
+                    options.itemFactory.createType("Ljava/util/Objects;"),
                     options.itemFactory.createProto(
                         options.itemFactory.objectType, options.itemFactory.objectType),
                     options.itemFactory.createString("requireNonNull")),
@@ -678,7 +727,7 @@
             new CfInvoke(
                 182,
                 options.itemFactory.createMethod(
-                    options.itemFactory.createSynthesizedType("Ljava/util/ArrayList;"),
+                    options.itemFactory.createType("Ljava/util/ArrayList;"),
                     options.itemFactory.createProto(
                         options.itemFactory.booleanType, options.itemFactory.objectType),
                     options.itemFactory.createString("add")),
@@ -692,10 +741,10 @@
             new CfInvoke(
                 184,
                 options.itemFactory.createMethod(
-                    options.itemFactory.createSynthesizedType("Ljava/util/Collections;"),
+                    options.itemFactory.createType("Ljava/util/Collections;"),
                     options.itemFactory.createProto(
-                        options.itemFactory.createSynthesizedType("Ljava/util/List;"),
-                        options.itemFactory.createSynthesizedType("Ljava/util/List;")),
+                        options.itemFactory.createType("Ljava/util/List;"),
+                        options.itemFactory.createType("Ljava/util/List;")),
                     options.itemFactory.createString("unmodifiableList")),
                 false),
             new CfReturn(ValueType.OBJECT),
@@ -717,15 +766,14 @@
         ImmutableList.of(
             label0,
             new CfNew(
-                options.itemFactory.createSynthesizedType(
-                    "Ljava/util/AbstractMap$SimpleImmutableEntry;")),
+                options.itemFactory.createType("Ljava/util/AbstractMap$SimpleImmutableEntry;")),
             new CfStackInstruction(CfStackInstruction.Opcode.Dup),
             new CfLoad(ValueType.OBJECT, 0),
             label1,
             new CfInvoke(
                 184,
                 options.itemFactory.createMethod(
-                    options.itemFactory.createSynthesizedType("Ljava/util/Objects;"),
+                    options.itemFactory.createType("Ljava/util/Objects;"),
                     options.itemFactory.createProto(
                         options.itemFactory.objectType, options.itemFactory.objectType),
                     options.itemFactory.createString("requireNonNull")),
@@ -735,7 +783,7 @@
             new CfInvoke(
                 184,
                 options.itemFactory.createMethod(
-                    options.itemFactory.createSynthesizedType("Ljava/util/Objects;"),
+                    options.itemFactory.createType("Ljava/util/Objects;"),
                     options.itemFactory.createProto(
                         options.itemFactory.objectType, options.itemFactory.objectType),
                     options.itemFactory.createString("requireNonNull")),
@@ -743,8 +791,7 @@
             new CfInvoke(
                 183,
                 options.itemFactory.createMethod(
-                    options.itemFactory.createSynthesizedType(
-                        "Ljava/util/AbstractMap$SimpleImmutableEntry;"),
+                    options.itemFactory.createType("Ljava/util/AbstractMap$SimpleImmutableEntry;"),
                     options.itemFactory.createProto(
                         options.itemFactory.voidType,
                         options.itemFactory.objectType,
@@ -775,14 +822,14 @@
         8,
         ImmutableList.of(
             label0,
-            new CfNew(options.itemFactory.createSynthesizedType("Ljava/util/HashMap;")),
+            new CfNew(options.itemFactory.createType("Ljava/util/HashMap;")),
             new CfStackInstruction(CfStackInstruction.Opcode.Dup),
             new CfLoad(ValueType.OBJECT, 0),
             new CfArrayLength(),
             new CfInvoke(
                 183,
                 options.itemFactory.createMethod(
-                    options.itemFactory.createSynthesizedType("Ljava/util/HashMap;"),
+                    options.itemFactory.createType("Ljava/util/HashMap;"),
                     options.itemFactory.createProto(
                         options.itemFactory.voidType, options.itemFactory.intType),
                     options.itemFactory.createString("<init>")),
@@ -809,14 +856,14 @@
             new CfInvoke(
                 185,
                 options.itemFactory.createMethod(
-                    options.itemFactory.createSynthesizedType("Ljava/util/Map$Entry;"),
+                    options.itemFactory.createType("Ljava/util/Map$Entry;"),
                     options.itemFactory.createProto(options.itemFactory.objectType),
                     options.itemFactory.createString("getKey")),
                 true),
             new CfInvoke(
                 184,
                 options.itemFactory.createMethod(
-                    options.itemFactory.createSynthesizedType("Ljava/util/Objects;"),
+                    options.itemFactory.createType("Ljava/util/Objects;"),
                     options.itemFactory.createProto(
                         options.itemFactory.objectType, options.itemFactory.objectType),
                     options.itemFactory.createString("requireNonNull")),
@@ -827,14 +874,14 @@
             new CfInvoke(
                 185,
                 options.itemFactory.createMethod(
-                    options.itemFactory.createSynthesizedType("Ljava/util/Map$Entry;"),
+                    options.itemFactory.createType("Ljava/util/Map$Entry;"),
                     options.itemFactory.createProto(options.itemFactory.objectType),
                     options.itemFactory.createString("getValue")),
                 true),
             new CfInvoke(
                 184,
                 options.itemFactory.createMethod(
-                    options.itemFactory.createSynthesizedType("Ljava/util/Objects;"),
+                    options.itemFactory.createType("Ljava/util/Objects;"),
                     options.itemFactory.createProto(
                         options.itemFactory.objectType, options.itemFactory.objectType),
                     options.itemFactory.createString("requireNonNull")),
@@ -847,7 +894,7 @@
             new CfInvoke(
                 182,
                 options.itemFactory.createMethod(
-                    options.itemFactory.createSynthesizedType("Ljava/util/HashMap;"),
+                    options.itemFactory.createType("Ljava/util/HashMap;"),
                     options.itemFactory.createProto(
                         options.itemFactory.objectType,
                         options.itemFactory.objectType,
@@ -856,8 +903,7 @@
                 false),
             new CfIf(If.Type.EQ, ValueType.OBJECT, label7),
             label6,
-            new CfNew(
-                options.itemFactory.createSynthesizedType("Ljava/lang/IllegalArgumentException;")),
+            new CfNew(options.itemFactory.createType("Ljava/lang/IllegalArgumentException;")),
             new CfStackInstruction(CfStackInstruction.Opcode.Dup),
             new CfNew(options.itemFactory.stringBuilderType),
             new CfStackInstruction(CfStackInstruction.Opcode.Dup),
@@ -896,8 +942,7 @@
             new CfInvoke(
                 183,
                 options.itemFactory.createMethod(
-                    options.itemFactory.createSynthesizedType(
-                        "Ljava/lang/IllegalArgumentException;"),
+                    options.itemFactory.createType("Ljava/lang/IllegalArgumentException;"),
                     options.itemFactory.createProto(
                         options.itemFactory.voidType, options.itemFactory.stringType),
                     options.itemFactory.createString("<init>")),
@@ -911,10 +956,10 @@
             new CfInvoke(
                 184,
                 options.itemFactory.createMethod(
-                    options.itemFactory.createSynthesizedType("Ljava/util/Collections;"),
+                    options.itemFactory.createType("Ljava/util/Collections;"),
                     options.itemFactory.createProto(
-                        options.itemFactory.createSynthesizedType("Ljava/util/Map;"),
-                        options.itemFactory.createSynthesizedType("Ljava/util/Map;")),
+                        options.itemFactory.createType("Ljava/util/Map;"),
+                        options.itemFactory.createType("Ljava/util/Map;")),
                     options.itemFactory.createString("unmodifiableMap")),
                 false),
             new CfReturn(ValueType.OBJECT),
@@ -938,14 +983,14 @@
         6,
         ImmutableList.of(
             label0,
-            new CfNew(options.itemFactory.createSynthesizedType("Ljava/util/HashSet;")),
+            new CfNew(options.itemFactory.createType("Ljava/util/HashSet;")),
             new CfStackInstruction(CfStackInstruction.Opcode.Dup),
             new CfLoad(ValueType.OBJECT, 0),
             new CfArrayLength(),
             new CfInvoke(
                 183,
                 options.itemFactory.createMethod(
-                    options.itemFactory.createSynthesizedType("Ljava/util/HashSet;"),
+                    options.itemFactory.createType("Ljava/util/HashSet;"),
                     options.itemFactory.createProto(
                         options.itemFactory.voidType, options.itemFactory.intType),
                     options.itemFactory.createString("<init>")),
@@ -973,7 +1018,7 @@
             new CfInvoke(
                 184,
                 options.itemFactory.createMethod(
-                    options.itemFactory.createSynthesizedType("Ljava/util/Objects;"),
+                    options.itemFactory.createType("Ljava/util/Objects;"),
                     options.itemFactory.createProto(
                         options.itemFactory.objectType, options.itemFactory.objectType),
                     options.itemFactory.createString("requireNonNull")),
@@ -981,15 +1026,14 @@
             new CfInvoke(
                 182,
                 options.itemFactory.createMethod(
-                    options.itemFactory.createSynthesizedType("Ljava/util/HashSet;"),
+                    options.itemFactory.createType("Ljava/util/HashSet;"),
                     options.itemFactory.createProto(
                         options.itemFactory.booleanType, options.itemFactory.objectType),
                     options.itemFactory.createString("add")),
                 false),
             new CfIf(If.Type.NE, ValueType.INT, label5),
             label4,
-            new CfNew(
-                options.itemFactory.createSynthesizedType("Ljava/lang/IllegalArgumentException;")),
+            new CfNew(options.itemFactory.createType("Ljava/lang/IllegalArgumentException;")),
             new CfStackInstruction(CfStackInstruction.Opcode.Dup),
             new CfNew(options.itemFactory.stringBuilderType),
             new CfStackInstruction(CfStackInstruction.Opcode.Dup),
@@ -1028,8 +1072,7 @@
             new CfInvoke(
                 183,
                 options.itemFactory.createMethod(
-                    options.itemFactory.createSynthesizedType(
-                        "Ljava/lang/IllegalArgumentException;"),
+                    options.itemFactory.createType("Ljava/lang/IllegalArgumentException;"),
                     options.itemFactory.createProto(
                         options.itemFactory.voidType, options.itemFactory.stringType),
                     options.itemFactory.createString("<init>")),
@@ -1043,10 +1086,10 @@
             new CfInvoke(
                 184,
                 options.itemFactory.createMethod(
-                    options.itemFactory.createSynthesizedType("Ljava/util/Collections;"),
+                    options.itemFactory.createType("Ljava/util/Collections;"),
                     options.itemFactory.createProto(
-                        options.itemFactory.createSynthesizedType("Ljava/util/Set;"),
-                        options.itemFactory.createSynthesizedType("Ljava/util/Set;")),
+                        options.itemFactory.createType("Ljava/util/Set;"),
+                        options.itemFactory.createType("Ljava/util/Set;")),
                     options.itemFactory.createString("unmodifiableSet")),
                 false),
             new CfReturn(ValueType.OBJECT),
@@ -1069,20 +1112,20 @@
         4,
         ImmutableList.of(
             label0,
-            new CfNew(options.itemFactory.createSynthesizedType("Ljava/util/ArrayList;")),
+            new CfNew(options.itemFactory.createType("Ljava/util/ArrayList;")),
             new CfStackInstruction(CfStackInstruction.Opcode.Dup),
             new CfLoad(ValueType.OBJECT, 0),
             new CfInvoke(
                 185,
                 options.itemFactory.createMethod(
-                    options.itemFactory.createSynthesizedType("Ljava/util/Collection;"),
+                    options.itemFactory.createType("Ljava/util/Collection;"),
                     options.itemFactory.createProto(options.itemFactory.intType),
                     options.itemFactory.createString("size")),
                 true),
             new CfInvoke(
                 183,
                 options.itemFactory.createMethod(
-                    options.itemFactory.createSynthesizedType("Ljava/util/ArrayList;"),
+                    options.itemFactory.createType("Ljava/util/ArrayList;"),
                     options.itemFactory.createProto(
                         options.itemFactory.voidType, options.itemFactory.intType),
                     options.itemFactory.createString("<init>")),
@@ -1093,9 +1136,9 @@
             new CfInvoke(
                 185,
                 options.itemFactory.createMethod(
-                    options.itemFactory.createSynthesizedType("Ljava/util/Collection;"),
+                    options.itemFactory.createType("Ljava/util/Collection;"),
                     options.itemFactory.createProto(
-                        options.itemFactory.createSynthesizedType("Ljava/util/Iterator;")),
+                        options.itemFactory.createType("Ljava/util/Iterator;")),
                     options.itemFactory.createString("iterator")),
                 true),
             new CfStore(ValueType.OBJECT, 2),
@@ -1104,7 +1147,7 @@
             new CfInvoke(
                 185,
                 options.itemFactory.createMethod(
-                    options.itemFactory.createSynthesizedType("Ljava/util/Iterator;"),
+                    options.itemFactory.createType("Ljava/util/Iterator;"),
                     options.itemFactory.createProto(options.itemFactory.booleanType),
                     options.itemFactory.createString("hasNext")),
                 true),
@@ -1113,7 +1156,7 @@
             new CfInvoke(
                 185,
                 options.itemFactory.createMethod(
-                    options.itemFactory.createSynthesizedType("Ljava/util/Iterator;"),
+                    options.itemFactory.createType("Ljava/util/Iterator;"),
                     options.itemFactory.createProto(options.itemFactory.objectType),
                     options.itemFactory.createString("next")),
                 true),
@@ -1124,7 +1167,7 @@
             new CfInvoke(
                 184,
                 options.itemFactory.createMethod(
-                    options.itemFactory.createSynthesizedType("Ljava/util/Objects;"),
+                    options.itemFactory.createType("Ljava/util/Objects;"),
                     options.itemFactory.createProto(
                         options.itemFactory.objectType, options.itemFactory.objectType),
                     options.itemFactory.createString("requireNonNull")),
@@ -1132,7 +1175,7 @@
             new CfInvoke(
                 182,
                 options.itemFactory.createMethod(
-                    options.itemFactory.createSynthesizedType("Ljava/util/ArrayList;"),
+                    options.itemFactory.createType("Ljava/util/ArrayList;"),
                     options.itemFactory.createProto(
                         options.itemFactory.booleanType, options.itemFactory.objectType),
                     options.itemFactory.createString("add")),
@@ -1145,10 +1188,10 @@
             new CfInvoke(
                 184,
                 options.itemFactory.createMethod(
-                    options.itemFactory.createSynthesizedType("Ljava/util/Collections;"),
+                    options.itemFactory.createType("Ljava/util/Collections;"),
                     options.itemFactory.createProto(
-                        options.itemFactory.createSynthesizedType("Ljava/util/List;"),
-                        options.itemFactory.createSynthesizedType("Ljava/util/List;")),
+                        options.itemFactory.createType("Ljava/util/List;"),
+                        options.itemFactory.createType("Ljava/util/List;")),
                     options.itemFactory.createString("unmodifiableList")),
                 false),
             new CfReturn(ValueType.OBJECT),
@@ -1174,20 +1217,20 @@
         4,
         ImmutableList.of(
             label0,
-            new CfNew(options.itemFactory.createSynthesizedType("Ljava/util/HashMap;")),
+            new CfNew(options.itemFactory.createType("Ljava/util/HashMap;")),
             new CfStackInstruction(CfStackInstruction.Opcode.Dup),
             new CfLoad(ValueType.OBJECT, 0),
             new CfInvoke(
                 185,
                 options.itemFactory.createMethod(
-                    options.itemFactory.createSynthesizedType("Ljava/util/Map;"),
+                    options.itemFactory.createType("Ljava/util/Map;"),
                     options.itemFactory.createProto(options.itemFactory.intType),
                     options.itemFactory.createString("size")),
                 true),
             new CfInvoke(
                 183,
                 options.itemFactory.createMethod(
-                    options.itemFactory.createSynthesizedType("Ljava/util/HashMap;"),
+                    options.itemFactory.createType("Ljava/util/HashMap;"),
                     options.itemFactory.createProto(
                         options.itemFactory.voidType, options.itemFactory.intType),
                     options.itemFactory.createString("<init>")),
@@ -1198,17 +1241,17 @@
             new CfInvoke(
                 185,
                 options.itemFactory.createMethod(
-                    options.itemFactory.createSynthesizedType("Ljava/util/Map;"),
+                    options.itemFactory.createType("Ljava/util/Map;"),
                     options.itemFactory.createProto(
-                        options.itemFactory.createSynthesizedType("Ljava/util/Set;")),
+                        options.itemFactory.createType("Ljava/util/Set;")),
                     options.itemFactory.createString("entrySet")),
                 true),
             new CfInvoke(
                 185,
                 options.itemFactory.createMethod(
-                    options.itemFactory.createSynthesizedType("Ljava/util/Set;"),
+                    options.itemFactory.createType("Ljava/util/Set;"),
                     options.itemFactory.createProto(
-                        options.itemFactory.createSynthesizedType("Ljava/util/Iterator;")),
+                        options.itemFactory.createType("Ljava/util/Iterator;")),
                     options.itemFactory.createString("iterator")),
                 true),
             new CfStore(ValueType.OBJECT, 2),
@@ -1217,7 +1260,7 @@
             new CfInvoke(
                 185,
                 options.itemFactory.createMethod(
-                    options.itemFactory.createSynthesizedType("Ljava/util/Iterator;"),
+                    options.itemFactory.createType("Ljava/util/Iterator;"),
                     options.itemFactory.createProto(options.itemFactory.booleanType),
                     options.itemFactory.createString("hasNext")),
                 true),
@@ -1226,11 +1269,11 @@
             new CfInvoke(
                 185,
                 options.itemFactory.createMethod(
-                    options.itemFactory.createSynthesizedType("Ljava/util/Iterator;"),
+                    options.itemFactory.createType("Ljava/util/Iterator;"),
                     options.itemFactory.createProto(options.itemFactory.objectType),
                     options.itemFactory.createString("next")),
                 true),
-            new CfCheckCast(options.itemFactory.createSynthesizedType("Ljava/util/Map$Entry;")),
+            new CfCheckCast(options.itemFactory.createType("Ljava/util/Map$Entry;")),
             new CfStore(ValueType.OBJECT, 3),
             label3,
             new CfLoad(ValueType.OBJECT, 1),
@@ -1239,14 +1282,14 @@
             new CfInvoke(
                 185,
                 options.itemFactory.createMethod(
-                    options.itemFactory.createSynthesizedType("Ljava/util/Map$Entry;"),
+                    options.itemFactory.createType("Ljava/util/Map$Entry;"),
                     options.itemFactory.createProto(options.itemFactory.objectType),
                     options.itemFactory.createString("getKey")),
                 true),
             new CfInvoke(
                 184,
                 options.itemFactory.createMethod(
-                    options.itemFactory.createSynthesizedType("Ljava/util/Objects;"),
+                    options.itemFactory.createType("Ljava/util/Objects;"),
                     options.itemFactory.createProto(
                         options.itemFactory.objectType, options.itemFactory.objectType),
                     options.itemFactory.createString("requireNonNull")),
@@ -1256,14 +1299,14 @@
             new CfInvoke(
                 185,
                 options.itemFactory.createMethod(
-                    options.itemFactory.createSynthesizedType("Ljava/util/Map$Entry;"),
+                    options.itemFactory.createType("Ljava/util/Map$Entry;"),
                     options.itemFactory.createProto(options.itemFactory.objectType),
                     options.itemFactory.createString("getValue")),
                 true),
             new CfInvoke(
                 184,
                 options.itemFactory.createMethod(
-                    options.itemFactory.createSynthesizedType("Ljava/util/Objects;"),
+                    options.itemFactory.createType("Ljava/util/Objects;"),
                     options.itemFactory.createProto(
                         options.itemFactory.objectType, options.itemFactory.objectType),
                     options.itemFactory.createString("requireNonNull")),
@@ -1272,7 +1315,7 @@
             new CfInvoke(
                 182,
                 options.itemFactory.createMethod(
-                    options.itemFactory.createSynthesizedType("Ljava/util/HashMap;"),
+                    options.itemFactory.createType("Ljava/util/HashMap;"),
                     options.itemFactory.createProto(
                         options.itemFactory.objectType,
                         options.itemFactory.objectType,
@@ -1287,10 +1330,10 @@
             new CfInvoke(
                 184,
                 options.itemFactory.createMethod(
-                    options.itemFactory.createSynthesizedType("Ljava/util/Collections;"),
+                    options.itemFactory.createType("Ljava/util/Collections;"),
                     options.itemFactory.createProto(
-                        options.itemFactory.createSynthesizedType("Ljava/util/Map;"),
-                        options.itemFactory.createSynthesizedType("Ljava/util/Map;")),
+                        options.itemFactory.createType("Ljava/util/Map;"),
+                        options.itemFactory.createType("Ljava/util/Map;")),
                     options.itemFactory.createString("unmodifiableMap")),
                 false),
             new CfReturn(ValueType.OBJECT),
@@ -1313,20 +1356,20 @@
         4,
         ImmutableList.of(
             label0,
-            new CfNew(options.itemFactory.createSynthesizedType("Ljava/util/HashSet;")),
+            new CfNew(options.itemFactory.createType("Ljava/util/HashSet;")),
             new CfStackInstruction(CfStackInstruction.Opcode.Dup),
             new CfLoad(ValueType.OBJECT, 0),
             new CfInvoke(
                 185,
                 options.itemFactory.createMethod(
-                    options.itemFactory.createSynthesizedType("Ljava/util/Collection;"),
+                    options.itemFactory.createType("Ljava/util/Collection;"),
                     options.itemFactory.createProto(options.itemFactory.intType),
                     options.itemFactory.createString("size")),
                 true),
             new CfInvoke(
                 183,
                 options.itemFactory.createMethod(
-                    options.itemFactory.createSynthesizedType("Ljava/util/HashSet;"),
+                    options.itemFactory.createType("Ljava/util/HashSet;"),
                     options.itemFactory.createProto(
                         options.itemFactory.voidType, options.itemFactory.intType),
                     options.itemFactory.createString("<init>")),
@@ -1337,9 +1380,9 @@
             new CfInvoke(
                 185,
                 options.itemFactory.createMethod(
-                    options.itemFactory.createSynthesizedType("Ljava/util/Collection;"),
+                    options.itemFactory.createType("Ljava/util/Collection;"),
                     options.itemFactory.createProto(
-                        options.itemFactory.createSynthesizedType("Ljava/util/Iterator;")),
+                        options.itemFactory.createType("Ljava/util/Iterator;")),
                     options.itemFactory.createString("iterator")),
                 true),
             new CfStore(ValueType.OBJECT, 2),
@@ -1348,7 +1391,7 @@
             new CfInvoke(
                 185,
                 options.itemFactory.createMethod(
-                    options.itemFactory.createSynthesizedType("Ljava/util/Iterator;"),
+                    options.itemFactory.createType("Ljava/util/Iterator;"),
                     options.itemFactory.createProto(options.itemFactory.booleanType),
                     options.itemFactory.createString("hasNext")),
                 true),
@@ -1357,7 +1400,7 @@
             new CfInvoke(
                 185,
                 options.itemFactory.createMethod(
-                    options.itemFactory.createSynthesizedType("Ljava/util/Iterator;"),
+                    options.itemFactory.createType("Ljava/util/Iterator;"),
                     options.itemFactory.createProto(options.itemFactory.objectType),
                     options.itemFactory.createString("next")),
                 true),
@@ -1368,7 +1411,7 @@
             new CfInvoke(
                 184,
                 options.itemFactory.createMethod(
-                    options.itemFactory.createSynthesizedType("Ljava/util/Objects;"),
+                    options.itemFactory.createType("Ljava/util/Objects;"),
                     options.itemFactory.createProto(
                         options.itemFactory.objectType, options.itemFactory.objectType),
                     options.itemFactory.createString("requireNonNull")),
@@ -1376,7 +1419,7 @@
             new CfInvoke(
                 182,
                 options.itemFactory.createMethod(
-                    options.itemFactory.createSynthesizedType("Ljava/util/HashSet;"),
+                    options.itemFactory.createType("Ljava/util/HashSet;"),
                     options.itemFactory.createProto(
                         options.itemFactory.booleanType, options.itemFactory.objectType),
                     options.itemFactory.createString("add")),
@@ -1389,10 +1432,10 @@
             new CfInvoke(
                 184,
                 options.itemFactory.createMethod(
-                    options.itemFactory.createSynthesizedType("Ljava/util/Collections;"),
+                    options.itemFactory.createType("Ljava/util/Collections;"),
                     options.itemFactory.createProto(
-                        options.itemFactory.createSynthesizedType("Ljava/util/Set;"),
-                        options.itemFactory.createSynthesizedType("Ljava/util/Set;")),
+                        options.itemFactory.createType("Ljava/util/Set;"),
+                        options.itemFactory.createType("Ljava/util/Set;")),
                     options.itemFactory.createString("unmodifiableSet")),
                 false),
             new CfReturn(ValueType.OBJECT),
@@ -1413,18 +1456,18 @@
             new CfInvoke(
                 184,
                 options.itemFactory.createMethod(
-                    options.itemFactory.createSynthesizedType("Ljava/util/Collections;"),
+                    options.itemFactory.createType("Ljava/util/Collections;"),
                     options.itemFactory.createProto(
-                        options.itemFactory.createSynthesizedType("Ljava/util/List;")),
+                        options.itemFactory.createType("Ljava/util/List;")),
                     options.itemFactory.createString("emptyList")),
                 false),
             new CfInvoke(
                 184,
                 options.itemFactory.createMethod(
-                    options.itemFactory.createSynthesizedType("Ljava/util/Collections;"),
+                    options.itemFactory.createType("Ljava/util/Collections;"),
                     options.itemFactory.createProto(
-                        options.itemFactory.createSynthesizedType("Ljava/util/Enumeration;"),
-                        options.itemFactory.createSynthesizedType("Ljava/util/Collection;")),
+                        options.itemFactory.createType("Ljava/util/Enumeration;"),
+                        options.itemFactory.createType("Ljava/util/Collection;")),
                     options.itemFactory.createString("enumeration")),
                 false),
             new CfReturn(ValueType.OBJECT)),
@@ -1443,17 +1486,17 @@
             new CfInvoke(
                 184,
                 options.itemFactory.createMethod(
-                    options.itemFactory.createSynthesizedType("Ljava/util/Collections;"),
+                    options.itemFactory.createType("Ljava/util/Collections;"),
                     options.itemFactory.createProto(
-                        options.itemFactory.createSynthesizedType("Ljava/util/List;")),
+                        options.itemFactory.createType("Ljava/util/List;")),
                     options.itemFactory.createString("emptyList")),
                 false),
             new CfInvoke(
                 185,
                 options.itemFactory.createMethod(
-                    options.itemFactory.createSynthesizedType("Ljava/util/List;"),
+                    options.itemFactory.createType("Ljava/util/List;"),
                     options.itemFactory.createProto(
-                        options.itemFactory.createSynthesizedType("Ljava/util/Iterator;")),
+                        options.itemFactory.createType("Ljava/util/Iterator;")),
                     options.itemFactory.createString("iterator")),
                 true),
             new CfReturn(ValueType.OBJECT)),
@@ -1473,17 +1516,17 @@
             new CfInvoke(
                 184,
                 options.itemFactory.createMethod(
-                    options.itemFactory.createSynthesizedType("Ljava/util/Collections;"),
+                    options.itemFactory.createType("Ljava/util/Collections;"),
                     options.itemFactory.createProto(
-                        options.itemFactory.createSynthesizedType("Ljava/util/List;")),
+                        options.itemFactory.createType("Ljava/util/List;")),
                     options.itemFactory.createString("emptyList")),
                 false),
             new CfInvoke(
                 185,
                 options.itemFactory.createMethod(
-                    options.itemFactory.createSynthesizedType("Ljava/util/List;"),
+                    options.itemFactory.createType("Ljava/util/List;"),
                     options.itemFactory.createProto(
-                        options.itemFactory.createSynthesizedType("Ljava/util/ListIterator;")),
+                        options.itemFactory.createType("Ljava/util/ListIterator;")),
                     options.itemFactory.createString("listIterator")),
                 true),
             new CfReturn(ValueType.OBJECT)),
@@ -1505,7 +1548,7 @@
             new CfInvoke(
                 184,
                 options.itemFactory.createMethod(
-                    options.itemFactory.createSynthesizedType("Ljava/lang/Double;"),
+                    options.itemFactory.createType("Ljava/lang/Double;"),
                     options.itemFactory.createProto(
                         options.itemFactory.longType, options.itemFactory.doubleType),
                     options.itemFactory.createString("doubleToLongBits")),
@@ -1539,7 +1582,7 @@
             new CfInvoke(
                 184,
                 options.itemFactory.createMethod(
-                    options.itemFactory.createSynthesizedType("Ljava/lang/Double;"),
+                    options.itemFactory.createType("Ljava/lang/Double;"),
                     options.itemFactory.createProto(
                         options.itemFactory.booleanType, options.itemFactory.doubleType),
                     options.itemFactory.createString("isInfinite")),
@@ -1549,7 +1592,7 @@
             new CfInvoke(
                 184,
                 options.itemFactory.createMethod(
-                    options.itemFactory.createSynthesizedType("Ljava/lang/Double;"),
+                    options.itemFactory.createType("Ljava/lang/Double;"),
                     options.itemFactory.createProto(
                         options.itemFactory.booleanType, options.itemFactory.doubleType),
                     options.itemFactory.createString("isNaN")),
@@ -1581,7 +1624,7 @@
             new CfInvoke(
                 184,
                 options.itemFactory.createMethod(
-                    options.itemFactory.createSynthesizedType("Ljava/lang/Float;"),
+                    options.itemFactory.createType("Ljava/lang/Float;"),
                     options.itemFactory.createProto(
                         options.itemFactory.booleanType, options.itemFactory.floatType),
                     options.itemFactory.createString("isInfinite")),
@@ -1591,7 +1634,7 @@
             new CfInvoke(
                 184,
                 options.itemFactory.createMethod(
-                    options.itemFactory.createSynthesizedType("Ljava/lang/Float;"),
+                    options.itemFactory.createType("Ljava/lang/Float;"),
                     options.itemFactory.createProto(
                         options.itemFactory.booleanType, options.itemFactory.floatType),
                     options.itemFactory.createString("isNaN")),
@@ -1666,7 +1709,7 @@
             new CfInvoke(
                 184,
                 options.itemFactory.createMethod(
-                    options.itemFactory.createSynthesizedType("Ljava/lang/Integer;"),
+                    options.itemFactory.createType("Ljava/lang/Integer;"),
                     options.itemFactory.createProto(
                         options.itemFactory.intType,
                         options.itemFactory.intType,
@@ -1726,7 +1769,7 @@
             new CfInvoke(
                 184,
                 options.itemFactory.createMethod(
-                    options.itemFactory.createSynthesizedType("Ljava/lang/Integer;"),
+                    options.itemFactory.createType("Ljava/lang/Integer;"),
                     options.itemFactory.createProto(
                         options.itemFactory.intType,
                         options.itemFactory.stringType,
@@ -1794,7 +1837,7 @@
             new CfInvoke(
                 184,
                 options.itemFactory.createMethod(
-                    options.itemFactory.createSynthesizedType("Ljava/lang/Long;"),
+                    options.itemFactory.createType("Ljava/lang/Long;"),
                     options.itemFactory.createProto(
                         options.itemFactory.longType,
                         options.itemFactory.stringType,
@@ -1810,8 +1853,7 @@
             new CfCmp(Cmp.Bias.NONE, NumericType.LONG),
             new CfIf(If.Type.EQ, ValueType.INT, label5),
             label4,
-            new CfNew(
-                options.itemFactory.createSynthesizedType("Ljava/lang/NumberFormatException;")),
+            new CfNew(options.itemFactory.createType("Ljava/lang/NumberFormatException;")),
             new CfStackInstruction(CfStackInstruction.Opcode.Dup),
             new CfNew(options.itemFactory.stringBuilderType),
             new CfStackInstruction(CfStackInstruction.Opcode.Dup),
@@ -1878,7 +1920,7 @@
             new CfInvoke(
                 183,
                 options.itemFactory.createMethod(
-                    options.itemFactory.createSynthesizedType("Ljava/lang/NumberFormatException;"),
+                    options.itemFactory.createType("Ljava/lang/NumberFormatException;"),
                     options.itemFactory.createProto(
                         options.itemFactory.voidType, options.itemFactory.stringType),
                     options.itemFactory.createString("<init>")),
@@ -1959,7 +2001,7 @@
             new CfInvoke(
                 184,
                 options.itemFactory.createMethod(
-                    options.itemFactory.createSynthesizedType("Ljava/lang/Integer;"),
+                    options.itemFactory.createType("Ljava/lang/Integer;"),
                     options.itemFactory.createProto(
                         options.itemFactory.stringType,
                         options.itemFactory.intType,
@@ -1994,7 +2036,7 @@
             new CfInvoke(
                 184,
                 options.itemFactory.createMethod(
-                    options.itemFactory.createSynthesizedType("Ljava/lang/Long;"),
+                    options.itemFactory.createType("Ljava/lang/Long;"),
                     options.itemFactory.createProto(
                         options.itemFactory.stringType,
                         options.itemFactory.longType,
@@ -2033,7 +2075,7 @@
             new CfInvoke(
                 184,
                 options.itemFactory.createMethod(
-                    options.itemFactory.createSynthesizedType("Ljava/lang/Long;"),
+                    options.itemFactory.createType("Ljava/lang/Long;"),
                     options.itemFactory.createProto(
                         options.itemFactory.intType,
                         options.itemFactory.longType,
@@ -2184,7 +2226,7 @@
             new CfInvoke(
                 184,
                 options.itemFactory.createMethod(
-                    options.itemFactory.createSynthesizedType("Ljava/lang/Long;"),
+                    options.itemFactory.createType("Ljava/lang/Long;"),
                     options.itemFactory.createProto(
                         options.itemFactory.longType,
                         options.itemFactory.stringType,
@@ -2240,14 +2282,13 @@
             new CfLoad(ValueType.INT, 2),
             new CfIf(If.Type.NE, ValueType.INT, label3),
             label2,
-            new CfNew(
-                options.itemFactory.createSynthesizedType("Ljava/lang/NumberFormatException;")),
+            new CfNew(options.itemFactory.createType("Ljava/lang/NumberFormatException;")),
             new CfStackInstruction(CfStackInstruction.Opcode.Dup),
             new CfConstString(options.itemFactory.createString("empty string")),
             new CfInvoke(
                 183,
                 options.itemFactory.createMethod(
-                    options.itemFactory.createSynthesizedType("Ljava/lang/NumberFormatException;"),
+                    options.itemFactory.createType("Ljava/lang/NumberFormatException;"),
                     options.itemFactory.createProto(
                         options.itemFactory.voidType, options.itemFactory.stringType),
                     options.itemFactory.createString("<init>")),
@@ -2261,8 +2302,7 @@
             new CfConstNumber(36, ValueType.INT),
             new CfIfCmp(If.Type.LE, ValueType.INT, label5),
             label4,
-            new CfNew(
-                options.itemFactory.createSynthesizedType("Ljava/lang/NumberFormatException;")),
+            new CfNew(options.itemFactory.createType("Ljava/lang/NumberFormatException;")),
             new CfStackInstruction(CfStackInstruction.Opcode.Dup),
             new CfConstString(options.itemFactory.createString("illegal radix: ")),
             new CfLoad(ValueType.INT, 1),
@@ -2285,7 +2325,7 @@
             new CfInvoke(
                 183,
                 options.itemFactory.createMethod(
-                    options.itemFactory.createSynthesizedType("Ljava/lang/NumberFormatException;"),
+                    options.itemFactory.createType("Ljava/lang/NumberFormatException;"),
                     options.itemFactory.createProto(
                         options.itemFactory.voidType, options.itemFactory.stringType),
                     options.itemFactory.createString("<init>")),
@@ -2298,7 +2338,7 @@
             new CfInvoke(
                 184,
                 options.itemFactory.createMethod(
-                    options.itemFactory.createSynthesizedType("Ljava/lang/Long;"),
+                    options.itemFactory.createType("Ljava/lang/Long;"),
                     options.itemFactory.createProto(
                         options.itemFactory.longType,
                         options.itemFactory.longType,
@@ -2366,14 +2406,13 @@
             new CfConstNumber(-1, ValueType.INT),
             new CfIfCmp(If.Type.NE, ValueType.INT, label15),
             label14,
-            new CfNew(
-                options.itemFactory.createSynthesizedType("Ljava/lang/NumberFormatException;")),
+            new CfNew(options.itemFactory.createType("Ljava/lang/NumberFormatException;")),
             new CfStackInstruction(CfStackInstruction.Opcode.Dup),
             new CfLoad(ValueType.OBJECT, 0),
             new CfInvoke(
                 183,
                 options.itemFactory.createMethod(
-                    options.itemFactory.createSynthesizedType("Ljava/lang/NumberFormatException;"),
+                    options.itemFactory.createType("Ljava/lang/NumberFormatException;"),
                     options.itemFactory.createProto(
                         options.itemFactory.voidType, options.itemFactory.stringType),
                     options.itemFactory.createString("<init>")),
@@ -2400,7 +2439,7 @@
             new CfInvoke(
                 184,
                 options.itemFactory.createMethod(
-                    options.itemFactory.createSynthesizedType("Ljava/lang/Long;"),
+                    options.itemFactory.createType("Ljava/lang/Long;"),
                     options.itemFactory.createProto(
                         options.itemFactory.longType,
                         options.itemFactory.longType,
@@ -2410,8 +2449,7 @@
             new CfNumberConversion(NumericType.LONG, NumericType.INT),
             new CfIfCmp(If.Type.LE, ValueType.INT, label18),
             label17,
-            new CfNew(
-                options.itemFactory.createSynthesizedType("Ljava/lang/NumberFormatException;")),
+            new CfNew(options.itemFactory.createType("Ljava/lang/NumberFormatException;")),
             new CfStackInstruction(CfStackInstruction.Opcode.Dup),
             new CfConstString(options.itemFactory.createString("Too large for unsigned long: ")),
             new CfLoad(ValueType.OBJECT, 0),
@@ -2426,7 +2464,7 @@
             new CfInvoke(
                 183,
                 options.itemFactory.createMethod(
-                    options.itemFactory.createSynthesizedType("Ljava/lang/NumberFormatException;"),
+                    options.itemFactory.createType("Ljava/lang/NumberFormatException;"),
                     options.itemFactory.createProto(
                         options.itemFactory.voidType, options.itemFactory.stringType),
                     options.itemFactory.createString("<init>")),
@@ -2570,7 +2608,7 @@
             new CfInvoke(
                 184,
                 options.itemFactory.createMethod(
-                    options.itemFactory.createSynthesizedType("Ljava/lang/Long;"),
+                    options.itemFactory.createType("Ljava/lang/Long;"),
                     options.itemFactory.createProto(
                         options.itemFactory.stringType,
                         options.itemFactory.longType,
@@ -2636,7 +2674,7 @@
             new CfInvoke(
                 184,
                 options.itemFactory.createMethod(
-                    options.itemFactory.createSynthesizedType("Ljava/lang/Long;"),
+                    options.itemFactory.createType("Ljava/lang/Long;"),
                     options.itemFactory.createProto(
                         options.itemFactory.stringType,
                         options.itemFactory.longType,
@@ -2674,7 +2712,7 @@
             new CfInvoke(
                 184,
                 options.itemFactory.createMethod(
-                    options.itemFactory.createSynthesizedType("Ljava/lang/Integer;"),
+                    options.itemFactory.createType("Ljava/lang/Integer;"),
                     options.itemFactory.createProto(
                         options.itemFactory.intType, options.itemFactory.intType),
                     options.itemFactory.createString("numberOfTrailingZeros")),
@@ -2741,7 +2779,7 @@
             new CfInvoke(
                 184,
                 options.itemFactory.createMethod(
-                    options.itemFactory.createSynthesizedType("Ljava/lang/Long;"),
+                    options.itemFactory.createType("Ljava/lang/Long;"),
                     options.itemFactory.createProto(
                         options.itemFactory.longType,
                         options.itemFactory.longType,
@@ -2870,12 +2908,12 @@
             new CfLoad(ValueType.INT, 4),
             new CfReturn(ValueType.INT),
             label4,
-            new CfNew(options.itemFactory.createSynthesizedType("Ljava/lang/ArithmeticException;")),
+            new CfNew(options.itemFactory.createType("Ljava/lang/ArithmeticException;")),
             new CfStackInstruction(CfStackInstruction.Opcode.Dup),
             new CfInvoke(
                 183,
                 options.itemFactory.createMethod(
-                    options.itemFactory.createSynthesizedType("Ljava/lang/ArithmeticException;"),
+                    options.itemFactory.createType("Ljava/lang/ArithmeticException;"),
                     options.itemFactory.createProto(options.itemFactory.voidType),
                     options.itemFactory.createString("<init>")),
                 false),
@@ -2934,12 +2972,12 @@
             new CfLoad(ValueType.LONG, 4),
             new CfReturn(ValueType.LONG),
             label7,
-            new CfNew(options.itemFactory.createSynthesizedType("Ljava/lang/ArithmeticException;")),
+            new CfNew(options.itemFactory.createType("Ljava/lang/ArithmeticException;")),
             new CfStackInstruction(CfStackInstruction.Opcode.Dup),
             new CfInvoke(
                 183,
                 options.itemFactory.createMethod(
-                    options.itemFactory.createSynthesizedType("Ljava/lang/ArithmeticException;"),
+                    options.itemFactory.createType("Ljava/lang/ArithmeticException;"),
                     options.itemFactory.createProto(options.itemFactory.voidType),
                     options.itemFactory.createString("<init>")),
                 false),
@@ -2964,12 +3002,12 @@
             new CfConstNumber(-2147483648, ValueType.INT),
             new CfIfCmp(If.Type.NE, ValueType.INT, label2),
             label1,
-            new CfNew(options.itemFactory.createSynthesizedType("Ljava/lang/ArithmeticException;")),
+            new CfNew(options.itemFactory.createType("Ljava/lang/ArithmeticException;")),
             new CfStackInstruction(CfStackInstruction.Opcode.Dup),
             new CfInvoke(
                 183,
                 options.itemFactory.createMethod(
-                    options.itemFactory.createSynthesizedType("Ljava/lang/ArithmeticException;"),
+                    options.itemFactory.createType("Ljava/lang/ArithmeticException;"),
                     options.itemFactory.createProto(options.itemFactory.voidType),
                     options.itemFactory.createString("<init>")),
                 false),
@@ -3000,12 +3038,12 @@
             new CfCmp(Cmp.Bias.NONE, NumericType.LONG),
             new CfIf(If.Type.NE, ValueType.INT, label2),
             label1,
-            new CfNew(options.itemFactory.createSynthesizedType("Ljava/lang/ArithmeticException;")),
+            new CfNew(options.itemFactory.createType("Ljava/lang/ArithmeticException;")),
             new CfStackInstruction(CfStackInstruction.Opcode.Dup),
             new CfInvoke(
                 183,
                 options.itemFactory.createMethod(
-                    options.itemFactory.createSynthesizedType("Ljava/lang/ArithmeticException;"),
+                    options.itemFactory.createType("Ljava/lang/ArithmeticException;"),
                     options.itemFactory.createProto(options.itemFactory.voidType),
                     options.itemFactory.createString("<init>")),
                 false),
@@ -3155,7 +3193,7 @@
             new CfInvoke(
                 184,
                 options.itemFactory.createMethod(
-                    options.itemFactory.createSynthesizedType("Ljava/lang/Math;"),
+                    options.itemFactory.createType("Ljava/lang/Math;"),
                     options.itemFactory.createProto(
                         options.itemFactory.longType,
                         options.itemFactory.longType,
@@ -3287,7 +3325,7 @@
             new CfInvoke(
                 184,
                 options.itemFactory.createMethod(
-                    options.itemFactory.createSynthesizedType("Ljava/lang/Math;"),
+                    options.itemFactory.createType("Ljava/lang/Math;"),
                     options.itemFactory.createProto(
                         options.itemFactory.longType,
                         options.itemFactory.longType,
@@ -3316,12 +3354,12 @@
             new CfConstNumber(2147483647, ValueType.INT),
             new CfIfCmp(If.Type.NE, ValueType.INT, label2),
             label1,
-            new CfNew(options.itemFactory.createSynthesizedType("Ljava/lang/ArithmeticException;")),
+            new CfNew(options.itemFactory.createType("Ljava/lang/ArithmeticException;")),
             new CfStackInstruction(CfStackInstruction.Opcode.Dup),
             new CfInvoke(
                 183,
                 options.itemFactory.createMethod(
-                    options.itemFactory.createSynthesizedType("Ljava/lang/ArithmeticException;"),
+                    options.itemFactory.createType("Ljava/lang/ArithmeticException;"),
                     options.itemFactory.createProto(options.itemFactory.voidType),
                     options.itemFactory.createString("<init>")),
                 false),
@@ -3352,12 +3390,12 @@
             new CfCmp(Cmp.Bias.NONE, NumericType.LONG),
             new CfIf(If.Type.NE, ValueType.INT, label2),
             label1,
-            new CfNew(options.itemFactory.createSynthesizedType("Ljava/lang/ArithmeticException;")),
+            new CfNew(options.itemFactory.createType("Ljava/lang/ArithmeticException;")),
             new CfStackInstruction(CfStackInstruction.Opcode.Dup),
             new CfInvoke(
                 183,
                 options.itemFactory.createMethod(
-                    options.itemFactory.createSynthesizedType("Ljava/lang/ArithmeticException;"),
+                    options.itemFactory.createType("Ljava/lang/ArithmeticException;"),
                     options.itemFactory.createProto(options.itemFactory.voidType),
                     options.itemFactory.createString("<init>")),
                 false),
@@ -3405,12 +3443,12 @@
             new CfLoad(ValueType.INT, 4),
             new CfReturn(ValueType.INT),
             label4,
-            new CfNew(options.itemFactory.createSynthesizedType("Ljava/lang/ArithmeticException;")),
+            new CfNew(options.itemFactory.createType("Ljava/lang/ArithmeticException;")),
             new CfStackInstruction(CfStackInstruction.Opcode.Dup),
             new CfInvoke(
                 183,
                 options.itemFactory.createMethod(
-                    options.itemFactory.createSynthesizedType("Ljava/lang/ArithmeticException;"),
+                    options.itemFactory.createType("Ljava/lang/ArithmeticException;"),
                     options.itemFactory.createProto(options.itemFactory.voidType),
                     options.itemFactory.createString("<init>")),
                 false),
@@ -3449,7 +3487,7 @@
             new CfInvoke(
                 184,
                 options.itemFactory.createMethod(
-                    options.itemFactory.createSynthesizedType("Ljava/lang/Long;"),
+                    options.itemFactory.createType("Ljava/lang/Long;"),
                     options.itemFactory.createProto(
                         options.itemFactory.intType, options.itemFactory.longType),
                     options.itemFactory.createString("numberOfLeadingZeros")),
@@ -3461,7 +3499,7 @@
             new CfInvoke(
                 184,
                 options.itemFactory.createMethod(
-                    options.itemFactory.createSynthesizedType("Ljava/lang/Long;"),
+                    options.itemFactory.createType("Ljava/lang/Long;"),
                     options.itemFactory.createProto(
                         options.itemFactory.intType, options.itemFactory.longType),
                     options.itemFactory.createString("numberOfLeadingZeros")),
@@ -3472,7 +3510,7 @@
             new CfInvoke(
                 184,
                 options.itemFactory.createMethod(
-                    options.itemFactory.createSynthesizedType("Ljava/lang/Long;"),
+                    options.itemFactory.createType("Ljava/lang/Long;"),
                     options.itemFactory.createProto(
                         options.itemFactory.intType, options.itemFactory.longType),
                     options.itemFactory.createString("numberOfLeadingZeros")),
@@ -3485,7 +3523,7 @@
             new CfInvoke(
                 184,
                 options.itemFactory.createMethod(
-                    options.itemFactory.createSynthesizedType("Ljava/lang/Long;"),
+                    options.itemFactory.createType("Ljava/lang/Long;"),
                     options.itemFactory.createProto(
                         options.itemFactory.intType, options.itemFactory.longType),
                     options.itemFactory.createString("numberOfLeadingZeros")),
@@ -3545,12 +3583,12 @@
             new CfLoad(ValueType.LONG, 5),
             new CfReturn(ValueType.LONG),
             label15,
-            new CfNew(options.itemFactory.createSynthesizedType("Ljava/lang/ArithmeticException;")),
+            new CfNew(options.itemFactory.createType("Ljava/lang/ArithmeticException;")),
             new CfStackInstruction(CfStackInstruction.Opcode.Dup),
             new CfInvoke(
                 183,
                 options.itemFactory.createMethod(
-                    options.itemFactory.createSynthesizedType("Ljava/lang/ArithmeticException;"),
+                    options.itemFactory.createType("Ljava/lang/ArithmeticException;"),
                     options.itemFactory.createProto(options.itemFactory.voidType),
                     options.itemFactory.createString("<init>")),
                 false),
@@ -3575,7 +3613,7 @@
             new CfInvoke(
                 184,
                 options.itemFactory.createMethod(
-                    options.itemFactory.createSynthesizedType("Ljava/lang/Math;"),
+                    options.itemFactory.createType("Ljava/lang/Math;"),
                     options.itemFactory.createProto(
                         options.itemFactory.longType,
                         options.itemFactory.longType,
@@ -3727,12 +3765,12 @@
             new CfConstNumber(-2147483648, ValueType.INT),
             new CfIfCmp(If.Type.NE, ValueType.INT, label2),
             label1,
-            new CfNew(options.itemFactory.createSynthesizedType("Ljava/lang/ArithmeticException;")),
+            new CfNew(options.itemFactory.createType("Ljava/lang/ArithmeticException;")),
             new CfStackInstruction(CfStackInstruction.Opcode.Dup),
             new CfInvoke(
                 183,
                 options.itemFactory.createMethod(
-                    options.itemFactory.createSynthesizedType("Ljava/lang/ArithmeticException;"),
+                    options.itemFactory.createType("Ljava/lang/ArithmeticException;"),
                     options.itemFactory.createProto(options.itemFactory.voidType),
                     options.itemFactory.createString("<init>")),
                 false),
@@ -3762,12 +3800,12 @@
             new CfCmp(Cmp.Bias.NONE, NumericType.LONG),
             new CfIf(If.Type.NE, ValueType.INT, label2),
             label1,
-            new CfNew(options.itemFactory.createSynthesizedType("Ljava/lang/ArithmeticException;")),
+            new CfNew(options.itemFactory.createType("Ljava/lang/ArithmeticException;")),
             new CfStackInstruction(CfStackInstruction.Opcode.Dup),
             new CfInvoke(
                 183,
                 options.itemFactory.createMethod(
-                    options.itemFactory.createSynthesizedType("Ljava/lang/ArithmeticException;"),
+                    options.itemFactory.createType("Ljava/lang/ArithmeticException;"),
                     options.itemFactory.createProto(options.itemFactory.voidType),
                     options.itemFactory.createString("<init>")),
                 false),
@@ -3795,7 +3833,7 @@
             new CfInvoke(
                 184,
                 options.itemFactory.createMethod(
-                    options.itemFactory.createSynthesizedType("Ljava/lang/Math;"),
+                    options.itemFactory.createType("Ljava/lang/Math;"),
                     options.itemFactory.createProto(
                         options.itemFactory.doubleType, options.itemFactory.doubleType),
                     options.itemFactory.createString("nextUp")),
@@ -3821,7 +3859,7 @@
             new CfInvoke(
                 184,
                 options.itemFactory.createMethod(
-                    options.itemFactory.createSynthesizedType("Ljava/lang/Math;"),
+                    options.itemFactory.createType("Ljava/lang/Math;"),
                     options.itemFactory.createProto(
                         options.itemFactory.floatType, options.itemFactory.floatType),
                     options.itemFactory.createString("nextUp")),
@@ -3866,12 +3904,12 @@
             new CfLoad(ValueType.INT, 4),
             new CfReturn(ValueType.INT),
             label4,
-            new CfNew(options.itemFactory.createSynthesizedType("Ljava/lang/ArithmeticException;")),
+            new CfNew(options.itemFactory.createType("Ljava/lang/ArithmeticException;")),
             new CfStackInstruction(CfStackInstruction.Opcode.Dup),
             new CfInvoke(
                 183,
                 options.itemFactory.createMethod(
-                    options.itemFactory.createSynthesizedType("Ljava/lang/ArithmeticException;"),
+                    options.itemFactory.createType("Ljava/lang/ArithmeticException;"),
                     options.itemFactory.createProto(options.itemFactory.voidType),
                     options.itemFactory.createString("<init>")),
                 false),
@@ -3930,12 +3968,12 @@
             new CfLoad(ValueType.LONG, 4),
             new CfReturn(ValueType.LONG),
             label7,
-            new CfNew(options.itemFactory.createSynthesizedType("Ljava/lang/ArithmeticException;")),
+            new CfNew(options.itemFactory.createType("Ljava/lang/ArithmeticException;")),
             new CfStackInstruction(CfStackInstruction.Opcode.Dup),
             new CfInvoke(
                 183,
                 options.itemFactory.createMethod(
-                    options.itemFactory.createSynthesizedType("Ljava/lang/ArithmeticException;"),
+                    options.itemFactory.createType("Ljava/lang/ArithmeticException;"),
                     options.itemFactory.createProto(options.itemFactory.voidType),
                     options.itemFactory.createString("<init>")),
                 false),
@@ -3967,12 +4005,12 @@
             new CfCmp(Cmp.Bias.NONE, NumericType.LONG),
             new CfIf(If.Type.EQ, ValueType.INT, label3),
             label2,
-            new CfNew(options.itemFactory.createSynthesizedType("Ljava/lang/ArithmeticException;")),
+            new CfNew(options.itemFactory.createType("Ljava/lang/ArithmeticException;")),
             new CfStackInstruction(CfStackInstruction.Opcode.Dup),
             new CfInvoke(
                 183,
                 options.itemFactory.createMethod(
-                    options.itemFactory.createSynthesizedType("Ljava/lang/ArithmeticException;"),
+                    options.itemFactory.createType("Ljava/lang/ArithmeticException;"),
                     options.itemFactory.createProto(options.itemFactory.voidType),
                     options.itemFactory.createString("<init>")),
                 false),
@@ -4009,8 +4047,7 @@
             new CfArithmeticBinop(CfArithmeticBinop.Opcode.Sub, NumericType.INT),
             new CfIfCmp(If.Type.LE, ValueType.INT, label2),
             label1,
-            new CfNew(
-                options.itemFactory.createSynthesizedType("Ljava/lang/IndexOutOfBoundsException;")),
+            new CfNew(options.itemFactory.createType("Ljava/lang/IndexOutOfBoundsException;")),
             new CfStackInstruction(CfStackInstruction.Opcode.Dup),
             new CfNew(options.itemFactory.stringBuilderType),
             new CfStackInstruction(CfStackInstruction.Opcode.Dup),
@@ -4103,8 +4140,7 @@
             new CfInvoke(
                 183,
                 options.itemFactory.createMethod(
-                    options.itemFactory.createSynthesizedType(
-                        "Ljava/lang/IndexOutOfBoundsException;"),
+                    options.itemFactory.createType("Ljava/lang/IndexOutOfBoundsException;"),
                     options.itemFactory.createProto(
                         options.itemFactory.voidType, options.itemFactory.stringType),
                     options.itemFactory.createString("<init>")),
@@ -4138,8 +4174,7 @@
             new CfLoad(ValueType.INT, 2),
             new CfIfCmp(If.Type.LE, ValueType.INT, label2),
             label1,
-            new CfNew(
-                options.itemFactory.createSynthesizedType("Ljava/lang/IndexOutOfBoundsException;")),
+            new CfNew(options.itemFactory.createType("Ljava/lang/IndexOutOfBoundsException;")),
             new CfStackInstruction(CfStackInstruction.Opcode.Dup),
             new CfNew(options.itemFactory.stringBuilderType),
             new CfStackInstruction(CfStackInstruction.Opcode.Dup),
@@ -4214,8 +4249,7 @@
             new CfInvoke(
                 183,
                 options.itemFactory.createMethod(
-                    options.itemFactory.createSynthesizedType(
-                        "Ljava/lang/IndexOutOfBoundsException;"),
+                    options.itemFactory.createType("Ljava/lang/IndexOutOfBoundsException;"),
                     options.itemFactory.createProto(
                         options.itemFactory.voidType, options.itemFactory.stringType),
                     options.itemFactory.createString("<init>")),
@@ -4246,8 +4280,7 @@
             new CfLoad(ValueType.INT, 1),
             new CfIfCmp(If.Type.LT, ValueType.INT, label2),
             label1,
-            new CfNew(
-                options.itemFactory.createSynthesizedType("Ljava/lang/IndexOutOfBoundsException;")),
+            new CfNew(options.itemFactory.createType("Ljava/lang/IndexOutOfBoundsException;")),
             new CfStackInstruction(CfStackInstruction.Opcode.Dup),
             new CfNew(options.itemFactory.stringBuilderType),
             new CfStackInstruction(CfStackInstruction.Opcode.Dup),
@@ -4304,8 +4337,7 @@
             new CfInvoke(
                 183,
                 options.itemFactory.createMethod(
-                    options.itemFactory.createSynthesizedType(
-                        "Ljava/lang/IndexOutOfBoundsException;"),
+                    options.itemFactory.createType("Ljava/lang/IndexOutOfBoundsException;"),
                     options.itemFactory.createProto(
                         options.itemFactory.voidType, options.itemFactory.stringType),
                     options.itemFactory.createString("<init>")),
@@ -4342,7 +4374,7 @@
             new CfInvoke(
                 185,
                 options.itemFactory.createMethod(
-                    options.itemFactory.createSynthesizedType("Ljava/util/Comparator;"),
+                    options.itemFactory.createType("Ljava/util/Comparator;"),
                     options.itemFactory.createProto(
                         options.itemFactory.intType,
                         options.itemFactory.objectType,
@@ -4428,7 +4460,7 @@
             new CfInvoke(
                 184,
                 options.itemFactory.createMethod(
-                    options.itemFactory.createSynthesizedType("Ljava/util/Arrays;"),
+                    options.itemFactory.createType("Ljava/util/Arrays;"),
                     options.itemFactory.createProto(
                         options.itemFactory.booleanType,
                         options.itemFactory.booleanArrayType,
@@ -4457,7 +4489,7 @@
             new CfInvoke(
                 184,
                 options.itemFactory.createMethod(
-                    options.itemFactory.createSynthesizedType("Ljava/util/Arrays;"),
+                    options.itemFactory.createType("Ljava/util/Arrays;"),
                     options.itemFactory.createProto(
                         options.itemFactory.booleanType,
                         options.itemFactory.byteArrayType,
@@ -4486,7 +4518,7 @@
             new CfInvoke(
                 184,
                 options.itemFactory.createMethod(
-                    options.itemFactory.createSynthesizedType("Ljava/util/Arrays;"),
+                    options.itemFactory.createType("Ljava/util/Arrays;"),
                     options.itemFactory.createProto(
                         options.itemFactory.booleanType,
                         options.itemFactory.charArrayType,
@@ -4515,7 +4547,7 @@
             new CfInvoke(
                 184,
                 options.itemFactory.createMethod(
-                    options.itemFactory.createSynthesizedType("Ljava/util/Arrays;"),
+                    options.itemFactory.createType("Ljava/util/Arrays;"),
                     options.itemFactory.createProto(
                         options.itemFactory.booleanType,
                         options.itemFactory.doubleArrayType,
@@ -4544,7 +4576,7 @@
             new CfInvoke(
                 184,
                 options.itemFactory.createMethod(
-                    options.itemFactory.createSynthesizedType("Ljava/util/Arrays;"),
+                    options.itemFactory.createType("Ljava/util/Arrays;"),
                     options.itemFactory.createProto(
                         options.itemFactory.booleanType,
                         options.itemFactory.floatArrayType,
@@ -4573,7 +4605,7 @@
             new CfInvoke(
                 184,
                 options.itemFactory.createMethod(
-                    options.itemFactory.createSynthesizedType("Ljava/util/Arrays;"),
+                    options.itemFactory.createType("Ljava/util/Arrays;"),
                     options.itemFactory.createProto(
                         options.itemFactory.booleanType,
                         options.itemFactory.intArrayType,
@@ -4602,7 +4634,7 @@
             new CfInvoke(
                 184,
                 options.itemFactory.createMethod(
-                    options.itemFactory.createSynthesizedType("Ljava/util/Arrays;"),
+                    options.itemFactory.createType("Ljava/util/Arrays;"),
                     options.itemFactory.createProto(
                         options.itemFactory.booleanType,
                         options.itemFactory.longArrayType,
@@ -4631,7 +4663,7 @@
             new CfInvoke(
                 184,
                 options.itemFactory.createMethod(
-                    options.itemFactory.createSynthesizedType("Ljava/util/Arrays;"),
+                    options.itemFactory.createType("Ljava/util/Arrays;"),
                     options.itemFactory.createProto(
                         options.itemFactory.booleanType,
                         options.itemFactory.shortArrayType,
@@ -4647,24 +4679,24 @@
             new CfReturn(ValueType.INT),
             label34,
             new CfLoad(ValueType.OBJECT, 0),
-            new CfInstanceOf(options.itemFactory.createSynthesizedType("[Ljava/lang/Object;")),
+            new CfInstanceOf(options.itemFactory.createType("[Ljava/lang/Object;")),
             new CfIf(If.Type.EQ, ValueType.INT, label38),
             label35,
             new CfLoad(ValueType.OBJECT, 1),
-            new CfInstanceOf(options.itemFactory.createSynthesizedType("[Ljava/lang/Object;")),
+            new CfInstanceOf(options.itemFactory.createType("[Ljava/lang/Object;")),
             new CfIf(If.Type.EQ, ValueType.INT, label36),
             new CfLoad(ValueType.OBJECT, 0),
-            new CfCheckCast(options.itemFactory.createSynthesizedType("[Ljava/lang/Object;")),
+            new CfCheckCast(options.itemFactory.createType("[Ljava/lang/Object;")),
             new CfLoad(ValueType.OBJECT, 1),
-            new CfCheckCast(options.itemFactory.createSynthesizedType("[Ljava/lang/Object;")),
+            new CfCheckCast(options.itemFactory.createType("[Ljava/lang/Object;")),
             new CfInvoke(
                 184,
                 options.itemFactory.createMethod(
-                    options.itemFactory.createSynthesizedType("Ljava/util/Arrays;"),
+                    options.itemFactory.createType("Ljava/util/Arrays;"),
                     options.itemFactory.createProto(
                         options.itemFactory.booleanType,
-                        options.itemFactory.createSynthesizedType("[Ljava/lang/Object;"),
-                        options.itemFactory.createSynthesizedType("[Ljava/lang/Object;")),
+                        options.itemFactory.createType("[Ljava/lang/Object;"),
+                        options.itemFactory.createType("[Ljava/lang/Object;")),
                     options.itemFactory.createString("deepEquals")),
                 false),
             new CfIf(If.Type.EQ, ValueType.INT, label36),
@@ -4831,7 +4863,7 @@
             new CfInvoke(
                 184,
                 options.itemFactory.createMethod(
-                    options.itemFactory.createSynthesizedType("Ljava/util/Objects;"),
+                    options.itemFactory.createType("Ljava/util/Objects;"),
                     options.itemFactory.createProto(
                         options.itemFactory.objectType,
                         options.itemFactory.objectType,
@@ -4866,19 +4898,18 @@
             new CfInvoke(
                 184,
                 options.itemFactory.createMethod(
-                    options.itemFactory.createSynthesizedType("Ljava/util/Objects;"),
+                    options.itemFactory.createType("Ljava/util/Objects;"),
                     options.itemFactory.createProto(
                         options.itemFactory.objectType,
                         options.itemFactory.objectType,
                         options.itemFactory.stringType),
                     options.itemFactory.createString("requireNonNull")),
                 false),
-            new CfCheckCast(
-                options.itemFactory.createSynthesizedType("Ljava/util/function/Supplier;")),
+            new CfCheckCast(options.itemFactory.createType("Ljava/util/function/Supplier;")),
             new CfInvoke(
                 185,
                 options.itemFactory.createMethod(
-                    options.itemFactory.createSynthesizedType("Ljava/util/function/Supplier;"),
+                    options.itemFactory.createType("Ljava/util/function/Supplier;"),
                     options.itemFactory.createProto(options.itemFactory.objectType),
                     options.itemFactory.createString("get")),
                 true),
@@ -4889,7 +4920,7 @@
             new CfInvoke(
                 184,
                 options.itemFactory.createMethod(
-                    options.itemFactory.createSynthesizedType("Ljava/util/Objects;"),
+                    options.itemFactory.createType("Ljava/util/Objects;"),
                     options.itemFactory.createProto(
                         options.itemFactory.objectType,
                         options.itemFactory.objectType,
@@ -4917,14 +4948,13 @@
             new CfLoad(ValueType.OBJECT, 0),
             new CfIf(If.Type.NE, ValueType.OBJECT, label2),
             label1,
-            new CfNew(
-                options.itemFactory.createSynthesizedType("Ljava/lang/NullPointerException;")),
+            new CfNew(options.itemFactory.createType("Ljava/lang/NullPointerException;")),
             new CfStackInstruction(CfStackInstruction.Opcode.Dup),
             new CfLoad(ValueType.OBJECT, 1),
             new CfInvoke(
                 183,
                 options.itemFactory.createMethod(
-                    options.itemFactory.createSynthesizedType("Ljava/lang/NullPointerException;"),
+                    options.itemFactory.createType("Ljava/lang/NullPointerException;"),
                     options.itemFactory.createProto(
                         options.itemFactory.voidType, options.itemFactory.stringType),
                     options.itemFactory.createString("<init>")),
@@ -4952,7 +4982,7 @@
             new CfInvoke(
                 184,
                 options.itemFactory.createMethod(
-                    options.itemFactory.createSynthesizedType("Ljava/util/Objects;"),
+                    options.itemFactory.createType("Ljava/util/Objects;"),
                     options.itemFactory.createProto(
                         options.itemFactory.stringType,
                         options.itemFactory.objectType,
@@ -5012,7 +5042,7 @@
             new CfInvoke(
                 182,
                 options.itemFactory.createMethod(
-                    options.itemFactory.createSynthesizedType("Ljava/util/Optional;"),
+                    options.itemFactory.createType("Ljava/util/Optional;"),
                     options.itemFactory.createProto(options.itemFactory.booleanType),
                     options.itemFactory.createString("isPresent")),
                 false),
@@ -5023,14 +5053,14 @@
             new CfInvoke(
                 182,
                 options.itemFactory.createMethod(
-                    options.itemFactory.createSynthesizedType("Ljava/util/Optional;"),
+                    options.itemFactory.createType("Ljava/util/Optional;"),
                     options.itemFactory.createProto(options.itemFactory.objectType),
                     options.itemFactory.createString("get")),
                 false),
             new CfInvoke(
                 185,
                 options.itemFactory.createMethod(
-                    options.itemFactory.createSynthesizedType("Ljava/util/function/Consumer;"),
+                    options.itemFactory.createType("Ljava/util/function/Consumer;"),
                     options.itemFactory.createProto(
                         options.itemFactory.voidType, options.itemFactory.objectType),
                     options.itemFactory.createString("accept")),
@@ -5041,7 +5071,7 @@
             new CfInvoke(
                 185,
                 options.itemFactory.createMethod(
-                    options.itemFactory.createSynthesizedType("Ljava/lang/Runnable;"),
+                    options.itemFactory.createType("Ljava/lang/Runnable;"),
                     options.itemFactory.createProto(options.itemFactory.voidType),
                     options.itemFactory.createString("run")),
                 true),
@@ -5069,7 +5099,7 @@
             new CfInvoke(
                 182,
                 options.itemFactory.createMethod(
-                    options.itemFactory.createSynthesizedType("Ljava/util/OptionalDouble;"),
+                    options.itemFactory.createType("Ljava/util/OptionalDouble;"),
                     options.itemFactory.createProto(options.itemFactory.booleanType),
                     options.itemFactory.createString("isPresent")),
                 false),
@@ -5080,15 +5110,14 @@
             new CfInvoke(
                 182,
                 options.itemFactory.createMethod(
-                    options.itemFactory.createSynthesizedType("Ljava/util/OptionalDouble;"),
+                    options.itemFactory.createType("Ljava/util/OptionalDouble;"),
                     options.itemFactory.createProto(options.itemFactory.doubleType),
                     options.itemFactory.createString("getAsDouble")),
                 false),
             new CfInvoke(
                 185,
                 options.itemFactory.createMethod(
-                    options.itemFactory.createSynthesizedType(
-                        "Ljava/util/function/DoubleConsumer;"),
+                    options.itemFactory.createType("Ljava/util/function/DoubleConsumer;"),
                     options.itemFactory.createProto(
                         options.itemFactory.voidType, options.itemFactory.doubleType),
                     options.itemFactory.createString("accept")),
@@ -5099,7 +5128,7 @@
             new CfInvoke(
                 185,
                 options.itemFactory.createMethod(
-                    options.itemFactory.createSynthesizedType("Ljava/lang/Runnable;"),
+                    options.itemFactory.createType("Ljava/lang/Runnable;"),
                     options.itemFactory.createProto(options.itemFactory.voidType),
                     options.itemFactory.createString("run")),
                 true),
@@ -5127,7 +5156,7 @@
             new CfInvoke(
                 182,
                 options.itemFactory.createMethod(
-                    options.itemFactory.createSynthesizedType("Ljava/util/OptionalInt;"),
+                    options.itemFactory.createType("Ljava/util/OptionalInt;"),
                     options.itemFactory.createProto(options.itemFactory.booleanType),
                     options.itemFactory.createString("isPresent")),
                 false),
@@ -5138,14 +5167,14 @@
             new CfInvoke(
                 182,
                 options.itemFactory.createMethod(
-                    options.itemFactory.createSynthesizedType("Ljava/util/OptionalInt;"),
+                    options.itemFactory.createType("Ljava/util/OptionalInt;"),
                     options.itemFactory.createProto(options.itemFactory.intType),
                     options.itemFactory.createString("getAsInt")),
                 false),
             new CfInvoke(
                 185,
                 options.itemFactory.createMethod(
-                    options.itemFactory.createSynthesizedType("Ljava/util/function/IntConsumer;"),
+                    options.itemFactory.createType("Ljava/util/function/IntConsumer;"),
                     options.itemFactory.createProto(
                         options.itemFactory.voidType, options.itemFactory.intType),
                     options.itemFactory.createString("accept")),
@@ -5156,7 +5185,7 @@
             new CfInvoke(
                 185,
                 options.itemFactory.createMethod(
-                    options.itemFactory.createSynthesizedType("Ljava/lang/Runnable;"),
+                    options.itemFactory.createType("Ljava/lang/Runnable;"),
                     options.itemFactory.createProto(options.itemFactory.voidType),
                     options.itemFactory.createString("run")),
                 true),
@@ -5184,7 +5213,7 @@
             new CfInvoke(
                 182,
                 options.itemFactory.createMethod(
-                    options.itemFactory.createSynthesizedType("Ljava/util/OptionalLong;"),
+                    options.itemFactory.createType("Ljava/util/OptionalLong;"),
                     options.itemFactory.createProto(options.itemFactory.booleanType),
                     options.itemFactory.createString("isPresent")),
                 false),
@@ -5195,14 +5224,14 @@
             new CfInvoke(
                 182,
                 options.itemFactory.createMethod(
-                    options.itemFactory.createSynthesizedType("Ljava/util/OptionalLong;"),
+                    options.itemFactory.createType("Ljava/util/OptionalLong;"),
                     options.itemFactory.createProto(options.itemFactory.longType),
                     options.itemFactory.createString("getAsLong")),
                 false),
             new CfInvoke(
                 185,
                 options.itemFactory.createMethod(
-                    options.itemFactory.createSynthesizedType("Ljava/util/function/LongConsumer;"),
+                    options.itemFactory.createType("Ljava/util/function/LongConsumer;"),
                     options.itemFactory.createProto(
                         options.itemFactory.voidType, options.itemFactory.longType),
                     options.itemFactory.createString("accept")),
@@ -5213,7 +5242,7 @@
             new CfInvoke(
                 185,
                 options.itemFactory.createMethod(
-                    options.itemFactory.createSynthesizedType("Ljava/lang/Runnable;"),
+                    options.itemFactory.createType("Ljava/lang/Runnable;"),
                     options.itemFactory.createProto(options.itemFactory.voidType),
                     options.itemFactory.createString("run")),
                 true),
@@ -5239,7 +5268,7 @@
             new CfInvoke(
                 182,
                 options.itemFactory.createMethod(
-                    options.itemFactory.createSynthesizedType("Ljava/util/Optional;"),
+                    options.itemFactory.createType("Ljava/util/Optional;"),
                     options.itemFactory.createProto(options.itemFactory.booleanType),
                     options.itemFactory.createString("isPresent")),
                 false),
@@ -5270,7 +5299,7 @@
             new CfInvoke(
                 182,
                 options.itemFactory.createMethod(
-                    options.itemFactory.createSynthesizedType("Ljava/util/OptionalDouble;"),
+                    options.itemFactory.createType("Ljava/util/OptionalDouble;"),
                     options.itemFactory.createProto(options.itemFactory.booleanType),
                     options.itemFactory.createString("isPresent")),
                 false),
@@ -5301,7 +5330,7 @@
             new CfInvoke(
                 182,
                 options.itemFactory.createMethod(
-                    options.itemFactory.createSynthesizedType("Ljava/util/OptionalInt;"),
+                    options.itemFactory.createType("Ljava/util/OptionalInt;"),
                     options.itemFactory.createProto(options.itemFactory.booleanType),
                     options.itemFactory.createString("isPresent")),
                 false),
@@ -5332,7 +5361,7 @@
             new CfInvoke(
                 182,
                 options.itemFactory.createMethod(
-                    options.itemFactory.createSynthesizedType("Ljava/util/OptionalLong;"),
+                    options.itemFactory.createType("Ljava/util/OptionalLong;"),
                     options.itemFactory.createProto(options.itemFactory.booleanType),
                     options.itemFactory.createString("isPresent")),
                 false),
@@ -5365,7 +5394,7 @@
             new CfInvoke(
                 184,
                 options.itemFactory.createMethod(
-                    options.itemFactory.createSynthesizedType("Ljava/util/Objects;"),
+                    options.itemFactory.createType("Ljava/util/Objects;"),
                     options.itemFactory.createProto(
                         options.itemFactory.objectType, options.itemFactory.objectType),
                     options.itemFactory.createString("requireNonNull")),
@@ -5376,7 +5405,7 @@
             new CfInvoke(
                 182,
                 options.itemFactory.createMethod(
-                    options.itemFactory.createSynthesizedType("Ljava/util/Optional;"),
+                    options.itemFactory.createType("Ljava/util/Optional;"),
                     options.itemFactory.createProto(options.itemFactory.booleanType),
                     options.itemFactory.createString("isPresent")),
                 false),
@@ -5389,23 +5418,23 @@
             new CfInvoke(
                 185,
                 options.itemFactory.createMethod(
-                    options.itemFactory.createSynthesizedType("Ljava/util/function/Supplier;"),
+                    options.itemFactory.createType("Ljava/util/function/Supplier;"),
                     options.itemFactory.createProto(options.itemFactory.objectType),
                     options.itemFactory.createString("get")),
                 true),
-            new CfCheckCast(options.itemFactory.createSynthesizedType("Ljava/util/Optional;")),
+            new CfCheckCast(options.itemFactory.createType("Ljava/util/Optional;")),
             new CfStore(ValueType.OBJECT, 2),
             label4,
             new CfLoad(ValueType.OBJECT, 2),
             new CfInvoke(
                 184,
                 options.itemFactory.createMethod(
-                    options.itemFactory.createSynthesizedType("Ljava/util/Objects;"),
+                    options.itemFactory.createType("Ljava/util/Objects;"),
                     options.itemFactory.createProto(
                         options.itemFactory.objectType, options.itemFactory.objectType),
                     options.itemFactory.createString("requireNonNull")),
                 false),
-            new CfCheckCast(options.itemFactory.createSynthesizedType("Ljava/util/Optional;")),
+            new CfCheckCast(options.itemFactory.createType("Ljava/util/Optional;")),
             new CfReturn(ValueType.OBJECT),
             label5),
         ImmutableList.of(),
@@ -5427,7 +5456,7 @@
             new CfInvoke(
                 182,
                 options.itemFactory.createMethod(
-                    options.itemFactory.createSynthesizedType("Ljava/util/Optional;"),
+                    options.itemFactory.createType("Ljava/util/Optional;"),
                     options.itemFactory.createProto(options.itemFactory.booleanType),
                     options.itemFactory.createString("isPresent")),
                 false),
@@ -5437,16 +5466,16 @@
             new CfInvoke(
                 182,
                 options.itemFactory.createMethod(
-                    options.itemFactory.createSynthesizedType("Ljava/util/Optional;"),
+                    options.itemFactory.createType("Ljava/util/Optional;"),
                     options.itemFactory.createProto(options.itemFactory.objectType),
                     options.itemFactory.createString("get")),
                 false),
             new CfInvoke(
                 184,
                 options.itemFactory.createMethod(
-                    options.itemFactory.createSynthesizedType("Ljava/util/stream/Stream;"),
+                    options.itemFactory.createType("Ljava/util/stream/Stream;"),
                     options.itemFactory.createProto(
-                        options.itemFactory.createSynthesizedType("Ljava/util/stream/Stream;"),
+                        options.itemFactory.createType("Ljava/util/stream/Stream;"),
                         options.itemFactory.objectType),
                     options.itemFactory.createString("of")),
                 true),
@@ -5455,9 +5484,9 @@
             new CfInvoke(
                 184,
                 options.itemFactory.createMethod(
-                    options.itemFactory.createSynthesizedType("Ljava/util/stream/Stream;"),
+                    options.itemFactory.createType("Ljava/util/stream/Stream;"),
                     options.itemFactory.createProto(
-                        options.itemFactory.createSynthesizedType("Ljava/util/stream/Stream;")),
+                        options.itemFactory.createType("Ljava/util/stream/Stream;")),
                     options.itemFactory.createString("empty")),
                 true),
             new CfReturn(ValueType.OBJECT),
@@ -5481,7 +5510,7 @@
             new CfInvoke(
                 182,
                 options.itemFactory.createMethod(
-                    options.itemFactory.createSynthesizedType("Ljava/util/OptionalDouble;"),
+                    options.itemFactory.createType("Ljava/util/OptionalDouble;"),
                     options.itemFactory.createProto(options.itemFactory.booleanType),
                     options.itemFactory.createString("isPresent")),
                 false),
@@ -5491,17 +5520,16 @@
             new CfInvoke(
                 182,
                 options.itemFactory.createMethod(
-                    options.itemFactory.createSynthesizedType("Ljava/util/OptionalDouble;"),
+                    options.itemFactory.createType("Ljava/util/OptionalDouble;"),
                     options.itemFactory.createProto(options.itemFactory.doubleType),
                     options.itemFactory.createString("getAsDouble")),
                 false),
             new CfInvoke(
                 184,
                 options.itemFactory.createMethod(
-                    options.itemFactory.createSynthesizedType("Ljava/util/stream/DoubleStream;"),
+                    options.itemFactory.createType("Ljava/util/stream/DoubleStream;"),
                     options.itemFactory.createProto(
-                        options.itemFactory.createSynthesizedType(
-                            "Ljava/util/stream/DoubleStream;"),
+                        options.itemFactory.createType("Ljava/util/stream/DoubleStream;"),
                         options.itemFactory.doubleType),
                     options.itemFactory.createString("of")),
                 true),
@@ -5510,10 +5538,9 @@
             new CfInvoke(
                 184,
                 options.itemFactory.createMethod(
-                    options.itemFactory.createSynthesizedType("Ljava/util/stream/DoubleStream;"),
+                    options.itemFactory.createType("Ljava/util/stream/DoubleStream;"),
                     options.itemFactory.createProto(
-                        options.itemFactory.createSynthesizedType(
-                            "Ljava/util/stream/DoubleStream;")),
+                        options.itemFactory.createType("Ljava/util/stream/DoubleStream;")),
                     options.itemFactory.createString("empty")),
                 true),
             new CfReturn(ValueType.OBJECT),
@@ -5537,7 +5564,7 @@
             new CfInvoke(
                 182,
                 options.itemFactory.createMethod(
-                    options.itemFactory.createSynthesizedType("Ljava/util/OptionalInt;"),
+                    options.itemFactory.createType("Ljava/util/OptionalInt;"),
                     options.itemFactory.createProto(options.itemFactory.booleanType),
                     options.itemFactory.createString("isPresent")),
                 false),
@@ -5547,16 +5574,16 @@
             new CfInvoke(
                 182,
                 options.itemFactory.createMethod(
-                    options.itemFactory.createSynthesizedType("Ljava/util/OptionalInt;"),
+                    options.itemFactory.createType("Ljava/util/OptionalInt;"),
                     options.itemFactory.createProto(options.itemFactory.intType),
                     options.itemFactory.createString("getAsInt")),
                 false),
             new CfInvoke(
                 184,
                 options.itemFactory.createMethod(
-                    options.itemFactory.createSynthesizedType("Ljava/util/stream/IntStream;"),
+                    options.itemFactory.createType("Ljava/util/stream/IntStream;"),
                     options.itemFactory.createProto(
-                        options.itemFactory.createSynthesizedType("Ljava/util/stream/IntStream;"),
+                        options.itemFactory.createType("Ljava/util/stream/IntStream;"),
                         options.itemFactory.intType),
                     options.itemFactory.createString("of")),
                 true),
@@ -5565,9 +5592,9 @@
             new CfInvoke(
                 184,
                 options.itemFactory.createMethod(
-                    options.itemFactory.createSynthesizedType("Ljava/util/stream/IntStream;"),
+                    options.itemFactory.createType("Ljava/util/stream/IntStream;"),
                     options.itemFactory.createProto(
-                        options.itemFactory.createSynthesizedType("Ljava/util/stream/IntStream;")),
+                        options.itemFactory.createType("Ljava/util/stream/IntStream;")),
                     options.itemFactory.createString("empty")),
                 true),
             new CfReturn(ValueType.OBJECT),
@@ -5591,7 +5618,7 @@
             new CfInvoke(
                 182,
                 options.itemFactory.createMethod(
-                    options.itemFactory.createSynthesizedType("Ljava/util/OptionalLong;"),
+                    options.itemFactory.createType("Ljava/util/OptionalLong;"),
                     options.itemFactory.createProto(options.itemFactory.booleanType),
                     options.itemFactory.createString("isPresent")),
                 false),
@@ -5601,16 +5628,16 @@
             new CfInvoke(
                 182,
                 options.itemFactory.createMethod(
-                    options.itemFactory.createSynthesizedType("Ljava/util/OptionalLong;"),
+                    options.itemFactory.createType("Ljava/util/OptionalLong;"),
                     options.itemFactory.createProto(options.itemFactory.longType),
                     options.itemFactory.createString("getAsLong")),
                 false),
             new CfInvoke(
                 184,
                 options.itemFactory.createMethod(
-                    options.itemFactory.createSynthesizedType("Ljava/util/stream/LongStream;"),
+                    options.itemFactory.createType("Ljava/util/stream/LongStream;"),
                     options.itemFactory.createProto(
-                        options.itemFactory.createSynthesizedType("Ljava/util/stream/LongStream;"),
+                        options.itemFactory.createType("Ljava/util/stream/LongStream;"),
                         options.itemFactory.longType),
                     options.itemFactory.createString("of")),
                 true),
@@ -5619,9 +5646,9 @@
             new CfInvoke(
                 184,
                 options.itemFactory.createMethod(
-                    options.itemFactory.createSynthesizedType("Ljava/util/stream/LongStream;"),
+                    options.itemFactory.createType("Ljava/util/stream/LongStream;"),
                     options.itemFactory.createProto(
-                        options.itemFactory.createSynthesizedType("Ljava/util/stream/LongStream;")),
+                        options.itemFactory.createType("Ljava/util/stream/LongStream;")),
                     options.itemFactory.createString("empty")),
                 true),
             new CfReturn(ValueType.OBJECT),
@@ -5723,9 +5750,9 @@
             new CfInvoke(
                 184,
                 options.itemFactory.createMethod(
-                    options.itemFactory.createSynthesizedType("Ljava/util/stream/Stream;"),
+                    options.itemFactory.createType("Ljava/util/stream/Stream;"),
                     options.itemFactory.createProto(
-                        options.itemFactory.createSynthesizedType("Ljava/util/stream/Stream;")),
+                        options.itemFactory.createType("Ljava/util/stream/Stream;")),
                     options.itemFactory.createString("empty")),
                 true),
             new CfGoto(label2),
@@ -5734,9 +5761,9 @@
             new CfInvoke(
                 184,
                 options.itemFactory.createMethod(
-                    options.itemFactory.createSynthesizedType("Ljava/util/stream/Stream;"),
+                    options.itemFactory.createType("Ljava/util/stream/Stream;"),
                     options.itemFactory.createProto(
-                        options.itemFactory.createSynthesizedType("Ljava/util/stream/Stream;"),
+                        options.itemFactory.createType("Ljava/util/stream/Stream;"),
                         options.itemFactory.objectType),
                     options.itemFactory.createString("of")),
                 true),
@@ -5849,14 +5876,13 @@
             label0,
             new CfLoad(ValueType.OBJECT, 0),
             new CfIf(If.Type.NE, ValueType.OBJECT, label1),
-            new CfNew(
-                options.itemFactory.createSynthesizedType("Ljava/lang/NullPointerException;")),
+            new CfNew(options.itemFactory.createType("Ljava/lang/NullPointerException;")),
             new CfStackInstruction(CfStackInstruction.Opcode.Dup),
             new CfConstString(options.itemFactory.createString("delimiter")),
             new CfInvoke(
                 183,
                 options.itemFactory.createMethod(
-                    options.itemFactory.createSynthesizedType("Ljava/lang/NullPointerException;"),
+                    options.itemFactory.createType("Ljava/lang/NullPointerException;"),
                     options.itemFactory.createProto(
                         options.itemFactory.voidType, options.itemFactory.stringType),
                     options.itemFactory.createString("<init>")),
@@ -5965,14 +5991,13 @@
             label0,
             new CfLoad(ValueType.OBJECT, 0),
             new CfIf(If.Type.NE, ValueType.OBJECT, label1),
-            new CfNew(
-                options.itemFactory.createSynthesizedType("Ljava/lang/NullPointerException;")),
+            new CfNew(options.itemFactory.createType("Ljava/lang/NullPointerException;")),
             new CfStackInstruction(CfStackInstruction.Opcode.Dup),
             new CfConstString(options.itemFactory.createString("delimiter")),
             new CfInvoke(
                 183,
                 options.itemFactory.createMethod(
-                    options.itemFactory.createSynthesizedType("Ljava/lang/NullPointerException;"),
+                    options.itemFactory.createType("Ljava/lang/NullPointerException;"),
                     options.itemFactory.createProto(
                         options.itemFactory.voidType, options.itemFactory.stringType),
                     options.itemFactory.createString("<init>")),
@@ -5994,9 +6019,9 @@
             new CfInvoke(
                 185,
                 options.itemFactory.createMethod(
-                    options.itemFactory.createSynthesizedType("Ljava/lang/Iterable;"),
+                    options.itemFactory.createType("Ljava/lang/Iterable;"),
                     options.itemFactory.createProto(
-                        options.itemFactory.createSynthesizedType("Ljava/util/Iterator;")),
+                        options.itemFactory.createType("Ljava/util/Iterator;")),
                     options.itemFactory.createString("iterator")),
                 true),
             new CfStore(ValueType.OBJECT, 3),
@@ -6005,7 +6030,7 @@
             new CfInvoke(
                 185,
                 options.itemFactory.createMethod(
-                    options.itemFactory.createSynthesizedType("Ljava/util/Iterator;"),
+                    options.itemFactory.createType("Ljava/util/Iterator;"),
                     options.itemFactory.createProto(options.itemFactory.booleanType),
                     options.itemFactory.createString("hasNext")),
                 true),
@@ -6016,7 +6041,7 @@
             new CfInvoke(
                 185,
                 options.itemFactory.createMethod(
-                    options.itemFactory.createSynthesizedType("Ljava/util/Iterator;"),
+                    options.itemFactory.createType("Ljava/util/Iterator;"),
                     options.itemFactory.createProto(options.itemFactory.objectType),
                     options.itemFactory.createString("next")),
                 true),
@@ -6036,7 +6061,7 @@
             new CfInvoke(
                 185,
                 options.itemFactory.createMethod(
-                    options.itemFactory.createSynthesizedType("Ljava/util/Iterator;"),
+                    options.itemFactory.createType("Ljava/util/Iterator;"),
                     options.itemFactory.createProto(options.itemFactory.booleanType),
                     options.itemFactory.createString("hasNext")),
                 true),
@@ -6060,7 +6085,7 @@
             new CfInvoke(
                 185,
                 options.itemFactory.createMethod(
-                    options.itemFactory.createSynthesizedType("Ljava/util/Iterator;"),
+                    options.itemFactory.createType("Ljava/util/Iterator;"),
                     options.itemFactory.createProto(options.itemFactory.objectType),
                     options.itemFactory.createString("next")),
                 true),
@@ -6115,8 +6140,7 @@
             new CfLoad(ValueType.INT, 1),
             new CfIf(If.Type.GE, ValueType.INT, label2),
             label1,
-            new CfNew(
-                options.itemFactory.createSynthesizedType("Ljava/lang/IllegalArgumentException;")),
+            new CfNew(options.itemFactory.createType("Ljava/lang/IllegalArgumentException;")),
             new CfStackInstruction(CfStackInstruction.Opcode.Dup),
             new CfNew(options.itemFactory.stringBuilderType),
             new CfStackInstruction(CfStackInstruction.Opcode.Dup),
@@ -6155,8 +6179,7 @@
             new CfInvoke(
                 183,
                 options.itemFactory.createMethod(
-                    options.itemFactory.createSynthesizedType(
-                        "Ljava/lang/IllegalArgumentException;"),
+                    options.itemFactory.createType("Ljava/lang/IllegalArgumentException;"),
                     options.itemFactory.createProto(
                         options.itemFactory.voidType, options.itemFactory.stringType),
                     options.itemFactory.createString("<init>")),
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/AssertionsRewriter.java b/src/main/java/com/android/tools/r8/ir/optimize/AssertionsRewriter.java
index da20118..a356e8b 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/AssertionsRewriter.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/AssertionsRewriter.java
@@ -112,18 +112,18 @@
           break;
         case PACKAGE:
           if (entry.value.size == 0) {
-            if (!method.method.holder.descriptor.contains(dexItemFactory.descriptorSeparator)) {
+            if (!method.holder().descriptor.contains(dexItemFactory.descriptorSeparator)) {
               transformation = entry.entry.getTransformation();
             }
-          } else if (method.method.holder.descriptor.startsWith(entry.value)) {
+          } else if (method.holder().descriptor.startsWith(entry.value)) {
             transformation = entry.entry.getTransformation();
           }
           break;
         case CLASS:
-          if (method.method.holder.descriptor.equals(entry.value)) {
+          if (method.holder().descriptor.equals(entry.value)) {
             transformation = entry.entry.getTransformation();
           }
-          if (isDescriptorForClassOrInnerClass(entry.value, method.method.holder.descriptor)) {
+          if (isDescriptorForClassOrInnerClass(entry.value, method.holder().descriptor)) {
             transformation = entry.entry.getTransformation();
           }
           break;
@@ -311,7 +311,7 @@
     if (method.isClassInitializer()) {
       clinit = method;
     } else {
-      DexClass clazz = appView.definitionFor(method.method.holder);
+      DexClass clazz = appView.definitionFor(method.holder());
       if (clazz == null) {
         return;
       }
@@ -328,7 +328,7 @@
       if (current.isInvokeMethod()) {
         InvokeMethod invoke = current.asInvokeMethod();
         if (invoke.getInvokedMethod() == dexItemFactory.classMethods.desiredAssertionStatus) {
-          if (method.method.holder == dexItemFactory.kotlin.kotlinAssertions) {
+          if (method.holder() == dexItemFactory.kotlin.kotlinAssertions) {
             rewriteKotlinAssertionEnable(code, transformation, iterator, invoke);
           } else {
             iterator.replaceCurrentInstruction(code.createIntConstant(0));
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 368b58a..35895b7 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
@@ -98,7 +98,7 @@
             continue;
           }
         }
-        Collection<DexEncodedMethod> targets = invoke.lookupTargets(appView, context.method.holder);
+        Collection<DexEncodedMethod> targets = invoke.lookupTargets(appView, context.holder());
         assert invoke.isInvokeMethodWithDynamicDispatch()
             // For other invocation types, the size of targets should be at most one.
             || targets == null || targets.size() <= 1;
@@ -231,7 +231,7 @@
       if (abstractValue.isSingleValue()) {
         assert appView.options().enablePropagationOfConstantsAtCallSites;
         SingleValue singleValue = abstractValue.asSingleValue();
-        if (singleValue.isMaterializableInContext(appView, code.method.method.holder)) {
+        if (singleValue.isMaterializableInContext(appView, code.method.holder())) {
           Instruction replacement =
               singleValue.createMaterializingInstruction(appView, code, instr);
           replacement.setPosition(instr.getPosition());
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 025c178..ce853a2 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
@@ -133,7 +133,7 @@
       return ClassInitializerDefaultsResult.empty();
     }
 
-    DexClass clazz = appView.definitionFor(method.method.holder);
+    DexClass clazz = appView.definitionFor(method.holder());
     if (clazz == null) {
       return ClassInitializerDefaultsResult.empty();
     }
@@ -160,7 +160,7 @@
       Value value = put.value();
       if (unnecessaryStaticPuts.contains(put)) {
         if (fieldType == dexItemFactory.stringType) {
-          fieldsWithStaticValues.put(field, getDexStringValue(value, method.method.holder));
+          fieldsWithStaticValues.put(field, getDexStringValue(value, method.holder()));
         } else if (fieldType.isClassType() || fieldType.isArrayType()) {
           if (value.isZero()) {
             fieldsWithStaticValues.put(field, DexValueNull.NULL);
@@ -380,7 +380,7 @@
           } else if (instruction.isStaticGet()) {
             StaticGet get = instruction.asStaticGet();
             DexEncodedField field = appView.appInfo().resolveField(get.getField());
-            if (field != null && field.field.holder == clazz.type) {
+            if (field != null && field.holder() == clazz.type) {
               isReadBefore.add(field.field);
             } else if (instruction.instructionMayHaveSideEffects(appView, clazz.type)) {
               // Reading another field is only OK if the read does not have side-effects.
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 5b78d8a..fee1f02 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
@@ -1115,7 +1115,7 @@
     BasicBlock defaultTarget = theSwitch.fallthroughBlock();
     SwitchCaseEliminator eliminator = null;
     BasicBlockBehavioralSubsumption behavioralSubsumption =
-        new BasicBlockBehavioralSubsumption(appView, code.method.method.holder);
+        new BasicBlockBehavioralSubsumption(appView, code.method.holder());
 
     // Compute the set of switch cases that can be removed.
     for (int i = 0; i < theSwitch.numberOfKeys(); i++) {
@@ -1231,7 +1231,7 @@
         }
 
         // Check if the invoked method is known to return one of its arguments.
-        DexEncodedMethod target = invoke.lookupSingleTarget(appView, code.method.method.holder);
+        DexEncodedMethod target = invoke.lookupSingleTarget(appView, code.method.holder());
         if (target != null && target.getOptimizationInfo().returnsArgument()) {
           int argumentIndex = target.getOptimizationInfo().getReturnedArgument();
           // Replace the out value of the invoke with the argument and ignore the out value.
@@ -1353,7 +1353,7 @@
     // If the cast type is not accessible in the current context, we should not remove the cast
     // in order to preserve IllegalAccessError. Note that JVM and ART behave differently: see
     // {@link com.android.tools.r8.ir.optimize.checkcast.IllegalAccessErrorTest}.
-    if (!isTypeVisibleFromContext(appView, code.method.method.holder, castType)) {
+    if (!isTypeVisibleFromContext(appView, code.method.holder(), castType)) {
       return RemoveCheckCastInstructionIfTrivialResult.NO_REMOVALS;
     }
 
@@ -1410,7 +1410,7 @@
       InstanceOf instanceOf, InstructionListIterator it, IRCode code) {
     // If the instance-of type is not accessible in the current context, we should not remove the
     // instance-of instruction in order to preserve IllegalAccessError.
-    if (!isTypeVisibleFromContext(appView, code.method.method.holder, instanceOf.type())) {
+    if (!isTypeVisibleFromContext(appView, code.method.holder(), instanceOf.type())) {
       return false;
     }
 
@@ -2050,8 +2050,7 @@
           for (ConstInstruction value : values) {
             stringValues.add(value.outValue());
           }
-          Value invokeValue =
-              code.createValue(newArray.outValue().getType(), newArray.getLocalInfo());
+          Value invokeValue = code.createValue(newArray.getOutType(), newArray.getLocalInfo());
           InvokeNewArray invoke =
               new InvokeNewArray(dexItemFactory.stringArrayType, invokeValue, stringValues);
           for (Value value : newArray.inValues()) {
@@ -2469,7 +2468,7 @@
               }
             }
           } else {
-            DexType context = code.method.method.holder;
+            DexType context = code.method.holder();
             AbstractValue abstractValue = lhs.getAbstractValue(appView, context);
             if (abstractValue.isSingleConstClassValue() || abstractValue.isSingleFieldValue()) {
               AbstractValue otherAbstractValue = rhs.getAbstractValue(appView, context);
@@ -2761,7 +2760,7 @@
 
         InvokeMethod invoke = insn.asInvokeMethod();
         DexEncodedMethod singleTarget =
-            invoke.lookupSingleTarget(appView.withLiveness(), code.method.method.holder);
+            invoke.lookupSingleTarget(appView.withLiveness(), code.method.holder());
         if (singleTarget == null || !singleTarget.getOptimizationInfo().neverReturnsNormally()) {
           continue;
         }
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/ConstantCanonicalizer.java b/src/main/java/com/android/tools/r8/ir/optimize/ConstantCanonicalizer.java
index 2dff525..c98c262 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/ConstantCanonicalizer.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/ConstantCanonicalizer.java
@@ -88,7 +88,7 @@
 
   public void canonicalize(AppView<?> appView, IRCode code) {
     DexEncodedMethod method = code.method;
-    DexType context = method.method.holder;
+    DexType context = method.holder();
     Object2ObjectLinkedOpenCustomHashMap<Instruction, List<Value>> valuesDefinedByConstant =
         new Object2ObjectLinkedOpenCustomHashMap<>(
             new Strategy<Instruction>() {
@@ -149,8 +149,7 @@
           continue;
         }
         SingleFieldValue singleFieldValue = abstractValue.asSingleFieldValue();
-        if (method.isClassInitializer()
-            && method.method.holder == singleFieldValue.getField().holder) {
+        if (method.isClassInitializer() && method.holder() == singleFieldValue.getField().holder) {
           // Avoid that canonicalization inserts a read before the unique write in the class
           // initializer, as that would change the program behavior.
           continue;
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 d225220..d9773d9 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
@@ -105,7 +105,7 @@
       return true;
     }
 
-    DexClass clazz = appView.definitionFor(singleTarget.method.holder);
+    DexClass clazz = appView.definitionFor(singleTarget.holder());
     if (!clazz.isProgramClass()) {
       if (clazz.isClasspathClass()) {
         whyAreYouNotInliningReporter.reportClasspathMethod();
@@ -211,12 +211,12 @@
     // Don't inline code with references beyond root main dex classes into a root main dex class.
     // If we do this it can increase the size of the main dex dependent classes.
     if (reason != Reason.FORCE
-        && inlineeRefersToClassesNotInMainDex(method.method.holder, singleTarget)) {
+        && inlineeRefersToClassesNotInMainDex(method.holder(), singleTarget)) {
       whyAreYouNotInliningReporter.reportInlineeRefersToClassesNotInMainDex();
       return false;
     }
     assert reason != Reason.FORCE
-        || !inlineeRefersToClassesNotInMainDex(method.method.holder, singleTarget);
+        || !inlineeRefersToClassesNotInMainDex(method.holder(), singleTarget);
     return true;
   }
 
@@ -360,8 +360,8 @@
     // - the current method has already triggered the holder for the target method to be
     //   initialized, or
     // - there is no non-trivial class initializer.
-    DexType targetHolder = target.method.holder;
-    if (appView.appInfo().isSubtype(method.method.holder, targetHolder)) {
+    DexType targetHolder = target.holder();
+    if (appView.appInfo().isSubtype(method.holder(), targetHolder)) {
       return true;
     }
     DexClass clazz = appView.definitionFor(targetHolder);
@@ -374,14 +374,14 @@
           appView.withInitializedClassesInInstanceMethods(
               analysis ->
                   analysis.isClassDefinitelyLoadedInInstanceMethodsOn(
-                      target.method.holder, method.method.holder),
+                      target.holder(), method.holder()),
               false);
       if (targetIsGuaranteedToBeInitialized) {
         return true;
       }
     }
     if (classInitializationAnalysis.isClassDefinitelyLoadedBeforeInstruction(
-        target.method.holder, invoke)) {
+        target.holder(), invoke)) {
       return true;
     }
     // Check for class initializer side effects when loading this class, as inlining might remove
@@ -445,8 +445,8 @@
 
     // Allow inlining a constructor into a constructor of the same class, as the constructor code
     // is expected to adhere to the VM specification.
-    DexType callerMethodHolder = method.method.holder;
-    DexType calleeMethodHolder = inlinee.method.method.holder;
+    DexType callerMethodHolder = method.holder();
+    DexType calleeMethodHolder = inlinee.method.holder();
     // Calling a constructor on the same class from a constructor can always be inlined.
     if (method.isInstanceInitializer() && callerMethodHolder == calleeMethodHolder) {
       return true;
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 82ea8ab..897b85b 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
@@ -136,7 +136,7 @@
         if (target == null) {
           continue;
         }
-        DexType holderType = target.method.holder;
+        DexType holderType = target.holder();
         DexClass holderClass = appView.definitionFor(holderType);
         // Make sure we are not landing on another interface, e.g., interface's default method.
         if (holderClass == null || holderClass.isInterface()) {
@@ -284,7 +284,7 @@
       // Most likely due to a missing class, or invoke is already as specific as it gets.
       return target;
     }
-    DexClass newTargetClass = appView.definitionFor(newTarget.method.holder);
+    DexClass newTargetClass = appView.definitionFor(newTarget.holder());
     if (newTargetClass == null
         || newTargetClass.isLibraryClass()
         || !canInvokeTargetWithInvokeVirtual(newTarget)
@@ -296,13 +296,12 @@
   }
 
   private boolean canInvokeTargetWithInvokeVirtual(DexEncodedMethod target) {
-    return target.isNonPrivateVirtualMethod()
-        && appView.isInterface(target.method.holder).isFalse();
+    return target.isNonPrivateVirtualMethod() && appView.isInterface(target.holder()).isFalse();
   }
 
   private boolean hasAccessToInvokeTargetFromContext(DexEncodedMethod target, DexType context) {
     assert !target.accessFlags.isPrivate();
-    DexType holder = target.method.holder;
+    DexType holder = target.holder();
     if (holder == context) {
       // It is always safe to invoke a method from the same enclosing class.
       return true;
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 8048e3a..d518ef6 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
@@ -78,8 +78,7 @@
               TypeElement.fromDexType(invokedMethod.holder, definitelyNotNull(), appView);
           dynamicLowerBoundType = null;
         } else {
-          DexEncodedMethod singleTarget =
-              invoke.lookupSingleTarget(appView, code.method.method.holder);
+          DexEncodedMethod singleTarget = invoke.lookupSingleTarget(appView, code.method.holder());
           if (singleTarget == null) {
             continue;
           }
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 5dba772..76ca615 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
@@ -110,7 +110,7 @@
               }
             });
 
-    DexType context = code.method.method.holder;
+    DexType context = code.method.holder();
     // Collect invocations along with arguments.
     for (BasicBlock block : code.blocks) {
       for (Instruction current : block.getInstructions()) {
@@ -143,7 +143,7 @@
           }
           // Verify that the target method is accessible in the current context.
           if (!isMemberVisibleFromOriginalContext(
-              appView, context, target.method.holder, target.accessFlags)) {
+              appView, context, target.holder(), target.accessFlags)) {
             continue;
           }
           // Check if the call could throw a NPE as a result of the receiver being null.
@@ -207,7 +207,7 @@
                 }
               }
               Value canonicalizedValue =
-                  code.createValue(invoke.outValue().getType(), invoke.outValue().getLocalInfo());
+                  code.createValue(invoke.getOutType(), invoke.outValue().getLocalInfo());
               Invoke canonicalizedInvoke =
                   Invoke.create(
                       invoke.getType(),
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 b56dece..33d982a 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
@@ -164,7 +164,7 @@
         new InliningConstraints(appView, GraphLense.getIdentityLense());
     for (Instruction instruction : code.instructions()) {
       ConstraintWithTarget state =
-          instructionAllowedForInlining(instruction, inliningConstraints, method.method.holder);
+          instructionAllowedForInlining(instruction, inliningConstraints, method.holder());
       if (state == ConstraintWithTarget.NEVER) {
         result = state;
         break;
@@ -192,12 +192,12 @@
   }
 
   boolean hasInliningAccess(DexEncodedMethod method, DexEncodedMethod target) {
-    if (!isVisibleWithFlags(target.method.holder, method.method.holder, target.accessFlags)) {
+    if (!isVisibleWithFlags(target.holder(), method.holder(), target.accessFlags)) {
       return false;
     }
     // The class needs also to be visible for us to have access.
-    DexClass targetClass = appView.definitionFor(target.method.holder);
-    return isVisibleWithFlags(target.method.holder, method.method.holder, targetClass.accessFlags);
+    DexClass targetClass = appView.definitionFor(target.holder());
+    return isVisibleWithFlags(target.holder(), method.holder(), targetClass.accessFlags);
   }
 
   private boolean isVisibleWithFlags(DexType target, DexType context, AccessFlags flags) {
@@ -710,7 +710,7 @@
           lockValue =
               code.createValue(
                   TypeElement.fromDexType(dexItemFactory.objectType, definitelyNotNull(), appView));
-          monitorEnterBlockIterator.add(new ConstClass(lockValue, target.method.holder));
+          monitorEnterBlockIterator.add(new ConstClass(lockValue, target.holder()));
         } else {
           lockValue = entryBlock.getInstructions().getFirst().asArgument().outValue();
         }
@@ -738,10 +738,10 @@
 
       if (inliningIRProvider.shouldApplyCodeRewritings(code.method)) {
         assert lensCodeRewriter != null;
-        lensCodeRewriter.rewrite(code, target);
         if (enumUnboxer != null) {
           enumUnboxer.rewriteCode(code);
         }
+        lensCodeRewriter.rewrite(code, target);
       }
       if (lambdaMerger != null) {
         lambdaMerger.rewriteCodeForInlining(target, code, context, inliningIRProvider);
@@ -964,7 +964,7 @@
         if (current.isInvokeMethod()) {
           InvokeMethod invoke = current.asInvokeMethod();
           // TODO(b/142116551): This should be equivalent to invoke.lookupSingleTarget()!
-          DexEncodedMethod singleTarget = oracle.lookupSingleTarget(invoke, context.method.holder);
+          DexEncodedMethod singleTarget = oracle.lookupSingleTarget(invoke, context.holder());
           if (singleTarget == null) {
             WhyAreYouNotInliningReporter.handleInvokeWithUnknownTarget(invoke, appView, context);
             continue;
@@ -1115,8 +1115,8 @@
         // method holder as a fallback.
         receiverType = invoke.getInvokedMethod().holder;
       }
-      if (!appView.appInfo().isSubtype(receiverType, target.method.holder)) {
-        return target.method.holder;
+      if (!appView.appInfo().isSubtype(receiverType, target.holder())) {
+        return target.holder();
       }
     }
     return null;
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/InliningConstraints.java b/src/main/java/com/android/tools/r8/ir/optimize/InliningConstraints.java
index a1307a0..ea77caf 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/InliningConstraints.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/InliningConstraints.java
@@ -197,7 +197,7 @@
     DexEncodedMethod alternativeDexEncodedMethod =
         lookupFunction.apply(singleResolutionResult, superContext);
     if (alternativeDexEncodedMethod != null
-        && alternativeDexEncodedMethod.method.holder == superContext.type) {
+        && alternativeDexEncodedMethod.holder() == superContext.type) {
       return alternativeDexEncodedMethod;
     }
     return null;
@@ -354,8 +354,8 @@
       // `invocationContext` has access to the definition of the field.
       //
       // See, for example, InlineNonReboundFieldTest (b/128604123).
-      if (field.holder != target.field.holder) {
-        DexType actualFieldHolder = graphLense.lookupType(target.field.holder);
+      if (field.holder != target.holder()) {
+        DexType actualFieldHolder = graphLense.lookupType(target.holder());
         fieldConstraintWithTarget =
             ConstraintWithTarget.meet(
                 fieldConstraintWithTarget,
@@ -379,7 +379,7 @@
       return ConstraintWithTarget.ALWAYS;
     }
     if (target != null) {
-      DexType methodHolder = graphLense.lookupType(target.method.holder);
+      DexType methodHolder = graphLense.lookupType(target.holder());
       DexClass methodClass = appView.definitionFor(methodHolder);
       if (methodClass != null) {
         if (!allowStaticInterfaceMethodCalls && methodClass.isInterface() && target.hasCode()) {
@@ -423,7 +423,7 @@
       return ConstraintWithTarget.NEVER;
     }
 
-    DexType methodHolder = graphLense.lookupType(resolutionTarget.method.holder);
+    DexType methodHolder = graphLense.lookupType(resolutionTarget.holder());
     DexClass methodClass = appView.definitionFor(methodHolder);
     assert methodClass != null;
     ConstraintWithTarget methodConstraintWithTarget =
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 4bf7a6d..bf7aa9b 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
@@ -4,6 +4,7 @@
 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.maybeNull;
 
 import com.android.tools.r8.graph.AppView;
 import com.android.tools.r8.graph.DexDefinition;
@@ -14,7 +15,6 @@
 import com.android.tools.r8.graph.DexProgramClass;
 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.TypeAnalysis;
 import com.android.tools.r8.ir.analysis.type.TypeElement;
 import com.android.tools.r8.ir.analysis.value.AbstractValue;
@@ -139,10 +139,9 @@
           .createMaterializingInstruction(appView, code, instruction);
     }
 
-    TypeElement typeLattice = instruction.outValue().getType();
     if (returnValueRule.isField()) {
       DexField field = returnValueRule.getField();
-      assert typeLattice == TypeElement.fromDexType(field.type, Nullability.maybeNull(), appView);
+      assert instruction.getOutType() == TypeElement.fromDexType(field.type, maybeNull(), appView);
 
       DexEncodedField staticField = appView.appInfo().lookupStaticTarget(field.holder, field);
       if (staticField == null) {
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/NestUtils.java b/src/main/java/com/android/tools/r8/ir/optimize/NestUtils.java
index c6694f4..508e394 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/NestUtils.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/NestUtils.java
@@ -46,7 +46,7 @@
     InstructionListIterator iterator = code.instructionListIterator();
     DexClass callerHolderClass = appView.definitionFor(callerHolder);
     assert callerHolderClass != null;
-    assert code.method.method.holder != callerHolder;
+    assert code.method.holder() != callerHolder;
     while (iterator.hasNext()) {
       Instruction instruction = iterator.next();
       if (instruction.isInvokeDirect()) {
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 15904c5..2e41703 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
@@ -98,8 +98,7 @@
             }
           }
 
-          DexEncodedMethod singleTarget =
-              invoke.lookupSingleTarget(appView, code.method.method.holder);
+          DexEncodedMethod singleTarget = invoke.lookupSingleTarget(appView, code.method.holder());
           if (singleTarget != null) {
             MethodOptimizationInfo optimizationInfo = singleTarget.getOptimizationInfo();
 
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 ab9fa71..d9ebcf6 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
@@ -862,7 +862,7 @@
       // See whether we could move this invoke somewhere else. We reuse the logic from inlining
       // here, as the constraints are the same.
       ConstraintWithTarget constraint =
-          invoke.inliningConstraint(inliningConstraints, method.method.holder);
+          invoke.inliningConstraint(inliningConstraints, method.holder());
       if (constraint != ConstraintWithTarget.ALWAYS) {
         return false;
       }
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/RedundantFieldLoadElimination.java b/src/main/java/com/android/tools/r8/ir/optimize/RedundantFieldLoadElimination.java
index de9d4cc..81b32e8 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/RedundantFieldLoadElimination.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/RedundantFieldLoadElimination.java
@@ -4,12 +4,15 @@
 
 package com.android.tools.r8.ir.optimize;
 
+import static com.android.tools.r8.utils.PredicateUtils.not;
+
 import com.android.tools.r8.errors.Unreachable;
 import com.android.tools.r8.graph.AppView;
 import com.android.tools.r8.graph.DexEncodedField;
 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.graph.classmerging.VerticallyMergedClasses;
 import com.android.tools.r8.ir.analysis.type.TypeAnalysis;
 import com.android.tools.r8.ir.analysis.value.SingleValue;
 import com.android.tools.r8.ir.code.BasicBlock;
@@ -29,12 +32,11 @@
 import com.android.tools.r8.ir.code.Value;
 import com.android.tools.r8.ir.optimize.info.field.InstanceFieldInitializationInfoCollection;
 import com.android.tools.r8.ir.optimize.info.initializer.InstanceInitializerInfo;
-import com.android.tools.r8.utils.SetUtils;
 import com.google.common.collect.Sets;
-import java.util.ArrayList;
+import java.util.ArrayDeque;
+import java.util.Deque;
 import java.util.HashMap;
 import java.util.IdentityHashMap;
-import java.util.List;
 import java.util.Map;
 import java.util.Set;
 
@@ -56,18 +58,11 @@
   private final Set<Value> affectedValues = Sets.newIdentityHashSet();
 
   // Maps keeping track of fields that have an already loaded value at basic block entry.
-  private final Map<BasicBlock, Set<DexType>> activeInitializedClassesAtEntry =
-      new IdentityHashMap<>();
-  private final Map<BasicBlock, Map<FieldAndObject, FieldValue>> activeInstanceFieldsAtEntry =
-      new IdentityHashMap<>();
-  private final Map<BasicBlock, Map<DexField, FieldValue>> activeStaticFieldsAtEntry =
-      new IdentityHashMap<>();
+  private final Map<BasicBlock, State> activeStateAtExit = new IdentityHashMap<>();
 
   // Maps keeping track of fields with already loaded values for the current block during
   // elimination.
-  private Set<DexType> activeInitializedClasses;
-  private Map<FieldAndObject, FieldValue> activeInstanceFieldValues;
-  private Map<DexField, FieldValue> activeStaticFieldValues;
+  private State activeState;
 
   public RedundantFieldLoadElimination(AppView<?> appView, IRCode code) {
     this.appView = appView;
@@ -78,7 +73,7 @@
 
   public static boolean shouldRun(AppView<?> appView, IRCode code) {
     return appView.options().enableRedundantFieldLoadElimination
-        && code.metadata().mayHaveFieldGet();
+        && (code.metadata().mayHaveFieldGet() || code.metadata().mayHaveInitClass());
   }
 
   private interface FieldValue {
@@ -145,41 +140,28 @@
     }
   }
 
-  private boolean couldBeVolatile(DexField field) {
-    DexEncodedField definition;
+  private DexEncodedField resolveField(DexField field) {
     if (appView.enableWholeProgramOptimizations()) {
-      definition = appView.appInfo().resolveField(field);
-    } else {
-      if (field.holder != method.method.holder) {
-        return true;
-      }
-      definition = appView.definitionFor(field);
+      return appView.appInfo().resolveField(field);
     }
-    return definition == null || definition.accessFlags.isVolatile();
+    if (field.holder == method.holder()) {
+      return appView.definitionFor(field);
+    }
+    return null;
   }
 
   public void run() {
-    DexType context = method.method.holder;
+    DexType context = method.holder();
     for (BasicBlock block : dominatorTree.getSortedBlocks()) {
-      activeInitializedClasses =
-          activeInitializedClassesAtEntry.containsKey(block)
-              ? activeInitializedClassesAtEntry.get(block)
-              : Sets.newIdentityHashSet();
-      activeInstanceFieldValues =
-          activeInstanceFieldsAtEntry.containsKey(block)
-              ? activeInstanceFieldsAtEntry.get(block)
-              : new HashMap<>();
-      activeStaticFieldValues =
-          activeStaticFieldsAtEntry.containsKey(block)
-              ? activeStaticFieldsAtEntry.get(block)
-              : new IdentityHashMap<>();
+      computeActiveStateOnBlockEntry(block);
       InstructionListIterator it = block.listIterator(code);
       while (it.hasNext()) {
         Instruction instruction = it.next();
         if (instruction.isFieldInstruction()) {
           DexField field = instruction.asFieldInstruction().getField();
-          if (couldBeVolatile(field)) {
-            killAllActiveFields();
+          DexEncodedField definition = resolveField(field);
+          if (definition == null || definition.isVolatile()) {
+            killAllNonFinalActiveFields();
             continue;
           }
 
@@ -190,56 +172,71 @@
             }
             Value object = instanceGet.object().getAliasedValue();
             FieldAndObject fieldAndObject = new FieldAndObject(field, object);
-            if (activeInstanceFieldValues.containsKey(fieldAndObject)) {
-              FieldValue replacement = activeInstanceFieldValues.get(fieldAndObject);
+            FieldValue replacement = activeState.getInstanceFieldValue(fieldAndObject);
+            if (replacement != null) {
               replacement.eliminateRedundantRead(it, instanceGet);
             } else {
-              activeInstanceFieldValues.put(fieldAndObject, new ExistingValue(instanceGet.value()));
+              activeState.putNonFinalInstanceField(
+                  fieldAndObject, new ExistingValue(instanceGet.value()));
             }
           } else if (instruction.isInstancePut()) {
             InstancePut instancePut = instruction.asInstancePut();
             // An instance-put instruction can potentially write the given field on all objects
             // because of aliases.
-            killActiveFields(instancePut);
+            killNonFinalActiveFields(instancePut);
             // ... but at least we know the field value for this particular object.
             Value object = instancePut.object().getAliasedValue();
             FieldAndObject fieldAndObject = new FieldAndObject(field, object);
-            activeInstanceFieldValues.put(fieldAndObject, new ExistingValue(instancePut.value()));
+            ExistingValue value = new ExistingValue(instancePut.value());
+            if (definition.isFinal()) {
+              assert method.isInstanceInitializer() || verifyWasInstanceInitializer();
+              activeState.putFinalInstanceField(fieldAndObject, value);
+            } else {
+              activeState.putNonFinalInstanceField(fieldAndObject, value);
+            }
           } else if (instruction.isStaticGet()) {
             StaticGet staticGet = instruction.asStaticGet();
             if (staticGet.outValue().hasLocalInfo()) {
               continue;
             }
-            if (activeStaticFieldValues.containsKey(field)) {
-              FieldValue replacement = activeStaticFieldValues.get(field);
+            FieldValue replacement = activeState.getStaticFieldValue(field);
+            if (replacement != null) {
               replacement.eliminateRedundantRead(it, staticGet);
             } else {
               // A field get on a different class can cause <clinit> to run and change static
               // field values.
-              killActiveFields(staticGet);
-              activeStaticFieldValues.put(field, new ExistingValue(staticGet.value()));
+              killNonFinalActiveFields(staticGet);
+              activeState.putNonFinalStaticField(field, new ExistingValue(staticGet.value()));
             }
           } else if (instruction.isStaticPut()) {
             StaticPut staticPut = instruction.asStaticPut();
             // A field put on a different class can cause <clinit> to run and change static
             // field values.
-            killActiveFields(staticPut);
-            activeStaticFieldValues.put(field, new ExistingValue(staticPut.value()));
+            killNonFinalActiveFields(staticPut);
+            ExistingValue value = new ExistingValue(staticPut.value());
+            if (definition.isFinal()) {
+              assert method.isClassInitializer();
+              activeState.putFinalStaticField(field, value);
+            } else {
+              activeState.putNonFinalStaticField(field, value);
+            }
           }
         } else if (instruction.isInitClass()) {
           InitClass initClass = instruction.asInitClass();
           assert !initClass.outValue().hasAnyUsers();
-          if (activeInitializedClasses.contains(initClass.getClassValue())) {
+          DexType clazz = initClass.getClassValue();
+          if (activeState.isClassInitialized(clazz)) {
             it.removeOrReplaceByDebugLocalRead();
           }
+          activeState.markClassAsInitialized(clazz);
         } else if (instruction.isMonitor()) {
           if (instruction.asMonitor().isEnter()) {
-            killAllActiveFields();
+            killAllNonFinalActiveFields();
           }
         } else if (instruction.isInvokeDirect()) {
           handleInvokeDirect(instruction.asInvokeDirect());
         } else if (instruction.isInvokeMethod() || instruction.isInvokeCustom()) {
-          killAllActiveFields();
+          killAllNonFinalActiveFields();
         } else if (instruction.isNewInstance()) {
           NewInstance newInstance = instruction.asNewInstance();
           if (newInstance.clazz.classInitializationMayHaveSideEffects(
@@ -247,7 +244,7 @@
               // Types that are a super type of `context` are guaranteed to be initialized already.
               type -> appView.isSubtype(context, type).isTrue(),
               Sets.newIdentityHashSet())) {
-            killAllActiveFields();
+            killAllNonFinalActiveFields();
           }
         } else {
           // If the current instruction could trigger a method invocation, it could also cause field
@@ -286,7 +283,7 @@
               : "Unexpected instruction of type " + instruction.getClass().getTypeName();
         }
       }
-      propagateActiveStateFrom(block);
+      recordActiveStateOnBlockExit(block);
     }
     if (!affectedValues.isEmpty()) {
       new TypeAnalysis(appView).narrowing(affectedValues);
@@ -294,22 +291,33 @@
     assert code.isConsistentSSA();
   }
 
+  private boolean verifyWasInstanceInitializer() {
+    VerticallyMergedClasses verticallyMergedClasses = appView.verticallyMergedClasses();
+    assert verticallyMergedClasses != null;
+    assert verticallyMergedClasses.isTarget(method.holder());
+    assert appView
+        .dexItemFactory()
+        .isConstructor(appView.graphLense().getOriginalMethodSignature(method.method));
+    assert method.getOptimizationInfo().forceInline();
+    return true;
+  }
+
   private void handleInvokeDirect(InvokeDirect invoke) {
     if (!appView.enableWholeProgramOptimizations()) {
-      killAllActiveFields();
+      killAllNonFinalActiveFields();
       return;
     }
 
     DexEncodedMethod singleTarget = invoke.lookupSingleTarget(appView, method.holder());
     if (singleTarget == null || !singleTarget.isInstanceInitializer()) {
-      killAllActiveFields();
+      killAllNonFinalActiveFields();
       return;
     }
 
     InstanceInitializerInfo instanceInitializerInfo =
         singleTarget.getOptimizationInfo().getInstanceInitializerInfo();
     if (instanceInitializerInfo.mayHaveOtherSideEffectsThanInstanceFieldAssignments()) {
-      killAllActiveFields();
+      killAllNonFinalActiveFields();
     }
 
     InstanceFieldInitializationInfoCollection fieldInitializationInfos =
@@ -325,13 +333,13 @@
                 invoke.getArgument(info.asArgumentInitializationInfo().getArgumentIndex());
             Value object = invoke.getReceiver().getAliasedValue();
             FieldAndObject fieldAndObject = new FieldAndObject(field.field, object);
-            activeInstanceFieldValues.put(fieldAndObject, new ExistingValue(value));
+            activeState.putNonFinalInstanceField(fieldAndObject, new ExistingValue(value));
           } else if (info.isSingleValue()) {
             SingleValue value = info.asSingleValue();
             if (value.isMaterializableInContext(appView, method.holder())) {
               Value object = invoke.getReceiver().getAliasedValue();
               FieldAndObject fieldAndObject = new FieldAndObject(field.field, object);
-              activeInstanceFieldValues.put(fieldAndObject, new MaterializableValue(value));
+              activeState.putNonFinalInstanceField(fieldAndObject, new MaterializableValue(value));
             }
           } else {
             assert info.isTypeInitializationInfo();
@@ -339,84 +347,206 @@
         });
   }
 
-  private void propagateActiveStateFrom(BasicBlock block) {
-    for (BasicBlock successor : block.getSuccessors()) {
+  private void computeActiveStateOnBlockEntry(BasicBlock block) {
+    if (block.isEntry()) {
+      activeState = new State();
+      return;
+    }
+    Deque<State> predecessorExitStates = new ArrayDeque<>(block.getPredecessors().size());
+    for (BasicBlock predecessor : block.getPredecessors()) {
+      State predecessorExitState = activeStateAtExit.get(predecessor);
+      if (predecessorExitState == null) {
+        // Not processed yet.
+        activeState = new State();
+        return;
+      }
       // Allow propagation across exceptional edges, just be careful not to propagate if the
       // throwing instruction is a field instruction.
-      if (successor.getPredecessors().size() == 1) {
-        if (block.hasCatchSuccessor(successor)) {
-          Instruction exceptionalExit = block.exceptionalExit();
-          if (exceptionalExit != null) {
-            if (exceptionalExit.isFieldInstruction()) {
-              killActiveFieldsForExceptionalExit(exceptionalExit.asFieldInstruction());
-            } else if (exceptionalExit.isInitClass()) {
-              killActiveInitializedClassesForExceptionalExit(exceptionalExit.asInitClass());
-            }
+      if (predecessor.hasCatchSuccessor(block)) {
+        Instruction exceptionalExit = predecessor.exceptionalExit();
+        if (exceptionalExit != null) {
+          predecessorExitState = new State(predecessorExitState);
+          if (exceptionalExit.isFieldInstruction()) {
+            predecessorExitState.killActiveFieldsForExceptionalExit(
+                exceptionalExit.asFieldInstruction());
+          } else if (exceptionalExit.isInitClass()) {
+            predecessorExitState.killActiveInitializedClassesForExceptionalExit(
+                exceptionalExit.asInitClass());
           }
         }
-        assert !activeInitializedClassesAtEntry.containsKey(successor);
-        activeInitializedClassesAtEntry.put(
-            successor, SetUtils.newIdentityHashSet(activeInitializedClasses));
-        assert !activeInstanceFieldsAtEntry.containsKey(successor);
-        activeInstanceFieldsAtEntry.put(successor, new HashMap<>(activeInstanceFieldValues));
-        assert !activeStaticFieldsAtEntry.containsKey(successor);
-        activeStaticFieldsAtEntry.put(successor, new IdentityHashMap<>(activeStaticFieldValues));
       }
+      predecessorExitStates.addLast(predecessorExitState);
     }
+    State state = new State(predecessorExitStates.removeFirst());
+    predecessorExitStates.forEach(state::intersect);
+    activeState = state;
   }
 
-  private void killAllActiveFields() {
-    activeInstanceFieldValues.clear();
-    activeStaticFieldValues.clear();
+  private void recordActiveStateOnBlockExit(BasicBlock block) {
+    assert !activeStateAtExit.containsKey(block);
+    activeStateAtExit.put(block, activeState);
   }
 
-  private void killActiveFields(FieldInstruction instruction) {
+  private void killAllNonFinalActiveFields() {
+    activeState.clearNonFinalInstanceFields();
+    activeState.clearNonFinalStaticFields();
+  }
+
+  private void killNonFinalActiveFields(FieldInstruction instruction) {
     DexField field = instruction.getField();
     if (instruction.isInstancePut()) {
       // Remove all the field/object pairs that refer to this field to make sure
       // that we are conservative.
-      List<FieldAndObject> keysToRemove = new ArrayList<>();
-      for (FieldAndObject key : activeInstanceFieldValues.keySet()) {
-        if (key.field == field) {
-          keysToRemove.add(key);
-        }
-      }
-      keysToRemove.forEach(activeInstanceFieldValues::remove);
+      activeState.removeNonFinalInstanceFields(field);
     } else if (instruction.isStaticPut()) {
-      if (field.holder != code.method.method.holder) {
+      if (field.holder != code.method.holder()) {
         // Accessing a static field on a different object could cause <clinit> to run which
         // could modify any static field on any other object.
-        activeStaticFieldValues.clear();
+        activeState.clearNonFinalStaticFields();
       } else {
-        activeStaticFieldValues.remove(field);
+        activeState.removeNonFinalStaticField(field);
       }
     } else if (instruction.isStaticGet()) {
-      if (field.holder != code.method.method.holder) {
+      if (field.holder != code.method.holder()) {
         // Accessing a static field on a different object could cause <clinit> to run which
         // could modify any static field on any other object.
-        activeStaticFieldValues.clear();
+        activeState.clearNonFinalStaticFields();
       }
     } else if (instruction.isInstanceGet()) {
       throw new Unreachable();
     }
   }
 
-  // If a field get instruction throws an exception it did not have an effect on the
-  // value of the field. Therefore, when propagating across exceptional edges for a
-  // field get instruction we have to exclude that field from the set of known
-  // field values.
-  private void killActiveFieldsForExceptionalExit(FieldInstruction instruction) {
-    DexField field = instruction.getField();
-    if (instruction.isInstanceGet()) {
-      Value object = instruction.asInstanceGet().object().getAliasedValue();
-      FieldAndObject fieldAndObject = new FieldAndObject(field, object);
-      activeInstanceFieldValues.remove(fieldAndObject);
-    } else if (instruction.isStaticGet()) {
-      activeStaticFieldValues.remove(field);
-    }
-  }
+  static class State {
 
-  private void killActiveInitializedClassesForExceptionalExit(InitClass instruction) {
-    activeInitializedClasses.remove(instruction.getClassValue());
+    private final Map<FieldAndObject, FieldValue> finalInstanceFieldValues = new HashMap<>();
+
+    private final Map<DexField, FieldValue> finalStaticFieldValues = new IdentityHashMap<>();
+
+    private final Set<DexType> initializedClasses = Sets.newIdentityHashSet();
+
+    private final Map<FieldAndObject, FieldValue> nonFinalInstanceFieldValues = new HashMap<>();
+
+    private final Map<DexField, FieldValue> nonFinalStaticFieldValues = new IdentityHashMap<>();
+
+    public State() {}
+
+    public State(State state) {
+      finalInstanceFieldValues.putAll(state.finalInstanceFieldValues);
+      finalStaticFieldValues.putAll(state.finalStaticFieldValues);
+      initializedClasses.addAll(state.initializedClasses);
+      nonFinalInstanceFieldValues.putAll(state.nonFinalInstanceFieldValues);
+      nonFinalStaticFieldValues.putAll(state.nonFinalStaticFieldValues);
+    }
+
+    public void clearNonFinalInstanceFields() {
+      nonFinalInstanceFieldValues.clear();
+    }
+
+    public void clearNonFinalStaticFields() {
+      nonFinalStaticFieldValues.clear();
+    }
+
+    public FieldValue getInstanceFieldValue(FieldAndObject field) {
+      FieldValue value = nonFinalInstanceFieldValues.get(field);
+      return value != null ? value : finalInstanceFieldValues.get(field);
+    }
+
+    public FieldValue getStaticFieldValue(DexField field) {
+      FieldValue value = nonFinalStaticFieldValues.get(field);
+      return value != null ? value : finalStaticFieldValues.get(field);
+    }
+
+    public void intersect(State state) {
+      intersectFieldValues(finalInstanceFieldValues, state.finalInstanceFieldValues);
+      intersectFieldValues(finalStaticFieldValues, state.finalStaticFieldValues);
+      intersectInitializedClasses(initializedClasses, state.initializedClasses);
+      intersectFieldValues(nonFinalInstanceFieldValues, state.nonFinalInstanceFieldValues);
+      intersectFieldValues(nonFinalStaticFieldValues, state.nonFinalStaticFieldValues);
+    }
+
+    private static <K> void intersectFieldValues(
+        Map<K, FieldValue> fieldValues, Map<K, FieldValue> other) {
+      fieldValues.entrySet().removeIf(entry -> other.get(entry.getKey()) != entry.getValue());
+    }
+
+    private static void intersectInitializedClasses(
+        Set<DexType> initializedClasses, Set<DexType> other) {
+      initializedClasses.removeIf(not(other::contains));
+    }
+
+    public boolean isClassInitialized(DexType clazz) {
+      return initializedClasses.contains(clazz);
+    }
+
+    // If a field get instruction throws an exception it did not have an effect on the value of the
+    // field. Therefore, when propagating across exceptional edges for a field get instruction we
+    // have to exclude that field from the set of known field values.
+    public void killActiveFieldsForExceptionalExit(FieldInstruction instruction) {
+      DexField field = instruction.getField();
+      if (instruction.isInstanceGet()) {
+        Value object = instruction.asInstanceGet().object().getAliasedValue();
+        FieldAndObject fieldAndObject = new FieldAndObject(field, object);
+        removeNonFinalInstanceField(fieldAndObject);
+      } else if (instruction.isStaticGet()) {
+        removeNonFinalStaticField(field);
+      }
+    }
+
+    private void killActiveInitializedClassesForExceptionalExit(InitClass instruction) {
+      initializedClasses.remove(instruction.getClassValue());
+    }
+
+    public void markClassAsInitialized(DexType clazz) {
+      initializedClasses.add(clazz);
+    }
+
+    public void removeInstanceField(FieldAndObject field) {
+      removeFinalInstanceField(field);
+      removeNonFinalInstanceField(field);
+    }
+
+    public void removeFinalInstanceField(FieldAndObject field) {
+      finalInstanceFieldValues.remove(field);
+    }
+
+    public void removeNonFinalInstanceField(FieldAndObject field) {
+      nonFinalInstanceFieldValues.remove(field);
+    }
+
+    public void removeNonFinalInstanceFields(DexField field) {
+      nonFinalInstanceFieldValues.keySet().removeIf(key -> key.field == field);
+    }
+
+    public void removeStaticField(DexField field) {
+      removeFinalStaticField(field);
+      removeNonFinalStaticField(field);
+    }
+
+    public void removeFinalStaticField(DexField field) {
+      finalStaticFieldValues.remove(field);
+    }
+
+    public void removeNonFinalStaticField(DexField field) {
+      nonFinalStaticFieldValues.remove(field);
+    }
+
+    public void putFinalInstanceField(FieldAndObject field, FieldValue value) {
+      finalInstanceFieldValues.put(field, value);
+    }
+
+    public void putFinalStaticField(DexField field, FieldValue value) {
+      finalStaticFieldValues.put(field, value);
+    }
+
+    public void putNonFinalInstanceField(FieldAndObject field, FieldValue value) {
+      assert !finalInstanceFieldValues.containsKey(field);
+      nonFinalInstanceFieldValues.put(field, value);
+    }
+
+    public void putNonFinalStaticField(DexField field, FieldValue value) {
+      assert !nonFinalStaticFieldValues.containsKey(field);
+      nonFinalStaticFieldValues.put(field, value);
+    }
   }
 }
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 1b56e8c..1e19dea 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
@@ -36,7 +36,7 @@
   public static void rewriteGetClassOrForNameToConstClass(
       AppView<AppInfoWithLiveness> appView, IRCode code) {
     Set<Value> affectedValues = Sets.newIdentityHashSet();
-    DexType context = code.method.method.holder;
+    DexType context = code.method.holder();
     ClassInitializationAnalysis classInitializationAnalysis =
         new ClassInitializationAnalysis(appView, code);
     for (BasicBlock block : code.blocks) {
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/UninstantiatedTypeOptimization.java b/src/main/java/com/android/tools/r8/ir/optimize/UninstantiatedTypeOptimization.java
index 8e0858a..6b854e6 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/UninstantiatedTypeOptimization.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/UninstantiatedTypeOptimization.java
@@ -351,8 +351,7 @@
         Instruction instruction = instructionIterator.next();
         if (instruction.throwsOnNullInput()) {
           Value couldBeNullValue = instruction.getNonNullInput();
-          if (isThrowNullCandidate(
-              couldBeNullValue, instruction, appView, code.method.method.holder)) {
+          if (isThrowNullCandidate(couldBeNullValue, instruction, appView, code.method.holder())) {
             if (instruction.isInstanceGet() || instruction.isInstancePut()) {
               ++numberOfInstanceGetOrInstancePutWithNullReceiver;
             } else if (instruction.isInvokeMethodWithReceiver()) {
@@ -451,7 +450,7 @@
       IRCode code,
       AssumeDynamicTypeRemover assumeDynamicTypeRemover,
       Set<Value> affectedValues) {
-    DexType context = code.method.method.holder;
+    DexType context = code.method.holder();
     DexField field = instruction.getField();
     DexType fieldType = field.type;
     if (fieldType.isAlwaysNull(appView)) {
@@ -507,7 +506,7 @@
       AssumeDynamicTypeRemover assumeDynamicTypeRemover,
       Set<BasicBlock> blocksToBeRemoved,
       Set<Value> affectedValues) {
-    DexEncodedMethod target = invoke.lookupSingleTarget(appView, code.method.method.holder);
+    DexEncodedMethod target = invoke.lookupSingleTarget(appView, code.method.holder());
     if (target == null) {
       return;
     }
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/UnusedArgumentsCollector.java b/src/main/java/com/android/tools/r8/ir/optimize/UnusedArgumentsCollector.java
index db393ed..7b60b90 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/UnusedArgumentsCollector.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/UnusedArgumentsCollector.java
@@ -157,8 +157,7 @@
           // Constructors must be named `<init>`.
           return null;
         }
-        newSignature =
-            appView.dexItemFactory().createMethod(method.method.holder, newProto, newName);
+        newSignature = appView.dexItemFactory().createMethod(method.holder(), newProto, newName);
         count++;
       } while (!isMethodSignatureAvailable(newSignature));
       return newSignature;
@@ -198,8 +197,7 @@
           // Constructors must be named `<init>`.
           return null;
         }
-        newSignature =
-            appView.dexItemFactory().createMethod(method.method.holder, newProto, newName);
+        newSignature = appView.dexItemFactory().createMethod(method.holder(), newProto, newName);
         count++;
       } while (methodPool.hasSeen(equivalence.wrap(newSignature)));
       return newSignature;
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 81f68de..7f0ad04 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
@@ -160,7 +160,7 @@
       if (eligibleClass.classInitializationMayHaveSideEffects(
           appView,
           // Types that are a super type of the current context are guaranteed to be initialized.
-          type -> appView.isSubtype(method.method.holder, type).isTrue(),
+          type -> appView.isSubtype(method.holder(), type).isTrue(),
           Sets.newIdentityHashSet())) {
         return EligibilityStatus.HAS_CLINIT;
       }
@@ -170,7 +170,7 @@
     assert root.isStaticGet();
 
     StaticGet staticGet = root.asStaticGet();
-    if (staticGet.instructionMayHaveSideEffects(appView, method.method.holder)) {
+    if (staticGet.instructionMayHaveSideEffects(appView, method.holder())) {
       return EligibilityStatus.RETRIEVAL_MAY_HAVE_SIDE_EFFECTS;
     }
     DexEncodedField field = appView.appInfo().resolveField(staticGet.getField());
@@ -265,8 +265,7 @@
 
         if (user.isInvokeMethod()) {
           InvokeMethod invokeMethod = user.asInvokeMethod();
-          DexEncodedMethod singleTarget =
-              invokeMethod.lookupSingleTarget(appView, method.method.holder);
+          DexEncodedMethod singleTarget = invokeMethod.lookupSingleTarget(appView, method.holder());
           if (singleTarget == null) {
             return user; // Not eligible.
           }
@@ -507,8 +506,7 @@
             continue;
           }
 
-          DexEncodedMethod singleTarget =
-              invoke.lookupSingleTarget(appView, code.method.method.holder);
+          DexEncodedMethod singleTarget = invoke.lookupSingleTarget(appView, code.method.holder());
           if (singleTarget == null || !indirectMethodCallsOnInstance.contains(singleTarget)) {
             throw new IllegalClassInlinerStateException();
           }
@@ -569,7 +567,7 @@
           continue;
         }
 
-        DexEncodedMethod singleTarget = invoke.lookupSingleTarget(appView, method.method.holder);
+        DexEncodedMethod singleTarget = invoke.lookupSingleTarget(appView, method.holder());
         if (singleTarget != null) {
           Predicate<InvokeMethod> noSideEffectsPredicate =
               dexItemFactory.libraryMethodsWithoutSideEffects.getOrDefault(
@@ -1156,7 +1154,7 @@
       }
 
       // Check if the method is inline-able by standard inliner.
-      DexEncodedMethod singleTarget = invoke.lookupSingleTarget(appView, method.method.holder);
+      DexEncodedMethod singleTarget = invoke.lookupSingleTarget(appView, method.holder());
       if (singleTarget == null) {
         return false;
       }
@@ -1190,7 +1188,7 @@
   }
 
   private boolean exemptFromInstructionLimit(DexEncodedMethod inlinee) {
-    DexType inlineeHolder = inlinee.method.holder;
+    DexType inlineeHolder = inlinee.holder();
     DexClass inlineeClass = appView.definitionFor(inlineeHolder);
     assert inlineeClass != null;
 
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 84b952b..ea9141f 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
@@ -4,6 +4,8 @@
 
 package com.android.tools.r8.ir.optimize.enums;
 
+import static com.android.tools.r8.ir.analysis.type.Nullability.definitelyNotNull;
+
 import com.android.tools.r8.graph.AppView;
 import com.android.tools.r8.graph.DexApplication;
 import com.android.tools.r8.graph.DexClass;
@@ -28,6 +30,7 @@
 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.CheckCast;
 import com.android.tools.r8.ir.code.ConstClass;
 import com.android.tools.r8.ir.code.FieldInstruction;
 import com.android.tools.r8.ir.code.IRCode;
@@ -86,13 +89,14 @@
       debugLogEnabled = false;
       debugLogs = null;
     }
+    assert !appView.options().debug;
     enumsUnboxingCandidates = new EnumUnboxingCandidateAnalysis(appView, this).findCandidates();
   }
 
   public void analyzeEnums(IRCode code) {
     // Enum <clinit> and <init> are analyzed in between the two processing phases using optimization
     // feedback. Methods valueOf and values are generated by javac and are analyzed differently.
-    DexClass dexClass = appView.definitionFor(code.method.method.holder);
+    DexClass dexClass = appView.definitionFor(code.method.holder());
     if (dexClass.isEnum()
         && (code.method.isInitializer()
             || appView.dexItemFactory().enumMethods.isValueOfMethod(code.method.method, dexClass)
@@ -140,11 +144,6 @@
           if (enumClass != null) {
             Reason reason = validateEnumUsages(code, outValue, enumClass);
             if (reason == Reason.ELIGIBLE) {
-              if (instruction.isCheckCast()) {
-                // We are doing a type check, which typically means the in-value is of an upper
-                // type and cannot be dealt with.
-                markEnumAsUnboxable(Reason.DOWN_CAST, enumClass);
-              }
               eligibleEnums.add(enumClass.type);
             }
           }
@@ -152,15 +151,10 @@
             addNullDependencies(outValue.uniqueUsers(), eligibleEnums);
           }
         }
-        // If we have a ConstClass referencing directly an enum, it cannot be unboxed, except if
-        // the constClass is in an enum valueOf method (in this case the valueOf method will be
-        // removed or the enum will be marked as non unboxable).
         if (instruction.isConstClass()) {
-          ConstClass constClass = instruction.asConstClass();
-          if (enumsUnboxingCandidates.containsKey(constClass.getValue())) {
-            markEnumAsUnboxable(
-                Reason.CONST_CLASS, appView.definitionForProgramType(constClass.getValue()));
-          }
+          analyzeConstClass(instruction.asConstClass());
+        } else if (instruction.isCheckCast()) {
+          analyzeCheckCast(instruction.asCheckCast());
         } 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
@@ -204,6 +198,48 @@
     }
   }
 
+  private void analyzeCheckCast(CheckCast checkCast) {
+    // We are doing a type check, which typically means the in-value is of an upper
+    // type and cannot be dealt with.
+    // If the cast is on a dynamically typed object, the checkCast can be simply removed.
+    // This allows enum array clone and valueOf to work correctly.
+    TypeElement objectType = checkCast.object().getDynamicUpperBoundType(appView);
+    if (objectType.equalUpToNullability(
+        TypeElement.fromDexType(checkCast.getType(), definitelyNotNull(), appView))) {
+      return;
+    }
+    DexProgramClass enumClass =
+        getEnumUnboxingCandidateOrNull(checkCast.getType().toBaseType(factory));
+    if (enumClass != null) {
+      markEnumAsUnboxable(Reason.DOWN_CAST, enumClass);
+    }
+  }
+
+  private void analyzeConstClass(ConstClass constClass) {
+    // We are using the ConstClass of an enum, which typically means the enum cannot be unboxed.
+    // We however allow unboxing if the ConstClass is only used as an argument to Enum#valueOf, to
+    // allow unboxing of: MyEnum a = Enum.valueOf(MyEnum.class, "A");.
+    if (!enumsUnboxingCandidates.containsKey(constClass.getValue())) {
+      return;
+    }
+    if (constClass.outValue() == null) {
+      return;
+    }
+    if (constClass.outValue().hasPhiUsers()) {
+      markEnumAsUnboxable(
+          Reason.CONST_CLASS, appView.definitionForProgramType(constClass.getValue()));
+      return;
+    }
+    for (Instruction user : constClass.outValue().uniqueUsers()) {
+      if (!(user.isInvokeStatic()
+          && user.asInvokeStatic().getInvokedMethod() == factory.enumMethods.valueOf)) {
+        markEnumAsUnboxable(
+            Reason.CONST_CLASS, appView.definitionForProgramType(constClass.getValue()));
+        return;
+      }
+    }
+  }
+
   private void addNullDependencies(Set<Instruction> uses, Set<DexType> eligibleEnums) {
     for (Instruction use : uses) {
       if (use.isInvokeMethod()) {
@@ -349,7 +385,7 @@
         return Reason.INVALID_INVOKE_ON_ARRAY;
       }
       DexEncodedMethod encodedSingleTarget =
-          invokeMethod.lookupSingleTarget(appView, code.method.method.holder);
+          invokeMethod.lookupSingleTarget(appView, code.method.holder());
       if (encodedSingleTarget == null) {
         return Reason.INVALID_INVOKE;
       }
@@ -359,14 +395,6 @@
         return Reason.INVALID_INVOKE;
       }
       if (dexClass.isProgramClass()) {
-        // All invokes in the program are generally valid, but specific care is required
-        // for values() and valueOf().
-        if (dexClass.isEnum() && factory.enumMethods.isValuesMethod(singleTarget, dexClass)) {
-          return Reason.VALUES_INVOKE;
-        }
-        if (dexClass.isEnum() && factory.enumMethods.isValueOfMethod(singleTarget, dexClass)) {
-          return Reason.VALUE_OF_INVOKE;
-        }
         int offset = BooleanUtils.intValue(!encodedSingleTarget.isStatic());
         for (int i = 0; i < singleTarget.proto.parameters.size(); i++) {
           if (invokeMethod.inValues().get(offset + i) == enumValue) {
@@ -384,8 +412,8 @@
       if (dexClass.type != factory.enumType) {
         return Reason.UNSUPPORTED_LIBRARY_CALL;
       }
-      // TODO(b/147860220): Methods toString(), name(), compareTo(), EnumSet and EnumMap may be
-      // interesting to model. A the moment rewrite only Enum#ordinal().
+      // TODO(b/147860220): Methods toString(), name(), compareTo(), EnumSet and EnumMap may
+      // be interesting to model. A the moment rewrite only Enum#ordinal() and Enum#valueOf.
       if (debugLogEnabled) {
         if (singleTarget == factory.enumMethods.compareTo) {
           return Reason.COMPARE_TO_INVOKE;
@@ -397,10 +425,10 @@
           return Reason.TO_STRING_INVOKE;
         }
       }
-      if (singleTarget != factory.enumMethods.ordinal) {
-        return Reason.UNSUPPORTED_LIBRARY_CALL;
+      if (singleTarget == factory.enumMethods.ordinal) {
+        return Reason.ELIGIBLE;
       }
-      return Reason.ELIGIBLE;
+      return Reason.UNSUPPORTED_LIBRARY_CALL;
     }
 
     // A field put is valid only if the field is not on an enum, and the field type and the valuePut
@@ -411,7 +439,7 @@
       if (field == null) {
         return Reason.INVALID_FIELD_PUT;
       }
-      DexProgramClass dexClass = appView.definitionForProgramType(field.field.holder);
+      DexProgramClass dexClass = appView.definitionForProgramType(field.holder());
       if (dexClass == null) {
         return Reason.INVALID_FIELD_PUT;
       }
@@ -603,7 +631,7 @@
       for (DexProgramClass clazz : appView.appInfo().classes()) {
         if (enumsToUnbox.contains(clazz.type)) {
           assert clazz.instanceFields().size() == 0;
-          clearEnumtoUnboxMethods(clazz);
+          clearEnumToUnboxMethods(clazz);
         } else {
           clazz.getMethodCollection().replaceMethods(this::fixupMethod);
           fixupFields(clazz.staticFields(), clazz::setStaticField);
@@ -616,7 +644,7 @@
       return lensBuilder.build(factory, appView.graphLense());
     }
 
-    private void clearEnumtoUnboxMethods(DexProgramClass clazz) {
+    private void clearEnumToUnboxMethods(DexProgramClass clazz) {
       // The compiler may have references to the enum methods, but such methods will be removed
       // and they cannot be reprocessed since their rewriting through the lensCodeRewriter/
       // enumUnboxerRewriter will generate invalid code.
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/enums/EnumUnboxingCfMethods.java b/src/main/java/com/android/tools/r8/ir/optimize/enums/EnumUnboxingCfMethods.java
index 0bf0aed..a09ea37 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/enums/EnumUnboxingCfMethods.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/enums/EnumUnboxingCfMethods.java
@@ -26,6 +26,7 @@
 import com.android.tools.r8.cf.code.CfStore;
 import com.android.tools.r8.cf.code.CfThrow;
 import com.android.tools.r8.graph.CfCode;
+import com.android.tools.r8.graph.DexItemFactory;
 import com.android.tools.r8.graph.DexMethod;
 import com.android.tools.r8.ir.code.If;
 import com.android.tools.r8.ir.code.MemberType;
@@ -36,6 +37,10 @@
 
 public final class EnumUnboxingCfMethods {
 
+  public static void registerSynthesizedCodeReferences(DexItemFactory factory) {
+    factory.createSynthesizedType("Ljava/lang/NullPointerException;");
+  }
+
   public static CfCode EnumUnboxingMethods_compareTo(InternalOptions options, DexMethod method) {
     CfLabel label0 = new CfLabel();
     CfLabel label1 = new CfLabel();
@@ -52,13 +57,12 @@
             new CfLoad(ValueType.INT, 1),
             new CfIf(If.Type.NE, ValueType.INT, label2),
             label1,
-            new CfNew(
-                options.itemFactory.createSynthesizedType("Ljava/lang/NullPointerException;")),
+            new CfNew(options.itemFactory.createType("Ljava/lang/NullPointerException;")),
             new CfStackInstruction(CfStackInstruction.Opcode.Dup),
             new CfInvoke(
                 183,
                 options.itemFactory.createMethod(
-                    options.itemFactory.createSynthesizedType("Ljava/lang/NullPointerException;"),
+                    options.itemFactory.createType("Ljava/lang/NullPointerException;"),
                     options.itemFactory.createProto(options.itemFactory.voidType),
                     options.itemFactory.createString("<init>")),
                 false),
@@ -89,13 +93,12 @@
             new CfLoad(ValueType.INT, 0),
             new CfIf(If.Type.NE, ValueType.INT, label2),
             label1,
-            new CfNew(
-                options.itemFactory.createSynthesizedType("Ljava/lang/NullPointerException;")),
+            new CfNew(options.itemFactory.createType("Ljava/lang/NullPointerException;")),
             new CfStackInstruction(CfStackInstruction.Opcode.Dup),
             new CfInvoke(
                 183,
                 options.itemFactory.createMethod(
-                    options.itemFactory.createSynthesizedType("Ljava/lang/NullPointerException;"),
+                    options.itemFactory.createType("Ljava/lang/NullPointerException;"),
                     options.itemFactory.createProto(options.itemFactory.voidType),
                     options.itemFactory.createString("<init>")),
                 false),
@@ -129,13 +132,12 @@
             new CfLoad(ValueType.INT, 0),
             new CfIf(If.Type.NE, ValueType.INT, label2),
             label1,
-            new CfNew(
-                options.itemFactory.createSynthesizedType("Ljava/lang/NullPointerException;")),
+            new CfNew(options.itemFactory.createType("Ljava/lang/NullPointerException;")),
             new CfStackInstruction(CfStackInstruction.Opcode.Dup),
             new CfInvoke(
                 183,
                 options.itemFactory.createMethod(
-                    options.itemFactory.createSynthesizedType("Ljava/lang/NullPointerException;"),
+                    options.itemFactory.createType("Ljava/lang/NullPointerException;"),
                     options.itemFactory.createProto(options.itemFactory.voidType),
                     options.itemFactory.createString("<init>")),
                 false),
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 0d065ff..73eff15 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
@@ -24,27 +24,28 @@
 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.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;
+import com.android.tools.r8.ir.code.Value;
 import com.android.tools.r8.ir.conversion.IRConverter;
+import com.android.tools.r8.ir.synthetic.EnumUnboxingCfCodeProvider;
 import com.android.tools.r8.origin.SynthesizedOrigin;
 import com.android.tools.r8.shaking.AppInfoWithLiveness;
 import com.google.common.collect.Sets;
 import java.util.ArrayList;
 import java.util.Collections;
 import java.util.List;
+import java.util.Map;
 import java.util.Set;
+import java.util.concurrent.ConcurrentHashMap;
 import java.util.concurrent.ExecutionException;
 import java.util.concurrent.ExecutorService;
 
@@ -56,6 +57,7 @@
   private final AppView<AppInfoWithLiveness> appView;
   private final DexItemFactory factory;
   private final EnumValueInfoMapCollection enumsToUnbox;
+  private final Map<DexMethod, DexType> extraMethods = new ConcurrentHashMap<>();
 
   private final DexType utilityClassType;
   private final DexMethod ordinalUtilityMethod;
@@ -71,7 +73,7 @@
     }
     this.enumsToUnbox = builder.build();
 
-    this.utilityClassType = factory.createType("L" + ENUM_UNBOXING_UTILITY_CLASS_NAME + ";");
+    this.utilityClassType = factory.enumUnboxingUtilityType;
     this.ordinalUtilityMethod =
         factory.createMethod(
             utilityClassType,
@@ -89,6 +91,7 @@
     if (enumsToUnbox.isEmpty()) {
       return;
     }
+    assert code.isConsistentSSABeforeTypesAreCorrect();
     Set<Phi> affectedPhis = Sets.newIdentityHashSet();
     InstructionListIterator iterator = code.instructionListIterator();
     while (iterator.hasNext()) {
@@ -99,14 +102,38 @@
         InvokeMethodWithReceiver invokeMethod = instruction.asInvokeMethodWithReceiver();
         DexMethod invokedMethod = invokeMethod.getInvokedMethod();
         if (invokedMethod == factory.enumMethods.ordinal
-            && invokeMethod.getReceiver().getType().isInt()) {
+            && isEnumToUnboxOrInt(invokeMethod.getReceiver().getType())) {
           instruction =
               new InvokeStatic(
                   ordinalUtilityMethod, invokeMethod.outValue(), invokeMethod.inValues());
           iterator.replaceCurrentInstruction(instruction);
           requiresOrdinalUtilityMethod = true;
+          continue;
         }
         // TODO(b/147860220): rewrite also other enum methods.
+      } else if (instruction.isInvokeStatic()) {
+        InvokeStatic invokeStatic = instruction.asInvokeStatic();
+        DexMethod invokedMethod = invokeStatic.getInvokedMethod();
+        if (invokedMethod == factory.enumMethods.valueOf
+            && invokeStatic.inValues().get(0).isConstClass()) {
+          DexType enumType =
+              invokeStatic.inValues().get(0).getConstInstruction().asConstClass().getValue();
+          if (enumsToUnbox.containsEnum(enumType)) {
+            DexMethod valueOfMethod = computeValueOfUtilityMethod(enumType);
+            Value outValue = invokeStatic.outValue();
+            Value rewrittenOutValue = null;
+            if (outValue != null) {
+              rewrittenOutValue = code.createValue(TypeElement.getInt());
+              affectedPhis.addAll(outValue.uniquePhiUsers());
+            }
+            iterator.replaceCurrentInstruction(
+                new InvokeStatic(
+                    valueOfMethod,
+                    rewrittenOutValue,
+                    Collections.singletonList(invokeStatic.inValues().get(1))));
+            continue;
+          }
+        }
       }
       // Rewrites direct access to enum values into the corresponding int, $VALUES is not
       // supported.
@@ -124,10 +151,8 @@
           EnumValueInfo enumValueInfo = enumValueInfoMap.getEnumValueInfo(staticGet.getField());
           assert enumValueInfo != null
               : "Invalid read to " + staticGet.getField().name + ", error during enum analysis";
-          instruction = new ConstNumber(staticGet.outValue(), enumValueInfo.convertToInt());
-          staticGet.outValue().setType(PrimitiveTypeElement.fromNumericType(NumericType.INT));
-          iterator.replaceCurrentInstruction(instruction);
           affectedPhis.addAll(staticGet.outValue().uniquePhiUsers());
+          iterator.replaceCurrentInstruction(code.createIntConstant(enumValueInfo.convertToInt()));
         }
       }
       // Rewrite array accesses from MyEnum[] (OBJECT) to int[] (INT).
@@ -137,8 +162,8 @@
           instruction = arrayAccess.withMemberType(MemberType.INT);
           iterator.replaceCurrentInstruction(instruction);
         }
+        assert validateArrayAccess(instruction.asArrayAccess());
       }
-      assert validateEnumToUnboxRemoved(instruction);
     }
     if (!affectedPhis.isEmpty()) {
       new DestructivePhiTypeUpdater(appView).recomputeAndPropagateTypes(code, affectedPhis);
@@ -146,33 +171,43 @@
     assert code.isConsistentSSABeforeTypesAreCorrect();
   }
 
-  private boolean shouldRewriteArrayAccess(ArrayAccess arrayAccess) {
+  private boolean validateArrayAccess(ArrayAccess arrayAccess) {
     ArrayTypeElement arrayType = arrayAccess.array().getType().asArrayType();
-    return arrayAccess.getMemberType() == MemberType.OBJECT
-        && arrayType.getNesting() == 1
-        && arrayType.getBaseType().isInt();
+    assert arrayAccess.getMemberType() != MemberType.OBJECT
+        || arrayType.getNesting() > 1
+        || arrayType.getBaseType().isReferenceType();
+    return true;
   }
 
-  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) {
+  private boolean isEnumToUnboxOrInt(TypeElement type) {
+    if (type.isInt()) {
       return true;
     }
-    TypeElement typeLattice = instruction.outValue().getType();
-    assert !typeLattice.isClassType()
-        || !enumsToUnbox.containsEnum(typeLattice.asClassType().getClassType());
-    if (typeLattice.isArrayType()) {
-      TypeElement arrayBaseTypeLattice = typeLattice.asArrayType().getBaseType();
-      assert !arrayBaseTypeLattice.isClassType()
-          || !enumsToUnbox.containsEnum(arrayBaseTypeLattice.asClassType().getClassType());
+    if (!type.isClassType()) {
+      return false;
     }
-    return true;
+    return enumsToUnbox.containsEnum(type.asClassType().getClassType());
+  }
+
+  private DexMethod computeValueOfUtilityMethod(DexType type) {
+    assert enumsToUnbox.containsEnum(type);
+    DexMethod valueOf =
+        factory.createMethod(
+            utilityClassType,
+            factory.createProto(factory.intType, factory.stringType),
+            "valueOf" + type.toSourceString().replace('.', '$'));
+    extraMethods.putIfAbsent(valueOf, type);
+    return valueOf;
+  }
+
+  private boolean shouldRewriteArrayAccess(ArrayAccess arrayAccess) {
+    ArrayTypeElement arrayType = arrayAccess.array().getType().asArrayType();
+    if (arrayType.getNesting() != 1) {
+      return false;
+    }
+    TypeElement baseType = arrayType.getBaseType();
+    return baseType.isClassType()
+        && enumsToUnbox.containsEnum(baseType.asClassType().getClassType());
   }
 
   // TODO(b/150172351): Synthesize the utility class upfront in the enqueuer.
@@ -182,6 +217,11 @@
     // Synthesize a class which holds various utility methods that may be called from the IR
     // rewriting. If any of these methods are not used, they will be removed by the Enqueuer.
     List<DexEncodedMethod> requiredMethods = new ArrayList<>();
+    extraMethods.forEach(
+        (method, enumType) -> {
+          requiredMethods.add(synthesizeValueOfUtilityMethod(method, enumType));
+        });
+    requiredMethods.sort((m1, m2) -> m1.method.name.slowCompareTo(m2.method.name));
     if (requiresOrdinalUtilityMethod) {
       requiredMethods.add(synthesizeOrdinalMethod());
     }
@@ -215,6 +255,21 @@
     converter.optimizeSynthesizedClass(utilityClass, executorService);
   }
 
+  private DexEncodedMethod synthesizeValueOfUtilityMethod(DexMethod method, DexType enumType) {
+    CfCode cfCode =
+        new EnumUnboxingCfCodeProvider.EnumUnboxingValueOfCfCodeProvider(
+                appView, utilityClassType, enumType, enumsToUnbox.getEnumValueInfoMap(enumType))
+            .generateCfCode();
+    return new DexEncodedMethod(
+        method,
+        synthesizedMethodAccessFlags(),
+        DexAnnotationSet.empty(),
+        ParameterAnnotationsList.empty(),
+        cfCode,
+        REQUIRED_CLASS_FILE_VERSION,
+        true);
+  }
+
   // TODO(b/150178516): Add a test for this case.
   private boolean utilityClassInMainDexList() {
     for (DexType toUnbox : enumsToUnbox.enumSet()) {
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 ba24e19..791c889 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
@@ -85,7 +85,7 @@
     TypeElement[] staticTypes = new TypeElement[size];
     if (!encodedMethod.isStatic()) {
       staticTypes[0] =
-          TypeElement.fromDexType(encodedMethod.method.holder, definitelyNotNull(), appView);
+          TypeElement.fromDexType(encodedMethod.holder(), definitelyNotNull(), appView);
     }
     for (int i = 0; i < encodedMethod.method.getArity(); i++) {
       staticTypes[i + argOffset] =
@@ -168,7 +168,7 @@
         Value aliasedValue = arg.getAliasedValue();
         if (!aliasedValue.isPhi()) {
           AbstractValue abstractValue =
-              aliasedValue.definition.getAbstractValue(appView, method.method.holder);
+              aliasedValue.definition.getAbstractValue(appView, method.holder());
           if (abstractValue.isNonTrivial()) {
             newCallSiteInfo.constants.put(i, abstractValue);
           }
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 19c897f..4889b2e 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
@@ -177,7 +177,7 @@
       return;
     }
 
-    DexClass clazz = appView.definitionFor(method.method.holder);
+    DexClass clazz = appView.definitionFor(method.holder());
     if (clazz == null) {
       return;
     }
@@ -241,8 +241,7 @@
         case INVOKE_STATIC:
           {
             InvokeStatic invoke = insn.asInvokeStatic();
-            DexEncodedMethod singleTarget =
-                invoke.lookupSingleTarget(appView, method.method.holder);
+            DexEncodedMethod singleTarget = invoke.lookupSingleTarget(appView, method.holder());
             if (singleTarget == null) {
               return; // Not allowed.
             }
@@ -263,7 +262,7 @@
             DexMethod invokedMethod = invoke.getInvokedMethod();
             DexType returnType = invokedMethod.proto.returnType;
             if (returnType.isClassType()
-                && appView.appInfo().isRelatedBySubtyping(returnType, method.method.holder)) {
+                && appView.appInfo().isRelatedBySubtyping(returnType, method.holder())) {
               return; // Not allowed, could introduce an alias of the receiver.
             }
             callsReceiver.add(new Pair<>(Invoke.Type.VIRTUAL, invokedMethod));
@@ -373,7 +372,7 @@
         if (definition.isArgument()) {
           feedback.methodReturnsArgument(method, definition.asArgument().getIndex());
         }
-        DexType context = method.method.holder;
+        DexType context = method.holder();
         AbstractValue abstractReturnValue = definition.getAbstractValue(appView, context);
         if (abstractReturnValue.isNonTrivial()) {
           feedback.methodReturnsAbstractValue(method, appView, abstractReturnValue);
@@ -417,7 +416,7 @@
       return;
     }
 
-    DexClass clazz = appView.appInfo().definitionFor(method.method.holder);
+    DexClass clazz = appView.appInfo().definitionFor(method.holder());
     if (clazz == null) {
       assert false;
       return;
@@ -687,7 +686,7 @@
     if (method.isStatic()) {
       // Identifies if the method preserves class initialization after inlining.
       feedback.markTriggerClassInitBeforeAnySideEffect(
-          method, triggersClassInitializationBeforeSideEffect(method.method.holder, code, appView));
+          method, triggersClassInitializationBeforeSideEffect(method.holder(), code, appView));
     } else {
       // Identifies if the method preserves null check of the receiver after inlining.
       final Value receiver = code.getThis();
@@ -707,7 +706,7 @@
     return alwaysTriggerExpectedEffectBeforeAnythingElse(
         code,
         (instruction, it) -> {
-          DexType context = code.method.method.holder;
+          DexType context = code.method.holder();
           if (instruction.definitelyTriggersClassInitialization(
               clazz, context, appView, DIRECTLY, AnalysisAssumption.INSTRUCTION_DOES_NOT_THROW)) {
             // In order to preserve class initialization semantic, the exception must not be caught
@@ -849,7 +848,7 @@
               // We found a NPE check on the value.
               return InstructionEffect.DESIRED_EFFECT;
             }
-          } else if (instr.instructionMayHaveSideEffects(appView, code.method.method.holder)) {
+          } else if (instr.instructionMayHaveSideEffects(appView, code.method.holder())) {
             // If the current instruction is const-string, this could load the parameter name.
             // Just make sure it is indeed not throwing.
             if (instr.isConstString() && !instr.instructionInstanceCanThrow()) {
@@ -1014,7 +1013,7 @@
     if (appView.appInfo().mayHaveSideEffects.containsKey(method.method)) {
       return;
     }
-    DexType context = method.method.holder;
+    DexType context = method.holder();
     if (method.isClassInitializer()) {
       // For class initializers, we also wish to compute if the class initializer has observable
       // side effects.
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/inliner/InliningIRProvider.java b/src/main/java/com/android/tools/r8/ir/optimize/inliner/InliningIRProvider.java
index 3a98637..608c41f 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/inliner/InliningIRProvider.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/inliner/InliningIRProvider.java
@@ -38,7 +38,7 @@
       return cached;
     }
     Position position = Position.getPositionForInlining(appView, invoke, context);
-    Origin origin = appView.appInfo().originFor(method.method.holder);
+    Origin origin = appView.appInfo().originFor(method.holder());
     return method.buildInliningIR(
         context, appView, valueNumberGenerator, position, origin, methodProcessor);
   }
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/inliner/WhyAreYouNotInliningReporter.java b/src/main/java/com/android/tools/r8/ir/optimize/inliner/WhyAreYouNotInliningReporter.java
index 1ff91c0..091a5e5 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/inliner/WhyAreYouNotInliningReporter.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/inliner/WhyAreYouNotInliningReporter.java
@@ -32,8 +32,7 @@
       return;
     }
 
-    Collection<DexEncodedMethod> possibleTargets =
-        invoke.lookupTargets(appView, context.method.holder);
+    Collection<DexEncodedMethod> possibleTargets = invoke.lookupTargets(appView, context.holder());
     if (possibleTargets == null) {
       // In principle, this invoke might target any method in the program, but we do not want to
       // report a message for each of the methods in `AppInfoWithLiveness#whyAreYouNotInlining`,
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/inliner/WhyAreYouNotInliningReporterImpl.java b/src/main/java/com/android/tools/r8/ir/optimize/inliner/WhyAreYouNotInliningReporterImpl.java
index 64026ca..b141817 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/inliner/WhyAreYouNotInliningReporterImpl.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/inliner/WhyAreYouNotInliningReporterImpl.java
@@ -220,7 +220,7 @@
         "final field `"
             + instancePut.getField()
             + "` must be initialized in a constructor of `"
-            + callee.method.holder.toSourceString()
+            + callee.holder().toSourceString()
             + "`.");
   }
 
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/lambda/CodeProcessor.java b/src/main/java/com/android/tools/r8/ir/optimize/lambda/CodeProcessor.java
index 1648958..eb8b742 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/lambda/CodeProcessor.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/lambda/CodeProcessor.java
@@ -218,7 +218,7 @@
 
   private boolean shouldRewrite(DexType type) {
     // Rewrite references to lambda classes if we are outside the class.
-    return type != (context != null ? context : method).method.holder;
+    return type != (context != null ? context : method).holder();
   }
 
   @Override
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 ce2394c..0bdaa57 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
@@ -133,7 +133,7 @@
         Inliner inliner,
         DexEncodedMethod context,
         InliningIRProvider provider) {
-      DexProgramClass clazz = appView.definitionFor(method.method.holder).asProgramClass();
+      DexProgramClass clazz = appView.definitionFor(method.holder()).asProgramClass();
       assert clazz != null;
 
       LambdaGroup lambdaGroup = lambdaGroups.get(clazz);
@@ -162,7 +162,7 @@
           assert resolution.isSingleResolution();
           DexEncodedMethod singleTarget = resolution.getSingleTarget();
           assert singleTarget != null;
-          invokesToInline.put(invoke, new InliningInfo(singleTarget, singleTarget.method.holder));
+          invokesToInline.put(invoke, new InliningInfo(singleTarget, singleTarget.holder()));
         }
       }
 
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 3e602b7..4d79186 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
@@ -54,7 +54,7 @@
     return field.name == context.kotlin.functional.kotlinStyleLambdaInstanceName
         && lambda == field.type
         && context.factory.isClassConstructor(context.method.method)
-        && context.method.method.holder == lambda;
+        && context.method.holder() == lambda;
   }
 
   @Override
@@ -100,10 +100,10 @@
     // Allow calls to a constructor from other classes if the lambda is singleton,
     // otherwise allow such a call only from the same class static initializer.
     boolean isSingletonLambda = group.isStateless() && group.isSingletonLambda(lambda);
-    return (isSingletonLambda == (context.method.method.holder == lambda)) &&
-        invoke.isInvokeDirect() &&
-        context.factory.isConstructor(method) &&
-        CaptureSignature.getCaptureSignature(method.proto.parameters).equals(group.id().capture);
+    return (isSingletonLambda == (context.method.holder() == lambda))
+        && invoke.isInvokeDirect()
+        && context.factory.isConstructor(method)
+        && CaptureSignature.getCaptureSignature(method.proto.parameters).equals(group.id().capture);
   }
 
   private boolean isValidVirtualCall(InvokeMethod invoke) {
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/library/BooleanMethodOptimizer.java b/src/main/java/com/android/tools/r8/ir/optimize/library/BooleanMethodOptimizer.java
index 31444c6..16d514c 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/library/BooleanMethodOptimizer.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/library/BooleanMethodOptimizer.java
@@ -70,7 +70,7 @@
       InvokeMethod invoke,
       Set<Value> affectedValues) {
     Value argument = invoke.arguments().get(0);
-    AbstractValue abstractValue = argument.getAbstractValue(appView, code.method.method.holder);
+    AbstractValue abstractValue = argument.getAbstractValue(appView, code.method.holder());
     if (abstractValue.isSingleNumberValue()) {
       instructionIterator.replaceCurrentInstructionWithStaticGet(
           appView,
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/library/EnumMethodOptimizer.java b/src/main/java/com/android/tools/r8/ir/optimize/library/EnumMethodOptimizer.java
new file mode 100644
index 0000000..fd9d62b
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/ir/optimize/library/EnumMethodOptimizer.java
@@ -0,0 +1,76 @@
+// 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.optimize.library;
+
+import static com.android.tools.r8.ir.analysis.type.Nullability.definitelyNotNull;
+
+import com.android.tools.r8.graph.AppView;
+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.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.IRCode;
+import com.android.tools.r8.ir.code.InstructionListIterator;
+import com.android.tools.r8.ir.code.InvokeMethod;
+import com.android.tools.r8.ir.code.Position;
+import com.android.tools.r8.ir.code.Value;
+import java.util.Set;
+
+public class EnumMethodOptimizer implements LibraryMethodModelCollection {
+  private final AppView<?> appView;
+
+  EnumMethodOptimizer(AppView<?> appView) {
+    this.appView = appView;
+  }
+
+  @Override
+  public DexType getType() {
+    return appView.dexItemFactory().enumType;
+  }
+
+  @Override
+  public void optimize(
+      IRCode code,
+      InstructionListIterator instructionIterator,
+      InvokeMethod invoke,
+      DexEncodedMethod singleTarget,
+      Set<Value> affectedValues) {
+    if (singleTarget.method == appView.dexItemFactory().enumMethods.valueOf
+        && invoke.inValues().get(0).isConstClass()) {
+      insertAssumeDynamicType(code, instructionIterator, invoke);
+    }
+  }
+
+  private void insertAssumeDynamicType(
+      IRCode code, InstructionListIterator instructionIterator, InvokeMethod invoke) {
+    // TODO(b/152516470): Support unboxing enums with Enum#valueOf in try-catch.
+    if (invoke.getBlock().hasCatchHandlers()) {
+      return;
+    }
+    DexType enumType = invoke.inValues().get(0).getConstInstruction().asConstClass().getValue();
+    DexProgramClass enumClass = appView.definitionForProgramType(enumType);
+    if (enumClass == null || enumClass.superType != appView.dexItemFactory().enumType) {
+      return;
+    }
+    TypeElement dynamicUpperBoundType =
+        TypeElement.fromDexType(enumType, definitelyNotNull(), appView);
+    Value outValue = invoke.outValue();
+    if (outValue == null) {
+      return;
+    }
+    // Replace usages of out-value by the out-value of the AssumeDynamicType instruction.
+    Value specializedOutValue = code.createValue(outValue.getType(), outValue.getLocalInfo());
+    outValue.replaceUsers(specializedOutValue);
+
+    // Insert AssumeDynamicType instruction.
+    Assume<DynamicTypeAssumption> assumeInstruction =
+        Assume.createAssumeDynamicTypeInstruction(
+            dynamicUpperBoundType, null, specializedOutValue, outValue, invoke, appView);
+    assumeInstruction.setPosition(appView.options().debug ? invoke.getPosition() : Position.none());
+    instructionIterator.add(assumeInstruction);
+  }
+}
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/library/LibraryMethodOptimizer.java b/src/main/java/com/android/tools/r8/ir/optimize/library/LibraryMethodOptimizer.java
index f3d7cc6..fc687a0 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/library/LibraryMethodOptimizer.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/library/LibraryMethodOptimizer.java
@@ -37,6 +37,9 @@
     register(new ObjectMethodOptimizer(appView));
     register(new ObjectsMethodOptimizer(appView));
     register(new StringMethodOptimizer(appView));
+    if (appView.appInfo().hasSubtyping() && appView.options().enableDynamicTypeOptimization) {
+      register(new EnumMethodOptimizer(appView));
+    }
 
     if (LogMethodOptimizer.isEnabled(appView)) {
       register(new LogMethodOptimizer(appView));
@@ -83,8 +86,7 @@
       Instruction instruction = instructionIterator.next();
       if (instruction.isInvokeMethod()) {
         InvokeMethod invoke = instruction.asInvokeMethod();
-        DexEncodedMethod singleTarget =
-            invoke.lookupSingleTarget(appView, code.method.method.holder);
+        DexEncodedMethod singleTarget = invoke.lookupSingleTarget(appView, code.method.holder());
         if (singleTarget != null) {
           optimizeInvoke(code, instructionIterator, invoke, singleTarget, affectedValues);
         }
@@ -103,7 +105,7 @@
       Set<Value> affectedValues) {
     LibraryMethodModelCollection optimizer =
         libraryMethodModelCollections.getOrDefault(
-            singleTarget.method.holder, NopLibraryMethodModelCollection.getInstance());
+            singleTarget.holder(), NopLibraryMethodModelCollection.getInstance());
     optimizer.optimize(code, instructionIterator, invoke, singleTarget, affectedValues);
   }
 }
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/library/StringMethodOptimizer.java b/src/main/java/com/android/tools/r8/ir/optimize/library/StringMethodOptimizer.java
index 501a761..09c8f4e 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/library/StringMethodOptimizer.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/library/StringMethodOptimizer.java
@@ -47,7 +47,7 @@
   private void optimizeEquals(
       IRCode code, InstructionListIterator instructionIterator, InvokeMethod invoke) {
     if (appView.appInfo().hasLiveness()) {
-      DexType context = code.method.method.holder;
+      DexType context = code.method.holder();
       Value first = invoke.arguments().get(0).getAliasedValue();
       Value second = invoke.arguments().get(1).getAliasedValue();
       if (isPrunedClassNameComparison(first, second, context)
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 127b144..3b93c50 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
@@ -113,7 +113,7 @@
     Instruction current = it.next();
     if (position != current.getPosition()
         || !current.isConstNumber()
-        || current.outValue().getType() != TypeElement.getInt()
+        || current.getOutType() != TypeElement.getInt()
         || current.asConstNumber().getIntValue() < -128
         || current.asConstNumber().getIntValue() > 127
         || !it.hasNext()) {
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/staticizer/ClassStaticizer.java b/src/main/java/com/android/tools/r8/ir/optimize/staticizer/ClassStaticizer.java
index 3c99a56..41df56a 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/staticizer/ClassStaticizer.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/staticizer/ClassStaticizer.java
@@ -30,6 +30,7 @@
 import com.android.tools.r8.ir.optimize.info.OptimizationFeedback;
 import com.android.tools.r8.shaking.AppInfoWithLiveness;
 import com.android.tools.r8.utils.ListUtils;
+import com.android.tools.r8.utils.SetUtils;
 import com.google.common.collect.Lists;
 import com.google.common.collect.Sets;
 import java.util.ArrayList;
@@ -78,11 +79,11 @@
     }
 
     boolean isHostClassInitializer(DexEncodedMethod method) {
-      return factory.isClassConstructor(method.method) && method.method.holder == hostType();
+      return factory.isClassConstructor(method.method) && method.holder() == hostType();
     }
 
     DexType hostType() {
-      return singletonField.field.holder;
+      return singletonField.holder();
     }
 
     DexClass hostClass() {
@@ -212,7 +213,7 @@
   public final void examineMethodCode(DexEncodedMethod method, IRCode code) {
     Set<Instruction> alreadyProcessed = Sets.newIdentityHashSet();
 
-    CandidateInfo receiverClassCandidateInfo = candidates.get(method.method.holder);
+    CandidateInfo receiverClassCandidateInfo = candidates.get(method.holder());
     Value receiverValue = code.getThis(); // NOTE: is null for static methods.
     if (receiverClassCandidateInfo != null) {
       if (receiverValue != null) {
@@ -224,13 +225,13 @@
 
         // If the candidate is still valid, ignore all instructions
         // we treat as valid usages on receiver.
-        if (candidates.get(method.method.holder) != null) {
+        if (candidates.get(method.holder()) != null) {
           alreadyProcessed.addAll(receiverValue.uniqueUsers());
         }
       } else {
         // We are inside a static method of candidate class.
         // Check if this is a valid getter of the singleton field.
-        if (method.method.proto.returnType == method.method.holder) {
+        if (method.method.proto.returnType == method.holder()) {
           List<Instruction> examined = isValidGetter(receiverClassCandidateInfo, code);
           if (examined != null) {
             DexEncodedMethod getter = receiverClassCandidateInfo.getter.get();
@@ -267,12 +268,13 @@
         NewInstance newInstance = instruction.asNewInstance();
         CandidateInfo candidateInfo = processInstantiation(method, iterator, newInstance);
         if (candidateInfo != null) {
+          alreadyProcessed.addAll(newInstance.outValue().aliasedUsers());
           // For host class initializers having eligible instantiation we also want to
           // ensure that the rest of the initializer consist of code w/o side effects.
           // This must guarantee that removing field access will not result in missing side
           // effects, otherwise we can still staticize, but cannot remove singleton reads.
           while (iterator.hasNext()) {
-            if (!isAllowedInHostClassInitializer(method.method.holder, iterator.next(), code)) {
+            if (!isAllowedInHostClassInitializer(method.holder(), iterator.next(), code)) {
               candidateInfo.preserveRead.set(true);
               iterator.previous();
               break;
@@ -396,10 +398,10 @@
       return candidateInfo.invalidate();
     }
 
-    if (candidateValue.numberOfUsers() != 2) {
-      // We expect only two users for each instantiation: constructor call and
-      // static field write. We only check count here, since the exact instructions
-      // will be checked later.
+    if (candidateValue.numberOfUsers() < 2) {
+      // We expect two special users for each instantiation: constructor call and static field
+      // write. We allow the instance to have other users as well, as long as they are valid
+      // according to the user analysis.
       return candidateInfo.invalidate();
     }
 
@@ -411,7 +413,7 @@
     //        invoke-direct {v0, ...}, void <candidate-type>.<init>(...)
     //        sput-object v0, <instance-field>
     //        ...
-    //        ...
+    //        ... // other usages that are valid according to the user analysis.
     //
     // In case we guarantee candidate constructor does not access <instance-field>
     // directly or indirectly we can guarantee that all the potential reads get
@@ -422,26 +424,33 @@
       // Intentionally empty.
     }
     iterator.previous();
-
     if (!iterator.hasNext()) {
       return candidateInfo.invalidate();
     }
-    if (!isValidInitCall(candidateInfo, iterator.next(), candidateValue, candidateType)) {
+    Set<Instruction> users = SetUtils.newIdentityHashSet(candidateValue.uniqueUsers());
+    Instruction constructorCall = iterator.next();
+    if (!isValidInitCall(candidateInfo, constructorCall, candidateValue, candidateType)) {
       iterator.previous();
       return candidateInfo.invalidate();
     }
-
+    boolean removedConstructorCall = users.remove(constructorCall);
+    assert removedConstructorCall;
     if (!iterator.hasNext()) {
       return candidateInfo.invalidate();
     }
-    if (!isValidStaticPut(candidateInfo, iterator.next())) {
+    Instruction staticPut = iterator.next();
+    if (!isValidStaticPut(candidateInfo, staticPut)) {
       iterator.previous();
       return candidateInfo.invalidate();
     }
+    boolean removedStaticPut = users.remove(staticPut);
+    assert removedStaticPut;
     if (candidateInfo.fieldWrites.incrementAndGet() > 1) {
       return candidateInfo.invalidate();
     }
-
+    if (!isSelectedValueUsersValid(candidateInfo, candidateValue, false, users)) {
+      return candidateInfo.invalidate();
+    }
     return candidateInfo;
   }
 
@@ -463,7 +472,7 @@
 
     if (ListUtils.lastIndexMatching(values, v -> v.getAliasedValue() == candidateValue) != 0
         || methodInvoked == null
-        || methodInvoked.method.holder != candidateType) {
+        || methodInvoked.holder() != candidateType) {
       return false;
     }
 
@@ -567,59 +576,79 @@
   private CandidateInfo analyzeAllValueUsers(
       CandidateInfo candidateInfo, Value value, boolean ignoreSuperClassInitInvoke) {
     assert value != null && value == value.getAliasedValue();
-
     if (value.numberOfPhiUsers() > 0) {
       return candidateInfo.invalidate();
     }
+    if (!isSelectedValueUsersValid(
+        candidateInfo, value, ignoreSuperClassInitInvoke, value.uniqueUsers())) {
+      return candidateInfo.invalidate();
+    }
+    return candidateInfo;
+  }
 
-    Set<Instruction> currentUsers = value.uniqueUsers();
+  private boolean isSelectedValueUsersValid(
+      CandidateInfo candidateInfo,
+      Value value,
+      boolean ignoreSuperClassInitInvoke,
+      Set<Instruction> currentUsers) {
     while (!currentUsers.isEmpty()) {
       Set<Instruction> indirectUsers = Sets.newIdentityHashSet();
       for (Instruction user : currentUsers) {
-        if (user.isAssume()) {
-          if (user.outValue().numberOfPhiUsers() > 0) {
-            return candidateInfo.invalidate();
-          }
-          indirectUsers.addAll(user.outValue().uniqueUsers());
-          continue;
+        if (!isValidValueUser(
+            candidateInfo, value, ignoreSuperClassInitInvoke, indirectUsers, user)) {
+          return false;
         }
-        if (user.isInvokeVirtual() || user.isInvokeDirect() /* private methods */) {
-          InvokeMethodWithReceiver invoke = user.asInvokeMethodWithReceiver();
-          Predicate<Value> isAliasedValue = v -> v.getAliasedValue() == value;
-          DexMethod methodReferenced = invoke.getInvokedMethod();
-          if (factory.isConstructor(methodReferenced)) {
-            assert user.isInvokeDirect();
-            if (ignoreSuperClassInitInvoke
-                && ListUtils.lastIndexMatching(invoke.inValues(), isAliasedValue) == 0
-                && methodReferenced == factory.objectMembers.constructor) {
-              // If we are inside candidate constructor and analyzing usages
-              // of the receiver, we want to ignore invocations of superclass
-              // constructor which will be removed after staticizing.
-              continue;
-            }
-            return candidateInfo.invalidate();
-          }
-          AppInfoWithLiveness appInfo = appView.appInfo();
-          ResolutionResult resolutionResult =
-              appInfo.resolveMethod(methodReferenced.holder, methodReferenced);
-          DexEncodedMethod methodInvoked =
-              user.isInvokeDirect()
-                  ? resolutionResult.lookupInvokeDirectTarget(candidateInfo.candidate, appInfo)
-                  : resolutionResult.isVirtualTarget() ? resolutionResult.getSingleTarget() : null;
-          if (ListUtils.lastIndexMatching(invoke.inValues(), isAliasedValue) == 0
-              && methodInvoked != null
-              && methodInvoked.method.holder == candidateInfo.candidate.type) {
-            continue;
-          }
-        }
-
-        // All other users are not allowed.
-        return candidateInfo.invalidate();
       }
       currentUsers = indirectUsers;
     }
+    return true;
+  }
 
-    return candidateInfo;
+  private boolean isValidValueUser(
+      CandidateInfo candidateInfo,
+      Value value,
+      boolean ignoreSuperClassInitInvoke,
+      Set<Instruction> indirectUsers,
+      Instruction user) {
+    if (user.isAssume()) {
+      if (user.outValue().numberOfPhiUsers() > 0) {
+        return false;
+      }
+      indirectUsers.addAll(user.outValue().uniqueUsers());
+      return true;
+    }
+    if (user.isInvokeVirtual() || user.isInvokeDirect() /* private methods */) {
+      InvokeMethodWithReceiver invoke = user.asInvokeMethodWithReceiver();
+      Predicate<Value> isAliasedValue = v -> v.getAliasedValue() == value;
+      DexMethod methodReferenced = invoke.getInvokedMethod();
+      if (factory.isConstructor(methodReferenced)) {
+        assert user.isInvokeDirect();
+        if (ignoreSuperClassInitInvoke
+            && ListUtils.lastIndexMatching(invoke.inValues(), isAliasedValue) == 0
+            && methodReferenced == factory.objectMembers.constructor) {
+          // If we are inside candidate constructor and analyzing usages
+          // of the receiver, we want to ignore invocations of superclass
+          // constructor which will be removed after staticizing.
+          return true;
+        }
+        return false;
+      }
+      AppInfoWithLiveness appInfo = appView.appInfo();
+      ResolutionResult resolutionResult =
+          appInfo.resolveMethod(methodReferenced.holder, methodReferenced);
+      DexEncodedMethod methodInvoked =
+          user.isInvokeDirect()
+              ? resolutionResult.lookupInvokeDirectTarget(candidateInfo.candidate, appInfo)
+              : resolutionResult.isVirtualTarget() ? resolutionResult.getSingleTarget() : null;
+      if (ListUtils.lastIndexMatching(invoke.inValues(), isAliasedValue) == 0
+          && methodInvoked != null
+          && methodInvoked.holder() == candidateInfo.candidate.type) {
+        return true;
+      }
+    }
+
+    // All other users are not allowed.
+    return false;
   }
 
   // Perform staticizing candidates:
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 f79593b..4a1924b 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
@@ -4,6 +4,8 @@
 
 package com.android.tools.r8.ir.optimize.staticizer;
 
+import static com.android.tools.r8.ir.analysis.type.Nullability.maybeNull;
+
 import com.android.tools.r8.graph.AppView;
 import com.android.tools.r8.graph.DebugLocalInfo;
 import com.android.tools.r8.graph.DexClass;
@@ -18,8 +20,10 @@
 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.InvokeDirect;
 import com.android.tools.r8.ir.code.InvokeMethodWithReceiver;
 import com.android.tools.r8.ir.code.InvokeStatic;
+import com.android.tools.r8.ir.code.NewInstance;
 import com.android.tools.r8.ir.code.Phi;
 import com.android.tools.r8.ir.code.StaticGet;
 import com.android.tools.r8.ir.code.StaticPut;
@@ -184,7 +188,7 @@
         if (method.isStatic() || factory().isConstructor(method.method)) {
           continue;
         }
-        IRCode code = method.buildIR(appView, appView.appInfo().originFor(method.method.holder));
+        IRCode code = method.buildIR(appView, appView.appInfo().originFor(method.holder()));
         assert code != null;
         Value thisValue = code.getThis();
         assert thisValue != null;
@@ -205,7 +209,7 @@
       // CHECK: references to field read usages are fixable.
       boolean fixableFieldReads = true;
       for (DexEncodedMethod method : info.referencedFrom) {
-        IRCode code = method.buildIR(appView, appView.appInfo().originFor(method.method.holder));
+        IRCode code = method.buildIR(appView, appView.appInfo().originFor(method.holder()));
         assert code != null;
         List<StaticGet> singletonFieldReads =
             Streams.stream(code.instructionIterator())
@@ -301,7 +305,7 @@
       Collection<BiConsumer<IRCode, MethodProcessor>> codeOptimizations,
       OptimizationFeedback feedback,
       OneTimeMethodProcessor methodProcessor) {
-    Origin origin = appView.appInfo().originFor(method.method.holder);
+    Origin origin = appView.appInfo().originFor(method.holder());
     IRCode code = method.buildIR(appView, origin);
     codeOptimizations.forEach(codeOptimization -> codeOptimization.accept(code, methodProcessor));
     CodeRewriter.removeAssumeInstructions(appView, code);
@@ -329,9 +333,8 @@
     assert candidateInfo != null;
 
     // Find and remove instantiation and its users.
-    for (Instruction instruction : code.instructions()) {
-      if (instruction.isNewInstance()
-          && instruction.asNewInstance().clazz == candidateInfo.candidate.type) {
+    for (NewInstance newInstance : code.<NewInstance>instructions(Instruction::isNewInstance)) {
+      if (newInstance.clazz == candidateInfo.candidate.type) {
         // Remove all usages
         // NOTE: requiring (a) the instance initializer to be trivial, (b) not allowing
         //       candidates with instance fields and (c) requiring candidate to directly
@@ -340,10 +343,31 @@
         assert candidateInfo.candidate.superType == factory().objectType;
         assert candidateInfo.candidate.instanceFields().size() == 0;
 
-        Value singletonValue = instruction.outValue();
+        Value singletonValue = newInstance.outValue();
         assert singletonValue != null;
-        singletonValue.uniqueUsers().forEach(user -> user.removeOrReplaceByDebugLocalRead(code));
-        instruction.removeOrReplaceByDebugLocalRead(code);
+
+        InvokeDirect uniqueConstructorInvoke =
+            newInstance.getUniqueConstructorInvoke(appView.dexItemFactory());
+        assert uniqueConstructorInvoke != null;
+        uniqueConstructorInvoke.removeOrReplaceByDebugLocalRead(code);
+
+        StaticPut uniqueStaticPut = null;
+        for (Instruction user : singletonValue.uniqueUsers()) {
+          if (user.isStaticPut()) {
+            assert uniqueStaticPut == null;
+            uniqueStaticPut = user.asStaticPut();
+          }
+        }
+        assert uniqueStaticPut != null;
+        uniqueStaticPut.removeOrReplaceByDebugLocalRead(code);
+
+        if (newInstance.outValue().hasAnyUsers()) {
+          TypeElement type = TypeElement.fromDexType(newInstance.clazz, maybeNull(), appView);
+          newInstance.replace(
+              new StaticGet(code.createValue(type), candidateInfo.singletonField.field), code);
+        } else {
+          newInstance.removeOrReplaceByDebugLocalRead(code);
+        }
         return;
       }
     }
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 20ea5df..140c844 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
@@ -228,24 +228,23 @@
       int concatenationCount = 0;
       // During the second iteration, count builders' usage.
       for (Instruction instr : code.instructions()) {
-        if (!instr.isInvokeVirtual()) {
-          continue;
-        }
-        InvokeVirtual invoke = instr.asInvokeVirtual();
-        DexMethod invokedMethod = invoke.getInvokedMethod();
-        if (optimizationConfiguration.isAppendMethod(invokedMethod)) {
-          concatenationCount++;
-          // The analysis might be overwhelmed.
-          if (concatenationCount > CONCATENATION_THRESHOLD) {
-            return ImmutableSet.of();
-          }
-        } else if (optimizationConfiguration.isToStringMethod(invokedMethod)) {
-          assert invoke.inValues().size() == 1;
-          Value receiver = invoke.getReceiver().getAliasedValue();
-          for (Value builder : collectAllLinkedBuilders(receiver)) {
-            if (builderToStringCounts.containsKey(builder)) {
-              int count = builderToStringCounts.getInt(builder);
-              builderToStringCounts.put(builder, count + 1);
+        if (instr.isInvokeMethod()) {
+          InvokeMethod invoke = instr.asInvokeMethod();
+          DexMethod invokedMethod = invoke.getInvokedMethod();
+          if (optimizationConfiguration.isAppendMethod(invokedMethod)) {
+            concatenationCount++;
+            // The analysis might be overwhelmed.
+            if (concatenationCount > CONCATENATION_THRESHOLD) {
+              return ImmutableSet.of();
+            }
+          } else if (optimizationConfiguration.isToStringMethod(invokedMethod)) {
+            assert invoke.arguments().size() == 1;
+            Value receiver = invoke.getArgument(0).getAliasedValue();
+            for (Value builder : collectAllLinkedBuilders(receiver)) {
+              if (builderToStringCounts.containsKey(builder)) {
+                int count = builderToStringCounts.getInt(builder);
+                builderToStringCounts.put(builder, count + 1);
+              }
             }
           }
         }
@@ -582,9 +581,9 @@
         if (instr == null) {
           break;
         }
-        InvokeVirtual invoke = instr.asInvokeVirtual();
-        assert invoke.inValues().size() == 1;
-        Value builder = invoke.getReceiver().getAliasedValue();
+        InvokeMethod invoke = instr.asInvokeMethod();
+        assert invoke.arguments().size() == 1;
+        Value builder = invoke.getArgument(0).getAliasedValue();
         Value outValue = invoke.outValue();
         if (outValue == null || outValue.isDead(appView, code)) {
           // If the out value is still used but potentially dead, replace it with a dummy string.
@@ -627,16 +626,16 @@
     }
 
     private boolean isToStringOfInterest(Set<Value> candidateBuilders, Instruction instr) {
-      if (!instr.isInvokeVirtual()) {
+      if (!instr.isInvokeMethod()) {
         return false;
       }
-      InvokeVirtual invoke = instr.asInvokeVirtual();
+      InvokeMethod invoke = instr.asInvokeMethod();
       DexMethod invokedMethod = invoke.getInvokedMethod();
       if (!optimizationConfiguration.isToStringMethod(invokedMethod)) {
         return false;
       }
-      assert invoke.inValues().size() == 1;
-      Value builder = invoke.getReceiver().getAliasedValue();
+      assert invoke.arguments().size() == 1;
+      Value builder = invoke.getArgument(0).getAliasedValue();
       if (!candidateBuilders.contains(builder)) {
         return false;
       }
@@ -736,11 +735,11 @@
       InstructionListIterator it = code.instructionListIterator();
       while (it.hasNext()) {
         Instruction instr = it.next();
-        if (instr.isInvokeVirtual()) {
-          InvokeVirtual invoke = instr.asInvokeVirtual();
+        if (instr.isInvokeMethod()) {
+          InvokeMethod invoke = instr.asInvokeMethod();
           DexMethod invokedMethod = invoke.getInvokedMethod();
           if (optimizationConfiguration.isToStringMethod(invokedMethod)
-              && buildersToRemove.contains(invoke.getReceiver().getAliasedValue())) {
+              && buildersToRemove.contains(invoke.getArgument(0).getAliasedValue())) {
             it.removeOrReplaceByDebugLocalRead();
           }
         }
@@ -852,7 +851,8 @@
     @Override
     public boolean isToStringMethod(DexMethod method) {
       return method == factory.stringBuilderMethods.toString
-          || method == factory.stringBufferMethods.toString;
+          || method == factory.stringBufferMethods.toString
+          || method == factory.stringMethods.valueOf;
     }
 
     private boolean canHandleArgumentType(DexType argType) {
@@ -900,20 +900,12 @@
 
         InvokeMethod invoke = escapeRoute.asInvokeMethod();
         DexMethod invokedMethod = invoke.getInvokedMethod();
-        // Make sure builder's uses are local, i.e., not escaping from the current method.
-        if (invokedMethod.holder != builderType) {
-          logEscapingRoute(false);
-          return false;
-        }
-        // <init> is legitimate.
-        if (optimizationConfiguration.isBuilderInit(invokedMethod, builderType)) {
-          return true;
-        }
+
         if (optimizationConfiguration.isToStringMethod(invokedMethod)) {
           Value out = escapeRoute.outValue();
           if (out != null) {
-            // If Builder#toString is interned, it could be used for equality check.
-            // Replacing builder-based runtime result with a compile time constant may change
+            // If Builder#toString or String#valueOf is interned, it could be used for equality
+            // check. Replacing builder-based runtime result with a compile time constant may change
             // the program's runtime behavior.
             for (Instruction outUser : out.uniqueUsers()) {
               if (outUser.isInvokeMethodWithReceiver()
@@ -924,7 +916,18 @@
               }
             }
           }
-          // Otherwise, use of Builder#toString is legitimate.
+          // Otherwise, use of Builder#toString and String#valueOf is legitimate.
+          return true;
+        }
+
+        // Make sure builder's uses are local, i.e., not escaping from the current method.
+        if (invokedMethod.holder != builderType) {
+          logEscapingRoute(false);
+          return false;
+        }
+
+        // <init> is legitimate.
+        if (optimizationConfiguration.isBuilderInit(invokedMethod, builderType)) {
           return true;
         }
         // Even though all invocations belong to the builder type, there are some methods other
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 1cb6482..f7e377d 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
@@ -139,14 +139,14 @@
       if (move.definition.isArgument()) {
         Argument argument = move.definition.asArgument();
         int argumentRegister = argument.outValue().getLiveIntervals().getRegister();
-        Value to = new FixedRegisterValue(argument.outValue().getType(), move.dst);
-        Value from = new FixedRegisterValue(argument.outValue().getType(), argumentRegister);
+        Value to = new FixedRegisterValue(argument.getOutType(), move.dst);
+        Value from = new FixedRegisterValue(argument.getOutType(), 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().getType(), move.dst);
+          Value to = new FixedRegisterValue(move.definition.getOutType(), 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/synthetic/DesugaredLibraryAPIConversionCfCodeProvider.java b/src/main/java/com/android/tools/r8/ir/synthetic/DesugaredLibraryAPIConversionCfCodeProvider.java
index 50885dc..cb1823a 100644
--- a/src/main/java/com/android/tools/r8/ir/synthetic/DesugaredLibraryAPIConversionCfCodeProvider.java
+++ b/src/main/java/com/android/tools/r8/ir/synthetic/DesugaredLibraryAPIConversionCfCodeProvider.java
@@ -30,7 +30,6 @@
 import com.android.tools.r8.ir.code.If;
 import com.android.tools.r8.ir.code.ValueType;
 import com.android.tools.r8.ir.desugar.DesugaredLibraryAPIConverter;
-import com.android.tools.r8.utils.StringDiagnostic;
 import java.util.ArrayList;
 import java.util.List;
 import org.objectweb.asm.Opcodes;
@@ -41,24 +40,14 @@
     super(appView, holder);
   }
 
-  boolean shouldConvert(
-      DexType type, DesugaredLibraryAPIConverter converter, DexString methodName) {
+  boolean shouldConvert(DexType type, DesugaredLibraryAPIConverter converter, DexMethod method) {
     if (!appView.rewritePrefix.hasRewrittenType(type, appView)) {
       return false;
     }
     if (converter.canConvert(type)) {
       return true;
     }
-    appView
-        .options()
-        .reporter
-        .warning(
-            new StringDiagnostic(
-                "Desugared library API conversion failed for "
-                    + type
-                    + ", unexpected behavior for method "
-                    + methodName
-                    + "."));
+    converter.reportInvalidInvoke(type, method, "");
     return false;
   }
 
@@ -101,7 +90,7 @@
       DexType[] newParameters = forwardMethod.proto.parameters.values.clone();
       for (DexType param : forwardMethod.proto.parameters.values) {
         instructions.add(new CfLoad(ValueType.fromDexType(param), stackIndex));
-        if (shouldConvert(param, converter, forwardMethod.name)) {
+        if (shouldConvert(param, converter, forwardMethod)) {
           instructions.add(
               new CfInvoke(
                   Opcodes.INVOKESTATIC,
@@ -132,7 +121,7 @@
         instructions.add(new CfInvoke(Opcodes.INVOKEVIRTUAL, newForwardMethod, false));
       }
 
-      if (shouldConvert(returnType, converter, forwardMethod.name)) {
+      if (shouldConvert(returnType, converter, forwardMethod)) {
         instructions.add(
             new CfInvoke(
                 Opcodes.INVOKESTATIC,
@@ -188,7 +177,7 @@
       int stackIndex = 1;
       for (DexType param : forwardMethod.proto.parameters.values) {
         instructions.add(new CfLoad(ValueType.fromDexType(param), stackIndex));
-        if (shouldConvert(param, converter, forwardMethod.name)) {
+        if (shouldConvert(param, converter, forwardMethod)) {
           instructions.add(
               new CfInvoke(
                   Opcodes.INVOKESTATIC,
@@ -208,7 +197,7 @@
       }
 
       DexType returnType = forwardMethod.proto.returnType;
-      if (shouldConvert(returnType, converter, forwardMethod.name)) {
+      if (shouldConvert(returnType, converter, forwardMethod)) {
         instructions.add(
             new CfInvoke(
                 Opcodes.INVOKESTATIC,
diff --git a/src/main/java/com/android/tools/r8/ir/synthetic/EnumUnboxingCfCodeProvider.java b/src/main/java/com/android/tools/r8/ir/synthetic/EnumUnboxingCfCodeProvider.java
new file mode 100644
index 0000000..fbfacb8
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/ir/synthetic/EnumUnboxingCfCodeProvider.java
@@ -0,0 +1,107 @@
+// 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.synthetic;
+
+import com.android.tools.r8.cf.code.CfConstNumber;
+import com.android.tools.r8.cf.code.CfConstString;
+import com.android.tools.r8.cf.code.CfIf;
+import com.android.tools.r8.cf.code.CfInstruction;
+import com.android.tools.r8.cf.code.CfInvoke;
+import com.android.tools.r8.cf.code.CfLabel;
+import com.android.tools.r8.cf.code.CfLoad;
+import com.android.tools.r8.cf.code.CfNew;
+import com.android.tools.r8.cf.code.CfReturn;
+import com.android.tools.r8.cf.code.CfStackInstruction;
+import com.android.tools.r8.cf.code.CfStackInstruction.Opcode;
+import com.android.tools.r8.cf.code.CfThrow;
+import com.android.tools.r8.graph.AppView;
+import com.android.tools.r8.graph.CfCode;
+import com.android.tools.r8.graph.DexItemFactory;
+import com.android.tools.r8.graph.DexType;
+import com.android.tools.r8.graph.EnumValueInfoMapCollection.EnumValueInfoMap;
+import com.android.tools.r8.ir.code.If;
+import com.android.tools.r8.ir.code.ValueType;
+import java.util.ArrayList;
+import java.util.List;
+import org.objectweb.asm.Opcodes;
+
+public abstract class EnumUnboxingCfCodeProvider extends SyntheticCfCodeProvider {
+
+  EnumUnboxingCfCodeProvider(AppView<?> appView, DexType holder) {
+    super(appView, holder);
+  }
+
+  public static class EnumUnboxingValueOfCfCodeProvider extends EnumUnboxingCfCodeProvider {
+
+    private DexType enumType;
+    private EnumValueInfoMap map;
+
+    public EnumUnboxingValueOfCfCodeProvider(
+        AppView<?> appView, DexType holder, DexType enumType, EnumValueInfoMap map) {
+      super(appView, holder);
+      this.enumType = enumType;
+      this.map = map;
+    }
+
+    @Override
+    public CfCode generateCfCode() {
+      // Generated static method, for class com.x.MyEnum {A,B} would look like:
+      // int UtilityClass#com.x.MyEnum_valueOf(String s) {
+      // 	if (s == null) { throw npe("Name is null"); }
+      // 	if (s.equals("A")) { return 1;}
+      // 	if (s.equals("B")) { return 2;}
+      //  throw new IllegalArgumentException(
+      //            "No enum constant com.x.MyEnum." + s);
+      DexItemFactory factory = appView.dexItemFactory();
+      List<CfInstruction> instructions = new ArrayList<>();
+
+      // if (s == null) { throw npe("Name is null"); }
+      CfLabel nullDest = new CfLabel();
+      instructions.add(new CfLoad(ValueType.fromDexType(factory.stringType), 0));
+      instructions.add(new CfIf(If.Type.NE, ValueType.OBJECT, nullDest));
+      instructions.add(new CfNew(factory.npeType));
+      instructions.add(new CfStackInstruction(Opcode.Dup));
+      instructions.add(new CfConstString(appView.dexItemFactory().createString("Name is null")));
+      instructions.add(
+          new CfInvoke(Opcodes.INVOKESPECIAL, factory.npeMethods.initWithMessage, false));
+      instructions.add(new CfThrow());
+      instructions.add(nullDest);
+
+      // if (s.equals("A")) { return 1;}
+      // if (s.equals("B")) { return 2;}
+      map.forEach(
+          (field, enumValueInfo) -> {
+            CfLabel dest = new CfLabel();
+            instructions.add(new CfLoad(ValueType.fromDexType(factory.stringType), 0));
+            instructions.add(new CfConstString(field.name));
+            instructions.add(
+                new CfInvoke(Opcodes.INVOKEVIRTUAL, factory.stringMethods.equals, false));
+            instructions.add(new CfIf(If.Type.EQ, ValueType.INT, dest));
+            instructions.add(new CfConstNumber(enumValueInfo.convertToInt(), ValueType.INT));
+            instructions.add(new CfReturn(ValueType.INT));
+            instructions.add(dest);
+          });
+
+      // throw new IllegalArgumentException("No enum constant com.x.MyEnum." + s);
+      instructions.add(new CfNew(factory.illegalArgumentExceptionType));
+      instructions.add(new CfStackInstruction(Opcode.Dup));
+      instructions.add(
+          new CfConstString(
+              appView
+                  .dexItemFactory()
+                  .createString(
+                      "No enum constant " + enumType.toSourceString().replace('$', '.') + ".")));
+      instructions.add(new CfLoad(ValueType.fromDexType(factory.stringType), 0));
+      instructions.add(new CfInvoke(Opcodes.INVOKEVIRTUAL, factory.stringMethods.concat, false));
+      instructions.add(
+          new CfInvoke(
+              Opcodes.INVOKESPECIAL,
+              factory.illegalArgumentExceptionMethods.initWithMessage,
+              false));
+      instructions.add(new CfThrow());
+      return standardCfCodeFromInstructions(instructions);
+    }
+  }
+}
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 688d59b..e04f07c 100644
--- a/src/main/java/com/android/tools/r8/jar/CfApplicationWriter.java
+++ b/src/main/java/com/android/tools/r8/jar/CfApplicationWriter.java
@@ -128,7 +128,8 @@
     ClassWriter writer = new ClassWriter(0);
     int markerStringPoolIndex = writer.newConst(markerString);
     assert markerStringPoolIndex == MARKER_STRING_CONSTANT_POOL_INDEX;
-    writer.visitSource(clazz.sourceFile != null ? clazz.sourceFile.toString() : null, null);
+    String sourceDebug = getSourceDebugExtension(clazz.annotations());
+    writer.visitSource(clazz.sourceFile != null ? clazz.sourceFile.toString() : null, sourceDebug);
     int version = getClassFileVersion(clazz);
     int access = clazz.accessFlags.getAsCfAccessFlags();
     String desc = namingLens.lookupDescriptor(clazz.type).toString();
@@ -242,6 +243,16 @@
     return res.toString();
   }
 
+  private String getSourceDebugExtension(DexAnnotationSet annotations) {
+    DexValue debugExtensions =
+        getSystemAnnotationValue(
+            annotations, application.dexItemFactory.annotationSourceDebugExtension);
+    if (debugExtensions == null) {
+      return null;
+    }
+    return debugExtensions.asDexValueString().getValue().toString();
+  }
+
   private ImmutableMap<DexString, DexValue> getAnnotationDefaults(DexAnnotationSet annotations) {
     DexValue value =
         getSystemAnnotationValue(annotations, application.dexItemFactory.annotationDefault);
diff --git a/src/main/java/com/android/tools/r8/kotlin/ClassTypeSignatureToRenamedKmTypeConverter.java b/src/main/java/com/android/tools/r8/kotlin/ClassTypeSignatureToRenamedKmTypeConverter.java
new file mode 100644
index 0000000..ea1ddc3
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/kotlin/ClassTypeSignatureToRenamedKmTypeConverter.java
@@ -0,0 +1,78 @@
+// 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;
+
+import static kotlinx.metadata.FlagsKt.flagsOf;
+
+import com.android.tools.r8.graph.AppView;
+import com.android.tools.r8.graph.DexType;
+import com.android.tools.r8.graph.GenericSignature.ClassTypeSignature;
+import com.android.tools.r8.graph.GenericSignature.FieldTypeSignature;
+import com.android.tools.r8.shaking.AppInfoWithLiveness;
+import java.util.List;
+import java.util.function.Function;
+import kotlinx.metadata.KmType;
+import kotlinx.metadata.KmTypeParameter;
+import kotlinx.metadata.KmTypeProjection;
+import kotlinx.metadata.KmVariance;
+
+class ClassTypeSignatureToRenamedKmTypeConverter implements ClassTypeSignature.Converter<KmType> {
+
+  private final AppView<AppInfoWithLiveness> appView;
+  private final List<KmTypeParameter> typeParameters;
+  private final Function<DexType, String> toRenamedClassClassifier;
+
+  ClassTypeSignatureToRenamedKmTypeConverter(
+      AppView<AppInfoWithLiveness> appView,
+      List<KmTypeParameter> typeParameters,
+      Function<DexType, String> toRenamedClassClassifier) {
+    this.appView = appView;
+    this.typeParameters = typeParameters;
+    this.toRenamedClassClassifier = toRenamedClassClassifier;
+  }
+
+  @Override
+  public KmType init() {
+    return new KmType(flagsOf());
+  }
+
+  @Override
+  public KmType visitType(DexType type, KmType result) {
+    String classifier = toRenamedClassClassifier.apply(type);
+    if (classifier == null) {
+      return null;
+    }
+    result.visitClass(classifier);
+    return result;
+  }
+
+  @Override
+  public KmType visitTypeArgument(FieldTypeSignature typeArgument, KmType result) {
+    if (result == null) {
+      return null;
+    }
+    List<KmTypeProjection> arguments = result.getArguments();
+    KotlinMetadataSynthesizerUtils.populateKmTypeFromSignature(
+        typeArgument,
+        () -> {
+          KmType kmTypeArgument = new KmType(flagsOf());
+          arguments.add(new KmTypeProjection(KmVariance.INVARIANT, kmTypeArgument));
+          return kmTypeArgument;
+        },
+        typeParameters,
+        appView.dexItemFactory());
+    return result;
+  }
+
+  @Override
+  public KmType visitInnerTypeSignature(ClassTypeSignature innerTypeSignature, KmType result) {
+    // Do nothing
+    return result;
+  }
+
+  public List<KmTypeParameter> getTypeParameters() {
+    return typeParameters;
+  }
+}
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 136947b..c891897 100644
--- a/src/main/java/com/android/tools/r8/kotlin/KotlinClass.java
+++ b/src/main/java/com/android/tools/r8/kotlin/KotlinClass.java
@@ -6,9 +6,6 @@
 
 import static com.android.tools.r8.kotlin.Kotlin.addKotlinPrefix;
 import static com.android.tools.r8.kotlin.KotlinMetadataSynthesizer.toKmType;
-import static com.android.tools.r8.kotlin.KotlinMetadataSynthesizer.toRenamedClassifier;
-import static com.android.tools.r8.kotlin.KotlinMetadataSynthesizer.toRenamedKmConstructor;
-import static com.android.tools.r8.kotlin.KotlinMetadataSynthesizer.toRenamedKmType;
 import static kotlinx.metadata.Flag.IS_SEALED;
 
 import com.android.tools.r8.graph.AppView;
@@ -22,7 +19,6 @@
 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;
@@ -77,21 +73,26 @@
 
   @Override
   void rewrite(AppView<AppInfoWithLiveness> appView, NamingLens lens) {
+    KotlinMetadataSynthesizer synthesizer = new KotlinMetadataSynthesizer(appView, lens, this);
     if (appView.options().enableKotlinMetadataRewritingForRenamedClasses
         && lens.lookupType(clazz.type, appView.dexItemFactory()) != clazz.type) {
-      String renamedClassifier = toRenamedClassifier(clazz.type, appView, lens);
+      String renamedClassifier = synthesizer.toRenamedClassifier(clazz.type);
       if (renamedClassifier != null) {
         assert !kmClass.getName().equals(renamedClassifier);
         kmClass.setName(renamedClassifier);
       }
     }
 
+    ClassTypeSignatureToRenamedKmTypeConverter converter =
+        new ClassTypeSignatureToRenamedKmTypeConverter(
+            appView, getTypeParameters(), synthesizer::toRenamedClassifier);
+
     // Rewriting upward hierarchy.
     List<KmType> superTypes = kmClass.getSupertypes();
     superTypes.clear();
     for (DexType itfType : clazz.interfaces.values) {
       // TODO(b/129925954): Use GenericSignature.ClassSignature#superInterfaceSignatures
-      KmType kmType = toRenamedKmType(itfType, null, appView, lens);
+      KmType kmType = synthesizer.toRenamedKmType(itfType, null, null, converter);
       if (kmType != null) {
         superTypes.add(kmType);
       }
@@ -99,7 +100,8 @@
     assert clazz.superType != null;
     if (clazz.superType != appView.dexItemFactory().objectType) {
       // TODO(b/129925954): Use GenericSignature.ClassSignature#superClassSignature
-      KmType kmTypeForSupertype = toRenamedKmType(clazz.superType, null, appView, lens);
+      KmType kmTypeForSupertype =
+          synthesizer.toRenamedKmType(clazz.superType, null, null, converter);
       if (kmTypeForSupertype != null) {
         superTypes.add(kmTypeForSupertype);
       }
@@ -131,7 +133,7 @@
     sealedSubclasses.clear();
     if (IS_SEALED.invoke(kmClass.getFlags())) {
       for (DexType subtype : appView.appInfo().allImmediateSubtypes(clazz.type)) {
-        String classifier = toRenamedClassifier(subtype, appView, lens);
+        String classifier = synthesizer.toRenamedClassifier(subtype);
         if (classifier != null) {
           sealedSubclasses.add(classifier);
         }
@@ -149,7 +151,7 @@
       if (!method.isInstanceInitializer()) {
         continue;
       }
-      KmConstructor constructor = toRenamedKmConstructor(method, appView, lens);
+      KmConstructor constructor = synthesizer.toRenamedKmConstructor(method);
       if (constructor != null) {
         constructors.add(constructor);
       }
@@ -162,7 +164,7 @@
 
     // TODO(b/151193864): enum entries
 
-    rewriteDeclarationContainer(appView, lens);
+    rewriteDeclarationContainer(synthesizer);
   }
 
   @Override
@@ -190,11 +192,13 @@
   @Override
   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("}");
+    appendKmSection(
+        indent,
+        "Metadata.Class",
+        sb,
+        newIndent -> {
+          appendKmClass(newIndent, sb, kmClass);
+        });
     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 627ac9d..fc3fbbe 100644
--- a/src/main/java/com/android/tools/r8/kotlin/KotlinClassFacade.java
+++ b/src/main/java/com/android/tools/r8/kotlin/KotlinClassFacade.java
@@ -4,8 +4,6 @@
 
 package com.android.tools.r8.kotlin;
 
-import static com.android.tools.r8.kotlin.KotlinMetadataSynthesizer.toRenamedBinaryName;
-
 import com.android.tools.r8.graph.AppView;
 import com.android.tools.r8.graph.DexClass;
 import com.android.tools.r8.graph.DexType;
@@ -46,12 +44,13 @@
   @Override
   void rewrite(AppView<AppInfoWithLiveness> appView, NamingLens lens) {
     ListIterator<String> partClassIterator = partClassNames.listIterator();
+    KotlinMetadataSynthesizer synthesizer = new KotlinMetadataSynthesizer(appView, lens, this);
     while (partClassIterator.hasNext()) {
       String partClassName = partClassIterator.next();
       partClassIterator.remove();
       DexType partClassType = appView.dexItemFactory().createType(
           DescriptorUtils.getDescriptorFromClassBinaryName(partClassName));
-      String renamedPartClassName = toRenamedBinaryName(partClassType, appView, lens);
+      String renamedPartClassName = synthesizer.toRenamedBinaryName(partClassType);
       if (renamedPartClassName != null) {
         partClassIterator.add(renamedPartClassName);
       }
@@ -82,6 +81,6 @@
 
   @Override
   public String toString(String indent) {
-    return indent + "MultiFileClassFacade(" + StringUtils.join(partClassNames, ", ") + ")";
+    return indent + "MetaData.MultiFileClassFacade(" + StringUtils.join(partClassNames, ", ") + ")";
   }
 }
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 e67cd05..90f265c 100644
--- a/src/main/java/com/android/tools/r8/kotlin/KotlinClassPart.java
+++ b/src/main/java/com/android/tools/r8/kotlin/KotlinClassPart.java
@@ -4,15 +4,12 @@
 
 package com.android.tools.r8.kotlin;
 
-import static com.android.tools.r8.kotlin.KotlinMetadataSynthesizer.toRenamedBinaryName;
-
 import com.android.tools.r8.graph.AppView;
 import com.android.tools.r8.graph.DexClass;
 import com.android.tools.r8.graph.DexType;
 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;
@@ -45,11 +42,12 @@
   void rewrite(AppView<AppInfoWithLiveness> appView, NamingLens lens) {
     DexType facadeClassType = appView.dexItemFactory().createType(
         DescriptorUtils.getDescriptorFromClassBinaryName(facadeClassName));
-    facadeClassName = toRenamedBinaryName(facadeClassType, appView, lens);
+    KotlinMetadataSynthesizer synthesizer = new KotlinMetadataSynthesizer(appView, lens, this);
+    facadeClassName = synthesizer.toRenamedBinaryName(facadeClassType);
     if (!appView.options().enableKotlinMetadataRewritingForMembers) {
       return;
     }
-    rewriteDeclarationContainer(appView, lens);
+    rewriteDeclarationContainer(synthesizer);
   }
 
   @Override
@@ -85,12 +83,14 @@
   @Override
   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("}");
+    appendKmSection(
+        indent,
+        "Metadata.MultiFileClassPart",
+        sb,
+        newIndent -> {
+          appendKeyValue(newIndent, "facadeClassName", sb, facadeClassName);
+          appendKmPackage(newIndent, sb, kmPackage);
+        });
     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 08fc8ef..8ec966b 100644
--- a/src/main/java/com/android/tools/r8/kotlin/KotlinFile.java
+++ b/src/main/java/com/android/tools/r8/kotlin/KotlinFile.java
@@ -38,7 +38,7 @@
     if (!appView.options().enableKotlinMetadataRewritingForMembers) {
       return;
     }
-    rewriteDeclarationContainer(appView, lens);
+    rewriteDeclarationContainer(new KotlinMetadataSynthesizer(appView, lens, this));
   }
 
   @Override
@@ -66,11 +66,13 @@
   @Override
   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("}");
+    appendKmSection(
+        indent,
+        "Metadata.FileFacade",
+        sb,
+        newIndent -> {
+          appendKmPackage(newIndent, sb, kmPackage);
+        });
     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 967cf05..64ed559 100644
--- a/src/main/java/com/android/tools/r8/kotlin/KotlinInfo.java
+++ b/src/main/java/com/android/tools/r8/kotlin/KotlinInfo.java
@@ -4,7 +4,7 @@
 
 package com.android.tools.r8.kotlin;
 
-import static com.android.tools.r8.kotlin.KotlinMetadataSynthesizer.toRenamedKmFunction;
+import static com.android.tools.r8.utils.StringUtils.LINE_SEPARATOR;
 import static kotlinx.metadata.Flag.Class.IS_COMPANION_OBJECT;
 
 import com.android.tools.r8.errors.Unreachable;
@@ -12,29 +12,46 @@
 import com.android.tools.r8.graph.DexClass;
 import com.android.tools.r8.graph.DexEncodedField;
 import com.android.tools.r8.graph.DexEncodedMethod;
+import com.android.tools.r8.kotlin.KotlinMemberInfo.KotlinFieldInfo;
+import com.android.tools.r8.kotlin.KotlinMemberInfo.KotlinPropertyInfo;
 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.Action;
 import com.android.tools.r8.utils.StringUtils;
+import com.google.common.collect.ImmutableList;
+import java.util.Comparator;
 import java.util.HashMap;
 import java.util.List;
 import java.util.Map;
-import java.util.function.BiFunction;
+import java.util.function.BiConsumer;
+import java.util.function.Consumer;
 import java.util.function.Predicate;
 import java.util.stream.Collectors;
+import kotlinx.metadata.KmAnnotation;
+import kotlinx.metadata.KmAnnotationArgument;
+import kotlinx.metadata.KmClass;
+import kotlinx.metadata.KmConstructor;
 import kotlinx.metadata.KmDeclarationContainer;
 import kotlinx.metadata.KmFunction;
+import kotlinx.metadata.KmPackage;
 import kotlinx.metadata.KmProperty;
 import kotlinx.metadata.KmType;
 import kotlinx.metadata.KmTypeAlias;
 import kotlinx.metadata.KmTypeParameter;
+import kotlinx.metadata.KmTypeProjection;
+import kotlinx.metadata.KmValueParameter;
+import kotlinx.metadata.jvm.JvmExtensionsKt;
+import kotlinx.metadata.jvm.JvmFieldSignature;
+import kotlinx.metadata.jvm.JvmMethodSignature;
 import kotlinx.metadata.jvm.KotlinClassHeader;
 import kotlinx.metadata.jvm.KotlinClassMetadata;
 
 // Provides access to package/class-level Kotlin information.
 public abstract class KotlinInfo<MetadataKind extends KotlinClassMetadata> {
+
   final DexClass clazz;
+  private static final List<KmTypeParameter> EMPTY_TYPE_PARAMS = ImmutableList.of();
 
   KotlinInfo(MetadataKind metadata, DexClass clazz) {
     assert clazz != null;
@@ -52,6 +69,13 @@
 
   abstract KotlinClassHeader createHeader();
 
+  public final List<KmTypeParameter> getTypeParameters() {
+    if (!this.isClass()) {
+      return EMPTY_TYPE_PARAMS;
+    }
+    return this.asClass().kmClass.getTypeParameters();
+  }
+
   public enum Kind {
     Class, File, Synthetic, Part, Facade
   }
@@ -116,15 +140,37 @@
 
   // {@link KmClass} and {@link KmPackage} are inherited from {@link KmDeclarationContainer} that
   // abstract functions and properties. Rewriting of those portions can be unified here.
-  void rewriteDeclarationContainer(AppView<AppInfoWithLiveness> appView, NamingLens lens) {
+  void rewriteDeclarationContainer(KotlinMetadataSynthesizer synthesizer) {
     assert clazz != null;
 
     KmDeclarationContainer kmDeclarationContainer = getDeclarations();
-    Map<String, KmPropertyGroup.Builder> propertyGroupBuilderMap = new HashMap<>();
+    rewriteFunctions(synthesizer, kmDeclarationContainer.getFunctions());
+    rewriteProperties(synthesizer, kmDeclarationContainer.getProperties());
+  }
 
+  private void rewriteFunctions(KotlinMetadataSynthesizer synthesizer, List<KmFunction> functions) {
+    functions.clear();
+    for (DexEncodedMethod method : clazz.methods()) {
+      if (method.isInitializer()) {
+        continue;
+      }
+      if (method.isKotlinFunction() || method.isKotlinExtensionFunction()) {
+        KmFunction function = synthesizer.toRenamedKmFunction(method);
+        if (function != null) {
+          functions.add(function);
+        }
+      }
+      // TODO(b/151194869): What should we do for methods that fall into this category---no mark?
+    }
+  }
+
+  private void rewriteProperties(
+      KotlinMetadataSynthesizer synthesizer, List<KmProperty> properties) {
+    Map<String, KmPropertyGroup.Builder> propertyGroupBuilderMap = new HashMap<>();
     // Backing fields for a companion object are declared in its host class.
     Iterable<DexEncodedField> fields = clazz.fields();
     Predicate<DexEncodedField> backingFieldTester = DexEncodedField::isKotlinBackingField;
+    List<KmTypeParameter> classTypeParameters = getTypeParameters();
     if (isClass()) {
       KotlinClass ktClass = asClass();
       if (IS_COMPANION_OBJECT.invoke(ktClass.kmClass.getFlags()) && ktClass.hostClass != null) {
@@ -132,54 +178,46 @@
         backingFieldTester = DexEncodedField::isKotlinBackingFieldForCompanionObject;
       }
     }
-
     for (DexEncodedField field : fields) {
       if (backingFieldTester.test(field)) {
-        String name = field.getKotlinMemberInfo().propertyName;
+        KotlinFieldInfo kotlinFieldInfo = field.getKotlinMemberInfo().asFieldInfo();
+        assert kotlinFieldInfo != null;
+        String name = kotlinFieldInfo.propertyName;
         assert name != null;
         KmPropertyGroup.Builder builder =
             propertyGroupBuilderMap.computeIfAbsent(
                 name,
-                k -> KmPropertyGroup.builder(field.getKotlinMemberInfo().propertyFlags, name));
+                k -> KmPropertyGroup.builder(kotlinFieldInfo.flags, name, classTypeParameters));
         builder.foundBackingField(field);
       }
     }
-
-    List<KmFunction> functions = kmDeclarationContainer.getFunctions();
-    functions.clear();
     for (DexEncodedMethod method : clazz.methods()) {
       if (method.isInitializer()) {
         continue;
       }
-
-      if (method.isKotlinFunction() || method.isKotlinExtensionFunction()) {
-        KmFunction function = toRenamedKmFunction(method, appView, lens);
-        if (function != null) {
-          functions.add(function);
-        }
-        continue;
-      }
       if (method.isKotlinProperty() || method.isKotlinExtensionProperty()) {
-        String name = method.getKotlinMemberInfo().propertyName;
+        assert method.getKotlinMemberInfo().isPropertyInfo();
+        KotlinPropertyInfo kotlinPropertyInfo = method.getKotlinMemberInfo().asPropertyInfo();
+        String name = kotlinPropertyInfo.propertyName;
         assert name != null;
         KmPropertyGroup.Builder builder =
             propertyGroupBuilderMap.computeIfAbsent(
                 name,
                 // Hitting here (creating a property builder) after visiting all fields means that
                 // this property doesn't have a backing field. Don't use members' flags.
-                k -> KmPropertyGroup.builder(method.getKotlinMemberInfo().propertyFlags, name));
-        switch (method.getKotlinMemberInfo().memberKind) {
+                k -> KmPropertyGroup.builder(kotlinPropertyInfo.flags, name, classTypeParameters));
+        switch (kotlinPropertyInfo.memberKind) {
           case EXTENSION_PROPERTY_GETTER:
             builder.isExtensionGetter();
             // fallthrough;
           case PROPERTY_GETTER:
-            builder.foundGetter(method, method.getKotlinMemberInfo().flags);
+            builder.foundGetter(method, kotlinPropertyInfo);
             break;
           case EXTENSION_PROPERTY_SETTER:
             builder.isExtensionSetter();
             // fallthrough;
           case PROPERTY_SETTER:
-            builder.foundSetter(method, method.getKotlinMemberInfo().flags);
+            builder.foundSetter(method, kotlinPropertyInfo);
             break;
           case EXTENSION_PROPERTY_ANNOTATIONS:
             builder.isExtensionAnnotations();
@@ -190,20 +228,16 @@
           default:
             throw new Unreachable("Not a Kotlin property: " + method.getKotlinMemberInfo());
         }
-        continue;
       }
-
       // TODO(b/151194869): What should we do for methods that fall into this category---no mark?
     }
-
-    List<KmProperty> properties = kmDeclarationContainer.getProperties();
     properties.clear();
     for (KmPropertyGroup.Builder builder : propertyGroupBuilderMap.values()) {
       KmPropertyGroup group = builder.build();
       if (group == null) {
         continue;
       }
-      KmProperty property = group.toRenamedKmProperty(appView, lens);
+      KmProperty property = group.toRenamedKmProperty(synthesizer);
       if (property != null) {
         properties.add(property);
       }
@@ -212,124 +246,557 @@
 
   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();
+  static final String INDENT = "  ";
+
+  private static <T> void appendKmHelper(
+      String key, StringBuilder sb, Action appendContent, String start, String end) {
+    sb.append(key);
+    sb.append(start);
+    appendContent.execute();
+    sb.append(end);
   }
 
-  final String INDENT = "  ";
+  static <T> void appendKmSection(
+      String indent, String typeDescription, StringBuilder sb, Consumer<String> appendContent) {
+    appendKmHelper(
+        typeDescription,
+        sb,
+        () -> appendContent.accept(indent + INDENT),
+        "{" + LINE_SEPARATOR,
+        indent + "}");
+  }
 
-  private <T> void appendKmSection(
+  static <T> void appendKmList(
       String indent,
-      String header,
+      String typeDescription,
+      StringBuilder sb,
       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);
+      BiConsumer<String, T> appendItem) {
+    if (items.isEmpty()) {
+      sb.append(typeDescription).append("[]");
+      return;
     }
-    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);
-    }
+    appendKmHelper(
+        typeDescription,
+        sb,
+        () -> {
+          for (T kmItem : items) {
+            sb.append(indent).append(INDENT);
+            appendItem.accept(indent + INDENT, kmItem);
+            sb.append(LINE_SEPARATOR);
+          }
+        },
+        "[" + LINE_SEPARATOR,
+        indent + "]");
   }
 
-  private String kmFunctionToString(String indent, KmFunction function) {
-    assert function != null;
-    StringBuilder sb = new StringBuilder();
+  static void appendKeyValue(
+      String indent, String key, StringBuilder sb, Consumer<String> appendValue) {
     sb.append(indent);
-    sb.append("KmFunction {");
-    sb.append(StringUtils.LINE_SEPARATOR);
-    String newIndent = indent + INDENT;
-    KmType receiverParameterType = function.getReceiverParameterType();
+    appendKmHelper(key, sb, () -> appendValue.accept(indent), ": ", "," + LINE_SEPARATOR);
+  }
+
+  static void appendKeyValue(String indent, String key, StringBuilder sb, String value) {
+    sb.append(indent);
+    appendKmHelper(key, sb, () -> sb.append(value), ": ", "," + LINE_SEPARATOR);
+  }
+
+  static void appendKmDeclarationContainer(
+      String indent, StringBuilder sb, KmDeclarationContainer container) {
     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();
+        indent,
+        "functions",
+        sb,
+        newIndent -> {
+          appendKmList(
+              newIndent,
+              "KmFunction",
+              sb,
+              container.getFunctions().stream()
+                  .sorted(Comparator.comparing(KmFunction::getName))
+                  .collect(Collectors.toList()),
+              (nextIndent, kmFunction) -> {
+                appendKmFunction(nextIndent, sb, kmFunction);
+              });
+        });
+    appendKeyValue(
+        indent,
+        "properties",
+        sb,
+        newIndent -> {
+          appendKmList(
+              newIndent,
+              "KmProperty",
+              sb,
+              container.getProperties().stream()
+                  .sorted(Comparator.comparing(KmProperty::getName))
+                  .collect(Collectors.toList()),
+              (nextIndent, kmProperty) -> {
+                appendKmProperty(nextIndent, sb, kmProperty);
+              });
+        });
+    appendKeyValue(
+        indent,
+        "typeAliases",
+        sb,
+        newIndent -> {
+          appendKmList(
+              newIndent,
+              "KmTypeAlias",
+              sb,
+              container.getTypeAliases().stream()
+                  .sorted(Comparator.comparing(KmTypeAlias::getName))
+                  .collect(Collectors.toList()),
+              (nextIndent, kmTypeAlias) -> {
+                appendTypeAlias(nextIndent, sb, kmTypeAlias);
+              });
+        });
   }
 
-  private String kmPropertyToString(String indent, KmProperty property) {
-    // TODO(b/148581822): Add information.
-    return indent + "KmProperty { " + property + "}";
+  static void appendKmPackage(String indent, StringBuilder sb, KmPackage kmPackage) {
+    appendKmDeclarationContainer(indent, sb, kmPackage);
+    appendKeyValue(indent, "moduleName", sb, JvmExtensionsKt.getModuleName(kmPackage));
+    appendKeyValue(
+        indent,
+        "localDelegatedProperties",
+        sb,
+        nextIndent -> {
+          appendKmList(
+              nextIndent,
+              "KmProperty",
+              sb,
+              JvmExtensionsKt.getLocalDelegatedProperties(kmPackage),
+              (nextNextIndent, kmProperty) -> {
+                appendKmProperty(nextNextIndent, sb, kmProperty);
+              });
+        });
   }
 
-  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);
+  static void appendKmClass(String indent, StringBuilder sb, KmClass kmClass) {
+    appendKeyValue(indent, "flags", sb, kmClass.getFlags() + "");
+    appendKeyValue(indent, "name", sb, kmClass.getName());
+    appendKeyValue(
+        indent,
+        "typeParameters",
+        sb,
+        newIndent -> {
+          appendTypeParameters(newIndent, sb, kmClass.getTypeParameters());
+        });
+    appendKeyValue(
+        indent,
+        "superTypes",
+        sb,
+        newIndent -> {
+          appendKmList(
+              newIndent,
+              "KmType",
+              sb,
+              kmClass.getSupertypes(),
+              (nextIndent, kmType) -> {
+                appendKmType(nextIndent, sb, kmType);
+              });
+        });
+    String companionObject = kmClass.getCompanionObject();
+    appendKeyValue(
+        indent, "enumEntries", sb, "[" + StringUtils.join(kmClass.getEnumEntries(), ",") + "]");
+    appendKeyValue(
+        indent, "companionObject", sb, companionObject == null ? "null" : companionObject);
+    appendKeyValue(
+        indent,
+        "sealedSubclasses",
+        sb,
+        "[" + StringUtils.join(kmClass.getSealedSubclasses(), ",") + "]");
+    appendKeyValue(
+        indent, "nestedClasses", sb, "[" + StringUtils.join(kmClass.getNestedClasses(), ",") + "]");
+    appendKeyValue(
+        indent,
+        "anonymousObjectOriginName",
+        sb,
+        JvmExtensionsKt.getAnonymousObjectOriginName(kmClass));
+    appendKeyValue(indent, "moduleName", sb, JvmExtensionsKt.getModuleName(kmClass));
+    appendKeyValue(
+        indent,
+        "localDelegatedProperties",
+        sb,
+        nextIndent -> {
+          appendKmList(
+              nextIndent,
+              "KmProperty",
+              sb,
+              JvmExtensionsKt.getLocalDelegatedProperties(kmClass),
+              (nextNextIndent, kmProperty) -> {
+                appendKmProperty(nextNextIndent, sb, kmProperty);
+              });
+        });
+    appendKeyValue(
+        indent,
+        "constructors",
+        sb,
+        newIndent -> {
+          appendKmList(
+              newIndent,
+              "KmConstructor",
+              sb,
+              kmClass.getConstructors(),
+              (nextIndent, constructor) -> {
+                appendKmConstructor(nextIndent, sb, constructor);
+              });
+        });
+    appendKmDeclarationContainer(indent, sb, kmClass);
+  }
+
+  private static void appendKmConstructor(
+      String indent, StringBuilder sb, KmConstructor constructor) {
+    appendKmSection(
+        indent,
+        "KmConstructor",
+        sb,
+        newIndent -> {
+          appendKeyValue(newIndent, "flags", sb, constructor.getFlags() + "");
+          appendKeyValue(
+              newIndent,
+              "valueParameters",
+              sb,
+              nextIndent ->
+                  appendValueParameters(nextIndent, sb, constructor.getValueParameters()));
+          JvmMethodSignature signature = JvmExtensionsKt.getSignature(constructor);
+          appendKeyValue(
+              newIndent, "signature", sb, signature != null ? signature.asString() : "null");
+        });
+  }
+
+  private static void appendKmFunction(String indent, StringBuilder sb, KmFunction function) {
+    appendKmSection(
+        indent,
+        "KmFunction",
+        sb,
+        newIndent -> {
+          appendKeyValue(newIndent, "flags", sb, function.getFlags() + "");
+          appendKeyValue(newIndent, "name", sb, function.getName());
+          appendKeyValue(
+              newIndent,
+              "receiverParameterType",
+              sb,
+              nextIndent -> appendKmType(nextIndent, sb, function.getReceiverParameterType()));
+          appendKeyValue(
+              newIndent,
+              "returnType",
+              sb,
+              nextIndent -> appendKmType(nextIndent, sb, function.getReturnType()));
+          appendKeyValue(
+              newIndent,
+              "typeParameters",
+              sb,
+              nextIndent -> appendTypeParameters(nextIndent, sb, function.getTypeParameters()));
+          appendKeyValue(
+              newIndent,
+              "valueParameters",
+              sb,
+              nextIndent -> appendValueParameters(nextIndent, sb, function.getValueParameters()));
+          JvmMethodSignature signature = JvmExtensionsKt.getSignature(function);
+          appendKeyValue(
+              newIndent, "signature", sb, signature != null ? signature.asString() : "null");
+          appendKeyValue(
+              newIndent,
+              "lambdaClassOriginName",
+              sb,
+              JvmExtensionsKt.getLambdaClassOriginName(function));
+        });
+  }
+
+  private static void appendKmProperty(String indent, StringBuilder sb, KmProperty kmProperty) {
+    appendKmSection(
+        indent,
+        "KmProperty",
+        sb,
+        newIndent -> {
+          appendKeyValue(newIndent, "flags", sb, kmProperty.getFlags() + "");
+          appendKeyValue(newIndent, "name", sb, kmProperty.getName());
+          appendKeyValue(
+              newIndent,
+              "receiverParameterType",
+              sb,
+              nextIndent -> appendKmType(nextIndent, sb, kmProperty.getReceiverParameterType()));
+          appendKeyValue(
+              newIndent,
+              "returnType",
+              sb,
+              nextIndent -> appendKmType(nextIndent, sb, kmProperty.getReturnType()));
+          appendKeyValue(
+              newIndent,
+              "typeParameters",
+              sb,
+              nextIndent -> appendTypeParameters(nextIndent, sb, kmProperty.getTypeParameters()));
+          appendKeyValue(newIndent, "getterFlags", sb, kmProperty.getGetterFlags() + "");
+          appendKeyValue(newIndent, "setterFlags", sb, kmProperty.getSetterFlags() + "");
+          appendKeyValue(
+              newIndent,
+              "setterParameter",
+              sb,
+              nextIndent -> appendValueParameter(nextIndent, sb, kmProperty.getSetterParameter()));
+          appendKeyValue(newIndent, "jvmFlags", sb, JvmExtensionsKt.getJvmFlags(kmProperty) + "");
+          JvmFieldSignature fieldSignature = JvmExtensionsKt.getFieldSignature(kmProperty);
+          appendKeyValue(
+              newIndent,
+              "fieldSignature",
+              sb,
+              fieldSignature != null ? fieldSignature.asString() : "null");
+          JvmMethodSignature getterSignature = JvmExtensionsKt.getGetterSignature(kmProperty);
+          appendKeyValue(
+              newIndent,
+              "getterSignature",
+              sb,
+              getterSignature != null ? getterSignature.asString() : "null");
+          JvmMethodSignature setterSignature = JvmExtensionsKt.getSetterSignature(kmProperty);
+          appendKeyValue(
+              newIndent,
+              "setterSignature",
+              sb,
+              setterSignature != null ? setterSignature.asString() : "null");
+          JvmMethodSignature syntheticMethod =
+              JvmExtensionsKt.getSyntheticMethodForAnnotations(kmProperty);
+          appendKeyValue(
+              newIndent,
+              "syntheticMethodForAnnotations",
+              sb,
+              syntheticMethod != null ? syntheticMethod.asString() : "null");
+        });
+  }
+
+  private static void appendKmType(String indent, StringBuilder sb, KmType kmType) {
+    if (kmType == null) {
+      sb.append("null");
+      return;
     }
-    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();
+    appendKmSection(
+        indent,
+        "KmType",
+        sb,
+        newIndent -> {
+          appendKeyValue(newIndent, "flags", sb, kmType.getFlags() + "");
+          appendKeyValue(newIndent, "classifier", sb, kmType.classifier.toString());
+          appendKeyValue(
+              newIndent,
+              "arguments",
+              sb,
+              nextIndent -> {
+                appendKmList(
+                    nextIndent,
+                    "KmTypeProjection",
+                    sb,
+                    kmType.getArguments(),
+                    (nextNextIndent, kmTypeProjection) -> {
+                      appendKmTypeProjection(nextNextIndent, sb, kmTypeProjection);
+                    });
+              });
+          appendKeyValue(
+              newIndent,
+              "abbreviatedType",
+              sb,
+              nextIndent -> appendKmType(newIndent, sb, kmType.getAbbreviatedType()));
+          appendKeyValue(
+              newIndent,
+              "outerType",
+              sb,
+              nextIndent -> appendKmType(newIndent, sb, kmType.getOuterType()));
+          appendKeyValue(
+              newIndent,
+              "extensions",
+              sb,
+              nextIndent -> {
+                appendKmList(
+                    nextIndent,
+                    "KmAnnotion",
+                    sb,
+                    JvmExtensionsKt.getAnnotations(kmType),
+                    (nextNextIndent, kmAnnotation) -> {
+                      appendKmAnnotation(nextNextIndent, sb, kmAnnotation);
+                    });
+              });
+        });
   }
 
-  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);
+  private static void appendKmTypeProjection(
+      String indent, StringBuilder sb, KmTypeProjection projection) {
+    appendKmSection(
+        indent,
+        "KmTypeProjection",
+        sb,
+        newIndent -> {
+          appendKeyValue(
+              newIndent,
+              "type",
+              sb,
+              nextIndent -> {
+                appendKmType(nextIndent, sb, projection.getType());
+              });
+          if (projection.getVariance() != null) {
+            appendKeyValue(newIndent, "variance", sb, projection.getVariance().name());
+          }
+        });
+  }
+
+  private static void appendValueParameters(
+      String indent, StringBuilder sb, List<KmValueParameter> valueParameters) {
+    appendKmList(
+        indent,
+        "KmValueParameter",
+        sb,
+        valueParameters,
+        (newIndent, parameter) -> {
+          appendValueParameter(newIndent, sb, parameter);
+        });
+  }
+
+  private static void appendValueParameter(
+      String indent, StringBuilder sb, KmValueParameter valueParameter) {
+    if (valueParameter == null) {
+      sb.append("null");
+      return;
     }
-    sb.append(indent);
-    sb.append("}");
-    sb.append(StringUtils.LINE_SEPARATOR);
+    appendKmSection(
+        indent,
+        "KmValueParameter",
+        sb,
+        newIndent -> {
+          appendKeyValue(newIndent, "flags", sb, valueParameter.getFlags() + "");
+          appendKeyValue(newIndent, "name", sb, valueParameter.getName());
+          appendKeyValue(
+              newIndent,
+              "type",
+              sb,
+              nextIndent -> {
+                appendKmType(nextIndent, sb, valueParameter.getType());
+              });
+          appendKeyValue(
+              newIndent,
+              "varargElementType",
+              sb,
+              nextIndent -> {
+                appendKmType(nextIndent, sb, valueParameter.getVarargElementType());
+              });
+        });
   }
 
-  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 static void appendTypeParameters(
+      String indent, StringBuilder sb, List<KmTypeParameter> typeParameters) {
+    appendKmList(
+        indent,
+        "KmTypeParameter",
+        sb,
+        typeParameters,
+        (newIndent, parameter) -> {
+          appendTypeParameter(newIndent, sb, parameter);
+        });
   }
 
-  private String kmTypeToString(KmType type) {
-    return DescriptorUtils.getDescriptorFromKmType(type);
+  private static void appendTypeParameter(
+      String indent, StringBuilder sb, KmTypeParameter typeParameter) {
+    appendKmSection(
+        indent,
+        "KmTypeParameter",
+        sb,
+        newIndent -> {
+          appendKeyValue(newIndent, "id", sb, typeParameter.getId() + "");
+          appendKeyValue(newIndent, "flags", sb, typeParameter.getFlags() + "");
+          appendKeyValue(newIndent, "name", sb, typeParameter.getName());
+          appendKeyValue(newIndent, "variance", sb, typeParameter.getVariance().name());
+          appendKeyValue(
+              newIndent,
+              "upperBounds",
+              sb,
+              nextIndent -> {
+                appendKmList(
+                    nextIndent,
+                    "KmType",
+                    sb,
+                    typeParameter.getUpperBounds(),
+                    (nextNextIndent, kmType) -> {
+                      appendKmType(nextNextIndent, sb, kmType);
+                    });
+              });
+          appendKeyValue(
+              newIndent,
+              "extensions",
+              sb,
+              nextIndent -> {
+                appendKmList(
+                    nextIndent,
+                    "KmAnnotion",
+                    sb,
+                    JvmExtensionsKt.getAnnotations(typeParameter),
+                    (nextNextIndent, kmAnnotation) -> {
+                      appendKmAnnotation(nextNextIndent, sb, kmAnnotation);
+                    });
+              });
+        });
+  }
+
+  private static void appendTypeAlias(String indent, StringBuilder sb, KmTypeAlias kmTypeAlias) {
+    appendKmSection(
+        indent,
+        "KmTypeAlias",
+        sb,
+        newIndent -> {
+          appendKeyValue(
+              newIndent,
+              "annotations",
+              sb,
+              nextIndent -> {
+                appendKmList(
+                    nextIndent,
+                    "KmAnnotation",
+                    sb,
+                    kmTypeAlias.getAnnotations(),
+                    (nextNextIndent, kmAnnotation) -> {
+                      appendKmAnnotation(nextNextIndent, sb, kmAnnotation);
+                    });
+              });
+          appendKeyValue(
+              newIndent,
+              "expandedType",
+              sb,
+              nextIndent -> {
+                appendKmType(nextIndent, sb, kmTypeAlias.expandedType);
+              });
+          appendKeyValue(newIndent, "flags", sb, kmTypeAlias.getFlags() + "");
+          appendKeyValue(newIndent, "name", sb, kmTypeAlias.getName());
+          appendKeyValue(
+              newIndent,
+              "typeParameters",
+              sb,
+              nextIndent -> {
+                appendTypeParameters(nextIndent, sb, kmTypeAlias.getTypeParameters());
+              });
+          appendKeyValue(
+              newIndent,
+              "underlyingType",
+              sb,
+              nextIndent -> {
+                appendKmType(nextIndent, sb, kmTypeAlias.underlyingType);
+              });
+        });
+  }
+
+  private static void appendKmAnnotation(
+      String indent, StringBuilder sb, KmAnnotation kmAnnotation) {
+    appendKmSection(
+        indent,
+        "KmAnnotation",
+        sb,
+        newIndent -> {
+          appendKeyValue(newIndent, "className", sb, kmAnnotation.getClassName());
+          appendKeyValue(
+              newIndent,
+              "arguments",
+              sb,
+              nextIndent -> {
+                Map<String, KmAnnotationArgument<?>> arguments = kmAnnotation.getArguments();
+                for (String key : arguments.keySet()) {
+                  appendKeyValue(nextIndent, key, sb, arguments.get(key).toString());
+                }
+              });
+        });
   }
 
   @Override
diff --git a/src/main/java/com/android/tools/r8/kotlin/KotlinMemberInfo.java b/src/main/java/com/android/tools/r8/kotlin/KotlinMemberInfo.java
index dfdb28b..ed7b49f 100644
--- a/src/main/java/com/android/tools/r8/kotlin/KotlinMemberInfo.java
+++ b/src/main/java/com/android/tools/r8/kotlin/KotlinMemberInfo.java
@@ -3,6 +3,14 @@
 // BSD-style license that can be found in the LICENSE file.
 package com.android.tools.r8.kotlin;
 
+import static com.android.tools.r8.kotlin.KotlinMemberInfo.MemberKind.CONSTRUCTOR;
+import static com.android.tools.r8.kotlin.KotlinMemberInfo.MemberKind.EXTENSION_FUNCTION;
+import static com.android.tools.r8.kotlin.KotlinMemberInfo.MemberKind.EXTENSION_PROPERTY_GETTER;
+import static com.android.tools.r8.kotlin.KotlinMemberInfo.MemberKind.EXTENSION_PROPERTY_SETTER;
+import static com.android.tools.r8.kotlin.KotlinMemberInfo.MemberKind.FUNCTION;
+import static com.android.tools.r8.kotlin.KotlinMemberInfo.MemberKind.PROPERTY_GETTER;
+import static com.android.tools.r8.kotlin.KotlinMemberInfo.MemberKind.PROPERTY_SETTER;
+import static com.android.tools.r8.kotlin.KotlinMetadataJvmExtensionUtils.getJvmMethodSignature;
 import static com.android.tools.r8.kotlin.KotlinMetadataJvmExtensionUtils.toJvmFieldSignature;
 import static com.android.tools.r8.kotlin.KotlinMetadataJvmExtensionUtils.toJvmMethodSignature;
 import static com.android.tools.r8.kotlin.KotlinMetadataSynthesizer.isExtension;
@@ -10,7 +18,6 @@
 import com.android.tools.r8.graph.DexClass;
 import com.android.tools.r8.graph.DexEncodedField;
 import com.android.tools.r8.graph.DexEncodedMethod;
-import com.android.tools.r8.kotlin.KotlinMetadataJvmExtensionUtils.KmFunctionProcessor;
 import com.android.tools.r8.kotlin.KotlinMetadataJvmExtensionUtils.KmPropertyProcessor;
 import com.android.tools.r8.utils.Reporter;
 import com.google.common.collect.ImmutableList;
@@ -18,18 +25,20 @@
 import java.util.HashMap;
 import java.util.List;
 import java.util.Map;
+import kotlinx.metadata.KmConstructor;
 import kotlinx.metadata.KmDeclarationContainer;
 import kotlinx.metadata.KmFunction;
 import kotlinx.metadata.KmProperty;
+import kotlinx.metadata.KmType;
 import kotlinx.metadata.KmValueParameter;
+import kotlinx.metadata.jvm.JvmMethodSignature;
 
 // Provides access to field/method-level Kotlin information.
-public class KotlinMemberInfo {
-  private static final List<KmValueParameter> EMPTY_PARAM = ImmutableList.of();
+public abstract class KotlinMemberInfo {
+
   private static final List<KotlinValueParameterInfo> EMPTY_PARAM_INFO = ImmutableList.of();
 
-  private static final KotlinMemberInfo NO_KOTLIN_MEMBER_INFO =
-      new KotlinMemberInfo(MemberKind.NONE, 0, EMPTY_PARAM);
+  private static final KotlinMemberInfo NO_KOTLIN_MEMBER_INFO = new NoKotlinMemberInfo();
 
   public static KotlinMemberInfo getNoKotlinMemberInfo() {
     return NO_KOTLIN_MEMBER_INFO;
@@ -38,76 +47,198 @@
   public final MemberKind memberKind;
   // Original member flags. May be necessary to keep Kotlin-specific flag, e.g., suspend function.
   final int flags;
-  // TODO(b/151194869): better to split into FunctionInfo v.s. PropertyInfo ?
-  // Original property flags. E.g., for property getter, getter flags are stored to `flags`, while
-  // the property's flags are stored here, in case of properties without a backing field.
-  final int propertyFlags;
-  // Original property name for (extension) property. Otherwise, null.
-  final String propertyName;
-  // Information from original KmValueParameter(s) if available. Otherwise, null.
-  private final List<KotlinValueParameterInfo> valueParameterInfos;
 
-  // Constructor for KmFunction
-  private KotlinMemberInfo(
-      MemberKind memberKind, int flags, List<KmValueParameter> kmValueParameters) {
-    this(memberKind, flags, 0, null, kmValueParameters);
-  }
-
-  // Constructor for a backing field and a getter in KmProperty
-  private KotlinMemberInfo(
-      MemberKind memberKind, int flags, int propertyFlags, String propertyName) {
-    this(memberKind, flags, propertyFlags, propertyName, EMPTY_PARAM);
-  }
-
-  // Constructor for a setter in KmProperty
-  private KotlinMemberInfo(
-      MemberKind memberKind,
-      int flags,
-      int propertyFlags,
-      String propertyName,
-      KmValueParameter kmValueParameter) {
-    this(
-        memberKind,
-        flags,
-        propertyFlags,
-        propertyName,
-        kmValueParameter != null ? ImmutableList.of(kmValueParameter) : EMPTY_PARAM);
-  }
-
-  private KotlinMemberInfo(
-      MemberKind memberKind,
-      int flags,
-      int propertyFlags,
-      String propertyName,
-      List<KmValueParameter> kmValueParameters) {
+  private KotlinMemberInfo(MemberKind memberKind, int flags) {
     this.memberKind = memberKind;
     this.flags = flags;
-    this.propertyFlags = propertyFlags;
-    this.propertyName = propertyName;
-    assert kmValueParameters != null;
-    if (kmValueParameters.isEmpty()) {
-      this.valueParameterInfos = EMPTY_PARAM_INFO;
-    } else {
-      this.valueParameterInfos = new ArrayList<>(kmValueParameters.size());
-      for (KmValueParameter kmValueParameter : kmValueParameters) {
-        valueParameterInfos.add(KotlinValueParameterInfo.fromKmValueParameter(kmValueParameter));
-      }
+  }
+
+  public boolean isFunctionInfo() {
+    return false;
+  }
+
+  public KotlinFunctionInfo asFunctionInfo() {
+    return null;
+  }
+
+  public boolean isFieldInfo() {
+    return false;
+  }
+
+  public KotlinFieldInfo asFieldInfo() {
+    return null;
+  }
+
+  public boolean isPropertyInfo() {
+    return false;
+  }
+
+  public KotlinPropertyInfo asPropertyInfo() {
+    return null;
+  }
+
+  private static class NoKotlinMemberInfo extends KotlinMemberInfo {
+
+    private NoKotlinMemberInfo() {
+      super(MemberKind.NONE, 0);
     }
   }
 
-  KotlinValueParameterInfo getValueParameterInfo(int i) {
-    if (valueParameterInfos.isEmpty()) {
-      return null;
+  public static class KotlinFunctionInfo extends KotlinMemberInfo {
+
+    // Information from original KmValueParameter(s) if available.
+    final List<KotlinValueParameterInfo> valueParameterInfos;
+    // Information from original KmFunction.returnType. Null if this is from a KmConstructor.
+    public final KotlinTypeInfo returnType;
+
+    private KotlinFunctionInfo(
+        MemberKind memberKind,
+        int flags,
+        KotlinTypeInfo returnType,
+        List<KotlinValueParameterInfo> valueParameterInfos) {
+      super(memberKind, flags);
+      assert memberKind.isFunction() || memberKind.isConstructor();
+      this.returnType = returnType;
+      this.valueParameterInfos = valueParameterInfos;
     }
-    if (i < 0 || i >= valueParameterInfos.size()) {
-      return null;
+
+    KotlinValueParameterInfo getValueParameterInfo(int i) {
+      if (valueParameterInfos.isEmpty()) {
+        return null;
+      }
+      if (i < 0 || i >= valueParameterInfos.size()) {
+        return null;
+      }
+      return valueParameterInfos.get(i);
     }
-    return valueParameterInfos.get(i);
+
+    @Override
+    public boolean isFunctionInfo() {
+      return true;
+    }
+
+    @Override
+    public KotlinFunctionInfo asFunctionInfo() {
+      return this;
+    }
+  }
+
+  public static class KotlinFieldInfo extends KotlinMemberInfo {
+
+    // Original property name for (extension) property. Otherwise, null.
+    final String propertyName;
+
+    private KotlinFieldInfo(MemberKind memberKind, int flags, String propertyName) {
+      super(memberKind, flags);
+      this.propertyName = propertyName;
+    }
+
+    @Override
+    public boolean isFieldInfo() {
+      return true;
+    }
+
+    @Override
+    public KotlinFieldInfo asFieldInfo() {
+      return this;
+    }
+  }
+
+  public static class KotlinPropertyInfo extends KotlinMemberInfo {
+
+    // Original getter flags. E.g., for property getter.
+    final int getterFlags;
+
+    // Original setter flags. E.g., for property setter.
+    final int setterFlags;
+
+    // Original property name for (extension) property. Otherwise, null.
+    final String propertyName;
+
+    // Original return type information. This should never be NULL (even for setters without field).
+    final KotlinTypeInfo returnType;
+
+    // Information from original KmValueParameter if available.
+    final KotlinValueParameterInfo valueParameterInfo;
+
+    private KotlinPropertyInfo(
+        MemberKind memberKind,
+        int flags,
+        int getterFlags,
+        int setterFlags,
+        String propertyName,
+        KotlinTypeInfo returnType,
+        KotlinValueParameterInfo valueParameterInfo) {
+      super(memberKind, flags);
+      this.getterFlags = getterFlags;
+      this.setterFlags = setterFlags;
+      this.propertyName = propertyName;
+      this.returnType = returnType;
+      this.valueParameterInfo = valueParameterInfo;
+    }
+
+    @Override
+    public KotlinPropertyInfo asPropertyInfo() {
+      return this;
+    }
+
+    @Override
+    public boolean isPropertyInfo() {
+      return true;
+    }
+  }
+
+  private static KotlinFunctionInfo createFunctionInfoFromConstructor(KmConstructor kmConstructor) {
+    return createFunctionInfo(
+        CONSTRUCTOR, kmConstructor.getFlags(), null, kmConstructor.getValueParameters());
+  }
+
+  private static KotlinFunctionInfo createFunctionInfo(
+      MemberKind memberKind, KmFunction kmFunction) {
+    return createFunctionInfo(
+        memberKind,
+        kmFunction.getFlags(),
+        kmFunction.getReturnType(),
+        kmFunction.getValueParameters());
+  }
+
+  private static KotlinFunctionInfo createFunctionInfo(
+      MemberKind memberKind, int flags, KmType returnType, List<KmValueParameter> valueParameters) {
+    assert memberKind.isFunction() || memberKind.isConstructor();
+    KotlinTypeInfo returnTypeInfo = KotlinTypeInfo.create(returnType);
+    assert memberKind.isFunction() || memberKind.isConstructor();
+    if (valueParameters.isEmpty()) {
+      return new KotlinFunctionInfo(memberKind, flags, returnTypeInfo, EMPTY_PARAM_INFO);
+    }
+    List<KotlinValueParameterInfo> valueParameterInfos = new ArrayList<>(valueParameters.size());
+    for (KmValueParameter kmValueParameter : valueParameters) {
+      valueParameterInfos.add(KotlinValueParameterInfo.fromKmValueParameter(kmValueParameter));
+    }
+    return new KotlinFunctionInfo(memberKind, flags, returnTypeInfo, valueParameterInfos);
+  }
+
+  private static KotlinFieldInfo createFieldInfo(MemberKind memberKind, KmProperty kmProperty) {
+    assert memberKind.isBackingField() || memberKind.isBackingFieldForCompanionObject();
+    return new KotlinFieldInfo(memberKind, kmProperty.getFlags(), kmProperty.getName());
+  }
+
+  private static KotlinPropertyInfo createPropertyInfo(
+      MemberKind memberKind, KmProperty kmProperty) {
+    assert memberKind.isProperty();
+    return new KotlinPropertyInfo(
+        memberKind,
+        kmProperty.getFlags(),
+        kmProperty.getGetterFlags(),
+        kmProperty.getSetterFlags(),
+        kmProperty.getName(),
+        KotlinTypeInfo.create(kmProperty.getReturnType()),
+        KotlinValueParameterInfo.fromKmValueParameter(kmProperty.getSetterParameter()));
   }
 
   public enum MemberKind {
     NONE,
 
+    CONSTRUCTOR,
     FUNCTION,
     EXTENSION_FUNCTION,
 
@@ -122,6 +253,10 @@
     EXTENSION_PROPERTY_SETTER,
     EXTENSION_PROPERTY_ANNOTATIONS;
 
+    public boolean isConstructor() {
+      return this == CONSTRUCTOR;
+    }
+
     public boolean isFunction() {
       return this == FUNCTION || isExtensionFunction();
     }
@@ -159,23 +294,37 @@
       return;
     }
 
-    KmDeclarationContainer kmDeclarationContainer = kotlinInfo.getDeclarations();
-    String companionObject = null;
-    if (kotlinInfo.isClass()) {
-      companionObject = kotlinInfo.asClass().kmClass.getCompanionObject();
-    }
-
+    Map<String, KmConstructor> kmConstructorMap = new HashMap<>();
     Map<String, KmFunction> kmFunctionMap = new HashMap<>();
     Map<String, KmProperty> kmPropertyFieldMap = new HashMap<>();
     Map<String, KmProperty> kmPropertyGetterMap = new HashMap<>();
     Map<String, KmProperty> kmPropertySetterMap = new HashMap<>();
 
-    kmDeclarationContainer.getFunctions().forEach(kmFunction -> {
-      KmFunctionProcessor functionProcessor = new KmFunctionProcessor(kmFunction, reporter);
-      if (functionProcessor.signature() != null) {
-        kmFunctionMap.put(functionProcessor.signature().asString(), kmFunction);
-      }
-    });
+    KmDeclarationContainer kmDeclarationContainer = kotlinInfo.getDeclarations();
+    String companionObject = null;
+    if (kotlinInfo.isClass()) {
+      companionObject = kotlinInfo.asClass().kmClass.getCompanionObject();
+      kotlinInfo
+          .asClass()
+          .kmClass
+          .getConstructors()
+          .forEach(
+              kmConstructor -> {
+                JvmMethodSignature methodSignature = getJvmMethodSignature(kmConstructor, reporter);
+                if (methodSignature != null) {
+                  kmConstructorMap.put(methodSignature.asString(), kmConstructor);
+                }
+              });
+    }
+    kmDeclarationContainer
+        .getFunctions()
+        .forEach(
+            kmFunction -> {
+              JvmMethodSignature methodSignature = getJvmMethodSignature(kmFunction, reporter);
+              if (methodSignature != null) {
+                kmFunctionMap.put(methodSignature.asString(), kmFunction);
+              }
+            });
     kmDeclarationContainer.getProperties().forEach(kmProperty -> {
       KmPropertyProcessor propertyProcessor = new KmPropertyProcessor(kmProperty, reporter);
       if (propertyProcessor.fieldSignature() != null) {
@@ -200,72 +349,35 @@
       if (kmPropertyFieldMap.containsKey(key)) {
         KmProperty kmProperty = kmPropertyFieldMap.get(key);
         field.setKotlinMemberInfo(
-            new KotlinMemberInfo(
+            createFieldInfo(
                 clazz == kotlinInfo.clazz
                     ? MemberKind.PROPERTY_BACKING_FIELD
                     : MemberKind.COMPANION_OBJECT_BACKING_FIELD,
-                kmProperty.getFlags(),
-                kmProperty.getFlags(),
-                kmProperty.getName()));
+                kmProperty));
       }
     }
 
     for (DexEncodedMethod method : clazz.methods()) {
-      if (method.isInitializer()) {
-        continue;
-      }
       String key = toJvmMethodSignature(method.method).asString();
-      if (kmFunctionMap.containsKey(key)) {
+      if (kmConstructorMap.containsKey(key)) {
+        // Interestingly we cannot assert that the method is a jvm initializer, because the jvm
+        // signature can be a different.
+        method.setKotlinMemberInfo(createFunctionInfoFromConstructor(kmConstructorMap.get(key)));
+      } else if (kmFunctionMap.containsKey(key)) {
         KmFunction kmFunction = kmFunctionMap.get(key);
-        if (isExtension(kmFunction)) {
-          method.setKotlinMemberInfo(
-              new KotlinMemberInfo(
-                  MemberKind.EXTENSION_FUNCTION,
-                  kmFunction.getFlags(),
-                  kmFunction.getValueParameters()));
-        } else {
-          method.setKotlinMemberInfo(
-              new KotlinMemberInfo(
-                  MemberKind.FUNCTION,
-                  kmFunction.getFlags(),
-                  kmFunction.getValueParameters()));
-        }
+        method.setKotlinMemberInfo(
+            createFunctionInfo(
+                isExtension(kmFunction) ? EXTENSION_FUNCTION : FUNCTION, kmFunction));
       } else if (kmPropertyGetterMap.containsKey(key)) {
         KmProperty kmProperty = kmPropertyGetterMap.get(key);
-        if (isExtension(kmProperty)) {
-          method.setKotlinMemberInfo(
-              new KotlinMemberInfo(
-                  MemberKind.EXTENSION_PROPERTY_GETTER,
-                  kmProperty.getGetterFlags(),
-                  kmProperty.getFlags(),
-                  kmProperty.getName()));
-        } else {
-          method.setKotlinMemberInfo(
-              new KotlinMemberInfo(
-                  MemberKind.PROPERTY_GETTER,
-                  kmProperty.getGetterFlags(),
-                  kmProperty.getFlags(),
-                  kmProperty.getName()));
-        }
+        method.setKotlinMemberInfo(
+            createPropertyInfo(
+                isExtension(kmProperty) ? EXTENSION_PROPERTY_GETTER : PROPERTY_GETTER, kmProperty));
       } else if (kmPropertySetterMap.containsKey(key)) {
         KmProperty kmProperty = kmPropertySetterMap.get(key);
-        if (isExtension(kmProperty)) {
-          method.setKotlinMemberInfo(
-              new KotlinMemberInfo(
-                  MemberKind.EXTENSION_PROPERTY_SETTER,
-                  kmProperty.getSetterFlags(),
-                  kmProperty.getFlags(),
-                  kmProperty.getName(),
-                  kmProperty.getSetterParameter()));
-        } else {
-          method.setKotlinMemberInfo(
-              new KotlinMemberInfo(
-                  MemberKind.PROPERTY_SETTER,
-                  kmProperty.getSetterFlags(),
-                  kmProperty.getFlags(),
-                  kmProperty.getName(),
-                  kmProperty.getSetterParameter()));
-        }
+        method.setKotlinMemberInfo(
+            createPropertyInfo(
+                isExtension(kmProperty) ? EXTENSION_PROPERTY_SETTER : PROPERTY_SETTER, kmProperty));
       }
     }
   }
diff --git a/src/main/java/com/android/tools/r8/kotlin/KotlinMetadataJvmExtensionUtils.java b/src/main/java/com/android/tools/r8/kotlin/KotlinMetadataJvmExtensionUtils.java
index 474c123..855a71f 100644
--- a/src/main/java/com/android/tools/r8/kotlin/KotlinMetadataJvmExtensionUtils.java
+++ b/src/main/java/com/android/tools/r8/kotlin/KotlinMetadataJvmExtensionUtils.java
@@ -24,14 +24,12 @@
 import kotlinx.metadata.KmConstructorVisitor;
 import kotlinx.metadata.KmExtensionType;
 import kotlinx.metadata.KmFunction;
-import kotlinx.metadata.KmFunctionExtensionVisitor;
-import kotlinx.metadata.KmFunctionVisitor;
 import kotlinx.metadata.KmProperty;
 import kotlinx.metadata.KmPropertyExtensionVisitor;
 import kotlinx.metadata.KmPropertyVisitor;
 import kotlinx.metadata.jvm.JvmConstructorExtensionVisitor;
+import kotlinx.metadata.jvm.JvmExtensionsKt;
 import kotlinx.metadata.jvm.JvmFieldSignature;
-import kotlinx.metadata.jvm.JvmFunctionExtensionVisitor;
 import kotlinx.metadata.jvm.JvmMethodSignature;
 import kotlinx.metadata.jvm.JvmPropertyExtensionVisitor;
 
@@ -174,37 +172,25 @@
     }
   }
 
-  static class KmFunctionProcessor {
-    // Custom name via @JvmName("..."). Otherwise, null.
-    private JvmMethodSignature signature = null;
+  // Custom name via @JvmName("..."). Otherwise, null.
+  static JvmMethodSignature getJvmMethodSignature(KmConstructor kmConstructor, Reporter reporter) {
+    return remapJvmMethodSignature(JvmExtensionsKt.getSignature(kmConstructor), reporter);
+  }
 
-    KmFunctionProcessor(KmFunction kmFunction, Reporter reporter) {
-      kmFunction.accept(new KmFunctionVisitor() {
-        @Override
-        public KmFunctionExtensionVisitor visitExtensions(KmExtensionType type) {
-          if (type != JvmFunctionExtensionVisitor.TYPE) {
-            return null;
-          }
-          return new JvmFunctionExtensionVisitor() {
-            @Override
-            public void visit(JvmMethodSignature desc) {
-              assert signature == null : signature.asString();
-              signature = desc;
-            }
-          };
-        }
-      });
-      if (signature != null) {
-        String remappedDesc = remapKotlinTypeInDesc(signature.getDesc(), reporter);
-        if (remappedDesc != null && !remappedDesc.equals(signature.getDesc())) {
-          signature = new JvmMethodSignature(signature.getName(), remappedDesc);
-        }
+  // Custom name via @JvmName("..."). Otherwise, null.
+  static JvmMethodSignature getJvmMethodSignature(KmFunction kmFunction, Reporter reporter) {
+    return remapJvmMethodSignature(JvmExtensionsKt.getSignature(kmFunction), reporter);
+  }
+
+  private static JvmMethodSignature remapJvmMethodSignature(
+      JvmMethodSignature signature, Reporter reporter) {
+    if (signature != null) {
+      String remappedDesc = remapKotlinTypeInDesc(signature.getDesc(), reporter);
+      if (remappedDesc != null && !remappedDesc.equals(signature.getDesc())) {
+        signature = new JvmMethodSignature(signature.getName(), remappedDesc);
       }
     }
-
-    JvmMethodSignature signature() {
-      return signature;
-    }
+    return signature;
   }
 
   static class KmPropertyProcessor {
diff --git a/src/main/java/com/android/tools/r8/kotlin/KotlinMetadataRewriter.java b/src/main/java/com/android/tools/r8/kotlin/KotlinMetadataRewriter.java
index 5d1777f..f83796f 100644
--- a/src/main/java/com/android/tools/r8/kotlin/KotlinMetadataRewriter.java
+++ b/src/main/java/com/android/tools/r8/kotlin/KotlinMetadataRewriter.java
@@ -61,6 +61,8 @@
   }
 
   public void run(ExecutorService executorService) throws ExecutionException {
+    // TODO(b/152283077): Don't disable the assert.
+    appView.appInfo().disableDefinitionForAssert();
     ThreadUtils.processItems(
         appView.appInfo().classes(),
         clazz -> {
@@ -92,6 +94,7 @@
           }
         },
         executorService);
+    appView.appInfo().enableDefinitionForAssert();
   }
 
   private DexAnnotation createKotlinMetadataAnnotation(KotlinClassHeader header) {
diff --git a/src/main/java/com/android/tools/r8/kotlin/KotlinMetadataSynthesizer.java b/src/main/java/com/android/tools/r8/kotlin/KotlinMetadataSynthesizer.java
index 43b5cf2..2e90094 100644
--- a/src/main/java/com/android/tools/r8/kotlin/KotlinMetadataSynthesizer.java
+++ b/src/main/java/com/android/tools/r8/kotlin/KotlinMetadataSynthesizer.java
@@ -3,9 +3,10 @@
 // BSD-style license that can be found in the LICENSE file.
 package com.android.tools.r8.kotlin;
 
-import static com.android.tools.r8.kotlin.Kotlin.NAME;
 import static com.android.tools.r8.kotlin.KotlinMetadataJvmExtensionUtils.toJvmFieldSignature;
 import static com.android.tools.r8.kotlin.KotlinMetadataJvmExtensionUtils.toJvmMethodSignature;
+import static com.android.tools.r8.kotlin.KotlinMetadataSynthesizerUtils.populateKmTypeFromSignature;
+import static com.android.tools.r8.kotlin.KotlinMetadataSynthesizerUtils.toClassifier;
 import static com.android.tools.r8.utils.DescriptorUtils.descriptorToKotlinClassifier;
 import static com.android.tools.r8.utils.DescriptorUtils.getBinaryNameFromDescriptor;
 import static com.android.tools.r8.utils.DescriptorUtils.getDescriptorFromKmType;
@@ -24,16 +25,21 @@
 import com.android.tools.r8.graph.GenericSignature;
 import com.android.tools.r8.graph.GenericSignature.ClassTypeSignature;
 import com.android.tools.r8.graph.GenericSignature.FieldTypeSignature;
+import com.android.tools.r8.graph.GenericSignature.FormalTypeParameter;
 import com.android.tools.r8.graph.GenericSignature.MethodTypeSignature;
 import com.android.tools.r8.graph.GenericSignature.TypeSignature;
+import com.android.tools.r8.kotlin.KotlinMemberInfo.KotlinFunctionInfo;
+import com.android.tools.r8.kotlin.KotlinMemberInfo.KotlinPropertyInfo;
 import com.android.tools.r8.naming.NamingLens;
 import com.android.tools.r8.shaking.AppInfoWithLiveness;
+import com.android.tools.r8.utils.Box;
 import java.util.List;
 import java.util.function.Consumer;
 import kotlinx.metadata.KmConstructor;
 import kotlinx.metadata.KmFunction;
 import kotlinx.metadata.KmProperty;
 import kotlinx.metadata.KmType;
+import kotlinx.metadata.KmTypeParameter;
 import kotlinx.metadata.KmTypeProjection;
 import kotlinx.metadata.KmValueParameter;
 import kotlinx.metadata.KmVariance;
@@ -41,6 +47,17 @@
 
 class KotlinMetadataSynthesizer {
 
+  private final AppView<AppInfoWithLiveness> appView;
+  private final NamingLens lens;
+  private final List<KmTypeParameter> classTypeParameters;
+
+  public KotlinMetadataSynthesizer(
+      AppView<AppInfoWithLiveness> appView, NamingLens lens, KotlinInfo<?> kotlinInfo) {
+    this.appView = appView;
+    this.lens = lens;
+    this.classTypeParameters = kotlinInfo.getTypeParameters();
+  }
+
   static boolean isExtension(KmFunction kmFunction) {
     return kmFunction.getReceiverParameterType() != null;
   }
@@ -55,8 +72,7 @@
     return kmType;
   }
 
-  static DexType toRenamedType(
-      DexType type, AppView<AppInfoWithLiveness> appView, NamingLens lens) {
+  private DexType toRenamedType(DexType type) {
     // For library or classpath class, synthesize @Metadata always.
     // For a program class, make sure it is live.
     if (!appView.appInfo().isNonProgramTypeOrLiveProgramType(type)) {
@@ -70,141 +86,92 @@
     return renamedType;
   }
 
-  static String toRenamedBinaryName(
-      DexType type, AppView<AppInfoWithLiveness> appView, NamingLens lens) {
-    DexType renamedType = toRenamedType(type, appView, lens);
+  String toRenamedBinaryName(DexType type) {
+    DexType renamedType = toRenamedType(type);
     if (renamedType == null) {
       return null;
     }
     return getBinaryNameFromDescriptor(renamedType.toDescriptorString());
   }
 
-  static String toRenamedClassifier(
-      DexType type, AppView<AppInfoWithLiveness> appView, NamingLens lens) {
-    // E.g., V -> kotlin/Unit, J -> kotlin/Long, [J -> kotlin/LongArray
-    if (appView.dexItemFactory().kotlin.knownTypeConversion.containsKey(type)) {
-      DexType convertedType = appView.dexItemFactory().kotlin.knownTypeConversion.get(type);
-      assert convertedType != null;
-      return descriptorToKotlinClassifier(convertedType.toDescriptorString());
-    }
-    // E.g., [Ljava/lang/String; -> kotlin/Array
-    if (type.isArrayType()) {
-      return NAME + "/Array";
-    }
-    DexType renamedType = toRenamedType(type, appView, lens);
-    if (renamedType == null) {
+  String toRenamedClassifier(DexType type) {
+    type = type.isClassType() ? toRenamedType(type) : type;
+    if (type == null) {
       return null;
     }
-    return descriptorToKotlinClassifier(renamedType.toDescriptorString());
+    return toClassifier(type, appView.dexItemFactory());
   }
 
   // TODO(b/148654451): Canonicalization?
-  static KmType toRenamedKmType(
+  KmType toRenamedKmType(
       DexType type,
       TypeSignature typeSignature,
-      AppView<AppInfoWithLiveness> appView,
-      NamingLens lens) {
-    if (typeSignature != null
-        && typeSignature.isFieldTypeSignature()
-        && typeSignature.asFieldTypeSignature().isClassTypeSignature()
-        && typeSignature.asFieldTypeSignature().asClassTypeSignature().type() == type) {
-      return toRenamedKmType(
-          typeSignature.asFieldTypeSignature().asClassTypeSignature(), appView, lens);
+      KotlinTypeInfo originalKotlinTypeInfo,
+      ClassTypeSignatureToRenamedKmTypeConverter converter) {
+    if (originalKotlinTypeInfo != null && originalKotlinTypeInfo.isTypeAlias()) {
+      KmType kmType = new KmType(flagsOf());
+      kmType.visitTypeAlias(originalKotlinTypeInfo.asTypeAlias().getName());
+      return kmType;
     }
+    return toRenamedKmTypeWithClassifier(type, typeSignature, converter);
+  }
 
-    String classifier = toRenamedClassifier(type, appView, lens);
+  private KmType toRenamedKmTypeWithClassifierForFieldSignature(
+      DexType type,
+      FieldTypeSignature fieldSignature,
+      ClassTypeSignatureToRenamedKmTypeConverter converter) {
+    if (fieldSignature.isClassTypeSignature()) {
+      ClassTypeSignature classTypeSignature = fieldSignature.asClassTypeSignature();
+      if (classTypeSignature.type() != type) {
+        return null;
+      }
+      return classTypeSignature.convert(converter);
+    }
+    // If we fail to set kmType.classifier we will get a UninitializedPropertyAccessException:
+    // lateinit property classifier has not been initialized.
+    Box<KmType> kmTypeBox = new Box<>();
+    populateKmTypeFromSignature(
+        fieldSignature,
+        () -> {
+          KmType value = new KmType(flagsOf());
+          kmTypeBox.set(value);
+          return value;
+        },
+        converter.getTypeParameters(),
+        appView.dexItemFactory());
+    return kmTypeBox.get();
+  }
+
+  private KmType toRenamedKmTypeWithClassifier(
+      DexType type,
+      TypeSignature typeSignature,
+      ClassTypeSignatureToRenamedKmTypeConverter converter) {
+    if (typeSignature != null && typeSignature.isFieldTypeSignature()) {
+      KmType renamedKmType =
+          toRenamedKmTypeWithClassifierForFieldSignature(
+              type, typeSignature.asFieldTypeSignature(), converter);
+      if (renamedKmType != null) {
+        return renamedKmType;
+      }
+    }
+    String classifier = toRenamedClassifier(type);
     if (classifier == null) {
       return null;
     }
-    // TODO(b/151194869): Mysterious, why attempts to properly set flags bothers kotlinc?
-    //   and/or why wiping out flags works for KmType but not KmFunction?!
-    KmType kmType = new KmType(flagsOf());
-    kmType.visitClass(classifier);
+    // Seems like no flags for KmType are ever set, thus passing in flagsOf() seems ok.
+    KmType renamedKmType = new KmType(flagsOf());
+    renamedKmType.visitClass(classifier);
     // TODO(b/151194164): Can be generalized too, like ArrayTypeSignature.Converter ?
     // E.g., java.lang.String[] -> KmType(kotlin/Array, KmTypeProjection(OUT, kotlin/String))
     if (type.isArrayType() && !type.isPrimitiveArrayType()) {
       DexType elementType = type.toArrayElementType(appView.dexItemFactory());
-      KmType argumentType = toRenamedKmType(elementType, null, appView, lens);
-      kmType.getArguments().add(new KmTypeProjection(KmVariance.OUT, argumentType));
-    }
-    return kmType;
-  }
-
-  static KmType toRenamedKmType(
-      ClassTypeSignature classTypeSignature,
-      AppView<AppInfoWithLiveness> appView,
-      NamingLens lens) {
-    return classTypeSignature.convert(
-        new ClassTypeSignatureToRenamedKmTypeConverter(appView, lens));
-  }
-
-  static class ClassTypeSignatureToRenamedKmTypeConverter
-      implements ClassTypeSignature.Converter<KmType> {
-
-    private final AppView<AppInfoWithLiveness> appView;
-    private final NamingLens lens;
-
-    ClassTypeSignatureToRenamedKmTypeConverter(
-        AppView<AppInfoWithLiveness> appView, NamingLens lens) {
-      this.appView = appView;
-      this.lens = lens;
-    }
-
-    @Override
-    public KmType init() {
-      return new KmType(flagsOf());
-    }
-
-    @Override
-    public KmType visitType(DexType type, KmType result) {
-      if (result == null) {
-        return result;
-      }
-      String classifier = toRenamedClassifier(type, appView, lens);
-      if (classifier == null) {
-        return null;
-      }
-      result.visitClass(classifier);
-      return result;
-    }
-
-    @Override
-    public KmType visitTypeArgument(FieldTypeSignature typeArgument, KmType result) {
-      if (result == null) {
-        return result;
-      }
-      if (typeArgument.isClassTypeSignature()) {
-        KmType argumentType = typeArgument.asClassTypeSignature().convert(this);
-        result.getArguments().add(new KmTypeProjection(KmVariance.INVARIANT, argumentType));
-      }
-      // TODO(b/151194164): for TypeVariableSignature, there is KmType::visitTypeParameter.
-      return result;
-    }
-
-    @Override
-    public KmType visitInnerTypeSignature(ClassTypeSignature innerTypeSignature, KmType result) {
-      // Do nothing
-      return result;
-    }
-  }
-
-  private static KmType setRenamedKmType(
-      DexType type,
-      TypeSignature signature,
-      AppView<AppInfoWithLiveness> appView,
-      NamingLens lens,
-      Consumer<KmType> consumer) {
-    KmType renamedKmType = toRenamedKmType(type, signature, appView, lens);
-    if (renamedKmType != null) {
-      consumer.accept(renamedKmType);
+      KmType argumentType = toRenamedKmTypeWithClassifier(elementType, null, converter);
+      renamedKmType.getArguments().add(new KmTypeProjection(KmVariance.OUT, argumentType));
     }
     return renamedKmType;
   }
 
-  static KmConstructor toRenamedKmConstructor(
-      DexEncodedMethod method,
-      AppView<AppInfoWithLiveness> appView,
-      NamingLens lens) {
+  KmConstructor toRenamedKmConstructor(DexEncodedMethod method) {
     // Make sure it is an instance initializer and live.
     if (!method.isInstanceInitializer()
         || !appView.appInfo().liveMethods.contains(method.method)) {
@@ -213,17 +180,23 @@
     KmConstructor kmConstructor = new KmConstructor(method.accessFlags.getAsKotlinFlags());
     JvmExtensionsKt.setSignature(kmConstructor, toJvmMethodSignature(method.method));
     MethodTypeSignature signature = GenericSignature.Parser.toMethodTypeSignature(method, appView);
+    List<KmTypeParameter> typeParameters =
+        convertFormalTypeParameters(
+            signature.getFormalTypeParameters(),
+            kmTypeParameter -> {
+              assert false : "KmConstructor cannot have additional type parameters";
+            });
+    ClassTypeSignatureToRenamedKmTypeConverter converter =
+        new ClassTypeSignatureToRenamedKmTypeConverter(
+            appView, typeParameters, this::toRenamedClassifier);
     List<KmValueParameter> parameters = kmConstructor.getValueParameters();
-    if (!populateKmValueParameters(parameters, method, signature, appView, lens)) {
+    if (!populateKmValueParameters(method, signature, parameters, converter)) {
       return null;
     }
     return kmConstructor;
   }
 
-  static KmFunction toRenamedKmFunction(
-      DexEncodedMethod method,
-      AppView<AppInfoWithLiveness> appView,
-      NamingLens lens) {
+  KmFunction toRenamedKmFunction(DexEncodedMethod method) {
     // For library overrides, synthesize @Metadata always.
     // For regular methods, make sure it is live or pinned.
     if (!method.isLibraryMethodOverride().isTrue()
@@ -237,9 +210,11 @@
         : method.toSourceString() + " -> " + renamedMethod.toSourceString();
     // TODO(b/151194869): Should we keep kotlin-specific flags only while synthesizing the base
     //  value from general JVM flags?
+    KotlinFunctionInfo kotlinMemberInfo = method.getKotlinMemberInfo().asFunctionInfo();
+    assert kotlinMemberInfo != null;
     int flag =
-        appView.appInfo().isPinned(method.method) && method.getKotlinMemberInfo() != null
-            ? method.getKotlinMemberInfo().flags
+        appView.appInfo().isPinned(method.method)
+            ? kotlinMemberInfo.flags
             : method.accessFlags.getAsKotlinFlags();
     KmFunction kmFunction = new KmFunction(flag, renamedMethod.name.toString());
     JvmExtensionsKt.setSignature(kmFunction, toJvmMethodSignature(renamedMethod));
@@ -248,57 +223,65 @@
     //  on demand? That may require utils to map internal encoding of signature back to
     //  corresponding backend definitions, like DexAnnotation (DEX) or Signature attribute (CF).
     MethodTypeSignature signature = GenericSignature.Parser.toMethodTypeSignature(method, appView);
+    List<KmTypeParameter> typeParameters = kmFunction.getTypeParameters();
+    ClassTypeSignatureToRenamedKmTypeConverter converter =
+        new ClassTypeSignatureToRenamedKmTypeConverter(
+            appView,
+            convertFormalTypeParameters(signature.getFormalTypeParameters(), typeParameters::add),
+            this::toRenamedClassifier);
 
     DexProto proto = method.method.proto;
     DexType returnType = proto.returnType;
     TypeSignature returnSignature = signature.returnType().typeSignature();
-    KmType kmReturnType = setRenamedKmType(
-        returnType, returnSignature, appView, lens, kmFunction::setReturnType);
+    KmType kmReturnType =
+        toRenamedKmType(returnType, returnSignature, kotlinMemberInfo.returnType, converter);
     if (kmReturnType == null) {
       return null;
     }
-
+    kmFunction.setReturnType(kmReturnType);
     if (method.isKotlinExtensionFunction()) {
-      assert proto.parameters.values.length > 0
-          : method.method.toSourceString();
+      assert proto.parameters.values.length > 0 : method.method.toSourceString();
       DexType receiverType = proto.parameters.values[0];
       TypeSignature receiverSignature = signature.getParameterTypeSignature(0);
-      KmType kmReceiverType = setRenamedKmType(
-          receiverType, receiverSignature, appView, lens, kmFunction::setReceiverParameterType);
+      KmType kmReceiverType = toRenamedKmType(receiverType, receiverSignature, null, converter);
       if (kmReceiverType == null) {
         return null;
       }
+      kmFunction.setReceiverParameterType(kmReceiverType);
     }
 
-    List<KmValueParameter> parameters = kmFunction.getValueParameters();
-    if (!populateKmValueParameters(parameters, method, signature, appView, lens)) {
+    if (!populateKmValueParameters(method, signature, kmFunction.getValueParameters(), converter)) {
       return null;
     }
     return kmFunction;
   }
 
-  private static boolean populateKmValueParameters(
-      List<KmValueParameter> parameters,
+  private List<KmTypeParameter> convertFormalTypeParameters(
+      List<FormalTypeParameter> parameters, Consumer<KmTypeParameter> addedFromParameters) {
+    return KotlinMetadataSynthesizerUtils.convertFormalTypeParameters(
+        classTypeParameters, parameters, appView.dexItemFactory(), addedFromParameters);
+  }
+
+  private boolean populateKmValueParameters(
       DexEncodedMethod method,
       MethodTypeSignature signature,
-      AppView<AppInfoWithLiveness> appView,
-      NamingLens lens) {
+      List<KmValueParameter> parameters,
+      ClassTypeSignatureToRenamedKmTypeConverter converter) {
+    KotlinFunctionInfo kotlinFunctionInfo = method.getKotlinMemberInfo().asFunctionInfo();
+    if (kotlinFunctionInfo == null) {
+      return false;
+    }
     boolean isExtension = method.isKotlinExtensionFunction();
     for (int i = isExtension ? 1 : 0; i < method.method.proto.parameters.values.length; i++) {
       DexType parameterType = method.method.proto.parameters.values[i];
       DebugLocalInfo debugLocalInfo = method.getParameterInfo().get(i);
       String parameterName = debugLocalInfo != null ? debugLocalInfo.name.toString() : ("p" + i);
       KotlinValueParameterInfo valueParameterInfo =
-          method.getKotlinMemberInfo().getValueParameterInfo(isExtension ? i - 1 : i);
+          kotlinFunctionInfo.getValueParameterInfo(isExtension ? i - 1 : i);
       TypeSignature parameterTypeSignature = signature.getParameterTypeSignature(i);
       KmValueParameter kmValueParameter =
           toRewrittenKmValueParameter(
-              valueParameterInfo,
-              parameterType,
-              parameterTypeSignature,
-              parameterName,
-              appView,
-              lens);
+              valueParameterInfo, parameterType, parameterTypeSignature, parameterName, converter);
       if (kmValueParameter == null) {
         return false;
       }
@@ -307,26 +290,28 @@
     return true;
   }
 
-  private static KmValueParameter toRewrittenKmValueParameter(
+  private KmValueParameter toRewrittenKmValueParameter(
       KotlinValueParameterInfo valueParameterInfo,
       DexType parameterType,
       TypeSignature parameterTypeSignature,
       String candidateParameterName,
-      AppView<AppInfoWithLiveness> appView,
-      NamingLens lens) {
+      ClassTypeSignatureToRenamedKmTypeConverter converter) {
     int flag = valueParameterInfo != null ? valueParameterInfo.flag : flagsOf();
     String name = valueParameterInfo != null ? valueParameterInfo.name : candidateParameterName;
     KmValueParameter kmValueParameter = new KmValueParameter(flag, name);
-    KmType kmParamType = setRenamedKmType(
-        parameterType, parameterTypeSignature, appView, lens, kmValueParameter::setType);
+    KotlinTypeInfo originalKmTypeInfo = valueParameterInfo != null ? valueParameterInfo.type : null;
+    KmType kmParamType =
+        toRenamedKmType(parameterType, parameterTypeSignature, originalKmTypeInfo, converter);
     if (kmParamType == null) {
       return null;
     }
+    kmValueParameter.setType(kmParamType);
     if (valueParameterInfo != null) {
       JvmExtensionsKt.getAnnotations(kmParamType).addAll(valueParameterInfo.annotations);
     }
 
     if (valueParameterInfo != null && valueParameterInfo.isVararg) {
+      // TODO(b/152389234): Test for arrays in varargs.
       if (!parameterType.isArrayType()) {
         return null;
       }
@@ -334,11 +319,11 @@
       TypeSignature elementSignature =
           parameterTypeSignature != null
               ? parameterTypeSignature.toArrayElementTypeSignature(appView) : null;
-      KmType kmElementType = setRenamedKmType(
-          elementType, elementSignature, appView, lens, kmValueParameter::setVarargElementType);
+      KmType kmElementType = toRenamedKmType(elementType, elementSignature, null, converter);
       if (kmElementType == null) {
         return null;
       }
+      kmValueParameter.setVarargElementType(kmElementType);
     }
 
     return kmValueParameter;
@@ -370,58 +355,65 @@
    * getter, and so on.
    */
   static class KmPropertyGroup {
+
     final int flags;
     final String name;
     final DexEncodedField field;
-    final DexEncodedMethod getter;
-    final int getterFlags;
-    final DexEncodedMethod setter;
-    final int setterFlags;
+    private final DexEncodedMethod getter;
+    private final KotlinPropertyInfo getterInfo;
+    private final DexEncodedMethod setter;
+    private final KotlinPropertyInfo setterInfo;
     final DexEncodedMethod annotations;
     final boolean isExtension;
+    private final List<KmTypeParameter> classTypeParameters;
 
     private KmPropertyGroup(
         int flags,
         String name,
         DexEncodedField field,
         DexEncodedMethod getter,
-        int getterFlags,
+        KotlinPropertyInfo getterInfo,
         DexEncodedMethod setter,
-        int setterFlags,
+        KotlinPropertyInfo setterInfo,
         DexEncodedMethod annotations,
-        boolean isExtension) {
+        boolean isExtension,
+        List<KmTypeParameter> classTypeParameters) {
       this.flags = flags;
       this.name = name;
       this.field = field;
       this.getter = getter;
-      this.getterFlags = getterFlags;
+      this.getterInfo = getterInfo;
       this.setter = setter;
-      this.setterFlags = setterFlags;
+      this.setterInfo = setterInfo;
       this.annotations = annotations;
       this.isExtension = isExtension;
+      this.classTypeParameters = classTypeParameters;
     }
 
-    static Builder builder(int flags, String name) {
-      return new Builder(flags, name);
+    static Builder builder(int flags, String name, List<KmTypeParameter> classTypeParameters) {
+      return new Builder(flags, name, classTypeParameters);
     }
 
     static class Builder {
+
       private final int flags;
       private final String name;
       private DexEncodedField field;
       private DexEncodedMethod getter;
-      private int getterFlags;
+      private KotlinPropertyInfo getterInfo;
       private DexEncodedMethod setter;
-      private int setterFlags;
+      private KotlinPropertyInfo setterInfo;
       private DexEncodedMethod annotations;
+      private List<KmTypeParameter> classTypeParameters;
 
       private boolean isExtensionGetter;
       private boolean isExtensionSetter;
       private boolean isExtensionAnnotations;
 
-      private Builder(int flags, String name) {
+      private Builder(int flags, String name, List<KmTypeParameter> classTypeParameters) {
         this.flags = flags;
         this.name = name;
+        this.classTypeParameters = classTypeParameters;
       }
 
       Builder foundBackingField(DexEncodedField field) {
@@ -429,15 +421,15 @@
         return this;
       }
 
-      Builder foundGetter(DexEncodedMethod getter, int flags) {
+      Builder foundGetter(DexEncodedMethod getter, KotlinPropertyInfo propertyInfo) {
         this.getter = getter;
-        this.getterFlags = flags;
+        this.getterInfo = propertyInfo;
         return this;
       }
 
-      Builder foundSetter(DexEncodedMethod setter, int flags) {
+      Builder foundSetter(DexEncodedMethod setter, KotlinPropertyInfo propertyInfo) {
         this.setter = setter;
-        this.setterFlags = flags;
+        this.setterInfo = propertyInfo;
         return this;
       }
 
@@ -476,177 +468,234 @@
           }
         }
         return new KmPropertyGroup(
-            flags, name, field, getter, getterFlags, setter, setterFlags, annotations, isExtension);
+            flags,
+            name,
+            field,
+            getter,
+            getterInfo,
+            setter,
+            setterInfo,
+            annotations,
+            isExtension,
+            classTypeParameters);
       }
     }
 
-    KmProperty toRenamedKmProperty(AppView<AppInfoWithLiveness> appView, NamingLens lens) {
-      KmProperty kmProperty = new KmProperty(flags, name, flagsOf(), flagsOf());
-      KmType kmPropertyType = null;
-      KmType kmReceiverType = null;
-
-      // A flag to indicate we can rename the property name. This will become false if any member
-      // is pinned. Then, we conservatively assume that users want the property to be pinned too.
-      // That is, we won't rename the property even though some other members could be renamed.
-      boolean canChangePropertyName = true;
-      // A candidate property name. Not overwritten by the following members, hence the order of
-      // preference: a backing field, getter, and setter.
-      String renamedPropertyName = name;
-      if (field != null) {
-        if (appView.appInfo().isPinned(field.field)) {
-          canChangePropertyName = false;
+    private String setFieldForProperty(
+        KmProperty kmProperty,
+        KmType defaultPropertyType,
+        AppView<AppInfoWithLiveness> appView,
+        NamingLens lens,
+        KotlinMetadataSynthesizer synthesizer) {
+      DexField renamedField = lens.lookupField(field.field, appView.dexItemFactory());
+      ClassTypeSignatureToRenamedKmTypeConverter converter =
+          new ClassTypeSignatureToRenamedKmTypeConverter(
+              appView, this.classTypeParameters, synthesizer::toRenamedClassifier);
+      if (kmProperty.getReturnType() == defaultPropertyType) {
+        KmType kmPropertyType =
+            synthesizer.toRenamedKmType(field.field.type, null, null, converter);
+        if (kmPropertyType != null) {
+          kmProperty.setReturnType(kmPropertyType);
         }
-        DexField renamedField = lens.lookupField(field.field, appView.dexItemFactory());
-        if (canChangePropertyName && renamedField.name != field.field.name) {
-          renamedPropertyName = renamedField.name.toString();
-        }
-        FieldTypeSignature signature =
-            GenericSignature.Parser.toFieldTypeSignature(field, appView);
-        kmPropertyType =
-            setRenamedKmType(field.field.type, signature, appView, lens, kmProperty::setReturnType);
-        JvmExtensionsKt.setFieldSignature(kmProperty, toJvmFieldSignature(renamedField));
       }
+      JvmExtensionsKt.setFieldSignature(kmProperty, toJvmFieldSignature(renamedField));
+      return appView.appInfo().isPinned(field.field) ? null : renamedField.name.toString();
+    }
 
-      GetterSetterCriteria criteria = checkGetterCriteria();
-      if (criteria == GetterSetterCriteria.VIOLATE) {
-        return null;
+    private boolean setReceiverParameterTypeForExtensionProperty(
+        KmProperty kmProperty,
+        DexEncodedMethod method,
+        AppView<AppInfoWithLiveness> appView,
+        KotlinMetadataSynthesizer synthesizer) {
+      if (!isExtension) {
+        return true;
       }
+      MethodTypeSignature signature =
+          GenericSignature.Parser.toMethodTypeSignature(method, appView);
+      List<KmTypeParameter> typeParameters =
+          KotlinMetadataSynthesizerUtils.convertFormalTypeParameters(
+              classTypeParameters,
+              signature.getFormalTypeParameters(),
+              appView.dexItemFactory(),
+              kmTypeParameter -> {});
+      ClassTypeSignatureToRenamedKmTypeConverter converter =
+          new ClassTypeSignatureToRenamedKmTypeConverter(
+              appView, typeParameters, synthesizer::toRenamedClassifier);
+      DexType receiverType = method.method.proto.parameters.values[0];
+      TypeSignature receiverSignature = signature.getParameterTypeSignature(0);
+      KmType kmReceiverType =
+          synthesizer.toRenamedKmType(receiverType, receiverSignature, null, converter);
+      if (kmProperty.getReceiverParameterType() != null) {
+        // If the receiver type for the extension property is set already make sure it's consistent.
+        return getDescriptorFromKmType(kmReceiverType)
+            .equals(getDescriptorFromKmType(kmProperty.getReceiverParameterType()));
+      }
+      kmProperty.setReceiverParameterType(kmReceiverType);
+      return true;
+    }
 
-      if (criteria == GetterSetterCriteria.MET) {
-        assert getter != null && getter.method.proto.parameters.size() == (isExtension ? 1 : 0)
-            : "checkGetterCriteria: " + this.toString();
-        MethodTypeSignature signature =
-            GenericSignature.Parser.toMethodTypeSignature(getter, appView);
-        if (isExtension) {
-          TypeSignature receiverSignature = signature.getParameterTypeSignature(0);
-          kmReceiverType =
-              setRenamedKmType(
-                  getter.method.proto.parameters.values[0],
-                  receiverSignature,
-                  appView,
-                  lens,
-                  kmProperty::setReceiverParameterType);
-        }
-
-        DexType returnType = getter.method.proto.returnType;
-        TypeSignature returnSignature = signature.returnType().typeSignature();
-        if (kmPropertyType == null) {
-          // The property type is not set yet.
-          kmPropertyType = setRenamedKmType(
-              returnType, returnSignature, appView, lens, kmProperty::setReturnType);
-        } else {
-          // If property type is set already (via backing field), make sure it's consistent.
-          KmType kmPropertyTypeFromGetter =
-              toRenamedKmType(returnType, returnSignature, appView, lens);
-          if (!getDescriptorFromKmType(kmPropertyType)
-              .equals(getDescriptorFromKmType(kmPropertyTypeFromGetter))) {
-            return null;
-          }
-        }
-        if (appView.appInfo().isPinned(getter.method)) {
-          canChangePropertyName = false;
-        }
-        DexMethod renamedGetter = lens.lookupMethod(getter.method, appView.dexItemFactory());
-        if (canChangePropertyName
-            && renamedGetter.name != getter.method.name
-            && renamedPropertyName.equals(name)) {
-          renamedPropertyName = renamedGetter.name.toString();
-        }
-        kmProperty.setGetterFlags(getterFlags);
-        JvmExtensionsKt.setGetterSignature(kmProperty, toJvmMethodSignature(renamedGetter));
-      } else if (field != null) {
+    private String setGetterForProperty(
+        KmProperty kmProperty,
+        KmType defaultPropertyType,
+        AppView<AppInfoWithLiveness> appView,
+        NamingLens lens,
+        KotlinMetadataSynthesizer synthesizer) {
+      if (checkGetterCriteria() == GetterSetterCriteria.NOT_AVAILABLE) {
         // Property without getter.
         // Even though a getter does not exist, `kotlinc` still set getter flags and use them to
         // determine when to direct field access v.s. getter calls for property resolution.
-        kmProperty.setGetterFlags(field.accessFlags.getAsKotlinFlags());
+        if (field != null) {
+          kmProperty.setGetterFlags(field.accessFlags.getAsKotlinFlags());
+        }
+        return kmProperty.getName();
       }
+      assert checkGetterCriteria() == GetterSetterCriteria.MET;
+      assert getter != null;
+      DexProto proto = getter.method.proto;
+      assert proto.parameters.size() == (isExtension ? 1 : 0)
+          : "checkGetterCriteria: " + this.toString();
+      MethodTypeSignature signature =
+          GenericSignature.Parser.toMethodTypeSignature(getter, appView);
+      List<KmTypeParameter> typeParameters =
+          KotlinMetadataSynthesizerUtils.convertFormalTypeParameters(
+              this.classTypeParameters,
+              signature.getFormalTypeParameters(),
+              appView.dexItemFactory(),
+              kmTypeParameter -> {});
+      ClassTypeSignatureToRenamedKmTypeConverter converter =
+          new ClassTypeSignatureToRenamedKmTypeConverter(
+              appView, typeParameters, synthesizer::toRenamedClassifier);
+      DexType returnType = proto.returnType;
+      TypeSignature returnSignature = signature.returnType().typeSignature();
+      KmType kmPropertyType =
+          synthesizer.toRenamedKmType(
+              returnType, returnSignature, getterInfo.returnType, converter);
+      if (kmProperty.getReturnType() == defaultPropertyType) {
+        // The property type is not set yet.
+        kmProperty.setReturnType(kmPropertyType);
+      } else if (!getDescriptorFromKmType(kmPropertyType)
+          .equals(getDescriptorFromKmType(kmProperty.getReturnType()))) {
+        // If property type is set already (via backing field), make sure it's consistent.
+        return null;
+      }
+      DexMethod renamedGetter = lens.lookupMethod(getter.method, appView.dexItemFactory());
+      kmProperty.setGetterFlags(getter.accessFlags.getAsKotlinFlags());
+      JvmExtensionsKt.setGetterSignature(kmProperty, toJvmMethodSignature(renamedGetter));
+      return appView.appInfo().isPinned(getter.method) ? null : renamedGetter.name.toString();
+    }
 
-      criteria = checkSetterCriteria();
+    private String setSetterForProperty(
+        KmProperty kmProperty,
+        KmType defaultPropertyType,
+        AppView<AppInfoWithLiveness> appView,
+        NamingLens lens,
+        KotlinMetadataSynthesizer synthesizer) {
+      GetterSetterCriteria criteria = checkSetterCriteria();
       if (criteria == GetterSetterCriteria.VIOLATE) {
-        return null;
+        return kmProperty.getName();
       }
-
-      if (criteria == GetterSetterCriteria.MET) {
-        assert setter != null && setter.method.proto.parameters.size() == (isExtension ? 2 : 1)
-            : "checkSetterCriteria: " + this.toString();
-        MethodTypeSignature signature =
-            GenericSignature.Parser.toMethodTypeSignature(setter, appView);
-        if (isExtension) {
-          DexType receiverType = setter.method.proto.parameters.values[0];
-          TypeSignature receiverSignature = signature.getParameterTypeSignature(0);
-          if (kmReceiverType == null) {
-            kmReceiverType =
-                setRenamedKmType(
-                    receiverType,
-                    receiverSignature,
-                    appView,
-                    lens,
-                    kmProperty::setReceiverParameterType);
-          } else {
-            // If the receiver type for the extension property is set already (via getter),
-            // make sure it's consistent.
-            KmType kmReceiverTypeFromSetter =
-                toRenamedKmType(receiverType, receiverSignature, appView, lens);
-            if (!getDescriptorFromKmType(kmReceiverType)
-                .equals(getDescriptorFromKmType(kmReceiverTypeFromSetter))) {
-              return null;
-            }
-          }
+      if (criteria == GetterSetterCriteria.NOT_AVAILABLE) {
+        if (field != null && IS_VAR.invoke(flags)) {
+          // Editable property without setter.
+          // Even though a setter does not exist, `kotlinc` still set setter flags and use them to
+          // determine when to direct field access v.s. setter calls for property resolution.
+          kmProperty.setSetterFlags(field.accessFlags.getAsKotlinFlags());
         }
-
-        int valueIndex = isExtension ? 1 : 0;
-        DexType valueType = setter.method.proto.parameters.values[valueIndex];
-        TypeSignature valueSignature = signature.getParameterTypeSignature(valueIndex);
-        if (kmPropertyType == null) {
-          // The property type is not set yet.
-          kmPropertyType =
-              setRenamedKmType(valueType, valueSignature, appView, lens, kmProperty::setReturnType);
-        } else {
-          // If property type is set already (via either backing field or getter),
-          // make sure it's consistent.
-          KmType kmPropertyTypeFromSetter =
-              toRenamedKmType(valueType, valueSignature, appView, lens);
-          if (!getDescriptorFromKmType(kmPropertyType)
-              .equals(getDescriptorFromKmType(kmPropertyTypeFromSetter))) {
-            return null;
-          }
-        }
-        KotlinValueParameterInfo valueParameterInfo =
-            setter.getKotlinMemberInfo().getValueParameterInfo(valueIndex);
-        KmValueParameter kmValueParameter = toRewrittenKmValueParameter(
-            valueParameterInfo, valueType, valueSignature, "value", appView, lens);
-        if (kmValueParameter != null) {
-          kmProperty.setSetterParameter(kmValueParameter);
-        }
-        if (appView.appInfo().isPinned(setter.method)) {
-          canChangePropertyName = false;
-        }
-        DexMethod renamedSetter = lens.lookupMethod(setter.method, appView.dexItemFactory());
-        if (canChangePropertyName
-            && renamedSetter.name != setter.method.name
-            && renamedPropertyName.equals(name)) {
-          renamedPropertyName = renamedSetter.name.toString();
-        }
-        kmProperty.setSetterFlags(setterFlags);
-        JvmExtensionsKt.setSetterSignature(kmProperty, toJvmMethodSignature(renamedSetter));
-      } else if (field != null && IS_VAR.invoke(flags)) {
-        // Editable property without setter.
-        // Even though a setter does not exist, `kotlinc` still set setter flags and use them to
-        // determine when to direct field access v.s. setter calls for property resolution.
-        kmProperty.setGetterFlags(field.accessFlags.getAsKotlinFlags());
+        return kmProperty.getName();
       }
-
-      // If the property type remains null at the end, bail out to synthesize this property.
-      if (kmPropertyType == null) {
-        return null;
+      assert criteria == GetterSetterCriteria.MET;
+      assert setter != null;
+      DexProto proto = setter.method.proto;
+      assert proto.parameters.size() == (isExtension ? 2 : 1)
+          : "checkSetterCriteria: " + this.toString();
+      MethodTypeSignature signature =
+          GenericSignature.Parser.toMethodTypeSignature(setter, appView);
+      List<KmTypeParameter> typeParameters =
+          KotlinMetadataSynthesizerUtils.convertFormalTypeParameters(
+              classTypeParameters,
+              signature.getFormalTypeParameters(),
+              appView.dexItemFactory(),
+              kmTypeParameter -> {});
+      ClassTypeSignatureToRenamedKmTypeConverter converter =
+          new ClassTypeSignatureToRenamedKmTypeConverter(
+              appView, typeParameters, synthesizer::toRenamedClassifier);
+      int valueIndex = isExtension ? 1 : 0;
+      DexType valueType = proto.parameters.values[valueIndex];
+      TypeSignature valueSignature = signature.getParameterTypeSignature(valueIndex);
+      KmType kmPropertyType =
+          synthesizer.toRenamedKmType(valueType, valueSignature, setterInfo.returnType, converter);
+      if (kmProperty.getReturnType() == defaultPropertyType) {
+        // The property type is not set yet.
+        kmProperty.setReturnType(kmPropertyType);
+      } else {
+        // If property type is set already make sure it's consistent.
+        if (!getDescriptorFromKmType(kmPropertyType)
+            .equals(getDescriptorFromKmType(kmProperty.getReturnType()))) {
+          return null;
+        }
       }
-      // For extension property, if the receiver type remains null at the end, bail out too.
-      if (isExtension && kmReceiverType == null) {
-        return null;
+      assert setter.getKotlinMemberInfo().isPropertyInfo();
+      KotlinValueParameterInfo valueParameterInfo =
+          setter.getKotlinMemberInfo().asPropertyInfo().valueParameterInfo;
+      KmValueParameter kmValueParameter =
+          synthesizer.toRewrittenKmValueParameter(
+              valueParameterInfo, valueType, valueSignature, "value", converter);
+      if (kmValueParameter != null) {
+        kmProperty.setSetterParameter(kmValueParameter);
+      }
+      DexMethod renamedSetter = lens.lookupMethod(setter.method, appView.dexItemFactory());
+      kmProperty.setSetterFlags(setterInfo.setterFlags);
+      JvmExtensionsKt.setSetterSignature(kmProperty, toJvmMethodSignature(renamedSetter));
+      return appView.appInfo().isPinned(setter.method) ? null : renamedSetter.name.toString();
+    }
+
+    KmProperty toRenamedKmProperty(KotlinMetadataSynthesizer synthesizer) {
+      AppView<AppInfoWithLiveness> appView = synthesizer.appView;
+      NamingLens lens = synthesizer.lens;
+      KmProperty kmProperty = new KmProperty(flags, name, flagsOf(), flagsOf());
+
+      // Set default values
+      KmType defaultPropertyType = new KmType(flagsOf());
+      kmProperty.setReturnType(defaultPropertyType);
+
+      String renamedPropertyName = name;
+      if (getter != null) {
+        if (checkGetterCriteria() == GetterSetterCriteria.VIOLATE) {
+          return null;
+        }
+        if (!setReceiverParameterTypeForExtensionProperty(
+            kmProperty, getter, appView, synthesizer)) {
+          return null;
+        }
+        renamedPropertyName =
+            setGetterForProperty(kmProperty, defaultPropertyType, appView, lens, synthesizer);
+      }
+      if (setter != null) {
+        if (checkSetterCriteria() == GetterSetterCriteria.VIOLATE) {
+          return null;
+        }
+        if (!setReceiverParameterTypeForExtensionProperty(
+            kmProperty, setter, appView, synthesizer)) {
+          return null;
+        }
+        String renamedName =
+            setSetterForProperty(kmProperty, defaultPropertyType, appView, lens, synthesizer);
+        if (renamedPropertyName != null) {
+          renamedPropertyName = renamedName;
+        }
+      }
+      // Setting the property type from the field has to be done after the getter, otherwise we
+      // may potentially loose type-argument information.
+      if (field != null) {
+        String renamedName =
+            setFieldForProperty(kmProperty, defaultPropertyType, appView, lens, synthesizer);
+        if (renamedPropertyName != null) {
+          renamedPropertyName = renamedName;
+        }
       }
       // Rename the property name if and only if none of participating members is pinned, and
       // any of them is indeed renamed (to a new name).
-      if (canChangePropertyName && !renamedPropertyName.equals(name)) {
+      if (renamedPropertyName != null) {
         kmProperty.setName(renamedPropertyName);
       }
       return kmProperty;
diff --git a/src/main/java/com/android/tools/r8/kotlin/KotlinMetadataSynthesizerUtils.java b/src/main/java/com/android/tools/r8/kotlin/KotlinMetadataSynthesizerUtils.java
new file mode 100644
index 0000000..9382cd9
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/kotlin/KotlinMetadataSynthesizerUtils.java
@@ -0,0 +1,174 @@
+// 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;
+
+import static com.android.tools.r8.kotlin.Kotlin.NAME;
+import static com.android.tools.r8.utils.DescriptorUtils.descriptorToKotlinClassifier;
+import static kotlinx.metadata.FlagsKt.flagsOf;
+
+import com.android.tools.r8.graph.DexItemFactory;
+import com.android.tools.r8.graph.DexType;
+import com.android.tools.r8.graph.GenericSignature.ArrayTypeSignature;
+import com.android.tools.r8.graph.GenericSignature.ClassTypeSignature;
+import com.android.tools.r8.graph.GenericSignature.FieldTypeSignature;
+import com.android.tools.r8.graph.GenericSignature.FormalTypeParameter;
+import com.android.tools.r8.graph.GenericSignature.TypeVariableSignature;
+import com.google.common.collect.ImmutableList;
+import java.util.List;
+import java.util.function.Consumer;
+import java.util.function.Supplier;
+import kotlinx.metadata.KmTypeParameter;
+import kotlinx.metadata.KmTypeVisitor;
+import kotlinx.metadata.KmVariance;
+
+class KotlinMetadataSynthesizerUtils {
+
+  static void populateKmTypeFromSignature(
+      FieldTypeSignature typeSignature,
+      Supplier<KmTypeVisitor> typeVisitor,
+      List<KmTypeParameter> allTypeParameters,
+      DexItemFactory factory) {
+    if (typeSignature.isClassTypeSignature()) {
+      populateKmTypeFromClassTypeSignature(
+          typeSignature.asClassTypeSignature(), typeVisitor, allTypeParameters, factory);
+    } else if (typeSignature.isArrayTypeSignature()) {
+      populateKmTypeFromArrayTypeSignature(
+          typeSignature.asArrayTypeSignature(), typeVisitor, allTypeParameters, factory);
+    } else {
+      assert typeSignature.isTypeVariableSignature();
+      populateKmTypeFromTypeVariableSignature(
+          typeSignature.asTypeVariableSignature(), typeVisitor, allTypeParameters);
+    }
+  }
+
+  private static void populateKmTypeFromTypeVariableSignature(
+      TypeVariableSignature typeSignature,
+      Supplier<KmTypeVisitor> visitor,
+      List<KmTypeParameter> allTypeParameters) {
+    for (KmTypeParameter typeParameter : allTypeParameters) {
+      if (typeParameter
+          .getName()
+          .equals(typeSignature.asTypeVariableSignature().getTypeVariable())) {
+        visitor.get().visitTypeParameter(typeParameter.getId());
+        return;
+      }
+    }
+  }
+
+  private static void populateKmTypeFromArrayTypeSignature(
+      ArrayTypeSignature typeSignature,
+      Supplier<KmTypeVisitor> visitor,
+      List<KmTypeParameter> allTypeParameters,
+      DexItemFactory factory) {
+    ArrayTypeSignature arrayTypeSignature = typeSignature.asArrayTypeSignature();
+    if (!arrayTypeSignature.elementSignature().isFieldTypeSignature()) {
+      return;
+    }
+    KmTypeVisitor kmType = visitor.get();
+    kmType.visitClass(NAME + "/Array");
+    populateKmTypeFromSignature(
+        arrayTypeSignature.elementSignature().asFieldTypeSignature(),
+        () -> kmType.visitArgument(flagsOf(), KmVariance.INVARIANT),
+        allTypeParameters,
+        factory);
+  }
+
+  private static void populateKmTypeFromClassTypeSignature(
+      ClassTypeSignature typeSignature,
+      Supplier<KmTypeVisitor> visitor,
+      List<KmTypeParameter> allTypeParameters,
+      DexItemFactory factory) {
+    // No need to record the trivial argument.
+    if (factory.objectType == typeSignature.type()) {
+      return;
+    }
+    KmTypeVisitor kmType = visitor.get();
+    kmType.visitClass(toClassifier(typeSignature.type(), factory));
+    for (FieldTypeSignature typeArgument : typeSignature.typeArguments()) {
+      populateKmTypeFromSignature(
+          typeArgument,
+          () -> kmType.visitArgument(flagsOf(), KmVariance.INVARIANT),
+          allTypeParameters,
+          factory);
+    }
+  }
+
+  static String toClassifier(DexType type, DexItemFactory factory) {
+    // E.g., V -> kotlin/Unit, J -> kotlin/Long, [J -> kotlin/LongArray
+    if (factory.kotlin.knownTypeConversion.containsKey(type)) {
+      DexType convertedType = factory.kotlin.knownTypeConversion.get(type);
+      assert convertedType != null;
+      return descriptorToKotlinClassifier(convertedType.toDescriptorString());
+    }
+    // E.g., [Ljava/lang/String; -> kotlin/Array
+    if (type.isArrayType()) {
+      return NAME + "/Array";
+    }
+    return descriptorToKotlinClassifier(type.toDescriptorString());
+  }
+
+  /**
+   * Utility method building up all type-parameters from {@code classTypeParameters} combined with
+   * {@code parameters}, where the consumer {@code addedFromParameters} is called for every
+   * conversion of {@Code FormalTypeParameter} to {@Code KmTypeParameter}.
+   *
+   * <pre>
+   *  classTypeParameters: [KmTypeParameter(T), KmTypeParameter(S)]
+   *  parameters: [FormalTypeParameter(R)]
+   *  result: [KmTypeParameter(T), KmTypeParameter(S), KmTypeParameter(R)].
+   * </pre>
+   *
+   * @param classTypeParameters
+   * @param parameters
+   * @param factory
+   * @param addedFromParameters
+   * @return
+   */
+  static List<KmTypeParameter> convertFormalTypeParameters(
+      List<KmTypeParameter> classTypeParameters,
+      List<FormalTypeParameter> parameters,
+      DexItemFactory factory,
+      Consumer<KmTypeParameter> addedFromParameters) {
+    if (parameters.isEmpty()) {
+      return classTypeParameters;
+    }
+    ImmutableList.Builder<KmTypeParameter> builder = ImmutableList.builder();
+    builder.addAll(classTypeParameters);
+    int idCounter = classTypeParameters.size();
+    // Assign type-variables ids to names. All generic signatures has been minified at this point,
+    // but it may be that type-variables are used before we can see them (is that allowed?).
+    for (FormalTypeParameter parameter : parameters) {
+      KmTypeParameter element =
+          new KmTypeParameter(flagsOf(), parameter.getName(), idCounter++, KmVariance.INVARIANT);
+      builder.add(element);
+      addedFromParameters.accept(element);
+    }
+    idCounter = 0;
+    ImmutableList<KmTypeParameter> allTypeParameters = builder.build();
+    for (FormalTypeParameter parameter : parameters) {
+      KmTypeParameter kmTypeParameter =
+          allTypeParameters.get(classTypeParameters.size() + idCounter++);
+      visitUpperBound(parameter.getClassBound(), allTypeParameters, kmTypeParameter, factory);
+      if (parameter.getInterfaceBounds() != null) {
+        for (FieldTypeSignature interfaceBound : parameter.getInterfaceBounds()) {
+          visitUpperBound(interfaceBound, allTypeParameters, kmTypeParameter, factory);
+        }
+      }
+    }
+    return allTypeParameters;
+  }
+
+  private static void visitUpperBound(
+      FieldTypeSignature typeSignature,
+      List<KmTypeParameter> allTypeParameters,
+      KmTypeParameter parameter,
+      DexItemFactory factory) {
+    if (typeSignature.isUnknown()) {
+      return;
+    }
+    populateKmTypeFromSignature(
+        typeSignature, () -> parameter.visitUpperBound(flagsOf()), allTypeParameters, factory);
+  }
+}
diff --git a/src/main/java/com/android/tools/r8/kotlin/KotlinTypeInfo.java b/src/main/java/com/android/tools/r8/kotlin/KotlinTypeInfo.java
new file mode 100644
index 0000000..130a660
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/kotlin/KotlinTypeInfo.java
@@ -0,0 +1,51 @@
+// 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;
+
+import com.google.common.collect.ImmutableList;
+import java.util.List;
+import kotlinx.metadata.KmClassifier;
+import kotlinx.metadata.KmType;
+import kotlinx.metadata.KmTypeProjection;
+
+// Provides access to Kotlin information about