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() {