Refactor NestedGraphLens to a top-level class

Bug: 182099754
Change-Id: I73560e7b5f10c38ed5345cdb72bedc25d85ee73f
diff --git a/src/main/java/com/android/tools/r8/graph/GraphLens.java b/src/main/java/com/android/tools/r8/graph/GraphLens.java
index 3912ae6..9e056df 100644
--- a/src/main/java/com/android/tools/r8/graph/GraphLens.java
+++ b/src/main/java/com/android/tools/r8/graph/GraphLens.java
@@ -13,7 +13,6 @@
 import com.android.tools.r8.utils.Action;
 import com.android.tools.r8.utils.IterableUtils;
 import com.android.tools.r8.utils.SetUtils;
-import com.android.tools.r8.utils.collections.BidirectionalManyToManyRepresentativeMap;
 import com.android.tools.r8.utils.collections.BidirectionalManyToOneRepresentativeHashMap;
 import com.android.tools.r8.utils.collections.BidirectionalManyToOneRepresentativeMap;
 import com.android.tools.r8.utils.collections.BidirectionalOneToOneHashMap;
@@ -33,7 +32,6 @@
 import java.util.concurrent.ConcurrentHashMap;
 import java.util.function.Function;
 import java.util.function.Predicate;
-import java.util.stream.Collectors;
 
 /**
  * A GraphLens implements a virtual view on top of the graph, used to delay global rewrites until
@@ -959,288 +957,4 @@
       return getIdentityLens().isContextFreeForMethods();
     }
   }
-
-  /**
-   * GraphLens implementation with a parent lens using a simple mapping for type, method and field
-   * mapping.
-   *
-   * <p>Subclasses can override the lookup methods.
-   *
-   * <p>For method mapping where invocation type can change just override {@link
-   * #mapInvocationType(DexMethod, DexMethod, Type)} if the default name mapping applies, and only
-   * invocation type might need to change.
-   */
-  public static class NestedGraphLens extends NonIdentityGraphLens {
-
-    protected final DexItemFactory dexItemFactory;
-
-    protected final Map<DexType, DexType> typeMap;
-    protected final Map<DexMethod, DexMethod> methodMap;
-    protected final BidirectionalManyToOneRepresentativeMap<DexField, DexField> fieldMap;
-
-    // Map that store the original signature of methods that have been affected, for example, by
-    // vertical class merging. Needed to generate a correct Proguard map in the end.
-    protected BidirectionalManyToManyRepresentativeMap<DexMethod, DexMethod>
-        originalMethodSignatures;
-
-    // Overrides this if the sub type needs to be a nested lens while it doesn't have any mappings
-    // at all, e.g., publicizer lens that changes invocation type only.
-    protected boolean isLegitimateToHaveEmptyMappings() {
-      return false;
-    }
-
-    public NestedGraphLens(
-        Map<DexType, DexType> typeMap,
-        Map<DexMethod, DexMethod> methodMap,
-        BidirectionalManyToOneRepresentativeMap<DexField, DexField> fieldMap,
-        BidirectionalManyToManyRepresentativeMap<DexMethod, DexMethod> originalMethodSignatures,
-        GraphLens previousLens,
-        DexItemFactory dexItemFactory) {
-      super(dexItemFactory, previousLens);
-      assert !typeMap.isEmpty()
-          || !methodMap.isEmpty()
-          || !fieldMap.isEmpty()
-          || isLegitimateToHaveEmptyMappings();
-      this.typeMap = typeMap.isEmpty() ? null : typeMap;
-      this.methodMap = methodMap;
-      this.fieldMap = fieldMap;
-      this.originalMethodSignatures = originalMethodSignatures;
-      this.dexItemFactory = dexItemFactory;
-    }
-
-    public static Builder builder() {
-      return new Builder();
-    }
-
-    protected DexType internalGetOriginalType(DexType previous) {
-      return previous;
-    }
-
-    protected Iterable<DexType> internalGetOriginalTypes(DexType previous) {
-      return IterableUtils.singleton(internalGetOriginalType(previous));
-    }
-
-    @Override
-    public DexType getOriginalType(DexType type) {
-      return getPrevious().getOriginalType(internalGetOriginalType(type));
-    }
-
-    @Override
-    public Iterable<DexType> getOriginalTypes(DexType type) {
-      return IterableUtils.flatMap(internalGetOriginalTypes(type), getPrevious()::getOriginalTypes);
-    }
-
-    @Override
-    public DexField getOriginalFieldSignature(DexField field) {
-      DexField originalField = fieldMap.getRepresentativeKeyOrDefault(field, field);
-      return getPrevious().getOriginalFieldSignature(originalField);
-    }
-
-    @Override
-    public DexMethod getOriginalMethodSignature(DexMethod method) {
-      DexMethod originalMethod = internalGetPreviousMethodSignature(method);
-      return getPrevious().getOriginalMethodSignature(originalMethod);
-    }
-
-    @Override
-    public DexField getRenamedFieldSignature(DexField originalField) {
-      DexField renamedField = getPrevious().getRenamedFieldSignature(originalField);
-      return internalGetNextFieldSignature(renamedField);
-    }
-
-    @Override
-    public DexMethod getRenamedMethodSignature(DexMethod originalMethod, GraphLens applied) {
-      if (this == applied) {
-        return originalMethod;
-      }
-      DexMethod renamedMethod = getPrevious().getRenamedMethodSignature(originalMethod, applied);
-      return internalGetNextMethodSignature(renamedMethod);
-    }
-
-    @Override
-    protected DexType internalDescribeLookupClassType(DexType previous) {
-      return typeMap != null ? typeMap.getOrDefault(previous, previous) : previous;
-    }
-
-    @Override
-    protected FieldLookupResult internalDescribeLookupField(FieldLookupResult previous) {
-      if (previous.hasReboundReference()) {
-        // Rewrite the rebound reference and then "fixup" the non-rebound reference.
-        DexField rewrittenReboundReference = previous.getRewrittenReboundReference(fieldMap);
-        DexField rewrittenNonReboundReference =
-            previous.getReference() == previous.getReboundReference()
-                ? rewrittenReboundReference
-                : rewrittenReboundReference.withHolder(
-                    internalDescribeLookupClassType(previous.getReference().getHolderType()),
-                    dexItemFactory);
-        return FieldLookupResult.builder(this)
-            .setReboundReference(rewrittenReboundReference)
-            .setReference(rewrittenNonReboundReference)
-            .setCastType(previous.getRewrittenCastType(this::internalDescribeLookupClassType))
-            .build();
-      } else {
-        // TODO(b/168282032): We should always have the rebound reference, so this should become
-        //  unreachable.
-        DexField rewrittenReference = previous.getRewrittenReference(fieldMap);
-        return FieldLookupResult.builder(this)
-            .setReference(rewrittenReference)
-            .setCastType(previous.getRewrittenCastType(this::internalDescribeLookupClassType))
-            .build();
-      }
-    }
-
-    @Override
-    public MethodLookupResult internalDescribeLookupMethod(
-        MethodLookupResult previous, DexMethod context) {
-      if (previous.hasReboundReference()) {
-        // TODO(sgjesse): Should we always do interface to virtual mapping? Is it a performance win
-        //  that only subclasses which are known to need it actually do it?
-        DexMethod rewrittenReboundReference = previous.getRewrittenReboundReference(methodMap);
-        DexMethod rewrittenReference =
-            previous.getReference() == previous.getReboundReference()
-                ? rewrittenReboundReference
-                : // This assumes that the holder will always be moved in lock-step with the method!
-                rewrittenReboundReference.withHolder(
-                    internalDescribeLookupClassType(previous.getReference().getHolderType()),
-                    dexItemFactory);
-        return MethodLookupResult.builder(this)
-            .setReference(rewrittenReference)
-            .setReboundReference(rewrittenReboundReference)
-            .setPrototypeChanges(
-                internalDescribePrototypeChanges(
-                    previous.getPrototypeChanges(), rewrittenReboundReference))
-            .setType(
-                mapInvocationType(
-                    rewrittenReboundReference, previous.getReference(), previous.getType()))
-            .build();
-      } else {
-        // TODO(b/168282032): We should always have the rebound reference, so this should become
-        //  unreachable.
-        DexMethod newMethod = methodMap.get(previous.getReference());
-        if (newMethod == null) {
-          return previous;
-        }
-        // TODO(sgjesse): Should we always do interface to virtual mapping? Is it a performance win
-        //  that only subclasses which are known to need it actually do it?
-        return MethodLookupResult.builder(this)
-            .setReference(newMethod)
-            .setPrototypeChanges(
-                internalDescribePrototypeChanges(previous.getPrototypeChanges(), newMethod))
-            .setType(mapInvocationType(newMethod, previous.getReference(), previous.getType()))
-            .build();
-      }
-    }
-
-    @Override
-    public RewrittenPrototypeDescription lookupPrototypeChangesForMethodDefinition(
-        DexMethod method) {
-      DexMethod previous = internalGetPreviousMethodSignature(method);
-      RewrittenPrototypeDescription lookup =
-          getPrevious().lookupPrototypeChangesForMethodDefinition(previous);
-      return internalDescribePrototypeChanges(lookup, method);
-    }
-
-    protected RewrittenPrototypeDescription internalDescribePrototypeChanges(
-        RewrittenPrototypeDescription prototypeChanges, DexMethod method) {
-      return prototypeChanges;
-    }
-
-    protected DexField internalGetNextFieldSignature(DexField field) {
-      return fieldMap.getOrDefault(field, field);
-    }
-
-    @Override
-    protected DexMethod internalGetPreviousMethodSignature(DexMethod method) {
-      return originalMethodSignatures.getRepresentativeValueOrDefault(method, method);
-    }
-
-    protected DexMethod internalGetNextMethodSignature(DexMethod method) {
-      return originalMethodSignatures.getRepresentativeKeyOrDefault(method, method);
-    }
-
-    @Override
-    public DexMethod lookupGetFieldForMethod(DexField field, DexMethod context) {
-      return getPrevious().lookupGetFieldForMethod(field, context);
-    }
-
-    @Override
-    public DexMethod lookupPutFieldForMethod(DexField field, DexMethod context) {
-      return getPrevious().lookupPutFieldForMethod(field, context);
-    }
-
-    /**
-     * Default invocation type mapping.
-     *
-     * <p>This is an identity mapping. If a subclass need invocation type mapping either override
-     * this method or {@link #lookupMethod(DexMethod, DexMethod, Type)}
-     */
-    protected Type mapInvocationType(DexMethod newMethod, DexMethod originalMethod, Type type) {
-      return type;
-    }
-
-    /**
-     * Standard mapping between interface and virtual invoke type.
-     *
-     * <p>Handle methods moved from interface to class or class to interface.
-     */
-    public static Type mapVirtualInterfaceInvocationTypes(
-        DexDefinitionSupplier definitions,
-        DexMethod newMethod,
-        DexMethod originalMethod,
-        Type type) {
-      if (type == Type.VIRTUAL || type == Type.INTERFACE) {
-        // Get the invoke type of the actual definition.
-        DexClass newTargetClass = definitions.definitionFor(newMethod.getHolderType());
-        if (newTargetClass == null) {
-          return type;
-        }
-        DexClass originalTargetClass = definitions.definitionFor(originalMethod.getHolderType());
-        if (originalTargetClass != null
-            && (originalTargetClass.isInterface() ^ (type == Type.INTERFACE))) {
-          // The invoke was wrong to start with, so we keep it wrong. This is to ensure we get
-          // the IncompatibleClassChangeError the original invoke would have triggered.
-          return newTargetClass.accessFlags.isInterface() ? Type.VIRTUAL : Type.INTERFACE;
-        }
-        return newTargetClass.accessFlags.isInterface() ? Type.INTERFACE : Type.VIRTUAL;
-      }
-      return type;
-    }
-
-    @Override
-    public boolean isContextFreeForMethods() {
-      return getPrevious().isContextFreeForMethods();
-    }
-
-    @Override
-    public boolean verifyIsContextFreeForMethod(DexMethod method) {
-      assert getPrevious().verifyIsContextFreeForMethod(method);
-      return true;
-    }
-
-    @Override
-    public String toString() {
-      StringBuilder builder = new StringBuilder();
-      if (typeMap != null) {
-        for (Map.Entry<DexType, DexType> entry : typeMap.entrySet()) {
-          builder.append(entry.getKey().toSourceString()).append(" -> ");
-          builder.append(entry.getValue().toSourceString()).append(System.lineSeparator());
-        }
-      }
-      for (Map.Entry<DexMethod, DexMethod> entry : methodMap.entrySet()) {
-        builder.append(entry.getKey().toSourceString()).append(" -> ");
-        builder.append(entry.getValue().toSourceString()).append(System.lineSeparator());
-      }
-      fieldMap.forEachManyToOneMapping(
-          (keys, value) -> {
-            builder.append(
-                keys.stream()
-                    .map(DexField::toSourceString)
-                    .collect(Collectors.joining("," + System.lineSeparator())));
-            builder.append(" -> ");
-            builder.append(value.toSourceString()).append(System.lineSeparator());
-          });
-      builder.append(getPrevious().toString());
-      return builder.toString();
-    }
-  }
 }
