Infer lower bound type information for effectively final classes

Change-Id: I8e86776d2bd5be413136fc1527465db85a34e4e9
diff --git a/src/main/java/com/android/tools/r8/graph/DexClass.java b/src/main/java/com/android/tools/r8/graph/DexClass.java
index 72e372a..c340ae7 100644
--- a/src/main/java/com/android/tools/r8/graph/DexClass.java
+++ b/src/main/java/com/android/tools/r8/graph/DexClass.java
@@ -626,6 +626,14 @@
     return accessFlags.isAbstract();
   }
 
+  public boolean isFinal() {
+    return accessFlags.isFinal();
+  }
+
+  public boolean isEffectivelyFinal(AppView<?> appView) {
+    return isFinal();
+  }
+
   public boolean isInterface() {
     return accessFlags.isInterface();
   }
diff --git a/src/main/java/com/android/tools/r8/graph/DexProgramClass.java b/src/main/java/com/android/tools/r8/graph/DexProgramClass.java
index 92a4963..5a89dcf 100644
--- a/src/main/java/com/android/tools/r8/graph/DexProgramClass.java
+++ b/src/main/java/com/android/tools/r8/graph/DexProgramClass.java
@@ -10,6 +10,7 @@
 import com.android.tools.r8.errors.CompilationError;
 import com.android.tools.r8.kotlin.KotlinInfo;
 import com.android.tools.r8.origin.Origin;
+import com.android.tools.r8.shaking.AppInfoWithLiveness;
 import java.util.ArrayList;
 import java.util.Arrays;
 import java.util.Collection;
@@ -228,6 +229,26 @@
     return type.toSourceString();
   }
 
+  /**
+   * Returns true if this class is final, or it is a non-pinned program class with no instantiated
+   * subtypes.
+   */
+  @Override
+  public boolean isEffectivelyFinal(AppView<?> appView) {
+    if (isFinal()) {
+      return true;
+    }
+    if (appView.enableWholeProgramOptimizations()) {
+      assert appView.appInfo().hasLiveness();
+      AppInfoWithLiveness appInfo = appView.appInfo().withLiveness();
+      if (appInfo.isPinned(type)) {
+        return false;
+      }
+      return !appInfo.hasSubtypes(type) || !appInfo.isInstantiatedIndirectly(type);
+    }
+    return false;
+  }
+
   @Override
   public boolean isProgramClass() {
     return true;
diff --git a/src/main/java/com/android/tools/r8/ir/analysis/type/ClassTypeLatticeElement.java b/src/main/java/com/android/tools/r8/ir/analysis/type/ClassTypeLatticeElement.java
index 4adc57e..3ad41d4 100644
--- a/src/main/java/com/android/tools/r8/ir/analysis/type/ClassTypeLatticeElement.java
+++ b/src/main/java/com/android/tools/r8/ir/analysis/type/ClassTypeLatticeElement.java
@@ -89,7 +89,7 @@
   }
 
   @Override
