Support exact class and this expression relations in StrongModeConstraint

Change-Id: I32e6e5b6f55d4ef4240b6ba87eb26dfc2a699130
Reviewed-on: https://dart-review.googlesource.com/c/85166
Reviewed-by: Sigmund Cherem <sigmund@google.com>
Commit-Queue: Johnni Winther <johnniwinther@google.com>
diff --git a/pkg/compiler/lib/src/ir/static_type.dart b/pkg/compiler/lib/src/ir/static_type.dart
index 2402479..78e4277 100644
--- a/pkg/compiler/lib/src/ir/static_type.dart
+++ b/pkg/compiler/lib/src/ir/static_type.dart
@@ -10,6 +10,20 @@
 import 'scope.dart';
 import 'static_type_base.dart';
 
+/// Enum values for how the target of a static type should be interpreted.
+enum ClassRelation {
+  /// The target is any subtype of the static type.
+  subtype,
+
+  /// The target is a subclass or mixin application of the static type.
+  ///
+  /// This corresponds to accessing a member through a this expression.
+  thisExpression,
+
+  /// The target is an exact instance of the static type.
+  exact,
+}
+
 /// Visitor that computes and caches the static type of expression while
 /// visiting the full tree at expression level.
 ///
diff --git a/pkg/compiler/lib/src/kernel/kernel_impact.dart b/pkg/compiler/lib/src/kernel/kernel_impact.dart
index 0e7cf76..f6bcf24 100644
--- a/pkg/compiler/lib/src/kernel/kernel_impact.dart
+++ b/pkg/compiler/lib/src/kernel/kernel_impact.dart
@@ -507,7 +507,7 @@
       ir.DartType returnType) {
     Selector selector = elementMap.getSelector(node);
     List<DartType> typeArguments = _getTypeArguments(node.arguments);
-    var receiver = node.receiver;
+    ir.Expression receiver = node.receiver;
     if (receiver is ir.VariableGet &&
         receiver.variable.isFinal &&
         receiver.variable.parent is ir.FunctionDeclaration) {
@@ -523,6 +523,9 @@
       impactBuilder.registerDynamicUse(
           new ConstrainedDynamicUse(selector, null, typeArguments));
     } else {
+      ClassRelation relation = receiver is ir.ThisExpression
+          ? ClassRelation.thisExpression
+          : ClassRelation.subtype;
       DartType receiverDartType = elementMap.getDartType(receiverType);
 
       ir.Member interfaceTarget = node.interfaceTarget;
@@ -538,8 +541,8 @@
       } else {
         Object constraint;
         if (receiverDartType is InterfaceType) {
-          constraint = new StrongModeConstraint(
-              commonElements, _nativeBasicData, receiverDartType.element);
+          constraint = new StrongModeConstraint(commonElements,
+              _nativeBasicData, receiverDartType.element, relation);
         }
 
         if (interfaceTarget is ir.Field ||
@@ -575,8 +578,11 @@
     Object constraint;
     DartType receiverDartType = elementMap.getDartType(receiverType);
     if (receiverDartType is InterfaceType) {
+      ClassRelation relation = node.receiver is ir.ThisExpression
+          ? ClassRelation.thisExpression
+          : ClassRelation.subtype;
       constraint = new StrongModeConstraint(
-          commonElements, _nativeBasicData, receiverDartType.element);
+          commonElements, _nativeBasicData, receiverDartType.element, relation);
     }
     impactBuilder.registerDynamicUse(new ConstrainedDynamicUse(
         new Selector.getter(elementMap.getName(node.name)),
@@ -614,8 +620,11 @@
     Object constraint;
     DartType receiverDartType = elementMap.getDartType(receiverType);
     if (receiverDartType is InterfaceType) {
+      ClassRelation relation = node.receiver is ir.ThisExpression
+          ? ClassRelation.thisExpression
+          : ClassRelation.subtype;
       constraint = new StrongModeConstraint(
-          commonElements, _nativeBasicData, receiverDartType.element);
+          commonElements, _nativeBasicData, receiverDartType.element, relation);
     }
     impactBuilder.registerDynamicUse(new ConstrainedDynamicUse(
         new Selector.setter(elementMap.getName(node.name)),
diff --git a/pkg/compiler/lib/src/kernel/kernel_world.dart b/pkg/compiler/lib/src/kernel/kernel_world.dart
index 563fc9c..af7cde7 100644
--- a/pkg/compiler/lib/src/kernel/kernel_world.dart
+++ b/pkg/compiler/lib/src/kernel/kernel_world.dart
@@ -16,7 +16,6 @@
 import '../js_backend/runtime_types.dart';
 import '../options.dart';
 import '../universe/class_hierarchy.dart';
-import '../universe/class_set.dart';
 import '../universe/resolution_world_builder.dart';
 import '../world.dart';
 
@@ -74,12 +73,9 @@
       this.processedMembers,
       this.mixinUses,
       this.typesImplementedBySubclasses,
-      Map<ClassEntity, ClassHierarchyNode> classHierarchyNodes,
-      Map<ClassEntity, ClassSet> classSets,
+      this.classHierarchy,
       this.annotationsData})
-      : _implementedClasses = implementedClasses,
-        classHierarchy = new ClassHierarchyImpl(
-            commonElements, classHierarchyNodes, classSets) {
+      : _implementedClasses = implementedClasses {
     _rtiNeed = rtiNeedBuilder.computeRuntimeTypesNeed(
         resolutionWorldBuilder, this, options);
   }
diff --git a/pkg/compiler/lib/src/universe/class_hierarchy.dart b/pkg/compiler/lib/src/universe/class_hierarchy.dart
index 7032550..181714f 100644
--- a/pkg/compiler/lib/src/universe/class_hierarchy.dart
+++ b/pkg/compiler/lib/src/universe/class_hierarchy.dart
@@ -571,9 +571,9 @@
 class ClassHierarchyBuilder {
   // We keep track of subtype and subclass relationships in four
   // distinct sets to make class hierarchy analysis faster.
-  final Map<ClassEntity, ClassHierarchyNode> classHierarchyNodes =
+  final Map<ClassEntity, ClassHierarchyNode> _classHierarchyNodes =
       <ClassEntity, ClassHierarchyNode>{};
-  final Map<ClassEntity, ClassSet> classSets = <ClassEntity, ClassSet>{};
+  final Map<ClassEntity, ClassSet> _classSets = <ClassEntity, ClassSet>{};
   final Map<ClassEntity, Set<ClassEntity>> mixinUses =
       new Map<ClassEntity, Set<ClassEntity>>();
 
@@ -582,12 +582,22 @@
 
   ClassHierarchyBuilder(this._commonElements, this._classQueries);
 
+  ClassHierarchy close() {
+    assert(
+        _classHierarchyNodes.length == _classSets.length,
+        "ClassHierarchyNode/ClassSet mismatch: "
+        "${_classHierarchyNodes} vs "
+        "${_classSets}");
+    return new ClassHierarchyImpl(
+        _commonElements, _classHierarchyNodes, _classSets);
+  }
+
   void registerClass(ClassEntity cls) {
     _ensureClassSet(_classQueries.getDeclaration(cls));
   }
 
   ClassHierarchyNode _ensureClassHierarchyNode(ClassEntity cls) {
-    return classHierarchyNodes.putIfAbsent(cls, () {
+    return _classHierarchyNodes.putIfAbsent(cls, () {
       ClassHierarchyNode parentNode;
       ClassEntity superclass = _classQueries.getSuperClass(cls);
       if (superclass != null) {
@@ -599,7 +609,7 @@
   }
 
   ClassSet _ensureClassSet(ClassEntity cls) {
-    return classSets.putIfAbsent(cls, () {
+    return _classSets.putIfAbsent(cls, () {
       ClassHierarchyNode node = _ensureClassHierarchyNode(cls);
       ClassSet classSet = new ClassSet(node);
 
@@ -665,55 +675,170 @@
   }
 
   bool _isSubtypeOf(ClassEntity x, ClassEntity y) {
-    assert(
-        classSets.containsKey(x), "ClassSet for $x has not been computed yet.");
-    ClassSet classSet = classSets[y];
+    assert(_classSets.containsKey(x),
+        "ClassSet for $x has not been computed yet.");
+    ClassSet classSet = _classSets[y];
     assert(classSet != null,
-        failedAt(y, "No ClassSet for $y (${y.runtimeType}): ${classSets}"));
-    ClassHierarchyNode classHierarchyNode = classHierarchyNodes[x];
+        failedAt(y, "No ClassSet for $y (${y.runtimeType}): ${_classSets}"));
+    ClassHierarchyNode classHierarchyNode = _classHierarchyNodes[x];
     assert(classHierarchyNode != null,
         failedAt(x, "No ClassHierarchyNode for $x"));
     return classSet.hasSubtype(classHierarchyNode);
   }
 
-  Map<ClassEntity, _InheritedCache> _inheritedCacheMap = {};
+  /// Returns `true` if a dynamic access on an instance of [exactClass] can
+  /// target a member declared in [memberHoldingClass].
+  bool isInheritedInExactClass(
+      ClassEntity memberHoldingClass, ClassEntity exactClass) {
+    ClassHierarchyNode exactClassNode = _classHierarchyNodes[exactClass];
+    if (!exactClassNode.isAbstractlyInstantiated &&
+        !exactClassNode.isDirectlyInstantiated) {
+      // No instances of [thisClass] are live.
+      return false;
+    }
+    ClassSet memberHoldingClassSet = _classSets[memberHoldingClass];
+    if (memberHoldingClassSet.hasSubclass(exactClassNode)) {
+      /// A member from a super class can be accessed.
+      return true;
+    }
+    for (ClassHierarchyNode mixinApplication
+        in memberHoldingClassSet.mixinApplicationNodes) {
+      if (mixinApplication.hasSubclass(exactClassNode)) {
+        /// A member from a mixed in class can be accessed.
+        return true;
+      }
+    }
+    return false;
+  }
+
+  Map<ClassEntity, _InheritedInThisClassCache> _inheritedInThisClassCacheMap =
+      {};
+
+  /// Returns `true` if a `this` expression in [thisClass] can target a member
+  /// declared in [memberHoldingClass].
+  bool isInheritedInThisClass(
+      ClassEntity memberHoldingClass, ClassEntity thisClass) {
+    _InheritedInThisClassCache cache =
+        _inheritedInThisClassCacheMap[memberHoldingClass] ??=
+            new _InheritedInThisClassCache();
+    return cache.isInheritedInThisClassOf(this, memberHoldingClass, thisClass);
+  }
+
+  Map<ClassEntity, _InheritedInSubtypeCache> _inheritedInSubtypeCacheMap = {};
 
   bool isInheritedInSubtypeOf(ClassEntity x, ClassEntity y) {
-    _InheritedCache cache = _inheritedCacheMap[x] ??= new _InheritedCache();
+    _InheritedInSubtypeCache cache =
+        _inheritedInSubtypeCacheMap[x] ??= new _InheritedInSubtypeCache();
     return cache.isInheritedInSubtypeOf(this, x, y);
   }
 }
 
+/// Cache used for computing when a member of a given class, the so-called
+/// member holding class, can be inherited into a live class.
+class _InheritedInThisClassCache {
+  /// Set of classes that inherits members from the member holding class.
+  Set<ClassEntity> _inheritingClasses;
+
+  /// Cache for liveness computation for a `this` expressions of a given class.
+  Map<ClassEntity, _LiveSet> _map;
+
+  /// Returns `true` if members of [memberHoldingClass] can be inherited into
+  /// a live class that can be the target of a `this` expression in [thisClass].
+  bool isInheritedInThisClassOf(ClassHierarchyBuilder builder,
+      ClassEntity memberHoldingClass, ClassEntity thisClass) {
+    _LiveSet set;
+    if (_map == null) {
+      _map = {};
+    } else {
+      set = _map[thisClass];
+    }
+    if (set == null) {
+      set = _map[thisClass] = _computeInheritingInThisClassSet(
+          builder, memberHoldingClass, thisClass);
+    }
+    return set.hasLiveClass(builder);
+  }
+
+  _LiveSet _computeInheritingInThisClassSet(ClassHierarchyBuilder builder,
+      ClassEntity memberHoldingClass, ClassEntity thisClass) {
+    ClassHierarchyNode memberHoldingClassNode =
+        builder._classHierarchyNodes[memberHoldingClass];
+
+    if (_inheritingClasses == null) {
+      _inheritingClasses = new Set<ClassEntity>();
+      _inheritingClasses.addAll(memberHoldingClassNode
+          .subclassesByMask(ClassHierarchyNode.ALL, strict: false));
+      for (ClassHierarchyNode mixinApplication
+          in builder._classSets[memberHoldingClass].mixinApplicationNodes) {
+        _inheritingClasses.addAll(mixinApplication
+            .subclassesByMask(ClassHierarchyNode.ALL, strict: false));
+      }
+    }
+
+    Set<ClassEntity> validatingSet = new Set<ClassEntity>();
+
+    void processHierarchy(ClassHierarchyNode mixerNode) {
+      for (ClassEntity inheritingClass in _inheritingClasses) {
+        ClassHierarchyNode inheritingClassNode =
+            builder._classHierarchyNodes[inheritingClass];
+        if (!validatingSet.contains(mixerNode.cls) &&
+            inheritingClassNode.hasSubclass(mixerNode)) {
+          // If [mixerNode.cls] is live then a `this` expression can target
+          // members inherited from [memberHoldingClass] into [inheritingClass].
+          validatingSet.add(mixerNode.cls);
+        }
+        if (mixerNode.hasSubclass(inheritingClassNode)) {
+          // If [inheritingClass] is live then a `this` expression can target
+          // members inherited from [memberHoldingClass] into `inheritingClass`
+          // into a subclass of [mixerNode.cls].
+          validatingSet.add(inheritingClass);
+        }
+      }
+    }
+
+    ClassSet thisClassSet = builder._classSets[thisClass];
+
+    processHierarchy(thisClassSet.node);
+
+    for (ClassHierarchyNode mixinApplication
+        in thisClassSet.mixinApplicationNodes) {
+      processHierarchy(mixinApplication);
+    }
+
+    return new _LiveSet(validatingSet);
+  }
+}
+
 /// A cache object used for [ClassHierarchyBuilder.isInheritedInSubtypeOf].
-class _InheritedCache {
-  Map<ClassEntity, _InheritingSet> _map;
+class _InheritedInSubtypeCache {
+  Map<ClassEntity, _LiveSet> _map;
 
   /// Returns whether a live class currently known to inherit from [x] and
   /// implement [y].
   bool isInheritedInSubtypeOf(
       ClassHierarchyBuilder builder, ClassEntity x, ClassEntity y) {
-    _InheritingSet set;
+    _LiveSet set;
     if (_map == null) {
       _map = {};
     } else {
       set = _map[y];
     }
     if (set == null) {
-      set = _map[y] = _computeInheritingSet(builder, x, y);
+      set = _map[y] = _computeInheritingInSubtypeSet(builder, x, y);
     }
     return set.hasLiveClass(builder);
   }
 
-  /// Creates an [_InheritingSet] of classes that inherit members of a class [x]
+  /// Creates an [_LiveSet] of classes that inherit members of a class [x]
   /// while implementing class [y].
-  _InheritingSet _computeInheritingSet(
+  _LiveSet _computeInheritingInSubtypeSet(
       ClassHierarchyBuilder builder, ClassEntity x, ClassEntity y) {
-    ClassSet classSet = builder.classSets[x];
+    ClassSet classSet = builder._classSets[x];
 
     assert(
         classSet != null,
         failedAt(
-            x, "No ClassSet for $x (${x.runtimeType}): ${builder.classSets}"));
+            x, "No ClassSet for $x (${x.runtimeType}): ${builder._classSets}"));
 
     Set<ClassEntity> classes = new Set<ClassEntity>();
 
@@ -740,16 +865,16 @@
       subclassImplements(mixinApplication, strict: false);
     }
 
-    return new _InheritingSet(classes);
+    return new _LiveSet(classes);
   }
 }
 
-/// A set of classes that inherit members of a class 'x' while implementing
-/// class 'y'.
+/// A set of potentially live classes.
 ///
-/// The set is used [ClassHierarchyBuilder.isInheritedInSubtypeOf] to determine
+/// The set is used [ClassHierarchyBuilder.isInheritedInSubtypeOf] and
+/// [ClassHierarchyBuilder.isInheritedInThisClassOf] to determine
 /// when members of a class is live.
-class _InheritingSet {
+class _LiveSet {
   /// If `true` the set of classes is known to contain a live class. In this
   /// case [_classes] is `null`. If `false` the set of classes is empty and
   /// therefore known never to contain live classes. In this case [_classes]
@@ -758,7 +883,7 @@
   bool _result;
   Set<ClassEntity> _classes;
 
-  _InheritingSet(Set<ClassEntity> classes)
+  _LiveSet(Set<ClassEntity> classes)
       : _result = classes.isEmpty ? false : null,
         _classes = classes.isNotEmpty ? classes : null;
 
@@ -777,7 +902,7 @@
   bool hasLiveClass(ClassHierarchyBuilder builder) {
     if (_result != null) return _result;
     for (ClassEntity cls in _classes) {
-      if (builder.classHierarchyNodes[cls].isInstantiated) {
+      if (builder._classHierarchyNodes[cls].isInstantiated) {
         // We now know this set contains a live class and done need to remember
         // that set of classes anymore.
         _result = true;
diff --git a/pkg/compiler/lib/src/universe/class_set.dart b/pkg/compiler/lib/src/universe/class_set.dart
index 85d96a2..fa29bf7 100644
--- a/pkg/compiler/lib/src/universe/class_set.dart
+++ b/pkg/compiler/lib/src/universe/class_set.dart
@@ -612,10 +612,16 @@
     return true;
   }
 
+  /// Returns an [Iterable] of the classes that implement [cls] directly or
+  /// through supertypes.
+  ///
+  /// A class that implements [cls] through its superclasses is not included in
+  /// the iterable.
   Iterable<ClassHierarchyNode> get subtypeNodes {
     return _subtypes ?? const <ClassHierarchyNode>[];
   }
 
+  /// Returns an [Iterable] of the classes that mix in [cls] directly.
   Iterable<ClassHierarchyNode> get mixinApplicationNodes {
     return _mixinApplications ?? const <ClassHierarchyNode>[];
   }
diff --git a/pkg/compiler/lib/src/universe/resolution_world_builder.dart b/pkg/compiler/lib/src/universe/resolution_world_builder.dart
index 88cee2e..e396467 100644
--- a/pkg/compiler/lib/src/universe/resolution_world_builder.dart
+++ b/pkg/compiler/lib/src/universe/resolution_world_builder.dart
@@ -8,6 +8,7 @@
 import '../constants/values.dart';
 import '../elements/entities.dart';
 import '../elements/types.dart';
+import '../ir/static_type.dart';
 import '../js_backend/annotations.dart';
 import '../js_backend/allocator_analysis.dart' show KAllocatorAnalysis;
 import '../js_backend/backend_usage.dart'
@@ -970,11 +971,28 @@
     _classHierarchyBuilder.registerClass(cls);
   }
 
-  bool isInheritedInSubtypeOf(MemberEntity member, ClassEntity type) {
+  @override
+  bool isInheritedIn(
+      MemberEntity member, ClassEntity type, ClassRelation relation) {
     // TODO(johnniwinther): Use the [member] itself to avoid enqueueing members
     // that are overridden.
-    return _classHierarchyBuilder.isInheritedInSubtypeOf(
-        member.enclosingClass, type);
+    return isInheritedInClass(member.enclosingClass, type, relation);
+  }
+
+  bool isInheritedInClass(ClassEntity memberHoldingClass, ClassEntity type,
+      ClassRelation relation) {
+    switch (relation) {
+      case ClassRelation.exact:
+        return _classHierarchyBuilder.isInheritedInExactClass(
+            memberHoldingClass, type);
+      case ClassRelation.thisExpression:
+        return _classHierarchyBuilder.isInheritedInThisClass(
+            memberHoldingClass, type);
+      case ClassRelation.subtype:
+        return _classHierarchyBuilder.isInheritedInSubtypeOf(
+            memberHoldingClass, type);
+    }
+    throw new UnsupportedError("Unexpected ClassRelation $relation.");
   }
 
   @override
@@ -1000,12 +1018,6 @@
 
     BackendUsage backendUsage = _backendUsageBuilder.close();
     _closed = true;
-    assert(
-        _classHierarchyBuilder.classHierarchyNodes.length ==
-            _classHierarchyBuilder.classSets.length,
-        "ClassHierarchyNode/ClassSet mismatch: "
-        "${_classHierarchyBuilder.classHierarchyNodes} vs "
-        "${_classHierarchyBuilder.classSets}");
 
     AnnotationsData annotationsData = processAnnotations(
         reporter, _commonElements, _elementEnvironment, _processedMembers);
@@ -1029,8 +1041,7 @@
         processedMembers: _processedMembers,
         mixinUses: _classHierarchyBuilder.mixinUses,
         typesImplementedBySubclasses: typesImplementedBySubclasses,
-        classHierarchyNodes: _classHierarchyBuilder.classHierarchyNodes,
-        classSets: _classHierarchyBuilder.classSets,
+        classHierarchy: _classHierarchyBuilder.close(),
         annotationsData: annotationsData);
     if (retainDataForTesting) {
       _closedWorldCache = closedWorld;
diff --git a/pkg/compiler/lib/src/universe/use.dart b/pkg/compiler/lib/src/universe/use.dart
index 5e7b719..a5e2b41 100644
--- a/pkg/compiler/lib/src/universe/use.dart
+++ b/pkg/compiler/lib/src/universe/use.dart
@@ -46,6 +46,11 @@
     if (receiverConstraint != null) {
       var constraint = receiverConstraint;
       if (constraint is StrongModeConstraint) {
+        if (constraint.isThis) {
+          sb.write('<');
+        } else if (constraint.isExact) {
+          sb.write('=');
+        }
         sb.write(constraint.cls.name);
       } else {
         sb.write(constraint);
diff --git a/pkg/compiler/lib/src/universe/world_builder.dart b/pkg/compiler/lib/src/universe/world_builder.dart
index 31838fc..9eb4560 100644
--- a/pkg/compiler/lib/src/universe/world_builder.dart
+++ b/pkg/compiler/lib/src/universe/world_builder.dart
@@ -7,6 +7,7 @@
 import '../common_elements.dart';
 import '../elements/entities.dart';
 import '../elements/types.dart';
+import '../ir/static_type.dart';
 import '../js_backend/native_data.dart' show NativeBasicData;
 import '../world.dart' show World, JClosedWorld, OpenWorld;
 import 'selector.dart' show Selector;
@@ -158,34 +159,42 @@
 
 class StrongModeConstraint {
   final ClassEntity cls;
+  final ClassRelation relation;
 
   factory StrongModeConstraint(CommonElements commonElements,
-      NativeBasicData nativeBasicData, ClassEntity cls) {
+      NativeBasicData nativeBasicData, ClassEntity cls,
+      [ClassRelation relation = ClassRelation.subtype]) {
     if (nativeBasicData.isJsInteropClass(cls)) {
       // We can not tell js-interop classes apart, so we just assume the
       // receiver could be any js-interop class.
       cls = commonElements.jsJavaScriptObjectClass;
+      relation = ClassRelation.subtype;
     }
-    return new StrongModeConstraint.internal(cls);
+    return new StrongModeConstraint.internal(cls, relation);
   }
 
-  const StrongModeConstraint.internal(this.cls);
+  const StrongModeConstraint.internal(this.cls, this.relation);
 
   bool needsNoSuchMethodHandling(Selector selector, World world) => true;
 
   bool canHit(MemberEntity element, Selector selector, OpenWorld world) {
-    return world.isInheritedInSubtypeOf(element, cls);
+    return world.isInheritedIn(element, cls, relation);
   }
 
-  bool operator ==(other) {
+  bool get isExact => relation == ClassRelation.exact;
+
+  bool get isThis => relation == ClassRelation.thisExpression;
+
+  bool operator ==(Object other) {
     if (identical(this, other)) return true;
-    if (other is! StrongModeConstraint) return false;
-    return cls == other.cls;
+    return other is StrongModeConstraint &&
+        cls == other.cls &&
+        relation == other.relation;
   }
 
   int get hashCode => cls.hashCode * 13;
 
-  String toString() => 'StrongModeConstraint($cls)';
+  String toString() => 'StrongModeConstraint($cls,$relation)';
 }
 
 /// The [WorldBuilder] is an auxiliary class used in the process of computing
diff --git a/pkg/compiler/lib/src/world.dart b/pkg/compiler/lib/src/world.dart
index 01ba8f9..6a0fd25 100644
--- a/pkg/compiler/lib/src/world.dart
+++ b/pkg/compiler/lib/src/world.dart
@@ -17,6 +17,7 @@
 import 'diagnostics/diagnostic_listener.dart';
 import 'elements/entities.dart';
 import 'elements/types.dart';
+import 'ir/static_type.dart';
 import 'js_backend/annotations.dart';
 import 'js_backend/allocator_analysis.dart'
     show JAllocatorAnalysis, KAllocatorAnalysis;
@@ -225,10 +226,11 @@
   ///     abstract class I { m(); }
   ///     abstract class J implements A { }
   ///
-  /// Here `A.m` is inherited into `A`, `B`, and `C`. Becausec `B` and
-  /// `C` implement `I`, `isInheritedInSubtypeOf(A.M, I)` is true, but
-  /// `isInheritedInSubtypeOf(A.M, J)` is false.
-  bool isInheritedInSubtypeOf(MemberEntity member, ClassEntity type);
+  /// Here `A.m` is inherited into `A`, `B`, and `C`. Because `B` and
+  /// `C` implement `I`, `isInheritedInSubtypeOf(A.m, I)` is true, but
+  /// `isInheritedInSubtypeOf(A.m, J)` is false.
+  bool isInheritedIn(
+      MemberEntity member, ClassEntity type, ClassRelation relation);
 }
 
 abstract class KClosedWorld {
diff --git a/tests/compiler/dart2js/analyses/dart2js_allowed.json b/tests/compiler/dart2js/analyses/dart2js_allowed.json
index ea21856..920dc49 100644
--- a/tests/compiler/dart2js/analyses/dart2js_allowed.json
+++ b/tests/compiler/dart2js/analyses/dart2js_allowed.json
@@ -195,9 +195,6 @@
     "Dynamic access of 'names'.": 1,
     "Dynamic access of 'isNonLeaf'.": 1
   },
-  "pkg/compiler/lib/src/universe/world_builder.dart": {
-    "Dynamic access of 'cls'.": 1
-  },
   "pkg/compiler/lib/src/elements/names.dart": {
     "Dynamic access of 'library'.": 1
   },
diff --git a/tests/compiler/dart2js/impact/data/runtime_type_strong.dart b/tests/compiler/dart2js/impact/data/runtime_type_strong.dart
index 63d2dea..32fa0a7 100644
--- a/tests/compiler/dart2js/impact/data/runtime_type_strong.dart
+++ b/tests/compiler/dart2js/impact/data/runtime_type_strong.dart
@@ -5,7 +5,7 @@
 /*element: Class1a.:static=[Object.(0)]*/
 class Class1a<T> {
   /*element: Class1a.==:
-   dynamic=[Class1a.runtimeType,Object.runtimeType,Type.==],
+   dynamic=[<Class1a.runtimeType,Object.runtimeType,Type.==],
    runtimeType=[equals:Class1a<Class1a.T>/dynamic]
   */
   bool operator ==(other) {
@@ -16,7 +16,7 @@
 /*element: Class1b.:static=[Class1a.(0)]*/
 class Class1b<T> extends Class1a<T> {
   /*element: Class1b.==:
-   dynamic=[Class1b.runtimeType,Object.runtimeType,Type.==],
+   dynamic=[<Class1b.runtimeType,Object.runtimeType,Type.==],
    runtimeType=[equals:dynamic/Class1b<Class1b.T>]
   */
   bool operator ==(other) {
@@ -27,7 +27,7 @@
 /*element: Class1c.:static=[Object.(0)]*/
 class Class1c<T> implements Class1a<T> {
   /*element: Class1c.==:
-   dynamic=[Class1c.runtimeType,Object.==,Object.runtimeType,Type.==],
+   dynamic=[<Class1c.runtimeType,Object.==,Object.runtimeType,Type.==],
    runtimeType=[equals:Class1c<Class1c.T>/dynamic],
    type=[inst:JSNull]
   */
@@ -39,7 +39,7 @@
 /*element: Class1d.:static=[Object.(0)]*/
 class Class1d<T> implements Class1a<T> {
   /*element: Class1d.==:
-   dynamic=[Class1d.runtimeType,Object.==,Object.runtimeType,Type.==],
+   dynamic=[<Class1d.runtimeType,Object.==,Object.runtimeType,Type.==],
    runtimeType=[equals:dynamic/Class1d<Class1d.T>],
    type=[inst:JSNull]
   */
diff --git a/tests/compiler/dart2js/impact/data/this.dart b/tests/compiler/dart2js/impact/data/this.dart
new file mode 100644
index 0000000..2579dda
--- /dev/null
+++ b/tests/compiler/dart2js/impact/data/this.dart
@@ -0,0 +1,69 @@
+// 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.
+
+/*element: Class.:static=[Object.(0)]*/
+class Class {
+  /*element: Class.field1:type=[inst:JSNull]*/
+  var field1;
+
+  /*element: Class.field2:type=[inst:JSNull]*/
+  var field2;
+
+  /*element: Class.method1:dynamic=[<Class.method2(0)]*/
+  method1() {
+    method2();
+  }
+
+  /*element: Class.method2:dynamic=[<Class.field1=,<Class.field2]*/
+  method2() {
+    field1 = field2;
+  }
+}
+
+/*element: Subclass.:static=[Class.(0)]*/
+class Subclass extends Class {
+  /*element: Subclass.field1:type=[inst:JSNull]*/
+  var field1;
+  /*element: Subclass.field2:type=[inst:JSNull]*/
+  var field2;
+
+  /*element: Subclass.method1:*/
+  method1() {}
+
+  /*element: Subclass.method2:dynamic=[<Subclass.method3(0)]*/
+  method2() {
+    method3();
+  }
+
+  method3() {}
+}
+
+/*element: Subtype.:static=[Object.(0)]*/
+class Subtype implements Class {
+  /*element: Subtype.field1:type=[inst:JSNull]*/
+  var field1;
+  /*element: Subtype.field2:type=[inst:JSNull]*/
+  var field2;
+
+  method1() {}
+
+  method2() {
+    method4();
+  }
+
+  method4() {
+    method2();
+  }
+}
+
+/*element: main:
+ dynamic=[Class.method1(0)],
+ static=[Class.(0),Subclass.(0),Subtype.(0)]
+*/
+main() {
+  var c = new Class();
+  c = new Subclass();
+  c = new Subtype();
+  c.method1();
+}
diff --git a/tests/compiler/dart2js/model/open_world_test.dart b/tests/compiler/dart2js/model/open_world_test.dart
new file mode 100644
index 0000000..b315b5c
--- /dev/null
+++ b/tests/compiler/dart2js/model/open_world_test.dart
@@ -0,0 +1,434 @@
+// 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.
+
+import 'package:expect/expect.dart';
+import 'package:async_helper/async_helper.dart';
+import 'package:compiler/src/compiler.dart';
+import 'package:compiler/src/common_elements.dart';
+import 'package:compiler/src/elements/entities.dart';
+import 'package:compiler/src/ir/static_type.dart';
+import 'package:compiler/src/js_backend/native_data.dart';
+import 'package:compiler/src/universe/resolution_world_builder.dart';
+import 'package:compiler/src/universe/world_builder.dart';
+import '../helpers/memory_compiler.dart';
+
+main() {
+  asyncTest(() async {
+    await runTest();
+  });
+}
+
+runTest() async {
+  String classes = '''
+@JS()
+library lib;
+
+import 'package:js/js.dart';
+
+class A {}
+class A1 extends A {}
+class A2 extends A1 {}
+
+class B implements A {}
+class B1 extends B {}
+
+class C {}
+class C0 {}
+class C1 = C with A;
+class C2 extends C1 {}
+class C3 = C with C0, A;
+class C4 = C with A, C0;
+
+@JS()
+class D {}
+
+@JS()
+class D1 extends D {}
+
+''';
+
+  CommonElements commonElements;
+  NativeBasicData nativeBasicData;
+  ResolutionWorldBuilderImpl world;
+
+  List<ClassEntity> allClasses;
+
+  ClassEntity A;
+  ClassEntity A1;
+  ClassEntity A2;
+  ClassEntity B;
+  ClassEntity B1;
+  ClassEntity C;
+  ClassEntity C0;
+  ClassEntity C1;
+  ClassEntity C2;
+  ClassEntity C3;
+  ClassEntity C4;
+  ClassEntity D;
+  ClassEntity D1;
+
+  List<ClassRelation> allRelations = ClassRelation.values;
+  List<ClassRelation> notExact = [
+    ClassRelation.thisExpression,
+    ClassRelation.subtype
+  ];
+  List<ClassRelation> subtype = [ClassRelation.subtype];
+
+  run(List<String> liveClasses) async {
+    String source = '''
+$classes
+main() {
+${liveClasses.map((c) => '  new $c();').join('\n')}
+}
+''';
+    print('------------------------------------------------------------------');
+    print(source);
+    CompilationResult result =
+        await runCompiler(memorySourceFiles: {'main.dart': source});
+    Expect.isTrue(result.isSuccess);
+    Compiler compiler = result.compiler;
+    commonElements = compiler.frontendStrategy.commonElements;
+    ElementEnvironment elementEnvironment =
+        compiler.frontendStrategy.elementEnvironment;
+    nativeBasicData = compiler.frontendStrategy.nativeBasicData;
+    world = compiler.resolutionWorldBuilder;
+
+    ClassEntity findClass(String name) {
+      ClassEntity cls =
+          elementEnvironment.lookupClass(elementEnvironment.mainLibrary, name);
+      Expect.isNotNull(cls, 'Class $name not found.');
+      return cls;
+    }
+
+    allClasses = [
+      A = findClass('A'),
+      A1 = findClass('A1'),
+      A2 = findClass('A2'),
+      B = findClass('B'),
+      B1 = findClass('B1'),
+      C = findClass('C'),
+      C0 = findClass('C0'),
+      C1 = findClass('C1'),
+      C2 = findClass('C2'),
+      C3 = findClass('C3'),
+      C4 = findClass('C4'),
+      D = findClass('D'),
+      D1 = findClass('D1'),
+    ];
+  }
+
+  void check(
+      Map<ClassEntity, Map<ClassEntity, List<ClassRelation>>> expectedResults) {
+    for (ClassEntity cls in allClasses) {
+      for (ClassEntity memberHoldingClass in allClasses) {
+        Map<ClassEntity, List<ClassRelation>> memberResults =
+            expectedResults[memberHoldingClass] ?? {};
+        for (ClassRelation relation in allRelations) {
+          List<ClassRelation> expectRelations = memberResults[cls];
+          bool expectedResult =
+              expectRelations != null && expectRelations.contains(relation);
+          StrongModeConstraint constraint = new StrongModeConstraint(
+              commonElements, nativeBasicData, cls, relation);
+          Expect.equals(
+              expectedResult,
+              world.isInheritedInClass(
+                  memberHoldingClass, constraint.cls, constraint.relation),
+              "Unexpected results for member of $memberHoldingClass on a "
+              "receiver $constraint (cls=$cls, relation=$relation)");
+        }
+      }
+    }
+  }
+
+  await run([]);
+  check({});
+
+  await run(['A']);
+  check({
+    A: {A: allRelations},
+  });
+
+  await run(['A1']);
+  check({
+    A: {
+      A: notExact,
+      A1: allRelations,
+    },
+    A1: {
+      A: notExact,
+      A1: allRelations,
+    },
+  });
+
+  await run(['A', 'A1']);
+  check({
+    A: {
+      A: allRelations,
+      A1: allRelations,
+    },
+    A1: {
+      A: notExact,
+      A1: allRelations,
+    },
+  });
+
+  await run(['A2']);
+  check({
+    A: {
+      A: notExact,
+      A1: notExact,
+      A2: allRelations,
+    },
+    A1: {
+      A: notExact,
+      A1: notExact,
+      A2: allRelations,
+    },
+    A2: {
+      A: notExact,
+      A1: notExact,
+      A2: allRelations,
+    },
+  });
+
+  await run(['A', 'A2']);
+  check({
+    A: {
+      A: allRelations,
+      A1: notExact,
+      A2: allRelations,
+    },
+    A1: {
+      A: notExact,
+      A1: notExact,
+      A2: allRelations,
+    },
+    A2: {
+      A: notExact,
+      A1: notExact,
+      A2: allRelations,
+    },
+  });
+
+  await run(['A1', 'A2']);
+  check({
+    A: {
+      A: notExact,
+      A1: allRelations,
+      A2: allRelations,
+    },
+    A1: {
+      A: notExact,
+      A1: allRelations,
+      A2: allRelations,
+    },
+    A2: {
+      A: notExact,
+      A1: notExact,
+      A2: allRelations,
+    },
+  });
+
+  await run(['B']);
+  check({
+    B: {
+      A: subtype,
+      B: allRelations,
+    },
+  });
+
+  await run(['B1']);
+  check({
+    B: {
+      A: subtype,
+      B: notExact,
+      B1: allRelations,
+    },
+    B1: {
+      A: subtype,
+      B: notExact,
+      B1: allRelations,
+    },
+  });
+
+  await run(['A', 'A2', 'B']);
+  check({
+    A: {
+      A: allRelations,
+      A1: notExact,
+      A2: allRelations,
+    },
+    A1: {
+      A: notExact,
+      A1: notExact,
+      A2: allRelations,
+    },
+    A2: {
+      A: notExact,
+      A1: notExact,
+      A2: allRelations,
+    },
+    B: {A: subtype, B: allRelations},
+  });
+
+  await run(['C']);
+  check({
+    C: {
+      C: allRelations,
+    },
+  });
+
+  await run(['C1']);
+  check({
+    A: {
+      A: notExact,
+      C: notExact,
+      C1: allRelations,
+    },
+    C: {
+      A: notExact,
+      C: notExact,
+      C1: allRelations,
+    },
+    C1: {
+      A: notExact,
+      C: notExact,
+      C1: allRelations,
+    },
+  });
+
+  await run(['C2']);
+  check({
+    A: {
+      A: notExact,
+      C: notExact,
+      C1: notExact,
+      C2: allRelations,
+    },
+    C: {
+      A: notExact,
+      C: notExact,
+      C1: notExact,
+      C2: allRelations,
+    },
+    C1: {
+      A: notExact,
+      C: notExact,
+      C1: notExact,
+      C2: allRelations,
+    },
+    C2: {
+      A: notExact,
+      C: notExact,
+      C1: notExact,
+      C2: allRelations,
+    },
+  });
+
+  await run(['C3']);
+  check({
+    A: {
+      A: notExact,
+      C: notExact,
+      C0: notExact,
+      C3: allRelations,
+    },
+    C: {
+      A: notExact,
+      C: notExact,
+      C0: notExact,
+      C3: allRelations,
+    },
+    C0: {
+      A: notExact,
+      C: notExact,
+      C0: notExact,
+      C3: allRelations,
+    },
+    C3: {
+      A: notExact,
+      C: notExact,
+      C0: notExact,
+      C3: allRelations,
+    },
+  });
+
+  await run(['C4']);
+  check({
+    A: {
+      A: notExact,
+      C: notExact,
+      C0: notExact,
+      C4: allRelations,
+    },
+    C: {
+      A: notExact,
+      C: notExact,
+      C0: notExact,
+      C4: allRelations,
+    },
+    C0: {
+      A: notExact,
+      C: notExact,
+      C0: notExact,
+      C4: allRelations,
+    },
+    C4: {
+      A: notExact,
+      C: notExact,
+      C0: notExact,
+      C4: allRelations,
+    },
+  });
+
+  await run(['A2', 'C1']);
+  check({
+    A: {
+      A: notExact,
+      A1: notExact,
+      A2: allRelations,
+      C: notExact,
+      C1: allRelations,
+    },
+    A1: {
+      A: notExact,
+      A1: notExact,
+      A2: allRelations,
+    },
+    A2: {
+      A: notExact,
+      A1: notExact,
+      A2: allRelations,
+    },
+    C: {
+      A: notExact,
+      C: notExact,
+      C1: allRelations,
+    },
+    C1: {
+      A: notExact,
+      C: notExact,
+      C1: allRelations,
+    },
+  });
+
+  await run(['D']);
+  check({
+    D: {
+      D: allRelations,
+      D1: allRelations,
+    },
+  });
+  await run(['D1']);
+  check({
+    D: {
+      D: allRelations,
+      D1: allRelations,
+    },
+    D1: {
+      D: allRelations,
+      D1: allRelations,
+    },
+  });
+}