Improvements for flow analysis.

Support for 'IfNull' expression.
Support for 'rethrow'.
Tests for definite assignment and assignment expressions.

Change-Id: I053d27ca05e4ccaccc6a9fc7e10a481cd481ab21
Reviewed-on: https://dart-review.googlesource.com/c/90102
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 e33f899..46a2025 100644
--- a/pkg/analyzer/lib/src/dart/resolver/flow_analysis.dart
+++ b/pkg/analyzer/lib/src/dart/resolver/flow_analysis.dart
@@ -215,6 +215,15 @@
     _current = _State.identity;
   }
 
+  void ifNullExpression_end() {
+    var afterLeft = _stack.removeLast();
+    _current = _current.combine(typeSystem, afterLeft);
+  }
+
+  void ifNullExpression_rightBegin() {
+    _stack.add(_current); // afterLeft
+  }
+
   void ifStatement_elseBegin() {
     var afterThen = _current;
     var falseCondition = _stack.removeLast();
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 9710109..9114973 100644
--- a/pkg/analyzer/test/src/dart/resolution/flow_analysis_test.dart
+++ b/pkg/analyzer/test/src/dart/resolution/flow_analysis_test.dart
@@ -38,6 +38,99 @@
     expect(readBeforeWritten, unorderedEquals(expected));
   }
 
