| Index: sdk/lib/_internal/compiler/implementation/types/simple_types_inferrer.dart
|
| ===================================================================
|
| --- sdk/lib/_internal/compiler/implementation/types/simple_types_inferrer.dart (revision 0)
|
| +++ sdk/lib/_internal/compiler/implementation/types/simple_types_inferrer.dart (revision 0)
|
| @@ -0,0 +1,781 @@
|
| +// 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 '../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> {
|
| + 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;
|
| +}
|
| +
|
| +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 a name to elements in the universe that have that name.
|
| + */
|
| + final Map<SourceString, Set<Element>> methodCache =
|
| + new Map<SourceString, Set<Element>>();
|
| +
|
| + /**
|
| + * Maps an element to the number of times this type inferrer
|
| + * analyzed it.
|
| + */
|
| + final Map<Element, int> analyzeCount = new Map<Element, int>();
|
| +
|
| + /**
|
| + * 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 the call graph.
|
| + final Stopwatch memberWatch = new Stopwatch();
|
| + // 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];
|
| + 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(selector.isGetter()
|
| + ? returnTypeOfSelector(selector)
|
| + : 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); });
|
| + }
|
| + }
|
| + }
|
| +
|
| + 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 spent ${memberWatch.elapsedMilliseconds} ms '
|
| + 'computing a call graph.');
|
| + 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();
|
| + }
|
| +
|
| + bool analyze(Element element) {
|
| + if (element.isField()) {
|
| + // TODO(ngeoffray): Analyze its initializer.
|
| + return false;
|
| + } else {
|
| + Visitor visitor = new SimpleTypeInferrerVisitor(element, compiler, this);
|
| + return visitor.run();
|
| + }
|
| + }
|
| +
|
| + /**
|
| + * 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];
|
| + if (existing == null) {
|
| + // First time we analyzed [analyzedElement]. Initialize the
|
| + // return type.
|
| + assert(!analyzeCount.containsKey(analyzedElement));
|
| + returnTypeOf[analyzedElement] = returnType;
|
| + // If the return type is useful, say it has changed.
|
| + return returnType != compiler.dynamicClass
|
| + && returnType != compiler.nullClass;
|
| + } else if (existing == compiler.dynamicClass) {
|
| + // Previous analysis did not find any type.
|
| + returnTypeOf[analyzedElement] = returnType;
|
| + // If the return type is useful, say it has changed.
|
| + return returnType != compiler.dynamicClass
|
| + && returnType != compiler.nullClass;
|
| + } else if (existing == giveUpType) {
|
| + // If we already gave up on the return type, we don't change it.
|
| + return false;
|
| + } else if (existing != returnType) {
|
| + // The method is returning two different types. Give up for now.
|
| + // TODO(ngeoffray): Compute LUB.
|
| + returnTypeOf[analyzedElement] = giveUpType;
|
| + return true;
|
| + }
|
| + return false;
|
| + }
|
| +
|
| + /**
|
| + * 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;
|
| + }
|
| + 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;
|
| + }
|
| + });
|
| + return result;
|
| + }
|
| +
|
| + /**
|
| + * Registers that [caller] calls [callee] with the given
|
| + * [arguments].
|
| + */
|
| + void registerCalledElement(Element caller,
|
| + Element callee,
|
| + ArgumentsTypes arguments) {
|
| + if (analyzeCount.containsKey(caller)) return;
|
| + callee = callee.implementation;
|
| + Set<FunctionElement> callers = callersOf.putIfAbsent(
|
| + callee, () => new Set<FunctionElement>());
|
| + callers.add(caller);
|
| + }
|
| +
|
| + /**
|
| + * Registers that [caller] accesses [callee] through a property
|
| + * access.
|
| + */
|
| + void registerGetterOnElement(Element caller,
|
| + Element callee) {
|
| + if (analyzeCount.containsKey(caller)) return;
|
| + callee = callee.implementation;
|
| + Set<FunctionElement> callers = callersOf.putIfAbsent(
|
| + callee, () => new Set<FunctionElement>());
|
| + callers.add(caller);
|
| + }
|
| +
|
| + /**
|
| + * Registers that [caller] calls an element matching [selector]
|
| + * with the given [arguments].
|
| + */
|
| + void registerCalledSelector(Element caller,
|
| + Selector selector,
|
| + ArgumentsTypes arguments) {
|
| + if (analyzeCount.containsKey(caller)) return;
|
| + iterateOverElements(selector, (Element element) {
|
| + assert(element.isImplementation);
|
| + Set<FunctionElement> callers = callersOf.putIfAbsent(
|
| + element, () => new Set<FunctionElement>());
|
| + callers.add(caller);
|
| + return true;
|
| + });
|
| + }
|
| +
|
| + /**
|
| + * Registers that [caller] accesses an element matching [selector]
|
| + * through a property access.
|
| + */
|
| + void registerGetterOnSelector(Element caller, Selector selector) {
|
| + if (analyzeCount.containsKey(caller)) return;
|
| + iterateOverElements(selector, (Element element) {
|
| + assert(element.isImplementation);
|
| + if (element.isAbstractField()) {
|
| + AbstractFieldElement field = element;
|
| + element = element.getter;
|
| + if (element == null) return true;
|
| + }
|
| + Set<FunctionElement> callers = callersOf.putIfAbsent(
|
| + element, () => new Set<FunctionElement>());
|
| + callers.add(caller);
|
| + return true;
|
| + });
|
| + }
|
| +
|
| + /**
|
| + * Registers that [caller] closurizes [function].
|
| + */
|
| + void registerGetFunction(Element caller, Element function) {
|
| + 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)) {
|
| + SourceString name = selector.name;
|
| +
|
| + // The following is already computed by the resolver, but it does
|
| + // not save it yet.
|
| + Set<Element> methods = methodCache[name];
|
| + if (methods == null) {
|
| + memberWatch.start();
|
| + methods = new Set<Element>();
|
| + void add(element) {
|
| + if (!element.isInstanceMember()) return;
|
| + if (element.isAbstract(compiler)) return;
|
| + if (!compiler.enqueuer.resolution.isProcessed(element)) return;
|
| + methods.add(element.implementation);
|
| + }
|
| + for (ClassElement cls in compiler.enqueuer.resolution.seenClasses) {
|
| + var element = cls.lookupLocalMember(name);
|
| + if (element != null) {
|
| + if (element.isAbstractField()) {
|
| + if (element.getter != null) add(element.getter);
|
| + if (element.setter != null) add(element.setter);
|
| + } else {
|
| + add(element);
|
| + }
|
| + }
|
| + }
|
| + methodCache[name] = methods;
|
| + memberWatch.stop();
|
| + }
|
| +
|
| + for (Element element in methods) {
|
| + if (selector.appliesUnnamed(element, compiler)) {
|
| + if (!f(element)) return;
|
| + }
|
| + }
|
| + }
|
| +}
|
| +
|
| +/**
|
| + * 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 }";
|
| +}
|
| +
|
| +class SimpleTypeInferrerVisitor extends ResolvedVisitor {
|
| + final FunctionElement analyzedElement;
|
| + final SimpleTypesInferrer inferrer;
|
| + final Compiler compiler;
|
| + Element returnType;
|
| +
|
| + SimpleTypeInferrerVisitor(FunctionElement element,
|
| + Compiler compiler,
|
| + this.inferrer)
|
| + : super(compiler.enqueuer.resolution.resolvedElements[element.declaration]),
|
| + analyzedElement = element,
|
| + compiler = compiler {
|
| + assert(elements != null);
|
| + }
|
| +
|
| + bool run() {
|
| + FunctionExpression node =
|
| + analyzedElement.implementation.parseNode(compiler);
|
| + bool changed;
|
| + if (analyzedElement.isGenerativeConstructor()) {
|
| + FunctionSignature signature = analyzedElement.computeSignature(compiler);
|
| + // TODO(ngeoffray): handle initializing formals.
|
| + // TODO(ngeoffray): handle initializers.
|
| + node.body.accept(this);
|
| + // We always know the return type of a generative constructor.
|
| + changed = false;
|
| + } else if (analyzedElement.isNative()) {
|
| + // Native methods do not have a body, and we currently just say
|
| + // they return dynamic.
|
| + inferrer.recordReturnType(analyzedElement, compiler.dynamicClass);
|
| + changed = false;
|
| + } else {
|
| + node.body.accept(this);
|
| + if (returnType == null) {
|
| + // No return in the body.
|
| + returnType = compiler.nullClass;
|
| + }
|
| + changed = inferrer.recordReturnType(analyzedElement, returnType);
|
| + }
|
| + if (inferrer.analyzeCount.containsKey(analyzedElement)) {
|
| + inferrer.analyzeCount[analyzedElement]++;
|
| + } else {
|
| + inferrer.analyzeCount[analyzedElement] = 1;
|
| + }
|
| + return changed;
|
| + }
|
| +
|
| + recordReturnType(ClassElement cls) {
|
| + if (returnType == null) {
|
| + returnType = cls;
|
| + } else if (returnType != inferrer.giveUpType
|
| + && cls == compiler.dynamicClass) {
|
| + returnType = cls;
|
| + } else if (returnType == compiler.dynamicClass) {
|
| + // Nothing to do. Stay dynamic.
|
| + } else if (leastUpperBound(cls, returnType) == compiler.dynamicClass) {
|
| + returnType = inferrer.giveUpType;
|
| + }
|
| + }
|
| +
|
| + visitNode(Node node) {
|
| + node.visitChildren(this);
|
| + return compiler.dynamicClass;
|
| + }
|
| +
|
| + visitNewExpression(NewExpression node) {
|
| + return node.send.accept(this);
|
| + }
|
| +
|
| + visitFunctionExpression(FunctionExpression node) {
|
| + // 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.
|
| + SimpleTypeInferrerVisitor visitor =
|
| + new SimpleTypeInferrerVisitor(elements[node], compiler, inferrer);
|
| + visitor.run();
|
| + return compiler.functionClass;
|
| + }
|
| +
|
| + 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) {
|
| + // TODO(ngeoffray): return the right hand side's type.
|
| + 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(analyzedElement, element);
|
| + return inferrer.returnTypeOfElement(element);
|
| + } else if (element.isFunction()) {
|
| + ArgumentsTypes arguments = analyzeArguments(node.arguments);
|
| + inferrer.registerCalledElement(analyzedElement, 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(analyzedElement, 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.typesInstantiated.isEmpty) {
|
| + return compiler.dynamicClass;
|
| + }
|
| + ClassElement returnType;
|
| + for (var type in nativeBehavior.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) {
|
| + node.visitChildren(this);
|
| + 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)) {
|
| + if (element.isGetter()) {
|
| + inferrer.registerGetterOnElement(analyzedElement, element);
|
| + return inferrer.returnTypeOfElement(element);
|
| + } else {
|
| + // Nothing yet.
|
| + // TODO: Analyze initializer of element.
|
| + return compiler.dynamicClass;
|
| + }
|
| + } else if (Elements.isInstanceSend(node, elements)) {
|
| + ClassElement receiverType;
|
| + if (node.receiver == null) {
|
| + receiverType = analyzedElement.getEnclosingClass();
|
| + } else {
|
| + receiverType = node.receiver.accept(this);
|
| + }
|
| + Selector selector = elements.getSelector(node);
|
| + inferrer.registerGetterOnSelector(analyzedElement, selector);
|
| + return inferrer.returnTypeOfSelector(selector);
|
| + } else if (Elements.isStaticOrTopLevelFunction(element)) {
|
| + inferrer.registerGetFunction(analyzedElement, element);
|
| + return compiler.functionClass;
|
| + } else {
|
| + // TODO: Analyze variable.
|
| + return compiler.dynamicClass;
|
| + }
|
| + }
|
| +
|
| + visitClosureSend(Send node) {
|
| + node.visitChildren(this);
|
| + return compiler.dynamicClass;
|
| + }
|
| +
|
| + visitDynamicSend(Send node) {
|
| + ClassElement receiverType;
|
| + if (node.receiver == null) {
|
| + receiverType = analyzedElement.getEnclosingClass();
|
| + } else {
|
| + receiverType = node.receiver.accept(this);
|
| + }
|
| + ArgumentsTypes arguments = analyzeArguments(node.arguments);
|
| + Selector selector = elements.getSelector(node);
|
| + inferrer.registerCalledSelector(analyzedElement, 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);
|
| + Element firstType = node.thenExpression.accept(this);
|
| + Element secondType = node.elseExpression.accept(this);
|
| + return leastUpperBound(firstType, secondType);
|
| + }
|
| +
|
| + leastUpperBound(Element firstType, Element secondType) {
|
| + if (firstType == secondType) return firstType;
|
| + return compiler.dynamicClass;
|
| + }
|
| +}
|
|
|