Move NullabilityGraph into nullability_node.dart.

This will allow NullabilityGraph and NullabilityNode to have shared
access to private data without having to expose it to all other
libraries.

Change-Id: Idbef9a60cdeb273cd5739c45d501370d89b653df
Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/102403
Reviewed-by: Konstantin Shcheglov <scheglov@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 5a56ba8..e209eea 100644
--- a/pkg/analysis_server/lib/src/nullability/constraint_gatherer.dart
+++ b/pkg/analysis_server/lib/src/nullability/constraint_gatherer.dart
@@ -6,7 +6,6 @@
 import 'package:analysis_server/src/nullability/constraint_variable_gatherer.dart';
 import 'package:analysis_server/src/nullability/decorated_type.dart';
 import 'package:analysis_server/src/nullability/expression_checks.dart';
-import 'package:analysis_server/src/nullability/nullability_graph.dart';
 import 'package:analysis_server/src/nullability/nullability_node.dart';
 import 'package:analysis_server/src/nullability/transitional_api.dart';
 import 'package:analyzer/dart/ast/ast.dart';
diff --git a/pkg/analysis_server/lib/src/nullability/constraint_variable_gatherer.dart b/pkg/analysis_server/lib/src/nullability/constraint_variable_gatherer.dart
index 1db17fa..f30cd7d 100644
--- a/pkg/analysis_server/lib/src/nullability/constraint_variable_gatherer.dart
+++ b/pkg/analysis_server/lib/src/nullability/constraint_variable_gatherer.dart
@@ -5,7 +5,6 @@
 import 'package:analysis_server/src/nullability/conditional_discard.dart';
 import 'package:analysis_server/src/nullability/decorated_type.dart';
 import 'package:analysis_server/src/nullability/expression_checks.dart';
-import 'package:analysis_server/src/nullability/nullability_graph.dart';
 import 'package:analysis_server/src/nullability/nullability_node.dart';
 import 'package:analysis_server/src/nullability/transitional_api.dart';
 import 'package:analyzer/dart/ast/ast.dart';
diff --git a/pkg/analysis_server/lib/src/nullability/decorated_type.dart b/pkg/analysis_server/lib/src/nullability/decorated_type.dart
index 526b338..81abe99 100644
--- a/pkg/analysis_server/lib/src/nullability/decorated_type.dart
+++ b/pkg/analysis_server/lib/src/nullability/decorated_type.dart
@@ -2,7 +2,6 @@
 // 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:analysis_server/src/nullability/nullability_graph.dart';
 import 'package:analysis_server/src/nullability/nullability_node.dart';
 import 'package:analysis_server/src/nullability/transitional_api.dart';
 import 'package:analyzer/dart/element/element.dart';