+  test_assignment_leftExpression() async {
+    await trackCode(r'''
+void f() {
+  List<int> v;
+  v[0] = (v = [1, 2])[1];
+  v;
+}
+''');
+    assertReadBeforeWritten('v');
+  }
+
+  test_assignment_leftLocal_compound() async {
+    await trackCode(r'''
+void f() {
+  int v;
+  v += 1;
+}
+''');
+    assertReadBeforeWritten('v');
+  }
+
+  test_assignment_leftLocal_compound_assignInRight() async {
+    await trackCode(r'''
+void f() {
+  int v;
+  v += (v = v);
+}
+''');
+    assertReadBeforeWritten('v');
+  }
+
+  test_assignment_leftLocal_pure_eq() async {
+    await trackCode(r'''
+void f() {
+  int v;
+  v = 0;
+}
+''');
+    assertReadBeforeWritten();
+  }
+
+  test_assignment_leftLocal_pure_eq_self() async {
+    await trackCode(r'''
+void f() {
+  int v;
+  v = v;
+}
+''');
+    assertReadBeforeWritten('v');
+  }
+
+  test_assignment_leftLocal_pure_questionEq() async {
+    await trackCode(r'''
+void f() {
+  int v;
+  v ??= 0;
+}
+''');
+    assertReadBeforeWritten('v');
+  }
+
+  test_assignment_leftLocal_pure_questionEq_self() async {
+    await trackCode(r'''
+void f() {
+  int v;
+  v ??= v;
+}
+''');
+    assertReadBeforeWritten('v');
+  }
+
+  test_binaryExpression_ifNull_left() async {
+    await trackCode(r'''
+void f() {
+  int v;
+  (v = 0) ?? 0;
+  v;
+}
+''');
+    assertReadBeforeWritten();
+  }
+
+  test_binaryExpression_ifNull_right() async {
+    await trackCode(r'''
+void f(int a) {
+  int v;
+  a ?? (v = 0);
+  v;
+}
+''');
+    assertReadBeforeWritten('v');
+  }
+
   test_binaryExpression_logicalAnd_left() async {
     await trackCode(r'''
 main(bool c) {
@@ -137,6 +230,17 @@
     assertReadBeforeWritten('v');
   }
 
+  test_conditionalExpression_condition() async {
+    await trackCode(r'''
+main() {
+  int v;
+  (v = 0) >= 0 ? 1 : 2;
+  v;
+}
+''');
+    assertReadBeforeWritten();
+  }
+
   test_doWhile_break_afterAssignment() async {
     await trackCode(r'''
 void f(bool b) {
@@ -894,6 +998,21 @@
     assertReadBeforeWritten();
   }
 
+  test_tryCatch_body_catchRethrow() async {
+    await trackCode(r'''
+void f() {
+  int v;
+  try {
+    v = 0;
+  } catch (_) {
+    rethrow;
+  }
+  v;
+}
+''');
+    assertReadBeforeWritten();
+  }
+
   test_tryCatch_catch() async {
     await trackCode(r'''
 void f() {
@@ -1212,9 +1331,33 @@
     assertNotPromoted('x; // 1');
   }
 
-  test_conditional() async {
+  test_binaryExpression_ifNull() async {
     await trackCode(r'''
-f(bool b, Object x) {
+void f(Object x) {
+  ((x is num) || (throw 1)) ?? ((x is int) || (throw 2));
+  x; // 1
+}
+''');
+    assertPromoted('x; // 1', 'num');
+  }
+
+  test_binaryExpression_ifNull_rightUnPromote() async {
+    await trackCode(r'''
+void f(Object x, Object y, Object z) {
+  if (x is int) {
+    x; // 1
+    y ?? (x = z);
+    x; // 2
+  }
+}
+''');
+    assertPromoted('x; // 1', 'int');
+    assertNotPromoted('x; // 2');
+  }
+
+  test_conditional_both() async {
+    await trackCode(r'''
+void f(bool b, Object x) {
   b ? ((x is num) || (throw 1)) : ((x is int) || (throw 2));
   x; // 1
 }
@@ -1222,6 +1365,26 @@
     assertPromoted('x; // 1', 'num');
   }
 
+  test_conditional_else() async {
+    await trackCode(r'''
+void f(bool b, Object x) {
+  b ? 0 : ((x is int) || (throw 2));
+  x; // 1
+}
+''');
+    assertNotPromoted('x; // 1');
+  }
+
+  test_conditional_then() async {
+    await trackCode(r'''
+void f(bool b, Object x) {
+  b ? ((x is num) || (throw 1)) : 0;
+  x; // 1
+}
+''');
+    assertNotPromoted('x; // 1');
+  }
+
   test_do_condition_isNotType() async {
     await trackCode(r'''
 void f(Object x) {
@@ -1568,6 +1731,18 @@
     assertNotPromoted('v; // 3');
   }
 
+  test_if_then_isNotType_return() async {
+    await trackCode(r'''
+void f(bool b, Object x) {
+  if (b) {
+    if (x is! String) return;
+  }
+  x; // 1
+}
+''');
+    assertNotPromoted('x; // 1');
+  }
+
   test_logicalOr_throw() async {
     await trackCode(r'''
 main(v) {
@@ -1688,6 +1863,26 @@
     assertPromoted('x; // 3', 'String');
   }
 
+  test_try_isNotType_exit_body_catchRethrow() async {
+    await trackCode(r'''
+void f(Object x) {
+  try {
+    if (x is! String) return;
+    x; // 1
+  } catch (_) {
+    x; // 2
+    rethrow;
+  }
+  x; // 3
+}
+
+void g() {}
+''');
+    assertPromoted('x; // 1', 'String');
+    assertNotPromoted('x; // 2');
+    assertPromoted('x; // 3', 'String');
+  }
+
   test_try_isNotType_exit_catch() async {
     await trackCode(r'''
 void f(Object x) {
@@ -1950,26 +2145,17 @@
       right.accept(this);
 
       flow.logicalOr_end(node);
+    } else if (operator == TokenType.QUESTION_QUESTION) {
+      left.accept(this);
+
+      flow.ifNullExpression_rightBegin();
+      right.accept(this);
+
+      flow.ifNullExpression_end();
     } 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
@@ -2133,6 +2319,12 @@
   }
 
   @override
+  void visitRethrowExpression(RethrowExpression node) {
+    super.visitRethrowExpression(node);
+    flow.handleExit();
+  }
+
+  @override
   void visitReturnStatement(ReturnStatement node) {
     super.visitReturnStatement(node);
     flow.handleExit();