| // 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.isEmpty); |
| _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; |
| } |
| } |