blob: 75e35e1e707a86550ae10745fd33641e46861c64 [file] [log] [blame]
// Copyright (c) 2013, the Dart project authors. Please see the AUTHORS file
// for details. All rights reserved. Use of this source code is governed by a
// BSD-style license that can be found in the LICENSE file.
part of simple_types_inferrer;
TypeMask narrowType(TypeMask type,
DartType annotation,
Compiler compiler,
{bool isNullable: true}) {
if (annotation.isDynamic) return type;
if (annotation.isMalformed) return type;
if (annotation.isVoid) return compiler.typesTask.nullType;
if (annotation.element == compiler.objectClass) return type;
TypeMask otherType;
if (annotation.kind == TypeKind.TYPEDEF
|| annotation.kind == TypeKind.FUNCTION) {
otherType = compiler.typesTask.typesInferrer.functionType;
} else if (annotation.kind == TypeKind.TYPE_VARIABLE) {
return type;
} else {
assert(annotation.kind == TypeKind.INTERFACE);
otherType = new TypeMask.nonNullSubtype(annotation);
}
if (isNullable) otherType = otherType.nullable();
if (type == null) return otherType;
return type.intersection(otherType, compiler);
}
/**
* Returns the least upper bound between [firstType] and
* [secondType].
*/
TypeMask computeLUB(TypeMask firstType,
TypeMask secondType,
Compiler compiler) {
TypeMask dynamicType = compiler.typesTask.dynamicType;
if (firstType == null) {
return secondType;
} else if (secondType == dynamicType) {
return secondType;
} else if (firstType == dynamicType) {
return firstType;
} else {
TypeMask union = firstType.union(secondType, compiler);
// TODO(kasperl): If the union isn't nullable it seems wasteful
// to use dynamic. Fix that.
return union.containsAll(compiler) ? dynamicType : union;
}
}
/**
* Placeholder for inferred types of local variables.
*/
class LocalsHandler {
final Compiler compiler;
final TypesInferrer inferrer;
final Map<Element, TypeMask> locals;
final Map<Element, Element> capturedAndBoxed;
final Map<Element, TypeMask> fieldsInitializedInConstructor;
final bool inTryBlock;
bool isThisExposed;
bool seenReturn = false;
bool seenBreakOrContinue = false;
bool get aborts {
return seenReturn || seenBreakOrContinue;
}
LocalsHandler(this.inferrer, this.compiler)
: locals = new Map<Element, TypeMask>(),
capturedAndBoxed = new Map<Element, Element>(),
fieldsInitializedInConstructor = new Map<Element, TypeMask>(),
inTryBlock = false,
isThisExposed = true;
LocalsHandler.from(LocalsHandler other, {bool inTryBlock: false})
: locals = new Map<Element, TypeMask>.from(other.locals),
capturedAndBoxed = new Map<Element, Element>.from(
other.capturedAndBoxed),
fieldsInitializedInConstructor = new Map<Element, TypeMask>.from(
other.fieldsInitializedInConstructor),
inTryBlock = other.inTryBlock || inTryBlock,
inferrer = other.inferrer,
compiler = other.compiler,
isThisExposed = other.isThisExposed;
TypeMask use(Element local) {
if (capturedAndBoxed.containsKey(local)) {
return inferrer.getTypeOfElement(capturedAndBoxed[local]);
}
return locals[local];
}
void update(Element local, TypeMask type) {
assert(type != null);
if (compiler.trustTypeAnnotations || compiler.enableTypeAssertions) {
type = narrowType(type, local.computeType(compiler), compiler);
}
if (capturedAndBoxed.containsKey(local) || inTryBlock) {
// If a local is captured and boxed, or is set in a try block,
// we compute the LUB of its assignments.
//
// We don't know if an assignment in a try block
// will be executed, so all assigments in that block are
// potential types after we have left it.
type = computeLUB(locals[local], type, compiler);
}
locals[local] = type;
}
void setCapturedAndBoxed(Element local, Element field) {
capturedAndBoxed[local] = field;
}
/**
* Merge handlers [first] and [second] into [:this:] and returns
* whether the merge changed one of the variables types in [first].
*/
bool merge(LocalsHandler other, {bool discardIfAborts: true}) {
bool changed = false;
List<Element> toRemove = <Element>[];
// Iterating over a map and just updating its entries is OK.
locals.forEach((Element local, TypeMask oldType) {
TypeMask otherType = other.locals[local];
bool isCaptured = capturedAndBoxed.containsKey(local);
if (otherType == null) {
if (!isCaptured) {
// If [local] is not in the other map and is not captured
// and boxed, we know it is not a
// local we want to keep. For example, in an if/else, we don't
// want to keep variables declared in the if or in the else
// branch at the merge point.
toRemove.add(local);
}
return;
}
if (!isCaptured && aborts && discardIfAborts) {
locals[local] = otherType;
} else if (!isCaptured && other.aborts && discardIfAborts) {
// Don't do anything.
} else {
TypeMask type = computeLUB(oldType, otherType, compiler);
if (type != oldType) changed = true;
locals[local] = type;
}
});
// Remove locals that will not be used anymore.
toRemove.forEach((Element element) {
locals.remove(element);
});
// Update the locals that are captured and boxed. We
// unconditionally add them to [this] because we register the type
// of boxed variables after analyzing all closures.
other.capturedAndBoxed.forEach((Element local, Element field) {
capturedAndBoxed[local] = field;
// If [element] is not in our [locals], we need to update it.
// Otherwise, we have already computed the LUB of it.
if (locals[local] == null) {
locals[local] = other.locals[local];
}
});
// Merge instance fields initialized in both handlers. This is
// only relevant for generative constructors.
toRemove = <Element>[];
// Iterate over the map in [:this:]. The map in [other] may
// contain different fields, but if this map does not contain it,
// then we know the field can be null and we don't need to track
// it.
fieldsInitializedInConstructor.forEach((Element element, TypeMask type) {
TypeMask otherType = other.fieldsInitializedInConstructor[element];
if (otherType == null) {
toRemove.add(element);
} else {
fieldsInitializedInConstructor[element] =
computeLUB(type, otherType, compiler);
}
});
// Remove fields that were not initialized in [other].
toRemove.forEach((Element element) {
fieldsInitializedInConstructor.remove(element);
});
isThisExposed = isThisExposed || other.isThisExposed;
seenReturn = seenReturn && other.seenReturn;
seenBreakOrContinue = seenBreakOrContinue && other.seenBreakOrContinue;
return changed;
}
void updateField(Element element, TypeMask type) {
if (isThisExposed) return;
fieldsInitializedInConstructor[element] = type;
}
}
abstract class InferrerVisitor extends ResolvedVisitor<TypeMask> {
final Element analyzedElement;
// Subclasses know more about this field. Typing it dynamic to avoid
// warnings.
final /* TypesInferrer */ inferrer;
final Compiler compiler;
final Map<TargetElement, List<LocalsHandler>> breaksFor =
new Map<TargetElement, List<LocalsHandler>>();
final Map<TargetElement, List<LocalsHandler>> continuesFor =
new Map<TargetElement, List<LocalsHandler>>();
LocalsHandler locals;
bool accumulateIsChecks = false;
bool conditionIsSimple = false;
List<Send> isChecks;
int loopLevel = 0;
bool get inLoop => loopLevel > 0;
bool get isThisExposed => locals.isThisExposed;
void set isThisExposed(value) { locals.isThisExposed = value; }
InferrerVisitor(Element analyzedElement,
this.inferrer,
Compiler compiler,
[LocalsHandler handler])
: this.compiler = compiler,
this.analyzedElement = analyzedElement,
super(compiler.enqueuer.resolution.getCachedElements(analyzedElement)) {
locals = (handler == null)
? new LocalsHandler(inferrer, compiler)
: handler;
}
TypeMask visitSendSet(SendSet node);
TypeMask visitSuperSend(Send node);
TypeMask visitStaticSend(Send node);
TypeMask visitGetterSend(Send node);
TypeMask visitClosureSend(Send node);
TypeMask visitDynamicSend(Send node);
TypeMask visitForIn(ForIn node);
TypeMask visitReturn(Return node);
TypeMask visitNode(Node node) {
node.visitChildren(this);
return inferrer.dynamicType;
}
TypeMask visitNewExpression(NewExpression node) {
return node.send.accept(this);
}
TypeMask visit(Node node) {
return node == null ? inferrer.dynamicType : node.accept(this);
}
TypeMask visitFunctionExpression(FunctionExpression node) {
node.visitChildren(this);
return inferrer.functionType;
}
TypeMask visitFunctionDeclaration(FunctionDeclaration node) {
locals.update(elements[node], inferrer.functionType);
return visit(node.function);
}
TypeMask visitLiteralString(LiteralString node) {
return inferrer.stringType;
}
TypeMask visitStringInterpolation(StringInterpolation node) {
node.visitChildren(this);
return inferrer.stringType;
}
TypeMask visitStringJuxtaposition(StringJuxtaposition node) {
node.visitChildren(this);
return inferrer.stringType;
}
TypeMask visitLiteralBool(LiteralBool node) {
return inferrer.boolType;
}
TypeMask visitLiteralDouble(LiteralDouble node) {
return inferrer.doubleType;
}
TypeMask visitLiteralInt(LiteralInt node) {
return inferrer.intType;
}
TypeMask visitLiteralList(LiteralList node) {
node.visitChildren(this);
return node.isConst()
? inferrer.constListType
: inferrer.growableListType;
}
TypeMask visitLiteralMap(LiteralMap node) {
node.visitChildren(this);
return node.isConst()
? inferrer.constMapType
: inferrer.mapType;
}
TypeMask visitLiteralNull(LiteralNull node) {
return inferrer.nullType;
}
TypeMask visitTypeReferenceSend(Send node) {
return inferrer.typeType;
}
bool isThisOrSuper(Node node) => node.isThis() || node.isSuper();
Element get outermostElement {
return
analyzedElement.getOutermostEnclosingMemberOrTopLevel().implementation;
}
TypeMask _thisType;
TypeMask get thisType {
if (_thisType != null) return _thisType;
ClassElement cls = outermostElement.getEnclosingClass();
if (compiler.world.isUsedAsMixin(cls)) {
return _thisType = new TypeMask.nonNullSubtype(cls.rawType);
} else if (compiler.world.hasAnySubclass(cls)) {
return _thisType = new TypeMask.nonNullSubclass(cls.rawType);
} else {
return _thisType = new TypeMask.nonNullExact(cls.rawType);
}
}
TypeMask _superType;
TypeMask get superType {
if (_superType != null) return _superType;
return _superType = new TypeMask.nonNullExact(
outermostElement.getEnclosingClass().superclass.rawType);
}
TypeMask visitIdentifier(Identifier node) {
if (node.isThis()) {
return thisType;
} else if (node.isSuper()) {
return superType;
}
return inferrer.dynamicType;
}
void potentiallyAddIsCheck(Send node) {
if (!accumulateIsChecks) return;
if (!Elements.isLocal(elements[node.receiver])) return;
isChecks.add(node);
}
void updateIsChecks(List<Node> tests, {bool usePositive}) {
if (tests == null) return;
for (Send node in tests) {
if (node.isIsNotCheck) {
if (usePositive) continue;
} else {
if (!usePositive) continue;
}
DartType type = elements.getType(node.typeAnnotationFromIsCheck);
Element element = elements[node.receiver];
TypeMask existing = locals.use(element);
TypeMask newType = narrowType(
existing, type, compiler, isNullable: false);
locals.update(element, newType);
}
}
TypeMask visitOperatorSend(Send node) {
Operator op = node.selector;
if (const SourceString("[]") == op.source) {
return visitDynamicSend(node);
} else if (const SourceString("&&") == op.source) {
conditionIsSimple = false;
bool oldAccumulateIsChecks = accumulateIsChecks;
accumulateIsChecks = true;
if (isChecks == null) isChecks = <Send>[];
visit(node.receiver);
accumulateIsChecks = oldAccumulateIsChecks;
if (!accumulateIsChecks) isChecks = null;
LocalsHandler saved = new LocalsHandler.from(locals);
updateIsChecks(isChecks, usePositive: true);
visit(node.arguments.head);
locals.merge(saved);
return inferrer.boolType;
} else if (const SourceString("||") == op.source) {
conditionIsSimple = false;
visit(node.receiver);
LocalsHandler saved = new LocalsHandler.from(locals);
updateIsChecks(isChecks, usePositive: false);
bool oldAccumulateIsChecks = accumulateIsChecks;
accumulateIsChecks = false;
visit(node.arguments.head);
accumulateIsChecks = oldAccumulateIsChecks;
locals.merge(saved);
return inferrer.boolType;
} else if (const SourceString("!") == op.source) {
bool oldAccumulateIsChecks = accumulateIsChecks;
accumulateIsChecks = false;
node.visitChildren(this);
accumulateIsChecks = oldAccumulateIsChecks;
return inferrer.boolType;
} else if (const SourceString("is") == op.source) {
potentiallyAddIsCheck(node);
node.visitChildren(this);
return inferrer.boolType;
} else if (const SourceString("as") == op.source) {
TypeMask receiverType = visit(node.receiver);
DartType type = elements.getType(node.arguments.head);
return narrowType(receiverType, type, compiler);
} else if (node.isParameterCheck) {
node.visitChildren(this);
return inferrer.boolType;
} else if (node.argumentsNode is Prefix) {
// Unary operator.
return visitDynamicSend(node);
} else if (const SourceString('===') == op.source
|| const SourceString('!==') == op.source) {
node.visitChildren(this);
return inferrer.boolType;
} else {
// Binary operator.
return visitDynamicSend(node);
}
}
// Because some nodes just visit their children, we may end up
// visiting a type annotation, that may contain a send in case of a
// prefixed type. Therefore we explicitly visit the type annotation
// to avoid confusing the [ResolvedVisitor].
visitTypeAnnotation(TypeAnnotation node) {}
TypeMask visitConditional(Conditional node) {
List<Send> tests = <Send>[];
bool simpleCondition = handleCondition(node.condition, tests);
LocalsHandler saved = new LocalsHandler.from(locals);
updateIsChecks(tests, usePositive: true);
TypeMask firstType = visit(node.thenExpression);
LocalsHandler thenLocals = locals;
locals = saved;
if (simpleCondition) updateIsChecks(tests, usePositive: false);
TypeMask secondType = visit(node.elseExpression);
locals.merge(thenLocals);
TypeMask type = computeLUB(firstType, secondType, compiler);
return type;
}
TypeMask visitVariableDefinitions(VariableDefinitions node) {
for (Link<Node> link = node.definitions.nodes;
!link.isEmpty;
link = link.tail) {
Node definition = link.head;
if (definition is Identifier) {
locals.update(elements[definition], inferrer.nullType);
} else {
assert(definition.asSendSet() != null);
visit(definition);
}
}
return inferrer.dynamicType;
}
bool handleCondition(Node node, List<Send> tests) {
bool oldConditionIsSimple = conditionIsSimple;
bool oldAccumulateIsChecks = accumulateIsChecks;
List<Send> oldIsChecks = isChecks;
accumulateIsChecks = true;
conditionIsSimple = true;
isChecks = tests;
visit(node);
bool simpleCondition = conditionIsSimple;
accumulateIsChecks = oldAccumulateIsChecks;
isChecks = oldIsChecks;
conditionIsSimple = oldConditionIsSimple;
return simpleCondition;
}
TypeMask visitIf(If node) {
List<Send> tests = <Send>[];
bool simpleCondition = handleCondition(node.condition, tests);
LocalsHandler saved = new LocalsHandler.from(locals);
updateIsChecks(tests, usePositive: true);
visit(node.thenPart);
LocalsHandler thenLocals = locals;
locals = saved;
if (simpleCondition) updateIsChecks(tests, usePositive: false);
visit(node.elsePart);
locals.merge(thenLocals);
return inferrer.dynamicType;
}
void setupBreaksAndContinues(TargetElement element) {
if (element == null) return;
if (element.isContinueTarget) continuesFor[element] = <LocalsHandler>[];
if (element.isBreakTarget) breaksFor[element] = <LocalsHandler>[];
}
void clearBreaksAndContinues(TargetElement element) {
continuesFor.remove(element);
breaksFor.remove(element);
}
void mergeBreaks(TargetElement element) {
if (element == null) return;
if (!element.isBreakTarget) return;
for (LocalsHandler handler in breaksFor[element]) {
locals.merge(handler, discardIfAborts: false);
}
}
bool mergeContinues(TargetElement element) {
if (element == null) return false;
if (!element.isContinueTarget) return false;
bool changed = false;
for (LocalsHandler handler in continuesFor[element]) {
changed = locals.merge(handler, discardIfAborts: false) || changed;
}
return changed;
}
TypeMask handleLoop(Node node, void logic()) {
loopLevel++;
bool changed = false;
TargetElement target = elements[node];
setupBreaksAndContinues(target);
do {
LocalsHandler saved = new LocalsHandler.from(locals);
logic();
changed = saved.merge(locals);
locals = saved;
changed = mergeContinues(target) || changed;
} while (changed);
loopLevel--;
mergeBreaks(target);
clearBreaksAndContinues(target);
return inferrer.dynamicType;
}
TypeMask visitWhile(While node) {
return handleLoop(node, () {
List<Send> tests = <Send>[];
handleCondition(node.condition, tests);
updateIsChecks(tests, usePositive: true);
visit(node.body);
});
}
TypeMask visitDoWhile(DoWhile node) {
return handleLoop(node, () {
visit(node.body);
List<Send> tests = <Send>[];
handleCondition(node.condition, tests);
updateIsChecks(tests, usePositive: true);
});
}
TypeMask visitFor(For node) {
visit(node.initializer);
return handleLoop(node, () {
List<Send> tests = <Send>[];
handleCondition(node.condition, tests);
updateIsChecks(tests, usePositive: true);
visit(node.body);
visit(node.update);
});
}
TypeMask visitTryStatement(TryStatement node) {
LocalsHandler saved = locals;
locals = new LocalsHandler.from(locals, inTryBlock: true);
visit(node.tryBlock);
saved.merge(locals);
locals = saved;
for (Node catchBlock in node.catchBlocks) {
saved = new LocalsHandler.from(locals);
visit(catchBlock);
saved.merge(locals);
locals = saved;
}
visit(node.finallyBlock);
return inferrer.dynamicType;
}
TypeMask visitThrow(Throw node) {
node.visitChildren(this);
locals.seenReturn = true;
return inferrer.dynamicType;
}
TypeMask visitCatchBlock(CatchBlock node) {
Node exception = node.exception;
if (exception != null) {
DartType type = elements.getType(node.type);
TypeMask mask = type == null
? inferrer.dynamicType
: new TypeMask.nonNullSubtype(type.asRaw());
locals.update(elements[exception], mask);
}
Node trace = node.trace;
if (trace != null) {
locals.update(elements[trace], inferrer.dynamicType);
}
visit(node.block);
return inferrer.dynamicType;
}
TypeMask visitParenthesizedExpression(ParenthesizedExpression node) {
return visit(node.expression);
}
TypeMask visitBlock(Block node) {
if (node.statements != null) {
for (Node statement in node.statements) {
visit(statement);
if (locals.aborts) break;
}
}
return inferrer.dynamicType;
}
TypeMask visitLabeledStatement(LabeledStatement node) {
Statement body = node.statement;
if (body is Loop
|| body is SwitchStatement
|| Elements.isUnusedLabel(node, elements)) {
// Loops and switches handle their own labels.
visit(body);
return inferrer.dynamicType;
}
TargetElement targetElement = elements[body];
setupBreaksAndContinues(targetElement);
visit(body);
mergeBreaks(targetElement);
clearBreaksAndContinues(targetElement);
return inferrer.dynamicType;
}
TypeMask visitBreakStatement(BreakStatement node) {
TargetElement target = elements[node];
breaksFor[target].add(locals);
locals.seenBreakOrContinue = true;
return inferrer.dynamicType;
}
TypeMask visitContinueStatement(ContinueStatement node) {
TargetElement target = elements[node];
continuesFor[target].add(locals);
locals.seenBreakOrContinue = true;
return inferrer.dynamicType;
}
void internalError(String reason, {Node node}) {
compiler.internalError(reason, node: node);
}
TypeMask visitSwitchStatement(SwitchStatement node) {
visit(node.parenthesizedExpression);
setupBreaksAndContinues(elements[node]);
if (Elements.switchStatementHasContinue(node, elements)) {
void forEachLabeledCase(void action(TargetElement target)) {
for (SwitchCase switchCase in node.cases) {
for (Node labelOrCase in switchCase.labelsAndCases) {
if (labelOrCase.asLabel() == null) continue;
LabelElement labelElement = elements[labelOrCase];
if (labelElement != null) {
action(labelElement.target);
}
}
}
}
forEachLabeledCase((TargetElement target) {
setupBreaksAndContinues(target);
});
// If the switch statement has a continue, we conservatively
// visit all cases and update [locals] until we have reached a
// fixed point.
bool changed;
do {
changed = false;
for (Node switchCase in node.cases) {
LocalsHandler saved = new LocalsHandler.from(locals);
visit(switchCase);
changed = saved.merge(locals, discardIfAborts: false) || changed;
locals = saved;
}
} while (changed);
forEachLabeledCase((TargetElement target) {
clearBreaksAndContinues(target);
});
} else {
LocalsHandler saved = new LocalsHandler.from(locals);
// If there is a default case, the current values of the local
// variable might be overwritten, so we don't need the current
// [locals] for the join block.
LocalsHandler result = Elements.switchStatementHasDefault(node)
? null
: new LocalsHandler.from(locals);
for (Node switchCase in node.cases) {
locals = new LocalsHandler.from(saved);
visit(switchCase);
if (result == null) {
result = locals;
} else {
result.merge(locals, discardIfAborts: false);
}
}
locals = result;
}
clearBreaksAndContinues(elements[node]);
// In case there is a default in the switch we discard the
// incoming localsHandler, because the types it holds do not need
// to be merged after the switch statement. This means that, if all
// cases, including the default, break or continue, the [result]
// handler may think it just aborts the current block. Therefore
// we set the current locals to not have any break or continue, so
// that the [visitBlock] method does not assume the code after the
// switch is dead code.
locals.seenBreakOrContinue = false;
return inferrer.dynamicType;
}
}