Stop using ConstraintVariable.always to represent the nullability of `dynamic`.

This is a necessary part of reworking nullability migration to stop
using constraint variables entirely.

Change-Id: If6c347c16882edfc2e8bdbeac761e09d8256c9bb
Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/101622
Reviewed-by: Brian Wilkerson <brianwilkerson@google.com>
Commit-Queue: Paul Berry <paulberry@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 c6d9e80..c30ef23 100644
--- a/pkg/analysis_server/lib/src/nullability/constraint_gatherer.dart
+++ b/pkg/analysis_server/lib/src/nullability/constraint_gatherer.dart
@@ -89,14 +89,14 @@
       this._source,
       this._permissive,
       this.assumptions)
-      : _notNullType =
-            DecoratedType(typeProvider.objectType, NullabilityNode.never),
+      : _notNullType = DecoratedType(
+            typeProvider.objectType, NullabilityNode.never, _graph),
         _nonNullableBoolType =
-            DecoratedType(typeProvider.boolType, NullabilityNode.never),
+            DecoratedType(typeProvider.boolType, NullabilityNode.never, _graph),
         _nonNullableTypeType =
-            DecoratedType(typeProvider.typeType, NullabilityNode.never),
-        _nullType =
-            DecoratedType(typeProvider.nullType, NullabilityNode.always);
+            DecoratedType(typeProvider.typeType, NullabilityNode.never, _graph),
+        _nullType = DecoratedType(
+            typeProvider.nullType, NullabilityNode.always, _graph);
 
   /// Gets the decorated type of [element] from [_variables], performing any
   /// necessary substitutions.
@@ -133,7 +133,7 @@
         throw element.runtimeType; // TODO(paulberry)
       }
       return decoratedBaseType.substitute(
-          _constraints, substitution, elementType);
+          _constraints, _graph, substitution, elementType);
     } else {
       return decoratedBaseType;
     }
@@ -196,7 +196,7 @@
 
   @override
   DecoratedType visitBooleanLiteral(BooleanLiteral node) {
-    return DecoratedType(node.staticType, NullabilityNode.never);
+    return DecoratedType(node.staticType, NullabilityNode.never, _graph);
   }
 
   @override
@@ -216,7 +216,8 @@
     var overallType = DecoratedType(
         node.staticType,
         NullabilityNode.forLUB(
-            node, thenType.node, elseType.node, _graph, _joinNullabilities));
+            node, thenType.node, elseType.node, _graph, _joinNullabilities),
+        _graph);
     _variables.recordDecoratedExpressionType(node, overallType);
     return overallType;
   }
@@ -311,7 +312,7 @@
 
   @override
   DecoratedType visitIntegerLiteral(IntegerLiteral node) {
-    return DecoratedType(node.staticType, NullabilityNode.never);
+    return DecoratedType(node.staticType, NullabilityNode.never, _graph);
   }
 
   @override
@@ -412,24 +413,24 @@
 
   @override
   DecoratedType visitStringLiteral(StringLiteral node) {
-    return DecoratedType(node.staticType, NullabilityNode.never);
+    return DecoratedType(node.staticType, NullabilityNode.never, _graph);
   }
 
   @override
   DecoratedType visitThisExpression(ThisExpression node) {
-    return DecoratedType(node.staticType, NullabilityNode.never);
+    return DecoratedType(node.staticType, NullabilityNode.never, _graph);
   }
 
   @override
   DecoratedType visitThrowExpression(ThrowExpression node) {
     node.expression.accept(this);
     // TODO(paulberry): do we need to check the expression type?  I think not.
-    return DecoratedType(node.staticType, NullabilityNode.never);
+    return DecoratedType(node.staticType, NullabilityNode.never, _graph);
   }
 
   @override
   DecoratedType visitTypeName(TypeName typeName) {
-    return DecoratedType(typeName.type, NullabilityNode.never);
+    return DecoratedType(typeName.type, NullabilityNode.never, _graph);
   }
 
   /// Creates the necessary constraint(s) for an assignment from [sourceType] to
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 adbae5e..51ae58c 100644
--- a/pkg/analysis_server/lib/src/nullability/constraint_variable_gatherer.dart
+++ b/pkg/analysis_server/lib/src/nullability/constraint_variable_gatherer.dart
@@ -5,6 +5,7 @@
 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';
@@ -40,8 +41,10 @@
 
   final NullabilityMigrationAssumptions assumptions;
 