diff --git a/src/main/java/com/android/tools/r8/graph/NestedGraphLens.java b/src/main/java/com/android/tools/r8/graph/NestedGraphLens.java
new file mode 100644
index 0000000..84e2a37
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/graph/NestedGraphLens.java
@@ -0,0 +1,292 @@
+// Copyright (c) 2021, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+package com.android.tools.r8.graph;
+
+import com.android.tools.r8.graph.GraphLens.NonIdentityGraphLens;
+import com.android.tools.r8.ir.code.Invoke.Type;
+import com.android.tools.r8.utils.IterableUtils;
+import com.android.tools.r8.utils.collections.BidirectionalManyToManyRepresentativeMap;
+import com.android.tools.r8.utils.collections.BidirectionalManyToOneRepresentativeMap;
+import java.util.Map;
+import java.util.stream.Collectors;
+
+/**
+ * GraphLens implementation with a parent lens using a simple mapping for type, method and field
+ * mapping.
+ *
+ * <p>Subclasses can override the lookup methods.
+ *
+ * <p>For method mapping where invocation type can change just override {@link
+ * #mapInvocationType(DexMethod, DexMethod, Type)} if the default name mapping applies, and only
+ * invocation type might need to change.
+ */
+public class NestedGraphLens extends NonIdentityGraphLens {
+
+  protected final DexItemFactory dexItemFactory;
+
+  protected final Map<DexType, DexType> typeMap;
+  protected final Map<DexMethod, DexMethod> methodMap;
+  protected final BidirectionalManyToOneRepresentativeMap<DexField, DexField> fieldMap;
+
+  // Map that store the original signature of methods that have been affected, for example, by
+  // vertical class merging. Needed to generate a correct Proguard map in the end.
+  protected BidirectionalManyToManyRepresentativeMap<DexMethod, DexMethod> originalMethodSignatures;
+
+  // Overrides this if the sub type needs to be a nested lens while it doesn't have any mappings
+  // at all, e.g., publicizer lens that changes invocation type only.
+  protected boolean isLegitimateToHaveEmptyMappings() {
+    return false;
+  }
+
+  public NestedGraphLens(
+      Map<DexType, DexType> typeMap,
+      Map<DexMethod, DexMethod> methodMap,
+      BidirectionalManyToOneRepresentativeMap<DexField, DexField> fieldMap,
+      BidirectionalManyToManyRepresentativeMap<DexMethod, DexMethod> originalMethodSignatures,
+      GraphLens previousLens,
+      DexItemFactory dexItemFactory) {
+    super(dexItemFactory, previousLens);
+    assert !typeMap.isEmpty()
+        || !methodMap.isEmpty()
+        || !fieldMap.isEmpty()
+        || isLegitimateToHaveEmptyMappings();
+    this.typeMap = typeMap.isEmpty() ? null : typeMap;
+    this.methodMap = methodMap;
+    this.fieldMap = fieldMap;
+    this.originalMethodSignatures = originalMethodSignatures;
+    this.dexItemFactory = dexItemFactory;
+  }
+
+  public static Builder builder() {
+    return new Builder();
+  }
+
+  protected DexType internalGetOriginalType(DexType previous) {
+    return previous;
+  }
+
+  protected Iterable<DexType> internalGetOriginalTypes(DexType previous) {
+    return IterableUtils.singleton(internalGetOriginalType(previous));
+  }
+
+  @Override
+  public DexType getOriginalType(DexType type) {
+    return getPrevious().getOriginalType(internalGetOriginalType(type));
+  }
+
+  @Override
+  public Iterable<DexType> getOriginalTypes(DexType type) {
+    return IterableUtils.flatMap(internalGetOriginalTypes(type), getPrevious()::getOriginalTypes);
+  }
+
+  @Override
+  public DexField getOriginalFieldSignature(DexField field) {
+    DexField originalField = fieldMap.getRepresentativeKeyOrDefault(field, field);
+    return getPrevious().getOriginalFieldSignature(originalField);
+  }
+
+  @Override
+  public DexMethod getOriginalMethodSignature(DexMethod method) {
+    DexMethod originalMethod = internalGetPreviousMethodSignature(method);
+    return getPrevious().getOriginalMethodSignature(originalMethod);
+  }
+
+  @Override
+  public DexField getRenamedFieldSignature(DexField originalField) {
+    DexField renamedField = getPrevious().getRenamedFieldSignature(originalField);
+    return internalGetNextFieldSignature(renamedField);
+  }
+
+  @Override
+  public DexMethod getRenamedMethodSignature(DexMethod originalMethod, GraphLens applied) {
+    if (this == applied) {
+      return originalMethod;
+    }
+    DexMethod renamedMethod = getPrevious().getRenamedMethodSignature(originalMethod, applied);
+    return internalGetNextMethodSignature(renamedMethod);
+  }
+
+  @Override
+  protected DexType internalDescribeLookupClassType(DexType previous) {
+    return typeMap != null ? typeMap.getOrDefault(previous, previous) : previous;
+  }
+
+  @Override
+  protected FieldLookupResult internalDescribeLookupField(FieldLookupResult previous) {
+    if (previous.hasReboundReference()) {
+      // Rewrite the rebound reference and then "fixup" the non-rebound reference.
+      DexField rewrittenReboundReference = previous.getRewrittenReboundReference(fieldMap);
+      DexField rewrittenNonReboundReference =
+          previous.getReference() == previous.getReboundReference()
+              ? rewrittenReboundReference
+              : rewrittenReboundReference.withHolder(
+                  internalDescribeLookupClassType(previous.getReference().getHolderType()),
+                  dexItemFactory);
+      return FieldLookupResult.builder(this)
+          .setReboundReference(rewrittenReboundReference)
+          .setReference(rewrittenNonReboundReference)
+          .setCastType(previous.getRewrittenCastType(this::internalDescribeLookupClassType))
+          .build();
+    } else {
+      // TODO(b/168282032): We should always have the rebound reference, so this should become
+      //  unreachable.
+      DexField rewrittenReference = previous.getRewrittenReference(fieldMap);
+      return FieldLookupResult.builder(this)
+          .setReference(rewrittenReference)
+          .setCastType(previous.getRewrittenCastType(this::internalDescribeLookupClassType))
+          .build();
+    }
+  }
+
+  @Override
+  public MethodLookupResult internalDescribeLookupMethod(
+      MethodLookupResult previous, DexMethod context) {
+    if (previous.hasReboundReference()) {
+      // TODO(sgjesse): Should we always do interface to virtual mapping? Is it a performance win
+      //  that only subclasses which are known to need it actually do it?
+      DexMethod rewrittenReboundReference = previous.getRewrittenReboundReference(methodMap);
+      DexMethod rewrittenReference =
+          previous.getReference() == previous.getReboundReference()
+              ? rewrittenReboundReference
+              : // This assumes that the holder will always be moved in lock-step with the method!
+              rewrittenReboundReference.withHolder(
+                  internalDescribeLookupClassType(previous.getReference().getHolderType()),
+                  dexItemFactory);
+      return MethodLookupResult.builder(this)
+          .setReference(rewrittenReference)
+          .setReboundReference(rewrittenReboundReference)
+          .setPrototypeChanges(
+              internalDescribePrototypeChanges(
+                  previous.getPrototypeChanges(), rewrittenReboundReference))
+          .setType(
+              mapInvocationType(
+                  rewrittenReboundReference, previous.getReference(), previous.getType()))
+          .build();
+    } else {
+      // TODO(b/168282032): We should always have the rebound reference, so this should become
+      //  unreachable.
+      DexMethod newMethod = methodMap.get(previous.getReference());
+      if (newMethod == null) {
+        return previous;
+      }
+      // TODO(sgjesse): Should we always do interface to virtual mapping? Is it a performance win
+      //  that only subclasses which are known to need it actually do it?
+      return MethodLookupResult.builder(this)
+          .setReference(newMethod)
+          .setPrototypeChanges(
+              internalDescribePrototypeChanges(previous.getPrototypeChanges(), newMethod))
+          .setType(mapInvocationType(newMethod, previous.getReference(), previous.getType()))
+          .build();
+    }
+  }
+
+  @Override
+  public RewrittenPrototypeDescription lookupPrototypeChangesForMethodDefinition(DexMethod method) {
+    DexMethod previous = internalGetPreviousMethodSignature(method);
+    RewrittenPrototypeDescription lookup =
+        getPrevious().lookupPrototypeChangesForMethodDefinition(previous);
+    return internalDescribePrototypeChanges(lookup, method);
+  }
+
+  protected RewrittenPrototypeDescription internalDescribePrototypeChanges(
+      RewrittenPrototypeDescription prototypeChanges, DexMethod method) {
+    return prototypeChanges;
+  }
+
+  protected DexField internalGetNextFieldSignature(DexField field) {
+    return fieldMap.getOrDefault(field, field);
+  }
+
+  @Override
+  protected DexMethod internalGetPreviousMethodSignature(DexMethod method) {
+    return originalMethodSignatures.getRepresentativeValueOrDefault(method, method);
+  }
+
+  protected DexMethod internalGetNextMethodSignature(DexMethod method) {
+    return originalMethodSignatures.getRepresentativeKeyOrDefault(method, method);
+  }
+
+  @Override
+  public DexMethod lookupGetFieldForMethod(DexField field, DexMethod context) {
+    return getPrevious().lookupGetFieldForMethod(field, context);
+  }
+
+  @Override
+  public DexMethod lookupPutFieldForMethod(DexField field, DexMethod context) {
+    return getPrevious().lookupPutFieldForMethod(field, context);
+  }
+
+  /**
+   * Default invocation type mapping.
+   *
+   * <p>This is an identity mapping. If a subclass need invocation type mapping either override this
+   * method or {@link #lookupMethod(DexMethod, DexMethod, Type)}
+   */
+  protected Type mapInvocationType(DexMethod newMethod, DexMethod originalMethod, Type type) {
+    return type;
+  }
+
+  /**
+   * Standard mapping between interface and virtual invoke type.
+   *
+   * <p>Handle methods moved from interface to class or class to interface.
+   */
+  public static Type mapVirtualInterfaceInvocationTypes(
+      DexDefinitionSupplier definitions, DexMethod newMethod, DexMethod originalMethod, Type type) {
+    if (type == Type.VIRTUAL || type == Type.INTERFACE) {
+      // Get the invoke type of the actual definition.
+      DexClass newTargetClass = definitions.definitionFor(newMethod.getHolderType());
+      if (newTargetClass == null) {
+        return type;
+      }
+      DexClass originalTargetClass = definitions.definitionFor(originalMethod.getHolderType());
+      if (originalTargetClass != null
+          && (originalTargetClass.isInterface() ^ (type == Type.INTERFACE))) {
+        // The invoke was wrong to start with, so we keep it wrong. This is to ensure we get
+        // the IncompatibleClassChangeError the original invoke would have triggered.
+        return newTargetClass.accessFlags.isInterface() ? Type.VIRTUAL : Type.INTERFACE;
+      }
+      return newTargetClass.accessFlags.isInterface() ? Type.INTERFACE : Type.VIRTUAL;
+    }
+    return type;
+  }
+
+  @Override
+  public boolean isContextFreeForMethods() {
+    return getPrevious().isContextFreeForMethods();
+  }
+
+  @Override
+  public boolean verifyIsContextFreeForMethod(DexMethod method) {
+    assert getPrevious().verifyIsContextFreeForMethod(method);
+    return true;
+  }
+
+  @Override
+  public String toString() {
+    StringBuilder builder = new StringBuilder();
+    if (typeMap != null) {
+      for (Map.Entry<DexType, DexType> entry : typeMap.entrySet()) {
+        builder.append(entry.getKey().toSourceString()).append(" -> ");
+        builder.append(entry.getValue().toSourceString()).append(System.lineSeparator());
+      }
+    }
+    for (Map.Entry<DexMethod, DexMethod> entry : methodMap.entrySet()) {
+      builder.append(entry.getKey().toSourceString()).append(" -> ");
+      builder.append(entry.getValue().toSourceString()).append(System.lineSeparator());
+    }
+    fieldMap.forEachManyToOneMapping(
+        (keys, value) -> {
+          builder.append(
+              keys.stream()
+                  .map(DexField::toSourceString)
+                  .collect(Collectors.joining("," + System.lineSeparator())));
+          builder.append(" -> ");
+          builder.append(value.toSourceString()).append(System.lineSeparator());
+        });
+    builder.append(getPrevious().toString());
+    return builder.toString();
+  }
+}
diff --git a/src/main/java/com/android/tools/r8/horizontalclassmerging/HorizontalClassMergerGraphLens.java b/src/main/java/com/android/tools/r8/horizontalclassmerging/HorizontalClassMergerGraphLens.java
index bc713ce..742affa 100644
--- a/src/main/java/com/android/tools/r8/horizontalclassmerging/HorizontalClassMergerGraphLens.java
+++ b/src/main/java/com/android/tools/r8/horizontalclassmerging/HorizontalClassMergerGraphLens.java
@@ -8,7 +8,7 @@
 import com.android.tools.r8.graph.DexField;
 import com.android.tools.r8.graph.DexMethod;
 import com.android.tools.r8.graph.DexType;
