blob: 893113f3f7730377908fdefa759a5f3e2c9b1c7e [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 '../closure.dart' show ClosureClassElement;
import '../common/names.dart' show Selectors, Identifiers;
import '../compiler.dart' as dart2js show Compiler;
import '../constants/constant_system.dart';
import '../constants/values.dart';
import '../dart_types.dart' as types;
import '../elements/elements.dart';
import '../io/source_information.dart' show SourceInformation;
import '../js_backend/js_backend.dart' show JavaScriptBackend;
import '../types/types.dart';
import '../types/constants.dart' show computeTypeMask;
import '../universe/selector.dart' show Selector;
import '../world.dart' show World;
enum AbstractBool {
True, False, Maybe, Nothing
}
class TypeMaskSystem {
final TypesTask inferrer;
final World classWorld;
final JavaScriptBackend backend;
TypeMask get dynamicType => inferrer.dynamicType;
TypeMask get typeType => inferrer.typeType;
TypeMask get functionType => inferrer.functionType;
TypeMask get boolType => inferrer.boolType;
TypeMask get intType => inferrer.intType;
TypeMask get doubleType => inferrer.doubleType;
TypeMask get numType => inferrer.numType;
TypeMask get stringType => inferrer.stringType;
TypeMask get listType => inferrer.listType;
TypeMask get mapType => inferrer.mapType;
TypeMask get nonNullType => inferrer.nonNullType;
TypeMask get nullType => inferrer.nullType;
TypeMask get extendableNativeListType => backend.extendableArrayType;
TypeMask numStringBoolType;
ClassElement get jsNullClass => backend.jsNullClass;
// TODO(karlklose): remove compiler here.
TypeMaskSystem(dart2js.Compiler compiler)
: inferrer = compiler.typesTask,
classWorld = compiler.world,
backend = compiler.backend {
// 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);
}
bool methodUsesReceiverArgument(FunctionElement function) {
assert(backend.isInterceptedMethod(function));
ClassElement clazz = function.enclosingClass.declaration;
return clazz.isSubclassOf(backend.jsInterceptorClass) ||
classWorld.isUsedAsMixin(clazz);
}
Element locateSingleElement(TypeMask mask, Selector selector) {
return mask.locateSingleElement(selector, mask, classWorld.compiler);
}
ClassElement singleClass(TypeMask mask) {
return mask.singleClass(classWorld);
}
bool needsNoSuchMethodHandling(TypeMask mask, Selector selector) {
return mask.needsNoSuchMethodHandling(selector, classWorld);
}
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);
}
}
TypeMask getParameterType(ParameterElement parameter) {
return inferrer.getGuaranteedTypeOfElement(parameter);
}
TypeMask getReturnType(FunctionElement function) {
return inferrer.getGuaranteedReturnTypeOfElement(function);
}
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;
}
TypeMask getFieldType(FieldElement field) {
return inferrer.getGuaranteedTypeOfElement(field);
}
TypeMask join(TypeMask a, TypeMask b) {
return a.union(b, classWorld);
}
TypeMask getTypeOf(ConstantValue constant) {
return computeTypeMask(inferrer.compiler, constant);
}
// Returns the constant value if a TypeMask represents a single value.
// Returns `null` if [mask] is not a constant.
ConstantValue getConstantOf(TypeMask mask) {
if (!mask.isValue) return null;
if (mask.isNullable) return null; // e.g. 'true or null'.
ValueTypeMask valueMask = mask;
var value = valueMask.value;
// TODO(sra): Why is ValueTypeMask.value not a ConstantValue?
if (value == false) return new FalseConstantValue();
if (value == true) return new TrueConstantValue();
// TODO(sra): Consider other values. Be careful with large strings.
return null;
}
TypeMask nonNullExact(ClassElement element) {
// 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);
}
TypeMask nonNullSubclass(ClassElement element) {
if (element.isClosure) return functionType;
return new TypeMask.nonNullSubclass(element.declaration, classWorld);
}
TypeMask nonNullSubtype(ClassElement element) {
if (element.isClosure) return functionType;
return new TypeMask.nonNullSubtype(element.declaration, classWorld);
}
bool isDefinitelyBool(TypeMask t, {bool allowNull: false}) {
if (!allowNull && t.isNullable) return false;
return t.nonNullable().containsOnlyBool(classWorld);
}
bool isDefinitelyNum(TypeMask t, {bool allowNull: false}) {
if (!allowNull && t.isNullable) return false;
return t.nonNullable().containsOnlyNum(classWorld);
}
bool isDefinitelyString(TypeMask t, {bool allowNull: false}) {
if (!allowNull && t.isNullable) return false;
return t.nonNullable().containsOnlyString(classWorld);
}
bool isDefinitelyNumStringBool(TypeMask t, {bool allowNull: false}) {
if (!allowNull && t.isNullable) return false;
return numStringBoolType.containsMask(t.nonNullable(), classWorld);
}
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.
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);
}
bool isDefinitelyInt(TypeMask t, {bool allowNull: false}) {
if (!allowNull && t.isNullable) return false;
return t.satisfies(backend.jsIntClass, classWorld);
}
bool isDefinitelyNativeList(TypeMask t, {bool allowNull: false}) {
if (!allowNull && t.isNullable) return false;
return t.nonNullable().satisfies(backend.jsArrayClass, classWorld);
}
bool isDefinitelyMutableNativeList(TypeMask t, {bool allowNull: false}) {
if (!allowNull && t.isNullable) return false;
return t.nonNullable().satisfies(backend.jsMutableArrayClass, classWorld);
}
bool isDefinitelyFixedNativeList(TypeMask t, {bool allowNull: false}) {
if (!allowNull && t.isNullable) return false;
return t.nonNullable().satisfies(backend.jsFixedArrayClass, classWorld);
}
bool isDefinitelyExtendableNativeList(TypeMask t, {bool allowNull: false}) {
if (!allowNull && t.isNullable) return false;
return t.nonNullable().satisfies(backend.jsExtendableArrayClass,
classWorld);
}
bool isDefinitelyIndexable(TypeMask t, {bool allowNull: false}) {
if (!allowNull && t.isNullable) return false;
return t.nonNullable().satisfies(backend.jsIndexableClass, classWorld);
}
bool areDisjoint(TypeMask leftType, TypeMask rightType) {
TypeMask intersection = leftType.intersection(rightType, classWorld);
return intersection.isEmpty && !intersection.isNullable;
}
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.
AbstractBool boolify(TypeMask type) {
if (isDefinitelyNotNumStringBool(type) && !type.isNullable) {
return AbstractBool.True;
}
return AbstractBool.Maybe;
}
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].
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.
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.
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(backend.typedArrayClass, classWorld)) {
if (type.satisfies(backend.typedArrayOfIntClass, classWorld)) {
return intType;
}
return numType;
}
return dynamicType;
}
/// The length of something of [type], or `null` if unknown.
int getContainerLength(TypeMask type) {
if (type is ContainerTypeMask) {
return type.length;
} else {
return null;
}
}
}