Check for instantiated class when passing in lower bound

Change-Id: Iacf1c9c71f43b918d84c7936129a814d82c673cd
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 f9cea40..5c1611d 100644
--- a/src/main/java/com/android/tools/r8/graph/ResolutionResult.java
+++ b/src/main/java/com/android/tools/r8/graph/ResolutionResult.java
@@ -410,14 +410,11 @@
       // TODO(b/148769279): Remove the check for hasInstantiatedLambdas.
       Box<Boolean> hasInstantiatedLambdas = new Box<>(false);
       InstantiatedSubTypeInfo instantiatedSubTypeInfo =
-          refinedReceiverLowerBound == null
-              ? instantiatedSubTypeInfoWithoutLowerBound(
-                  appInfo, refinedReceiverUpperBound, hasInstantiatedLambdas)
-              : instantiatedSubTypeInfoWithLowerBound(
-                  appInfo,
-                  refinedReceiverUpperBound,
-                  refinedReceiverLowerBound,
-                  hasInstantiatedLambdas);
+          instantiatedSubTypeInfoForInstantiatedType(
+              appInfo,
+              refinedReceiverUpperBound,
+              refinedReceiverLowerBound,
+              hasInstantiatedLambdas);
       LookupResult lookupResult =
           lookupVirtualDispatchTargets(
               context,
@@ -430,37 +427,28 @@
       return lookupResult;
     }
 
