blob: bfdb564ca4c76e79e547ee64b7ed63289e38e93d [file] [log] [blame]
// Copyright (c) 2012, 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 ssa;
abstract class HType {
const HType();
/**
* Returns an [HType] with the given type mask. The factory method
* takes care to track whether or not the resulting type may be a
* primitive type.
*/
factory HType.fromMask(TypeMask mask, Compiler compiler) {
bool isNullable = mask.isNullable;
if (mask.isEmpty) {
return isNullable ? HType.NULL : HType.CONFLICTING;
}
JavaScriptBackend backend = compiler.backend;
if (mask.containsOnlyInt(compiler)) {
return isNullable ? HType.INTEGER_OR_NULL : HType.INTEGER;
} else if (mask.containsOnlyDouble(compiler)) {
return isNullable ? HType.DOUBLE_OR_NULL : HType.DOUBLE;
} else if (mask.containsOnlyNum(compiler)
|| mask.satisfies(backend.jsNumberClass, compiler)) {
return isNullable ? HType.NUMBER_OR_NULL : HType.NUMBER;
} else if (mask.containsOnlyString(compiler)) {
// TODO(ngeoffray): Avoid creating [TypeMask]s with the string
// class as base.
return isNullable
? new HBoundedType(new TypeMask.exact(backend.jsStringClass))
: backend.stringType;
} else if (mask.containsOnlyBool(compiler)) {
return isNullable ? HType.BOOLEAN_OR_NULL : HType.BOOLEAN;
} else if (mask.containsOnlyNull(compiler)) {
return HType.NULL;
}
// TODO(kasperl): A lot of the code in the system currently
// expects the top type to be 'unknown'. I'll rework this.
if (mask.containsAll(compiler)) {
return isNullable ? HType.UNKNOWN : HType.NON_NULL;
}
return new HBoundedType(mask);
}
factory HType.exact(ClassElement type, Compiler compiler) {
TypeMask mask = new TypeMask.exact(type.declaration);
return new HType.fromMask(mask, compiler);
}
factory HType.subclass(ClassElement type, Compiler compiler) {
TypeMask mask = new TypeMask.subclass(type.declaration);
return new HType.fromMask(mask, compiler);
}
factory HType.subtype(ClassElement type, Compiler compiler) {
TypeMask mask = new TypeMask.subtype(type.declaration);
return new HType.fromMask(mask, compiler);
}
factory HType.nonNullExact(ClassElement type, Compiler compiler) {
TypeMask mask = new TypeMask.nonNullExact(type.declaration);
return new HType.fromMask(mask, compiler);
}
factory HType.nonNullSubclass(ClassElement type, Compiler compiler) {
TypeMask mask = new TypeMask.nonNullSubclass(type.declaration);
return new HType.fromMask(mask, compiler);
}
factory HType.nonNullSubtype(ClassElement type, Compiler compiler) {
TypeMask mask = new TypeMask.nonNullSubtype(type.declaration);
return new HType.fromMask(mask, compiler);
}
factory HType.fromInferredType(TypeMask mask, Compiler compiler) {
if (mask == null) return HType.UNKNOWN;
return new HType.fromMask(mask, compiler);
}
factory HType.inferredReturnTypeForElement(
Element element, Compiler compiler) {
return new HType.fromInferredType(
compiler.typesTask.getGuaranteedReturnTypeOfElement(element),
compiler);
}
factory HType.inferredTypeForElement(Element element, Compiler compiler) {
return new HType.fromInferredType(
compiler.typesTask.getGuaranteedTypeOfElement(element),
compiler);
}
factory HType.inferredTypeForSelector(Selector selector, Compiler compiler) {
return new HType.fromInferredType(
compiler.typesTask.getGuaranteedTypeOfSelector(selector),
compiler);
}
factory HType.inferredForNode(Element owner, Node node, Compiler compiler) {
return new HType.fromInferredType(
compiler.typesTask.getGuaranteedTypeOfNode(owner, node),
compiler);
}
factory HType.fromNativeBehavior(native.NativeBehavior nativeBehavior,
Compiler compiler) {
if (nativeBehavior.typesReturned.isEmpty) return HType.UNKNOWN;
HType result = nativeBehavior.typesReturned
.map((type) => fromNativeType(type, compiler))
.reduce((t1, t2) => t1.union(t2, compiler));
assert(!result.isConflicting());
return result;
}
// [type] is either an instance of [DartType] or special objects
// like [native.SpecialType.JsObject].
static HType fromNativeType(type, Compiler compiler) {
if (type == native.SpecialType.JsObject) {
return new HType.nonNullExact(compiler.objectClass, compiler);
} else if (type.isVoid) {
return HType.NULL;
} else if (type.element == compiler.nullClass) {
return HType.NULL;
} else if (type.treatAsDynamic) {
return HType.UNKNOWN;
} else if (compiler.world.hasAnySubtype(type.element)) {
return new HType.nonNullSubtype(type.element, compiler);
} else if (compiler.world.hasAnySubclass(type.element)) {
return new HType.nonNullSubclass(type.element, compiler);
} else {
return new HType.nonNullExact(type.element, compiler);
}
}
static const HType CONFLICTING = const HConflictingType();
static const HType UNKNOWN = const HUnknownType();
static const HType NON_NULL = const HNonNullType();
static const HType BOOLEAN = const HBooleanType();
static const HType NUMBER = const HNumberType();
static const HType INTEGER = const HIntegerType();
static const HType DOUBLE = const HDoubleType();
static const HType NULL = const HNullType();
static const HType BOOLEAN_OR_NULL = const HBooleanOrNullType();
static const HType NUMBER_OR_NULL = const HNumberOrNullType();
static const HType INTEGER_OR_NULL = const HIntegerOrNullType();
static const HType DOUBLE_OR_NULL = const HDoubleOrNullType();
bool isConflicting() => identical(this, CONFLICTING);
bool isUnknown() => identical(this, UNKNOWN);
bool isExact() => false;
bool isNull() => false;
bool isBoolean() => false;
bool isNumber() => false;
bool isInteger() => false;
bool isDouble() => false;
bool isString(Compiler compiler) => false;
bool isFixedArray(Compiler compiler) => false;
bool isReadableArray(Compiler compiler) => false;
bool isMutableArray(Compiler compiler) => false;
bool isExtendableArray(Compiler compiler) => false;
bool isPrimitive(Compiler compiler) => false;
bool isPrimitiveOrNull(Compiler compiler) => false;
bool isBooleanOrNull() => false;
bool isNumberOrNull() => false;
bool isIntegerOrNull() => false;
bool isDoubleOrNull() => false;
// TODO(kasperl): Get rid of this one.
bool isIndexablePrimitive(Compiler compiler) => false;
bool isIndexable(Compiler compiler) {
JavaScriptBackend backend = compiler.backend;
return implementsInterface(backend.jsIndexableClass, compiler);
}
bool isMutableIndexable(Compiler compiler) {
JavaScriptBackend backend = compiler.backend;
return implementsInterface(backend.jsMutableIndexableClass, compiler);
}
bool implementsInterface(ClassElement interfaceElement, Compiler compiler) {
TypeMask mask = new TypeMask.subtype(interfaceElement);
return mask == mask.union(computeMask(compiler), compiler);
}
bool canBeNull() => false;
bool canBePrimitive(Compiler compiler) => false;
bool canBePrimitiveNumber(Compiler compiler) => false;
bool canBePrimitiveString(Compiler compiler) => false;
bool canBePrimitiveArray(Compiler compiler) => false;
bool canBePrimitiveBoolean(Compiler compiler) => false;
/** A type is useful it is not unknown, not conflicting, and not null. */
bool isUseful() => !isUnknown() && !isConflicting() && !isNull();
/** Alias for isReadableArray. */
bool isArray(Compiler compiler) => isReadableArray(compiler);
TypeMask computeMask(Compiler compiler);
Selector refine(Selector selector, Compiler compiler) {
// TODO(kasperl): Should we check if the refinement really is more
// specialized than the starting point?
TypeMask mask = computeMask(compiler);
if (selector.mask == mask) return selector;
return new TypedSelector(mask, selector);
}
/**
* The intersection of two types is the intersection of its values. For
* example:
* * INTEGER.intersect(NUMBER) => INTEGER.
* * DOUBLE.intersect(INTEGER) => CONFLICTING.
* * MUTABLE_ARRAY.intersect(READABLE_ARRAY) => MUTABLE_ARRAY.
*
* When there is no predefined type to represent the intersection returns
* [CONFLICTING].
*
* An intersection with [UNKNOWN] returns the non-UNKNOWN type. An
* intersection with [CONFLICTING] returns [CONFLICTING].
*/
HType intersection(HType other, Compiler compiler) {
TypeMask mask = computeMask(compiler);
TypeMask otherMask = other.computeMask(compiler);
TypeMask intersection = mask.intersection(otherMask, compiler);
return new HType.fromMask(intersection, compiler);
}
/**
* The union of two types is the union of its values. For example:
* * INTEGER.union(NUMBER) => NUMBER.
* * DOUBLE.union(INTEGER) => NUMBER.
* * MUTABLE_ARRAY.union(READABLE_ARRAY) => READABLE_ARRAY.
*
* When there is no predefined type to represent the union returns
* [UNKNOWN].
*
* A union with [UNKNOWN] returns [UNKNOWN].
* A union of [CONFLICTING] with any other types returns the other type.
*/
HType union(HType other, Compiler compiler) {
TypeMask mask = computeMask(compiler);
TypeMask otherMask = other.computeMask(compiler);
TypeMask union = mask.union(otherMask, compiler);
return new HType.fromMask(union, compiler);
}
HType simplify(Compiler compiler) => this;
HType nonNullable(compiler) {
return new HType.fromMask(computeMask(compiler).nonNullable(), compiler);
}
}
/** Used to represent [HType.UNKNOWN] and [HType.CONFLICTING]. */
abstract class HAnalysisType extends HType {
final String name;
const HAnalysisType(this.name);
String toString() => name;
}
class HUnknownType extends HAnalysisType {
const HUnknownType() : super("unknown");
bool canBePrimitive(Compiler compiler) => true;
bool canBeNull() => true;
bool canBePrimitiveNumber(Compiler compiler) => true;
bool canBePrimitiveString(Compiler compiler) => true;
bool canBePrimitiveArray(Compiler compiler) => true;
bool canBePrimitiveBoolean(Compiler compiler) => true;
TypeMask computeMask(Compiler compiler) {
return new TypeMask.subclass(compiler.objectClass);
}
}
class HNonNullType extends HAnalysisType {
const HNonNullType() : super("non-null");
bool canBePrimitive(Compiler compiler) => true;
bool canBeNull() => false;
bool canBePrimitiveNumber(Compiler compiler) => true;
bool canBePrimitiveString(Compiler compiler) => true;
bool canBePrimitiveArray(Compiler compiler) => true;
bool canBePrimitiveBoolean(Compiler compiler) => true;
TypeMask computeMask(Compiler compiler) {
return new TypeMask.nonNullSubclass(compiler.objectClass);
}
}
class HConflictingType extends HAnalysisType {
const HConflictingType() : super("conflicting");
bool canBePrimitive(Compiler compiler) => true;
bool canBeNull() => false;
TypeMask computeMask(Compiler compiler) {
return new TypeMask.nonNullEmpty();
}
}
abstract class HPrimitiveType extends HType {
const HPrimitiveType();
bool isPrimitive(Compiler compiler) => true;
bool canBePrimitive(Compiler compiler) => true;
bool isPrimitiveOrNull(Compiler compiler) => true;
}
class HNullType extends HPrimitiveType {
const HNullType();
bool canBeNull() => true;
bool isNull() => true;
String toString() => 'null type';
bool isExact() => true;
TypeMask computeMask(Compiler compiler) {
return new TypeMask.empty();
}
}
abstract class HPrimitiveOrNullType extends HType {
const HPrimitiveOrNullType();
bool canBePrimitive(Compiler compiler) => true;
bool canBeNull() => true;
bool isPrimitiveOrNull(Compiler compiler) => true;
}
class HBooleanOrNullType extends HPrimitiveOrNullType {
const HBooleanOrNullType();
String toString() => "boolean or null";
bool isBooleanOrNull() => true;
bool canBePrimitiveBoolean(Compiler compiler) => true;
TypeMask computeMask(Compiler compiler) {
JavaScriptBackend backend = compiler.backend;
return new TypeMask.exact(backend.jsBoolClass);
}
}
class HBooleanType extends HPrimitiveType {
const HBooleanType();
bool isBoolean() => true;
bool isBooleanOrNull() => true;
String toString() => "boolean";
bool isExact() => true;
bool canBePrimitiveBoolean(Compiler compiler) => true;
TypeMask computeMask(Compiler compiler) {
JavaScriptBackend backend = compiler.backend;
return new TypeMask.nonNullExact(backend.jsBoolClass);
}
}
class HNumberOrNullType extends HPrimitiveOrNullType {
const HNumberOrNullType();
bool isNumberOrNull() => true;
String toString() => "number or null";
bool canBePrimitiveNumber(Compiler compiler) => true;
TypeMask computeMask(Compiler compiler) {
JavaScriptBackend backend = compiler.backend;
return new TypeMask.subclass(backend.jsNumberClass);
}
}
class HNumberType extends HPrimitiveType {
const HNumberType();
bool isNumber() => true;
bool isNumberOrNull() => true;
String toString() => "number";
bool canBePrimitiveNumber(Compiler compiler) => true;
TypeMask computeMask(Compiler compiler) {
JavaScriptBackend backend = compiler.backend;
return new TypeMask.nonNullSubclass(backend.jsNumberClass);
}
}
class HIntegerOrNullType extends HNumberOrNullType {
const HIntegerOrNullType();
bool isIntegerOrNull() => true;
String toString() => "integer or null";
TypeMask computeMask(Compiler compiler) {
JavaScriptBackend backend = compiler.backend;
return new TypeMask.exact(backend.jsIntClass);
}
}
class HIntegerType extends HNumberType {
const HIntegerType();
bool isInteger() => true;
bool isIntegerOrNull() => true;
String toString() => "integer";
bool isExact() => true;
TypeMask computeMask(Compiler compiler) {
JavaScriptBackend backend = compiler.backend;
return new TypeMask.nonNullExact(backend.jsIntClass);
}
}
class HDoubleOrNullType extends HNumberOrNullType {
const HDoubleOrNullType();
bool isDoubleOrNull() => true;
String toString() => "double or null";
TypeMask computeMask(Compiler compiler) {
JavaScriptBackend backend = compiler.backend;
return new TypeMask.exact(backend.jsDoubleClass);
}
}
class HDoubleType extends HNumberType {
const HDoubleType();
bool isDouble() => true;
bool isDoubleOrNull() => true;
String toString() => "double";
bool isExact() => true;
TypeMask computeMask(Compiler compiler) {
JavaScriptBackend backend = compiler.backend;
return new TypeMask.nonNullExact(backend.jsDoubleClass);
}
}
class HBoundedType extends HType {
final TypeMask mask;
const HBoundedType(this.mask);
bool isExact() => mask.isExact;
bool canBeNull() => mask.isNullable;
bool canBePrimitive(Compiler compiler) {
return canBePrimitiveNumber(compiler)
|| canBePrimitiveArray(compiler)
|| canBePrimitiveBoolean(compiler)
|| canBePrimitiveString(compiler);
}
bool canBePrimitiveNumber(Compiler compiler) {
JavaScriptBackend backend = compiler.backend;
return mask.contains(backend.jsNumberClass, compiler)
|| mask.contains(backend.jsIntClass, compiler)
|| mask.contains(backend.jsDoubleClass, compiler);
}
bool canBePrimitiveBoolean(Compiler compiler) {
JavaScriptBackend backend = compiler.backend;
return mask.contains(backend.jsBoolClass, compiler);
}
bool canBePrimitiveArray(Compiler compiler) {
JavaScriptBackend backend = compiler.backend;
return mask.contains(backend.jsArrayClass, compiler)
|| mask.contains(backend.jsFixedArrayClass, compiler)
|| mask.contains(backend.jsExtendableArrayClass, compiler);
}
bool isIndexablePrimitive(Compiler compiler) {
JavaScriptBackend backend = compiler.backend;
return mask.satisfies(backend.jsIndexableClass, compiler);
}
bool isFixedArray(Compiler compiler) {
JavaScriptBackend backend = compiler.backend;
return mask.containsOnly(backend.jsFixedArrayClass);
}
bool isExtendableArray(Compiler compiler) {
JavaScriptBackend backend = compiler.backend;
return mask.containsOnly(backend.jsExtendableArrayClass);
}
bool isMutableArray(Compiler compiler) {
JavaScriptBackend backend = compiler.backend;
return mask.satisfies(backend.jsMutableArrayClass, compiler);
}
bool isReadableArray(Compiler compiler) {
JavaScriptBackend backend = compiler.backend;
return mask.satisfies(backend.jsArrayClass, compiler);
}
bool canBePrimitiveString(Compiler compiler) {
JavaScriptBackend backend = compiler.backend;
return mask.contains(backend.jsStringClass, compiler);
}
TypeMask computeMask(Compiler compiler) => mask;
HType simplify(Compiler compiler) {
return new HType.fromMask(mask.simplify(compiler), compiler);
}
bool isString(Compiler compiler) {
return mask.containsOnlyString(compiler);
}
bool isPrimitive(Compiler compiler) {
return isIndexablePrimitive(compiler) && !mask.isNullable;
}
bool isPrimitiveOrNull(Compiler compiler) {
return isIndexablePrimitive(compiler);
}
bool operator ==(other) {
if (other is !HBoundedType) return false;
HBoundedType bounded = other;
return mask == bounded.mask;
}
int get hashCode => mask.hashCode;
String toString() {
return 'BoundedType(mask=$mask)';
}
}