| // 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._(this, other, '&&'); | |
| /// Returns the result of `this` `||` [other]. | |
| Expression or(Expression other) => BinaryExpression._(this, other, '||'); | |
| /// Returns the result of `!this`. | |
| Expression negate() => BinaryExpression._(_empty, this, '!', addSpace: false); | |
| /// Returns the result of `this` `as` [other]. | |
| Expression asA(Expression other) => | |
| ParenthesizedExpression._(BinaryExpression._(this, other, 'as')); | |
| /// Returns accessing the index operator (`[]`) on `this`. | |
| Expression index(Expression index) => BinaryExpression._( | |
| this, | |
| CodeExpression(Block.of([const Code('['), index.code, const Code(']')])), | |
| '', | |
| ); | |
| /// Accesses the index operator (`[]`) on `this` via a cascade (`..`). | |
| Expression cascadeIndex(Expression index) => BinaryExpression._( | |
| this, | |
| CodeExpression(Block.of([const Code('['), index.code, const Code(']')])), | |
| '..', | |
| addSpace: false, | |
| ); | |
| /// Accesses the index operator (`[]`) on `this` via a null-aware cascade | |
| /// (`?..`). | |
| /// | |
| /// Only the first cascade in a chain needs to use `?..`; subsequent | |
| /// cascades in the same chain should use [cascadeIndex] or [cascade]. | |
| Expression nullSafeCascadeIndex(Expression index) => BinaryExpression._( | |
| this, | |
| CodeExpression(Block.of([const Code('['), index.code, const Code(']')])), | |
| '?..', | |
| addSpace: false, | |
| ); | |
| /// Returns the result of `this` `is` [other]. | |
| Expression isA(Expression other) => BinaryExpression._(this, other, 'is'); | |
| /// Returns the result of `this` `is!` [other]. | |
| Expression isNotA(Expression other) => BinaryExpression._(this, other, 'is!'); | |
| /// Returns the result of `this` `==` [other]. | |
| Expression equalTo(Expression other) => BinaryExpression._(this, other, '=='); | |
| /// Returns the result of `this` `!=` [other]. | |
| Expression notEqualTo(Expression other) => | |
| BinaryExpression._(this, other, '!='); | |
| /// Returns the result of `this` `>` [other]. | |
| Expression greaterThan(Expression other) => | |
| BinaryExpression._(this, other, '>'); | |
| /// Returns the result of `this` `<` [other]. | |
| Expression lessThan(Expression other) => BinaryExpression._(this, other, '<'); | |
| /// Returns the result of `this` `>=` [other]. | |
| Expression greaterOrEqualTo(Expression other) => | |
| BinaryExpression._(this, other, '>='); | |
| /// Returns the result of `this` `<=` [other]. | |
| Expression lessOrEqualTo(Expression other) => | |
| BinaryExpression._(this, other, '<='); | |
| /// Returns the result of `this` `+` [other]. | |
| Expression operatorAdd(Expression other) => | |
| BinaryExpression._(this, other, '+'); | |
| /// Returns the result of `this` `-` [other]. | |
| Expression operatorSubtract(Expression other) => | |
| BinaryExpression._(this, other, '-'); | |
| @Deprecated('Use `operatorSubtract` instead') | |
| Expression operatorSubstract(Expression other) => operatorSubtract(other); | |
| /// Returns the result of `this` `/` [other]. | |
| Expression operatorDivide(Expression other) => | |
| BinaryExpression._(this, other, '/'); | |
| /// Returns the result of `this` `*` [other]. | |
| Expression operatorMultiply(Expression other) => | |
| BinaryExpression._(this, other, '*'); | |
| /// Returns the result of `this` `%` [other]. | |
| Expression operatorEuclideanModulo(Expression other) => | |
| BinaryExpression._(this, other, '%'); | |
| /// Returns the result of `this` `~/` [other]. | |
| Expression operatorIntDivide(Expression other) => | |
| BinaryExpression._(this, other, '~/'); | |
| Expression conditional(Expression whenTrue, Expression whenFalse) => | |
| BinaryExpression._( | |
| this, | |
| BinaryExpression._(whenTrue, whenFalse, ':'), | |
| '?', | |
| ); | |
| /// This expression preceded by `await`. | |
| Expression get awaited => BinaryExpression._(_empty, this, 'await'); | |
| /// Returns the result of `++this`. | |
| Expression operatorUnaryPrefixIncrement() => | |
| BinaryExpression._(_empty, this, '++', addSpace: false); | |
| /// Return the result of `this++`. | |
| Expression operatorUnaryPostfixIncrement() => | |
| BinaryExpression._(this, _empty, '++', addSpace: false); | |
| /// Returns the result of `-this`. | |
| Expression operatorUnaryMinus() => | |
| BinaryExpression._(_empty, this, '-', addSpace: false); | |
| /// Returns the result of `--this`. | |
| Expression operatorUnaryPrefixDecrement() => | |
| BinaryExpression._(_empty, this, '--', addSpace: false); | |
| /// Return the result of `this--`. | |
| Expression operatorUnaryPostfixDecrement() => | |
| BinaryExpression._(this, _empty, '--', addSpace: false); | |
| /// Returns the result of `this` `&` [other]. | |
| Expression operatorBitwiseAnd(Expression other) => | |
| BinaryExpression._(this, other, '&'); | |
| /// Returns the result of `this` `|` [other]. | |
| Expression operatorBitwiseOr(Expression other) => | |
| BinaryExpression._(this, other, '|'); | |
| /// Returns the result of `this` `^` [other]. | |
| Expression operatorBitwiseXor(Expression other) => | |
| BinaryExpression._(this, other, '^'); | |
| /// Returns the result of `~this`. | |
| Expression operatorUnaryBitwiseComplement() => | |
| BinaryExpression._(_empty, this, '~', addSpace: false); | |
| /// Returns the result of `this` `<<` [other]. | |
| Expression operatorShiftLeft(Expression other) => | |
| BinaryExpression._(this, other, '<<'); | |
| /// Returns the result of `this` `>>` [other]. | |
| Expression operatorShiftRight(Expression other) => | |
| BinaryExpression._(this, other, '>>'); | |
| /// Returns the result of `this` `>>>` [other]. | |
| Expression operatorShiftRightUnsigned(Expression other) => | |
| BinaryExpression._(this, 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, 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, ''), | |
| 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, ''), | |
| 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. | |
| /// | |
| /// Only the first cascade in a chain needs to use `?..`; subsequent | |
| /// cascades in the same chain should use [cascade]. | |
| Expression nullSafeCascade(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, ''); | |
| /// Returns `this`. | |
| @visibleForOverriding | |
| @Deprecated('No longer needed. Use the expression directly.') | |
| Expression get expression => this; | |
| /// Returns this expression wrapped in parenthesis. | |
| ParenthesizedExpression get parenthesized => ParenthesizedExpression._(this); | |
| /// Wildcard expression (`_`). | |
| static const wildcard = LiteralExpression._('_'); | |
| } | |
| /// 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, 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, | |
| '=', | |
| ).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 mixin 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 case final name? when name.isNotEmpty) { | |
| out | |
| ..write('.') | |
| ..write(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; | |
| } | |
| } |