diff --git a/pkg/analysis_server/lib/src/nullability/nullability_graph.dart b/pkg/analysis_server/lib/src/nullability/nullability_graph.dart
deleted file mode 100644
index 3c9057e..0000000
--- a/pkg/analysis_server/lib/src/nullability/nullability_graph.dart
+++ /dev/null
@@ -1,191 +0,0 @@
-// 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:analysis_server/src/nullability/nullability_node.dart';
-import 'package:meta/meta.dart';
-
-/// Data structure to keep track of the relationship between [NullabilityNode]
-/// objects.
-class NullabilityGraph {
-  /// Map from a nullability node to a list of [_NullabilityEdge] objects
-  /// describing the node's relationship to other nodes that are "downstream"
-  /// from it (meaning that if a key node is nullable, then all the nodes in the
-  /// corresponding value will either have to be nullable, or null checks will
-  /// have to be added).
-  final _downstream = Map<NullabilityNode, List<_NullabilityEdge>>.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();
-
-  final _nonNullIntentNodes = Set<NullabilityNode>.identity();
-
-  /// Records that [sourceNode] is immediately upstream from [destinationNode].
-  void connect(NullabilityNode sourceNode, NullabilityNode destinationNode,
-      {bool unconditional: false, List<NullabilityNode> guards: const []}) {
-    var sources = [sourceNode]..addAll(guards);
-    var edge = _NullabilityEdge(destinationNode, sources);
-    for (var source in sources) {
-      (_downstream[source] ??= []).add(edge);
-    }
-    if (unconditional) {
-      (_unconditionalUpstream[destinationNode] ??= []).add(sourceNode);
-    }
-  }
-
-  void debugDump() {
-    for (var entry in _downstream.entries) {
-      var destinations = entry.value
-          .where((edge) => edge.primarySource == entry.key)
-          .map((edge) {
-        var suffixes = <Object>[];
-        if (getUnconditionalUpstreamNodes(edge.destinationNode)
-            .contains(entry.key)) {
-          suffixes.add('unconditional');
-        }
-        suffixes.addAll(edge.guards);
-        var suffix = suffixes.isNotEmpty ? ' (${suffixes.join(', ')})' : '';
-        return '${edge.destinationNode}$suffix';
-      });
-      var suffixes = <String>[];
-      if (entry.key.isNullable) {
-        suffixes.add('nullable');
-      }
-      if (_nonNullIntentNodes.contains(entry.key)) {
-        suffixes.add('non-null intent');
-      }
-      var suffix = suffixes.isNotEmpty ? ' (${suffixes.join(', ')})' : '';
-      print('${entry.key}$suffix -> ${destinations.join(', ')}');
-    }
-  }
-
-  /// Iterates through all nodes that are "downstream" of [node] (i.e. if
-  /// [node] is nullable, then all the iterated nodes 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> getDownstreamNodes(NullabilityNode node) =>
-      (_downstream[node] ?? const [])
-          .where((edge) => edge.primarySource == node)
-          .map((edge) => edge.destinationNode);
-
-  /// 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).
-  ///
-  /// There is no guarantee of uniqueness of the iterated nodes.
-  ///
-  /// This method is inefficent since it has to search the entire graph, so it
-  /// is for testing only.
-  @visibleForTesting
-  Iterable<NullabilityNode> getUpstreamNodesForTesting(
-      NullabilityNode node) sync* {
-    for (var entry in _downstream.entries) {
-      for (var edge in entry.value) {
-        if (edge.destinationNode == node) {
-          yield entry.key;
-        }
-      }
-    }
-  }
-
-  /// Determines the nullability of each node in the graph by propagating
-  /// nullability information from one node to another.
-  void propagate() {
-    _propagateUpstream();
-    _propagateDownstream();
-  }
-
-  /// Propagates nullability downstream.
-  void _propagateDownstream() {
-    var pendingEdges = <_NullabilityEdge>[]
-      ..addAll(_downstream[NullabilityNode.always] ?? const []);
-    var pendingSubstitutions = <NullabilityNodeForSubstitution>[];
-    while (true) {
-      nextEdge:
-      while (pendingEdges.isNotEmpty) {
-        var edge = pendingEdges.removeLast();
-        var node = edge.destinationNode;
-        if (_nonNullIntentNodes.contains(node)) {
-          // Non-null intent nodes are never made nullable; a null check will need
-          // to be added instead.
-          continue;
-        }
-        for (var source in edge.sources) {
-          if (!source.isNullable) {
-            // Note all sources are nullable, so this edge doesn't apply yet.
-            continue nextEdge;
-          }
-        }
-        if (node is NullabilityNodeMutable && node.becomeNullable()) {
-          // Was not previously nullable, so we need to propagate.
-          pendingEdges.addAll(_downstream[node] ?? const []);
-          if (node is NullabilityNodeForSubstitution) {
-            pendingSubstitutions.add(node);
-          }
-        }
-      }
-      if (pendingSubstitutions.isEmpty) break;
-      var node = pendingSubstitutions.removeLast();
-      if (node.innerNode.isNullable || node.outerNode.isNullable) {
-        // No further propagation is needed, since some other connection already
-        // propagated nullability to either the inner or outer node.
-        continue;
-      }
-      // Heuristically choose to propagate to the inner node since this seems
-      // to lead to better quality migrations.
-      pendingEdges.add(_NullabilityEdge(node.innerNode, const []));
-    }
-  }
-
-  /// Propagates non-null intent upstream along unconditional control flow
-  /// lines.
-  void _propagateUpstream() {
-    var pendingNodes = [NullabilityNode.never];
-    while (pendingNodes.isNotEmpty) {
-      var node = pendingNodes.removeLast();
-      if (node == NullabilityNode.always) {
-        // The "always" node cannot have non-null intent.
-        continue;
-      }
-      if (_nonNullIntentNodes.add(node)) {
-        // Was not previously in the set of non-null intent nodes, so we need to
-        // propagate.
-        pendingNodes.addAll(getUnconditionalUpstreamNodes(node));
-      }
-    }
-  }
-}
-
-/// Data structure to keep track of the relationship from one [NullabilityNode]
-/// object to another [NullabilityNode] that is "downstream" from it (meaning
-/// that if the former node is nullable, then the latter node will either have
-/// to be nullable, or null checks will have to be added).
-class _NullabilityEdge {
-  /// The node that is downstream.
-  final NullabilityNode destinationNode;
-
-  /// A set of source nodes.  By convention, the first node is the primary
-  /// source and the other nodes are "guards".  The destination node will only
-  /// need to be made nullable if all the source nodes are nullable.
-  final List<NullabilityNode> sources;
-
-  _NullabilityEdge(this.destinationNode, this.sources);
-
-  Iterable<NullabilityNode> get guards => sources.skip(1);
-
-  NullabilityNode get primarySource => sources.first;
-}
diff --git a/pkg/analysis_server/lib/src/nullability/nullability_node.dart b/pkg/analysis_server/lib/src/nullability/nullability_node.dart
index ae8b92e..39b579a 100644
--- a/pkg/analysis_server/lib/src/nullability/nullability_node.dart
+++ b/pkg/analysis_server/lib/src/nullability/nullability_node.dart
@@ -2,10 +2,174 @@
 // 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:analysis_server/src/nullability/nullability_graph.dart';
 import 'package:analyzer/dart/ast/ast.dart';
 import 'package:meta/meta.dart';
 
