blob: c77bd0282f9782b4fb945113a082a3fea8a74659 [file] [log] [blame]
// Copyright (c) 2015, 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.
library dart2js.type_mask_system;
import '../common/names.dart' show Selectors, Identifiers;
import '../compiler.dart' as dart2js show Compiler;
import '../constants/values.dart';
import '../dart_types.dart' as types;
import '../elements/elements.dart';
import '../js_backend/backend_helpers.dart' show BackendHelpers;
import '../js_backend/js_backend.dart' show JavaScriptBackend;
import '../types/abstract_value_domain.dart';
import '../types/types.dart';
import '../types/constants.dart' show computeTypeMask;
import '../universe/selector.dart' show Selector;
import '../world.dart' show World;
import '../closure.dart' show ClosureFieldElement, BoxLocal, TypeVariableLocal;
class TypeMaskSystem implements AbstractValueDomain {
final TypesTask inferrer;
final World classWorld;
final JavaScriptBackend backend;
TypeMask _numStringBoolType;
TypeMask _fixedLengthType;
TypeMask _interceptorType;
TypeMask _interceptedTypes; // Does not include null.
TypeMask __indexableTypeTest;
// The full type of a constant (e.g. a ContainerTypeMask) is not available on
// the constant. Type inference flows the type to some place where it is used,
// e.g. a parameter. For constant values that are the value of static const
// fields we need to remember the association.
final Map<ConstantValue, TypeMask> _constantMasks =
<ConstantValue, TypeMask>{};
@override
TypeMask get dynamicType => inferrer.dynamicType;
@override
TypeMask get typeType => inferrer.typeType;
@override
TypeMask get functionType => inferrer.functionType;
@override
TypeMask get boolType => inferrer.boolType;
@override
TypeMask get intType => inferrer.intType;
@override
TypeMask get doubleType => inferrer.doubleType;
@override
TypeMask get numType => inferrer.numType;
@override
TypeMask get stringType => inferrer.stringType;
@override
TypeMask get listType => inferrer.listType;
@override
TypeMask get mapType => inferrer.mapType;
@override
TypeMask get nonNullType => inferrer.nonNullType;
@override
TypeMask get nullType => inferrer.nullType;
@override
TypeMask get extendableArrayType => backend.extendableArrayType;
@override
TypeMask get fixedArrayType => backend.fixedArrayType;
@override
TypeMask get arrayType =>
new TypeMask.nonNullSubclass(helpers.jsArrayClass, classWorld);
@override
TypeMask get uint31Type => inferrer.uint31Type;
@override
TypeMask get uint32Type => inferrer.uint32Type;
@override
TypeMask get uintType => inferrer.positiveIntType;
@override
TypeMask get numStringBoolType {
if (_numStringBoolType == null) {
// Build the number+string+bool type. To make containment tests more
// inclusive, we use the num, String, bool types for this, not
// the JSNumber, JSString, JSBool subclasses.
TypeMask anyNum =
new TypeMask.nonNullSubtype(classWorld.numClass, classWorld);
TypeMask anyString =
new TypeMask.nonNullSubtype(classWorld.stringClass, classWorld);
TypeMask anyBool =
new TypeMask.nonNullSubtype(classWorld.boolClass, classWorld);
_numStringBoolType = new TypeMask.unionOf(
<TypeMask>[anyNum, anyString, anyBool], classWorld);
}
return _numStringBoolType;
}
@override
TypeMask get fixedLengthType {
if (_fixedLengthType == null) {
List<TypeMask> fixedLengthTypes = <TypeMask>[
stringType,
backend.fixedArrayType
];
if (classWorld.isInstantiated(helpers.typedArrayClass)) {
fixedLengthTypes.add(nonNullSubclass(helpers.typedArrayClass));
}
_fixedLengthType = new TypeMask.unionOf(fixedLengthTypes, classWorld);
}
return _fixedLengthType;
}
@override
TypeMask get interceptorType {
if (_interceptorType == null) {
_interceptorType =
new TypeMask.nonNullSubtype(helpers.jsInterceptorClass, classWorld);
}
return _interceptorType;
}
@override
TypeMask get interceptedTypes {
// Does not include null.
if (_interceptedTypes == null) {
// We redundantly include subtypes of num/string/bool as intercepted
// types, because the type system does not infer that their
// implementations are all subclasses of Interceptor.
_interceptedTypes = new TypeMask.unionOf(
<TypeMask>[interceptorType, numStringBoolType], classWorld);
}
return _interceptedTypes;
}
TypeMask get _indexableTypeTest {
if (__indexableTypeTest == null) {
// Make a TypeMask containing Indexable and (redundantly) subtypes of
// string because the type inference does not infer that all strings are
// indexables.
TypeMask indexable =
new TypeMask.nonNullSubtype(helpers.jsIndexableClass, classWorld);
TypeMask anyString =
new TypeMask.nonNullSubtype(classWorld.stringClass, classWorld);
__indexableTypeTest =
new TypeMask.unionOf(<TypeMask>[indexable, anyString], classWorld);
}
return __indexableTypeTest;
}
ClassElement get jsNullClass => helpers.jsNullClass;
BackendHelpers get helpers => backend.helpers;
// TODO(karlklose): remove compiler here.
TypeMaskSystem(dart2js.Compiler compiler)
: inferrer = compiler.typesTask,
classWorld = compiler.world,
backend = compiler.backend {}
@override
bool methodIgnoresReceiverArgument(FunctionElement function) {
assert(backend.isInterceptedMethod(function));
ClassElement clazz = function.enclosingClass.declaration;
return !clazz.isSubclassOf(helpers.jsInterceptorClass) &&
!classWorld.isUsedAsMixin(clazz);
}
@override
bool targetIgnoresReceiverArgument(TypeMask type, Selector selector) {
// Check if any of the possible targets depend on the extra receiver
// argument. Mixins do this, and tear-offs always needs the extra receiver
// argument because BoundClosure uses it for equality and hash code.
// TODO(15933): Make automatically generated property extraction
// closures work with the dummy receiver optimization.
bool needsReceiver(Element target) {
if (target is! FunctionElement) return false;
FunctionElement function = target;
return selector.isGetter && !function.isGetter ||
!methodIgnoresReceiverArgument(function);
}
return !classWorld.allFunctions.filter(selector, type).any(needsReceiver);
}
@override
Element locateSingleElement(TypeMask mask, Selector selector) {
return mask.locateSingleElement(selector, mask, classWorld.compiler);
}
@override
ClassElement singleClass(TypeMask mask) {
return mask.singleClass(classWorld);
}
@override
bool needsNoSuchMethodHandling(TypeMask mask, Selector selector) {
return mask.needsNoSuchMethodHandling(selector, classWorld);
}
@override
TypeMask getReceiverType(MethodElement method) {
assert(method.isInstanceMember);
if (classWorld.isUsedAsMixin(method.enclosingClass.declaration)) {
// If used as a mixin, the receiver could be any of the classes that mix
// in the class, and these are not considered subclasses.
// TODO(asgerf): Exclude the subtypes that only `implement` the class.
return nonNullSubtype(method.enclosingClass);
} else {
return nonNullSubclass(method.enclosingClass);
}
}
@override
TypeMask getParameterType(ParameterElement parameter) {
return inferrer.getGuaranteedTypeOfElement(parameter);
}
@override
TypeMask getReturnType(FunctionElement function) {
return inferrer.getGuaranteedReturnTypeOfElement(function);
}
@override
TypeMask getInvokeReturnType(Selector selector, TypeMask mask) {
TypeMask result = inferrer.getGuaranteedTypeOfSelector(selector, mask);
// Tearing off .call from a function returns the function itself.
if (selector.isGetter &&
selector.name == Identifiers.call &&
!areDisjoint(functionType, mask)) {
result = join(result, functionType);
}
return result;
}
@override
TypeMask getFieldType(FieldElement field) {
if (field is ClosureFieldElement) {
// The type inference does not report types for all closure fields.
// Box fields are never null.
if (field.local is BoxLocal) return nonNullType;
// Closure fields for type variables contain the internal representation
// of the type (which can be null), not the Type object.
if (field.local is TypeVariableLocal) return dynamicType;
}
return inferrer.getGuaranteedTypeOfElement(field);
}
@override
TypeMask join(TypeMask a, TypeMask b) {
return a.union(b, classWorld);
}
@override
TypeMask intersection(TypeMask a, TypeMask b) {
if (a == null) return b;
if (b == null) return a;
return a.intersection(b, classWorld);
}
void associateConstantValueWithElement(
ConstantValue constant, Element element) {
// TODO(25093): Replace this code with an approach that works for anonymous
// constants and non-constant literals.
if (constant is ListConstantValue || constant is MapConstantValue) {
// Inferred type is usually better (e.g. a ContainerTypeMask) but is
// occasionally less general.
TypeMask computed = computeTypeMask(inferrer.compiler, constant);
TypeMask inferred = inferrer.getGuaranteedTypeOfElement(element);
TypeMask best = intersection(inferred, computed);
assert(!best.isEmptyOrNull);
_constantMasks[constant] = best;
}
}
@override
TypeMask getTypeOf(ConstantValue constant) {
return _constantMasks[constant] ??
computeTypeMask(inferrer.compiler, constant);
}
@override
ConstantValue getConstantOf(TypeMask mask) {
if (!mask.isValue) return null;
if (mask.isNullable) return null; // e.g. 'true or null'.
ValueTypeMask valueMask = mask;
if (valueMask.value.isBool) return valueMask.value;
// TODO(sra): Consider other values. Be careful with large strings.
return null;
}
@override
TypeMask nonNullExact(ClassElement element) {
// TODO(johnniwinther): I don't think the follow is valid anymore.
// The class world does not know about classes created by
// closure conversion, so just treat those as a subtypes of Function.
// TODO(asgerf): Maybe closure conversion should create a new ClassWorld?
if (element.isClosure) return functionType;
return new TypeMask.nonNullExact(element.declaration, classWorld);
}
@override
TypeMask nonNullSubclass(ClassElement element) {
if (element.isClosure) return functionType;
return new TypeMask.nonNullSubclass(element.declaration, classWorld);
}
@override
TypeMask nonNullSubtype(ClassElement element) {
if (element.isClosure) return functionType;
return new TypeMask.nonNullSubtype(element.declaration, classWorld);
}
@override
bool isDefinitelyBool(TypeMask t, {bool allowNull: false}) {
if (!allowNull && t.isNullable) return false;
return t.nonNullable().containsOnlyBool(classWorld);
}
@override
bool isDefinitelyNum(TypeMask t, {bool allowNull: false}) {
if (!allowNull && t.isNullable) return false;
return t.nonNullable().containsOnlyNum(classWorld);
}
@override
bool isDefinitelyString(TypeMask t, {bool allowNull: false}) {
if (!allowNull && t.isNullable) return false;
return t.nonNullable().containsOnlyString(classWorld);
}
@override
bool isDefinitelyNumStringBool(TypeMask t, {bool allowNull: false}) {
if (!allowNull && t.isNullable) return false;
return numStringBoolType.containsMask(t.nonNullable(), classWorld);
}
@override
bool isDefinitelyNotNumStringBool(TypeMask t) {
return areDisjoint(t, numStringBoolType);
}
/// True if all values of [t] are either integers or not numbers at all.
///
/// This does not imply that the value is an integer, since most other values
/// such as null are also not a non-integer double.
@override
bool isDefinitelyNotNonIntegerDouble(TypeMask t) {
// Even though int is a subclass of double in the JS type system, we can
// still check this with disjointness, because [doubleType] is the *exact*
// double class, so this excludes things that are known to be instances of a
// more specific class.
// We currently exploit that there are no subclasses of double that are
// not integers (e.g. there is no UnsignedDouble class or whatever).
return areDisjoint(t, doubleType);
}
@override
bool isDefinitelyNonNegativeInt(TypeMask t, {bool allowNull: false}) {
if (!allowNull && t.isNullable) return false;
// The JSPositiveInt class includes zero, despite the name.
return t.satisfies(helpers.jsPositiveIntClass, classWorld);
}
@override
bool isDefinitelyInt(TypeMask t, {bool allowNull: false}) {
if (!allowNull && t.isNullable) return false;
return t.nonNullable().containsOnlyInt(classWorld);
}
@override
bool isDefinitelyUint31(TypeMask t, {bool allowNull: false}) {
if (!allowNull && t.isNullable) return false;
return t.satisfies(helpers.jsUInt31Class, classWorld);
}
@override
bool isDefinitelyUint32(TypeMask t, {bool allowNull: false}) {
if (!allowNull && t.isNullable) return false;
return t.satisfies(helpers.jsUInt32Class, classWorld);
}
@override
bool isDefinitelyUint(TypeMask t, {bool allowNull: false}) {
if (!allowNull && t.isNullable) return false;
return t.satisfies(helpers.jsPositiveIntClass, classWorld);
}
@override
bool isDefinitelyArray(TypeMask t, {bool allowNull: false}) {
if (!allowNull && t.isNullable) return false;
return t.nonNullable().satisfies(helpers.jsArrayClass, classWorld);
}
@override
bool isDefinitelyMutableArray(TypeMask t, {bool allowNull: false}) {
if (!allowNull && t.isNullable) return false;
return t.nonNullable().satisfies(helpers.jsMutableArrayClass, classWorld);
}
@override
bool isDefinitelyFixedArray(TypeMask t, {bool allowNull: false}) {
if (!allowNull && t.isNullable) return false;
return t.nonNullable().satisfies(helpers.jsFixedArrayClass, classWorld);
}
@override
bool isDefinitelyExtendableArray(TypeMask t, {bool allowNull: false}) {
if (!allowNull && t.isNullable) return false;
return t
.nonNullable()
.satisfies(helpers.jsExtendableArrayClass, classWorld);
}
@override
bool isDefinitelyIndexable(TypeMask t, {bool allowNull: false}) {
if (!allowNull && t.isNullable) return false;
return _indexableTypeTest.containsMask(t.nonNullable(), classWorld);
}
@override
bool isDefinitelyMutableIndexable(TypeMask t, {bool allowNull: false}) {
if (!allowNull && t.isNullable) return false;
return t
.nonNullable()
.satisfies(helpers.jsMutableIndexableClass, classWorld);
}
@override
bool isDefinitelyFixedLengthIndexable(TypeMask t, {bool allowNull: false}) {
if (!allowNull && t.isNullable) return false;
return fixedLengthType.containsMask(t.nonNullable(), classWorld);
}
@override
bool isDefinitelyIntercepted(TypeMask t, {bool allowNull}) {
assert(allowNull != null);
if (!allowNull && t.isNullable) return false;
return interceptedTypes.containsMask(t.nonNullable(), classWorld);
}
@override
bool isDefinitelySelfInterceptor(TypeMask t, {bool allowNull: false}) {
assert(allowNull != null);
if (!allowNull && t.isNullable) return false;
return areDisjoint(t, interceptorType);
}
/// Given a class from the interceptor hierarchy, returns a [TypeMask]
/// matching all values with that interceptor (or a subtype thereof).
@override
TypeMask getInterceptorSubtypes(ClassElement class_) {
if (class_ == helpers.jsInterceptorClass) {
return interceptorType.nullable();
} else if (class_ == helpers.jsNullClass) {
return nullType;
} else {
return nonNullSubclass(class_);
}
}
@override
bool areDisjoint(TypeMask leftType, TypeMask rightType) =>
leftType.isDisjoint(rightType, classWorld);
@override
bool isMorePreciseOrEqual(TypeMask t1, TypeMask t2) {
return t2.containsMask(t1, classWorld);
}
@override
AbstractBool isSubtypeOf(TypeMask value, types.DartType type,
{bool allowNull}) {
assert(allowNull != null);
if (type is types.DynamicType) {
return AbstractBool.True;
}
if (type is types.InterfaceType) {
TypeMask typeAsMask = allowNull
? new TypeMask.subtype(type.element, classWorld)
: new TypeMask.nonNullSubtype(type.element, classWorld);
if (areDisjoint(value, typeAsMask)) {
// Disprove the subtype relation based on the class alone.
return AbstractBool.False;
}
if (!type.treatAsRaw) {
// If there are type arguments, we cannot prove the subtype relation,
// because the type arguments are unknown on both the value and type.
return AbstractBool.Maybe;
}
if (typeAsMask.containsMask(value, classWorld)) {
// All possible values are contained in the set of allowed values.
// Note that we exploit the fact that [typeAsMask] is an exact
// representation of [type], not an approximation.
return AbstractBool.True;
}
// The value is neither contained in the type, nor disjoint from the type.
return AbstractBool.Maybe;
}
// TODO(asgerf): Support function types, and what else might be missing.
return AbstractBool.Maybe;
}
/// Returns whether [type] is one of the falsy values: false, 0, -0, NaN,
/// the empty string, or null.
@override
AbstractBool boolify(TypeMask type) {
if (isDefinitelyNotNumStringBool(type) && !type.isNullable) {
return AbstractBool.True;
}
return AbstractBool.Maybe;
}
@override
AbstractBool strictBoolify(TypeMask type) {
if (areDisjoint(type, boolType)) return AbstractBool.False;
return AbstractBool.Maybe;
}
/// Create a type mask containing at least all subtypes of [type].
@override
TypeMask subtypesOf(types.DartType type) {
if (type is types.InterfaceType) {
ClassElement element = type.element;
if (element.isObject) {
return dynamicType;
}
if (element == classWorld.nullClass) {
return nullType;
}
if (element == classWorld.stringClass) {
return stringType;
}
if (element == classWorld.numClass || element == classWorld.doubleClass) {
return numType;
}
if (element == classWorld.intClass) {
return intType;
}
if (element == classWorld.boolClass) {
return boolType;
}
return new TypeMask.nonNullSubtype(element, classWorld);
}
if (type is types.FunctionType) {
return functionType;
}
return dynamicType;
}
/// Returns a subset of [mask] containing at least the types
/// that can respond to [selector] without throwing.
@override
TypeMask receiverTypeFor(Selector selector, TypeMask mask) {
return classWorld.allFunctions.receiverType(selector, mask);
}
/// The result of an index operation on something of [type], or the dynamic
/// type if unknown.
@override
TypeMask elementTypeOfIndexable(TypeMask type) {
if (type is UnionTypeMask) {
return new TypeMask.unionOf(
type.disjointMasks.map(elementTypeOfIndexable), classWorld);
}
if (type is ContainerTypeMask) {
return type.elementType;
}
if (isDefinitelyString(type)) {
return stringType;
}
if (type.satisfies(helpers.jsIndexingBehaviorInterface, classWorld)) {
return getInvokeReturnType(new Selector.index(), type);
}
return dynamicType;
}
/// The length of something of [type], or `null` if unknown.
@override
int getContainerLength(TypeMask type) {
if (type is ContainerTypeMask) {
return type.length;
} else {
return null;
}
}
/// Returns the type of the entry at a given index, `null` if unknown.
TypeMask indexWithConstant(TypeMask container, ConstantValue indexValue) {
if (container is DictionaryTypeMask) {
if (indexValue is StringConstantValue) {
String key = indexValue.primitiveValue.slowToString();
TypeMask result = container.typeMap[key];
if (result != null) return result;
}
}
if (container is ContainerTypeMask) {
return container.elementType;
}
return null;
}
}