Improvements for flow analysis.

Support for 'for'.
Support for 'for-each'.
Support for 'switch'.
Support for local functions and closures.
Support for 'try/catch/finally'.

R=paulberry@google.com

Change-Id: Idd0e528b706c1094e2bfdf32f84a9bcd7c80968a
Reviewed-on: https://dart-review.googlesource.com/c/89921
Reviewed-by: Paul Berry <paulberry@google.com>
Commit-Queue: Konstantin Shcheglov <scheglov@google.com>
diff --git a/pkg/analyzer/lib/src/dart/resolver/flow_analysis.dart b/pkg/analyzer/lib/src/dart/resolver/flow_analysis.dart
index 5867f95..e33f899 100644
--- a/pkg/analyzer/lib/src/dart/resolver/flow_analysis.dart
+++ b/pkg/analyzer/lib/src/dart/resolver/flow_analysis.dart
@@ -128,6 +128,69 @@
     _conditionFalse = _current;
   }
 
+  void forEachStatement_bodyBegin(Set<VariableElement> loopAssigned) {
+    _stack.add(_current);
+    _current = _current.removePromotedAll(loopAssigned);
+  }
+
+  void forEachStatement_end() {
+    var afterIterable = _stack.removeLast();
+    _current = _current.combine(typeSystem, afterIterable);
+  }
+
+  void forStatement_bodyBegin(ForStatement node, Expression condition) {
+    _conditionalEnd(condition);
+    // Tail of the stack: falseCondition, trueCondition
+
+    var trueCondition = _stack.removeLast();
+
+    _statementToStackIndex[node] = _stack.length;
+    _stack.add(_State.identity); // break
+    _stack.add(_State.identity); // continue
+
+    _current = trueCondition;
+  }
+
+  void forStatement_conditionBegin(Set<VariableElement> loopAssigned) {
+    _current = _current.removePromotedAll(loopAssigned);
+  }
+
+  void forStatement_end() {
+    // Tail of the stack: falseCondition, break
+    var breakState = _stack.removeLast();
+    var falseCondition = _stack.removeLast();
+
+    _current = falseCondition.combine(typeSystem, breakState);
+  }
+
+  void forStatement_updaterBegin() {
+    // Tail of the stack: falseCondition, break, continue
+    var afterBody = _current;
+    var continueState = _stack.removeLast();
+
+    _current = afterBody.combine(typeSystem, continueState);
+  }
+
+  void functionExpression_begin() {
+    _stack.add(_current);
+
+    Set<VariableElement> notPromoted = null;
+    for (var variable in _current.promoted.keys) {
+      if (functionBody.isPotentiallyMutatedInScope(variable)) {
+        notPromoted ??= Set<VariableElement>.identity();
+        notPromoted.add(variable);
+      }
+    }
+
+    if (notPromoted != null) {
+      _current = _current.removePromotedAll(notPromoted);
+    }
+  }
+
+  void functionExpression_end() {
+    _current = _stack.removeLast();
+  }
+
   void handleBreak(AstNode target) {
     var breakIndex = _statementToStackIndex[target];
     if (breakIndex != null) {
@@ -284,19 +347,77 @@
     }
   }
 
+  /// The [notPromoted] set contains all variables that are potentially
+  /// assigned in other cases that might target this with `continue`, so
+  /// these variables might have different types and are "un-promoted" from
+  /// the "afterExpression" state.
+  void switchStatement_beginCase(Set<VariableElement> notPromoted) {
+    _current = _stack.last.removePromotedAll(notPromoted);
+  }
+
+  void switchStatement_end(SwitchStatement node, bool hasDefault) {
+    // Tail of the stack: break, continue, afterExpression
+    var afterExpression = _current = _stack.removeLast();
+    _stack.removeLast(); // continue
+    var breakState = _stack.removeLast();
+
+    if (hasDefault) {
+      _current = breakState;
+    } else {
+      _current = breakState.combine(typeSystem, afterExpression);
+    }
+  }
+
+  void switchStatement_expressionEnd(SwitchStatement node) {
+    _statementToStackIndex[node] = _stack.length;
+    _stack.add(_State.identity); // break
+    _stack.add(_State.identity); // continue
+    _stack.add(_current); // afterExpression
+  }
+
   void trueLiteral(BooleanLiteral expression) {
     _condition = expression;
     _conditionTrue = _current;
     _conditionFalse = _State.identity;
   }
 
