Introduce distinct subclasses for _SimpleContext.

Note that due to https://github.com/dart-lang/sdk/issues/43725 we
can't yet downcast to these subclasses in all cases; added a test case
that would fail if we did.

Change-Id: Idd498840484a6a4c60a0cf2f8e01092ba75e7d2e
Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/166722
Reviewed-by: Johnni Winther <johnniwinther@google.com>
Commit-Queue: Paul Berry <paulberry@google.com>
diff --git a/pkg/_fe_analyzer_shared/lib/src/flow_analysis/flow_analysis.dart b/pkg/_fe_analyzer_shared/lib/src/flow_analysis/flow_analysis.dart
index de9e6cc..03877e4 100644
--- a/pkg/_fe_analyzer_shared/lib/src/flow_analysis/flow_analysis.dart
+++ b/pkg/_fe_analyzer_shared/lib/src/flow_analysis/flow_analysis.dart
@@ -2938,7 +2938,7 @@
         _assignedVariables._getInfoForNode(node);
     ++_functionNestingLevel;
     _current = _current.conservativeJoin(const [], info._written);
-    _stack.add(new _SimpleContext(_current));
+    _stack.add(new _FunctionExpressionContext(_current));
     _current = _current.conservativeJoin(_assignedVariables._anywhere._written,
         _assignedVariables._anywhere._captured);
   }
@@ -2948,7 +2948,7 @@
     --_functionNestingLevel;
     assert(_functionNestingLevel >= 0);
     _SimpleContext<Variable, Type> context =
-        _stack.removeLast() as _SimpleContext<Variable, Type>;
+        _stack.removeLast() as _FunctionExpressionContext<Variable, Type>;
     _current = context._previous;
   }
 
@@ -2979,6 +2979,9 @@
 
   @override
   void ifNullExpression_end() {
+    // TODO(paulberry): CFE sometimes calls ifNullExpression_end and
+    // nullAwareAccess_end out of order, so as a workaround we cast to the
+    // common base class.  See https://github.com/dart-lang/sdk/issues/43725.
     _SimpleContext<Variable, Type> context =
         _stack.removeLast() as _SimpleContext<Variable, Type>;
     _current = _join(_current, context._previous);
@@ -2997,7 +3000,7 @@
     } else {
       promoted = _current;
     }
-    _stack.add(new _SimpleContext<Variable, Type>(promoted));
+    _stack.add(new _IfNullExpressionContext<Variable, Type>(promoted));
     return true;
   }
 
@@ -3148,6 +3151,9 @@
 
   @override
   void nullAwareAccess_end() {
+    // TODO(paulberry): CFE sometimes calls ifNullExpression_end and
+    // nullAwareAccess_end out of order, so as a workaround we cast to the
+    // common base class.  See https://github.com/dart-lang/sdk/issues/43725.
     _SimpleContext<Variable, Type> context =
         _stack.removeLast() as _SimpleContext<Variable, Type>;
     _current = _join(_current, context._previous);
@@ -3156,7 +3162,7 @@
   @override
   bool nullAwareAccess_rightBegin(Expression target, Type targetType) {
     assert(targetType != null);
-    _stack.add(new _SimpleContext<Variable, Type>(_current));
+    _stack.add(new _NullAwareAccessContext<Variable, Type>(_current));
     if (target != null) {
       ExpressionInfo<Variable, Type> targetInfo = _getExpressionInfo(target);
       if (targetInfo is _VariableReadInfo<Variable, Type>) {
@@ -3406,6 +3412,16 @@
 /// language for which flow analysis information needs to be tracked.
 abstract class _FlowContext {}
 
+/// [_FlowContext] representing a function expression.
+class _FunctionExpressionContext<Variable, Type>
+    extends _SimpleContext<Variable, Type> {
+  _FunctionExpressionContext(FlowModel<Variable, Type> previous)
+      : super(previous);
+
+  @override
+  String toString() => '_FunctionExpressionContext(previous: $_previous)';
+}
+
 /// [_FlowContext] representing an `if` statement.
 class _IfContext<Variable, Type> extends _BranchContext<Variable, Type> {
   /// Flow model associated with the state of program execution after the `if`
@@ -3420,6 +3436,25 @@
       '_IfContext(conditionInfo: $_conditionInfo, afterThen: $_afterThen)';
 }
 
+/// [_FlowContext] representing an "if-null" (`??`) expression.
+class _IfNullExpressionContext<Variable, Type>
+    extends _SimpleContext<Variable, Type> {
+  _IfNullExpressionContext(FlowModel<Variable, Type> previous)
+      : super(previous);
+
+  @override
+  String toString() => '_IfNullExpressionContext(previous: $_previous)';
+}
+
+/// [_FlowContext] representing a null aware access (`?.`).
+class _NullAwareAccessContext<Variable, Type>
+    extends _SimpleContext<Variable, Type> {
+  _NullAwareAccessContext(FlowModel<Variable, Type> previous) : super(previous);
+
+  @override
+  String toString() => '_NullAwareAccessContext(previous: $_previous)';
+}
+
 /// [ExpressionInfo] representing a `null` literal.
 class _NullInfo<Variable, Type> implements ExpressionInfo<Variable, Type> {
   @override
@@ -3437,7 +3472,7 @@
 /// [_FlowContext] representing a language construct for which flow analysis
 /// must store a flow model state to be retrieved later, such as a `try`
 /// statement, function expression, or "if-null" (`??`) expression.
-class _SimpleContext<Variable, Type> extends _FlowContext {
+abstract class _SimpleContext<Variable, Type> extends _FlowContext {
   /// The stored state.  For a `try` statement, this is the state from the
   /// beginning of the `try` block.  For a function expression, this is the
   /// state at the point the function expression was created.  For an "if-null"
@@ -3446,9 +3481,6 @@
   final FlowModel<Variable, Type> _previous;
 
   _SimpleContext(this._previous);
-
-  @override
-  String toString() => '_SimpleContext(previous: $_previous)';
 }
 
 /// [_FlowContext] representing a language construct that can be targeted by
diff --git a/pkg/_fe_analyzer_shared/test/flow_analysis/type_promotion/data/mixed_if_null_and_null_aware.dart b/pkg/_fe_analyzer_shared/test/flow_analysis/type_promotion/data/mixed_if_null_and_null_aware.dart
new file mode 100644
index 0000000..31091b0
--- /dev/null
+++ b/pkg/_fe_analyzer_shared/test/flow_analysis/type_promotion/data/mixed_if_null_and_null_aware.dart
@@ -0,0 +1,8 @@
+// Copyright (c) 2020, 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.
+
+// This test verifies that we don't crash when trying to analyze an expression
+// that combines if-null with null-aware access.
+
+f(int? i, List? l) => i ?? l?.length;
diff --git a/pkg/front_end/test/spell_checking_list_common.txt b/pkg/front_end/test/spell_checking_list_common.txt
index 47cfd38..62f6492 100644
--- a/pkg/front_end/test/spell_checking_list_common.txt
+++ b/pkg/front_end/test/spell_checking_list_common.txt
@@ -273,6 +273,7 @@
 behaved
 behaves
 behavior
+behavioral
 behaviors
 behind
 being
@@ -844,6 +845,7 @@
 diet
 differ
 difference
+differences
 different
 differently
 dig