Migration: address a more complex case of instantiate-to-bounds.

Change-Id: I8a7d5e2e4189c3d910077ad4ec88e6c4e384dc25
Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/105731
Reviewed-by: Brian Wilkerson <brianwilkerson@google.com>
Commit-Queue: Paul Berry <paulberry@google.com>
diff --git a/pkg/nnbd_migration/lib/src/graph_builder.dart b/pkg/nnbd_migration/lib/src/graph_builder.dart
index c7f7b14..bfe82d0 100644
--- a/pkg/nnbd_migration/lib/src/graph_builder.dart
+++ b/pkg/nnbd_migration/lib/src/graph_builder.dart
@@ -499,18 +499,27 @@
   DecoratedType visitTypeName(TypeName typeName) {
     var typeArguments = typeName.typeArguments?.arguments;
     var element = typeName.name.staticElement;
-    if (typeArguments != null) {
-      for (int i = 0; i < typeArguments.length; i++) {
-        DecoratedType bound;
-        if (element is TypeParameterizedElement) {
+    if (element is TypeParameterizedElement) {
+      if (typeArguments == null) {
+        var instantiatedType =
+            _variables.decoratedTypeAnnotation(_source, typeName);
+        for (int i = 0; i < instantiatedType.typeArguments.length; i++) {
+          _unionDecoratedTypes(
+              instantiatedType.typeArguments[0],
+              _variables.decoratedElementType(element.typeParameters[i],
+                  create: true));
+        }
+      } else {
+        for (int i = 0; i < typeArguments.length; i++) {
+          DecoratedType bound;
           bound = _variables.decoratedElementType(element.typeParameters[i],
               create: true);
-        } else {
-          throw new UnimplementedError('TODO(paulberry)');
+          _checkAssignment(
+              bound,
+              _variables.decoratedTypeAnnotation(_source, typeArguments[i]),
+              null,
+              hard: true);
         }
-        _checkAssignment(bound,
-            _variables.decoratedTypeAnnotation(_source, typeArguments[i]), null,
-            hard: true);
       }
     }
     return DecoratedType(typeName.type, _graph.never);
@@ -669,6 +678,20 @@
     }
     return false;
   }
+
+  void _unionDecoratedTypes(DecoratedType x, DecoratedType y) {
+    _graph.union(x.node, y.node);
+    if (x.typeArguments.isNotEmpty ||
+        y.typeArguments.isNotEmpty ||
+        x.returnType != null ||
+        y.returnType != null ||
+        x.positionalParameters.isNotEmpty ||
+        y.positionalParameters.isNotEmpty ||
+        x.namedParameters.isNotEmpty ||
+        y.namedParameters.isNotEmpty) {
+      throw UnimplementedError('TODO(paulberry): _unionDecoratedTypes($x, $y)');
+    }
+  }
 }
 
 /// Information about a binary expression whose boolean value could possibly
diff --git a/pkg/nnbd_migration/lib/src/node_builder.dart b/pkg/nnbd_migration/lib/src/node_builder.dart
index 51548fd..89ff432 100644
--- a/pkg/nnbd_migration/lib/src/node_builder.dart
+++ b/pkg/nnbd_migration/lib/src/node_builder.dart
@@ -237,6 +237,10 @@
   DecoratedType _decorateImplicitTypeArgument(DartType type) {
     if (type.isDynamic) {
       return DecoratedType(type, _graph.always);
+    } else if (type is InterfaceType) {
+      return DecoratedType(type, NullabilityNode.forInferredType(),
+          typeArguments:
+              type.typeArguments.map(_decorateImplicitTypeArgument).toList());
     }
     throw UnimplementedError('TODO(paulberry): ${type.runtimeType}');
   }
diff --git a/pkg/nnbd_migration/lib/src/nullability_node.dart b/pkg/nnbd_migration/lib/src/nullability_node.dart
index 2935a26..b8fd097 100644
--- a/pkg/nnbd_migration/lib/src/nullability_node.dart
+++ b/pkg/nnbd_migration/lib/src/nullability_node.dart
@@ -273,6 +273,11 @@
     return node;
   }
 
+  /// Creates a [NullabilityNode] representing the nullability of a variable
+  /// whose type is determined by type inference.
+  factory NullabilityNode.forInferredType() =>
+      _NullabilityNodeSimple('inferred');
+
   /// Creates a [NullabilityNode] representing the nullability of an
   /// expression which is nullable iff both [a] and [b] are nullable.
   ///
