Improvements for flow analysis.

Support for ConditionalExpression.
Check for potential mutations in closures.
Un-promote on assignment.
Support for 'while' statement.
Support for 'do-while' statement.

R=paulberry@google.com

Change-Id: Ic9e33a08057dc3519efec759702c52ddada728ed
Reviewed-on: https://dart-review.googlesource.com/c/89763
Reviewed-by: Paul Berry <paulberry@google.com>
Commit-Queue: Konstantin Shcheglov <scheglov@google.com>
diff --git a/pkg/analyzer/lib/dart/element/type.dart b/pkg/analyzer/lib/dart/element/type.dart
index f2ddef8..f52cc53 100644
--- a/pkg/analyzer/lib/dart/element/type.dart
+++ b/pkg/analyzer/lib/dart/element/type.dart
@@ -69,6 +69,12 @@
   bool get isDartAsyncFutureOr;
 
   /**
+   * Return `true` if this type represents the type 'bool' defined in the
+   * dart:core library.
+   */
+  bool get isDartCoreBool;
+
+  /**
    * Return `true` if this type represents the type 'Function' defined in the
    * dart:core library.
    */
diff --git a/pkg/analyzer/lib/src/dart/element/type.dart b/pkg/analyzer/lib/src/dart/element/type.dart
index 6b7d311..4ea8023 100644
--- a/pkg/analyzer/lib/src/dart/element/type.dart
+++ b/pkg/analyzer/lib/src/dart/element/type.dart
@@ -1496,6 +1496,15 @@
   }
 
   @override
+  bool get isDartCoreBool {
+    ClassElement element = this.element;
+    if (element == null) {
+      return false;
+    }
+    return element.name == "bool" && element.library.isDartCore;
+  }
+
+  @override
   bool get isDartCoreFunction {
     ClassElement element = this.element;
     if (element == null) {
@@ -2735,6 +2744,9 @@
   bool get isDartAsyncFutureOr => false;
 
   @override
+  bool get isDartCoreBool => false;
+
+  @override
   bool get isDartCoreFunction => false;
 
   @override
diff --git a/pkg/analyzer/lib/src/dart/resolver/flow_analysis.dart b/pkg/analyzer/lib/src/dart/resolver/flow_analysis.dart
index e0940d2..5867f95 100644
--- a/pkg/analyzer/lib/src/dart/resolver/flow_analysis.dart
+++ b/pkg/analyzer/lib/src/dart/resolver/flow_analysis.dart
@@ -12,9 +12,21 @@
   /// TODO(scheglov) use _ElementSet?
   final List<LocalVariableElement> readBeforeWritten = [];
 
+  /// The [TypeSystem] of the enclosing library, used to check subtyping.
   final TypeSystem typeSystem;
+
+  /// The enclosing [FunctionBody], used to check for potential mutations.
+  final FunctionBody functionBody;
+
+  /// The stack of states of variables that are not definitely assigned.
   final List<_State> _stack = [];
 
+  /// The mapping from labeled [Statement]s to the index in the [_stack]
+  /// where the first related element is located.  The number of elements
+  /// is statement specific.  Loops have two elements: `break` and `continue`
+  /// states.
+  final Map<Statement, int> _statementToStackIndex = {};
+
   _State _current;
 
   /// The last boolean condition, for [_conditionTrue] and [_conditionFalse].
@@ -26,7 +38,7 @@
   /// The state when [_condition] evaluates to `false`.
   _State _conditionFalse;
 
-  FlowAnalysis(this.typeSystem) {
+  FlowAnalysis(this.typeSystem, this.functionBody) {
     _current = _State(false, _ElementSet.empty, const {});
   }
 
@@ -37,12 +49,103 @@
     }
   }
 
+  void conditional_elseBegin(ConditionalExpression node, bool isBool) {
+    var afterThen = _current;
+    var falseCondition = _stack.removeLast();
+
+    if (isBool) {
+      _conditionalEnd(node.thenExpression);
+      // Tail of the stack: falseThen, trueThen
+    }
+
+    _stack.add(afterThen);
+    _current = falseCondition;
+  }
+
+  void conditional_end(ConditionalExpression node, bool isBool) {
+    var afterThen = _stack.removeLast();
+    var afterElse = _current;
+
+    if (isBool) {
+      _conditionalEnd(node.elseExpression);
+      // Tail of the stack: falseThen, trueThen, falseElse, trueElse
+
+      var trueElse = _stack.removeLast();
+      var falseElse = _stack.removeLast();
+
+      var trueThen = _stack.removeLast();
+      var falseThen = _stack.removeLast();
+
+      var trueResult = trueThen.combine(typeSystem, trueElse);
+      var falseResult = falseThen.combine(typeSystem, falseElse);
+
+      _condition = node;
+      _conditionTrue = trueResult;
+      _conditionFalse = falseResult;
+    }
+
+    _current = afterThen.combine(typeSystem, afterElse);
+  }
+
+  void conditional_thenBegin(ConditionalExpression node) {
+    _conditionalEnd(node.condition);
+    // Tail of the stack: falseCondition, trueCondition
+
+    var trueCondition = _stack.removeLast();
+    _current = trueCondition;
+  }
+
+  void doStatement_bodyBegin(
+      DoStatement node, Set<VariableElement> loopAssigned) {
+    _current = _current.removePromotedAll(loopAssigned);
+
+    _statementToStackIndex[node] = _stack.length;
+    _stack.add(_State.identity); // break
+    _stack.add(_State.identity); // continue
+  }
+
+  void doStatement_conditionBegin() {
+    // Tail of the stack: break, continue
+
+    var continueState = _stack.removeLast();
+    _current = _current.combine(typeSystem, continueState);
+  }
+
+  void doStatement_end(DoStatement node) {
+    _conditionalEnd(node.condition);
+    // Tail of the stack:  break, falseCondition, trueCondition
+
+    _stack.removeLast(); // trueCondition
+    var falseCondition = _stack.removeLast();
+    var breakState = _stack.removeLast();
+
+    _current = falseCondition.combine(typeSystem, breakState);
+  }
+
   void falseLiteral(BooleanLiteral expression) {
     _condition = expression;
     _conditionTrue = _State.identity;
     _conditionFalse = _current;
   }
 
+  void handleBreak(AstNode target) {
+    var breakIndex = _statementToStackIndex[target];
+    if (breakIndex != null) {
+      _stack[breakIndex] = _stack[breakIndex].combine(typeSystem, _current);
+    }
+    _current = _State.identity;
+  }
+
+  void handleContinue(AstNode target) {
+    var breakIndex = _statementToStackIndex[target];
+    if (breakIndex != null) {
+      var continueIndex = breakIndex + 1;
+      _stack[continueIndex] =
+          _stack[continueIndex].combine(typeSystem, _current);
+    }
+    _current = _State.identity;
+  }
+
   /// Register the fact that the current state definitely exists, e.g. returns
   /// from the body, throws an exception, etc.
   void handleExit() {
@@ -78,15 +181,18 @@
   }
 
   void isExpression_end(
-      IsExpression isExpression, LocalElement element, DartType type) {
-    // TODO(scheglov) check for mutations
+      IsExpression isExpression, VariableElement variable, DartType type) {
+    if (functionBody.isPotentiallyMutatedInClosure(variable)) {
+      return;
+    }
+
     _condition = isExpression;
     if (isExpression.notOperator == null) {
-      _conditionTrue = _current.promote(typeSystem, element, type);
+      _conditionTrue = _current.promote(typeSystem, variable, type);
       _conditionFalse = _current;
     } else {
       _conditionTrue = _current;
-      _conditionFalse = _current.promote(typeSystem, element, type);
+      _conditionFalse = _current.promote(typeSystem, variable, type);
     }
   }
 
@@ -158,10 +264,10 @@
     _current = falseLeft;
   }
 
