blob: d357a354f4c797256006c6859f424283c1b0ffdf [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.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 if (firstType == secondType) {
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;
}
}
/**
* A variable scope holds types for variables. It has a link to a
* parent scope, but never changes the types in that parent. Instead,
* updates to locals of a parent scope are put in the current scope.
* The inferrer makes sure updates get merged into the parent scope,
* once the control flow block has been visited.
*/
class VariableScope {
Map<Element, TypeMask> variables;
/// The parent of this scope. Null for the root scope.
final VariableScope parent;
/// The block level of this scope. Starts at 0 for the root scope.
final int blockLevel;
VariableScope([parent])
: this.variables = null,
this.parent = parent,
this.blockLevel = parent == null ? 0 : parent.blockLevel + 1;
VariableScope.deepCopyOf(VariableScope other)
: variables = other.variables == null
? null
: new Map<Element, TypeMask>.from(other.variables),
blockLevel = other.blockLevel,
parent = other.parent == null
? null
: new VariableScope.deepCopyOf(other.parent);
TypeMask operator [](Element variable) {
TypeMask result;
if (variables == null || (result = variables[variable]) == null) {
return parent == null ? null : parent[variable];
}
return result;
}
void operator []=(Element variable, TypeMask mask) {
assert(mask != null);
if (variables == null) {
variables = new Map<Element, TypeMask>();
}
variables[variable] = mask;
}
void forEachOwnLocal(void f(Element element, TypeMask mask)) {
if (variables == null) return;
variables.forEach(f);
}
void remove(Element element) {
variables.remove(element);
}
String toString() {
String rest = parent == null ? "null" : parent.toString();
return '$blockLevel: $variables $rest';
}
}
/**
* Placeholder for inferred types of local variables.
*/
class LocalsHandler {
final Compiler compiler;
final TypesInferrer inferrer;
final VariableScope locals;
final Map<Element, Element> capturedAndBoxed;
final Map<Element, TypeMask> fieldsInitializedInConstructor;
final bool inTryBlock;
bool isThisExposed;
bool seenReturnOrThrow = false;
bool seenBreakOrContinue = false;
bool get aborts {
return seenReturnOrThrow || seenBreakOrContinue;
}
LocalsHandler(this.inferrer, this.compiler)
: locals = new VariableScope(),
capturedAndBoxed = new Map<Element, Element>(),
fieldsInitializedInConstructor = new Map<Element, TypeMask>(),
inTryBlock = false,
isThisExposed = true;
LocalsHandler.from(LocalsHandler other, {bool inTryBlock: false})
: locals = new VariableScope(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;
LocalsHandler.deepCopyOf(LocalsHandler other)
: locals = new VariableScope.deepCopyOf(other.locals),
capturedAndBoxed = new Map<Element, Element>.from(
other.capturedAndBoxed),
fieldsInitializedInConstructor = new Map<Element, TypeMask>.from(
other.fieldsInitializedInConstructor),
inTryBlock = other.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}) {
VariableScope currentOther = other.locals;
assert(currentOther != locals);
bool changed = false;
// Iterate over all updates in the other handler until we reach
// the block level of this handler. We know that [VariableScope]s
// that are lower in block level, are the same.
do {
currentOther.forEachOwnLocal((Element local, TypeMask otherType) {
TypeMask myType = locals[local];
if (myType == null) return;
bool isCaptured = capturedAndBoxed.containsKey(local);
if (!isCaptured && aborts && discardIfAborts) {
locals[local] = otherType;
} else if (!isCaptured && other.aborts && discardIfAborts) {
// Don't do anything.
} else {
TypeMask type = computeLUB(myType, otherType, compiler);
if (type != myType) {
changed = true;
}
locals[local] = type;
}
});
currentOther = currentOther.parent;
} while (currentOther != null
&& currentOther.blockLevel >= locals.blockLevel);
List<Element> toRemove = <Element>[];
locals.forEachOwnLocal((Element local, _) {
bool isCaptured = capturedAndBoxed.containsKey(local);
if (other.locals[local] == null && !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);
}
});
// 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 && other.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;
seenReturnOrThrow = seenReturnOrThrow && other.seenReturnOrThrow;
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 compiler.typesTask.dynamicType;
}
TypeMask visitNewExpression(NewExpression node) {
return node.send.accept(this);
}
TypeMask visit(Node node) {
return node == null ? compiler.typesTask.dynamicType : node.accept(this);
}
TypeMask visitFunctionExpression(FunctionExpression node) {
node.visitChildren(this);
return compiler.typesTask.functionType;
}
TypeMask visitFunctionDeclaration(FunctionDeclaration node) {
locals.update(elements[node], compiler.typesTask.functionType);
return visit(node.function);
}
TypeMask visitLiteralString(LiteralString node) {
return compiler.typesTask.stringType;
}
TypeMask visitStringInterpolation(StringInterpolation node) {
node.visitChildren(this);
return compiler.typesTask.stringType;
}
TypeMask visitStringJuxtaposition(StringJuxtaposition node) {
node.visitChildren(this);
return compiler.typesTask.stringType;
}
TypeMask visitLiteralBool(LiteralBool node) {
return compiler.typesTask.boolType;
}
TypeMask visitLiteralDouble(LiteralDouble node) {
return compiler.typesTask.doubleType;
}
TypeMask visitLiteralInt(LiteralInt node) {
ConstantSystem constantSystem = compiler.backend.constantSystem;
Constant constant = constantSystem.createInt(node.value);
// The JavaScript backend may turn this literal into a double at
// runtime.
return constantSystem.isDouble(constant)
? compiler.typesTask.doubleType
: compiler.typesTask.intType;
}
TypeMask visitLiteralList(LiteralList node) {
node.visitChildren(this);
return node.isConst()
? compiler.typesTask.constListType
: compiler.typesTask.growableListType;
}
TypeMask visitLiteralMap(LiteralMap node) {
node.visitChildren(this);
return node.isConst()
? compiler.typesTask.constMapType
: compiler.typesTask.mapType;
}
TypeMask visitLiteralNull(LiteralNull node) {
return compiler.typesTask.nullType;
}
TypeMask visitTypeReferenceSend(Send node) {
return compiler.typesTask.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 compiler.typesTask.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.typeAnnotationFromIsCheckOrCast);
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 = locals;
locals = new LocalsHandler.from(locals);
updateIsChecks(isChecks, usePositive: true);
visit(node.arguments.head);
saved.merge(locals);
locals = saved;
return compiler.typesTask.boolType;
} else if (const SourceString("||") == op.source) {
conditionIsSimple = false;
visit(node.receiver);
LocalsHandler saved = locals;
locals = new LocalsHandler.from(locals);
updateIsChecks(isChecks, usePositive: false);
bool oldAccumulateIsChecks = accumulateIsChecks;
accumulateIsChecks = false;
visit(node.arguments.head);
accumulateIsChecks = oldAccumulateIsChecks;
saved.merge(locals);
locals = saved;
return compiler.typesTask.boolType;
} else if (const SourceString("!") == op.source) {
bool oldAccumulateIsChecks = accumulateIsChecks;
accumulateIsChecks = false;
node.visitChildren(this);
accumulateIsChecks = oldAccumulateIsChecks;
return compiler.typesTask.boolType;
} else if (const SourceString("is") == op.source) {
potentiallyAddIsCheck(node);
node.visitChildren(this);
return compiler.typesTask.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.argumentsNode is Prefix) {
// Unary operator.
return visitDynamicSend(node);
} else if (const SourceString('===') == op.source
|| const SourceString('!==') == op.source) {
node.visitChildren(this);
return compiler.typesTask.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 = locals;
locals = 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], compiler.typesTask.nullType);
} else {
assert(definition.asSendSet() != null);
visit(definition);
}
}
return compiler.typesTask.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 = locals;
locals = 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 compiler.typesTask.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 = locals;
locals = new LocalsHandler.from(locals);
logic();
changed = saved.merge(locals);
locals = saved;
changed = mergeContinues(target) || changed;
} while (changed);
loopLevel--;
mergeBreaks(target);
clearBreaksAndContinues(target);
return compiler.typesTask.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 = locals;
locals = new LocalsHandler.from(locals);
visit(catchBlock);
saved.merge(locals);
locals = saved;
}
visit(node.finallyBlock);
return compiler.typesTask.dynamicType;
}
TypeMask visitThrow(Throw node) {
node.visitChildren(this);
locals.seenReturnOrThrow = true;
return compiler.typesTask.dynamicType;
}
TypeMask visitCatchBlock(CatchBlock node) {
Node exception = node.exception;
if (exception != null) {
DartType type = elements.getType(node.type);
TypeMask mask = type == null
? compiler.typesTask.dynamicType
: new TypeMask.nonNullSubtype(type.asRaw());
locals.update(elements[exception], mask);
}
Node trace = node.trace;
if (trace != null) {
locals.update(elements[trace], compiler.typesTask.dynamicType);
}
visit(node.block);
return compiler.typesTask.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 compiler.typesTask.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 compiler.typesTask.dynamicType;
}
TargetElement targetElement = elements[body];
setupBreaksAndContinues(targetElement);
visit(body);
mergeBreaks(targetElement);
clearBreaksAndContinues(targetElement);
return compiler.typesTask.dynamicType;
}
TypeMask visitBreakStatement(BreakStatement node) {
TargetElement target = elements[node];
locals.seenBreakOrContinue = true;
// Do a deep-copy of the locals, because the code following the
// break will change them.
breaksFor[target].add(new LocalsHandler.deepCopyOf(locals));
return compiler.typesTask.dynamicType;
}
TypeMask visitContinueStatement(ContinueStatement node) {
TargetElement target = elements[node];
locals.seenBreakOrContinue = true;
// Do a deep-copy of the locals, because the code following the
// continue will change them.
continuesFor[target].add(new LocalsHandler.deepCopyOf(locals));
return compiler.typesTask.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 = locals;
locals = 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 = locals;
List<LocalsHandler> localsToMerge = <LocalsHandler>[];
for (SwitchCase switchCase in node.cases) {
if (switchCase.isDefaultCase) {
// 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.
locals = saved;
visit(switchCase);
} else {
locals = new LocalsHandler.from(saved);
visit(switchCase);
localsToMerge.add(locals);
}
}
for (LocalsHandler handler in localsToMerge) {
saved.merge(handler, discardIfAborts: false);
}
locals = saved;
}
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 compiler.typesTask.dynamicType;
}
}