blob: b55482fcb7ceea8af17d42f8430abe04929428f4 [file]
// 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.
part of 'internal_ast.dart';
// Coverage-ignore(suite): Not run.
/// Base class for all control-flow elements.
sealed class ControlFlowElement extends InternalExpression {
/// 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].
InternalMapLiteralEntry? toMapLiteralEntry(
void onConvertElement(TreeNode from, TreeNode to),
);
@override
ExpressionInferenceResult acceptInference(
InferenceVisitorImpl visitor,
DartType typeContext,
) {
return unsupported("acceptInference", fileOffset, getFileUri(this));
}
}
/// A spread element in a list or set literal.
class SpreadElement extends ControlFlowElement {
final Expression expression;
final bool isNullAware;
new(this.expression, {required this.isNullAware}) {
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 {
final Expression expression;
new(this.expression);
@override
// Coverage-ignore(suite): Not run.
InternalMapLiteralEntry? 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
String toString() {
return "NullAwareElement(${toStringInternal()})";
}
}
/// An 'if' element in a list or set literal.
class IfElement extends ControlFlowElement {
final Expression condition;
final Expression then;
final Expression? otherwise;
new(this.condition, this.then, this.otherwise) {
condition.parent = this;
then.parent = this;
otherwise?.parent = this;
}
@override
InternalMapLiteralEntry? toMapLiteralEntry(
void onConvertElement(TreeNode from, TreeNode to),
) {
InternalMapLiteralEntry? thenEntry;
Expression then = this.then;
if (then is ControlFlowElement) {
ControlFlowElement thenElement = then;
thenEntry = thenElement.toMapLiteralEntry(onConvertElement);
}
if (thenEntry == null) return null;
InternalMapLiteralEntry? 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 implements ForElementBase {
// May be empty, but not null.
@override
final List<InternalVariableDeclaration> variables;
@override
final Expression? condition; // May be null.
@override
final List<Expression> updates; // May be empty, but not null.
@override
final Expression body;
new(this.variables, this.condition, this.updates, this.body) {
setParents(variables, this);
condition?.parent = this;
setParents(updates, this);
body.parent = this;
}
@override
InternalMapLiteralEntry? toMapLiteralEntry(
void onConvertElement(TreeNode from, TreeNode to),
) {
InternalMapLiteralEntry? 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(', ');
}
variables[index].variable.toTextInternal(
printer,
includeModifiersAndType: index == 0,
initializer: variables[index].initializer,
);
}
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 {
final InternalForInElement element;
final Expression iterable;
final Expression body;
final bool isAsync; // True if this is an 'await for' loop.
/// File offset for the `for` keyword.
final int forOffset;
new(
this.element,
this.iterable,
this.body, {
required this.isAsync,
required int fileOffset,
required this.forOffset,
}) {
this.fileOffset = fileOffset;
iterable.parent = this;
body.parent = this;
}
@override
// Coverage-ignore(suite): Not run.
void toTextInternal(AstPrinter state) {
// TODO(johnniwinther): Implement this.
}
@override
String toString() {
return "ForInElement(${toStringInternal()})";
}
@override
InternalMapLiteralEntry? toMapLiteralEntry(
void Function(TreeNode from, TreeNode to) onConvertElement,
) {
InternalMapLiteralEntry? bodyEntry;
Expression body = this.body;
if (body is ControlFlowElement) {
bodyEntry = body.toMapLiteralEntry(onConvertElement);
}
if (bodyEntry == null) return null;
ForInMapEntry result = new ForInMapEntry(
element,
iterable,
bodyEntry,
isAsync: isAsync,
fileOffset: fileOffset,
forOffset: forOffset,
);
onConvertElement(this, result);
return result;
}
}
class IfCaseElement extends ControlFlowElement {
final Expression expression;
final InternalPatternGuard patternGuard;
final Expression then;
final Expression? otherwise;
new({
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 (');
printer.writeExpression(expression);
printer.write(' case ');
patternGuard.toTextInternal(printer);
printer.write(') ');
printer.writeExpression(then);
if (otherwise != null) {
printer.write(' else ');
printer.writeExpression(otherwise!);
}
}
@override
InternalMapLiteralEntry? toMapLiteralEntry(
void Function(TreeNode from, TreeNode to) onConvertElement,
) {
InternalMapLiteralEntry? thenEntry;
Expression then = this.then;
if (then is ControlFlowElement) {
ControlFlowElement thenElement = then;
thenEntry = thenElement.toMapLiteralEntry(onConvertElement);
}
if (thenEntry == null) return null;
InternalMapLiteralEntry? 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(
expression: expression,
patternGuard: this.patternGuard,
then: thenEntry,
otherwise: otherwiseEntry,
)..fileOffset = fileOffset;
onConvertElement(this, result);
return result;
}
@override
String toString() {
return "IfCaseElement(${toStringInternal()})";
}
}
abstract interface class ForElementBase implements AuxiliaryExpression {
List<InternalVariableDeclaration> get variables;
Expression? get condition;
List<Expression> get updates;
Expression get body;
}
class PatternForElement extends ControlFlowElement implements ForElementBase {
final InternalPatternVariableDeclaration patternVariableDeclaration;
final List<InternalVariableDeclaration> intermediateVariables;
// May be empty, but not null.
@override
final List<InternalVariableDeclaration> variables;
@override
final Expression? condition; // May be null.
@override
final List<Expression> updates; // May be empty, but not null.
@override
final Expression body;
new({
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(', ');
}
variables[index].variable.toTextInternal(
printer,
includeModifiersAndType: index == 0,
initializer: variables[index].initializer,
);
}
printer.write('; ');
if (condition != null) {
printer.writeExpression(condition!);
}
printer.write('; ');
printer.writeExpressions(updates);
printer.write(') ');
printer.writeExpression(body);
}
@override
InternalMapLiteralEntry? toMapLiteralEntry(
void Function(TreeNode from, TreeNode to) onConvertElement,
) {
throw new UnimplementedError("toMapLiteralEntry");
}
@override
String toString() {
return "PatternForElement(${toStringInternal()})";
}
}
// Coverage-ignore(suite): Not run.
/// Base class for all control-flow map entries.
sealed class ControlFlowMapEntry extends TreeNode
with InternalTreeNode
implements InternalMapLiteralEntry {
@override
R accept<R>(TreeVisitor<R> v) {
throw new UnsupportedError('$runtimeType.accept');
}
@override
R accept1<R, A>(TreeVisitor1<R, A> v, A arg) {
throw new UnsupportedError('$runtimeType.accept1');
}
@override
String toStringInternal() => toText(defaultAstTextStrategy);
}
/// A null-aware entry in a map literal.
class NullAwareMapEntry extends ControlFlowMapEntry {
/// `true` if the key expression is null-aware, that is, marked with `?`.
final bool isKeyNullAware;
final Expression key;
/// `true` if the value expression is null-aware, that is, marked with `?`.
final bool isValueNullAware;
final Expression value;
new({
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
String toString() {
return "$runtimeType(${toStringInternal()})";
}
}
/// A spread element in a map literal.
class SpreadMapEntry extends ControlFlowMapEntry {
final Expression expression;
final bool isNullAware;
new(this.expression, {required this.isNullAware}) {
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 ControlFlowMapEntry {
final Expression condition;
final InternalMapLiteralEntry then;
final InternalMapLiteralEntry? otherwise;
new(this.condition, this.then, this.otherwise) {
condition.parent = this;
then.parent = this;
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);
}
}
}
sealed class ForMapEntryBase implements TreeNode, InternalMapLiteralEntry {
List<InternalVariableDeclaration> get variables;
Expression? get condition;
List<Expression> get updates;
InternalMapLiteralEntry get body;
}
/// A 'for' element in a map literal.
class ForMapEntry extends ControlFlowMapEntry implements ForMapEntryBase {
// May be empty, but not null.
@override
final List<InternalVariableDeclaration> variables;
@override
final Expression? condition; // May be null.
@override
final List<Expression> updates; // May be empty, but not null.
@override
final InternalMapLiteralEntry body;
new(this.variables, this.condition, this.updates, this.body) {
setParents(variables, this);
condition?.parent = this;
setParents(updates, this);
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(', ');
}
variables[index].variable.toTextInternal(
printer,
includeModifiersAndType: index == 0,
initializer: variables[index].initializer,
);
}
printer.write('; ');
if (condition != null) {
printer.writeExpression(condition!);
}
printer.write('; ');
printer.writeExpressions(updates);
printer.write(') ');
body.toTextInternal(printer);
}
}
class PatternForMapEntry extends ControlFlowMapEntry
implements ForMapEntryBase {
final InternalPatternVariableDeclaration patternVariableDeclaration;
final List<InternalVariableDeclaration> intermediateVariables;
@override
final List<InternalVariableDeclaration> variables;
@override
final Expression? condition;
@override
final List<Expression> updates;
@override
final InternalMapLiteralEntry body;
new({
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(', ');
}
variables[index].variable.toTextInternal(
printer,
includeModifiersAndType: index == 0,
initializer: variables[index].initializer,
);
}
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 ControlFlowMapEntry {
final InternalForInElement element;
final Expression iterable;
final InternalMapLiteralEntry body;
final bool isAsync; // True if this is an 'await for' loop.
/// File offset for the `for` keyword.
final int forOffset;
new(
this.element,
this.iterable,
this.body, {
required this.isAsync,
required int fileOffset,
required this.forOffset,
}) {
this.fileOffset = fileOffset;
iterable.parent = this;
body.parent = this;
}
@override
// Coverage-ignore(suite): Not run.
void toTextInternal(AstPrinter state) {
// TODO(johnniwinther): Implement this.
}
@override
String toString() {
return "ForInMapEntry(${toStringInternal()})";
}
}
class IfCaseMapEntry extends ControlFlowMapEntry {
final Expression expression;
final InternalPatternGuard patternGuard;
final InternalMapLiteralEntry then;
final InternalMapLiteralEntry? otherwise;
new({
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()})";
}
}
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.
InternalMapLiteralEntry convertToMapEntry(
Expression element,
ProblemReporting problemReporting,
CompilerContext compilerContext,
Uri fileUri,
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,
problemReporting,
compilerContext,
fileUri,
);
case IfElement():
IfMapEntry result = new IfMapEntry(
element.condition,
convertToMapEntry(
element.then,
problemReporting,
compilerContext,
fileUri,
onConvertElement,
),
element.otherwise == null
? null
: convertToMapEntry(
element.otherwise!,
problemReporting,
compilerContext,
fileUri,
onConvertElement,
),
)..fileOffset = element.fileOffset;
onConvertElement(element, result);
return result;
case IfCaseElement():
IfCaseMapEntry result = new IfCaseMapEntry(
expression: element.expression,
patternGuard: element.patternGuard,
then: convertToMapEntry(
element.then,
problemReporting,
compilerContext,
fileUri,
onConvertElement,
),
otherwise: element.otherwise == null
? null
: convertToMapEntry(
element.otherwise!,
problemReporting,
compilerContext,
fileUri,
onConvertElement,
),
)..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,
problemReporting,
compilerContext,
fileUri,
onConvertElement,
),
)..fileOffset = element.fileOffset;
onConvertElement(element, result);
return result;
case ForElement():
ForMapEntry result = new ForMapEntry(
element.variables,
element.condition,
element.updates,
convertToMapEntry(
element.body,
problemReporting,
compilerContext,
fileUri,
onConvertElement,
),
)..fileOffset = element.fileOffset;
onConvertElement(element, result);
return result;
case ForInElement():
ForInMapEntry result = new ForInMapEntry(
element.element,
element.iterable,
convertToMapEntry(
element.body,
problemReporting,
compilerContext,
fileUri,
onConvertElement,
),
fileOffset: element.fileOffset,
forOffset: element.forOffset,
isAsync: element.isAsync,
);
onConvertElement(element, result);
return result;
}
} else {
return _convertToErroneousMapEntry(
element,
problemReporting,
compilerContext,
fileUri,
);
}
}
InternalMapLiteralEntry _convertToErroneousMapEntry(
Expression element,
ProblemReporting problemReporting,
CompilerContext compilerContext,
Uri fileUri,
) {
return intern.createMapLiteralEntry(
intern.createInvalidExpressionFromErrorText(
problemReporting.buildProblem(
compilerContext: compilerContext,
message: diag.expectedAfterButGot.withArguments(expected: ':'),
fileUri: fileUri,
fileOffset: element.fileOffset,
// TODO(danrubel): what is the length of the expression?
length: noLength,
),
),
intern.createNullLiteral(element.fileOffset),
fileOffset: element.fileOffset,
);
}