-  /// Retrieves the type that [element] is promoted to, if [element] is
-  /// currently promoted.  Otherwise returns `null`.
-  DartType promotedType(LocalElement element) {
-    return _current.promoted[element];
+  /// Retrieves the type that the [variable] is promoted to, if the [variable]
+  /// is currently promoted.  Otherwise returns `null`.
+  DartType promotedType(VariableElement variable) {
+    return _current.promoted[variable];
   }
 
   /// Register read of the given [variable] in the current state.
@@ -188,8 +294,34 @@
     assert(_stack.isEmpty);
   }
 
+  void whileStatement_bodyBegin(WhileStatement node) {
+    _conditionalEnd(node.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 whileStatement_conditionBegin(
+      WhileStatement node, Set<VariableElement> loopAssigned) {
+    _current = _current.removePromotedAll(loopAssigned);
+  }
+
+  void whileStatement_end() {
+    _stack.removeLast(); // continue
+    var breakState = _stack.removeLast();
+    var falseCondition = _stack.removeLast();
+
+    _current = falseCondition.combine(typeSystem, breakState);
+  }
+
   /// Register write of the given [variable] in the current state.
-  void write(LocalVariableElement variable) {
+  void write(VariableElement variable) {
     _current = _current.write(variable);
   }
 
@@ -207,6 +339,38 @@
   }
 }
 
+/// Sets of variables that are potentially assigned in loops.
+class LoopAssignedVariables {
+  static final _emptySet = Set<VariableElement>();
+
+  /// Mapping from a loop [AstNode] to the set of variables that are
+  /// potentially assigned in this loop.
+  final Map<AstNode, Set<VariableElement>> _map = {};
+
+  /// The stack of nested loops.
+  final List<Set<VariableElement>> _stack = [];
+
+  /// Return the set of variables that are potentially assigned in the [loop].
+  Set<VariableElement> operator [](AstNode loop) {
+    return _map[loop] ?? _emptySet;
+  }
+
+  void beginLoop() {
+    var set = Set<VariableElement>.identity();
+    _stack.add(set);
+  }
+
+  void endLoop(AstNode loop) {
+    _map[loop] = _stack.removeLast();
+  }
+
+  void write(VariableElement variable) {
+    for (var i = 0; i < _stack.length; ++i) {
+      _stack[i].add(variable);
+    }
+  }
+}
+
 /// List based immutable set of elements.
 class _ElementSet {
   static final empty = _ElementSet._(
@@ -279,11 +443,11 @@
 }
 
 class _State {
-  static final identity = _State(false, _ElementSet.empty, null);
+  static final identity = _State(false, _ElementSet.empty, const {});
 
   final bool reachable;
   final _ElementSet notAssigned;
-  final Map<LocalElement, DartType> promoted;
+  final Map<VariableElement, DartType> promoted;
 
   _State(this.reachable, this.notAssigned, this.promoted);
 
@@ -316,39 +480,48 @@
     return _State(newReachable, newNotAssigned, newPromoted);
   }
 
-  _State promote(TypeSystem typeSystem, LocalElement element, DartType type) {
-    var previousType = promoted[element];
-    if (previousType == null) {
-      if (element is LocalVariableElement) {
-        previousType = element.type;
-      } else if (element is ParameterElement) {
-        previousType = element.type;
-      } else {
-        throw StateError('Unexpected type: (${element.runtimeType}) $element');
-      }
-    }
+  _State promote(
+      TypeSystem typeSystem, VariableElement variable, DartType type) {
+    var previousType = promoted[variable];
+    previousType ??= variable.type;
 
     if (typeSystem.isSubtypeOf(type, previousType) && type != previousType) {
-      var newPromoted = <LocalElement, DartType>{}..addAll(promoted);
-      newPromoted[element] = type;
+      var newPromoted = <VariableElement, DartType>{}..addAll(promoted);
+      newPromoted[variable] = type;
       return _State(reachable, notAssigned, newPromoted);
     }
 
     return this;
   }
 
-  _State write(LocalVariableElement variable) {
-    var newNotAssigned = notAssigned.remove(variable);
-    if (identical(newNotAssigned, notAssigned)) return this;
-    return _State(reachable, newNotAssigned, promoted);
+  _State removePromotedAll(Set<VariableElement> variables) {
+    var newPromoted = _removePromotedAll(promoted, variables);
+
+    if (identical(newPromoted, promoted)) return this;
+
+    return _State(reachable, notAssigned, newPromoted);
   }
 
-  Map<LocalElement, DartType> _combinePromoted(TypeSystem typeSystem,
-      Map<LocalElement, DartType> a, Map<LocalElement, DartType> b) {
+  _State write(VariableElement variable) {
+    var newNotAssigned = variable is LocalVariableElement
+        ? notAssigned.remove(variable)
+        : notAssigned;
+    var newPromoted = _removePromoted(promoted, variable);
+
+    if (identical(newNotAssigned, notAssigned) &&
+        identical(newPromoted, promoted)) {
+      return this;
+    }
+
+    return _State(reachable, newNotAssigned, newPromoted);
+  }
+
+  static Map<VariableElement, DartType> _combinePromoted(TypeSystem typeSystem,
+      Map<VariableElement, DartType> a, Map<VariableElement, DartType> b) {
     if (identical(a, b)) return a;
     if (a.isEmpty || b.isEmpty) return const {};
 
-    var result = <LocalElement, DartType>{};
+    var result = <VariableElement, DartType>{};
     var alwaysA = true;
     var alwaysB = true;
     for (var element in a.keys) {
@@ -376,4 +549,34 @@
     if (result.isEmpty) return const {};
     return result;
   }
+
+  static Map<VariableElement, DartType> _removePromoted(
+      Map<VariableElement, DartType> map, VariableElement variable) {
+    if (map.isEmpty) return const {};
+
+    var result = <VariableElement, DartType>{};
+    for (var key in map.keys) {
+      if (!identical(key, variable)) {
+        result[key] = map[key];
+      }
+    }
+
+    if (result.isEmpty) return const {};
+    return result;
+  }
+
+  static Map<VariableElement, DartType> _removePromotedAll(
+      Map<VariableElement, DartType> map, Set<VariableElement> variables) {
+    if (map.isEmpty) return const {};
+
+    var result = <VariableElement, DartType>{};
+    for (var key in map.keys) {
+      if (!variables.contains(key)) {
+        result[key] = map[key];
+      }
+    }
+
+    if (result.isEmpty) return const {};
+    return result;
+  }
 }
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 d185aa6..7873fce 100644
--- a/pkg/analyzer/test/src/dart/resolution/flow_analysis_test.dart
+++ b/pkg/analyzer/test/src/dart/resolution/flow_analysis_test.dart
@@ -7,6 +7,7 @@
 import 'package:analyzer/dart/ast/visitor.dart';
 import 'package:analyzer/dart/element/element.dart';
 import 'package:analyzer/dart/element/type.dart';
+import 'package:analyzer/dart/element/type_system.dart';
 import 'package:analyzer/src/dart/resolver/flow_analysis.dart';
 import 'package:test/test.dart';
 import 'package:test_reflective_loader/test_reflective_loader.dart';
@@ -22,7 +23,7 @@
 
 @reflectiveTest
 class DefiniteAssignmentFlowTest extends DriverResolutionTest {
-  FlowAnalysis flow;
+  final List<LocalVariableElement> readBeforeWritten = [];
 
   /// Assert that only local variables with the given names are marked as read
   /// before being written.  All the other local variables are implicitly
@@ -33,7 +34,7 @@
         .where((i) => i != null)
         .map((name) => findElement.localVar(name))
         .toList();
-    expect(flow.readBeforeWritten, unorderedEquals(expected));
+    expect(readBeforeWritten, unorderedEquals(expected));
   }
 
   test_binaryExpression_logicalAnd_left() async {
@@ -102,6 +103,184 @@
     assertReadBeforeWritten();
   }
 
+  test_conditional_both() async {
+    await trackCode(r'''
+f(bool v) {
+  int v;
+  b ? (v = 1) : (v = 2);
+  v;
+}
+''');
+    assertReadBeforeWritten();
+  }
+
+  test_conditional_else() async {
+    await trackCode(r'''
+f(bool v) {
+  int v;
+  b ? 1 : (v = 2);
+  v;
+}
+''');
+    assertReadBeforeWritten('v');
+  }
+
+  test_conditional_then() async {
+    await trackCode(r'''
+f(bool v) {
+  int v;
+  b ? (v = 1) : 2;
+  v;
+}
+''');
+    assertReadBeforeWritten('v');
+  }
+
+  test_doWhile_break_afterAssignment() async {
+    await trackCode(r'''
+void f(bool b) {
+  int v;
+  do {
+    v = 0;
+    v;
+    if (b) break;
+  } while (b);
+  v;
+}
+''');
+    assertReadBeforeWritten();
+  }
+
+  test_doWhile_break_beforeAssignment() async {
+    await trackCode(r'''
+void f(bool b) {
+  int v;
+  do {
+    if (b) break;
+    v = 0;
+  } while (b);
+  v;
+}
+''');
+    assertReadBeforeWritten('v');
+  }
+
+  test_doWhile_breakOuterFromInner() async {
+    await trackCode(r'''
+void f(bool b) {
+  int v1, v2, v3;
+  L1: do {
+    do {
+      v1 = 0;
+      if (b) break L1;
+      v2 = 0;
+      v3 = 0;
+    } while (b);
+    v2;
+  } while (b);
+  v1;
+  v3;
+}
+''');
+    assertReadBeforeWritten('v3');
+  }
+
+  test_doWhile_condition() async {
+    await trackCode(r'''
+void f() {
+  int v1, v2;
+  do {
+    v1; // assigned in the condition, but not yet
+  } while ((v1 = 0) + (v2 = 0) >= 0);
+  v2;
+}
+''');
+    assertReadBeforeWritten('v1');
+  }
+
+  test_doWhile_condition_break() async {
+    await trackCode(r'''
+void f(bool b) {
+  int v;
+  do {
+    if (b) break;
+  } while ((v = 0) >= 0);
+  v;
+}
+''');
+    assertReadBeforeWritten('v');
+  }
+
+  test_doWhile_condition_break_continue() async {
+    await trackCode(r'''
+void f(bool b1, b2) {
+  int v1, v2, v3, v4, v5, v6;
+  do {
+    v1 = 0; // visible outside, visible to the condition
+    if (b1) break;
+    v2 = 0; // not visible outside, visible to the condition
+    v3 = 0; // not visible outside, visible to the condition
+    if (b2) continue;
+    v4 = 0; // not visible
+    v5 = 0; // not visible
+  } while ((v6 = v1 + v2 + v4) == 0); // has break => v6 is not visible outside
+  v1;
+  v3;
+  v5;
+  v6;
+}
+''');
+    assertReadBeforeWritten('v3', 'v4', 'v5', 'v6');
+  }
+
+  test_doWhile_condition_continue() async {
+    await trackCode(r'''
+void f(bool b) {
+  int v1, v2, v3, v4;
+  do {
+    v1 = 0; // visible outside, visible to the condition
+    if (b) continue;
+    v2 = 0; // not visible
+    v3 = 0; // not visible
+  } while ((v4 = v1 + v2) == 0); // no break => v4 visible outside
+  v1;
+  v3;
+  v4;
+}
+''');
+    assertReadBeforeWritten('v2', 'v3');
+  }
+
+  test_doWhile_continue_beforeAssignment() async {
+    await trackCode(r'''
+void f(bool b) {
+  int v;
+  do {
+    if (b) continue;
+    v = 0;
+  } while (b);
+  v;
+}
+''');
+    assertReadBeforeWritten('v');
+  }
+
+  test_doWhile_true_assignInBreak() async {
+    await trackCode(r'''
+void f(bool b) {
+  int v;
+  do {
+    if (b) {
+      v = 0;
+      break;
+    }
+  } while (true);
+  v;
+}
+''');
+    assertReadBeforeWritten();
+  }
+
   test_if_condition() async {
     await trackCode(r'''
 main() {
@@ -118,7 +297,6 @@
   }
 
   test_if_condition_false() async {
-    // new test
     await trackCode(r'''
 void f() {
   int v;
@@ -134,7 +312,6 @@
   }
 
   test_if_condition_logicalAnd_else() async {
-    // new test
     await trackCode(r'''
 void f(bool b, int i) {
   int v;
@@ -148,7 +325,6 @@
   }
 
   test_if_condition_logicalAnd_then() async {
-    // new test
     await trackCode(r'''
 void f(bool b, int i) {
   int v;
@@ -161,7 +337,6 @@
   }
 
   test_if_condition_logicalOr_else() async {
-    // new test
     await trackCode(r'''
 void f(bool b, int i) {
   int v;
@@ -175,7 +350,6 @@
   }
 
   test_if_condition_logicalOr_then() async {
-    // new test
     await trackCode(r'''
 void f(bool b, int i) {
   int v;
@@ -189,7 +363,6 @@
   }
 
   test_if_condition_notFalse() async {
-    // new test
     await trackCode(r'''
 void f() {
   int v;
@@ -203,7 +376,6 @@
   }
 
   test_if_condition_notTrue() async {
-    // new test
     await trackCode(r'''
 void f() {
   int v;
@@ -219,7 +391,6 @@
   }
 
   test_if_condition_true() async {
-    // new test
     await trackCode(r'''
 void f() {
   int v;
@@ -292,28 +463,205 @@
     assertReadBeforeWritten('v');
   }
 
+  test_while_condition() async {
+    await trackCode(r'''
+void f() {
+  int v;
+  while ((v = 0) >= 0) {
+    v;
+  }
+  v;
+}
+''');
+    assertReadBeforeWritten();
+  }
+
+  test_while_condition_notTrue() async {
+    await trackCode(r'''
+void f(bool b) {
+  int v1, v2;
+  while (b) {
+    v1 = 0;
+    v2 = 0;
+    v1;
+  }
+  v2;
+}
+''');
+    assertReadBeforeWritten('v2');
+  }
+
+  test_while_true_break_afterAssignment() async {
+    await trackCode(r'''
+void f(bool b) {
+  int v1, v2;
+  while (true) {
+    v1 = 0;
+    v1;
+    if (b) break;
+    v1;
+    v2 = 0;
+    v2;
+  }
+  v1;
+}
+''');
+    assertReadBeforeWritten();
+  }
+
+  test_while_true_break_beforeAssignment() async {
+    await trackCode(r'''
+void f(bool b) {
+  int v1, v2;
+  while (true) {
+    if (b) break;
+    v1 = 0;
+    v2 = 0;
+    v2;
+  }
+  v1;
+}
+''');
+    assertReadBeforeWritten('v1');
+  }
+
+  test_while_true_break_if() async {
+    await trackCode(r'''
+void f(bool b) {
+  int v;
+  while (true) {
+    if (b) {
+      v = 0;
+      break;
+    } else {
+      v = 0;
+      break;
+    }
+    v;
+  }
+  v;
+}
+''');
+    assertReadBeforeWritten();
+  }
+
+  test_while_true_break_if2() async {
+    await trackCode(r'''
+void f(bool b) {
+  var v;
+  while (true) {
+    if (b) {
+      break;
+    } else {
+      v = 0;
+    }
+    v;
+  }
+}
+''');
+    assertReadBeforeWritten();
+  }
+
+  test_while_true_break_if3() async {
+    await trackCode(r'''
+void f(bool b) {
+  int v1, v2;
+  while (true) {
+    if (b) {
+      v1 = 0;
+      v2 = 0;
+      if (b) break;
+    } else {
+      if (b) break;
+      v1 = 0;
+      v2 = 0;
+    }
+    v1;
+  }
+  v2;
+}
+''');
+    assertReadBeforeWritten('v2');
+  }
+
+  test_while_true_breakOuterFromInner() async {
+    await trackCode(r'''
+void f(bool b) {
+  int v1, v2, v3;
+  L1: while (true) {
+    L2: while (true) {
+      v1 = 0;
+      if (b) break L1;
+      v2 = 0;
+      v3 = 0;
+      if (b) break L2;
+    }
+    v2;
+  }
+  v1;
+  v3;
+}
+''');
+    assertReadBeforeWritten('v3');
+  }
+
+  test_while_true_continue() async {
+    await trackCode(r'''
+void f(bool b) {
+  int v;
+  while (true) {
+    if (b) continue;
+    v = 0;
+  }
+  v;
+}
+''');
+    assertReadBeforeWritten();
+  }
+
+  test_while_true_noBreak() async {
+    await trackCode(r'''
+void f() {
+  int v;
+  while (true) {
+    // No assignment, but no break.
+    // So, we don't exit the loop.
+    // So, all variables are assigned.
+  }
+  v;
+}
+''');
+    assertReadBeforeWritten();
+  }
+
   /// Resolve the given [code] and track assignments in the unit.
   Future<void> trackCode(String code) async {
     addTestFile(code);
     await resolveTestFile();
 
-    var typeSystem = result.unit.declaredElement.context.typeSystem;
-    flow = FlowAnalysis(typeSystem);
+    var unit = result.unit;
 
-    var visitor = _AstVisitor(flow, {});
-    result.unit.accept(visitor);
+    var loopAssignedVariables = LoopAssignedVariables();
+    unit.accept(_LoopAssignedVariablesVisitor(loopAssignedVariables));
+
+    var typeSystem = unit.declaredElement.context.typeSystem;
+    unit.accept(_AstVisitor(
+      typeSystem,
+      loopAssignedVariables,
+      {},
+      readBeforeWritten,
+    ));
   }
 }
 
 @reflectiveTest
 class TypePromotionFlowTest extends DriverResolutionTest {
-  Map<AstNode, DartType> promotedTypes = {};
-  FlowAnalysis flow;
+  final Map<AstNode, DartType> promotedTypes = {};
 
   void assertNotPromoted(String search) {
     var node = findNode.simple(search);
     var actualType = promotedTypes[node];
-    expect(actualType, isNull);
+    expect(actualType, isNull, reason: search);
   }
 
   void assertPromoted(String search, String expectedType) {
@@ -325,8 +673,119 @@
     assertElementTypeString(actualType, expectedType);
   }
 
+  test_assignment() async {
+    await trackCode(r'''
+f(Object x) {
+  if (x is String) {
+    x = 42;
+    x; // 1
+  }
+}
+''');
+    assertNotPromoted('x; // 1');
+  }
+
+  test_conditional() async {
+    await trackCode(r'''
+f(bool b, Object x) {
+  b ? ((x is num) || (throw 1)) : ((x is int) || (throw 2));
+  x; // 1
+}
+''');
+    assertPromoted('x; // 1', 'num');
+  }
+
+  test_do_condition_isNotType() async {
+    await trackCode(r'''
+void f(Object x) {
+  do {
+    x; // 1
+    x = '';
+  } while (x is! String)
+  x; // 2
+}
+''');
+    assertNotPromoted('x; // 1');
+    assertPromoted('x; // 2', 'String');
+  }
+
+  test_do_condition_isType() async {
+    await trackCode(r'''
+void f(Object x) {
+  do {
+    x; // 1
+  } while (x is String)
+  x; // 2
+}
+''');
+    assertNotPromoted('x; // 1');
+    assertNotPromoted('x; // 2');
+  }
+
+  test_do_outerIsType() async {
+    await trackCode(r'''
+void f(bool b, Object x) {
+  if (x is String) {
+    do {
+      x; // 1
+    } while (b);
+    x; // 2
+  }
+}
+''');
+    assertPromoted('x; // 1', 'String');
+    assertPromoted('x; // 2', 'String');
+  }
+
+  test_do_outerIsType_loopAssigned_body() async {
+    await trackCode(r'''
+void f(bool b, Object x) {
+  if (x is String) {
+    do {
+      x; // 1
+      x = x.length;
+    } while (b);
+    x; // 2
+  }
+}
+''');
+    assertNotPromoted('x; // 1');
+    assertNotPromoted('x; // 2');
+  }
+
+  test_do_outerIsType_loopAssigned_condition() async {
+    await trackCode(r'''
+void f(bool b, Object x) {
+  if (x is String) {
+    do {
+      x; // 1
+      x = x.length;
+    } while (x != 0);
+    x; // 2
+  }
+}
+''');
+    assertNotPromoted('x != 0');
+    assertNotPromoted('x; // 1');
+    assertNotPromoted('x; // 2');
+  }
+
+  test_do_outerIsType_loopAssigned_condition2() async {
+    await trackCode(r'''
+void f(bool b, Object x) {
+  if (x is String) {
+    do {
+      x; // 1
+    } while ((x = 1) != 0);
+    x; // 2
+  }
+}
+''');
+    assertNotPromoted('x; // 1');
+    assertNotPromoted('x; // 2');
+  }
+
   test_if_combine_empty() async {
-    // new test
     await trackCode(r'''
 main(bool b, Object v) {
   if (b) {
@@ -340,8 +799,39 @@
     assertNotPromoted('v; // 3');
   }
 
+  test_if_conditional_isNotType() async {
+    await trackCode(r'''
+f(bool b, Object v) {
+  if (b ? (v is! int) : (v is! num)) {
+    v; // 1
+  } else {
+    v; // 2
+  }
+  v; // 3
+}
+''');
+    assertNotPromoted('v; // 1');
+    assertPromoted('v; // 2', 'num');
+    assertNotPromoted('v; // 3');
+  }
+
+  test_if_conditional_isType() async {
+    await trackCode(r'''
+f(bool b, Object v) {
+  if (b ? (v is int) : (v is num)) {
+    v; // 1
+  } else {
+    v; // 2
+  }
+  v; // 3
+}
+''');
+    assertPromoted('v; // 1', 'num');
+    assertNotPromoted('v; // 2');
+    assertNotPromoted('v; // 3');
+  }
+
   test_if_isNotType() async {
-    // new test
     await trackCode(r'''
 main(v) {
   if (v is! String) {
@@ -358,7 +848,6 @@
   }
 
   test_if_isNotType_return() async {
-    // new test
     await trackCode(r'''
 main(v) {
   if (v is! String) return;
@@ -368,6 +857,16 @@
     assertPromoted('v; // ref', 'String');
   }
 
+  test_if_isNotType_throw() async {
+    await trackCode(r'''
+main(v) {
+  if (v is! String) throw 42;
+  v; // ref
+}
+''');
+    assertPromoted('v; // ref', 'String');
+  }
+
   test_if_isType() async {
     await trackCode(r'''
 main(v) {
@@ -396,7 +895,6 @@
   }
 
   test_if_logicalNot_isType() async {
-    // new test
     await trackCode(r'''
 main(v) {
   if (!(v is String)) {
@@ -413,7 +911,6 @@
   }
 
   test_logicalOr_throw() async {
-    // new test
     await trackCode(r'''
 main(v) {
   v is String || (throw 42);
@@ -423,36 +920,151 @@
     assertPromoted('v; // ref', 'String');
   }
 
+  test_potentiallyMutatedInClosure() async {
+    await trackCode(r'''
+f(Object x) {
+  localFunction() {
+    x = 42;
+  }
+
+  if (x is String) {
+    localFunction();
+    x; // 1
+  }
+}
+''');
+    assertNotPromoted('x; // 1');
+  }
+
+  test_potentiallyMutatedInScope() async {
+    await trackCode(r'''
+f(Object x) {
+  if (x is String) {
+    x; // 1
+  }
+
+  x = 42;
+}
+''');
+    assertPromoted('x; // 1', 'String');
+  }
+
+  test_while_condition_false() async {
+    await trackCode(r'''
+void f(Object x) {
+  while (x is! String) {
+    x; // 1
+  }
+  x; // 2
+}
+''');
+    assertNotPromoted('x; // 1');
+    assertPromoted('x; // 2', 'String');
+  }
+
+  test_while_condition_true() async {
+    await trackCode(r'''
+void f(Object x) {
+  while (x is String) {
+    x; // 1
+  }
+  x; // 2
+}
+''');
+    assertPromoted('x; // 1', 'String');
+    assertNotPromoted('x; // 2');
+  }
+
+  test_while_outerIsType() async {
+    await trackCode(r'''
+void f(bool b, Object x) {
+  if (x is String) {
+    while (b) {
+      x; // 1
+    }
+    x; // 2
+  }
+}
+''');
+    assertPromoted('x; // 1', 'String');
+    assertPromoted('x; // 2', 'String');
+  }
+
+  test_while_outerIsType_loopAssigned_body() async {
+    await trackCode(r'''
+void f(bool b, Object x) {
+  if (x is String) {
+    while (b) {
+      x; // 1
+      x = x.length;
+    }
+    x; // 2
+  }
+}
+''');
+    assertNotPromoted('x; // 1');
+    assertNotPromoted('x; // 2');
+  }
+
+  test_while_outerIsType_loopAssigned_condition() async {
+    await trackCode(r'''
+void f(bool b, Object x) {
+  if (x is String) {
+    while (x != 0) {
+      x; // 1
+      x = x.length;
+    }
+    x; // 2
+  }
+}
+''');
+    assertNotPromoted('x != 0');
+    assertNotPromoted('x; // 1');
+    assertNotPromoted('x; // 2');
+  }
+
   /// Resolve the given [code] and track assignments in the unit.
   Future<void> trackCode(String code) async {
     addTestFile(code);
     await resolveTestFile();
 
-    var typeSystem = result.unit.declaredElement.context.typeSystem;
-    flow = FlowAnalysis(typeSystem);
+    var unit = result.unit;
 
-    var visitor = _AstVisitor(flow, promotedTypes);
-    result.unit.accept(visitor);
+    var loopAssignedVariables = LoopAssignedVariables();
+    unit.accept(_LoopAssignedVariablesVisitor(loopAssignedVariables));
+
+    var typeSystem = unit.declaredElement.context.typeSystem;
+    unit.accept(_AstVisitor(
+      typeSystem,
+      loopAssignedVariables,
+      promotedTypes,
+      [],
+    ));
   }
 }
 
 /// [AstVisitor] that drives the [flow] in the way we expect the resolver
 /// will do in production.
 class _AstVisitor extends RecursiveAstVisitor<void> {
-  final FlowAnalysis flow;
+  final TypeSystem typeSystem;
+  final LoopAssignedVariables loopAssignedVariables;
   final Map<AstNode, DartType> promotedTypes;
+  final List<LocalVariableElement> readBeforeWritten;
 
-  _AstVisitor(this.flow, this.promotedTypes);
+  FlowAnalysis flow;
+
+  _AstVisitor(this.typeSystem, this.loopAssignedVariables, this.promotedTypes,
+      this.readBeforeWritten);
 
   @override
   void visitAssignmentExpression(AssignmentExpression node) {
     var left = node.leftHandSide;
     var right = node.rightHandSide;
 
-    LocalVariableElement localElement;
+    VariableElement localElement;
     if (left is SimpleIdentifier) {
       var element = left.staticElement;
-      if (element is LocalVariableElement) {
+      if (element is VariableElement) {
         localElement = element;
       }
     }
@@ -515,8 +1127,16 @@
 
   @override
   void visitBlockFunctionBody(BlockFunctionBody node) {
+    var isFlowOwner = flow == null;
+    flow ??= FlowAnalysis(typeSystem, node);
+
     super.visitBlockFunctionBody(node);
-    flow.verifyStackEmpty();
+
+    if (isFlowOwner) {
+      readBeforeWritten.addAll(flow.readBeforeWritten);
+      flow.verifyStackEmpty();
+      flow = null;
+    }
   }
 
   @override
@@ -531,9 +1151,49 @@
   }
 
   @override
-  void visitExpressionFunctionBody(ExpressionFunctionBody node) {
-    super.visitExpressionFunctionBody(node);
-    flow.verifyStackEmpty();
+  void visitBreakStatement(BreakStatement node) {
+    var target = _getLabelTarget(node, node.label?.staticElement);
+    flow.handleBreak(target);
+    super.visitBreakStatement(node);
+  }
+
+  @override
+  void visitConditionalExpression(ConditionalExpression node) {
+    var condition = node.condition;
+    var thenExpression = node.thenExpression;
+    var elseExpression = node.elseExpression;
+
+    condition.accept(this);
+
+    flow.conditional_thenBegin(node);
+    thenExpression.accept(this);
+    var isBool = thenExpression.staticType.isDartCoreBool;
+
+    flow.conditional_elseBegin(node, isBool);
+    elseExpression.accept(this);
+
+    flow.conditional_end(node, isBool);
+  }
+
+  @override
+  void visitContinueStatement(ContinueStatement node) {
+    var target = _getLabelTarget(node, node.label?.staticElement);
+    flow.handleContinue(target);
+    super.visitContinueStatement(node);
+  }
+
+  @override
+  void visitDoStatement(DoStatement node) {
+    var body = node.body;
+    var condition = node.condition;
+
+    flow.doStatement_bodyBegin(node, loopAssignedVariables[node]);
+    body.accept(this);
+
+    flow.doStatement_conditionBegin();
+    condition.accept(this);
+
+    flow.doStatement_end(node);
   }
 
   @override
@@ -563,7 +1223,7 @@
 
     if (expression is SimpleIdentifier) {
       var element = expression.staticElement;
-      if (element is LocalElement) {
+      if (element is VariableElement) {
         flow.isExpression_end(node, element, typeAnnotation.type);
       }
     }
@@ -598,7 +1258,7 @@
           flow.read(element);
         }
 
-        var promotedType = flow.promotedType(element);
+        var promotedType = flow?.promotedType(element);
         if (promotedType != null) {
           promotedTypes[node] = promotedType;
         }
@@ -626,6 +1286,54 @@
     super.visitVariableDeclarationStatement(node);
   }
 
+  @override
+  void visitWhileStatement(WhileStatement node) {
+    var condition = node.condition;
+    var body = node.body;
+
+    flow.whileStatement_conditionBegin(node, loopAssignedVariables[node]);
+    condition.accept(this);
+
+    flow.whileStatement_bodyBegin(node);
+    body.accept(this);
+
+    flow.whileStatement_end();
+  }
+
+  /// This code has OK performance for tests, but think if there is something
+  /// better when using in production.
+  AstNode _getLabelTarget(AstNode node, LabelElement element) {
+    for (; node != null; node = node.parent) {
+      if (node is DoStatement ||
+          node is ForEachStatement ||
+          node is ForStatement ||
+          node is SwitchStatement ||
+          node is WhileStatement) {
+        if (element == null) {
+          return node;
+        }
+        var parent = node.parent;
+        if (parent is LabeledStatement) {
+          for (var nodeLabel in parent.labels) {
+            if (identical(nodeLabel.label.staticElement, element)) {
+              return node;
+            }
+          }
+        }
+      }
+      if (element != null && node is SwitchStatement) {
+        for (var member in node.members) {
+          for (var nodeLabel in member.labels) {
+            if (identical(nodeLabel.label.staticElement, element)) {
+              return node;
+            }
+          }
+        }
+      }
+    }
+    return null;
+  }
+
   static bool _isFalseLiteral(AstNode node) {
     return node is BooleanLiteral && !node.value;
   }
@@ -634,3 +1342,37 @@
     return node is BooleanLiteral && node.value;
   }
 }
+
+class _LoopAssignedVariablesVisitor extends RecursiveAstVisitor<void> {
+  final LoopAssignedVariables loopAssignedVariables;
+
+  _LoopAssignedVariablesVisitor(this.loopAssignedVariables);
+
+  @override
+  void visitAssignmentExpression(AssignmentExpression node) {
+    var left = node.leftHandSide;
+
+    super.visitAssignmentExpression(node);
+
+    if (left is SimpleIdentifier) {
+      var element = left.staticElement;
+      if (element is VariableElement) {
+        loopAssignedVariables.write(element);
+      }
+    }
+  }
+
+  @override
+  void visitDoStatement(DoStatement node) {
+    loopAssignedVariables.beginLoop();
+    super.visitDoStatement(node);
+    loopAssignedVariables.endLoop(node);
+  }
+
+  @override
+  void visitWhileStatement(WhileStatement node) {
+    loopAssignedVariables.beginLoop();
+    super.visitWhileStatement(node);
+    loopAssignedVariables.endLoop(node);
+  }
+}