blob: bd524265666beaf2077f21455cc57046e3baa552 [file] [log] [blame]
// Copyright (c) 2013, 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.
part of types;
/**
* A flat type mask is a type mask that has been flattened to contain a
* base type.
*/
class FlatTypeMask implements TypeMask {
static const int EMPTY = 0;
static const int EXACT = 1;
static const int SUBCLASS = 2;
static const int SUBTYPE = 3;
final ClassElement base;
final int flags;
FlatTypeMask(ClassElement 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);
const FlatTypeMask.nonNullEmpty(): base = null, flags = 0;
const FlatTypeMask.empty() : base = null, flags = 1;
FlatTypeMask.nonNullExact(ClassElement base)
: this.internal(base, EXACT << 1);
FlatTypeMask.nonNullSubclass(ClassElement base)
: this.internal(base, SUBCLASS << 1);
FlatTypeMask.nonNullSubtype(ClassElement base)
: this.internal(base, SUBTYPE << 1);
FlatTypeMask.internal(this.base, this.flags) {
assert(base == null || base.isDeclaration);
}
/**
* 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, World world) {
if ((flags >> 1) == EMPTY || ((flags >> 1) == EXACT)) {
return new FlatTypeMask.internal(base, flags);
}
if ((flags >> 1) == SUBTYPE) {
if (!world.hasAnySubtype(base) || world.hasOnlySubclasses(base)) {
flags = (flags & 0x1) | (SUBCLASS << 1);
}
}
if (((flags >> 1) == SUBCLASS) && !world.hasAnySubclass(base)) {
flags = (flags & 0x1) | (EXACT << 1);
}
return new FlatTypeMask.internal(base, flags);
}
bool get isEmpty => (flags >> 1) == EMPTY;
bool get isExact => (flags >> 1) == EXACT;
bool get isNullable => (flags & 1) != 0;
bool get isUnion => false;
bool get isContainer => false;
bool get isMap => false;
bool get isDictionary => false;
bool get isForwarding => false;
bool get isValue => false;
// TODO(kasperl): Get rid of these. They should not be a visible
// part of the implementation because they make it hard to add
// proper union types if we ever want to.
bool get isSubclass => (flags >> 1) == SUBCLASS;
bool get isSubtype => (flags >> 1) == SUBTYPE;
TypeMask nullable() {
return isNullable ? this : new FlatTypeMask.internal(base, flags | 1);
}
TypeMask nonNullable() {
return isNullable ? new FlatTypeMask.internal(base, flags & ~1) : this;
}
bool contains(ClassElement type, ClassWorld classWorld) {
assert(type.isDeclaration);
if (isEmpty) {
return false;
} else if (identical(base, type)) {
return true;
} else if (isExact) {
return false;
} else if (isSubclass) {
return classWorld.isSubclassOf(type, base);
} else {
assert(isSubtype);
return classWorld.isSubtypeOf(type, base);
}
}
bool isSingleImplementationOf(ClassElement cls, ClassWorld classWorld) {
// 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
// implements [base] and [base] is not instantiated. We however do
// not track correctly the list of truly instantiated classes.
Backend backend = classWorld.backend;
if (containsOnlyString(classWorld)) {
return cls == classWorld.stringClass ||
cls == backend.stringImplementation;
}
if (containsOnlyBool(classWorld)) {
return cls == classWorld.boolClass || cls == backend.boolImplementation;
}
if (containsOnlyInt(classWorld)) {
return cls == classWorld.intClass
|| cls == backend.intImplementation
|| cls == backend.positiveIntImplementation
|| cls == backend.uint32Implementation
|| cls == backend.uint31Implementation;
}
if (containsOnlyDouble(classWorld)) {
return cls == classWorld.doubleClass
|| cls == backend.doubleImplementation;
}
return false;
}
bool isInMask(TypeMask other, ClassWorld classWorld) {
// null is treated separately, so the empty mask might still contain it.
if (isEmpty) return isNullable ? other.isNullable : true;
// The empty type contains no classes.
if (other.isEmpty) return false;
// Quick check whether to handle null.
if (isNullable && !other.isNullable) return false;
other = TypeMask.nonForwardingMask(other);
// If other is union, delegate to UnionTypeMask.containsMask.
if (other is! FlatTypeMask) return other.containsMask(this, classWorld);
// The other must be flat, so compare base and flags.
FlatTypeMask flatOther = other;
ClassElement otherBase = flatOther.base;
// If other is exact, it only contains its base.
// TODO(herhut): Get rid of isSingleImplementationOf.
if (flatOther.isExact) {
return (isExact && base == otherBase)
|| isSingleImplementationOf(otherBase, classWorld);
}
// If other is subclass, this has to be subclass, as well. Unless
// flatOther.base covers all subtypes of this. Currently, we only
// consider object to behave that way.
// TODO(herhut): Add check whether flatOther.base is superclass of
// all subclasses of this.base.
if (flatOther.isSubclass) {
if (isSubtype) return (otherBase == classWorld.objectClass);
return classWorld.isSubclassOf(base, otherBase);
}
assert(flatOther.isSubtype);
// Check whether this TypeMask satisfies otherBase's interface.
return satisfies(otherBase, classWorld);
}
bool containsMask(TypeMask other, ClassWorld classWorld) {
return other.isInMask(this, classWorld);
}
bool containsOnlyInt(ClassWorld classWorld) {
Backend backend = classWorld.backend;
return base == classWorld.intClass
|| base == backend.intImplementation
|| base == backend.positiveIntImplementation
|| base == backend.uint31Implementation
|| base == backend.uint32Implementation;
}
bool containsOnlyDouble(ClassWorld classWorld) {
Backend backend = classWorld.backend;
return base == classWorld.doubleClass
|| base == backend.doubleImplementation;
}
bool containsOnlyNum(ClassWorld classWorld) {
Backend backend = classWorld.backend;
return containsOnlyInt(classWorld)
|| containsOnlyDouble(classWorld)
|| base == classWorld.numClass
|| base == backend.numImplementation;
}
bool containsOnlyBool(ClassWorld classWorld) {
Backend backend = classWorld.backend;
return base == classWorld.boolClass
|| base == backend.boolImplementation;
}
bool containsOnlyString(ClassWorld classWorld) {
Backend backend = classWorld.backend;
return base == classWorld.stringClass
|| base == backend.stringImplementation;
}
bool containsOnly(ClassElement cls) {
assert(cls.isDeclaration);
return base == cls;
}
bool satisfies(ClassElement cls, ClassWorld classWorld) {
assert(cls.isDeclaration);
if (isEmpty) return false;
if (classWorld.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(ClassWorld classWorld) {
if (isEmpty) return null;
if (isNullable) return null; // It is Null and some other class.
if (isExact) {
return base;
} else if (isSubclass) {
return classWorld.hasAnyStrictSubclass(base) ? null : base;
} else {
assert(isSubtype);
return null;
}
}
/**
* Returns whether or not this type mask contains all types.
*/
bool containsAll(ClassWorld classWorld) {
if (isEmpty || isExact) return false;
return identical(base, classWorld.objectClass);
}
TypeMask union(TypeMask other, ClassWorld classWorld) {
assert(other != null);
assert(TypeMask.assertIsNormalized(this, classWorld));
assert(TypeMask.assertIsNormalized(other, classWorld));
if (other is! FlatTypeMask) return other.union(this, classWorld);
FlatTypeMask flatOther = other;
if (isEmpty) {
return isNullable ? flatOther.nullable() : flatOther;
} else if (flatOther.isEmpty) {
return flatOther.isNullable ? nullable() : this;
} else if (base == flatOther.base) {
return unionSame(flatOther, classWorld);
} else if (classWorld.isSubclassOf(flatOther.base, base)) {
return unionStrictSubclass(flatOther, classWorld);
} else if (classWorld.isSubclassOf(base, flatOther.base)) {
return flatOther.unionStrictSubclass(this, classWorld);
} else if (classWorld.isSubtypeOf(flatOther.base, base)) {
return unionStrictSubtype(flatOther, classWorld);
} else if (classWorld.isSubtypeOf(base, flatOther.base)) {
return flatOther.unionStrictSubtype(this, classWorld);
} else {
return new UnionTypeMask._internal(<FlatTypeMask>[this, flatOther]);
}
}
TypeMask unionSame(FlatTypeMask other, ClassWorld classWorld) {
assert(base == other.base);
assert(TypeMask.assertIsNormalized(this, classWorld));
assert(TypeMask.assertIsNormalized(other, classWorld));
// The two masks share the base type, so we must chose the least
// constraining kind (the highest) of the two. If either one of
// the masks are nullable the result should be nullable too.
// As both masks are normalized, the result will be, too.
int combined = (flags > other.flags)
? flags | (other.flags & 1)
: other.flags | (flags & 1);
if (flags == combined) {
return this;
} else if (other.flags == combined) {
return other;
} else {
return new FlatTypeMask.normalized(base, combined, classWorld);
}
}
TypeMask unionStrictSubclass(FlatTypeMask other, ClassWorld classWorld) {
assert(base != other.base);
assert(classWorld.isSubclassOf(other.base, base));
assert(TypeMask.assertIsNormalized(this, classWorld));
assert(TypeMask.assertIsNormalized(other, classWorld));
int combined;
if ((isExact && other.isExact) || base == classWorld.objectClass) {
// Since the other mask is a subclass of this mask, we need the
// resulting union to be a subclass too. If either one of the
// masks are nullable the result should be nullable too.
combined = (SUBCLASS << 1) | ((flags | other.flags) & 1);
} else {
// Both masks are at least subclass masks, so we pick the least
// constraining kind (the highest) of the two. If either one of
// the masks are nullable the result should be nullable too.
combined = (flags > other.flags)
? flags | (other.flags & 1)
: other.flags | (flags & 1);
}
// If we weaken the constraint on this type, we have to make sure that
// the result is normalized.
return (flags != combined)
? new FlatTypeMask.normalized(base, combined, classWorld)
: this;
}
TypeMask unionStrictSubtype(FlatTypeMask other, ClassWorld classWorld) {
assert(base != other.base);
assert(!classWorld.isSubclassOf(other.base, base));
assert(classWorld.isSubtypeOf(other.base, base));
assert(TypeMask.assertIsNormalized(this, classWorld));
assert(TypeMask.assertIsNormalized(other, classWorld));
// Since the other mask is a subtype of this mask, we need the
// resulting union to be a subtype too. If either one of the masks
// are nullable the result should be nullable too.
int combined = (SUBTYPE << 1) | ((flags | other.flags) & 1);
// We know there is at least one subtype, [other.base], so no need
// to normalize.
return (flags != combined)
? new FlatTypeMask.normalized(base, combined, classWorld)
: this;
}
TypeMask intersection(TypeMask other, ClassWorld classWorld) {
assert(other != null);
if (other is! FlatTypeMask) return other.intersection(this, classWorld);
assert(TypeMask.assertIsNormalized(this, classWorld));
assert(TypeMask.assertIsNormalized(other, classWorld));
FlatTypeMask flatOther = other;
if (isEmpty) {
return flatOther.isNullable ? this : nonNullable();
} else if (flatOther.isEmpty) {
return isNullable ? flatOther : other.nonNullable();
} else if (base == flatOther.base) {
return intersectionSame(flatOther, classWorld);
} else if (classWorld.isSubclassOf(flatOther.base, base)) {
return intersectionStrictSubclass(flatOther, classWorld);
} else if (classWorld.isSubclassOf(base, flatOther.base)) {
return flatOther.intersectionStrictSubclass(this, classWorld);
} else if (classWorld.isSubtypeOf(flatOther.base, base)) {
return intersectionStrictSubtype(flatOther, classWorld);
} else if (classWorld.isSubtypeOf(base, flatOther.base)) {
return flatOther.intersectionStrictSubtype(this, classWorld);
} else {
return intersectionDisjoint(flatOther, classWorld);
}
}
TypeMask intersectionSame(FlatTypeMask other, ClassWorld classWorld) {
assert(base == other.base);
// The two masks share the base type, so we must chose the most
// constraining kind (the lowest) of the two. Only if both masks
// are nullable, will the result be nullable too.
// The result will be normalized, as the two inputs are normalized, too.
int combined = (flags < other.flags)
? flags & ((other.flags & 1) | ~1)
: other.flags & ((flags & 1) | ~1);
if (flags == combined) {
return this;
} else if (other.flags == combined) {
return other;
} else {
return new FlatTypeMask.normalized(base, combined, classWorld);
}
}
TypeMask intersectionStrictSubclass(FlatTypeMask other,
ClassWorld classWorld) {
assert(base != other.base);
assert(classWorld.isSubclassOf(other.base, base));
// If this mask isn't at least a subclass mask, then the
// intersection with the other mask is empty.
if (isExact) return intersectionEmpty(other);
// Only the other mask puts constraints on the intersection mask,
// so base the combined flags on the other mask. Only if both
// masks are nullable, will the result be nullable too.
// The result is guaranteed to be normalized, as the other type
// was normalized.
int combined = other.flags & ((flags & 1) | ~1);
if (other.flags == combined) {
return other;
} else {
return new FlatTypeMask.normalized(other.base, combined, classWorld);
}
}
TypeMask intersectionStrictSubtype(FlatTypeMask other,
ClassWorld classWorld) {
assert(base != other.base);
assert(classWorld.isSubtypeOf(other.base, base));
if (!isSubtype) return intersectionHelper(other, classWorld);
// Only the other mask puts constraints on the intersection mask,
// so base the combined flags on the other mask. Only if both
// masks are nullable, will the result be nullable too.
// The result is guaranteed to be normalized, as the other type
// was normalized.
int combined = other.flags & ((flags & 1) | ~1);
if (other.flags == combined) {
return other;
} else {
return new FlatTypeMask.normalized(other.base, combined, classWorld);
}
}
TypeMask intersectionDisjoint(FlatTypeMask other, ClassWorld classWorld) {
assert(base != other.base);
assert(!classWorld.isSubtypeOf(base, other.base));
assert(!classWorld.isSubtypeOf(other.base, base));
return intersectionHelper(other, classWorld);
}
TypeMask intersectionHelper(FlatTypeMask other, ClassWorld classWorld) {
assert(base != other.base);
assert(!classWorld.isSubclassOf(base, other.base));
assert(!classWorld.isSubclassOf(other.base, base));
// If one of the masks are exact or if both of them are subclass
// masks, then the intersection is empty.
if (isExact || other.isExact) return intersectionEmpty(other);
if (isSubclass && other.isSubclass) return intersectionEmpty(other);
assert(isSubtype || other.isSubtype);
int kind = (isSubclass || other.isSubclass) ? SUBCLASS : SUBTYPE;
// Compute the set of classes that are contained in both type masks.
Set<ClassElement> common = commonContainedClasses(this, other, classWorld);
if (common == null || common.isEmpty) return intersectionEmpty(other);
// Narrow down the candidates by only looking at common classes
// that do not have a superclass or supertype that will be a
// better candidate.
Iterable<ClassElement> candidates = common.where((ClassElement each) {
bool containsSuperclass = common.contains(each.supertype.element);
// If the superclass is also a candidate, then we don't want to
// deal with this class. If we're only looking for a subclass we
// know we don't have to look at the list of interfaces because
// they can never be in the common set.
if (containsSuperclass || kind == SUBCLASS) return !containsSuperclass;
// Run through the direct supertypes of the class. If the common
// set contains the direct supertype of the class, we ignore the
// the class because the supertype is a better candidate.
for (Link link = each.interfaces; !link.isEmpty; link = link.tail) {
if (common.contains(link.head.element)) return false;
}
return true;
});
// Run through the list of candidates and compute the union. The
// 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) {
return new FlatTypeMask.normalized(cls, combined, classWorld);
});
return UnionTypeMask.unionOf(masks, classWorld);
}
TypeMask intersectionEmpty(FlatTypeMask other) {
return isNullable && other.isNullable ? new TypeMask.empty()
: 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.lookupSelector(selector);
}
/**
* Returns whether [element] is a potential target when being
* invoked on this type mask. [selector] is used to ensure library
* privacy is taken into account.
*/
bool canHit(Element element, Selector selector, ClassWorld classWorld) {
Backend backend = classWorld.backend;
assert(element.name == selector.name);
if (isEmpty) {
if (!isNullable) return false;
return hasElementIn(backend.nullImplementation, selector, element);
}
// TODO(kasperl): Can't we just avoid creating typed selectors
// based of function types?
Element self = base;
if (self.isTypedef) {
// A typedef is a function type that doesn't have any
// user-defined members.
return false;
}
ClassElement other = element.enclosingClass;
if (other == backend.nullImplementation) {
return isNullable;
} else if (isExact) {
return hasElementIn(self, selector, element);
} else if (isSubclass) {
assert(classWorld.isClosed);
return hasElementIn(self, selector, element)
|| other.isSubclassOf(self)
|| classWorld.hasAnySubclassThatMixes(self, other);
} else {
assert(isSubtype);
assert(classWorld.isClosed);
bool result = hasElementIn(self, selector, element)
|| other.implementsInterface(self)
|| classWorld.hasAnySubclassThatImplements(other, base)
|| classWorld.hasAnySubclassOfMixinUseThatImplements(other, base);
if (result) return true;
// If the class is used as a mixin, we have to check if the element
// can be hit from any of the mixin applications.
Iterable<ClassElement> mixinUses = classWorld.mixinUsesOf(self);
return mixinUses.any((mixinApplication) =>
hasElementIn(mixinApplication, selector, element)
|| other.isSubclassOf(mixinApplication)
|| classWorld.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,
World world) {
assert(invariant(cls,
world.compiler.resolverWorld.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, world);
}
bool needsNoSuchMethodHandling(Selector selector, ClassWorld classWorld) {
// A call on an empty type mask is either dead code, or a call on
// `null`.
if (isEmpty) 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.
// 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 (!classWorld.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, classWorld);
}
bool baseNeedsNoSuchMethod = needsNoSuchMethod(base);
if (isExact || baseNeedsNoSuchMethod) {
return baseNeedsNoSuchMethod;
}
Iterable<ClassElement> subclassesToCheck;
if (isSubtype) {
subclassesToCheck = classWorld.subtypesOf(base);
} else {
assert(isSubclass);
subclassesToCheck = classWorld.subclassesOf(base);
}
return subclassesToCheck != null &&
subclassesToCheck.any(needsNoSuchMethod);
}
Element locateSingleElement(Selector selector, Compiler compiler) {
if (isEmpty) return null;
Iterable<Element> targets = compiler.world.allFunctions.filter(selector);
if (targets.length != 1) return null;
Element result = targets.first;
ClassElement enclosing = result.enclosingClass;
// We only return the found element if it is guaranteed to be
// implemented on the exact receiver type. It could be found in a
// subclass or in an inheritance-wise unrelated class in case of
// subtype selectors.
return (base.isSubclassOf(enclosing)) ? result : null;
}
bool operator ==(var other) {
if (other is !FlatTypeMask) return false;
FlatTypeMask otherMask = other;
return (flags == otherMask.flags) && (base == otherMask.base);
}
int get hashCode {
return (base == null ? 0 : base.hashCode) + 31 * flags.hashCode;
}
String toString() {
if (isEmpty) return isNullable ? '[null]' : '[empty]';
StringBuffer buffer = new StringBuffer();
if (isNullable) buffer.write('null|');
if (isExact) buffer.write('exact=');
if (isSubclass) buffer.write('subclass=');
if (isSubtype) buffer.write('subtype=');
buffer.write(base.name);
return "[$buffer]";
}
static Set<ClassElement> commonContainedClasses(FlatTypeMask x,
FlatTypeMask y,
ClassWorld classWorld) {
Iterable<ClassElement> xSubset = containedSubset(x, classWorld);
if (xSubset == null) return null;
Iterable<ClassElement> ySubset = containedSubset(y, classWorld);
if (ySubset == null) return null;
Iterable<ClassElement> smallSet, largeSet;
if (xSubset.length <= ySubset.length) {
smallSet = xSubset;
largeSet = ySubset;
} else {
smallSet = ySubset;
largeSet = xSubset;
}
var result = smallSet.where((ClassElement each) => largeSet.contains(each));
return result.toSet();
}
static Iterable<ClassElement> containedSubset(FlatTypeMask x,
ClassWorld classWorld) {
ClassElement element = x.base;
if (x.isExact) {
return null;
} else if (x.isSubclass) {
return classWorld.subclassesOf(element);
} else {
assert(x.isSubtype);
return classWorld.subtypesOf(element);
}
}
}