From b0ecbef3cc976c5b60eafa807a1d9a626658f9b1 Mon Sep 17 00:00:00 2001 From: Johnni Winther Date: Fri, 28 Oct 2016 08:45:57 +0200 Subject: [PATCH] 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 . --- .../src/inferrer/simple_types_inferrer.dart | 4 +- .../lib/src/types/union_type_mask.dart | 4 +- pkg/compiler/lib/src/universe/class_set.dart | 104 ++++++--- .../lib/src/universe/world_builder.dart | 26 ++- pkg/compiler/lib/src/world.dart | 72 ++++-- tests/compiler/dart2js/class_set_test.dart | 2 +- .../dart2js/jsinterop/world_test.dart | 19 +- tests/compiler/dart2js/world_test.dart | 216 +++++++++++++++++- 8 files changed, 387 insertions(+), 60 deletions(-) diff --git a/pkg/compiler/lib/src/inferrer/simple_types_inferrer.dart b/pkg/compiler/lib/src/inferrer/simple_types_inferrer.dart index 7aba1204e3a..0e95ce5288f 100644 --- a/pkg/compiler/lib/src/inferrer/simple_types_inferrer.dart +++ b/pkg/compiler/lib/src/inferrer/simple_types_inferrer.dart @@ -481,9 +481,7 @@ class SimpleTypeInferrerVisitor }); } 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 diff --git a/pkg/compiler/lib/src/types/union_type_mask.dart b/pkg/compiler/lib/src/types/union_type_mask.dart index 8ba9a6ad3fb..49f9b44bf02 100644 --- a/pkg/compiler/lib/src/types/union_type_mask.dart +++ b/pkg/compiler/lib/src/types/union_type_mask.dart @@ -88,6 +88,8 @@ class UnionTypeMask implements TypeMask { } static TypeMask flatten(List 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; diff --git a/pkg/compiler/lib/src/universe/class_set.dart b/pkg/compiler/lib/src/universe/class_set.dart index 637fd168f1c..39f507a3030 100644 --- a/pkg/compiler/lib/src/universe/class_set.dart +++ b/pkg/compiler/lib/src/universe/class_set.dart @@ -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 INSTANTIATED = new EnumSet.fromValues(const [ 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 DIRECTLY_INSTANTIATED = - new EnumSet.fromValues( - const [Instantiation.DIRECTLY_INSTANTIATED], - fixed: true); + static final EnumSet EXPLICITLY_INSTANTIATED = + new EnumSet.fromValues(const [ + 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 createMask( {bool includeDirectlyInstantiated: true, bool includeIndirectlyInstantiated: true, - bool includeUninstantiated: true}) { + bool includeUninstantiated: true, + bool includeAbstractlyInstantiated: true}) { EnumSet mask = new EnumSet(); 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 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) { diff --git a/pkg/compiler/lib/src/universe/world_builder.dart b/pkg/compiler/lib/src/universe/world_builder.dart index d574f3ae4b6..470adf3b643 100644 --- a/pkg/compiler/lib/src/universe/world_builder.dart +++ b/pkg/compiler/lib/src/universe/world_builder.dart @@ -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 get fieldSetters; + + /// Call [f] for all directly or abstractly instantiated classes. + void forEachInstantiatedClass( + f(ClassElement cls, EnumSet 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 _directlyInstantiatedClasses = - new Set(); + final Map> _directlyInstantiatedClasses = + >{}; /// 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 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 instantiations = _directlyInstantiatedClasses + .putIfAbsent(cls, () => new EnumSet()); + 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 instantiations)) { + _directlyInstantiatedClasses.forEach(f); + } + bool _hasMatchingSelector(Map selectors, Element member, OpenWorld world) { if (selectors == null) return false; diff --git a/pkg/compiler/lib/src/world.dart b/pkg/compiler/lib/src/world.dart index c6a20e07be0..dbeee8a9338 100644 --- a/pkg/compiler/lib/src/world.dart +++ b/pkg/compiler/lib/src/world.dart @@ -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 []; - 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 []; - 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 []; } 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 []; } 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 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; diff --git a/tests/compiler/dart2js/class_set_test.dart b/tests/compiler/dart2js/class_set_test.dart index 64442b13083..9eea4f16634 100644 --- a/tests/compiler/dart2js/class_set_test.dart +++ b/tests/compiler/dart2js/class_set_test.dart @@ -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); diff --git a/tests/compiler/dart2js/jsinterop/world_test.dart b/tests/compiler/dart2js/jsinterop/world_test.dart index e994791f440..074860f0945 100644 --- a/tests/compiler/dart2js/jsinterop/world_test.dart +++ b/tests/compiler/dart2js/jsinterop/world_test.dart @@ -21,6 +21,7 @@ void main() { testClasses() async { test(String mainSource, {List directlyInstantiated: const [], + List abstractlyInstantiated: const [], List indirectlyInstantiated: const []}) 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']); } diff --git a/tests/compiler/dart2js/world_test.dart b/tests/compiler/dart2js/world_test.dart index c68e05a268a..4e293077f43 100644 --- a/tests/compiler/dart2js/world_test.dart +++ b/tests/compiler/dart2js/world_test.dart @@ -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 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 subclasses: const [], + List subtypes: const []}) { + 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 strictSubclasses = new Set(); + closedWorld.forEachStrictSubclassOf(cls, (ClassElement other) { + if (allClasses.contains(other)) { + strictSubclasses.add(other); + } + }); + Expect.setEquals(subclasses, strictSubclasses, + "Unexpected strict subclasses of $cls: ${strictSubclasses}."); + + Set strictSubtypes = new Set(); + 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); +}