// 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(covariant ExpressionVisitor 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]. Expression operatorSubtract(Expression other) => BinaryExpression._(expression, other, '-'); @Deprecated('Use `operatorSubtract` instead') Expression operatorSubstract(Expression other) => operatorSubtract(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, '%'); /// Returns the result of `this` `~/` [other]. Expression operatorIntDivide(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'); /// Returns the result of `++this`. Expression operatorUnaryPrefixIncrement() => BinaryExpression._(_empty, expression, '++', addSpace: false); /// Return the result of `this++`. Expression operatorUnaryPostfixIncrement() => BinaryExpression._(expression, _empty, '++', addSpace: false); /// Returns the result of `-this`. Expression operatorUnaryMinus() => BinaryExpression._(_empty, expression, '-', addSpace: false); /// Returns the result of `--this`. Expression operatorUnaryPrefixDecrement() => BinaryExpression._(_empty, expression, '--', addSpace: false); /// Return the result of `this--`. Expression operatorUnaryPostfixDecrement() => BinaryExpression._(expression, _empty, '--', addSpace: false); /// Returns the result of `this` `&` [other]. Expression operatorBitwiseAnd(Expression other) => BinaryExpression._(expression, other, '&'); /// Returns the result of `this` `|` [other]. Expression operatorBitwiseOr(Expression other) => BinaryExpression._(expression, other, '|'); /// Returns the result of `this` `^` [other]. Expression operatorBitwiseXor(Expression other) => BinaryExpression._(expression, other, '^'); /// Returns the result of `~this`. Expression operatorUnaryBitwiseComplement() => BinaryExpression._(_empty, expression, '~', addSpace: false); /// Returns the result of `this` `<<` [other]. Expression operatorShiftLeft(Expression other) => BinaryExpression._(expression, other, '<<'); /// Returns the result of `this` `>>` [other]. Expression operatorShiftRight(Expression other) => BinaryExpression._(expression, other, '>>'); /// Returns the result of `this` `>>>` [other]. Expression operatorShiftRightUnsigned(Expression other) => BinaryExpression._(expression, other, '>>>'); /// Return `{this} = {other}`. Expression assign(Expression other) => BinaryExpression._(this, other, '=', isConst: isConst); /// Return `this` += [other]. Expression addAssign(Expression other) => BinaryExpression._(this, other, '+='); /// Return `this` -= [other]. Expression subtractAssign(Expression other) => BinaryExpression._(this, other, '-='); /// Return `this` *= [other]. Expression multiplyAssign(Expression other) => BinaryExpression._(this, other, '*='); /// Return `this` /= [other]. Expression divideAssign(Expression other) => BinaryExpression._(this, other, '/='); /// Return `this` ~/= [other]. Expression intDivideAssign(Expression other) => BinaryExpression._(this, other, '~/='); /// Return `this` %= [other]. Expression euclideanModuloAssign(Expression other) => BinaryExpression._(this, other, '%='); /// Return `this` <<= [other]. Expression shiftLeftAssign(Expression other) => BinaryExpression._(this, other, '<<='); /// Return `this` >>= [other]. Expression shiftRightAssign(Expression other) => BinaryExpression._(this, other, '>>='); /// Return `this` >>>= [other]. Expression shiftRightUnsignedAssign(Expression other) => BinaryExpression._(this, other, '>>>='); /// Return `this` &= [other]. Expression bitwiseAndAssign(Expression other) => BinaryExpression._(this, other, '&='); /// Return `this` ^= [other]. Expression bitwiseXorAssign(Expression other) => BinaryExpression._(this, other, '^='); /// Return `this` |= [other]. Expression bitwiseOrAssign(Expression other) => BinaryExpression._(this, other, '|='); /// 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 positionalArguments, [ Map namedArguments = const {}, List typeArguments = const [], ]) => InvokeExpression._( this, positionalArguments.toList(), namedArguments, typeArguments, ); /// Returns an expression accessing `.` on this expression. Expression property(String name) => BinaryExpression._(this, LiteralExpression._(name), '.', addSpace: false); /// Returns an expression accessing `..` on this expression. Expression cascade(String name) => BinaryExpression._( this, LiteralExpression._(name), '..', addSpace: false, ); /// Returns an expression accessing `?.` 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; /// Returns this expression wrapped in parenthesis. ParenthesizedExpression get parenthesized => ParenthesizedExpression._(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(CodeVisitor visitor, [R? context]) => (visitor as ExpressionVisitor).visitToCodeExpression(this, context); @override String toString() => code.toString(); } /// Knowledge of different types of expressions in Dart. /// /// **INTERNAL ONLY**. abstract class ExpressionVisitor implements SpecVisitor { 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 mixin class ExpressionEmitter implements ExpressionVisitor { @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; 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 case final name? when name.isNotEmpty) { out ..write('.') ..write(name); } if (expression.typeArguments.isNotEmpty) { out.write('<'); visitAll(expression.typeArguments, out, (type) { type.accept(this, out); }); out.write('>'); } out.write('('); visitAll(expression.positionalArguments, out, (spec) { spec.accept(this, out); }); if (expression.positionalArguments.isNotEmpty && expression.namedArguments.isNotEmpty) { out.write(', '); } visitAll(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(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(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(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(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>( 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; } }