Linter Demo Errors: 12Warnings: 113File: /home/fstrocco/Dart/dart/benchmark/compiler/lib/src/inferrer/concrete_types_inferrer.dart // 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 concrete_types_inferrer; import 'dart:collection' show Queue, IterableBase; import '../native/native.dart' as native; import '../closure.dart' show BoxFieldElement; import '../dart2jslib.dart' hide Selector, TypedSelector; import '../dart_types.dart' show DartType, TypeKind; import '../elements/elements.dart'; import '../tree/tree.dart'; import '../universe/universe.dart'; import '../util/util.dart'; import 'inferrer_visitor.dart'; import '../types/types.dart' show TypeMask, FlatTypeMask, UnionTypeMask, TypesInferrer; import 'simple_types_inferrer.dart'; /** * A singleton concrete type. More precisely, a [BaseType] is one of the * following: * * - a non-asbtract class like [:int:] or [:Uri:] (but not [:List:]) * - the null base type * - the unknown base type */ abstract class BaseType { bool isClass(); bool isUnknown(); bool isNull(); } /** * A non-asbtract class like [:int:] or [:Uri:] (but not [:List:]). */ class ClassBaseType implements BaseType { final ClassElement element; ClassBaseType(this.element); bool operator ==(var other) { if (identical(this, other)) return true; if (other is! ClassBaseType) return false; return element == other.element; } int get hashCode => element.hashCode; String toString() { return element == null ? 'toplevel' : element.name; } bool isClass() => true; bool isUnknown() => false; bool isNull() => false; } /** * The unknown base type. */ class UnknownBaseType implements BaseType { const UnknownBaseType(); bool operator ==(BaseType other) => other is UnknownBaseType; int get hashCode => 0; bool isClass() => false; bool isUnknown() => true; bool isNull() => false; toString() => "unknown"; } /** * The null base type. */ class NullBaseType implements BaseType { const NullBaseType(); bool operator ==(BaseType other) => identical(other, this); int get hashCode => 1; bool isClass() => false; bool isUnknown() => false; bool isNull() => true; toString() => "null"; } /** * An immutable set of base types like [:{int, bool}:], or the unknown concrete * type. */ abstract class ConcreteType { ConcreteType(); factory ConcreteType.empty(int maxConcreteTypeSize, BaseTypes classBaseTypes) { return new UnionType(maxConcreteTypeSize, classBaseTypes, new Set()); } /** * The singleton constituted of the unknown base type is the unknown concrete * type. */ factory ConcreteType.singleton(int maxConcreteTypeSize, BaseTypes classBaseTypes, BaseType baseType) { if (baseType.isUnknown() || maxConcreteTypeSize < 1) { return const UnknownConcreteType(); } Set singletonSet = new Set(); singletonSet.add(baseType); return new UnionType(maxConcreteTypeSize, classBaseTypes, singletonSet); } factory ConcreteType.unknown() { return const UnknownConcreteType(); } ConcreteType union(ConcreteType other); ConcreteType intersection(ConcreteType other); ConcreteType refine(Selector selector, Compiler compiler); bool isUnknown(); bool isEmpty(); Set get baseTypes; /** * Returns the unique element of [:this:] if [:this:] is a singleton, null * otherwise. */ ClassElement getUniqueType(); } /** * The unkown concrete type: it is absorbing for the union. */ class UnknownConcreteType implements ConcreteType { const UnknownConcreteType(); bool isUnknown() => true; bool isEmpty() => false; bool operator ==(ConcreteType other) => identical(this, other); Set get baseTypes => new Set.from([const UnknownBaseType()]); int get hashCode => 0; ConcreteType union(ConcreteType other) => this; ConcreteType intersection(ConcreteType other) => other; ConcreteType refine(Selector selector, Compiler compiler) => this; ClassElement getUniqueType() => null; toString() => "unknown"; } /** * An immutable set of base types, like [: {int, bool} :]. */ class UnionType implements ConcreteType { final int maxConcreteTypeSize; final BaseTypes classBaseTypes; final Set baseTypes; /** * The argument should NOT be mutated later. Do not call directly, use * ConcreteType.singleton instead. */ UnionType(this.maxConcreteTypeSize, this.classBaseTypes, this.baseTypes); bool isUnknown() => false; bool isEmpty() => baseTypes.isEmpty; bool operator ==(ConcreteType other) { if (other is! UnionType) return false; if (baseTypes.length != other.baseTypes.length) return false; return baseTypes.containsAll(other.baseTypes); } int get hashCode { int result = 1; for (final baseType in baseTypes) { result = 31 * result + baseType.hashCode; } return result; } ConcreteType _simplify(Set baseTypes) { // normalize all flavors of ints to int // TODO(polux): handle different ints better if (baseTypes.contains(classBaseTypes.uint31Type)) { baseTypes.remove(classBaseTypes.uint31Type); baseTypes.add(classBaseTypes.intBaseType); } if (baseTypes.contains(classBaseTypes.uint32Type)) { baseTypes.remove(classBaseTypes.uint32Type); baseTypes.add(classBaseTypes.intBaseType); } if (baseTypes.contains(classBaseTypes.positiveIntType)) { baseTypes.remove(classBaseTypes.positiveIntType); baseTypes.add(classBaseTypes.intBaseType); } // normalize {int, float}, {int, num} or {float, num} into num // TODO(polux): generalize this to all types when we extend the concept of // "concrete type" to other abstract classes than num if (baseTypes.contains(classBaseTypes.numBaseType) || (baseTypes.contains(classBaseTypes.intBaseType) && baseTypes.contains(classBaseTypes.doubleBaseType))) { baseTypes.remove(classBaseTypes.intBaseType); baseTypes.remove(classBaseTypes.doubleBaseType); baseTypes.add(classBaseTypes.numBaseType); } // widen big types to dynamic return baseTypes.length > maxConcreteTypeSize ? const UnknownConcreteType() : new UnionType(maxConcreteTypeSize, classBaseTypes, baseTypes); } ConcreteType union(ConcreteType other) { if (other.isUnknown()) { return const UnknownConcreteType(); } UnionType otherUnion = other; // cast Set newBaseTypes = new Set.from(baseTypes); newBaseTypes.addAll(otherUnion.baseTypes); return _simplify(newBaseTypes); } ConcreteType intersection(ConcreteType other) { if (other.isUnknown()) { return this; } Set thisBaseTypes = new Set.from(baseTypes); Set otherBaseTypes = new Set.from(other.baseTypes); return _simplify(thisBaseTypes.intersection(otherBaseTypes)); } ConcreteType refine(Selector selector, Compiler compiler) { Set newBaseTypes = new Set(); for (BaseType baseType in baseTypes) { if (baseType.isClass()) { ClassBaseType classBaseType = baseType; if (classBaseType.element.lookupSelector(selector) != null) { newBaseTypes.add(baseType); } } else { newBaseTypes.add(baseType); } } return _simplify(newBaseTypes); } ClassElement getUniqueType() { if (baseTypes.length == 1) { var iterator = baseTypes.iterator; iterator.moveNext(); BaseType uniqueBaseType = iterator.current; if (uniqueBaseType.isClass()) { ClassBaseType uniqueClassType = uniqueBaseType; return uniqueClassType.element; } } return null; } String toString() => baseTypes.toString(); } class ConcreteTypeSystem extends TypeSystem { final Compiler compiler; final ConcreteTypesInferrer inferrer; final BaseTypes baseTypes; final ConcreteType nullType; final ConcreteType _intType; final ConcreteType _uint31Type; final ConcreteType _uint32Type; final ConcreteType _positiveIntType; final ConcreteType _doubleType; final ConcreteType _numType; final ConcreteType _boolType; final ConcreteType _functionType; final ConcreteType _listType; final ConcreteType _constListType; final ConcreteType _fixedListType; final ConcreteType _growableListType; final ConcreteType _mapType; final ConcreteType _constMapType; final ConcreteType _stringType; final ConcreteType dynamicType; final ConcreteType typeType; final ConcreteType nonNullEmptyType; ConcreteTypeSystem.internal(ConcreteTypesInferrer inferrer, BaseTypes baseTypes, ConcreteType singleton(BaseType baseType)) : this.compiler = inferrer.compiler , this.inferrer = inferrer , this.baseTypes = baseTypes , this._constListType = singleton(baseTypes.constMapBaseType) , this._constMapType = singleton(baseTypes.constMapBaseType) , this._doubleType = singleton(baseTypes.doubleBaseType) , this._fixedListType = singleton(baseTypes.fixedListBaseType) , this._functionType = singleton(baseTypes.functionBaseType) , this._growableListType = singleton(baseTypes.growableListBaseType) , this._intType = singleton(baseTypes.intBaseType) , this._listType = singleton(baseTypes.listBaseType) , this._mapType = singleton(baseTypes.mapBaseType) , this._numType = singleton(baseTypes.numBaseType) , this._boolType = singleton(baseTypes.boolBaseType) , this._stringType = singleton(baseTypes.stringBaseType) , this.typeType = singleton(baseTypes.typeBaseType) , this.dynamicType = const UnknownConcreteType() , this.nullType = singleton(const NullBaseType()) , this.nonNullEmptyType = new ConcreteType.empty( inferrer.compiler.maxConcreteTypeSize, baseTypes) // TODO(polux): have better types here , this._uint31Type = singleton(baseTypes.intBaseType) , this._uint32Type = singleton(baseTypes.intBaseType) , this._positiveIntType = singleton(baseTypes.intBaseType); factory ConcreteTypeSystem(ConcreteTypesInferrer inferrer) { Compiler compiler = inferrer.compiler; BaseTypes baseTypes = new BaseTypes(compiler); return new ConcreteTypeSystem.internal( inferrer, baseTypes, (BaseType baseType) => new ConcreteType.singleton( compiler.maxConcreteTypeSize, baseTypes, baseType)); } @override ConcreteType get intType { inferrer.augmentSeenClasses(compiler.backend.intImplementation); return _intType; } @override ConcreteType get uint31Type { inferrer.augmentSeenClasses(compiler.backend.uint31Implementation); return _uint31Type; } @override ConcreteType get uint32Type { inferrer.augmentSeenClasses(compiler.backend.uint32Implementation); return _uint32Type; } @override ConcreteType get positiveIntType { inferrer.augmentSeenClasses(compiler.backend.positiveIntImplementation); return _positiveIntType; } @override ConcreteType get doubleType { inferrer.augmentSeenClasses(compiler.backend.doubleImplementation); return _doubleType; } @override ConcreteType get numType { inferrer.augmentSeenClasses(compiler.backend.numImplementation); return _numType; } @override ConcreteType get boolType { inferrer.augmentSeenClasses(compiler.backend.boolImplementation); return _boolType; } @override ConcreteType get functionType { inferrer.augmentSeenClasses(compiler.backend.functionImplementation); return _functionType; } @override ConcreteType get listType { inferrer.augmentSeenClasses(compiler.backend.listImplementation); return _listType; } @override ConcreteType get constListType { inferrer.augmentSeenClasses(compiler.backend.constListImplementation); return _constListType; } @override ConcreteType get fixedListType { inferrer.augmentSeenClasses(compiler.backend.fixedListImplementation); return _fixedListType; } @override ConcreteType get growableListType { inferrer.augmentSeenClasses(compiler.backend.growableListImplementation); return _growableListType; } @override ConcreteType get mapType { inferrer.augmentSeenClasses(compiler.backend.mapImplementation); return _mapType; } @override ConcreteType get constMapType { inferrer.augmentSeenClasses(compiler.backend.constMapImplementation); return _constMapType; } @override ConcreteType get stringType { inferrer.augmentSeenClasses(compiler.backend.stringImplementation); return _stringType; } @override ConcreteType stringLiteralType(_) { inferrer.augmentSeenClasses(compiler.backend.stringImplementation); return _stringType; } /** * Returns the [TypeMask] representation of [baseType]. */ TypeMask baseTypeToTypeMask(BaseType baseType) { if (baseType.isUnknown()) { return const DynamicTypeMask(); } else if (baseType.isNull()) { return new TypeMask.empty(); } else { ClassBaseType classBaseType = baseType; final element = classBaseType.element; assert(element != null); if (element == compiler.backend.numImplementation) { return new TypeMask.nonNullSubclass(compiler.backend.numImplementation, compiler.world); } else if (element == compiler.backend.intImplementation) { return new TypeMask.nonNullSubclass(compiler.backend.intImplementation, compiler.world); } else { return new TypeMask.nonNullExact(element.declaration, compiler.world); } } } /** * Returns the [TypeMask] representation of [concreteType]. */ TypeMask concreteTypeToTypeMask(ConcreteType concreteType) { if (concreteType == null) return null; TypeMask typeMask = new TypeMask.nonNullEmpty(); for (BaseType baseType in concreteType.baseTypes) { TypeMask baseMask = baseTypeToTypeMask(baseType); if (baseMask == const DynamicTypeMask()) return baseMask; typeMask = typeMask.union(baseMask, compiler.world); } return typeMask; } @override ConcreteType addPhiInput(Local variable, ConcreteType phiType, ConcreteType newType) { return computeLUB(phiType, newType); } @override ConcreteType allocateDiamondPhi(ConcreteType firstInput, ConcreteType secondInput) { return computeLUB(firstInput, secondInput); } @override ConcreteType allocatePhi(Node node, Local variable, ConcreteType inputType) { return inputType; } @override ConcreteType allocateLoopPhi(Node node, Local variable, ConcreteType inputType) { return inputType; } @override ConcreteType computeLUB(ConcreteType firstType, ConcreteType secondType) { if (firstType == null) { return secondType; } else if (secondType == null) { return firstType; } else { return firstType.union(secondType); } } // Implementation Inspired by // type_graph_inferrer.TypeInformationSystem.narrowType @override ConcreteType narrowType(ConcreteType type, DartType annotation, {bool isNullable: true}) { if (annotation.treatAsDynamic) return type; if (annotation.isVoid) return nullType; if (annotation.element == compiler.objectClass) return type; ConcreteType otherType; if (annotation.isTypedef || annotation.isFunctionType) { otherType = functionType; } else if (annotation.isTypeVariable) { // TODO(polux): Narrow to bound. return type; } else { assert(annotation.isInterfaceType); otherType = nonNullSubtype(annotation.element); } if (isNullable) otherType = otherType.union(nullType); if (type == null) return otherType; return type.intersection(otherType); } @override Selector newTypedSelector(ConcreteType receiver, Selector selector) { return new TypedSelector(concreteTypeToTypeMask(receiver), selector, compiler.world); } @override ConcreteType nonNullEmpty() { return nonNullEmptyType; } @override ConcreteType nonNullExact(ClassElement cls) { return nonNullSubtype(cls); } /** * Helper method for [nonNullSubtype] and [nonNullSubclass]. */ ConcreteType nonNullSubX(ClassElement cls, Iterable extractor(ClassElement cls)) { if (cls == compiler.objectClass) { return dynamicType; } ConcreteType result = nonNullEmptyType; void registerClass(ClassElement element) { if (!element.isAbstract) { result = result.union( new ConcreteType.singleton(compiler.maxConcreteTypeSize, baseTypes, new ClassBaseType(element))); inferrer.augmentSeenClasses(element); } } registerClass(cls); Iterable subtypes = extractor(cls); subtypes.forEach(registerClass); return result; } @override ConcreteType nonNullSubclass(ClassElement cls) { return nonNullSubX(cls, compiler.world.subclassesOf); } @override ConcreteType nonNullSubtype(ClassElement cls) { return nonNullSubX(cls, compiler.world.subtypesOf); } @override ConcreteType simplifyPhi(Node node, Local variable, ConcreteType phiType) { return phiType; } @override bool selectorNeedsUpdate(ConcreteType type, Selector selector) { return concreteTypeToTypeMask(type) != selector.mask; } @override ConcreteType refineReceiver(Selector selector, ConcreteType receiverType) { return receiverType.refine(selector, compiler); } @override bool isNull(ConcreteType type) { return (type.baseTypes.length == 1) && (type.baseTypes.first.isNull()); } @override ConcreteType allocateClosure(Node node, Element element) { // TODO(polux): register closure here instead of in visitor? return functionType; } @override ConcreteType allocateList(ConcreteType type, Node node, Element enclosing, [ConcreteType elementType, int length]) { if (elementType != null) { inferrer.augmentListElementType(elementType); } return type; } @override ConcreteType allocateMap(ConcreteType type, Node node, Element element, [List keyTypes, List valueTypes]) { // TODO(polux): treat maps the same way we treat lists return type; } @override ConcreteType getConcreteTypeFor(TypeMask mask) { if (mask.isUnion) { UnionTypeMask union = mask; return union.disjointMasks.fold( nonNullEmptyType, (type1, type2) => type1.union(getConcreteTypeFor(type2))); } else { FlatTypeMask flat = mask; ConcreteType result; if (flat.isEmpty) { result = nonNullEmptyType; } else if (flat.isExact) { result = nonNullExact(flat.base); } else if (flat.isSubclass) { result = nonNullSubclass(flat.base); } else if (flat.isSubtype) { result = nonNullSubtype(flat.base); } else { throw new ArgumentError("unexpected mask"); } return flat.isNullable ? result.union(nullType) : result; } } } /** * The cartesian product of concrete types: an iterable of * [ConcreteTypesEnvironment]s. */ class ConcreteTypeCartesianProduct extends IterableBase { final ConcreteTypesInferrer inferrer; final ClassElement typeOfThis; final Map concreteTypes; ConcreteTypeCartesianProduct(this.inferrer, this.typeOfThis, this.concreteTypes); Iterator get iterator => concreteTypes.isEmpty ? [new ConcreteTypesEnvironment(typeOfThis)].iterator : new ConcreteTypeCartesianProductIterator(inferrer, typeOfThis, concreteTypes); String toString() => this.toList().toString(); } /** * An helper class for [ConcreteTypeCartesianProduct]. */ class ConcreteTypeCartesianProductIterator implements Iterator { final ConcreteTypesInferrer inferrer; final ClassElement classOfThis; final Map concreteTypes; final Map nextValues; final Map state; int size = 1; int counter = 0; ConcreteTypesEnvironment _current; ConcreteTypeCartesianProductIterator(this.inferrer, this.classOfThis, Map concreteTypes) : this.concreteTypes = concreteTypes, nextValues = new Map(), state = new Map() { if (concreteTypes.isEmpty) { size = 0; return; } for (final e in concreteTypes.keys) { final baseTypes = concreteTypes[e].baseTypes; size *= baseTypes.length; } } ConcreteTypesEnvironment get current => _current; ConcreteTypesEnvironment takeSnapshot() { Map result = new Map(); nextValues.forEach((k, v) { result[k] = inferrer.singletonConcreteType(v); }); return new ConcreteTypesEnvironment.of(result, classOfThis); } bool moveNext() { if (counter >= size) { _current = null; return false; } Element keyToIncrement = null; for (final key in concreteTypes.keys) { final iterator = state[key]; if (iterator != null && iterator.moveNext()) { nextValues[key] = state[key].current; break; } Iterator newIterator = concreteTypes[key].baseTypes.iterator; state[key] = newIterator; newIterator.moveNext(); nextValues[key] = newIterator.current; } counter++; _current = takeSnapshot(); return true; } } /** * [BaseType] Constants. */ class BaseTypes { final ClassBaseType intBaseType; final ClassBaseType doubleBaseType; final ClassBaseType numBaseType; final ClassBaseType boolBaseType; final ClassBaseType stringBaseType; final ClassBaseType listBaseType; final ClassBaseType growableListBaseType; final ClassBaseType fixedListBaseType; final ClassBaseType constListBaseType; final ClassBaseType mapBaseType; final ClassBaseType constMapBaseType; final ClassBaseType objectBaseType; final ClassBaseType typeBaseType; final ClassBaseType functionBaseType; final ClassBaseType uint31Type; final ClassBaseType uint32Type; final ClassBaseType positiveIntType; BaseTypes(Compiler compiler) : intBaseType = new ClassBaseType(compiler.backend.intImplementation), doubleBaseType = new ClassBaseType(compiler.backend.doubleImplementation), numBaseType = new ClassBaseType(compiler.backend.numImplementation), boolBaseType = new ClassBaseType(compiler.backend.boolImplementation), stringBaseType = new ClassBaseType(compiler.backend.stringImplementation), listBaseType = new ClassBaseType(compiler.backend.listImplementation), growableListBaseType = new ClassBaseType(compiler.backend.growableListImplementation), fixedListBaseType = new ClassBaseType(compiler.backend.fixedListImplementation), constListBaseType = new ClassBaseType(compiler.backend.constListImplementation), mapBaseType = new ClassBaseType(compiler.backend.mapImplementation), constMapBaseType = new ClassBaseType(compiler.backend.constMapImplementation), objectBaseType = new ClassBaseType(compiler.objectClass), typeBaseType = new ClassBaseType(compiler.backend.typeImplementation), functionBaseType = new ClassBaseType(compiler.backend.functionImplementation), uint31Type = new ClassBaseType(compiler.backend.uint31Implementation), uint32Type = new ClassBaseType(compiler.backend.uint32Implementation), positiveIntType = new ClassBaseType(compiler.backend.positiveIntImplementation); } /** * An immutable mapping from method arguments to [ConcreteTypes]. */ class ConcreteTypesEnvironment { final Map environment; final ClassElement classOfThis; ConcreteTypesEnvironment([this.classOfThis]) : environment = new Map(); ConcreteTypesEnvironment.of(this.environment, this.classOfThis); ConcreteType lookupType(Element element) => environment[element]; bool operator ==(ConcreteTypesEnvironment other) { if (other is! ConcreteTypesEnvironment) return false; if (classOfThis != other.classOfThis) return false; if (environment.length != other.environment.length) return false; for (Element key in environment.keys) { if (!other.environment.containsKey(key) || (environment[key] != other.environment[key])) { return false; } } return true; } int get hashCode { int result = (classOfThis != null) ? classOfThis.hashCode : 1; environment.forEach((element, concreteType) { result = 31 * (31 * result + element.hashCode) + concreteType.hashCode; }); return result; } String toString() => "{ this: $classOfThis, env: $environment }"; } class ClosureEnvironment { ConcreteType thisType; final LocalsHandler locals; ClosureEnvironment(this.thisType, this.locals); bool mergeLocals(LocalsHandler newLocals) { assert((locals == null) == (newLocals == null)); return (locals != null) ? locals.mergeAll([newLocals]) : false; } /// Returns true if changed. bool merge(ConcreteType thisType, LocalsHandler locals) { ConcreteType oldThisType = this.thisType; if (this.thisType == null) { this.thisType = thisType; } else if (thisType != null) { this.thisType = this.thisType.union(thisType); } return mergeLocals(locals) || (this.thisType != oldThisType); } toString() => "ClosureEnvironment { thisType = $thisType, locals = ... }"; } /** * A set of encoutered closures. */ class Closures { final Compiler compiler; final Map closures = new Map(); Closures(this.compiler); /// Returns true if the environment of the closure has changed. bool put(FunctionElement closure, ConcreteType typeOfThis, LocalsHandler locals) { ClosureEnvironment oldEnvironent = closures[closure]; if (oldEnvironent == null) { closures[closure] = new ClosureEnvironment(typeOfThis, locals); return true; } else { return oldEnvironent.merge(typeOfThis, locals); } } ClosureEnvironment getEnvironmentOrNull(FunctionElement function) { return closures[function]; } Iterable get functionElements => closures.keys; bool contains(FunctionElement function) => closures.containsKey(function); String toString() => closures.toString(); } /** * A work item for the type inference queue. */ class InferenceWorkItem { Element method; ConcreteTypesEnvironment environment; InferenceWorkItem(this.method, this.environment); toString() => "{ method = $method, environment = $environment }"; bool operator ==(other) { return (other is InferenceWorkItem) && method == other.method && environment == other.environment; } int get hashCode => 31 * method.hashCode + environment.hashCode; } /** * A sentinel type mask class representing the dynamicType. It is absorbing * for [:ConcreteTypesEnvironment.typeMaskUnion:]. */ class DynamicTypeMask implements TypeMask { const DynamicTypeMask(); String toString() => 'sentinel type mask'; TypeMask nullable() { throw new UnsupportedError(""); } TypeMask nonNullable() { throw new UnsupportedError(""); } bool get isEmpty { throw new UnsupportedError(""); } bool get isNullable { throw new UnsupportedError(""); } bool get isExact { throw new UnsupportedError(""); } bool get isUnion { throw new UnsupportedError(""); } bool get isContainer { throw new UnsupportedError(""); } bool get isMap { throw new UnsupportedError(""); } bool get isDictionary { throw new UnsupportedError(""); } bool get isForwarding { throw new UnsupportedError(""); } bool get isValue { throw new UnsupportedError(""); } bool containsOnlyInt(ClassWorld classWorld) { throw new UnsupportedError(""); } bool containsOnlyDouble(ClassWorld classWorld) { throw new UnsupportedError(""); } bool containsOnlyNum(ClassWorld classWorld) { throw new UnsupportedError(""); } bool containsOnlyBool(ClassWorld classWorld) { throw new UnsupportedError(""); } bool containsOnlyString(ClassWorld classWorld) { throw new UnsupportedError(""); } bool containsOnly(ClassElement element) { throw new UnsupportedError(""); } bool satisfies(ClassElement cls, ClassWorld classWorld) { throw new UnsupportedError(""); } bool contains(ClassElement type, ClassWorld classWorld) { throw new UnsupportedError(""); } bool containsAll(ClassWorld classWorld) { throw new UnsupportedError(""); } ClassElement singleClass(ClassWorld classWorld) { throw new UnsupportedError(""); } TypeMask union(TypeMask other, ClassWorld classWorld) { throw new UnsupportedError(""); } TypeMask intersection(TypeMask other, ClassWorld classWorld) { throw new UnsupportedError(""); } bool canHit(Element element, Selector selector, ClassWorld classWorld) { throw new UnsupportedError(""); } Element locateSingleElement(Selector selector, Compiler compiler) { throw new UnsupportedError(""); } bool needsNoSuchMethodHandling(Selector selector, ClassWorld classWorld) { throw new UnsupportedError(""); } bool isInMask(TypeMask other, ClassWorld classWorld) { throw new UnsupportedError(""); } bool containsMask(TypeMask other, ClassWorld classWorld) { throw new UnsupportedError(""); } } class WorkQueue { final Queue queue = new Queue(); void add(InferenceWorkItem workItem) { if (!queue.contains(workItem)) { queue.addLast(workItem); } } InferenceWorkItem remove() { return queue.removeFirst(); } bool get isEmpty => queue.isEmpty; } /** * A task which conservatively infers a [ConcreteType] for each sub expression * of the program. The entry point is [analyzeMain]. */ class ConcreteTypesInferrer extends InferrerEngine implements TypesInferrer { final String name = "Type inferrer"; /** * When true, the string literal [:"__dynamic_for_test":] is inferred to * have the unknown type. */ // TODO(polux): get rid of this hack once we have a natural way of inferring // the unknown type. bool testMode = false; // --- constants --- /** * Constants representing builtin base types. Initialized by [initialize] * and not by the constructor because the compiler elements are not yet * populated. */ BaseTypes baseTypes; /** The associated type system */ ConcreteTypeSystem types; /** * Constant representing [:ConcreteList#[]:] where [:ConcreteList:] is the * concrete implementation of lists for the selected backend. */ FunctionElement listIndex; /** * Constant representing [:ConcreteList#[]=:] where [:ConcreteList:] is the * concrete implementation of lists for the selected backend. */ FunctionElement listIndexSet; /** * Constant representing [:ConcreteList#add:] where [:ConcreteList:] is the * concrete implementation of lists for the selected backend. */ FunctionElement listAdd; /** * Constant representing [:ConcreteList#removeAt:] where [:ConcreteList:] is * the concrete implementation of lists for the selected backend. */ FunctionElement listRemoveAt; /** * Constant representing [:ConcreteList#insert:] where [:ConcreteList:] is * the concrete implementation of lists for the selected backend. */ FunctionElement listInsert; /** * Constant representing [:ConcreteList#removeLast:] where [:ConcreteList:] is * the concrete implementation of lists for the selected backend. */ FunctionElement listRemoveLast; /** Constant representing [:List():]. */ FunctionElement listConstructor; /** The unknown concrete type */ final ConcreteType unknownConcreteType; /** The empty concrete type */ ConcreteType emptyConcreteType; /** The null concrete type */ ConcreteType nullConcreteType; // --- state updated by the inference --- /** * A map from (function x argument base types) to their inferred concrete * type. Another way of seeing [methodToTemplates] is as a map from * [FunctionElement]s to "templates" in the sense of "The Cartesian Product * Algorithm - Simple and Precise Type Inference of Parametric Polymorphism" * by Ole Agesen. */ // TODO(polux): build a better abstraction, like Closures final Map> methodToTemplates; /** The set of encountered closures. */ final Closures closures; /** A map from expressions to their inferred concrete types. */ final Map inferredTypes; /** A map from fields to their inferred concrete types. */ final Map inferredFieldTypes; /** * [:callers[f]:] is the list of [:f:]'s possible callers or fields * whose initialization is a call to [:f:]. */ final Map> callers; /** * [:readers[field]:] is the list of [:field:]'s possible readers or fields * whose initialization is a read of [:field:]. */ final Map> fieldReaders; /** * [:readers[local]:] is the list of [:local:]'s possible readers. */ final Map> capturedLocalsReaders; /// The set of classes encountered so far. final Set seenClasses; /** * A map from selector names to callers of methods with this name on objects * of unknown inferred type. */ final Map> dynamicCallers; /** The inferred type of elements stored in Lists. */ ConcreteType listElementType; /** * A map from parameters to their inferred concrete types. It plays no role * in the analysis, it is write only. */ final Map inferredParameterTypes; /** * A map from selectors to their inferred type masks, indexed by the mask * of the receiver. It plays no role in the analysis, it is write only. */ final Map> inferredSelectorTypes; /** The work queue consumed by [analyzeMain]. */ final WorkQueue workQueue; /** The item being worked on. */ InferenceWorkItem currentWorkItem; ConcreteTypesInferrer(Compiler compiler) : methodToTemplates = new Map>(), closures = new Closures(compiler), inferredTypes = new Map(), inferredFieldTypes = new Map(), inferredParameterTypes = new Map(), workQueue = new WorkQueue(), callers = new Map>(), fieldReaders = new Map>(), capturedLocalsReaders = new Map>(), seenClasses = new Set(), dynamicCallers = new Map>(), inferredSelectorTypes = new Map>(), unknownConcreteType = new ConcreteType.unknown(), super(compiler, null); /* Initialization code that cannot be run in the constructor because it * requires the compiler's elements to be populated. */ void initialize() { baseTypes = new BaseTypes(compiler); types = new ConcreteTypeSystem(this); ClassElement jsArrayClass = baseTypes.listBaseType.element; listIndex = jsArrayClass.lookupMember('[]'); listIndexSet = jsArrayClass.lookupMember('[]='); listAdd = jsArrayClass.lookupMember('add'); listRemoveAt = jsArrayClass.lookupMember('removeAt'); listInsert = jsArrayClass.lookupMember('insert'); listRemoveLast = jsArrayClass.lookupMember('removeLast'); List typePreservingOps = const ['+', '-', '*']; listConstructor = compiler.listClass.lookupDefaultConstructor(); if (listConstructor != null) { listConstructor = listConstructor.implementation; } emptyConcreteType = new ConcreteType.empty(compiler.maxConcreteTypeSize, baseTypes); nullConcreteType = singletonConcreteType(const NullBaseType()); listElementType = emptyConcreteType; } // --- utility methods --- /** Creates a singleton concrete type containing [baseType]. */ ConcreteType singletonConcreteType(BaseType baseType) { return new ConcreteType.singleton(compiler.maxConcreteTypeSize, baseTypes, baseType); } /** * Computes the union of [mask1] and [mask2] where [mask1] and [mask2] are * possibly equal to [: DynamicTypeMask.instance :]. */ TypeMask typeMaskUnion(TypeMask mask1, TypeMask mask2) { if (mask1 == const DynamicTypeMask() || mask2 == const DynamicTypeMask()) { return const DynamicTypeMask(); } return mask1.union(mask2, compiler.world); } /** * Returns all the members matching [selector]. */ Set getMembersBySelector(Selector selector) { // TODO(polux): memoize? Set result = new Set(); for (ClassElement cls in seenClasses) { Element elem = cls.lookupSelector(selector); if (elem != null) { result.add(elem.implementation); } } return result; } /** * Returns all the subtypes of [cls], [cls] included. */ Set getReflexiveSubtypesOf(ClassElement cls) { // TODO(polux): memoize? Set result = new Set()..add(cls); for (ClassElement candidate in seenClasses) { if (compiler.world.isSubtypeOf(candidate, cls)) { result.add(candidate); } } return result; } /** * Sets the concrete type associated to [node] to the union of the inferred * concrete type so far and of [type]. */ void augmentInferredType(Node node, ConcreteType type) { ConcreteType currentType = inferredTypes[node]; inferredTypes[node] = (currentType == null) ? type : currentType.union(type); } /** * Sets the concrete type associated to [selector] to the union of the * inferred concrete type so far and of [returnType]. * * Precondition: [:(typeOfThis != null) && (returnType != null):] */ void augmentInferredSelectorType(Selector selector, TypeMask typeOfThis, TypeMask returnType) { assert(returnType != null); assert(typeOfThis != null); selector = selector.asUntyped; Map currentMap = inferredSelectorTypes.putIfAbsent( selector, () => new Map()); TypeMask currentReturnType = currentMap[typeOfThis]; currentMap[typeOfThis] = (currentReturnType == null) ? returnType : typeMaskUnion(currentReturnType, returnType); } /** * Returns the current inferred concrete type of [field]. */ ConcreteType getFieldType(Selector selector, Element field) { ensureFieldInitialized(field); ConcreteType result = inferredFieldTypes[field]; result = (result == null) ? emptyConcreteType : result; if (selector != null) { Element enclosing = field.enclosingElement; if (enclosing.isClass) { ClassElement cls = enclosing; TypeMask receiverMask = new TypeMask.exact(cls.declaration, classWorld); TypeMask resultMask = types.concreteTypeToTypeMask(result); augmentInferredSelectorType(selector, receiverMask, resultMask); } } return result; } /** * Sets the concrete type associated to [field] to the union of the inferred * concrete type so far and of [type]. */ void augmentFieldType(Element field, ConcreteType type) { ensureFieldInitialized(field); ConcreteType oldType = inferredFieldTypes[field]; ConcreteType newType = (oldType != null) ? oldType.union(type) : type; if (oldType != newType) { inferredFieldTypes[field] = newType; invalidateReaders(field); } } /** Augment the inferred type of elements stored in Lists. */ void augmentListElementType(ConcreteType type) { ConcreteType newType = listElementType.union(type); if (newType != listElementType) { invalidateCallers(listIndex); listElementType = newType; } } /** * Sets the concrete type associated to [parameter] to the union of the * inferred concrete type so far and of [type]. */ void augmentParameterType(VariableElement parameter, ConcreteType type) { ConcreteType oldType = inferredParameterTypes[parameter]; inferredParameterTypes[parameter] = (oldType == null) ? type : oldType.union(type); } /** Augments the set of classes encountered so far. */ void augmentSeenClasses(ClassElement cls) { if (!seenClasses.contains(cls)) { seenClasses.add(cls); cls.forEachMember((_, Element member) { Set functions = dynamicCallers[member.name]; if (functions != null) { functions.forEach(invalidate); } }, includeSuperAndInjectedMembers: true); } } /** * Add [caller] to the set of [callee]'s callers. */ void addCaller(FunctionElement callee, Element caller) { callers.putIfAbsent(callee, () => new Set()) .add(caller); } /** * Add [caller] to the set of [callee]'s dynamic callers. */ void addDynamicCaller(Selector callee, FunctionElement caller) { dynamicCallers .putIfAbsent(callee.name, () => new Set()) .add(caller); } /** * Add [reader] to the set of [field]'s readers. */ void addFieldReader(Element field, Element reader) { fieldReaders.putIfAbsent(field, () => new Set()) .add(reader); } /** * Add [reader] to the set of [local]'s readers. */ void addCapturedLocalReader(Local local, FunctionElement reader) { capturedLocalsReaders.putIfAbsent(local, () => new Set()) .add(reader); } /** * Add a closure to the set of seen closures. Invalidate callers if * the set of locals has changed. */ void addClosure(FunctionElement closure, ConcreteType typeOfThis, LocalsHandler locals) { if (closures.put(closure, typeOfThis, locals)) { invalidateCallers(closure); } } /** * Invalidate all callers of [function]. */ void invalidateCallers(FunctionElement function) { Set methodCallers = callers[function]; if (methodCallers != null) { methodCallers.forEach(invalidate); } } /** * Invalidate all reader of [field]. */ void invalidateReaders(Element field) { Set readers = fieldReaders[field]; if (readers != null) { readers.forEach(invalidate); } } /** * Add all templates of [methodOrField] to the workqueue. */ void invalidate(Element methodOrField) { if (methodOrField.isField) { workQueue.add(new InferenceWorkItem( methodOrField, new ConcreteTypesEnvironment())); } else { Map templates = methodToTemplates[methodOrField]; if (templates != null) { templates.forEach((environment, _) { workQueue.add( new InferenceWorkItem(methodOrField, environment)); }); } } } /** * Returns the template associated to [function] or create an empty template * for [function] return it. */ // TODO(polux): encapsulate this in an abstraction for templates Map getTemplatesOrEmpty(FunctionElement function) { return methodToTemplates.putIfAbsent( function, () => new Map()); } // -- methods of types.TypesInferrer (interface with the backend) -- /** Get the inferred concrete type of [node]. */ @override TypeMask getTypeOfNode(Element owner, Node node) { TypeMask result = types.concreteTypeToTypeMask(inferredTypes[node]); return (result == const DynamicTypeMask()) ? null : result; } /** Get the inferred concrete type of [element]. */ @override TypeMask getTypeOfElement(Element element) { final result = types.concreteTypeToTypeMask(typeOfElement(element)); return (result == const DynamicTypeMask()) ? null : result; } /** * Get the inferred concrete return type of [element]. A null return value * means "I don't know". */ @override TypeMask getReturnTypeOfElement(Element element) { assert(element is FunctionElement); Map templates = methodToTemplates[element]; if (templates == null) return null; ConcreteType returnType = emptyConcreteType; templates.forEach((_, concreteType) { returnType = returnType.union(concreteType); }); TypeMask result = types.concreteTypeToTypeMask(returnType); return (result == const DynamicTypeMask()) ? null : result; } /** * Get the inferred concrete type of [selector]. A null return value means * "I don't know". */ @override TypeMask getTypeOfSelector(Selector selector) { Map candidates = inferredSelectorTypes[selector.asUntyped]; if (candidates == null) { return null; } TypeMask result = new TypeMask.nonNullEmpty(); if (selector.mask == null) { candidates.forEach((TypeMask receiverType, TypeMask returnType) { result = typeMaskUnion(result, returnType); }); } else { candidates.forEach((TypeMask receiverType, TypeMask returnType) { TypeMask intersection = receiverType.intersection(selector.mask, compiler.world); if (!intersection.isEmpty || intersection.isNullable) { result = typeMaskUnion(result, returnType); } }); } return result == const DynamicTypeMask() ? null : result; } @override void clear() { throw new UnsupportedError("clearing is not yet implemented"); } @override bool isCalledOnce(Element element) { // Never called by SimpleTypeInferrer. throw new UnsupportedError(""); } @override bool isFixedArrayCheckedForGrowable(Node node) { // Never called by SimpleTypeInferrer. throw new UnsupportedError(""); } // --- analysis --- /** * Returns the concrete type returned by [function] given arguments of * concrete types [argumentsTypes]. If [function] is static then * [receiverType] must be null, else [function] must be a member of * [receiverType]. */ ConcreteType getSendReturnType(Selector selector, FunctionElement function, ClassElement receiverType, ArgumentsTypes argumentsTypes) { assert(function != null); ConcreteType result = emptyConcreteType; Map argumentMap = associateArguments(function, argumentsTypes); // if the association failed, this send will never occur or will fail if (argumentMap == null) { return emptyConcreteType; } argumentMap.forEach(augmentParameterType); ConcreteTypeCartesianProduct product = new ConcreteTypeCartesianProduct(this, receiverType, argumentMap); for (ConcreteTypesEnvironment environment in product) { result = result.union( getMonomorphicSendReturnType(function, environment)); } if (selector != null && receiverType != null) { // TODO(polux): generalize to any abstract class if we ever handle other // abstract classes than num. TypeMask receiverMask = (receiverType == compiler.backend.numImplementation || receiverType == compiler.backend.intImplementation) ? new TypeMask.nonNullSubclass(receiverType.declaration, compiler.world) : new TypeMask.nonNullExact(receiverType.declaration, compiler.world); TypeMask resultMask = types.concreteTypeToTypeMask(result); augmentInferredSelectorType(selector, receiverMask, resultMask); } return result; } /** * Given a method signature and a list of concrete types, builds a map from * formals to their corresponding concrete types. Returns null if the * association is impossible (for instance: too many arguments). */ Map associateArguments( FunctionElement function, ArgumentsTypes argumentsTypes) { final Map result = new Map(); final FunctionSignature signature = function.functionSignature; // guard 1: too many arguments if (argumentsTypes.length > signature.parameterCount) { return null; } // guard 2: not enough arguments if (argumentsTypes.positional.length < signature.requiredParameterCount) { return null; } // guard 3: too many positional arguments if (signature.optionalParametersAreNamed && argumentsTypes.positional.length > signature.requiredParameterCount) { return null; } handleLeftoverOptionalParameter(ParameterElement parameter) { Expression initializer = parameter.initializer; result[parameter] = (initializer == null) ? nullConcreteType : analyzeDefaultValue(function, initializer); } final Iterator remainingPositionalArguments = argumentsTypes.positional.iterator; // we attach each positional parameter to its corresponding positional // argument for (Link requiredParameters = signature.requiredParameters; !requiredParameters.isEmpty; requiredParameters = requiredParameters.tail) { final Element requiredParameter = requiredParameters.head; // we know moveNext() succeeds because of guard 2 remainingPositionalArguments.moveNext(); result[requiredParameter] = remainingPositionalArguments.current; } if (signature.optionalParametersAreNamed) { // we build a map out of the remaining named parameters Link remainingOptionalParameters = signature.optionalParameters; final Map leftOverNamedParameters = new Map(); for (; !remainingOptionalParameters.isEmpty; remainingOptionalParameters = remainingOptionalParameters.tail) { final Element namedParameter = remainingOptionalParameters.head; leftOverNamedParameters[namedParameter.name] = namedParameter; } // we attach the named arguments to their corresponding optional // parameters for (String source in argumentsTypes.named.keys) { final ConcreteType concreteType = argumentsTypes.named[source]; final Element namedParameter = leftOverNamedParameters[source]; // unexisting or already used named parameter if (namedParameter == null) return null; result[namedParameter] = concreteType; leftOverNamedParameters.remove(source); } leftOverNamedParameters.forEach((_, Element parameter) { handleLeftoverOptionalParameter(parameter); }); } else { // optional parameters are positional // we attach the remaining positional arguments to their corresponding // optional parameters Link remainingOptionalParameters = signature.optionalParameters; while (remainingPositionalArguments.moveNext()) { final Element optionalParameter = remainingOptionalParameters.head; result[optionalParameter] = remainingPositionalArguments.current; // we know tail is defined because of guard 1 remainingOptionalParameters = remainingOptionalParameters.tail; } for (; !remainingOptionalParameters.isEmpty; remainingOptionalParameters = remainingOptionalParameters.tail) { handleLeftoverOptionalParameter(remainingOptionalParameters.head); } } return result; } ConcreteType getMonomorphicSendReturnType( FunctionElement function, ConcreteTypesEnvironment environment) { Map template = getTemplatesOrEmpty(function); ConcreteType type = template[environment]; ConcreteType specialType = getSpecialCaseReturnType(function, environment); if (type != null) { return specialType != null ? specialType : type; } else { workQueue.add(new InferenceWorkItem(function, environment)); return specialType != null ? specialType : emptyConcreteType; } } /** * Handles external methods that cannot be cached because they depend on some * other state of [ConcreteTypesInferrer] like [:List#[]:] and * [:List#[]=:]. Returns null if [function] and [environment] don't form a * special case */ ConcreteType getSpecialCaseReturnType(FunctionElement function, ConcreteTypesEnvironment environment) { // Handles int + int, double + double, int - int, ... // We cannot compare function to int#+, int#-, etc. because int and double // don't override these methods. So for 1+2, getSpecialCaseReturnType will // be invoked with function = num#+. We use environment.typeOfThis instead. ClassElement cls = environment.classOfThis; if (cls != null) { String name = function.name; if ((cls == baseTypes.intBaseType.element || cls == baseTypes.doubleBaseType.element) && (name == '+' || name == '-' || name == '*')) { Link parameters = function.functionSignature.requiredParameters; ConcreteType argumentType = environment.lookupType(parameters.head); if (argumentType.getUniqueType() == cls) { return singletonConcreteType(new ClassBaseType(cls)); } } } if (function == listIndex || function == listRemoveAt) { Link parameters = function.functionSignature.requiredParameters; ConcreteType indexType = environment.lookupType(parameters.head); if (!indexType.baseTypes.contains(baseTypes.intBaseType)) { return emptyConcreteType; } return listElementType; } else if (function == listIndexSet || function == listInsert) { Link parameters = function.functionSignature.requiredParameters; ConcreteType indexType = environment.lookupType(parameters.head); if (!indexType.baseTypes.contains(baseTypes.intBaseType)) { return emptyConcreteType; } ConcreteType elementType = environment.lookupType(parameters.tail.head); augmentListElementType(elementType); return emptyConcreteType; } else if (function == listAdd) { Link parameters = function.functionSignature.requiredParameters; ConcreteType elementType = environment.lookupType(parameters.head); augmentListElementType(elementType); return emptyConcreteType; } else if (function == listRemoveLast) { return listElementType; } return null; } ConcreteType analyzeMethodOrClosure(Element element, ConcreteTypesEnvironment environment) { ConcreteType specialResult = handleSpecialMethod(element, environment); if (specialResult != null) return specialResult; ClosureEnvironment closureEnv = closures.getEnvironmentOrNull(element); return (closureEnv == null) ? analyzeMethod(element, environment) : analyzeClosure(element, closureEnv, environment); } ConcreteType analyzeMethod(Element element, ConcreteTypesEnvironment environment) { TypeInferrerVisitor visitor = new TypeInferrerVisitor( element, this, singletonConcreteType(new ClassBaseType(environment.classOfThis)), environment.environment); visitor.run(); return visitor.returnType; } ConcreteType analyzeClosure(Element element, ClosureEnvironment closureEnv, ConcreteTypesEnvironment environment) { assert(environment.classOfThis == null); LocalsHandler locals = (closureEnv.locals != null) ? new LocalsHandler.deepCopyOf(closureEnv.locals) : null; TypeInferrerVisitor visitor = new TypeInferrerVisitor(element, this, closureEnv.thisType, environment.environment, locals); visitor.run(); return visitor.returnType; } /** * Analyze the initializer of a field if it has not yet been done and update * [inferredFieldTypes] accordingly. Invalidate the readers of the field if * needed. */ void ensureFieldInitialized(Element field) { // This is test is needed for fitering out BoxFieldElements. if (field is! BoxFieldElement && inferredFieldTypes[field] == null) { analyzeFieldInitialization(field); } } /** * Analyze the initializer of a field and update [inferredFieldTypes] * accordingly. Invalidate the readers of the field if needed. */ ConcreteType analyzeFieldInitialization(VariableElement field) { Visitor visitor = new TypeInferrerVisitor(field, this, null, new Map()); ConcreteType type; if (field.initializer != null) { type = field.initializer.accept(visitor); inferredFieldTypes[field] = type; invalidateReaders(field); } return type; } /** * Analyze a default value. */ ConcreteType analyzeDefaultValue(Element function, Node expression) { assert((function != null) && (expression != null)); Visitor visitor = new TypeInferrerVisitor(function, this, null, {}); return expression.accept(visitor); } /** * Hook that performs side effects on some special method calls (like * [:List(length):]) and possibly returns a concrete type. */ ConcreteType handleSpecialMethod(FunctionElement element, ConcreteTypesEnvironment environment) { // We trust the return type of native elements if (isNativeElement(element)) { var elementType = element.type; assert(elementType.isFunctionType); return typeOfNativeBehavior( native.NativeBehavior.ofMethod(element, compiler)); } // When List([length]) is called with some length, we must augment // listElementType with {null}. if (element == listConstructor) { Link parameters = listConstructor.functionSignature.optionalParameters; ConcreteType lengthType = environment.lookupType(parameters.head); if (lengthType.baseTypes.contains(baseTypes.intBaseType)) { augmentListElementType(nullConcreteType); } } return null; } /** * Performs concrete type inference of the code reachable from [element]. */ @override bool analyzeMain(Element element) { initialize(); workQueue.add( new InferenceWorkItem(element, new ConcreteTypesEnvironment())); while (!workQueue.isEmpty) { currentWorkItem = workQueue.remove(); if (currentWorkItem.method.isField) { analyzeFieldInitialization(currentWorkItem.method); } else { Map template = getTemplatesOrEmpty(currentWorkItem.method); template.putIfAbsent( currentWorkItem.environment, () => emptyConcreteType); recordReturnType( currentWorkItem.method, analyzeMethodOrClosure(currentWorkItem.method, currentWorkItem.environment)); } } return true; } /** * Dumps debugging information on the standard output. */ void debug() { print("queue:"); for (InferenceWorkItem workItem in workQueue.queue) { print(" $workItem"); } print("seen classes:"); for (ClassElement cls in seenClasses) { print(" ${cls.name}"); } print("callers:"); callers.forEach((k,v) { print(" $k: $v"); }); print("dynamic callers:"); dynamicCallers.forEach((k,v) { print(" $k: $v"); }); print("readers:"); fieldReaders.forEach((k,v) { print(" $k: $v"); }); print("readers of captured locals:"); capturedLocalsReaders.forEach((k,v) { print(" $k: $v"); }); print("inferredFieldTypes:"); inferredFieldTypes.forEach((k,v) { print(" $k: $v"); }); print("listElementType:"); print(" $listElementType"); print("inferredParameterTypes:"); inferredParameterTypes.forEach((k,v) { print(" $k: $v"); }); print("inferred selector types:"); inferredSelectorTypes.forEach((selector, map) { print(" $selector:"); map.forEach((k, v) { print(" $k: $v"); }); }); print("cache:"); methodToTemplates.forEach((k,v) { print(" $k: $v"); }); print("closures:"); closures.closures.forEach((k, ClosureEnvironment v) { print(" $k"); print(" this: ${v.thisType}"); if (v.locals != null) { v.locals.locals.forEachLocal((local, type) { print(" $local: $type"); }); } }); print("inferred expression types:"); inferredTypes.forEach((k,v) { print(" $k: $v"); }); } @override ConcreteType addReturnTypeFor(Element analyzedElement, ConcreteType currentType, ConcreteType newType) { return (currentType == null) ? newType : currentType.union(newType); } @override void forEachElementMatching(Selector selector, bool f(Element element)) { getMembersBySelector(selector).forEach(f); } @override void recordReturnType(Element element, ConcreteType type) { assert((type != null) && (element == currentWorkItem.method)); Map template = getTemplatesOrEmpty(element); if (template[currentWorkItem.environment] != type) { template[currentWorkItem.environment] = type; invalidateCallers(element); } } @override void recordType(Element element, ConcreteType type) { assert(element is FieldElement); augmentFieldType(element, type); } @override void recordTypeOfFinalField(Node node, Element nodeHolder, Element field, ConcreteType type) { augmentFieldType(field, type); } @override void recordTypeOfNonFinalField(Spannable node, Element field, ConcreteType type) { augmentFieldType(field, type); } @override void recordCapturedLocalRead(Local local) { addCapturedLocalReader(local, currentWorkItem.method); } @override void recordLocalUpdate(Local local, ConcreteType type) { Set localReaders = capturedLocalsReaders[local]; if (localReaders != null) { localReaders.forEach(invalidate); } } /** * Returns the caller of the current analyzed element, given the alleged * caller provided by SimpleTypeInferrer. * * SimpleTypeInferrer lies about the caller when it's a closure. * Unfortunately we cannot always trust currentWorkItem.method either because * it is wrong for fields initializers. */ Element getRealCaller(Element allegedCaller) { Element currentMethod = currentWorkItem.method; if ((currentMethod != allegedCaller) && currentMethod.isFunction && closures.contains(currentMethod)) { return currentMethod; } else { return allegedCaller; } } @override ConcreteType registerCalledElement(Spannable node, Selector selector, Element caller, Element callee, ArgumentsTypes arguments, SideEffects sideEffects, bool inLoop) { caller = getRealCaller(caller); if ((selector == null) || (selector.kind == SelectorKind.CALL)) { callee = callee.implementation; if (selector != null && selector.name == 'JS') { return null; } if (callee.isField) { // toplevel closure call getFieldType(selector, callee); // trigger toplevel field analysis addFieldReader(callee, caller); ConcreteType result = emptyConcreteType; for (FunctionElement function in closures.functionElements) { addCaller(function, caller); result = result.union( getSendReturnType(selector, function, null, arguments)); } return result; } else { // method or constructor call addCaller(callee, caller); ClassElement receiverClass = null; if (callee.isGenerativeConstructor) { receiverClass = callee.enclosingClass; } else if (node is Send) { Send send = node; if (send.receiver != null) { if (send.receiver.isSuper()) { receiverClass = currentWorkItem.environment.classOfThis.superclass; } else { receiverClass = currentWorkItem.environment.classOfThis; } } } return getSendReturnType(selector, callee, receiverClass, arguments); } } else if (selector.kind == SelectorKind.GETTER) { if (callee.isField) { addFieldReader(callee, caller); return getFieldType(selector, callee); } else if (callee.isGetter) { Element enclosing = callee.enclosingElement.isCompilationUnit ? null : callee.enclosingElement; addCaller(callee, caller); ArgumentsTypes noArguments = new ArgumentsTypes([], new Map()); return getSendReturnType(selector, callee, enclosing, noArguments); } else if (callee.isFunction) { addClosure(callee, null, null); return singletonConcreteType(baseTypes.functionBaseType); } } else if (selector.kind == SelectorKind.SETTER) { ConcreteType argumentType = arguments.positional.first; if (callee.isField) { augmentFieldType(callee, argumentType); } else if (callee.isSetter) { FunctionElement setter = callee; // TODO(polux): A setter always returns void so there's no need to // invalidate its callers even if it is called with new arguments. // However, if we start to record more than returned types, like // exceptions for instance, we need to do it by uncommenting the // following line. // inferrer.addCaller(setter, currentMethod); Element enclosing = callee.enclosingElement.isCompilationUnit ? null : callee.enclosingElement; return getSendReturnType(selector, setter, enclosing, new ArgumentsTypes([argumentType], new Map())); } } else { throw new ArgumentError("unexpected selector kind"); } return null; } @override ConcreteType registerCalledSelector(Node node, Selector selector, ConcreteType receiverType, Element caller, ArgumentsTypes arguments, SideEffects sideEffects, bool inLoop) { caller = getRealCaller(caller); switch (selector.kind) { case SelectorKind.GETTER: return registerDynamicGetterSend(selector, receiverType, caller); case SelectorKind.SETTER: return registerDynamicSetterSend( selector, receiverType, caller, arguments); default: return registerDynamicSend(selector, receiverType, caller, arguments); } } ConcreteType registerDynamicGetterSend(Selector selector, ConcreteType receiverType, Element caller) { caller = getRealCaller(caller); ConcreteType result = emptyConcreteType; void augmentResult(ClassElement baseReceiverType, Element member) { if (member.isField) { addFieldReader(member, caller); result = result.union(getFieldType(selector, member)); } else if (member.isGetter) { addCaller(member, caller); ArgumentsTypes noArguments = new ArgumentsTypes([], new Map()); result = result.union( getSendReturnType(selector, member, baseReceiverType, noArguments)); } else if (member.isFunction) { addClosure(member, receiverType, null); result = result.union( singletonConcreteType(baseTypes.functionBaseType)); } else { throw new ArgumentError("unexpected element type"); } } if (receiverType.isUnknown()) { addDynamicCaller(selector, caller); Set members = getMembersBySelector(selector); for (Element member in members) { if (!(member.isField || member.isGetter)) continue; for (ClassElement cls in getReflexiveSubtypesOf(member.enclosingElement)) { augmentResult(cls, member); } } } else { for (BaseType baseReceiverType in receiverType.baseTypes) { if (!baseReceiverType.isNull()) { ClassBaseType classBaseType = baseReceiverType; ClassElement cls = classBaseType.element; Element getterOrField = cls.lookupSelector(selector); if (getterOrField != null) { augmentResult(cls, getterOrField.implementation); } } } } return result; } ConcreteType registerDynamicSetterSend( Selector selector, ConcreteType receiverType, Element caller, ArgumentsTypes arguments) { caller = getRealCaller(caller); ConcreteType argumentType = arguments.positional.first; void augmentField(ClassElement receiverType, Element setterOrField) { if (setterOrField.isField) { augmentFieldType(setterOrField, argumentType); } else if (setterOrField.isSetter) { // A setter always returns void so there's no need to invalidate its // callers even if it is called with new arguments. However, if we // start to record more than returned types, like exceptions for // instance, we need to do it by uncommenting the following line. // inferrer.addCaller(setter, currentMethod); getSendReturnType(selector, setterOrField, receiverType, new ArgumentsTypes([argumentType], new Map())); } else { throw new ArgumentError("unexpected element type"); } } if (receiverType.isUnknown()) { // Same remark as above // addDynamicCaller(selector, caller); for (Element member in getMembersBySelector(selector)) { if (!(member.isField || member.isSetter)) continue; Element cls = member.enclosingClass; augmentField(cls, member); } } else { for (BaseType baseReceiverType in receiverType.baseTypes) { if (!baseReceiverType.isNull()) { ClassBaseType classBaseType = baseReceiverType; ClassElement cls = classBaseType.element; Element setterOrField = cls.lookupSelector(selector); if (setterOrField != null) { augmentField(cls, setterOrField.implementation); } } } } return argumentType; } ConcreteType registerDynamicSend(Selector selector, ConcreteType receiverType, Element caller, ArgumentsTypes arguments) { caller = getRealCaller(caller); ConcreteType result = emptyConcreteType; if (receiverType.isUnknown()) { addDynamicCaller(selector, caller); Set elements = getMembersBySelector(selector); for (Element element in elements) { if (element.isFunction) { FunctionElement method = element; addCaller(method, caller); for (ClassElement cls in getReflexiveSubtypesOf(method.enclosingElement)) { result = result.union( getSendReturnType(selector, method, cls, arguments)); } } else { // closure call assert(element.isField); for (FunctionElement function in closures.functionElements) { addCaller(function, caller); result = result.union( getSendReturnType(selector, function, null, arguments)); } } } } else { for (BaseType baseReceiverType in receiverType.baseTypes) { if (!baseReceiverType.isNull()) { ClassBaseType classBaseReceiverType = baseReceiverType; ClassElement cls = classBaseReceiverType.element; Element method = cls.lookupSelector(selector); if (method != null) { if (method.isFunction) { assert(method is FunctionElement); method = method.implementation; addCaller(method, caller); result = result.union( getSendReturnType(selector, method, cls, arguments)); } else { // closure call for (FunctionElement function in closures.functionElements) { addCaller(function, caller); result = result.union( getSendReturnType(selector, function, null, arguments)); } } } } } } return result; } ConcreteType registerAwait(Node node, ConcreteType argumentType) { // TODO(polux): Properly handle await expressions. return types.dynamicType; } @override void setDefaultTypeOfParameter(ParameterElement parameter, ConcreteType type) { // We handle default parameters our own way in associateArguments } /** * TODO(johnniwinther): Remove once synthetic parameters get their own default * values. */ bool hasAlreadyComputedTypeOfParameterDefault(Element parameter) => false; @override ConcreteType registerCalledClosure(Node node, Selector selector, ConcreteType closure, Element caller, ArgumentsTypes arguments, SideEffects sideEffects, bool inLoop) { caller = getRealCaller(caller); ConcreteType result = emptyConcreteType; for (FunctionElement function in closures.functionElements) { addCaller(function, caller); result = result.union( getSendReturnType(selector, function, null, arguments)); } return result; } @override ConcreteType returnTypeOfElement(Element element) { // Never called by SimpleTypeInferrer. throw new UnsupportedError(""); } @override ConcreteType typeOfElement(Element element) { if (currentWorkItem != null) { final result = currentWorkItem.environment.lookupType(element); if (result != null) return result; } if (element.isParameter || element.isInitializingFormal) { return inferredParameterTypes[element]; } else if (element.isField) { return inferredFieldTypes[element]; } throw new ArgumentError("unexpected element type"); } @override void analyze(Element element, ArgumentsTypes arguments) { FunctionElement function = element; getSendReturnType( null, function, currentWorkItem.environment.classOfThis, arguments); } } class TypeInferrerVisitor extends SimpleTypeInferrerVisitor { final ConcreteType thisType; ConcreteTypesInferrer get inferrer => super.inferrer; TypeInferrerVisitor(Element element, ConcreteTypesInferrer inferrer, this.thisType, Map environment, [LocalsHandler handler]) : super(element, inferrer.compiler, inferrer, handler); @override ConcreteType visitFunctionExpression(FunctionExpression node) { Element element = elements[node]; // visitFunctionExpression should be only called for closures assert(element != analyzedElement); inferrer.addClosure( element, thisType, new LocalsHandler.deepCopyOf(locals)); return types.functionType; } @override ConcreteType visitLiteralString(LiteralString node) { // TODO(polux): get rid of this hack once we have a natural way of inferring // the unknown type. if (inferrer.testMode && (node.dartString.slowToString() == "__dynamic_for_test")) { return inferrer.unknownConcreteType; } return super.visitLiteralString(node); } /** * Same as super.visitLiteralList except it doesn't cache anything. */ @override ConcreteType visitLiteralList(LiteralList node) { ConcreteType elementType; int length = 0; for (Node element in node.elements.nodes) { ConcreteType type = visit(element); elementType = elementType == null ? types.allocatePhi(null, null, type) : types.addPhiInput(null, elementType, type); length++; } elementType = elementType == null ? types.nonNullEmpty() : types.simplifyPhi(null, null, elementType); ConcreteType containerType = node.isConst ? types.constListType : types.growableListType; return types.allocateList( containerType, node, outermostElement, elementType, length); } /** * Same as super.visitGetterSend except it records the type of nodes in test * mode. */ @override ConcreteType visitGetterSend(Send node) { if (inferrer.testMode) { var element = elements[node]; if (element is Local) { ConcreteType type = locals.use(element); if (type != null) { inferrer.augmentInferredType(node, type); } } } return super.visitGetterSend(node); } }