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('''