diff --git a/pkg/compiler/lib/src/types/flat_type_mask.dart b/pkg/compiler/lib/src/types/flat_type_mask.dart index 5a3ef8e1ef0..ce6cd550f90 100644 --- a/pkg/compiler/lib/src/types/flat_type_mask.dart +++ b/pkg/compiler/lib/src/types/flat_type_mask.dart @@ -14,17 +14,15 @@ class FlatTypeMask implements TypeMask { static const int SUBCLASS = 2; static const int SUBTYPE = 3; - final ClassElement base; + final Entity base; final int flags; - FlatTypeMask(ClassElement base, int kind, bool isNullable) + FlatTypeMask(Entity base, int kind, bool isNullable) : this.internal(base, (kind << 1) | (isNullable ? 1 : 0)); - FlatTypeMask.exact(ClassElement base) : this.internal(base, (EXACT << 1) | 1); - FlatTypeMask.subclass(ClassElement base) - : this.internal(base, (SUBCLASS << 1) | 1); - FlatTypeMask.subtype(ClassElement base) - : this.internal(base, (SUBTYPE << 1) | 1); + FlatTypeMask.exact(Entity base) : this.internal(base, (EXACT << 1) | 1); + FlatTypeMask.subclass(Entity base) : this.internal(base, (SUBCLASS << 1) | 1); + FlatTypeMask.subtype(Entity base) : this.internal(base, (SUBTYPE << 1) | 1); const FlatTypeMask.nonNullEmpty() : base = null, @@ -33,23 +31,22 @@ class FlatTypeMask implements TypeMask { : base = null, flags = 1; - FlatTypeMask.nonNullExact(ClassElement base) - : this.internal(base, EXACT << 1); - FlatTypeMask.nonNullSubclass(ClassElement base) + FlatTypeMask.nonNullExact(Entity base) : this.internal(base, EXACT << 1); + FlatTypeMask.nonNullSubclass(Entity base) : this.internal(base, SUBCLASS << 1); - FlatTypeMask.nonNullSubtype(ClassElement base) - : this.internal(base, SUBTYPE << 1); + FlatTypeMask.nonNullSubtype(Entity base) : this.internal(base, SUBTYPE << 1); + + bool _validateBase(ClassElement element) => element.isDeclaration; FlatTypeMask.internal(this.base, this.flags) { - assert(base == null || base.isDeclaration); + assert(base == null || _validateBase(base)); } /** * Ensures that the generated mask is normalized, i.e., a call to * [TypeMask.assertIsNormalized] with the factory's result returns `true`. */ - factory FlatTypeMask.normalized( - ClassElement base, int flags, ClosedWorld world) { + factory FlatTypeMask.normalized(Entity base, int flags, ClosedWorld world) { if ((flags >> 1) == EMPTY || ((flags >> 1) == EXACT)) { return new FlatTypeMask.internal(base, flags); } @@ -61,14 +58,8 @@ class FlatTypeMask implements TypeMask { if (((flags >> 1) == SUBCLASS) && !world.hasAnyStrictSubclass(base)) { flags = (flags & 0x1) | (EXACT << 1); } - Map cachedMasks = - world.canonicalizedTypeMasks[flags]; - if (cachedMasks == null) { - world.canonicalizedTypeMasks[flags] = - cachedMasks = {}; - } - return cachedMasks.putIfAbsent( - base, () => new FlatTypeMask.internal(base, flags)); + return world.getCachedMask( + base, flags, () => new FlatTypeMask.internal(base, flags)); } bool get isEmpty => isEmptyOrNull && !isNullable; @@ -98,23 +89,23 @@ class FlatTypeMask implements TypeMask { return isNullable ? new FlatTypeMask.internal(base, flags & ~1) : this; } - bool contains(ClassElement type, ClosedWorld closedWorld) { - assert(type.isDeclaration); + bool contains(Entity other, ClosedWorld closedWorld) { + assert(_validateBase(other)); if (isEmptyOrNull) { return false; - } else if (identical(base, type)) { + } else if (identical(base, other)) { return true; } else if (isExact) { return false; } else if (isSubclass) { - return closedWorld.isSubclassOf(type, base); + return closedWorld.isSubclassOf(other, base); } else { assert(isSubtype); - return closedWorld.isSubtypeOf(type, base); + return closedWorld.isSubtypeOf(other, base); } } - bool isSingleImplementationOf(ClassElement cls, ClosedWorld closedWorld) { + bool isSingleImplementationOf(Entity cls, ClosedWorld closedWorld) { // Special case basic types so that, for example, JSString is the // single implementation of String. // The general optimization is to realize there is only one class that @@ -154,7 +145,7 @@ class FlatTypeMask implements TypeMask { if (other is! FlatTypeMask) return other.containsMask(this, closedWorld); // The other must be flat, so compare base and flags. FlatTypeMask flatOther = other; - ClassElement otherBase = flatOther.base; + Entity otherBase = flatOther.base; // If other is exact, it only contains its base. // TODO(herhut): Get rid of isSingleImplementationOf. if (flatOther.isExact) { @@ -214,23 +205,21 @@ class FlatTypeMask implements TypeMask { base == backendClasses.stringImplementation; } - bool containsOnly(ClassElement cls) { - assert(cls.isDeclaration); + bool containsOnly(Entity cls) { + assert(_validateBase(cls)); return base == cls; } - bool satisfies(ClassElement cls, ClosedWorld closedWorld) { - assert(cls.isDeclaration); + bool satisfies(Entity cls, ClosedWorld closedWorld) { + assert(_validateBase(cls)); if (isEmptyOrNull) return false; if (closedWorld.isSubtypeOf(base, cls)) return true; return false; } - /** - * Returns the [ClassElement] if this type represents a single class, - * otherwise returns `null`. This method is conservative. - */ - ClassElement singleClass(ClosedWorld closedWorld) { + /// Returns the [Entity] if this type represents a single class, otherwise + /// returns `null`. This method is conservative. + Entity singleClass(ClosedWorld closedWorld) { if (isEmptyOrNull) return null; if (isNullable) return null; // It is Null and some other class. if (isExact) { @@ -479,6 +468,7 @@ class FlatTypeMask implements TypeMask { if (isSubclass && other.isSubclass) return intersectionEmpty(other); assert(isSubtype || other.isSubtype); int kind = (isSubclass || other.isSubclass) ? SUBCLASS : SUBTYPE; + // TODO(johnniwinther): Move this computation to [ClosedWorld]. // Compute the set of classes that are contained in both type masks. Set common = commonContainedClasses(this, other, closedWorld); if (common == null || common.isEmpty) return intersectionEmpty(other); @@ -504,7 +494,7 @@ class FlatTypeMask implements TypeMask { // result will only be nullable if both masks are nullable. We have // to normalize here, as we generate types based on new base classes. int combined = (kind << 1) | (flags & other.flags & 1); - Iterable masks = candidates.map((ClassElement cls) { + Iterable masks = candidates.map((Entity cls) { return new FlatTypeMask.normalized(cls, combined, closedWorld); }); return UnionTypeMask.unionOf(masks, closedWorld); @@ -516,27 +506,6 @@ class FlatTypeMask implements TypeMask { : new TypeMask.nonNullEmpty(); } - /** - * Returns whether [element] will be the one used at runtime when being - * invoked on an instance of [cls]. [selector] is used to ensure library - * privacy is taken into account. - */ - static bool hasElementIn( - ClassElement cls, Selector selector, Element element) { - // Use [:implementation:] of [element] - // because our function set only stores declarations. - Element result = findMatchIn(cls, selector); - return result == null - ? false - : result.implementation == element.implementation; - } - - static Element findMatchIn(ClassElement cls, Selector selector) { - // Use the [:implementation] of [cls] in case the found [element] - // is in the patch class. - return cls.implementation.lookupByName(selector.memberName); - } - /** * Returns whether [element] is a potential target when being * invoked on this type mask. [selector] is used to ensure library @@ -547,7 +516,8 @@ class FlatTypeMask implements TypeMask { assert(element.name == selector.name); if (isEmpty) return false; if (isNull) { - return hasElementIn(backendClasses.nullImplementation, selector, element); + return closedWorld.hasElementIn( + backendClasses.nullImplementation, selector, element); } // TODO(kasperl): Can't we just avoid creating typed selectors @@ -563,14 +533,14 @@ class FlatTypeMask implements TypeMask { if (other == backendClasses.nullImplementation) { return isNullable; } else if (isExact) { - return hasElementIn(self, selector, element); + return closedWorld.hasElementIn(self, selector, element); } else if (isSubclass) { - return hasElementIn(self, selector, element) || + return closedWorld.hasElementIn(self, selector, element) || other.isSubclassOf(self) || closedWorld.hasAnySubclassThatMixes(self, other); } else { assert(isSubtype); - bool result = hasElementIn(self, selector, element) || + bool result = closedWorld.hasElementIn(self, selector, element) || other.implementsInterface(self) || closedWorld.hasAnySubclassThatImplements(other, base) || closedWorld.hasAnySubclassOfMixinUseThatImplements(other, base); @@ -579,108 +549,28 @@ class FlatTypeMask implements TypeMask { // can be hit from any of the mixin applications. Iterable mixinUses = closedWorld.mixinUsesOf(self); return mixinUses.any((mixinApplication) => - hasElementIn(mixinApplication, selector, element) || + closedWorld.hasElementIn(mixinApplication, selector, element) || other.isSubclassOf(mixinApplication) || closedWorld.hasAnySubclassThatMixes(mixinApplication, other)); } } - /** - * Returns whether a [selector] call on an instance of [cls] - * will hit a method at runtime, and not go through [noSuchMethod]. - */ - static bool hasConcreteMatch( - ClassElement cls, Selector selector, ClosedWorld world) { - assert(invariant(cls, world.isInstantiated(cls), - message: '$cls has not been instantiated.')); - Element element = findMatchIn(cls, selector); - if (element == null) return false; - - if (element.isAbstract) { - ClassElement enclosingClass = element.enclosingClass; - return hasConcreteMatch(enclosingClass.superclass, selector, world); - } - return selector.appliesUntyped(element); - } - bool needsNoSuchMethodHandling(Selector selector, ClosedWorld closedWorld) { // A call on an empty type mask is either dead code, or a call on // `null`. if (isEmptyOrNull) return false; // A call on an exact mask for an abstract class is dead code. - if (isExact && base.isAbstract) return false; - // If the receiver is guaranteed to have a member that - // matches what we're looking for, there's no need to - // introduce a noSuchMethod handler. It will never be called. - // - // As an example, consider this class hierarchy: - // - // A <-- noSuchMethod - // / \ - // C B <-- foo - // - // If we know we're calling foo on an object of type B we - // don't have to worry about the noSuchMethod method in A - // because objects of type B implement foo. On the other hand, - // if we end up calling foo on something of type C we have to - // add a handler for it. + // TODO(johnniwinther): A type mask cannot be abstract. Remove the need + // for this noise (currently used for super-calls in inference and mirror + // usage). + if (isExact && closedWorld.isAbstract(base)) return false; - // If the holders of all user-defined noSuchMethod - // implementations that might be applicable to the receiver - // type have a matching member for the current name and - // selector, we avoid introducing a noSuchMethod handler. - // - // As an example, consider this class hierarchy: - // - // A <-- foo - // / \ - // noSuchMethod --> B C <-- bar - // | | - // C D <-- noSuchMethod - // - // When calling foo on an object of type A, we know that the - // implementations of noSuchMethod are in the classes B and D - // that also (indirectly) implement foo, so we do not need a - // handler for it. - // - // If we're calling bar on an object of type D, we don't need - // the handler either because all objects of type D implement - // bar through inheritance. - // - // If we're calling bar on an object of type A we do need the - // handler because we may have to call B.noSuchMethod since B - // does not implement bar. - - /// Returns `true` if [cls] is an instantiated class that does not have - /// a concrete method matching [selector]. - bool needsNoSuchMethod(ClassElement cls) { - // We can skip uninstantiated subclasses. - // TODO(johnniwinther): Put filtering into the (Class)World. - if (!closedWorld.isInstantiated(cls)) { - return false; - } - // We can just skip abstract classes because we know no - // instance of them will be created at runtime, and - // therefore there is no instance that will require - // [noSuchMethod] handling. - return !cls.isAbstract && !hasConcreteMatch(cls, selector, closedWorld); - } - - bool baseNeedsNoSuchMethod = needsNoSuchMethod(base); - if (isExact || baseNeedsNoSuchMethod) { - return baseNeedsNoSuchMethod; - } - - Iterable subclassesToCheck; - if (isSubtype) { - subclassesToCheck = closedWorld.strictSubtypesOf(base); - } else { - assert(isSubclass); - subclassesToCheck = closedWorld.strictSubclassesOf(base); - } - - return subclassesToCheck != null && - subclassesToCheck.any(needsNoSuchMethod); + return closedWorld.needsNoSuchMethod( + base, + selector, + isExact + ? ClassQuery.EXACT + : (isSubclass ? ClassQuery.SUBCLASS : ClassQuery.SUBTYPE)); } Element locateSingleElement(Selector selector, Compiler compiler) { @@ -689,7 +579,7 @@ class FlatTypeMask implements TypeMask { compiler.closedWorld.allFunctions.filter(selector, this); if (targets.length != 1) return null; Element result = targets.first; - ClassElement enclosing = result.enclosingClass; + ClassElement enclosing = result.enclosingClass.declaration; // We only return the found element if it is guaranteed to be implemented on // all classes in the receiver type [this]. It could be found only in a // subclass or in an inheritance-wise unrelated class in case of subtype @@ -703,7 +593,7 @@ class FlatTypeMask implements TypeMask { //} return null; } else { - if (base.isSubclassOf(enclosing)) return result; + if (closedWorld.isSubclassOf(base, enclosing)) return result; if (closedWorld.isSubclassOfMixinUseOf(base, enclosing)) return result; } return null; diff --git a/pkg/compiler/lib/src/types/forwarding_type_mask.dart b/pkg/compiler/lib/src/types/forwarding_type_mask.dart index cdf4f169433..f285a59fefa 100644 --- a/pkg/compiler/lib/src/types/forwarding_type_mask.dart +++ b/pkg/compiler/lib/src/types/forwarding_type_mask.dart @@ -54,23 +54,23 @@ abstract class ForwardingTypeMask implements TypeMask { return forwardTo.containsOnlyString(closedWorld); } - bool containsOnly(ClassElement element) { - return forwardTo.containsOnly(element); + bool containsOnly(Entity cls) { + return forwardTo.containsOnly(cls); } - bool satisfies(ClassElement cls, ClosedWorld closedWorld) { + bool satisfies(Entity cls, ClosedWorld closedWorld) { return forwardTo.satisfies(cls, closedWorld); } - bool contains(ClassElement type, ClosedWorld closedWorld) { - return forwardTo.contains(type, closedWorld); + bool contains(Entity cls, ClosedWorld closedWorld) { + return forwardTo.contains(cls, closedWorld); } bool containsAll(ClosedWorld closedWorld) { return forwardTo.containsAll(closedWorld); } - ClassElement singleClass(ClosedWorld closedWorld) { + Entity singleClass(ClosedWorld closedWorld) { return forwardTo.singleClass(closedWorld); } diff --git a/pkg/compiler/lib/src/types/masks.dart b/pkg/compiler/lib/src/types/masks.dart index fe24d535ae6..85775587458 100644 --- a/pkg/compiler/lib/src/types/masks.dart +++ b/pkg/compiler/lib/src/types/masks.dart @@ -18,7 +18,7 @@ import '../universe/world_builder.dart' UniverseSelectorConstraints, SelectorConstraintsStrategy; import '../util/util.dart'; -import '../world.dart' show ClosedWorld; +import '../world.dart' show ClassQuery, ClosedWorld; import 'abstract_value_domain.dart' show AbstractValue; part 'container_type_mask.dart'; diff --git a/pkg/compiler/lib/src/types/type_mask.dart b/pkg/compiler/lib/src/types/type_mask.dart index cd92ed968eb..a3ba10ddf23 100644 --- a/pkg/compiler/lib/src/types/type_mask.dart +++ b/pkg/compiler/lib/src/types/type_mask.dart @@ -77,30 +77,30 @@ class TypeMaskStrategy implements SelectorConstraintsStrategy { */ abstract class TypeMask implements ReceiverConstraint, AbstractValue { factory TypeMask( - ClassElement base, int kind, bool isNullable, ClosedWorld closedWorld) { + Entity base, int kind, bool isNullable, ClosedWorld closedWorld) { return new FlatTypeMask.normalized( base, (kind << 1) | (isNullable ? 1 : 0), closedWorld); } const factory TypeMask.empty() = FlatTypeMask.empty; - factory TypeMask.exact(ClassElement base, ClosedWorld closedWorld) { + factory TypeMask.exact(Entity base, ClosedWorld closedWorld) { assert(invariant(base, closedWorld.isInstantiated(base), message: () => "Cannot create exact type mask for uninstantiated " "class $base.\n${closedWorld.dump(base)}")); return new FlatTypeMask.exact(base); } - factory TypeMask.exactOrEmpty(ClassElement base, ClosedWorld closedWorld) { + factory TypeMask.exactOrEmpty(Entity base, ClosedWorld closedWorld) { if (closedWorld.isInstantiated(base)) return new FlatTypeMask.exact(base); return const TypeMask.empty(); } - factory TypeMask.subclass(ClassElement base, ClosedWorld closedWorld) { + factory TypeMask.subclass(Entity base, ClosedWorld closedWorld) { assert(invariant(base, closedWorld.isInstantiated(base), message: () => "Cannot create subclass type mask for uninstantiated " "class $base.\n${closedWorld.dump(base)}")); - ClassElement topmost = closedWorld.getLubOfInstantiatedSubclasses(base); + Entity topmost = closedWorld.getLubOfInstantiatedSubclasses(base); if (topmost == null) { return new TypeMask.empty(); } else if (closedWorld.hasAnyStrictSubclass(topmost)) { @@ -110,8 +110,8 @@ abstract class TypeMask implements ReceiverConstraint, AbstractValue { } } - factory TypeMask.subtype(ClassElement base, ClosedWorld closedWorld) { - ClassElement topmost = closedWorld.getLubOfInstantiatedSubtypes(base); + factory TypeMask.subtype(Entity base, ClosedWorld closedWorld) { + Entity topmost = closedWorld.getLubOfInstantiatedSubtypes(base); if (topmost == null) { return new TypeMask.empty(); } @@ -127,26 +127,25 @@ abstract class TypeMask implements ReceiverConstraint, AbstractValue { const factory TypeMask.nonNullEmpty() = FlatTypeMask.nonNullEmpty; - factory TypeMask.nonNullExact(ClassElement base, ClosedWorld closedWorld) { + factory TypeMask.nonNullExact(Entity base, ClosedWorld closedWorld) { assert(invariant(base, closedWorld.isInstantiated(base), message: () => "Cannot create exact type mask for uninstantiated " "class $base.\n${closedWorld.dump(base)}")); return new FlatTypeMask.nonNullExact(base); } - factory TypeMask.nonNullExactOrEmpty( - ClassElement base, ClosedWorld closedWorld) { + factory TypeMask.nonNullExactOrEmpty(Entity base, ClosedWorld closedWorld) { if (closedWorld.isInstantiated(base)) { return new FlatTypeMask.nonNullExact(base); } return const TypeMask.nonNullEmpty(); } - factory TypeMask.nonNullSubclass(ClassElement base, ClosedWorld closedWorld) { + factory TypeMask.nonNullSubclass(Entity base, ClosedWorld closedWorld) { assert(invariant(base, closedWorld.isInstantiated(base), message: () => "Cannot create subclass type mask for uninstantiated " "class $base.\n${closedWorld.dump(base)}")); - ClassElement topmost = closedWorld.getLubOfInstantiatedSubclasses(base); + Entity topmost = closedWorld.getLubOfInstantiatedSubclasses(base); if (topmost == null) { return new TypeMask.nonNullEmpty(); } else if (closedWorld.hasAnyStrictSubclass(topmost)) { @@ -156,8 +155,8 @@ abstract class TypeMask implements ReceiverConstraint, AbstractValue { } } - factory TypeMask.nonNullSubtype(ClassElement base, ClosedWorld closedWorld) { - ClassElement topmost = closedWorld.getLubOfInstantiatedSubtypes(base); + factory TypeMask.nonNullSubtype(Entity base, ClosedWorld closedWorld) { + Entity topmost = closedWorld.getLubOfInstantiatedSubtypes(base); if (topmost == null) { return new TypeMask.nonNullEmpty(); } @@ -287,7 +286,7 @@ abstract class TypeMask implements ReceiverConstraint, AbstractValue { bool containsOnlyNum(ClosedWorld closedWorld); bool containsOnlyBool(ClosedWorld closedWorld); bool containsOnlyString(ClosedWorld closedWorld); - bool containsOnly(ClassElement element); + bool containsOnly(Entity cls); /** * Compares two [TypeMask] objects for structural equality. @@ -316,23 +315,21 @@ abstract class TypeMask implements ReceiverConstraint, AbstractValue { /** * Returns whether this type mask is an instance of [cls]. */ - bool satisfies(ClassElement cls, ClosedWorld closedWorld); + bool satisfies(Entity cls, ClosedWorld closedWorld); /** - * Returns whether or not this type mask contains the given type. + * Returns whether or not this type mask contains the given class [cls]. */ - bool contains(ClassElement type, ClosedWorld closedWorld); + bool contains(Entity cls, ClosedWorld closedWorld); /** * Returns whether or not this type mask contains all types. */ bool containsAll(ClosedWorld closedWorld); - /** - * Returns the [ClassElement] if this type represents a single class, - * otherwise returns `null`. This method is conservative. - */ - ClassElement singleClass(ClosedWorld closedWorld); + /// Returns the [Entity] if this type represents a single class, otherwise + /// returns `null`. This method is conservative. + Entity singleClass(ClosedWorld closedWorld); /** * Returns a type mask representing the union of [this] and [other]. diff --git a/pkg/compiler/lib/src/types/union_type_mask.dart b/pkg/compiler/lib/src/types/union_type_mask.dart index 8e4aa394c86..8ba9a6ad3fb 100644 --- a/pkg/compiler/lib/src/types/union_type_mask.dart +++ b/pkg/compiler/lib/src/types/union_type_mask.dart @@ -94,15 +94,14 @@ class UnionTypeMask implements TypeMask { bool useSubclass = masks.every((e) => !e.isSubtype); bool isNullable = masks.any((e) => e.isNullable); - List masksBases = masks.map((mask) => mask.base).toList(); - Iterable candidates = - closedWorld.commonSupertypesOf(masksBases); + List masksBases = masks.map((mask) => mask.base).toList(); + Iterable candidates = closedWorld.commonSupertypesOf(masksBases); // Compute the best candidate and its kind. - ClassElement bestElement; + Entity bestElement; int bestKind; int bestSize; - for (ClassElement candidate in candidates) { + for (Entity candidate in candidates) { bool isInstantiatedStrictSubclass(cls) => cls != candidate && closedWorld.isDirectlyInstantiated(cls) && @@ -231,14 +230,14 @@ class UnionTypeMask implements TypeMask { // Check we cover the base class. if (!contains(flat.base, closedWorld)) return false; // Check for other members. - Iterable members; + Iterable members; if (flat.isSubclass) { members = closedWorld.strictSubclassesOf(flat.base); } else { assert(flat.isSubtype); members = closedWorld.strictSubtypesOf(flat.base); } - return members.every((ClassElement cls) => this.contains(cls, closedWorld)); + return members.every((Entity cls) => this.contains(cls, closedWorld)); } bool isInMask(TypeMask other, ClosedWorld closedWorld) { @@ -306,23 +305,23 @@ class UnionTypeMask implements TypeMask { return disjointMasks.every((mask) => mask.containsOnlyString(closedWorld)); } - bool containsOnly(ClassElement element) { + bool containsOnly(Entity element) { return disjointMasks.every((mask) => mask.containsOnly(element)); } - bool satisfies(ClassElement cls, ClosedWorld closedWorld) { + bool satisfies(Entity cls, ClosedWorld closedWorld) { return disjointMasks.every((mask) => mask.satisfies(cls, closedWorld)); } - bool contains(ClassElement type, ClosedWorld closedWorld) { - return disjointMasks.any((e) => e.contains(type, closedWorld)); + bool contains(Entity cls, ClosedWorld closedWorld) { + return disjointMasks.any((e) => e.contains(cls, closedWorld)); } bool containsAll(ClosedWorld closedWorld) { return disjointMasks.any((mask) => mask.containsAll(closedWorld)); } - ClassElement singleClass(ClosedWorld closedWorld) => null; + Entity singleClass(ClosedWorld closedWorld) => null; bool needsNoSuchMethodHandling(Selector selector, ClosedWorld closedWorld) { return disjointMasks diff --git a/pkg/compiler/lib/src/world.dart b/pkg/compiler/lib/src/world.dart index 40e73a5bfc5..8c69baeb5c8 100644 --- a/pkg/compiler/lib/src/world.dart +++ b/pkg/compiler/lib/src/world.dart @@ -55,6 +55,10 @@ abstract class ClosedWorld implements World { /// subclass. bool isIndirectlyInstantiated(ClassElement cls); + /// Returns `true` if [cls] is abstract and thus can only be instantiated + /// through subclasses. + bool isAbstract(ClassElement cls); + /// Returns `true` if [cls] is implemented by an instantiated class. bool isImplemented(ClassElement cls); @@ -156,6 +160,52 @@ abstract class ClosedWorld implements World { /// Returns `true` if any subclass of [superclass] implements [type]. bool hasAnySubclassThatImplements(ClassElement superclass, ClassElement type); + /// Returns `true` if a call of [selector] on [cls] and/or subclasses/subtypes + /// need noSuchMethod handling. + /// + /// If the receiver is guaranteed to have a member that matches what we're + /// looking for, there's no need to introduce a noSuchMethod handler. It will + /// never be called. + /// + /// As an example, consider this class hierarchy: + /// + /// A <-- noSuchMethod + /// / \ + /// C B <-- foo + /// + /// If we know we're calling foo on an object of type B we don't have to worry + /// about the noSuchMethod method in A because objects of type B implement + /// foo. On the other hand, if we end up calling foo on something of type C we + /// have to add a handler for it. + /// + /// If the holders of all user-defined noSuchMethod implementations that might + /// be applicable to the receiver type have a matching member for the current + /// name and selector, we avoid introducing a noSuchMethod handler. + /// + /// As an example, consider this class hierarchy: + /// + /// A <-- foo + /// / \ + /// noSuchMethod --> B C <-- bar + /// | | + /// C D <-- noSuchMethod + /// + /// When calling foo on an object of type A, we know that the implementations + /// of noSuchMethod are in the classes B and D that also (indirectly) + /// implement foo, so we do not need a handler for it. + /// + /// If we're calling bar on an object of type D, we don't need the handler + /// either because all objects of type D implement bar through inheritance. + /// + /// If we're calling bar on an object of type A we do need the handler because + /// we may have to call B.noSuchMethod since B does not implement bar. + bool needsNoSuchMethod(ClassElement cls, Selector selector, ClassQuery query); + + /// Returns whether [element] will be the one used at runtime when being + /// invoked on an instance of [cls]. [selector] is used to ensure library + /// privacy is taken into account. + bool hasElementIn(ClassElement cls, Selector selector, Element element); + /// Returns [ClassHierarchyNode] for [cls] used to model the class hierarchies /// of known classes. /// @@ -170,9 +220,10 @@ abstract class ClosedWorld implements World { /// methods defined in [ClosedWorld]. ClassSet getClassSet(ClassElement cls); - // TODO(johnniwinther): Find a better strategy for caching these. - @deprecated - List> get canonicalizedTypeMasks; + /// Return the cached mask for [base] with the given flags, or + /// calls [createMask] to create the mask and cache it. + // TODO(johnniwinther): Find a better strategy for caching these? + TypeMask getCachedMask(ClassElement base, int flags, TypeMask createMask()); /// Returns the [FunctionSet] containing all live functions in the closed /// world. @@ -274,9 +325,15 @@ abstract class OpenWorld implements World { class WorldImpl implements ClosedWorld, ClosedWorldRefiner, OpenWorld { bool _closed = false; + TypeMask getCachedMask(ClassElement base, int flags, TypeMask createMask()) { + Map cachedMasks = + _canonicalizedTypeMasks[flags] ??= {}; + return cachedMasks.putIfAbsent(base, createMask); + } + /// Cache of [FlatTypeMask]s grouped by the 8 possible values of the /// `FlatTypeMask.flags` property. - List> canonicalizedTypeMasks = + final List> _canonicalizedTypeMasks = new List>.filled(8, null); bool checkInvariants(ClassElement cls, {bool mustBeInstantiated: true}) { @@ -342,6 +399,9 @@ class WorldImpl implements ClosedWorld, ClosedWorldRefiner, OpenWorld { return node != null && node.isIndirectlyInstantiated; } + @override + bool isAbstract(ClassElement cls) => cls.isAbstract; + /// Returns `true` if [cls] is implemented by an instantiated class. bool isImplemented(ClassElement cls) { assert(isClosed); @@ -622,14 +682,14 @@ class WorldImpl implements ClosedWorld, ClosedWorldRefiner, OpenWorld { /// Returns `true` if [cls] or any superclass mixes in [mixin]. bool isSubclassOfMixinUseOf(ClassElement cls, ClassElement mixin) { assert(isClosed); + assert(cls.isDeclaration); + assert(mixin.isDeclaration); if (isUsedAsMixin(mixin)) { - ClassElement current = cls.declaration; - mixin = mixin.declaration; + ClassElement current = cls; while (current != null) { - current = current.declaration; if (current.isMixinApplication) { MixinApplicationElement application = current; - if (application.mixin.declaration == mixin) return true; + if (application.mixin == mixin) return true; } current = current.superclass; } @@ -641,8 +701,8 @@ class WorldImpl implements ClosedWorld, ClosedWorldRefiner, OpenWorld { /// of a mixin application of [y]. bool everySubtypeIsSubclassOfOrMixinUseOf(ClassElement x, ClassElement y) { assert(isClosed); - x = x.declaration; - y = y.declaration; + assert(x.isDeclaration); + assert(y.isDeclaration); Map secondMap = _subtypeCoveredByCache[x] ??= {}; return secondMap[y] ??= subtypesOf(x).every((ClassElement cls) => @@ -658,6 +718,72 @@ class WorldImpl implements ClosedWorld, ClosedWorldRefiner, OpenWorld { return subclasses.contains(type); } + @override + bool hasElementIn(ClassElement cls, Selector selector, Element element) { + // Use [:implementation:] of [element] + // because our function set only stores declarations. + Element result = findMatchIn(cls, selector); + return result == null + ? false + : result.implementation == element.implementation; + } + + Element findMatchIn(ClassElement cls, Selector selector) { + // Use the [:implementation] of [cls] in case the found [element] + // is in the patch class. + var result = cls.implementation.lookupByName(selector.memberName); + return result; + } + + /// Returns whether a [selector] call on an instance of [cls] + /// will hit a method at runtime, and not go through [noSuchMethod]. + bool hasConcreteMatch(ClassElement cls, Selector selector) { + assert(invariant(cls, isInstantiated(cls), + message: '$cls has not been instantiated.')); + Element element = findMatchIn(cls, selector); + if (element == null) return false; + + if (element.isAbstract) { + ClassElement enclosingClass = element.enclosingClass; + return hasConcreteMatch(enclosingClass.superclass, selector); + } + return selector.appliesUntyped(element); + } + + @override + bool needsNoSuchMethod( + ClassElement base, Selector selector, ClassQuery query) { + /// Returns `true` if [cls] is an instantiated class that does not have + /// a concrete method matching [selector]. + bool needsNoSuchMethod(ClassElement cls) { + // We can skip uninstantiated subclasses. + if (!isInstantiated(cls)) { + return false; + } + // We can just skip abstract classes because we know no + // instance of them will be created at runtime, and + // therefore there is no instance that will require + // [noSuchMethod] handling. + return !cls.isAbstract && !hasConcreteMatch(cls, selector); + } + + bool baseNeedsNoSuchMethod = needsNoSuchMethod(base); + if (query == ClassQuery.EXACT || baseNeedsNoSuchMethod) { + return baseNeedsNoSuchMethod; + } + + Iterable subclassesToCheck; + if (query == ClassQuery.SUBTYPE) { + subclassesToCheck = strictSubtypesOf(base); + } else { + assert(query == ClassQuery.SUBCLASS); + subclassesToCheck = strictSubclassesOf(base); + } + + return subclassesToCheck != null && + subclassesToCheck.any(needsNoSuchMethod); + } + final Compiler _compiler; BackendClasses get backendClasses => _backend.backendClasses; JavaScriptBackend get _backend => _compiler.backend; @@ -1011,3 +1137,16 @@ class WorldImpl implements ClosedWorld, ClosedWorldRefiner, OpenWorld { return getMightBePassedToApply(element); } } + +/// Enum values defining subset of classes included in queries. +enum ClassQuery { + /// Only the class itself is included. + EXACT, + + /// The class and all subclasses (transitively) are included. + SUBCLASS, + + /// The class and all classes that implement or subclass it (transitively) + /// are included. + SUBTYPE, +} diff --git a/tests/compiler/dart2js/needs_no_such_method_test.dart b/tests/compiler/dart2js/needs_no_such_method_test.dart new file mode 100644 index 00000000000..3dadbb7f694 --- /dev/null +++ b/tests/compiler/dart2js/needs_no_such_method_test.dart @@ -0,0 +1,293 @@ +// Copyright (c) 2016, 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 'dart:async'; +import 'package:async_helper/async_helper.dart'; +import 'package:expect/expect.dart'; +import 'package:compiler/src/common.dart'; +import 'package:compiler/src/elements/elements.dart' + show Element, ClassElement, PublicName; +import 'package:compiler/src/universe/call_structure.dart'; +import 'package:compiler/src/universe/selector.dart'; +import 'package:compiler/src/world.dart' show ClosedWorld, ClassQuery; +import 'type_test_helper.dart'; + +void main() { + asyncTest(() async { + await testClassSets(); + }); +} + +const String CLASSES = r""" +class Superclass { + foo() {} +} +class Subclass extends Superclass { + bar() {} +} +class Subtype implements Superclass { + bar() {} +} +"""; + +testClassSets() async { + Selector foo, bar, baz; + ClosedWorld closedWorld; + ClassElement superclass, subclass, subtype; + + Future run(List instantiated) async { + StringBuffer main = new StringBuffer(); + main.write('main() {'); + for (String cls in instantiated) { + main.write('new $cls();'); + } + main.write('}'); + + var env = await TypeEnvironment.create(CLASSES, + mainSource: main.toString(), useMockCompiler: false); + foo = new Selector.call(const PublicName('foo'), CallStructure.NO_ARGS); + bar = new Selector.call(const PublicName('bar'), CallStructure.NO_ARGS); + baz = new Selector.call(const PublicName('baz'), CallStructure.NO_ARGS); + + closedWorld = env.compiler.closedWorld; + superclass = env.getElement('Superclass'); + subclass = env.getElement('Subclass'); + subtype = env.getElement('Subtype'); + } + + void check(ClassElement cls, ClassQuery query, Selector selector, + bool expectedResult) { + bool result = closedWorld.needsNoSuchMethod(cls, selector, query); + Expect.equals(expectedResult, result, + 'Unexpected result for $selector in $cls ($query)'); + } + + await run([]); + + Expect.isFalse(closedWorld.isDirectlyInstantiated(superclass)); + Expect.isFalse(closedWorld.isIndirectlyInstantiated(superclass)); + Expect.isFalse(closedWorld.isImplemented(superclass)); + + Expect.isFalse(closedWorld.isDirectlyInstantiated(subclass)); + Expect.isFalse(closedWorld.isIndirectlyInstantiated(subclass)); + Expect.isFalse(closedWorld.isImplemented(subclass)); + + Expect.isFalse(closedWorld.isDirectlyInstantiated(subtype)); + Expect.isFalse(closedWorld.isIndirectlyInstantiated(subtype)); + Expect.isFalse(closedWorld.isImplemented(subtype)); + + check(superclass, ClassQuery.EXACT, foo, false); + check(superclass, ClassQuery.EXACT, bar, false); + check(superclass, ClassQuery.EXACT, baz, false); + check(superclass, ClassQuery.SUBCLASS, foo, false); + check(superclass, ClassQuery.SUBCLASS, bar, false); + check(superclass, ClassQuery.SUBCLASS, baz, false); + check(superclass, ClassQuery.SUBTYPE, foo, false); + check(superclass, ClassQuery.SUBTYPE, bar, false); + check(superclass, ClassQuery.SUBTYPE, baz, false); + + check(subclass, ClassQuery.EXACT, foo, false); + check(subclass, ClassQuery.EXACT, bar, false); + check(subclass, ClassQuery.EXACT, baz, false); + check(subclass, ClassQuery.SUBCLASS, foo, false); + check(subclass, ClassQuery.SUBCLASS, bar, false); + check(subclass, ClassQuery.SUBCLASS, baz, false); + check(subclass, ClassQuery.SUBTYPE, foo, false); + check(subclass, ClassQuery.SUBTYPE, bar, false); + check(subclass, ClassQuery.SUBTYPE, baz, false); + + check(subtype, ClassQuery.EXACT, foo, false); + check(subtype, ClassQuery.EXACT, bar, false); + check(subtype, ClassQuery.EXACT, baz, false); + check(subtype, ClassQuery.SUBCLASS, foo, false); + check(subtype, ClassQuery.SUBCLASS, bar, false); + check(subtype, ClassQuery.SUBCLASS, baz, false); + check(subtype, ClassQuery.SUBTYPE, foo, false); + check(subtype, ClassQuery.SUBTYPE, bar, false); + check(subtype, ClassQuery.SUBTYPE, baz, false); + + await run(['Superclass']); + + Expect.isTrue(closedWorld.isDirectlyInstantiated(superclass)); + Expect.isFalse(closedWorld.isIndirectlyInstantiated(superclass)); + Expect.isTrue(closedWorld.isImplemented(superclass)); + + Expect.isFalse(closedWorld.isDirectlyInstantiated(subclass)); + Expect.isFalse(closedWorld.isIndirectlyInstantiated(subclass)); + Expect.isFalse(closedWorld.isImplemented(subclass)); + + Expect.isFalse(closedWorld.isDirectlyInstantiated(subtype)); + Expect.isFalse(closedWorld.isIndirectlyInstantiated(subtype)); + Expect.isFalse(closedWorld.isImplemented(subtype)); + + check(superclass, ClassQuery.EXACT, foo, false); + check(superclass, ClassQuery.EXACT, bar, true); + check(superclass, ClassQuery.EXACT, baz, true); + check(superclass, ClassQuery.SUBCLASS, foo, false); + check(superclass, ClassQuery.SUBCLASS, bar, true); + check(superclass, ClassQuery.SUBCLASS, baz, true); + check(superclass, ClassQuery.SUBTYPE, foo, false); + check(superclass, ClassQuery.SUBTYPE, bar, true); + check(superclass, ClassQuery.SUBTYPE, baz, true); + + check(subclass, ClassQuery.EXACT, foo, false); + check(subclass, ClassQuery.EXACT, bar, false); + check(subclass, ClassQuery.EXACT, baz, false); + check(subclass, ClassQuery.SUBCLASS, foo, false); + check(subclass, ClassQuery.SUBCLASS, bar, false); + check(subclass, ClassQuery.SUBCLASS, baz, false); + check(subclass, ClassQuery.SUBTYPE, foo, false); + check(subclass, ClassQuery.SUBTYPE, bar, false); + check(subclass, ClassQuery.SUBTYPE, baz, false); + + check(subtype, ClassQuery.EXACT, foo, false); + check(subtype, ClassQuery.EXACT, bar, false); + check(subtype, ClassQuery.EXACT, baz, false); + check(subtype, ClassQuery.SUBCLASS, foo, false); + check(subtype, ClassQuery.SUBCLASS, bar, false); + check(subtype, ClassQuery.SUBCLASS, baz, false); + check(subtype, ClassQuery.SUBTYPE, foo, false); + check(subtype, ClassQuery.SUBTYPE, bar, false); + check(subtype, ClassQuery.SUBTYPE, baz, false); + + await run(['Subclass']); + + Expect.isFalse(closedWorld.isDirectlyInstantiated(superclass)); + Expect.isTrue(closedWorld.isIndirectlyInstantiated(superclass)); + Expect.isTrue(closedWorld.isImplemented(superclass)); + + Expect.isTrue(closedWorld.isDirectlyInstantiated(subclass)); + Expect.isFalse(closedWorld.isIndirectlyInstantiated(subclass)); + Expect.isTrue(closedWorld.isImplemented(subclass)); + + Expect.isFalse(closedWorld.isDirectlyInstantiated(subtype)); + Expect.isFalse(closedWorld.isIndirectlyInstantiated(subtype)); + Expect.isFalse(closedWorld.isImplemented(subtype)); + + check(superclass, ClassQuery.EXACT, foo, false); + // Should be false since the class is not directly instantiated: + check(superclass, ClassQuery.EXACT, bar, true); + // Should be false since the class is not directly instantiated: + check(superclass, ClassQuery.EXACT, baz, true); + check(superclass, ClassQuery.SUBCLASS, foo, false); + // Should be false since all live subclasses have a concrete implementation: + check(superclass, ClassQuery.SUBCLASS, bar, true); + check(superclass, ClassQuery.SUBCLASS, baz, true); + check(superclass, ClassQuery.SUBTYPE, foo, false); + // Should be false since all live subtypes have a concrete implementation: + check(superclass, ClassQuery.SUBTYPE, bar, true); + check(superclass, ClassQuery.SUBTYPE, baz, true); + + check(subclass, ClassQuery.EXACT, foo, false); + check(subclass, ClassQuery.EXACT, bar, false); + check(subclass, ClassQuery.EXACT, baz, true); + check(subclass, ClassQuery.SUBCLASS, foo, false); + check(subclass, ClassQuery.SUBCLASS, bar, false); + check(subclass, ClassQuery.SUBCLASS, baz, true); + check(subclass, ClassQuery.SUBTYPE, foo, false); + check(subclass, ClassQuery.SUBTYPE, bar, false); + check(subclass, ClassQuery.SUBTYPE, baz, true); + + check(subtype, ClassQuery.EXACT, foo, false); + check(subtype, ClassQuery.EXACT, bar, false); + check(subtype, ClassQuery.EXACT, baz, false); + check(subtype, ClassQuery.SUBCLASS, foo, false); + check(subtype, ClassQuery.SUBCLASS, bar, false); + check(subtype, ClassQuery.SUBCLASS, baz, false); + check(subtype, ClassQuery.SUBTYPE, foo, false); + check(subtype, ClassQuery.SUBTYPE, bar, false); + check(subtype, ClassQuery.SUBTYPE, baz, false); + + await run(['Subtype']); + + Expect.isFalse(closedWorld.isDirectlyInstantiated(superclass)); + Expect.isFalse(closedWorld.isIndirectlyInstantiated(superclass)); + Expect.isTrue(closedWorld.isImplemented(superclass)); + + Expect.isFalse(closedWorld.isDirectlyInstantiated(subclass)); + Expect.isFalse(closedWorld.isIndirectlyInstantiated(subclass)); + Expect.isFalse(closedWorld.isImplemented(subclass)); + + Expect.isTrue(closedWorld.isDirectlyInstantiated(subtype)); + Expect.isFalse(closedWorld.isIndirectlyInstantiated(subtype)); + Expect.isTrue(closedWorld.isImplemented(subtype)); + + check(superclass, ClassQuery.EXACT, foo, false); + check(superclass, ClassQuery.EXACT, bar, false); + check(superclass, ClassQuery.EXACT, baz, false); + check(superclass, ClassQuery.SUBCLASS, foo, false); + check(superclass, ClassQuery.SUBCLASS, bar, false); + check(superclass, ClassQuery.SUBCLASS, baz, false); + check(superclass, ClassQuery.SUBTYPE, foo, true); + check(superclass, ClassQuery.SUBTYPE, bar, false); + check(superclass, ClassQuery.SUBTYPE, baz, true); + + check(subclass, ClassQuery.EXACT, foo, false); + check(subclass, ClassQuery.EXACT, bar, false); + check(subclass, ClassQuery.EXACT, baz, false); + check(subclass, ClassQuery.SUBCLASS, foo, false); + check(subclass, ClassQuery.SUBCLASS, bar, false); + check(subclass, ClassQuery.SUBCLASS, baz, false); + check(subclass, ClassQuery.SUBTYPE, foo, false); + check(subclass, ClassQuery.SUBTYPE, bar, false); + check(subclass, ClassQuery.SUBTYPE, baz, false); + + check(subtype, ClassQuery.EXACT, foo, true); + check(subtype, ClassQuery.EXACT, bar, false); + check(subtype, ClassQuery.EXACT, baz, true); + check(subtype, ClassQuery.SUBCLASS, foo, true); + check(subtype, ClassQuery.SUBCLASS, bar, false); + check(subtype, ClassQuery.SUBCLASS, baz, true); + check(subtype, ClassQuery.SUBTYPE, foo, true); + check(subtype, ClassQuery.SUBTYPE, bar, false); + check(subtype, ClassQuery.SUBTYPE, baz, true); + + await run(['Subclass', 'Subtype']); + + Expect.isFalse(closedWorld.isDirectlyInstantiated(superclass)); + Expect.isTrue(closedWorld.isIndirectlyInstantiated(superclass)); + Expect.isTrue(closedWorld.isImplemented(superclass)); + + Expect.isTrue(closedWorld.isDirectlyInstantiated(subclass)); + Expect.isFalse(closedWorld.isIndirectlyInstantiated(subclass)); + Expect.isTrue(closedWorld.isImplemented(subclass)); + + Expect.isTrue(closedWorld.isDirectlyInstantiated(subtype)); + Expect.isFalse(closedWorld.isIndirectlyInstantiated(subtype)); + Expect.isTrue(closedWorld.isImplemented(subtype)); + + check(superclass, ClassQuery.EXACT, foo, false); + // Should be false since the class is not directly instantiated: + check(superclass, ClassQuery.EXACT, bar, true); + // Should be false since the class is not directly instantiated: + check(superclass, ClassQuery.EXACT, baz, true); + check(superclass, ClassQuery.SUBCLASS, foo, false); + // Should be false since all live subclasses have a concrete implementation: + check(superclass, ClassQuery.SUBCLASS, bar, true); + check(superclass, ClassQuery.SUBCLASS, baz, true); + check(superclass, ClassQuery.SUBTYPE, foo, true); + // Should be false since all live subtypes have a concrete implementation: + check(superclass, ClassQuery.SUBTYPE, bar, true); + check(superclass, ClassQuery.SUBTYPE, baz, true); + + check(subclass, ClassQuery.EXACT, foo, false); + check(subclass, ClassQuery.EXACT, bar, false); + check(subclass, ClassQuery.EXACT, baz, true); + check(subclass, ClassQuery.SUBCLASS, foo, false); + check(subclass, ClassQuery.SUBCLASS, bar, false); + check(subclass, ClassQuery.SUBCLASS, baz, true); + check(subclass, ClassQuery.SUBTYPE, foo, false); + check(subclass, ClassQuery.SUBTYPE, bar, false); + check(subclass, ClassQuery.SUBTYPE, baz, true); + + check(subtype, ClassQuery.EXACT, foo, true); + check(subtype, ClassQuery.EXACT, bar, false); + check(subtype, ClassQuery.EXACT, baz, true); + check(subtype, ClassQuery.SUBCLASS, foo, true); + check(subtype, ClassQuery.SUBCLASS, bar, false); + check(subtype, ClassQuery.SUBCLASS, baz, true); + check(subtype, ClassQuery.SUBTYPE, foo, true); + check(subtype, ClassQuery.SUBTYPE, bar, false); + check(subtype, ClassQuery.SUBTYPE, baz, true); +}