-  ConstraintVariableGatherer(
-      this._variables, this._source, this._permissive, this.assumptions);
+  final NullabilityGraph _graph;
+
+  ConstraintVariableGatherer(this._variables, this._source, this._permissive,
+      this.assumptions, this._graph);
 
   /// Creates and stores a [DecoratedType] object corresponding to the given
   /// [type] AST, and returns it.
@@ -50,8 +53,8 @@
         // 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())
+        ? new DecoratedType(DynamicTypeImpl.instance,
+            NullabilityNode.forInferredDynamicType(_graph), _graph)
         : type.accept(this);
   }
 
@@ -125,8 +128,8 @@
     assert(node is NamedType); // TODO(paulberry)
     var type = node.type;
     if (type.isVoid) {
-      return DecoratedType(
-          type, NullabilityNode.forTypeAnnotation(node.end, always: true));
+      return DecoratedType(type,
+          NullabilityNode.forTypeAnnotation(node.end, always: true), _graph);
     }
     assert(
         type is InterfaceType || type is TypeParameterType); // TODO(paulberry)
@@ -145,6 +148,7 @@
         NullabilityNode.forTypeAnnotation(node.end,
             always: node.question != null),
         node.end,
+        _graph,
         typeArguments: typeArguments);
     _variables.recordDecoratedTypeAnnotation(_source, node, decoratedType);
     return decoratedType;
@@ -161,7 +165,7 @@
     // TODO(paulberry): test that it's correct to use `null` for the nullability
     // of the function type
     var functionType = DecoratedType(
-        declaredElement.type, NullabilityNode.never,
+        declaredElement.type, NullabilityNode.never, _graph,
         returnType: decoratedReturnType,
         positionalParameters: [],
         namedParameters: {});
diff --git a/pkg/analysis_server/lib/src/nullability/decorated_type.dart b/pkg/analysis_server/lib/src/nullability/decorated_type.dart
index a1ae73c..a926ba0 100644
--- a/pkg/analysis_server/lib/src/nullability/decorated_type.dart
+++ b/pkg/analysis_server/lib/src/nullability/decorated_type.dart
@@ -2,6 +2,7 @@
 // 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:analysis_server/src/nullability/unit_propagation.dart';
@@ -36,25 +37,27 @@
   /// TODO(paulberry): how should we handle generic typedefs?
   final List<DecoratedType> typeArguments;
 
