| // 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.collections; |
| |
| import 'package:kernel/ast.dart'; |
| |
| import 'package:kernel/src/printer.dart'; |
| |
| import 'package:kernel/type_environment.dart' show StaticTypeContext; |
| |
| import '../messages.dart' |
| show noLength, templateExpectedAfterButGot, templateExpectedButGot; |
| |
| import '../problems.dart' show getFileUri, unsupported; |
| |
| import '../type_inference/inference_helper.dart' show InferenceHelper; |
| |
| /// Mixin for spread and control-flow elements. |
| /// |
| /// Spread and control-flow elements are not truly expressions and they cannot |
| /// appear in arbitrary expression contexts in the Kernel program. They can |
| /// only appear as elements in list or set literals. They are translated into |
| /// a lower-level representation and never serialized to .dill files. |
| mixin ControlFlowElement on Expression { |
| /// Spread and control-flow elements are not expressions and do not have a |
| /// static type. |
| @override |
| DartType getStaticType(StaticTypeContext context) { |
| return unsupported("getStaticType", fileOffset, getFileUri(this)); |
| } |
| |
| @override |
| DartType getStaticTypeInternal(StaticTypeContext context) { |
| return unsupported("getStaticTypeInternal", fileOffset, getFileUri(this)); |
| } |
| |
| @override |
| R accept<R>(ExpressionVisitor<R> v) => v.defaultExpression(this); |
| |
| @override |
| R accept1<R, A>(ExpressionVisitor1<R, A> v, A arg) => |
| v.defaultExpression(this, arg); |
| |
| /// Returns this control flow element as a [MapLiteralEntry], or `null` if |
| /// this control flow element cannot be converted into a [MapLiteralEntry]. |
| /// |
| /// [onConvertElement] is called when a [ForElement], [ForInElement], or |
| /// [IfElement] is converted to a [ForMapEntry], [ForInMapEntry], or |
| /// [IfMapEntry], respectively. |
| // TODO(johnniwinther): Merge this with [convertToMapEntry]. |
| MapLiteralEntry? toMapLiteralEntry( |
| void onConvertElement(TreeNode from, TreeNode to)); |
| } |
| |
| /// A spread element in a list or set literal. |
| class SpreadElement extends Expression with ControlFlowElement { |
| Expression expression; |
| bool isNullAware; |
| |
| /// The type of the elements of the collection that [expression] evaluates to. |
| /// |
| /// It is set during type inference and is used to add appropriate type casts |
| /// during the desugaring. |
| DartType? elementType; |
| |
| SpreadElement(this.expression, {required this.isNullAware}) |
| // ignore: unnecessary_null_comparison |
| : assert(expression != null), |
| // ignore: unnecessary_null_comparison |
| assert(isNullAware != null) { |
| expression.parent = this; |
| } |
| |
| @override |
| void visitChildren(Visitor v) { |
| expression.accept(v); |
| } |
| |
| @override |
| void transformChildren(Transformer v) { |
| // ignore: unnecessary_null_comparison |
| if (expression != null) { |
| expression = v.transform(expression); |
| expression.parent = this; |
| } |
| } |
| |
| @override |
| void transformOrRemoveChildren(RemovingTransformer v) { |
| // ignore: unnecessary_null_comparison |
| if (expression != null) { |
| expression = v.transform(expression); |
| expression.parent = this; |
| } |
| } |
| |
| @override |
| SpreadMapEntry toMapLiteralEntry( |
| void onConvertElement(TreeNode from, TreeNode to)) { |
| return new SpreadMapEntry(expression, isNullAware: isNullAware) |
| ..fileOffset = fileOffset; |
| } |
| |
| @override |
| String toString() { |
| return "SpreadElement(${toStringInternal()})"; |
| } |
| |
| @override |
| void toTextInternal(AstPrinter printer) { |
| printer.write('...'); |
| if (isNullAware) { |
| printer.write('?'); |
| } |
| printer.writeExpression(expression); |
| } |
| } |
| |
| /// An 'if' element in a list or set literal. |
| class IfElement extends Expression with ControlFlowElement { |
| Expression condition; |
| Expression then; |
| Expression? otherwise; |
| |
| IfElement(this.condition, this.then, this.otherwise) |
| // ignore: unnecessary_null_comparison |
| : assert(condition != null), |
| // ignore: unnecessary_null_comparison |
| assert(then != null) { |
| condition.parent = this; |
| then.parent = this; |
| otherwise?.parent = this; |
| } |
| |
| @override |
| void visitChildren(Visitor v) { |
| condition.accept(v); |
| then.accept(v); |
| otherwise?.accept(v); |
| } |
| |
| @override |
| void transformChildren(Transformer v) { |
| // ignore: unnecessary_null_comparison |
| if (condition != null) { |
| condition = v.transform(condition); |
| condition.parent = this; |
| } |
| // ignore: unnecessary_null_comparison |
| if (then != null) { |
| then = v.transform(then); |
| then.parent = this; |
| } |
| if (otherwise != null) { |
| otherwise = v.transform(otherwise!); |
| otherwise?.parent = this; |
| } |
| } |
| |
| @override |
| void transformOrRemoveChildren(RemovingTransformer v) { |
| // ignore: unnecessary_null_comparison |
| if (condition != null) { |
| condition = v.transform(condition); |
| condition.parent = this; |
| } |
| // ignore: unnecessary_null_comparison |
| if (then != null) { |
| then = v.transform(then); |
| then.parent = this; |
| } |
| if (otherwise != null) { |
| otherwise = v.transformOrRemoveExpression(otherwise!); |
| otherwise?.parent = this; |
| } |
| } |
| |
| @override |
| MapLiteralEntry? toMapLiteralEntry( |
| void onConvertElement(TreeNode from, TreeNode to)) { |
| MapLiteralEntry? thenEntry; |
| Expression then = this.then; |
| if (then is ControlFlowElement) { |
| ControlFlowElement thenElement = then; |
| thenEntry = thenElement.toMapLiteralEntry(onConvertElement); |
| } |
| if (thenEntry == null) return null; |
| MapLiteralEntry? otherwiseEntry; |
| Expression? otherwise = this.otherwise; |
| if (otherwise != null) { |
| if (otherwise is ControlFlowElement) { |
| ControlFlowElement otherwiseElement = otherwise; |
| otherwiseEntry = otherwiseElement.toMapLiteralEntry(onConvertElement); |
| } |
| if (otherwiseEntry == null) return null; |
| } |
| IfMapEntry result = new IfMapEntry(condition, thenEntry, otherwiseEntry) |
| ..fileOffset = fileOffset; |
| onConvertElement(this, result); |
| return result; |
| } |
| |
| @override |
| String toString() { |
| return "IfElement(${toStringInternal()})"; |
| } |
| |
| @override |
| void toTextInternal(AstPrinter printer) { |
| printer.write('if ('); |
| printer.writeExpression(condition); |
| printer.write(') '); |
| printer.writeExpression(then); |
| if (otherwise != null) { |
| printer.write(' else '); |
| printer.writeExpression(otherwise!); |
| } |
| } |
| } |
| |
| /// A 'for' element in a list or set literal. |
| class ForElement extends Expression with ControlFlowElement { |
| final List<VariableDeclaration> variables; // May be empty, but not null. |
| Expression? condition; // May be null. |
| final List<Expression> updates; // May be empty, but not null. |
| Expression body; |
| |
| ForElement(this.variables, this.condition, this.updates, this.body) |
| // ignore: unnecessary_null_comparison |
| : assert(body != null) { |
| setParents(variables, this); |
| condition?.parent = this; |
| setParents(updates, this); |
| body.parent = this; |
| } |
| |
| @override |
| void visitChildren(Visitor v) { |
| visitList(variables, v); |
| condition?.accept(v); |
| visitList(updates, v); |
| body.accept(v); |
| } |
| |
| @override |
| void transformChildren(Transformer v) { |
| v.transformList(variables, this); |
| if (condition != null) { |
| condition = v.transform(condition!); |
| condition?.parent = this; |
| } |
| v.transformList(updates, this); |
| // ignore: unnecessary_null_comparison |
| if (body != null) { |
| body = v.transform(body); |
| body.parent = this; |
| } |
| } |
| |
| @override |
| void transformOrRemoveChildren(RemovingTransformer v) { |
| v.transformVariableDeclarationList(variables, this); |
| if (condition != null) { |
| condition = v.transformOrRemoveExpression(condition!); |
| condition?.parent = this; |
| } |
| v.transformExpressionList(updates, this); |
| // ignore: unnecessary_null_comparison |
| if (body != null) { |
| body = v.transform(body); |
| body.parent = this; |
| } |
| } |
| |
| @override |
| MapLiteralEntry? toMapLiteralEntry( |
| void onConvertElement(TreeNode from, TreeNode to)) { |
| MapLiteralEntry? bodyEntry; |
| Expression body = this.body; |
| if (body is ControlFlowElement) { |
| ControlFlowElement bodyElement = body; |
| bodyEntry = bodyElement.toMapLiteralEntry(onConvertElement); |
| } |
| if (bodyEntry == null) return null; |
| ForMapEntry result = |
| new ForMapEntry(variables, condition, updates, bodyEntry) |
| ..fileOffset = fileOffset; |
| onConvertElement(this, result); |
| return result; |
| } |
| |
| @override |
| String toString() { |
| return "ForElement(${toStringInternal()})"; |
| } |
| |
| @override |
| void toTextInternal(AstPrinter state) { |
| // TODO(johnniwinther): Implement this. |
| } |
| } |
| |
| /// A 'for-in' element in a list or set literal. |
| class ForInElement extends Expression with ControlFlowElement { |
| VariableDeclaration variable; // Has no initializer. |
| Expression iterable; |
| Expression? syntheticAssignment; // May be null. |
| Statement? expressionEffects; // May be null. |
| Expression body; |
| Expression? problem; // May be null. |
| bool isAsync; // True if this is an 'await for' loop. |
| |
| ForInElement(this.variable, this.iterable, this.syntheticAssignment, |
| this.expressionEffects, this.body, this.problem, |
| {this.isAsync: false}) |
| // ignore: unnecessary_null_comparison |
| : assert(variable != null), |
| // ignore: unnecessary_null_comparison |
| assert(iterable != null), |
| // ignore: unnecessary_null_comparison |
| assert(body != null) { |
| variable.parent = this; |
| iterable.parent = this; |
| syntheticAssignment?.parent = this; |
| expressionEffects?.parent = this; |
| body.parent = this; |
| problem?.parent = this; |
| } |
| |
| Statement? get prologue => syntheticAssignment != null |
| ? (new ExpressionStatement(syntheticAssignment!) |
| ..fileOffset = syntheticAssignment!.fileOffset) |
| : expressionEffects; |
| |
| @override |
| void visitChildren(Visitor v) { |
| variable.accept(v); |
| iterable.accept(v); |
| syntheticAssignment?.accept(v); |
| expressionEffects?.accept(v); |
| body.accept(v); |
| problem?.accept(v); |
| } |
| |
| @override |
| void transformChildren(Transformer v) { |
| // ignore: unnecessary_null_comparison |
| if (variable != null) { |
| variable = v.transform(variable); |
| variable.parent = this; |
| } |
| // ignore: unnecessary_null_comparison |
| if (iterable != null) { |
| iterable = v.transform(iterable); |
| iterable.parent = this; |
| } |
| if (syntheticAssignment != null) { |
| syntheticAssignment = v.transform(syntheticAssignment!); |
| syntheticAssignment?.parent = this; |
| } |
| if (expressionEffects != null) { |
| expressionEffects = v.transform(expressionEffects!); |
| expressionEffects?.parent = this; |
| } |
| // ignore: unnecessary_null_comparison |
| if (body != null) { |
| body = v.transform(body); |
| body.parent = this; |
| } |
| if (problem != null) { |
| problem = v.transform(problem!); |
| problem?.parent = this; |
| } |
| } |
| |
| @override |
| void transformOrRemoveChildren(RemovingTransformer v) { |
| // ignore: unnecessary_null_comparison |
| if (variable != null) { |
| variable = v.transform(variable); |
| variable.parent = this; |
| } |
| // ignore: unnecessary_null_comparison |
| if (iterable != null) { |
| iterable = v.transform(iterable); |
| iterable.parent = this; |
| } |
| if (syntheticAssignment != null) { |
| syntheticAssignment = v.transformOrRemoveExpression(syntheticAssignment!); |
| syntheticAssignment?.parent = this; |
| } |
| if (expressionEffects != null) { |
| expressionEffects = v.transformOrRemoveStatement(expressionEffects!); |
| expressionEffects?.parent = this; |
| } |
| // ignore: unnecessary_null_comparison |
| if (body != null) { |
| body = v.transform(body); |
| body.parent = this; |
| } |
| if (problem != null) { |
| problem = v.transformOrRemoveExpression(problem!); |
| problem?.parent = this; |
| } |
| } |
| |
| @override |
| MapLiteralEntry? toMapLiteralEntry( |
| void onConvertElement(TreeNode from, TreeNode to)) { |
| MapLiteralEntry? bodyEntry; |
| Expression body = this.body; |
| if (body is ControlFlowElement) { |
| bodyEntry = body.toMapLiteralEntry(onConvertElement); |
| } |
| if (bodyEntry == null) return null; |
| ForInMapEntry result = new ForInMapEntry(variable, iterable, |
| syntheticAssignment, expressionEffects, bodyEntry, problem, |
| isAsync: isAsync) |
| ..fileOffset = fileOffset; |
| onConvertElement(this, result); |
| return result; |
| } |
| |
| @override |
| String toString() { |
| return "ForInElement(${toStringInternal()})"; |
| } |
| |
| @override |
| void toTextInternal(AstPrinter state) { |
| // TODO(johnniwinther): Implement this. |
| } |
| } |
| |
| mixin ControlFlowMapEntry implements MapLiteralEntry { |
| @override |
| Expression get key { |
| throw new UnsupportedError('ControlFlowMapEntry.key getter'); |
| } |
| |
| @override |
| void set key(Expression expr) { |
| throw new UnsupportedError('ControlFlowMapEntry.key setter'); |
| } |
| |
| @override |
| Expression get value { |
| throw new UnsupportedError('ControlFlowMapEntry.value getter'); |
| } |
| |
| @override |
| void set value(Expression expr) { |
| throw new UnsupportedError('ControlFlowMapEntry.value setter'); |
| } |
| |
| @override |
| R accept<R>(TreeVisitor<R> v) => v.defaultTreeNode(this); |
| |
| @override |
| R accept1<R, A>(TreeVisitor1<R, A> v, A arg) => v.defaultTreeNode(this, arg); |
| |
| @override |
| String toStringInternal() => toText(defaultAstTextStrategy); |
| |
| @override |
| String toText(AstTextStrategy strategy) { |
| AstPrinter state = new AstPrinter(strategy); |
| toTextInternal(state); |
| return state.getText(); |
| } |
| } |
| |
| /// A spread element in a map literal. |
| class SpreadMapEntry extends TreeNode with ControlFlowMapEntry { |
| Expression expression; |
| bool isNullAware; |
| |
| /// The type of the map entries of the map that [expression] evaluates to. |
| /// |
| /// It is set during type inference and is used to add appropriate type casts |
| /// during the desugaring. |
| DartType? entryType; |
| |
| SpreadMapEntry(this.expression, {required this.isNullAware}) |
| // ignore: unnecessary_null_comparison |
| : assert(expression != null), |
| // ignore: unnecessary_null_comparison |
| assert(isNullAware != null) { |
| expression.parent = this; |
| } |
| |
| @override |
| void visitChildren(Visitor v) { |
| expression.accept(v); |
| } |
| |
| @override |
| void transformChildren(Transformer v) { |
| // ignore: unnecessary_null_comparison |
| if (expression != null) { |
| expression = v.transform(expression); |
| expression.parent = this; |
| } |
| } |
| |
| @override |
| void transformOrRemoveChildren(RemovingTransformer v) { |
| // ignore: unnecessary_null_comparison |
| if (expression != null) { |
| expression = v.transform(expression); |
| expression.parent = this; |
| } |
| } |
| |
| @override |
| String toString() { |
| return "SpreadMapEntry(${toStringInternal()})"; |
| } |
| |
| @override |
| void toTextInternal(AstPrinter state) { |
| // TODO(johnniwinther): Implement this. |
| } |
| } |
| |
| /// An 'if' element in a map literal. |
| class IfMapEntry extends TreeNode with ControlFlowMapEntry { |
| Expression condition; |
| MapLiteralEntry then; |
| MapLiteralEntry? otherwise; |
| |
| IfMapEntry(this.condition, this.then, this.otherwise) |
| // ignore: unnecessary_null_comparison |
| : assert(condition != null), |
| // ignore: unnecessary_null_comparison |
| assert(then != null) { |
| condition.parent = this; |
| then.parent = this; |
| otherwise?.parent = this; |
| } |
| |
| @override |
| void visitChildren(Visitor v) { |
| condition.accept(v); |
| then.accept(v); |
| otherwise?.accept(v); |
| } |
| |
| @override |
| void transformChildren(Transformer v) { |
| // ignore: unnecessary_null_comparison |
| if (condition != null) { |
| condition = v.transform(condition); |
| condition.parent = this; |
| } |
| // ignore: unnecessary_null_comparison |
| if (then != null) { |
| then = v.transform(then); |
| then.parent = this; |
| } |
| if (otherwise != null) { |
| otherwise = v.transform(otherwise!); |
| otherwise?.parent = this; |
| } |
| } |
| |
| @override |
| void transformOrRemoveChildren(RemovingTransformer v) { |
| // ignore: unnecessary_null_comparison |
| if (condition != null) { |
| condition = v.transform(condition); |
| condition.parent = this; |
| } |
| // ignore: unnecessary_null_comparison |
| if (then != null) { |
| then = v.transform(then); |
| then.parent = this; |
| } |
| if (otherwise != null) { |
| otherwise = v.transformOrRemove(otherwise!, dummyMapLiteralEntry); |
| otherwise?.parent = this; |
| } |
| } |
| |
| @override |
| String toString() { |
| return "IfMapEntry(${toStringInternal()})"; |
| } |
| |
| @override |
| void toTextInternal(AstPrinter state) { |
| // TODO(johnniwinther): Implement this. |
| } |
| } |
| |
| /// A 'for' element in a map literal. |
| class ForMapEntry extends TreeNode with ControlFlowMapEntry { |
| final List<VariableDeclaration> variables; // May be empty, but not null. |
| Expression? condition; // May be null. |
| final List<Expression> updates; // May be empty, but not null. |
| MapLiteralEntry body; |
| |
| ForMapEntry(this.variables, this.condition, this.updates, this.body) |
| // ignore: unnecessary_null_comparison |
| : assert(body != null) { |
| setParents(variables, this); |
| condition?.parent = this; |
| setParents(updates, this); |
| body.parent = this; |
| } |
| |
| @override |
| void visitChildren(Visitor v) { |
| visitList(variables, v); |
| condition?.accept(v); |
| visitList(updates, v); |
| body.accept(v); |
| } |
| |
| @override |
| void transformChildren(Transformer v) { |
| v.transformList(variables, this); |
| if (condition != null) { |
| condition = v.transform(condition!); |
| condition?.parent = this; |
| } |
| v.transformList(updates, this); |
| // ignore: unnecessary_null_comparison |
| if (body != null) { |
| body = v.transform(body); |
| body.parent = this; |
| } |
| } |
| |
| @override |
| void transformOrRemoveChildren(RemovingTransformer v) { |
| v.transformVariableDeclarationList(variables, this); |
| if (condition != null) { |
| condition = v.transformOrRemoveExpression(condition!); |
| condition?.parent = this; |
| } |
| v.transformExpressionList(updates, this); |
| // ignore: unnecessary_null_comparison |
| if (body != null) { |
| body = v.transform(body); |
| body.parent = this; |
| } |
| } |
| |
| @override |
| String toString() { |
| return "ForMapEntry(${toStringInternal()})"; |
| } |
| |
| @override |
| void toTextInternal(AstPrinter state) { |
| // TODO(johnniwinther): Implement this. |
| } |
| } |
| |
| /// A 'for-in' element in a map literal. |
| class ForInMapEntry extends TreeNode with ControlFlowMapEntry { |
| VariableDeclaration variable; // Has no initializer. |
| Expression iterable; |
| Expression? syntheticAssignment; // May be null. |
| Statement? expressionEffects; // May be null. |
| MapLiteralEntry body; |
| Expression? problem; // May be null. |
| bool isAsync; // True if this is an 'await for' loop. |
| |
| ForInMapEntry(this.variable, this.iterable, this.syntheticAssignment, |
| this.expressionEffects, this.body, this.problem, |
| {required this.isAsync}) |
| // ignore: unnecessary_null_comparison |
| : assert(iterable != null), |
| // ignore: unnecessary_null_comparison |
| assert(body != null), |
| // ignore: unnecessary_null_comparison |
| assert(isAsync != null) { |
| variable.parent = this; |
| iterable.parent = this; |
| syntheticAssignment?.parent = this; |
| expressionEffects?.parent = this; |
| body.parent = this; |
| problem?.parent = this; |
| } |
| |
| Statement? get prologue => syntheticAssignment != null |
| ? (new ExpressionStatement(syntheticAssignment!) |
| ..fileOffset = syntheticAssignment!.fileOffset) |
| : expressionEffects; |
| |
| @override |
| void visitChildren(Visitor v) { |
| variable.accept(v); |
| iterable.accept(v); |
| syntheticAssignment?.accept(v); |
| expressionEffects?.accept(v); |
| body.accept(v); |
| problem?.accept(v); |
| } |
| |
| @override |
| void transformChildren(Transformer v) { |
| // ignore: unnecessary_null_comparison |
| if (variable != null) { |
| variable = v.transform(variable); |
| variable.parent = this; |
| } |
| // ignore: unnecessary_null_comparison |
| if (iterable != null) { |
| iterable = v.transform(iterable); |
| iterable.parent = this; |
| } |
| if (syntheticAssignment != null) { |
| syntheticAssignment = v.transform(syntheticAssignment!); |
| syntheticAssignment?.parent = this; |
| } |
| if (expressionEffects != null) { |
| expressionEffects = v.transform(expressionEffects!); |
| expressionEffects?.parent = this; |
| } |
| // ignore: unnecessary_null_comparison |
| if (body != null) { |
| body = v.transform(body); |
| body.parent = this; |
| } |
| if (problem != null) { |
| problem = v.transform(problem!); |
| problem?.parent = this; |
| } |
| } |
| |
| @override |
| void transformOrRemoveChildren(RemovingTransformer v) { |
| // ignore: unnecessary_null_comparison |
| if (variable != null) { |
| variable = v.transform(variable); |
| variable.parent = this; |
| } |
| // ignore: unnecessary_null_comparison |
| if (iterable != null) { |
| iterable = v.transform(iterable); |
| iterable.parent = this; |
| } |
| if (syntheticAssignment != null) { |
| syntheticAssignment = v.transformOrRemoveExpression(syntheticAssignment!); |
| syntheticAssignment?.parent = this; |
| } |
| if (expressionEffects != null) { |
| expressionEffects = v.transformOrRemoveStatement(expressionEffects!); |
| expressionEffects?.parent = this; |
| } |
| // ignore: unnecessary_null_comparison |
| if (body != null) { |
| body = v.transform(body); |
| body.parent = this; |
| } |
| if (problem != null) { |
| problem = v.transformOrRemoveExpression(problem!); |
| problem?.parent = this; |
| } |
| } |
| |
| @override |
| String toString() { |
| return "ForInMapEntry(${toStringInternal()})"; |
| } |
| |
| @override |
| void toTextInternal(AstPrinter state) { |
| // TODO(johnniwinther): Implement this. |
| } |
| } |
| |
| /// Convert [entry] to an [Expression], if possible. If [entry] cannot be |
| /// converted an error reported through [helper] and an invalid expression is |
| /// returned. |
| /// |
| /// [onConvertMapEntry] is called when a [ForMapEntry], [ForInMapEntry], or |
| /// [IfMapEntry] is converted to a [ForElement], [ForInElement], or [IfElement], |
| /// respectively. |
| Expression convertToElement(MapLiteralEntry entry, InferenceHelper? helper, |
| void onConvertMapEntry(TreeNode from, TreeNode to)) { |
| if (entry is SpreadMapEntry) { |
| return new SpreadElement(entry.expression, isNullAware: entry.isNullAware) |
| ..fileOffset = entry.expression.fileOffset; |
| } |
| if (entry is IfMapEntry) { |
| IfElement result = new IfElement( |
| entry.condition, |
| convertToElement(entry.then, helper, onConvertMapEntry), |
| entry.otherwise == null |
| ? null |
| : convertToElement(entry.otherwise!, helper, onConvertMapEntry)) |
| ..fileOffset = entry.fileOffset; |
| onConvertMapEntry(entry, result); |
| return result; |
| } |
| if (entry is ForMapEntry) { |
| ForElement result = new ForElement(entry.variables, entry.condition, |
| entry.updates, convertToElement(entry.body, helper, onConvertMapEntry)) |
| ..fileOffset = entry.fileOffset; |
| onConvertMapEntry(entry, result); |
| return result; |
| } |
| if (entry is ForInMapEntry) { |
| ForInElement result = new ForInElement( |
| entry.variable, |
| entry.iterable, |
| entry.syntheticAssignment, |
| entry.expressionEffects, |
| convertToElement(entry.body, helper, onConvertMapEntry), |
| entry.problem, |
| isAsync: entry.isAsync) |
| ..fileOffset = entry.fileOffset; |
| onConvertMapEntry(entry, result); |
| return result; |
| } |
| Expression key = entry.key; |
| if (key is InvalidExpression) { |
| Expression value = entry.value; |
| if (value is NullLiteral && value.fileOffset == TreeNode.noOffset) { |
| // entry arose from an error. Don't build another error. |
| return key; |
| } |
| } |
| // TODO(johnniwinther): How can this be triggered? This will fail if |
| // encountered in top level inference. |
| return helper!.buildProblem( |
| templateExpectedButGot.withArguments(','), |
| entry.fileOffset, |
| 1, |
| ); |
| } |
| |
| bool isConvertibleToMapEntry(Expression element) { |
| if (element is SpreadElement) return true; |
| if (element is IfElement) { |
| return isConvertibleToMapEntry(element.then) && |
| (element.otherwise == null || |
| isConvertibleToMapEntry(element.otherwise!)); |
| } |
| if (element is ForElement) { |
| return isConvertibleToMapEntry(element.body); |
| } |
| if (element is ForInElement) { |
| return isConvertibleToMapEntry(element.body); |
| } |
| return false; |
| } |
| |
| /// Convert [element] to a [MapLiteralEntry], if possible. If [element] cannot |
| /// be converted an error reported through [helper] and a map entry holding an |
| /// invalid expression is returned. |
| /// |
| /// [onConvertElement] is called when a [ForElement], [ForInElement], or |
| /// [IfElement] is converted to a [ForMapEntry], [ForInMapEntry], or |
| /// [IfMapEntry], respectively. |
| MapLiteralEntry convertToMapEntry(Expression element, InferenceHelper helper, |
| void onConvertElement(TreeNode from, TreeNode to)) { |
| if (element is SpreadElement) { |
| return new SpreadMapEntry(element.expression, |
| isNullAware: element.isNullAware) |
| ..fileOffset = element.expression.fileOffset; |
| } |
| if (element is IfElement) { |
| IfMapEntry result = new IfMapEntry( |
| element.condition, |
| convertToMapEntry(element.then, helper, onConvertElement), |
| element.otherwise == null |
| ? null |
| : convertToMapEntry(element.otherwise!, helper, onConvertElement)) |
| ..fileOffset = element.fileOffset; |
| onConvertElement(element, result); |
| return result; |
| } |
| if (element is ForElement) { |
| ForMapEntry result = new ForMapEntry( |
| element.variables, |
| element.condition, |
| element.updates, |
| convertToMapEntry(element.body, helper, onConvertElement)) |
| ..fileOffset = element.fileOffset; |
| onConvertElement(element, result); |
| return result; |
| } |
| if (element is ForInElement) { |
| ForInMapEntry result = new ForInMapEntry( |
| element.variable, |
| element.iterable, |
| element.syntheticAssignment, |
| element.expressionEffects, |
| convertToMapEntry(element.body, helper, onConvertElement), |
| element.problem, |
| isAsync: element.isAsync) |
| ..fileOffset = element.fileOffset; |
| onConvertElement(element, result); |
| return result; |
| } |
| return new MapLiteralEntry( |
| helper.buildProblem( |
| templateExpectedAfterButGot.withArguments(':'), |
| element.fileOffset, |
| // TODO(danrubel): what is the length of the expression? |
| noLength, |
| ), |
| new NullLiteral()..fileOffset = element.fileOffset) |
| ..fileOffset = element.fileOffset; |
| } |