[dartdevc] Add implicit casts for SpreadElement nodes

Issue: #36006
Change-Id: Ie7252f70bd62d9b2b2382335a7853f5eb7f09480
Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/97684
Reviewed-by: Jenny Messerly <jmesserly@google.com>
diff --git a/pkg/dev_compiler/lib/src/analyzer/code_generator.dart b/pkg/dev_compiler/lib/src/analyzer/code_generator.dart
index f4fe055..a20d1da 100644
--- a/pkg/dev_compiler/lib/src/analyzer/code_generator.dart
+++ b/pkg/dev_compiler/lib/src/analyzer/code_generator.dart
@@ -6530,49 +6530,97 @@
 
   @override
   JS.Statement visitSpreadElement(SpreadElement node) {
-    /// Returns `true` if [node] is or is a sub element of a Map literal.
-    isMap(AstNode node) {
+    /// Returns `true` if [node] is or is a child element of a map literal.
+    bool isMap(AstNode node) {
       if (node is SetOrMapLiteral) return node.isMap;
       if (node is ListLiteral) return false;
       return isMap(node.parent);
     }
 
-    /// Appends all elements in [spreadItems] to the collection being built.
-    ///
-    /// Walks the parent nodes starting at [parent] to determine if this is
-    /// spread is in a Map literal.
-    emitSpread(AstNode parent, JS.Expression spreadExpression) {
-      if (isMap(parent)) {
-        return js.statement('#.forEach((k, v) => {#.push(k); #.push(v)})', [
-          spreadExpression,
-          _currentCollectionVariable,
-          _currentCollectionVariable
-        ]);
-      }
-      return js.statement('#.forEach((i) => #.push(i))',
-          [spreadExpression, _currentCollectionVariable]);
-    }
+    /// Returns [expression] wrapped in an implict cast to [castType] or
+    /// [expression] as provided if [castType] is `null` signifying that
+    /// no cast is needed.
+    JS.Expression wrapInImplicitCast(JS.Expression expression, castType) =>
+        castType == null ? expression : _emitCast(castType, expression);
 
-    /// Emits a null check on [spreadExpression] then appends all elements to
-    /// the collection being built.
+    /// Returns a statement spreading the elements of [expression] into
+    /// [_currentCollectionVariable].
     ///
-    /// Walks the parent nodes starting at [parent] to determine if this is
-    /// spread is in a Map literal.
-    emitNullSafeSpread(AstNode parent, JS.Expression spreadExpression) {
-      // TODO(nshahan) Could optimize out if we know the value is null.
-      var spreadItems = _emitSimpleIdentifier(
-          _createTemporary('items', getStaticType(node.expression)));
-      return JS.Block([
-        js.statement('let # = #', [spreadItems, spreadExpression]),
-        js.statement('if (# != null) #',
-            [spreadItems, emitSpread(node.parent, spreadItems)])
+    /// Expects the collection literal containing [expression] to be a list or
+    /// set literal. Inserts implicit casts to [elementCastType] for each
+    /// element if needed.
+    JS.Statement emitListOrSetSpread(
+        JS.Expression expression, DartType elementCastType) {
+      var forEachTemp =
+          _emitSimpleIdentifier(_createTemporary('i', types.dynamicType));
+      return js.statement('#.forEach((#) => #.push(#))', [
+        expression,
+        forEachTemp,
+        _currentCollectionVariable,
+        wrapInImplicitCast(forEachTemp, elementCastType)
       ]);
     }
 
-    var spreadExpression = _visitExpression(node.expression);
+    /// Returns a statement spreading the key/value pairs of [expression]
+    /// into [_currentCollectionVariable].
+    ///
+    /// Expects the collection literal containing [expression] to be a map
+    /// literal. Inserts implicit casts to [keyCastType] for keys and
+    /// [valueCastType] for values if needed.
+    JS.Statement emitMapSpread(JS.Expression expression, DartType keyCastType,
+        DartType valueCastType) {
+      var keyTemp =
+          _emitSimpleIdentifier(_createTemporary('k', types.dynamicType));
+      var valueTemp =
+          _emitSimpleIdentifier(_createTemporary('v', types.dynamicType));
+      return js.statement('#.forEach((#, #) => {#.push(#); #.push(#)})', [
+        expression,
+        keyTemp,
+        valueTemp,
+        _currentCollectionVariable,
+        wrapInImplicitCast(keyTemp, keyCastType),
+        _currentCollectionVariable,
+        wrapInImplicitCast(valueTemp, valueCastType)
+      ]);
+    }
+
+    /// Appends all elements in [expression] to the collection being built.
+    ///
+    /// Uses implict cast information from [node] to insert the correct casts
+    /// for the collection elements when spreading. Inspects parents of [node]
+    /// to determine the type of the enclosing collection literal.
+    JS.Statement emitSpread(JS.Expression expression, Expression node) {
+      expression = wrapInImplicitCast(expression, getImplicitCast(node));
+
+      // Start searching for a map literal at the parent of the SpreadElement.
+      if (isMap(node.parent.parent)) {
+        return emitMapSpread(expression, getImplicitSpreadKeyCast(node),
+            getImplicitSpreadValueCast(node));
+      }
+      return emitListOrSetSpread(expression, getImplicitSpreadCast(node));
+    }
+
+    /// Emits a null check on [expression] then appends all elements to the
+    /// collection being built.
+    ///
+    /// Uses implict cast information from [node] to insert the correct casts
+    /// for the collection elements when spreading. Inspects parents of [node]
+    /// to determine the type of the enclosing collection literal.
+    JS.Statement emitNullSafeSpread(JS.Expression expression, Expression node) {
+      // TODO(nshahan) Could optimize out if we know the value is null.
+      var spreadItems =
+          _emitSimpleIdentifier(_createTemporary('items', getStaticType(node)));
+      return JS.Block([
+        js.statement('let # = #', [spreadItems, expression]),
+        js.statement(
+            'if (# != null) #', [spreadItems, emitSpread(spreadItems, node)])
+      ]);
+    }
+
+    var expression = _visitExpression(node.expression);
     return node.beginToken.lexeme == '...?'
-        ? emitNullSafeSpread(node.parent, spreadExpression)
-        : emitSpread(node.parent, spreadExpression);
+        ? emitNullSafeSpread(expression, node.expression)
+        : emitSpread(expression, node.expression);
   }
 
   @override
diff --git a/pkg/dev_compiler/lib/src/analyzer/reify_coercions.dart b/pkg/dev_compiler/lib/src/analyzer/reify_coercions.dart
index 67bd4d7..a071fcb 100644
--- a/pkg/dev_compiler/lib/src/analyzer/reify_coercions.dart
+++ b/pkg/dev_compiler/lib/src/analyzer/reify_coercions.dart
@@ -9,7 +9,6 @@
 import 'package:analyzer/src/dart/ast/ast.dart' show FunctionBodyImpl;
 import 'package:analyzer/src/dart/ast/utilities.dart'
     show AstCloner, NodeReplacer;
-import 'package:analyzer/src/dart/element/type.dart' show DynamicTypeImpl;
 import 'package:analyzer/src/generated/parser.dart' show ResolutionCopier;
 import 'package:analyzer/src/task/strong/ast_properties.dart' as ast_properties;
 
@@ -67,6 +66,13 @@
   }
 
   @override
+  visitSpreadElement(SpreadElement node) {
+    // Skip visiting the expression so we can handle all casts during code
+    // generation.
+    node.expression.visitChildren(this);
+  }
+
+  @override
   visitMethodInvocation(MethodInvocation node) {
     if (isInlineJS(node.methodName.staticElement)) {
       // Don't cast our inline-JS code in SDK.
@@ -128,6 +134,12 @@
           clone, ast_properties.getImplicitCast(node));
       ast_properties.setImplicitOperationCast(
           clone, ast_properties.getImplicitOperationCast(node));
+      ast_properties.setImplicitSpreadCast(
+          clone, ast_properties.getImplicitSpreadCast(node));
+      ast_properties.setImplicitSpreadKeyCast(
+          clone, ast_properties.getImplicitSpreadKeyCast(node));
+      ast_properties.setImplicitSpreadValueCast(
+          clone, ast_properties.getImplicitSpreadValueCast(node));
       ast_properties.setIsDynamicInvoke(
           clone, ast_properties.isDynamicInvoke(node));
     }