Extend context sensitive GraphLense API

Extends the method GraphLense.lookupMethod with an argument of type Invoke.Type, such that graph lenses will have to opportunity to be context sensitive in the type of the invoke. This is needed for vertical class merging.

In addition to the lookupMethod API extension, this CL adds a new GraphLense (VerticalClassMergerGraphLense) that will be instantiated by the vertical class merger.

Change-Id: Ic6b72eacbb0f2cce8b9cda24a9d92722784bc5f3
diff --git a/src/main/java/com/android/tools/r8/R8.java b/src/main/java/com/android/tools/r8/R8.java
index 2e0b531..484bfe8 100644
--- a/src/main/java/com/android/tools/r8/R8.java
+++ b/src/main/java/com/android/tools/r8/R8.java
@@ -331,6 +331,14 @@
       GraphLense graphLense = GraphLense.getIdentityLense();
 
       if (appInfo.hasLiveness()) {
+        if (options.proguardConfiguration.hasApplyMappingFile()) {
+          SeedMapper seedMapper =
+              SeedMapper.seedMapperFromFile(options.proguardConfiguration.getApplyMappingFile());
+          timing.begin("apply-mapping");
+          graphLense =
+              new ProguardMapApplier(appInfo.withLiveness(), graphLense, seedMapper).run(timing);
+          timing.end();
+        }
         graphLense = new MemberRebindingAnalysis(appInfo.withLiveness(), graphLense).run();
         // Class merging requires inlining.
         if (options.enableClassMerging && options.enableInlining) {
@@ -343,14 +351,6 @@
           appInfo = appInfo.withLiveness()
               .prunedCopyFrom(application, classMerger.getRemovedClasses());
         }
-        if (options.proguardConfiguration.hasApplyMappingFile()) {
-          SeedMapper seedMapper = SeedMapper.seedMapperFromFile(
-              options.proguardConfiguration.getApplyMappingFile());
-          timing.begin("apply-mapping");
-          graphLense = new ProguardMapApplier(appInfo.withLiveness(), graphLense, seedMapper)
-              .run(timing);
-          timing.end();
-        }
         application = application.asDirect().rewrittenWithLense(graphLense);
         appInfo = appInfo.withLiveness().rewrittenWithLense(application.asDirect(), graphLense);
         // Collect switch maps and ordinals maps.
diff --git a/src/main/java/com/android/tools/r8/graph/DexMethodHandle.java b/src/main/java/com/android/tools/r8/graph/DexMethodHandle.java
index 06ea28d..cd3c093 100644
--- a/src/main/java/com/android/tools/r8/graph/DexMethodHandle.java
+++ b/src/main/java/com/android/tools/r8/graph/DexMethodHandle.java
@@ -6,6 +6,7 @@
 import com.android.tools.r8.dex.Constants;
 import com.android.tools.r8.dex.IndexedItemCollection;
 import com.android.tools.r8.errors.Unreachable;
+import com.android.tools.r8.ir.code.Invoke.Type;
 import com.android.tools.r8.naming.NamingLens;
 import org.objectweb.asm.Handle;
 import org.objectweb.asm.Opcodes;
@@ -159,6 +160,26 @@
     public boolean isInvokeConstructor() {
       return this == MethodHandleType.INVOKE_CONSTRUCTOR;
     }
+
+    public Type toInvokeType() {
+      assert isMethodType();
+      switch (this) {
+        case INVOKE_STATIC:
+          return Type.STATIC;
+        case INVOKE_INSTANCE:
+          return Type.VIRTUAL;
+        case INVOKE_CONSTRUCTOR:
+          return Type.DIRECT;
+        case INVOKE_DIRECT:
+          return Type.DIRECT;
+        case INVOKE_INTERFACE:
+          return Type.INTERFACE;
+        case INVOKE_SUPER:
+          return Type.SUPER;
+        default:
+          throw new Unreachable("DexMethodHandle with unexpected type: " + this);
+      }
+    }
   }
 
   public MethodHandleType type;