-import com.android.tools.r8.graph.GraphLens.NestedGraphLens;
+import com.android.tools.r8.graph.NestedGraphLens;
 import com.android.tools.r8.ir.conversion.ExtraParameter;
 import com.android.tools.r8.utils.IterableUtils;
 import com.android.tools.r8.utils.collections.BidirectionalManyToOneHashMap;
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 9c65847..b1a0e85 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
@@ -37,9 +37,9 @@
 import com.android.tools.r8.graph.GenericSignature.FieldTypeSignature;
 import com.android.tools.r8.graph.GenericSignature.MethodTypeSignature;
 import com.android.tools.r8.graph.GraphLens;
-import com.android.tools.r8.graph.GraphLens.NestedGraphLens;
 import com.android.tools.r8.graph.MethodAccessFlags;
 import com.android.tools.r8.graph.MethodCollection;
+import com.android.tools.r8.graph.NestedGraphLens;
 import com.android.tools.r8.graph.ParameterAnnotationsList;
 import com.android.tools.r8.graph.ProgramMethod;
 import com.android.tools.r8.ir.code.Invoke.Type;
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 3e58a94..9e508bd 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
@@ -14,7 +14,7 @@
 import com.android.tools.r8.graph.DexProgramClass;
 import com.android.tools.r8.graph.DexProto;
 import com.android.tools.r8.graph.DexType;
