Add LocalState to KernelTypeGraphBuilder

Move LocalsHandler.seenBreakOrContinue/seenReturnOrThrow/_tryBlock to LocalState
Move KernelTypeGraphBuilder._locals and KernelTypeGraphBuilder._fieldScope from visitor to LocalState

Change-Id: I712558ab3d18c6196fb5b96a4f8084d44848b83b
Reviewed-on: https://dart-review.googlesource.com/c/85444
Reviewed-by: Sigmund Cherem <sigmund@google.com>
Commit-Queue: Johnni Winther <johnniwinther@google.com>
diff --git a/pkg/compiler/lib/src/inferrer/builder_kernel.dart b/pkg/compiler/lib/src/inferrer/builder_kernel.dart
index 3009a68..8a7dfdd 100644
--- a/pkg/compiler/lib/src/inferrer/builder_kernel.dart
+++ b/pkg/compiler/lib/src/inferrer/builder_kernel.dart
@@ -48,13 +48,12 @@
   final GlobalTypeInferenceElementData _memberData;
   final bool _inGenerativeConstructor;
 
-  LocalsHandler _locals;
-  FieldInitializationScope _fieldScope;
+  LocalState _state;
   final SideEffectsBuilder _sideEffectsBuilder;
-  final Map<JumpTarget, List<LocalsHandler>> _breaksFor =
-      <JumpTarget, List<LocalsHandler>>{};
-  final Map<JumpTarget, List<LocalsHandler>> _continuesFor =
-      <JumpTarget, List<LocalsHandler>>{};
+  final Map<JumpTarget, List<LocalState>> _breaksFor =
+      <JumpTarget, List<LocalState>>{};
+  final Map<JumpTarget, List<LocalState>> _continuesFor =
+      <JumpTarget, List<LocalState>>{};
   TypeInformation _returnType;
   final Set<Local> _capturedVariables = new Set<Local>();
   final Map<Local, FieldEntity> _capturedAndBoxed;
@@ -71,9 +70,7 @@
 
   KernelTypeGraphBuilder(this._options, this._closedWorld, this._inferrer,
       this._analyzedMember, this._analyzedNode, this._localsMap,
-      [this._locals,
-      this._fieldScope,
-      Map<Local, FieldEntity> capturedAndBoxed])
+      [this._state, Map<Local, FieldEntity> capturedAndBoxed])
       : this._types = _inferrer.types,
         this._memberData = _inferrer.dataOfMember(_analyzedMember),
         // TODO(johnniwinther): Should side effects also be tracked for field
@@ -86,11 +83,10 @@
         this._capturedAndBoxed = capturedAndBoxed != null
             ? new Map<Local, FieldEntity>.from(capturedAndBoxed)
             : <Local, FieldEntity>{} {
-    if (_locals != null) return;
+    if (_state != null) return;
 
-    _fieldScope =
-        _inGenerativeConstructor ? new FieldInitializationScope() : null;
-    _locals = new LocalsHandler(_analyzedNode);
+    _state = new LocalState.initial(_analyzedNode,
+        inGenerativeConstructor: _inGenerativeConstructor);
   }
 
   JsToElementMap get _elementMap => _closedWorld.elementMap;
@@ -101,16 +97,6 @@
 
   bool get inLoop => _loopLevel > 0;
 
