Encode unconditional control flow information in nullability graph.

Change-Id: I598e05b61d6639d72ae6f84ab48f4e4051c87014
Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/102302
Reviewed-by: Brian Wilkerson <brianwilkerson@google.com>
diff --git a/pkg/analysis_server/lib/src/nullability/constraint_gatherer.dart b/pkg/analysis_server/lib/src/nullability/constraint_gatherer.dart
index c30ef23..c96c5b9 100644
--- a/pkg/analysis_server/lib/src/nullability/constraint_gatherer.dart
+++ b/pkg/analysis_server/lib/src/nullability/constraint_gatherer.dart
@@ -146,7 +146,7 @@
       if (!_inConditionalControlFlow &&
           _conditionInfo.trueDemonstratesNonNullIntent != null) {
         _conditionInfo.trueDemonstratesNonNullIntent
-            ?.recordNonNullIntent(_constraints, _guards);
+            ?.recordNonNullIntent(_constraints, _guards, _graph);
       }
     }
     node.message?.accept(this);
diff --git a/pkg/analysis_server/lib/src/nullability/nullability_graph.dart b/pkg/analysis_server/lib/src/nullability/nullability_graph.dart
index 311db8a..c48537b 100644
--- a/pkg/analysis_server/lib/src/nullability/nullability_graph.dart
+++ b/pkg/analysis_server/lib/src/nullability/nullability_graph.dart
@@ -18,15 +18,32 @@
   /// key node will have to be nullable, or null checks will have to be added).
   final _upstream = Map<NullabilityNode, List<NullabilityNode>>.identity();
 
+  /// Map from a nullability node to those nodes that are "upstream" from it
+  /// via unconditional control flow (meaning that if a node in the value is
+  /// nullable, then there exists code that is unguarded by an "if" statement
+  /// that indicates that the corresponding key node will have to be nullable,
+  /// or null checks will have to be added).
+  final _unconditionalUpstream =
+      Map<NullabilityNode, List<NullabilityNode>>.identity();
+
   /// Records that [sourceNode] is immediately upstream from [destinationNode].
-  void connect(NullabilityNode sourceNode, NullabilityNode destinationNode) {
+  void connect(NullabilityNode sourceNode, NullabilityNode destinationNode,
+      {bool unconditional: false}) {
     (_downstream[sourceNode] ??= []).add(destinationNode);
     (_upstream[destinationNode] ??= []).add(sourceNode);
+    if (unconditional) {
+      (_unconditionalUpstream[destinationNode] ??= []).add(sourceNode);
+    }
   }
 
   void debugDump() {
     for (var entry in _downstream.entries) {
-      print('${entry.key} -> ${entry.value.join(', ')}');
+      print('${entry.key} -> ${entry.value.map((value) {
+        var suffix = getUnconditionalUpstreamNodes(value).contains(entry.key)
+            ? ' (unconditional)'
+            : '';
+        return '$value$suffix';
+      }).join(', ')}');
     }
   }
 
@@ -38,6 +55,14 @@
   Iterable<NullabilityNode> getDownstreamNodes(NullabilityNode node) =>
       _downstream[node] ?? const [];
 
+  /// Iterates through all nodes that are "upstream" of [node] due to
+  /// unconditional control flow.
+  ///
+  /// There is no guarantee of uniqueness of the iterated nodes.
+  Iterable<NullabilityNode> getUnconditionalUpstreamNodes(
+          NullabilityNode node) =>
+      _unconditionalUpstream[node] ?? const [];
+
   /// Iterates through all nodes that are "upstream" of [node] (i.e. if
   /// any of the iterated nodes are nullable, then [node] will either have to be
   /// nullable, or null checks will have to be added).
diff --git a/pkg/analysis_server/lib/src/nullability/nullability_node.dart b/pkg/analysis_server/lib/src/nullability/nullability_node.dart
index b5c809d..f332806 100644
--- a/pkg/analysis_server/lib/src/nullability/nullability_node.dart
+++ b/pkg/analysis_server/lib/src/nullability/nullability_node.dart
@@ -126,9 +126,10 @@
     }
   }
 
-  void recordNonNullIntent(
-      Constraints constraints, List<NullabilityNode> guards) {
+  void recordNonNullIntent(Constraints constraints,
+      List<NullabilityNode> guards, NullabilityGraph graph) {
     _recordConstraints(constraints, guards, const [], nonNullIntent);
+    graph.connect(this, NullabilityNode.never, unconditional: true);
   }
 
   String toString() {
@@ -186,7 +187,8 @@
       NullabilityGraph graph,
       bool inConditionalControlFlow) {
     var additionalConditions = <ConstraintVariable>[];
-    graph.connect(sourceNode, destinationNode);
+    graph.connect(sourceNode, destinationNode,
+        unconditional: !inConditionalControlFlow);
     if (sourceNode._nullable != null) {
       additionalConditions.add(sourceNode._nullable);
       var destinationNonNullIntent = destinationNode.nonNullIntent;