Add the ability to do a debug dump of a nullability graph.

Change-Id: Ia3536423349ded8810c06ef2d4a631607273af88
Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/102300
Reviewed-by: Brian Wilkerson <brianwilkerson@google.com>
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 51ae58c..ea6265c 100644
--- a/pkg/analysis_server/lib/src/nullability/constraint_variable_gatherer.dart
+++ b/pkg/analysis_server/lib/src/nullability/constraint_variable_gatherer.dart
@@ -48,13 +48,16 @@
 
   /// Creates and stores a [DecoratedType] object corresponding to the given
   /// [type] AST, and returns it.
-  DecoratedType decorateType(TypeAnnotation type) {
+  DecoratedType decorateType(TypeAnnotation type, AstNode enclosingNode) {
     return type == null
         // TODO(danrubel): Return something other than this
         // to indicate that we should insert a type for the declaration
         // that is missing a type reference.
-        ? new DecoratedType(DynamicTypeImpl.instance,
-            NullabilityNode.forInferredDynamicType(_graph), _graph)
+        ? new DecoratedType(
+            DynamicTypeImpl.instance,
+            NullabilityNode.forInferredDynamicType(
+                _graph, enclosingNode.offset),
+            _graph)
         : type.accept(this);
   }
 
@@ -84,14 +87,14 @@
   @override
   DecoratedType visitFunctionDeclaration(FunctionDeclaration node) {
     _handleExecutableDeclaration(node.declaredElement, node.returnType,
-        node.functionExpression.parameters);
+        node.functionExpression.parameters, node);
     return null;
   }
 
   @override
   DecoratedType visitMethodDeclaration(MethodDeclaration node) {
     _handleExecutableDeclaration(
-        node.declaredElement, node.returnType, node.parameters);
+        node.declaredElement, node.returnType, node.parameters, node);
     return null;
   }
 
