Rework some migration tests to make use of the nullability graph.

This required tracking the map from a node to the nodes that are
upstream from it.

Change-Id: I079fac2196f3928b521b0866fd6f1790e6f24573
Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/101489
Reviewed-by: Brian Wilkerson <brianwilkerson@google.com>
Commit-Queue: Paul Berry <paulberry@google.com>
diff --git a/pkg/analysis_server/lib/src/nullability/nullability_graph.dart b/pkg/analysis_server/lib/src/nullability/nullability_graph.dart
index f1c9d45..bcc2300 100644
--- a/pkg/analysis_server/lib/src/nullability/nullability_graph.dart
+++ b/pkg/analysis_server/lib/src/nullability/nullability_graph.dart
@@ -13,9 +13,15 @@
   /// have to be added).
   final _downstream = Map<NullabilityNode, List<NullabilityNode>>.identity();
 
+  /// Map from a nullability node to those nodes that are "upstream" from it
+  /// (meaning that if a node in the value is nullable, then the corresponding
+  /// key node will have to be nullable, or null checks will have to be added).
+  final _upstream = Map<NullabilityNode, List<NullabilityNode>>.identity();
+
   /// Records that [sourceNode] is immediately upstream from [destinationNode].
   void connect(NullabilityNode sourceNode, NullabilityNode destinationNode) {
     (_downstream[sourceNode] ??= []).add(destinationNode);
+    (_upstream[destinationNode] ??= []).add(sourceNode);
   }
 
   /// Iterates through all nodes that are "downstream" of [node] (i.e. if
@@ -25,4 +31,12 @@
   /// There is no guarantee of uniqueness of the iterated nodes.
   Iterable<NullabilityNode> getDownstreamNodes(NullabilityNode node) =>
       _downstream[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).
+  //  ///
+  //  /// There is no guarantee of uniqueness of the iterated nodes.
+  Iterable<NullabilityNode> getUpstreamNodes(NullabilityNode node) =>
+      _upstream[node] ?? const [];
 }
diff --git a/pkg/analysis_server/test/src/nullability/migration_visitor_test.dart b/pkg/analysis_server/test/src/nullability/migration_visitor_test.dart
index 5501916..61c133d 100644
--- a/pkg/analysis_server/test/src/nullability/migration_visitor_test.dart
+++ b/pkg/analysis_server/test/src/nullability/migration_visitor_test.dart
@@ -81,6 +81,14 @@
     }
   }
 
+  /// Checks that there are no nullability nodes upstream from [node] that could
+  /// cause it to become nullable.
+  void assertNoUpstreamNullability(NullabilityNode node) {
+    for (var upstreamNode in graph.getUpstreamNodes(node)) {
+      expect(upstreamNode, NullabilityNode.never);
+    }
+  }
+
   /// Verifies that a null check will occur under the proper circumstances.
   ///
   /// [expressionChecks] is the object tracking whether or not a null check is
@@ -175,7 +183,7 @@
 int f(int i, int j) => i + j;
 ''');
 
-    assertNoConstraints(decoratedTypeAnnotation('int f').node.nullable);
+    assertNoUpstreamNullability(decoratedTypeAnnotation('int f').node);
   }
 
   test_binaryExpression_add_right_check() async {
@@ -205,7 +213,7 @@
 bool f(int i, int j) => i == j;
 ''');
 
-    assertNoConstraints(decoratedTypeAnnotation('bool f').node.nullable);
+    assertNoUpstreamNullability(decoratedTypeAnnotation('bool f').node);
   }
 
   test_boolLiteral() async {
@@ -214,7 +222,7 @@
   return true;
 }
 ''');
-    assertNoConstraints(decoratedTypeAnnotation('bool').node.nullable);
+    assertNoUpstreamNullability(decoratedTypeAnnotation('bool').node);
   }
 
   test_conditionalExpression_condition_check() async {
@@ -312,7 +320,7 @@
 void f({int i = 1}) {}
 ''');
 
