blob: 63739211939e81bcacfc1a187f8a28094d348d50 [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 simple_types_inferrer;
import '../closure.dart' show ClosureClassMap;
import '../native_handler.dart' as native;
import '../elements/elements.dart';
import '../dart2jslib.dart';
import '../tree/tree.dart';
import '../util/util.dart' show Link;
import 'types.dart' show TypesInferrer, ConcreteType, ClassBaseType;
/**
* A work queue that ensures there are no duplicates, and adds and
* removes in LIFO.
*/
class WorkSet<E extends Element> {
final List<E> queue = new List<E>();
final Set<E> elementsInQueue = new Set<E>();
void add(E element) {
element = element.implementation;
if (elementsInQueue.contains(element)) return;
queue.addLast(element);
elementsInQueue.add(element);
}
E remove() {
E element = queue.removeLast();
elementsInQueue.remove(element);
return element;
}
bool get isEmpty => queue.isEmpty;
}
/**
* Placeholder for type information of final fields of classes.
*/
class ClassInfoForFinalFields {
/**
* Maps a final field to a map from generative constructor to the
* inferred type of the field in that generative constructor.
*/
final Map<Element, Map<Element, Element>> typesOfFinalFields =
new Map<Element, Map<Element, Element>>();
/**
* The number of generative constructors that need to be visited
* before we can take any decision on the type of the fields.
* Given that all generative constructors must be analyzed before
* re-analyzing one, we know that once [constructorsToVisitCount]
* reaches to 0, all generative constructors have been analyzed.
*/
int constructorsToVisitCount;
ClassInfoForFinalFields(this.constructorsToVisitCount);
/**
* Records that the generative [constructor] has inferred [type]
* for the final [field].
*/
void recordFinalFieldType(Element constructor, Element field, Element type) {
Map<Element, Element> typesFor = typesOfFinalFields.putIfAbsent(
field, () => new Map<Element, Element>());
typesFor[constructor] = type;
}
/**
* Records that [constructor] has been analyzed. If not at 0,
* decrement [constructorsToVisitCount].
*/
void doneAnalyzingGenerativeConstructor(Element constructor) {
if (constructorsToVisitCount != 0) constructorsToVisitCount--;
}
/**
* Returns whether all generative constructors of the class have
* been analyzed.
*/
bool get isDone => constructorsToVisitCount == 0;
}
class SimpleTypesInferrer extends TypesInferrer {
/**
* Maps an element to its callers.
*/
final Map<Element, Set<Element>> callersOf =
new Map<Element, Set<Element>>();
/**
* Maps an element to its return type.
*/
final Map<Element, Element> returnTypeOf =
new Map<Element, Element>();
/**
* Maps an element to the number of times this type inferrer
* analyzed it.
*/
final Map<Element, int> analyzeCount = new Map<Element, int>();
/**
* Maps a class to a [ClassInfoForFinalFields] to help collect type
* information of final fields.
*/
final Map<ClassElement, ClassInfoForFinalFields> classInfoForFinalFields =
new Map<ClassElement, ClassInfoForFinalFields>();
/**
* The work list of the inferrer.
*/
final WorkSet<Element> workSet = new WorkSet<Element>();
/**
* Heuristic for avoiding too many re-analysis of an element.
*/
final int MAX_ANALYSIS_COUNT_PER_ELEMENT = 5;
/**
* Sentinal used by the inferrer to notify that it gave up finding a type
* on a specific element.
*/
Element giveUpType;
final Compiler compiler;
// Times the computation of re-analysis of methods.
final Stopwatch recomputeWatch = new Stopwatch();
// Number of re-analysis.
int recompiles = 0;
SimpleTypesInferrer(this.compiler);
/**
* Main entry point of the inferrer. Analyzes all elements that the
* resolver found as reachable. Returns whether it succeeded.
*/
bool analyzeMain(Element element) {
// We use the given element as the sentinel. This is a temporary
// situation as long as this inferrer is using [ClassElement] for
// expressing types.
giveUpType = element;
buildWorkQueue();
int analyzed = 0;
compiler.progress.reset();
do {
if (compiler.progress.elapsedMilliseconds > 500) {
compiler.log('Inferred $analyzed methods.');
compiler.progress.reset();
}
element = workSet.remove();
if (element.isErroneous()) continue;
bool wasAnalyzed = analyzeCount.containsKey(element);
if (wasAnalyzed) {
recompiles++;
recomputeWatch.start();
}
bool changed = analyze(element);
analyzed++;
if (wasAnalyzed) {
recomputeWatch.stop();
}
if (!changed) continue;
// If something changed during the analysis of [element],
// put back callers of it in the work list.
Set<Element> methodCallers = callersOf[element];
if (methodCallers != null) {
methodCallers.forEach(enqueueAgain);
}
} while (!workSet.isEmpty);
dump();
clear();
return true;
}
/**
* Query method after the analysis to know the type of [element].
*/
getConcreteTypeOfElement(element) {
return getTypeIfValuable(returnTypeOf[element]);
}
getTypeIfValuable(returnType) {
if (returnType == null
|| returnType == compiler.dynamicClass
|| returnType == giveUpType) {
return null;
}
return new ConcreteType.singleton(
compiler.maxConcreteTypeSize, new ClassBaseType(returnType));
}
/**
* Query method after the analysis to know the type of [node],
* defined in the context of [owner].
*/
getConcreteTypeOfNode(Element owner, Node node) {
var elements = compiler.enqueuer.resolution.resolvedElements[owner];
// TODO(ngeoffray): Not sure why the resolver would put a null
// mapping.
if (elements == null) return null;
Selector selector = elements.getSelector(node);
// TODO(ngeoffray): Should the builder call this method with a
// SendSet?
if (selector == null || selector.isSetter() || selector.isIndexSet()) {
return null;
}
return getTypeIfValuable(returnTypeOfSelector(selector));
}
/**
* Enqueues [e] in the work queue if it is valuable.
*/
void enqueueAgain(Element e) {
Element returnType = returnTypeOf[e];
// If we have found a type for [e], no need to re-analyze it.
if (returnType != compiler.dynamicClass) return;
if (analyzeCount[e] > MAX_ANALYSIS_COUNT_PER_ELEMENT) return;
workSet.add(e);
}
/**
* Builds the initial work queue by adding all resolved elements in
* the work queue, ordered by the number of selectors they use. This
* order is benficial for the analysis of return types, but we may
* have to refine it once we analyze parameter types too.
*/
void buildWorkQueue() {
int max = 0;
Map<int, Set<Element>> methodSizes = new Map<int, Set<Element>>();
compiler.enqueuer.resolution.resolvedElements.forEach(
(Element element, TreeElementMapping mapping) {
// TODO(ngeoffray): Not sure why the resolver would put a null
// mapping.
if (mapping == null) return;
if (element.isAbstract(compiler)) return;
int length = mapping.selectors.length;
max = length > max ? length : max;
Set<Element> set = methodSizes.putIfAbsent(
length, () => new Set<Element>());
set.add(element);
});
// This iteration assumes the [WorkSet] is LIFO.
for (int i = max; i >= 0; i--) {
Set<Element> set = methodSizes[i];
if (set != null) {
set.forEach((e) { workSet.add(e); });
}
}
// Build the [classInfoForFinalFields] map by iterating over all
// seen classes and counting the number of their generative
// constructors.
// We iterate over the seen classes and not the instantiated ones,
// because we also need to analyze the final fields of super
// classes that are not instantiated.
compiler.enqueuer.resolution.seenClasses.forEach((ClassElement cls) {
int constructorCount = 0;
cls.forEachMember((_, member) {
if (member.isGenerativeConstructor()) constructorCount++;
});
classInfoForFinalFields[cls.implementation] =
new ClassInfoForFinalFields(constructorCount);
});
}
dump() {
int interestingTypes = 0;
int giveUpTypes = 0;
returnTypeOf.forEach((Element method, Element type) {
if (type == giveUpType) {
giveUpTypes++;
} else if (type != compiler.nullClass && type != compiler.dynamicClass) {
interestingTypes++;
}
});
compiler.log('Type inferrer re-analyzed methods $recompiles times '
'in ${recomputeWatch.elapsedMilliseconds} ms.');
compiler.log('Type inferrer found $interestingTypes interesting '
'return types and gave up on $giveUpTypes methods.');
}
/**
* Clear data structures that are not used after the analysis.
*/
void clear() {
callersOf.clear();
analyzeCount.clear();
classInfoForFinalFields.clear();
}
bool analyze(Element element) {
SimpleTypeInferrerVisitor visitor =
new SimpleTypeInferrerVisitor(element, compiler, this);
Element returnType = visitor.run();
if (analyzeCount.containsKey(element)) {
analyzeCount[element]++;
} else {
analyzeCount[element] = 1;
}
if (element.isGenerativeConstructor()) {
// We always know the return type of a generative constructor.
return false;
} else if (element.isField()) {
if (element.modifiers.isFinal() || element.modifiers.isConst()) {
if (element.parseNode(compiler).asSendSet() != null) {
// If [element] is final and has an initializer, we record
// the inferred type.
return recordReturnType(element, returnType);
}
}
// We don't record anything for non-final fields.
return false;
} else {
return recordReturnType(element, returnType);
}
}
/**
* Records [returnType] as the return type of [analyzedElement].
* Returns whether the new type is worth recompiling the callers of
* [analyzedElement].
*/
bool recordReturnType(analyzedElement, returnType) {
assert(returnType != null);
Element existing = returnTypeOf[analyzedElement];
Element newType = existing == compiler.dynamicClass
? returnType // Previous analysis did not find any type.
: computeLUB(existing, returnType);
returnTypeOf[analyzedElement] = newType;
// If the return type is useful, say it has changed.
return existing != newType
&& newType != compiler.dynamicClass
&& newType != compiler.nullClass;
}
/**
* Returns the return type of [element]. Returns [:Dynamic:] if
* [element] has not been analyzed yet.
*/
ClassElement returnTypeOfElement(Element element) {
element = element.implementation;
if (element.isGenerativeConstructor()) return element.getEnclosingClass();
Element returnType = returnTypeOf[element];
if (returnType == null || returnType == giveUpType) {
return compiler.dynamicClass;
}
assert(returnType != null);
return returnType;
}
/**
* Returns the union of the return types of all elements that match
* the called [selector].
*/
ClassElement returnTypeOfSelector(Selector selector) {
ClassElement result;
iterateOverElements(selector, (Element element) {
assert(element.isImplementation);
Element cls;
if (element.isFunction() && selector.isGetter()) {
cls = compiler.functionClass;
} else {
cls = returnTypeOf[element];
}
if (cls == null
|| cls == compiler.dynamicClass
|| cls == giveUpType
|| (cls != result && result != null)) {
result = compiler.dynamicClass;
return false;
} else {
result = cls;
return true;
}
});
if (result == null) {
result = compiler.dynamicClass;
}
return result;
}
bool isNotClosure(Element element) {
// If the outermost enclosing element of [element] is [element]
// itself, we know it cannot be a closure.
Element outermost = element.getOutermostEnclosingMemberOrTopLevel();
return outermost.declaration == element.declaration;
}
/**
* Registers that [caller] calls [callee] with the given
* [arguments].
*/
void registerCalledElement(Element caller,
Element callee,
ArgumentsTypes arguments) {
assert(isNotClosure(caller));
if (analyzeCount.containsKey(caller)) return;
callee = callee.implementation;
Set<Element> callers = callersOf.putIfAbsent(
callee, () => new Set<Element>());
callers.add(caller);
}
/**
* Registers that [caller] accesses [callee] through a property
* access.
*/
void registerGetterOnElement(Element caller,
Element callee) {
assert(isNotClosure(caller));
if (analyzeCount.containsKey(caller)) return;
callee = callee.implementation;
Set<Element> callers = callersOf.putIfAbsent(
callee, () => new Set<Element>());
callers.add(caller);
}
/**
* Registers that [caller] calls an element matching [selector]
* with the given [arguments].
*/
void registerCalledSelector(Element caller,
Selector selector,
ArgumentsTypes arguments) {
assert(isNotClosure(caller));
if (analyzeCount.containsKey(caller)) return;
iterateOverElements(selector, (Element element) {
assert(element.isImplementation);
Set<Element> callers = callersOf.putIfAbsent(
element, () => new Set<Element>());
callers.add(caller);
return true;
});
}
/**
* Registers that [caller] accesses an element matching [selector]
* through a property access.
*/
void registerGetterOnSelector(Element caller, Selector selector) {
assert(isNotClosure(caller));
if (analyzeCount.containsKey(caller)) return;
iterateOverElements(selector, (Element element) {
assert(element.isImplementation);
Set<Element> callers = callersOf.putIfAbsent(
element, () => new Set<Element>());
callers.add(caller);
return true;
});
}
/**
* Registers that [caller] closurizes [function].
*/
void registerGetFunction(Element caller, Element function) {
assert(isNotClosure(caller));
assert(caller.isImplementation);
if (analyzeCount.containsKey(caller)) return;
// We don't register that [caller] calls [function] because we
// don't know if the code is going to call it, and if it is, then
// the inferrer has lost track of its identity anyway.
}
/**
* Applies [f] to all elements in the universe that match
* [selector]. If [f] returns false, aborts the iteration.
*/
void iterateOverElements(Selector selector, bool f(Element element)) {
Iterable<Element> elements = compiler.world.allFunctions.filter(selector);
for (Element e in elements) {
if (!f(e.implementation)) return;
}
}
/**
* Records in [classInfoForFinalFields] that [constructor] has
* inferred [type] for the final [field].
*/
void recordFinalFieldType(Element constructor, Element field, Element type) {
// If the field is being set at its declaration site, it is not
// being tracked in the [classInfoForFinalFields] map.
if (constructor == field) return;
assert(field.modifiers.isFinal() || field.modifiers.isConst());
ClassElement cls = constructor.getEnclosingClass();
ClassInfoForFinalFields info = classInfoForFinalFields[cls.implementation];
info.recordFinalFieldType(constructor, field, type);
}
/**
* Records that we are done analyzing [constructor]. If all
* generative constructors of its enclosing class have already been
* analyzed, this method updates the types of final fields.
*/
void doneAnalyzingGenerativeConstructor(Element constructor) {
ClassElement cls = constructor.getEnclosingClass();
ClassInfoForFinalFields info = classInfoForFinalFields[cls.implementation];
info.doneAnalyzingGenerativeConstructor(constructor);
if (info.isDone) {
updateFieldTypes(info);
}
}
/**
* Updates types of final fields listed in [info].
*/
void updateFieldTypes(ClassInfoForFinalFields info) {
assert(info.isDone);
info.typesOfFinalFields.forEach((Element field,
Map<Element, Element> types) {
assert(field.modifiers.isFinal());
Element fieldType;
types.forEach((_, type) {
fieldType = computeLUB(fieldType, type);
});
returnTypeOf[field] = fieldType;
});
}
/**
* Returns the least upper bound between [firstType] and
* [secondType].
*/
Element computeLUB(Element firstType, Element secondType) {
bool isNumber(type) {
return type == compiler.numClass
|| type == compiler.doubleClass
|| type == compiler.intClass;
}
assert(secondType != null);
if (firstType == null) {
return secondType;
} else if (firstType == giveUpType) {
return firstType;
} else if (secondType == compiler.dynamicClass) {
return secondType;
} else if (firstType == compiler.dynamicClass) {
return firstType;
} else if (firstType != secondType) {
if (isNumber(firstType) && isNumber(secondType)) {
// The JavaScript backend knows how to deal with numbers.
return compiler.numClass;
}
// TODO(ngeoffray): Actually compute the least upper bound.
return giveUpType;
} else {
assert(firstType == secondType);
return firstType;
}
}
}
/**
* Placeholder for inferred arguments types on sends.
*/
class ArgumentsTypes {
final List<Element> positional;
final Map<Identifier, Element> named;
ArgumentsTypes(this.positional, this.named);
int get length => positional.length + named.length;
toString() => "{ positional = $positional, named = $named }";
}
/**
* Placeholder for inferred types of local variables.
*/
class LocalsHandler {
final SimpleTypesInferrer inferrer;
final Map<Element, Element> locals;
final Set<Element> captured;
final bool inTryBlock;
LocalsHandler(this.inferrer)
: locals = new Map<Element, Element>(),
captured = new Set<Element>(),
inTryBlock = false;
LocalsHandler.from(LocalsHandler other, {bool inTryBlock: false})
: locals = new Map<Element, Element>.from(other.locals),
captured = new Set<Element>.from(other.captured),
inTryBlock = other.inTryBlock || inTryBlock,
inferrer = other.inferrer;
Element use(Element local) {
return locals[local];
}
void update(Element local, Element type) {
assert(type != null);
if (captured.contains(local) || inTryBlock) {
// If a local is captured or is set in a try block, we compute the
// LUB of its assignments. We don't know if an assignment in a try block
// will be executed, so all assigments in a try block are
// potential types after we have left the try block.
type = inferrer.computeLUB(locals[local], type);
if (type == inferrer.giveUpType) type = inferrer.compiler.dynamicClass;
}
locals[local] = type;
}
void setCaptured(Element local) {
captured.add(local);
}
/**
* Merge handlers [first] and [second] into [:this:] and returns
* whether the merge changed one of the variables types in [first].
*/
bool merge(LocalsHandler other) {
bool changed = false;
List<Element> toRemove = <Element>[];
// Iterating over a map and just updating its entries is OK.
locals.forEach((local, oldType) {
Element otherType = other.locals[local];
if (otherType == null) {
// If [local] is not in the other map, we know it is not a
// local we want to keep. For example, in an if/else, we don't
// want to keep variables declared in the if or in the else
// branch at the merge point.
toRemove.add(local);
return;
}
var type = inferrer.computeLUB(oldType, otherType);
if (type == inferrer.giveUpType) type = inferrer.compiler.dynamicClass;
if (type != oldType) changed = true;
locals[local] = type;
});
// Remove locals that will not be used anymore.
toRemove.forEach((Element element) {
locals.remove(element);
});
// Update the locals that are captured.
other.captured.forEach((Element element) {
if (locals.containsKey(element)) {
captured.add(element);
}
});
return changed;
}
}
class SimpleTypeInferrerVisitor extends ResolvedVisitor {
final Element analyzedElement;
final Element outermostElement;
final SimpleTypesInferrer inferrer;
final Compiler compiler;
LocalsHandler locals;
Element returnType;
SimpleTypeInferrerVisitor.internal(TreeElements mapping,
this.analyzedElement,
this.outermostElement,
this.inferrer,
this.compiler,
this.locals)
: super(mapping);
factory SimpleTypeInferrerVisitor(Element element,
Compiler compiler,
SimpleTypesInferrer inferrer,
[LocalsHandler handler]) {
Element outermostElement =
element.getOutermostEnclosingMemberOrTopLevel().implementation;
TreeElements elements = compiler.enqueuer.resolution.resolvedElements[
outermostElement.declaration];
assert(elements != null);
assert(outermostElement != null);
handler = handler != null ? handler : new LocalsHandler(inferrer);
return new SimpleTypeInferrerVisitor.internal(
elements, element, outermostElement, inferrer, compiler, handler);
}
Element run() {
var node = analyzedElement.implementation.parseNode(compiler);
if (analyzedElement.isField()) {
returnType = visit(node);
} else if (analyzedElement.isGenerativeConstructor()) {
FunctionElement function = analyzedElement;
FunctionSignature signature = function.computeSignature(compiler);
signature.forEachParameter((element) {
// We don't track argument types yet, so just set the fields
// and parameters as dynamic.
if (element.kind == ElementKind.FIELD_PARAMETER
&& element.modifiers.isFinal()) {
inferrer.recordFinalFieldType(
analyzedElement, element.fieldElement, compiler.dynamicClass);
} else {
locals.update(element, compiler.dynamicClass);
}
});
visit(node.initializers);
visit(node.body);
inferrer.doneAnalyzingGenerativeConstructor(analyzedElement);
returnType = analyzedElement.getEnclosingClass();
} else if (analyzedElement.isNative()) {
// Native methods do not have a body, and we currently just say
// they return dynamic.
returnType = compiler.dynamicClass;
} else {
FunctionElement function = analyzedElement;
FunctionSignature signature = function.computeSignature(compiler);
signature.forEachParameter((element) {
// We don't track argument types yet, so just set the
// parameters as dynamic.
locals.update(element, compiler.dynamicClass);
});
visit(node.body);
if (returnType == null) {
// No return in the body.
returnType = compiler.nullClass;
}
}
return returnType;
}
recordReturnType(ClassElement cls) {
returnType = inferrer.computeLUB(returnType, cls);
}
visitNode(Node node) {
node.visitChildren(this);
return compiler.dynamicClass;
}
visitNewExpression(NewExpression node) {
return node.send.accept(this);
}
visit(Node node) {
return node == null ? compiler.dynamicClass : node.accept(this);
}
visitFunctionExpression(FunctionExpression node) {
// Fetch the closure data of [node] and mark all locals that are
// captured by [node] to be captured in the locals handler.
compiler.closureToClassMapper.computeClosureToClassMapping(
outermostElement, outermostElement.parseNode(compiler), elements);
ClosureClassMap nestedClosureData =
compiler.closureToClassMapper.getMappingForNestedFunction(node);
nestedClosureData.forEachCapturedVariable((variable) {
locals.setCaptured(variable);
});
// We don't put the closure in the work queue of the
// inferrer, because it will share information with its enclosing
// method, like for example the types of local variables.
LocalsHandler closureLocals = new LocalsHandler.from(locals);
SimpleTypeInferrerVisitor visitor = new SimpleTypeInferrerVisitor(
elements[node], compiler, inferrer, closureLocals);
visitor.run();
// Do not register the runtime type, because the analysis may be
// invalidated due to updates on locals.
// TODO(ngeoffray): Track return types of closures.
// The closure may have updated the type of locals.
locals.merge(closureLocals);
return compiler.functionClass;
}
visitFunctionDeclaration(FunctionDeclaration node) {
locals.update(elements[node], compiler.functionClass);
return visit(node.function);
}
visitLiteralString(LiteralString node) {
return compiler.stringClass;
}
visitStringInterpolation(StringInterpolation node) {
return compiler.stringClass;
}
visitStringJuxtaposition(StringJuxtaposition node) {
return compiler.stringClass;
}
visitLiteralBool(LiteralBool node) {
return compiler.boolClass;
}
visitLiteralDouble(LiteralDouble node) {
return compiler.doubleClass;
}
visitLiteralInt(LiteralInt node) {
return compiler.intClass;
}
visitLiteralList(LiteralList node) {
return compiler.listClass;
}
visitLiteralMap(LiteralMap node) {
return compiler.mapClass;
}
visitLiteralNull(LiteralNull node) {
return compiler.nullClass;
}
visitTypeReferenceSend(Send node) {
return compiler.typeClass;
}
visitSendSet(SendSet node) {
Element element = elements[node];
if (!Elements.isUnresolved(element) && element.impliesType()) {
node.visitChildren(this);
return compiler.dynamicClass;
}
Operator op = node.assignmentOperator;
if (node.isSuperCall) {
// [: super.foo = 42 :] or [: super.foo++ :] or [: super.foo += 1 :].
node.visitChildren(this);
return compiler.dynamicClass;
} else if (node.isIndex) {
if (const SourceString("=") == op.source) {
// [: foo[0] = 42 :]
visit(node.receiver);
Element returnType;
for (Node argument in node.arguments) {
returnType = argument.accept(this);
}
return returnType;
} else {
// [: foo[0] += 42 :] or [: foo[0]++ :].
node.visitChildren(this);
return compiler.dynamicClass;
}
} else if (const SourceString("=") == op.source) {
// [: foo = 42 :] or [: foo.bar = 42 :].
if (element == null) {
visit(node.receiver);
}
Link<Node> link = node.arguments;
assert(!link.isEmpty && link.tail.isEmpty);
Element type = link.head.accept(this);
if (!Elements.isUnresolved(element)) {
if (element.isField() && element.modifiers.isFinal()) {
inferrer.recordFinalFieldType(outermostElement, element, type);
} else if (element.isVariable()) {
locals.update(element, type);
}
}
return type;
} else {
// [: foo++ :] or [: foo += 1 :].
assert(const SourceString("++") == op.source ||
const SourceString("--") == op.source ||
node.assignmentOperator.source.stringValue.endsWith("="));
node.visitChildren(this);
return compiler.dynamicClass;
}
}
visitIdentifier(Identifier node) {
if (node.isThis() || node.isSuper()) {
// TODO(ngeoffray): Represent subclasses.
return compiler.dynamicClass;
}
return compiler.dynamicClass;
}
visitSuperSend(Send node) {
Element element = elements[node];
if (Elements.isUnresolved(element)) {
return compiler.dynamicClass;
}
Selector selector = elements.getSelector(node);
if (node.isPropertyAccess) {
inferrer.registerGetterOnElement(outermostElement, element);
return inferrer.returnTypeOfElement(element);
} else if (element.isFunction()) {
ArgumentsTypes arguments = analyzeArguments(node.arguments);
inferrer.registerCalledElement(outermostElement, element, arguments);
return inferrer.returnTypeOfElement(element);
} else {
// Closure call on a getter. We don't have function types yet,
// so we just return [:Dynamic:].
return compiler.dynamicClass;
}
}
visitStaticSend(Send node) {
Element element = elements[node];
if (Elements.isUnresolved(element)) {
return compiler.dynamicClass;
}
if (element.isForeign(compiler)) {
return handleForeignSend(node);
}
ArgumentsTypes arguments = analyzeArguments(node.arguments);
inferrer.registerCalledElement(outermostElement, element, arguments);
return inferrer.returnTypeOfElement(element);
}
handleForeignSend(Send node) {
node.visitChildren(this);
Selector selector = elements.getSelector(node);
SourceString name = selector.name;
if (name == const SourceString('JS')) {
native.NativeBehavior nativeBehavior =
compiler.enqueuer.resolution.nativeEnqueuer.getNativeBehaviorOf(node);
if (nativeBehavior == null) return compiler.dynamicClass;
List typesReturned = nativeBehavior.typesReturned;
if (typesReturned.isEmpty) return compiler.dynamicClass;
ClassElement returnType;
for (var type in typesReturned) {
ClassElement mappedType;
if (type == native.SpecialType.JsObject) {
mappedType = compiler.objectClass;
} else if (type == native.SpecialType.JsArray) {
mappedType = compiler.listClass;
} else {
mappedType = type.element;
// For primitive types, we know how to handle them here and
// in the backend.
if (mappedType != compiler.stringClass
&& mappedType != compiler.intClass
&& mappedType != compiler.doubleClass
&& mappedType != compiler.boolClass
&& mappedType != compiler.numClass) {
Set<ClassElement> subtypes = compiler.world.subtypes[mappedType];
// TODO(ngeoffray): Handle subtypes and subclasses.
if (subtypes != null && !subtypes.isEmpty) {
return compiler.dynamicClass;
}
}
}
if (returnType == null) {
returnType = mappedType;
} else {
return compiler.dynamicClass;
}
}
return returnType;
} else if (name == const SourceString('JS_OPERATOR_IS_PREFIX')) {
return compiler.stringClass;
} else {
return compiler.dynamicClass;
}
}
analyzeArguments(Link<Node> arguments) {
List<ClassElement> positional = [];
Map<Identifier, ClassElement> named = new Map<Identifier, ClassElement>();
for (Node argument in arguments) {
NamedArgument namedArgument = argument.asNamedArgument();
if (namedArgument != null) {
named[namedArgument.name] = namedArgument.expression.accept(this);
} else {
positional.add(argument.accept(this));
}
}
return new ArgumentsTypes(positional, named);
}
visitOperatorSend(Send node) {
Operator op = node.selector;
if (const SourceString("[]") == op.source) {
return visitDynamicSend(node);
} else if (const SourceString("&&") == op.source ||
const SourceString("||") == op.source) {
visit(node.receiver);
LocalsHandler saved = new LocalsHandler.from(locals);
visit(node.arguments.head);
saved.merge(locals);
locals = saved;
return compiler.boolClass;
} else if (const SourceString("!") == op.source) {
node.visitChildren(this);
return compiler.boolClass;
} else if (const SourceString("is") == op.source) {
node.visitChildren(this);
return compiler.boolClass;
} else if (const SourceString("as") == op.source) {
node.visitChildren(this);
return compiler.dynamicClass;
} else if (node.isParameterCheck) {
node.visitChildren(this);
return compiler.boolClass;
} else if (node.argumentsNode is Prefix) {
// Unary operator.
return visitDynamicSend(node);
} else if (const SourceString('===') == op.source
|| const SourceString('!==') == op.source) {
node.visitChildren(this);
return compiler.boolClass;
} else {
// Binary operator.
return visitDynamicSend(node);
}
}
// Because some nodes just visit their children, we may end up
// visiting a type annotation, that may contain a send in case of a
// prefixed type. Therefore we explicitly visit the type annotation
// to avoid confusing the [ResolvedVisitor].
visitTypeAnnotation(TypeAnnotation node) {}
visitGetterSend(Send node) {
Element element = elements[node];
if (Elements.isStaticOrTopLevelField(element)) {
inferrer.registerGetterOnElement(outermostElement, element);
return inferrer.returnTypeOfElement(element);
} else if (Elements.isInstanceSend(node, elements)) {
ClassElement receiverType;
if (node.receiver == null) {
receiverType = outermostElement.getEnclosingClass();
} else {
receiverType = node.receiver.accept(this);
}
Selector selector = elements.getSelector(node);
inferrer.registerGetterOnSelector(outermostElement, selector);
return inferrer.returnTypeOfSelector(selector);
} else if (Elements.isStaticOrTopLevelFunction(element)) {
inferrer.registerGetFunction(outermostElement, element);
return compiler.functionClass;
} else if (Elements.isErroneousElement(element)) {
return compiler.dynamicClass;
} else if (Elements.isLocal(element)) {
assert(locals.use(element) != null);
return locals.use(element);
} else {
node.visitChildren(this);
return compiler.dynamicClass;
}
}
visitClosureSend(Send node) {
node.visitChildren(this);
return compiler.dynamicClass;
}
visitDynamicSend(Send node) {
ClassElement receiverType;
if (node.receiver == null) {
receiverType = outermostElement.getEnclosingClass();
} else {
receiverType = node.receiver.accept(this);
}
ArgumentsTypes arguments = analyzeArguments(node.arguments);
Selector selector = elements.getSelector(node);
inferrer.registerCalledSelector(outermostElement, selector, arguments);
return inferrer.returnTypeOfSelector(selector);
}
visitReturn(Return node) {
Node expression = node.expression;
recordReturnType(expression == null
? compiler.nullClass
: expression.accept(this));
}
visitConditional(Conditional node) {
node.condition.accept(this);
LocalsHandler saved = new LocalsHandler.from(locals);
Element firstType = node.thenExpression.accept(this);
LocalsHandler thenLocals = locals;
locals = saved;
Element secondType = node.elseExpression.accept(this);
locals.merge(thenLocals);
Element type = inferrer.computeLUB(firstType, secondType);
if (type == inferrer.giveUpType) type = compiler.dynamicClass;
return type;
}
visitVariableDefinitions(VariableDefinitions node) {
for (Link<Node> link = node.definitions.nodes;
!link.isEmpty;
link = link.tail) {
Node definition = link.head;
if (definition is Identifier) {
locals.update(elements[definition], compiler.nullClass);
} else {
assert(definition.asSendSet() != null);
visit(definition);
}
}
}
visitIf(If node) {
visit(node.condition);
LocalsHandler saved = new LocalsHandler.from(locals);
visit(node.thenPart);
LocalsHandler thenLocals = locals;
locals = saved;
visit(node.elsePart);
locals.merge(thenLocals);
return compiler.dynamicClass;
}
visitWhile(While node) {
bool changed = false;
do {
LocalsHandler saved = new LocalsHandler.from(locals);
visit(node.condition);
visit(node.body);
changed = saved.merge(locals);
locals = saved;
} while (changed);
return compiler.dynamicClass;
}
visitDoWhile(DoWhile node) {
bool changed = false;
do {
LocalsHandler saved = new LocalsHandler.from(locals);
visit(node.body);
visit(node.condition);
changed = saved.merge(locals);
locals = saved;
} while (changed);
return compiler.dynamicClass;
}
visitFor(For node) {
bool changed = false;
visit(node.initializer);
do {
LocalsHandler saved = new LocalsHandler.from(locals);
visit(node.condition);
visit(node.body);
visit(node.update);
changed = saved.merge(locals);
locals = saved;
} while (changed);
return compiler.dynamicClass;
}
visitForIn(ForIn node) {
bool changed = false;
visit(node.expression);
Element variable;
if (node.declaredIdentifier.asSend() != null) {
variable = elements[node.declaredIdentifier];
} else {
assert(node.declaredIdentifier.asVariableDefinitions() != null);
VariableDefinitions variableDefinitions = node.declaredIdentifier;
variable = elements[variableDefinitions.definitions.nodes.head];
}
locals.update(variable, compiler.dynamicClass);
do {
LocalsHandler saved = new LocalsHandler.from(locals);
visit(node.body);
changed = saved.merge(locals);
locals = saved;
} while (changed);
return compiler.dynamicClass;
}
visitTryStatement(TryStatement node) {
LocalsHandler saved = locals;
locals = new LocalsHandler.from(locals, inTryBlock: true);
visit(node.tryBlock);
saved.merge(locals);
locals = saved;
for (Node catchBlock in node.catchBlocks) {
saved = new LocalsHandler.from(locals);
visit(catchBlock);
saved.merge(locals);
locals = saved;
}
visit(node.finallyBlock);
}
internalError(String reason, {Node node}) {
compiler.internalError(reason, node: node);
}
}