-  public ReferenceTypeLatticeElement getOrCreateVariant(Nullability nullability) {
+  public ClassTypeLatticeElement getOrCreateVariant(Nullability nullability) {
     ClassTypeLatticeElement variant = variants.get(nullability);
     if (variant != null) {
       return variant;
@@ -116,6 +116,11 @@
   }
 
   @Override
+  public ClassTypeLatticeElement asMeetWithNotNull() {
+    return getOrCreateVariant(nullability.meet(Nullability.definitelyNotNull()));
+  }
+
+  @Override
   public String toString() {
     StringBuilder builder = new StringBuilder();
     builder.append(nullability);
diff --git a/src/main/java/com/android/tools/r8/ir/code/InvokeMethodWithReceiver.java b/src/main/java/com/android/tools/r8/ir/code/InvokeMethodWithReceiver.java
index aea5a70..87b93a4 100644
--- a/src/main/java/com/android/tools/r8/ir/code/InvokeMethodWithReceiver.java
+++ b/src/main/java/com/android/tools/r8/ir/code/InvokeMethodWithReceiver.java
@@ -3,7 +3,6 @@
 // BSD-style license that can be found in the LICENSE file.
 package com.android.tools.r8.ir.code;
 
-import com.android.tools.r8.graph.AppInfoWithSubtyping;
 import com.android.tools.r8.graph.AppView;
 import com.android.tools.r8.graph.DexEncodedMethod;
 import com.android.tools.r8.graph.DexItemFactory;
@@ -17,6 +16,7 @@
 import com.android.tools.r8.ir.optimize.Inliner.InlineAction;
 import com.android.tools.r8.ir.optimize.Inliner.Reason;
 import com.android.tools.r8.ir.optimize.inliner.WhyAreYouNotInliningReporter;
+import com.android.tools.r8.shaking.AppInfoWithLiveness;
 import java.util.List;
 
 public abstract class InvokeMethodWithReceiver extends InvokeMethod {
@@ -74,15 +74,15 @@
     TypeLatticeElement receiverType = receiver.getTypeLattice();
     assert receiverType.isPreciseType();
 
-    if (appView.appInfo().hasSubtyping()) {
-      AppView<? extends AppInfoWithSubtyping> appViewWithSubtyping = appView.withSubtyping();
+    if (appView.appInfo().hasLiveness()) {
+      AppView<AppInfoWithLiveness> appViewWithLiveness = appView.withLiveness();
       ClassTypeLatticeElement receiverLowerBoundType =
-          receiver.getDynamicLowerBoundType(appViewWithSubtyping);
+          receiver.getDynamicLowerBoundType(appViewWithLiveness);
       if (receiverLowerBoundType != null) {
         DexType refinedReceiverType =
-            TypeAnalysis.getRefinedReceiverType(appViewWithSubtyping, this);
+            TypeAnalysis.getRefinedReceiverType(appViewWithLiveness, this);
         assert receiverLowerBoundType.getClassType() == refinedReceiverType
-            || receiverLowerBoundType.isBasedOnMissingClass(appViewWithSubtyping);
+            || receiverLowerBoundType.isBasedOnMissingClass(appViewWithLiveness);
       }
     }
 
diff --git a/src/main/java/com/android/tools/r8/ir/code/Value.java b/src/main/java/com/android/tools/r8/ir/code/Value.java
index f9ca31d..97d42fb 100644
--- a/src/main/java/com/android/tools/r8/ir/code/Value.java
+++ b/src/main/java/com/android/tools/r8/ir/code/Value.java
@@ -19,6 +19,7 @@
 import com.android.tools.r8.ir.regalloc.LiveIntervals;
 import com.android.tools.r8.origin.Origin;
 import com.android.tools.r8.position.MethodPosition;
+import com.android.tools.r8.shaking.AppInfoWithLiveness;
 import com.android.tools.r8.utils.LongInterval;
 import com.android.tools.r8.utils.Reporter;
 import com.android.tools.r8.utils.SetUtils;
@@ -1114,12 +1115,12 @@
     return lattice;
   }
 
-  public ClassTypeLatticeElement getDynamicLowerBoundType(
-      AppView<? extends AppInfoWithSubtyping> appView) {
+  public ClassTypeLatticeElement getDynamicLowerBoundType(AppView<AppInfoWithLiveness> appView) {
     Value root = getAliasedValue();
     if (root.isPhi()) {
       return null;
     }
+
     Instruction definition = root.definition;
     if (definition.isNewInstance()) {
       DexType type = definition.asNewInstance().clazz;
@@ -1129,6 +1130,7 @@
       }
       return null;
     }
+
     // Try to find an alias of the receiver, which is defined by an instruction of the type
     // Assume<DynamicTypeAssumption>.
     Value aliasedValue = getSpecificAliasedValue(value -> value.definition.isAssumeDynamicType());
@@ -1136,9 +1138,20 @@
       ClassTypeLatticeElement lattice =
           aliasedValue.definition.asAssumeDynamicType().getAssumption().getDynamicLowerBoundType();
       return lattice != null && typeLattice.isDefinitelyNotNull() && lattice.isNullable()
-          ? lattice.asMeetWithNotNull().asClassTypeLatticeElement()
+          ? lattice.asMeetWithNotNull()
           : lattice;
     }
+
+    // If it is a final or effectively-final class type, then we know the lower bound.
+    if (getTypeLattice().isClassType()) {
+      ClassTypeLatticeElement classType = getTypeLattice().asClassTypeLatticeElement();
+      DexType type = classType.getClassType();
+      DexClass clazz = appView.definitionFor(type);
+      if (clazz != null && clazz.isEffectivelyFinal(appView)) {
+        return classType;
+      }
+    }
+
     return null;
   }
 }
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/DynamicTypeOptimization.java b/src/main/java/com/android/tools/r8/ir/optimize/DynamicTypeOptimization.java
index d9e51ea..06c5dfe 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/DynamicTypeOptimization.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/DynamicTypeOptimization.java
@@ -76,7 +76,8 @@
 
         MethodOptimizationInfo optimizationInfo = singleTarget.getOptimizationInfo();
         if (optimizationInfo.returnsArgument()) {
-          assert optimizationInfo.getDynamicLowerBoundType() == null;
+          // Don't insert an assume-instruction since we will replace all usages of the out-value by
+          // the corresponding argument.
           continue;
         }
 
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/staticizer/StaticizingProcessor.java b/src/main/java/com/android/tools/r8/ir/optimize/staticizer/StaticizingProcessor.java
index a467cb2..12992ca 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/staticizer/StaticizingProcessor.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/staticizer/StaticizingProcessor.java
@@ -94,7 +94,9 @@
                 .add(this::insertAssumeInstructions)
                 .add(collectOptimizationInfo(feedback)));
 
-    // Enqueue instance methods to be staticized (only remove references to 'this').
+    // Enqueue instance methods to be staticized (only remove references to 'this'). Intentionally
+    // not collection optimization info for these methods, since they will be reprocessed again
+    // below once staticized.
     enqueueMethodsWithCodeOptimizations(
         methodsToBeStaticized, optimizations -> optimizations.add(this::removeReferencesToThis));