-    assertNoConstraints(decoratedTypeAnnotation('int').node.nullable);
+    assertNoUpstreamNullability(decoratedTypeAnnotation('int').node);
   }
 
   test_functionDeclaration_parameter_named_default_null() async {
@@ -344,7 +352,7 @@
             namedNoDefaultParameterHeuristic:
                 NamedNoDefaultParameterHeuristic.assumeRequired));
 
-    assertNoConstraints(decoratedTypeAnnotation('int').node.nullable);
+    assertNoUpstreamNullability(decoratedTypeAnnotation('int').node);
   }
 
   test_functionDeclaration_parameter_named_no_default_required_assume_nullable() async {
@@ -357,7 +365,7 @@
             namedNoDefaultParameterHeuristic:
                 NamedNoDefaultParameterHeuristic.assumeNullable));
 
-    assertNoConstraints(decoratedTypeAnnotation('int').node.nullable);
+    assertNoUpstreamNullability(decoratedTypeAnnotation('int').node);
   }
 
   test_functionDeclaration_parameter_named_no_default_required_assume_required() async {
@@ -370,7 +378,7 @@
             namedNoDefaultParameterHeuristic:
                 NamedNoDefaultParameterHeuristic.assumeRequired));
 
-    assertNoConstraints(decoratedTypeAnnotation('int').node.nullable);
+    assertNoUpstreamNullability(decoratedTypeAnnotation('int').node);
   }
 
   test_functionDeclaration_parameter_positionalOptional_default_notNull() async {
@@ -378,7 +386,7 @@
 void f([int i = 1]) {}
 ''');
 
-    assertNoConstraints(decoratedTypeAnnotation('int').node.nullable);
+    assertNoUpstreamNullability(decoratedTypeAnnotation('int').node);
   }
 
   test_functionDeclaration_parameter_positionalOptional_default_null() async {
@@ -481,8 +489,8 @@
     // The call at `f()` is presumed to be in error; no constraint is recorded.
     var optional_i = possiblyOptionalParameter('int i');
     expect(optional_i, isNull);
-    var nullable_i = decoratedTypeAnnotation('int i').node.nullable;
-    assertNoConstraints(nullable_i);
+    var nullable_i = decoratedTypeAnnotation('int i').node;
+    assertNoUpstreamNullability(nullable_i);
   }
 
   test_functionInvocation_parameter_null() async {
@@ -615,7 +623,7 @@
   return 0;
 }
 ''');
-    assertNoConstraints(decoratedTypeAnnotation('int').node.nullable);
+    assertNoUpstreamNullability(decoratedTypeAnnotation('int').node);
   }
 
   test_methodDeclaration_resets_unconditional_control_flow() async {
@@ -757,7 +765,7 @@
   return 'x';
 }
 ''');
-    assertNoConstraints(decoratedTypeAnnotation('String').node.nullable);
+    assertNoUpstreamNullability(decoratedTypeAnnotation('String').node);
   }
 
   test_thisExpression() async {
@@ -767,7 +775,7 @@
 }
 ''');
 
-    assertNoConstraints(decoratedTypeAnnotation('C f').node.nullable);
+    assertNoUpstreamNullability(decoratedTypeAnnotation('C f').node);
   }
 
   test_throwExpression() async {
@@ -776,7 +784,7 @@
   return throw null;
 }
 ''');
-    assertNoConstraints(decoratedTypeAnnotation('int').node.nullable);
+    assertNoUpstreamNullability(decoratedTypeAnnotation('int').node);
   }
 
   test_typeName() async {
@@ -785,7 +793,7 @@
   return int;
 }
 ''');
-    assertNoConstraints(decoratedTypeAnnotation('Type').node.nullable);
+    assertNoUpstreamNullability(decoratedTypeAnnotation('Type').node);
   }
 }