Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(449)

Unified Diff: lib/compiler/implementation/types/concrete_types_inferrer.dart

Issue 10907028: Standalone concrete type inference (not integrated in the compiler). (Closed) Base URL: https://dart.googlecode.com/svn/branches/bleeding_edge/dart
Patch Set: fix tests Created 8 years, 2 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View side-by-side diff with in-line comments
Download patch
« no previous file with comments | « no previous file | lib/compiler/implementation/types/types.dart » ('j') | no next file with comments »
Expand Comments ('e') | Collapse Comments ('c') | Show Comments Hide Comments ('s')
Index: lib/compiler/implementation/types/concrete_types_inferrer.dart
diff --git a/lib/compiler/implementation/types/concrete_types_inferrer.dart b/lib/compiler/implementation/types/concrete_types_inferrer.dart
new file mode 100644
index 0000000000000000000000000000000000000000..60979c3f4aeb03f961108ce97f1c268ffa69bda3
--- /dev/null
+++ b/lib/compiler/implementation/types/concrete_types_inferrer.dart
@@ -0,0 +1,1227 @@
+// 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.
+
+class CancelTypeInferenceException {
+ final Node node;
+ final String reason;
+
+ CancelTypeInferenceException(this.node, this.reason);
+}
+
+/**
+ * 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 extends Hashable {
+ 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 ==(BaseType other) {
+ if (this === other) return true;
+ if (other is! ClassBaseType) return false;
+ return element == other.element;
+ }
+ int hashCode() => element.hashCode();
+ String toString() => element.name.slowToString();
+ bool isUnknown() => false;
+ bool isNull() => false;
+}
+
+/**
+ * The unknown base type.
+ */
+class UnknownBaseType implements BaseType {
+ const UnknownBaseType();
+ bool operator ==(BaseType other) => other is UnknownBaseType;
+ int hashCode() => 0;
+ bool isUnknown() => true;
+ bool isNull() => false;
+ toString() => "unknown";
+}
+
+/**
+ * The null base type.
+ */
+class NullBaseType implements BaseType {
+ const NullBaseType();
+ bool operator ==(BaseType other) => other is NullBaseType;
+ int hashCode() => 1;
+ 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 extends Hashable {
+ factory ConcreteType.empty() => new UnionType(new Set<BaseType>());
+
+ /**
+ * The singleton constituted of the unknown base type is the unknown concrete
+ * type.
+ */
+ factory ConcreteType.singleton(BaseType baseType) {
+ if (baseType.isUnknown()) {
+ return const UnknownConcreteType();
+ }
+ Set<BaseType> singletonSet = new Set<BaseType>();
+ singletonSet.add(baseType);
+ return new UnionType(singletonSet);
+ }
+
+ factory ConcreteType.unknown() => const UnknownConcreteType();
+
+ abstract ConcreteType union(ConcreteType other);
+ abstract bool isUnkown();
+ abstract Set<BaseType> get baseTypes();
+}
+
+/**
+ * The unkown concrete type: it is absorbing for the union.
+ */
+class UnknownConcreteType implements ConcreteType {
+ const UnknownConcreteType();
+ bool isUnkown() => true;
+ bool operator ==(ConcreteType other) => this === other;
+ Set<BaseType> get baseTypes() =>
+ new Set<BaseType>.from([const UnknownBaseType()]);
+ int hashCode() => 0;
+ ConcreteType union(ConcreteType other) => this;
+ toString() => "unknown";
+}
+
+/**
+ * An immutable set of base types, like [: {int, bool} :].
+ */
+class UnionType implements ConcreteType {
+ final Set<BaseType> baseTypes;
+
+ /**
+ * The argument should NOT be mutated later. Do not call directly, use
+ * ConcreteType.singleton instead.
+ */
+ UnionType(this.baseTypes);
+
+ bool isUnkown() => false;
+
+ bool operator ==(ConcreteType other) {
+ if (other is! UnionType) return false;
+ if (baseTypes.length != other.baseTypes.length) return false;
+ return baseTypes.containsAll(other.baseTypes);
+ }
+
+ int hashCode() {
+ int result = 1;
+ for (final baseType in baseTypes) {
+ result = 31 * result + baseType.hashCode();
+ }
+ return result;
+ }
+
+ ConcreteType union(ConcreteType other) {
+ if (other.isUnkown()) {
+ return const UnknownConcreteType();
+ }
+ UnionType otherUnion = other; // cast
+ Set<BaseType> newBaseTypes = new Set<BaseType>.from(baseTypes);
+ newBaseTypes.addAll(otherUnion.baseTypes);
+ return new UnionType(newBaseTypes);
+ }
+
+ String toString() => baseTypes.toString();
+}
+
+/**
+ * The cartesian product of concrete types: an iterable of [BaseTypeTuple]s. For
+ * instance, the cartesian product of the concrete types [: {A, B} :] and
+ * [: {C, D} :] is an itearble whose iterators will yield [: (A, C) :],
+ * [: (A, D) :], [: (B, C) :] and finally [: (B, D) :].
+ */
+class ConcreteTypeCartesianProduct
+ implements Iterable<ConcreteTypesEnvironment> {
+ final BaseType baseTypeOfThis;
+ final Map<Element, ConcreteType> concreteTypes;
+ ConcreteTypeCartesianProduct(this.baseTypeOfThis, this.concreteTypes);
+ Iterator iterator() => concreteTypes.isEmpty()
+ ? [new ConcreteTypesEnvironment(baseTypeOfThis)].iterator()
+ : new ConcreteTypeCartesianProductIterator(baseTypeOfThis, concreteTypes);
+ String toString() {
+ List<ConcreteTypesEnvironment> cartesianProduct =
+ new List<ConcreteTypesEnvironment>.from(this);
+ return cartesianProduct.toString();
+ }
+}
+
+/**
+ * An helper class for [ConcreteTypeCartesianProduct].
+ */
+class ConcreteTypeCartesianProductIterator implements Iterator {
+ final BaseType baseTypeOfThis;
+ final Map<Element, ConcreteType> concreteTypes;
+ final Map<Element, BaseType> nextValues;
+ final Map<Element, Iterator> state;
+ int size = 1;
+ int counter = 0;
+
+ ConcreteTypeCartesianProductIterator(this.baseTypeOfThis,
+ Map<Element, ConcreteType> concreteTypes) :
+ this.concreteTypes = concreteTypes,
+ nextValues = new Map<Element, BaseType>(),
+ state = new Map<Element, Iterator>() {
+ if (concreteTypes.isEmpty()) {
+ size = 0;
+ return;
+ }
+ for (final e in concreteTypes.getKeys()) {
+ final baseTypes = concreteTypes[e].baseTypes;
+ size *= baseTypes.length;
+ }
+ }
+
+ bool hasNext() {
+ return counter < size;
+ }
+
+ ConcreteTypesEnvironment takeSnapshot() {
+ Map<Element, ConcreteType> result = new Map<Element, ConcreteType>();
+ nextValues.forEach((k, v) { result[k] = new ConcreteType.singleton(v); });
+ return new ConcreteTypesEnvironment.of(result, baseTypeOfThis);
+ }
+
+ ConcreteTypesEnvironment next() {
+ if (!hasNext()) throw new NoMoreElementsException();
+ Element keyToIncrement = null;
+ for (final key in concreteTypes.getKeys()) {
+ final iterator = state[key];
+ if (iterator != null && iterator.hasNext()) {
+ nextValues[key] = state[key].next();
+ break;
+ }
+ Iterator newIterator = concreteTypes[key].baseTypes.iterator();
+ state[key] = newIterator;
+ nextValues[key] = newIterator.next();
+ }
+ counter++;
+ return takeSnapshot();
+ }
+}
+
+/**
+ * [BaseType] Constants.
+ */
+class BaseTypes {
+ final BaseType intBaseType;
+ final BaseType doubleBaseType;
+ final BaseType boolBaseType;
+ final BaseType stringBaseType;
+ final BaseType objectBaseType;
+
+ BaseTypes(Compiler compiler) :
+ intBaseType = new ClassBaseType(compiler.intClass),
+ doubleBaseType = new ClassBaseType(compiler.doubleClass),
+ boolBaseType = new ClassBaseType(compiler.boolClass),
+ stringBaseType = new ClassBaseType(compiler.stringClass),
+ objectBaseType = new ClassBaseType(compiler.objectClass);
+}
+
+/**
+ * A method-local immutable mapping from variables to their inferred
+ * [ConcreteTypes]. Each visitor owns one.
+ */
+class ConcreteTypesEnvironment implements Hashable {
+ final Map<Element, ConcreteType> environment;
+ final BaseType typeOfThis;
+ ConcreteTypesEnvironment([this.typeOfThis]) :
+ this.environment = new Map<Element, ConcreteType>();
+ ConcreteTypesEnvironment.of(this.environment, this.typeOfThis);
+
+ ConcreteType lookupType(Element element) => environment[element];
+ ConcreteType lookupTypeOfThis() {
+ return (typeOfThis === null)
+ ? null
+ : new ConcreteType.singleton(typeOfThis);
+ }
+
+ ConcreteTypesEnvironment put(Element element, ConcreteType type) {
+ Map<Element, ConcreteType> newMap =
+ new Map<Element, ConcreteType>.from(environment);
+ newMap[element] = type;
+ return new ConcreteTypesEnvironment.of(newMap, typeOfThis);
+ }
+
+ ConcreteTypesEnvironment join(ConcreteTypesEnvironment other) {
+ if (typeOfThis != other.typeOfThis) {
+ throw "trying to join incompatible environments";
+ }
+ Map<Element, ConcreteType> newMap =
+ new Map<Element, ConcreteType>.from(environment);
+ other.environment.forEach((element, type) {
+ ConcreteType currentType = newMap[element];
+ if (element == null) {
+ newMap[element] = type;
+ } else {
+ newMap[element] = currentType.union(type);
+ }
+ });
+ return new ConcreteTypesEnvironment.of(newMap, typeOfThis);
+ }
+
+ bool operator ==(ConcreteTypesEnvironment other) {
+ if (other is! ConcreteTypesEnvironment) return false;
+ if (typeOfThis != other.typeOfThis) return false;
+ if (environment.length != other.environment.length) return false;
+ for (Element key in environment.getKeys()) {
+ if (!other.environment.containsKey(key)
+ || (environment[key] != other.environment[key])) {
+ return false;
+ }
+ }
+ return true;
+ }
+
+ int hashCode() {
+ int result = (typeOfThis !== null) ? typeOfThis.hashCode() : 1;
+ environment.forEach((element, concreteType) {
+ result = 31 * (31 * result + element.hashCode()) +
+ concreteType.hashCode();
+ });
+ return result;
+ }
+
+ String toString() => "{ this: $typeOfThis, env: ${environment.toString()} }";
+}
+
+/**
+ * A work item for the type inference queue.
+ */
+class InferenceWorkItem {
+ FunctionElement method;
+ ConcreteTypesEnvironment environment;
+ InferenceWorkItem(this.method, this.environment);
+
+ toString() => "{ method = ${method.name.slowToString()}, "
+ "environment = $environment }";
+}
+
+/**
+ * A task which conservatively infers a [ConcreteType] for each sub expression
+ * of the program. The entry point is [analyzeMain].
+ */
+class ConcreteTypesInferrer {
+
+ static final bool LOG_FAILURES = true;
+
+ final String name = "Type inferrer";
+
+ final Compiler compiler;
+
+ /**
+ * Constants representing builtin base types. Initialized in [analyzeMain]
+ * and not in the constructor because the compiler elements are not yet
+ * populated.
+ */
+ BaseTypes baseTypes;
+
+ /**
+ * A cache from (function x argument base types) to concrete types,
+ * used to memoize [analyzeMonoSend]. Another way of seeing [cache] 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.
+ */
+ final Map<FunctionElement, Map<ConcreteTypesEnvironment, ConcreteType>> cache;
+
+ /** A map from expressions to their inferred concrete types. */
+ final Map<Node, ConcreteType> inferredTypes;
+
+ /** A map from fields to their inferred concrete types. */
+ final Map<Element, ConcreteType> inferredFieldTypes;
+
+ /** The work queue consumed by [analyzeMain]. */
+ final Queue<InferenceWorkItem> workQueue;
+
+ /** [: callers[f] :] is the list of [: f :]'s possible callers. */
+ final Map<FunctionElement, Set<FunctionElement>> callers;
+
+ /** [: readers[field] :] is the list of [: field :]'s possible readers. */
+ final Map<Element, Set<FunctionElement>> readers;
+
+ /**
+ * A map from parameters to their inferred concrete types. It plays no role
+ * in the analysis, it is write only.
+ */
+ final Map<VariableElement, ConcreteType> inferredParameterTypes;
+
+ ConcreteTypesInferrer(Compiler compiler)
+ : this.compiler = compiler,
+ cache = new Map<FunctionElement,
+ Map<ConcreteTypesEnvironment, ConcreteType>>(),
+ inferredTypes = new Map<Node, ConcreteType>(),
+ inferredFieldTypes = new Map<Element, ConcreteType>(),
+ inferredParameterTypes = new Map<VariableElement, ConcreteType>(),
+ workQueue = new Queue<InferenceWorkItem>(),
+ callers = new Map<FunctionElement, Set<FunctionElement>>(),
+ readers = new Map<Element, Set<FunctionElement>>();
+
+ // --- utility methods ---
+
+ /**
+ * Returns all the members with name [methodName].
+ */
+ List<FunctionElement> getMembersByName(SourceString methodName) {
+ // TODO(polux): make this faster!
+ var result = new List<FunctionElement>();
+ for (final cls in compiler.enqueuer.resolution.seenClasses) {
+ Element elem = cls.lookupLocalMember(methodName);
+ if (elem !== null) {
+ result.add(elem);
+ }
+ }
+ return result;
+ }
+
+ /**
+ * Sets the concrete type associated to [node] to the union of the inferred
+ * concrete type so far and [type].
+ */
+ void augmentInferredType(Node node, ConcreteType type) {
+ ConcreteType currentType = inferredTypes[node];
+ inferredTypes[node] = (currentType == null)
+ ? type
+ : currentType.union(type);
+ }
+
+ /**
+ * Returns the current inferred concrete type of [field].
+ */
+ ConcreteType getFieldType(Element field) {
+ ConcreteType result = inferredFieldTypes[field];
+ return (result == null) ? new ConcreteType.empty() : result;
+ }
+
+ /**
+ * Sets the concrete type associated to [field] to the union of the inferred
+ * concrete type so far and [type].
+ */
+ void augmentFieldType(Element field, ConcreteType type) {
+ ConcreteType oldType = inferredFieldTypes[field];
+ ConcreteType newType = (oldType !== null)
+ ? oldType.union(type)
+ : type;
+ if (oldType != newType) {
+ inferredFieldTypes[field] = newType;
+ final fieldReaders = readers[field];
+ if (fieldReaders != null) {
+ for (final reader in fieldReaders) {
+ final readerInstances = cache[reader];
+ if (readerInstances !== null) {
+ readerInstances.forEach((environment, _) {
+ workQueue.addLast(new InferenceWorkItem(reader, environment));
+ });
+ }
+ }
+ }
+ }
+ }
+
+ /**
+ * Sets the concrete type associated to [parameter] to the union of the
+ * inferred concrete type so far and [type].
+ */
+ void augmentParameterType(VariableElement parameter, ConcreteType type) {
+ ConcreteType oldType = inferredParameterTypes[parameter];
+ inferredParameterTypes[parameter] =
+ (oldType === null) ? type : oldType.union(type);
+ }
+
+ /**
+ * Add [caller] to the set of [callee]'s callers.
+ */
+ void addCaller(FunctionElement callee, FunctionElement caller) {
+ Set<FunctionElement> current = callers[callee];
+ if (current != null) {
+ current.add(caller);
+ } else {
+ Set<FunctionElement> newSet = new Set<FunctionElement>();
+ newSet.add(caller);
+ callers[callee] = newSet;
+ }
+ }
+
+ /**
+ * Add [reader] to the set of [field]'s readers.
+ */
+ void addReader(Element field, FunctionElement reader) {
+ Set<FunctionElement> current = readers[field];
+ if (current != null) {
+ current.add(reader);
+ } else {
+ Set<FunctionElement> newSet = new Set<FunctionElement>();
+ newSet.add(reader);
+ readers[field] = newSet;
+ }
+ }
+
+ // -- query --
+
+ /**
+ * Get the inferred concrete type of [node].
+ */
+ ConcreteType getConcreteTypeOfNode(Node node) => inferredTypes[node];
+
+ /**
+ * Get the inferred concrete type of [parameter].
+ */
+ ConcreteType getConcreteTypeOfParameter(VariableElement parameter) {
+ return inferredParameterTypes[parameter];
+ }
+
+ // --- 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 the class
+ * of [receiverType].
+ */
+ ConcreteType getSendReturnType(FunctionElement function,
+ BaseType receiverType,
+ ArgumentsTypes argumentsTypes) {
+ ConcreteType result = new ConcreteType.empty();
+ Map<Element, ConcreteType> argumentMap =
+ associateArguments(function, argumentsTypes);
+ argumentMap.forEach((Element parameter, ConcreteType type) {
+ augmentParameterType(parameter, type);
+ });
+ // if the association failed, this send will never occur or will fail
+ if (argumentMap == null) {
+ return new ConcreteType.empty();
+ }
+ ConcreteTypeCartesianProduct product =
+ new ConcreteTypeCartesianProduct(receiverType, argumentMap);
+ for (ConcreteTypesEnvironment environment in product) {
+ result = result.union(
+ getMonomorphicSendReturnType(function, environment));
+ }
+ 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<Element, ConcreteType> associateArguments(FunctionElement function,
+ ArgumentsTypes argumentsTypes) {
+ final Map<Element, ConcreteType> result = new Map<Element, ConcreteType>();
+ final FunctionSignature signature = function.functionSignature;
+ // too many arguments
+ if (argumentsTypes.length > signature.parameterCount) {
+ return null;
+ }
+ // not enough arguments
+ if (argumentsTypes.positional.length < signature.requiredParameterCount) {
+ return null;
+ }
+ final Iterator<ConcreteType> remainingPositionalArguments =
+ argumentsTypes.positional.iterator();
+ // we attach each positional parameter to its corresponding positional
+ // argument
+ for (Link<Element> requiredParameters = signature.requiredParameters;
+ !requiredParameters.isEmpty();
+ requiredParameters = requiredParameters.tail) {
+ final Element requiredParameter = requiredParameters.head;
+ // we know next() is defined because of the guard above
+ result[requiredParameter] = remainingPositionalArguments.next();
+ }
+ // we attach the remaining positional arguments to their corresponding
+ // named arguments
+ Link<Element> remainingNamedParameters = signature.optionalParameters;
+ while (remainingPositionalArguments.hasNext()) {
+ final Element namedParameter = remainingNamedParameters.head;
+ result[namedParameter] = remainingPositionalArguments.next();
+ // we know tail is defined because of the guard above
+ remainingNamedParameters = remainingNamedParameters.tail;
+ }
+ // we build a map out of the remaining named parameters
+ final Map<SourceString, Element> leftOverNamedParameters =
+ new Map<SourceString, Element>();
+ for (;
+ !remainingNamedParameters.isEmpty();
+ remainingNamedParameters = remainingNamedParameters.tail) {
+ final Element namedParameter = remainingNamedParameters.head;
+ leftOverNamedParameters[namedParameter.name] = namedParameter;
+ }
+ // we attach the named arguments to their corresponding named paramaters
+ // (we don't use foreach because we want to be able to return early)
+ for (Identifier identifier in argumentsTypes.named.getKeys()) {
+ final ConcreteType concreteType = argumentsTypes.named[identifier];
+ SourceString source = identifier.source;
+ final Element namedParameter = leftOverNamedParameters[source];
+ // unexisting or already used named parameter
+ if (namedParameter === null) return null;
+ result[namedParameter] = concreteType;
+ leftOverNamedParameters.remove(source);
+ };
+ // we use null for each unused named parameter
+ // TODO(polux): use default value whenever available
+ // TODO(polux): add a marker to indicate whether an argument was provided
+ // in order to handle "?parameter" tests
+ leftOverNamedParameters.forEach((_, Element namedParameter) {
+ result[namedParameter] =
+ new ConcreteType.singleton(const NullBaseType());
+ });
+ return result;
+ }
+
+ ConcreteType getMonomorphicSendReturnType(
+ FunctionElement function,
+ ConcreteTypesEnvironment environment) {
+
+ Map<ConcreteTypesEnvironment, ConcreteType> template = cache[function];
+ if (template === null) {
+ template = new Map<ConcreteTypesEnvironment, ConcreteType>();
+ cache[function] = template;
+ }
+ ConcreteType type = template[environment];
+ if (type != null) {
+ return type;
+ } else {
+ workQueue.addLast(
+ new InferenceWorkItem(function, environment));
+ // in case of a constructor, optimize by returning the class
+ return new ConcreteType.empty();
+ }
+ }
+
+ ConcreteType analyze(FunctionElement element,
+ ConcreteTypesEnvironment environment) {
+ return element.isGenerativeConstructor()
+ ? analyzeConstructor(element, environment)
+ : analyzeMethod(element, environment);
+ }
+
+ ConcreteType analyzeMethod(FunctionElement element,
+ ConcreteTypesEnvironment environment) {
+ FunctionExpression tree = element.parseNode(compiler);
+ TreeElements elements =
+ compiler.enqueuer.resolution.resolvedElements[element];
+ Visitor visitor =
+ new TypeInferrerVisitor(elements, element, this, environment);
+ return tree.accept(visitor);
+ }
+
+ ConcreteType analyzeConstructor(FunctionElement element,
+ ConcreteTypesEnvironment environment) {
+ ClassElement enclosingClass = element.enclosingElement;
+ FunctionExpression tree = compiler.parser.parse(element);
+ TreeElements elements =
+ compiler.enqueuer.resolution.resolvedElements[element];
+ Visitor visitor =
+ new TypeInferrerVisitor(elements, element, this, environment);
+
+ // handle initializing formals
+ element.functionSignature.forEachParameter((param) {
+ if (param.kind == ElementKind.FIELD_PARAMETER) {
+ FieldParameterElement fieldParam = param;
+ augmentFieldType(fieldParam.fieldElement,
+ environment.lookupType(param));
+ }
+ });
+
+ // analyze initializers, including a possible call to super or a redirect
+ bool foundSuperOrRedirect = false;
+ if (tree.initializers != null) {
+ // we look for a possible call to super in the initializer list
+ for (final init in tree.initializers) {
+ init.accept(visitor);
+ if (init.asSendSet() == null) {
+ foundSuperOrRedirect = true;
+ }
+ }
+ }
+
+ // if no call to super or redirect has been found, call the default
+ // constructor (if the current class is not Object).
+ if (!foundSuperOrRedirect) {
+ ClassElement superClass = enclosingClass.superclass;
+ if (enclosingClass != compiler.objectClass) {
+ FunctionElement target = superClass.lookupConstructor(superClass.name);
+ final superClassConcreteType = new ConcreteType.singleton(
+ new ClassBaseType(enclosingClass));
+ getSendReturnType(target, new ClassBaseType(enclosingClass),
+ new ArgumentsTypes(new List(), new Map()));
+ }
+ }
+
+ tree.accept(visitor);
+ return new ConcreteType.singleton(new ClassBaseType(enclosingClass));
+ }
+
+ void analyzeMain(Element element) {
+ baseTypes = new BaseTypes(compiler);
+ cache[element] = new Map<ConcreteTypesEnvironment, ConcreteType>();
+ try {
+ workQueue.addLast(
+ new InferenceWorkItem(element, new ConcreteTypesEnvironment()));
+ while (!workQueue.isEmpty()) {
+ InferenceWorkItem item = workQueue.removeFirst();
+ ConcreteType concreteType = analyze(item.method, item.environment);
+ var template = cache[item.method];
+ if (template[item.environment] == concreteType) continue;
+ template[item.environment] = concreteType;
+ final methodCallers = callers[item.method];
+ if (methodCallers == null) continue;
+ for (final caller in methodCallers) {
+ final callerInstances = cache[caller];
+ if (callerInstances !== null) {
+ callerInstances.forEach((environment, _) {
+ workQueue.addLast(
+ new InferenceWorkItem(caller, environment));
+ });
+ }
+ }
+ }
+ } on CancelTypeInferenceException catch(e) {
+ if (LOG_FAILURES) {
+ compiler.log("'${e.node}': ${e.reason}");
+ }
+ }
+ }
+
+ /**
+ * Dumps debugging information on the standard output.
+ */
+ void debug() {
+ print("callers :");
+ callers.forEach((k,v) {
+ print(" $k: $v");
+ });
+ print("readers :");
+ readers.forEach((k,v) {
+ print(" $k: $v");
+ });
+ print("inferredFieldTypes:");
+ inferredFieldTypes.forEach((k,v) {
+ print(" $k: $v");
+ });
+ print("inferredParameterTypes:");
+ inferredParameterTypes.forEach((k,v) {
+ print(" $k: $v");
+ });
+ print("cache:");
+ cache.forEach((k,v) {
+ print(" $k: $v");
+ });
+ print("inferred expression types: ");
+ inferredTypes.forEach((k,v) {
+ print(" $k: $v");
+ });
+ }
+
+ /**
+ * Fail with a message and abort.
+ */
+ void fail(node, [reason]) {
+ throw "fail: ${node.toDebugString()}";
+ String message = 'cannot infer types';
+ if (reason !== null) {
+ message = '$message: $reason';
+ }
+ throw new CancelTypeInferenceException(node, message);
+ }
+}
+
+/**
+ * Represents the concrete types of the arguments of a send, indexed by
+ * position or name.
+ */
+class ArgumentsTypes {
+ final List<ConcreteType> positional;
+ final Map<Identifier, ConcreteType> named;
+ ArgumentsTypes(this.positional, this.named);
+ int get length() => positional.length + named.length;
+ toString() => "{ positional = $positional, named = $named }";
+}
+
+/**
+ * The core logic of the type inference algorithm.
+ */
+class TypeInferrerVisitor extends ResolvedVisitor<ConcreteType> {
+ final ConcreteTypesInferrer inferrer;
+
+ final FunctionElement currentMethod;
+ ConcreteTypesEnvironment environment;
+ Node lastSeenNode;
+
+ TypeInferrerVisitor(TreeElements elements, this.currentMethod, this.inferrer,
+ this.environment)
+ : super(elements);
+
+ ArgumentsTypes analyzeArguments(Link<Node> arguments) {
+ final positional = new List<ConcreteType>();
+ final named = new Map<Identifier, ConcreteType>();
+ for(Link<Node> iterator = arguments;
+ !iterator.isEmpty();
+ iterator = iterator.tail) {
+ Node node = iterator.head;
+ NamedArgument namedArgument = node.asNamedArgument();
+ if (namedArgument !== null) {
+ named[namedArgument.name] = analyze(namedArgument.expression);
+ } else {
+ positional.add(analyze(node));
+ }
+ }
+ return new ArgumentsTypes(positional, named);
+ }
+
+ /**
+ * A proxy to accept which does book keeping and error reporting. Returns null
+ * if [node] is a non-returning statement, its inferred concrete type
+ * otherwise.
+ */
+ ConcreteType analyze(Node node) {
+ if (node === null) {
+ final String error = 'internal error: unexpected node: null';
+ inferrer.fail(lastSeenNode, error);
+ } else {
+ lastSeenNode = node;
+ }
+ ConcreteType result = node.accept(this);
+ if (result === null) {
+ inferrer.fail(node, 'internal error: inferred type is null');
+ }
+ inferrer.augmentInferredType(node, result);
+ return result;
+ }
+
+ ConcreteType visitBlock(Block node) {
+ return analyze(node.statements);
+ }
+
+ ConcreteType visitCascade(Cascade node) {
+ inferrer.fail(node, 'not yet implemented');
+ }
+
+ ConcreteType visitCascadeReceiver(CascadeReceiver node) {
+ inferrer.fail(node, 'not yet implemented');
+ }
+
+ ConcreteType visitClassNode(ClassNode node) {
+ inferrer.fail(node, 'not implemented');
+ }
+
+ ConcreteType visitDoWhile(DoWhile node) {
+ inferrer.fail(node, 'not yet implemented');
+ }
+
+ ConcreteType visitExpressionStatement(ExpressionStatement node) {
+ analyze(node.expression);
+ return new ConcreteType.empty();
+ }
+
+ ConcreteType visitFor(For node) {
+ inferrer.fail(node, 'not yet implemented');
+ }
+
+ ConcreteType visitFunctionDeclaration(FunctionDeclaration node) {
+ inferrer.fail(node, 'not yet implemented');
+ }
+
+ ConcreteType visitFunctionExpression(FunctionExpression node) {
+ return analyze(node.body);
+ }
+
+ ConcreteType visitIdentifier(Identifier node) {
+ if (node.isThis()) {
+ ConcreteType result = environment.lookupTypeOfThis();
+ if (result === null) {
+ inferrer.fail(node, '"this" has no type');
+ }
+ return result;
+ }
+ inferrer.fail(node, 'not yet implemented');
+ }
+
+ ConcreteType visitIf(If node) {
+ analyze(node.condition);
+ ConcreteType thenType = analyze(node.thenPart);
+ ConcreteTypesEnvironment snapshot = environment;
+ ConcreteType elseType = node.hasElsePart ? analyze(node.elsePart)
+ : new ConcreteType.empty();
+ environment = environment.join(snapshot);
+ return thenType.union(elseType);
+ }
+
+ ConcreteType visitLoop(Loop node) {
+ inferrer.fail(node, 'not yet implemented');
+ }
+
+ // TODO(polux): handle sendset as expression
+ ConcreteType visitSendSet(SendSet node) {
+ Identifier selector = node.selector;
+ final name = node.assignmentOperator.source.stringValue;
+ if (name === '++' || name === '--') {
+ inferrer.fail(node, 'not yet implemented');
+ } else {
+ Element element = elements[node];
+ if (element !== null) {
+ ConcreteType type = analyze(node.argumentsNode);
+ environment = environment.put(elements[node], type);
+ if (element.isField()) {
+ inferrer.augmentFieldType(element, type);
+ }
+ } else {
+ ConcreteType receiverType = analyze(node.receiver);
+ ConcreteType type = analyze(node.argumentsNode);
+ SourceString source = node.selector.asIdentifier().source;
+
+ void augmentField(BaseType baseReceiverType, Element fieldOrSetter) {
+ if (fieldOrSetter.isField()) {
+ inferrer.augmentFieldType(fieldOrSetter, type);
+ } else {
+ FunctionElement setter =
+ (fieldOrSetter as AbstractFieldElement).setter;
+ // TODO: uncomment if we add an effect system
+ //inferrer.addCaller(setter, currentMethod);
+ inferrer.getSendReturnType(setter, baseReceiverType,
+ new ArgumentsTypes([type], new Map()));
+ }
+ }
+
+ if (receiverType.isUnkown()) {
+ for (final member in inferrer.getMembersByName(source)) {
+ Element classElem = member.enclosingElement;
+ BaseType baseReceiverType = new ClassBaseType(classElem);
+ augmentField(baseReceiverType, member);
+ }
+ } else {
+ for (ClassBaseType baseReceiverType in receiverType.baseTypes) {
+ Element member = baseReceiverType.element.lookupMember(source);
+ if (member !== null) {
+ augmentField(baseReceiverType, member);
+ }
+ }
+ }
+ }
+ }
+ return new ConcreteType.empty();
+ }
+
+ ConcreteType visitLiteralInt(LiteralInt node) {
+ return new ConcreteType.singleton(inferrer.baseTypes.intBaseType);
+ }
+
+ ConcreteType visitLiteralDouble(LiteralDouble node) {
+ return new ConcreteType.singleton(inferrer.baseTypes.doubleBaseType);
+ }
+
+ ConcreteType visitLiteralBool(LiteralBool node) {
+ return new ConcreteType.singleton(inferrer.baseTypes.boolBaseType);
+ }
+
+ ConcreteType visitLiteralString(LiteralString node) {
+ return new ConcreteType.singleton(inferrer.baseTypes.stringBaseType);
+ }
+
+ ConcreteType visitStringJuxtaposition(StringJuxtaposition node) {
+ analyze(node.first);
+ analyze(node.second);
+ return new ConcreteType.singleton(inferrer.baseTypes.stringBaseType);
+ }
+
+ ConcreteType visitLiteralNull(LiteralNull node) {
+ return new ConcreteType.singleton(const NullBaseType());
+ }
+
+ ConcreteType visitNewExpression(NewExpression node) {
+ Element constructor = elements[node.send];
+ inferrer.addCaller(constructor, currentMethod);
+ ClassElement cls = constructor.enclosingElement;
+ return inferrer.getSendReturnType(constructor,
+ new ClassBaseType(cls), analyzeArguments(node.send.arguments));
+ }
+
+ ConcreteType visitLiteralList(LiteralList node) {
+ inferrer.fail(node, 'not yet implemented');
+ }
+
+ ConcreteType visitNodeList(NodeList node) {
+ ConcreteType type = new ConcreteType.empty();
+ // The concrete type of a sequence of statements is the union of the
+ // statement's types.
+ for (Link<Node> link = node.nodes; !link.isEmpty(); link = link.tail) {
+ type = type.union(analyze(link.head));
+ }
+ return type;
+ }
+
+ ConcreteType visitOperator(Operator node) {
+ inferrer.fail(node, 'not yet implemented');
+ }
+
+ /** Dart Programming Language Specification: 11.10 Return */
+ ConcreteType visitReturn(Return node) {
+ final expression = node.expression;
+ return (expression === null)
+ ? new ConcreteType.empty()
+ : analyze(expression);
+ }
+
+ ConcreteType visitThrow(Throw node) {
+ if (node.expression !== null) analyze(node.expression);
+ return new ConcreteType.empty();
+ }
+
+ ConcreteType visitTypeAnnotation(TypeAnnotation node) {
+ inferrer.fail(node, 'not yet implemented');
+ }
+
+ ConcreteType visitTypeVariable(TypeVariable node) {
+ inferrer.fail(node, 'not yet implemented');
+ }
+
+ ConcreteType visitVariableDefinitions(VariableDefinitions node) {
+ for (Link<Node> link = node.definitions.nodes; !link.isEmpty();
+ link = link.tail) {
+ analyze(link.head);
+ }
+ return new ConcreteType.empty();
+ }
+
+ ConcreteType visitWhile(While node) {
+ analyze(node.condition);
+ ConcreteType result = new ConcreteType.empty();
+ ConcreteTypesEnvironment oldEnvironment;
+ do {
+ oldEnvironment = environment;
+ analyze(node.condition);
+ analyze(node.body);
+ environment = oldEnvironment.join(environment);
+ } while (oldEnvironment != environment);
+ return result;
+ }
+
+ ConcreteType visitParenthesizedExpression(ParenthesizedExpression node) {
+ return analyze(node.expression);
+ }
+
+ ConcreteType visitConditional(Conditional node) {
+ analyze(node.condition);
+ ConcreteType thenType = analyze(node.thenExpression);
+ ConcreteType elseType = analyze(node.elseExpression);
+ return thenType.union(elseType);
+ }
+
+ ConcreteType visitModifiers(Modifiers node) {
+ inferrer.fail(node, 'not yet implemented');
+ }
+
+ ConcreteType visitStringInterpolation(StringInterpolation node) {
+ node.visitChildren(this);
+ return new ConcreteType.singleton(inferrer.baseTypes.stringBaseType);
+ }
+
+ ConcreteType visitStringInterpolationPart(StringInterpolationPart node) {
+ node.visitChildren(this);
+ return new ConcreteType.singleton(inferrer.baseTypes.stringBaseType);
+ }
+
+ ConcreteType visitEmptyStatement(EmptyStatement node) {
+ return new ConcreteType.empty();
+ }
+
+ ConcreteType visitBreakStatement(BreakStatement node) {
+ return new ConcreteType.empty();
+ }
+
+ ConcreteType visitContinueStatement(ContinueStatement node) {
+ return new ConcreteType.empty();
+ }
+
+ ConcreteType visitForIn(ForIn node) {
+ inferrer.fail(node, 'not yet implemented');
+ }
+
+ ConcreteType visitLabel(Label node) {
+ inferrer.fail(node, 'not yet implemented');
+ }
+
+ ConcreteType visitLabeledStatement(LabeledStatement node) {
+ return analyze(node.statement);
+ }
+
+ ConcreteType visitLiteralMap(LiteralMap node) {
+ inferrer.fail(node, 'not yet implemented');
+ }
+
+ ConcreteType visitLiteralMapEntry(LiteralMapEntry node) {
+ inferrer.fail(node, 'not yet implemented');
+ }
+
+ ConcreteType visitNamedArgument(NamedArgument node) {
+ inferrer.fail(node, 'not yet implemented');
+ }
+
+ ConcreteType visitSwitchStatement(SwitchStatement node) {
+ inferrer.fail(node, 'not yet implemented');
+ }
+
+ ConcreteType visitSwitchCase(SwitchCase node) {
+ inferrer.fail(node, 'not yet implemented');
+ }
+
+ ConcreteType visitCaseMatch(CaseMatch node) {
+ inferrer.fail(node, 'not yet implemented');
+ }
+
+ ConcreteType visitTryStatement(TryStatement node) {
+ inferrer.fail(node, 'not yet implemented');
+ }
+
+ ConcreteType visitScriptTag(ScriptTag node) {
+ inferrer.fail(node, 'not yet implemented');
+ }
+
+ ConcreteType visitCatchBlock(CatchBlock node) {
+ inferrer.fail(node, 'not yet implemented');
+ }
+
+ ConcreteType visitTypedef(Typedef node) {
+ inferrer.fail(node, 'not implemented');
+ }
+
+ ConcreteType visitSuperSend(Send node) {
+ inferrer.fail(node, 'not implemented');
+ }
+
+ ConcreteType visitOperatorSend(Send node) {
+ inferrer.fail(node, 'not implemented');
+ }
+
+ ConcreteType visitGetterSend(Send node) {
+ Element element = elements[node];
+ if (element !== null) {
+ // node is a local variable or a field of this
+ ConcreteType result = environment.lookupType(element);
+ if (result !== null) {
+ // node is a local variable
+ return result;
+ } else {
+ // node is a field of this
+ inferrer.addReader(element, currentMethod);
+ return inferrer.getFieldType(element);
+ }
+ } else {
+ // node is a field of not(this)
+ assert(node.receiver !== null);
+
+ ConcreteType result = new ConcreteType.empty();
+ void augmentResult(BaseType baseReceiverType, Element getterOrField) {
+ if (getterOrField.isField()) {
+ inferrer.addReader(getterOrField, currentMethod);
+ result = result.union(inferrer.getFieldType(getterOrField));
+ } else {
+ // call to a getter
+ FunctionElement getter =
+ (getterOrField as AbstractFieldElement).getter;
+ inferrer.addCaller(getter, currentMethod);
+ ConcreteType returnType =
+ inferrer.getSendReturnType(getter,
+ baseReceiverType,
+ new ArgumentsTypes([], new Map()));
+ result = result.union(returnType);
+ }
+ }
+
+ ConcreteType receiverType = analyze(node.receiver);
+ if (receiverType.isUnkown()) {
+ List<Element> members =
+ inferrer.getMembersByName(node.selector.asIdentifier().source);
+ for (final member in members) {
+ Element classElement = member.enclosingElement;
+ ClassBaseType baseReceiverType = new ClassBaseType(classElement);
+ augmentResult(baseReceiverType, member);
+ }
+ } else {
+ for (BaseType baseReceiverType in receiverType.baseTypes) {
+ if (!baseReceiverType.isNull()) {
+ ClassBaseType classBaseType = baseReceiverType;
+ Element getterOrField = classBaseType.element
+ .lookupMember(node.selector.asIdentifier().source);
+ if (getterOrField !== null) {
+ augmentResult(baseReceiverType, getterOrField);
+ }
+ }
+ }
+ }
+ return result;
+ }
+ }
+
+ ConcreteType visitClosureSend(Send node) {
+ inferrer.fail(node, 'not implemented');
+ }
+
+ ConcreteType visitDynamicSend(Send node) {
+ ConcreteType receiverType = analyze(node.receiver);
+ ConcreteType result = new ConcreteType.empty();
+ final argumentsTypes = analyzeArguments(node.arguments);
+
+ if (receiverType.isUnkown()) {
+ List<FunctionElement> methods =
+ inferrer.getMembersByName(node.selector.asIdentifier().source);
+ for (final method in methods) {
+ inferrer.addCaller(method, currentMethod);
+ Element classElem = method.enclosingElement;
+ ClassBaseType baseReceiverType = new ClassBaseType(classElem);
+ result = result.union(
+ inferrer.getSendReturnType(method, baseReceiverType, argumentsTypes));
+ }
+
+ } else {
+ for (BaseType baseReceiverType in receiverType.baseTypes) {
+ if (!baseReceiverType.isNull()) {
+ FunctionElement method = (baseReceiverType as ClassBaseType).element
+ .lookupMember(node.selector.asIdentifier().source);
+ if (method !== null) {
+ inferrer.addCaller(method, currentMethod);
+ result = result.union(inferrer.getSendReturnType(method,
+ baseReceiverType, argumentsTypes));
+ }
+ }
+ }
+ }
+ return result;
+ }
+
+ ConcreteType visitForeignSend(Send node) {
+ inferrer.fail(node, 'not implemented');
+ }
+
+ ConcreteType visitStaticSend(Send node) {
+ Element element = elements[node];
+ inferrer.addCaller(element, currentMethod);
+ return inferrer.getSendReturnType(element, null,
+ analyzeArguments(node.arguments));
+ }
+
+ void internalError(String reason, [Node node]) {
+ inferrer.fail(node, reason);
+ }
+}
« no previous file with comments | « no previous file | lib/compiler/implementation/types/types.dart » ('j') | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698