-import com.android.tools.r8.graph.GraphLens.NestedGraphLens;
+import com.android.tools.r8.graph.NestedGraphLens;
 import com.android.tools.r8.graph.RewrittenPrototypeDescription;
 import com.android.tools.r8.graph.RewrittenPrototypeDescription.ArgumentInfoCollection;
 import com.android.tools.r8.graph.RewrittenPrototypeDescription.RemovedArgumentInfo;
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 0ebf96b..ed01d9f 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
@@ -14,7 +14,7 @@
 import com.android.tools.r8.graph.DexString;
 import com.android.tools.r8.graph.DexType;
 import com.android.tools.r8.graph.GraphLens;
-import com.android.tools.r8.graph.GraphLens.NestedGraphLens;
+import com.android.tools.r8.graph.NestedGraphLens;
 import com.android.tools.r8.graph.RewrittenPrototypeDescription;
 import com.android.tools.r8.graph.RewrittenPrototypeDescription.ArgumentInfoCollection;
 import com.android.tools.r8.graph.RewrittenPrototypeDescription.RemovedArgumentInfo;
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/enums/EnumUnboxingLens.java b/src/main/java/com/android/tools/r8/ir/optimize/enums/EnumUnboxingLens.java
index c64e51e..1a5b6bc 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/enums/EnumUnboxingLens.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/enums/EnumUnboxingLens.java
@@ -9,6 +9,7 @@
 import com.android.tools.r8.graph.DexMethod;
 import com.android.tools.r8.graph.DexType;
 import com.android.tools.r8.graph.GraphLens;
