| // Copyright (c) 2012, the Dart project authors. Please see the AUTHORS file |
| // for details. All rights reserved. Use of this source code is governed by a |
| // BSD-style license that can be found in the LICENSE file. |
| |
| library types; |
| |
| import '../dart2jslib.dart' hide Selector; |
| import '../tree/tree.dart'; |
| import '../elements/elements.dart'; |
| import '../util/util.dart'; |
| import '../universe/universe.dart'; |
| |
| part 'concrete_types_inferrer.dart'; |
| |
| /** |
| * The types task infers guaranteed types globally. |
| */ |
| class TypesTask extends CompilerTask { |
| final String name = 'Type inference'; |
| final Set<Element> untypedElements; |
| final Map<Element, Link<Element>> typedSends; |
| final ConcreteTypesInferrer concreteTypesInferrer; |
| |
| TypesTask(Compiler compiler, bool enableConcreteTypeInference) |
| : untypedElements = new Set<Element>(), |
| typedSends = new Map<Element, Link<Element>>(), |
| concreteTypesInferrer = enableConcreteTypeInference |
| ? new ConcreteTypesInferrer(compiler) : null, |
| super(compiler); |
| |
| /** |
| * Called once for each method during the resolution phase of the |
| * compiler. |
| */ |
| void analyze(Node node, TreeElements elements) { |
| measure(() { |
| node.accept(new ConcreteTypeInferencer(this, elements)); |
| }); |
| } |
| |
| /** |
| * Called when resolution is complete. |
| */ |
| void onResolutionComplete(Element mainElement) { |
| measure(() { |
| if (concreteTypesInferrer != null) { |
| concreteTypesInferrer.analyzeMain(mainElement); |
| } |
| }); |
| } |
| |
| /** |
| * Return the (inferred) guaranteed concrete type of [element] or null. |
| */ |
| ConcreteType getGuaranteedTypeOfElement(Element element) { |
| return measure(() { |
| if (!element.isParameter()) return null; |
| if (concreteTypesInferrer != null) { |
| ConcreteType guaranteedType = concreteTypesInferrer |
| .getConcreteTypeOfParameter(element); |
| if (guaranteedType != null) return guaranteedType; |
| } |
| Element holder = element.enclosingElement; |
| Link<Element> types = typedSends[holder]; |
| if (types == null) return null; |
| if (!holder.isFunction()) return null; |
| if (untypedElements.contains(holder)) return null; |
| FunctionElement function = holder; |
| FunctionSignature signature = function.computeSignature(compiler); |
| for (Element parameter in signature.requiredParameters) { |
| if (types.isEmpty) return null; |
| if (element == parameter) { |
| return new ConcreteType.singleton(new ClassBaseType(types.head)); |
| } |
| types = types.tail; |
| } |
| return null; |
| }); |
| } |
| |
| /** |
| * Return the (inferred) guaranteed concrete type of [node] or null. |
| * [node] must be an AST node of [owner]. |
| */ |
| ConcreteType getGuaranteedTypeOfNode(Node node, Element owner) { |
| return measure(() { |
| if (concreteTypesInferrer != null) { |
| return concreteTypesInferrer.getConcreteTypeOfNode(node); |
| } |
| return null; |
| }); |
| } |
| } |
| |
| /** |
| * Infers concrete types for a single method or expression. |
| */ |
| class ConcreteTypeInferencer extends Visitor { |
| final TypesTask task; |
| final TreeElements elements; |
| final ClassElement boolClass; |
| final ClassElement doubleClass; |
| final ClassElement intClass; |
| final ClassElement listClass; |
| final ClassElement nullClass; |
| final ClassElement stringClass; |
| |
| final Map<Node, ClassElement> concreteTypes; |
| |
| ConcreteTypeInferencer(TypesTask task, this.elements) |
| : this.task = task, |
| this.boolClass = task.compiler.boolClass, |
| this.doubleClass = task.compiler.doubleClass, |
| this.intClass = task.compiler.intClass, |
| this.listClass = task.compiler.listClass, |
| this.nullClass = task.compiler.nullClass, |
| this.stringClass = task.compiler.stringClass, |
| this.concreteTypes = new Map<Node, ClassElement>(); |
| |
| visitNode(Node node) => node.visitChildren(this); |
| |
| visitLiteralString(LiteralString node) { |
| recordConcreteType(node, stringClass); |
| } |
| |
| visitStringInterpolation(StringInterpolation node) { |
| node.visitChildren(this); |
| recordConcreteType(node, stringClass); |
| } |
| |
| visitStringJuxtaposition(StringJuxtaposition node) { |
| node.visitChildren(this); |
| recordConcreteType(node, stringClass); |
| } |
| |
| recordConcreteType(Node node, ClassElement cls) { |
| concreteTypes[node] = cls; |
| } |
| |
| visitLiteralBool(LiteralBool node) { |
| recordConcreteType(node, boolClass); |
| } |
| |
| visitLiteralDouble(LiteralDouble node) { |
| recordConcreteType(node, doubleClass); |
| } |
| |
| visitLiteralInt(LiteralInt node) { |
| recordConcreteType(node, intClass); |
| } |
| |
| visitLiteralList(LiteralList node) { |
| node.visitChildren(this); |
| recordConcreteType(node, listClass); |
| } |
| |
| visitLiteralMap(LiteralMap node) { |
| node.visitChildren(this); |
| // TODO(ahe): map class? |
| } |
| |
| visitLiteralNull(LiteralNull node) { |
| recordConcreteType(node, nullClass); |
| } |
| |
| Link<Element> computeConcreteSendArguments(Send node) { |
| if (node.argumentsNode == null) return null; |
| if (node.arguments.isEmpty) return const Link<Element>(); |
| if (node.receiver != null && concreteTypes[node.receiver] == null) { |
| return null; |
| } |
| LinkBuilder<Element> types = new LinkBuilder<Element>(); |
| for (Node argument in node.arguments) { |
| Element type = concreteTypes[argument]; |
| if (type == null) return null; |
| types.addLast(type); |
| } |
| return types.toLink(); |
| } |
| |
| visitSend(Send node) { |
| node.visitChildren(this); |
| Element element = elements[node.selector]; |
| if (element == null) return; |
| if (!Elements.isStaticOrTopLevelFunction(element)) return; |
| if (node.argumentsNode == null) { |
| // interest(node, 'closurized method'); |
| task.untypedElements.add(element); |
| return; |
| } |
| Link<Element> types = computeConcreteSendArguments(node); |
| if (types != null) { |
| Link<Element> existing = task.typedSends[element]; |
| if (existing == null) { |
| task.typedSends[element] = types; |
| } else { |
| // interest(node, 'multiple invocations'); |
| Link<Element> lub = computeLubs(existing, types); |
| if (lub == null) { |
| task.untypedElements.add(element); |
| } else { |
| task.typedSends[element] = lub; |
| } |
| } |
| } else { |
| // interest(node, 'dynamically typed invocation'); |
| task.untypedElements.add(element); |
| } |
| } |
| |
| visitSendSet(SendSet node) { |
| // TODO(ahe): Implement this. For now, overridden to avoid calling |
| // visitSend through super. |
| node.visitChildren(this); |
| } |
| |
| void interest(Node node, String note) { |
| var message = MessageKind.GENERIC.message([note]); |
| task.compiler.reportWarning(node, message); |
| } |
| |
| /** |
| * Computes the pairwise Least Upper Bound (LUB) of the elements of |
| * [a] and [b]. Returns [:null:] if it gives up, or if the lists |
| * aren't the same length. |
| */ |
| Link<Element> computeLubs(Link<Element> a, Link<Element> b) { |
| LinkBuilder<Element> lubs = new LinkBuilder<Element>(); |
| while (!a.isEmpty && !b.isEmpty) { |
| Element lub = computeLub(a.head, b.head); |
| if (lub == null) return null; |
| lubs.addLast(lub); |
| a = a.tail; |
| b = b.tail; |
| } |
| return (a.isEmpty && b.isEmpty) ? lubs.toLink() : null; |
| } |
| |
| /** |
| * Computes the Least Upper Bound (LUB) of [a] and [b]. Returns |
| * [:null:] if it gives up. |
| */ |
| Element computeLub(Element a, Element b) { |
| // Fast common case, but also simple initial implementation. |
| if (identical(a, b)) return a; |
| |
| // TODO(ahe): Improve the following "computation"... |
| return null; |
| } |
| } |