[nnbd_migration] support spread elements

Change-Id: I170a820f1049856115e6d7c4302c75038d14f27b
Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/116367
Reviewed-by: Paul Berry <paulberry@google.com>
Commit-Queue: Paul Berry <paulberry@google.com>
Auto-Submit: Mike Fairhurst <mfairhurst@google.com>
diff --git a/pkg/nnbd_migration/lib/src/decorated_type.dart b/pkg/nnbd_migration/lib/src/decorated_type.dart
index 8ce96e7..9982d1f 100644
--- a/pkg/nnbd_migration/lib/src/decorated_type.dart
+++ b/pkg/nnbd_migration/lib/src/decorated_type.dart
@@ -140,20 +140,38 @@
   /// nodes everywhere that don't correspond to any source location.  These
   /// nodes can later be unioned with other nodes.
   factory DecoratedType.forImplicitType(
-      TypeProvider typeProvider, DartType type, NullabilityGraph graph) {
+      TypeProvider typeProvider, DartType type, NullabilityGraph graph,
+      {List<DecoratedType> typeArguments}) {
     if (type.isDynamic || type.isVoid) {
+      assert(typeArguments == null);
       return DecoratedType(type, graph.always);
     } else if (type is InterfaceType) {
+      assert(() {
+        if (typeArguments != null) {
+          assert(typeArguments.length == type.typeArguments.length);
+          for (var i = 0; i < typeArguments.length; ++i) {
+            assert(typeArguments[i].type == type.typeArguments[i]);
+          }
+        }
+        return true;
+      }());
+
+      typeArguments ??= type.typeArguments
+          .map((t) => DecoratedType.forImplicitType(typeProvider, t, graph))
+          .toList();
       return DecoratedType(type, NullabilityNode.forInferredType(),
-          typeArguments: type.typeArguments
-              .map((t) => DecoratedType.forImplicitType(typeProvider, t, graph))
-              .toList());
+          typeArguments: typeArguments);
     } else if (type is FunctionType) {
+      if (typeArguments != null) {
+        throw "Not supported: implicit function type with explicit type arguments";
+      }
       return DecoratedType.forImplicitFunction(
           typeProvider, type, NullabilityNode.forInferredType(), graph);
     } else if (type is TypeParameterType) {
+      assert(typeArguments == null);
       return DecoratedType(type, NullabilityNode.forInferredType());
     } else if (type is BottomTypeImpl) {
+      assert(typeArguments == null);
       return DecoratedType(type, NullabilityNode.forInferredType());
     }
     // TODO(paulberry)
diff --git a/pkg/nnbd_migration/lib/src/edge_builder.dart b/pkg/nnbd_migration/lib/src/edge_builder.dart
index 9784703a0..a2400b2 100644
--- a/pkg/nnbd_migration/lib/src/edge_builder.dart
+++ b/pkg/nnbd_migration/lib/src/edge_builder.dart
@@ -1132,6 +1132,42 @@
   }
 
   @override
