Add ExactDynamicType and account for incomplete type canonicalization

Bug: 197237400
Change-Id: Iaa5981ac2a00a8fbfebfde6d8a747e0c0665e7d1
diff --git a/src/main/java/com/android/tools/r8/ir/analysis/type/ClassTypeElement.java b/src/main/java/com/android/tools/r8/ir/analysis/type/ClassTypeElement.java
index c171e7d..9e83183 100644
--- a/src/main/java/com/android/tools/r8/ir/analysis/type/ClassTypeElement.java
+++ b/src/main/java/com/android/tools/r8/ir/analysis/type/ClassTypeElement.java
@@ -145,6 +145,11 @@
   }
 
   @Override
+  public ClassTypeElement meetNullability(Nullability nullability) {
+    return getOrCreateVariant(nullability().meet(nullability));
+  }
+
+  @Override
   public String toString() {
     StringBuilder builder = new StringBuilder();
     builder.append(nullability);
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 12d1c4c..5db6f54 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
@@ -7,6 +7,7 @@
 import com.android.tools.r8.graph.AppView;
 import com.android.tools.r8.ir.code.Value;
 import com.android.tools.r8.shaking.AppInfoWithLiveness;
+import java.util.Objects;
 
 /**
  * Represents the runtime type of a reference value. This type may be more precise than the value's
@@ -27,18 +28,40 @@
     this.dynamicUpperBoundType = dynamicUpperBoundType;
   }
 
-  public static DynamicType create(Value value, AppView<AppInfoWithLiveness> appView) {
-    assert value.getType().isReferenceType();
-    TypeElement dynamicUpperBoundType = value.getDynamicUpperBoundType(appView);
-    ClassTypeElement dynamicLowerBoundType = value.getDynamicLowerBoundType(appView);
+  public static DynamicType create(
+      AppView<AppInfoWithLiveness> appView,
+      TypeElement dynamicUpperBoundType,
+      ClassTypeElement dynamicLowerBoundType) {
+    if (dynamicUpperBoundType.isBottom()) {
+      return bottom();
+    }
+    if (dynamicUpperBoundType.isTop()) {
+      return unknown();
+    }
     if (dynamicLowerBoundType != null) {
       assert dynamicUpperBoundType.isClassType();
+      assert dynamicUpperBoundType.nullability() == dynamicLowerBoundType.nullability();
+      if (dynamicUpperBoundType.equals(dynamicLowerBoundType)) {
+        return createExact(dynamicLowerBoundType);
+      }
       return DynamicTypeWithLowerBound.create(
           appView, dynamicUpperBoundType.asClassType(), dynamicLowerBoundType);
     }
     return new DynamicType(dynamicUpperBoundType);
   }
 
+  public static DynamicType createExact(ClassTypeElement exactDynamicType) {
+    return new ExactDynamicType(exactDynamicType);
+  }
+
+  public static DynamicType create(Value value, AppView<AppInfoWithLiveness> appView) {
+    assert value.getType().isReferenceType();
+    TypeElement dynamicUpperBoundType = value.getDynamicUpperBoundType(appView);
+    ClassTypeElement dynamicLowerBoundType =
+        value.getDynamicLowerBoundType(appView, dynamicUpperBoundType.nullability());
+    return create(appView, dynamicUpperBoundType, dynamicLowerBoundType);
+  }
+
   public static DynamicType bottom() {
     return BOTTOM;
   }
@@ -59,21 +82,61 @@
     return null;
   }
 
+  public boolean isBottom() {
+    return getDynamicUpperBoundType().isBottom();
+  }
+
   public boolean isTrivial(TypeElement staticType) {
-    return staticType == getDynamicUpperBoundType() || isUnknown();
+    return staticType.equals(getDynamicUpperBoundType()) || isUnknown();
   }
 
   public boolean isUnknown() {
     return getDynamicUpperBoundType().isTop();
   }
 
+  public DynamicType join(AppView<AppInfoWithLiveness> appView, DynamicType dynamicType) {
+    if (isBottom()) {
+      return dynamicType;
+    }
+    if (dynamicType.isBottom() || equals(dynamicType)) {
+      return this;
+    }
+    if (isUnknown() || dynamicType.isUnknown()) {
+      return unknown();
+    }
+    TypeElement upperBoundType =
+        getDynamicUpperBoundType().join(dynamicType.getDynamicUpperBoundType(), appView);
+    ClassTypeElement lowerBoundType = meetDynamicLowerBound(appView, dynamicType);
+    if (upperBoundType.equals(getDynamicUpperBoundType())
+        && Objects.equals(lowerBoundType, getDynamicLowerBoundType())) {
+      return this;
+    }
+    return create(appView, upperBoundType, lowerBoundType);
+  }
+
+  private ClassTypeElement meetDynamicLowerBound(
+      AppView<AppInfoWithLiveness> appView, DynamicType dynamicType) {
+    if (!hasDynamicLowerBoundType() || !dynamicType.hasDynamicLowerBoundType()) {
+      return null;
+    }
+    ClassTypeElement lowerBoundType = getDynamicLowerBoundType();
+    ClassTypeElement otherLowerBoundType = dynamicType.getDynamicLowerBoundType();
+    if (lowerBoundType.lessThanOrEqualUpToNullability(otherLowerBoundType, appView)) {
+      return lowerBoundType.joinNullability(otherLowerBoundType.nullability());
+    }
+    if (otherLowerBoundType.lessThanOrEqualUpToNullability(lowerBoundType, appView)) {
+      return otherLowerBoundType.joinNullability(lowerBoundType.nullability());
+    }
+    return null;
+  }
+
   @Override
   public boolean equals(Object other) {
     if (other == null || getClass() != other.getClass()) {
       return false;
     }
-    DynamicType assumption = (DynamicType) other;
-    return dynamicUpperBoundType == assumption.dynamicUpperBoundType;
+    DynamicType dynamicType = (DynamicType) other;
+    return dynamicUpperBoundType.equals(dynamicType.dynamicUpperBoundType);
   }
 
   @Override
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 ae61ee3..400f99e 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
@@ -15,16 +15,21 @@
   DynamicTypeWithLowerBound(
       ClassTypeElement dynamicUpperBoundType, ClassTypeElement dynamicLowerBoundType) {
     super(dynamicUpperBoundType);
+    assert !dynamicUpperBoundType.equals(dynamicLowerBoundType);
     this.dynamicLowerBoundType = dynamicLowerBoundType;
   }
 
-  public static DynamicTypeWithLowerBound create(
+  static DynamicTypeWithLowerBound create(
       AppView<AppInfoWithLiveness> appView,
       ClassTypeElement dynamicUpperBoundType,
       ClassTypeElement dynamicLowerBoundType) {
+    assert dynamicUpperBoundType != null;
+    assert dynamicLowerBoundType != null;
+    assert dynamicUpperBoundType.nullability() == dynamicLowerBoundType.nullability();
     assert appView
         .appInfo()
-        .isSubtype(dynamicLowerBoundType.getClassType(), dynamicUpperBoundType.getClassType());
+        .isStrictSubtypeOf(
+            dynamicLowerBoundType.getClassType(), dynamicUpperBoundType.getClassType());
     return new DynamicTypeWithLowerBound(dynamicUpperBoundType, dynamicLowerBoundType);
   }
 
@@ -48,9 +53,9 @@
     if (other == null || getClass() != other.getClass()) {
       return false;
     }
-    DynamicTypeWithLowerBound assumption = (DynamicTypeWithLowerBound) other;
-    return getDynamicUpperBoundType() == assumption.getDynamicUpperBoundType()
-        && getDynamicLowerBoundType() == assumption.getDynamicLowerBoundType();
+    DynamicTypeWithLowerBound dynamicType = (DynamicTypeWithLowerBound) other;
+    return getDynamicUpperBoundType().equals(dynamicType.getDynamicUpperBoundType())
+        && getDynamicLowerBoundType().equals(dynamicType.getDynamicLowerBoundType());
   }
 
   @Override
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
new file mode 100644
index 0000000..a6061d9
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/ir/analysis/type/ExactDynamicType.java
@@ -0,0 +1,41 @@
+// Copyright (c) 2021, 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.ir.analysis.type;
+
+public class ExactDynamicType extends DynamicType {
+
+  ExactDynamicType(ClassTypeElement exactDynamicType) {
+    super(exactDynamicType);
+  }
+
+  @Override
+  public boolean hasDynamicLowerBoundType() {
+    return true;
+  }
+
+  @Override
+  public ClassTypeElement getDynamicLowerBoundType() {
+    return getDynamicUpperBoundType().asClassType();
+  }
+
+  @Override
+  public boolean isTrivial(TypeElement staticType) {
+    return false;
+  }
+
+  @Override
+  public boolean equals(Object other) {
+    if (other == null || getClass() != other.getClass()) {
+      return false;
+    }
+    ExactDynamicType dynamicType = (ExactDynamicType) other;
+    return getDynamicUpperBoundType().equals(dynamicType.getDynamicUpperBoundType());
+  }
+
+  @Override
+  public int hashCode() {
+    return getDynamicLowerBoundType().hashCode();
+  }
+}
diff --git a/src/main/java/com/android/tools/r8/ir/analysis/type/ReferenceTypeElement.java b/src/main/java/com/android/tools/r8/ir/analysis/type/ReferenceTypeElement.java
index 5a82ac4..3577c72 100644
--- a/src/main/java/com/android/tools/r8/ir/analysis/type/ReferenceTypeElement.java
+++ b/src/main/java/com/android/tools/r8/ir/analysis/type/ReferenceTypeElement.java
@@ -94,6 +94,10 @@
     return getOrCreateVariant(nullability().join(nullability));
   }
 
+  public ReferenceTypeElement meetNullability(Nullability nullability) {
+    return getOrCreateVariant(nullability().meet(nullability));
+  }
+
   @Override
   public boolean isReferenceType() {
     return true;
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 643fdcb..4df7f91 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
@@ -23,6 +23,7 @@
 import com.android.tools.r8.graph.ProgramMethod;
 import com.android.tools.r8.ir.analysis.type.ClassTypeElement;
 import com.android.tools.r8.ir.analysis.type.DynamicType;
+import com.android.tools.r8.ir.analysis.type.Nullability;
 import com.android.tools.r8.ir.analysis.type.TypeElement;
 import com.android.tools.r8.ir.analysis.value.AbstractValue;
 import com.android.tools.r8.ir.analysis.value.UnknownValue;
@@ -1136,13 +1137,18 @@
   }
 
   public ClassTypeElement getDynamicLowerBoundType(AppView<AppInfoWithLiveness> appView) {
+    return getDynamicLowerBoundType(appView, 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;
+        return classType.meetNullability(maxNullability);
       }
     }
 
@@ -1151,11 +1157,12 @@
       return null;
     }
 
-    Instruction definition = root.definition;
+    Instruction definition = root.getDefinition();
     if (definition.isNewInstance()) {
-      DexType type = definition.asNewInstance().clazz;
+      DexType type = definition.asNewInstance().getType();
       DexClass clazz = appView.definitionFor(type);
       if (clazz != null && !clazz.isInterface()) {
+        assert !maxNullability.isBottom();
         return TypeElement.fromDexType(type, definitelyNotNull(), appView).asClassType();
       }
       return null;
@@ -1163,13 +1170,19 @@
 
     // Try to find an alias of the receiver, which is defined by an instruction of the type Assume.
     Value aliasedValue =
-        getSpecificAliasedValue(value -> value.definition.isAssumeWithDynamicTypeAssumption());
+        getSpecificAliasedValue(value -> value.getDefinition().isAssumeWithDynamicTypeAssumption());
     if (aliasedValue != null) {
-      ClassTypeElement lattice =
-          aliasedValue.definition.asAssume().getDynamicTypeAssumption().getDynamicLowerBoundType();
-      return lattice != null && type.isDefinitelyNotNull() && lattice.isNullable()
-          ? lattice.asMeetWithNotNull()
-          : lattice;
+      ClassTypeElement aliasedValueType =
+          aliasedValue
+              .getDefinition()
+              .asAssume()
+              .getDynamicTypeAssumption()
+              .getDynamicLowerBoundType();
+      if (aliasedValueType != null) {
+        aliasedValueType = aliasedValueType.meetNullability(getType().nullability());
+        assert aliasedValueType.nullability().lessThanOrEqual(maxNullability);
+        return aliasedValueType;
+      }
     }
 
     return null;