+  void tryStatement_bodyBegin() {
+    _stack.add(_current);
+    // Tail of the stack: beforeBody
+  }
+
+  void tryStatement_bodyEnd(Set<VariableElement> notPromoted) {
+    var beforeBody = _stack.removeLast();
+    var beforeCatch = beforeBody.removePromotedAll(notPromoted);
+    _stack.add(beforeCatch);
+    _stack.add(_current); // afterBodyAndCatches
+    // Tail of the stack: beforeCatch, afterBodyAndCatches
+  }
+
+  void tryStatement_catchBegin() {
+    var beforeCatch = _stack[_stack.length - 2];
+    _current = beforeCatch;
+  }
+
+  void tryStatement_catchEnd() {
+    var afterBodyAndCatches = _stack.last;
+    _stack.last = afterBodyAndCatches.combine(typeSystem, _current);
+  }
+
+  void tryStatement_finallyBegin() {
+    var afterBodyAndCatches = _stack.removeLast();
+    _stack.removeLast(); // beforeCatch
+
+    _current = afterBodyAndCatches;
+  }
+
   void verifyStackEmpty() {
     assert(_stack.isEmpty);
   }
 
   void whileStatement_bodyBegin(WhileStatement node) {
     _conditionalEnd(node.condition);
-    // Tail of the stack:  falseCondition, trueCondition
+    // Tail of the stack: falseCondition, trueCondition
 
     var trueCondition = _stack.removeLast();
 
@@ -307,8 +428,7 @@
     _current = trueCondition;
   }
 
