Replace ResolutionResult hasSingleTarget() by isSingleResolution().

This CL also moves the implementation of existing lookup methods on
ResolutionResult to the SingleResolutionResult class.

Change-Id: Ibff85c3f2aee4405c9e83bd5f9db9f736b55d963
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 bd71028..bdea265 100644
--- a/src/main/java/com/android/tools/r8/graph/ResolutionResult.java
+++ b/src/main/java/com/android/tools/r8/graph/ResolutionResult.java
@@ -47,14 +47,11 @@
     return null;
   }
 
+  /** Short-hand to get the single resolution method if resolution finds it, null otherwise. */
   public final DexEncodedMethod getSingleTarget() {
     return isSingleResolution() ? asSingleResolution().getResolvedMethod() : null;
   }
 
-  public final boolean hasSingleTarget() {
-    return isSingleResolution();
-  }
-
   public abstract boolean isAccessibleFrom(DexProgramClass context, AppInfoWithSubtyping appInfo);
 
   public abstract boolean isAccessibleForVirtualDispatchFrom(
@@ -64,114 +61,19 @@
 
   public abstract boolean isValidVirtualTargetForDynamicDispatch();
 
-  public DexEncodedMethod lookupInvokeSuperTarget(DexType context, AppInfo appInfo) {
-    return null;
-  }
+  /** Lookup the single target of an invoke-super on this resolution result if possible. */
+  public abstract DexEncodedMethod lookupInvokeSuperTarget(DexType context, AppInfo appInfo);
 
-  public Set<DexEncodedMethod> lookupVirtualDispatchTargets(
+  public final Set<DexEncodedMethod> lookupVirtualDispatchTargets(
       boolean isInterface, AppInfoWithSubtyping appInfo) {
     return isInterface ? lookupInterfaceTargets(appInfo) : lookupVirtualTargets(appInfo);
   }
 
-  // TODO(b/140204899): Leverage refined receiver type if available.
-  public Set<DexEncodedMethod> lookupVirtualTargets(AppInfoWithSubtyping appInfo) {
-    assert isValidVirtualTarget(appInfo.app().options);
-    // First add the target for receiver type method.type.
-    DexEncodedMethod encodedMethod = getSingleTarget();
-    Set<DexEncodedMethod> result = SetUtils.newIdentityHashSet(encodedMethod);
-    // Add all matching targets from the subclass hierarchy.
-    DexMethod method = encodedMethod.method;
-    // TODO(b/140204899): Instead of subtypes of holder, we could iterate subtypes of refined
-    //   receiver type if available.
-    for (DexType type : appInfo.subtypes(method.holder)) {
-      DexClass clazz = appInfo.definitionFor(type);
-      if (!clazz.isInterface()) {
-        ResolutionResult methods = appInfo.resolveMethodOnClass(clazz, method);
-        DexEncodedMethod target = methods.getSingleTarget();
-        if (target != null && target.isVirtualMethod()) {
-          result.add(target);
-        }
-      }
-    }
-    return result;
-  }
+  public abstract Set<DexEncodedMethod> lookupVirtualTargets(AppInfoWithSubtyping appInfo);
 
-  // TODO(b/140204899): Leverage refined receiver type if available.
-  public Set<DexEncodedMethod> lookupInterfaceTargets(AppInfoWithSubtyping appInfo) {
-    assert isValidVirtualTarget(appInfo.app().options);
-    Set<DexEncodedMethod> result = Sets.newIdentityHashSet();
-    if (hasSingleTarget()) {
-      // Add default interface methods to the list of targets.
-      //
-      // This helps to make sure we take into account synthesized lambda classes
-      // that we are not aware of. Like in the following example, we know that all
-      // classes, XX in this case, override B::bar(), but there are also synthesized
-      // classes for lambda which don't, so we still need default method to be live.
-      //
-      //   public static void main(String[] args) {
-      //     X x = () -> {};
-      //     x.bar();
-      //   }
-      //
-      //   interface X {
-      //     void foo();
-      //     default void bar() { }
-      //   }
-      //
-      //   class XX implements X {
-      //     public void foo() { }
-      //     public void bar() { }
-      //   }
-      //
-      DexEncodedMethod singleTarget = getSingleTarget();
-      if (singleTarget.hasCode()) {
-        DexProgramClass holder =
-            asProgramClassOrNull(appInfo.definitionFor(singleTarget.method.holder));
-        if (appInfo.hasAnyInstantiatedLambdas(holder)) {
-          result.add(singleTarget);
-        }
-      }
-    }
+  public abstract Set<DexEncodedMethod> lookupInterfaceTargets(AppInfoWithSubtyping appInfo);
 
-    DexEncodedMethod encodedMethod = getSingleTarget();
-    DexMethod method = encodedMethod.method;
-    Consumer<DexEncodedMethod> addIfNotAbstract =
-        m -> {
-          if (!m.accessFlags.isAbstract()) {
-            result.add(m);
-          }
-        };
-    // Default methods are looked up when looking at a specific subtype that does not override
-    // them.
-    // Otherwise, we would look up default methods that are actually never used. However, we have
-    // to
-    // add bridge methods, otherwise we can remove a bridge that will be used.
-    Consumer<DexEncodedMethod> addIfNotAbstractAndBridge =
-        m -> {
-          if (!m.accessFlags.isAbstract() && m.accessFlags.isBridge()) {
-            result.add(m);
-          }
-        };
-
-    // TODO(b/140204899): Instead of subtypes of holder, we could iterate subtypes of refined
-    //   receiver type if available.
-    for (DexType type : appInfo.subtypes(method.holder)) {
-      DexClass clazz = appInfo.definitionFor(type);
-      if (clazz.isInterface()) {
-        ResolutionResult targetMethods = appInfo.resolveMethodOnInterface(clazz, method);
-        if (targetMethods.hasSingleTarget()) {
-          addIfNotAbstractAndBridge.accept(targetMethods.getSingleTarget());
-        }
-      } else {
-        ResolutionResult targetMethods = appInfo.resolveMethodOnClass(clazz, method);
-        if (targetMethods.hasSingleTarget()) {
-          addIfNotAbstract.accept(targetMethods.getSingleTarget());
-        }
-      }
-    }
-    return result;
-  }
-
+  /** Result for a resolution that succeeds with a known declaration/definition. */
   public static class SingleResolutionResult extends ResolutionResult {
     private final DexClass initialResolutionHolder;
     private final DexClass resolvedHolder;
@@ -297,21 +199,128 @@
           ? resolution.resolvedMethod
           : null;
     }
-  }
-
-  public abstract static class EmptyResult extends ResolutionResult {
 
     @Override
+    // TODO(b/140204899): Leverage refined receiver type if available.
     public Set<DexEncodedMethod> lookupVirtualTargets(AppInfoWithSubtyping appInfo) {
+      assert isValidVirtualTarget(appInfo.app().options);
+      // First add the target for receiver type method.type.
+      DexEncodedMethod encodedMethod = getSingleTarget();
+      Set<DexEncodedMethod> result = SetUtils.newIdentityHashSet(encodedMethod);
+      // Add all matching targets from the subclass hierarchy.
+      DexMethod method = encodedMethod.method;
+      // TODO(b/140204899): Instead of subtypes of holder, we could iterate subtypes of refined
+      //   receiver type if available.
+      for (DexType type : appInfo.subtypes(method.holder)) {
+        DexClass clazz = appInfo.definitionFor(type);
+        if (!clazz.isInterface()) {
+          ResolutionResult methods = appInfo.resolveMethodOnClass(clazz, method);
+          DexEncodedMethod target = methods.getSingleTarget();
+          if (target != null && target.isVirtualMethod()) {
+            result.add(target);
+          }
+        }
+      }
+      return result;
+    }
+
+    @Override
+    // TODO(b/140204899): Leverage refined receiver type if available.
+    public Set<DexEncodedMethod> lookupInterfaceTargets(AppInfoWithSubtyping appInfo) {
+      assert isValidVirtualTarget(appInfo.app().options);
+      Set<DexEncodedMethod> result = Sets.newIdentityHashSet();
+      if (isSingleResolution()) {
+        // Add default interface methods to the list of targets.
+        //
+        // This helps to make sure we take into account synthesized lambda classes
+        // that we are not aware of. Like in the following example, we know that all
+        // classes, XX in this case, override B::bar(), but there are also synthesized
+        // classes for lambda which don't, so we still need default method to be live.
+        //
+        //   public static void main(String[] args) {
+        //     X x = () -> {};
+        //     x.bar();
+        //   }
+        //
+        //   interface X {
+        //     void foo();
+        //     default void bar() { }
+        //   }
+        //
+        //   class XX implements X {
+        //     public void foo() { }
+        //     public void bar() { }
+        //   }
+        //
+        DexEncodedMethod singleTarget = getSingleTarget();
+        if (singleTarget.hasCode()) {
+          DexProgramClass holder =
+              asProgramClassOrNull(appInfo.definitionFor(singleTarget.method.holder));
+          if (appInfo.hasAnyInstantiatedLambdas(holder)) {
+            result.add(singleTarget);
+          }
+        }
+      }
+
+      DexEncodedMethod encodedMethod = getSingleTarget();
+      DexMethod method = encodedMethod.method;
+      Consumer<DexEncodedMethod> addIfNotAbstract =
+          m -> {
+            if (!m.accessFlags.isAbstract()) {
+              result.add(m);
+            }
+          };
+      // Default methods are looked up when looking at a specific subtype that does not override
+      // them.
+      // Otherwise, we would look up default methods that are actually never used. However, we have
+      // to
+      // add bridge methods, otherwise we can remove a bridge that will be used.
+      Consumer<DexEncodedMethod> addIfNotAbstractAndBridge =
+          m -> {
+            if (!m.accessFlags.isAbstract() && m.accessFlags.isBridge()) {
+              result.add(m);
+            }
+          };
+
+      // TODO(b/140204899): Instead of subtypes of holder, we could iterate subtypes of refined
+      //   receiver type if available.
+      for (DexType type : appInfo.subtypes(method.holder)) {
+        DexClass clazz = appInfo.definitionFor(type);
+        if (clazz.isInterface()) {
+          ResolutionResult targetMethods = appInfo.resolveMethodOnInterface(clazz, method);
+          if (targetMethods.isSingleResolution()) {
+            addIfNotAbstractAndBridge.accept(targetMethods.getSingleTarget());
+          }
+        } else {
+          ResolutionResult targetMethods = appInfo.resolveMethodOnClass(clazz, method);
+          if (targetMethods.isSingleResolution()) {
+            addIfNotAbstract.accept(targetMethods.getSingleTarget());
+          }
+        }
+      }
+      return result;
+    }
+  }
+
+  abstract static class EmptyResult extends ResolutionResult {
+
+    @Override
+    public final DexEncodedMethod lookupInvokeSuperTarget(DexType context, AppInfo appInfo) {
       return null;
     }
 
     @Override
-    public Set<DexEncodedMethod> lookupInterfaceTargets(AppInfoWithSubtyping appInfo) {
+    public final Set<DexEncodedMethod> lookupVirtualTargets(AppInfoWithSubtyping appInfo) {
+      return null;
+    }
+
+    @Override
+    public final Set<DexEncodedMethod> lookupInterfaceTargets(AppInfoWithSubtyping appInfo) {
       return null;
     }
   }
 
+  /** Singleton result for the special case resolving the array clone() method. */
   public static class ArrayCloneMethodResult extends EmptyResult {
 
     static final ArrayCloneMethodResult INSTANCE = new ArrayCloneMethodResult();
@@ -342,6 +351,7 @@
     }
   }
 
+  /** Base class for all types of failed resolutions. */
   public abstract static class FailedResolutionResult extends EmptyResult {
 
     @Override
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 e5ace97..1049fb8 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
@@ -336,7 +336,7 @@
       DexMethod method = instruction.getInvokedMethod();
       ResolutionResult resolutionResult =
           appView.appInfo().resolveMethodOnInterface(method.holder, method);
-      if (!resolutionResult.hasSingleTarget()) {
+      if (!resolutionResult.isSingleResolution()) {
         return false;
       }
       DexType holder = resolutionResult.getSingleTarget().method.holder;
@@ -394,7 +394,7 @@
       }
       ResolutionResult resolutionResult =
           appView.appInfo().resolveMethod(superType, method, instruction.itf);
-      if (!resolutionResult.hasSingleTarget()) {
+      if (!resolutionResult.isSingleResolution()) {
         return false;
       }
       DexType holder = resolutionResult.getSingleTarget().method.holder;
@@ -430,7 +430,7 @@
       DexMethod method = instruction.getInvokedMethod();
       ResolutionResult resolutionResult =
           appView.appInfo().resolveMethodOnClass(method.holder, method);
-      if (!resolutionResult.hasSingleTarget()) {
+      if (!resolutionResult.isSingleResolution()) {
         return false;
       }
       DexType holder = resolutionResult.getSingleTarget().method.holder;
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 484c4e3..e7d2ab1 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
@@ -110,7 +110,7 @@
     if (refinedReceiverType != staticReceiverType) {
       ResolutionResult refinedResolution =
           appView.appInfo().resolveMethod(refinedReceiverType, method);
-      if (refinedResolution.hasSingleTarget()) {
+      if (refinedResolution.isSingleResolution()) {
         DexEncodedMethod refinedTarget = refinedResolution.getSingleTarget();
         Set<DexEncodedMethod> result = Sets.newIdentityHashSet();
         for (DexEncodedMethod target : targets) {
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 d9d65e5..407742e 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
@@ -175,7 +175,7 @@
     DexItemFactory dexItemFactory = appView.dexItemFactory();
     ResolutionResult finalizeResolutionResult =
         appView.appInfo().resolveMethod(clazz, dexItemFactory.objectMethods.finalize);
-    if (finalizeResolutionResult.hasSingleTarget()) {
+    if (finalizeResolutionResult.isSingleResolution()) {
       DexMethod finalizeMethod = finalizeResolutionResult.getSingleTarget().method;
       if (finalizeMethod != dexItemFactory.enumMethods.finalize
           && finalizeMethod != dexItemFactory.objectMethods.finalize) {
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 49b11a5..e83a17a 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
@@ -93,7 +93,7 @@
           }
           // If the resolution ended up with a single target, check if it is a library override.
           // And if so, bail out early (to avoid expensive target lookup).
-          if (resolutionResult.hasSingleTarget()
+          if (resolutionResult.isSingleResolution()
               && isLibraryMethodOrLibraryMethodOverride(resolutionResult.getSingleTarget())) {
             continue;
           }
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 582de88..f420071 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
@@ -851,7 +851,7 @@
     // We should not inline a method if the invocation has type interface or virtual and the
     // signature of the invocation resolves to a private or static method.
     ResolutionResult resolutionResult = appView.appInfo().resolveMethod(callee.holder, callee);
-    if (resolutionResult.hasSingleTarget()
+    if (resolutionResult.isSingleResolution()
         && !resolutionResult.getSingleTarget().isVirtualMethod()) {
       return null;
     }
diff --git a/src/main/java/com/android/tools/r8/naming/MinifiedRenaming.java b/src/main/java/com/android/tools/r8/naming/MinifiedRenaming.java
index 7d93488..8ea3d93 100644
--- a/src/main/java/com/android/tools/r8/naming/MinifiedRenaming.java
+++ b/src/main/java/com/android/tools/r8/naming/MinifiedRenaming.java
@@ -100,7 +100,7 @@
     }
     // If the method does not have a direct renaming, return the resolutions mapping.
     ResolutionResult resolutionResult = appView.appInfo().resolveMethod(method.holder, method);
-    if (resolutionResult.hasSingleTarget()) {
+    if (resolutionResult.isSingleResolution()) {
       return renaming.getOrDefault(resolutionResult.getSingleTarget().method, method.name);
     }
     // If resolution fails, the method must be renamed consistently with the targets that give rise
diff --git a/src/main/java/com/android/tools/r8/shaking/AppInfoWithLiveness.java b/src/main/java/com/android/tools/r8/shaking/AppInfoWithLiveness.java
index e2641d1..347ae0f 100644
--- a/src/main/java/com/android/tools/r8/shaking/AppInfoWithLiveness.java
+++ b/src/main/java/com/android/tools/r8/shaking/AppInfoWithLiveness.java
@@ -887,7 +887,7 @@
     // overrides the kept method.
     if (isPinned(clazz.type)) {
       ResolutionResult resolutionResult = resolveMethod(clazz, method);
-      if (resolutionResult.hasSingleTarget()) {
+      if (resolutionResult.isSingleResolution()) {
         DexEncodedMethod resolutionTarget = resolutionResult.getSingleTarget();
         return !resolutionTarget.isProgramMethod(this)
             || resolutionTarget.isLibraryMethodOverride().isPossiblyTrue()
@@ -1041,10 +1041,10 @@
     // from the runtime type of the receiver.
     if (receiverLowerBoundType != null) {
       if (receiverLowerBoundType.getClassType() == refinedReceiverType) {
-        if (resolutionResult.hasSingleTarget()
+        if (resolutionResult.isSingleResolution()
             && resolutionResult.isValidVirtualTargetForDynamicDispatch()) {
           ResolutionResult refinedResolutionResult = resolveMethod(refinedReceiverType, method);
-          if (refinedResolutionResult.hasSingleTarget()
+          if (refinedResolutionResult.isSingleResolution()
               && refinedResolutionResult.isValidVirtualTargetForDynamicDispatch()) {
             return validateSingleVirtualTarget(
                 refinedResolutionResult.getSingleTarget(), resolutionResult.getSingleTarget());
@@ -1083,7 +1083,7 @@
     // First get the target for the holder type.
     ResolutionResult topMethod = resolveMethodOnClass(holder, method);
     // We might hit none or multiple targets. Both make this fail at runtime.
-    if (!topMethod.hasSingleTarget() || !topMethod.isValidVirtualTarget(options())) {
+    if (!topMethod.isSingleResolution() || !topMethod.isValidVirtualTarget(options())) {
       method.setSingleVirtualMethodCache(refinedReceiverType, null);
       return null;
     }
@@ -1225,10 +1225,10 @@
     if (receiverLowerBoundType != null) {
       if (receiverLowerBoundType.getClassType() == refinedReceiverType) {
         ResolutionResult resolutionResult = resolveMethod(method.holder, method, true);
-        if (resolutionResult.hasSingleTarget()
+        if (resolutionResult.isSingleResolution()
             && resolutionResult.isValidVirtualTargetForDynamicDispatch()) {
           ResolutionResult refinedResolutionResult = resolveMethod(refinedReceiverType, method);
-          if (refinedResolutionResult.hasSingleTarget()
+          if (refinedResolutionResult.isSingleResolution()
               && refinedResolutionResult.isValidVirtualTargetForDynamicDispatch()) {
             return validateSingleVirtualTarget(
                 refinedResolutionResult.getSingleTarget(), resolutionResult.getSingleTarget());
diff --git a/src/main/java/com/android/tools/r8/shaking/RootSetBuilder.java b/src/main/java/com/android/tools/r8/shaking/RootSetBuilder.java
index fee13db..24b5004 100644
--- a/src/main/java/com/android/tools/r8/shaking/RootSetBuilder.java
+++ b/src/main/java/com/android/tools/r8/shaking/RootSetBuilder.java
@@ -533,7 +533,7 @@
       ResolutionResult resolutionResult =
           appView.appInfo().resolveMethod(originalClazz, method.method);
       if (!resolutionResult.isValidVirtualTarget(appView.options())
-          || !resolutionResult.hasSingleTarget()) {
+          || !resolutionResult.isSingleResolution()) {
         return;
       }
       DexEncodedMethod methodToKeep = resolutionResult.getSingleTarget();
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 9f4b3e3..77de8e2 100644
--- a/src/main/java/com/android/tools/r8/shaking/VerticalClassMerger.java
+++ b/src/main/java/com/android/tools/r8/shaking/VerticalClassMerger.java
@@ -1244,7 +1244,7 @@
     // Returns the method that shadows the given method, or null if method is not shadowed.
     private DexEncodedMethod findMethodInTarget(DexEncodedMethod method) {
       ResolutionResult resolutionResult = appInfo.resolveMethod(target, method.method);
-      if (!resolutionResult.hasSingleTarget()) {
+      if (!resolutionResult.isSingleResolution()) {
         // May happen in case of missing classes, or if multiple implementations were found.
         abortMerge = true;
         return null;