+import com.android.tools.r8.graph.NestedGraphLens;
 import com.android.tools.r8.graph.RewrittenPrototypeDescription;
 import com.android.tools.r8.ir.code.Invoke;
 import com.android.tools.r8.utils.BooleanUtils;
@@ -21,7 +22,7 @@
 import java.util.Map;
 import java.util.Set;
 
-class EnumUnboxingLens extends GraphLens.NestedGraphLens {
+class EnumUnboxingLens extends NestedGraphLens {
 
   private final Map<DexMethod, RewrittenPrototypeDescription> prototypeChangesPerMethod;
   private final Set<DexType> unboxedEnums;
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 1602032..eed5089 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
@@ -21,8 +21,8 @@
 import com.android.tools.r8.graph.DexProgramClass;
 import com.android.tools.r8.graph.DexType;
 import com.android.tools.r8.graph.GenericSignature.MethodTypeSignature;
-import com.android.tools.r8.graph.GraphLens.NestedGraphLens;
 import com.android.tools.r8.graph.MethodAccessFlags;
+import com.android.tools.r8.graph.NestedGraphLens;
 import com.android.tools.r8.graph.ParameterAnnotationsList;
 import com.android.tools.r8.graph.ProgramMethod;
 import com.android.tools.r8.ir.analysis.type.ArrayTypeElement;
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/staticizer/ClassStaticizerGraphLens.java b/src/main/java/com/android/tools/r8/ir/optimize/staticizer/ClassStaticizerGraphLens.java
index 75a78ab..f66bf8f 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/staticizer/ClassStaticizerGraphLens.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/staticizer/ClassStaticizerGraphLens.java
@@ -7,7 +7,7 @@
 import com.android.tools.r8.graph.AppView;
 import com.android.tools.r8.graph.DexField;
 import com.android.tools.r8.graph.DexMethod;
-import com.android.tools.r8.graph.GraphLens.NestedGraphLens;
+import com.android.tools.r8.graph.NestedGraphLens;
 import com.android.tools.r8.ir.code.Invoke.Type;
 import com.android.tools.r8.utils.collections.BidirectionalOneToOneMap;
 import com.google.common.collect.ImmutableMap;
diff --git a/src/main/java/com/android/tools/r8/optimize/MemberRebindingLens.java b/src/main/java/com/android/tools/r8/optimize/MemberRebindingLens.java
index 171dcbe..4310236 100644
--- a/src/main/java/com/android/tools/r8/optimize/MemberRebindingLens.java
+++ b/src/main/java/com/android/tools/r8/optimize/MemberRebindingLens.java
@@ -4,7 +4,7 @@
 
 package com.android.tools.r8.optimize;
 
-import static com.android.tools.r8.graph.GraphLens.NestedGraphLens.mapVirtualInterfaceInvocationTypes;
+import static com.android.tools.r8.graph.NestedGraphLens.mapVirtualInterfaceInvocationTypes;
 
 import com.android.tools.r8.graph.AppView;
 import com.android.tools.r8.graph.DexField;
diff --git a/src/main/java/com/android/tools/r8/optimize/PublicizerLens.java b/src/main/java/com/android/tools/r8/optimize/PublicizerLens.java
index 8ea4e1f..9a29509 100644
--- a/src/main/java/com/android/tools/r8/optimize/PublicizerLens.java
+++ b/src/main/java/com/android/tools/r8/optimize/PublicizerLens.java
@@ -8,7 +8,7 @@
 import com.android.tools.r8.graph.DexEncodedMethod;
 import com.android.tools.r8.graph.DexMethod;
 import com.android.tools.r8.graph.GraphLens;
-import com.android.tools.r8.graph.GraphLens.NestedGraphLens;
+import com.android.tools.r8.graph.NestedGraphLens;
 import com.android.tools.r8.ir.code.Invoke.Type;
 import com.android.tools.r8.utils.collections.EmptyBidirectionalOneToOneMap;
 import com.google.common.collect.ImmutableMap;
diff --git a/src/main/java/com/android/tools/r8/repackaging/RepackagingLens.java b/src/main/java/com/android/tools/r8/repackaging/RepackagingLens.java
index e6d16eb..8ac9320 100644
--- a/src/main/java/com/android/tools/r8/repackaging/RepackagingLens.java
+++ b/src/main/java/com/android/tools/r8/repackaging/RepackagingLens.java
@@ -10,7 +10,7 @@
 import com.android.tools.r8.graph.DexMethod;
 import com.android.tools.r8.graph.DexReference;
 import com.android.tools.r8.graph.DexType;
-import com.android.tools.r8.graph.GraphLens.NestedGraphLens;
+import com.android.tools.r8.graph.NestedGraphLens;
 import com.android.tools.r8.shaking.AppInfoWithLiveness;
 import com.android.tools.r8.utils.IterableUtils;
 import com.android.tools.r8.utils.collections.BidirectionalOneToOneHashMap;
diff --git a/src/main/java/com/android/tools/r8/shaking/VerticalClassMergerGraphLens.java b/src/main/java/com/android/tools/r8/shaking/VerticalClassMergerGraphLens.java
index 008380c..7c6f295 100644
--- a/src/main/java/com/android/tools/r8/shaking/VerticalClassMergerGraphLens.java
+++ b/src/main/java/com/android/tools/r8/shaking/VerticalClassMergerGraphLens.java
@@ -12,7 +12,7 @@
 import com.android.tools.r8.graph.DexProto;
 import com.android.tools.r8.graph.DexType;
 import com.android.tools.r8.graph.GraphLens;
-import com.android.tools.r8.graph.GraphLens.NestedGraphLens;
+import com.android.tools.r8.graph.NestedGraphLens;
 import com.android.tools.r8.graph.RewrittenPrototypeDescription;
 import com.android.tools.r8.graph.classmerging.VerticallyMergedClasses;
 import com.android.tools.r8.ir.code.Invoke.Type;
diff --git a/src/main/java/com/android/tools/r8/synthesis/SyntheticFinalization.java b/src/main/java/com/android/tools/r8/synthesis/SyntheticFinalization.java
index d4c124f..27ec751 100644
--- a/src/main/java/com/android/tools/r8/synthesis/SyntheticFinalization.java
+++ b/src/main/java/com/android/tools/r8/synthesis/SyntheticFinalization.java
@@ -18,8 +18,8 @@
 import com.android.tools.r8.graph.DexProgramClass;
 import com.android.tools.r8.graph.DexType;
 import com.android.tools.r8.graph.GraphLens;
-import com.android.tools.r8.graph.GraphLens.NestedGraphLens;
 import com.android.tools.r8.graph.GraphLens.NonIdentityGraphLens;
+import com.android.tools.r8.graph.NestedGraphLens;
 import com.android.tools.r8.graph.PrunedItems;
 import com.android.tools.r8.graph.TreeFixerBase;
 import com.android.tools.r8.ir.code.NumberGenerator;