diff --git a/src/main/java/com/android/tools/r8/graph/DirectMappedDexApplication.java b/src/main/java/com/android/tools/r8/graph/DirectMappedDexApplication.java
index bd22444..e4f1c8c 100644
--- a/src/main/java/com/android/tools/r8/graph/DirectMappedDexApplication.java
+++ b/src/main/java/com/android/tools/r8/graph/DirectMappedDexApplication.java
@@ -69,7 +69,6 @@
   }
 
   public DirectMappedDexApplication rewrittenWithLense(GraphLense graphLense) {
-    assert graphLense.isContextFreeForMethods();
     assert mappingIsValid(graphLense, programClasses.getAllTypes());
     assert mappingIsValid(graphLense, libraryClasses.keySet());
     // As a side effect, this will rebuild the program classes and library classes maps.
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 7c6c941..096de5a 100644
--- a/src/main/java/com/android/tools/r8/graph/GraphLense.java
+++ b/src/main/java/com/android/tools/r8/graph/GraphLense.java
@@ -3,8 +3,12 @@
 // BSD-style license that can be found in the LICENSE file.
 package com.android.tools.r8.graph;
 
+import com.android.tools.r8.ir.code.Invoke.Type;
+import com.google.common.collect.ImmutableSet;
+import java.util.HashSet;
 import java.util.IdentityHashMap;
 import java.util.Map;
+import java.util.Set;
 
 /**
  * A GraphLense implements a virtual view on top of the graph, used to delay global rewrites until
@@ -61,17 +65,32 @@
 
   public abstract DexType lookupType(DexType type);
 
+  // This overload can be used when the graph lense is known to be context insensitive.
   public DexMethod lookupMethod(DexMethod method) {
-    assert isContextFreeForMethods();
-    return lookupMethod(method, null);
+    assert isContextFreeForMethod(method);
+    return lookupMethod(method, null, null);
   }
 
-  public abstract DexMethod lookupMethod(DexMethod method, DexEncodedMethod context);
+  public abstract DexMethod lookupMethod(DexMethod method, DexEncodedMethod context, Type type);
+
+  // Context sensitive graph lenses should override this method.
+  public Set<DexMethod> lookupMethodInAllContexts(DexMethod method) {
+    assert isContextFreeForMethod(method);
+    DexMethod result = lookupMethod(method);
+    if (result != null) {
+      return ImmutableSet.of(result);
+    }
+    return ImmutableSet.of();
+  }
 
   public abstract DexField lookupField(DexField field);
 
   public abstract boolean isContextFreeForMethods();
 
+  public boolean isContextFreeForMethod(DexMethod method) {
+    return isContextFreeForMethods();
+  }
+
   public static GraphLense getIdentityLense() {
     return new IdentityGraphLense();
   }
@@ -88,7 +107,7 @@
     }
 
     @Override
-    public DexMethod lookupMethod(DexMethod method, DexEncodedMethod context) {
+    public DexMethod lookupMethod(DexMethod method, DexEncodedMethod context, Type type) {
       return method;
     }
 
@@ -146,12 +165,21 @@
     }
 
     @Override
-    public DexMethod lookupMethod(DexMethod method, DexEncodedMethod context) {
-      DexMethod previous = previousLense.lookupMethod(method, context);
+    public DexMethod lookupMethod(DexMethod method, DexEncodedMethod context, Type type) {
+      DexMethod previous = previousLense.lookupMethod(method, context, type);
       return methodMap.getOrDefault(previous, previous);
     }
 
     @Override
+    public Set<DexMethod> lookupMethodInAllContexts(DexMethod method) {
+      Set<DexMethod> result = new HashSet<>();
+      for (DexMethod previous : previousLense.lookupMethodInAllContexts(method)) {
+        result.add(methodMap.getOrDefault(previous, previous));
+      }
+      return result;
+    }
+
+    @Override
     public DexField lookupField(DexField field) {
       DexField previous = previousLense.lookupField(field);
       return fieldMap.getOrDefault(previous, previous);
@@ -163,6 +191,11 @@
     }
 
     @Override
+    public boolean isContextFreeForMethod(DexMethod method) {
+      return previousLense.isContextFreeForMethod(method);
+    }
+
+    @Override
     public String toString() {
       StringBuilder builder = new StringBuilder();
       for (Map.Entry<DexType, DexType> entry : typeMap.entrySet()) {
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 8330e06..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);
+      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/LensCodeRewriter.java b/src/main/java/com/android/tools/r8/ir/conversion/LensCodeRewriter.java
index 28213e1..1926b14 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
@@ -96,7 +96,7 @@
           if (!invokedHolder.isClassType()) {
             continue;
           }
-          DexMethod actualTarget = graphLense.lookupMethod(invokedMethod, method);
+          DexMethod actualTarget = graphLense.lookupMethod(invokedMethod, method, invoke.getType());
           Invoke.Type invokeType = getInvokeType(invoke, actualTarget, invokedMethod);
           if (actualTarget != invokedMethod || invoke.getType() != invokeType) {
             Invoke newInvoke = Invoke.create(invokeType, actualTarget, null,
@@ -216,7 +216,8 @@
       DexEncodedMethod method, DexMethodHandle methodHandle) {
     if (methodHandle.isMethodHandle()) {
       DexMethod invokedMethod = methodHandle.asMethod();
-      DexMethod actualTarget = graphLense.lookupMethod(invokedMethod, method);
+      DexMethod actualTarget =
+          graphLense.lookupMethod(invokedMethod, method, methodHandle.type.toInvokeType());
       if (actualTarget != invokedMethod) {
         DexClass clazz = appInfo.definitionFor(actualTarget.holder);
         MethodHandleType newType = methodHandle.type;
diff --git a/src/main/java/com/android/tools/r8/naming/ProguardMapApplier.java b/src/main/java/com/android/tools/r8/naming/ProguardMapApplier.java
index 45200d4..7dfd840 100644
--- a/src/main/java/com/android/tools/r8/naming/ProguardMapApplier.java
+++ b/src/main/java/com/android/tools/r8/naming/ProguardMapApplier.java
@@ -37,6 +37,7 @@
       AppInfoWithLiveness appInfo,
       GraphLense previousLense,
       SeedMapper seedMapper) {
+    assert previousLense.isContextFreeForMethods();
     this.appInfo = appInfo;
     this.previousLense = previousLense;
     this.seedMapper = seedMapper;
@@ -44,7 +45,7 @@
 
   public GraphLense run(Timing timing) {
     timing.begin("from-pg-map-to-lense");
-    GraphLense lenseFromMap = new MapToLenseConverter().run(previousLense);
+    GraphLense lenseFromMap = new MapToLenseConverter().run();
     timing.end();
     timing.begin("fix-types-in-programs");
     GraphLense typeFixedLense = new TreeFixer(lenseFromMap).run();
@@ -61,7 +62,7 @@
       lenseBuilder = new ConflictFreeBuilder();
     }
 
-    private GraphLense run(GraphLense previousLense) {
+    private GraphLense run() {
       // To handle inherited yet undefined methods in library classes, we are traversing types in
       // a subtyping order. That also helps us detect conflicted mappings in a diamond case:
       //   LibItfA#foo -> a, LibItfB#foo -> b, while PrgA implements LibItfA and LibItfB.
@@ -308,7 +309,7 @@
       }
       for (int i = 0; i < methods.length; i++) {
         DexEncodedMethod encodedMethod = methods[i];
-        DexMethod appliedMethod = appliedLense.lookupMethod(encodedMethod.method, encodedMethod);
+        DexMethod appliedMethod = appliedLense.lookupMethod(encodedMethod.method);
         DexType newHolderType = substituteType(appliedMethod.holder, encodedMethod);
         DexProto newProto = substituteTypesIn(appliedMethod.proto, encodedMethod);
         DexMethod newMethod;
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 a920455d..56089f4 100644
--- a/src/main/java/com/android/tools/r8/optimize/BridgeMethodAnalysis.java
+++ b/src/main/java/com/android/tools/r8/optimize/BridgeMethodAnalysis.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.GraphLense;
+import com.android.tools.r8.ir.code.Invoke.Type;
 import com.android.tools.r8.logging.Log;
 import com.android.tools.r8.optimize.InvokeSingleTargetExtractor.InvokeKind;
 import com.android.tools.r8.shaking.Enqueuer.AppInfoWithLiveness;
@@ -43,16 +44,17 @@
       InvokeKind kind = targetExtractor.getKind();
       if (target != null && target.getArity() == method.method.getArity()) {
         assert !method.accessFlags.isPrivate() && !method.accessFlags.isConstructor();
-        target = lense.lookupMethod(target, method);
         if (kind == InvokeKind.STATIC) {
           assert method.accessFlags.isStatic();
-          DexEncodedMethod targetMethod = appInfo.lookupStaticTarget(target);
+          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.
-          DexEncodedMethod targetMethod = appInfo.lookupSingleVirtualTarget(target);
+          DexMethod actualTarget = lense.lookupMethod(target, method, Type.VIRTUAL);
+          DexEncodedMethod targetMethod = appInfo.lookupSingleVirtualTarget(actualTarget);
           if (targetMethod != null) {
             addForwarding(method, targetMethod);
           }
@@ -91,17 +93,15 @@
     }
 
     @Override
-    public DexMethod lookupMethod(DexMethod method, DexEncodedMethod context) {
-      DexMethod previous = previousLense.lookupMethod(method, context);
+    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)) {
+      if (bridge == null || (context.accessFlags.isBridge() && bridge == context.method)) {
         return previous;
-      } else {
-        return bridge;
       }
+      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 43b45b4..fc408e3 100644
--- a/src/main/java/com/android/tools/r8/optimize/MemberRebindingAnalysis.java
+++ b/src/main/java/com/android/tools/r8/optimize/MemberRebindingAnalysis.java
@@ -115,7 +115,7 @@
   private void computeMethodRebinding(Set<DexMethod> methods,
       Function<DexMethod, DexEncodedMethod> lookupTarget) {
     for (DexMethod method : methods) {
-      method = lense.lookupMethod(method, null);
+      method = lense.lookupMethod(method);
       // We can safely ignore array types, as the corresponding methods are defined in a library.
       if (!method.getHolder().isClassType()) {
         continue;
diff --git a/src/main/java/com/android/tools/r8/shaking/Enqueuer.java b/src/main/java/com/android/tools/r8/shaking/Enqueuer.java
index 9387e3e..2b51720 100644
--- a/src/main/java/com/android/tools/r8/shaking/Enqueuer.java
+++ b/src/main/java/com/android/tools/r8/shaking/Enqueuer.java
@@ -1638,8 +1638,8 @@
       this.liveTypes = rewriteItems(previous.liveTypes, lense::lookupType);
       this.instantiatedTypes = rewriteItems(previous.instantiatedTypes, lense::lookupType);
       this.instantiatedLambdas = rewriteItems(previous.instantiatedLambdas, lense::lookupType);
-      this.targetedMethods = rewriteItems(previous.targetedMethods, lense::lookupMethod);
-      this.liveMethods = rewriteItems(previous.liveMethods, lense::lookupMethod);
+      this.targetedMethods = rewriteMethodsConservatively(previous.targetedMethods, lense);
+      this.liveMethods = rewriteMethodsConservatively(previous.liveMethods, lense);
       this.liveFields = rewriteItems(previous.liveFields, lense::lookupField);
       this.instanceFieldReads =
           rewriteKeysWhileMergingValues(previous.instanceFieldReads, lense::lookupField);
@@ -1652,11 +1652,11 @@
       this.fieldsRead = rewriteItems(previous.fieldsRead, lense::lookupField);
       this.fieldsWritten = rewriteItems(previous.fieldsWritten, lense::lookupField);
       this.pinnedItems = rewriteMixedItems(previous.pinnedItems, lense);
-      this.virtualInvokes = rewriteItems(previous.virtualInvokes, lense::lookupMethod);
-      this.interfaceInvokes = rewriteItems(previous.interfaceInvokes, lense::lookupMethod);
-      this.superInvokes = rewriteItems(previous.superInvokes, lense::lookupMethod);
-      this.directInvokes = rewriteItems(previous.directInvokes, lense::lookupMethod);
-      this.staticInvokes = rewriteItems(previous.staticInvokes, lense::lookupMethod);
+      this.virtualInvokes = rewriteMethodsConservatively(previous.virtualInvokes, lense);
+      this.interfaceInvokes = rewriteMethodsConservatively(previous.interfaceInvokes, lense);
+      this.superInvokes = rewriteMethodsConservatively(previous.superInvokes, lense);
+      this.directInvokes = rewriteMethodsConservatively(previous.directInvokes, lense);
+      this.staticInvokes = rewriteMethodsConservatively(previous.staticInvokes, lense);
       this.prunedTypes = rewriteItems(previous.prunedTypes, lense::lookupType);
       // TODO(herhut): Migrate these to Descriptors, as well.
       assert assertNotModifiedByLense(previous.noSideEffects.keySet(), lense);
@@ -1744,7 +1744,7 @@
           // We only allow changes to bridge methods, as these get retargeted even if they
           // are kept.
           assert method.accessFlags.isBridge()
-              || lense.lookupMethod(method.method, null) == method.method;
+              || lense.lookupMethod(method.method) == method.method;
         } else if (item instanceof DexEncodedField) {
           DexField field = ((DexEncodedField) item).field;
           assert lense.lookupField(field) == field;
@@ -1792,6 +1792,29 @@
       return builder.build();
     }
 
+    private static ImmutableSortedSet<DexMethod> rewriteMethodsConservatively(
+        Set<DexMethod> original, GraphLense lense) {
+      ImmutableSortedSet.Builder<DexMethod> builder =
+          new ImmutableSortedSet.Builder<>(PresortedComparable::slowCompare);
+      if (lense.isContextFreeForMethods()) {
+        for (DexMethod item : original) {
+          builder.add(lense.lookupMethod(item));
+        }
+      } else {
+        for (DexMethod item : original) {
+          // Avoid using lookupMethodInAllContexts when possible.
+          if (lense.isContextFreeForMethod(item)) {
+            builder.add(lense.lookupMethod(item));
+          } else {
+            // The lense is context sensitive, but we do not have the context here. Therefore, we
+            // conservatively look up the method in all contexts.
+            builder.addAll(lense.lookupMethodInAllContexts(item));
+          }
+        }
+      }
+      return builder.build();
+    }
+
     private static <T extends PresortedComparable<T>> ImmutableSortedSet<T> rewriteItems(
         Set<T> original, Function<T, T> rewrite) {
       ImmutableSortedSet.Builder<T> builder =
@@ -1892,7 +1915,6 @@
 
     public AppInfoWithLiveness rewrittenWithLense(DirectMappedDexApplication application,
         GraphLense lense) {
-      assert lense.isContextFreeForMethods();
       return new AppInfoWithLiveness(this, application, lense);
     }
 
diff --git a/src/main/java/com/android/tools/r8/shaking/SimpleClassMerger.java b/src/main/java/com/android/tools/r8/shaking/SimpleClassMerger.java
index a80e758..c9d34ee 100644
--- a/src/main/java/com/android/tools/r8/shaking/SimpleClassMerger.java
+++ b/src/main/java/com/android/tools/r8/shaking/SimpleClassMerger.java
@@ -181,7 +181,8 @@
     if (Log.ENABLED) {
       Log.debug(getClass(), "Merged %d classes.", numberOfMerges);
     }
-    return renamedMembersLense.build(application.dexItemFactory, graphLense);
+    return new VerticalClassMergerGraphLense(
+        renamedMembersLense.build(application.dexItemFactory, graphLense));
   }
 
   private class ClassMerger {
diff --git a/src/main/java/com/android/tools/r8/shaking/VerticalClassMergerGraphLense.java b/src/main/java/com/android/tools/r8/shaking/VerticalClassMergerGraphLense.java
new file mode 100644
index 0000000..1bdeb7c
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/shaking/VerticalClassMergerGraphLense.java
@@ -0,0 +1,85 @@
+// 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.shaking;
+
+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.ir.code.Invoke.Type;
+import com.google.common.collect.ImmutableSet;
+import java.util.Set;
+
+// This graph lense is instantiated during vertical class merging. The graph lense is context
+// sensitive in the enclosing class of a given invoke *and* the type of the invoke (e.g., invoke-
+// super vs invoke-virtual). This is illustrated by the following example.
+//
+// public class A {
+//   public void m() { ... }
+// }
+// public class B extends A {
+//   @Override
+//   public void m() { invoke-super A.m(); ... }
+//
+//   public void m2() { invoke-virtual A.m(); ... }
+// }
+//
+// Vertical class merging will merge class A into class B. Since class B already has a method with
+// the signature "void B.m()", the method A.m will be given a fresh name and moved to class B.
+// During this process, the method corresponding to A.m will be made private such that it can be
+// called via an invoke-direct instruction.
+//
+// For the invocation "invoke-super A.m()" in B.m, this graph lense will return the newly created,
+// private method corresponding to A.m (that is now in B.m with a fresh name), such that the
+// 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 GraphLense {
+  private final GraphLense previousLense;
+
+  public VerticalClassMergerGraphLense(GraphLense previousLense) {
+    this.previousLense = previousLense;
+  }
+
+  @Override
+  public DexType lookupType(DexType type) {
+    return previousLense.lookupType(type);
+  }
+
+  @Override
+  public DexMethod lookupMethod(DexMethod method, DexEncodedMethod context, Type type) {
+    // TODO(christofferqa): If [type] is Type.SUPER and [method] has been merged into the class of
+    // [context], then return the DIRECT method that has been created for [method] by SimpleClass-
+    // Merger. Otherwise, return the VIRTUAL method corresponding to [method].
+    return previousLense.lookupMethod(method, context, type);
+  }
+
+  @Override
+  public Set<DexMethod> lookupMethodInAllContexts(DexMethod method) {
+    DexMethod result = lookupMethod(method);
+    if (result != null) {
+      return ImmutableSet.of(result);
+    }
+    return ImmutableSet.of();
+  }
+
+  @Override
+  public DexField lookupField(DexField field) {
+    return previousLense.lookupField(field);
+  }
+
+  @Override
+  public boolean isContextFreeForMethods() {
+    return false;
+  }
+
+  @Override
+  public boolean isContextFreeForMethod(DexMethod method) {
+    // TODO(christofferqa): Should return false for methods where this graph lense is context
+    // sensitive.
+    return true;
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/classmerging/ClassMergingTest.java b/src/test/java/com/android/tools/r8/classmerging/ClassMergingTest.java
index cf356df..65181dc 100644
--- a/src/test/java/com/android/tools/r8/classmerging/ClassMergingTest.java
+++ b/src/test/java/com/android/tools/r8/classmerging/ClassMergingTest.java
@@ -36,6 +36,9 @@
 import org.junit.Ignore;
 import org.junit.Test;
 
+// TODO(christofferqa): Add tests to check that statically typed invocations on method handles
+// continue to work after class merging. Rewriting of method handles should be carried out by
+// LensCodeRewriter.rewriteDexMethodHandle.
 public class ClassMergingTest extends TestBase {
 
   private static final Path CF_DIR =