Use VariableScope.parent instead of VariableScope.block

Change-Id: I5cf15ae9562f365d0dad8b4f47b7e8da74908198
Reviewed-on: https://dart-review.googlesource.com/c/85706
Commit-Queue: Johnni Winther <johnniwinther@google.com>
Reviewed-by: Sigmund Cherem <sigmund@google.com>
diff --git a/pkg/compiler/lib/src/inferrer/builder_kernel.dart b/pkg/compiler/lib/src/inferrer/builder_kernel.dart
index aea3904..add6849 100644
--- a/pkg/compiler/lib/src/inferrer/builder_kernel.dart
+++ b/pkg/compiler/lib/src/inferrer/builder_kernel.dart
@@ -126,7 +126,7 @@
             : <Local, FieldEntity>{} {
     if (_state != null) return;
 
-    _state = new LocalState.initial(_analyzedNode,
+    _state = new LocalState.initial(
         inGenerativeConstructor: _inGenerativeConstructor);
   }
 
@@ -479,7 +479,7 @@
     handleCondition(node.condition);
     LocalState afterConditionWhenTrue = _stateAfterWhenTrue;
     LocalState afterConditionWhenFalse = _stateAfterWhenFalse;
-    _state = new LocalState.childPath(afterConditionWhenFalse, node.message);
+    _state = new LocalState.childPath(afterConditionWhenFalse);
     visit(node.message);
     LocalState stateAfterMessage = _state;
     stateAfterMessage.seenReturnOrThrow = true;
@@ -545,7 +545,7 @@
         changed = false;
         for (ir.SwitchCase switchCase in node.cases) {
           LocalState stateBeforeCase = _state;
-          _state = new LocalState.childPath(stateBeforeCase, switchCase);
+          _state = new LocalState.childPath(stateBeforeCase);
           visit(switchCase);
           LocalState stateAfterCase = _state;
           changed =
@@ -565,7 +565,7 @@
         if (switchCase.isDefault) {
           hasDefaultCase = true;
         }
-        _state = new LocalState.childPath(stateBeforeCase, switchCase);
+        _state = new LocalState.childPath(stateBeforeCase);
         visit(switchCase);
         statesToMerge.add(_state);
       }
@@ -1041,7 +1041,7 @@
       // Setup (and clear in case of multiple iterations of the loop)
       // the lists of breaks and continues seen in the loop.
       _setupBreaksAndContinues(target);
-      _state = new LocalState.childPath(stateBefore, node);
+      _state = new LocalState.childPath(stateBefore);
       logic();
       changed = stateBefore.mergeAll(_inferrer, _getLoopBackEdges(target));
     } while (changed);
