Migration: Handle contravariant uses of already-migrated classes.
Change-Id: I1ce1adc1975461010ce993ba31ac74856033351e
Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/107320
Commit-Queue: Paul Berry <paulberry@google.com>
Reviewed-by: Konstantin Shcheglov <scheglov@google.com>
diff --git a/pkg/analyzer/lib/src/test_utilities/mock_sdk.dart b/pkg/analyzer/lib/src/test_utilities/mock_sdk.dart
index 917fc1c..52afb8a 100644
--- a/pkg/analyzer/lib/src/test_utilities/mock_sdk.dart
+++ b/pkg/analyzer/lib/src/test_utilities/mock_sdk.dart
@@ -461,6 +461,7 @@
double nextDouble() => 2.0;
int nextInt() => 1;
}
+class Point<T extends num> {}
''');
const List<SdkLibrary> _LIBRARIES = const [
diff --git a/pkg/nnbd_migration/lib/src/decorated_type.dart b/pkg/nnbd_migration/lib/src/decorated_type.dart
index e3f31e6..be81ed0 100644
--- a/pkg/nnbd_migration/lib/src/decorated_type.dart
+++ b/pkg/nnbd_migration/lib/src/decorated_type.dart
@@ -72,6 +72,8 @@
throw UnimplementedError('Decorating ${type.displayName}');
}
return DecoratedType(type, graph.never);
+ } else if (type is TypeParameterType) {
+ return DecoratedType(type, graph.never);
} else {
throw type.runtimeType; // TODO(paulberry)
}
@@ -92,6 +94,10 @@
decoratedType = decorate(element.type);
} else if (element is TopLevelVariableElement) {
decoratedType = decorate(element.type);
+ } else if (element is TypeParameterElement) {
+ // By convention, type parameter elements are decorated with the type of
+ // their bounds.
+ decoratedType = decorate(element.bound ?? DynamicTypeImpl.instance);
} else {
// TODO(paulberry)
throw UnimplementedError('Decorating ${element.runtimeType}');
diff --git a/pkg/nnbd_migration/test/api_test.dart b/pkg/nnbd_migration/test/api_test.dart
index c026523..8505cfb 100644
--- a/pkg/nnbd_migration/test/api_test.dart
+++ b/pkg/nnbd_migration/test/api_test.dart
@@ -426,6 +426,29 @@
await _checkSingleFileChanges(content, expected);
}
+ test_data_flow_generic_contravariant_inward_using_core_class() async {
+ var content = '''
+void f(List<int> x, int i) {
+ x.add(i);
+}
+void test(List<int> x) {
+ f(x, null);
+}
+''';
+ // TODO(paulberry): possible improvement: detect that since add uses T in
+ // a contravariant way, and deduce that test should change to
+ // `void test(List<int?> x)`
+ var expected = '''
+void f(List<int?> x, int? i) {
+ x.add(i);
+}
+void test(List<int> x) {
+ f(x, null);
+}
+''';
+ await _checkSingleFileChanges(content, expected);
+ }
+
test_data_flow_generic_covariant_outward() async {
var content = '''
class C<T> {
diff --git a/pkg/nnbd_migration/test/edge_builder_test.dart b/pkg/nnbd_migration/test/edge_builder_test.dart
index a664f07..10a6032 100644
--- a/pkg/nnbd_migration/test/edge_builder_test.dart
+++ b/pkg/nnbd_migration/test/edge_builder_test.dart
@@ -3,6 +3,8 @@
// BSD-style license that can be found in the LICENSE file.
import 'package:analyzer/dart/ast/ast.dart';
+import 'package:analyzer/dart/element/element.dart';
+import 'package:analyzer/src/dart/element/member.dart';
import 'package:nnbd_migration/src/decorated_type.dart';
import 'package:nnbd_migration/src/edge_builder.dart';
import 'package:nnbd_migration/src/expression_checks.dart';
@@ -1226,6 +1228,32 @@
assertEdge(nullable_i, nullable_c_t_or_nullable_t, hard: true));
}
+ test_methodInvocation_parameter_contravariant_from_migrated_class() async {
+ await analyze('''
+void f(List<int> x, int i) {
+ x.add(i/*check*/);
+}
+''');
+
+ var nullable_i = decoratedTypeAnnotation('int i').node;
+ var nullable_list_t =
+ decoratedTypeAnnotation('List<int>').typeArguments[0].node;
+ var addMethod = findNode.methodInvocation('x.add').methodName.staticElement
+ as MethodMember;
+ var nullable_t = variables
+ .decoratedElementType(addMethod.baseElement)
+ .positionalParameters[0]
+ .node;
+ expect(nullable_t, same(never));
+ var check_i = checkExpression('i/*check*/');
+ var nullable_list_t_or_nullable_t =
+ check_i.edges.single.destinationNode as NullabilityNodeForSubstitution;
+ expect(nullable_list_t_or_nullable_t.innerNode, same(nullable_list_t));
+ expect(nullable_list_t_or_nullable_t.outerNode, same(nullable_t));
+ assertNullCheck(check_i,
+ assertEdge(nullable_i, nullable_list_t_or_nullable_t, hard: true));
+ }
+
test_methodInvocation_parameter_generic() async {
await analyze('''
class C<T> {}
@@ -2037,6 +2065,31 @@
hard: true);
}
+ test_type_parameterized_migrated_bound_class() async {
+ await analyze('''
+import 'dart:math';
+void f(Point<int> x) {}
+''');
+ var pointClass =
+ findNode.typeName('Point').name.staticElement as ClassElement;
+ var pointBound =
+ variables.decoratedElementType(pointClass.typeParameters[0]);
+ expect(pointBound.type.toString(), 'num');
+ assertEdge(decoratedTypeAnnotation('int>').node, pointBound.node,
+ hard: true);
+ }
+
+ test_type_parameterized_migrated_bound_dynamic() async {
+ await analyze('''
+void f(List<int> x) {}
+''');
+ var listClass = typeProvider.listType.element;
+ var listBound = variables.decoratedElementType(listClass.typeParameters[0]);
+ expect(listBound.type.toString(), 'dynamic');
+ assertEdge(decoratedTypeAnnotation('int>').node, listBound.node,
+ hard: true);
+ }
+
test_typeName() async {
await analyze('''
Type f() {