| // 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 '../base/messages.dart' |
| show noLength, templateExpectedAfterButGot, templateExpectedButGot; |
| import '../base/problems.dart' show getFileUri, unsupported; |
| import '../type_inference/inference_helper.dart' show InferenceHelper; |
| import '../type_inference/inference_results.dart'; |
| import '../type_inference/inference_visitor.dart'; |
| import 'internal_ast.dart'; |
| |
| /// Base class for all control-flow elements. |
| sealed class ControlFlowElement extends AuxiliaryExpression { |
| /// 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)); |
| } |
| |
| /// Base class for control-flow elements with default internal implementations. |
| /// |
| /// Such elements are, for example, control-flow elements containing patterns. |
| sealed class ControlFlowElementImpl extends InternalExpression |
| implements ControlFlowElement {} |
| |
| /// 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. |
| /// |
| /// [ControlFlowElementMixin] doesn't use [ControlFlowElement] as its `on`-type |
| /// to avoid being required in switch-statements over [ControlFlowElement]s. |
| mixin ControlFlowElementMixin on AuxiliaryExpression { |
| /// Spread and control-flow elements are not expressions and do not have a |
| /// static type. |
| @override |
| // Coverage-ignore(suite): Not run. |
| DartType getStaticType(StaticTypeContext context) { |
| return unsupported("getStaticType", fileOffset, getFileUri(this)); |
| } |
| |
| @override |
| // Coverage-ignore(suite): Not run. |
| DartType getStaticTypeInternal(StaticTypeContext context) { |
| return unsupported("getStaticTypeInternal", fileOffset, getFileUri(this)); |
| } |
| |
| @override |
| // Coverage-ignore(suite): Not run. |
| R accept<R>(ExpressionVisitor<R> v) => v.visitAuxiliaryExpression(this); |
| |
| @override |
| // Coverage-ignore(suite): Not run. |
| R accept1<R, A>(ExpressionVisitor1<R, A> v, A arg) => |
| v.visitAuxiliaryExpression(this, arg); |
| } |
| |
| /// A spread element in a list or set literal. |
| class SpreadElement extends ControlFlowElement with ControlFlowElementMixin { |
| 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}) { |
| expression.parent = this; |
| } |
| |
| @override |
| // Coverage-ignore(suite): Not run. |
| void visitChildren(Visitor v) { |
| expression.accept(v); |
| } |
| |
| @override |
| // Coverage-ignore(suite): Not run. |
| void transformChildren(Transformer v) { |
| expression = v.transform(expression); |
| expression.parent = this; |
| } |
| |
| @override |
| // Coverage-ignore(suite): Not run. |
| void transformOrRemoveChildren(RemovingTransformer v) { |
| 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 |
| // Coverage-ignore(suite): Not run. |
| void toTextInternal(AstPrinter printer) { |
| printer.write('...'); |
| if (isNullAware) { |
| printer.write('?'); |
| } |
| printer.writeExpression(expression); |
| } |
| } |
| |
| class NullAwareElement extends ControlFlowElement with ControlFlowElementMixin { |
| Expression expression; |
| |
| NullAwareElement(this.expression); |
| |
| @override |
| // Coverage-ignore(suite): Not run. |
| MapLiteralEntry? toMapLiteralEntry( |
| void Function(TreeNode from, TreeNode to) onConvertElement) { |
| return unsupported("toMapLiteralEntry", fileOffset, getFileUri(this)); |
| } |
| |
| @override |
| // Coverage-ignore(suite): Not run. |
| void toTextInternal(AstPrinter printer) { |
| printer.write('?'); |
| printer.writeExpression(expression); |
| } |
| |
| @override |
| // Coverage-ignore(suite): Not run. |
| void transformChildren(Transformer v) { |
| expression = v.transform(expression); |
| expression.parent = this; |
| } |
| |
| @override |
| // Coverage-ignore(suite): Not run. |
| void transformOrRemoveChildren(RemovingTransformer v) { |
| expression = v.transform(expression); |
| expression.parent = this; |
| } |
| |
| @override |
| // Coverage-ignore(suite): Not run. |
| void visitChildren(Visitor v) { |
| expression.accept(v); |
| } |
| |
| @override |
| String toString() { |
| return "NullAwareElement(${toStringInternal()})"; |
| } |
| } |
| |
| /// An 'if' element in a list or set literal. |
| class IfElement extends ControlFlowElement with ControlFlowElementMixin { |
| Expression condition; |
| Expression then; |
| Expression? otherwise; |
| |
| IfElement(this.condition, this.then, this.otherwise) { |
| condition.parent = this; |
| then.parent = this; |
| otherwise?.parent = this; |
| } |
| |
| @override |
| // Coverage-ignore(suite): Not run. |
| void visitChildren(Visitor v) { |
| condition.accept(v); |
| then.accept(v); |
| otherwise?.accept(v); |
| } |
| |
| @override |
| // Coverage-ignore(suite): Not run. |
| void transformChildren(Transformer v) { |
| condition = v.transform(condition); |
| condition.parent = this; |
| then = v.transform(then); |
| then.parent = this; |
| if (otherwise != null) { |
| otherwise = v.transform(otherwise!); |
| otherwise?.parent = this; |
| } |
| } |
| |
| @override |
| // Coverage-ignore(suite): Not run. |
| void transformOrRemoveChildren(RemovingTransformer v) { |
| condition = v.transform(condition); |
| condition.parent = this; |
| 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) { |
| // Coverage-ignore-block(suite): Not run. |
| 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 |
| // Coverage-ignore(suite): Not run. |
| 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 ControlFlowElement |
| with ControlFlowElementMixin |
| implements ForElementBase { |
| @override |
| final List<VariableDeclaration> variables; // May be empty, but not null. |
| |
| @override |
| Expression? condition; // May be null. |
| |
| @override |
| final List<Expression> updates; // May be empty, but not null. |
| |
| @override |
| Expression body; |
| |
| ForElement(this.variables, this.condition, this.updates, this.body) { |
| setParents(variables, this); |
| condition?.parent = this; |
| setParents(updates, this); |
| body.parent = this; |
| } |
| |
| @override |
| // Coverage-ignore(suite): Not run. |
| void visitChildren(Visitor v) { |
| visitList(variables, v); |
| condition?.accept(v); |
| visitList(updates, v); |
| body.accept(v); |
| } |
| |
| @override |
| // Coverage-ignore(suite): Not run. |
| void transformChildren(Transformer v) { |
| v.transformList(variables, this); |
| if (condition != null) { |
| condition = v.transform(condition!); |
| condition?.parent = this; |
| } |
| v.transformList(updates, this); |
| body = v.transform(body); |
| body.parent = this; |
| } |
| |
| @override |
| // Coverage-ignore(suite): Not run. |
| void transformOrRemoveChildren(RemovingTransformer v) { |
| v.transformVariableDeclarationList(variables, this); |
| if (condition != null) { |
| condition = v.transformOrRemoveExpression(condition!); |
| condition?.parent = this; |
| } |
| v.transformExpressionList(updates, this); |
| 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 |
| // Coverage-ignore(suite): Not run. |
| void toTextInternal(AstPrinter printer) { |
| printer.write('for ('); |
| for (int index = 0; index < variables.length; index++) { |
| if (index > 0) { |
| printer.write(', '); |
| } |
| printer.writeVariableDeclaration(variables[index], |
| includeModifiersAndType: index == 0); |
| } |
| printer.write('; '); |
| if (condition != null) { |
| printer.writeExpression(condition!); |
| } |
| printer.write('; '); |
| printer.writeExpressions(updates); |
| printer.write(') '); |
| printer.writeExpression(body); |
| } |
| } |
| |
| /// A 'for-in' element in a list or set literal. |
| class ForInElement extends ControlFlowElement with ControlFlowElementMixin { |
| 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}) { |
| variable.parent = this; |
| iterable.parent = this; |
| syntheticAssignment?.parent = this; |
| expressionEffects?.parent = this; |
| body.parent = this; |
| problem |
| // Coverage-ignore(suite): Not run. |
| ?.parent = this; |
| } |
| |
| Statement? get prologue => syntheticAssignment != null |
| ? (new ExpressionStatement(syntheticAssignment!) |
| ..fileOffset = syntheticAssignment!.fileOffset) |
| : expressionEffects; |
| |
| @override |
| // Coverage-ignore(suite): Not run. |
| void visitChildren(Visitor v) { |
| variable.accept(v); |
| iterable.accept(v); |
| syntheticAssignment?.accept(v); |
| expressionEffects?.accept(v); |
| body.accept(v); |
| problem?.accept(v); |
| } |
| |
| @override |
| // Coverage-ignore(suite): Not run. |
| void transformChildren(Transformer v) { |
| variable = v.transform(variable); |
| variable.parent = this; |
| 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; |
| } |
| body = v.transform(body); |
| body.parent = this; |
| if (problem != null) { |
| problem = v.transform(problem!); |
| problem?.parent = this; |
| } |
| } |
| |
| @override |
| // Coverage-ignore(suite): Not run. |
| void transformOrRemoveChildren(RemovingTransformer v) { |
| variable = v.transform(variable); |
| variable.parent = this; |
| 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; |
| } |
| 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 |
| // Coverage-ignore(suite): Not run. |
| void toTextInternal(AstPrinter state) { |
| // TODO(johnniwinther): Implement this. |
| } |
| } |
| |
| class IfCaseElement extends ControlFlowElementImpl |
| with ControlFlowElementMixin { |
| Expression expression; |
| PatternGuard patternGuard; |
| Expression then; |
| Expression? otherwise; |
| List<Statement> prelude; |
| |
| /// The type of the expression against which this pattern is matched. |
| /// |
| /// This is set during inference. |
| DartType? matchedValueType; |
| |
| IfCaseElement( |
| {required this.prelude, |
| required this.expression, |
| required this.patternGuard, |
| required this.then, |
| this.otherwise}) { |
| setParents(prelude, this); |
| expression.parent = this; |
| patternGuard.parent = this; |
| then.parent = this; |
| otherwise?.parent = this; |
| } |
| |
| @override |
| ExpressionInferenceResult acceptInference( |
| InferenceVisitorImpl visitor, DartType typeContext) { |
| throw new UnsupportedError("IfCaseElement.acceptInference"); |
| } |
| |
| @override |
| // Coverage-ignore(suite): Not run. |
| void toTextInternal(AstPrinter printer) { |
| printer.write('if ('); |
| printer.writeExpression(expression); |
| printer.write(' case '); |
| patternGuard.toTextInternal(printer); |
| printer.write(') '); |
| printer.writeExpression(then); |
| if (otherwise != null) { |
| printer.write(' else '); |
| printer.writeExpression(otherwise!); |
| } |
| } |
| |
| @override |
| MapLiteralEntry? toMapLiteralEntry( |
| void Function(TreeNode from, TreeNode to) onConvertElement) { |
| 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) { |
| // Coverage-ignore-block(suite): Not run. |
| if (otherwise is ControlFlowElement) { |
| ControlFlowElement otherwiseElement = otherwise; |
| otherwiseEntry = otherwiseElement.toMapLiteralEntry(onConvertElement); |
| } |
| if (otherwiseEntry == null) return null; |
| } |
| IfCaseMapEntry result = new IfCaseMapEntry( |
| prelude: prelude, |
| expression: expression, |
| patternGuard: patternGuard, |
| then: thenEntry, |
| otherwise: otherwiseEntry) |
| ..matchedValueType = matchedValueType |
| ..fileOffset = fileOffset; |
| onConvertElement(this, result); |
| return result; |
| } |
| |
| @override |
| String toString() { |
| return "IfCaseElement(${toStringInternal()})"; |
| } |
| } |
| |
| abstract interface class ForElementBase implements AuxiliaryExpression { |
| List<VariableDeclaration> get variables; |
| |
| abstract Expression? condition; |
| |
| List<Expression> get updates; |
| |
| abstract Expression body; |
| } |
| |
| class PatternForElement extends ControlFlowElementImpl |
| with ControlFlowElementMixin |
| implements ForElementBase { |
| PatternVariableDeclaration patternVariableDeclaration; |
| List<VariableDeclaration> intermediateVariables; |
| |
| @override |
| final List<VariableDeclaration> variables; // May be empty, but not null. |
| |
| @override |
| Expression? condition; // May be null. |
| |
| @override |
| final List<Expression> updates; // May be empty, but not null. |
| |
| @override |
| Expression body; |
| |
| PatternForElement( |
| {required this.patternVariableDeclaration, |
| required this.intermediateVariables, |
| required this.variables, |
| required this.condition, |
| required this.updates, |
| required this.body}); |
| |
| @override |
| ExpressionInferenceResult acceptInference( |
| InferenceVisitorImpl visitor, DartType typeContext) { |
| throw new UnsupportedError("PatternForElement.acceptInference"); |
| } |
| |
| @override |
| // Coverage-ignore(suite): Not run. |
| void toTextInternal(AstPrinter printer) { |
| patternVariableDeclaration.toTextInternal(printer); |
| printer.write('for ('); |
| for (int index = 0; index < variables.length; index++) { |
| if (index > 0) { |
| printer.write(', '); |
| } |
| printer.writeVariableDeclaration(variables[index], |
| includeModifiersAndType: index == 0); |
| } |
| printer.write('; '); |
| if (condition != null) { |
| printer.writeExpression(condition!); |
| } |
| printer.write('; '); |
| printer.writeExpressions(updates); |
| printer.write(') '); |
| printer.writeExpression(body); |
| } |
| |
| @override |
| MapLiteralEntry? toMapLiteralEntry( |
| void Function(TreeNode from, TreeNode to) onConvertElement) { |
| throw new UnimplementedError("toMapLiteralEntry"); |
| } |
| |
| @override |
| String toString() { |
| return "PatternForElement(${toStringInternal()})"; |
| } |
| } |
| |
| /// Base class for all control-flow map entries. |
| sealed class ControlFlowMapEntry implements MapLiteralEntry {} |
| |
| mixin ControlFlowMapEntryMixin 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 |
| // Coverage-ignore(suite): Not run. |
| R accept<R>(TreeVisitor<R> v) => v.visitMapLiteralEntry(this); |
| |
| @override |
| // Coverage-ignore(suite): Not run. |
| R accept1<R, A>(TreeVisitor1<R, A> v, A arg) => |
| v.visitMapLiteralEntry(this, arg); |
| |
| @override |
| // Coverage-ignore(suite): Not run. |
| String toStringInternal() => toText(defaultAstTextStrategy); |
| } |
| |
| /// A null-aware entry in a map literal. |
| class NullAwareMapEntry extends TreeNode |
| with ControlFlowMapEntryMixin |
| implements ControlFlowMapEntry { |
| /// `true` if the key expression is null-aware, that is, marked with `?`. |
| bool isKeyNullAware; |
| |
| @override |
| Expression key; |
| |
| /// `true` if the value expression is null-aware, that is, marked with `?`. |
| bool isValueNullAware; |
| |
| @override |
| Expression value; |
| |
| NullAwareMapEntry( |
| {required this.isKeyNullAware, |
| required this.key, |
| required this.isValueNullAware, |
| required this.value}); |
| |
| @override |
| // Coverage-ignore(suite): Not run. |
| void toTextInternal(AstPrinter printer) { |
| if (isKeyNullAware) { |
| printer.write('?'); |
| } |
| key.toTextInternal(printer); |
| printer.write(': '); |
| if (isValueNullAware) { |
| printer.write('?'); |
| } |
| value.toTextInternal(printer); |
| } |
| |
| @override |
| // Coverage-ignore(suite): Not run. |
| void transformChildren(Transformer v) { |
| key = v.transform(key); |
| key.parent = this; |
| value = v.transform(value); |
| value.parent = this; |
| } |
| |
| @override |
| // Coverage-ignore(suite): Not run. |
| void transformOrRemoveChildren(RemovingTransformer v) { |
| key = v.transform(key); |
| key.parent = this; |
| value = v.transform(value); |
| value.parent = this; |
| } |
| |
| @override |
| // Coverage-ignore(suite): Not run. |
| void visitChildren(Visitor v) { |
| key.accept(v); |
| value.accept(v); |
| } |
| } |
| |
| /// A spread element in a map literal. |
| class SpreadMapEntry extends TreeNode |
| with ControlFlowMapEntryMixin |
| implements 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}) { |
| expression.parent = this; |
| } |
| |
| @override |
| // Coverage-ignore(suite): Not run. |
| void visitChildren(Visitor v) { |
| expression.accept(v); |
| } |
| |
| @override |
| // Coverage-ignore(suite): Not run. |
| void transformChildren(Transformer v) { |
| expression = v.transform(expression); |
| expression.parent = this; |
| } |
| |
| @override |
| // Coverage-ignore(suite): Not run. |
| void transformOrRemoveChildren(RemovingTransformer v) { |
| expression = v.transform(expression); |
| expression.parent = this; |
| } |
| |
| @override |
| String toString() { |
| return "SpreadMapEntry(${toStringInternal()})"; |
| } |
| |
| @override |
| // Coverage-ignore(suite): Not run. |
| void toTextInternal(AstPrinter printer) { |
| printer.write('...'); |
| expression.toTextInternal(printer); |
| } |
| } |
| |
| /// An 'if' element in a map literal. |
| class IfMapEntry extends TreeNode |
| with ControlFlowMapEntryMixin |
| implements ControlFlowMapEntry { |
| Expression condition; |
| MapLiteralEntry then; |
| MapLiteralEntry? otherwise; |
| |
| IfMapEntry(this.condition, this.then, this.otherwise) { |
| condition.parent = this; |
| then.parent = this; |
| otherwise?.parent = this; |
| } |
| |
| @override |
| // Coverage-ignore(suite): Not run. |
| void visitChildren(Visitor v) { |
| condition.accept(v); |
| then.accept(v); |
| otherwise?.accept(v); |
| } |
| |
| @override |
| // Coverage-ignore(suite): Not run. |
| void transformChildren(Transformer v) { |
| condition = v.transform(condition); |
| condition.parent = this; |
| then = v.transform(then); |
| then.parent = this; |
| if (otherwise != null) { |
| otherwise = v.transform(otherwise!); |
| otherwise?.parent = this; |
| } |
| } |
| |
| @override |
| // Coverage-ignore(suite): Not run. |
| void transformOrRemoveChildren(RemovingTransformer v) { |
| condition = v.transform(condition); |
| condition.parent = this; |
| 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 |
| // Coverage-ignore(suite): Not run. |
| void toTextInternal(AstPrinter printer) { |
| printer.write('if ('); |
| condition.toTextInternal(printer); |
| printer.write(') '); |
| then.toTextInternal(printer); |
| if (otherwise != null) { |
| printer.write(' else '); |
| otherwise!.toTextInternal(printer); |
| } |
| } |
| } |
| |
| abstract interface class ForMapEntryBase implements TreeNode, MapLiteralEntry { |
| List<VariableDeclaration> get variables; |
| |
| abstract Expression? condition; |
| |
| List<Expression> get updates; |
| |
| abstract MapLiteralEntry body; |
| } |
| |
| /// A 'for' element in a map literal. |
| class ForMapEntry extends TreeNode |
| with ControlFlowMapEntryMixin |
| implements ForMapEntryBase, ControlFlowMapEntry { |
| @override |
| final List<VariableDeclaration> variables; // May be empty, but not null. |
| |
| @override |
| Expression? condition; // May be null. |
| |
| @override |
| final List<Expression> updates; // May be empty, but not null. |
| |
| @override |
| MapLiteralEntry body; |
| |
| ForMapEntry(this.variables, this.condition, this.updates, this.body) { |
| setParents(variables, this); |
| condition?.parent = this; |
| setParents(updates, this); |
| body.parent = this; |
| } |
| |
| @override |
| // Coverage-ignore(suite): Not run. |
| void visitChildren(Visitor v) { |
| visitList(variables, v); |
| condition?.accept(v); |
| visitList(updates, v); |
| body.accept(v); |
| } |
| |
| @override |
| // Coverage-ignore(suite): Not run. |
| void transformChildren(Transformer v) { |
| v.transformList(variables, this); |
| if (condition != null) { |
| condition = v.transform(condition!); |
| condition?.parent = this; |
| } |
| v.transformList(updates, this); |
| body = v.transform(body); |
| body.parent = this; |
| } |
| |
| @override |
| // Coverage-ignore(suite): Not run. |
| void transformOrRemoveChildren(RemovingTransformer v) { |
| v.transformVariableDeclarationList(variables, this); |
| if (condition != null) { |
| condition = v.transformOrRemoveExpression(condition!); |
| condition?.parent = this; |
| } |
| v.transformExpressionList(updates, this); |
| body = v.transform(body); |
| body.parent = this; |
| } |
| |
| @override |
| String toString() { |
| return "ForMapEntry(${toStringInternal()})"; |
| } |
| |
| @override |
| // Coverage-ignore(suite): Not run. |
| void toTextInternal(AstPrinter printer) { |
| printer.write('for ('); |
| for (int index = 0; index < variables.length; index++) { |
| if (index > 0) { |
| printer.write(', '); |
| } |
| printer.writeVariableDeclaration(variables[index], |
| includeModifiersAndType: index == 0); |
| } |
| printer.write('; '); |
| if (condition != null) { |
| printer.writeExpression(condition!); |
| } |
| printer.write('; '); |
| printer.writeExpressions(updates); |
| printer.write(') '); |
| body.toTextInternal(printer); |
| } |
| } |
| |
| class PatternForMapEntry extends TreeNode |
| with InternalTreeNode, ControlFlowMapEntryMixin |
| implements ForMapEntryBase, ControlFlowMapEntry { |
| PatternVariableDeclaration patternVariableDeclaration; |
| List<VariableDeclaration> intermediateVariables; |
| |
| @override |
| final List<VariableDeclaration> variables; |
| |
| @override |
| Expression? condition; |
| |
| @override |
| final List<Expression> updates; |
| |
| @override |
| MapLiteralEntry body; |
| |
| PatternForMapEntry( |
| {required this.patternVariableDeclaration, |
| required this.intermediateVariables, |
| required this.variables, |
| required this.condition, |
| required this.updates, |
| required this.body}); |
| |
| @override |
| // Coverage-ignore(suite): Not run. |
| void toTextInternal(AstPrinter printer) { |
| patternVariableDeclaration.toTextInternal(printer); |
| printer.write('for ('); |
| for (int index = 0; index < variables.length; index++) { |
| if (index > 0) { |
| printer.write(', '); |
| } |
| printer.writeVariableDeclaration(variables[index], |
| includeModifiersAndType: index == 0); |
| } |
| printer.write('; '); |
| if (condition != null) { |
| printer.writeExpression(condition!); |
| } |
| printer.write('; '); |
| printer.writeExpressions(updates); |
| printer.write(') '); |
| body.toTextInternal(printer); |
| } |
| |
| @override |
| String toString() { |
| return "PatternForMapEntry(${toStringInternal()})"; |
| } |
| } |
| |
| /// A 'for-in' element in a map literal. |
| class ForInMapEntry extends TreeNode |
| with ControlFlowMapEntryMixin |
| implements 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}) { |
| variable.parent = this; |
| iterable.parent = this; |
| syntheticAssignment?.parent = this; |
| expressionEffects?.parent = this; |
| body.parent = this; |
| problem |
| // Coverage-ignore(suite): Not run. |
| ?.parent = this; |
| } |
| |
| Statement? get prologue => syntheticAssignment != null |
| ? (new ExpressionStatement(syntheticAssignment!) |
| ..fileOffset = syntheticAssignment!.fileOffset) |
| : expressionEffects; |
| |
| @override |
| // Coverage-ignore(suite): Not run. |
| void visitChildren(Visitor v) { |
| variable.accept(v); |
| iterable.accept(v); |
| syntheticAssignment?.accept(v); |
| expressionEffects?.accept(v); |
| body.accept(v); |
| problem?.accept(v); |
| } |
| |
| @override |
| // Coverage-ignore(suite): Not run. |
| void transformChildren(Transformer v) { |
| variable = v.transform(variable); |
| variable.parent = this; |
| 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; |
| } |
| body = v.transform(body); |
| body.parent = this; |
| if (problem != null) { |
| problem = v.transform(problem!); |
| problem?.parent = this; |
| } |
| } |
| |
| @override |
| // Coverage-ignore(suite): Not run. |
| void transformOrRemoveChildren(RemovingTransformer v) { |
| variable = v.transform(variable); |
| variable.parent = this; |
| 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; |
| } |
| body = v.transform(body); |
| body.parent = this; |
| if (problem != null) { |
| problem = v.transformOrRemoveExpression(problem!); |
| problem?.parent = this; |
| } |
| } |
| |
| @override |
| String toString() { |
| return "ForInMapEntry(${toStringInternal()})"; |
| } |
| |
| @override |
| // Coverage-ignore(suite): Not run. |
| void toTextInternal(AstPrinter state) { |
| // TODO(johnniwinther): Implement this. |
| } |
| } |
| |
| class IfCaseMapEntry extends TreeNode |
| with InternalTreeNode, ControlFlowMapEntryMixin |
| implements ControlFlowMapEntry { |
| Expression expression; |
| PatternGuard patternGuard; |
| MapLiteralEntry then; |
| MapLiteralEntry? otherwise; |
| List<Statement> prelude; |
| |
| /// The type of the expression against which this pattern is matched. |
| /// |
| /// This is set during inference. |
| DartType? matchedValueType; |
| |
| IfCaseMapEntry( |
| {required this.prelude, |
| required this.expression, |
| required this.patternGuard, |
| required this.then, |
| this.otherwise}) { |
| expression.parent = this; |
| patternGuard.parent = this; |
| then.parent = this; |
| otherwise?.parent = this; |
| } |
| |
| @override |
| // Coverage-ignore(suite): Not run. |
| void toTextInternal(AstPrinter printer) { |
| printer.write('if ('); |
| expression.toTextInternal(printer); |
| printer.write(' case '); |
| patternGuard.toTextInternal(printer); |
| printer.write(') '); |
| then.toTextInternal(printer); |
| if (otherwise != null) { |
| printer.write(' else '); |
| otherwise!.toTextInternal(printer); |
| } |
| } |
| |
| @override |
| String toString() { |
| return "IfCaseMapEntry(${toStringInternal()})"; |
| } |
| } |
| |
| /// 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), { |
| DartType? actualType, |
| }) { |
| if (entry is ControlFlowMapEntry) { |
| switch (entry) { |
| case SpreadMapEntry(): |
| return new SpreadElement(entry.expression, |
| isNullAware: entry.isNullAware) |
| ..elementType = actualType |
| ..fileOffset = entry.expression.fileOffset; |
| case IfMapEntry(): |
| IfElement result = new IfElement( |
| entry.condition, |
| convertToElement(entry.then, helper, onConvertMapEntry), |
| entry.otherwise == null |
| ? null |
| : |
| // Coverage-ignore(suite): Not run. |
| convertToElement(entry.otherwise!, helper, onConvertMapEntry)) |
| ..fileOffset = entry.fileOffset; |
| onConvertMapEntry(entry, result); |
| return result; |
| case NullAwareMapEntry(): |
| // Coverage-ignore(suite): Not run. |
| return _convertToErroneousElement(entry, helper); |
| case IfCaseMapEntry(): |
| IfCaseElement result = new IfCaseElement( |
| prelude: entry.prelude, |
| expression: entry.expression, |
| patternGuard: entry.patternGuard, |
| then: convertToElement(entry.then, helper, onConvertMapEntry), |
| otherwise: entry.otherwise == null |
| ? null |
| : |
| // Coverage-ignore(suite): Not run. |
| convertToElement(entry.otherwise!, helper, onConvertMapEntry)) |
| ..matchedValueType = entry.matchedValueType |
| ..fileOffset = entry.fileOffset; |
| onConvertMapEntry(entry, result); |
| return result; |
| case PatternForMapEntry(): |
| PatternForElement result = new PatternForElement( |
| patternVariableDeclaration: entry.patternVariableDeclaration, |
| intermediateVariables: entry.intermediateVariables, |
| variables: entry.variables, |
| condition: entry.condition, |
| updates: entry.updates, |
| body: convertToElement(entry.body, helper, onConvertMapEntry)) |
| ..fileOffset = entry.fileOffset; |
| onConvertMapEntry(entry, result); |
| return result; |
| case ForMapEntry(): |
| ForElement result = new ForElement( |
| entry.variables, |
| entry.condition, |
| entry.updates, |
| convertToElement(entry.body, helper, onConvertMapEntry)) |
| ..fileOffset = entry.fileOffset; |
| onConvertMapEntry(entry, result); |
| return result; |
| case 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; |
| } |
| } else { |
| return _convertToErroneousElement(entry, helper); |
| } |
| } |
| |
| Expression _convertToErroneousElement( |
| MapLiteralEntry entry, InferenceHelper? helper) { |
| 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; |
| } |
| } |
| // Coverage-ignore(suite): Not run. |
| // 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 ControlFlowElement) { |
| switch (element) { |
| case SpreadElement(): |
| return true; |
| case NullAwareElement(): |
| return false; |
| case IfElement(): |
| return isConvertibleToMapEntry(element.then) && |
| (element.otherwise == null || |
| isConvertibleToMapEntry(element.otherwise!)); |
| case IfCaseElement(): |
| return isConvertibleToMapEntry(element.then) && |
| (element.otherwise == null || |
| isConvertibleToMapEntry(element.otherwise!)); |
| case ForElement(): |
| return isConvertibleToMapEntry(element.body); |
| case PatternForElement(): |
| return isConvertibleToMapEntry(element.body); |
| case ForInElement(): |
| return isConvertibleToMapEntry(element.body); |
| } |
| } else { |
| 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 ControlFlowElement) { |
| switch (element) { |
| case SpreadElement(): |
| return new SpreadMapEntry(element.expression, |
| isNullAware: element.isNullAware) |
| ..fileOffset = element.expression.fileOffset; |
| |
| case NullAwareElement(): |
| // Coverage-ignore(suite): Not run. |
| return _convertToErroneousMapEntry(element, helper); |
| |
| case 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; |
| |
| case IfCaseElement(): |
| IfCaseMapEntry result = new IfCaseMapEntry( |
| prelude: [], |
| expression: element.expression, |
| patternGuard: element.patternGuard, |
| then: convertToMapEntry(element.then, helper, onConvertElement), |
| otherwise: element.otherwise == null |
| ? null |
| : convertToMapEntry( |
| element.otherwise!, helper, onConvertElement)) |
| ..matchedValueType = element.matchedValueType |
| ..fileOffset = element.fileOffset; |
| onConvertElement(element, result); |
| return result; |
| |
| case PatternForElement(): |
| PatternForMapEntry result = new PatternForMapEntry( |
| patternVariableDeclaration: element.patternVariableDeclaration, |
| intermediateVariables: element.intermediateVariables, |
| variables: element.variables, |
| condition: element.condition, |
| updates: element.updates, |
| body: convertToMapEntry(element.body, helper, onConvertElement)) |
| ..fileOffset = element.fileOffset; |
| onConvertElement(element, result); |
| return result; |
| |
| case ForElement(): |
| ForMapEntry result = new ForMapEntry( |
| element.variables, |
| element.condition, |
| element.updates, |
| convertToMapEntry(element.body, helper, onConvertElement)) |
| ..fileOffset = element.fileOffset; |
| onConvertElement(element, result); |
| return result; |
| |
| case 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; |
| } |
| } else { |
| // Coverage-ignore-block(suite): Not run. |
| return _convertToErroneousMapEntry(element, helper); |
| } |
| } |
| |
| // Coverage-ignore(suite): Not run. |
| MapLiteralEntry _convertToErroneousMapEntry( |
| Expression element, InferenceHelper helper) { |
| 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; |
| } |