-  void whileStatement_conditionBegin(
-      WhileStatement node, Set<VariableElement> loopAssigned) {
+  void whileStatement_conditionBegin(Set<VariableElement> loopAssigned) {
     _current = _current.removePromotedAll(loopAssigned);
   }
 
@@ -341,7 +461,7 @@
 
 /// Sets of variables that are potentially assigned in loops.
 class LoopAssignedVariables {
-  static final _emptySet = Set<VariableElement>();
+  static final emptySet = Set<VariableElement>();
 
   /// Mapping from a loop [AstNode] to the set of variables that are
   /// potentially assigned in this loop.
@@ -352,7 +472,7 @@
 
   /// Return the set of variables that are potentially assigned in the [loop].
   Set<VariableElement> operator [](AstNode loop) {
-    return _map[loop] ?? _emptySet;
+    return _map[loop] ?? emptySet;
   }
 
   void beginLoop() {
diff --git a/pkg/analyzer/test/src/dart/resolution/flow_analysis_test.dart b/pkg/analyzer/test/src/dart/resolution/flow_analysis_test.dart
index 7873fce..9710109 100644
--- a/pkg/analyzer/test/src/dart/resolution/flow_analysis_test.dart
+++ b/pkg/analyzer/test/src/dart/resolution/flow_analysis_test.dart
@@ -3,6 +3,7 @@
 // BSD-style license that can be found in the LICENSE file.
 
 import 'package:analyzer/dart/ast/ast.dart';
+import 'package:analyzer/dart/ast/standard_ast_factory.dart';
 import 'package:analyzer/dart/ast/token.dart';
 import 'package:analyzer/dart/ast/visitor.dart';
 import 'package:analyzer/dart/element/element.dart';
@@ -281,6 +282,289 @@
     assertReadBeforeWritten();
   }
 
+  test_for_body() async {
+    await trackCode(r'''
+void f(bool b) {
+  int v;
+  for (; b;) {
+    v = 0;
+  }
+  v;
+}
+''');
+    assertReadBeforeWritten('v');
+  }
+
+  test_for_break() async {
+    await trackCode(r'''
+void f(bool b) {
+  int v1, v2;
+  for (; b;) {
+    v1 = 0;
+    if (b) break;
+    v2 = 0;
+  }
+  v1;
+  v2;
+}
+''');
+    assertReadBeforeWritten('v1', 'v2');
+  }
+
+  test_for_break_updaters() async {
+    await trackCode(r'''
+void f(bool b) {
+  int v1, v2;
+  for (; b; v1 + v2) {
+    v1 = 0;
+    if (b) break;
+    v2 = 0;
+  }
+}
+''');
+    assertReadBeforeWritten();
+  }
+
+  test_for_condition() async {
+    await trackCode(r'''
+void f() {
+  int v;
+  for (; (v = 0) >= 0;) {
+    v;
+  }
+  v;
+}
+''');
+    assertReadBeforeWritten();
+  }
+
+  test_for_continue() async {
+    await trackCode(r'''
+void f(bool b) {
+  int v1, v2;
+  for (; b;) {
+    v1 = 0;
+    if (b) continue;
+    v2 = 0;
+  }
+  v1;
+  v2;
+}
+''');
+    assertReadBeforeWritten('v1', 'v2');
+  }
+
+  test_for_continue_updaters() async {
+    await trackCode(r'''
+void f(bool b) {
+  int v1, v2;
+  for (; b; v1 + v2) {
+    v1 = 0;
+    if (b) continue;
+    v2 = 0;
+  }
+}
+''');
+    assertReadBeforeWritten('v2');
+  }
+
+  test_for_initializer_expression() async {
+    await trackCode(r'''
+void f() {
+  int v;
+  for (v = 0;;) {
+    v;
+  }
+  v;
+}
+''');
+    assertReadBeforeWritten();
+  }
+
+  test_for_initializer_variable() async {
+    await trackCode(r'''
+void f() {
+  int v;
+  for (var t = (v = 0);;) {
+    v;
+  }
+  v;
+}
+''');
+    assertReadBeforeWritten();
+  }
+
+  test_for_updaters() async {
+    await trackCode(r'''
+void f(bool b) {
+  int v1, v2, v3, v4;
+  for (; b; v1 = 0, v2 = 0, v3 = 0, v4) {
+    v1;
+  }
+  v2;
+}
+''');
+    assertReadBeforeWritten('v1', 'v2', 'v4');
+  }
+
+  test_for_updaters_afterBody() async {
+    await trackCode(r'''
+void f(bool b) {
+  int v;
+  for (; b; v) {
+    v = 0;
+  }
+}
+''');
+    assertReadBeforeWritten();
+  }
+
+  test_forEach() async {
+    await trackCode(r'''
+void f() {
+  int v1, v2;
+  for (var _ in (v1 = [0, 1, 2])) {
+    v2 = 0;
+  }
+  v1;
+  v2;
+}
+''');
+    assertReadBeforeWritten('v2');
+  }
+
+  test_forEach_break() async {
+    await trackCode(r'''
+void f(bool b) {
+  int v1, v2;
+  for (var _ in [0, 1, 2]) {
+    v1 = 0;
+    if (b) break;
+    v2 = 0;
+  }
+  v1;
+  v2;
+}
+''');
+    assertReadBeforeWritten('v1', 'v2');
+  }
+
+  test_forEach_continue() async {
+    await trackCode(r'''
+void f(bool b) {
+  int v1, v2;
+  for (var _ in [0, 1, 2]) {
+    v1 = 0;
+    if (b) continue;
+    v2 = 0;
+  }
+  v1;
+  v2;
+}
+''');
+    assertReadBeforeWritten('v1', 'v2');
+  }
+
+  test_functionExpression_closure_read() async {
+    await trackCode(r'''
+void f() {
+  int v1, v2;
+  
+  v1 = 0;
+  
+  [0, 1, 2].forEach((t) {
+    v1;
+    v2;
+  });
+}
+''');
+    assertReadBeforeWritten('v2');
+  }
+
+  test_functionExpression_closure_write() async {
+    await trackCode(r'''
+void f() {
+  int v;
+  
+  [0, 1, 2].forEach((t) {
+    v = t;
+  });
+
+  v;
+}
+''');
+    assertReadBeforeWritten('v');
+  }
+
+  test_functionExpression_localFunction_local() async {
+    await trackCode(r'''
+void f() {
+  int v;
+
+  v = 0;
+
+  void f() {
+    int v; // 1
+    v;
+  }
+}
+''');
+    var localV = findNode.simple('v; // 1').staticElement;
+    expect(readBeforeWritten, unorderedEquals([localV]));
+  }
+
+  test_functionExpression_localFunction_local2() async {
+    await trackCode(r'''
+void f() {
+  int v1;
+
+  v1 = 0;
+
+  void f() {
+    int v2, v3;
+    v2 = 0;
+    v1;
+    v2;
+    v3;
+  }
+}
+''');
+    assertReadBeforeWritten('v3');
+  }
+
+  test_functionExpression_localFunction_read() async {
+    await trackCode(r'''
+void f() {
+  int v1, v2, v3;
+
+  v1 = 0;
+
+  void f() {
+    v1;
+    v2;
+  }
+
+  v2 = 0;
+}
+''');
+    assertReadBeforeWritten('v2');
+  }
+
+  test_functionExpression_localFunction_write() async {
+    await trackCode(r'''
+void f() {
+  int v;
+
+  void f() {
+    v = 0;
+  }
+
+  v;
+}
+''');
+    assertReadBeforeWritten('v');
+  }
+
   test_if_condition() async {
     await trackCode(r'''
 main() {
@@ -463,6 +747,249 @@
     assertReadBeforeWritten('v');
   }
 
+  test_switch_case1_default() async {
+    await trackCode(r'''
+void f(int e) {
+  int v;
+  switch (e) {
+    case 1:
+      v = 0;
+      break;
+    case 2:
+      // not assigned
+      break;
+    default:
+      v = 0;
+  }
+  v;
+}
+''');
+    assertReadBeforeWritten('v');
+  }
+
+  test_switch_case2_default() async {
+    await trackCode(r'''
+void f(int e) {
+  int v1, v2;
+  switch (e) {
+    case 1:
+      v1 = 0;
+      v2 = 0;
+      v1;
+      break;
+    default:
+      v1 = 0;
+      v1;
+  }
+  v1;
+  v2;
+}
+''');
+    assertReadBeforeWritten('v2');
+  }
+
+  test_switch_case_default_break() async {
+    await trackCode(r'''
+void f(bool b, int e) {
+  int v1, v2;
+  switch (e) {
+    case 1:
+      v1 = 0;
+      if (b) break;
+      v2 = 0;
+      break;
+    default:
+      v1 = 0;
+      if (b) break;
+      v2 = 0;
+  }
+  v1;
+  v2;
+}
+''');
+    assertReadBeforeWritten('v2');
+  }
+
+  test_switch_case_default_continue() async {
+    await trackCode(r'''
+void f(int e) {
+  int v;
+  switch (e) {
+    L: case 1:
+      v = 0;
+      break;
+    case 2:
+      continue L;
+      break;
+    default:
+      v = 0;
+  }
+  v;
+}
+''');
+    // We don't analyze to which `case` we go from `continue L`,
+    // but we don't have to. If all cases assign, then the variable is
+    // removed from the unassigned set in the `breakState`. And if there is a
+    // case when it is not assigned, then the variable will be left unassigned
+    // in the `breakState`.
+    assertReadBeforeWritten();
+  }
+
+  test_switch_case_noDefault() async {
+    await trackCode(r'''
+void f(int e) {
+  int v;
+  switch (e) {
+    case 1:
+      v = 0;
+      break;
+  }
+  v;
+}
+''');
+    assertReadBeforeWritten('v');
+  }
+
+  test_switch_expression() async {
+    await trackCode(r'''
+void f() {
+  int v;
+  switch (v = 0) {}
+  v;
+}
+''');
+    assertReadBeforeWritten();
+  }
+
+  test_tryCatch_body() async {
+    await trackCode(r'''
+void f() {
+  int v;
+  try {
+    v = 0;
+  } catch (_) {
+    // not assigned
+  }
+  v;
+}
+''');
+    assertReadBeforeWritten('v');
+  }
+
+  test_tryCatch_body_catch() async {
+    await trackCode(r'''
+void f() {
+  int v;
+  try {
+    g();
+    v = 0;
+  } catch (_) {
+    v = 0;
+  }
+  v;
+}
+
+void g() {}
+''');
+    assertReadBeforeWritten();
+  }
+
+  test_tryCatch_catch() async {
+    await trackCode(r'''
+void f() {
+  int v;
+  try {
+    // not assigned
+  } catch (_) {
+    v = 0;
+  }
+  v;
+}
+''');
+    assertReadBeforeWritten('v');
+  }
+
+  test_tryCatchFinally_body() async {
+    await trackCode(r'''
+void f() {
+  int v;
+  try {
+    v = 0;
+  } catch (_) {
+    // not assigned
+  } finally {
+    // not assigned
+  }
+  v;
+}
+''');
+    assertReadBeforeWritten('v');
+  }
+
+  test_tryCatchFinally_catch() async {
+    await trackCode(r'''
+void f() {
+  int v;
+  try {
+    // not assigned
+  } catch (_) {
+    v = 0;
+  } finally {
+    // not assigned
+  }
+  v;
+}
+''');
+    assertReadBeforeWritten('v');
+  }
+
+  test_tryCatchFinally_finally() async {
+    await trackCode(r'''
+void f() {
+  int v;
+  try {
+    // not assigned
+  } catch (_) {
+    // not assigned
+  } finally {
+    v = 0;
+  }
+  v;
+}
+''');
+    assertReadBeforeWritten();
+  }
+
+  test_tryFinally_body() async {
+    await trackCode(r'''
+void f() {
+  int v;
+  try {
+    v = 0;
+  } finally {
+    // not assigned
+  }
+  v;
+}
+''');
+    assertReadBeforeWritten();
+  }
+
+  test_tryFinally_finally() async {
+    await trackCode(r'''
+void f() {
+  int v;
+  try {
+    // not assigned
+  } finally {
+    v = 0;
+  }
+  v;
+}
+''');
+    assertReadBeforeWritten();
+  }
+
   test_while_condition() async {
     await trackCode(r'''
 void f() {
@@ -668,7 +1195,7 @@
     var node = findNode.simple(search);
     var actualType = promotedTypes[node];
     if (actualType == null) {
-      fail('$expectedType expected, but actually not promoted');
+      fail('$expectedType expected, but actually not promoted\n$search');
     }
     assertElementTypeString(actualType, expectedType);
   }
@@ -785,6 +1312,137 @@
     assertNotPromoted('x; // 2');
   }
 
+  test_for_outerIsType() async {
+    await trackCode(r'''
+void f(bool b, Object x) {
+  if (x is String) {
+    for (; b;) {
+      x; // 1
+    }
+    x; // 2
+  }
+}
+''');
+    assertPromoted('x; // 1', 'String');
+    assertPromoted('x; // 2', 'String');
+  }
+
+  test_for_outerIsType_loopAssigned_body() async {
+    await trackCode(r'''
+void f(bool b, Object x) {
+  if (x is String) {
+    for (; b;) {
+      x; // 1
+      x = 42;
+    }
+    x; // 2
+  }
+}
+''');
+    assertNotPromoted('x; // 1');
+    assertNotPromoted('x; // 2');
+  }
+
+  test_for_outerIsType_loopAssigned_condition() async {
+    await trackCode(r'''
+void f(Object x) {
+  if (x is String) {
+    for (; (x = 42) > 0;) {
+      x; // 1
+    }
+    x; // 2
+  }
+}
+''');
+    assertNotPromoted('x; // 1');
+    assertNotPromoted('x; // 2');
+  }
+
+  test_for_outerIsType_loopAssigned_updaters() async {
+    await trackCode(r'''
+void f(bool b, Object x) {
+  if (x is String) {
+    for (; b; x = 42) {
+      x; // 1
+    }
+    x; // 2
+  }
+}
+''');
+    assertNotPromoted('x; // 1');
+    assertNotPromoted('x; // 2');
+  }
+
+  test_forEach_outerIsType_loopAssigned() async {
+    await trackCode(r'''
+void f(Object x) {
+  if (x is String) {
+    for (var _ in (v1 = [0, 1, 2])) {
+      x; // 1
+      x = 42;
+    }
+    x; // 2
+  }
+}
+''');
+    assertNotPromoted('x; // 1');
+    assertNotPromoted('x; // 2');
+  }
+
+  test_functionExpression_isType() async {
+    await trackCode(r'''
+void f() {
+  void g(Object x) {
+    if (x is String) {
+      x; // 1
+    }
+    x = 42;
+  }
+}
+''');
+    assertPromoted('x; // 1', 'String');
+  }
+
+  test_functionExpression_isType_mutatedInClosure2() async {
+    await trackCode(r'''
+void f() {
+  void g(Object x) {
+    if (x is String) {
+      x; // 1
+    }
+    
+    void h() {
+      x = 42;
+    }
+  }
+}
+''');
+    assertNotPromoted('x; // 1');
+  }
+
+  test_functionExpression_outerIsType_assignedOutside() async {
+    await trackCode(r'''
+void f(Object x) {
+  void Function() g;
+  
+  if (x is String) {
+    x; // 1
+
+    g = () {
+      x; // 2
+    }
+  }
+
+  x = 42;
+  x; // 3
+  g();
+}
+''');
+    assertPromoted('x; // 1', 'String');
+    assertNotPromoted('x; // 2');
+    assertNotPromoted('x; // 3');
+  }
+
   test_if_combine_empty() async {
     await trackCode(r'''
 main(bool b, Object v) {
@@ -949,6 +1607,193 @@
     assertPromoted('x; // 1', 'String');
   }
 
+  test_switch_outerIsType_assignedInCase() async {
+    await trackCode(r'''
+void f(int e, Object x) {
+  if (x is String) {
+    switch (e) {
+      L: case 1:
+        x; // 1
+        break;
+      case 2: // no label
+        x; // 2
+        break;
+      case 3:
+        x = 42;
+        continue L;
+    }
+    x; // 3
+  }
+}
+''');
+    assertNotPromoted('x; // 1');
+    assertPromoted('x; // 2', 'String');
+    assertNotPromoted('x; // 3');
+  }
+
+  test_try_assigned_body() async {
+    await trackCode(r'''
+void f(Object x) {
+  if (x is! String) return;
+  x; // 1
+  try {
+    x = 42;
+    g(); // might throw
+    if (x is! String) return;
+    x; // 2
+  } catch (_) {}
+  x; // 3
+}
+
+void g() {}
+''');
+    assertPromoted('x; // 1', 'String');
+    assertPromoted('x; // 2', 'String');
+    assertNotPromoted('x; // 3');
+  }
+
+  test_try_isNotType_exit_body() async {
+    await trackCode(r'''
+void f(Object x) {
+  try {
+    if (x is! String) return;
+    x; // 1
+  } catch (_) {}
+  x; // 2
+}
+
+void g() {}
+''');
+    assertPromoted('x; // 1', 'String');
+    assertNotPromoted('x; // 2');
+  }
+
+  test_try_isNotType_exit_body_catch() async {
+    await trackCode(r'''
+void f(Object x) {
+  try {
+    if (x is! String) return;
+    x; // 1
+  } catch (_) {
+    if (x is! String) return;
+    x; // 2
+  }
+  x; // 3
+}
+
+void g() {}
+''');
+    assertPromoted('x; // 1', 'String');
+    assertPromoted('x; // 2', 'String');
+    assertPromoted('x; // 3', 'String');
+  }
+
+  test_try_isNotType_exit_catch() async {
+    await trackCode(r'''
+void f(Object x) {
+  try {
+  } catch (_) {
+    if (x is! String) return;
+    x; // 1
+  }
+  x; // 2
+}
+
+void g() {}
+''');
+    assertPromoted('x; // 1', 'String');
+    assertNotPromoted('x; // 2');
+  }
+
+  test_try_outerIsType() async {
+    await trackCode(r'''
+void f(Object x) {
+  if (x is String) {
+    try {
+      x; // 1
+    } catch (_) {
+      x; // 2
+    } finally {
+      x; // 3
+    }
+    x; // 4
+  }
+}
+
+void g() {}
+''');
+    assertPromoted('x; // 1', 'String');
+    assertPromoted('x; // 2', 'String');
+    assertPromoted('x; // 3', 'String');
+    assertPromoted('x; // 4', 'String');
+  }
+
+  test_try_outerIsType_assigned_body() async {
+    await trackCode(r'''
+void f(Object x) {
+  if (x is String) {
+    try {
+      x; // 1
+      x = 42;
+      g();
+    } catch (_) {
+      x; // 2
+    } finally {
+      x; // 3
+    }
+    x; // 4
+  }
+}
+
+void g() {}
+''');
+    assertPromoted('x; // 1', 'String');
+    assertNotPromoted('x; // 2');
+    assertNotPromoted('x; // 3');
+    assertNotPromoted('x; // 4');
+  }
+
+  test_try_outerIsType_assigned_catch() async {
+    await trackCode(r'''
+void f(Object x) {
+  if (x is String) {
+    try {
+      x; // 1
+    } catch (_) {
+      x; // 2
+      x = 42;
+    } finally {
+      x; // 3
+    }
+    x; // 4
+  }
+}
+''');
+    assertPromoted('x; // 1', 'String');
+    assertPromoted('x; // 2', 'String');
+    assertNotPromoted('x; // 3');
+    assertNotPromoted('x; // 4');
+  }
+
+  test_try_outerIsType_assigned_finally() async {
+    await trackCode(r'''
+void f(Object x) {
+  if (x is String) {
+    try {
+      x; // 1
+    } finally {
+      x; // 2
+      x = 42;
+    }
+    x; // 3
+  }
+}
+''');
+    assertPromoted('x; // 1', 'String');
+    assertPromoted('x; // 2', 'String');
+    assertNotPromoted('x; // 3');
+  }
+
   test_while_condition_false() async {
     await trackCode(r'''
 void f(Object x) {
@@ -1046,6 +1891,8 @@
 /// [AstVisitor] that drives the [flow] in the way we expect the resolver
 /// will do in production.
 class _AstVisitor extends RecursiveAstVisitor<void> {
+  static final trueLiteral = astFactory.booleanLiteral(null, true);
+
   final TypeSystem typeSystem;
   final LoopAssignedVariables loopAssignedVariables;
   final Map<AstNode, DartType> promotedTypes;
@@ -1197,6 +2044,49 @@
   }
 
   @override
+  void visitForEachStatement(ForEachStatement node) {
+    var iterable = node.iterable;
+    var body = node.body;
+
+    iterable.accept(this);
+    flow.forEachStatement_bodyBegin(loopAssignedVariables[node]);
+
+    body.accept(this);
+
+    flow.forEachStatement_end();
+  }
+
+  @override
+  void visitForStatement(ForStatement node) {
+    var condition = node.condition;
+
+    node.initialization?.accept(this);
+    node.variables?.accept(this);
+
+    flow.forStatement_conditionBegin(loopAssignedVariables[node]);
+    if (condition != null) {
+      condition.accept(this);
+    } else {
+      flow.trueLiteral(trueLiteral);
+    }
+
+    flow.forStatement_bodyBegin(node, condition ?? trueLiteral);
+    node.body.accept(this);
+
+    flow.forStatement_updaterBegin();
+    node.updaters?.accept(this);
+
+    flow.forStatement_end();
+  }
+
+  @override
+  void visitFunctionExpression(FunctionExpression node) {
+    flow?.functionExpression_begin();
+    super.visitFunctionExpression(node);
+    flow?.functionExpression_end();
+  }
+
+  @override
   void visitIfStatement(IfStatement node) {
     var condition = node.condition;
     var thenStatement = node.thenStatement;
@@ -1269,12 +2159,63 @@
   }
 
   @override
+  void visitSwitchStatement(SwitchStatement node) {
+    node.expression.accept(this);
+    flow.switchStatement_expressionEnd(node);
+
+    var assignedInCases = loopAssignedVariables[node];
+
+    var members = node.members;
+    var membersLength = members.length;
+    var hasDefault = false;
+    for (var i = 0; i < membersLength; i++) {
+      var member = members[i];
+
+      flow.switchStatement_beginCase(
+        member.labels.isNotEmpty
+            ? assignedInCases
+            : LoopAssignedVariables.emptySet,
+      );
+      member.accept(this);
+
+      // Implicit `break` at the end of `default`.
+      if (member is SwitchDefault) {
+        hasDefault = true;
+        flow.handleBreak(node);
+      }
+    }
+
+    flow.switchStatement_end(node, hasDefault);
+  }
+
+  @override
   void visitThrowExpression(ThrowExpression node) {
     super.visitThrowExpression(node);
     flow.handleExit();
   }
 
   @override
+  void visitTryStatement(TryStatement node) {
+    var body = node.body;
+    var catchClauses = node.catchClauses;
+
+    flow.tryStatement_bodyBegin();
+    body.accept(this);
+    flow.tryStatement_bodyEnd(loopAssignedVariables[node.body]);
+
+    var catchLength = catchClauses.length;
+    for (var i = 0; i < catchLength; ++i) {
+      var catchClause = catchClauses[i];
+      flow.tryStatement_catchBegin();
+      catchClause.accept(this);
+      flow.tryStatement_catchEnd();
+    }
+
+    flow.tryStatement_finallyBegin();
+    node.finallyBlock?.accept(this);
+  }
+
+  @override
   void visitVariableDeclarationStatement(VariableDeclarationStatement node) {
     var variables = node.variables.variables;
     for (var i = 0; i < variables.length; ++i) {
@@ -1291,7 +2232,7 @@
     var condition = node.condition;
     var body = node.body;
 
-    flow.whileStatement_conditionBegin(node, loopAssignedVariables[node]);
+    flow.whileStatement_conditionBegin(loopAssignedVariables[node]);
     condition.accept(this);
 
     flow.whileStatement_bodyBegin(node);
@@ -1370,6 +2311,52 @@
   }
 
   @override
+  void visitForEachStatement(ForEachStatement node) {
+    var iterable = node.iterable;
+    var body = node.body;
+
+    iterable.accept(this);
+
+    loopAssignedVariables.beginLoop();
+    body.accept(this);
+    loopAssignedVariables.endLoop(node);
+  }
+
+  @override
+  void visitForStatement(ForStatement node) {
+    node.initialization?.accept(this);
+    node.variables?.accept(this);
+
+    loopAssignedVariables.beginLoop();
+    node.condition?.accept(this);
+    node.body.accept(this);
+    node.updaters?.accept(this);
+    loopAssignedVariables.endLoop(node);
+  }
+
+  @override
+  void visitSwitchStatement(SwitchStatement node) {
+    var expression = node.expression;
+    var members = node.members;
+
+    expression.accept(this);
+
+    loopAssignedVariables.beginLoop();
+    members.accept(this);
+    loopAssignedVariables.endLoop(node);
+  }
+
+  @override
+  void visitTryStatement(TryStatement node) {
+    loopAssignedVariables.beginLoop();
+    node.body.accept(this);
+    loopAssignedVariables.endLoop(node.body);
+
+    node.catchClauses.accept(this);
+    node.finallyBlock?.accept(this);
+  }
+
+  @override
   void visitWhileStatement(WhileStatement node) {
     loopAssignedVariables.beginLoop();
     super.visitWhileStatement(node);