blob: 10d96a077bf5bf67ed14b77976f40895fa7d32e6 [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.
library type_graph_inferrer;
import 'dart:collection' show Queue, LinkedHashSet, IterableBase, HashMap;
import '../dart_types.dart' show DartType, InterfaceType, TypeKind;
import '../elements/elements.dart';
import '../tree/tree.dart' show Node;
import 'types.dart' show TypeMask, ContainerTypeMask, TypesInferrer;
import '../universe/universe.dart' show Selector, TypedSelector, SideEffects;
import '../dart2jslib.dart' show Compiler, SourceString, TreeElementMapping;
import 'inferrer_visitor.dart' show TypeSystem, ArgumentsTypes, CallSite;
import '../native_handler.dart' as native;
import '../util/util.dart' show Spannable;
import 'simple_types_inferrer.dart';
import '../dart2jslib.dart' show invariant;
/**
* Common class for all nodes in the graph. The current nodes are:
*
* - Concrete types
* - Elements
* - Call sites
* - Narrowing instructions
* - Phi instructions
* - Containers (for lists)
* - Type of the element in a container
*
* A node has a set of assignments and users. Assignments are used to
* compute the type of the node ([TypeInformation.refine]). Users are
* added to the inferrer's work queue when the type of the node
* changes.
*/
abstract class TypeInformation {
var /* List|Set */ users;
var /* List|ParameterAssignments */ assignments;
/// The type the inferrer has found for this [TypeInformation].
/// Initially dynamic.
TypeMask type;
/// We give up on inferencing for special elements, as well as for
/// complicated cyclic dependencies.
bool abandonInferencing = false;
/// Number of times this [TypeInformation] has changed type.
int refineCount = 0;
/// Whether this [TypeInformation] is currently in the inferrer's
/// work queue.
bool inQueue = false;
// TypeInformations are unique.
static int staticHashCode = 0;
final int hashCode = staticHashCode++;
bool get isConcrete => false;
TypeInformation(this.type, [users, assignments])
: users = (users == null) ? new Set<TypeInformation>() : users,
assignments = (assignments == null) ? <TypeInformation>[] : assignments;
void addUser(TypeInformation user) {
assert(!user.isConcrete);
users.add(user);
}
void removeUser(TypeInformation user) {
assert(!user.isConcrete);
users.remove(user);
}
void addAssignment(TypeInformation assignment) {
if (abandonInferencing) return;
// Cheap one-level cycle detection.
if (assignment == this) return;
assignments.add(assignment);
assignment.addUser(this);
}
void removeAssignment(TypeInformation assignment) {
if (!abandonInferencing) {
assignments.remove(assignment);
}
// We can have multiple assignments of the same [TypeInformation].
if (!assignments.contains(assignment)) {
assignment.removeUser(this);
}
}
TypeMask refine(TypeGraphInferrerEngine inferrer) {
return type;
}
TypeMask refineOptimistic(TypeGraphInferrerEngine inferrer) {
return refine(inferrer);
}
void giveUp(TypeGraphInferrerEngine inferrer) {
abandonInferencing = true;
type = inferrer.types.dynamicType.type;
assignments = const <TypeInformation>[];
}
void clear() {
assignments = const <TypeInformation>[];
users = const <TypeInformation>[];
}
}
/**
* Parameters of instance functions behave differently than other
* elements because the inferrer may remove assignments. This happens
* when the receiver of a dynamic call site can be refined
* to a type where we know more about which instance method is being
* called.
*/
class ParameterAssignments extends IterableBase<TypeInformation> {
final Map<TypeInformation, int> assignments =
new HashMap<TypeInformation, int>();
void remove(TypeInformation info) {
int existing = assignments[info];
if (existing == null) return;
if (existing == 1) {
assignments.remove(info);
} else {
assignments[info] = existing - 1;
}
}
void add(TypeInformation info) {
int existing = assignments[info];
if (existing == null) {
assignments[info] = 1;
} else {
assignments[info] = existing + 1;
}
}
Iterator<TypeInformation> get iterator => assignments.keys.iterator;
Iterable<TypeInformation> where(Function f) => assignments.keys.where(f);
bool contains(TypeInformation info) => assignments.containsKey(info);
}
/**
* A node representing a resolved element of the program. The kind of
* elements that need an [ElementTypeRepresentation] are:
*
* - Functions (including getters and setters)
* - Constructors (factory or generative)
* - Fields
* - Parameters
* - Local variables mutated in closures
*
* The [ElementTypeInformation] of a function and a constructor is its
* return type.
*
* Note that a few elements of these kinds must be treated specially,
* and they are dealt in [ElementTypeInformation.handleSpecialCase]:
*
* - Parameters of closures, [noSuchMethod] and [call] instance
* methods: we currently do not infer types for those.
*
* - Fields and parameters being assigned by synthesized calls done by
* the backend: we do not know what types the backend will use.
*
* - Native functions and fields: because native methods contain no Dart
* code, and native fields do not have Dart assignments, we just
* trust their type annotation.
*
*/
class ElementTypeInformation extends TypeInformation {
final Element element;
final Map<Element, Set<Spannable>> callers =
new Map<Element, Set<Spannable>>();
ElementTypeInformation.internal(this.element, type, assignments)
: super(type, null, assignments);
factory ElementTypeInformation(Element element, TypeMask type) {
var assignments = null;
if (element.enclosingElement.isInstanceMember()
&& (element.isParameter() || element.isFieldParameter())) {
assignments = new ParameterAssignments();
}
return new ElementTypeInformation.internal(element, type, assignments);
}
void addCall(Element caller, Spannable node) {
callers.putIfAbsent(caller, () => new Set<Spannable>()).add(node);
}
void removeCall(Element caller, Spannable node) {
Set<Spannable> calls = callers[caller];
if (calls == null) return;
calls.remove(node);
if (calls.isEmpty) {
callers.remove(caller);
}
}
TypeMask handleSpecialCases(TypeGraphInferrerEngine inferrer) {
if (abandonInferencing) {
return type;
}
if (element.isParameter()) {
Element enclosing = element.enclosingElement;
if (Elements.isLocal(enclosing)) {
// Do not infer types for parameters of closures.
giveUp(inferrer);
return type;
} else if (enclosing.isInstanceMember()
&& (enclosing.name == Compiler.NO_SUCH_METHOD
|| enclosing.name == Compiler.CALL_OPERATOR_NAME)) {
// Do not infer types for parameters of [noSuchMethod] and
// [call] instance methods.
giveUp(inferrer);
return type;
}
}
if (element.isField()
|| element.isParameter()
|| element.isFieldParameter()) {
if (!inferrer.compiler.backend.canBeUsedForGlobalOptimizations(element)) {
// Do not infer types for fields and parameters being assigned
// by synthesized calls.
giveUp(inferrer);
return type;
}
}
if (inferrer.isNativeElement(element)) {
// Use the type annotation as the type for native elements. We
// also give up on inferring to make sure this element never
// goes in the work queue.
giveUp(inferrer);
if (element.isField()) {
InterfaceType rawType = element.computeType(inferrer.compiler).asRaw();
return rawType.treatAsDynamic
? inferrer.types.dynamicType.type
: new TypeMask.subtype(rawType);
} else {
assert(element.isFunction()
|| element.isGetter()
|| element.isSetter());
var elementType = element.computeType(inferrer.compiler);
if (elementType.kind != TypeKind.FUNCTION) {
return type;
} else {
return inferrer.typeOfNativeBehavior(
native.NativeBehavior.ofMethod(element, inferrer.compiler)).type;
}
}
}
return null;
}
TypeMask potentiallyNarrowType(TypeMask mask,
TypeGraphInferrerEngine inferrer) {
Compiler compiler = inferrer.compiler;
if (!compiler.trustTypeAnnotations && !compiler.enableTypeAssertions) {
return mask;
}
if (element.isGenerativeConstructor() || element.isSetter()) return mask;
var type = element.computeType(compiler);
if (element.isFunction()
|| element.isGetter()
|| element.isFactoryConstructor()) {
type = type.returnType;
}
return new TypeMaskSystem(compiler).narrowType(mask, type);
}
TypeMask refine(TypeGraphInferrerEngine inferrer) {
TypeMask special = handleSpecialCases(inferrer);
if (special != null) return potentiallyNarrowType(special, inferrer);
return potentiallyNarrowType(
inferrer.types.computeTypeMask(assignments), inferrer);
}
TypeMask refineOptimistic(TypeGraphInferrerEngine inferrer) {
TypeMask special = handleSpecialCases(inferrer);
if (special != null) return potentiallyNarrowType(special, inferrer);
return potentiallyNarrowType(inferrer.types.computeTypeMask(
assignments.where((e) => e.isConcrete)), inferrer);
}
String toString() => 'Element $element';
}
/**
* A [CallSiteTypeInformation] is a call found in the AST, or a
* synthesized call for implicit calls in Dart (such as forwarding
* factories). The [call] field is a [Node] for the former, and an
* [Element] for the latter.
*
* In the inferrer graph, [CallSiteTypeInformation] nodes do not have
* any assignment. They rely on the [caller] field for static calls,
* and [selector] and [receiver] fields for dynamic calls.
*/
abstract class CallSiteTypeInformation extends TypeInformation {
final Spannable call;
final Element caller;
final Selector selector;
final ArgumentsTypes arguments;
CallSiteTypeInformation(
this.call,
this.caller,
this.selector,
this.arguments,
TypeMask type) : super(type, null, const <TypeInformation>[]);
String toString() => 'Call site $call';
/// Add [this] to the graph being computed by [engine].
void addToGraph(TypeGraphInferrerEngine engine);
/// Return an iterable over the targets of this call.
Iterable<Element> get callees;
}
class StaticCallSiteTypeInformation extends CallSiteTypeInformation {
final Element calledElement;
StaticCallSiteTypeInformation(
Spannable call,
Element enclosing,
this.calledElement,
Selector selector,
ArgumentsTypes arguments,
TypeMask type) : super(call, enclosing, selector, arguments, type);
void addToGraph(TypeGraphInferrerEngine inferrer) {
ElementTypeInformation callee =
inferrer.types.getInferredTypeOf(calledElement);
callee.addCall(caller, call);
callee.addUser(this);
if (arguments != null) {
arguments.forEach((info) => info.addUser(this));
}
inferrer.updateParameterAssignments(
this, calledElement, arguments, selector, remove: false, init: true);
}
bool get isSynthesized {
// Some calls do not have a corresponding node, for example
// fowarding factory constructors, or synthesized super
// constructor calls. We synthesize these calls but do
// not create a selector for them.
return selector == null;
}
TypeMask refine(TypeGraphInferrerEngine inferrer) {
if (isSynthesized) {
assert(arguments != null);
return inferrer.types.getInferredTypeOf(calledElement).type;
} else {
return inferrer.typeOfElementWithSelector(calledElement, selector).type;
}
}
Iterable<Element> get callees => [calledElement.implementation];
}
class DynamicCallSiteTypeInformation extends CallSiteTypeInformation {
final TypeInformation receiver;
/// Cached targets of this call.
Iterable<Element> targets;
DynamicCallSiteTypeInformation(
Spannable call,
Element enclosing,
Selector selector,
this.receiver,
ArgumentsTypes arguments,
TypeMask type) : super(call, enclosing, selector, arguments, type);
void addToGraph(TypeGraphInferrerEngine inferrer) {
assert(receiver != null);
Selector typedSelector = computeTypedSelector(inferrer);
targets = inferrer.compiler.world.allFunctions.filter(typedSelector);
receiver.addUser(this);
if (arguments != null) {
arguments.forEach((info) => info.addUser(this));
}
for (Element element in targets) {
ElementTypeInformation callee = inferrer.types.getInferredTypeOf(element);
callee.addCall(caller, call);
callee.addUser(this);
inferrer.updateParameterAssignments(
this, element, arguments, typedSelector, remove: false, init: true);
}
}
Iterable<Element> get callees => targets.map((e) => e.implementation);
Selector computeTypedSelector(TypeGraphInferrerEngine inferrer) {
TypeMask receiverType = receiver.type;
if (selector.mask != receiverType) {
return receiverType == inferrer.compiler.typesTask.dynamicType
? selector.asUntyped
: new TypedSelector(receiverType, selector);
} else {
return selector;
}
}
bool hasOnePositionalArgumentWithType(TypeMask type) {
return arguments.named.isEmpty
&& arguments.positional.length == 1
&& arguments.positional[0].type == type;
}
/**
* We optimize certain operations on the [int] class because we know
* more about their return type than the actual Dart code. For
* example, we know int + int returns an int. The Dart code for
* [int.operator+] only says it returns a [num].
*/
TypeInformation handleIntrisifiedSelector(Selector selector,
TypeGraphInferrerEngine inferrer) {
if (!inferrer.compiler.backend.intImplementation.isResolved) return null;
TypeMask intType = inferrer.compiler.typesTask.intType;
if (selector.mask != intType) return null;
if (!selector.isCall() && !selector.isOperator()) return null;
if (!arguments.named.isEmpty) return null;
if (arguments.positional.length > 1) return null;
SourceString name = selector.name;
if (name == const SourceString('*')
|| name == const SourceString('+')
|| name == const SourceString('%')
|| name == const SourceString('remainder')) {
return hasOnePositionalArgumentWithType(intType)
? inferrer.types.intType
: null;
} else if (name == const SourceString('-')) {
if (arguments.hasNoArguments()) return inferrer.types.intType;
if (hasOnePositionalArgumentWithType(intType)) {
return inferrer.types.intType;
}
return null;
} else if (name == const SourceString('abs')) {
return arguments.hasNoArguments() ? inferrer.types.intType : null;
}
return null;
}
TypeMask refine(TypeGraphInferrerEngine inferrer) {
Iterable<Element> oldTargets = targets;
Selector typedSelector = computeTypedSelector(inferrer);
inferrer.updateSelectorInTree(caller, call, typedSelector);
targets = inferrer.compiler.world.allFunctions.filter(typedSelector);
// Walk over the found targets, and compute the joined union type mask
// for all these targets.
TypeMask newType = inferrer.types.computeTypeMask(targets.map((element) {
if (!oldTargets.contains(element)) {
ElementTypeInformation callee =
inferrer.types.getInferredTypeOf(element);
callee.addCall(caller, call);
callee.addUser(this);
inferrer.updateParameterAssignments(
this, element, arguments, typedSelector, remove: false);
}
if (receiver.type.isContainer && selector.isIndex()) {
// Find the [ElementInContainerTypeInformation] node and tell
// that this node is a user of it. Later, when the element
// type changes, this node will be notified.
ContainerTypeMask mask = receiver.type;
ContainerTypeInformation container =
inferrer.types.allocatedContainers[mask.allocationNode];
ElementInContainerTypeInformation element = container.elementType;
if (!element.users.contains(element)) {
element.addUser(this);
}
return element;
} else {
TypeInformation info =
handleIntrisifiedSelector(typedSelector, inferrer);
if (info != null) return info;
return inferrer.typeOfElementWithSelector(element, typedSelector);
}
}));
// Walk over the old targets, and remove calls that cannot happen
// anymore.
oldTargets.forEach((element) {
if (!targets.contains(element)) {
ElementTypeInformation callee =
inferrer.types.getInferredTypeOf(element);
callee.removeCall(caller, call);
callee.removeUser(this);
inferrer.updateParameterAssignments(
this, element, arguments, typedSelector, remove: true);
}
});
return newType;
}
void giveUp(TypeGraphInferrerEngine inferrer) {
inferrer.updateSelectorInTree(caller, call, selector);
Iterable<Element> oldTargets = targets;
targets = inferrer.compiler.world.allFunctions.filter(selector);
for (Element element in targets) {
if (!oldTargets.contains(element)) {
ElementTypeInformation callee =
inferrer.types.getInferredTypeOf(element);
callee.addCall(caller, call);
inferrer.updateParameterAssignments(
this, element, arguments, selector, remove: false);
}
}
super.giveUp(inferrer);
}
}
/**
* A [ConcreteTypeInformation] represents a type that needed
* to be materialized during the creation of the graph. For example,
* literals, [:this:] or [:super:] need a [ConcreteTypeInformation].
*
* [ConcreteTypeInformation] nodes have no assignment. Also, to save
* on memory, we do not add users to [ConcreteTypeInformation] nodes,
* because we know such node will never be refined to a different
* type.
*/
class ConcreteTypeInformation extends TypeInformation {
ConcreteTypeInformation(TypeMask type)
: super(type, const <TypeInformation>[], const <TypeInformation>[]);
bool get isConcrete => true;
void addUser(TypeInformation user) {
// Nothing to do, a concrete type does not get updated so never
// needs to notify its users.
}
void removeUser(TypeInformation user) {
}
void addAssignment(TypeInformation assignment) {
}
void removeAssignment(TypeInformation assignment) {
assert(false);
}
String toString() => 'Type $type';
}
/**
* A [NarrowTypeInformation] narrows a [TypeInformation] to a type,
* represented in [typeAnnotation].
*
* A [NarrowTypeInformation] node has only one assignment: the
* [TypeInformation] it narrows.
*
* [NarrowTypeInformation] nodes are created for:
*
* - Code after `is` and `as` checks, where we have more information
* on the type of the right hand side of the expression.
*
* - Code after a dynamic call, where we have more information on the
* type of the receiver: it can only be of a class that holds a
* potential target of this dynamic call.
*
* - In checked mode, after a type annotation, we have more
* information on the type of a local.
*/
class NarrowTypeInformation extends TypeInformation {
final TypeMask typeAnnotation;
NarrowTypeInformation(narrowedType,
this.typeAnnotation,
TypeMask type) : super(type) {
addAssignment(narrowedType);
}
TypeMask refine(TypeGraphInferrerEngine inferrer) {
return assignments[0].type.intersection(typeAnnotation, inferrer.compiler);
}
String toString() => 'Narrow ${assignments.first} to $typeAnnotation';
}
/**
* A [ContainerTypeInformation] is a [ConcreteTypeInformation] created
* for each `List` instantiations.
*/
class ContainerTypeInformation extends ConcreteTypeInformation {
final TypeInformation elementType;
ContainerTypeInformation(containerType, this.elementType)
: super(containerType);
String toString() => 'Container type';
}
/**
* An [ElementInContainerTypeInformation] holds the common type of the
* elements in a [ContainerTypeInformation].
*/
class ElementInContainerTypeInformation extends TypeInformation {
final ContainerTypeMask container;
ElementInContainerTypeInformation(elementType, this.container, type)
: super(type) {
// [elementType] is not null for const lists.
if (elementType != null) addAssignment(elementType);
}
TypeMask refine(TypeGraphInferrerEngine inferrer) {
if (assignments.isEmpty) return type;
return container.elementType =
inferrer.types.computeTypeMask(assignments);
}
String toString() => 'Element in container';
}
/**
* A [PhiElementTypeInformation] is an union of
* [ElementTypeInformation], that is local to a method.
*/
class PhiElementTypeInformation extends TypeInformation {
final Node branchNode;
final bool isLoopPhi;
final Element element;
PhiElementTypeInformation(this.branchNode, this.isLoopPhi, this.element, type)
: super(type);
TypeMask refine(TypeGraphInferrerEngine inferrer) {
return inferrer.types.computeTypeMask(assignments);
}
TypeMask refineOptimistic(TypeGraphInferrerEngine inferrer) {
return isLoopPhi
? assignments[0].type
: inferrer.types.computeTypeMask(assignments);
}
String toString() => 'Phi $element';
}
class TypeInformationSystem extends TypeSystem<TypeInformation> {
final Compiler compiler;
/// [ElementTypeInformation]s for elements.
final Map<Element, TypeInformation> typeInformations =
new Map<Element, TypeInformation>();
/// [ContainerTypeInformation] for allocated containers.
final Map<Node, TypeInformation> allocatedContainers =
new Map<Node, TypeInformation>();
/// Cache of [ConcreteTypeInformation].
final Map<TypeMask, TypeInformation> concreteTypes =
new Map<TypeMask, TypeInformation>();
/// List of [TypeInformation]s allocated inside method bodies (calls,
/// narrowing, phis, and containers).
final List<TypeInformation> allocatedTypes = <TypeInformation>[];
TypeInformationSystem(this.compiler) {
nonNullEmptyType = getConcreteTypeFor(const TypeMask.nonNullEmpty());
}
TypeInformation nullTypeCache;
TypeInformation get nullType {
if (nullTypeCache != null) return nullTypeCache;
return nullTypeCache = getConcreteTypeFor(compiler.typesTask.nullType);
}
TypeInformation intTypeCache;
TypeInformation get intType {
if (intTypeCache != null) return intTypeCache;
return intTypeCache = getConcreteTypeFor(compiler.typesTask.intType);
}
TypeInformation doubleTypeCache;
TypeInformation get doubleType {
if (doubleTypeCache != null) return doubleTypeCache;
return doubleTypeCache = getConcreteTypeFor(compiler.typesTask.doubleType);
}
TypeInformation numTypeCache;
TypeInformation get numType {
if (numTypeCache != null) return numTypeCache;
return numTypeCache = getConcreteTypeFor(compiler.typesTask.numType);
}
TypeInformation boolTypeCache;
TypeInformation get boolType {
if (boolTypeCache != null) return boolTypeCache;
return boolTypeCache = getConcreteTypeFor(compiler.typesTask.boolType);
}
TypeInformation functionTypeCache;
TypeInformation get functionType {
if (functionTypeCache != null) return functionTypeCache;
return functionTypeCache =
getConcreteTypeFor(compiler.typesTask.functionType);
}
TypeInformation listTypeCache;
TypeInformation get listType {
if (listTypeCache != null) return listTypeCache;
return listTypeCache = getConcreteTypeFor(compiler.typesTask.listType);
}
TypeInformation constListTypeCache;
TypeInformation get constListType {
if (constListTypeCache != null) return constListTypeCache;
return constListTypeCache =
getConcreteTypeFor(compiler.typesTask.constListType);
}
TypeInformation fixedListTypeCache;
TypeInformation get fixedListType {
if (fixedListTypeCache != null) return fixedListTypeCache;
return fixedListTypeCache =
getConcreteTypeFor(compiler.typesTask.fixedListType);
}
TypeInformation growableListTypeCache;
TypeInformation get growableListType {
if (growableListTypeCache != null) return growableListTypeCache;
return growableListTypeCache =
getConcreteTypeFor(compiler.typesTask.growableListType);
}
TypeInformation mapTypeCache;
TypeInformation get mapType {
if (mapTypeCache != null) return mapTypeCache;
return mapTypeCache = getConcreteTypeFor(compiler.typesTask.mapType);
}
TypeInformation constMapTypeCache;
TypeInformation get constMapType {
if (constMapTypeCache != null) return constMapTypeCache;
return constMapTypeCache =
getConcreteTypeFor(compiler.typesTask.constMapType);
}
TypeInformation stringTypeCache;
TypeInformation get stringType {
if (stringTypeCache != null) return stringTypeCache;
return stringTypeCache = getConcreteTypeFor(compiler.typesTask.stringType);
}
TypeInformation typeTypeCache;
TypeInformation get typeType {
if (typeTypeCache != null) return typeTypeCache;
return typeTypeCache = getConcreteTypeFor(compiler.typesTask.typeType);
}
TypeInformation dynamicTypeCache;
TypeInformation get dynamicType {
if (dynamicTypeCache != null) return dynamicTypeCache;
return dynamicTypeCache =
getConcreteTypeFor(compiler.typesTask.dynamicType);
}
TypeInformation nonNullEmptyType;
TypeInformation computeLUB(TypeInformation firstType,
TypeInformation secondType) {
if (firstType == null) return secondType;
if (firstType == secondType) return firstType;
if (firstType == nonNullEmptyType) return secondType;
if (secondType == nonNullEmptyType) return firstType;
if (firstType == dynamicType || secondType == dynamicType) {
return dynamicType;
}
return getConcreteTypeFor(
firstType.type.union(secondType.type, compiler));
}
TypeInformation refineReceiver(Selector selector, TypeInformation receiver) {
if (receiver.type.isExact) return receiver;
TypeMask otherType = compiler.world.allFunctions.receiverType(selector);
// If this is refining to nullable subtype of `Object` just return
// the receiver. We know the narrowing is useless.
if (otherType.isNullable && otherType.containsAll(compiler)) {
return receiver;
}
TypeInformation newType =
new NarrowTypeInformation(receiver, otherType, dynamicType.type);
allocatedTypes.add(newType);
return newType;
}
TypeInformation narrowType(TypeInformation type,
DartType annotation,
{bool isNullable: true}) {
if (annotation.treatAsDynamic) return type;
if (annotation.isVoid) return nullType;
if (annotation.element == compiler.objectClass) return type;
TypeMask otherType;
if (annotation.kind == TypeKind.TYPEDEF
|| annotation.kind == TypeKind.FUNCTION) {
otherType = functionType.type;
} else if (annotation.kind == TypeKind.TYPE_VARIABLE) {
// TODO(ngeoffray): Narrow to bound.
return type;
} else {
assert(annotation.kind == TypeKind.INTERFACE);
otherType = new TypeMask.nonNullSubtype(annotation);
}
if (isNullable) otherType = otherType.nullable();
if (type.type.isExact) {
return type;
} else {
TypeInformation newType =
new NarrowTypeInformation(type, otherType, dynamicType.type);
allocatedTypes.add(newType);
return newType;
}
}
ElementTypeInformation getInferredTypeOf(Element element) {
element = element.implementation;
return typeInformations.putIfAbsent(element, () {
return new ElementTypeInformation(element, dynamicType.type);
});
}
ConcreteTypeInformation getConcreteTypeFor(TypeMask mask) {
return concreteTypes.putIfAbsent(mask, () {
return new ConcreteTypeInformation(mask);
});
}
TypeInformation nonNullSubtype(DartType type) {
return getConcreteTypeFor(new TypeMask.nonNullSubtype(type));
}
TypeInformation nonNullSubclass(DartType type) {
return getConcreteTypeFor(new TypeMask.nonNullSubclass(type));
}
TypeInformation nonNullExact(DartType type) {
return getConcreteTypeFor(new TypeMask.nonNullExact(type));
}
TypeInformation nonNullEmpty() {
return nonNullEmptyType;
}
TypeInformation allocateContainer(TypeInformation type,
Node node,
Element enclosing,
[TypeInformation elementType, int length]) {
ContainerTypeMask mask = new ContainerTypeMask(type.type, node, enclosing);
mask.elementType = elementType == null ? null : elementType.type;
mask.length = length;
TypeMask elementTypeMask = elementType == null
? dynamicType.type
: elementType.type;
TypeInformation element = new ElementInContainerTypeInformation(
elementType, mask, elementTypeMask);
allocatedTypes.add(element);
return allocatedContainers[node] =
new ContainerTypeInformation(mask, element);
}
Selector newTypedSelector(TypeInformation info, Selector selector) {
return new TypedSelector(info.type, selector);
}
TypeInformation allocateDiamondPhi(TypeInformation firstInput,
TypeInformation secondInput) {
PhiElementTypeInformation result =
new PhiElementTypeInformation(null, false, null, dynamicType.type);
result.addAssignment(firstInput);
result.addAssignment(secondInput);
allocatedTypes.add(result);
return result;
}
PhiElementTypeInformation allocatePhi(Node node,
Element element,
inputType) {
// Check if [inputType] is a phi for a local updated in
// the try/catch block [node]. If it is, no need to allocate a new
// phi.
if (inputType is PhiElementTypeInformation
&& inputType.branchNode == node) {
return inputType;
}
PhiElementTypeInformation result =
new PhiElementTypeInformation(node, true, element, dynamicType.type);
allocatedTypes.add(result);
result.addAssignment(inputType);
return result;
}
TypeInformation simplifyPhi(Node node,
Element element,
PhiElementTypeInformation phiType) {
if (phiType.assignments.length == 1) return phiType.assignments.first;
return phiType;
}
PhiElementTypeInformation addPhiInput(Element element,
PhiElementTypeInformation phiType,
TypeInformation newType) {
phiType.addAssignment(newType);
return phiType;
}
TypeMask computeTypeMask(Iterable<TypeInformation> assignments) {
TypeMask newType = const TypeMask.nonNullEmpty();
for (var info in assignments) {
newType = newType.union(info.type, compiler);
}
return newType.containsAll(compiler) ? dynamicType.type : newType;
}
}
/**
* A work queue for the inferrer. It filters out nodes on
* which we gave up on inferencing, as well as ensures through
* [TypeInformation.inQueue] that a node is in the queue only once at
* a time.
*/
class WorkQueue {
final Queue<TypeInformation> queue = new Queue<TypeInformation>();
void add(TypeInformation element) {
if (element.abandonInferencing) return;
if (element.inQueue) return;
queue.addLast(element);
element.inQueue = true;
}
void addAll(Iterable<TypeInformation> all) {
all.forEach(add);
}
TypeInformation remove() {
TypeInformation element = queue.removeFirst();
element.inQueue = false;
return element;
}
bool get isEmpty => queue.isEmpty;
int get length => queue.length;
}
/**
* An inferencing engine that computes a call graph of
* [TypeInformation] nodes by visiting the AST of the application, and
* then does the inferencing on the graph.
*
* The inferencing is currently done in three steps:
*
* 1) Compute the call graph.
* 2) Refine all nodes in a way that avoids cycles.
* 3) Refine all nodes.
*
*/
class TypeGraphInferrerEngine
extends InferrerEngine<TypeInformation, TypeInformationSystem> {
final Map<Element, ConcreteTypeInformation> defaultTypeOfParameter =
new Map<Element, ConcreteTypeInformation>();
final WorkQueue workQueue = new WorkQueue();
/// The maximum number of times we allow a node in the graph to
/// change types. If a node reaches that limit, we give up
/// inferencing on it and give it the dynamic type.
final int MAX_CHANGE_COUNT = 5;
int overallRefineCount = 0;
TypeGraphInferrerEngine(Compiler compiler)
: super(compiler, new TypeInformationSystem(compiler));
void runOverAllElements() {
if (compiler.disableTypeInference) return;
int addedInGraph = 0;
compiler.progress.reset();
sortResolvedElements().forEach((Element element) {
if (compiler.progress.elapsedMilliseconds > 500) {
compiler.log('Added $addedInGraph elements in inferencing graph.');
compiler.progress.reset();
}
SimpleTypeInferrerVisitor visitor =
new SimpleTypeInferrerVisitor(element, compiler, this);
TypeInformation type;
compiler.withCurrentElement(element, () {
type = visitor.run();
});
addedInGraph++;
if (element.isField()) {
Node node = element.parseNode(compiler);
if (element.modifiers.isFinal() || element.modifiers.isConst()) {
// If [element] is final and has an initializer, we record
// the inferred type.
if (node.asSendSet() != null) {
recordType(element, type);
} else if (!element.isInstanceMember()) {
recordType(element, types.nullType);
}
} else if (node.asSendSet() == null) {
// Only update types of static fields if there is no
// assignment. Instance fields are dealt with in the constructor.
if (Elements.isStaticOrTopLevelField(element)) {
recordTypeOfNonFinalField(node, element, type, null);
}
} else {
recordTypeOfNonFinalField(node, element, type, null);
}
if (Elements.isStaticOrTopLevelField(element)
&& node.asSendSet() != null
&& !element.modifiers.isConst()) {
var argument = node.asSendSet().arguments.head;
// TODO(13429): We could do better here by using the
// constant handler to figure out if it's a lazy field or not.
if (argument.asSend() != null
|| (argument.asNewExpression() != null && !argument.isConst())) {
recordType(element, types.nullType);
}
}
} else {
recordReturnType(element, type);
}
});
compiler.log('Added $addedInGraph elements in inferencing graph.');
buildWorkQueue();
refineOptimistic();
buildWorkQueue();
refine();
compiler.log('Inferred $overallRefineCount types.');
if (compiler.enableTypeAssertions) {
// Undo the narrowing of parameters types. Parameters are being
// checked by the method, and we can therefore only trust their
// type after the checks. It is okay for the inferrer to rely on
// the type annotations, but the backend should has to
// insert the checks.
types.typeInformations.forEach((Element element,
ElementTypeInformation info) {
if (element.isParameter() || element.isFieldParameter()) {
if (info.abandonInferencing) {
info.type = types.dynamicType.type;
} else {
info.type = types.computeTypeMask(info.assignments);
}
}
});
}
}
void refineOptimistic() {
while (!workQueue.isEmpty) {
if (compiler.progress.elapsedMilliseconds > 500) {
compiler.log('Inferred $overallRefineCount types.');
compiler.progress.reset();
}
TypeInformation info = workQueue.remove();
TypeMask oldType = info.type;
TypeMask newType = info.refineOptimistic(this);
if ((info.type = newType) != oldType) {
overallRefineCount++;
workQueue.addAll(info.users);
}
}
}
void refine() {
while (!workQueue.isEmpty) {
if (compiler.progress.elapsedMilliseconds > 500) {
compiler.log('Inferred $overallRefineCount types.');
compiler.progress.reset();
}
TypeInformation info = workQueue.remove();
TypeMask oldType = info.type;
TypeMask newType = info.refine(this);
if ((info.type = newType) != oldType) {
overallRefineCount++;
info.refineCount++;
if (info.refineCount > MAX_CHANGE_COUNT) {
info.giveUp(this);
}
workQueue.addAll(info.users);
}
}
}
void buildWorkQueue() {
workQueue.addAll(types.typeInformations.values);
workQueue.addAll(types.allocatedTypes);
}
/**
* Update the assignments to parameters in the graph. [remove] tells
* wheter assignments must be added or removed. If [init] is true,
* parameters are added to the work queue.
*/
void updateParameterAssignments(TypeInformation caller,
Element callee,
ArgumentsTypes arguments,
Selector selector,
{bool remove, bool init: false}) {
if (callee.name == Compiler.NO_SUCH_METHOD) return;
if (callee.isField()) {
if (selector.isSetter()) {
ElementTypeInformation info = types.getInferredTypeOf(callee);
if (remove) {
info.removeAssignment(arguments.positional[0]);
} else {
info.addAssignment(arguments.positional[0]);
}
if (!init) workQueue.add(info);
}
} else if (callee.isGetter()) {
return;
} else if (selector != null && selector.isGetter()) {
if (!remove) {
FunctionElement function = callee.implementation;
FunctionSignature signature = function.computeSignature(compiler);
signature.forEachParameter((Element parameter) {
ElementTypeInformation info = types.getInferredTypeOf(parameter);
info.giveUp(this);
});
}
} else {
FunctionElement function = callee.implementation;
FunctionSignature signature = function.computeSignature(compiler);
int parameterIndex = 0;
bool visitingRequiredParameter = true;
signature.forEachParameter((Element parameter) {
if (parameter == signature.firstOptionalParameter) {
visitingRequiredParameter = false;
}
TypeInformation type = visitingRequiredParameter
? arguments.positional[parameterIndex]
: signature.optionalParametersAreNamed
? arguments.named[parameter.name]
: parameterIndex < arguments.positional.length
? arguments.positional[parameterIndex]
: null;
if (type == null) type = getDefaultTypeOfParameter(parameter);
TypeInformation info = types.getInferredTypeOf(parameter);
if (remove) {
info.removeAssignment(type);
} else {
info.addAssignment(type);
}
parameterIndex++;
if (!init) workQueue.add(info);
});
}
}
void updateAllParametersOf(FunctionElement function) {}
void onGenerativeConstructorAnalyzed(Element element) {}
void setDefaultTypeOfParameter(Element parameter, TypeInformation type) {
assert(parameter.enclosingElement.isImplementation);
getDefaultTypeOfParameter(parameter).type = type.type;
}
TypeInformation getDefaultTypeOfParameter(Element parameter) {
return defaultTypeOfParameter.putIfAbsent(parameter, () {
return new ConcreteTypeInformation(types.dynamicType.type);
});
}
TypeInformation typeOfElement(Element element) {
if (element is FunctionElement) return types.functionType;
return types.getInferredTypeOf(element);
}
TypeInformation returnTypeOfElement(Element element) {
if (element is !FunctionElement) return types.dynamicType;
return types.getInferredTypeOf(element);
}
void recordTypeOfFinalField(Spannable node,
Element analyzed,
Element element,
TypeInformation type,
CallSite constraint) {
types.getInferredTypeOf(element).addAssignment(type);
}
void recordTypeOfNonFinalField(Spannable node,
Element element,
TypeInformation type,
CallSite constraint) {
types.getInferredTypeOf(element).addAssignment(type);
}
bool recordType(Element element, TypeInformation type) {
types.getInferredTypeOf(element).addAssignment(type);
return false;
}
void recordReturnType(Element element, TypeInformation type) {
TypeInformation info = types.getInferredTypeOf(element);
if (element.name == const SourceString('==')) {
info.addAssignment(types.boolType);
}
// TODO(ngeoffray): Clean up. We do these checks because
// [SimpleTypesInferrer] deals with two different inferrers.
if (type == null) return;
if (info.assignments.isEmpty) info.addAssignment(type);
}
TypeInformation addReturnTypeFor(Element element,
TypeInformation unused,
TypeInformation newType) {
TypeInformation type = types.getInferredTypeOf(element);
// TODO(ngeoffray): Clean up. We do this check because
// [SimpleTypesInferrer] deals with two different inferrers.
if (element.isGenerativeConstructor()) return type;
type.addAssignment(newType);
return type;
}
TypeInformation registerCalledElement(Spannable node,
Selector selector,
Element caller,
Element callee,
ArgumentsTypes arguments,
CallSite constraint,
SideEffects sideEffects,
bool inLoop) {
CallSiteTypeInformation info = new StaticCallSiteTypeInformation(
node, caller, callee, selector, arguments, types.dynamicType.type);
if (inLoop) {
compiler.world.addFunctionCalledInLoop(callee);
}
info.addToGraph(this);
updateSideEffects(sideEffects, selector, callee);
return info;
}
TypeInformation registerCalledSelector(Node node,
Selector selector,
TypeInformation receiverType,
Element caller,
ArgumentsTypes arguments,
CallSite constraint,
SideEffects sideEffects,
bool inLoop) {
if (selector.isClosureCall()) return types.dynamicType;
if (inLoop && selector.mask != null) {
// For instance methods, we only register a selector called in a
// loop if it is a typed selector, to avoid marking too many
// methods as being called from within a loop. This cuts down
// on the code bloat.
// TODO(ngeoffray): We should move the filtering on the selector
// in the backend. It is not the inferrer role to do this kind
// of optimization.
compiler.world.allFunctions.filter(selector).forEach((callee) {
compiler.world.addFunctionCalledInLoop(callee);
});
}
compiler.world.allFunctions.filter(selector).forEach((callee) {
updateSideEffects(sideEffects, selector, callee);
});
DynamicCallSiteTypeInformation info = new DynamicCallSiteTypeInformation(
node, caller, selector, receiverType, arguments,
types.dynamicType.type);
info.addToGraph(this);
return info;
}
// Sorts the resolved elements by size. We do this for this inferrer
// to get the same results for [ContainerTracer] compared to the
// [SimpleTypesInferrer].
Iterable<Element> sortResolvedElements() {
int max = 0;
Map<int, Set<Element>> methodSizes = new Map<int, Set<Element>>();
compiler.enqueuer.resolution.resolvedElements.forEach(
(Element element, TreeElementMapping mapping) {
element = element.implementation;
if (element.impliesType()) return;
assert(invariant(element,
element.isField() ||
element.isFunction() ||
element.isGenerativeConstructor() ||
element.isGetter() ||
element.isSetter(),
message: 'Unexpected element kind: ${element.kind}'));
// TODO(ngeoffray): Not sure why the resolver would put a null
// mapping.
if (mapping == null) return;
if (element.isAbstract(compiler)) return;
// Put the other operators in buckets by length, later to be added in
// length order.
int length = mapping.selectors.length;
max = length > max ? length : max;
Set<Element> set = methodSizes.putIfAbsent(
length, () => new LinkedHashSet<Element>());
set.add(element);
});
List<Element> result = <Element>[];
for (int i = 0; i <= max; i++) {
Set<Element> set = methodSizes[i];
if (set != null) {
result.addAll(set);
}
}
return result;
}
void clear() {
defaultTypeOfParameter.clear();
types.typeInformations.values.forEach((info) => info.clear());
types.allocatedTypes.clear();
types.concreteTypes.clear();
}
Iterable<Element> getCallersOf(Element element) {
if (compiler.disableTypeInference) {
throw new UnsupportedError(
"Cannot query the type inferrer when type inference is disabled.");
}
return types.getInferredTypeOf(element).callers.keys;
}
}
class TypeGraphInferrer implements TypesInferrer {
TypeGraphInferrerEngine inferrer;
final Compiler compiler;
TypeGraphInferrer(Compiler this.compiler);
String get name => 'Graph inferrer';
void analyzeMain(_) {
inferrer = new TypeGraphInferrerEngine(compiler);
inferrer.runOverAllElements();
}
TypeMask getReturnTypeOfElement(Element element) {
if (compiler.disableTypeInference) return compiler.typesTask.dynamicType;
return inferrer.types.getInferredTypeOf(element).type;
}
TypeMask getTypeOfElement(Element element) {
if (compiler.disableTypeInference) return compiler.typesTask.dynamicType;
return inferrer.types.getInferredTypeOf(element).type;
}
TypeMask getTypeOfNode(Element owner, Node node) {
if (compiler.disableTypeInference) return compiler.typesTask.dynamicType;
return inferrer.types.allocatedContainers[node].type;
}
TypeMask getTypeOfSelector(Selector selector) {
if (compiler.disableTypeInference) return compiler.typesTask.dynamicType;
// Bailout for closure calls. We're not tracking types of
// closures.
if (selector.isClosureCall()) return compiler.typesTask.dynamicType;
if (selector.isSetter() || selector.isIndexSet()) {
return compiler.typesTask.dynamicType;
}
if (selector.isIndex()
&& selector.mask != null
&& selector.mask.isContainer) {
ContainerTypeMask mask = selector.mask;
TypeMask elementType = mask.elementType;
return elementType == null ? compiler.typesTask.dynamicType : elementType;
}
TypeMask result = const TypeMask.nonNullEmpty();
Iterable<Element> elements = compiler.world.allFunctions.filter(selector);
for (Element element in elements) {
TypeMask type =
inferrer.typeOfElementWithSelector(element, selector).type;
result = result.union(type, compiler);
}
return result;
}
Iterable<TypeMask> get containerTypes {
if (compiler.disableTypeInference) {
throw new UnsupportedError(
"Cannot query the type inferrer when type inference is disabled.");
}
return inferrer.types.allocatedContainers.values.map((info) => info.type);
}
Iterable<Element> getCallersOf(Element element) {
if (compiler.disableTypeInference) {
throw new UnsupportedError(
"Cannot query the type inferrer when type inference is disabled.");
}
return inferrer.getCallersOf(element);
}
void clear() {
inferrer.clear();
}
}