Always set lower bound for dynamic types that are effectively final

This ensures that there is no way to create a dynamic type (upper=T, lower=null) if T is effectively final.

Change-Id: I8068541577a9a6ca49725edf78eafdb7aad73f9f
diff --git a/src/main/java/com/android/tools/r8/ir/analysis/type/DynamicType.java b/src/main/java/com/android/tools/r8/ir/analysis/type/DynamicType.java
index 5db6f54..cae552a 100644
--- a/src/main/java/com/android/tools/r8/ir/analysis/type/DynamicType.java
+++ b/src/main/java/com/android/tools/r8/ir/analysis/type/DynamicType.java
@@ -5,6 +5,7 @@
 package com.android.tools.r8.ir.analysis.type;
 
 import com.android.tools.r8.graph.AppView;
+import com.android.tools.r8.graph.DexClass;
 import com.android.tools.r8.ir.code.Value;
 import com.android.tools.r8.shaking.AppInfoWithLiveness;
 import java.util.Objects;
@@ -19,6 +20,7 @@
 public class DynamicType {
 
   private static final DynamicType BOTTOM = new DynamicType(TypeElement.getBottom());
+  private static final DynamicType NULL_TYPE = new DynamicType(TypeElement.getNull());
   private static final DynamicType UNKNOWN = new DynamicType(TypeElement.getTop());
 
   private final TypeElement dynamicUpperBoundType;
@@ -29,12 +31,29 @@
   }
 
   public static DynamicType create(
+      AppView<AppInfoWithLiveness> appView, TypeElement dynamicUpperBoundType) {
+    ClassTypeElement dynamicLowerBoundType = null;
+    if (dynamicUpperBoundType.isClassType()) {
+      ClassTypeElement dynamicUpperBoundClassType = dynamicUpperBoundType.asClassType();
+      DexClass dynamicUpperBoundClass =
+          appView.definitionFor(dynamicUpperBoundClassType.getClassType());
+      if (dynamicUpperBoundClass != null && dynamicUpperBoundClass.isEffectivelyFinal(appView)) {
+        dynamicLowerBoundType = dynamicUpperBoundClassType;
+      }
+    }
+    return create(appView, dynamicUpperBoundType, dynamicLowerBoundType);
+  }
+
+  public static DynamicType create(
       AppView<AppInfoWithLiveness> appView,
       TypeElement dynamicUpperBoundType,
       ClassTypeElement dynamicLowerBoundType) {
     if (dynamicUpperBoundType.isBottom()) {
       return bottom();
     }
+    if (dynamicUpperBoundType.isNullType()) {
+      return definitelyNull();
+    }
     if (dynamicUpperBoundType.isTop()) {
       return unknown();
     }
@@ -47,6 +66,7 @@
       return DynamicTypeWithLowerBound.create(
           appView, dynamicUpperBoundType.asClassType(), dynamicLowerBoundType);
     }
+    assert verifyNotEffectivelyFinalClassType(appView, dynamicUpperBoundType);
     return new DynamicType(dynamicUpperBoundType);
   }
 
@@ -54,11 +74,12 @@
     return new ExactDynamicType(exactDynamicType);
   }
 
-  public static DynamicType create(Value value, AppView<AppInfoWithLiveness> appView) {
+  public static DynamicType create(AppView<AppInfoWithLiveness> appView, Value value) {
     assert value.getType().isReferenceType();
     TypeElement dynamicUpperBoundType = value.getDynamicUpperBoundType(appView);
     ClassTypeElement dynamicLowerBoundType =
-        value.getDynamicLowerBoundType(appView, dynamicUpperBoundType.nullability());
+        value.getDynamicLowerBoundType(
+            appView, dynamicUpperBoundType, dynamicUpperBoundType.nullability());
     return create(appView, dynamicUpperBoundType, dynamicLowerBoundType);
   }
 
@@ -66,6 +87,10 @@
     return BOTTOM;
   }
 
+  public static DynamicType definitelyNull() {
+    return NULL_TYPE;
+  }
+
   public static DynamicType unknown() {
     return UNKNOWN;
   }
@@ -86,6 +111,10 @@
     return getDynamicUpperBoundType().isBottom();
   }
 