-    private InstantiatedSubTypeInfo instantiatedSubTypeInfoWithoutLowerBound(
-        AppInfoWithLiveness appInfo,
-        DexProgramClass refinedReceiverUpperBound,
-        Box<Boolean> hasInstantiatedLambdas) {
-      return (type, subTypeConsumer, callSiteConsumer) -> {
-        appInfo.forEachInstantiatedSubType(
-            refinedReceiverUpperBound.type,
-            subType -> {
-              if (appInfo.hasAnyInstantiatedLambdas(subType)) {
-                hasInstantiatedLambdas.set(true);
-              }
-              subTypeConsumer.accept(subType);
-            },
-            callSiteConsumer);
-      };
-    }
-
-    private InstantiatedSubTypeInfo instantiatedSubTypeInfoWithLowerBound(
+    private InstantiatedSubTypeInfo instantiatedSubTypeInfoForInstantiatedType(
         AppInfoWithLiveness appInfo,
         DexProgramClass refinedReceiverUpperBound,
         DexProgramClass refinedReceiverLowerBound,
         Box<Boolean> hasInstantiatedLambdas) {
-      return (type, subTypeConsumer, callSiteConsumer) -> {
-        List<DexProgramClass> subTypes =
-            appInfo.computeProgramClassRelationChain(
-                refinedReceiverLowerBound, refinedReceiverUpperBound);
-        for (DexProgramClass subType : subTypes) {
-          if (appInfo.hasAnyInstantiatedLambdas(subType)) {
-            hasInstantiatedLambdas.set(true);
-          }
-          subTypeConsumer.accept(subType);
+      return (ignored, subTypeConsumer, callSiteConsumer) -> {
+        Consumer<DexProgramClass> lambdaInstantiatedConsumer =
+            subType -> {
+              subTypeConsumer.accept(subType);
+              if (appInfo.hasAnyInstantiatedLambdas(subType)) {
+                hasInstantiatedLambdas.set(true);
+              }
+            };
+        if (refinedReceiverLowerBound == null) {
+          appInfo.forEachInstantiatedSubType(
+              refinedReceiverUpperBound.type, lambdaInstantiatedConsumer, callSiteConsumer);
+        } else {
+          appInfo.forEachInstantiatedSubTypeInChain(
+              refinedReceiverUpperBound,
+              refinedReceiverLowerBound,
+              lambdaInstantiatedConsumer,
+              callSiteConsumer);
         }
       };
     }
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 4ec2517..eef80a6 100644
--- a/src/main/java/com/android/tools/r8/shaking/AppInfoWithLiveness.java
+++ b/src/main/java/com/android/tools/r8/shaking/AppInfoWithLiveness.java
@@ -1364,14 +1364,32 @@
       if (clazz == null) {
         continue;
       }
-      if (isInstantiatedDirectly(clazz)
-          || isPinned(clazz.type)
-          || hasAnyInstantiatedLambdas(clazz)) {
+      if (isInstantiatedOrPinned(clazz)) {
         subTypeConsumer.accept(clazz);
       }
     }
   }
 
+  public void forEachInstantiatedSubTypeInChain(
+      DexProgramClass refinedReceiverUpperBound,
+      DexProgramClass refinedReceiverLowerBound,
+      Consumer<DexProgramClass> subTypeConsumer,
+      Consumer<LambdaDescriptor> callSiteConsumer) {
+    List<DexProgramClass> subTypes =
+        computeProgramClassRelationChain(refinedReceiverLowerBound, refinedReceiverUpperBound);
+    for (DexProgramClass subType : subTypes) {
+      if (isInstantiatedOrPinned(subType)) {
+        subTypeConsumer.accept(subType);
+      }
+    }
+  }
+
+  private boolean isInstantiatedOrPinned(DexProgramClass clazz) {
+    return isInstantiatedDirectly(clazz)
+        || isPinned(clazz.type)
+        || hasAnyInstantiatedLambdas(clazz);
+  }
+
   public boolean isPinnedNotProgramOrLibraryOverride(DexReference reference) {
     if (isPinned(reference)) {
       return true;
diff --git a/src/test/java/com/android/tools/r8/classmerging/KeptTargetsIncompleteDiamondTest.java b/src/test/java/com/android/tools/r8/classmerging/KeptTargetsIncompleteDiamondTest.java
index 6f2e729..005df04 100644
--- a/src/test/java/com/android/tools/r8/classmerging/KeptTargetsIncompleteDiamondTest.java
+++ b/src/test/java/com/android/tools/r8/classmerging/KeptTargetsIncompleteDiamondTest.java
@@ -44,11 +44,12 @@
   private AppView<AppInfoWithLiveness> computeAppViewWithLiveness(
       Class<?> methodToBeKept, Class<?> classToBeKept) throws Exception {
     return computeAppViewWithLiveness(
-        buildClasses(I.class, J.class, K.class, L.class, A.class).build(),
+        buildClasses(I.class, J.class, K.class, L.class, A.class, Main.class).build(),
         factory -> {
           List<ProguardConfigurationRule> rules = new ArrayList<>();
           rules.addAll(buildKeepRuleForClassAndMethods(methodToBeKept, factory));
           rules.addAll(buildKeepRuleForClass(classToBeKept, factory));
+          rules.addAll(buildKeepRuleForClassAndMethods(Main.class, factory));
           return rules;
         });
   }
@@ -265,4 +266,11 @@
   public interface L extends I, K {}
 
   public static class A implements L {}
+
+  public static class Main {
+
+    public static void main(String[] args) {
+      new A();
+    }
+  }
 }
diff --git a/src/test/java/com/android/tools/r8/resolution/virtualtargets/InstantiatedLowerBoundTest.java b/src/test/java/com/android/tools/r8/resolution/virtualtargets/InstantiatedLowerBoundTest.java
new file mode 100644
index 0000000..6001370
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/resolution/virtualtargets/InstantiatedLowerBoundTest.java
@@ -0,0 +1,165 @@
+// Copyright (c) 2020, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+package com.android.tools.r8.resolution.virtualtargets;
+
+import static junit.framework.TestCase.assertEquals;
+import static junit.framework.TestCase.assertNotNull;
+import static junit.framework.TestCase.assertTrue;
+import static org.junit.Assert.assertNull;
+
+import com.android.tools.r8.TestBase;
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.TestParametersCollection;
+import com.android.tools.r8.graph.AppView;
+import com.android.tools.r8.graph.DexEncodedMethod;
+import com.android.tools.r8.graph.DexMethod;
+import com.android.tools.r8.graph.DexProgramClass;
+import com.android.tools.r8.graph.DexType;
+import com.android.tools.r8.graph.LookupResult;
+import com.android.tools.r8.graph.ResolutionResult;
+import com.android.tools.r8.ir.analysis.type.ClassTypeLatticeElement;
+import com.android.tools.r8.ir.analysis.type.Nullability;
+import com.android.tools.r8.shaking.AppInfoWithLiveness;
+import com.google.common.collect.Sets;
+import java.util.ArrayList;
+import java.util.Set;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+import org.junit.runners.Parameterized.Parameters;
+
+@RunWith(Parameterized.class)
+public class InstantiatedLowerBoundTest extends TestBase {
+
+  @Parameters(name = "{0}")
+  public static TestParametersCollection data() {
+    return getTestParameters().withNoneRuntime().build();
+  }
+
+  public InstantiatedLowerBoundTest(TestParameters parameters) {
+    // Empty to satisfy construction of none-runtime.
+  }
+
+  @Test
+  public void testSingleTargetLowerBoundInstantiated() throws Exception {
+    AppView<AppInfoWithLiveness> appView =
+        computeAppViewWithLiveness(
+            buildClasses(A.class, B.class, Main.class).build(),
+            factory -> new ArrayList<>(buildKeepRuleForClassAndMethods(Main.class, factory)));
+    AppInfoWithLiveness appInfo = appView.appInfo();
+    DexType typeA = buildType(A.class, appInfo.dexItemFactory());
+    DexType typeB = buildType(B.class, appInfo.dexItemFactory());
+    DexType typeMain = buildType(Main.class, appInfo.dexItemFactory());
+    DexMethod fooA = buildNullaryVoidMethod(A.class, "foo", appInfo.dexItemFactory());
+    ClassTypeLatticeElement latticeB =
+        ClassTypeLatticeElement.create(typeB, Nullability.definitelyNotNull(), appView);
+    DexEncodedMethod singleTarget =
+        appInfo.lookupSingleVirtualTarget(fooA, typeMain, false, t -> false, typeA, latticeB);
+    assertNotNull(singleTarget);
+    DexMethod fooB = buildNullaryVoidMethod(B.class, "foo", appInfo.dexItemFactory());
+    assertEquals(fooB, singleTarget.method);
+  }
+
+  @Test
+  public void testSingleTargetLowerBoundInMiddleInstantiated() throws Exception {
+    AppView<AppInfoWithLiveness> appView =
+        computeAppViewWithLiveness(
+            buildClasses(A.class, B.class, C.class, Main.class).build(),
+            factory -> new ArrayList<>(buildKeepRuleForClassAndMethods(Main.class, factory)));
+    AppInfoWithLiveness appInfo = appView.appInfo();
+    DexType typeA = buildType(A.class, appInfo.dexItemFactory());
+    DexType typeB = buildType(B.class, appInfo.dexItemFactory());
+    DexType typeMain = buildType(Main.class, appInfo.dexItemFactory());
+    DexMethod fooA = buildNullaryVoidMethod(A.class, "foo", appInfo.dexItemFactory());
+    ClassTypeLatticeElement latticeB =
+        ClassTypeLatticeElement.create(typeB, Nullability.definitelyNotNull(), appView);
+    DexEncodedMethod singleTarget =
+        appInfo.lookupSingleVirtualTarget(fooA, typeMain, false, t -> false, typeA, latticeB);
+    assertNotNull(singleTarget);
+    DexMethod fooB = buildNullaryVoidMethod(B.class, "foo", appInfo.dexItemFactory());
+    assertEquals(fooB, singleTarget.method);
+  }
+
+  @Test
+  public void testSingleTargetLowerAllInstantiated() throws Exception {
+    AppView<AppInfoWithLiveness> appView =
+        computeAppViewWithLiveness(
+            buildClasses(A.class, B.class, C.class, MainAllInstantiated.class).build(),
+            factory ->
+                new ArrayList<>(
+                    buildKeepRuleForClassAndMethods(MainAllInstantiated.class, factory)));
+    AppInfoWithLiveness appInfo = appView.appInfo();
+    DexType typeA = buildType(A.class, appInfo.dexItemFactory());
+    DexType typeC = buildType(C.class, appInfo.dexItemFactory());
+    DexType typeMain = buildType(MainAllInstantiated.class, appInfo.dexItemFactory());
+    DexMethod fooA = buildNullaryVoidMethod(A.class, "foo", appInfo.dexItemFactory());
+    DexMethod fooB = buildNullaryVoidMethod(B.class, "foo", appInfo.dexItemFactory());
+    DexMethod fooC = buildNullaryVoidMethod(C.class, "foo", appInfo.dexItemFactory());
+    ResolutionResult resolution = appInfo.resolveMethod(typeA, fooA);
+    DexProgramClass context = appView.definitionForProgramType(typeMain);
+    DexProgramClass upperBound = appView.definitionForProgramType(typeA);
+    DexProgramClass lowerBound = appView.definitionForProgramType(typeC);
+    LookupResult lookupResult =
+        resolution.lookupVirtualDispatchTargets(context, appInfo, upperBound, lowerBound);
+    Set<DexMethod> expected = Sets.newIdentityHashSet();
+    expected.add(fooA);
+    expected.add(fooB);
+    expected.add(fooC);
+    assertTrue(lookupResult.isLookupResultSuccess());
+    Set<DexMethod> actual = Sets.newIdentityHashSet();
+    lookupResult
+        .asLookupResultSuccess()
+        .forEach(
+            clazzAndMethod -> actual.add(clazzAndMethod.getMethod().method),
+            lambdaTarget -> {
+              assert false;
+            });
+    assertEquals(expected, actual);
+    ClassTypeLatticeElement latticeC =
+        ClassTypeLatticeElement.create(typeC, Nullability.definitelyNotNull(), appView);
+    DexEncodedMethod singleTarget =
+        appInfo.lookupSingleVirtualTarget(fooA, typeMain, false, t -> false, typeA, latticeC);
+    assertNull(singleTarget);
+  }
+
+  public static class A {
+
+    public void foo() {
+      System.out.println("A.foo");
+    }
+  }
+
+  public static class B extends A {
+
+    @Override
+    public void foo() {
+      System.out.println("B.foo");
+    }
+  }
+
+  public static class C extends B {
+
+    @Override
+    public void foo() {
+      System.out.println("C.foo");
+    }
+  }
+
+  public static class Main {
+
+    public static void main(String[] args) {
+      new B();
+    }
+  }
+
+  public static class MainAllInstantiated {
+
+    public static void main(String[] args) {
+      new A();
+      new B();
+      new C();
+    }
+  }
+}