[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));
}