Merge "Revert "Move invocation type mapping to GraphLense""
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 7d17f11..2c12a90 100644
--- a/src/main/java/com/android/tools/r8/graph/GraphLense.java
+++ b/src/main/java/com/android/tools/r8/graph/GraphLense.java
@@ -20,35 +20,11 @@
  * <li>Renaming private methods/fields.</li>
  * <li>Moving methods/fields to a super/subclass.</li>
  * <li>Replacing method/field references by the same method/field on a super/subtype</li>
- * <li>Moved methods might require changed invocation type at the call site</li>
  * </ul>
  * Note that the latter two have to take visibility into account.
  */
 public abstract class GraphLense {
 
-  /**
-   * Result of a method lookup in a GraphLense.
-   *
-   * This provide the new target and the invoke type to use.
-   */
-  public static class GraphLenseLookupResult {
-    private final DexMethod method;
-    private final Type type;
-
-    public GraphLenseLookupResult(DexMethod method, Type type) {
-      this.method = method;
-      this.type = type;
-    }
-
-    public DexMethod getMethod() {
-      return method;
-    }
-
-    public Type getType() {
-      return type;
-    }
-  }
-
   public static class Builder {
 
     protected Builder() {
@@ -92,11 +68,10 @@
   // This overload can be used when the graph lense is known to be context insensitive.
   public DexMethod lookupMethod(DexMethod method) {
     assert isContextFreeForMethod(method);
-    return lookupMethod(method, null, null).getMethod();
+    return lookupMethod(method, null, null);
   }
 
-  public abstract GraphLenseLookupResult lookupMethod(
-      DexMethod method, DexEncodedMethod context, Type type);
+  public abstract DexMethod lookupMethod(DexMethod method, DexEncodedMethod context, Type type);
 
   // Context sensitive graph lenses should override this method.
   public Set<DexMethod> lookupMethodInAllContexts(DexMethod method) {
@@ -151,9 +126,8 @@
     }
 
     @Override
-    public GraphLenseLookupResult lookupMethod(
-        DexMethod method, DexEncodedMethod context, Type type) {
-      return new GraphLenseLookupResult(method, type);
+    public DexMethod lookupMethod(DexMethod method, DexEncodedMethod context, Type type) {
+      return method;
     }
 
     @Override
@@ -167,29 +141,19 @@
     }
   }
 
-  /**
-   * GraphLense implementation with a parent lense using a simple mapping for type, method and
-   * field mapping.
-   *
-   * Subclasses can override the lookup methods.
-   *
-   * For method mapping where invocation type can change just override
-   * {@link #mapInvocationType(DexMethod, DexMethod, DexEncodedMethod, Type)} if
-   * the default name mapping applies, and only invocation type might need to change.
-   */
   public static class NestedGraphLense extends GraphLense {
 
-    protected final GraphLense previousLense;
+    private final GraphLense previousLense;
     protected final DexItemFactory dexItemFactory;
 
-    protected final Map<DexType, DexType> typeMap;
+    private final Map<DexType, DexType> typeMap;
     private final Map<DexType, DexType> arrayTypeCache = new IdentityHashMap<>();
-    protected final Map<DexMethod, DexMethod> methodMap;
-    protected final Map<DexField, DexField> fieldMap;
+    private final Map<DexMethod, DexMethod> methodMap;
+    private final Map<DexField, DexField> fieldMap;
 
     public NestedGraphLense(Map<DexType, DexType> typeMap, Map<DexMethod, DexMethod> methodMap,
         Map<DexField, DexField> fieldMap, GraphLense previousLense, DexItemFactory dexItemFactory) {
-      this.typeMap = typeMap.isEmpty() ? null : typeMap;
+      this.typeMap = typeMap;
       this.methodMap = methodMap;
       this.fieldMap = fieldMap;
       this.previousLense = previousLense;
@@ -216,58 +180,13 @@
         }
       }
       DexType previous = previousLense.lookupType(type);
-      return typeMap != null ? typeMap.getOrDefault(previous, previous) : previous;
+      return typeMap.getOrDefault(previous, previous);
     }
 
     @Override
-    public GraphLenseLookupResult lookupMethod(
-        DexMethod method, DexEncodedMethod context, Type type) {
-      GraphLenseLookupResult previous = previousLense.lookupMethod(method, context, type);
-      DexMethod newMethod = methodMap.get(previous.getMethod());
-      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 new GraphLenseLookupResult(
-          newMethod, mapInvocationType(newMethod, method, context, type));
-    }
-
-    /**
-     * Default invocation type mapping.
-     *
-     * This is an identity mapping. If a subclass need invocation type mapping either override
-     * this method or {@link #lookupMethod(DexMethod, DexEncodedMethod, Type)}
-     */
-    protected Type mapInvocationType(
-        DexMethod newMethod, DexMethod originalMethod, DexEncodedMethod context, Type type) {
-      return type;
-    }
-
-    /**
-     * Standard mapping between interface and virtual invoke type.
-     *
-     * Handle methods moved from interface to class or class to interface.
-     */
-    final protected Type mapVirtualInterfaceInvocationTypes(
-        AppInfo appInfo, DexMethod newMethod, DexMethod originalMethod,
-        DexEncodedMethod context, Type type) {
-      if (type == Type.VIRTUAL || type == Type.INTERFACE) {
-        // Get the invoke type of the actual definition.
-        DexClass newTargetClass = appInfo.definitionFor(newMethod.holder);
-        if (newTargetClass == null) {
-          return type;
-        }
-        DexClass originalTargetClass = appInfo.definitionFor(originalMethod.holder);
-        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;
+    public DexMethod lookupMethod(DexMethod method, DexEncodedMethod context, Type type) {
+      DexMethod previous = previousLense.lookupMethod(method, context, type);
+      return methodMap.getOrDefault(previous, previous);
     }
 
     @Override
diff --git a/src/main/java/com/android/tools/r8/ir/conversion/CallGraph.java b/src/main/java/com/android/tools/r8/ir/conversion/CallGraph.java
index 85506ad..21b8dc4 100644
--- a/src/main/java/com/android/tools/r8/ir/conversion/CallGraph.java
+++ b/src/main/java/com/android/tools/r8/ir/conversion/CallGraph.java
@@ -363,7 +363,7 @@
 
     private void processInvoke(Type type, DexMethod method) {
       DexEncodedMethod source = caller.method;
-      method = graphLense.lookupMethod(method, source, type).getMethod();
+      method = graphLense.lookupMethod(method, source, type);
       DexEncodedMethod definition = appInfo.lookup(type, method, source.method.holder);
       if (definition != null) {
         assert !source.accessFlags.isBridge() || definition != caller.method;
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 52919c6..7d7e5f3 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
@@ -141,7 +141,7 @@
       this.memberValuePropagation =
           options.enableValuePropagation ?
               new MemberValuePropagation(appInfo.withLiveness()) : null;
-      this.lensCodeRewriter = new LensCodeRewriter(graphLense, appInfo.withSubtyping());
+      this.lensCodeRewriter = new LensCodeRewriter(graphLense, appInfo.withSubtyping(), options);
       if (appInfo.hasLiveness()) {
         // When disabling the pruner here, also disable the ProtoLiteExtension in R8.java.
         this.protoLiteRewriter =
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 23beded..4c0be09 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
@@ -5,6 +5,7 @@
 
 import com.android.tools.r8.graph.AppInfoWithSubtyping;
 import com.android.tools.r8.graph.DexCallSite;
+import com.android.tools.r8.graph.DexClass;
 import com.android.tools.r8.graph.DexEncodedMethod;
 import com.android.tools.r8.graph.DexField;
 import com.android.tools.r8.graph.DexMethod;
@@ -15,7 +16,6 @@
 import com.android.tools.r8.graph.DexValue;
 import com.android.tools.r8.graph.DexValue.DexValueMethodHandle;
 import com.android.tools.r8.graph.GraphLense;
-import com.android.tools.r8.graph.GraphLense.GraphLenseLookupResult;
 import com.android.tools.r8.ir.code.BasicBlock;
 import com.android.tools.r8.ir.code.CheckCast;
 import com.android.tools.r8.ir.code.ConstClass;
@@ -35,6 +35,7 @@
 import com.android.tools.r8.ir.code.StaticGet;
 import com.android.tools.r8.ir.code.StaticPut;
 import com.android.tools.r8.ir.code.Value;
+import com.android.tools.r8.utils.InternalOptions;
 import java.util.List;
 import java.util.ListIterator;
 import java.util.stream.Collectors;
@@ -43,10 +44,13 @@
 
   private final GraphLense graphLense;
   private final AppInfoWithSubtyping appInfo;
+  private final InternalOptions options;
 
-  public LensCodeRewriter(GraphLense graphLense, AppInfoWithSubtyping appInfo) {
+  public LensCodeRewriter(
+      GraphLense graphLense, AppInfoWithSubtyping appInfo, InternalOptions options) {
     this.graphLense = graphLense;
     this.appInfo = appInfo;
+    this.options = options;
   }
 
   private Value makeOutValue(Instruction insn, IRCode code) {
@@ -106,10 +110,8 @@
           if (!invokedHolder.isClassType()) {
             continue;
           }
-          GraphLenseLookupResult lenseLookup =
-              graphLense.lookupMethod(invokedMethod, method, invoke.getType());
-          DexMethod actualTarget = lenseLookup.getMethod();
-          Invoke.Type invokeType = lenseLookup.getType();
+          DexMethod actualTarget = graphLense.lookupMethod(invokedMethod, method, invoke.getType());
+          Invoke.Type invokeType = getInvokeType(invoke, actualTarget, invokedMethod, method);
           if (actualTarget != invokedMethod || invoke.getType() != invokeType) {
             Invoke newInvoke = Invoke.create(invokeType, actualTarget, null,
                     invoke.outValue(), invoke.inValues());
@@ -228,13 +230,17 @@
       DexEncodedMethod method, DexMethodHandle methodHandle) {
     if (methodHandle.isMethodHandle()) {
       DexMethod invokedMethod = methodHandle.asMethod();
-      GraphLenseLookupResult lenseLookup = graphLense.lookupMethod(
-          invokedMethod, method, methodHandle.type.toInvokeType());
-      DexMethod actualTarget = lenseLookup.getMethod();
+      DexMethod actualTarget =
+          graphLense.lookupMethod(invokedMethod, method, methodHandle.type.toInvokeType());
       if (actualTarget != invokedMethod) {
-        MethodHandleType newType = lenseLookup.getType() == Type.INTERFACE
-            ? MethodHandleType.INVOKE_INTERFACE
-            : MethodHandleType.INVOKE_INSTANCE;
+        DexClass clazz = appInfo.definitionFor(actualTarget.holder);
+        MethodHandleType newType = methodHandle.type;
+        if (clazz != null
+            && (newType.isInvokeInterface() || newType.isInvokeInstance())) {
+          newType = clazz.accessFlags.isInterface()
+              ? MethodHandleType.INVOKE_INTERFACE
+              : MethodHandleType.INVOKE_INSTANCE;
+        }
         return new DexMethodHandle(newType, actualTarget);
       }
     } else {
@@ -246,4 +252,55 @@
     }
     return methodHandle;
   }
+
+  private Type getInvokeType(
+      InvokeMethod invoke,
+      DexMethod actualTarget,
+      DexMethod originalTarget,
+      DexEncodedMethod invocationContext) {
+    // We might move methods from interfaces to classes and vice versa. So we have to support
+    // fixing the invoke kind, yet only if it was correct to start with.
+    if (invoke.isInvokeVirtual() || invoke.isInvokeInterface()) {
+      // Get the invoke type of the actual definition.
+      DexClass newTargetClass = appInfo.definitionFor(actualTarget.holder);
+      if (newTargetClass == null) {
+        return invoke.getType();
+      }
+      DexClass originalTargetClass = appInfo.definitionFor(originalTarget.holder);
+      if (originalTargetClass != null
+          && (originalTargetClass.isInterface() ^ (invoke.getType() == 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;
+    }
+    if (options.enableClassMerging && invoke.isInvokeSuper()) {
+      if (actualTarget.getHolder() == invocationContext.method.getHolder()) {
+        DexClass targetClass = appInfo.definitionFor(actualTarget.holder);
+        if (targetClass == null) {
+          return invoke.getType();
+        }
+
+        // If the super class A of the enclosing class B (i.e., invocationContext.method.holder)
+        // has been merged into B during vertical class merging, and this invoke-super instruction
+        // was resolving to a method in A, then the target method has been changed to a direct
+        // method and moved into B, so that we need to use an invoke-direct instruction instead of
+        // invoke-super.
+        //
+        // At this point, we have an invoke-super instruction where the static target is the
+        // enclosing class. However, such an instruction could occur even if a subclass has never
+        // been merged into the enclosing class. Therefore, to determine if vertical class merging
+        // has been applied, we look if there is a direct method with the right signature, and only
+        // return Type.DIRECT in that case.
+        DexEncodedMethod method = targetClass.lookupDirectMethod(actualTarget);
+        if (method != null) {
+          // The target method has been moved from the super class into the sub class during class
+          // merging such that we now need to use an invoke-direct instruction.
+          return Type.DIRECT;
+        }
+      }
+    }
+    return 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 25cd969..029b169 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
@@ -271,7 +271,7 @@
       Origin origin = appInfo.originFor(target.method.holder);
       IRCode code = target.buildInliningIR(appInfo, options, generator, callerPosition, origin);
       if (!target.isProcessed()) {
-        new LensCodeRewriter(graphLense, appInfo).rewrite(code, target);
+        new LensCodeRewriter(graphLense, appInfo, options).rewrite(code, target);
       }
       return code;
     }
diff --git a/src/main/java/com/android/tools/r8/optimize/BridgeMethodAnalysis.java b/src/main/java/com/android/tools/r8/optimize/BridgeMethodAnalysis.java
index 492bcd0..56089f4 100644
--- a/src/main/java/com/android/tools/r8/optimize/BridgeMethodAnalysis.java
+++ b/src/main/java/com/android/tools/r8/optimize/BridgeMethodAnalysis.java
@@ -46,14 +46,14 @@
         assert !method.accessFlags.isPrivate() && !method.accessFlags.isConstructor();
         if (kind == InvokeKind.STATIC) {
           assert method.accessFlags.isStatic();
-          DexMethod actualTarget = lense.lookupMethod(target, method, Type.STATIC).getMethod();
+          DexMethod actualTarget = lense.lookupMethod(target, method, Type.STATIC);
           DexEncodedMethod targetMethod = appInfo.lookupStaticTarget(actualTarget);
           if (targetMethod != null) {
             addForwarding(method, targetMethod);
           }
         } else if (kind == InvokeKind.VIRTUAL) {
           // TODO(herhut): Add support for bridges with multiple targets.
-          DexMethod actualTarget = lense.lookupMethod(target, method, Type.VIRTUAL).getMethod();
+          DexMethod actualTarget = lense.lookupMethod(target, method, Type.VIRTUAL);
           DexEncodedMethod targetMethod = appInfo.lookupSingleVirtualTarget(actualTarget);
           if (targetMethod != null) {
             addForwarding(method, targetMethod);
@@ -93,16 +93,15 @@
     }
 
     @Override
-    public GraphLenseLookupResult lookupMethod(
-        DexMethod method, DexEncodedMethod context, Type type) {
-      GraphLenseLookupResult previous = previousLense.lookupMethod(method, context, type);
-      DexMethod bridge = bridgeTargetToBridgeMap.get(previous.getMethod());
+    public DexMethod lookupMethod(DexMethod method, DexEncodedMethod context, Type type) {
+      DexMethod previous = previousLense.lookupMethod(method, context, type);
+      DexMethod bridge = bridgeTargetToBridgeMap.get(previous);
       // Do not forward calls from a bridge method to itself while the bridge method is still
       // a bridge.
       if (bridge == null || (context.accessFlags.isBridge() && bridge == context.method)) {
         return previous;
       }
-      return new GraphLenseLookupResult(bridge, type);
+      return bridge;
     }
 
     @Override
diff --git a/src/main/java/com/android/tools/r8/optimize/MemberRebindingAnalysis.java b/src/main/java/com/android/tools/r8/optimize/MemberRebindingAnalysis.java
index da7a787..fc408e3 100644
--- a/src/main/java/com/android/tools/r8/optimize/MemberRebindingAnalysis.java
+++ b/src/main/java/com/android/tools/r8/optimize/MemberRebindingAnalysis.java
@@ -26,13 +26,12 @@
 
   private final AppInfoWithLiveness appInfo;
   private final GraphLense lense;
-  private final MemberRebindingLense.Builder builder;
+  private final GraphLense.Builder builder = GraphLense.builder();
 
   public MemberRebindingAnalysis(AppInfoWithLiveness appInfo, GraphLense lense) {
     assert lense.isContextFreeForMethods();
     this.appInfo = appInfo;
     this.lense = lense;
-    this.builder = MemberRebindingLense.builder(appInfo);
   }
 
   private DexMethod validTargetFor(DexMethod target, DexMethod original) {
@@ -251,6 +250,6 @@
         mergeFieldAccessContexts(appInfo.instanceFieldReads, appInfo.instanceFieldWrites),
         appInfo::resolveFieldOn, DexClass::lookupField);
 
-    return builder.build(lense);
+    return builder.build(appInfo.dexItemFactory, lense);
   }
 }
diff --git a/src/main/java/com/android/tools/r8/optimize/MemberRebindingLense.java b/src/main/java/com/android/tools/r8/optimize/MemberRebindingLense.java
deleted file mode 100644
index 4ee0cee..0000000
--- a/src/main/java/com/android/tools/r8/optimize/MemberRebindingLense.java
+++ /dev/null
@@ -1,56 +0,0 @@
-// Copyright (c) 2018, the R8 project authors. Please see the AUTHORS file
-// for details. All rights reserved. Use of this source code is governed by a
-// BSD-style license that can be found in the LICENSE file.
-
-package com.android.tools.r8.optimize;
-
-import com.android.tools.r8.graph.AppInfo;
-import com.android.tools.r8.graph.DexEncodedMethod;
-import com.android.tools.r8.graph.DexField;
-import com.android.tools.r8.graph.DexMethod;
-import com.android.tools.r8.graph.GraphLense;
-import com.android.tools.r8.graph.GraphLense.NestedGraphLense;
-import com.android.tools.r8.ir.code.Invoke.Type;
-import com.google.common.collect.ImmutableMap;
-import java.util.Map;
-
-public class MemberRebindingLense extends NestedGraphLense {
-  public static class Builder extends NestedGraphLense.Builder {
-    private final AppInfo appInfo;
-
-    protected Builder(AppInfo appInfo) {
-      this.appInfo = appInfo;
-    }
-
-    public GraphLense build(GraphLense previousLense) {
-      assert typeMap.isEmpty();
-      if (methodMap.isEmpty() && fieldMap.isEmpty()) {
-        return previousLense;
-      }
-      return new MemberRebindingLense(appInfo, methodMap, fieldMap, previousLense);
-    }
-  }
-
-  private final AppInfo appInfo;
-
-  public MemberRebindingLense(
-      AppInfo appInfo,
-      Map<DexMethod, DexMethod> methodMap,
-      Map<DexField, DexField> fieldMap,
-      GraphLense previousLense) {
-    super(ImmutableMap.of(), methodMap, fieldMap, previousLense, appInfo.dexItemFactory);
-    this.appInfo = appInfo;
-  }
-
-  public static Builder builder(AppInfo appInfo) {
-    return new Builder(appInfo);
-  }
-
-
-  @Override
-  protected Type mapInvocationType(
-      DexMethod newMethod, DexMethod originalMethod, DexEncodedMethod context, Type type) {
-    return super.mapVirtualInterfaceInvocationTypes(
-        appInfo, newMethod, originalMethod, context, type);
-  }
-}
diff --git a/src/main/java/com/android/tools/r8/shaking/VerticalClassMerger.java b/src/main/java/com/android/tools/r8/shaking/VerticalClassMerger.java
index f0519a1..b11e33d 100644
--- a/src/main/java/com/android/tools/r8/shaking/VerticalClassMerger.java
+++ b/src/main/java/com/android/tools/r8/shaking/VerticalClassMerger.java
@@ -321,7 +321,7 @@
 
     // The resulting graph lense that should be used after class merging.
     VerticalClassMergerGraphLense.Builder renamedMembersLense =
-        VerticalClassMergerGraphLense.builder(appInfo);
+        new VerticalClassMergerGraphLense.Builder();
 
     Iterator<DexProgramClass> classIterator = classes.iterator();
 
@@ -490,7 +490,7 @@
     private final DexClass source;
     private final DexClass target;
     private final VerticalClassMergerGraphLense.Builder deferredRenamings =
-        VerticalClassMergerGraphLense.builder(appInfo);
+        new VerticalClassMergerGraphLense.Builder();
     private boolean abortMerge = false;
 
     private ClassMerger(DexClass source, DexClass target) {
diff --git a/src/main/java/com/android/tools/r8/shaking/VerticalClassMergerGraphLense.java b/src/main/java/com/android/tools/r8/shaking/VerticalClassMergerGraphLense.java
index 99fb63a..942a857 100644
--- a/src/main/java/com/android/tools/r8/shaking/VerticalClassMergerGraphLense.java
+++ b/src/main/java/com/android/tools/r8/shaking/VerticalClassMergerGraphLense.java
@@ -4,13 +4,11 @@
 
 package com.android.tools.r8.shaking;
 
-import com.android.tools.r8.graph.AppInfo;
 import com.android.tools.r8.graph.DexEncodedMethod;
 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.GraphLense;
-import com.android.tools.r8.graph.GraphLense.NestedGraphLense;
 import com.android.tools.r8.ir.code.Invoke.Type;
 import com.google.common.collect.ImmutableMap;
 import com.google.common.collect.ImmutableSet;
@@ -42,57 +40,47 @@
 // invocation will hit the same implementation as the original super.m() call.
 //
 // For the invocation "invoke-virtual A.m()" in B.m2, this graph lense will return the method B.m.
-public class VerticalClassMergerGraphLense extends NestedGraphLense {
-  private final AppInfo appInfo;
+public class VerticalClassMergerGraphLense extends GraphLense {
+  private final GraphLense previousLense;
 
+  private final Map<DexField, DexField> fieldMap;
+  private final Map<DexMethod, DexMethod> methodMap;
   private final Set<DexMethod> mergedMethods;
   private final Map<DexType, Map<DexMethod, DexMethod>> contextualVirtualToDirectMethodMaps;
 
   public VerticalClassMergerGraphLense(
-      AppInfo appInfo,
       Map<DexField, DexField> fieldMap,
       Map<DexMethod, DexMethod> methodMap,
       Set<DexMethod> mergedMethods,
       Map<DexType, Map<DexMethod, DexMethod>> contextualVirtualToDirectMethodMaps,
       GraphLense previousLense) {
-    super(ImmutableMap.of(), methodMap, fieldMap, previousLense, appInfo.dexItemFactory);
-    this.appInfo = appInfo;
+    this.previousLense = previousLense;
+    this.fieldMap = fieldMap;
+    this.methodMap = methodMap;
     this.mergedMethods = mergedMethods;
     this.contextualVirtualToDirectMethodMaps = contextualVirtualToDirectMethodMaps;
   }
 
-  public static Builder builder(AppInfo appInfo) {
-    return new Builder(appInfo);
+  @Override
+  public DexType lookupType(DexType type) {
+    return previousLense.lookupType(type);
   }
 
   @Override
-  public GraphLenseLookupResult lookupMethod(
-      DexMethod method, DexEncodedMethod context, Type type) {
+  public DexMethod lookupMethod(DexMethod method, DexEncodedMethod context, Type type) {
     assert isContextFreeForMethod(method) || (context != null && type != null);
-    GraphLenseLookupResult previous = previousLense.lookupMethod(method, context, type);
-    if (previous.getType() == Type.SUPER && !mergedMethods.contains(context.method)) {
+    DexMethod previous = previousLense.lookupMethod(method, context, type);
+    if (type == Type.SUPER && !mergedMethods.contains(context.method)) {
       Map<DexMethod, DexMethod> virtualToDirectMethodMap =
           contextualVirtualToDirectMethodMaps.get(context.method.holder);
       if (virtualToDirectMethodMap != null) {
-        DexMethod directMethod = virtualToDirectMethodMap.get(previous.getMethod());
+        DexMethod directMethod = virtualToDirectMethodMap.get(previous);
         if (directMethod != null) {
-          // If the super class A of the enclosing class B (i.e., context.method.holder)
-          // has been merged into B during vertical class merging, and this invoke-super instruction
-          // was resolving to a method in A, then the target method has been changed to a direct
-          // method and moved into B, so that we need to use an invoke-direct instruction instead of
-          // invoke-super.
-          return new GraphLenseLookupResult(directMethod, Type.DIRECT);
+          return directMethod;
         }
       }
     }
-    return super.lookupMethod(previous.getMethod(), context, previous.getType());
-  }
-
-  @Override
-  protected Type mapInvocationType(
-      DexMethod newMethod, DexMethod originalMethod, DexEncodedMethod context, Type type) {
-    return super.mapVirtualInterfaceInvocationTypes(
-        appInfo, newMethod, originalMethod, context, type);
+    return methodMap.getOrDefault(previous, previous);
   }
 
   @Override
@@ -112,6 +100,12 @@
   }
 
   @Override
+  public DexField lookupField(DexField field) {
+    DexField previous = previousLense.lookupField(field);
+    return fieldMap.getOrDefault(previous, previous);
+  }
+
+  @Override
   public boolean isContextFreeForMethods() {
     return contextualVirtualToDirectMethodMaps.isEmpty() && previousLense.isContextFreeForMethods();
   }
@@ -132,7 +126,6 @@
   }
 
   public static class Builder {
-    private final AppInfo appInfo;
 
     private final ImmutableMap.Builder<DexField, DexField> fieldMapBuilder = ImmutableMap.builder();
     private final ImmutableMap.Builder<DexMethod, DexMethod> methodMapBuilder =
@@ -141,9 +134,7 @@
     private final Map<DexType, Map<DexMethod, DexMethod>> contextualVirtualToDirectMethodMaps =
         new HashMap<>();
 
-    private Builder(AppInfo appInfo) {
-      this.appInfo = appInfo;
-    }
+    public Builder() {}
 
     public GraphLense build(GraphLense previousLense) {
       Map<DexField, DexField> fieldMap = fieldMapBuilder.build();
@@ -154,7 +145,6 @@
         return previousLense;
       }
       return new VerticalClassMergerGraphLense(
-          appInfo,
           fieldMap,
           methodMap,
           mergedMethodsBuilder.build(),