@@ -110,7 +113,7 @@
 
   @override
   DecoratedType visitSimpleFormalParameter(SimpleFormalParameter node) {
-    var type = decorateType(node.type);
+    var type = decorateType(node.type, node);
     var declaredElement = node.declaredElement;
     type.node.trackNonNullIntent(node.offset);
     _variables.recordDecoratedElementType(declaredElement, type);
@@ -158,9 +161,12 @@
   DecoratedType visitTypeName(TypeName node) => visitTypeAnnotation(node);
 
   /// Common handling of function and method declarations.
-  void _handleExecutableDeclaration(ExecutableElement declaredElement,
-      TypeAnnotation returnType, FormalParameterList parameters) {
-    var decoratedReturnType = decorateType(returnType);
+  void _handleExecutableDeclaration(
+      ExecutableElement declaredElement,
+      TypeAnnotation returnType,
+      FormalParameterList parameters,
+      AstNode enclosingNode) {
+    var decoratedReturnType = decorateType(returnType, enclosingNode);
     var previousFunctionType = _currentFunctionType;
     // TODO(paulberry): test that it's correct to use `null` for the nullability
     // of the function type
diff --git a/pkg/analysis_server/lib/src/nullability/nullability_graph.dart b/pkg/analysis_server/lib/src/nullability/nullability_graph.dart
index bcc2300..311db8a 100644
--- a/pkg/analysis_server/lib/src/nullability/nullability_graph.dart
+++ b/pkg/analysis_server/lib/src/nullability/nullability_graph.dart
@@ -24,6 +24,12 @@
     (_upstream[destinationNode] ??= []).add(sourceNode);
   }
 
+  void debugDump() {
+    for (var entry in _downstream.entries) {
+      print('${entry.key} -> ${entry.value.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).
diff --git a/pkg/analysis_server/lib/src/nullability/nullability_node.dart b/pkg/analysis_server/lib/src/nullability/nullability_node.dart
index 453a8cc..cc5ee45 100644
--- a/pkg/analysis_server/lib/src/nullability/nullability_node.dart
+++ b/pkg/analysis_server/lib/src/nullability/nullability_node.dart
@@ -15,14 +15,17 @@
 /// nullability inference graph is encoded into the wrapped constraint
 /// variables.  Over time this will be replaced by a first class representation
 /// of the nullability inference graph.
-class NullabilityNode {
+abstract class NullabilityNode {
   /// [NullabilityNode] used for types that are known a priori to be nullable
   /// (e.g. the type of the `null` literal).
-  static final always = NullabilityNode._(ConstraintVariable.always);
+  static final NullabilityNode always =
+      _NullabilityNodeSimple(ConstraintVariable.always, 'always');
 
   /// [NullabilityNode] used for types that are known a priori to be
   /// non-nullable (e.g. the type of an integer literal).
-  static final never = NullabilityNode._(null);
+  static final NullabilityNode never = _NullabilityNodeSimple(null, 'never');
+
+  static final _debugNamesInUse = Set<String>();
 
   /// [ConstraintVariable] whose value will be set to `true` if this type needs
   /// to be nullable.
@@ -35,13 +38,17 @@
 
   bool _isPossiblyOptional = false;
 
+  String _debugName;
+
   /// Creates a [NullabilityNode] representing the nullability of a variable
   /// whose type is `dynamic` due to type inference.
   ///
   /// TODO(paulberry): this should go away; we should decorate the actual
   /// inferred type rather than assuming `dynamic`.
-  factory NullabilityNode.forInferredDynamicType(NullabilityGraph graph) {
-    var node = NullabilityNode._(TypeIsNullable(null));
+  factory NullabilityNode.forInferredDynamicType(
+      NullabilityGraph graph, int offset) {
+    var node = _NullabilityNodeSimple(
+        TypeIsNullable(null), 'inferredDynamic($offset)');
     graph.connect(NullabilityNode.always, node);
     return node;
   }
@@ -80,8 +87,11 @@
 
   /// Creates a [NullabilityNode] representing the nullability of a type
   /// annotation appearing explicitly in the user's program.
-  NullabilityNode.forTypeAnnotation(int endOffset, {@required bool always})
-      : this._(always ? ConstraintVariable.always : TypeIsNullable(endOffset));
+  factory NullabilityNode.forTypeAnnotation(int endOffset,
+          {@required bool always}) =>
+      _NullabilityNodeSimple(
+          always ? ConstraintVariable.always : TypeIsNullable(endOffset),
+          'type($endOffset)');
 
   NullabilityNode._(this._nullable);
 
@@ -103,6 +113,8 @@
   /// an exception being thrown in the case of a `null` value at runtime).
   ConstraintVariable get nonNullIntent => _nonNullIntent;
 
+  String get _debugPrefix;
+
   /// Records the fact that an invocation was made to a function with named
   /// parameters, and the named parameter associated with this node was not
   /// supplied.
@@ -118,6 +130,24 @@
     _recordConstraints(constraints, guards, const [], nonNullIntent);
   }
 
+  String toString() {
+    if (_debugName == null) {
+      var prefix = _debugPrefix;
+      if (_debugNamesInUse.add(prefix)) {
+        _debugName = prefix;
+      } else {
+        for (int i = 0;; i++) {
+          var name = '${prefix}_$i';
+          if (_debugNamesInUse.add(name)) {
+            _debugName = name;
+            break;
+          }
+        }
+      }
+    }
+    return _debugName;
+  }
+
   /// Tracks that the possibility that this nullability node might demonstrate
   /// non-null intent, based on the fact that it corresponds to a formal
   /// parameter declaration at location [offset].
@@ -223,6 +253,9 @@
     graph.connect(left, this);
     graph.connect(right, this);
   }
+
+  @override
+  String get _debugPrefix => 'LUB($left, $right)';
 }
 
 /// Derived class for nullability nodes that arise from type variable
@@ -246,4 +279,15 @@
       Constraints constraints, this.innerNode, this.outerNode)
       : super._(ConstraintVariable.or(
             constraints, innerNode?._nullable, outerNode._nullable));
+
+  @override
+  String get _debugPrefix => 'Substituted($innerNode, $outerNode)';
+}
+
+class _NullabilityNodeSimple extends NullabilityNode {
+  @override
+  final String _debugPrefix;
+
+  _NullabilityNodeSimple(ConstraintVariable nullable, this._debugPrefix)
+      : super._(nullable);
 }