+  DecoratedType visitSpreadElement(SpreadElement node) {
+    final spreadType = node.expression.staticType;
+    if (_typeSystem.isSubtypeOf(
+        spreadType, _typeProvider.mapObjectObjectType)) {
+      assert(_currentMapKeyType != null && _currentMapValueType != null);
+      final expectedType = _typeSystem.instantiateType(_typeProvider.mapType,
+          [_currentMapKeyType.type, _currentMapValueType.type]);
+      final expectedDecoratedType = DecoratedType.forImplicitType(
+          _typeProvider, expectedType, _graph,
+          typeArguments: [_currentMapKeyType, _currentMapValueType]);
+
+      _handleAssignment(node.expression,
+          destinationType: expectedDecoratedType);
+    } else if (_typeSystem.isSubtypeOf(
+        spreadType, _typeProvider.iterableDynamicType)) {
+      assert(_currentLiteralElementType != null);
+      final expectedType = _typeSystem.instantiateType(
+          _typeProvider.iterableType, [_currentLiteralElementType.type]);
+      final expectedDecoratedType = DecoratedType.forImplicitType(
+          _typeProvider, expectedType, _graph,
+          typeArguments: [_currentLiteralElementType]);
+
+      _handleAssignment(node.expression,
+          destinationType: expectedDecoratedType);
+    } else {
+      // Downcast. We can't assume nullability here, so do nothing.
+    }
+
+    if (!node.isNullAware) {
+      _checkExpressionNotNull(node.expression);
+    }
+
+    return null;
+  }
+
+  @override
   DecoratedType visitStringLiteral(StringLiteral node) {
     node.visitChildren(this);
     return DecoratedType(node.staticType, _graph.never);
diff --git a/pkg/nnbd_migration/test/edge_builder_test.dart b/pkg/nnbd_migration/test/edge_builder_test.dart
index cf76670..8cce491a 100644
--- a/pkg/nnbd_migration/test/edge_builder_test.dart
+++ b/pkg/nnbd_migration/test/edge_builder_test.dart
@@ -4409,6 +4409,93 @@
     assertEdge(always, decoratedTypeAnnotation('int').node, hard: false);
   }
 
+  test_spread_element_list() async {
+    await analyze('''
+void f(List<int> ints) {
+  <int>[...ints];
+}
+''');
+
+    assertEdge(decoratedTypeAnnotation('List<int>').node, never, hard: true);
+    assertEdge(
+        substitutionNode(decoratedTypeAnnotation('int> ints').node, anyNode),
+        decoratedTypeAnnotation('int>[').node,
+        hard: false);
+  }
+
+  test_spread_element_list_dynamic() async {
+    await analyze('''
+void f(dynamic ints) {
+  <int>[...ints];
+}
+''');
+
+    // Mostly just check this doesn't crash.
+    assertEdge(decoratedTypeAnnotation('dynamic').node, never, hard: true);
+  }
+
+  test_spread_element_list_nullable() async {
+    await analyze('''
+void f(List<int> ints) {
+  <int>[...?ints];
+}
+''');
+
+    assertNoEdge(decoratedTypeAnnotation('List<int>').node, never);
+    assertEdge(
+        substitutionNode(decoratedTypeAnnotation('int> ints').node, anyNode),
+        decoratedTypeAnnotation('int>[').node,
+        hard: false);
+  }
+
+  test_spread_element_map() async {
+    await analyze('''
+void f(Map<String, int> map) {
+  <String, int>{...map};
+}
+''');
+
+    assertEdge(decoratedTypeAnnotation('Map<String, int>').node, never,
+        hard: true);
+    assertEdge(decoratedTypeAnnotation('String, int> map').node,
+        decoratedTypeAnnotation('String, int>{').node,
+        hard: false);
+    assertEdge(decoratedTypeAnnotation('int> map').node,
+        decoratedTypeAnnotation('int>{').node,
+        hard: false);
+  }
+
+  test_spread_element_set() async {
+    await analyze('''
+void f(Set<int> ints) {
+  <int>{...ints};
+}
+''');
+
+    assertEdge(decoratedTypeAnnotation('Set<int>').node, never, hard: true);
+    assertEdge(
+        substitutionNode(decoratedTypeAnnotation('int> ints').node, anyNode),
+        decoratedTypeAnnotation('int>{').node,
+        hard: false);
+  }
+
+  test_spread_element_subtype() async {
+    await analyze('''
+abstract class C<T, R> implements Iterable<R> {}
+void f(C<dynamic, int> ints) {
+  <int>[...ints];
+}
+''');
+
+    assertEdge(decoratedTypeAnnotation('C<dynamic, int>').node, never,
+        hard: true);
+    assertEdge(
+        substitutionNode(decoratedTypeAnnotation('int> ints').node,
+            decoratedTypeAnnotation('R> {}').node),
+        decoratedTypeAnnotation('int>[').node,
+        hard: false);
+  }
+
   test_static_method_call_prefixed() async {
     await analyze('''
 import 'dart:async' as a;