blob: 275404e1072f9a7b531cf9dbaa7c279d3343282a [file] [log] [blame]
// Copyright (c) 2017, 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.
import 'package:meta/meta.dart';
import '../base.dart';
import '../emitter.dart';
import '../visitors.dart';
import 'code.dart';
import 'method.dart';
import 'reference.dart';
import 'type_function.dart';
part 'expression/binary.dart';
part 'expression/closure.dart';
part 'expression/code.dart';
part 'expression/invoke.dart';
part 'expression/literal.dart';
part 'expression/parenthesized.dart';
/// Represents a [code] block that wraps an [Expression].
/// Represents a Dart expression.
///
/// See various concrete implementations for details.
abstract class Expression implements Spec {
const Expression();
/// An empty expression.
static const _empty = CodeExpression(Code(''));
/// Whether this expression implies a const context for sub expressions.
///
/// Collection literals that are const imply const for all values.
/// Assignment to a const variable implies a const value.
/// Invoking a const constructor implies const for all arguments.
///
/// The implied const context is used to omit redundant `const` keywords.
/// A value of `false` does not imply that the expression cannot be used in a
/// const context.
bool get isConst => false;
@override
R accept<R>(covariant ExpressionVisitor<R> visitor, [R? context]);
/// The expression as a valid [Code] block.
///
/// Also see [statement].
Code get code => ToCodeExpression(this);
/// The expression as a valid [Code] block with a trailing `;`.
Code get statement => ToCodeExpression(this, true);
/// Returns the result of `this` `&&` [other].
Expression and(Expression other) =>
BinaryExpression._(expression, other, '&&');
/// Returns the result of `this` `||` [other].
Expression or(Expression other) =>
BinaryExpression._(expression, other, '||');
/// Returns the result of `!this`.
Expression negate() =>
BinaryExpression._(_empty, expression, '!', addSpace: false);
/// Returns the result of `this` `as` [other].
Expression asA(Expression other) =>
ParenthesizedExpression._(BinaryExpression._(
expression,
other,
'as',
));
/// Returns accessing the index operator (`[]`) on `this`.
Expression index(Expression index) => BinaryExpression._(
expression,
CodeExpression(Block.of([
const Code('['),
index.code,
const Code(']'),
])),
'',
);
/// Returns the result of `this` `is` [other].
Expression isA(Expression other) => BinaryExpression._(
expression,
other,
'is',
);
/// Returns the result of `this` `is!` [other].
Expression isNotA(Expression other) => BinaryExpression._(
expression,
other,
'is!',
);
/// Returns the result of `this` `==` [other].
Expression equalTo(Expression other) => BinaryExpression._(
expression,
other,
'==',
);
/// Returns the result of `this` `!=` [other].
Expression notEqualTo(Expression other) => BinaryExpression._(
expression,
other,
'!=',
);
/// Returns the result of `this` `>` [other].
Expression greaterThan(Expression other) => BinaryExpression._(
expression,
other,
'>',
);
/// Returns the result of `this` `<` [other].
Expression lessThan(Expression other) => BinaryExpression._(
expression,
other,
'<',
);
/// Returns the result of `this` `>=` [other].
Expression greaterOrEqualTo(Expression other) => BinaryExpression._(
expression,
other,
'>=',
);
/// Returns the result of `this` `<=` [other].
Expression lessOrEqualTo(Expression other) => BinaryExpression._(
expression,
other,
'<=',
);
/// Returns the result of `this` `+` [other].
Expression operatorAdd(Expression other) => BinaryExpression._(
expression,
other,
'+',
);
/// Returns the result of `this` `-` [other].
// TODO(kevmoo): create a function spelled correctly and deprecate this one!
Expression operatorSubstract(Expression other) => BinaryExpression._(
expression,
other,
'-',
);
/// Returns the result of `this` `/` [other].
Expression operatorDivide(Expression other) => BinaryExpression._(
expression,
other,
'/',
);
/// Returns the result of `this` `*` [other].
Expression operatorMultiply(Expression other) => BinaryExpression._(
expression,
other,
'*',
);
/// Returns the result of `this` `%` [other].
Expression operatorEuclideanModulo(Expression other) => BinaryExpression._(
expression,
other,
'%',
);
Expression conditional(Expression whenTrue, Expression whenFalse) =>
BinaryExpression._(
expression,
BinaryExpression._(whenTrue, whenFalse, ':'),
'?',
);
/// This expression preceded by `await`.
Expression get awaited => BinaryExpression._(
_empty,
this,
'await',
);
/// Return `{this} = {other}`.
Expression assign(Expression other) =>
BinaryExpression._(this, other, '=', isConst: isConst);
/// Return `{this} ?? {other}`.
Expression ifNullThen(Expression other) => BinaryExpression._(
this,
other,
'??',
);
/// Return `{this} ??= {other}`.
Expression assignNullAware(Expression other) => BinaryExpression._(
this,
other,
'??=',
);
/// Return `var {name} = {this}`.
@Deprecated('Use `declareVar(name).assign(expression)`')
Expression assignVar(String name, [Reference? type]) => BinaryExpression._(
type == null
? LiteralExpression._('var $name')
: BinaryExpression._(
type.expression,
LiteralExpression._(name),
'',
),
this,
'=',
);
/// Return `final {name} = {this}`.
@Deprecated('Use `declareFinal(name).assign(expression)`')
Expression assignFinal(String name, [Reference? type]) => BinaryExpression._(
type == null
? const LiteralExpression._('final')
: BinaryExpression._(
const LiteralExpression._('final'),
type.expression,
'',
),
this,
'$name =',
);
/// Return `const {name} = {this}`.
@Deprecated('Use `declareConst(name).assign(expression)`')
Expression assignConst(String name, [Reference? type]) => BinaryExpression._(
type == null
? const LiteralExpression._('const')
: BinaryExpression._(
const LiteralExpression._('const'),
type.expression,
'',
),
this,
'$name =',
isConst: true,
);
/// Call this expression as a method.
Expression call(
Iterable<Expression> positionalArguments, [
Map<String, Expression> namedArguments = const {},
List<Reference> typeArguments = const [],
]) =>
InvokeExpression._(
this,
positionalArguments.toList(),
namedArguments,
typeArguments,
);
/// Returns an expression accessing `.<name>` on this expression.
Expression property(String name) => BinaryExpression._(
this,
LiteralExpression._(name),
'.',
addSpace: false,
);
/// Returns an expression accessing `..<name>` on this expression.
Expression cascade(String name) => BinaryExpression._(
this,
LiteralExpression._(name),
'..',
addSpace: false,
);
/// Returns an expression accessing `?.<name>` on this expression.
Expression nullSafeProperty(String name) => BinaryExpression._(
this,
LiteralExpression._(name),
'?.',
addSpace: false,
);
/// Applies the null check operator on this expression, returning `this` `!`.
///
/// Please note that this is only valid when emitting code with the null
/// safety syntax enabled.
Expression get nullChecked => BinaryExpression._(
this,
const LiteralExpression._('!'),
'',
addSpace: false,
);
/// This expression preceded by `return`.
Expression get returned => BinaryExpression._(
const LiteralExpression._('return'),
this,
'',
);
/// This expression preceded by the spread operator `...`.
Expression get spread => BinaryExpression._(
const LiteralExpression._('...'),
this,
'',
addSpace: false,
);
/// This expression preceded by the null safe spread operator `?...`.
Expression get nullSafeSpread => BinaryExpression._(
const LiteralExpression._('...?'),
this,
'',
addSpace: false,
);
/// This expression preceded by `throw`.
Expression get thrown => BinaryExpression._(
const LiteralExpression._('throw'),
this,
'',
);
/// May be overridden to support other types implementing [Expression].
@visibleForOverriding
Expression get expression => this;
}
/// Declare a const variable named [variableName].
///
/// Returns `const {variableName}`, or `const {type} {variableName}`.
Expression declareConst(String variableName, {Reference? type}) =>
BinaryExpression._(
const LiteralExpression._('const'),
type == null
? LiteralExpression._(variableName)
: _typedVar(variableName, type),
'',
isConst: true);
/// Declare a final variable named [variableName].
///
/// Returns `final {variableName}`, or `final {type} {variableName}`.
/// If [late] is true the declaration is prefixed with `late`.
Expression declareFinal(String variableName,
{Reference? type, bool late = false}) =>
_late(
late,
type == null
? LiteralExpression._('final $variableName')
: BinaryExpression._(const LiteralExpression._('final'),
_typedVar(variableName, type), ''));
/// Declare a variable named [variableName].
///
/// Returns `var {variableName}`, or `{type} {variableName}`.
/// If [late] is true the declaration is prefixed with `late`.
Expression declareVar(String variableName,
{Reference? type, bool late = false}) =>
_late(
late,
type == null
? LiteralExpression._('var $variableName')
: _typedVar(variableName, type));
Expression _typedVar(String variableName, Reference type) =>
BinaryExpression._(type.expression, LiteralExpression._(variableName), '');
Expression _late(bool late, Expression expression) => late
? BinaryExpression._(const LiteralExpression._('late'), expression, '')
: expression;
/// Creates `typedef {name} =`.
Code createTypeDef(String name, FunctionType type) => BinaryExpression._(
LiteralExpression._('typedef $name'), type.expression, '=')
.statement;
class ToCodeExpression implements Code {
final Expression code;
/// Whether this code should be considered a _statement_.
final bool isStatement;
@visibleForTesting
const ToCodeExpression(this.code, [this.isStatement = false]);
@override
R accept<R>(CodeVisitor<R> visitor, [R? context]) =>
(visitor as ExpressionVisitor<R>).visitToCodeExpression(this, context);
@override
String toString() => code.toString();
}
/// Knowledge of different types of expressions in Dart.
///
/// **INTERNAL ONLY**.
abstract class ExpressionVisitor<T> implements SpecVisitor<T> {
T visitToCodeExpression(ToCodeExpression code, [T? context]);
T visitBinaryExpression(BinaryExpression expression, [T? context]);
T visitClosureExpression(ClosureExpression expression, [T? context]);
T visitCodeExpression(CodeExpression expression, [T? context]);
T visitInvokeExpression(InvokeExpression expression, [T? context]);
T visitLiteralExpression(LiteralExpression expression, [T? context]);
T visitLiteralListExpression(LiteralListExpression expression, [T? context]);
T visitLiteralSetExpression(LiteralSetExpression expression, [T? context]);
T visitLiteralMapExpression(LiteralMapExpression expression, [T? context]);
T visitLiteralRecordExpression(LiteralRecordExpression expression,
[T? context]);
T visitParenthesizedExpression(ParenthesizedExpression expression,
[T? context]);
}
/// Knowledge of how to write valid Dart code from [ExpressionVisitor].
///
/// **INTERNAL ONLY**.
abstract class ExpressionEmitter implements ExpressionVisitor<StringSink> {
@override
StringSink visitToCodeExpression(ToCodeExpression expression,
[StringSink? output]) {
output ??= StringBuffer();
expression.code.accept(this, output);
if (expression.isStatement) {
output.write(';');
}
return output;
}
@override
StringSink visitBinaryExpression(BinaryExpression expression,
[StringSink? output]) {
output ??= StringBuffer();
expression.left.accept(this, output);
if (expression.addSpace) {
output.write(' ');
}
output.write(expression.operator);
if (expression.addSpace) {
output.write(' ');
}
startConstCode(expression.isConst, () {
expression.right.accept(this, output);
});
return output;
}
@override
StringSink visitClosureExpression(ClosureExpression expression,
[StringSink? output]) {
output ??= StringBuffer();
return expression.method.accept(this, output);
}
@override
StringSink visitCodeExpression(CodeExpression expression,
[StringSink? output]) {
output ??= StringBuffer();
final visitor = this as CodeVisitor<StringSink>;
return expression.code.accept(visitor, output);
}
@override
StringSink visitInvokeExpression(InvokeExpression expression,
[StringSink? output]) {
final out = output ??= StringBuffer();
return _writeConstExpression(out, expression.isConst, () {
expression.target.accept(this, out);
if (expression.name != null) {
out
..write('.')
..write(expression.name);
}
if (expression.typeArguments.isNotEmpty) {
out.write('<');
visitAll<Reference>(expression.typeArguments, out, (type) {
type.accept(this, out);
});
out.write('>');
}
out.write('(');
visitAll<Spec>(expression.positionalArguments, out, (spec) {
spec.accept(this, out);
});
if (expression.positionalArguments.isNotEmpty &&
expression.namedArguments.isNotEmpty) {
out.write(', ');
}
visitAll<String>(expression.namedArguments.keys, out, (name) {
out
..write(name)
..write(': ');
expression.namedArguments[name]!.accept(this, out);
});
final argumentCount = expression.positionalArguments.length +
expression.namedArguments.length;
if (argumentCount > 1) {
out.write(', ');
}
return out..write(')');
});
}
@override
StringSink visitLiteralExpression(LiteralExpression expression,
[StringSink? output]) {
output ??= StringBuffer();
return output..write(expression.literal);
}
void _acceptLiteral(Object? literalOrSpec, StringSink output) {
if (literalOrSpec is Spec) {
literalOrSpec.accept(this, output);
return;
}
literal(literalOrSpec).accept(this, output);
}
bool _withInConstExpression = false;
@override
StringSink visitLiteralListExpression(
LiteralListExpression expression, [
StringSink? output,
]) {
final out = output ??= StringBuffer();
return _writeConstExpression(output, expression.isConst, () {
if (expression.type != null) {
out.write('<');
expression.type!.accept(this, output);
out.write('>');
}
out.write('[');
visitAll<Object?>(expression.values, out, (value) {
_acceptLiteral(value, out);
});
if (expression.values.length > 1) {
out.write(', ');
}
return out..write(']');
});
}
@override
StringSink visitLiteralSetExpression(
LiteralSetExpression expression, [
StringSink? output,
]) {
final out = output ??= StringBuffer();
return _writeConstExpression(output, expression.isConst, () {
if (expression.type != null) {
out.write('<');
expression.type!.accept(this, output);
out.write('>');
}
out.write('{');
visitAll<Object?>(expression.values, out, (value) {
_acceptLiteral(value, out);
});
if (expression.values.length > 1) {
out.write(', ');
}
return out..write('}');
});
}
@override
StringSink visitLiteralMapExpression(
LiteralMapExpression expression, [
StringSink? output,
]) {
final out = output ??= StringBuffer();
return _writeConstExpression(out, expression.isConst, () {
if (expression.keyType != null) {
out.write('<');
expression.keyType!.accept(this, out);
out.write(', ');
if (expression.valueType == null) {
const Reference('dynamic', 'dart:core').accept(this, out);
} else {
expression.valueType!.accept(this, out);
}
out.write('>');
}
out.write('{');
visitAll<Object?>(expression.values.keys, out, (key) {
final value = expression.values[key];
_acceptLiteral(key, out);
if (key is! LiteralSpreadExpression) {
out.write(': ');
}
_acceptLiteral(value, out);
});
if (expression.values.length > 1) {
out.write(', ');
}
return out..write('}');
});
}
@override
StringSink visitLiteralRecordExpression(
LiteralRecordExpression expression, [
StringSink? output,
]) {
final out = output ??= StringBuffer();
return _writeConstExpression(out, expression.isConst, () {
out.write('(');
visitAll<Object?>(expression.positionalFieldValues, out, (value) {
_acceptLiteral(value, out);
});
if (expression.namedFieldValues.isNotEmpty) {
if (expression.positionalFieldValues.isNotEmpty) {
out.write(', ');
}
} else if (expression.positionalFieldValues.length == 1) {
out.write(',');
}
visitAll<MapEntry<String, Object?>>(
expression.namedFieldValues.entries, out, (entry) {
out.write('${entry.key}: ');
_acceptLiteral(entry.value, out);
});
return out..write(')');
});
}
@override
StringSink visitParenthesizedExpression(
ParenthesizedExpression expression, [
StringSink? output,
]) {
output ??= StringBuffer();
output.write('(');
expression.inner.accept(this, output);
output.write(')');
return output;
}
/// Executes [visit] within a context which may alter the output if [isConst]
/// is `true`.
///
/// This allows constant expressions to omit the `const` keyword if they
/// are already within a constant expression.
void startConstCode(
bool isConst,
Null Function() visit,
) {
final previousConstContext = _withInConstExpression;
if (isConst) {
_withInConstExpression = true;
}
visit();
_withInConstExpression = previousConstContext;
}
/// Similar to [startConstCode], but handles writing `"const "` if [isConst]
/// is `true` and the invocation is not nested under other invocations where
/// [isConst] is true.
StringSink _writeConstExpression(
StringSink sink,
bool isConst,
StringSink Function() visitExpression,
) {
final previousConstContext = _withInConstExpression;
if (isConst) {
if (!_withInConstExpression) {
sink.write('const ');
}
_withInConstExpression = true;
}
final returnedSink = visitExpression();
assert(identical(returnedSink, sink));
_withInConstExpression = previousConstContext;
return sink;
}
}