+  public boolean isNullType() {
+    return getDynamicUpperBoundType().isNullType();
+  }
+
   public boolean isTrivial(TypeElement staticType) {
     return staticType.equals(getDynamicUpperBoundType()) || isUnknown();
   }
@@ -116,6 +145,18 @@
 
   private ClassTypeElement meetDynamicLowerBound(
       AppView<AppInfoWithLiveness> appView, DynamicType dynamicType) {
+    if (isNullType()) {
+      if (dynamicType.hasDynamicLowerBoundType()) {
+        return dynamicType.getDynamicLowerBoundType().joinNullability(Nullability.definitelyNull());
+      }
+      return null;
+    }
+    if (dynamicType.isNullType()) {
+      if (hasDynamicLowerBoundType()) {
+        return getDynamicLowerBoundType().joinNullability(Nullability.definitelyNull());
+      }
+      return null;
+    }
     if (!hasDynamicLowerBoundType() || !dynamicType.hasDynamicLowerBoundType()) {
       return null;
     }
@@ -143,4 +184,27 @@
   public int hashCode() {
     return dynamicUpperBoundType.hashCode();
   }
+
+  private static boolean verifyNotEffectivelyFinalClassType(
+      AppView<AppInfoWithLiveness> appView, TypeElement type) {
+    if (type.isClassType()) {
+      ClassTypeElement classType = type.asClassType();
+      DexClass clazz = appView.definitionFor(classType.getClassType());
+      assert clazz == null || !clazz.isEffectivelyFinal(appView);
+    }
+    return true;
+  }
+
+  public DynamicType withNullability(Nullability nullability) {
+    assert !hasDynamicLowerBoundType();
+    if (!getDynamicUpperBoundType().isReferenceType()) {
+      return this;
+    }
+    ReferenceTypeElement dynamicUpperBoundReferenceType =
+        getDynamicUpperBoundType().asReferenceType();
+    if (dynamicUpperBoundReferenceType.nullability() == nullability) {
+      return this;
+    }
+    return new DynamicType(dynamicUpperBoundReferenceType.getOrCreateVariant(nullability));
+  }
 }
diff --git a/src/main/java/com/android/tools/r8/ir/analysis/type/DynamicTypeWithLowerBound.java b/src/main/java/com/android/tools/r8/ir/analysis/type/DynamicTypeWithLowerBound.java
index 400f99e..0983de7 100644
--- a/src/main/java/com/android/tools/r8/ir/analysis/type/DynamicTypeWithLowerBound.java
+++ b/src/main/java/com/android/tools/r8/ir/analysis/type/DynamicTypeWithLowerBound.java
@@ -12,7 +12,7 @@
 
   private final ClassTypeElement dynamicLowerBoundType;
 
-  DynamicTypeWithLowerBound(
+  private DynamicTypeWithLowerBound(
       ClassTypeElement dynamicUpperBoundType, ClassTypeElement dynamicLowerBoundType) {
     super(dynamicUpperBoundType);
     assert !dynamicUpperBoundType.equals(dynamicLowerBoundType);
@@ -34,6 +34,11 @@
   }
 
   @Override
+  public ClassTypeElement getDynamicUpperBoundType() {
+    return super.getDynamicUpperBoundType().asClassType();
+  }
+
+  @Override
   public boolean hasDynamicLowerBoundType() {
     return true;
   }
@@ -62,4 +67,14 @@
   public int hashCode() {
     return Objects.hash(getDynamicUpperBoundType(), getDynamicLowerBoundType());
   }
+
+  @Override
+  public DynamicType withNullability(Nullability nullability) {
+    if (getDynamicUpperBoundType().nullability() == nullability) {
+      return this;
+    }
+    return new DynamicTypeWithLowerBound(
+        getDynamicUpperBoundType().getOrCreateVariant(nullability),
+        getDynamicLowerBoundType().getOrCreateVariant(nullability));
+  }
 }
diff --git a/src/main/java/com/android/tools/r8/ir/analysis/type/ExactDynamicType.java b/src/main/java/com/android/tools/r8/ir/analysis/type/ExactDynamicType.java
index a6061d9..ed9f300 100644
--- a/src/main/java/com/android/tools/r8/ir/analysis/type/ExactDynamicType.java
+++ b/src/main/java/com/android/tools/r8/ir/analysis/type/ExactDynamicType.java
@@ -16,8 +16,13 @@
   }
 
   @Override
