| Index: sdk/lib/_internal/compiler/implementation/types/inferrer_visitor.dart
|
| ===================================================================
|
| --- sdk/lib/_internal/compiler/implementation/types/inferrer_visitor.dart (revision 23842)
|
| +++ sdk/lib/_internal/compiler/implementation/types/inferrer_visitor.dart (working copy)
|
| @@ -2,1339 +2,52 @@
|
| // 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;
|
| +part of simple_types_inferrer;
|
|
|
| -import 'dart:collection' show Queue, LinkedHashSet;
|
| -
|
| -import '../closure.dart' show ClosureClassMap, ClosureScope;
|
| -import '../dart_types.dart'
|
| - show DartType, InterfaceType, FunctionType, TypeKind;
|
| -import '../elements/elements.dart';
|
| -import '../native_handler.dart' as native;
|
| -import '../tree/tree.dart';
|
| -import '../util/util.dart' show Link;
|
| -import 'types.dart' show TypesInferrer, FlatTypeMask, TypeMask;
|
| -
|
| -// BUG(8802): There's a bug in the analyzer that makes the re-export
|
| -// of Selector from dart2jslib.dart fail. For now, we work around that
|
| -// by importing universe.dart explicitly and disabling the re-export.
|
| -import '../dart2jslib.dart' hide Selector, TypedSelector;
|
| -import '../universe/universe.dart' show Selector, SideEffects, TypedSelector;
|
| -
|
| -/**
|
| - * A work queue that ensures there are no duplicates, and adds and
|
| - * removes in FIFO.
|
| - */
|
| -class WorkSet<E extends Element> {
|
| - final Queue<E> queue = new Queue<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.removeFirst();
|
| - elementsInQueue.remove(element);
|
| - return element;
|
| - }
|
| -
|
| - bool get isEmpty => queue.isEmpty;
|
| -
|
| - int get length => queue.length;
|
| -}
|
| -
|
| -/**
|
| - * 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<Node, TypeMask>> typesOfFinalFields =
|
| - new Map<Element, Map<Node, TypeMask>>();
|
| -
|
| - /**
|
| - * 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(Node node,
|
| - Element constructor,
|
| - Element field,
|
| - TypeMask type) {
|
| - Map<Node, TypeMask> typesFor = typesOfFinalFields.putIfAbsent(
|
| - field, () => new Map<Node, TypeMask>());
|
| - typesFor[node] = 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;
|
| -}
|
| -
|
| -/**
|
| - * A sentinel type mask class used by the inferrer for the give up
|
| - * type, and the dynamic type.
|
| - */
|
| -class SentinelTypeMask extends FlatTypeMask {
|
| - final String name;
|
| -
|
| - SentinelTypeMask(this.name) : super(null, 0, false);
|
| -
|
| - bool operator==(other) {
|
| - return identical(this, other);
|
| - }
|
| -
|
| - TypeMask nullable() {
|
| - throw 'Unsupported operation';
|
| - }
|
| -
|
| - TypeMask intersection(TypeMask other, Compiler compiler) {
|
| - return other;
|
| - }
|
| -
|
| - bool get isNullable => true;
|
| -
|
| - String toString() => '$name sentinel type mask';
|
| -}
|
| -
|
| -final OPTIMISTIC = 0;
|
| -final RETRY = 1;
|
| -final PESSIMISTIC = 2;
|
| -
|
| -class SimpleTypesInferrer extends TypesInferrer {
|
| - InternalSimpleTypesInferrer internal;
|
| - Compiler compiler;
|
| -
|
| - SimpleTypesInferrer(Compiler compiler) :
|
| - compiler = compiler,
|
| - internal = new InternalSimpleTypesInferrer(compiler, OPTIMISTIC);
|
| -
|
| - TypeMask get dynamicType => internal.dynamicType;
|
| - TypeMask get nullType => internal.nullType;
|
| - TypeMask get intType => internal.intType;
|
| - TypeMask get doubleType => internal.doubleType;
|
| - TypeMask get numType => internal.numType;
|
| - TypeMask get boolType => internal.boolType;
|
| - TypeMask get functionType => internal.functionType;
|
| - TypeMask get listType => internal.listType;
|
| - TypeMask get constListType => internal.constListType;
|
| - TypeMask get fixedListType => internal.fixedListType;
|
| - TypeMask get growableListType => internal.growableListType;
|
| - TypeMask get mapType => internal.mapType;
|
| - TypeMask get constMapType => internal.constMapType;
|
| - TypeMask get stringType => internal.stringType;
|
| - TypeMask get typeType => internal.typeType;
|
| -
|
| - TypeMask getReturnTypeOfElement(Element element) {
|
| - if (compiler.disableTypeInference) return dynamicType;
|
| - return internal.getReturnTypeOfElement(element);
|
| - }
|
| - TypeMask getTypeOfElement(Element element) {
|
| - if (compiler.disableTypeInference) return dynamicType;
|
| - return internal.getTypeOfElement(element);
|
| - }
|
| - TypeMask getTypeOfNode(Element owner, Node node) {
|
| - if (compiler.disableTypeInference) return dynamicType;
|
| - return internal.getTypeOfNode(owner, node);
|
| - }
|
| - TypeMask getTypeOfSelector(Selector selector) {
|
| - if (compiler.disableTypeInference) return dynamicType;
|
| - return internal.getTypeOfSelector(selector);
|
| - }
|
| -
|
| - bool analyzeMain(Element element) {
|
| - if (compiler.disableTypeInference) return true;
|
| - bool result = internal.analyzeMain(element);
|
| - if (internal.optimismState == OPTIMISTIC) return result;
|
| - assert(internal.optimismState == RETRY);
|
| -
|
| - // Discard the inferrer and start again with a pessimistic one.
|
| - internal = new InternalSimpleTypesInferrer(compiler, PESSIMISTIC);
|
| - return internal.analyzeMain(element);
|
| - }
|
| -}
|
| -
|
| -
|
| -
|
| -class InternalSimpleTypesInferrer 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, TypeMask> returnTypeOf =
|
| - new Map<Element, TypeMask>();
|
| -
|
| - /**
|
| - * Maps an element to its type.
|
| - */
|
| - final Map<Element, TypeMask> typeOf = new Map<Element, TypeMask>();
|
| -
|
| - /**
|
| - * Maps an element to its assignments and the types inferred at
|
| - * these assignments.
|
| - */
|
| - final Map<Element, Map<Node, TypeMask>> typeOfFields =
|
| - new Map<Element, Map<Node, TypeMask>>();
|
| -
|
| - /**
|
| - * Maps an element to the type of its parameters at call sites.
|
| - */
|
| - final Map<Element, Map<Node, ArgumentsTypes>> typeOfArguments =
|
| - new Map<Element, Map<Node, ArgumentsTypes>>();
|
| -
|
| - /**
|
| - * Maps an optional parameter to its default type.
|
| - */
|
| - final Map<Element, TypeMask> defaultTypeOfParameter =
|
| - new Map<Element, TypeMask>();
|
| -
|
| - /**
|
| - * Set of methods that the inferrer found could be closurized. We
|
| - * don't compute parameter types for such methods.
|
| - */
|
| - final Set<Element> methodsThatCanBeClosurized = new Set<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>();
|
| -
|
| - /**
|
| - * A map of constraints on a setter. When computing the type
|
| - * of a field, these [Node] are initially discarded, and once the
|
| - * type is computed, we make sure these constraints are satisfied
|
| - * for that type. For example:
|
| - *
|
| - * [: field++ ], or [: field += 42 :], the constraint is on the
|
| - * operator+, and we make sure that a typed selector with the found
|
| - * type returns that type.
|
| - *
|
| - * [: field = other.field :], the constraint in on the [:field]
|
| - * getter selector, and we make sure that the getter selector
|
| - * returns that type.
|
| - *
|
| - */
|
| - final Map<Node, Selector> setterConstraints = new Map<Node, Selector>();
|
| -
|
| - /**
|
| - * 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;
|
| -
|
| - int optimismState;
|
| -
|
| - /**
|
| - * Sentinel used by the inferrer to notify that it does not know
|
| - * the type of a specific element.
|
| - */
|
| - TypeMask dynamicType;
|
| - bool isDynamicType(TypeMask type) => identical(type, dynamicType);
|
| -
|
| - TypeMask nullType;
|
| - TypeMask intType;
|
| - TypeMask doubleType;
|
| - TypeMask numType;
|
| - TypeMask boolType;
|
| - TypeMask functionType;
|
| - TypeMask listType;
|
| - TypeMask constListType;
|
| - TypeMask fixedListType;
|
| - TypeMask growableListType;
|
| - TypeMask mapType;
|
| - TypeMask constMapType;
|
| - TypeMask stringType;
|
| - TypeMask typeType;
|
| -
|
| - /**
|
| - * These are methods that are expected to return only bool. We optimistically
|
| - * assume that they do this. If we later find a contradiction, we have to
|
| - * restart the simple types inferrer, because it normally goes from less
|
| - * optimistic to more optimistic as it refines its type information. Without
|
| - * this optimization, method names that are mutually recursive in the tail
|
| - * position will be typed as dynamic.
|
| - */
|
| - // TODO(erikcorry): Autogenerate the alphanumeric names in this set.
|
| - Set<SourceString> PREDICATES = new Set<SourceString>.from([
|
| - const SourceString('=='),
|
| - const SourceString('<='),
|
| - const SourceString('>='),
|
| - const SourceString('>'),
|
| - const SourceString('<'),
|
| - const SourceString('moveNext')]);
|
| -
|
| - bool shouldOptimisticallyOptimizeToBool(Element element) {
|
| - return element == compiler.identicalFunction.implementation
|
| - || (element.isFunction()
|
| - && element.isInstanceMember()
|
| - && PREDICATES.contains(element.name));
|
| - }
|
| -
|
| - final Compiler compiler;
|
| -
|
| - // Times the computation of re-analysis of methods.
|
| - final Stopwatch recomputeWatch = new Stopwatch();
|
| - // Number of re-analysis.
|
| - int recompiles = 0;
|
| -
|
| - /**
|
| - * Set to [true] when the analysis has analyzed all elements in the
|
| - * world.
|
| - */
|
| - bool hasAnalyzedAll = false;
|
| -
|
| - /**
|
| - * The number of elements in the world.
|
| - */
|
| - int numberOfElementsToAnalyze;
|
| -
|
| - InternalSimpleTypesInferrer(this.compiler, this.optimismState);
|
| -
|
| - /**
|
| - * Main entry point of the inferrer. Analyzes all elements that the resolver
|
| - * found as reachable. Returns whether it succeeded.
|
| - */
|
| - bool analyzeMain(Element element) {
|
| - initializeTypes();
|
| - buildWorkQueue();
|
| - int analyzed = 0;
|
| - compiler.progress.reset();
|
| - int maxReanalysis = (numberOfElementsToAnalyze * 1.5).toInt();
|
| - 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++;
|
| - if (recompiles >= maxReanalysis) {
|
| - compiler.log('Ran out of budget for inferring.');
|
| - break;
|
| - }
|
| - if (compiler.verbose) recomputeWatch.start();
|
| - }
|
| - bool changed =
|
| - compiler.withCurrentElement(element, () => analyze(element));
|
| - if (optimismState == RETRY) return true; // Abort.
|
| - analyzed++;
|
| - if (wasAnalyzed && compiler.verbose) {
|
| - recomputeWatch.stop();
|
| - }
|
| - checkAnalyzedAll();
|
| - if (changed) {
|
| - // If something changed during the analysis of [element], put back
|
| - // callers of it in the work list.
|
| - enqueueCallersOf(element);
|
| - }
|
| - } while (!workSet.isEmpty);
|
| - dump();
|
| - clear();
|
| - return true;
|
| - }
|
| -
|
| - /**
|
| - * Query method after the analysis to know the type of [element].
|
| - */
|
| - TypeMask getReturnTypeOfElement(Element element) {
|
| - return getTypeIfValuable(returnTypeOf[element]);
|
| - }
|
| -
|
| - TypeMask getTypeOfElement(Element element) {
|
| - return getTypeIfValuable(typeOf[element]);
|
| - }
|
| -
|
| - TypeMask getTypeOfSelector(Selector selector) {
|
| - return getTypeIfValuable(typeOfSelector(selector));
|
| - }
|
| -
|
| - bool isTypeValuable(TypeMask returnType) {
|
| - return !isDynamicType(returnType);
|
| - }
|
| -
|
| - TypeMask getTypeIfValuable(TypeMask returnType) {
|
| - return isTypeValuable(returnType) ? returnType : null;
|
| - }
|
| -
|
| - /**
|
| - * Query method after the analysis to know the type of [node],
|
| - * defined in the context of [owner].
|
| - */
|
| - TypeMask getTypeOfNode(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(typeOfSelector(selector));
|
| - }
|
| -
|
| - void checkAnalyzedAll() {
|
| - if (hasAnalyzedAll) return;
|
| - if (analyzeCount.length != numberOfElementsToAnalyze) return;
|
| - hasAnalyzedAll = true;
|
| - // If we have analyzed all the world, we know all assigments to
|
| - // fields and can therefore infer a type for them.
|
| - typeOfFields.keys.forEach(updateNonFinalFieldType);
|
| - // We also know all calls to methods.
|
| - typeOfArguments.keys.forEach(updateArgumentsType);
|
| - }
|
| -
|
| - /**
|
| - * Enqueues [e] in the work queue if it is valuable.
|
| - */
|
| - void enqueueAgain(Element e) {
|
| - int count = analyzeCount[e];
|
| - if (count != null && count > MAX_ANALYSIS_COUNT_PER_ELEMENT) return;
|
| - workSet.add(e);
|
| - }
|
| -
|
| - void enqueueCallersOf(Element element) {
|
| - Set<Element> methodCallers = callersOf[element];
|
| - if (methodCallers != null) {
|
| - methodCallers.forEach(enqueueAgain);
|
| - }
|
| - }
|
| -
|
| - /**
|
| - * 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) {
|
| - if (element.impliesType()) return;
|
| - assert(invariant(element,
|
| - element.isField() ||
|
| - element.isFunction() ||
|
| - element.isGenerativeConstructor() ||
|
| - element.isGetter() ||
|
| - element.isSetter(),
|
| - message: 'Unexpected element kind: ${element.kind}'));
|
| - // TODO(ngeoffray): Not sure why the resolver would put a null
|
| - // mapping.
|
| - if (mapping == null) return;
|
| - if (element.isAbstract(compiler)) return;
|
| - // Add the relational operators, ==, !=, <, etc., before any
|
| - // others, as well as the identical function.
|
| - if (shouldOptimisticallyOptimizeToBool(element)) {
|
| - workSet.add(element);
|
| - // Optimistically assume that they return bool. We may need to back
|
| - // out of this.
|
| - if (optimismState == OPTIMISTIC) {
|
| - returnTypeOf[element.implementation] = boolType;
|
| - }
|
| - } else {
|
| - // Put the other operators in buckets by length, later to be added in
|
| - // length order.
|
| - int length = mapping.selectors.length;
|
| - max = length > max ? length : max;
|
| - Set<Element> set = methodSizes.putIfAbsent(
|
| - length, () => new LinkedHashSet<Element>());
|
| - set.add(element);
|
| - }
|
| - });
|
| -
|
| - // This iteration assumes the [WorkSet] is FIFO.
|
| - for (int i = 0; i <= max; i++) {
|
| - Set<Element> set = methodSizes[i];
|
| - if (set != null) {
|
| - set.forEach((e) { workSet.add(e); });
|
| - }
|
| - }
|
| - numberOfElementsToAnalyze = workSet.length;
|
| -
|
| - // 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()
|
| - && compiler.enqueuer.resolution.isProcessed(member)) {
|
| - constructorCount++;
|
| - }
|
| - });
|
| - classInfoForFinalFields[cls.implementation] =
|
| - new ClassInfoForFinalFields(constructorCount);
|
| - });
|
| - }
|
| -
|
| - // TODO(ngeoffray): Get rid of this method. Unit tests don't always
|
| - // ensure these classes are resolved.
|
| - rawTypeOf(ClassElement cls) {
|
| - cls.ensureResolved(compiler);
|
| - assert(cls.rawType != null);
|
| - return cls.rawType;
|
| - }
|
| -
|
| - void initializeTypes() {
|
| - nullType = new TypeMask.empty();
|
| -
|
| - Backend backend = compiler.backend;
|
| - intType = new TypeMask.nonNullExact(
|
| - rawTypeOf(backend.intImplementation));
|
| - doubleType = new TypeMask.nonNullExact(
|
| - rawTypeOf(backend.doubleImplementation));
|
| - numType = new TypeMask.nonNullSubclass(
|
| - rawTypeOf(backend.numImplementation));
|
| - stringType = new TypeMask.nonNullExact(
|
| - rawTypeOf(backend.stringImplementation));
|
| - boolType = new TypeMask.nonNullExact(
|
| - rawTypeOf(backend.boolImplementation));
|
| -
|
| - listType = new TypeMask.nonNullExact(
|
| - rawTypeOf(backend.listImplementation));
|
| - constListType = new TypeMask.nonNullExact(
|
| - rawTypeOf(backend.constListImplementation));
|
| - fixedListType = new TypeMask.nonNullExact(
|
| - rawTypeOf(backend.fixedListImplementation));
|
| - growableListType = new TypeMask.nonNullExact(
|
| - rawTypeOf(backend.growableListImplementation));
|
| -
|
| - mapType = new TypeMask.nonNullSubtype(
|
| - rawTypeOf(backend.mapImplementation));
|
| - constMapType = new TypeMask.nonNullSubtype(
|
| - rawTypeOf(backend.constMapImplementation));
|
| - functionType = new TypeMask.nonNullSubtype(
|
| - rawTypeOf(backend.functionImplementation));
|
| - typeType = new TypeMask.nonNullExact(
|
| - rawTypeOf(backend.typeImplementation));
|
| -
|
| - dynamicType = new TypeMask.subclass(rawTypeOf(compiler.objectClass));
|
| - }
|
| -
|
| - dump() {
|
| - int interestingTypes = 0;
|
| - returnTypeOf.forEach((Element element, TypeMask type) {
|
| - if (type != nullType && !isDynamicType(type)) {
|
| - interestingTypes++;
|
| - }
|
| - });
|
| - typeOf.forEach((Element element, TypeMask type) {
|
| - if (type != nullType && !isDynamicType(type)) {
|
| - interestingTypes++;
|
| - }
|
| - });
|
| -
|
| - compiler.log('Type inferrer re-analyzed methods $recompiles times '
|
| - 'in ${recomputeWatch.elapsedMilliseconds} ms.');
|
| - compiler.log('Type inferrer found $interestingTypes interesting '
|
| - 'types.');
|
| - }
|
| -
|
| - /**
|
| - * Clear data structures that are not used after the analysis.
|
| - */
|
| - void clear() {
|
| - callersOf.clear();
|
| - analyzeCount.clear();
|
| - classInfoForFinalFields.clear();
|
| - typeOfFields.clear();
|
| - setterConstraints.clear();
|
| - }
|
| -
|
| - bool analyze(Element element) {
|
| - if (element.isForwardingConstructor) {
|
| - element = element.targetConstructor;
|
| - }
|
| - SimpleTypeInferrerVisitor visitor =
|
| - new SimpleTypeInferrerVisitor(element, compiler, this);
|
| - TypeMask 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; // Nothing changed.
|
| - } else if (element.isField()) {
|
| - Node node = element.parseNode(compiler);
|
| - if (element.modifiers.isFinal() || element.modifiers.isConst()) {
|
| - // If [element] is final and has an initializer, we record
|
| - // the inferred type.
|
| - if (node.asSendSet() != null) {
|
| - return recordType(element, returnType);
|
| - }
|
| - return false;
|
| - } else if (node.asSendSet() == null) {
|
| - // Only update types of static fields if there is no
|
| - // assignment. Instance fields are dealt with in the constructor.
|
| - if (Elements.isStaticOrTopLevelField(element)) {
|
| - recordNonFinalFieldElementType(node, element, returnType, null);
|
| - }
|
| - return false;
|
| - } else {
|
| - recordNonFinalFieldElementType(node, element, returnType, null);
|
| - // [recordNonFinalFieldElementType] takes care of re-enqueuing
|
| - // users of the field.
|
| - return false;
|
| - }
|
| - } else {
|
| - return recordReturnType(element, returnType);
|
| - }
|
| - }
|
| -
|
| - bool recordType(Element analyzedElement, TypeMask type) {
|
| - assert(type != null);
|
| - assert(analyzedElement.isField()
|
| - || analyzedElement.isParameter()
|
| - || analyzedElement.isFieldParameter());
|
| - return internalRecordType(analyzedElement, type, typeOf);
|
| - }
|
| -
|
| - /**
|
| - * Records [returnType] as the return type of [analyzedElement].
|
| - * Returns whether the new type is worth recompiling the callers of
|
| - * [analyzedElement].
|
| - */
|
| - bool recordReturnType(Element analyzedElement, TypeMask returnType) {
|
| - assert(analyzedElement.implementation == analyzedElement);
|
| - if (optimismState == OPTIMISTIC
|
| - && shouldOptimisticallyOptimizeToBool(analyzedElement)
|
| - && returnType != returnTypeOf[analyzedElement]) {
|
| - // One of the functions turned out not to return what we expected.
|
| - // This means we need to restart the analysis.
|
| - optimismState = RETRY;
|
| - }
|
| - return internalRecordType(analyzedElement, returnType, returnTypeOf);
|
| - }
|
| -
|
| - bool isNativeElement(Element element) {
|
| - if (element.isNative()) return true;
|
| - return element.isMember()
|
| - && element.getEnclosingClass().isNative()
|
| - && element.isField();
|
| - }
|
| -
|
| - bool internalRecordType(Element analyzedElement,
|
| - TypeMask newType,
|
| - Map<Element, TypeMask> types) {
|
| - if (compiler.trustTypeAnnotations
|
| - // Parameters are being checked by the method, and we can
|
| - // therefore only trust their type after the checks.
|
| - || (compiler.enableTypeAssertions && !analyzedElement.isParameter())) {
|
| - var annotation = analyzedElement.computeType(compiler);
|
| - if (types == returnTypeOf) {
|
| - assert(annotation is FunctionType);
|
| - annotation = annotation.returnType;
|
| - }
|
| - newType = narrowType(newType, annotation);
|
| - }
|
| -
|
| - // Fields and native methods of native classes are handled
|
| - // specially when querying for their type or return type.
|
| - if (isNativeElement(analyzedElement)) return false;
|
| - assert(newType != null);
|
| - TypeMask existing = types[analyzedElement];
|
| - types[analyzedElement] = newType;
|
| - // If the return type is useful, say it has changed.
|
| - return existing != newType
|
| - && !isDynamicType(newType)
|
| - && newType != nullType;
|
| - }
|
| -
|
| - /**
|
| - * Returns the return type of [element]. Returns [:dynamic:] if
|
| - * [element] has not been analyzed yet.
|
| - */
|
| - TypeMask returnTypeOfElement(Element element) {
|
| - element = element.implementation;
|
| - if (element.isGenerativeConstructor()) {
|
| - return returnTypeOf.putIfAbsent(element, () {
|
| - return new TypeMask.nonNullExact(
|
| - rawTypeOf(element.getEnclosingClass()));
|
| - });
|
| - } else if (element.isNative()) {
|
| - return returnTypeOf.putIfAbsent(element, () {
|
| - var elementType = element.computeType(compiler);
|
| - if (elementType.kind != TypeKind.FUNCTION) {
|
| - return dynamicType;
|
| - }
|
| - return typeOfNativeBehavior(
|
| - native.NativeBehavior.ofMethod(element, compiler));
|
| - });
|
| - }
|
| - TypeMask returnType = returnTypeOf[element];
|
| - if (returnType == null) {
|
| - if ((compiler.trustTypeAnnotations || compiler.enableTypeAssertions)
|
| - && (element.isFunction()
|
| - || element.isGetter()
|
| - || element.isFactoryConstructor())) {
|
| - FunctionType functionType = element.computeType(compiler);
|
| - returnType = narrowType(dynamicType, functionType.returnType);
|
| - } else {
|
| - returnType = dynamicType;
|
| - }
|
| - }
|
| - return returnType;
|
| - }
|
| -
|
| - TypeMask typeOfNativeBehavior(native.NativeBehavior nativeBehavior) {
|
| - if (nativeBehavior == null) return dynamicType;
|
| - List typesReturned = nativeBehavior.typesReturned;
|
| - if (typesReturned.isEmpty) return dynamicType;
|
| - TypeMask returnType;
|
| - for (var type in typesReturned) {
|
| - TypeMask mappedType;
|
| - if (type == native.SpecialType.JsObject) {
|
| - mappedType = new TypeMask.nonNullExact(rawTypeOf(compiler.objectClass));
|
| - } else if (type == native.SpecialType.JsArray) {
|
| - mappedType = listType;
|
| - } else if (type.element == compiler.stringClass) {
|
| - mappedType = stringType;
|
| - } else if (type.element == compiler.intClass) {
|
| - mappedType = intType;
|
| - } else if (type.element == compiler.doubleClass) {
|
| - mappedType = doubleType;
|
| - } else if (type.element == compiler.numClass) {
|
| - mappedType = numType;
|
| - } else if (type.element == compiler.boolClass) {
|
| - mappedType = boolType;
|
| - } else if (type.element == compiler.nullClass) {
|
| - mappedType = nullType;
|
| - } else if (type.isVoid) {
|
| - mappedType = nullType;
|
| - } else if (type.isDynamic) {
|
| - return dynamicType;
|
| - } else if (compiler.world.hasAnySubclass(type.element)) {
|
| - mappedType = new TypeMask.nonNullSubclass(rawTypeOf(type.element));
|
| - } else if (compiler.world.hasAnySubtype(type.element)) {
|
| - mappedType = new TypeMask.nonNullSubtype(rawTypeOf(type.element));
|
| - } else {
|
| - mappedType = new TypeMask.nonNullExact(rawTypeOf(type.element));
|
| - }
|
| - returnType = computeLUB(returnType, mappedType);
|
| - if (!isTypeValuable(returnType)) {
|
| - returnType = dynamicType;
|
| - break;
|
| - }
|
| - }
|
| - return returnType;
|
| - }
|
| -
|
| -
|
| - /**
|
| - * Returns the type of [element]. Returns [:dynamic:] if
|
| - * [element] has not been analyzed yet.
|
| - */
|
| - TypeMask typeOfElement(Element element) {
|
| - element = element.implementation;
|
| - if (isNativeElement(element) && element.isField()) {
|
| - var type = typeOf.putIfAbsent(element, () {
|
| - InterfaceType rawType = element.computeType(compiler).asRaw();
|
| - return rawType.isDynamic ? dynamicType : new TypeMask.subtype(rawType);
|
| - });
|
| - assert(type != null);
|
| - return type;
|
| - }
|
| - TypeMask type = typeOf[element];
|
| - if (type == null) {
|
| - if ((compiler.trustTypeAnnotations
|
| - && (element.isField()
|
| - || element.isParameter()
|
| - || element.isVariable()))
|
| - // Parameters are being checked by the method, and we can
|
| - // therefore only trust their type after the checks.
|
| - || (compiler.enableTypeAssertions
|
| - && (element.isField() || element.isVariable()))) {
|
| - type = narrowType(dynamicType, element.computeType(compiler));
|
| - } else {
|
| - type = dynamicType;
|
| - }
|
| - }
|
| +TypeMask narrowType(TypeMask type,
|
| + DartType annotation,
|
| + Compiler compiler,
|
| + {bool isNullable: true}) {
|
| + if (annotation.isDynamic) return type;
|
| + if (annotation.isMalformed) return type;
|
| + if (annotation.isVoid) return compiler.typesTask.typesInferrer.nullType;
|
| + if (annotation.element == compiler.objectClass) return type;
|
| + TypeMask otherType;
|
| + if (annotation.kind == TypeKind.TYPEDEF
|
| + || annotation.kind == TypeKind.FUNCTION) {
|
| + otherType = compiler.typesTask.typesInferrer.functionType;
|
| + } else if (annotation.kind == TypeKind.TYPE_VARIABLE) {
|
| return type;
|
| + } else {
|
| + assert(annotation.kind == TypeKind.INTERFACE);
|
| + otherType = new TypeMask.nonNullSubtype(annotation);
|
| }
|
| -
|
| - /**
|
| - * Returns the union of the types of all elements that match
|
| - * the called [selector].
|
| - */
|
| - TypeMask typeOfSelector(Selector selector) {
|
| - // Bailout for closure calls. We're not tracking types of
|
| - // closures.
|
| - if (selector.isClosureCall()) return dynamicType;
|
| -
|
| - TypeMask result;
|
| - iterateOverElements(selector, (Element element) {
|
| - assert(element.isImplementation);
|
| - TypeMask type = typeOfElementWithSelector(element, selector);
|
| - result = computeLUB(result, type);
|
| - return isTypeValuable(result);
|
| - });
|
| - if (result == null) {
|
| - result = new TypeMask.nonNullEmpty();
|
| - }
|
| - return result;
|
| - }
|
| -
|
| - TypeMask typeOfElementWithSelector(Element element, Selector selector) {
|
| - if (element.name == Compiler.NO_SUCH_METHOD
|
| - && selector.name != element.name) {
|
| - // An invocation can resolve to a [noSuchMethod], in which case
|
| - // we get the return type of [noSuchMethod].
|
| - return returnTypeOfElement(element);
|
| - } else if (selector.isGetter()) {
|
| - if (element.isFunction()) {
|
| - // [functionType] is null if the inferrer did not run.
|
| - return functionType == null ? dynamicType : functionType;
|
| - } else if (element.isField()) {
|
| - return typeOfElement(element);
|
| - } else if (Elements.isUnresolved(element)) {
|
| - return dynamicType;
|
| - } else {
|
| - assert(element.isGetter());
|
| - return returnTypeOfElement(element);
|
| - }
|
| - } else {
|
| - return returnTypeOfElement(element);
|
| - }
|
| - }
|
| -
|
| - 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;
|
| - }
|
| -
|
| - void addCaller(Element caller, Element callee) {
|
| - assert(caller.isImplementation);
|
| - assert(callee.isImplementation);
|
| - assert(isNotClosure(caller));
|
| - Set<Element> callers = callersOf.putIfAbsent(
|
| - callee, () => new Set<Element>());
|
| - callers.add(caller);
|
| - }
|
| -
|
| - bool addArguments(Node node, Element element, ArgumentsTypes arguments) {
|
| - Map<Node, ArgumentsTypes> types = typeOfArguments.putIfAbsent(
|
| - element, () => new Map<Node, ArgumentsTypes>());
|
| - ArgumentsTypes existing = types[node];
|
| - types[node] = arguments;
|
| - return existing != arguments;
|
| - }
|
| -
|
| - void updateSideEffects(SideEffects sideEffects,
|
| - Selector selector,
|
| - Element callee) {
|
| - if (callee.isField()) {
|
| - if (callee.isInstanceMember()) {
|
| - if (selector.isSetter()) {
|
| - sideEffects.setChangesInstanceProperty();
|
| - } else if (selector.isGetter()) {
|
| - sideEffects.setDependsOnInstancePropertyStore();
|
| - } else {
|
| - sideEffects.setAllSideEffects();
|
| - sideEffects.setDependsOnSomething();
|
| - }
|
| - } else {
|
| - if (selector.isSetter()) {
|
| - sideEffects.setChangesStaticProperty();
|
| - } else if (selector.isGetter()) {
|
| - sideEffects.setDependsOnStaticPropertyStore();
|
| - } else {
|
| - sideEffects.setAllSideEffects();
|
| - sideEffects.setDependsOnSomething();
|
| - }
|
| - }
|
| - } else if (callee.isGetter() && !selector.isGetter()) {
|
| - sideEffects.setAllSideEffects();
|
| - sideEffects.setDependsOnSomething();
|
| - } else {
|
| - sideEffects.add(compiler.world.getSideEffectsOfElement(callee));
|
| - }
|
| - }
|
| -
|
| - /**
|
| - * Registers that [caller] calls [callee] with the given
|
| - * [arguments]. [constraint] is a setter constraint (see
|
| - * [setterConstraints] documentation).
|
| - */
|
| - void registerCalledElement(Node node,
|
| - Selector selector,
|
| - Element caller,
|
| - Element callee,
|
| - ArgumentsTypes arguments,
|
| - Selector constraint,
|
| - SideEffects sideEffects,
|
| - bool inLoop) {
|
| - updateSideEffects(sideEffects, selector, callee);
|
| -
|
| - // Bailout for closure calls. We're not tracking types of
|
| - // arguments for closures.
|
| - if (callee.isInstanceMember() && selector.isClosureCall()) {
|
| - return;
|
| - }
|
| - if (inLoop) {
|
| - // For instance methods, we only register a selector called in a
|
| - // loop if it is a typed selector, to avoid marking too many
|
| - // methods as being called from within a loop. This cuts down
|
| - // on the code bloat.
|
| - // TODO(ngeoffray): We should move the filtering on the selector
|
| - // in the backend. It is not the inferrer role to do this kind
|
| - // of optimization.
|
| - if (Elements.isStaticOrTopLevel(callee) || selector.mask != null) {
|
| - compiler.world.addFunctionCalledInLoop(callee);
|
| - }
|
| - }
|
| -
|
| - assert(isNotClosure(caller));
|
| - callee = callee.implementation;
|
| - if (!analyzeCount.containsKey(caller)) {
|
| - addCaller(caller, callee);
|
| - }
|
| -
|
| - if (selector.isSetter() && callee.isField()) {
|
| - recordNonFinalFieldElementType(
|
| - node, callee, arguments.positional[0], constraint);
|
| - return;
|
| - } else if (selector.isGetter()) {
|
| - assert(arguments == null);
|
| - if (callee.isFunction()) {
|
| - methodsThatCanBeClosurized.add(callee);
|
| - }
|
| - return;
|
| - } else if (callee.isField()) {
|
| - // We're not tracking closure calls.
|
| - return;
|
| - } else if (callee.isGetter()) {
|
| - // Getters don't have arguments.
|
| - return;
|
| - }
|
| - FunctionElement function = callee;
|
| - if (function.computeSignature(compiler).parameterCount == 0) return;
|
| -
|
| - assert(arguments != null);
|
| - bool isUseful = addArguments(node, callee, arguments);
|
| - if (hasAnalyzedAll && isUseful) {
|
| - enqueueAgain(callee);
|
| - }
|
| - }
|
| -
|
| - void unregisterCalledElement(Node node,
|
| - Selector selector,
|
| - Element caller,
|
| - Element callee) {
|
| - if (callee.isField()) {
|
| - if (selector.isSetter()) {
|
| - Map<Node, TypeMask> types = typeOfFields[callee];
|
| - if (types == null || !types.containsKey(node)) return;
|
| - types.remove(node);
|
| - if (hasAnalyzedAll) updateNonFinalFieldType(callee);
|
| - }
|
| - } else if (callee.isGetter()) {
|
| - return;
|
| - } else {
|
| - Map<Node, ArgumentsTypes> types = typeOfArguments[callee];
|
| - if (types == null || !types.containsKey(node)) return;
|
| - types.remove(node);
|
| - if (hasAnalyzedAll) enqueueAgain(callee);
|
| - }
|
| - }
|
| -
|
| - /**
|
| - * Computes the parameter types of [element], based on all call sites we
|
| - * have collected on that [element]. This method can only be called after
|
| - * we have analyzed all elements in the world.
|
| - */
|
| - void updateArgumentsType(FunctionElement element) {
|
| - assert(hasAnalyzedAll);
|
| - if (methodsThatCanBeClosurized.contains(element)) return;
|
| - // A [noSuchMethod] method can be the target of any call, with
|
| - // any number of arguments. For simplicity, we just do not
|
| - // infer any parameter types for [noSuchMethod].
|
| - if (element.name == Compiler.NO_SUCH_METHOD) return;
|
| - FunctionSignature signature = element.computeSignature(compiler);
|
| -
|
| - if (typeOfArguments[element] == null || typeOfArguments[element].isEmpty) {
|
| - signature.forEachParameter((Element parameter) {
|
| - typeOf.remove(parameter);
|
| - });
|
| - return;
|
| - }
|
| -
|
| - int parameterIndex = 0;
|
| - bool changed = false;
|
| - bool visitingOptionalParameter = false;
|
| - signature.forEachParameter((Element parameter) {
|
| - if (parameter == signature.firstOptionalParameter) {
|
| - visitingOptionalParameter = true;
|
| - }
|
| - TypeMask type;
|
| - typeOfArguments[element].forEach((_, ArgumentsTypes arguments) {
|
| - if (!visitingOptionalParameter) {
|
| - type = computeLUB(type, arguments.positional[parameterIndex]);
|
| - } else {
|
| - TypeMask argumentType = signature.optionalParametersAreNamed
|
| - ? arguments.named[parameter.name]
|
| - : parameterIndex < arguments.positional.length
|
| - ? arguments.positional[parameterIndex]
|
| - : null;
|
| - if (argumentType == null) {
|
| - argumentType = defaultTypeOfParameter[parameter];
|
| - }
|
| - assert(argumentType != null);
|
| - type = computeLUB(type, argumentType);
|
| - }
|
| - });
|
| - if (recordType(parameter, type)) {
|
| - changed = true;
|
| - }
|
| - parameterIndex++;
|
| - });
|
| -
|
| - if (changed) enqueueAgain(element);
|
| - }
|
| -
|
| - TypeMask handleIntrisifiedSelector(Selector selector,
|
| - ArgumentsTypes arguments) {
|
| - if (selector.mask != intType) return null;
|
| - if (!selector.isCall() && !selector.isOperator()) return null;
|
| - if (!arguments.named.isEmpty) return null;
|
| - if (arguments.positional.length > 1) return null;
|
| -
|
| - SourceString name = selector.name;
|
| - if (name == const SourceString('*')
|
| - || name == const SourceString('+')
|
| - || name == const SourceString('%')
|
| - || name == const SourceString('remainder')) {
|
| - return arguments.hasOnePositionalArgumentWithType(intType)
|
| - ? intType
|
| - : null;
|
| - } else if (name == const SourceString('-')) {
|
| - if (arguments.hasNoArguments()) return intType;
|
| - if (arguments.hasOnePositionalArgumentWithType(intType)) return intType;
|
| - return null;
|
| - } else if (name == const SourceString('abs')) {
|
| - return arguments.hasNoArguments() ? intType : null;
|
| - }
|
| - return null;
|
| - }
|
| -
|
| - /**
|
| - * Registers that [caller] calls an element matching [selector]
|
| - * with the given [arguments].
|
| - */
|
| - TypeMask registerCalledSelector(Node node,
|
| - Selector selector,
|
| - TypeMask receiverType,
|
| - Element caller,
|
| - ArgumentsTypes arguments,
|
| - Selector constraint,
|
| - SideEffects sideEffects,
|
| - bool inLoop) {
|
| - TypeMask result;
|
| - Iterable<Element> untypedTargets =
|
| - compiler.world.allFunctions.filter(selector.asUntyped);
|
| - Iterable<Element> typedTargets =
|
| - compiler.world.allFunctions.filter(selector);
|
| - for (Element element in untypedTargets) {
|
| - element = element.implementation;
|
| - if (!typedTargets.contains(element.declaration)) {
|
| - unregisterCalledElement(node, selector, caller, element);
|
| - } else {
|
| - registerCalledElement(
|
| - node, selector, caller, element, arguments,
|
| - constraint, sideEffects, inLoop);
|
| -
|
| - if (!selector.isSetter()) {
|
| - TypeMask type = handleIntrisifiedSelector(selector, arguments);
|
| - if (type == null) type = typeOfElementWithSelector(element, selector);
|
| - result = computeLUB(result, type);
|
| - }
|
| - }
|
| - }
|
| -
|
| - if (result == null) {
|
| - result = dynamicType;
|
| - }
|
| - return result;
|
| - }
|
| -
|
| - /**
|
| - * 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 an assignment to [element] with the given
|
| - * [argumentType].
|
| - */
|
| - void recordNonFinalFieldElementType(Node node,
|
| - Element element,
|
| - TypeMask argumentType,
|
| - Selector constraint) {
|
| - Map<Node, TypeMask> map =
|
| - typeOfFields.putIfAbsent(element, () => new Map<Node, TypeMask>());
|
| - map[node] = argumentType;
|
| - bool changed = typeOf[element] != argumentType;
|
| - if (constraint != null && constraint != setterConstraints[node]) {
|
| - changed = true;
|
| - setterConstraints[node] = constraint;
|
| - }
|
| - // If we have analyzed all elements, we can update the type of the
|
| - // field right away.
|
| - if (hasAnalyzedAll && changed) {
|
| - updateNonFinalFieldType(element);
|
| - }
|
| - }
|
| -
|
| - TypeMask computeFieldTypeWithConstraints(Element element, Map types) {
|
| - Set<Selector> constraints = new Set<Selector>();
|
| - TypeMask fieldType;
|
| - types.forEach((Node node, TypeMask mask) {
|
| - Selector constraint = setterConstraints[node];
|
| - if (constraint != null) {
|
| - // If this update has a constraint, we collect it and don't
|
| - // use its type.
|
| - constraints.add(constraint);
|
| - } else {
|
| - fieldType = computeLUB(fieldType, mask);
|
| - }
|
| - });
|
| -
|
| - if (!constraints.isEmpty && !isDynamicType(fieldType)) {
|
| - // Now that we have found a type, we go over the collected
|
| - // constraints, and make sure they apply to the found type. We
|
| - // update [typeOf] to make sure [typeOfSelector] knows the field
|
| - // type.
|
| - TypeMask existing = typeOf[element];
|
| - typeOf[element] = fieldType;
|
| -
|
| - for (Selector constraint in constraints) {
|
| - if (constraint.isOperator()) {
|
| - // If the constraint is on an operator, we type the receiver
|
| - // to be the field.
|
| - if (fieldType != null) {
|
| - constraint = new TypedSelector(fieldType, constraint);
|
| - }
|
| - } else {
|
| - // Otherwise the constraint is on the form [: field = other.field :].
|
| - assert(constraint.isGetter());
|
| - }
|
| - fieldType = computeLUB(fieldType, typeOfSelector(constraint));
|
| - }
|
| - if (existing == null) {
|
| - typeOf.remove(element);
|
| - } else {
|
| - typeOf[element] = existing;
|
| - }
|
| - }
|
| - return fieldType;
|
| - }
|
| -
|
| - /**
|
| - * Computes the type of [element], based on all assignments we have
|
| - * collected on that [element]. This method can only be called after
|
| - * we have analyzed all elements in the world.
|
| - */
|
| - void updateNonFinalFieldType(Element element) {
|
| - if (isNativeElement(element)) return;
|
| - assert(hasAnalyzedAll);
|
| -
|
| - if (typeOfFields[element] == null || typeOfFields[element].isEmpty) {
|
| - typeOf.remove(element);
|
| - return;
|
| - }
|
| -
|
| - TypeMask fieldType = computeFieldTypeWithConstraints(
|
| - element, typeOfFields[element]);
|
| -
|
| - // If the type of [element] has changed, re-analyze its users.
|
| - if (recordType(element, fieldType)) {
|
| - enqueueCallersOf(element);
|
| - }
|
| - }
|
| -
|
| - /**
|
| - * Records in [classInfoForFinalFields] that [constructor] has
|
| - * inferred [type] for the final [field].
|
| - */
|
| - void recordFinalFieldType(Node node,
|
| - Element constructor,
|
| - Element field,
|
| - TypeMask type,
|
| - Selector constraint) {
|
| - if (constraint != null) {
|
| - setterConstraints[node] = constraint;
|
| - }
|
| - // 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(node, 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) {
|
| - updateFinalFieldsType(info);
|
| - }
|
| - }
|
| -
|
| - /**
|
| - * Updates types of final fields listed in [info].
|
| - */
|
| - void updateFinalFieldsType(ClassInfoForFinalFields info) {
|
| - assert(info.isDone);
|
| - info.typesOfFinalFields.forEach((Element field,
|
| - Map<Node, TypeMask> types) {
|
| - if (isNativeElement(field)) return;
|
| - assert(field.modifiers.isFinal());
|
| - TypeMask fieldType = computeFieldTypeWithConstraints(field, types);
|
| - if (recordType(field, fieldType)) {
|
| - enqueueCallersOf(field);
|
| - }
|
| - });
|
| - }
|
| -
|
| - /**
|
| - * Returns the least upper bound between [firstType] and
|
| - * [secondType].
|
| - */
|
| - TypeMask computeLUB(TypeMask firstType, TypeMask secondType) {
|
| - if (firstType == null) {
|
| - return secondType;
|
| - } else if (isDynamicType(secondType)) {
|
| - return secondType;
|
| - } else if (isDynamicType(firstType)) {
|
| - return firstType;
|
| - } else {
|
| - TypeMask union = firstType.union(secondType, compiler);
|
| - // TODO(kasperl): If the union isn't nullable it seems wasteful
|
| - // to use dynamic. Fix that.
|
| - return union.containsAll(compiler) ? dynamicType : union;
|
| - }
|
| - }
|
| -
|
| - TypeMask narrowType(TypeMask type,
|
| - DartType annotation,
|
| - {bool isNullable: true}) {
|
| - if (annotation.isDynamic) return type;
|
| - if (annotation.isMalformed) return type;
|
| - if (annotation.isVoid) return nullType;
|
| - if (annotation.element == compiler.objectClass) return type;
|
| - TypeMask otherType;
|
| - if (annotation.kind == TypeKind.TYPEDEF
|
| - || annotation.kind == TypeKind.FUNCTION) {
|
| - otherType = functionType;
|
| - } else if (annotation.kind == TypeKind.TYPE_VARIABLE) {
|
| - return type;
|
| - } else {
|
| - assert(annotation.kind == TypeKind.INTERFACE);
|
| - otherType = new TypeMask.nonNullSubtype(annotation);
|
| - }
|
| - if (isNullable) otherType = otherType.nullable();
|
| - if (type == null) return otherType;
|
| - return type.intersection(otherType, compiler);
|
| - }
|
| + if (isNullable) otherType = otherType.nullable();
|
| + if (type == null) return otherType;
|
| + return type.intersection(otherType, compiler);
|
| }
|
|
|
| /**
|
| - * Placeholder for inferred arguments types on sends.
|
| + * Returns the least upper bound between [firstType] and
|
| + * [secondType].
|
| */
|
| -class ArgumentsTypes {
|
| - final List<TypeMask> positional;
|
| - final Map<SourceString, TypeMask> named;
|
| - ArgumentsTypes(this.positional, named)
|
| - : this.named = (named == null) ? new Map<SourceString, TypeMask>() : named;
|
| -
|
| - int get length => positional.length + named.length;
|
| -
|
| - String toString() => "{ positional = $positional, named = $named }";
|
| -
|
| - bool operator==(other) {
|
| - if (positional.length != other.positional.length) return false;
|
| - if (named.length != other.named.length) return false;
|
| - for (int i = 0; i < positional.length; i++) {
|
| - if (positional[i] != other.positional[i]) return false;
|
| - }
|
| - named.forEach((name, type) {
|
| - if (other.named[name] != type) return false;
|
| - });
|
| - return true;
|
| + TypeMask computeLUB(TypeMask firstType,
|
| + TypeMask secondType,
|
| + Compiler compiler) {
|
| + TypesInferrer inferrer = compiler.typesTask.typesInferrer;
|
| + TypeMask dynamicType = inferrer.dynamicType;
|
| + if (firstType == null) {
|
| + return secondType;
|
| + } else if (secondType == dynamicType) {
|
| + return secondType;
|
| + } else if (firstType == dynamicType) {
|
| + return firstType;
|
| + } else {
|
| + TypeMask union = firstType.union(secondType, compiler);
|
| + // TODO(kasperl): If the union isn't nullable it seems wasteful
|
| + // to use dynamic. Fix that.
|
| + return union.containsAll(compiler) ? dynamicType : union;
|
| }
|
| -
|
| - bool hasNoArguments() => positional.isEmpty && named.isEmpty;
|
| -
|
| - bool hasOnePositionalArgumentWithType(TypeMask type) {
|
| - return named.isEmpty && positional.length == 1 && positional[0] == type;
|
| - }
|
| }
|
|
|
| /**
|
| @@ -1372,16 +85,16 @@
|
|
|
| TypeMask use(Element local) {
|
| if (capturedAndBoxed.containsKey(local)) {
|
| - return inferrer.typeOfElement(capturedAndBoxed[local]);
|
| + return inferrer.getTypeOfElement(capturedAndBoxed[local]);
|
| }
|
| return locals[local];
|
| }
|
|
|
| void update(Element local, TypeMask type) {
|
| assert(type != null);
|
| - if (inferrer.compiler.trustTypeAnnotations
|
| - || inferrer.compiler.enableTypeAssertions) {
|
| - type = inferrer.narrowType(type, local.computeType(inferrer.compiler));
|
| + Compiler compiler = inferrer.compiler;
|
| + if (compiler.trustTypeAnnotations || compiler.enableTypeAssertions) {
|
| + type = narrowType(type, local.computeType(compiler), compiler);
|
| }
|
| if (capturedAndBoxed.containsKey(local) || inTryBlock) {
|
| // If a local is captured and boxed, or is set in a try block,
|
| @@ -1390,7 +103,7 @@
|
| // We don't know if an assignment in a try block
|
| // will be executed, so all assigments in that block are
|
| // potential types after we have left it.
|
| - type = inferrer.computeLUB(locals[local], type);
|
| + type = computeLUB(locals[local], type, compiler);
|
| }
|
| locals[local] = type;
|
| }
|
| @@ -1426,7 +139,7 @@
|
| } else if (!isCaptured && other.aborts && discardIfAborts) {
|
| // Don't do anything.
|
| } else {
|
| - TypeMask type = inferrer.computeLUB(oldType, otherType);
|
| + TypeMask type = computeLUB(oldType, otherType, inferrer.compiler);
|
| if (type != oldType) changed = true;
|
| locals[local] = type;
|
| }
|
| @@ -1462,7 +175,7 @@
|
| toRemove.add(element);
|
| } else {
|
| fieldsInitializedInConstructor[element] =
|
| - inferrer.computeLUB(type, otherType);
|
| + computeLUB(type, otherType, inferrer.compiler);
|
| }
|
| });
|
| // Remove fields that were not initialized in [other].
|
| @@ -1482,9 +195,8 @@
|
| }
|
| }
|
|
|
| -class SimpleTypeInferrerVisitor extends ResolvedVisitor<TypeMask> {
|
| +abstract class InferrerVisitor extends ResolvedVisitor<TypeMask> {
|
| final Element analyzedElement;
|
| - final Element outermostElement;
|
| final InternalSimpleTypesInferrer inferrer;
|
| final Compiler compiler;
|
| final Map<TargetElement, List<LocalsHandler>> breaksFor =
|
| @@ -1492,196 +204,40 @@
|
| final Map<TargetElement, List<LocalsHandler>> continuesFor =
|
| new Map<TargetElement, List<LocalsHandler>>();
|
| LocalsHandler locals;
|
| - TypeMask returnType;
|
|
|
| - bool visitingInitializers = false;
|
| - bool isConstructorRedirect = false;
|
| bool accumulateIsChecks = false;
|
| bool conditionIsSimple = false;
|
| -
|
| List<Send> isChecks;
|
| int loopLevel = 0;
|
| - SideEffects sideEffects = new SideEffects.empty();
|
|
|
| bool get inLoop => loopLevel > 0;
|
| bool get isThisExposed => locals.isThisExposed;
|
| void set isThisExposed(value) { locals.isThisExposed = value; }
|
|
|
| - SimpleTypeInferrerVisitor.internal(TreeElements mapping,
|
| - this.analyzedElement,
|
| - this.outermostElement,
|
| - this.inferrer,
|
| - this.compiler,
|
| - this.locals)
|
| - : super(mapping);
|
| + InferrerVisitor(Element analyzedElement,
|
| + this.inferrer,
|
| + Compiler compiler,
|
| + this.locals)
|
| + : this.compiler = compiler,
|
| + this.analyzedElement = analyzedElement,
|
| + super(compiler.enqueuer.resolution.getCachedElements(analyzedElement));
|
|
|
| - factory SimpleTypeInferrerVisitor(Element element,
|
| - Compiler compiler,
|
| - InternalSimpleTypesInferrer 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);
|
| - }
|
| + TypeMask visitSendSet(SendSet node);
|
|
|
| - TypeMask run() {
|
| - var node = analyzedElement.parseNode(compiler);
|
| - if (analyzedElement.isField() && node.asSendSet() == null) {
|
| - // Eagerly bailout, because computing the closure data only
|
| - // works for functions and field assignments.
|
| - return inferrer.nullType;
|
| - }
|
| - // Update the locals that are boxed in [locals]. These locals will
|
| - // be handled specially, in that we are computing their LUB at
|
| - // each update, and reading them yields the type that was found in a
|
| - // previous analysis of [outermostElement].
|
| - ClosureClassMap closureData =
|
| - compiler.closureToClassMapper.computeClosureToClassMapping(
|
| - analyzedElement, node, elements);
|
| - ClosureScope scopeData = closureData.capturingScopes[node];
|
| - if (scopeData != null) {
|
| - scopeData.capturedVariableMapping.forEach((variable, field) {
|
| - locals.setCapturedAndBoxed(variable, field);
|
| - });
|
| - }
|
| - if (analyzedElement.isField()) {
|
| - return visit(node.asSendSet().arguments.head);
|
| - }
|
| + TypeMask visitSuperSend(Send node);
|
|
|
| - FunctionElement function = analyzedElement;
|
| - if (inferrer.hasAnalyzedAll) {
|
| - inferrer.updateArgumentsType(function);
|
| - }
|
| - FunctionSignature signature = function.computeSignature(compiler);
|
| - signature.forEachOptionalParameter((element) {
|
| - Node node = element.parseNode(compiler);
|
| - Send send = node.asSendSet();
|
| - inferrer.defaultTypeOfParameter[element] = (send == null)
|
| - ? inferrer.nullType
|
| - : visit(send.arguments.head);
|
| - assert(inferrer.defaultTypeOfParameter[element] != null);
|
| - });
|
| + TypeMask visitStaticSend(Send node);
|
|
|
| - if (analyzedElement.isNative()) {
|
| - // Native methods do not have a body, and we currently just say
|
| - // they return dynamic.
|
| - return inferrer.dynamicType;
|
| - }
|
| + TypeMask visitGetterSend(Send node);
|
|
|
| - if (analyzedElement.isGenerativeConstructor()) {
|
| - isThisExposed = false;
|
| - signature.forEachParameter((element) {
|
| - TypeMask parameterType = inferrer.typeOfElement(element);
|
| - if (element.kind == ElementKind.FIELD_PARAMETER) {
|
| - if (element.fieldElement.modifiers.isFinal()) {
|
| - inferrer.recordFinalFieldType(
|
| - node,
|
| - analyzedElement,
|
| - element.fieldElement,
|
| - parameterType,
|
| - null);
|
| - } else {
|
| - locals.updateField(element.fieldElement, parameterType);
|
| - inferrer.recordNonFinalFieldElementType(
|
| - element.parseNode(compiler),
|
| - element.fieldElement,
|
| - parameterType,
|
| - null);
|
| - }
|
| - } else {
|
| - locals.update(element, parameterType);
|
| - }
|
| - });
|
| - visitingInitializers = true;
|
| - visit(node.initializers);
|
| - visitingInitializers = false;
|
| - visit(node.body);
|
| - ClassElement cls = analyzedElement.getEnclosingClass();
|
| - if (!isConstructorRedirect) {
|
| - // Iterate over all instance fields, and give a null type to
|
| - // fields that we haven't initialized for sure.
|
| - cls.forEachInstanceField((_, field) {
|
| - if (field.modifiers.isFinal()) return;
|
| - TypeMask type = locals.fieldsInitializedInConstructor[field];
|
| - if (type == null && field.parseNode(compiler).asSendSet() == null) {
|
| - inferrer.recordNonFinalFieldElementType(
|
| - node, field, inferrer.nullType, null);
|
| - }
|
| - });
|
| - }
|
| - inferrer.doneAnalyzingGenerativeConstructor(analyzedElement);
|
| - returnType = new TypeMask.nonNullExact(inferrer.rawTypeOf(cls));
|
| - } else {
|
| - signature.forEachParameter((element) {
|
| - locals.update(element, inferrer.typeOfElement(element));
|
| - });
|
| - visit(node.body);
|
| - if (returnType == null) {
|
| - // No return in the body.
|
| - returnType = inferrer.nullType;
|
| - } else if (!locals.seenReturn && !inferrer.isDynamicType(returnType)) {
|
| - // We haven't seen returns on all branches. So the method may
|
| - // also return null.
|
| - returnType = returnType.nullable();
|
| - }
|
| + TypeMask visitClosureSend(Send node);
|
|
|
| - if (analyzedElement.name == const SourceString('==')) {
|
| - // TODO(ngeoffray): Should this be done at the call site?
|
| - // When the argument passed in is null, we know we return a
|
| - // bool.
|
| - signature.forEachParameter((Element parameter) {
|
| - if (inferrer.typeOfElement(parameter).isNullable){
|
| - returnType = inferrer.computeLUB(returnType, inferrer.boolType);
|
| - }
|
| - });
|
| - }
|
| - }
|
| + TypeMask visitDynamicSend(Send node);
|
|
|
| - if (analyzedElement == outermostElement) {
|
| - bool changed = false;
|
| - locals.capturedAndBoxed.forEach((Element local, Element field) {
|
| - if (inferrer.recordType(field, locals.locals[local])) {
|
| - changed = true;
|
| - }
|
| - });
|
| - // TODO(ngeoffray): Re-analyze method if [changed]?
|
| - }
|
| - compiler.world.registerSideEffects(analyzedElement, sideEffects);
|
| - assert(breaksFor.isEmpty);
|
| - assert(continuesFor.isEmpty);
|
| - return returnType;
|
| - }
|
| + TypeMask visitForIn(ForIn node);
|
|
|
| - TypeMask _thisType;
|
| - TypeMask get thisType {
|
| - if (_thisType != null) return _thisType;
|
| - ClassElement cls = outermostElement.getEnclosingClass();
|
| - if (compiler.world.isUsedAsMixin(cls)) {
|
| - return _thisType = new TypeMask.nonNullSubtype(inferrer.rawTypeOf(cls));
|
| - } else if (compiler.world.hasAnySubclass(cls)) {
|
| - return _thisType = new TypeMask.nonNullSubclass(inferrer.rawTypeOf(cls));
|
| - } else {
|
| - return _thisType = new TypeMask.nonNullExact(inferrer.rawTypeOf(cls));
|
| - }
|
| - }
|
| + TypeMask visitReturn(Return node);
|
|
|
| - TypeMask _superType;
|
| - TypeMask get superType {
|
| - if (_superType != null) return _superType;
|
| - return _superType = new TypeMask.nonNullExact(
|
| - inferrer.rawTypeOf(outermostElement.getEnclosingClass().superclass));
|
| - }
|
| -
|
| - void recordReturnType(TypeMask type) {
|
| - returnType = inferrer.computeLUB(returnType, type);
|
| - }
|
| -
|
| TypeMask visitNode(Node node) {
|
| node.visitChildren(this);
|
| return inferrer.dynamicType;
|
| @@ -1696,32 +252,6 @@
|
| }
|
|
|
| TypeMask visitFunctionExpression(FunctionExpression node) {
|
| - Element element = elements[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.
|
| - LocalsHandler closureLocals = new LocalsHandler.from(locals);
|
| - SimpleTypeInferrerVisitor visitor = new SimpleTypeInferrerVisitor(
|
| - element, compiler, inferrer, closureLocals);
|
| - visitor.run();
|
| - inferrer.recordReturnType(element, visitor.returnType);
|
| - locals.merge(visitor.locals);
|
| -
|
| - // Record the types of captured non-boxed variables. Types of
|
| - // these variables may already be there, because of an analysis of
|
| - // a previous closure. Note that analyzing the same closure multiple
|
| - // times closure will refine the type of those variables, therefore
|
| - // [:inferrer.typeOf[variable]:] is not necessarilly null, nor the
|
| - // same as [newType].
|
| - ClosureClassMap nestedClosureData =
|
| - compiler.closureToClassMapper.getMappingForNestedFunction(node);
|
| - nestedClosureData.forEachNonBoxedCapturedVariable((variable, field) {
|
| - // The type may be null for instance contexts (this and type
|
| - // parameters), as well as captured argument checks.
|
| - if (locals.locals[variable] == null) return;
|
| - inferrer.recordType(field, locals.locals[variable]);
|
| - });
|
| -
|
| return inferrer.functionType;
|
| }
|
|
|
| @@ -1780,219 +310,29 @@
|
|
|
| bool isThisOrSuper(Node node) => node.isThis() || node.isSuper();
|
|
|
| - void checkIfExposesThis(Selector selector) {
|
| - if (isThisExposed) return;
|
| - inferrer.iterateOverElements(selector, (element) {
|
| - if (element.isField()) {
|
| - if (!selector.isSetter()
|
| - && element.getEnclosingClass() ==
|
| - outermostElement.getEnclosingClass()
|
| - && !element.modifiers.isFinal()
|
| - && locals.fieldsInitializedInConstructor[element] == null
|
| - && element.parseNode(compiler).asSendSet() == null) {
|
| - // If the field is being used before this constructor
|
| - // actually had a chance to initialize it, say it can be
|
| - // null.
|
| - inferrer.recordNonFinalFieldElementType(
|
| - analyzedElement.parseNode(compiler), element,
|
| - inferrer.nullType, null);
|
| - }
|
| - // Accessing a field does not expose [:this:].
|
| - return true;
|
| - }
|
| - // TODO(ngeoffray): We could do better here if we knew what we
|
| - // are calling does not expose this.
|
| - isThisExposed = true;
|
| - return false;
|
| - });
|
| + Element get outermostElement {
|
| + return
|
| + analyzedElement.getOutermostEnclosingMemberOrTopLevel().implementation;
|
| }
|
|
|
| - TypeMask visitSendSet(SendSet node) {
|
| - Element element = elements[node];
|
| - if (!Elements.isUnresolved(element) && element.impliesType()) {
|
| - node.visitChildren(this);
|
| - return inferrer.dynamicType;
|
| - }
|
| -
|
| - Selector getterSelector =
|
| - elements.getGetterSelectorInComplexSendSet(node);
|
| - Selector operatorSelector =
|
| - elements.getOperatorSelectorInComplexSendSet(node);
|
| - Selector setterSelector = elements.getSelector(node);
|
| -
|
| - String op = node.assignmentOperator.source.stringValue;
|
| - bool isIncrementOrDecrement = op == '++' || op == '--';
|
| -
|
| - TypeMask receiverType;
|
| - bool isCallOnThis = false;
|
| - if (node.receiver == null
|
| - && element != null
|
| - && element.isInstanceMember()) {
|
| - receiverType = thisType;
|
| - isCallOnThis = true;
|
| + TypeMask _thisType;
|
| + TypeMask get thisType {
|
| + if (_thisType != null) return _thisType;
|
| + ClassElement cls = outermostElement.getEnclosingClass();
|
| + if (compiler.world.isUsedAsMixin(cls)) {
|
| + return _thisType = new TypeMask.nonNullSubtype(inferrer.rawTypeOf(cls));
|
| + } else if (compiler.world.hasAnySubclass(cls)) {
|
| + return _thisType = new TypeMask.nonNullSubclass(inferrer.rawTypeOf(cls));
|
| } else {
|
| - receiverType = visit(node.receiver);
|
| - isCallOnThis = node.receiver != null && isThisOrSuper(node.receiver);
|
| + return _thisType = new TypeMask.nonNullExact(inferrer.rawTypeOf(cls));
|
| }
|
| -
|
| - TypeMask rhsType;
|
| - TypeMask indexType;
|
| -
|
| - if (isIncrementOrDecrement) {
|
| - rhsType = inferrer.intType;
|
| - if (node.isIndex) indexType = visit(node.arguments.head);
|
| - } else if (node.isIndex) {
|
| - indexType = visit(node.arguments.head);
|
| - rhsType = visit(node.arguments.tail.head);
|
| - } else {
|
| - rhsType = visit(node.arguments.head);
|
| - }
|
| -
|
| - if (!visitingInitializers && !isThisExposed) {
|
| - for (Node node in node.arguments) {
|
| - if (isThisOrSuper(node)) {
|
| - isThisExposed = true;
|
| - break;
|
| - }
|
| - }
|
| - if (!isThisExposed && isCallOnThis) {
|
| - checkIfExposesThis(new TypedSelector(receiverType, setterSelector));
|
| - }
|
| - }
|
| -
|
| - if (node.isIndex) {
|
| - if (op == '=') {
|
| - // [: foo[0] = 42 :]
|
| - handleDynamicSend(
|
| - node,
|
| - setterSelector,
|
| - receiverType,
|
| - new ArgumentsTypes([indexType, rhsType], null));
|
| - return rhsType;
|
| - } else {
|
| - // [: foo[0] += 42 :] or [: foo[0]++ :].
|
| - TypeMask getterType = handleDynamicSend(
|
| - node,
|
| - getterSelector,
|
| - receiverType,
|
| - new ArgumentsTypes([indexType], null));
|
| - TypeMask returnType = handleDynamicSend(
|
| - node,
|
| - operatorSelector,
|
| - getterType,
|
| - new ArgumentsTypes([rhsType], null));
|
| - handleDynamicSend(
|
| - node,
|
| - setterSelector,
|
| - receiverType,
|
| - new ArgumentsTypes([indexType, returnType], null));
|
| -
|
| - if (node.isPostfix) {
|
| - return getterType;
|
| - } else {
|
| - return returnType;
|
| - }
|
| - }
|
| - } else if (op == '=') {
|
| - return handlePlainAssignment(
|
| - node, element, setterSelector, receiverType, rhsType,
|
| - node.arguments.head);
|
| - } else {
|
| - // [: foo++ :] or [: foo += 1 :].
|
| - Selector constraint;
|
| - if (!Elements.isLocal(element)) {
|
| - // Record a constraint of the form [: field++ :], or [: field += 42 :].
|
| - constraint = operatorSelector;
|
| - }
|
| - TypeMask getterType;
|
| - TypeMask newType;
|
| - ArgumentsTypes operatorArguments = new ArgumentsTypes([rhsType], null);
|
| - if (Elements.isStaticOrTopLevelField(element)) {
|
| - Element getterElement = elements[node.selector];
|
| - getterType =
|
| - inferrer.typeOfElementWithSelector(getterElement, getterSelector);
|
| - handleStaticSend(node, getterSelector, getterElement, null);
|
| - newType = handleDynamicSend(
|
| - node, operatorSelector, getterType, operatorArguments);
|
| - handleStaticSend(
|
| - node, setterSelector, element,
|
| - new ArgumentsTypes([newType], null));
|
| - } else if (Elements.isUnresolved(element)
|
| - || element.isSetter()
|
| - || element.isField()) {
|
| - getterType = handleDynamicSend(
|
| - node, getterSelector, receiverType, null);
|
| - newType = handleDynamicSend(
|
| - node, operatorSelector, getterType, operatorArguments);
|
| - handleDynamicSend(node, setterSelector, receiverType,
|
| - new ArgumentsTypes([newType], null),
|
| - constraint);
|
| - } else if (Elements.isLocal(element)) {
|
| - getterType = locals.use(element);
|
| - newType = handleDynamicSend(
|
| - node, operatorSelector, getterType, operatorArguments);
|
| - locals.update(element, newType);
|
| - } else {
|
| - // Bogus SendSet, for example [: myMethod += 42 :].
|
| - getterType = inferrer.dynamicType;
|
| - newType = handleDynamicSend(
|
| - node, operatorSelector, getterType, operatorArguments);
|
| - }
|
| -
|
| - if (node.isPostfix) {
|
| - return getterType;
|
| - } else {
|
| - return newType;
|
| - }
|
| - }
|
| }
|
|
|
| - TypeMask handlePlainAssignment(Node node,
|
| - Element element,
|
| - Selector setterSelector,
|
| - TypeMask receiverType,
|
| - TypeMask rhsType,
|
| - Node rhs) {
|
| - Selector constraint;
|
| - if (node.asSend() != null && !Elements.isLocal(element)) {
|
| - // Recognize a constraint of the form [: field = other.field :].
|
| - // Note that we check if the right hand side is a local to
|
| - // recognize the situation [: var a = 42; this.a = a; :]. Our
|
| - // constraint mechanism only works with members or top level
|
| - // elements.
|
| - Send send = rhs.asSend();
|
| - if (send != null
|
| - && send.isPropertyAccess
|
| - && !Elements.isLocal(elements[rhs])
|
| - && send.selector.asIdentifier().source
|
| - == node.asSend().selector.asIdentifier().source) {
|
| - constraint = elements.getSelector(rhs);
|
| - }
|
| - }
|
| - ArgumentsTypes arguments = new ArgumentsTypes([rhsType], null);
|
| - if (Elements.isStaticOrTopLevelField(element)) {
|
| - handleStaticSend(node, setterSelector, element, arguments);
|
| - } else if (Elements.isUnresolved(element) || element.isSetter()) {
|
| - handleDynamicSend(
|
| - node, setterSelector, receiverType, arguments, constraint);
|
| - } else if (element.isField()) {
|
| - if (element.modifiers.isFinal()) {
|
| - inferrer.recordFinalFieldType(
|
| - node, outermostElement, element, rhsType, constraint);
|
| - } else {
|
| - locals.updateField(element, rhsType);
|
| - if (visitingInitializers) {
|
| - inferrer.recordNonFinalFieldElementType(
|
| - node, element, rhsType, constraint);
|
| - } else {
|
| - handleDynamicSend(
|
| - node, setterSelector, receiverType, arguments, constraint);
|
| - }
|
| - }
|
| - } else if (Elements.isLocal(element)) {
|
| - locals.update(element, rhsType);
|
| - }
|
| - return rhsType;
|
| + TypeMask _superType;
|
| + TypeMask get superType {
|
| + if (_superType != null) return _superType;
|
| + return _superType = new TypeMask.nonNullExact(
|
| + inferrer.rawTypeOf(outermostElement.getEnclosingClass().superclass));
|
| }
|
|
|
| TypeMask visitIdentifier(Identifier node) {
|
| @@ -2004,94 +344,6 @@
|
| return inferrer.dynamicType;
|
| }
|
|
|
| - TypeMask visitSuperSend(Send node) {
|
| - Element element = elements[node];
|
| - if (Elements.isUnresolved(element)) {
|
| - return inferrer.dynamicType;
|
| - }
|
| - Selector selector = elements.getSelector(node);
|
| - // TODO(ngeoffray): We could do better here if we knew what we
|
| - // are calling does not expose this.
|
| - isThisExposed = true;
|
| - if (node.isPropertyAccess) {
|
| - handleStaticSend(node, selector, element, null);
|
| - return inferrer.typeOfElementWithSelector(element, selector);
|
| - } else if (element.isFunction()) {
|
| - if (!selector.applies(element, compiler)) return inferrer.dynamicType;
|
| - ArgumentsTypes arguments = analyzeArguments(node.arguments);
|
| - handleStaticSend(node, selector, element, arguments);
|
| - return inferrer.returnTypeOfElement(element);
|
| - } else {
|
| - analyzeArguments(node.arguments);
|
| - // Closure call on a getter. We don't have function types yet,
|
| - // so we just return [:dynamic:].
|
| - return inferrer.dynamicType;
|
| - }
|
| - }
|
| -
|
| - TypeMask visitStaticSend(Send node) {
|
| - if (visitingInitializers && Initializers.isConstructorRedirect(node)) {
|
| - isConstructorRedirect = true;
|
| - }
|
| - Element element = elements[node];
|
| - if (element.isForeign(compiler)) {
|
| - return handleForeignSend(node);
|
| - }
|
| - Selector selector = elements.getSelector(node);
|
| - ArgumentsTypes arguments = analyzeArguments(node.arguments);
|
| - if (!selector.applies(element, compiler)) return inferrer.dynamicType;
|
| -
|
| - handleStaticSend(node, selector, element, arguments);
|
| - if (Elements.isGrowableListConstructorCall(element, node, compiler)) {
|
| - return inferrer.growableListType;
|
| - } else if (Elements.isFixedListConstructorCall(element, node, compiler)) {
|
| - return inferrer.fixedListType;
|
| - } else if (element.isFunction() || element.isConstructor()) {
|
| - return inferrer.returnTypeOfElement(element);
|
| - } else {
|
| - assert(element.isField() || element.isGetter());
|
| - // Closure call.
|
| - return inferrer.dynamicType;
|
| - }
|
| - }
|
| -
|
| - TypeMask 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);
|
| - sideEffects.add(nativeBehavior.sideEffects);
|
| - return inferrer.typeOfNativeBehavior(nativeBehavior);
|
| - } else if (name == const SourceString('JS_OPERATOR_IS_PREFIX')
|
| - || name == const SourceString('JS_OPERATOR_AS_PREFIX')
|
| - || name == const SourceString('JS_OBJECT_CLASS_NAME')) {
|
| - return inferrer.stringType;
|
| - } else {
|
| - sideEffects.setAllSideEffects();
|
| - return inferrer.dynamicType;
|
| - }
|
| - }
|
| -
|
| - ArgumentsTypes analyzeArguments(Link<Node> arguments) {
|
| - List<TypeMask> positional = [];
|
| - Map<SourceString, TypeMask> named = new Map<SourceString, TypeMask>();
|
| - for (var argument in arguments) {
|
| - NamedArgument namedArgument = argument.asNamedArgument();
|
| - if (namedArgument != null) {
|
| - argument = namedArgument.expression;
|
| - named[namedArgument.name.source] = argument.accept(this);
|
| - } else {
|
| - positional.add(argument.accept(this));
|
| - }
|
| - // TODO(ngeoffray): We could do better here if we knew what we
|
| - // are calling does not expose this.
|
| - isThisExposed = isThisExposed || argument.isThis();
|
| - }
|
| - return new ArgumentsTypes(positional, named);
|
| - }
|
| -
|
| void potentiallyAddIsCheck(Send node) {
|
| if (!accumulateIsChecks) return;
|
| if (!Elements.isLocal(elements[node.receiver])) return;
|
| @@ -2109,7 +361,8 @@
|
| DartType type = elements.getType(node.typeAnnotationFromIsCheck);
|
| Element element = elements[node.receiver];
|
| TypeMask existing = locals.use(element);
|
| - TypeMask newType = inferrer.narrowType(existing, type, isNullable: false);
|
| + TypeMask newType = narrowType(
|
| + existing, type, inferrer.compiler, isNullable: false);
|
| locals.update(element, newType);
|
| }
|
| }
|
| @@ -2155,7 +408,7 @@
|
| } else if (const SourceString("as") == op.source) {
|
| TypeMask receiverType = visit(node.receiver);
|
| DartType type = elements.getType(node.arguments.head);
|
| - return inferrer.narrowType(receiverType, type);
|
| + return narrowType(receiverType, type, inferrer.compiler);
|
| } else if (node.isParameterCheck) {
|
| node.visitChildren(this);
|
| return inferrer.boolType;
|
| @@ -2178,160 +431,6 @@
|
| // to avoid confusing the [ResolvedVisitor].
|
| visitTypeAnnotation(TypeAnnotation node) {}
|
|
|
| - TypeMask visitGetterSend(Send node) {
|
| - Element element = elements[node];
|
| - Selector selector = elements.getSelector(node);
|
| - if (Elements.isStaticOrTopLevelField(element)) {
|
| - handleStaticSend(node, selector, element, null);
|
| - return inferrer.typeOfElementWithSelector(element, selector);
|
| - } else if (Elements.isInstanceSend(node, elements)) {
|
| - return visitDynamicSend(node);
|
| - } else if (Elements.isStaticOrTopLevelFunction(element)) {
|
| - handleStaticSend(node, selector, element, null);
|
| - return inferrer.functionType;
|
| - } else if (Elements.isErroneousElement(element)) {
|
| - return inferrer.dynamicType;
|
| - } else if (Elements.isLocal(element)) {
|
| - assert(locals.use(element) != null);
|
| - return locals.use(element);
|
| - } else {
|
| - node.visitChildren(this);
|
| - return inferrer.dynamicType;
|
| - }
|
| - }
|
| -
|
| - TypeMask visitClosureSend(Send node) {
|
| - node.visitChildren(this);
|
| - Element element = elements[node];
|
| - Selector selector = elements.getSelector(node);
|
| - if (element != null && element.isFunction()) {
|
| - assert(Elements.isLocal(element));
|
| - // This only works for function statements. We need a
|
| - // more sophisticated type system with function types to support
|
| - // more.
|
| - inferrer.updateSideEffects(sideEffects, selector, element);
|
| - return inferrer.returnTypeOfElement(element);
|
| - }
|
| - sideEffects.setDependsOnSomething();
|
| - sideEffects.setAllSideEffects();
|
| - return inferrer.dynamicType;
|
| - }
|
| -
|
| - void handleStaticSend(Node node,
|
| - Selector selector,
|
| - Element element,
|
| - ArgumentsTypes arguments) {
|
| - if (Elements.isUnresolved(element)) return;
|
| - inferrer.registerCalledElement(
|
| - node, selector, outermostElement, element, arguments, null,
|
| - sideEffects, inLoop);
|
| - }
|
| -
|
| - void updateSelectorInTree(Node node, Selector selector) {
|
| - if (node.asSendSet() != null) {
|
| - if (selector.isSetter() || selector.isIndexSet()) {
|
| - elements.setSelector(node, selector);
|
| - } else if (selector.isGetter() || selector.isIndex()) {
|
| - elements.setGetterSelectorInComplexSendSet(node, selector);
|
| - } else {
|
| - assert(selector.isOperator());
|
| - elements.setOperatorSelectorInComplexSendSet(node, selector);
|
| - }
|
| - } else if (node.asSend() != null) {
|
| - elements.setSelector(node, selector);
|
| - } else {
|
| - assert(node.asForIn() != null);
|
| - if (selector.asUntyped == compiler.iteratorSelector) {
|
| - elements.setIteratorSelector(node, selector);
|
| - } else if (selector.asUntyped == compiler.currentSelector) {
|
| - elements.setCurrentSelector(node, selector);
|
| - } else {
|
| - assert(selector.asUntyped == compiler.moveNextSelector);
|
| - elements.setMoveNextSelector(node, selector);
|
| - }
|
| - }
|
| - }
|
| -
|
| - TypeMask handleDynamicSend(Node node,
|
| - Selector selector,
|
| - TypeMask receiver,
|
| - ArgumentsTypes arguments,
|
| - [Selector constraint]) {
|
| - if (selector.mask != receiver) {
|
| - selector = inferrer.isDynamicType(receiver)
|
| - ? selector.asUntyped
|
| - : new TypedSelector(receiver, selector);
|
| - updateSelectorInTree(node, selector);
|
| - }
|
| - return inferrer.registerCalledSelector(
|
| - node, selector, receiver, outermostElement, arguments,
|
| - constraint, sideEffects, inLoop);
|
| - }
|
| -
|
| - TypeMask visitDynamicSend(Send node) {
|
| - Element element = elements[node];
|
| - TypeMask receiverType;
|
| - bool isCallOnThis = false;
|
| - if (node.receiver == null) {
|
| - isCallOnThis = true;
|
| - receiverType = thisType;
|
| - } else {
|
| - Node receiver = node.receiver;
|
| - isCallOnThis = isThisOrSuper(receiver);
|
| - receiverType = visit(receiver);
|
| - }
|
| -
|
| - Selector selector = elements.getSelector(node);
|
| - if (!isThisExposed && isCallOnThis) {
|
| - checkIfExposesThis(new TypedSelector(receiverType, selector));
|
| - }
|
| -
|
| - ArgumentsTypes arguments = node.isPropertyAccess
|
| - ? null
|
| - : analyzeArguments(node.arguments);
|
| - return handleDynamicSend(node, selector, receiverType, arguments);
|
| - }
|
| -
|
| - TypeMask visitReturn(Return node) {
|
| - if (node.isRedirectingFactoryBody) {
|
| - Element element = elements[node.expression];
|
| - if (Elements.isErroneousElement(element)) {
|
| - recordReturnType(inferrer.dynamicType);
|
| - } else {
|
| - element = element.implementation;
|
| - // We don't create a selector for redirecting factories, and
|
| - // the send is just a property access. Therefore we must
|
| - // manually create the [ArgumentsTypes] of the call, and
|
| - // manually register [analyzedElement] as a caller of [element].
|
| - FunctionElement function = analyzedElement;
|
| - FunctionSignature signature = function.computeSignature(compiler);
|
| - List<TypeMask> unnamed = <TypeMask>[];
|
| - Map<SourceString, TypeMask> named = new Map<SourceString, TypeMask>();
|
| - signature.forEachRequiredParameter((Element element) {
|
| - unnamed.add(locals.use(element));
|
| - });
|
| - signature.forEachOptionalParameter((Element element) {
|
| - if (signature.optionalParametersAreNamed) {
|
| - named[element.name] = locals.use(element);
|
| - } else {
|
| - unnamed.add(locals.use(element));
|
| - }
|
| - });
|
| - ArgumentsTypes arguments = new ArgumentsTypes(unnamed, named);
|
| - inferrer.addCaller(analyzedElement, element);
|
| - inferrer.addArguments(node.expression, element, arguments);
|
| - recordReturnType(inferrer.returnTypeOfElement(element));
|
| - }
|
| - } else {
|
| - Node expression = node.expression;
|
| - recordReturnType(expression == null
|
| - ? inferrer.nullType
|
| - : expression.accept(this));
|
| - }
|
| - locals.seenReturn = true;
|
| - return inferrer.dynamicType;
|
| - }
|
| -
|
| TypeMask visitConditional(Conditional node) {
|
| List<Send> tests = <Send>[];
|
| bool simpleCondition = handleCondition(node.condition, tests);
|
| @@ -2343,7 +442,7 @@
|
| if (simpleCondition) updateIsChecks(tests, usePositive: false);
|
| TypeMask secondType = visit(node.elseExpression);
|
| locals.merge(thenLocals);
|
| - TypeMask type = inferrer.computeLUB(firstType, secondType);
|
| + TypeMask type = computeLUB(firstType, secondType, compiler);
|
| return type;
|
| }
|
|
|
| @@ -2467,49 +566,6 @@
|
| });
|
| }
|
|
|
| - TypeMask visitForIn(ForIn node) {
|
| - bool changed = false;
|
| - TypeMask expressionType = visit(node.expression);
|
| - Selector iteratorSelector = elements.getIteratorSelector(node);
|
| - Selector currentSelector = elements.getCurrentSelector(node);
|
| - Selector moveNextSelector = elements.getMoveNextSelector(node);
|
| -
|
| - TypeMask iteratorType =
|
| - handleDynamicSend(node, iteratorSelector, expressionType, null);
|
| - handleDynamicSend(node, moveNextSelector,
|
| - iteratorType, new ArgumentsTypes([], null));
|
| - TypeMask currentType =
|
| - handleDynamicSend(node, currentSelector, iteratorType, null);
|
| -
|
| - // We nullify the type in case there is no element in the
|
| - // iterable.
|
| - currentType = currentType.nullable();
|
| -
|
| - if (node.expression.isThis()) {
|
| - // Any reasonable implementation of an iterator would expose
|
| - // this, so we play it safe and assume it will.
|
| - isThisExposed = true;
|
| - }
|
| -
|
| - Node identifier = node.declaredIdentifier;
|
| - Element element = elements[identifier];
|
| - Selector selector = elements.getSelector(identifier);
|
| -
|
| - TypeMask receiverType;
|
| - if (element != null && element.isInstanceMember()) {
|
| - receiverType = thisType;
|
| - } else {
|
| - receiverType = inferrer.dynamicType;
|
| - }
|
| -
|
| - handlePlainAssignment(identifier, element, selector,
|
| - receiverType, currentType,
|
| - node.expression);
|
| - return handleLoop(node, () {
|
| - visit(node.body);
|
| - });
|
| - }
|
| -
|
| TypeMask visitTryStatement(TryStatement node) {
|
| LocalsHandler saved = locals;
|
| locals = new LocalsHandler.from(locals, inTryBlock: true);
|
|
|