+/// Data structure to keep track of the relationship between [NullabilityNode]
+/// objects.
+class NullabilityGraph {
+  /// Map from a nullability node to a list of [_NullabilityEdge] objects
+  /// describing the node's relationship to other nodes that are "downstream"
+  /// from it (meaning that if a key node is nullable, then all the nodes in the
+  /// corresponding value will either have to be nullable, or null checks will
+  /// have to be added).
+  final _downstream = Map<NullabilityNode, List<_NullabilityEdge>>.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();
+
+  final _nonNullIntentNodes = Set<NullabilityNode>.identity();
+
+  /// Records that [sourceNode] is immediately upstream from [destinationNode].
+  void connect(NullabilityNode sourceNode, NullabilityNode destinationNode,
+      {bool unconditional: false, List<NullabilityNode> guards: const []}) {
+    var sources = [sourceNode]..addAll(guards);
+    var edge = _NullabilityEdge(destinationNode, sources);
+    for (var source in sources) {
+      (_downstream[source] ??= []).add(edge);
+    }
+    if (unconditional) {
+      (_unconditionalUpstream[destinationNode] ??= []).add(sourceNode);
+    }
+  }
+
+  void debugDump() {
+    for (var entry in _downstream.entries) {
+      var destinations = entry.value
+          .where((edge) => edge.primarySource == entry.key)
+          .map((edge) {
+        var suffixes = <Object>[];
+        if (getUnconditionalUpstreamNodes(edge.destinationNode)
+            .contains(entry.key)) {
+          suffixes.add('unconditional');
+        }
+        suffixes.addAll(edge.guards);
+        var suffix = suffixes.isNotEmpty ? ' (${suffixes.join(', ')})' : '';
+        return '${edge.destinationNode}$suffix';
+      });
+      var suffixes = <String>[];
+      if (entry.key.isNullable) {
+        suffixes.add('nullable');
+      }
+      if (_nonNullIntentNodes.contains(entry.key)) {
+        suffixes.add('non-null intent');
+      }
+      var suffix = suffixes.isNotEmpty ? ' (${suffixes.join(', ')})' : '';
+      print('${entry.key}$suffix -> ${destinations.join(', ')}');
+    }
+  }
+
+  /// Iterates through all nodes that are "downstream" of [node] (i.e. if
+  /// [node] is nullable, then all the iterated nodes 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> getDownstreamNodes(NullabilityNode node) =>
+      (_downstream[node] ?? const [])
+          .where((edge) => edge.primarySource == node)
+          .map((edge) => edge.destinationNode);
+
+  /// 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).
+  ///
+  /// There is no guarantee of uniqueness of the iterated nodes.
+  ///
+  /// This method is inefficent since it has to search the entire graph, so it
+  /// is for testing only.
+  @visibleForTesting
+  Iterable<NullabilityNode> getUpstreamNodesForTesting(
+      NullabilityNode node) sync* {
+    for (var entry in _downstream.entries) {
+      for (var edge in entry.value) {
+        if (edge.destinationNode == node) {
+          yield entry.key;
+        }
+      }
+    }
+  }
+
+  /// Determines the nullability of each node in the graph by propagating
+  /// nullability information from one node to another.
+  void propagate() {
+    _propagateUpstream();
+    _propagateDownstream();
+  }
+
+  /// Propagates nullability downstream.
+  void _propagateDownstream() {
+    var pendingEdges = <_NullabilityEdge>[]
+      ..addAll(_downstream[NullabilityNode.always] ?? const []);
+    var pendingSubstitutions = <NullabilityNodeForSubstitution>[];
+    while (true) {
+      nextEdge:
+      while (pendingEdges.isNotEmpty) {
+        var edge = pendingEdges.removeLast();
+        var node = edge.destinationNode;
+        if (_nonNullIntentNodes.contains(node)) {
+          // Non-null intent nodes are never made nullable; a null check will need
+          // to be added instead.
+          continue;
+        }
+        for (var source in edge.sources) {
+          if (!source.isNullable) {
+            // Note all sources are nullable, so this edge doesn't apply yet.
+            continue nextEdge;
+          }
+        }
+        if (node is NullabilityNodeMutable && node.becomeNullable()) {
+          // Was not previously nullable, so we need to propagate.
+          pendingEdges.addAll(_downstream[node] ?? const []);
+          if (node is NullabilityNodeForSubstitution) {
+            pendingSubstitutions.add(node);
+          }
+        }
+      }
+      if (pendingSubstitutions.isEmpty) break;
+      var node = pendingSubstitutions.removeLast();
+      if (node.innerNode.isNullable || node.outerNode.isNullable) {
+        // No further propagation is needed, since some other connection already
+        // propagated nullability to either the inner or outer node.
+        continue;
+      }
+      // Heuristically choose to propagate to the inner node since this seems
+      // to lead to better quality migrations.
+      pendingEdges.add(_NullabilityEdge(node.innerNode, const []));
+    }
+  }
+
+  /// Propagates non-null intent upstream along unconditional control flow
+  /// lines.
+  void _propagateUpstream() {
+    var pendingNodes = [NullabilityNode.never];
+    while (pendingNodes.isNotEmpty) {
+      var node = pendingNodes.removeLast();
+      if (node == NullabilityNode.always) {
+        // The "always" node cannot have non-null intent.
+        continue;
+      }
+      if (_nonNullIntentNodes.add(node)) {
+        // Was not previously in the set of non-null intent nodes, so we need to
+        // propagate.
+        pendingNodes.addAll(getUnconditionalUpstreamNodes(node));
+      }
+    }
+  }
+}
+
 /// Representation of a single node in the nullability inference graph.
 ///
 /// Initially, this is just a wrapper over constraint variables, and the
