Use custom thisType to signal static type precision.

This also removes the dependency on the state (!) in ir.TypeEnvironment.

Change-Id: I0180d111ecf45b685c4abce12c6a9bd52c1f308e
Reviewed-on: https://dart-review.googlesource.com/c/88968
Commit-Queue: Johnni Winther <johnniwinther@google.com>
Reviewed-by: Sigmund Cherem <sigmund@google.com>
diff --git a/pkg/compiler/lib/src/ir/cached_static_type.dart b/pkg/compiler/lib/src/ir/cached_static_type.dart
index 19ff07f..5daea39 100644
--- a/pkg/compiler/lib/src/ir/cached_static_type.dart
+++ b/pkg/compiler/lib/src/ir/cached_static_type.dart
@@ -14,8 +14,10 @@
 /// and a precomputed cache for complex expression type.
 class CachedStaticType extends StaticTypeBase implements StaticTypeProvider {
   final Map<ir.Expression, ir.DartType> _cache;
+  final ThisInterfaceType thisType;
 
-  CachedStaticType(ir.TypeEnvironment typeEnvironment, this._cache)
+  CachedStaticType(
+      ir.TypeEnvironment typeEnvironment, this._cache, this.thisType)
       : super(typeEnvironment);
 
   @override
diff --git a/pkg/compiler/lib/src/ir/impact.dart b/pkg/compiler/lib/src/ir/impact.dart
index edd7503..d9be817 100644
--- a/pkg/compiler/lib/src/ir/impact.dart
+++ b/pkg/compiler/lib/src/ir/impact.dart
@@ -12,6 +12,7 @@
 import '../common.dart';
 import 'scope.dart';
 import 'static_type.dart';
+import 'static_type_base.dart';
 import 'util.dart';
 
 abstract class ImpactBuilder extends StaticTypeVisitor {
@@ -21,6 +22,16 @@
       ir.ClassHierarchy classHierarchy, this.variableScopeModel)
       : super(typeEnvironment, classHierarchy);
 
+  ClassRelation _computeClassRelationFromType(ir.DartType type) {
+    if (type is ThisInterfaceType) {
+      return ClassRelation.thisExpression;
+    } else if (type is ExactInterfaceType) {
+      return ClassRelation.exact;
+    } else {
+      return ClassRelation.subtype;
+    }
+  }
+
   void registerIntLiteral(int value);
 
   @override
@@ -402,9 +413,7 @@
         receiver.variable.parent is ir.FunctionDeclaration) {
       registerLocalFunctionInvocation(receiver.variable.parent, node.arguments);
     } else {
-      ClassRelation relation = receiver is ir.ThisExpression
-          ? ClassRelation.thisExpression
-          : ClassRelation.subtype;
+      ClassRelation relation = _computeClassRelationFromType(receiverType);
 
       ir.Member interfaceTarget = node.interfaceTarget;
       if (interfaceTarget == null) {
@@ -452,9 +461,7 @@
   @override
   void handlePropertyGet(
       ir.PropertyGet node, ir.DartType receiverType, ir.DartType resultType) {
-    ClassRelation relation = node.receiver is ir.ThisExpression
-        ? ClassRelation.thisExpression
-        : ClassRelation.subtype;
+    ClassRelation relation = _computeClassRelationFromType(receiverType);
     if (node.interfaceTarget != null) {
       registerInstanceGet(receiverType, relation, node.interfaceTarget);
     } else {
@@ -477,9 +484,7 @@
   @override
   void handlePropertySet(
       ir.PropertySet node, ir.DartType receiverType, ir.DartType valueType) {
-    ClassRelation relation = node.receiver is ir.ThisExpression
-        ? ClassRelation.thisExpression
-        : ClassRelation.subtype;
+    ClassRelation relation = _computeClassRelationFromType(receiverType);
     if (node.interfaceTarget != null) {
       registerInstanceSet(receiverType, relation, node.interfaceTarget);
     } else {
diff --git a/pkg/compiler/lib/src/ir/static_type.dart b/pkg/compiler/lib/src/ir/static_type.dart
index dae8bd2..8cafb6f 100644
--- a/pkg/compiler/lib/src/ir/static_type.dart
+++ b/pkg/compiler/lib/src/ir/static_type.dart
@@ -41,6 +41,8 @@
 
   final ir.ClassHierarchy hierarchy;
 
+  ThisInterfaceType _thisType;
+
   StaticTypeVisitor(ir.TypeEnvironment typeEnvironment, this.hierarchy)
       : super(typeEnvironment);
 
@@ -56,6 +58,16 @@
 
   VariableScopeModel get variableScopeModel;
 
+  ThisInterfaceType get thisType {
+    assert(_thisType != null);
+    return _thisType;
+  }
+
+  void set thisType(ThisInterfaceType value) {
+    assert(value == null || _thisType == null);
+    _thisType = value;
+  }
+
   bool completes(ir.DartType type) => type != const DoesNotCompleteType();
 
   Set<ir.VariableDeclaration> _currentVariables;
@@ -615,8 +627,8 @@
   ir.DartType visitConstructorInvocation(ir.ConstructorInvocation node) {
     ArgumentTypes argumentTypes = _visitArguments(node.arguments);
     ir.DartType resultType = node.arguments.types.isEmpty
-        ? node.target.enclosingClass.rawType
-        : new ir.InterfaceType(
+        ? new ExactInterfaceType.from(node.target.enclosingClass.rawType)
+        : new ExactInterfaceType(
             node.target.enclosingClass, node.arguments.types);
     _cache[node] = resultType;
     handleConstructorInvocation(node, argumentTypes, resultType);
@@ -637,8 +649,8 @@
       if (declaringClass.typeParameters.isEmpty) {
         resultType = node.interfaceTarget.getterType;
       } else {
-        ir.DartType receiver = typeEnvironment.getTypeAsInstanceOf(
-            typeEnvironment.thisType, declaringClass);
+        ir.DartType receiver =
+            typeEnvironment.getTypeAsInstanceOf(thisType, declaringClass);
         resultType = ir.Substitution.fromInterfaceType(receiver)
             .substituteType(node.interfaceTarget.getterType);
       }
@@ -670,8 +682,8 @@
       returnType = const ir.DynamicType();
     } else {
       ir.Class superclass = node.interfaceTarget.enclosingClass;
-      ir.InterfaceType receiverType = typeEnvironment.getTypeAsInstanceOf(
-          typeEnvironment.thisType, superclass);
+      ir.InterfaceType receiverType =
+          typeEnvironment.getTypeAsInstanceOf(thisType, superclass);
       returnType = ir.Substitution.fromInterfaceType(receiverType)
           .substituteType(node.interfaceTarget.function.returnType);
       returnType = ir.Substitution.fromPairs(
@@ -1158,22 +1170,21 @@
 
   @override
   Null visitProcedure(ir.Procedure node) {
-    typeEnvironment.thisType =
-        node.enclosingClass != null ? node.enclosingClass.thisType : null;
+    thisType = new ThisInterfaceType.from(node.enclosingClass?.thisType);
     _currentVariables = new Set<ir.VariableDeclaration>();
     visitSignature(node.function);
     visitNode(node.function.body);
     handleProcedure(node);
     _invalidatedVariables.removeAll(_currentVariables);
     _currentVariables = null;
-    typeEnvironment.thisType = null;
+    thisType = null;
   }
 
   void handleConstructor(ir.Constructor node) {}
 
   @override
   Null visitConstructor(ir.Constructor node) {
-    typeEnvironment.thisType = node.enclosingClass.thisType;
+    thisType = new ThisInterfaceType.from(node.enclosingClass.thisType);
     _currentVariables = new Set<ir.VariableDeclaration>();
     visitSignature(node.function);
     visitNodes(node.initializers);
@@ -1181,18 +1192,17 @@
     handleConstructor(node);
     _invalidatedVariables.removeAll(_currentVariables);
     _currentVariables = null;
-    typeEnvironment.thisType = null;
+    thisType = null;
   }
 
   void handleField(ir.Field node) {}
 
   @override
   Null visitField(ir.Field node) {
-    typeEnvironment.thisType =
-        node.enclosingClass != null ? node.enclosingClass.thisType : null;
+    thisType = new ThisInterfaceType.from(node.enclosingClass?.thisType);
     visitNode(node.initializer);
     handleField(node);
-    typeEnvironment.thisType = null;
+    thisType = null;
   }
 
   void handleVariableDeclaration(ir.VariableDeclaration node) {}
diff --git a/pkg/compiler/lib/src/ir/static_type_base.dart b/pkg/compiler/lib/src/ir/static_type_base.dart
index 6ab9972..20cf00c 100644
--- a/pkg/compiler/lib/src/ir/static_type_base.dart
+++ b/pkg/compiler/lib/src/ir/static_type_base.dart
@@ -13,6 +13,34 @@
 /// and return statements.
 class DoesNotCompleteType extends ir.BottomType {
   const DoesNotCompleteType();
+
+  String toString() => 'DoesNotCompleteType()';
+}
+
+/// Special interface type used to signal that the static type of an expression
+/// has precision of a this-expression.
+class ThisInterfaceType extends ir.InterfaceType {
+  ThisInterfaceType(ir.Class classNode, [List<ir.DartType> typeArguments])
+      : super(classNode, typeArguments);
+
+  factory ThisInterfaceType.from(ir.InterfaceType type) => type != null
+      ? new ThisInterfaceType(type.classNode, type.typeArguments)
+      : null;
+
+  String toString() => 'this:${super.toString()}';
+}
+
+/// Special interface type used to signal that the static type of an expression
+/// is exact, i.e. the runtime type is not a subtype or subclass of the type.
+class ExactInterfaceType extends ir.InterfaceType {
+  ExactInterfaceType(ir.Class classNode, [List<ir.DartType> typeArguments])
+      : super(classNode, typeArguments);
+
+  factory ExactInterfaceType.from(ir.InterfaceType type) => type != null
+      ? new ExactInterfaceType(type.classNode, type.typeArguments)
+      : null;
+
+  String toString() => 'exact:${super.toString()}';
 }
 
 /// Base class for computing static types.
@@ -25,7 +53,7 @@
 /// expression kind. For instance method invocations whose static type depend
 /// on the static types of the receiver and type arguments and the signature
 /// of the targeted procedure.
-class StaticTypeBase extends ir.Visitor<ir.DartType> {
+abstract class StaticTypeBase extends ir.Visitor<ir.DartType> {
   final ir.TypeEnvironment _typeEnvironment;
 
   StaticTypeBase(this._typeEnvironment);
@@ -34,6 +62,8 @@
 
   ir.TypeEnvironment get typeEnvironment => _typeEnvironment;
 
+  ThisInterfaceType get thisType;
+
   @override
   ir.DartType defaultNode(ir.Node node) {
     return null;
@@ -119,8 +149,7 @@
   }
 
   @override
-  ir.DartType visitThisExpression(ir.ThisExpression node) =>
-      typeEnvironment.thisType;
+  ThisInterfaceType visitThisExpression(ir.ThisExpression node) => thisType;
 
   @override
   ir.DartType visitStaticGet(ir.StaticGet node) => node.target.getterType;
diff --git a/pkg/compiler/lib/src/js_model/element_map_impl.dart b/pkg/compiler/lib/src/js_model/element_map_impl.dart
index ba6d29a..47433bc 100644
--- a/pkg/compiler/lib/src/js_model/element_map_impl.dart
+++ b/pkg/compiler/lib/src/js_model/element_map_impl.dart
@@ -33,6 +33,7 @@
 import '../ir/element_map.dart';
 import '../ir/types.dart';
 import '../ir/visitors.dart';
+import '../ir/static_type_base.dart';
 import '../ir/static_type_provider.dart';
 import '../ir/util.dart';
 import '../js/js.dart' as js;
@@ -1102,12 +1103,8 @@
     }
 
     assert(cachedStaticTypes != null, "No static types cached for $member.");
-    return new CachedStaticType(
-        // We need a copy of the type environment since the `thisType` field
-        // is holds state, making the environment contextually bound.
-        new ir.TypeEnvironment(typeEnvironment.coreTypes, classHierarchy)
-          ..thisType = thisType,
-        cachedStaticTypes);
+    return new CachedStaticType(typeEnvironment, cachedStaticTypes,
+        new ThisInterfaceType.from(thisType));
   }
 
   Name getName(ir.Name name) {
diff --git a/pkg/compiler/lib/src/serialization/abstract_source.dart b/pkg/compiler/lib/src/serialization/abstract_source.dart
index 28da0ca..28fdf8a 100644
--- a/pkg/compiler/lib/src/serialization/abstract_source.dart
+++ b/pkg/compiler/lib/src/serialization/abstract_source.dart
@@ -219,6 +219,8 @@
         return const ir.InvalidType();
       case DartTypeNodeKind.bottomType:
         return const ir.BottomType();
+      case DartTypeNodeKind.doesNotComplete:
+        return const DoesNotCompleteType();
       case DartTypeNodeKind.typeParameterType:
         ir.TypeParameter typeParameter = readTypeParameterNode();
         ir.DartType promotedBound = _readDartTypeNode(functionTypeVariables);
@@ -270,6 +272,16 @@
         List<ir.DartType> typeArguments =
             _readDartTypeNodes(functionTypeVariables);
         return new ir.InterfaceType(cls, typeArguments);
+      case DartTypeNodeKind.thisInterfaceType:
+        ir.Class cls = readClassNode();
+        List<ir.DartType> typeArguments =
+            _readDartTypeNodes(functionTypeVariables);
+        return new ThisInterfaceType(cls, typeArguments);
+      case DartTypeNodeKind.exactInterfaceType:
+        ir.Class cls = readClassNode();
+        List<ir.DartType> typeArguments =
+            _readDartTypeNodes(functionTypeVariables);
+        return new ExactInterfaceType(cls, typeArguments);
       case DartTypeNodeKind.typedef:
         ir.Typedef typedef = readTypedefNode();
         List<ir.DartType> typeArguments =
diff --git a/pkg/compiler/lib/src/serialization/helpers.dart b/pkg/compiler/lib/src/serialization/helpers.dart
index 589c6eef..a75b7e3 100644
--- a/pkg/compiler/lib/src/serialization/helpers.dart
+++ b/pkg/compiler/lib/src/serialization/helpers.dart
@@ -90,7 +90,7 @@
   interfaceType,
   typedef,
   dynamicType,
-  futureOr
+  futureOr,
 }
 
 /// Visitor that serializes [DartType] object together with [AbstractDataSink].
@@ -194,6 +194,9 @@
   dynamicType,
   bottomType,
   invalidType,
+  thisInterfaceType,
+  exactInterfaceType,
+  doesNotComplete,
 }
 
 const String functionTypeNodeTag = 'function-type-node';
@@ -235,12 +238,22 @@
 
   void visitBottomType(
       ir.BottomType node, List<ir.TypeParameter> functionTypeVariables) {
-    _sink.writeEnum(DartTypeNodeKind.bottomType);
+    if (node == const DoesNotCompleteType()) {
+      _sink.writeEnum(DartTypeNodeKind.doesNotComplete);
+    } else {
+      _sink.writeEnum(DartTypeNodeKind.bottomType);
+    }
   }
 
   void visitInterfaceType(
       ir.InterfaceType node, List<ir.TypeParameter> functionTypeVariables) {
-    _sink.writeEnum(DartTypeNodeKind.interfaceType);
+    if (node is ThisInterfaceType) {
+      _sink.writeEnum(DartTypeNodeKind.thisInterfaceType);
+    } else if (node is ExactInterfaceType) {
+      _sink.writeEnum(DartTypeNodeKind.exactInterfaceType);
+    } else {
+      _sink.writeEnum(DartTypeNodeKind.interfaceType);
+    }
     _sink.writeClassNode(node.classNode);
     visitTypes(node.typeArguments, functionTypeVariables);
   }
diff --git a/pkg/compiler/lib/src/serialization/serialization.dart b/pkg/compiler/lib/src/serialization/serialization.dart
index 2d2fd9b..3348edd 100644
--- a/pkg/compiler/lib/src/serialization/serialization.dart
+++ b/pkg/compiler/lib/src/serialization/serialization.dart
@@ -14,6 +14,7 @@
 import '../elements/entities.dart';
 import '../elements/indexed.dart';
 import '../elements/types.dart';
+import '../ir/static_type_base.dart';
 import '../js_model/closure.dart';
 import '../js_model/locals.dart';
 
diff --git a/tests/compiler/dart2js/impact/data/classes.dart b/tests/compiler/dart2js/impact/data/classes.dart
index a31b32f..bacab72 100644
--- a/tests/compiler/dart2js/impact/data/classes.dart
+++ b/tests/compiler/dart2js/impact/data/classes.dart
@@ -240,7 +240,7 @@
 }
 
 /*strong.element: testInstanceGenericMethod:
- dynamic=[GenericClass.genericMethod<bool>(1)],
+ dynamic=[exact:GenericClass.genericMethod<bool>(1)],
  static=[
   GenericClass.generative(0),
   assertIsSubtype,
diff --git a/tests/compiler/dart2js/impact/data/exact.dart b/tests/compiler/dart2js/impact/data/exact.dart
new file mode 100644
index 0000000..11a6d2d
--- /dev/null
+++ b/tests/compiler/dart2js/impact/data/exact.dart
@@ -0,0 +1,48 @@
+// Copyright (c) 2019, the Dart 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.
+
+/*element: A.:static=[Object.(0)]*/
+class A {
+  method1() {}
+  method2() {}
+  method3() {}
+}
+
+/*element: B.:static=[A.(0)]*/
+class B extends A {
+  method1() {}
+  method2() {}
+  method3() {}
+}
+
+/*element: C.:static=[B.(0)]*/
+class C extends B {
+  method1() {}
+  method2() {}
+  method3() {}
+}
+
+/*element: main:static=[callOnEffectivelyFinalB(0),callOnNewB(0),callOnNewC(0)]*/
+main() {
+  callOnNewB();
+  callOnNewC();
+  callOnEffectivelyFinalB();
+  callOnEffectivelyFinalB();
+}
+
+/*element: callOnNewB:dynamic=[exact:B.method1(0)],static=[B.(0)]*/
+callOnNewB() {
+  new B().method1();
+}
+
+/*element: callOnNewC:dynamic=[exact:C.method2(0)],static=[C.(0)]*/
+callOnNewC() {
+  new C().method2();
+}
+
+/*element: callOnEffectivelyFinalB:dynamic=[exact:B.method3(0)],static=[B.(0)]*/
+callOnEffectivelyFinalB() {
+  A a = new B();
+  a.method3();
+}
diff --git a/tests/compiler/dart2js/impact/data/runtime_type.dart b/tests/compiler/dart2js/impact/data/runtime_type.dart
index 93e027c..96fac37 100644
--- a/tests/compiler/dart2js/impact/data/runtime_type.dart
+++ b/tests/compiler/dart2js/impact/data/runtime_type.dart
@@ -379,7 +379,7 @@
 notEquals4(Class3 a, Class4 b) => a?.runtimeType != b?.runtimeType;
 
 /*element: main:
- dynamic=[Class1a.==],
+ dynamic=[exact:Class1a.==],
  static=[
   Class1a.(0),
   Class1b.(0),
diff --git a/tests/compiler/dart2js/model/strong_mode_closed_world_test.dart b/tests/compiler/dart2js/model/strong_mode_closed_world_test.dart
index d721dc7..aeb3108 100644
--- a/tests/compiler/dart2js/model/strong_mode_closed_world_test.dart
+++ b/tests/compiler/dart2js/model/strong_mode_closed_world_test.dart
@@ -17,11 +17,18 @@
 }
 
 runTest() async {
-  CompilationResult result = await runCompiler(memorySourceFiles: {
-    'main.dart': '''
+  // Pretend this is a dart2js_native test to allow use of 'native' keyword
+  // and import of private libraries.
+  String main = 'sdk/tests/compiler/dart2js_native/main.dart';
+  Uri entryPoint = Uri.parse('memory:$main');
+
+  CompilationResult result =
+      await runCompiler(entryPoint: entryPoint, memorySourceFiles: {
+    main: '''
 class A {
   method1() {}
   method2() {}
+  method4() {}
   get getter => 42;
   set setter(_) {}
 }
@@ -29,6 +36,7 @@
 class B {
   method1() {}
   method2() {}
+  method5() {}
   get getter => 42;
   set setter(_) {}
 }
@@ -36,6 +44,7 @@
 class C extends A {
   method1() {}
   method2() {}
+  method4() {} 
   get getter => 42;
   set setter(_) {}
 }
@@ -43,6 +52,7 @@
 class D implements B {
   method1() {}
   method2() {}
+  method5() {}
   get getter => 42;
   set setter(_) {}
 }
@@ -50,6 +60,7 @@
 class E implements A {
   method1() {}
   method2() {}
+  method4() {}
   get getter => 42;
   set setter(_) {}
 }
@@ -57,6 +68,7 @@
 class F extends B {
   method1() {}
   method2() {}
+  method5() {}
   get getter => 42;
   set setter(_) {}
 }
@@ -64,6 +76,7 @@
 class G {
   method1() {}
   method2() {}
+  method4() {} 
   get getter => 42;
   set setter(_) {}
 }
@@ -73,6 +86,7 @@
 class I {
   method1() {}
   method2() {}
+  method4() {}
   get getter => 42;
   set setter(_) {}
 }
@@ -124,6 +138,12 @@
 }
 
 main() {
+  method1();
+  method2();
+}
+
+@pragma('dart2js:disableFinal')
+method1() {
   A a = new A();
   B b = new B();
   a.method1();
@@ -148,14 +168,21 @@
   new Class1b();
   new Class2().c(0, 1, 2);
 }
+
+method2() {
+  A a = new A();
+  B b = new B();
+  a.method4();
+  b.method5();
+}
 '''
   });
   Expect.isTrue(result.isSuccess);
   Compiler compiler = result.compiler;
 
   Map<String, List<String>> expectedLiveMembersMap = <String, List<String>>{
-    'A': ['method1', 'getter'],
-    'B': ['method2', 'setter'],
+    'A': ['method1', 'getter', 'method4'],
+    'B': ['method2', 'setter', 'method5'],
     'C': ['method1', 'getter'],
     'D': ['method2', 'setter'],
     'G': ['method1', 'getter'],
diff --git a/tests/compiler/dart2js/serialization/data/custom_types.dart b/tests/compiler/dart2js/serialization/data/custom_types.dart
new file mode 100644
index 0000000..a301473
--- /dev/null
+++ b/tests/compiler/dart2js/serialization/data/custom_types.dart
@@ -0,0 +1,31 @@
+// Copyright (c) 2018, the Dart 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.
+
+class A {
+  method() {
+    Object c = this;
+    return c;
+  }
+}
+
+main() {
+  exactInterfaceType();
+  thisInterfaceType();
+  doesNotCompleteType();
+}
+
+exactInterfaceType() {
+  Object c = new A();
+  return c;
+}
+
+thisInterfaceType() {
+  new A().method();
+}
+
+doesNotCompleteType() {
+  Object c = throw '';
+  // ignore: dead_code
+  return c;
+}
diff --git a/tests/compiler/dart2js/static_type/static_type_test.dart b/tests/compiler/dart2js/static_type/static_type_test.dart
index 070d0f6..1f935d8 100644
--- a/tests/compiler/dart2js/static_type/static_type_test.dart
+++ b/tests/compiler/dart2js/static_type/static_type_test.dart
@@ -8,6 +8,7 @@
 import 'package:compiler/src/diagnostics/diagnostic_listener.dart';
 import 'package:compiler/src/elements/entities.dart';
 import 'package:compiler/src/ir/cached_static_type.dart';
+import 'package:compiler/src/ir/static_type_base.dart';
 import 'package:compiler/src/kernel/element_map_impl.dart';
 import 'package:compiler/src/kernel/kernel_strategy.dart';
 import 'package:kernel/ast.dart' as ir;
@@ -55,7 +56,9 @@
             compiler.reporter,
             actualMap,
             new CachedStaticType(
-                getTypeEnvironment(elementMap), staticTypeCache))
+                getTypeEnvironment(elementMap),
+                staticTypeCache,
+                new ThisInterfaceType.from(node.enclosingClass?.thisType)))
         .run(node);
   }