diff --git a/pkg/nnbd_migration/test/api_test.dart b/pkg/nnbd_migration/test/api_test.dart
index 07bb061..c4e8a9a 100644
--- a/pkg/nnbd_migration/test/api_test.dart
+++ b/pkg/nnbd_migration/test/api_test.dart
@@ -657,6 +657,33 @@
     await _checkSingleFileChanges(content, expected);
   }
 
+  test_genericType_noTypeArguments_use_bound() async {
+    var content = '''
+abstract class C<T extends Object> { // (1)
+  void put(T t);
+  T get();
+}
+Object f(C c) => c.get();            // (2)
+void g(C<int> c) {                   // (3)
+  c.put(null);                       // (4)
+}
+''';
+    // (4) forces `...C<int?>...` at (3), which means (1) must be
+    // `...extends Object?`.  Therefore (2) is equivalent to
+    // `...f(C<Object?> c)...`, so the return type of `f` is `Object?`.
+    var expected = '''
+abstract class C<T extends Object?> { // (1)
+  void put(T t);
+  T get();
+}
+Object? f(C c) => c.get();            // (2)
+void g(C<int?> c) {                   // (3)
+  c.put(null);                       // (4)
+}
+''';
+    await _checkSingleFileChanges(content, expected);
+  }
+
   test_getter_topLevel() async {
     var content = '''
 int get g => 0;
diff --git a/pkg/nnbd_migration/test/migration_visitor_test.dart b/pkg/nnbd_migration/test/migration_visitor_test.dart
index 0b96814..f897472 100644
--- a/pkg/nnbd_migration/test/migration_visitor_test.dart
+++ b/pkg/nnbd_migration/test/migration_visitor_test.dart
@@ -1162,6 +1162,16 @@
     assertNoUpstreamNullability(decoratedTypeAnnotation('Type').node);
   }
 
+  test_typeName_union_with_bound() async {
+    await analyze('''
+class C<T extends Object> {}
+void f(C c) {}
+''');
+    var cType = decoratedTypeAnnotation('C c');
+    var cBound = decoratedTypeAnnotation('Object');
+    assertUnion(cType.typeArguments[0].node, cBound.node);
+  }
+
   test_variableDeclaration() async {
     await analyze('''
 void f(int i) {
@@ -1352,6 +1362,40 @@
     expect(decoratedArgType.node, same(always));
   }
 
+  test_interfaceType_generic_instantiate_to_generic_type() async {
+    await analyze('''
+class C<T> {}
+class D<T extends C<int>> {}
+void f(D x) {}
+''');
+    var decoratedListType = decoratedTypeAnnotation('D x');
+    expect(decoratedFunctionType('f').positionalParameters[0],
+        same(decoratedListType));
+    expect(decoratedListType.node, TypeMatcher<NullabilityNodeMutable>());
+    expect(decoratedListType.typeArguments, hasLength(1));
+    var decoratedArgType = decoratedListType.typeArguments[0];
+    expect(decoratedArgType.node, TypeMatcher<NullabilityNodeMutable>());
+    expect(decoratedArgType.typeArguments, hasLength(1));
+    var decoratedArgArgType = decoratedArgType.typeArguments[0];
+    expect(decoratedArgArgType.node, TypeMatcher<NullabilityNodeMutable>());
+    expect(decoratedArgArgType.typeArguments, isEmpty);
+  }
+
+  test_interfaceType_generic_instantiate_to_object() async {
+    await analyze('''
+class C<T extends Object> {}
+void f(C x) {}
+''');
+    var decoratedListType = decoratedTypeAnnotation('C x');
+    expect(decoratedFunctionType('f').positionalParameters[0],
+        same(decoratedListType));
+    expect(decoratedListType.node, TypeMatcher<NullabilityNodeMutable>());
+    expect(decoratedListType.typeArguments, hasLength(1));
+    var decoratedArgType = decoratedListType.typeArguments[0];
+    expect(decoratedArgType.node, TypeMatcher<NullabilityNodeMutable>());
+    expect(decoratedArgType.typeArguments, isEmpty);
+  }
+
   test_interfaceType_typeParameter() async {
     await analyze('''
 void f(List<int> x) {}