Decouple TypeMask from ClassElement

- by typing FlatTypeMask.base with Entity
- moving needsNoSuchMethodHandling to ClosedWorld
- and testing needsNoSuchMethodHandling

R=sigmund@google.com

Review URL: https://codereview.chromium.org/2420073002 .
This commit is contained in:
Johnni Winther 2016-10-17 09:47:20 +02:00
parent c1b1151f54
commit 74b74a13f7
7 changed files with 528 additions and 210 deletions

View file

@ -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<ClassElement, TypeMask> cachedMasks =
world.canonicalizedTypeMasks[flags];
if (cachedMasks == null) {
world.canonicalizedTypeMasks[flags] =
cachedMasks = <ClassElement, TypeMask>{};
}
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<ClassElement> 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<TypeMask> masks = candidates.map((ClassElement cls) {
Iterable<TypeMask> 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<ClassElement> 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<ClassElement> 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;

View file

@ -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);
}

View file

@ -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';

View file

@ -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].

View file

@ -94,15 +94,14 @@ class UnionTypeMask implements TypeMask {
bool useSubclass = masks.every((e) => !e.isSubtype);
bool isNullable = masks.any((e) => e.isNullable);
List<ClassElement> masksBases = masks.map((mask) => mask.base).toList();
Iterable<ClassElement> candidates =
closedWorld.commonSupertypesOf(masksBases);
List masksBases = masks.map((mask) => mask.base).toList();
Iterable<Entity> 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<ClassElement> members;
Iterable<Entity> 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

View file

@ -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<Map<ClassElement, TypeMask>> 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<ClassElement, TypeMask> cachedMasks =
_canonicalizedTypeMasks[flags] ??= <ClassElement, TypeMask>{};
return cachedMasks.putIfAbsent(base, createMask);
}
/// Cache of [FlatTypeMask]s grouped by the 8 possible values of the
/// `FlatTypeMask.flags` property.
List<Map<ClassElement, TypeMask>> canonicalizedTypeMasks =
final List<Map<ClassElement, TypeMask>> _canonicalizedTypeMasks =
new List<Map<ClassElement, TypeMask>>.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<ClassElement, bool> secondMap =
_subtypeCoveredByCache[x] ??= <ClassElement, bool>{};
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<ClassElement> 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,
}

View file

@ -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<String> 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);
}