@@ -217,6 +381,26 @@
   }
 }
 
+/// Data structure to keep track of the relationship from one [NullabilityNode]
+/// object to another [NullabilityNode] that is "downstream" from it (meaning
+/// that if the former node is nullable, then the latter node will either have
+/// to be nullable, or null checks will have to be added).
+class _NullabilityEdge {
+  /// The node that is downstream.
+  final NullabilityNode destinationNode;
+
+  /// A set of source nodes.  By convention, the first node is the primary
+  /// source and the other nodes are "guards".  The destination node will only
+  /// need to be made nullable if all the source nodes are nullable.
+  final List<NullabilityNode> sources;
+
+  _NullabilityEdge(this.destinationNode, this.sources);
+
+  Iterable<NullabilityNode> get guards => sources.skip(1);
+
+  NullabilityNode get primarySource => sources.first;
+}
+
 class _NullabilityNodeImmutable extends NullabilityNode {
   @override
   final String _debugPrefix;
diff --git a/pkg/analysis_server/lib/src/nullability/transitional_api.dart b/pkg/analysis_server/lib/src/nullability/transitional_api.dart
index 261a03d..d05c141 100644
--- a/pkg/analysis_server/lib/src/nullability/transitional_api.dart
+++ b/pkg/analysis_server/lib/src/nullability/transitional_api.dart
@@ -7,7 +7,6 @@
 import 'package:analysis_server/src/nullability/constraint_variable_gatherer.dart';
 import 'package:analysis_server/src/nullability/decorated_type.dart';
 import 'package:analysis_server/src/nullability/expression_checks.dart';
-import 'package:analysis_server/src/nullability/nullability_graph.dart';
 import 'package:analysis_server/src/nullability/nullability_node.dart';
 import 'package:analyzer/dart/ast/ast.dart';
 import 'package:analyzer/dart/element/element.dart';
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 d761176..b08b808 100644
--- a/pkg/analysis_server/test/src/nullability/migration_visitor_test.dart
+++ b/pkg/analysis_server/test/src/nullability/migration_visitor_test.dart
@@ -7,7 +7,6 @@
 import 'package:analysis_server/src/nullability/constraint_variable_gatherer.dart';
 import 'package:analysis_server/src/nullability/decorated_type.dart';
 import 'package:analysis_server/src/nullability/expression_checks.dart';
-import 'package:analysis_server/src/nullability/nullability_graph.dart';
 import 'package:analysis_server/src/nullability/nullability_node.dart';
 import 'package:analysis_server/src/nullability/transitional_api.dart';
 import 'package:analyzer/dart/ast/ast.dart';