[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>
This commit is contained in:
Nicholas Shahan
2019-03-25 23:40:34 +00:00
parent f09097bab8
commit 093c2909fe
2 changed files with 93 additions and 33 deletions
@@ -6530,49 +6530,97 @@ class CodeGenerator extends Object
@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
@@ -9,7 +9,6 @@ import 'package:analyzer/dart/element/type.dart' show DartType;
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;
@@ -66,6 +65,13 @@ class CoercionReifier extends GeneralizingAstVisitor<void> {
}
}
@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)) {
@@ -128,6 +134,12 @@ class _TreeCloner extends AstCloner {
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));
}