-  bool get _isThisExposed {
-    return _inGenerativeConstructor ? _fieldScope.isThisExposed : true;
-  }
-
-  void _markThisAsExposed() {
-    if (_inGenerativeConstructor) {
-      _fieldScope.isThisExposed = true;
-    }
-  }
-
   /// Returns `true` if [member] is defined in a subclass of the current this
   /// type.
   bool _isInClassOrSubclass(MemberEntity member) {
@@ -129,14 +115,14 @@
   /// field is considered to have been read before initialization and the field
   /// is assumed to be potentially `null`.
   void _checkIfExposesThis(Selector selector, AbstractValue mask) {
-    if (_isThisExposed) {
+    if (_state.isThisExposed) {
       // We already consider `this` to have been exposed.
       return;
     }
     if (_inferrer.closedWorld.includesClosureCall(selector, mask)) {
       // TODO(ngeoffray): We could do better here if we knew what we
       // are calling does not expose this.
-      _markThisAsExposed();
+      _state.markThisAsExposed();
     } else {
       _inferrer.forEachElementMatching(selector, mask, (MemberEntity element) {
         if (element != null && element.isField) {
@@ -144,7 +130,7 @@
           if (!selector.isSetter &&
               _isInClassOrSubclass(field) &&
               field.isAssignable &&
-              _fieldScope.readField(field) == null &&
+              _state.readField(field) == null &&
               getFieldInitializer(_elementMap, field) == null) {
             // If the field is being used before this constructor
             // actually had a chance to initialize it, say it can be
@@ -156,7 +142,7 @@
         }
         // TODO(ngeoffray): We could do better here if we knew what we
         // are calling does not expose this.
-        _markThisAsExposed();
+        _state.markThisAsExposed();
         return false;
       });
     }
@@ -199,12 +185,6 @@
         _inferrer.addReturnTypeForMethod(analyzedMethod, _returnType, type);
   }
 
-  void initializationIsIndefinite() {
-    if (_inGenerativeConstructor) {
-      _fieldScope.isIndefinite = true;
-    }
-  }
-
   TypeInformation _thisType;
   TypeInformation get thisType {
     if (_thisType != null) return _thisType;
@@ -232,7 +212,7 @@
   void handleParameter(ir.VariableDeclaration node, {bool isOptional}) {
     Local local = _localsMap.getLocalVariable(node);
     DartType type = _localsMap.getLocalType(_elementMap, local);
-    _locals.update(_inferrer, _capturedAndBoxed, local,
+    _state.updateLocal(_inferrer, _capturedAndBoxed, local,
         _inferrer.typeOfParameter(local), node, type);
     if (isOptional) {
       TypeInformation type;
@@ -260,7 +240,7 @@
       _elementMap.elementEnvironment.forEachLocalClassMember(cls,
           (MemberEntity member) {
         if (member.isField && member.isInstanceMember && member.isAssignable) {
-          TypeInformation type = _fieldScope.readField(member);
+          TypeInformation type = _state.readField(member);
           MemberDefinition definition = _elementMap.getMemberDefinition(member);
           assert(definition.kind == MemberKind.regular);
           ir.Field node = definition.node;
@@ -272,7 +252,7 @@
         }
       });
     }
-    _inferrer.recordExposesThis(_analyzedMember, _isThisExposed);
+    _inferrer.recordExposesThis(_analyzedMember, _state.isThisExposed);
 
     if (cls.isAbstract) {
       if (_closedWorld.classHierarchy.isInstantiated(cls)) {
@@ -294,7 +274,7 @@
   visitFieldInitializer(ir.FieldInitializer node) {
     TypeInformation rhsType = visit(node.value);
     FieldEntity field = _elementMap.getField(node.field);
-    _fieldScope.updateField(field, rhsType);
+    _state.updateField(field, rhsType);
     _inferrer.recordTypeOfField(field, rhsType);
     return null;
   }
@@ -311,7 +291,7 @@
 
     _inferrer.analyze(constructor);
     if (_inferrer.checkIfExposesThis(constructor)) {
-      _markThisAsExposed();
+      _state.markThisAsExposed();
     }
     return null;
   }
@@ -328,7 +308,7 @@
 
     _inferrer.analyze(constructor);
     if (_inferrer.checkIfExposesThis(constructor)) {
-      _markThisAsExposed();
+      _state.markThisAsExposed();
     }
     return null;
   }
@@ -366,10 +346,10 @@
       case ir.AsyncMarker.Sync:
         if (_returnType == null) {
           // No return in the body.
-          _returnType = _locals.seenReturnOrThrow
+          _returnType = _state.seenReturnOrThrow
               ? _types.nonNullEmpty() // Body always throws.
               : _types.nullType;
-        } else if (!_locals.seenReturnOrThrow) {
+        } else if (!_state.seenReturnOrThrow) {
           // We haven'TypeInformation seen returns on all branches. So the
           // method may also return null.
           recordReturnType(_types.nullType);
@@ -429,7 +409,7 @@
   visitBlock(ir.Block block) {
     for (ir.Statement statement in block.statements) {
       visit(statement);
-      if (_locals.aborts) break;
+      if (_state.aborts) break;
     }
     return null;
   }
@@ -458,39 +438,33 @@
     List<IsCheck> negativeTests = <IsCheck>[];
     bool simpleCondition =
         handleCondition(node.condition, positiveTests, negativeTests);
-    LocalsHandler localsBefore = _locals;
-    FieldInitializationScope fieldScopeBefore = _fieldScope;
+    LocalState stateBefore = _state;
 
-    _locals = new LocalsHandler.from(localsBefore, node);
+    _state = new LocalState.childPath(stateBefore, node);
     _updateIsChecks(positiveTests, negativeTests);
-    LocalsHandler localsAfterCondition = _locals;
-    FieldInitializationScope fieldScopeAfterThen = _fieldScope;
+    LocalState stateAfterCondition = _state;
 
-    _locals = new LocalsHandler.from(localsBefore, node);
-    _fieldScope = new FieldInitializationScope.from(fieldScopeBefore);
+    _state = new LocalState.childPath(stateBefore, node);
     if (simpleCondition) _updateIsChecks(negativeTests, positiveTests);
     visit(node.message);
 
-    LocalsHandler localsAfterMessage = _locals;
-    FieldInitializationScope fieldScopeAfterMessage = _fieldScope;
-    localsAfterMessage.seenReturnOrThrow = true;
-    _locals = localsBefore.mergeDiamondFlow(
-        _inferrer, localsAfterCondition, localsAfterMessage);
-    _fieldScope = fieldScopeBefore?.mergeDiamondFlow(
-        _inferrer, fieldScopeAfterThen, fieldScopeAfterMessage);
+    LocalState stateAfterMessage = _state;
+    stateAfterMessage.seenReturnOrThrow = true;
+    _state = stateBefore.mergeDiamondFlow(
+        _inferrer, stateAfterCondition, stateAfterMessage);
     return null;
   }
 
   @override
   visitBreakStatement(ir.BreakStatement node) {
     JumpTarget target = _localsMap.getJumpTargetForBreak(node);
-    _locals.seenBreakOrContinue = true;
+    _state.seenBreakOrContinue = true;
     // Do a deep-copy of the locals, because the code following the
     // break will change them.
     if (_localsMap.generateContinueForBreak(node)) {
-      _continuesFor[target].add(new LocalsHandler.deepCopyOf(_locals));
+      _continuesFor[target].add(new LocalState.deepCopyOf(_state));
     } else {
-      _breaksFor[target].add(new LocalsHandler.deepCopyOf(_locals));
+      _breaksFor[target].add(new LocalState.deepCopyOf(_state));
     }
     return null;
   }
@@ -505,7 +479,7 @@
       JumpTarget jumpTarget = _localsMap.getJumpTargetForLabel(node);
       _setupBreaksAndContinues(jumpTarget);
       visit(body);
-      _locals.mergeAfterBreaks(_inferrer, _getBreaks(jumpTarget));
+      _state.mergeAfterBreaks(_inferrer, _getBreaks(jumpTarget));
       _clearBreaksAndContinues(jumpTarget);
     }
     return null;
@@ -533,43 +507,38 @@
       // visit all cases and update [locals] until we have reached a
       // fixed point.
       bool changed;
-      _locals.startLoop(_inferrer, node);
+      _state.startLoop(_inferrer, node);
       do {
         changed = false;
         for (ir.SwitchCase switchCase in node.cases) {
-          LocalsHandler localsBeforeCase = _locals;
-          FieldInitializationScope fieldScopeBeforeCase = _fieldScope;
-          _locals = new LocalsHandler.from(localsBeforeCase, switchCase);
-          _fieldScope = new FieldInitializationScope.from(fieldScopeBeforeCase);
+          LocalState stateBeforeCase = _state;
+          _state = new LocalState.childPath(stateBeforeCase, switchCase);
           visit(switchCase);
-          LocalsHandler localsAfterCase = _locals;
-          changed = localsBeforeCase.mergeAll(_inferrer, [localsAfterCase]) ||
-              changed;
-          _locals = localsBeforeCase;
-          _fieldScope = fieldScopeBeforeCase;
+          LocalState stateAfterCase = _state;
+          changed =
+              stateBeforeCase.mergeAll(_inferrer, [stateAfterCase]) || changed;
+          _state = stateBeforeCase;
         }
       } while (changed);
-      _locals.endLoop(_inferrer, node);
+      _state.endLoop(_inferrer, node);
 
       continueTargets.forEach(_clearBreaksAndContinues);
     } else {
-      LocalsHandler localsBeforeCase = _locals;
-      FieldInitializationScope fieldScopeBeforeCase = _fieldScope;
-      List<LocalsHandler> localsToMerge = <LocalsHandler>[];
+      LocalState stateBeforeCase = _state;
+      List<LocalState> statesToMerge = <LocalState>[];
       bool hasDefaultCase = false;
 
       for (ir.SwitchCase switchCase in node.cases) {
         if (switchCase.isDefault) {
           hasDefaultCase = true;
         }
-        _locals = new LocalsHandler.from(localsBeforeCase, switchCase);
+        _state = new LocalState.childPath(stateBeforeCase, switchCase);
         visit(switchCase);
-        localsToMerge.add(_locals);
+        statesToMerge.add(_state);
       }
-      localsBeforeCase.mergeAfterBreaks(_inferrer, localsToMerge,
+      stateBeforeCase.mergeAfterBreaks(_inferrer, statesToMerge,
           keepOwnLocals: !hasDefaultCase);
-      _locals = localsBeforeCase;
-      _fieldScope = fieldScopeBeforeCase;
+      _state = stateBeforeCase;
     }
     _clearBreaksAndContinues(jumpTarget);
     return null;
@@ -584,10 +553,10 @@
   @override
   visitContinueSwitchStatement(ir.ContinueSwitchStatement node) {
     JumpTarget target = _localsMap.getJumpTargetForContinueSwitch(node);
-    _locals.seenBreakOrContinue = true;
+    _state.seenBreakOrContinue = true;
     // Do a deep-copy of the locals, because the code following the
     // break will change them.
-    _continuesFor[target].add(new LocalsHandler.deepCopyOf(_locals));
+    _continuesFor[target].add(new LocalState.deepCopyOf(_state));
     return null;
   }
 
@@ -637,8 +606,8 @@
   TypeInformation visitReturnStatement(ir.ReturnStatement node) {
     ir.Node expression = node.expression;
     recordReturnType(expression == null ? _types.nullType : visit(expression));
-    _locals.seenReturnOrThrow = true;
-    initializationIsIndefinite();
+    _state.seenReturnOrThrow = true;
+    _state.markInitializationAsIndefinite();
     return null;
   }
 
@@ -706,14 +675,14 @@
     Local local = _localsMap.getLocalVariable(node);
     DartType type = _localsMap.getLocalType(_elementMap, local);
     if (node.initializer == null) {
-      _locals.update(
+      _state.updateLocal(
           _inferrer, _capturedAndBoxed, local, _types.nullType, node, type);
     } else {
-      _locals.update(_inferrer, _capturedAndBoxed, local,
+      _state.updateLocal(_inferrer, _capturedAndBoxed, local,
           visit(node.initializer), node, type);
     }
     if (node.initializer is ir.ThisExpression) {
-      _markThisAsExposed();
+      _state.markThisAsExposed();
     }
     return null;
   }
@@ -721,7 +690,8 @@
   @override
   TypeInformation visitVariableGet(ir.VariableGet node) {
     Local local = _localsMap.getLocalVariable(node.variable);
-    TypeInformation type = _locals.use(_inferrer, _capturedAndBoxed, local);
+    TypeInformation type =
+        _state.readLocal(_inferrer, _capturedAndBoxed, local);
     assert(type != null, "Missing type information for $local.");
     return type;
   }
@@ -730,11 +700,12 @@
   TypeInformation visitVariableSet(ir.VariableSet node) {
     TypeInformation rhsType = visit(node.value);
     if (node.value is ir.ThisExpression) {
-      _markThisAsExposed();
+      _state.markThisAsExposed();
     }
     Local local = _localsMap.getLocalVariable(node.variable);
     DartType type = _localsMap.getLocalType(_elementMap, local);
-    _locals.update(_inferrer, _capturedAndBoxed, local, rhsType, node, type);
+    _state.updateLocal(
+        _inferrer, _capturedAndBoxed, local, rhsType, node, type);
     return rhsType;
   }
 
@@ -745,7 +716,7 @@
       // TODO(ngeoffray): We could do better here if we knew what we
       // are calling does not expose this.
       if (argument is ir.ThisExpression) {
-        _markThisAsExposed();
+        _state.markThisAsExposed();
       }
       positional.add(visit(argument));
     }
@@ -755,7 +726,7 @@
       // TODO(ngeoffray): We could do better here if we knew what we
       // are calling does not expose this.
       if (value is ir.ThisExpression) {
-        _markThisAsExposed();
+        _state.markThisAsExposed();
       }
       named[argument.name] = visit(value);
     }
@@ -846,7 +817,7 @@
         TypeInformation refinedType = _types
             .refineReceiver(selector, mask, receiverType, isConditional: false);
         DartType type = _localsMap.getLocalType(_elementMap, local);
-        _locals.update(
+        _state.updateLocal(
             _inferrer, _capturedAndBoxed, local, refinedType, node, type);
         List<Refinement> refinements = _localRefinementMap[variable];
         if (refinements != null) {
@@ -931,12 +902,12 @@
         Local local = _localsMap.getLocalVariable(alias);
         DartType type = _localsMap.getLocalType(_elementMap, local);
         TypeInformation localType =
-            _locals.use(_inferrer, _capturedAndBoxed, local);
+            _state.readLocal(_inferrer, _capturedAndBoxed, local);
         for (Refinement refinement in refinements) {
           localType = _types.refineReceiver(
               refinement.selector, refinement.mask, localType,
               isConditional: true);
-          _locals.update(
+          _state.updateLocal(
               _inferrer, _capturedAndBoxed, local, localType, node, type);
         }
       }
@@ -949,7 +920,7 @@
     if (node.iterable is ir.ThisExpression) {
       // Any reasonable implementation of an iterator would expose
       // this, so we play it safe and assume it will.
-      _markThisAsExposed();
+      _state.markThisAsExposed();
     }
 
     AbstractValue currentMask;
@@ -990,7 +961,7 @@
 
     Local variable = _localsMap.getLocalVariable(node.variable);
     DartType variableType = _localsMap.getLocalType(_elementMap, variable);
-    _locals.update(_inferrer, _capturedAndBoxed, variable, currentType,
+    _state.updateLocal(_inferrer, _capturedAndBoxed, variable, currentType,
         node.variable, variableType);
 
     JumpTarget target = _localsMap.getJumpTargetForForIn(node);
@@ -1002,10 +973,10 @@
   void _setupBreaksAndContinues(JumpTarget target) {
     if (target == null) return;
     if (target.isContinueTarget) {
-      _continuesFor[target] = <LocalsHandler>[];
+      _continuesFor[target] = <LocalState>[];
     }
     if (target.isBreakTarget) {
-      _breaksFor[target] = <LocalsHandler>[];
+      _breaksFor[target] = <LocalState>[];
     }
   }
 
@@ -1014,15 +985,15 @@
     _breaksFor.remove(element);
   }
 
-  List<LocalsHandler> _getBreaks(JumpTarget target) {
-    List<LocalsHandler> list = <LocalsHandler>[_locals];
+  List<LocalState> _getBreaks(JumpTarget target) {
+    List<LocalState> list = <LocalState>[_state];
     if (target == null) return list;
     if (!target.isBreakTarget) return list;
     return list..addAll(_breaksFor[target]);
   }
 
-  List<LocalsHandler> _getLoopBackEdges(JumpTarget target) {
-    List<LocalsHandler> list = <LocalsHandler>[_locals];
+  List<LocalState> _getLoopBackEdges(JumpTarget target) {
+    List<LocalState> list = <LocalState>[_state];
     if (target == null) return list;
     if (!target.isContinueTarget) return list;
     return list..addAll(_continuesFor[target]);
@@ -1031,24 +1002,21 @@
   TypeInformation handleLoop(ir.Node node, JumpTarget target, void logic()) {
     _loopLevel++;
     bool changed = false;
-    LocalsHandler localsBefore = _locals;
-    FieldInitializationScope fieldScopeBefore = _fieldScope;
-    localsBefore.startLoop(_inferrer, node);
+    LocalState stateBefore = _state;
+    stateBefore.startLoop(_inferrer, node);
     do {
       // Setup (and clear in case of multiple iterations of the loop)
       // the lists of breaks and continues seen in the loop.
       _setupBreaksAndContinues(target);
-      _locals = new LocalsHandler.from(localsBefore, node);
-      _fieldScope = new FieldInitializationScope.from(fieldScopeBefore);
+      _state = new LocalState.childPath(stateBefore, node);
       logic();
-      changed = localsBefore.mergeAll(_inferrer, _getLoopBackEdges(target));
+      changed = stateBefore.mergeAll(_inferrer, _getLoopBackEdges(target));
     } while (changed);
     _loopLevel--;
-    localsBefore.endLoop(_inferrer, node);
+    stateBefore.endLoop(_inferrer, node);
     bool keepOwnLocals = node is! ir.DoStatement;
-    _locals = localsBefore.mergeAfterBreaks(_inferrer, _getBreaks(target),
+    _state = stateBefore.mergeAfterBreaks(_inferrer, _getBreaks(target),
         keepOwnLocals: keepOwnLocals);
-    _fieldScope = fieldScopeBefore;
     _clearBreaksAndContinues(target);
     return null;
   }
@@ -1249,7 +1217,7 @@
   TypeInformation visitStaticSet(ir.StaticSet node) {
     TypeInformation rhsType = visit(node.value);
     if (node.value is ir.ThisExpression) {
-      _markThisAsExposed();
+      _state.markThisAsExposed();
     }
     MemberEntity member = _elementMap.getMember(node.target);
     AbstractValue mask = _memberData.typeOfSend(node);
@@ -1299,7 +1267,7 @@
 
     TypeInformation rhsType = visit(node.value);
     if (node.value is ir.ThisExpression) {
-      _markThisAsExposed();
+      _state.markThisAsExposed();
     }
 
     if (_inGenerativeConstructor && node.receiver is ir.ThisExpression) {
@@ -1314,7 +1282,7 @@
           MemberEntity single = targets.first;
           if (single.isField) {
             FieldEntity field = single;
-            _fieldScope.updateField(field, rhsType);
+            _state.updateField(field, rhsType);
           }
         }
       }
@@ -1375,11 +1343,11 @@
       List<IsCheck> positiveTests, List<IsCheck> negativeTests) {
     for (IsCheck check in positiveTests) {
       if (check.type != null) {
-        _locals.narrow(
+        _state.narrowLocal(
             _inferrer, _capturedAndBoxed, check.local, check.type, check.node);
       } else {
         DartType localType = _localsMap.getLocalType(_elementMap, check.local);
-        _locals.update(_inferrer, _capturedAndBoxed, check.local,
+        _state.updateLocal(_inferrer, _capturedAndBoxed, check.local,
             _types.nullType, check.node, localType);
       }
     }
@@ -1387,7 +1355,7 @@
       if (check.type != null) {
         // TODO(johnniwinther): Use negative type knowledge.
       } else {
-        _locals.narrow(_inferrer, _capturedAndBoxed, check.local,
+        _state.narrowLocal(_inferrer, _capturedAndBoxed, check.local,
             _closedWorld.commonElements.objectType, check.node);
       }
     }
@@ -1399,28 +1367,21 @@
     List<IsCheck> negativeTests = <IsCheck>[];
     bool simpleCondition =
         handleCondition(node.condition, positiveTests, negativeTests);
-    LocalsHandler localsBefore = _locals;
-    FieldInitializationScope fieldScopeBefore = _fieldScope;
-    _locals = new LocalsHandler.from(localsBefore, node);
-    _fieldScope = new FieldInitializationScope.from(fieldScopeBefore);
+    LocalState stateBefore = _state;
+    _state = new LocalState.childPath(stateBefore, node);
     _updateIsChecks(positiveTests, negativeTests);
     visit(node.then);
 
-    LocalsHandler localsAfterThen = _locals;
-    FieldInitializationScope fieldScopeAfterThen = _fieldScope;
-    _locals = new LocalsHandler.from(localsBefore, node);
-    _fieldScope = new FieldInitializationScope.from(fieldScopeBefore);
+    LocalState stateAfterThen = _state;
+    _state = new LocalState.childPath(stateBefore, node);
     if (simpleCondition) {
       _updateIsChecks(negativeTests, positiveTests);
     }
     visit(node.otherwise);
-    LocalsHandler localsAfterElse = _locals;
-    FieldInitializationScope fieldScopeAfterElse = _fieldScope;
+    LocalState stateAfterElse = _state;
 
-    _locals = localsBefore.mergeDiamondFlow(
-        _inferrer, localsAfterThen, localsAfterElse);
-    _fieldScope = fieldScopeBefore?.mergeDiamondFlow(
-        _inferrer, fieldScopeAfterThen, fieldScopeAfterElse);
+    _state =
+        stateBefore.mergeDiamondFlow(_inferrer, stateAfterThen, stateAfterElse);
     return null;
   }
 
@@ -1456,15 +1417,13 @@
         _negativeIsChecks = <IsCheck>[];
       }
       visit(node.left, conditionContext: _accumulateIsChecks);
-      LocalsHandler localsBefore = _locals;
-      FieldInitializationScope fieldScopeBefore = _fieldScope;
-      _locals = new LocalsHandler.from(localsBefore, node);
-      _fieldScope = new FieldInitializationScope.from(fieldScopeBefore);
+      LocalState stateBefore = _state;
+      _state = new LocalState.childPath(stateBefore, node);
       _updateIsChecks(_positiveIsChecks, _negativeIsChecks);
 
-      LocalsHandler narrowed;
+      LocalState narrowed;
       if (oldAccumulateIsChecks) {
-        narrowed = new LocalsHandler.topLevelCopyOf(_locals);
+        narrowed = new LocalState.deepCopyOf(_state);
       } else {
         _accumulateIsChecks = false;
         _positiveIsChecks = oldPositiveIsChecks;
@@ -1474,17 +1433,16 @@
       if (oldAccumulateIsChecks) {
         bool invalidatedInRightHandSide(IsCheck check) {
           TypeInformation narrowedType =
-              narrowed.use(_inferrer, _capturedAndBoxed, check.local);
+              narrowed.readLocal(_inferrer, _capturedAndBoxed, check.local);
           TypeInformation currentType =
-              _locals.use(_inferrer, _capturedAndBoxed, check.local);
+              _state.readLocal(_inferrer, _capturedAndBoxed, check.local);
           return narrowedType != currentType;
         }
 
         _positiveIsChecks.removeWhere(invalidatedInRightHandSide);
         _negativeIsChecks.removeWhere(invalidatedInRightHandSide);
       }
-      _locals = localsBefore.mergeFlow(_inferrer, _locals);
-      _fieldScope = fieldScopeBefore;
+      _state = stateBefore.mergeFlow(_inferrer, _state);
       return _types.boolType;
     } else if (node.operator == '||') {
       _conditionIsSimple = false;
@@ -1492,16 +1450,13 @@
       List<IsCheck> negativeIsChecks = <IsCheck>[];
       bool isSimple =
           handleCondition(node.left, positiveIsChecks, negativeIsChecks);
-      LocalsHandler localsBefore = _locals;
-      FieldInitializationScope fieldScopeBefore = _fieldScope;
-      _locals = new LocalsHandler.from(_locals, node);
-      _fieldScope = new FieldInitializationScope.from(fieldScopeBefore);
+      LocalState stateBefore = _state;
+      _state = new LocalState.childPath(stateBefore, node);
       if (isSimple) {
         _updateIsChecks(negativeIsChecks, positiveIsChecks);
       }
       visit(node.right, conditionContext: false);
-      _locals = localsBefore.mergeFlow(_inferrer, _locals);
-      _fieldScope = fieldScopeBefore;
+      _state = stateBefore.mergeFlow(_inferrer, _state);
       return _types.boolType;
     }
     failedAt(CURRENT_ELEMENT_SPANNABLE,
@@ -1515,24 +1470,17 @@
     List<IsCheck> negativeTests = <IsCheck>[];
     bool simpleCondition =
         handleCondition(node.condition, positiveTests, negativeTests);
-    LocalsHandler localsBefore = _locals;
-    _locals = new LocalsHandler.from(localsBefore, node);
-    FieldInitializationScope fieldScopeBefore = _fieldScope;
-    _fieldScope = new FieldInitializationScope.from(fieldScopeBefore);
+    LocalState stateBefore = _state;
+    _state = new LocalState.childPath(stateBefore, node);
     _updateIsChecks(positiveTests, negativeTests);
     TypeInformation firstType = visit(node.then);
-    LocalsHandler localsAfterThen = _locals;
-    FieldInitializationScope fieldScopeAfterThen = _fieldScope;
-    _locals = new LocalsHandler.from(localsBefore, node);
-    _fieldScope = new FieldInitializationScope.from(fieldScopeBefore);
+    LocalState stateAfterThen = _state;
+    _state = new LocalState.childPath(stateBefore, node);
     if (simpleCondition) _updateIsChecks(negativeTests, positiveTests);
     TypeInformation secondType = visit(node.otherwise);
-    LocalsHandler localsAfterElse = _locals;
-    FieldInitializationScope fieldScopeAfterElse = _fieldScope;
-    _locals = localsBefore.mergeDiamondFlow(
-        _inferrer, localsAfterThen, localsAfterElse);
-    _fieldScope = fieldScopeBefore?.mergeDiamondFlow(
-        _inferrer, fieldScopeAfterThen, fieldScopeAfterElse);
+    LocalState stateAfterElse = _state;
+    _state =
+        stateBefore.mergeDiamondFlow(_inferrer, stateAfterThen, stateAfterElse);
     return _types.allocateDiamondPhi(firstType, secondType);
   }
 
@@ -1544,7 +1492,7 @@
     // analyzing the closure.
     // TODO(herhut): Analyze whether closure exposes this. Possibly using
     // whether the created closure as a `thisLocal`.
-    _markThisAsExposed();
+    _state.markThisAsExposed();
 
     ClosureRepresentationInfo info = _closureDataLookup.getClosureInfo(node);
 
@@ -1557,7 +1505,7 @@
           _inferrer.recordTypeOfField(field, thisType);
         }
         TypeInformation localType =
-            _locals.use(_inferrer, _capturedAndBoxed, variable);
+            _state.readLocal(_inferrer, _capturedAndBoxed, variable);
         // The type is null for type parameters.
         if (localType != null) {
           _inferrer.recordTypeOfField(field, localType);
@@ -1573,17 +1521,14 @@
     if (variable != null) {
       Local local = _localsMap.getLocalVariable(variable);
       DartType type = _localsMap.getLocalType(_elementMap, local);
-      _locals.update(
+      _state.updateLocal(
           _inferrer, _capturedAndBoxed, local, localFunctionType, node, type);
     }
 
     // We don't put the closure in the work queue of the
     // inferrer, because it will share information with its enclosing
     // method, like for example the types of local variables.
-    LocalsHandler closureLocals =
-        new LocalsHandler.from(_locals, node, useOtherTryBlock: false);
-    FieldInitializationScope closureFieldScope =
-        new FieldInitializationScope.from(_fieldScope);
+    LocalState closureState = new LocalState.closure(_state, node);
     KernelTypeGraphBuilder visitor = new KernelTypeGraphBuilder(
         _options,
         _closedWorld,
@@ -1591,8 +1536,7 @@
         info.callMethod,
         functionNode,
         _localsMap,
-        closureLocals,
-        closureFieldScope,
+        closureState,
         _capturedAndBoxed);
     visitor.run();
     _inferrer.recordReturnType(info.callMethod, visitor._returnType);
@@ -1655,41 +1599,30 @@
 
   @override
   visitTryCatch(ir.TryCatch node) {
-    LocalsHandler localsBefore = _locals;
-    FieldInitializationScope fieldScopeBefore = _fieldScope;
-    _locals = new LocalsHandler.from(localsBefore, node,
-        isTry: true, useOtherTryBlock: false);
-    _fieldScope = new FieldInitializationScope.from(fieldScopeBefore);
-    initializationIsIndefinite();
+    LocalState stateBefore = _state;
+    _state = new LocalState.tryBlock(stateBefore, node);
+    _state.markInitializationAsIndefinite();
     visit(node.body);
-    LocalsHandler localsAfterBody = _locals;
-    _locals = localsBefore.mergeFlow(_inferrer, localsAfterBody);
-    _fieldScope = fieldScopeBefore;
+    LocalState stateAfterBody = _state;
+    _state = stateBefore.mergeFlow(_inferrer, stateAfterBody);
     for (ir.Catch catchBlock in node.catches) {
-      LocalsHandler localsBeforeCatch = _locals;
-      FieldInitializationScope fieldScopeBeforeCatch = _fieldScope;
-      _locals = new LocalsHandler.from(localsBeforeCatch, catchBlock);
-      _fieldScope = new FieldInitializationScope.from(fieldScopeBeforeCatch);
+      LocalState stateBeforeCatch = _state;
+      _state = new LocalState.childPath(stateBeforeCatch, catchBlock);
       visit(catchBlock);
-      LocalsHandler localsAfterCatch = _locals;
-      _locals = localsBeforeCatch.mergeFlow(_inferrer, localsAfterCatch);
-      _fieldScope = fieldScopeBeforeCatch;
+      LocalState stateAfterCatch = _state;
+      _state = stateBeforeCatch.mergeFlow(_inferrer, stateAfterCatch);
     }
     return null;
   }
 
   @override
   visitTryFinally(ir.TryFinally node) {
-    LocalsHandler localsBefore = _locals;
-    FieldInitializationScope fieldScopeBefore = _fieldScope;
-    _locals = new LocalsHandler.from(localsBefore, node,
-        isTry: true, useOtherTryBlock: false);
-    _fieldScope = new FieldInitializationScope.from(fieldScopeBefore);
-    initializationIsIndefinite();
+    LocalState stateBefore = _state;
+    _state = new LocalState.tryBlock(stateBefore, node);
+    _state.markInitializationAsIndefinite();
     visit(node.body);
-    LocalsHandler localsAfterBody = _locals;
-    _locals = localsBefore.mergeFlow(_inferrer, localsAfterBody);
-    _fieldScope = fieldScopeBefore;
+    LocalState stateAfterBody = _state;
+    _state = stateBefore.mergeFlow(_inferrer, stateAfterBody);
     visit(node.finalizer);
     return null;
   }
@@ -1709,15 +1642,15 @@
         mask = _types.dynamicType;
       }
       Local local = _localsMap.getLocalVariable(exception);
-      _locals.update(
+      _state.updateLocal(
           _inferrer, _capturedAndBoxed, local, mask, node, const DynamicType());
     }
     ir.VariableDeclaration stackTrace = node.stackTrace;
     if (stackTrace != null) {
       Local local = _localsMap.getLocalVariable(stackTrace);
       // TODO(johnniwinther): Use a mask based on [StackTrace].
-      _locals.update(_inferrer, _capturedAndBoxed, local, _types.dynamicType,
-          node, const DynamicType());
+      _state.updateLocal(_inferrer, _capturedAndBoxed, local,
+          _types.dynamicType, node, const DynamicType());
     }
     visit(node.body);
     return null;
@@ -1726,13 +1659,13 @@
   @override
   TypeInformation visitThrow(ir.Throw node) {
     visit(node.expression);
-    _locals.seenReturnOrThrow = true;
+    _state.seenReturnOrThrow = true;
     return _types.nonNullEmpty();
   }
 
   @override
   TypeInformation visitRethrow(ir.Rethrow node) {
-    _locals.seenReturnOrThrow = true;
+    _state.seenReturnOrThrow = true;
     return _types.nonNullEmpty();
   }
 
@@ -1749,7 +1682,7 @@
   TypeInformation visitSuperPropertyGet(ir.SuperPropertyGet node) {
     // TODO(herhut): We could do better here if we knew what we
     // are calling does not expose this.
-    _markThisAsExposed();
+    _state.markThisAsExposed();
 
     MemberEntity member =
         _elementMap.getSuperMember(_analyzedMember, node.name);
@@ -1771,7 +1704,7 @@
   TypeInformation visitSuperPropertySet(ir.SuperPropertySet node) {
     // TODO(herhut): We could do better here if we knew what we
     // are calling does not expose this.
-    _markThisAsExposed();
+    _state.markThisAsExposed();
 
     TypeInformation rhsType = visit(node.value);
     MemberEntity member =
@@ -1791,7 +1724,7 @@
   TypeInformation visitSuperMethodInvocation(ir.SuperMethodInvocation node) {
     // TODO(herhut): We could do better here if we knew what we
     // are calling does not expose this.
-    _markThisAsExposed();
+    _state.markThisAsExposed();
 
     MemberEntity member =
         _elementMap.getSuperMember(_analyzedMember, node.name);
@@ -1867,3 +1800,188 @@
 
   Refinement(this.selector, this.mask);
 }
+
+class LocalState {
+  final LocalsHandler _locals;
+  final FieldInitializationScope _fields;
+  bool seenReturnOrThrow = false;
+  bool seenBreakOrContinue = false;
+  LocalsHandler _tryBlock;
+
+  LocalState.initial(ir.TreeNode node, {bool inGenerativeConstructor})
+      : this.internal(
+            new LocalsHandler(node),
+            inGenerativeConstructor ? new FieldInitializationScope() : null,
+            null,
+            seenReturnOrThrow: false,
+            seenBreakOrContinue: false);
+
+  LocalState.childPath(LocalState other, ir.TreeNode node)
+      : this.internal(new LocalsHandler.from(other._locals, node, isTry: false),
+            new FieldInitializationScope.from(other._fields), other._tryBlock,
+            seenReturnOrThrow: false, seenBreakOrContinue: false);
+
+  LocalState.closure(LocalState other, ir.TreeNode node)
+      : this.internal(new LocalsHandler.from(other._locals, node, isTry: false),
+            new FieldInitializationScope.from(other._fields), null,
+            seenReturnOrThrow: false, seenBreakOrContinue: false);
+
+  factory LocalState.tryBlock(LocalState other, ir.TreeNode node) {
+    LocalsHandler locals =
+        new LocalsHandler.from(other._locals, node, isTry: true);
+    FieldInitializationScope fieldScope =
+        new FieldInitializationScope.from(other._fields);
+    LocalsHandler tryBlock = locals;
+    return new LocalState.internal(locals, fieldScope, tryBlock,
+        seenReturnOrThrow: false, seenBreakOrContinue: false);
+  }
+
+  LocalState.deepCopyOf(LocalState other)
+      : _locals = new LocalsHandler.deepCopyOf(other._locals),
+        _tryBlock = other._tryBlock,
+        _fields = other._fields;
+
+  LocalState.internal(this._locals, this._fields, this._tryBlock,
+      {this.seenReturnOrThrow, this.seenBreakOrContinue});
+
+  bool get aborts {
+    return seenReturnOrThrow || seenBreakOrContinue;
+  }
+
+  bool get isThisExposed {
+    return _fields == null || _fields.isThisExposed;
+  }
+
+  void markThisAsExposed() {
+    _fields?.isThisExposed = true;
+  }
+
+  void markInitializationAsIndefinite() {
+    _fields?.isIndefinite = true;
+  }
+
+  TypeInformation readField(FieldEntity field) {
+    return _fields.readField(field);
+  }
+
+  void updateField(FieldEntity field, TypeInformation type) {
+    _fields.updateField(field, type);
+  }
+
+  TypeInformation readLocal(InferrerEngine inferrer,
+      Map<Local, FieldEntity> capturedAndBoxed, Local local) {
+    FieldEntity field = capturedAndBoxed[local];
+    if (field != null) {
+      return inferrer.typeOfMember(field);
+    } else {
+      return _locals.use(inferrer, local);
+    }
+  }
+
+  void updateLocal(
+      InferrerEngine inferrer,
+      Map<Local, FieldEntity> capturedAndBoxed,
+      Local local,
+      TypeInformation type,
+      ir.Node node,
+      DartType staticType) {
+    assert(type != null);
+    type = inferrer.types.narrowType(type, staticType);
+
+    FieldEntity field = capturedAndBoxed[local];
+    if (field != null) {
+      inferrer.recordTypeOfField(field, type);
+    } else {
+      _locals.update(inferrer, local, type, node, staticType, _tryBlock);
+    }
+  }
+
+  void narrowLocal(
+      InferrerEngine inferrer,
+      Map<Local, FieldEntity> capturedAndBoxed,
+      Local local,
+      DartType type,
+      ir.Node node) {
+    TypeInformation existing = readLocal(inferrer, capturedAndBoxed, local);
+    TypeInformation newType =
+        inferrer.types.narrowType(existing, type, isNullable: false);
+    updateLocal(inferrer, capturedAndBoxed, local, newType, node, type);
+  }
+
+  LocalState mergeFlow(InferrerEngine inferrer, LocalState other) {
+    seenReturnOrThrow = false;
+    seenBreakOrContinue = false;
+
+    if (other.aborts) {
+      return this;
+    }
+    LocalsHandler locals = _locals.mergeFlow(inferrer, other._locals);
+    return new LocalState.internal(locals, _fields, _tryBlock,
+        seenReturnOrThrow: seenReturnOrThrow,
+        seenBreakOrContinue: seenBreakOrContinue);
+  }
+
+  LocalState mergeDiamondFlow(
+      InferrerEngine inferrer, LocalState thenBranch, LocalState elseBranch) {
+    seenReturnOrThrow =
+        thenBranch.seenReturnOrThrow && elseBranch.seenReturnOrThrow;
+    seenBreakOrContinue =
+        thenBranch.seenBreakOrContinue && elseBranch.seenBreakOrContinue;
+
+    LocalsHandler locals;
+    if (aborts) {
+      locals = _locals;
+    } else if (thenBranch.aborts) {
+      locals = _locals.mergeFlow(inferrer, elseBranch._locals, inPlace: true);
+    } else if (elseBranch.aborts) {
+      locals = _locals.mergeFlow(inferrer, thenBranch._locals, inPlace: true);
+    } else {
+      locals = _locals.mergeDiamondFlow(
+          inferrer, thenBranch._locals, elseBranch._locals);
+    }
+
+    FieldInitializationScope fieldScope = _fields?.mergeDiamondFlow(
+        inferrer, thenBranch._fields, elseBranch._fields);
+    return new LocalState.internal(locals, fieldScope, _tryBlock,
+        seenReturnOrThrow: seenReturnOrThrow,
+        seenBreakOrContinue: seenBreakOrContinue);
+  }
+
+  LocalState mergeAfterBreaks(InferrerEngine inferrer, List<LocalState> states,
+      {bool keepOwnLocals: true}) {
+    bool allBranchesAbort = true;
+    for (LocalState state in states) {
+      allBranchesAbort = allBranchesAbort && state.seenReturnOrThrow;
+    }
+
+    keepOwnLocals = keepOwnLocals && !seenReturnOrThrow;
+
+    LocalsHandler locals = _locals.mergeAfterBreaks(
+        inferrer,
+        states
+            .where((LocalState state) => !state.seenReturnOrThrow)
+            .map((LocalState state) => state._locals),
+        keepOwnLocals: keepOwnLocals);
+    seenReturnOrThrow = allBranchesAbort && !keepOwnLocals;
+    return new LocalState.internal(locals, _fields, _tryBlock,
+        seenReturnOrThrow: seenReturnOrThrow,
+        seenBreakOrContinue: seenBreakOrContinue);
+  }
+
+  bool mergeAll(InferrerEngine inferrer, List<LocalState> states) {
+    assert(!seenReturnOrThrow);
+    return _locals.mergeAll(
+        inferrer,
+        states
+            .where((LocalState state) => !state.seenReturnOrThrow)
+            .map((LocalState state) => state._locals));
+  }
+
+  void startLoop(InferrerEngine inferrer, ir.Node loop) {
+    _locals.startLoop(inferrer, loop);
+  }
+
+  void endLoop(InferrerEngine inferrer, ir.Node loop) {
+    _locals.endLoop(inferrer, loop);
+  }
+}
diff --git a/pkg/compiler/lib/src/inferrer/locals_handler.dart b/pkg/compiler/lib/src/inferrer/locals_handler.dart
index 2ebc446..16fc757 100644
--- a/pkg/compiler/lib/src/inferrer/locals_handler.dart
+++ b/pkg/compiler/lib/src/inferrer/locals_handler.dart
@@ -251,69 +251,37 @@
  */
 class LocalsHandler {
   final VariableScope _locals;
-  LocalsHandler _tryBlock;
-  bool seenReturnOrThrow = false;
-  bool seenBreakOrContinue = false;
-
-  bool get aborts {
-    return seenReturnOrThrow || seenBreakOrContinue;
-  }
-
-  bool get inTryBlock => _tryBlock != null;
-
-  LocalsHandler.internal(ir.Node block, this._locals, this._tryBlock);
 
   LocalsHandler(ir.Node block)
-      : _locals = new VariableScope(block, isTry: false),
-        _tryBlock = null;
+      : _locals = new VariableScope(block, isTry: false);
 
   LocalsHandler.from(LocalsHandler other, ir.Node block,
       {bool isTry: false, bool useOtherTryBlock: true})
-      : _locals =
-            new VariableScope(block, isTry: isTry, parent: other._locals) {
-    _tryBlock = useOtherTryBlock ? other._tryBlock : this;
-  }
+      : _locals = new VariableScope(block, isTry: isTry, parent: other._locals);
 
   LocalsHandler.deepCopyOf(LocalsHandler other)
-      : _locals = new VariableScope.deepCopyOf(other._locals),
-        _tryBlock = other._tryBlock;
+      : _locals = new VariableScope.deepCopyOf(other._locals);
 
-  LocalsHandler.topLevelCopyOf(LocalsHandler other)
-      : _locals = new VariableScope.topLevelCopyOf(other._locals),
-        _tryBlock = other._tryBlock;
-
-  TypeInformation use(InferrerEngine inferrer,
-      Map<Local, FieldEntity> capturedAndBoxed, Local local) {
-    FieldEntity field = capturedAndBoxed[local];
-    if (field != null) {
-      return inferrer.typeOfMember(field);
-    } else {
-      return _locals[local];
-    }
+  TypeInformation use(InferrerEngine inferrer, Local local) {
+    return _locals[local];
   }
 
-  void update(InferrerEngine inferrer, Map<Local, FieldEntity> capturedAndBoxed,
-      Local local, TypeInformation type, ir.Node node, DartType staticType) {
-    assert(type != null);
-    type = inferrer.types.narrowType(type, staticType);
-
-    FieldEntity field = capturedAndBoxed[local];
-    if (field != null) {
-      inferrer.recordTypeOfField(field, type);
-    } else if (inTryBlock) {
+  void update(InferrerEngine inferrer, Local local, TypeInformation type,
+      ir.Node node, DartType staticType, LocalsHandler tryBlock) {
+    if (tryBlock != null) {
       // We don't know if an assignment in a try block
       // will be executed, so all assignments in that block are
       // potential types after we have left it. We update the parent
       // of the try block so that, at exit of the try block, we get
       // the right phi for it.
-      TypeInformation existing = _tryBlock._locals.parent[local];
+      TypeInformation existing = tryBlock._locals.parent[local];
       if (existing != null) {
         TypeInformation phiType = inferrer.types.allocatePhi(
-            _tryBlock._locals.block, local, existing,
-            isTry: _tryBlock._locals.isTry);
+            tryBlock._locals.block, local, existing,
+            isTry: tryBlock._locals.isTry);
         TypeInformation inputType =
             inferrer.types.addPhiInput(local, phiType, type);
-        _tryBlock._locals.parent[local] = inputType;
+        tryBlock._locals.parent[local] = inputType;
       }
       // Update the current handler unconditionally with the new
       // type.
@@ -323,30 +291,21 @@
     }
   }
 
-  void narrow(InferrerEngine inferrer, Map<Local, FieldEntity> capturedAndBoxed,
-      Local local, DartType type, ir.Node node) {
-    TypeInformation existing = use(inferrer, capturedAndBoxed, local);
-    TypeInformation newType =
-        inferrer.types.narrowType(existing, type, isNullable: false);
-    update(inferrer, capturedAndBoxed, local, newType, node, type);
-  }
-
   /// Returns the join between this locals handler and [other] which models the
   /// flow through either this or [other].
-  LocalsHandler mergeFlow(InferrerEngine inferrer, LocalsHandler other) {
-    seenReturnOrThrow = false;
-    seenBreakOrContinue = false;
-
-    if (other.aborts) {
-      return this;
-    } else {
-      other._locals.forEachOwnLocal((Local local, TypeInformation type) {
-        TypeInformation myType = _locals[local];
-        if (myType == null) return; // Variable is only defined in [other].
-        if (type == myType) return;
-        _locals[local] = inferrer.types.allocateDiamondPhi(myType, type);
-      });
-    }
+  ///
+  /// If [inPlace] is `true`, the variable types in this locals handler are
+  /// replaced by the variables types in [other]. Otherwise the variable types
+  /// from both are merged with a phi type.
+  LocalsHandler mergeFlow(InferrerEngine inferrer, LocalsHandler other,
+      {bool inPlace: false}) {
+    other._locals.forEachOwnLocal((Local local, TypeInformation type) {
+      TypeInformation myType = _locals[local];
+      if (myType == null) return; // Variable is only defined in [other].
+      if (type == myType) return;
+      _locals[local] =
+          inPlace ? type : inferrer.types.allocateDiamondPhi(myType, type);
+    });
     return this;
   }
 
@@ -355,48 +314,27 @@
   LocalsHandler mergeDiamondFlow(InferrerEngine inferrer,
       LocalsHandler thenBranch, LocalsHandler elseBranch) {
     assert(elseBranch != null);
-    seenReturnOrThrow =
-        thenBranch.seenReturnOrThrow && elseBranch.seenReturnOrThrow;
-    seenBreakOrContinue =
-        thenBranch.seenBreakOrContinue && elseBranch.seenBreakOrContinue;
-    if (aborts) return this;
 
-    void inPlaceUpdateOneBranch(LocalsHandler other) {
-      other._locals.forEachOwnLocal((Local local, TypeInformation type) {
-        TypeInformation myType = _locals[local];
-        if (myType == null) return; // Variable is only defined in [other].
-        if (type == myType) return;
-        _locals[local] = type;
-      });
-    }
-
-    if (thenBranch.aborts) {
-      inPlaceUpdateOneBranch(elseBranch);
-    } else if (elseBranch.aborts) {
-      inPlaceUpdateOneBranch(thenBranch);
-    } else {
-      void mergeLocal(Local local) {
-        TypeInformation myType = _locals[local];
-        if (myType == null) return;
-        TypeInformation elseType = elseBranch._locals[local];
-        TypeInformation thenType = thenBranch._locals[local];
-        if (thenType == elseType) {
-          _locals[local] = thenType;
-        } else {
-          _locals[local] =
-              inferrer.types.allocateDiamondPhi(thenType, elseType);
-        }
+    void mergeLocal(Local local) {
+      TypeInformation myType = _locals[local];
+      if (myType == null) return;
+      TypeInformation elseType = elseBranch._locals[local];
+      TypeInformation thenType = thenBranch._locals[local];
+      if (thenType == elseType) {
+        _locals[local] = thenType;
+      } else {
+        _locals[local] = inferrer.types.allocateDiamondPhi(thenType, elseType);
       }
-
-      thenBranch._locals.forEachOwnLocal((Local local, _) {
-        mergeLocal(local);
-      });
-      elseBranch._locals.forEachOwnLocal((Local local, _) {
-        // Discard locals we already processed when iterating over
-        // [thenBranch]'s locals.
-        if (!thenBranch._locals.updates(local)) mergeLocal(local);
-      });
     }
+
+    thenBranch._locals.forEachOwnLocal((Local local, _) {
+      mergeLocal(local);
+    });
+    elseBranch._locals.forEachOwnLocal((Local local, _) {
+      // Discard locals we already processed when iterating over
+      // [thenBranch]'s locals.
+      if (!thenBranch._locals.updates(local)) mergeLocal(local);
+    });
     return this;
   }
 
@@ -431,7 +369,7 @@
    * labeled statement that do not break out.
    */
   LocalsHandler mergeAfterBreaks(
-      InferrerEngine inferrer, List<LocalsHandler> handlers,
+      InferrerEngine inferrer, Iterable<LocalsHandler> handlers,
       {bool keepOwnLocals: true}) {
     ir.Node level = _locals.block;
     // Use a separate locals handler to perform the merge in, so that Phi
@@ -440,15 +378,13 @@
     LocalsHandler merged =
         new LocalsHandler.from(this, level, isTry: _locals.isTry);
     Set<Local> seenLocals = new Setlet<Local>();
-    bool allBranchesAbort = true;
     // Merge all other handlers.
     for (LocalsHandler handler in handlers) {
-      allBranchesAbort = allBranchesAbort && handler.seenReturnOrThrow;
       merged._mergeHandler(inferrer, handler, seenLocals);
     }
     // If we want to keep own locals, we merge [seenLocals] from [this] into
     // [merged] to update the Phi nodes with original values.
-    if (keepOwnLocals && !seenReturnOrThrow) {
+    if (keepOwnLocals) {
       for (Local variable in seenLocals) {
         TypeInformation originalType = _locals[variable];
         if (originalType != null) {
@@ -462,8 +398,6 @@
     merged._locals.forEachOwnLocal((Local variable, TypeInformation type) {
       _locals[variable] = inferrer.types.simplifyPhi(level, variable, type);
     });
-    seenReturnOrThrow =
-        allBranchesAbort && (!keepOwnLocals || seenReturnOrThrow);
     return this;
   }
 
@@ -475,7 +409,6 @@
    */
   bool _mergeHandler(InferrerEngine inferrer, LocalsHandler other,
       [Set<Local> seen]) {
-    if (other.seenReturnOrThrow) return false;
     bool changed = false;
     other._locals.forEachLocalUntilNode(_locals.block, (local, otherType) {
       TypeInformation myType = _locals[local];
@@ -500,9 +433,8 @@
    * Merge all [LocalsHandler] in [handlers] into this handler.
    * Returns whether a local in this handler has changed.
    */
-  bool mergeAll(InferrerEngine inferrer, List<LocalsHandler> handlers) {
+  bool mergeAll(InferrerEngine inferrer, Iterable<LocalsHandler> handlers) {
     bool changed = false;
-    assert(!seenReturnOrThrow);
     handlers.forEach((other) {
       changed = _mergeHandler(inferrer, other) || changed;
     });