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