blob: 660ee0a0259e24583ba0dde613a59c70d12ae22c [file] [log] [blame]
// 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;
}