A value from Angular's 'injector.get' in setUp signals non-null intent.
For b/254330485
Change-Id: I5a3993753dfe559426ce0517c1527eba27221759
Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/265120
Reviewed-by: Paul Berry <paulberry@google.com>
Commit-Queue: Samuel Rawlins <srawlins@google.com>
diff --git a/pkg/nnbd_migration/lib/instrumentation.dart b/pkg/nnbd_migration/lib/instrumentation.dart
index 270028e..6734bd8 100644
--- a/pkg/nnbd_migration/lib/instrumentation.dart
+++ b/pkg/nnbd_migration/lib/instrumentation.dart
@@ -252,6 +252,7 @@
alwaysNullableType,
angularAnnotation,
argumentErrorCheckNotNull,
+ assignmentFromAngularInjectorGet,
builtValueNullableAnnotation,
callTearOff,
compoundAssignment,
diff --git a/pkg/nnbd_migration/lib/src/edge_builder.dart b/pkg/nnbd_migration/lib/src/edge_builder.dart
index 6988917..3d1f378 100644
--- a/pkg/nnbd_migration/lib/src/edge_builder.dart
+++ b/pkg/nnbd_migration/lib/src/edge_builder.dart
@@ -489,11 +489,14 @@
}
}
+ var isInjectorGetAssignment = _isInjectorGetCall(node.rightHandSide);
+
var expressionType = _handleAssignment(node.rightHandSide,
assignmentExpression: node,
compoundOperatorInfo: isCompound ? node : null,
questionAssignNode: isQuestionAssign ? node : null,
- sourceIsSetupCall: sourceIsSetupCall);
+ sourceIsSetupCall: sourceIsSetupCall,
+ isInjectorGetAssignment: isInjectorGetAssignment);
var conditionalNode = _conditionalNodes[node.leftHandSide];
if (conditionalNode != null) {
expressionType = expressionType!.withNode(
@@ -2461,14 +2464,15 @@
/// proper time.
///
/// Set [wrapFuture] to true to handle assigning Future<flatten(T)> to R.
- DecoratedType? _handleAssignment(Expression? expression,
+ DecoratedType? _handleAssignment(Expression expression,
{DecoratedType? destinationType,
AssignmentExpression? assignmentExpression,
AssignmentExpression? compoundOperatorInfo,
AssignmentExpression? questionAssignNode,
bool fromDefaultValue = false,
bool wrapFuture = false,
- bool sourceIsSetupCall = false}) {
+ bool sourceIsSetupCall = false,
+ bool isInjectorGetAssignment = false}) {
assert(
(assignmentExpression == null) != (destinationType == null),
'Either assignmentExpression or destinationType should be supplied, '
@@ -2490,6 +2494,14 @@
destinationType = _dispatch(destinationExpression);
}
}
+ if (sourceIsSetupCall &&
+ isInjectorGetAssignment &&
+ destinationType != null) {
+ _graph.makeNonNullable(
+ destinationType.node,
+ AssignmentFromAngularInjectorGetOrigin(
+ source, assignmentExpression!.leftHandSide as SimpleIdentifier));
+ }
if (questionAssignNode != null) {
_guards.add(destinationType!.node);
@@ -2504,7 +2516,7 @@
}
if (sourceType == null) {
throw StateError('No type computed for ${expression.runtimeType} '
- '(${expression!.toSource()}) offset=${expression.offset}');
+ '(${expression.toSource()}) offset=${expression.offset}');
}
EdgeOrigin edgeOrigin = _makeEdgeOrigin(sourceType, expression,
isSetupAssignment: sourceIsSetupCall);
@@ -2525,7 +2537,7 @@
_checkAssignment(edgeOrigin, FixReasonTarget.root,
source: sourceType,
destination: compoundOperatorType.positionalParameters![0],
- hard: _shouldUseHardEdge(expression!),
+ hard: _shouldUseHardEdge(expression),
sourceIsFunctionLiteral: expression is FunctionExpression);
sourceType = _fixNumericTypes(compoundOperatorType.returnType!,
compoundOperatorInfo.staticType);
@@ -2544,7 +2556,7 @@
expression, edgeOrigin, sourceType, destinationType!)) {
// Nothing further to do.
} else {
- var hard = _shouldUseHardEdge(expression!,
+ var hard = _shouldUseHardEdge(expression,
isConditionallyExecuted: questionAssignNode != null);
_checkAssignment(edgeOrigin, FixReasonTarget.root,
source: sourceType,
@@ -3090,7 +3102,7 @@
var suppliedNamedParameters = <String>{};
for (var argument in arguments) {
String? name;
- Expression? expression;
+ Expression expression;
if (argument is NamedExpression) {
name = argument.name.label.name;
expression = argument.expression;
@@ -3099,7 +3111,7 @@
name = argument.name!.lexeme;
}
// TODO(scheglov) This is a hack.
- expression = (argument as FormalParameterImpl).identifierForMigration;
+ expression = (argument as FormalParameterImpl).identifierForMigration!;
} else {
expression = argument as Expression;
}
@@ -3364,7 +3376,7 @@
}
}
- EdgeOrigin _makeEdgeOrigin(DecoratedType sourceType, Expression? expression,
+ EdgeOrigin _makeEdgeOrigin(DecoratedType sourceType, Expression expression,
{bool isSetupAssignment = false}) {
if (sourceType.type!.isDynamic) {
return DynamicAssignmentOrigin(source, expression);
@@ -3373,7 +3385,7 @@
source, expression, ExpressionChecks(),
isSetupAssignment: isSetupAssignment);
_variables.recordExpressionChecks(
- source, expression!, expressionChecksOrigin);
+ source, expression, expressionChecksOrigin);
return expressionChecksOrigin;
}
}
@@ -3645,6 +3657,38 @@
return null;
}
}
+
+ /// Whether `expression` is a call to Angular's [Injector.get], with exactly
+ /// one argument.
+ static bool _isInjectorGetCall(Expression expression) {
+ if (expression is! MethodInvocation) {
+ return false;
+ }
+
+ if (expression.methodName.name != 'get') {
+ return false;
+ }
+ if (expression.argumentList.arguments.length != 1) {
+ // If a second argument is passed, which may indicate that a non-null
+ // value isn't necessarily expected, don't count this call.
+ return false;
+ }
+
+ var target = expression.target;
+ if (target is! Identifier) {
+ return false;
+ }
+
+ var receiver = target.staticType?.element;
+ if (receiver?.name != 'Injector') {
+ return false;
+ }
+
+ var uri = receiver?.library?.source.uri;
+ return uri != null &&
+ uri.isScheme('package') &&
+ uri.path.startsWith('angular/');
+ }
}
/// Implementation of [_checkAssignment] for [EdgeBuilder].
diff --git a/pkg/nnbd_migration/lib/src/edge_origin.dart b/pkg/nnbd_migration/lib/src/edge_origin.dart
index 0db2344..9e94536 100644
--- a/pkg/nnbd_migration/lib/src/edge_origin.dart
+++ b/pkg/nnbd_migration/lib/src/edge_origin.dart
@@ -96,6 +96,31 @@
EdgeOriginKind get kind => EdgeOriginKind.argumentErrorCheckNotNull;
}
+/// Edge origin resulting from an assignment from a call to Angular's
+/// `Injector.get` in a test `setUp` callback.
+///
+/// For example, in the following code snippet:
+///
+/// Foo foo;
+/// setUp(() {
+/// foo = injector.get(Foo);
+/// });
+///
+/// this class is used for the edge connecting the type of the `foo` variable to
+/// `never`, due to the assignment from `injector.get`. While `Injector.get` has
+/// a return type of `dynamic`, it's use in a test `setUp` callback is more than
+/// likely intended to yield a non-null value.
+class AssignmentFromAngularInjectorGetOrigin extends EdgeOrigin {
+ AssignmentFromAngularInjectorGetOrigin(
+ super.source, SimpleIdentifier super.node);
+
+ @override
+ String get description => 'value retrieved from Injector.get in test setUp';
+
+ @override
+ EdgeOriginKind get kind => EdgeOriginKind.assignmentFromAngularInjectorGet;
+}
+
/// Edge origin resulting from a use of built_value's `@nullable` annotation.
class BuiltValueNullableOrigin extends EdgeOrigin {
BuiltValueNullableOrigin(super.source, AstNode super.node);
diff --git a/pkg/nnbd_migration/test/abstract_context.dart b/pkg/nnbd_migration/test/abstract_context.dart
index b30e6a0..445d194 100644
--- a/pkg/nnbd_migration/test/abstract_context.dart
+++ b/pkg/nnbd_migration/test/abstract_context.dart
@@ -71,6 +71,9 @@
class ViewChildren {
const ViewChildren(Object selector, {Object? read});
}
+class Injector {
+ dynamic get(Object token, [Object? notFoundValue]) => null;
+}
''');
if (internalUris) {
addPackageFile('angular', 'angular.dart', '''
diff --git a/pkg/nnbd_migration/test/api_test.dart b/pkg/nnbd_migration/test/api_test.dart
index 170f10f..7aa818d 100644
--- a/pkg/nnbd_migration/test/api_test.dart
+++ b/pkg/nnbd_migration/test/api_test.dart
@@ -9151,6 +9151,92 @@
await _checkSingleFileChanges(content, expected);
}
+ Future<void> test_testVariable_assignedInjectorGet() async {
+ addAngularPackage();
+ addTestCorePackage();
+ var content = '''
+import 'package:angular/angular.dart';
+import 'package:test/test.dart';
+void main() {
+ int i;
+ setUp(() {
+ var injector = Injector();
+ i = injector.get(int);
+ });
+ test('a', () {
+ i.isEven;
+ });
+}
+''';
+ var expected = '''
+import 'package:angular/angular.dart';
+import 'package:test/test.dart';
+void main() {
+ late int i;
+ setUp(() {
+ var injector = Injector();
+ i = injector.get(int);
+ });
+ test('a', () {
+ i.isEven;
+ });
+}
+''';
+ await _checkSingleFileChanges(content, expected);
+ }
+
+ Future<void> test_testVariable_assignedInjectorGet_inDeclaration() async {
+ addAngularPackage();
+ addTestCorePackage();
+ var content = '''
+import 'package:angular/angular.dart';
+import 'package:test/test.dart';
+void main() {
+ setUp(() {
+ var injector = Injector();
+ int i = injector.get(int);
+ i.isEven;
+ });
+}
+''';
+ var expected = '''
+import 'package:angular/angular.dart';
+import 'package:test/test.dart';
+void main() {
+ setUp(() {
+ var injector = Injector();
+ int i = injector.get(int);
+ i.isEven;
+ });
+}
+''';
+ await _checkSingleFileChanges(content, expected);
+ }
+
+ Future<void> test_testVariable_assignedInjectorGet_outsideSetup() async {
+ addAngularPackage();
+ addTestCorePackage();
+ var content = '''
+import 'package:angular/angular.dart';
+void main() {
+ int i;
+ var injector = Injector();
+ i = injector.get(int);
+ i.isEven;
+}
+''';
+ var expected = '''
+import 'package:angular/angular.dart';
+void main() {
+ int? i;
+ var injector = Injector();
+ i = injector.get(int);
+ i!.isEven;
+}
+''';
+ await _checkSingleFileChanges(content, expected);
+ }
+
Future<void> test_testVariable_assignedNullableValue() async {
addTestCorePackage();
var content = '''