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,
+ },
+ });
+}