-  DecoratedType(this.type, this.node,
+  DecoratedType(this.type, this.node, NullabilityGraph graph,
       {this.returnType,
       this.positionalParameters = const [],
       this.namedParameters = const {},
       this.typeArguments = const []}) {
     assert(node != null);
     // The type system doesn't have a non-nullable version of `dynamic`.  So if
-    // the type is `dynamic`, verify that `nullable` is `always`.
-    assert(!type.isDynamic || node.isAlwaysNullable);
+    // the type is `dynamic`, verify that the node is directly downstream from
+    // Nullability.always.
+    assert(!type.isDynamic ||
+        graph.getUpstreamNodes(node).contains(NullabilityNode.always));
   }
 
   /// Creates a [DecoratedType] corresponding to the given [element], which is
   /// presumed to have come from code that is already migrated.
-  factory DecoratedType.forElement(Element element) {
+  factory DecoratedType.forElement(Element element, NullabilityGraph graph) {
     DecoratedType decorate(DartType type) {
       assert((type as TypeImpl).nullabilitySuffix ==
           NullabilitySuffix.star); // TODO(paulberry)
       if (type is FunctionType) {
-        var decoratedType = DecoratedType(type, NullabilityNode.never,
+        var decoratedType = DecoratedType(type, NullabilityNode.never, graph,
             returnType: decorate(type.returnType), positionalParameters: []);
         for (var parameter in type.parameters) {
           assert(parameter.isPositional); // TODO(paulberry)
@@ -63,7 +66,7 @@
         return decoratedType;
       } else if (type is InterfaceType) {
         assert(type.typeParameters.isEmpty); // TODO(paulberry)
-        return DecoratedType(type, NullabilityNode.never);
+        return DecoratedType(type, NullabilityNode.never, graph);
       } else {
         throw type.runtimeType; // TODO(paulberry)
       }
@@ -84,10 +87,11 @@
   /// the normal type system.
   DecoratedType substitute(
       Constraints constraints,
+      NullabilityGraph graph,
       Map<TypeParameterElement, DecoratedType> substitution,
       DartType undecoratedResult) {
     if (substitution.isEmpty) return this;
-    return _substitute(constraints, substitution, undecoratedResult);
+    return _substitute(constraints, graph, substitution, undecoratedResult);
   }
 
   @override
@@ -119,6 +123,7 @@
   /// Internal implementation of [_substitute], used as a recursion target.
   DecoratedType _substitute(
       Constraints constraints,
+      NullabilityGraph graph,
       Map<TypeParameterElement, DecoratedType> substitution,
       DartType undecoratedResult) {
     var type = this.type;
@@ -132,17 +137,19 @@
             ? undecoratedResult.normalParameterTypes[i]
             : undecoratedResult
                 .optionalParameterTypes[i - numRequiredParameters];
-        newPositionalParameters.add(positionalParameters[i]
-            ._substitute(constraints, substitution, undecoratedParameterType));
+        newPositionalParameters.add(positionalParameters[i]._substitute(
+            constraints, graph, substitution, undecoratedParameterType));
       }
-      return DecoratedType(undecoratedResult, node,
+      return DecoratedType(undecoratedResult, node, graph,
           returnType: returnType._substitute(
-              constraints, substitution, undecoratedResult.returnType),
+              constraints, graph, substitution, undecoratedResult.returnType),
           positionalParameters: newPositionalParameters);
     } else if (type is TypeParameterType) {
       var inner = substitution[type.element];
-      return DecoratedType(undecoratedResult,
-          NullabilityNode.forSubstitution(constraints, inner?.node, node));
+      return DecoratedType(
+          undecoratedResult,
+          NullabilityNode.forSubstitution(constraints, inner?.node, node),
+          graph);
     } else if (type is VoidType) {
       return this;
     }
@@ -159,10 +166,10 @@
     implements PotentialModification {
   final int _offset;
 
-  DecoratedTypeAnnotation(
-      DartType type, NullabilityNode nullabilityNode, this._offset,
+  DecoratedTypeAnnotation(DartType type, NullabilityNode nullabilityNode,
+      this._offset, NullabilityGraph graph,
       {List<DecoratedType> typeArguments = const []})
-      : super(type, nullabilityNode, typeArguments: typeArguments);
+      : super(type, nullabilityNode, graph, typeArguments: typeArguments);
 
   @override
   bool get isEmpty => !node.isNullable;
diff --git a/pkg/analysis_server/lib/src/nullability/nullability_node.dart b/pkg/analysis_server/lib/src/nullability/nullability_node.dart
index e871c28..03e1b43 100644
--- a/pkg/analysis_server/lib/src/nullability/nullability_node.dart
+++ b/pkg/analysis_server/lib/src/nullability/nullability_node.dart
@@ -40,7 +40,11 @@
   ///
   /// TODO(paulberry): this should go away; we should decorate the actual
   /// inferred type rather than assuming `dynamic`.
-  NullabilityNode.forInferredDynamicType() : this._(ConstraintVariable.always);
+  factory NullabilityNode.forInferredDynamicType(NullabilityGraph graph) {
+    var node = NullabilityNode._(TypeIsNullable(null));
+    graph.connect(NullabilityNode.always, node);
+    return node;
+  }
 
   /// Creates a [NullabilityNode] representing the nullability of an
   /// expression which is nullable iff both [a] and [b] are nullable.
diff --git a/pkg/analysis_server/lib/src/nullability/transitional_api.dart b/pkg/analysis_server/lib/src/nullability/transitional_api.dart
index ac1d8df..ffbad45 100644
--- a/pkg/analysis_server/lib/src/nullability/transitional_api.dart
+++ b/pkg/analysis_server/lib/src/nullability/transitional_api.dart
@@ -146,11 +146,11 @@
 
   final NullabilityMigrationAssumptions assumptions;
 
-  final _variables = Variables();
+  final Variables _variables;
 
   final _constraints = Solver();
 
-  final _graph = NullabilityGraph();
+  final NullabilityGraph _graph;
 
   /// Prepares to perform nullability migration.
   ///
@@ -160,8 +160,12 @@
   /// is fully implemented.
   NullabilityMigration(
       {bool permissive: false,
-      this.assumptions: const NullabilityMigrationAssumptions()})
-      : _permissive = permissive;
+      NullabilityMigrationAssumptions assumptions:
+          const NullabilityMigrationAssumptions()})
+      : this._(permissive, assumptions, NullabilityGraph());
+
+  NullabilityMigration._(this._permissive, this.assumptions, this._graph)
+      : _variables = Variables(_graph);
 
   Map<Source, List<PotentialModification>> finish() {
     _constraints.applyHeuristics();
@@ -169,8 +173,8 @@
   }
 
   void prepareInput(CompilationUnit unit) {
-    unit.accept(ConstraintVariableGatherer(
-        _variables, unit.declaredElement.source, _permissive, assumptions));
+    unit.accept(ConstraintVariableGatherer(_variables,
+        unit.declaredElement.source, _permissive, assumptions, _graph));
   }
 
   void processInput(CompilationUnit unit, TypeProvider typeProvider) {
@@ -269,10 +273,14 @@
 
   final _potentialModifications = <Source, List<PotentialModification>>{};
 
+  final NullabilityGraph _graph;
+
+  Variables(this._graph);
+
   @override
   DecoratedType decoratedElementType(Element element, {bool create: false}) =>
       _decoratedElementTypes[element] ??= create
-          ? DecoratedType.forElement(element)
+          ? DecoratedType.forElement(element, _graph)
           : throw StateError('No element found');
 
   Map<Source, List<PotentialModification>> getPotentialModifications() =>
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 764f29e..160a53a 100644
--- a/pkg/analysis_server/test/src/nullability/migration_visitor_test.dart
+++ b/pkg/analysis_server/test/src/nullability/migration_visitor_test.dart
@@ -32,9 +32,6 @@
   @override
   final _Constraints constraints = _Constraints();
 
-  @override
-  final graph = NullabilityGraph();
-
   void assertConditional(
       NullabilityNode node, NullabilityNode left, NullabilityNode right) {
     var conditionalNode = node as NullabilityNodeForLUB;
@@ -806,8 +803,6 @@
 abstract class ConstraintsTestBase extends MigrationVisitorTestBase {
   Constraints get constraints;
 
-  NullabilityGraph get graph;
-
   /// Analyzes the given source code, producing constraint variables and
   /// constraints for it.
   @override
@@ -851,7 +846,8 @@
     expect(decoratedFunctionType('f').positionalParameters[0],
         same(decoratedType));
     expect(decoratedType.type.isDynamic, isTrue);
-    expect(decoratedType.node.nullable, same(ConstraintVariable.always));
+    expect(graph.getUpstreamNodes(decoratedType.node),
+        contains(NullabilityNode.always));
   }
 
   test_topLevelFunction_parameterType_named_no_default() async {
@@ -918,7 +914,8 @@
 ''');
     var decoratedType = decoratedFunctionType('f').returnType;
     expect(decoratedType.type.isDynamic, isTrue);
-    expect(decoratedType.node.nullable, same(ConstraintVariable.always));
+    expect(graph.getUpstreamNodes(decoratedType.node),
+        contains(NullabilityNode.always));
   }
 
   test_topLevelFunction_returnType_simple() async {
@@ -932,18 +929,24 @@
 }
 
 class MigrationVisitorTestBase extends AbstractSingleUnitTest {
-  final _variables = _Variables();
+  final _Variables _variables;
 
   FindNode findNode;
 
+  final NullabilityGraph graph;
+
+  MigrationVisitorTestBase() : this._(NullabilityGraph());
+
+  MigrationVisitorTestBase._(this.graph) : _variables = _Variables(graph);
+
   TypeProvider get typeProvider => testAnalysisResult.typeProvider;
 
   Future<CompilationUnit> analyze(String code,
       {NullabilityMigrationAssumptions assumptions:
           const NullabilityMigrationAssumptions()}) async {
     await resolveTestUnit(code);
-    testUnit.accept(
-        ConstraintVariableGatherer(_variables, testSource, false, assumptions));
+    testUnit.accept(ConstraintVariableGatherer(
+        _variables, testSource, false, assumptions, graph));
     findNode = FindNode(code, testUnit);
     return testUnit;
   }
@@ -1024,6 +1027,8 @@
 
   final _possiblyOptional = <DefaultFormalParameter, NullabilityNode>{};
 
+  _Variables(NullabilityGraph graph) : super(graph);
+
   /// Gets the [ExpressionChecks] associated with the given [expression].
   ExpressionChecks checkExpression(Expression expression) =>
       _expressionChecks[_normalizeExpression(expression)];