Partial implementation of the unified specification of definite assignment, type promotion, and reachability analysis.

Tests marked with the "new test" comment would not pass without the
unified specification, i.e. with type promotion implementation that
currently works in analyzer, or definite assignment analysis that I
implemented earlier.

R=brianwilkerson@google.com, paulberry@google.com

Change-Id: I9ba6fde991567e9a2761a7606af28ca1e260de54
Reviewed-on: https://dart-review.googlesource.com/c/89260
Commit-Queue: Konstantin Shcheglov <scheglov@google.com>
Reviewed-by: Paul Berry <paulberry@google.com>
diff --git a/pkg/analyzer/lib/src/dart/resolver/flow_analysis.dart b/pkg/analyzer/lib/src/dart/resolver/flow_analysis.dart
new file mode 100644
index 0000000..e0940d2
--- /dev/null
+++ b/pkg/analyzer/lib/src/dart/resolver/flow_analysis.dart
@@ -0,0 +1,379 @@
+// Copyright (c) 2019, the Dart project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+import 'package:analyzer/dart/ast/ast.dart';
+import 'package:analyzer/dart/element/element.dart';
+import 'package:analyzer/dart/element/type.dart';
+import 'package:analyzer/dart/element/type_system.dart';
+
+class FlowAnalysis {
+  /// The output list of variables that were read before they were written.
+  /// TODO(scheglov) use _ElementSet?
+  final List<LocalVariableElement> readBeforeWritten = [];
+
+  final TypeSystem typeSystem;
+  final List<_State> _stack = [];
+
+  _State _current;
+
+  /// The last boolean condition, for [_conditionTrue] and [_conditionFalse].
+  Expression _condition;
+
+  /// The state when [_condition] evaluates to `true`.
+  _State _conditionTrue;
+
+  /// The state when [_condition] evaluates to `false`.
+  _State _conditionFalse;
+
+  FlowAnalysis(this.typeSystem) {
+    _current = _State(false, _ElementSet.empty, const {});
+  }
+
+  /// Add a new [variable], which might be already [assigned].
+  void add(LocalVariableElement variable, {bool assigned: false}) {
+    if (!assigned) {
+      _current = _current.add(variable);
+    }
+  }
+
+  void falseLiteral(BooleanLiteral expression) {
+    _condition = expression;
+    _conditionTrue = _State.identity;
+    _conditionFalse = _current;
+  }
+
+  /// Register the fact that the current state definitely exists, e.g. returns
+  /// from the body, throws an exception, etc.
+  void handleExit() {
+    _current = _State.identity;
+  }
+
+  void ifStatement_elseBegin() {
+    var afterThen = _current;
+    var falseCondition = _stack.removeLast();
+    _stack.add(afterThen);
+    _current = falseCondition;
+  }
+
+  void ifStatement_end(bool hasElse) {
+    _State afterThen;
+    _State afterElse;
+    if (hasElse) {
+      afterThen = _stack.removeLast();
+      afterElse = _current;
+    } else {
+      afterThen = _current; // no `else`, so `then` is still current
+      afterElse = _stack.removeLast(); // `falseCond` is still on the stack
+    }
+    _current = afterThen.combine(typeSystem, afterElse);
+  }
+
+  void ifStatement_thenBegin(IfStatement ifStatement) {
+    _conditionalEnd(ifStatement.condition);
+    // Tail of the stack:  falseCondition, trueCondition
+
+    var trueCondition = _stack.removeLast();
+    _current = trueCondition;
+  }
+
+  void isExpression_end(
+      IsExpression isExpression, LocalElement element, DartType type) {
+    // TODO(scheglov) check for mutations
+    _condition = isExpression;
+    if (isExpression.notOperator == null) {
+      _conditionTrue = _current.promote(typeSystem, element, type);
+      _conditionFalse = _current;
+    } else {
+      _conditionTrue = _current;
+      _conditionFalse = _current.promote(typeSystem, element, type);
+    }
+  }
+
+  void logicalAnd_end(BinaryExpression andExpression) {
+    _conditionalEnd(andExpression.rightOperand);
+    // Tail of the stack: falseLeft, trueLeft, falseRight, trueRight
+
+    var trueRight = _stack.removeLast();
+    var falseRight = _stack.removeLast();
+
+    _stack.removeLast(); // trueLeft is not used
+    var falseLeft = _stack.removeLast();
+
+    var trueResult = trueRight;
+    var falseResult = falseLeft.combine(typeSystem, falseRight);
+    var afterResult = trueResult.combine(typeSystem, falseResult);
+
+    _condition = andExpression;
+    _conditionTrue = trueResult;
+    _conditionFalse = falseResult;
+
+    _current = afterResult;
+  }
+
+  void logicalAnd_rightBegin(BinaryExpression andExpression) {
+    _conditionalEnd(andExpression.leftOperand);
+    // Tail of the stack: falseLeft, trueLeft
+
+    var trueLeft = _stack.last;
+    _current = trueLeft;
+  }
+
+  void logicalNot_end(PrefixExpression notExpression) {
+    _conditionalEnd(notExpression.operand);
+    var trueExpr = _stack.removeLast();
+    var falseExpr = _stack.removeLast();
+
+    _condition = notExpression;
+    _conditionTrue = falseExpr;
+    _conditionFalse = trueExpr;
+  }
+
+  void logicalOr_end(BinaryExpression orExpression) {
+    _conditionalEnd(orExpression.rightOperand);
+    // Tail of the stack: falseLeft, trueLeft, falseRight, trueRight
+
+    var trueRight = _stack.removeLast();
+    var falseRight = _stack.removeLast();
+
+    var trueLeft = _stack.removeLast();
+    _stack.removeLast(); // falseLeft is not used
+
+    var trueResult = trueLeft.combine(typeSystem, trueRight);
+    var falseResult = falseRight;
+    var afterResult = trueResult.combine(typeSystem, falseResult);
+
+    _condition = orExpression;
+    _conditionTrue = trueResult;
+    _conditionFalse = falseResult;
+
+    _current = afterResult;
+  }
+
+  void logicalOr_rightBegin(BinaryExpression orExpression) {
+    _conditionalEnd(orExpression.leftOperand);
+    // Tail of the stack: falseLeft, trueLeft
+
+    var falseLeft = _stack[_stack.length - 2];
+    _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];
+  }
+
+  /// Register read of the given [variable] in the current state.
+  void read(LocalVariableElement variable) {
+    if (_current.notAssigned.contains(variable)) {
+      // Add to the list of violating variables, if not there yet.
+      for (var i = 0; i < readBeforeWritten.length; ++i) {
+        var violatingVariable = readBeforeWritten[i];
+        if (identical(violatingVariable, variable)) {
+          return;
+        }
+      }
+      readBeforeWritten.add(variable);
+    }
+  }
+
+  void trueLiteral(BooleanLiteral expression) {
+    _condition = expression;
+    _conditionTrue = _current;
+    _conditionFalse = _State.identity;
+  }
+
+  void verifyStackEmpty() {
+    assert(_stack.isEmpty);
+  }
+
+  /// Register write of the given [variable] in the current state.
+  void write(LocalVariableElement variable) {
+    _current = _current.write(variable);
+  }
+
+  void _conditionalEnd(Expression condition) {
+    while (condition is ParenthesizedExpression) {
+      condition = (condition as ParenthesizedExpression).expression;
+    }
+    if (identical(condition, _condition)) {
+      _stack.add(_conditionFalse);
+      _stack.add(_conditionTrue);
+    } else {
+      _stack.add(_current);
+      _stack.add(_current);
+    }
+  }
+}
+
+/// List based immutable set of elements.
+class _ElementSet {
+  static final empty = _ElementSet._(
+    List<LocalVariableElement>(0),
+  );
+
+  final List<LocalVariableElement> elements;
+
+  _ElementSet._(this.elements);
+
+  _ElementSet add(LocalVariableElement addedElement) {
+    if (contains(addedElement)) {
+      return this;
+    }
+
+    var length = elements.length;
+    var newElements = List<LocalVariableElement>(length + 1);
+    for (var i = 0; i < length; ++i) {
+      newElements[i] = elements[i];
+    }
+    newElements[length] = addedElement;
+    return _ElementSet._(newElements);
+  }
+
+  bool contains(LocalVariableElement element) {
+    var length = elements.length;
+    for (var i = 0; i < length; ++i) {
+      if (identical(elements[i], element)) {
+        return true;
+      }
+    }
+    return false;
+  }
+
+  _ElementSet remove(LocalVariableElement removedElement) {
+    if (!contains(removedElement)) {
+      return this;
+    }
+
+    var length = elements.length;
+    if (length == 1) {
+      return empty;
+    }
+
+    var newElements = List<LocalVariableElement>(length - 1);
+    var newIndex = 0;
+    for (var i = 0; i < length; ++i) {
+      var element = elements[i];
+      if (!identical(element, removedElement)) {
+        newElements[newIndex++] = element;
+      }
+    }
+
+    return _ElementSet._(newElements);
+  }
+
+  _ElementSet union(_ElementSet other) {
+    if (other == null || other.elements.isEmpty) {
+      return this;
+    }
+
+    var result = this;
+    var otherElements = other.elements;
+    for (var i = 0; i < otherElements.length; ++i) {
+      var otherElement = otherElements[i];
+      result = result.add(otherElement);
+    }
+    return result;
+  }
+}
+
+class _State {
+  static final identity = _State(false, _ElementSet.empty, null);
+
+  final bool reachable;
+  final _ElementSet notAssigned;
+  final Map<LocalElement, DartType> promoted;
+
+  _State(this.reachable, this.notAssigned, this.promoted);
+
+  /// Add a new [variable] to track definite assignment.
+  _State add(LocalVariableElement variable) {
+    var newNotAssigned = notAssigned.add(variable);
+    if (identical(newNotAssigned, notAssigned)) return this;
+    return _State(reachable, newNotAssigned, promoted);
+  }
+
+  _State combine(TypeSystem typeSystem, _State other) {
+    if (identical(this, identity)) return other;
+    if (identical(other, identity)) return this;
+
+    var newReachable = reachable || other.reachable;
+    var newNotAssigned = notAssigned.union(other.notAssigned);
+    var newPromoted = _combinePromoted(typeSystem, promoted, other.promoted);
+
+    if (reachable == newReachable &&
+        identical(notAssigned, newNotAssigned) &&
+        identical(promoted, newPromoted)) {
+      return this;
+    }
+    if (other.reachable == newReachable &&
+        identical(other.notAssigned, newNotAssigned) &&
+        identical(other.promoted, newPromoted)) {
+      return other;
+    }
+
+    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');
+      }
+    }
+
+    if (typeSystem.isSubtypeOf(type, previousType) && type != previousType) {
+      var newPromoted = <LocalElement, DartType>{}..addAll(promoted);
+      newPromoted[element] = 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);
+  }
+
+  Map<LocalElement, DartType> _combinePromoted(TypeSystem typeSystem,
+      Map<LocalElement, DartType> a, Map<LocalElement, DartType> b) {
+    if (identical(a, b)) return a;
+    if (a.isEmpty || b.isEmpty) return const {};
+
+    var result = <LocalElement, DartType>{};
+    var alwaysA = true;
+    var alwaysB = true;
+    for (var element in a.keys) {
+      var aType = a[element];
+      var bType = b[element];
+      if (aType != null && bType != null) {
+        if (typeSystem.isSubtypeOf(aType, bType)) {
+          result[element] = bType;
+          alwaysA = false;
+        } else if (typeSystem.isSubtypeOf(bType, aType)) {
+          result[element] = aType;
+          alwaysB = false;
+        } else {
+          alwaysA = false;
+          alwaysB = false;
+        }
+      } else {
+        alwaysA = false;
+        alwaysB = false;
+      }
+    }
+
+    if (alwaysA) return a;
+    if (alwaysB) return b;
+    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
new file mode 100644
index 0000000..d185aa6
--- /dev/null
+++ b/pkg/analyzer/test/src/dart/resolution/flow_analysis_test.dart
@@ -0,0 +1,636 @@
+// Copyright (c) 2019, the Dart project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+import 'package:analyzer/dart/ast/ast.dart';
+import 'package:analyzer/dart/ast/token.dart';
+import 'package:analyzer/dart/ast/visitor.dart';
+import 'package:analyzer/dart/element/element.dart';
+import 'package:analyzer/dart/element/type.dart';
+import 'package:analyzer/src/dart/resolver/flow_analysis.dart';
+import 'package:test/test.dart';
+import 'package:test_reflective_loader/test_reflective_loader.dart';
+
+import 'driver_resolution.dart';
+
+main() {
+  defineReflectiveSuite(() {
+    defineReflectiveTests(DefiniteAssignmentFlowTest);
+    defineReflectiveTests(TypePromotionFlowTest);
+  });
+}
+
+@reflectiveTest
+class DefiniteAssignmentFlowTest extends DriverResolutionTest {
+  FlowAnalysis flow;
+
+  /// Assert that only local variables with the given names are marked as read
+  /// before being written.  All the other local variables are implicitly
+  /// considered definitely assigned.
+  void assertReadBeforeWritten(
+      [String name1, String name2, String name3, String name4]) {
+    var expected = [name1, name2, name3, name4]
+        .where((i) => i != null)
+        .map((name) => findElement.localVar(name))
+        .toList();
+    expect(flow.readBeforeWritten, unorderedEquals(expected));
+  }
+
+  test_binaryExpression_logicalAnd_left() async {
+    await trackCode(r'''
+main(bool c) {
+  int v;
+  ((v = 0) >= 0) && c;
+  v;
+}
+''');
+    assertReadBeforeWritten();
+  }
+
+  test_binaryExpression_logicalAnd_right() async {
+    await trackCode(r'''
+main(bool c) {
+  int v;
+  c && ((v = 0) >= 0);
+  v;
+}
+''');
+    assertReadBeforeWritten('v');
+  }
+
+  test_binaryExpression_logicalOr_left() async {
+    await trackCode(r'''
+main(bool c) {
+  int v;
+  ((v = 0) >= 0) || c;
+  v;
+}
+''');
+    assertReadBeforeWritten();
+  }
+
+  test_binaryExpression_logicalOr_right() async {
+    await trackCode(r'''
+main(bool c) {
+  int v;
+  c || ((v = 0) >= 0);
+  v;
+}
+''');
+    assertReadBeforeWritten('v');
+  }
+
+  test_binaryExpression_plus_left() async {
+    await trackCode(r'''
+main() {
+  int v;
+  (v = 0) + 1;
+  v;
+}
+''');
+    assertReadBeforeWritten();
+  }
+
+  test_binaryExpression_plus_right() async {
+    await trackCode(r'''
+main() {
+  int v;
+  1 + (v = 0);
+  v;
+}
+''');
+    assertReadBeforeWritten();
+  }
+
+  test_if_condition() async {
+    await trackCode(r'''
+main() {
+  int v;
+  if ((v = 0) >= 0) {
+    v;
+  } else {
+    v;
+  }
+  v;
+}
+''');
+    assertReadBeforeWritten();
+  }
+
+  test_if_condition_false() async {
+    // new test
+    await trackCode(r'''
+void f() {
+  int v;
+  if (false) {
+    // not assigned
+  } else {
+    v = 0;
+  }
+  v;
+}
+''');
+    assertReadBeforeWritten();
+  }
+
+  test_if_condition_logicalAnd_else() async {
+    // new test
+    await trackCode(r'''
+void f(bool b, int i) {
+  int v;
+  if (b && (v = i) > 0) {
+  } else {
+    v;
+  }
+}
+''');
+    assertReadBeforeWritten('v');
+  }
+
+  test_if_condition_logicalAnd_then() async {
+    // new test
+    await trackCode(r'''
+void f(bool b, int i) {
+  int v;
+  if (b && (v = i) > 0) {
+    v;
+  }
+}
+''');
+    assertReadBeforeWritten();
+  }
+
+  test_if_condition_logicalOr_else() async {
+    // new test
+    await trackCode(r'''
+void f(bool b, int i) {
+  int v;
+  if (b || (v = i) > 0) {
+  } else {
+    v;
+  }
+}
+''');
+    assertReadBeforeWritten();
+  }
+
+  test_if_condition_logicalOr_then() async {
+    // new test
+    await trackCode(r'''
+void f(bool b, int i) {
+  int v;
+  if (b || (v = i) > 0) {
+    v;
+  } else {
+  }
+}
+''');
+    assertReadBeforeWritten('v');
+  }
+
+  test_if_condition_notFalse() async {
+    // new test
+    await trackCode(r'''
+void f() {
+  int v;
+  if (!false) {
+    v = 0;
+  }
+  v;
+}
+''');
+    assertReadBeforeWritten();
+  }
+
+  test_if_condition_notTrue() async {
+    // new test
+    await trackCode(r'''
+void f() {
+  int v;
+  if (!true) {
+    // not assigned
+  } else {
+    v = 0;
+  }
+  v;
+}
+''');
+    assertReadBeforeWritten();
+  }
+
+  test_if_condition_true() async {
+    // new test
+    await trackCode(r'''
+void f() {
+  int v;
+  if (true) {
+    v = 0;
+  }
+  v;
+}
+''');
+    assertReadBeforeWritten();
+  }
+
+  test_if_then() async {
+    await trackCode(r'''
+main(bool c) {
+  int v;
+  if (c) {
+    v = 0;
+  }
+  v;
+}
+''');
+    assertReadBeforeWritten('v');
+  }
+
+  test_if_thenElse_all() async {
+    await trackCode(r'''
+main(bool c) {
+  int v;
+  if (c) {
+    v = 0;
+    v;
+  } else {
+    v = 0;
+    v;
+  }
+  v;
+}
+''');
+    assertReadBeforeWritten();
+  }
+
+  test_if_thenElse_else() async {
+    await trackCode(r'''
+main(bool c) {
+  int v;
+  if (c) {
+    // not assigned
+  } else {
+    v = 0;
+  }
+  v;
+}
+''');
+    assertReadBeforeWritten('v');
+  }
+
+  test_if_thenElse_then() async {
+    await trackCode(r'''
+main(bool c) {
+  int v;
+  if (c) {
+    v = 0;
+  } else {
+    // not assigned
+  }
+  v;
+}
+''');
+    assertReadBeforeWritten('v');
+  }
+
+  /// 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 visitor = _AstVisitor(flow, {});
+    result.unit.accept(visitor);
+  }
+}
+
+@reflectiveTest
+class TypePromotionFlowTest extends DriverResolutionTest {
+  Map<AstNode, DartType> promotedTypes = {};
+  FlowAnalysis flow;
+
+  void assertNotPromoted(String search) {
+    var node = findNode.simple(search);
+    var actualType = promotedTypes[node];
+    expect(actualType, isNull);
+  }
+
+  void assertPromoted(String search, String expectedType) {
+    var node = findNode.simple(search);
+    var actualType = promotedTypes[node];
+    if (actualType == null) {
+      fail('$expectedType expected, but actually not promoted');
+    }
+    assertElementTypeString(actualType, expectedType);
+  }
+
+  test_if_combine_empty() async {
+    // new test
+    await trackCode(r'''
+main(bool b, Object v) {
+  if (b) {
+    v is int || (throw 1);
+  } else {
+    v is String || (throw 2);
+  }
+  v; // 3
+}
+''');
+    assertNotPromoted('v; // 3');
+  }
+
+  test_if_isNotType() async {
+    // new test
+    await trackCode(r'''
+main(v) {
+  if (v is! String) {
+    v; // 1
+  } else {
+    v; // 2
+  }
+  v; // 3
+}
+''');
+    assertNotPromoted('v; // 1');
+    assertPromoted('v; // 2', 'String');
+    assertNotPromoted('v; // 3');
+  }
+
+  test_if_isNotType_return() async {
+    // new test
+    await trackCode(r'''
+main(v) {
+  if (v is! String) return;
+  v; // ref
+}
+''');
+    assertPromoted('v; // ref', 'String');
+  }
+
+  test_if_isType() async {
+    await trackCode(r'''
+main(v) {
+  if (v is String) {
+    v; // 1
+  } else {
+    v; // 2
+  }
+  v; // 3
+}
+''');
+    assertPromoted('v; // 1', 'String');
+    assertNotPromoted('v; // 2');
+    assertNotPromoted('v; // 3');
+  }
+
+  test_if_isType_thenNonBoolean() async {
+    await trackCode(r'''
+f(Object x) {
+  if ((x is String) != 3) {
+    x; // 1
+  }
+}
+''');
+    assertNotPromoted('x; // 1');
+  }
+
+  test_if_logicalNot_isType() async {
+    // new test
+    await trackCode(r'''
+main(v) {
+  if (!(v is String)) {
+    v; // 1
+  } else {
+    v; // 2
+  }
+  v; // 3
+}
+''');
+    assertNotPromoted('v; // 1');
+    assertPromoted('v; // 2', 'String');
+    assertNotPromoted('v; // 3');
+  }
+
+  test_logicalOr_throw() async {
+    // new test
+    await trackCode(r'''
+main(v) {
+  v is String || (throw 42);
+  v; // ref
+}
+''');
+    assertPromoted('v; // ref', 'String');
+  }
+
+  /// 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 visitor = _AstVisitor(flow, promotedTypes);
+    result.unit.accept(visitor);
+  }
+}
+
+/// [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 Map<AstNode, DartType> promotedTypes;
+
+  _AstVisitor(this.flow, this.promotedTypes);
+
+  @override
+  void visitAssignmentExpression(AssignmentExpression node) {
+    var left = node.leftHandSide;
+    var right = node.rightHandSide;
+
+    LocalVariableElement localElement;
+    if (left is SimpleIdentifier) {
+      var element = left.staticElement;
+      if (element is LocalVariableElement) {
+        localElement = element;
+      }
+    }
+
+    if (localElement != null) {
+      var isPure = node.operator.type == TokenType.EQ;
+      if (!isPure) {
+        flow.read(localElement);
+      }
+      right.accept(this);
+      flow.write(localElement);
+    } else {
+      left.accept(this);
+      right.accept(this);
+    }
+  }
+
+  @override
+  void visitBinaryExpression(BinaryExpression node) {
+    var left = node.leftOperand;
+    var right = node.rightOperand;
+
+    var operator = node.operator.type;
+
+    if (operator == TokenType.AMPERSAND_AMPERSAND) {
+      left.accept(this);
+
+      flow.logicalAnd_rightBegin(node);
+      right.accept(this);
+
+      flow.logicalAnd_end(node);
+    } else if (operator == TokenType.BAR_BAR) {
+      left.accept(this);
+
+      flow.logicalOr_rightBegin(node);
+      right.accept(this);
+
+      flow.logicalOr_end(node);
+    } else {
+      left.accept(this);
+      right.accept(this);
+    }
+
+//    var isLogical = operator == TokenType.AMPERSAND_AMPERSAND ||
+//        operator == TokenType.BAR_BAR ||
+//        operator == TokenType.QUESTION_QUESTION;
+//
+//    left.accept(this);
+//
+//    if (isLogical) {
+//      tracker.beginBinaryExpressionLogicalRight();
+//    }
+//
+//    right.accept(this);
+//
+//    if (isLogical) {
+//      tracker.endBinaryExpressionLogicalRight();
+//    }
+  }
+
+  @override
+  void visitBlockFunctionBody(BlockFunctionBody node) {
+    super.visitBlockFunctionBody(node);
+    flow.verifyStackEmpty();
+  }
+
+  @override
+  void visitBooleanLiteral(BooleanLiteral node) {
+    super.visitBooleanLiteral(node);
+    if (_isFalseLiteral(node)) {
+      flow.falseLiteral(node);
+    }
+    if (_isTrueLiteral(node)) {
+      flow.trueLiteral(node);
+    }
+  }
+
+  @override
+  void visitExpressionFunctionBody(ExpressionFunctionBody node) {
+    super.visitExpressionFunctionBody(node);
+    flow.verifyStackEmpty();
+  }
+
+  @override
+  void visitIfStatement(IfStatement node) {
+    var condition = node.condition;
+    var thenStatement = node.thenStatement;
+    var elseStatement = node.elseStatement;
+
+    condition.accept(this);
+
+    flow.ifStatement_thenBegin(node);
+    thenStatement.accept(this);
+
+    if (elseStatement != null) {
+      flow.ifStatement_elseBegin();
+      elseStatement.accept(this);
+    }
+
+    flow.ifStatement_end(elseStatement != null);
+  }
+
+  @override
+  void visitIsExpression(IsExpression node) {
+    super.visitIsExpression(node);
+    var expression = node.expression;
+    var typeAnnotation = node.type;
+
+    if (expression is SimpleIdentifier) {
+      var element = expression.staticElement;
+      if (element is LocalElement) {
+        flow.isExpression_end(node, element, typeAnnotation.type);
+      }
+    }
+  }
+
+  @override
+  void visitPrefixExpression(PrefixExpression node) {
+    var operand = node.operand;
+
+    var operator = node.operator.type;
+    if (operator == TokenType.BANG) {
+      operand.accept(this);
+      flow.logicalNot_end(node);
+    } else {
+      operand.accept(this);
+    }
+  }
+
+  @override
+  void visitReturnStatement(ReturnStatement node) {
+    super.visitReturnStatement(node);
+    flow.handleExit();
+  }
+
+  @override
+  void visitSimpleIdentifier(SimpleIdentifier node) {
+    var element = node.staticElement;
+    var isLocalVariable = element is LocalVariableElement;
+    if (isLocalVariable || element is ParameterElement) {
+      if (node.inGetterContext()) {
+        if (isLocalVariable) {
+          flow.read(element);
+        }
+
+        var promotedType = flow.promotedType(element);
+        if (promotedType != null) {
+          promotedTypes[node] = promotedType;
+        }
+      }
+    }
+
+    super.visitSimpleIdentifier(node);
+  }
+
+  @override
+  void visitThrowExpression(ThrowExpression node) {
+    super.visitThrowExpression(node);
+    flow.handleExit();
+  }
+
+  @override
+  void visitVariableDeclarationStatement(VariableDeclarationStatement node) {
+    var variables = node.variables.variables;
+    for (var i = 0; i < variables.length; ++i) {
+      var variable = variables[i];
+      flow.add(variable.declaredElement,
+          assigned: variable.initializer != null);
+    }
+
+    super.visitVariableDeclarationStatement(node);
+  }
+
+  static bool _isFalseLiteral(AstNode node) {
+    return node is BooleanLiteral && !node.value;
+  }
+
+  static bool _isTrueLiteral(AstNode node) {
+    return node is BooleanLiteral && node.value;
+  }
+}
diff --git a/pkg/analyzer/test/src/dart/resolution/test_all.dart b/pkg/analyzer/test/src/dart/resolution/test_all.dart
index 4848102..d8c0076 100644
--- a/pkg/analyzer/test/src/dart/resolution/test_all.dart
+++ b/pkg/analyzer/test/src/dart/resolution/test_all.dart
@@ -9,6 +9,7 @@
 import 'comment_test.dart' as comment_test;
 import 'constant_test.dart' as constant_test;
 import 'enum_test.dart' as enum_test;
+import 'flow_analysis_test.dart' as flow_analysis_test;
 import 'for_in_test.dart' as for_in_test;
 import 'generic_type_alias_test.dart' as generic_type_alias_test;
 import 'import_prefix_test.dart' as import_prefix_test;
@@ -31,6 +32,7 @@
     comment_test.main();
     constant_test.main();
     enum_test.main();
+    flow_analysis_test.main();
     for_in_test.main();
     generic_type_alias_test.main();
     import_prefix_test.main();