+  public ClassTypeElement getDynamicUpperBoundType() {
+    return super.getDynamicUpperBoundType().asClassType();
+  }
+
+  @Override
   public ClassTypeElement getDynamicLowerBoundType() {
-    return getDynamicUpperBoundType().asClassType();
+    return getDynamicUpperBoundType();
   }
 
   @Override
@@ -38,4 +43,12 @@
   public int hashCode() {
     return getDynamicLowerBoundType().hashCode();
   }
+
+  @Override
+  public DynamicType withNullability(Nullability nullability) {
+    if (getDynamicUpperBoundType().nullability() == nullability) {
+      return this;
+    }
+    return new ExactDynamicType(getDynamicUpperBoundType().getOrCreateVariant(nullability));
+  }
 }
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 4df7f91..0c58032 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
@@ -1086,7 +1086,7 @@
   }
 
   public DynamicType getDynamicType(AppView<AppInfoWithLiveness> appView) {
-    return DynamicType.create(this, appView);
+    return DynamicType.create(appView, this);
   }
 
   public TypeElement getDynamicUpperBoundType(
@@ -1137,18 +1137,23 @@
   }
 
   public ClassTypeElement getDynamicLowerBoundType(AppView<AppInfoWithLiveness> appView) {
-    return getDynamicLowerBoundType(appView, Nullability.maybeNull());
+    return getDynamicLowerBoundType(appView, null, Nullability.maybeNull());
   }
 
   public ClassTypeElement getDynamicLowerBoundType(
-      AppView<AppInfoWithLiveness> appView, Nullability maxNullability) {
-    // If it is a final or effectively-final class type, then we know the lower bound.
-    if (getType().isClassType()) {
-      ClassTypeElement classType = getType().asClassType();
-      DexType type = classType.getClassType();
-      DexClass clazz = appView.definitionFor(type);
-      if (clazz != null && clazz.isEffectivelyFinal(appView)) {
-        return classType.meetNullability(maxNullability);
+      AppView<AppInfoWithLiveness> appView,
+      TypeElement dynamicUpperBoundType,
+      Nullability maxNullability) {
+    // If the dynamic upper bound type is a final or effectively-final class type, then we know the
+    // lower bound. Since the dynamic upper bound type is below the static type in the class
+    // hierarchy, we only need to check if the dynamic upper bound type is effectively final if it
+    // is present.
+    TypeElement upperBoundType = dynamicUpperBoundType != null ? dynamicUpperBoundType : getType();
+    if (upperBoundType.isClassType()) {
+      ClassTypeElement upperBoundClassType = upperBoundType.asClassType();
+      DexClass upperBoundClass = appView.definitionFor(upperBoundClassType.getClassType());
+      if (upperBoundClass != null && upperBoundClass.isEffectivelyFinal(appView)) {
+        return upperBoundClassType.meetNullability(maxNullability);
       }
     }
 
diff --git a/src/main/java/com/android/tools/r8/optimize/argumentpropagation/ArgumentPropagatorCodeScanner.java b/src/main/java/com/android/tools/r8/optimize/argumentpropagation/ArgumentPropagatorCodeScanner.java
index 9847171..1ef0397 100644
--- a/src/main/java/com/android/tools/r8/optimize/argumentpropagation/ArgumentPropagatorCodeScanner.java
+++ b/src/main/java/com/android/tools/r8/optimize/argumentpropagation/ArgumentPropagatorCodeScanner.java
@@ -234,9 +234,6 @@
     //  and should therefore not be too expensive to compute). Doing so should have the same
     //  precision, but lead to less state propagation in the subsequent top-down class
     //  hierarchy traversals.
-    // TODO(b/190154391): Normalize type bounds that are the same, by removing trivial lower bound
-    //  information. For example, the types (upper=B, lower=unknown) and (upper=B, lower=B) are
-    //  identical if B does not have any subtypes.
     DynamicType bounds =
         invoke.isInvokeSuper()
             ? DynamicType.createExact(