| // 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; |
| } |
| } |