Introduce isAbstractlyInstantiated to avoid exact masks of abstract classes.

Classes can be instantiated directly, abstractly or indirectly:

When [cls] is directly instantiated: This means that at runtime instances of exactly [cls] are assumed to exist.

When [cls] is abstractly instantiated: This means that at runtime instances of [cls] or unknown subclasses of [cls] are assumed to exist.

This is used to mark native and/or reflectable classes as instantiated. For native classes we do not know the exact class that instantiates [cls] so [cls] here represents the root of the subclasses. For reflectable classes we need event abstract classes to be 'live' even though they cannot themselves be instantiated.

When [cls] is indirectly instantiated: This means that a subclass of [cls] is directly or abstractly instantiated.

R=sigmund@google.com

Review URL: https://codereview.chromium.org/2443953002 .
This commit is contained in:
Johnni Winther 2016-10-28 08:45:57 +02:00
parent bdeb6023af
commit b0ecbef3cc
8 changed files with 387 additions and 60 deletions

View file

@ -481,9 +481,7 @@ class SimpleTypeInferrerVisitor<T>
});
}
if (analyzedElement.isGenerativeConstructor && cls.isAbstract) {
if (compiler.closedWorld.isDirectlyInstantiated(cls)) {
returnType = types.nonNullExact(cls);
} else if (compiler.closedWorld.isIndirectlyInstantiated(cls)) {
if (compiler.closedWorld.isInstantiated(cls)) {
returnType = types.nonNullSubclass(cls);
} else {
// TODO(johnniwinther): Avoid analyzing [analyzedElement] in this

View file

@ -88,6 +88,8 @@ class UnionTypeMask implements TypeMask {
}
static TypeMask flatten(List<FlatTypeMask> masks, ClosedWorld closedWorld) {
// TODO(johnniwinther): Move this computation to [ClosedWorld] and use the
// class set structures.
assert(masks.length > 1);
// If either type mask is a subtype type mask, we cannot use a
// subclass type mask to represent their union.
@ -104,7 +106,7 @@ class UnionTypeMask implements TypeMask {
for (Entity candidate in candidates) {
bool isInstantiatedStrictSubclass(cls) =>
cls != candidate &&
closedWorld.isDirectlyInstantiated(cls) &&
closedWorld.isExplicitlyInstantiated(cls) &&
closedWorld.isSubclassOf(cls, candidate);
int size;

View file

@ -6,6 +6,7 @@ library dart2js.world.class_set;
import 'dart:collection' show IterableBase;
import '../common.dart';
import '../elements/elements.dart' show ClassElement;
import '../util/enumset.dart' show EnumSet;
import '../util/util.dart' show Link;
@ -15,6 +16,7 @@ enum Instantiation {
UNINSTANTIATED,
DIRECTLY_INSTANTIATED,
INDIRECTLY_INSTANTIATED,
ABSTRACTLY_INSTANTIATED,
}
/// Node for [cls] in a tree forming the subclass relation of [ClassElement]s.
@ -49,16 +51,18 @@ class ClassHierarchyNode {
static final EnumSet<Instantiation> INSTANTIATED =
new EnumSet<Instantiation>.fromValues(const <Instantiation>[
Instantiation.DIRECTLY_INSTANTIATED,
Instantiation.INDIRECTLY_INSTANTIATED
Instantiation.INDIRECTLY_INSTANTIATED,
Instantiation.ABSTRACTLY_INSTANTIATED,
], fixed: true);
/// Enum set for selecting directly instantiated classes in
/// Enum set for selecting directly and abstractly instantiated classes in
/// [ClassHierarchyNode.subclassesByMask],
/// [ClassHierarchyNode.subclassesByMask] and [ClassSet.subtypesByMask].
static final EnumSet<Instantiation> DIRECTLY_INSTANTIATED =
new EnumSet<Instantiation>.fromValues(
const <Instantiation>[Instantiation.DIRECTLY_INSTANTIATED],
fixed: true);
static final EnumSet<Instantiation> EXPLICITLY_INSTANTIATED =
new EnumSet<Instantiation>.fromValues(const <Instantiation>[
Instantiation.DIRECTLY_INSTANTIATED,
Instantiation.ABSTRACTLY_INSTANTIATED
], fixed: true);
/// Enum set for selecting all classes in
/// [ClassHierarchyNode.subclassesByMask],
@ -72,7 +76,8 @@ class ClassHierarchyNode {
static EnumSet<Instantiation> createMask(
{bool includeDirectlyInstantiated: true,
bool includeIndirectlyInstantiated: true,
bool includeUninstantiated: true}) {
bool includeUninstantiated: true,
bool includeAbstractlyInstantiated: true}) {
EnumSet<Instantiation> mask = new EnumSet<Instantiation>();
if (includeDirectlyInstantiated) {
mask.add(Instantiation.DIRECTLY_INSTANTIATED);
@ -83,6 +88,9 @@ class ClassHierarchyNode {
if (includeUninstantiated) {
mask.add(Instantiation.UNINSTANTIATED);
}
if (includeAbstractlyInstantiated) {
mask.add(Instantiation.ABSTRACTLY_INSTANTIATED);
}
return mask;
}
@ -106,23 +114,54 @@ class ClassHierarchyNode {
void set isDirectlyInstantiated(bool value) {
if (value != isDirectlyInstantiated) {
ClassHierarchyNode parent = parentNode;
if (value) {
_mask.remove(Instantiation.UNINSTANTIATED);
_mask.add(Instantiation.DIRECTLY_INSTANTIATED);
while (parent != null) {
parent._updateInstantiatedSubclassCount(1);
parent = parent.parentNode;
}
} else {
_mask.remove(Instantiation.DIRECTLY_INSTANTIATED);
if (_mask.isEmpty) {
_mask.add(Instantiation.UNINSTANTIATED);
}
while (parent != null) {
parent._updateInstantiatedSubclassCount(-1);
parent = parent.parentNode;
}
_updateParentInstantiatedSubclassCount(
Instantiation.DIRECTLY_INSTANTIATED,
add: value);
}
}
/// `true` if [cls] has been abstractly instantiated. This means that at
/// runtime instances of [cls] or unknown subclasses of [cls] are assumed to
/// exist.
///
/// This is used to mark native and/or reflectable classes as instantiated.
/// For native classes we do not know the exact class that instantiates [cls]
/// so [cls] here represents the root of the subclasses. For reflectable
/// classes we need event abstract classes to be 'live' even though they
/// cannot themselves be instantiated.
bool get isAbstractlyInstantiated =>
_mask.contains(Instantiation.ABSTRACTLY_INSTANTIATED);
void set isAbstractlyInstantiated(bool value) {
if (value != isAbstractlyInstantiated) {
_updateParentInstantiatedSubclassCount(
Instantiation.ABSTRACTLY_INSTANTIATED,
add: value);
}
}
/// `true` if [cls] is either directly or abstractly instantiated.
bool get isExplicitlyInstantiated =>
isDirectlyInstantiated || isAbstractlyInstantiated;
void _updateParentInstantiatedSubclassCount(Instantiation instantiation,
{bool add}) {
ClassHierarchyNode parent = parentNode;
if (add) {
_mask.remove(Instantiation.UNINSTANTIATED);
_mask.add(instantiation);
while (parent != null) {
parent._updateInstantiatedSubclassCount(1);
parent = parent.parentNode;
}
} else {
_mask.remove(instantiation);
if (_mask.isEmpty) {
_mask.add(Instantiation.UNINSTANTIATED);
}
while (parent != null) {
parent._updateInstantiatedSubclassCount(-1);
parent = parent.parentNode;
}
}
}
@ -188,8 +227,9 @@ class ClassHierarchyNode {
return false;
}
/// `true` if [cls] has been directly or indirectly instantiated.
bool get isInstantiated => isDirectlyInstantiated || isIndirectlyInstantiated;
/// `true` if [cls] has been directly, indirectly, or abstractly instantiated.
bool get isInstantiated =>
isExplicitlyInstantiated || isIndirectlyInstantiated;
/// Returns an [Iterable] of the subclasses of [cls] possibly including [cls].
///
@ -270,9 +310,12 @@ class ClassHierarchyNode {
}
ClassElement _computeLeastUpperInstantiatedSubclass() {
if (isDirectlyInstantiated) {
if (isExplicitlyInstantiated) {
return cls;
}
if (!isInstantiated) {
return null;
}
ClassHierarchyNode subclass;
for (Link<ClassHierarchyNode> link = _directSubclasses;
!link.isEmpty;
@ -311,6 +354,9 @@ class ClassHierarchyNode {
if (isIndirectlyInstantiated) {
sb.write(' indirectly');
}
if (isAbstractlyInstantiated) {
sb.write(' abstractly');
}
sb.write(' [');
if (_directSubclasses.isEmpty) {
sb.write(']');
@ -439,7 +485,7 @@ class ClassSet {
int count = node.instantiatedSubclassCount;
if (_subtypes != null) {
for (ClassHierarchyNode subtypeNode in _subtypes) {
if (subtypeNode.isDirectlyInstantiated) {
if (subtypeNode.isExplicitlyInstantiated) {
count++;
}
count += subtypeNode.instantiatedSubclassCount;
@ -647,7 +693,7 @@ class ClassSet {
}
ClassElement _computeLeastUpperInstantiatedSubtype() {
if (node.isDirectlyInstantiated) {
if (node.isExplicitlyInstantiated) {
return cls;
}
if (_subtypes == null) {

View file

@ -10,6 +10,8 @@ import '../common.dart';
import '../compiler.dart' show Compiler;
import '../dart_types.dart';
import '../elements/elements.dart';
import '../universe/class_set.dart' show Instantiation;
import '../util/enumset.dart';
import '../util/util.dart';
import '../world.dart' show World, ClosedWorld, OpenWorld;
import 'selector.dart' show Selector;
@ -156,6 +158,10 @@ abstract class ResolutionWorldBuilder implements WorldBuilder {
/// Set of all fields that are statically known to be written to.
Iterable<Element> get fieldSetters;
/// Call [f] for all directly or abstractly instantiated classes.
void forEachInstantiatedClass(
f(ClassElement cls, EnumSet<Instantiation> instantiations));
}
class ResolutionWorldBuilderImpl implements ResolutionWorldBuilder {
@ -166,8 +172,8 @@ class ResolutionWorldBuilderImpl implements ResolutionWorldBuilder {
/// Invariant: Elements are declaration elements.
// TODO(johnniwinther): [_directlyInstantiatedClasses] and
// [_instantiatedTypes] sets should be merged.
final Set<ClassElement> _directlyInstantiatedClasses =
new Set<ClassElement>();
final Map<ClassElement, EnumSet<Instantiation>> _directlyInstantiatedClasses =
<ClassElement, EnumSet<Instantiation>>{};
/// The set of all directly instantiated types, that is, the types of the
/// directly instantiated classes.
@ -237,7 +243,7 @@ class ResolutionWorldBuilderImpl implements ResolutionWorldBuilder {
/// super-call.
// TODO(johnniwinther): Improve semantic precision.
Iterable<ClassElement> get directlyInstantiatedClasses {
return _directlyInstantiatedClasses;
return _directlyInstantiatedClasses.keys;
}
/// All directly instantiated types, that is, the types of the directly
@ -280,7 +286,13 @@ class ResolutionWorldBuilderImpl implements ResolutionWorldBuilder {
// TODO(herhut): Track classes required by mirrors seperately.
||
byMirrors) {
_directlyInstantiatedClasses.add(cls);
EnumSet<Instantiation> instantiations = _directlyInstantiatedClasses
.putIfAbsent(cls, () => new EnumSet<Instantiation>());
if (isNative || byMirrors) {
instantiations.add(Instantiation.ABSTRACTLY_INSTANTIATED);
} else {
instantiations.add(Instantiation.DIRECTLY_INSTANTIATED);
}
}
// TODO(johnniwinther): Replace this by separate more specific mappings that
@ -295,6 +307,12 @@ class ResolutionWorldBuilderImpl implements ResolutionWorldBuilder {
}
}
@override
void forEachInstantiatedClass(
f(ClassElement cls, EnumSet<Instantiation> instantiations)) {
_directlyInstantiatedClasses.forEach(f);
}
bool _hasMatchingSelector(Map<Selector, SelectorConstraints> selectors,
Element member, OpenWorld world) {
if (selectors == null) return false;

View file

@ -25,6 +25,7 @@ import 'universe/class_set.dart';
import 'universe/function_set.dart' show FunctionSet;
import 'universe/selector.dart' show Selector;
import 'universe/side_effects.dart' show SideEffects;
import 'util/enumset.dart';
import 'util/util.dart' show Link;
/// Common superinterface for [OpenWorld] and [ClosedWorld].
@ -48,9 +49,26 @@ abstract class ClosedWorld implements World {
/// Returns `true` if [cls] is either directly or indirectly instantiated.
bool isInstantiated(ClassElement cls);
/// Returns `true` if [cls] is directly instantiated.
/// Returns `true` if [cls] is directly instantiated. This means that at
/// runtime instances of exactly [cls] are assumed to exist.
bool isDirectlyInstantiated(ClassElement cls);
/// Returns `true` if [cls] is abstractly instantiated. This means that at
/// runtime instances of [cls] or unknown subclasses of [cls] are assumed to
/// exist.
///
/// This is used to mark native and/or reflectable classes as instantiated.
/// For native classes we do not know the exact class that instantiates [cls]
/// so [cls] here represents the root of the subclasses. For reflectable
/// classes we need event abstract classes to be 'live' even though they
/// cannot themselves be instantiated.
bool isAbstractlyInstantiated(ClassElement cls);
/// Returns `true` if [cls] is either directly or abstractly instantiated.
///
/// See [isDirectlyInstantiated] and [isAbstractlyInstantiated].
bool isExplicitlyInstantiated(ClassElement cls);
/// Returns `true` if [cls] is indirectly instantiated, that is through a
/// subclass.
bool isIndirectlyInstantiated(ClassElement cls);
@ -392,6 +410,20 @@ class WorldImpl implements ClosedWorld, ClosedWorldRefiner, OpenWorld {
return node != null && node.isDirectlyInstantiated;
}
@override
bool isAbstractlyInstantiated(ClassElement cls) {
assert(isClosed);
ClassHierarchyNode node = _classHierarchyNodes[cls.declaration];
return node != null && node.isAbstractlyInstantiated;
}
@override
bool isExplicitlyInstantiated(ClassElement cls) {
assert(isClosed);
ClassHierarchyNode node = _classHierarchyNodes[cls.declaration];
return node != null && node.isExplicitlyInstantiated;
}
@override
bool isIndirectlyInstantiated(ClassElement cls) {
assert(isClosed);
@ -414,7 +446,8 @@ class WorldImpl implements ClosedWorld, ClosedWorldRefiner, OpenWorld {
assert(isClosed);
ClassHierarchyNode hierarchy = _classHierarchyNodes[cls.declaration];
if (hierarchy == null) return const <ClassElement>[];
return hierarchy.subclassesByMask(ClassHierarchyNode.DIRECTLY_INSTANTIATED);
return hierarchy
.subclassesByMask(ClassHierarchyNode.EXPLICITLY_INSTANTIATED);
}
/// Returns an iterable over the directly instantiated classes that extend
@ -423,7 +456,8 @@ class WorldImpl implements ClosedWorld, ClosedWorldRefiner, OpenWorld {
assert(isClosed);
ClassHierarchyNode subclasses = _classHierarchyNodes[cls.declaration];
if (subclasses == null) return const <ClassElement>[];
return subclasses.subclassesByMask(ClassHierarchyNode.DIRECTLY_INSTANTIATED,
return subclasses.subclassesByMask(
ClassHierarchyNode.EXPLICITLY_INSTANTIATED,
strict: true);
}
@ -443,7 +477,7 @@ class WorldImpl implements ClosedWorld, ClosedWorldRefiner, OpenWorld {
assert(isClosed);
ClassHierarchyNode subclasses = _classHierarchyNodes[cls.declaration];
if (subclasses == null) return;
subclasses.forEachSubclass(f, ClassHierarchyNode.DIRECTLY_INSTANTIATED,
subclasses.forEachSubclass(f, ClassHierarchyNode.EXPLICITLY_INSTANTIATED,
strict: true);
}
@ -454,7 +488,7 @@ class WorldImpl implements ClosedWorld, ClosedWorldRefiner, OpenWorld {
ClassHierarchyNode subclasses = _classHierarchyNodes[cls.declaration];
if (subclasses == null) return false;
return subclasses.anySubclass(
predicate, ClassHierarchyNode.DIRECTLY_INSTANTIATED,
predicate, ClassHierarchyNode.EXPLICITLY_INSTANTIATED,
strict: true);
}
@ -466,7 +500,8 @@ class WorldImpl implements ClosedWorld, ClosedWorldRefiner, OpenWorld {
if (classSet == null) {
return const <ClassElement>[];
} else {
return classSet.subtypesByMask(ClassHierarchyNode.DIRECTLY_INSTANTIATED);
return classSet
.subtypesByMask(ClassHierarchyNode.EXPLICITLY_INSTANTIATED);
}
}
@ -478,7 +513,7 @@ class WorldImpl implements ClosedWorld, ClosedWorldRefiner, OpenWorld {
if (classSet == null) {
return const <ClassElement>[];
} else {
return classSet.subtypesByMask(ClassHierarchyNode.DIRECTLY_INSTANTIATED,
return classSet.subtypesByMask(ClassHierarchyNode.EXPLICITLY_INSTANTIATED,
strict: true);
}
}
@ -499,7 +534,7 @@ class WorldImpl implements ClosedWorld, ClosedWorldRefiner, OpenWorld {
assert(isClosed);
ClassSet classSet = _classSets[cls.declaration];
if (classSet == null) return;
classSet.forEachSubtype(f, ClassHierarchyNode.DIRECTLY_INSTANTIATED,
classSet.forEachSubtype(f, ClassHierarchyNode.EXPLICITLY_INSTANTIATED,
strict: true);
}
@ -510,7 +545,7 @@ class WorldImpl implements ClosedWorld, ClosedWorldRefiner, OpenWorld {
ClassSet classSet = _classSets[cls.declaration];
if (classSet == null) return false;
return classSet.anySubtype(
predicate, ClassHierarchyNode.DIRECTLY_INSTANTIATED,
predicate, ClassHierarchyNode.EXPLICITLY_INSTANTIATED,
strict: true);
}
@ -769,7 +804,7 @@ class WorldImpl implements ClosedWorld, ClosedWorldRefiner, OpenWorld {
// The root subclass has a concrete implementation so no subclass needs
// noSuchMethod handling.
return false;
} else if (rootNode.isDirectlyInstantiated) {
} else if (rootNode.isExplicitlyInstantiated) {
// The root class need noSuchMethod handling.
return true;
}
@ -781,7 +816,7 @@ class WorldImpl implements ClosedWorld, ClosedWorldRefiner, OpenWorld {
// Stop fast - we found a need for noSuchMethod handling.
return IterationStep.STOP;
}
}, ClassHierarchyNode.DIRECTLY_INSTANTIATED, strict: true);
}, ClassHierarchyNode.EXPLICITLY_INSTANTIATED, strict: true);
// We stopped fast so we need noSuchMethod handling.
return result == IterationStep.STOP;
}
@ -943,19 +978,22 @@ class WorldImpl implements ClosedWorld, ClosedWorldRefiner, OpenWorld {
}
void _updateClassHierarchyNodeForClass(ClassElement cls,
{bool directlyInstantiated: false}) {
{bool directlyInstantiated: false, bool abstractlyInstantiated: false}) {
ClassHierarchyNode node = getClassHierarchyNode(cls);
_updateSuperClassHierarchyNodeForClass(node);
if (directlyInstantiated) {
node.isDirectlyInstantiated = true;
}
if (abstractlyInstantiated) {
node.isAbstractlyInstantiated = true;
}
}
ClosedWorld closeWorld() {
/// Updates the `isDirectlyInstantiated` and `isIndirectlyInstantiated`
/// properties of the [ClassHierarchyNode] for [cls].
void addSubtypes(ClassElement cls) {
void addSubtypes(ClassElement cls, EnumSet<Instantiation> instantiations) {
if (_compiler.options.hasIncrementalSupport &&
!alreadyPopulated.add(cls)) {
return;
@ -965,7 +1003,11 @@ class WorldImpl implements ClosedWorld, ClosedWorldRefiner, OpenWorld {
reporter.internalError(cls, 'Class "${cls.name}" is not resolved.');
}
_updateClassHierarchyNodeForClass(cls, directlyInstantiated: true);
_updateClassHierarchyNodeForClass(cls,
directlyInstantiated:
instantiations.contains(Instantiation.DIRECTLY_INSTANTIATED),
abstractlyInstantiated:
instantiations.contains(Instantiation.ABSTRACTLY_INSTANTIATED));
// Walk through the superclasses, and record the types
// implemented by that type on the superclasses.
@ -985,7 +1027,7 @@ class WorldImpl implements ClosedWorld, ClosedWorldRefiner, OpenWorld {
// classes: if the superclass of these classes require RTI, then
// they also need RTI, so that a constructor passes the type
// variables to the super constructor.
_compiler.resolverWorld.directlyInstantiatedClasses.forEach(addSubtypes);
_compiler.resolverWorld.forEachInstantiatedClass(addSubtypes);
_closed = true;
return this;

View file

@ -529,7 +529,7 @@ testForEach() async {
checkForEach(X, [X, A, B, D, C, G, F, I, H, E],
skipSubclasses: [D], forEachSubtype: true);
checkForEach(X, [A, D, C, G, F, I, H, E],
forEachSubtype: true, mask: ClassHierarchyNode.DIRECTLY_INSTANTIATED);
forEachSubtype: true, mask: ClassHierarchyNode.EXPLICITLY_INSTANTIATED);
checkForEach(X, [A, B, D, C, G, F, I, H, E],
forEachSubtype: true, mask: ClassHierarchyNode.INSTANTIATED);

View file

@ -21,6 +21,7 @@ void main() {
testClasses() async {
test(String mainSource,
{List<String> directlyInstantiated: const <String>[],
List<String> abstractlyInstantiated: const <String>[],
List<String> indirectlyInstantiated: const <String>[]}) async {
TypeEnvironment env = await TypeEnvironment.create(
r"""
@ -120,6 +121,13 @@ $mainSource
"Expected $name to be directly instantiated in `${mainSource}`:"
"\n${world.dump(cls)}");
}
if (abstractlyInstantiated.contains(name)) {
isInstantiated = true;
Expect.isTrue(
world.isAbstractlyInstantiated(cls),
"Expected $name to be abstractly instantiated in `${mainSource}`:"
"\n${world.dump(cls)}");
}
if (indirectlyInstantiated.contains(name)) {
isInstantiated = true;
Expect.isTrue(
@ -139,19 +147,19 @@ $mainSource
await test('main() {}');
await test('main() => newA();',
directlyInstantiated: ['A', 'B', 'C', 'D'],
abstractlyInstantiated: ['A', 'B', 'C', 'D'],
indirectlyInstantiated: ['Object', 'Interceptor', 'JavaScriptObject']);
await test('main() => newB();',
directlyInstantiated: ['A', 'B', 'C', 'D'],
abstractlyInstantiated: ['A', 'B', 'C', 'D'],
indirectlyInstantiated: ['Object', 'Interceptor', 'JavaScriptObject']);
await test('main() => newC();',
directlyInstantiated: ['A', 'B', 'C', 'D'],
abstractlyInstantiated: ['A', 'B', 'C', 'D'],
indirectlyInstantiated: ['Object', 'Interceptor', 'JavaScriptObject']);
await test('main() => newD();',
directlyInstantiated: ['A', 'B', 'C', 'D'],
abstractlyInstantiated: ['A', 'B', 'C', 'D'],
indirectlyInstantiated: ['Object', 'Interceptor', 'JavaScriptObject']);
await test('main() => newE();', directlyInstantiated: ['E']);
@ -159,6 +167,7 @@ $mainSource
await test('main() => newF();', directlyInstantiated: ['F']);
await test('main() => [newD(), newE()];',
directlyInstantiated: ['A', 'B', 'C', 'D', 'E'],
directlyInstantiated: ['E'],
abstractlyInstantiated: ['A', 'B', 'C', 'D'],
indirectlyInstantiated: ['Object', 'Interceptor', 'JavaScriptObject']);
}

View file

@ -7,8 +7,9 @@ library world_test;
import 'package:expect/expect.dart';
import 'package:async_helper/async_helper.dart';
import 'type_test_helper.dart';
import 'package:compiler/src/common.dart';
import 'package:compiler/src/elements/elements.dart' show Element, ClassElement;
import 'package:compiler/src/common/names.dart';
import 'package:compiler/src/elements/elements.dart'
show Element, ClassElement, LibraryElement;
import 'package:compiler/src/universe/class_set.dart';
import 'package:compiler/src/world.dart' show ClosedWorld;
@ -16,6 +17,7 @@ void main() {
asyncTest(() async {
await testClassSets();
await testProperties();
await testNativeClasses();
});
}
@ -33,6 +35,7 @@ testClassSets() async {
class X {}
""",
mainSource: r"""
import 'dart:html' as html;
main() {
new A();
new B();
@ -41,6 +44,8 @@ testClassSets() async {
new E();
new F();
new G();
html.window;
new html.Worker('');
}
""",
useMockCompiler: false);
@ -298,3 +303,210 @@ testProperties() async {
check("H3", hasStrictSubtype: false, hasOnlySubclasses: true);
check("H4", hasStrictSubtype: false, hasOnlySubclasses: true);
}
testNativeClasses() async {
var env = await TypeEnvironment.create('',
mainSource: r"""
import 'dart:html' as html;
main() {
html.window; // Creates 'Window'.
new html.Worker(''); // Creates 'Worker'.
new html.CanvasElement() // Creates CanvasElement
..getContext(''); // Creates CanvasRenderingContext2D
}
""",
useMockCompiler: false);
ClosedWorld closedWorld = env.compiler.closedWorld;
LibraryElement dart_html =
env.compiler.libraryLoader.lookupLibrary(Uris.dart_html);
ClassElement clsEventTarget = dart_html.findExported('EventTarget');
ClassElement clsWindow = dart_html.findExported('Window');
ClassElement clsAbstractWorker = dart_html.findExported('AbstractWorker');
ClassElement clsWorker = dart_html.findExported('Worker');
ClassElement clsCanvasElement = dart_html.findExported('CanvasElement');
ClassElement clsCanvasRenderingContext =
dart_html.findExported('CanvasRenderingContext');
ClassElement clsCanvasRenderingContext2D =
dart_html.findExported('CanvasRenderingContext2D');
List<ClassElement> allClasses = [
clsEventTarget,
clsWindow,
clsAbstractWorker,
clsWorker,
clsCanvasElement,
clsCanvasRenderingContext,
clsCanvasRenderingContext2D
];
check(ClassElement cls,
{bool isDirectlyInstantiated,
bool isAbstractlyInstantiated,
bool isIndirectlyInstantiated,
bool hasStrictSubtype,
bool hasOnlySubclasses,
ClassElement lubOfInstantiatedSubclasses,
ClassElement lubOfInstantiatedSubtypes,
int instantiatedSubclassCount,
int instantiatedSubtypeCount,
List<ClassElement> subclasses: const <ClassElement>[],
List<ClassElement> subtypes: const <ClassElement>[]}) {
ClassSet classSet = closedWorld.getClassSet(cls);
ClassHierarchyNode node = classSet.node;
String dumpText = '\n${closedWorld.dump(cls)}';
Expect.equals(
isDirectlyInstantiated,
closedWorld.isDirectlyInstantiated(cls),
"Unexpected isDirectlyInstantiated property on $cls.$dumpText");
Expect.equals(
isAbstractlyInstantiated,
closedWorld.isAbstractlyInstantiated(cls),
"Unexpected isAbstractlyInstantiated property on $cls.$dumpText");
Expect.equals(
isIndirectlyInstantiated,
closedWorld.isIndirectlyInstantiated(cls),
"Unexpected isIndirectlyInstantiated property on $cls.$dumpText");
Expect.equals(hasStrictSubtype, closedWorld.hasAnyStrictSubtype(cls),
"Unexpected hasAnyStrictSubtype property on $cls.$dumpText");
Expect.equals(hasOnlySubclasses, closedWorld.hasOnlySubclasses(cls),
"Unexpected hasOnlySubclasses property on $cls.$dumpText");
Expect.equals(
lubOfInstantiatedSubclasses,
node.getLubOfInstantiatedSubclasses(),
"Unexpected getLubOfInstantiatedSubclasses() result on $cls.$dumpText");
Expect.equals(
lubOfInstantiatedSubtypes,
classSet.getLubOfInstantiatedSubtypes(),
"Unexpected getLubOfInstantiatedSubtypes() result on $cls.$dumpText");
if (instantiatedSubclassCount != null) {
Expect.equals(instantiatedSubclassCount, node.instantiatedSubclassCount,
"Unexpected instantiatedSubclassCount property on $cls.$dumpText");
}
if (instantiatedSubtypeCount != null) {
Expect.equals(instantiatedSubtypeCount, classSet.instantiatedSubtypeCount,
"Unexpected instantiatedSubtypeCount property on $cls.$dumpText");
}
for (ClassElement other in allClasses) {
if (other == cls) continue;
if (!closedWorld.isExplicitlyInstantiated(other)) continue;
Expect.equals(
subclasses.contains(other),
closedWorld.isSubclassOf(other, cls),
"Unexpected subclass relation between $other and $cls.");
Expect.equals(
subtypes.contains(other),
closedWorld.isSubtypeOf(other, cls),
"Unexpected subtype relation between $other and $cls.");
}
Set<ClassElement> strictSubclasses = new Set<ClassElement>();
closedWorld.forEachStrictSubclassOf(cls, (ClassElement other) {
if (allClasses.contains(other)) {
strictSubclasses.add(other);
}
});
Expect.setEquals(subclasses, strictSubclasses,
"Unexpected strict subclasses of $cls: ${strictSubclasses}.");
Set<ClassElement> strictSubtypes = new Set<ClassElement>();
closedWorld.forEachStrictSubtypeOf(cls, (ClassElement other) {
if (allClasses.contains(other)) {
strictSubtypes.add(other);
}
});
Expect.setEquals(subtypes, strictSubtypes,
"Unexpected strict subtypes of $cls: $strictSubtypes.");
}
// Extended by Window.
check(clsEventTarget,
isDirectlyInstantiated: false,
isAbstractlyInstantiated: false,
isIndirectlyInstantiated: true,
hasStrictSubtype: true,
hasOnlySubclasses: true,
lubOfInstantiatedSubclasses: clsEventTarget,
lubOfInstantiatedSubtypes: clsEventTarget,
// May vary with implementation, do no test.
instantiatedSubclassCount: null,
instantiatedSubtypeCount: null,
subclasses: [clsWindow, clsCanvasElement, clsWorker],
subtypes: [clsWindow, clsCanvasElement, clsWorker]);
// Created by 'html.window'.
check(clsWindow,
isDirectlyInstantiated: false,
isAbstractlyInstantiated: true,
isIndirectlyInstantiated: false,
hasStrictSubtype: false,
hasOnlySubclasses: true,
lubOfInstantiatedSubclasses: clsWindow,
lubOfInstantiatedSubtypes: clsWindow,
instantiatedSubclassCount: 0,
instantiatedSubtypeCount: 0);
// Implemented by 'Worker'.
check(clsAbstractWorker,
isDirectlyInstantiated: false,
isAbstractlyInstantiated: false,
isIndirectlyInstantiated: false,
hasStrictSubtype: true,
hasOnlySubclasses: false,
lubOfInstantiatedSubclasses: null,
lubOfInstantiatedSubtypes: clsWorker,
instantiatedSubclassCount: 0,
instantiatedSubtypeCount: 1,
subtypes: [clsWorker]);
// Created by 'new html.Worker'.
check(clsWorker,
isDirectlyInstantiated: false,
isAbstractlyInstantiated: true,
isIndirectlyInstantiated: false,
hasStrictSubtype: false,
hasOnlySubclasses: true,
lubOfInstantiatedSubclasses: clsWorker,
lubOfInstantiatedSubtypes: clsWorker,
instantiatedSubclassCount: 0,
instantiatedSubtypeCount: 0);
// Created by 'new html.CanvasElement'.
check(clsCanvasElement,
isDirectlyInstantiated: false,
isAbstractlyInstantiated: true,
isIndirectlyInstantiated: false,
hasStrictSubtype: false,
hasOnlySubclasses: true,
lubOfInstantiatedSubclasses: clsCanvasElement,
lubOfInstantiatedSubtypes: clsCanvasElement,
instantiatedSubclassCount: 0,
instantiatedSubtypeCount: 0);
// Implemented by CanvasRenderingContext2D and RenderingContext.
check(clsCanvasRenderingContext,
isDirectlyInstantiated: false,
isAbstractlyInstantiated: false,
isIndirectlyInstantiated: false,
hasStrictSubtype: true,
hasOnlySubclasses: false,
lubOfInstantiatedSubclasses: null,
lubOfInstantiatedSubtypes: clsCanvasRenderingContext,
instantiatedSubclassCount: 0,
instantiatedSubtypeCount: 2,
subtypes: [clsCanvasRenderingContext2D]);
// Created by 'html.CanvasElement.getContext'.
check(clsCanvasRenderingContext2D,
isDirectlyInstantiated: false,
isAbstractlyInstantiated: true,
isIndirectlyInstantiated: false,
hasStrictSubtype: false,
hasOnlySubclasses: true,
lubOfInstantiatedSubclasses: clsCanvasRenderingContext2D,
lubOfInstantiatedSubtypes: clsCanvasRenderingContext2D,
instantiatedSubclassCount: 0,
instantiatedSubtypeCount: 0);
}