@@ -1346,10 +1346,8 @@
     if (operand is ir.VariableGet) {
       Local local = _localsMap.getLocalVariable(operand.variable);
       DartType type = _elementMap.getDartType(node.type);
-      LocalState stateAfterCheckWhenTrue =
-          new LocalState.childPath(_state, node);
-      LocalState stateAfterCheckWhenFalse =
-          new LocalState.childPath(_state, node);
+      LocalState stateAfterCheckWhenTrue = new LocalState.childPath(_state);
+      LocalState stateAfterCheckWhenFalse = new LocalState.childPath(_state);
       stateAfterCheckWhenTrue.narrowLocal(
           _inferrer, _capturedAndBoxed, local, type, node);
       _setStateAfter(_state, stateAfterCheckWhenTrue, stateAfterCheckWhenFalse);
@@ -1362,10 +1360,8 @@
     if (receiver is ir.VariableGet) {
       Local local = _localsMap.getLocalVariable(receiver.variable);
       DartType localType = _localsMap.getLocalType(_elementMap, local);
-      LocalState stateAfterCheckWhenTrue =
-          new LocalState.childPath(_state, node);
-      LocalState stateAfterCheckWhenFalse =
-          new LocalState.childPath(_state, node);
+      LocalState stateAfterCheckWhenTrue = new LocalState.childPath(_state);
+      LocalState stateAfterCheckWhenFalse = new LocalState.childPath(_state);
       stateAfterCheckWhenTrue.updateLocal(_inferrer, _capturedAndBoxed, local,
           _types.nullType, node, localType);
       stateAfterCheckWhenFalse.narrowLocal(_inferrer, _capturedAndBoxed, local,
@@ -1380,11 +1376,10 @@
     handleCondition(node.condition);
     LocalState stateAfterConditionWhenTrue = _stateAfterWhenTrue;
     LocalState stateAfterConditionWhenFalse = _stateAfterWhenFalse;
-    _state = new LocalState.childPath(stateAfterConditionWhenTrue, node.then);
+    _state = new LocalState.childPath(stateAfterConditionWhenTrue);
     visit(node.then);
     LocalState stateAfterThen = _state;
-    _state =
-        new LocalState.childPath(stateAfterConditionWhenFalse, node.otherwise);
+    _state = new LocalState.childPath(stateAfterConditionWhenFalse);
     visit(node.otherwise);
     LocalState stateAfterElse = _state;
     _state =
@@ -1413,17 +1408,17 @@
   TypeInformation visitLogicalExpression(ir.LogicalExpression node) {
     if (node.operator == '&&') {
       LocalState stateBefore = _state;
-      _state = new LocalState.childPath(stateBefore, node.left);
+      _state = new LocalState.childPath(stateBefore);
       handleCondition(node.left);
       LocalState stateAfterLeftWhenTrue = _stateAfterWhenTrue;
       LocalState stateAfterLeftWhenFalse = _stateAfterWhenFalse;
-      _state = new LocalState.childPath(stateAfterLeftWhenTrue, node.right);
+      _state = new LocalState.childPath(stateAfterLeftWhenTrue);
       handleCondition(node.right);
       LocalState stateAfterRightWhenTrue = _stateAfterWhenTrue;
       LocalState stateAfterRightWhenFalse = _stateAfterWhenFalse;
       LocalState stateAfterWhenTrue = stateAfterRightWhenTrue;
-      LocalState stateAfterWhenFalse =
-          new LocalState.childPath(stateBefore, node).mergeDiamondFlow(
+      LocalState stateAfterWhenFalse = new LocalState.childPath(stateBefore)
+          .mergeDiamondFlow(
               _inferrer, stateAfterLeftWhenFalse, stateAfterRightWhenFalse);
       LocalState after = stateBefore.mergeDiamondFlow(
           _inferrer, stateAfterWhenTrue, stateAfterWhenFalse);
@@ -1431,16 +1426,16 @@
       return _types.boolType;
     } else if (node.operator == '||') {
       LocalState stateBefore = _state;
-      _state = new LocalState.childPath(stateBefore, node.left);
+      _state = new LocalState.childPath(stateBefore);
       handleCondition(node.left);
       LocalState stateAfterLeftWhenTrue = _stateAfterWhenTrue;
       LocalState stateAfterLeftWhenFalse = _stateAfterWhenFalse;
-      _state = new LocalState.childPath(stateAfterLeftWhenFalse, node.right);
+      _state = new LocalState.childPath(stateAfterLeftWhenFalse);
       handleCondition(node.right);
       LocalState stateAfterRightWhenTrue = _stateAfterWhenTrue;
       LocalState stateAfterRightWhenFalse = _stateAfterWhenFalse;
-      LocalState stateAfterWhenTrue =
-          new LocalState.childPath(stateBefore, node).mergeDiamondFlow(
+      LocalState stateAfterWhenTrue = new LocalState.childPath(stateBefore)
+          .mergeDiamondFlow(
               _inferrer, stateAfterLeftWhenTrue, stateAfterRightWhenTrue);
       LocalState stateAfterWhenFalse = stateAfterRightWhenFalse;
       LocalState stateAfter = stateBefore.mergeDiamondFlow(
@@ -1459,10 +1454,10 @@
     handleCondition(node.condition);
     LocalState stateAfterWhenTrue = _stateAfterWhenTrue;
     LocalState stateAfterWhenFalse = _stateAfterWhenFalse;
-    _state = new LocalState.childPath(stateAfterWhenTrue, node.then);
+    _state = new LocalState.childPath(stateAfterWhenTrue);
     TypeInformation firstType = visit(node.then);
     LocalState stateAfterThen = _state;
-    _state = new LocalState.childPath(stateAfterWhenFalse, node.otherwise);
+    _state = new LocalState.childPath(stateAfterWhenFalse);
     TypeInformation secondType = visit(node.otherwise);
     LocalState stateAfterElse = _state;
     _state =
@@ -1514,7 +1509,7 @@
     // 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.
-    LocalState closureState = new LocalState.closure(_state, node);
+    LocalState closureState = new LocalState.closure(_state);
     KernelTypeGraphBuilder visitor = new KernelTypeGraphBuilder(
         _options,
         _closedWorld,
@@ -1544,7 +1539,7 @@
   visitWhileStatement(ir.WhileStatement node) {
     return handleLoop(node, _localsMap.getJumpTargetForWhile(node), () {
       handleCondition(node.condition);
-      _state = new LocalState.childPath(_stateAfterWhenTrue, node.body);
+      _state = new LocalState.childPath(_stateAfterWhenTrue);
       visit(node.body);
     });
   }
@@ -1569,7 +1564,7 @@
     }
     return handleLoop(node, _localsMap.getJumpTargetForFor(node), () {
       handleCondition(node.condition);
-      _state = new LocalState.childPath(_stateAfterWhenTrue, node.body);
+      _state = new LocalState.childPath(_stateAfterWhenTrue);
       visit(node.body);
       for (ir.Expression update in node.updates) {
         visit(update);
@@ -1587,7 +1582,7 @@
     _state = stateBefore.mergeFlow(_inferrer, stateAfterBody);
     for (ir.Catch catchBlock in node.catches) {
       LocalState stateBeforeCatch = _state;
-      _state = new LocalState.childPath(stateBeforeCatch, catchBlock);
+      _state = new LocalState.childPath(stateBeforeCatch);
       visit(catchBlock);
       LocalState stateAfterCatch = _state;
       _state = stateBeforeCatch.mergeFlow(_inferrer, stateAfterCatch);
@@ -1778,27 +1773,26 @@
   bool seenBreakOrContinue = false;
   LocalsHandler _tryBlock;
 
-  LocalState.initial(ir.TreeNode node, {bool inGenerativeConstructor})
+  LocalState.initial({bool inGenerativeConstructor})
       : this.internal(
-            new LocalsHandler(node),
+            new LocalsHandler(),
             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),
+  LocalState.childPath(LocalState other)
+      : this.internal(new LocalsHandler.from(other._locals),
             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),
+  LocalState.closure(LocalState other)
+      : this.internal(new LocalsHandler.from(other._locals),
             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);
+    LocalsHandler locals = new LocalsHandler.tryBlock(other._locals, node);
     FieldInitializationScope fieldScope =
         new FieldInitializationScope.from(other._fields);
     LocalsHandler tryBlock = locals;
diff --git a/pkg/compiler/lib/src/inferrer/inferrer_engine.dart b/pkg/compiler/lib/src/inferrer/inferrer_engine.dart
index 896f862..8045775 100644
--- a/pkg/compiler/lib/src/inferrer/inferrer_engine.dart
+++ b/pkg/compiler/lib/src/inferrer/inferrer_engine.dart
@@ -1278,7 +1278,8 @@
   bool checkLoopPhiNode(ir.Node node) => true;
 
   @override
-  bool checkPhiNode(ir.Node node) => true;
+  bool checkPhiNode(ir.Node node) =>
+      node == null || node is ir.TryCatch || node is ir.TryFinally;
 
   @override
   void forEachParameter(FunctionEntity function, void f(Local parameter)) {
diff --git a/pkg/compiler/lib/src/inferrer/locals_handler.dart b/pkg/compiler/lib/src/inferrer/locals_handler.dart
index 7b58f76..cfe7f1c 100644
--- a/pkg/compiler/lib/src/inferrer/locals_handler.dart
+++ b/pkg/compiler/lib/src/inferrer/locals_handler.dart
@@ -8,6 +8,7 @@
 import 'package:kernel/ast.dart' as ir;
 import '../elements/entities.dart';
 import '../elements/types.dart';
+import '../ir/util.dart';
 import '../util/util.dart';
 import 'inferrer_engine.dart';
 import 'type_graph_nodes.dart';
@@ -20,41 +21,69 @@
  * once the control flow block has been visited.
  */
 class VariableScope {
+  /// The number of parent scopes of this scope.
+  ///
+  /// This is used for computing common parents efficiently.
+  final int _level;
+
   Map<Local, TypeInformation> variables;
 
   /// The parent of this scope. Null for the root scope.
   final VariableScope parent;
 
   /// The [ir.Node] that created this scope.
-  final ir.Node block;
+  final ir.Node tryBlock;
 
-  /// `true` if this scope is for a try block.
-  final bool isTry;
+  final VariableScope copyOf;
 
-  VariableScope(this.block, {VariableScope parent, this.isTry})
+  VariableScope({this.parent})
       : this.variables = null,
-        this.parent = parent {
-    assert(isTry == (block is ir.TryCatch || block is ir.TryFinally),
-        "Unexpected block $block for isTry=$isTry");
+        this.copyOf = null,
+        this.tryBlock = null,
+        _level = (parent?._level ?? -1) + 1;
+
+  VariableScope.tryBlock(this.tryBlock, {this.parent})
+      : this.variables = null,
+        this.copyOf = null,
+        _level = (parent?._level ?? -1) + 1 {
+    assert(tryBlock is ir.TryCatch || tryBlock is ir.TryFinally,
+        "Unexpected block $tryBlock for VariableScope.tryBlock");
   }
 
   VariableScope.deepCopyOf(VariableScope other)
       : variables = other.variables == null
             ? null
             : new Map<Local, TypeInformation>.from(other.variables),
-        block = other.block,
-        isTry = other.isTry,
+        tryBlock = other.tryBlock,
+        copyOf = other.copyOf ?? other,
+        _level = other._level,
         parent = other.parent == null
             ? null
             : new VariableScope.deepCopyOf(other.parent);
 
-  VariableScope.topLevelCopyOf(VariableScope other)
-      : variables = other.variables == null
-            ? null
-            : new Map<Local, TypeInformation>.from(other.variables),
-        block = other.block,
-        isTry = other.isTry,
-        parent = other.parent;
+  /// `true` if this scope is for a try block.
+  bool get isTry => tryBlock != null;
+
+  /// Returns the [VariableScope] that defines the identity of this scope.
+  ///
+  /// If this scope is a copy of another scope, the identity is the identity
+  /// of the other scope, otherwise the identity is the scope itself.
+  VariableScope get identity => copyOf ?? this;
+
+  /// Returns the common parent between this and [other] based on [identity].
+  VariableScope commonParent(VariableScope other) {
+    if (identity == other.identity) {
+      return identity;
+    } else if (_level > other._level) {
+      return parent.commonParent(other);
+    } else if (_level < other._level) {
+      return commonParent(other.parent);
+    } else if (_level > 0) {
+      return parent.commonParent(other.parent);
+    } else {
+      return null;
+    }
+  }
 
   TypeInformation operator [](Local variable) {
     TypeInformation result;
@@ -72,15 +101,18 @@
     variables[variable] = mask;
   }
 
-  void forEachOwnLocal(void f(Local variable, TypeInformation type)) {
-    if (variables == null) return;
-    variables.forEach(f);
+  /// Calls [f] for all variables in this and parent scopes until and including
+  /// [scope]. [f] is called at most once for each variable.
+  void forEachLocalUntilScope(
+      VariableScope scope, void f(Local variable, TypeInformation type)) {
+    _forEachLocalUntilScope(scope, f, new Setlet<Local>(), this);
   }
 
-  void forEachLocalUntilNode(
-      ir.Node node, void f(Local variable, TypeInformation type),
-      [Setlet<Local> seenLocals]) {
-    if (seenLocals == null) seenLocals = new Setlet<Local>();
+  void _forEachLocalUntilScope(
+      VariableScope scope,
+      void f(Local variable, TypeInformation type),
+      Setlet<Local> seenLocals,
+      VariableScope origin) {
     if (variables != null) {
       variables.forEach((variable, type) {
         if (seenLocals.contains(variable)) return;
@@ -88,12 +120,22 @@
         f(variable, type);
       });
     }
-    if (node != null && block == node) return;
-    if (parent != null) parent.forEachLocalUntilNode(node, f, seenLocals);
+    if (scope?.identity == identity) {
+      return;
+    }
+    if (parent != null) {
+      parent._forEachLocalUntilScope(scope, f, seenLocals, origin);
+    } else {
+      assert(
+          scope == null,
+          "Scope not found: \n"
+          "origin=${origin.toStructuredText('')}\n"
+          "scope=${scope.toStructuredText('')}");
+    }
   }
 
   void forEachLocal(void f(Local variable, TypeInformation type)) {
-    forEachLocalUntilNode(null, f);
+    forEachLocalUntilScope(null, f);
   }
 
   bool updates(Local variable) {
@@ -109,12 +151,13 @@
 
   void _toStructuredText(StringBuffer sb, String indent) {
     sb.write('VariableScope($hashCode) [');
-    String blockText = block.toString().replaceAll('\n', ' ');
-    if (blockText.length > 20) {
-      blockText = blockText.substring(0, 17) + '...';
+    sb.write('\n${indent}  level:$_level');
+    if (copyOf != null) {
+      sb.write('\n${indent}  copyOf:VariableScope(${copyOf.hashCode})');
     }
-    sb.write('\n${indent}  block: '
-        '(${block.runtimeType}:${block.hashCode})${blockText}');
+    if (tryBlock != null) {
+      sb.write('\n${indent}  tryBlock: ${nodeToDebugString(tryBlock)}');
+    }
     if (variables != null) {
       sb.write('\n${indent}  variables:');
       variables.forEach((Local local, TypeInformation type) {
@@ -280,11 +323,13 @@
 class LocalsHandler {
   final VariableScope _locals;
 
-  LocalsHandler(ir.Node block)
-      : _locals = new VariableScope(block, isTry: false);
+  LocalsHandler() : _locals = new VariableScope();
 
-  LocalsHandler.from(LocalsHandler other, ir.Node block, {bool isTry: false})
-      : _locals = new VariableScope(block, isTry: isTry, parent: other._locals);
+  LocalsHandler.from(LocalsHandler other)
+      : _locals = new VariableScope(parent: other._locals);
+
+  LocalsHandler.tryBlock(LocalsHandler other, ir.TreeNode block)
+      : _locals = new VariableScope.tryBlock(block, parent: other._locals);
 
   LocalsHandler.deepCopyOf(LocalsHandler other)
       : _locals = new VariableScope.deepCopyOf(other._locals);
@@ -304,7 +349,7 @@
       TypeInformation existing = tryBlock._locals.parent[local];
       if (existing != null) {
         TypeInformation phiType = inferrer.types.allocatePhi(
-            tryBlock._locals.block, local, existing,
+            tryBlock._locals.tryBlock, local, existing,
             isTry: tryBlock._locals.isTry);
         TypeInformation inputType =
             inferrer.types.addPhiInput(local, phiType, type);
@@ -326,7 +371,18 @@
   /// from both are merged with a phi type.
   LocalsHandler mergeFlow(InferrerEngine inferrer, LocalsHandler other,
       {bool inPlace: false}) {
-    other._locals.forEachLocalUntilNode(_locals.block,
+    VariableScope common = _locals.commonParent(other._locals);
+    assert(
+        common != null,
+        "No common parent for\n"
+        "1:${_locals.toStructuredText('  ')}\n"
+        "2:${other._locals.toStructuredText('  ')}");
+    assert(
+        common == _locals || _locals.variables == null,
+        "Non-empty common parent for\n"
+        "1:${common.toStructuredText('  ')}\n"
+        "2:${_locals.toStructuredText('  ')}");
+    other._locals.forEachLocalUntilScope(common,
         (Local local, TypeInformation type) {
       TypeInformation myType = _locals[local];
       if (myType == null) return; // Variable is only defined in [other].
@@ -355,10 +411,27 @@
       }
     }
 
-    thenBranch._locals.forEachLocalUntilNode(_locals.block, (Local local, _) {
+    VariableScope common = _locals.commonParent(thenBranch._locals);
+    assert(
+        common != null,
+        "No common parent for\n"
+        "1:${_locals.toStructuredText('  ')}\n"
+        "2:${thenBranch._locals.toStructuredText('  ')}");
+    assert(
+        _locals.commonParent(elseBranch._locals) == common,
+        "Diff common parent for\n"
+        "1:${common.toStructuredText('  ')}\n2:"
+        "${_locals.commonParent(elseBranch._locals)?.toStructuredText('  ')}");
+    assert(
+        common == _locals || _locals.variables == null,
+        "Non-empty common parent for\n"
+        "common:${common.toStructuredText('  ')}\n"
+        "1:${_locals.toStructuredText('  ')}\n"
+        "2:${thenBranch._locals.toStructuredText('  ')}");
+    thenBranch._locals.forEachLocalUntilScope(common, (Local local, _) {
       mergeLocal(local);
     });
-    elseBranch._locals.forEachLocalUntilNode(_locals.block, (Local local, _) {
+    elseBranch._locals.forEachLocalUntilScope(common, (Local local, _) {
       // Discard locals we already processed when iterating over
       // [thenBranch]'s locals.
       if (!thenBranch._locals.updates(local)) mergeLocal(local);
@@ -399,16 +472,44 @@
   LocalsHandler mergeAfterBreaks(
       InferrerEngine inferrer, Iterable<LocalsHandler> handlers,
       {bool keepOwnLocals: true}) {
-    ir.Node level = _locals.block;
+    ir.Node tryBlock = _locals.tryBlock;
     // Use a separate locals handler to perform the merge in, so that Phi
     // creation does not invalidate previous type knowledge while we might
     // still look it up.
-    LocalsHandler merged =
-        new LocalsHandler.from(this, level, isTry: _locals.isTry);
+    VariableScope merged = tryBlock != null
+        ? new VariableScope.tryBlock(tryBlock, parent: _locals)
+        : new VariableScope(parent: _locals);
     Set<Local> seenLocals = new Setlet<Local>();
     // Merge all other handlers.
     for (LocalsHandler handler in handlers) {
-      merged._mergeHandler(inferrer, handler, seenLocals);
+      VariableScope common = _locals.commonParent(handler._locals);
+      assert(
+          common != null,
+          "No common parent for\n"
+          "1:${_locals.toStructuredText('  ')}\n"
+          "2:${handler._locals.toStructuredText('  ')}");
+      assert(
+          common == _locals || _locals.variables == null,
+          "Non-empty common parent for\n"
+          "common:${common.toStructuredText('  ')}\n"
+          "1:${_locals.toStructuredText('  ')}\n"
+          "2:${handler._locals.toStructuredText('  ')}");
+      handler._locals.forEachLocalUntilScope(common, (local, otherType) {
+        TypeInformation myType = merged[local];
+        if (myType == null) return;
+        TypeInformation newType;
+        if (!seenLocals.contains(local)) {
+          newType = inferrer.types.allocatePhi(
+              merged.tryBlock, local, otherType,
+              isTry: merged.isTry);
+          seenLocals.add(local);
+        } else {
+          newType = inferrer.types.addPhiInput(local, myType, otherType);
+        }
+        if (newType != myType) {
+          merged[local] = newType;
+        }
+      });
     }
     // If we want to keep own locals, we merge [seenLocals] from [this] into
     // [merged] to update the Phi nodes with original values.
@@ -416,56 +517,47 @@
       for (Local variable in seenLocals) {
         TypeInformation originalType = _locals[variable];
         if (originalType != null) {
-          merged._locals[variable] = inferrer.types
-              .addPhiInput(variable, merged._locals[variable], originalType);
+          merged[variable] = inferrer.types
+              .addPhiInput(variable, merged[variable], originalType);
         }
       }
     }
     // Clean up Phi nodes with single input and store back result into
     // actual locals handler.
-    merged._locals.forEachLocalUntilNode(_locals.block,
+    merged.forEachLocalUntilScope(merged,
         (Local variable, TypeInformation type) {
-      _locals[variable] = inferrer.types.simplifyPhi(level, variable, type);
+      _locals[variable] = inferrer.types.simplifyPhi(tryBlock, variable, type);
     });
     return this;
   }
 
-  /**
-   * Merge [other] into this handler. Returns whether a local in this
-   * has changed. If [seen] is not null, we allocate new Phi nodes
-   * unless the local is already present in the set [seen]. This effectively
-   * overwrites the current type knowledge in this handler.
-   */
-  bool _mergeHandler(InferrerEngine inferrer, LocalsHandler other,
-      [Set<Local> seen]) {
-    bool changed = false;
-    other._locals.forEachLocalUntilNode(_locals.block, (local, otherType) {
-      TypeInformation myType = _locals[local];
-      if (myType == null) return;
-      TypeInformation newType;
-      if (seen != null && !seen.contains(local)) {
-        newType = inferrer.types
-            .allocatePhi(_locals.block, local, otherType, isTry: _locals.isTry);
-        seen.add(local);
-      } else {
-        newType = inferrer.types.addPhiInput(local, myType, otherType);
-      }
-      if (newType != myType) {
-        changed = true;
-        _locals[local] = newType;
-      }
-    });
-    return changed;
-  }
-
-  /**
-   * Merge all [LocalsHandler] in [handlers] into this handler.
-   * Returns whether a local in this handler has changed.
-   */
+  /// Merge all [LocalsHandler] in [handlers] into this handler.
+  /// Returns whether a local in this handler has changed.
   bool mergeAll(InferrerEngine inferrer, Iterable<LocalsHandler> handlers) {
     bool changed = false;
-    handlers.forEach((other) {
-      changed = _mergeHandler(inferrer, other) || changed;
+    handlers.forEach((LocalsHandler other) {
+      VariableScope common = _locals.commonParent(other._locals);
+      assert(
+          common != null,
+          "No common parent for\n"
+          "1:${_locals.toStructuredText('  ')}\n"
+          "2:${other._locals.toStructuredText('  ')}");
+      assert(
+          common == _locals || _locals.variables == null,
+          "Non-empty common parent for\n"
+          "common:${common.toStructuredText('  ')}\n"
+          "1:${_locals.toStructuredText('  ')}\n"
+          "2:${other._locals.toStructuredText('  ')}");
+      other._locals.forEachLocalUntilScope(common, (local, otherType) {
+        TypeInformation myType = _locals[local];
+        if (myType == null) return;
+        TypeInformation newType =
+            inferrer.types.addPhiInput(local, myType, otherType);
+        if (newType != myType) {
+          changed = true;
+          _locals[local] = newType;
+        }
+      });
     });
     return changed;
   }
diff --git a/pkg/compiler/lib/src/ir/util.dart b/pkg/compiler/lib/src/ir/util.dart
index 8a4a0c4..fb6ba1e 100644
--- a/pkg/compiler/lib/src/ir/util.dart
+++ b/pkg/compiler/lib/src/ir/util.dart
@@ -10,6 +10,16 @@
 import '../common.dart';
 import '../elements/entities.dart';
 
+/// Returns a textual representation of [node] that include the runtime type and
+/// hash code of the node and a one line prefix of the node toString text.
+String nodeToDebugString(ir.Node node, [int textLength = 40]) {
+  String blockText = node.toString().replaceAll('\n', ' ');
+  if (blockText.length > textLength) {
+    blockText = blockText.substring(0, textLength - 3) + '...';
+  }
+  return '(${node.runtimeType}:${node.hashCode})${blockText}';
+}
+
 /// Comparator for the canonical order or named arguments.
 // TODO(johnniwinther): Remove this when named parameters are sorted in dill.
 int namedOrdering(ir.VariableDeclaration a, ir.VariableDeclaration b) {