| // Copyright (c) 2019, the Dart project authors. Please see the AUTHORS file |
| // for details. All rights reserved. Use of this source code is governed by a |
| // BSD-style license that can be found in the LICENSE file. |
| |
| library fasta.transform_collections; |
| |
| import 'dart:core' hide MapEntry; |
| |
| import 'package:kernel/ast.dart' |
| show |
| Arguments, |
| AsExpression, |
| Block, |
| BlockExpression, |
| Class, |
| DartType, |
| DynamicType, |
| Expression, |
| ExpressionStatement, |
| Field, |
| ForInStatement, |
| ForStatement, |
| IfStatement, |
| InterfaceType, |
| ListLiteral, |
| MapEntry, |
| MapLiteral, |
| MethodInvocation, |
| Name, |
| Not, |
| NullLiteral, |
| Procedure, |
| PropertyGet, |
| SetLiteral, |
| Statement, |
| StaticInvocation, |
| transformList, |
| TreeNode, |
| VariableDeclaration, |
| VariableGet; |
| |
| import 'package:kernel/core_types.dart' show CoreTypes; |
| |
| import 'package:kernel/type_environment.dart' show TypeEnvironment; |
| |
| import 'package:kernel/visitor.dart' show Transformer; |
| |
| import 'collections.dart' |
| show |
| ControlFlowElement, |
| ControlFlowMapEntry, |
| ForElement, |
| ForInElement, |
| ForInMapEntry, |
| ForMapEntry, |
| IfElement, |
| IfMapEntry, |
| SpreadElement, |
| SpreadMapEntry; |
| |
| import '../source/source_loader.dart' show SourceLoader; |
| |
| import 'redirecting_factory_body.dart' show RedirectingFactoryBody; |
| |
| class CollectionTransformer extends Transformer { |
| final CoreTypes coreTypes; |
| final TypeEnvironment typeEnvironment; |
| final Procedure listAdd; |
| final Procedure setFactory; |
| final Procedure setAdd; |
| final Procedure objectEquals; |
| final Procedure mapEntries; |
| final Procedure mapPut; |
| final Class mapEntryClass; |
| final Field mapEntryKey; |
| final Field mapEntryValue; |
| |
| static Procedure _findSetFactory(CoreTypes coreTypes) { |
| Procedure factory = coreTypes.index.getMember('dart:core', 'Set', ''); |
| RedirectingFactoryBody body = factory?.function?.body; |
| return body?.target; |
| } |
| |
| CollectionTransformer(SourceLoader loader) |
| : coreTypes = loader.coreTypes, |
| typeEnvironment = loader.typeInferenceEngine.typeSchemaEnvironment, |
| listAdd = loader.coreTypes.index.getMember('dart:core', 'List', 'add'), |
| setFactory = _findSetFactory(loader.coreTypes), |
| setAdd = loader.coreTypes.index.getMember('dart:core', 'Set', 'add'), |
| objectEquals = |
| loader.coreTypes.index.getMember('dart:core', 'Object', '=='), |
| mapEntries = |
| loader.coreTypes.index.getMember('dart:core', 'Map', 'get:entries'), |
| mapPut = loader.coreTypes.index.getMember('dart:core', 'Map', '[]='), |
| mapEntryClass = |
| loader.coreTypes.index.getClass('dart:core', 'MapEntry'), |
| mapEntryKey = |
| loader.coreTypes.index.getMember('dart:core', 'MapEntry', 'key'), |
| mapEntryValue = |
| loader.coreTypes.index.getMember('dart:core', 'MapEntry', 'value'); |
| |
| TreeNode _translateListOrSet( |
| Expression node, DartType elementType, List<Expression> elements, |
| {bool isSet: false}) { |
| // Translate elements in place up to the first non-expression, if any. |
| int i = 0; |
| for (; i < elements.length; ++i) { |
| if (elements[i] is ControlFlowElement) break; |
| elements[i] = elements[i].accept(this)..parent = node; |
| } |
| |
| // If there were only expressions, we are done. |
| if (i == elements.length) return node; |
| |
| // Build a block expression and create an empty list or set. |
| VariableDeclaration result; |
| if (isSet) { |
| // TODO(kmillikin): When all the back ends handle set literals we can use |
| // one here. |
| result = new VariableDeclaration.forValue( |
| new StaticInvocation( |
| setFactory, new Arguments([], types: [elementType])), |
| type: new InterfaceType(coreTypes.setClass, [elementType]), |
| isFinal: true); |
| } else { |
| result = new VariableDeclaration.forValue( |
| new ListLiteral([], typeArgument: elementType), |
| type: new InterfaceType(coreTypes.listClass, [elementType]), |
| isFinal: true); |
| } |
| List<Statement> body = [result]; |
| // Add the elements up to the first non-expression. |
| for (int j = 0; j < i; ++j) { |
| _addExpressionElement(elements[j], isSet, result, body); |
| } |
| // Translate the elements starting with the first non-expression. |
| for (; i < elements.length; ++i) { |
| _translateElement(elements[i], elementType, isSet, result, body); |
| } |
| |
| return new BlockExpression(new Block(body), new VariableGet(result)); |
| } |
| |
| void _translateElement(Expression element, DartType elementType, bool isSet, |
| VariableDeclaration result, List<Statement> body) { |
| if (element is SpreadElement) { |
| _translateSpreadElement(element, elementType, isSet, result, body); |
| } else if (element is IfElement) { |
| _translateIfElement(element, elementType, isSet, result, body); |
| } else if (element is ForElement) { |
| _translateForElement(element, elementType, isSet, result, body); |
| } else if (element is ForInElement) { |
| _translateForInElement(element, elementType, isSet, result, body); |
| } else { |
| _addExpressionElement(element.accept(this), isSet, result, body); |
| } |
| } |
| |
| void _addExpressionElement(Expression element, bool isSet, |
| VariableDeclaration result, List<Statement> body) { |
| body.add(new ExpressionStatement(new MethodInvocation( |
| new VariableGet(result), |
| new Name('add'), |
| new Arguments([element]), |
| isSet ? setAdd : listAdd))); |
| } |
| |
| void _translateIfElement(IfElement element, DartType elementType, bool isSet, |
| VariableDeclaration result, List<Statement> body) { |
| List<Statement> thenStatements = []; |
| _translateElement(element.then, elementType, isSet, result, thenStatements); |
| List<Statement> elseStatements; |
| if (element.otherwise != null) { |
| _translateElement(element.otherwise, elementType, isSet, result, |
| elseStatements = <Statement>[]); |
| } |
| Statement thenBody = thenStatements.length == 1 |
| ? thenStatements.first |
| : new Block(thenStatements); |
| Statement elseBody; |
| if (elseStatements != null && elseStatements.isNotEmpty) { |
| elseBody = elseStatements.length == 1 |
| ? elseStatements.first |
| : new Block(elseStatements); |
| } |
| body.add(new IfStatement(element.condition.accept(this), thenBody, elseBody) |
| ..fileOffset = element.fileOffset); |
| } |
| |
| void _translateForElement(ForElement element, DartType elementType, |
| bool isSet, VariableDeclaration result, List<Statement> body) { |
| List<Statement> statements = <Statement>[]; |
| _translateElement(element.body, elementType, isSet, result, statements); |
| Statement loopBody = |
| statements.length == 1 ? statements.first : new Block(statements); |
| ForStatement loop = new ForStatement(element.variables, |
| element.condition?.accept(this), element.updates, loopBody) |
| ..fileOffset = element.fileOffset; |
| transformList(loop.variables, this, loop); |
| transformList(loop.updates, this, loop); |
| body.add(loop); |
| } |
| |
| void _translateForInElement(ForInElement element, DartType elementType, |
| bool isSet, VariableDeclaration result, List<Statement> body) { |
| List<Statement> statements; |
| Statement prologue = element.prologue; |
| if (prologue == null) { |
| statements = <Statement>[]; |
| } else { |
| prologue = prologue.accept(this); |
| statements = |
| prologue is Block ? prologue.statements : <Statement>[prologue]; |
| } |
| _translateElement(element.body, elementType, isSet, result, statements); |
| Statement loopBody = |
| statements.length == 1 ? statements.first : new Block(statements); |
| if (element.problem != null) { |
| body.add(new ExpressionStatement(element.problem.accept(this))); |
| } |
| body.add(new ForInStatement( |
| element.variable, element.iterable.accept(this), loopBody, |
| isAsync: element.isAsync) |
| ..fileOffset = element.fileOffset); |
| } |
| |
| void _translateSpreadElement(SpreadElement element, DartType elementType, |
| bool isSet, VariableDeclaration result, List<Statement> body) { |
| Expression value = element.expression.accept(this); |
| // Null-aware spreads require testing the subexpression's value. |
| VariableDeclaration temp; |
| if (element.isNullAware) { |
| temp = new VariableDeclaration.forValue(value, |
| type: const DynamicType(), isFinal: true); |
| body.add(temp); |
| value = new VariableGet(temp); |
| } |
| |
| VariableDeclaration elt; |
| Statement loopBody; |
| if (element.elementType == null || |
| !typeEnvironment.isSubtypeOf(element.elementType, elementType)) { |
| elt = new VariableDeclaration(null, |
| type: const DynamicType(), isFinal: true); |
| VariableDeclaration castedVar = new VariableDeclaration.forValue( |
| new AsExpression(new VariableGet(elt), elementType) |
| ..isTypeError = true |
| ..fileOffset = element.expression.fileOffset, |
| type: elementType); |
| loopBody = new Block(<Statement>[ |
| castedVar, |
| new ExpressionStatement(new MethodInvocation( |
| new VariableGet(result), |
| new Name('add'), |
| new Arguments([new VariableGet(castedVar)]), |
| isSet ? setAdd : listAdd)) |
| ]); |
| } else { |
| elt = new VariableDeclaration(null, type: elementType, isFinal: true); |
| loopBody = new ExpressionStatement(new MethodInvocation( |
| new VariableGet(result), |
| new Name('add'), |
| new Arguments([new VariableGet(elt)]), |
| isSet ? setAdd : listAdd)); |
| } |
| Statement statement = new ForInStatement(elt, value, loopBody); |
| |
| if (element.isNullAware) { |
| statement = new IfStatement( |
| new Not(new MethodInvocation(new VariableGet(temp), new Name('=='), |
| new Arguments([new NullLiteral()]), objectEquals)), |
| statement, |
| null); |
| } |
| body.add(statement); |
| } |
| |
| @override |
| TreeNode visitListLiteral(ListLiteral node) { |
| // Const collections are handled by the constant evaluator. |
| if (node.isConst) return node; |
| |
| return _translateListOrSet(node, node.typeArgument, node.expressions, |
| isSet: false); |
| } |
| |
| @override |
| TreeNode visitSetLiteral(SetLiteral node) { |
| // Const collections are handled by the constant evaluator. |
| if (node.isConst) return node; |
| |
| return _translateListOrSet(node, node.typeArgument, node.expressions, |
| isSet: true); |
| } |
| |
| @override |
| TreeNode visitMapLiteral(MapLiteral node) { |
| // Const collections are handled by the constant evaluator. |
| if (node.isConst) return node; |
| |
| // Translate entries in place up to the first control-flow entry, if any. |
| int i = 0; |
| for (; i < node.entries.length; ++i) { |
| if (node.entries[i] is ControlFlowMapEntry) break; |
| node.entries[i] = node.entries[i].accept(this)..parent = node; |
| } |
| |
| // If there were no control-flow entries we are done. |
| if (i == node.entries.length) return node; |
| |
| // Build a block expression and create an empty map. |
| VariableDeclaration result = new VariableDeclaration.forValue( |
| new MapLiteral([], keyType: node.keyType, valueType: node.valueType), |
| type: new InterfaceType( |
| coreTypes.mapClass, [node.keyType, node.valueType]), |
| isFinal: true); |
| List<Statement> body = [result]; |
| // Add all the entries up to the first control-flow entry. |
| for (int j = 0; j < i; ++j) { |
| _addNormalEntry(node.entries[j], result, body); |
| } |
| for (; i < node.entries.length; ++i) { |
| _translateEntry( |
| node.entries[i], node.keyType, node.valueType, result, body); |
| } |
| |
| return new BlockExpression(new Block(body), new VariableGet(result)); |
| } |
| |
| void _translateEntry(MapEntry entry, DartType keyType, DartType valueType, |
| VariableDeclaration result, List<Statement> body) { |
| if (entry is SpreadMapEntry) { |
| _translateSpreadEntry(entry, keyType, valueType, result, body); |
| } else if (entry is IfMapEntry) { |
| _translateIfEntry(entry, keyType, valueType, result, body); |
| } else if (entry is ForMapEntry) { |
| _translateForEntry(entry, keyType, valueType, result, body); |
| } else if (entry is ForInMapEntry) { |
| _translateForInEntry(entry, keyType, valueType, result, body); |
| } else { |
| _addNormalEntry(entry.accept(this), result, body); |
| } |
| } |
| |
| void _addNormalEntry( |
| MapEntry entry, VariableDeclaration result, List<Statement> body) { |
| body.add(new ExpressionStatement(new MethodInvocation( |
| new VariableGet(result), |
| new Name('[]='), |
| new Arguments([entry.key, entry.value]), |
| mapPut))); |
| } |
| |
| void _translateIfEntry(IfMapEntry entry, DartType keyType, DartType valueType, |
| VariableDeclaration result, List<Statement> body) { |
| List<Statement> thenBody = []; |
| _translateEntry(entry.then, keyType, valueType, result, thenBody); |
| List<Statement> elseBody; |
| if (entry.otherwise != null) { |
| _translateEntry(entry.otherwise, keyType, valueType, result, |
| elseBody = <Statement>[]); |
| } |
| Statement thenStatement = |
| thenBody.length == 1 ? thenBody.first : new Block(thenBody); |
| Statement elseStatement; |
| if (elseBody != null && elseBody.isNotEmpty) { |
| elseStatement = |
| elseBody.length == 1 ? elseBody.first : new Block(elseBody); |
| } |
| body.add(new IfStatement( |
| entry.condition.accept(this), thenStatement, elseStatement)); |
| } |
| |
| void _translateForEntry(ForMapEntry entry, DartType keyType, |
| DartType valueType, VariableDeclaration result, List<Statement> body) { |
| List<Statement> statements = <Statement>[]; |
| _translateEntry(entry.body, keyType, valueType, result, statements); |
| Statement loopBody = |
| statements.length == 1 ? statements.first : new Block(statements); |
| ForStatement loop = new ForStatement( |
| entry.variables, entry.condition?.accept(this), entry.updates, loopBody) |
| ..fileOffset = entry.fileOffset; |
| transformList(loop.variables, this, loop); |
| transformList(loop.updates, this, loop); |
| body.add(loop); |
| } |
| |
| void _translateForInEntry(ForInMapEntry entry, DartType keyType, |
| DartType valueType, VariableDeclaration result, List<Statement> body) { |
| List<Statement> statements; |
| Statement prologue = entry.prologue; |
| if (prologue == null) { |
| statements = <Statement>[]; |
| } else { |
| prologue = prologue.accept(this); |
| statements = |
| prologue is Block ? prologue.statements : <Statement>[prologue]; |
| } |
| _translateEntry(entry.body, keyType, valueType, result, statements); |
| Statement loopBody = |
| statements.length == 1 ? statements.first : new Block(statements); |
| if (entry.problem != null) { |
| body.add(new ExpressionStatement(entry.problem.accept(this))); |
| } |
| body.add(new ForInStatement( |
| entry.variable, entry.iterable.accept(this), loopBody, |
| isAsync: entry.isAsync) |
| ..fileOffset = entry.fileOffset); |
| } |
| |
| void _translateSpreadEntry(SpreadMapEntry entry, DartType keyType, |
| DartType valueType, VariableDeclaration result, List<Statement> body) { |
| Expression value = entry.expression.accept(this); |
| // Null-aware spreads require testing the subexpression's value. |
| VariableDeclaration temp; |
| if (entry.isNullAware) { |
| temp = new VariableDeclaration.forValue(value, |
| type: coreTypes.mapClass.rawType); |
| body.add(temp); |
| value = new VariableGet(temp); |
| } |
| |
| DartType entryType = |
| new InterfaceType(mapEntryClass, <DartType>[keyType, valueType]); |
| VariableDeclaration elt; |
| Statement loopBody; |
| if (entry.entryType == null || |
| !typeEnvironment.isSubtypeOf(entry.entryType, entryType)) { |
| elt = new VariableDeclaration(null, |
| type: new InterfaceType(mapEntryClass, |
| <DartType>[const DynamicType(), const DynamicType()]), |
| isFinal: true); |
| VariableDeclaration keyVar = new VariableDeclaration.forValue( |
| new AsExpression( |
| new PropertyGet( |
| new VariableGet(elt), new Name('key'), mapEntryKey), |
| keyType) |
| ..isTypeError = true |
| ..fileOffset = entry.expression.fileOffset, |
| type: keyType); |
| VariableDeclaration valueVar = new VariableDeclaration.forValue( |
| new AsExpression( |
| new PropertyGet( |
| new VariableGet(elt), new Name('value'), mapEntryValue), |
| valueType) |
| ..isTypeError = true |
| ..fileOffset = entry.expression.fileOffset, |
| type: valueType); |
| loopBody = new Block(<Statement>[ |
| keyVar, |
| valueVar, |
| new ExpressionStatement(new MethodInvocation( |
| new VariableGet(result), |
| new Name('[]='), |
| new Arguments([new VariableGet(keyVar), new VariableGet(valueVar)]), |
| mapPut)) |
| ]); |
| } else { |
| elt = new VariableDeclaration(null, type: entryType, isFinal: true); |
| loopBody = new ExpressionStatement(new MethodInvocation( |
| new VariableGet(result), |
| new Name('[]='), |
| new Arguments([ |
| new PropertyGet(new VariableGet(elt), new Name('key'), mapEntryKey), |
| new PropertyGet( |
| new VariableGet(elt), new Name('value'), mapEntryValue) |
| ]), |
| mapPut)); |
| } |
| Statement statement = new ForInStatement( |
| elt, new PropertyGet(value, new Name('entries'), mapEntries), loopBody); |
| |
| if (entry.isNullAware) { |
| statement = new IfStatement( |
| new Not(new MethodInvocation(new VariableGet(temp), new Name('=='), |
| new Arguments([new NullLiteral()]), objectEquals)), |
| statement, |
| null); |
| } |
| body.add(statement); |
| } |
| } |