Only create hard edges for local variable/parameter references.

Exception: also create hard edges for the relation between a type
argument and the corresponding type parameter bound, because that
can't be fixed by a null check.
Change-Id: I71b21ddb11c1bb1060cb6f13efde13855770e2b5
Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/104485
Reviewed-by: Brian Wilkerson <brianwilkerson@google.com>
Reviewed-by: Konstantin Shcheglov <scheglov@google.com>
diff --git a/pkg/analysis_server/lib/src/nullability/graph_builder.dart b/pkg/analysis_server/lib/src/nullability/graph_builder.dart
index 4e48af8..10d34a6 100644
--- a/pkg/analysis_server/lib/src/nullability/graph_builder.dart
+++ b/pkg/analysis_server/lib/src/nullability/graph_builder.dart
@@ -413,10 +413,9 @@
         } else {
           throw new UnimplementedError('TODO(paulberry)');
         }
-        _checkAssignment(
-            bound,
-            _variables.decoratedTypeAnnotation(_source, typeArguments[i]),
-            null);
+        _checkAssignment(bound,
+            _variables.decoratedTypeAnnotation(_source, typeArguments[i]), null,
+            hard: true);
       }
     }
     return DecoratedType(typeName.type, NullabilityNode.never);
@@ -427,7 +426,8 @@
   /// [sourceType]; it is the expression we will have to null-check in the case
   /// where a nullable source is assigned to a non-nullable destination.
   void _checkAssignment(DecoratedType destinationType, DecoratedType sourceType,
-      Expression expression) {
+      Expression expression,
+      {bool hard}) {
     if (expression != null) {
       _variables.recordExpressionChecks(
           _source,
@@ -437,7 +437,9 @@
     }
     NullabilityNode.recordAssignment(
         sourceType.node, destinationType.node, _guards, _graph,
-        hard: !_inConditionalControlFlow);
+        hard: hard ??
+            (_isVariableOrParameterReference(expression) &&
+                !_inConditionalControlFlow));
     // TODO(paulberry): it's a cheat to pass in expression=null for the
     // recursive checks.  Really we want to unify all the checks in a single
     // ExpressionChecks object.
@@ -497,6 +499,15 @@
     if ((type.type as InterfaceType).typeParameters.isNotEmpty) return false;
     return true;
   }
+
+  bool _isVariableOrParameterReference(Expression expression) {
+    if (expression is SimpleIdentifier) {
+      var element = expression.staticElement;
+      if (element is LocalVariableElement) return true;
+      if (element is ParameterElement) return true;
+    }
+    return false;
+  }
 }
 
 /// Information about a binary expression whose boolean value could possibly
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 d40cb56..055677ac 100644
--- a/pkg/analysis_server/test/src/nullability/migration_visitor_test.dart
+++ b/pkg/analysis_server/test/src/nullability/migration_visitor_test.dart
@@ -296,7 +296,7 @@
 ''');
 
     assertEdge(NullabilityNode.always, decoratedTypeAnnotation('int').node,
-        hard: true);
+        hard: false);
   }
 
   test_functionDeclaration_parameter_named_no_default() async {
@@ -332,7 +332,7 @@
 ''');
 
     assertEdge(NullabilityNode.always, decoratedTypeAnnotation('int').node,
-        hard: true);
+        hard: false);
   }
 
   test_functionDeclaration_parameter_positionalOptional_no_default() async {
@@ -600,7 +600,7 @@
 
     assertEdge(decoratedTypeAnnotation('int/*3*/').node,
         decoratedTypeAnnotation('int/*1*/').node,
-        hard: true);
+        hard: false);
     assertNullCheck(checkExpression('c/*check*/'),
         decoratedTypeAnnotation('C<int/*3*/>/*4*/').node,
         contextNode: decoratedTypeAnnotation('C<int/*1*/>/*2*/').node);
@@ -674,7 +674,7 @@
 ''');
 
     assertEdge(NullabilityNode.always, decoratedTypeAnnotation('int').node,
-        hard: true);
+        hard: false);
   }
 
   test_return_null() async {
@@ -688,6 +688,15 @@
         contextNode: decoratedTypeAnnotation('int').node);
   }
 
+  test_soft_edge_for_non_variable_reference() async {
+    // Edges originating in things other than variable references should be
+    // soft.
+    await analyze('''
+int f() => null;
+''');
+    assertEdge(always, decoratedTypeAnnotation('int').node, hard: false);
+  }
+
   test_stringLiteral() async {
     // TODO(paulberry): also test string interpolations
     await analyze('''