Chromium Code Reviews| 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,780 @@ |
| +// 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. |
| + */ |
| +class SingletonWorkQueue<E> { |
| + final List<E> queue = new List<E>(); |
| + final Set<E> singletons = new Set<E>(); |
|
sra1
2013/02/12 03:20:27
I normally think of a singleton as a set containin
ngeoffray
2013/02/12 11:58:22
Renamed to elementsInqueue.
|
| + |
| + void addLast(E element) { |
|
kasperl
2013/02/12 10:33:44
Maybe rename this to WorkSet (obvious that it does
ngeoffray
2013/02/12 11:58:22
Changed to add/remove (not sure about the removeAn
|
| + element = element.implementation; |
| + if (singletons.contains(element)) return; |
| + queue.addLast(element); |
| + singletons.add(element); |
| + } |
| + |
| + E removeLast() { |
| + E element = queue.removeLast(); |
| + singletons.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 = |
|
kasperl
2013/02/12 10:33:44
Are all these maps being kept alive even after we'
ngeoffray
2013/02/12 11:58:22
Done. I might have to introduce a new class to hol
|
| + 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. |
|
kasperl
2013/02/12 10:33:44
Long term, you probably want to map selectors to t
ngeoffray
2013/02/12 11:58:22
Yes. About that: are you thinking along the lines
kasperl
2013/02/12 12:21:19
I was thinking one big FunctionSet.
|
| + */ |
| + 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 SingletonWorkQueue<Element> workQueue = |
|
kasperl
2013/02/12 10:33:44
workSet?
ngeoffray
2013/02/12 11:58:22
Done.
|
| + new SingletonWorkQueue<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 |
|
sra1
2013/02/12 03:20:27
Sentinel
ngeoffray
2013/02/12 11:58:22
Done.
|
| + * 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 = workQueue.removeLast(); |
| + 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 (!workQueue.isEmpty); |
| + dump(); |
| + return true; |
| + } |
| + |
| + /** |
| + * Query method after the analysis to know the type of [element]. |
| + */ |
| + getConcreteTypeOfElement(element) { |
| + Element returnType = returnTypeOf[element]; |
| + 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); |
| + if (selector == null || selector.isSetter() || selector.isIndexSet()) { |
|
kasperl
2013/02/12 10:33:44
Isn't it easy to deal with setters and index-sets?
ngeoffray
2013/02/12 11:58:22
Problem is that ++, []=, --, += are SendSet that a
|
| + return null; |
| + } |
| + Element returnType = selector.isGetter() |
| + ? returnTypeOfGetterSelector(selector) |
| + : returnTypeOfCalledSelector(selector); |
| + if (returnType == null |
|
kasperl
2013/02/12 10:33:44
Share this code with the stuff in getConcreteTypeO
ngeoffray
2013/02/12 11:58:22
Done.
|
| + || returnType == compiler.dynamicClass |
| + || returnType == giveUpType) { |
| + return null; |
| + } |
| + return new ConcreteType.singleton( |
| + compiler.maxConcreteTypeSize, new ClassBaseType(returnType)); |
| + } |
| + |
| + /** |
| + * 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; |
| + workQueue.addLast(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); |
| + }); |
| + |
| + for (int i = max; i >=0; i--) { |
|
kasperl
2013/02/12 10:33:44
>=0 should be >= 0
ngeoffray
2013/02/12 11:58:22
Done.
|
| + Set<Element> set = methodSizes[i]; |
| + if (set != null) { |
| + set.forEach((e) { workQueue.addLast(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.'); |
| + } |
| + |
| + 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 returnTypeOfCalledSelector(Selector selector) { |
| + ClassElement result; |
| + iterateOverElements(selector, (Element element) { |
| + assert(element.isImplementation); |
| + Element 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; |
| + } |
| + |
| + /** |
| + * Returns the union of the return types of all elements that match |
| + * the getter [selector]. |
| + */ |
| + ClassElement returnTypeOfGetterSelector(Selector selector) { |
| + ClassElement result; |
| + iterateOverElements(selector, (Element element) { |
| + assert(element.isImplementation); |
| + ClassElement cls; |
| + if (element.isFunction()) { |
| + cls = compiler.functionClass; |
| + } else { |
| + cls = returnTypeOf[element]; |
| + } |
| + if (cls == null |
|
kasperl
2013/02/12 10:33:44
Share this code with the stuff in returnTypeOfCall
ngeoffray
2013/02/12 11:58:22
Done.
|
| + || cls == compiler.dynamicClass |
| + || cls == giveUpType |
| + || cls != result) { |
| + 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); |
| + 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] 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>(); |
| + for (ClassElement cls in compiler.enqueuer.resolution.seenClasses) { |
| + Element element = cls.lookupLocalMember(name); |
| + if (element != null |
| + && element.isInstanceMember() |
| + && !element.isAbstract(compiler) |
| + && compiler.enqueuer.resolution.isProcessed(element)) { |
| + methods.add(element.implementation); |
| + } |
| + } |
| + methodCache[name] = methods; |
| + memberWatch.stop(); |
| + } |
| + |
| + bool check(element) { |
| + if (element == null) return true; |
| + if (selector.appliesUnnamed(element, compiler)) { |
| + if (!f(element)) return false; |
| + } |
| + return true; |
| + } |
| + |
| + for (var element in methods) { |
| + if (element.isAbstractField()) { |
| + if (!check(element.getter)) return; |
| + if (!check(element.setter)) return; |
| + } else { |
| + if (!check(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 { |
| + 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 (cls != returnType) { |
| + 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) { |
| + node.visitChildren(this); |
| + return compiler.dynamicClass; |
|
kasperl
2013/02/12 10:33:44
It seems reasonably easy to get a type for this?
ngeoffray
2013/02/12 11:58:22
Not really because of []= and compound assignments
|
| + } |
| + |
| + visitIdentifier(Identifier node) { |
| + if (node.isThis() || node.isSuper()) { |
| + return analyzedElement.getEnclosingClass(); |
|
kasperl
2013/02/12 10:33:44
Isn't this broken (this may refer to a subclass or
ngeoffray
2013/02/12 11:58:22
Good catch. Changed to return dynamic, and added a
|
| + } |
| + 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. |
| + inferrer.registerGetterOnSelector(analyzedElement, selector); |
| + 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.typesInstantiated) { |
|
sra1
2013/02/12 03:20:27
Should be using typesReturned (they should be the
ngeoffray
2013/02/12 11:58:22
Done.
|
| + ClassElement mappedType; |
| + if (type == native.SpecialType.JsObject) { |
| + mappedType = compiler.objectClass; |
| + } else if (type == native.SpecialType.JsArray) { |
| + mappedType = compiler.listClass; |
| + } else { |
|
sra1
2013/02/12 03:20:27
If the analysis assumes the types are nullable, al
ngeoffray
2013/02/12 11:58:22
Currently, it does not assume they are nullable. A
|
| + mappedType = type.element; |
| + } |
| + 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.returnTypeOfGetterSelector(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.returnTypeOfCalledSelector(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); |
| + if (firstType == secondType) return firstType; |
|
sra1
2013/02/12 03:20:27
Maybe abstract out leastUpperBound and
return l
ngeoffray
2013/02/12 11:58:22
Done.
|
| + return compiler.dynamicClass; |
| + } |
| +} |