| // 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. |
| |
| // @dart = 2.9 |
| |
| library fasta.transform_collections; |
| |
| import 'package:kernel/ast.dart'; |
| |
| import 'package:kernel/core_types.dart' show CoreTypes; |
| import 'package:kernel/src/legacy_erasure.dart'; |
| import 'package:kernel/type_algebra.dart'; |
| |
| import 'package:kernel/type_environment.dart' |
| show SubtypeCheckMode, TypeEnvironment; |
| |
| import 'package:kernel/visitor.dart' show Transformer; |
| |
| import 'collections.dart' |
| show |
| ControlFlowElement, |
| ControlFlowMapEntry, |
| ForElement, |
| ForInElement, |
| ForInMapEntry, |
| ForMapEntry, |
| IfElement, |
| IfMapEntry, |
| SpreadElement, |
| SpreadMapEntry; |
| |
| import '../problems.dart' show getFileUri, unhandled; |
| |
| import '../source/source_loader.dart'; |
| |
| import 'redirecting_factory_body.dart' show RedirectingFactoryBody; |
| |
| class CollectionTransformer extends Transformer { |
| final SourceLoader _loader; |
| final TypeEnvironment _typeEnvironment; |
| final Procedure _listAdd; |
| FunctionType _listAddFunctionType; |
| final Procedure _listAddAll; |
| FunctionType _listAddAllFunctionType; |
| final Procedure _listOf; |
| final Procedure _setFactory; |
| final Procedure _setAdd; |
| FunctionType _setAddFunctionType; |
| final Procedure _setAddAll; |
| FunctionType _setAddAllFunctionType; |
| final Procedure _setOf; |
| final Procedure _objectEquals; |
| final Procedure _mapEntries; |
| final Procedure _mapPut; |
| FunctionType _mapPutFunctionType; |
| final Class _mapEntryClass; |
| final Field _mapEntryKey; |
| final Field _mapEntryValue; |
| final SourceLoaderDataForTesting _dataForTesting; |
| final bool useNewMethodInvocationEncoding; |
| |
| /// Library that contains the transformed nodes. |
| /// |
| /// The transformation of the nodes is affected by the NNBD opt-in status of |
| /// the library. |
| Library _currentLibrary; |
| |
| static Procedure _findSetFactory(CoreTypes coreTypes, String name) { |
| Procedure factory = coreTypes.index.getMember('dart:core', 'Set', name); |
| RedirectingFactoryBody body = factory?.function?.body; |
| return body?.target; |
| } |
| |
| CollectionTransformer(this._loader) |
| : _typeEnvironment = _loader.typeInferenceEngine.typeSchemaEnvironment, |
| _listAdd = |
| _loader.coreTypes.index.getMember('dart:core', 'List', 'add'), |
| _listAddAll = |
| _loader.coreTypes.index.getMember('dart:core', 'List', 'addAll'), |
| _listOf = _loader.coreTypes.index.getMember('dart:core', 'List', 'of'), |
| _setFactory = _findSetFactory(_loader.coreTypes, ''), |
| _setAdd = _loader.coreTypes.index.getMember('dart:core', 'Set', 'add'), |
| _setAddAll = |
| _loader.coreTypes.index.getMember('dart:core', 'Set', 'addAll'), |
| _setOf = _findSetFactory(_loader.coreTypes, 'of'), |
| _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'), |
| _dataForTesting = _loader.dataForTesting, |
| useNewMethodInvocationEncoding = |
| _loader.target.backendTarget.supportsNewMethodInvocationEncoding { |
| _listAddFunctionType = _listAdd.getterType; |
| _listAddAllFunctionType = _listAddAll.getterType; |
| _setAddFunctionType = _setAdd.getterType; |
| _setAddAllFunctionType = _setAddAll.getterType; |
| _mapPutFunctionType = _mapPut.getterType; |
| } |
| |
| 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 index = 0; |
| for (; index < elements.length; ++index) { |
| if (elements[index] is ControlFlowElement) break; |
| elements[index] = elements[index].accept<TreeNode>(this)..parent = node; |
| } |
| |
| // If there were only expressions, we are done. |
| if (index == elements.length) return node; |
| |
| InterfaceType receiverType = isSet |
| ? _typeEnvironment.setType(elementType, _currentLibrary.nonNullable) |
| : _typeEnvironment.listType(elementType, _currentLibrary.nonNullable); |
| VariableDeclaration result; |
| if (index == 0 && elements[index] is SpreadElement) { |
| SpreadElement initialSpread = elements[index]; |
| final bool typeMatches = initialSpread.elementType != null && |
| _typeEnvironment.isSubtypeOf(initialSpread.elementType, elementType, |
| SubtypeCheckMode.withNullabilities); |
| if (typeMatches && !initialSpread.isNullAware) { |
| // Create a list or set of the initial spread element. |
| Expression value = initialSpread.expression.accept<TreeNode>(this); |
| index++; |
| if (isSet) { |
| result = _createVariable( |
| new StaticInvocation( |
| _setOf, |
| new Arguments([value], types: [elementType]) |
| ..fileOffset = node.fileOffset) |
| ..fileOffset = node.fileOffset, |
| receiverType); |
| } else { |
| result = _createVariable( |
| new StaticInvocation( |
| _listOf, |
| new Arguments([value], types: [elementType]) |
| ..fileOffset = node.fileOffset) |
| ..fileOffset = node.fileOffset, |
| receiverType); |
| } |
| } |
| } |
| List<Statement> body; |
| if (result == null) { |
| // Create a list or set with the elements up to the first non-expression. |
| if (isSet) { |
| if (_loader.target.backendTarget.supportsSetLiterals) { |
| // Include the elements up to the first non-expression in the set |
| // literal. |
| result = _createVariable( |
| _createSetLiteral( |
| node.fileOffset, elementType, elements.sublist(0, index)), |
| receiverType); |
| } else { |
| // TODO(johnniwinther): When all the back ends handle set literals we |
| // can use remove this branch. |
| |
| // Create an empty set using the [setFactory] constructor. |
| result = _createVariable( |
| new StaticInvocation( |
| _setFactory, |
| new Arguments([], types: [elementType]) |
| ..fileOffset = node.fileOffset) |
| ..fileOffset = node.fileOffset, |
| receiverType); |
| body = [result]; |
| // Add the elements up to the first non-expression. |
| for (int j = 0; j < index; ++j) { |
| _addExpressionElement( |
| elements[j], receiverType, isSet, result, body); |
| } |
| } |
| } else { |
| // Include the elements up to the first non-expression in the list |
| // literal. |
| result = _createVariable( |
| _createListLiteral( |
| node.fileOffset, elementType, elements.sublist(0, index)), |
| receiverType); |
| } |
| } |
| body ??= [result]; |
| // Translate the elements starting with the first non-expression. |
| for (; index < elements.length; ++index) { |
| _translateElement( |
| elements[index], receiverType, elementType, isSet, result, body); |
| } |
| |
| return _createBlockExpression( |
| node.fileOffset, _createBlock(body), _createVariableGet(result)); |
| } |
| |
| void _translateElement( |
| Expression element, |
| InterfaceType receiverType, |
| DartType elementType, |
| bool isSet, |
| VariableDeclaration result, |
| List<Statement> body) { |
| if (element is SpreadElement) { |
| _translateSpreadElement( |
| element, receiverType, elementType, isSet, result, body); |
| } else if (element is IfElement) { |
| _translateIfElement( |
| element, receiverType, elementType, isSet, result, body); |
| } else if (element is ForElement) { |
| _translateForElement( |
| element, receiverType, elementType, isSet, result, body); |
| } else if (element is ForInElement) { |
| _translateForInElement( |
| element, receiverType, elementType, isSet, result, body); |
| } else { |
| _addExpressionElement( |
| element.accept<TreeNode>(this), receiverType, isSet, result, body); |
| } |
| } |
| |
| void _addExpressionElement(Expression element, InterfaceType receiverType, |
| bool isSet, VariableDeclaration result, List<Statement> body) { |
| body.add(_createExpressionStatement( |
| _createAdd(_createVariableGet(result), receiverType, element, isSet))); |
| } |
| |
| void _translateIfElement( |
| IfElement element, |
| InterfaceType receiverType, |
| DartType elementType, |
| bool isSet, |
| VariableDeclaration result, |
| List<Statement> body) { |
| List<Statement> thenStatements = []; |
| _translateElement( |
| element.then, receiverType, elementType, isSet, result, thenStatements); |
| List<Statement> elseStatements; |
| if (element.otherwise != null) { |
| _translateElement(element.otherwise, receiverType, elementType, isSet, |
| result, elseStatements = <Statement>[]); |
| } |
| Statement thenBody = thenStatements.length == 1 |
| ? thenStatements.first |
| : _createBlock(thenStatements); |
| Statement elseBody; |
| if (elseStatements != null && elseStatements.isNotEmpty) { |
| elseBody = elseStatements.length == 1 |
| ? elseStatements.first |
| : _createBlock(elseStatements); |
| } |
| IfStatement ifStatement = _createIf(element.fileOffset, |
| element.condition.accept<TreeNode>(this), thenBody, elseBody); |
| _dataForTesting?.registerAlias(element, ifStatement); |
| body.add(ifStatement); |
| } |
| |
| void _translateForElement( |
| ForElement element, |
| InterfaceType receiverType, |
| DartType elementType, |
| bool isSet, |
| VariableDeclaration result, |
| List<Statement> body) { |
| List<Statement> statements = <Statement>[]; |
| _translateElement( |
| element.body, receiverType, elementType, isSet, result, statements); |
| Statement loopBody = |
| statements.length == 1 ? statements.first : _createBlock(statements); |
| ForStatement loop = _createForStatement( |
| element.fileOffset, |
| element.variables, |
| element.condition?.accept<TreeNode>(this), |
| element.updates, |
| loopBody); |
| transformList(loop.variables, loop); |
| transformList(loop.updates, loop); |
| _dataForTesting?.registerAlias(element, loop); |
| body.add(loop); |
| } |
| |
| void _translateForInElement( |
| ForInElement element, |
| InterfaceType receiverType, |
| 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<TreeNode>(this); |
| statements = |
| prologue is Block ? prologue.statements : <Statement>[prologue]; |
| } |
| _translateElement( |
| element.body, receiverType, elementType, isSet, result, statements); |
| Statement loopBody = |
| statements.length == 1 ? statements.first : _createBlock(statements); |
| if (element.problem != null) { |
| body.add( |
| _createExpressionStatement(element.problem.accept<TreeNode>(this))); |
| } |
| ForInStatement loop = _createForInStatement(element.fileOffset, |
| element.variable, element.iterable.accept<TreeNode>(this), loopBody, |
| isAsync: element.isAsync); |
| _dataForTesting?.registerAlias(element, loop); |
| body.add(loop); |
| } |
| |
| void _translateSpreadElement( |
| SpreadElement element, |
| InterfaceType receiverType, |
| DartType elementType, |
| bool isSet, |
| VariableDeclaration result, |
| List<Statement> body) { |
| Expression value = element.expression.accept<TreeNode>(this); |
| |
| final bool typeMatches = element.elementType != null && |
| _typeEnvironment.isSubtypeOf(element.elementType, elementType, |
| SubtypeCheckMode.withNullabilities); |
| if (typeMatches) { |
| // If the type guarantees that all elements are of the required type, use |
| // a single 'addAll' call instead of a for-loop with calls to 'add'. |
| |
| // Null-aware spreads require testing the subexpression's value. |
| VariableDeclaration temp; |
| if (element.isNullAware) { |
| temp = _createVariable( |
| value, |
| _typeEnvironment.iterableType( |
| typeMatches ? elementType : const DynamicType(), |
| _currentLibrary.nullable)); |
| body.add(temp); |
| value = _createNullCheckedVariableGet(temp); |
| } |
| |
| Statement statement = _createExpressionStatement(_createAddAll( |
| _createVariableGet(result), receiverType, value, isSet)); |
| |
| if (element.isNullAware) { |
| statement = _createIf( |
| temp.fileOffset, |
| _createEqualsNull(_createVariableGet(temp), notEquals: true), |
| statement); |
| } |
| body.add(statement); |
| } else { |
| // Null-aware spreads require testing the subexpression's value. |
| VariableDeclaration temp; |
| if (element.isNullAware) { |
| temp = _createVariable( |
| value, |
| _typeEnvironment.iterableType( |
| typeMatches ? elementType : const DynamicType(), |
| _currentLibrary.nullable)); |
| body.add(temp); |
| value = _createNullCheckedVariableGet(temp); |
| } |
| |
| VariableDeclaration variable; |
| Statement loopBody; |
| if (!typeMatches) { |
| variable = |
| _createForInVariable(element.fileOffset, const DynamicType()); |
| VariableDeclaration castedVar = _createVariable( |
| _createImplicitAs(element.expression.fileOffset, |
| _createVariableGet(variable), elementType), |
| elementType); |
| loopBody = _createBlock(<Statement>[ |
| castedVar, |
| _createExpressionStatement(_createAdd(_createVariableGet(result), |
| receiverType, _createVariableGet(castedVar), isSet)) |
| ]); |
| } else { |
| variable = _createForInVariable(element.fileOffset, elementType); |
| loopBody = _createExpressionStatement(_createAdd( |
| _createVariableGet(result), |
| receiverType, |
| _createVariableGet(variable), |
| isSet)); |
| } |
| Statement statement = |
| _createForInStatement(element.fileOffset, variable, value, loopBody); |
| |
| if (element.isNullAware) { |
| statement = _createIf( |
| temp.fileOffset, |
| _createEqualsNull(_createVariableGet(temp), notEquals: true), |
| statement); |
| } |
| body.add(statement); |
| } |
| } |
| |
| @override |
| TreeNode visitListLiteral(ListLiteral node) { |
| if (node.isConst) { |
| return _translateConstListOrSet(node, node.typeArgument, node.expressions, |
| isSet: false); |
| } |
| |
| return _translateListOrSet(node, node.typeArgument, node.expressions, |
| isSet: false); |
| } |
| |
| @override |
| TreeNode visitSetLiteral(SetLiteral node) { |
| if (node.isConst) { |
| return _translateConstListOrSet(node, node.typeArgument, node.expressions, |
| isSet: true); |
| } |
| |
| return _translateListOrSet(node, node.typeArgument, node.expressions, |
| isSet: true); |
| } |
| |
| @override |
| TreeNode visitMapLiteral(MapLiteral node) { |
| if (node.isConst) { |
| return _translateConstMap(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<TreeNode>(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. |
| InterfaceType receiverType = _typeEnvironment.mapType( |
| node.keyType, node.valueType, _currentLibrary.nonNullable); |
| VariableDeclaration result = _createVariable( |
| _createMapLiteral(node.fileOffset, node.keyType, node.valueType, []), |
| receiverType); |
| 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], receiverType, result, body); |
| } |
| for (; i < node.entries.length; ++i) { |
| _translateEntry(node.entries[i], receiverType, node.keyType, |
| node.valueType, result, body); |
| } |
| |
| return _createBlockExpression( |
| node.fileOffset, _createBlock(body), _createVariableGet(result)); |
| } |
| |
| void _translateEntry( |
| MapEntry entry, |
| InterfaceType receiverType, |
| DartType keyType, |
| DartType valueType, |
| VariableDeclaration result, |
| List<Statement> body) { |
| if (entry is SpreadMapEntry) { |
| _translateSpreadEntry( |
| entry, receiverType, keyType, valueType, result, body); |
| } else if (entry is IfMapEntry) { |
| _translateIfEntry(entry, receiverType, keyType, valueType, result, body); |
| } else if (entry is ForMapEntry) { |
| _translateForEntry(entry, receiverType, keyType, valueType, result, body); |
| } else if (entry is ForInMapEntry) { |
| _translateForInEntry( |
| entry, receiverType, keyType, valueType, result, body); |
| } else { |
| _addNormalEntry(entry.accept<TreeNode>(this), receiverType, result, body); |
| } |
| } |
| |
| void _addNormalEntry(MapEntry entry, InterfaceType receiverType, |
| VariableDeclaration result, List<Statement> body) { |
| body.add(_createExpressionStatement(_createIndexSet(entry.fileOffset, |
| _createVariableGet(result), receiverType, entry.key, entry.value))); |
| } |
| |
| void _translateIfEntry( |
| IfMapEntry entry, |
| InterfaceType receiverType, |
| DartType keyType, |
| DartType valueType, |
| VariableDeclaration result, |
| List<Statement> body) { |
| List<Statement> thenBody = []; |
| _translateEntry( |
| entry.then, receiverType, keyType, valueType, result, thenBody); |
| List<Statement> elseBody; |
| if (entry.otherwise != null) { |
| _translateEntry(entry.otherwise, receiverType, keyType, valueType, result, |
| elseBody = <Statement>[]); |
| } |
| Statement thenStatement = |
| thenBody.length == 1 ? thenBody.first : _createBlock(thenBody); |
| Statement elseStatement; |
| if (elseBody != null && elseBody.isNotEmpty) { |
| elseStatement = |
| elseBody.length == 1 ? elseBody.first : _createBlock(elseBody); |
| } |
| IfStatement ifStatement = _createIf(entry.fileOffset, |
| entry.condition.accept<TreeNode>(this), thenStatement, elseStatement); |
| _dataForTesting?.registerAlias(entry, ifStatement); |
| body.add(ifStatement); |
| } |
| |
| void _translateForEntry( |
| ForMapEntry entry, |
| InterfaceType receiverType, |
| DartType keyType, |
| DartType valueType, |
| VariableDeclaration result, |
| List<Statement> body) { |
| List<Statement> statements = <Statement>[]; |
| _translateEntry( |
| entry.body, receiverType, keyType, valueType, result, statements); |
| Statement loopBody = |
| statements.length == 1 ? statements.first : _createBlock(statements); |
| ForStatement loop = _createForStatement(entry.fileOffset, entry.variables, |
| entry.condition?.accept<TreeNode>(this), entry.updates, loopBody); |
| _dataForTesting?.registerAlias(entry, loop); |
| transformList(loop.variables, loop); |
| transformList(loop.updates, loop); |
| body.add(loop); |
| } |
| |
| void _translateForInEntry( |
| ForInMapEntry entry, |
| InterfaceType receiverType, |
| 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<TreeNode>(this); |
| statements = |
| prologue is Block ? prologue.statements : <Statement>[prologue]; |
| } |
| _translateEntry( |
| entry.body, receiverType, keyType, valueType, result, statements); |
| Statement loopBody = |
| statements.length == 1 ? statements.first : _createBlock(statements); |
| if (entry.problem != null) { |
| body.add( |
| _createExpressionStatement(entry.problem.accept<TreeNode>(this))); |
| } |
| ForInStatement loop = _createForInStatement(entry.fileOffset, |
| entry.variable, entry.iterable.accept<TreeNode>(this), loopBody, |
| isAsync: entry.isAsync); |
| _dataForTesting?.registerAlias(entry, loop); |
| body.add(loop); |
| } |
| |
| void _translateSpreadEntry( |
| SpreadMapEntry entry, |
| InterfaceType receiverType, |
| DartType keyType, |
| DartType valueType, |
| VariableDeclaration result, |
| List<Statement> body) { |
| Expression value = entry.expression.accept<TreeNode>(this); |
| |
| final DartType entryType = new InterfaceType(_mapEntryClass, |
| _currentLibrary.nonNullable, <DartType>[keyType, valueType]); |
| final bool typeMatches = entry.entryType != null && |
| _typeEnvironment.isSubtypeOf( |
| entry.entryType, entryType, SubtypeCheckMode.withNullabilities); |
| |
| // Null-aware spreads require testing the subexpression's value. |
| VariableDeclaration temp; |
| if (entry.isNullAware) { |
| temp = _createVariable( |
| value, |
| _typeEnvironment.mapType( |
| typeMatches ? keyType : const DynamicType(), |
| typeMatches ? valueType : const DynamicType(), |
| _currentLibrary.nullable)); |
| body.add(temp); |
| value = _createNullCheckedVariableGet(temp); |
| } |
| |
| VariableDeclaration variable; |
| Statement loopBody; |
| if (!typeMatches) { |
| variable = _createForInVariable( |
| entry.fileOffset, |
| new InterfaceType(_mapEntryClass, _currentLibrary.nonNullable, |
| <DartType>[const DynamicType(), const DynamicType()])); |
| VariableDeclaration keyVar = _createVariable( |
| _createImplicitAs( |
| entry.expression.fileOffset, |
| _createGetKey( |
| entry.expression.fileOffset, _createVariableGet(variable)), |
| keyType), |
| keyType); |
| VariableDeclaration valueVar = _createVariable( |
| _createImplicitAs( |
| entry.expression.fileOffset, |
| _createGetValue( |
| entry.expression.fileOffset, _createVariableGet(variable)), |
| valueType), |
| valueType); |
| loopBody = _createBlock(<Statement>[ |
| keyVar, |
| valueVar, |
| _createExpressionStatement(_createIndexSet( |
| entry.expression.fileOffset, |
| _createVariableGet(result), |
| receiverType, |
| _createVariableGet(keyVar), |
| _createVariableGet(valueVar))) |
| ]); |
| } else { |
| variable = _createForInVariable(entry.fileOffset, entryType); |
| loopBody = _createExpressionStatement(_createIndexSet( |
| entry.expression.fileOffset, |
| _createVariableGet(result), |
| receiverType, |
| _createGetKey( |
| entry.expression.fileOffset, _createVariableGet(variable)), |
| _createGetValue( |
| entry.expression.fileOffset, _createVariableGet(variable)))); |
| } |
| Statement statement = _createForInStatement(entry.fileOffset, variable, |
| _createGetEntries(entry.fileOffset, value), loopBody); |
| |
| if (entry.isNullAware) { |
| statement = _createIf( |
| temp.fileOffset, |
| _createEqualsNull(_createVariableGet(temp), notEquals: true), |
| statement); |
| } |
| body.add(statement); |
| } |
| |
| TreeNode _translateConstListOrSet( |
| 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<TreeNode>(this)..parent = node; |
| } |
| |
| // If there were only expressions, we are done. |
| if (i == elements.length) return node; |
| |
| Expression makeLiteral(int fileOffset, List<Expression> expressions) { |
| if (isSet) { |
| return _createSetLiteral(fileOffset, elementType, expressions, |
| isConst: true); |
| } else { |
| return _createListLiteral(fileOffset, elementType, expressions, |
| isConst: true); |
| } |
| } |
| |
| // Build a concatenation node. |
| List<Expression> parts = []; |
| List<Expression> currentPart = i > 0 ? elements.sublist(0, i) : null; |
| |
| DartType iterableType = |
| _typeEnvironment.iterableType(elementType, _currentLibrary.nonNullable); |
| |
| for (; i < elements.length; ++i) { |
| Expression element = elements[i]; |
| if (element is SpreadElement) { |
| if (currentPart != null) { |
| parts.add(makeLiteral(node.fileOffset, currentPart)); |
| currentPart = null; |
| } |
| Expression spreadExpression = element.expression.accept<TreeNode>(this); |
| if (element.isNullAware) { |
| VariableDeclaration temp = _createVariable( |
| spreadExpression, |
| _typeEnvironment.iterableType( |
| elementType, _currentLibrary.nullable)); |
| parts.add(_createNullAwareGuard(element.fileOffset, temp, |
| makeLiteral(element.fileOffset, []), iterableType)); |
| } else { |
| parts.add(spreadExpression); |
| } |
| } else if (element is IfElement) { |
| if (currentPart != null) { |
| parts.add(makeLiteral(node.fileOffset, currentPart)); |
| currentPart = null; |
| } |
| Expression condition = element.condition.accept<TreeNode>(this); |
| Expression then = makeLiteral(element.then.fileOffset, [element.then]) |
| .accept<TreeNode>(this); |
| Expression otherwise = element.otherwise != null |
| ? makeLiteral(element.otherwise.fileOffset, [element.otherwise]) |
| .accept<TreeNode>(this) |
| : makeLiteral(element.fileOffset, []); |
| parts.add(_createConditionalExpression( |
| element.fileOffset, condition, then, otherwise, iterableType)); |
| } else if (element is ForElement || element is ForInElement) { |
| // Rejected earlier. |
| unhandled("${element.runtimeType}", "_translateConstListOrSet", |
| element.fileOffset, getFileUri(element)); |
| } else { |
| currentPart ??= <Expression>[]; |
| currentPart.add(element.accept<TreeNode>(this)); |
| } |
| } |
| if (currentPart != null) { |
| parts.add(makeLiteral(node.fileOffset, currentPart)); |
| } |
| if (isSet) { |
| return new SetConcatenation(parts, typeArgument: elementType) |
| ..fileOffset = node.fileOffset; |
| } else { |
| return new ListConcatenation(parts, typeArgument: elementType) |
| ..fileOffset = node.fileOffset; |
| } |
| } |
| |
| TreeNode _translateConstMap(MapLiteral 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<TreeNode>(this)..parent = node; |
| } |
| |
| // If there were no control-flow entries we are done. |
| if (i == node.entries.length) return node; |
| |
| MapLiteral makeLiteral(int fileOffset, List<MapEntry> entries) { |
| return _createMapLiteral( |
| fileOffset, node.keyType, node.valueType, entries, |
| isConst: true); |
| } |
| |
| // Build a concatenation node. |
| List<Expression> parts = []; |
| List<MapEntry> currentPart = i > 0 ? node.entries.sublist(0, i) : null; |
| |
| DartType collectionType = _typeEnvironment.mapType( |
| node.keyType, node.valueType, _currentLibrary.nonNullable); |
| |
| for (; i < node.entries.length; ++i) { |
| MapEntry entry = node.entries[i]; |
| if (entry is SpreadMapEntry) { |
| if (currentPart != null) { |
| parts.add(makeLiteral(node.fileOffset, currentPart)); |
| currentPart = null; |
| } |
| Expression spreadExpression = entry.expression.accept<TreeNode>(this); |
| if (entry.isNullAware) { |
| VariableDeclaration temp = _createVariable(spreadExpression, |
| collectionType.withDeclaredNullability(_currentLibrary.nullable)); |
| parts.add(_createNullAwareGuard(entry.fileOffset, temp, |
| makeLiteral(entry.fileOffset, []), collectionType)); |
| } else { |
| parts.add(spreadExpression); |
| } |
| } else if (entry is IfMapEntry) { |
| if (currentPart != null) { |
| parts.add(makeLiteral(node.fileOffset, currentPart)); |
| currentPart = null; |
| } |
| Expression condition = entry.condition.accept<TreeNode>(this); |
| Expression then = makeLiteral(entry.then.fileOffset, [entry.then]) |
| .accept<TreeNode>(this); |
| Expression otherwise = entry.otherwise != null |
| ? makeLiteral(entry.otherwise.fileOffset, [entry.otherwise]) |
| .accept<TreeNode>(this) |
| : makeLiteral(node.fileOffset, []); |
| parts.add(_createConditionalExpression( |
| entry.fileOffset, condition, then, otherwise, collectionType)); |
| } else if (entry is ForMapEntry || entry is ForInMapEntry) { |
| // Rejected earlier. |
| unhandled("${entry.runtimeType}", "_translateConstMap", |
| entry.fileOffset, getFileUri(entry)); |
| } else { |
| currentPart ??= <MapEntry>[]; |
| currentPart.add(entry.accept<TreeNode>(this)); |
| } |
| } |
| if (currentPart != null) { |
| parts.add(makeLiteral(node.fileOffset, currentPart)); |
| } |
| return new MapConcatenation(parts, |
| keyType: node.keyType, valueType: node.valueType); |
| } |
| |
| void enterLibrary(Library library) { |
| assert( |
| _currentLibrary == null, |
| "Attempting to enter library '${library.fileUri}' " |
| "without having exited library '${_currentLibrary.fileUri}'."); |
| _currentLibrary = library; |
| } |
| |
| void exitLibrary() { |
| assert(_currentLibrary != null, |
| "Attempting to exit a library without having entered one."); |
| _currentLibrary = null; |
| } |
| |
| VariableDeclaration _createVariable(Expression expression, DartType type) { |
| assert(expression != null); |
| assert(expression.fileOffset != TreeNode.noOffset); |
| return new VariableDeclaration.forValue(expression, type: type) |
| ..fileOffset = expression.fileOffset; |
| } |
| |
| VariableDeclaration _createForInVariable(int fileOffset, DartType type) { |
| assert(fileOffset != TreeNode.noOffset); |
| return new VariableDeclaration.forValue(null, type: type) |
| ..fileOffset = fileOffset; |
| } |
| |
| VariableGet _createVariableGet(VariableDeclaration variable) { |
| assert(variable != null); |
| assert(variable.fileOffset != TreeNode.noOffset); |
| return new VariableGet(variable)..fileOffset = variable.fileOffset; |
| } |
| |
| VariableGet _createNullCheckedVariableGet(VariableDeclaration variable) { |
| assert(variable != null); |
| assert(variable.fileOffset != TreeNode.noOffset); |
| DartType promotedType = |
| variable.type.withDeclaredNullability(_currentLibrary.nonNullable); |
| if (promotedType != variable.type) { |
| return new VariableGet(variable, promotedType) |
| ..fileOffset = variable.fileOffset; |
| } |
| return _createVariableGet(variable); |
| } |
| |
| MapLiteral _createMapLiteral(int fileOffset, DartType keyType, |
| DartType valueType, List<MapEntry> entries, |
| {bool isConst: false}) { |
| assert(fileOffset != null); |
| assert(fileOffset != TreeNode.noOffset); |
| return new MapLiteral(entries, |
| keyType: keyType, valueType: valueType, isConst: isConst) |
| ..fileOffset = fileOffset; |
| } |
| |
| ListLiteral _createListLiteral( |
| int fileOffset, DartType elementType, List<Expression> elements, |
| {bool isConst: false}) { |
| assert(fileOffset != null); |
| assert(fileOffset != TreeNode.noOffset); |
| return new ListLiteral(elements, |
| typeArgument: elementType, isConst: isConst) |
| ..fileOffset = fileOffset; |
| } |
| |
| Expression _createSetLiteral( |
| int fileOffset, DartType elementType, List<Expression> elements, |
| {bool isConst: false}) { |
| assert(fileOffset != null); |
| assert(fileOffset != TreeNode.noOffset); |
| if (isConst) { |
| return new SetLiteral(elements, |
| typeArgument: elementType, isConst: isConst) |
| ..fileOffset = fileOffset; |
| } else { |
| return new SetLiteral(elements, |
| typeArgument: elementType, isConst: isConst) |
| ..fileOffset = fileOffset; |
| } |
| } |
| |
| ExpressionStatement _createExpressionStatement(Expression expression) { |
| assert(expression != null); |
| assert(expression.fileOffset != TreeNode.noOffset); |
| return new ExpressionStatement(expression) |
| ..fileOffset = expression.fileOffset; |
| } |
| |
| Expression _createAdd(Expression receiver, InterfaceType receiverType, |
| Expression argument, bool isSet) { |
| assert(receiver != null); |
| assert(argument != null); |
| assert(argument.fileOffset != TreeNode.noOffset, |
| "No fileOffset on ${argument}."); |
| if (useNewMethodInvocationEncoding) { |
| FunctionType functionType = Substitution.fromInterfaceType(receiverType) |
| .substituteType(isSet ? _setAddFunctionType : _listAddFunctionType); |
| if (!_currentLibrary.isNonNullableByDefault) { |
| functionType = legacyErasure(functionType); |
| } |
| return new InstanceInvocation(InstanceAccessKind.Instance, receiver, |
| new Name('add'), new Arguments([argument]), |
| functionType: functionType, |
| interfaceTarget: isSet ? _setAdd : _listAdd) |
| ..fileOffset = argument.fileOffset |
| ..isInvariant = true; |
| } else { |
| return new MethodInvocation(receiver, new Name('add'), |
| new Arguments([argument]), isSet ? _setAdd : _listAdd) |
| ..fileOffset = argument.fileOffset |
| ..isInvariant = true; |
| } |
| } |
| |
| Expression _createAddAll(Expression receiver, InterfaceType receiverType, |
| Expression argument, bool isSet) { |
| assert(receiver != null); |
| assert(argument != null); |
| assert(argument.fileOffset != TreeNode.noOffset, |
| "No fileOffset on ${argument}."); |
| if (useNewMethodInvocationEncoding) { |
| FunctionType functionType = Substitution.fromInterfaceType(receiverType) |
| .substituteType( |
| isSet ? _setAddAllFunctionType : _listAddAllFunctionType); |
| if (!_currentLibrary.isNonNullableByDefault) { |
| functionType = legacyErasure(functionType); |
| } |
| return new InstanceInvocation(InstanceAccessKind.Instance, receiver, |
| new Name('addAll'), new Arguments([argument]), |
| functionType: functionType, |
| interfaceTarget: isSet ? _setAddAll : _listAddAll) |
| ..fileOffset = argument.fileOffset |
| ..isInvariant = true; |
| } else { |
| return new MethodInvocation(receiver, new Name('addAll'), |
| new Arguments([argument]), isSet ? _setAddAll : _listAddAll) |
| ..fileOffset = argument.fileOffset |
| ..isInvariant = true; |
| } |
| } |
| |
| Expression _createEqualsNull(Expression expression, {bool notEquals: false}) { |
| assert(expression != null); |
| assert(expression.fileOffset != TreeNode.noOffset); |
| Expression check; |
| if (useNewMethodInvocationEncoding) { |
| check = new EqualsNull(expression)..fileOffset = expression.fileOffset; |
| } else { |
| check = new MethodInvocation( |
| expression, |
| new Name('=='), |
| new Arguments( |
| [new NullLiteral()..fileOffset = expression.fileOffset]), |
| _objectEquals) |
| ..fileOffset = expression.fileOffset; |
| } |
| if (notEquals) { |
| check = new Not(check)..fileOffset = expression.fileOffset; |
| } |
| return check; |
| } |
| |
| Expression _createIndexSet(int fileOffset, Expression receiver, |
| InterfaceType receiverType, Expression key, Expression value) { |
| assert(fileOffset != null); |
| assert(fileOffset != TreeNode.noOffset); |
| if (useNewMethodInvocationEncoding) { |
| FunctionType functionType = Substitution.fromInterfaceType(receiverType) |
| .substituteType(_mapPutFunctionType); |
| if (!_currentLibrary.isNonNullableByDefault) { |
| functionType = legacyErasure(functionType); |
| } |
| return new InstanceInvocation(InstanceAccessKind.Instance, receiver, |
| new Name('[]='), new Arguments([key, value]), |
| functionType: functionType, interfaceTarget: _mapPut) |
| ..fileOffset = fileOffset |
| ..isInvariant = true; |
| } else { |
| return new MethodInvocation( |
| receiver, new Name('[]='), new Arguments([key, value]), _mapPut) |
| ..fileOffset = fileOffset |
| ..isInvariant = true; |
| } |
| } |
| |
| AsExpression _createImplicitAs( |
| int fileOffset, Expression expression, DartType type) { |
| assert(fileOffset != null); |
| assert(fileOffset != TreeNode.noOffset); |
| return new AsExpression(expression, type) |
| ..isTypeError = true |
| ..isForNonNullableByDefault = _currentLibrary.isNonNullableByDefault |
| ..fileOffset = fileOffset; |
| } |
| |
| IfStatement _createIf(int fileOffset, Expression condition, Statement then, |
| [Statement otherwise]) { |
| assert(fileOffset != null); |
| assert(fileOffset != TreeNode.noOffset); |
| return new IfStatement(condition, then, otherwise)..fileOffset = fileOffset; |
| } |
| |
| PropertyGet _createGetKey(int fileOffset, Expression receiver) { |
| assert(fileOffset != null); |
| assert(fileOffset != TreeNode.noOffset); |
| return new PropertyGet(receiver, new Name('key'), _mapEntryKey) |
| ..fileOffset = fileOffset; |
| } |
| |
| PropertyGet _createGetValue(int fileOffset, Expression receiver) { |
| assert(fileOffset != null); |
| assert(fileOffset != TreeNode.noOffset); |
| return new PropertyGet(receiver, new Name('value'), _mapEntryValue) |
| ..fileOffset = fileOffset; |
| } |
| |
| PropertyGet _createGetEntries(int fileOffset, Expression receiver) { |
| assert(fileOffset != null); |
| assert(fileOffset != TreeNode.noOffset); |
| return new PropertyGet(receiver, new Name('entries'), _mapEntries) |
| ..fileOffset = fileOffset; |
| } |
| |
| ForStatement _createForStatement( |
| int fileOffset, |
| List<VariableDeclaration> variables, |
| Expression condition, |
| List<Expression> updates, |
| Statement body) { |
| assert(fileOffset != null); |
| assert(fileOffset != TreeNode.noOffset); |
| return new ForStatement(variables, condition, updates, body) |
| ..fileOffset = fileOffset; |
| } |
| |
| ForInStatement _createForInStatement(int fileOffset, |
| VariableDeclaration variable, Expression iterable, Statement body, |
| {bool isAsync: false}) { |
| assert(fileOffset != null); |
| assert(fileOffset != TreeNode.noOffset); |
| return new ForInStatement(variable, iterable, body, isAsync: isAsync) |
| ..fileOffset = fileOffset; |
| } |
| |
| Let _createNullAwareGuard(int fileOffset, VariableDeclaration variable, |
| Expression defaultValue, DartType type) { |
| return new Let( |
| variable, |
| _createConditionalExpression( |
| fileOffset, |
| _createEqualsNull(_createVariableGet(variable)), |
| defaultValue, |
| _createNullCheckedVariableGet(variable), |
| type)) |
| ..fileOffset = fileOffset; |
| } |
| |
| Block _createBlock(List<Statement> statements) { |
| return new Block(statements); |
| } |
| |
| BlockExpression _createBlockExpression( |
| int fileOffset, Block body, Expression value) { |
| assert(fileOffset != null); |
| assert(fileOffset != TreeNode.noOffset); |
| return new BlockExpression(body, value)..fileOffset = fileOffset; |
| } |
| |
| ConditionalExpression _createConditionalExpression( |
| int fileOffset, |
| Expression condition, |
| Expression then, |
| Expression otherwise, |
| DartType type) { |
| assert(fileOffset != null); |
| assert(fileOffset != TreeNode.noOffset); |
| return new ConditionalExpression(condition, then, otherwise, type) |
| ..fileOffset = fileOffset; |
| } |
| } |