e30cd0322c
This separates VariableDeclaration from Statement. VariableDeclaration no longer implements Statement and variable declared in a block or in a for-statement are now wrapped by a VariableStatement. Currently there are two VariableStatement implementations; LegacyVariableStatement for variables in the current model, called LegacyVariable, and VariableInitialization for variables used in the new, still experimental, encoding that supports scope computation. This CL is a step towards realigning the AST nodes to the new model in which each kind of variable has its own distinct subclass. (LocalVariable, PositionalParameter, NamedParameter, SyntheticVariable, etc.) Note that it is not the intent to use VariableStatement in ForStatement going forward but that will be handled in a follow-up. TEST=existing. Change-Id: I5b309cd62c9b138f95b74fb054686edffa49a393 Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/502681 Reviewed-by: Chloe Stefantsova <cstefantsova@google.com> Reviewed-by: Nicholas Shahan <nshahan@google.com> Reviewed-by: Alexander Markov <alexmarkov@google.com> Reviewed-by: Martin Kustermann <kustermann@google.com> Commit-Queue: Johnni Winther <johnniwinther@google.com>
1339 lines
42 KiB
Dart
1339 lines
42 KiB
Dart
// Copyright (c) 2023, 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:kernel/ast.dart';
|
|
import 'package:kernel/class_hierarchy.dart';
|
|
import 'package:kernel/core_types.dart';
|
|
import 'package:kernel/type_environment.dart';
|
|
|
|
import 'state_machine.dart' as stateMachineCodeGen;
|
|
|
|
/// This pass lifts `await` expressions to the top-level. After the pass, all
|
|
/// `await` expressions will have the form:
|
|
///
|
|
/// final $temp = await <simple expr>;
|
|
///
|
|
/// where `<simple expr>` is an expression without `await`.
|
|
///
|
|
/// `await`s in block expressions are also lifted to the statement level.
|
|
///
|
|
/// The idea is that after this pass every `await` will have a simple
|
|
/// continuation of "assign the awaited value to the variable, continue with
|
|
/// the next statement". This allows simple code generation for async inner
|
|
/// functions.
|
|
///
|
|
/// The implementation is mostly copied from the old VM async/await transformer
|
|
/// with some changes. The old pass was removed in commit 94c120a.
|
|
void transformLibraries(
|
|
List<Library> libraries,
|
|
ClassHierarchy hierarchy,
|
|
CoreTypes coreTypes,
|
|
) {
|
|
final typeEnv = TypeEnvironment(coreTypes, hierarchy);
|
|
|
|
var rewriter = _AwaitTransformer(
|
|
StatefulStaticTypeContext.stacked(typeEnv),
|
|
coreTypes,
|
|
);
|
|
|
|
for (var library in libraries) {
|
|
rewriter.transform(library);
|
|
}
|
|
}
|
|
|
|
class _AwaitTransformer extends Transformer {
|
|
final StatefulStaticTypeContext staticTypeContext;
|
|
|
|
final CoreTypes coreTypes;
|
|
|
|
List<Statement> statements = <Statement>[];
|
|
|
|
late final _ExpressionTransformer expressionTransformer;
|
|
|
|
_AwaitTransformer(this.staticTypeContext, this.coreTypes) {
|
|
expressionTransformer = _ExpressionTransformer(
|
|
this,
|
|
staticTypeContext,
|
|
coreTypes,
|
|
);
|
|
}
|
|
|
|
@override
|
|
TreeNode visitField(Field node) {
|
|
staticTypeContext.enterMember(node);
|
|
super.visitField(node);
|
|
staticTypeContext.leaveMember(node);
|
|
return node;
|
|
}
|
|
|
|
@override
|
|
TreeNode visitConstructor(Constructor node) {
|
|
staticTypeContext.enterMember(node);
|
|
final result = super.visitConstructor(node);
|
|
staticTypeContext.leaveMember(node);
|
|
return result;
|
|
}
|
|
|
|
@override
|
|
TreeNode visitProcedure(Procedure node) {
|
|
staticTypeContext.enterMember(node);
|
|
final result = node.isAbstract ? node : super.visitProcedure(node);
|
|
staticTypeContext.leaveMember(node);
|
|
return result;
|
|
}
|
|
|
|
@override
|
|
TreeNode visitFunctionNode(FunctionNode node) {
|
|
final Statement? body = node.body;
|
|
if (body != null) {
|
|
final transformer = _AwaitTransformer(staticTypeContext, coreTypes);
|
|
Statement newBody = transformer.transform(body);
|
|
|
|
final List<Statement> newStatements = [
|
|
for (final variable in transformer.expressionTransformer.variables)
|
|
VariableStatement(variable),
|
|
...transformer.statements,
|
|
];
|
|
|
|
if (newStatements.isNotEmpty) {
|
|
newBody = Block([
|
|
...newStatements,
|
|
...newBody is Block ? newBody.statements : [newBody],
|
|
]);
|
|
}
|
|
|
|
node.body = newBody..parent = node;
|
|
}
|
|
return node;
|
|
}
|
|
|
|
@override
|
|
TreeNode visitAssertBlock(AssertBlock stmt) {
|
|
final savedStatements = statements;
|
|
statements = [];
|
|
for (final stmt in stmt.statements) {
|
|
statements.add(transform(stmt));
|
|
}
|
|
final newBlock = AssertBlock(statements);
|
|
statements = savedStatements;
|
|
return newBlock;
|
|
}
|
|
|
|
@override
|
|
TreeNode visitAssertStatement(AssertStatement stmt) {
|
|
final List<Statement> condEffects = [];
|
|
final cond = expressionTransformer.rewrite(stmt.condition, condEffects);
|
|
final msg = stmt.message;
|
|
if (msg == null) {
|
|
stmt.condition = cond..parent = stmt;
|
|
// If the translation of the condition produced a non-empty list of
|
|
// statements, ensure they are guarded by whether asserts are enabled.
|
|
return condEffects.isEmpty ? stmt : AssertBlock(condEffects..add(stmt));
|
|
}
|
|
|
|
// The translation depends on the translation of the message.
|
|
final List<Statement> msgEffects = [];
|
|
stmt.message = expressionTransformer.rewrite(msg, msgEffects)
|
|
..parent = stmt;
|
|
if (condEffects.isEmpty) {
|
|
if (msgEffects.isEmpty) {
|
|
// The condition rewrote to ([], C) and the message rewrote to ([], M).
|
|
// The result is
|
|
//
|
|
// assert(C, M)
|
|
stmt.condition = cond..parent = stmt;
|
|
return stmt;
|
|
} else {
|
|
// The condition rewrote to ([], C) and the message rewrote to (S*, M)
|
|
// where S* is non-empty. The result is
|
|
//
|
|
// assert { if (C) {} else { S*; assert(false, M); }}
|
|
stmt.condition = BoolLiteral(false)..parent = stmt;
|
|
return AssertBlock([
|
|
IfStatement(cond, EmptyStatement(), Block(msgEffects..add(stmt))),
|
|
]);
|
|
}
|
|
} else {
|
|
if (msgEffects.isEmpty) {
|
|
// The condition rewrote to (S*, C) where S* is non-empty and the
|
|
// message rewrote to ([], M). The result is
|
|
//
|
|
// assert { S*; assert(C, M); }
|
|
stmt.condition = cond..parent = stmt;
|
|
condEffects.add(stmt);
|
|
} else {
|
|
// The condition rewrote to (S0*, C) and the message rewrote to (S1*, M)
|
|
// where both S0* and S1* are non-empty. The result is
|
|
//
|
|
// assert { S0*; if (C) {} else { S1*; assert(false, M); }}
|
|
stmt.condition = BoolLiteral(false)..parent = stmt;
|
|
condEffects.add(
|
|
IfStatement(cond, EmptyStatement(), Block(msgEffects..add(stmt))),
|
|
);
|
|
}
|
|
return AssertBlock(condEffects);
|
|
}
|
|
}
|
|
|
|
@override
|
|
TreeNode visitBlock(Block stmt) {
|
|
final savedStatements = statements;
|
|
statements = [];
|
|
for (final statement in stmt.statements) {
|
|
final newStatement = transform(statement);
|
|
statements.add(newStatement);
|
|
}
|
|
final newBlock = Block(statements);
|
|
statements = savedStatements;
|
|
return newBlock;
|
|
}
|
|
|
|
@override
|
|
TreeNode visitBreakStatement(BreakStatement stmt) => stmt;
|
|
|
|
@override
|
|
TreeNode visitContinueSwitchStatement(ContinueSwitchStatement stmt) => stmt;
|
|
|
|
Statement visitDelimited(Statement stmt) {
|
|
final saved = statements;
|
|
statements = [];
|
|
statements.add(transform(stmt));
|
|
final result = statements.length == 1
|
|
? statements.first
|
|
: Block(statements);
|
|
statements = saved;
|
|
return result;
|
|
}
|
|
|
|
@override
|
|
TreeNode visitDoStatement(DoStatement stmt) {
|
|
Statement body = visitDelimited(stmt.body); // block or single statement
|
|
final List<Statement> effects = [];
|
|
stmt.condition = expressionTransformer.rewrite(stmt.condition, effects)
|
|
..parent = stmt;
|
|
if (effects.isNotEmpty) {
|
|
// The condition rewrote to a non-empty sequence of statements S* and
|
|
// value V. Add the statements to the end of the loop body.
|
|
final Block block = body is Block ? body : body = Block([body]);
|
|
for (final effect in effects) {
|
|
block.statements.add(effect);
|
|
effect.parent = body;
|
|
}
|
|
}
|
|
stmt.body = body..parent = stmt;
|
|
return stmt;
|
|
}
|
|
|
|
@override
|
|
TreeNode visitEmptyStatement(EmptyStatement stmt) => stmt;
|
|
|
|
@override
|
|
TreeNode visitExpressionStatement(ExpressionStatement stmt) {
|
|
stmt.expression = expressionTransformer.rewrite(stmt.expression, statements)
|
|
..parent = stmt;
|
|
return stmt;
|
|
}
|
|
|
|
@override
|
|
TreeNode visitForInStatement(ForInStatement stmt) {
|
|
throw 'For statement at ${stmt.location}';
|
|
}
|
|
|
|
@override
|
|
TreeNode visitForStatement(ForStatement stmt) {
|
|
// Because of for-loop scoping and variable capture, it is tricky to deal
|
|
// with await in the loop's variable initializers or update expressions.
|
|
bool isSimple = true;
|
|
int length = stmt.variables.length;
|
|
List<List<Statement>> initEffects = List<List<Statement>>.generate(length, (
|
|
int i,
|
|
) {
|
|
VariableStatement decl = stmt.variables[i];
|
|
List<Statement> statements = <Statement>[];
|
|
if (decl.variable.initializer != null) {
|
|
decl.variable.initializer = expressionTransformer.rewrite(
|
|
decl.variable.initializer!,
|
|
statements,
|
|
)..parent = decl.variable;
|
|
}
|
|
isSimple = isSimple && statements.isEmpty;
|
|
return statements;
|
|
});
|
|
|
|
length = stmt.updates.length;
|
|
List<List<Statement>> updateEffects = List<List<Statement>>.generate(
|
|
length,
|
|
(int i) {
|
|
List<Statement> statements = <Statement>[];
|
|
stmt.updates[i] = expressionTransformer.rewrite(
|
|
stmt.updates[i],
|
|
statements,
|
|
)..parent = stmt;
|
|
isSimple = isSimple && statements.isEmpty;
|
|
return statements;
|
|
},
|
|
);
|
|
|
|
Statement body = visitDelimited(stmt.body);
|
|
Expression? cond = stmt.condition;
|
|
List<Statement>? condEffects;
|
|
if (cond != null) {
|
|
condEffects = <Statement>[];
|
|
cond = expressionTransformer.rewrite(stmt.condition!, condEffects);
|
|
}
|
|
|
|
if (isSimple) {
|
|
// If the condition contains await, we use a translation like the one for
|
|
// while loops, but leaving the variable declarations and the update
|
|
// expressions in place.
|
|
if (condEffects == null || condEffects.isEmpty) {
|
|
if (cond != null) stmt.condition = cond..parent = stmt;
|
|
stmt.body = body..parent = stmt;
|
|
return stmt;
|
|
} else {
|
|
LabeledStatement labeled = LabeledStatement(stmt);
|
|
// No condition in a for loop is the same as true.
|
|
stmt.condition = null;
|
|
condEffects.add(IfStatement(cond!, body, BreakStatement(labeled)));
|
|
stmt.body = Block(condEffects)..parent = stmt;
|
|
return labeled;
|
|
}
|
|
}
|
|
|
|
// If the rewrite of the initializer or update expressions produces a
|
|
// non-empty sequence of statements then the loop is desugared. If the loop
|
|
// has the form:
|
|
//
|
|
// label: for (Type x = init; cond; update) body
|
|
//
|
|
// it is translated as if it were:
|
|
//
|
|
// {
|
|
// bool first = true;
|
|
// Type temp;
|
|
// label: while (true) {
|
|
// Type x;
|
|
// if (first) {
|
|
// first = false;
|
|
// x = init;
|
|
// } else {
|
|
// x = temp;
|
|
// update;
|
|
// }
|
|
// if (cond) {
|
|
// body;
|
|
// temp = x;
|
|
// } else {
|
|
// break;
|
|
// }
|
|
// }
|
|
// }
|
|
|
|
// Place the loop variable declarations at the beginning of the body
|
|
// statements and move their initializers to a guarded list of statements.
|
|
// Add assignments to the loop variables from the previous iterations temp
|
|
// variables before the updates.
|
|
//
|
|
// temps.first is the flag 'first'.
|
|
List<VariableDeclaration> temps = <VariableDeclaration>[
|
|
VariableDeclaration.forValue(BoolLiteral(true), isFinal: false),
|
|
];
|
|
List<Statement> loopBody = <Statement>[];
|
|
List<Statement> initializers = <Statement>[
|
|
ExpressionStatement(VariableSet(temps.first, BoolLiteral(false))),
|
|
];
|
|
List<Statement> updates = <Statement>[];
|
|
List<Statement> newBody = <Statement>[body];
|
|
for (int i = 0; i < stmt.variables.length; ++i) {
|
|
VariableStatement decl = stmt.variables[i];
|
|
temps.add(
|
|
VariableDeclaration(
|
|
null,
|
|
type: decl.variable.type,
|
|
isSynthesized: true,
|
|
),
|
|
);
|
|
loopBody.add(decl);
|
|
if (decl.initializer != null) {
|
|
initializers.addAll(initEffects[i]);
|
|
initializers.add(
|
|
ExpressionStatement(VariableSet(decl.variable, decl.initializer!)),
|
|
);
|
|
decl.initializer = null;
|
|
}
|
|
updates.add(
|
|
ExpressionStatement(
|
|
VariableSet(decl.variable, VariableGet(temps.last)),
|
|
),
|
|
);
|
|
newBody.add(
|
|
ExpressionStatement(
|
|
VariableSet(temps.last, VariableGet(decl.variable)),
|
|
),
|
|
);
|
|
}
|
|
// Add the updates to their guarded list of statements.
|
|
for (int i = 0; i < stmt.updates.length; ++i) {
|
|
updates.addAll(updateEffects[i]);
|
|
updates.add(ExpressionStatement(stmt.updates[i]));
|
|
}
|
|
// Initializers or updates could be empty.
|
|
loopBody.add(
|
|
IfStatement(
|
|
VariableGet(temps.first),
|
|
Block(initializers),
|
|
Block(updates),
|
|
),
|
|
);
|
|
|
|
LabeledStatement labeled = LabeledStatement(null);
|
|
if (cond != null) {
|
|
loopBody.addAll(condEffects!);
|
|
} else {
|
|
cond = BoolLiteral(true);
|
|
}
|
|
loopBody.add(IfStatement(cond, Block(newBody), BreakStatement(labeled)));
|
|
labeled.body = WhileStatement(BoolLiteral(true), Block(loopBody))
|
|
..parent = labeled;
|
|
return Block(<Statement>[
|
|
for (VariableDeclaration temp in temps) VariableStatement(temp),
|
|
labeled,
|
|
]);
|
|
}
|
|
|
|
@override
|
|
TreeNode visitFunctionDeclaration(FunctionDeclaration stmt) {
|
|
stmt.function = transform(stmt.function)..parent = stmt;
|
|
return stmt;
|
|
}
|
|
|
|
@override
|
|
TreeNode visitIfStatement(IfStatement stmt) {
|
|
stmt.condition = expressionTransformer.rewrite(stmt.condition, statements)
|
|
..parent = stmt;
|
|
stmt.then = visitDelimited(stmt.then)..parent = stmt;
|
|
if (stmt.otherwise != null) {
|
|
stmt.otherwise = visitDelimited(stmt.otherwise!)..parent = stmt;
|
|
}
|
|
return stmt;
|
|
}
|
|
|
|
@override
|
|
TreeNode visitLabeledStatement(LabeledStatement stmt) {
|
|
stmt.body = visitDelimited(stmt.body)..parent = stmt;
|
|
return stmt;
|
|
}
|
|
|
|
@override
|
|
TreeNode visitReturnStatement(ReturnStatement stmt) {
|
|
if (stmt.expression != null) {
|
|
stmt.expression = expressionTransformer.rewrite(
|
|
stmt.expression!,
|
|
statements,
|
|
)..parent = stmt;
|
|
}
|
|
|
|
return stmt;
|
|
}
|
|
|
|
@override
|
|
TreeNode visitSwitchStatement(SwitchStatement stmt) {
|
|
stmt.expression = expressionTransformer.rewrite(stmt.expression, statements)
|
|
..parent = stmt;
|
|
for (final switchCase in stmt.cases) {
|
|
// Expressions in switch cases cannot contain await so they do not need to
|
|
// be translated.
|
|
switchCase.body = visitDelimited(switchCase.body)..parent = switchCase;
|
|
}
|
|
return stmt;
|
|
}
|
|
|
|
@override
|
|
TreeNode visitTryCatch(TryCatch stmt) {
|
|
stmt.body = visitDelimited(stmt.body)..parent = stmt;
|
|
for (final catch_ in stmt.catches) {
|
|
// Create a fresh variable for the exception and stack trace: when a
|
|
// catch block has an `await` we use the catch block variables to restore
|
|
// the current exception after the `await`.
|
|
//
|
|
// TODO (omersa): We could mark [TreeNode]s with `await`s and only do this
|
|
catch_.exception ??= VariableDeclaration(
|
|
null,
|
|
type: InterfaceType(coreTypes.objectClass, Nullability.nonNullable),
|
|
isSynthesized: true,
|
|
)..parent = catch_;
|
|
catch_.stackTrace ??= VariableDeclaration(
|
|
null,
|
|
type: InterfaceType(coreTypes.stackTraceClass, Nullability.nonNullable),
|
|
isSynthesized: true,
|
|
)..parent = catch_;
|
|
|
|
var body = visitDelimited(catch_.body);
|
|
|
|
// Add uses to exception and stack trace vars so that they will be added
|
|
// to the context if the catch block has an await.
|
|
if (body is Block) {
|
|
body.statements.add(
|
|
ExpressionStatement(VariableGet(catch_.exception!))..parent = body,
|
|
);
|
|
body.statements.add(
|
|
ExpressionStatement(VariableGet(catch_.stackTrace!))..parent = body,
|
|
);
|
|
} else {
|
|
body = Block([
|
|
body,
|
|
ExpressionStatement(VariableGet(catch_.exception!)),
|
|
ExpressionStatement(VariableGet(catch_.stackTrace!)),
|
|
]);
|
|
}
|
|
|
|
catch_.body = body..parent = catch_;
|
|
}
|
|
return stmt;
|
|
}
|
|
|
|
@override
|
|
TreeNode visitTryFinally(TryFinally stmt) {
|
|
// TODO (omersa): Wrapped in a block to be able to get the variable
|
|
// declarations using `parent.statements[0]` etc. when compiling the node.
|
|
// Ideally we may want to create these variables not in kernel but during
|
|
// code generation.
|
|
|
|
// Variable for the finalizer block continuation.
|
|
final continuationVar = VariableDeclaration(
|
|
null,
|
|
initializer: IntLiteral(stateMachineCodeGen.continuationFallthrough),
|
|
type: InterfaceType(coreTypes.intClass, Nullability.nonNullable),
|
|
isSynthesized: true,
|
|
);
|
|
|
|
// When the finalizer continuation is "rethrow", this stores the exception
|
|
// to rethrow.
|
|
final exceptionVar = VariableDeclaration(
|
|
null,
|
|
type: InterfaceType(coreTypes.objectClass, Nullability.nonNullable),
|
|
isSynthesized: true,
|
|
);
|
|
|
|
// When the finalizer continuation is "rethrow", this stores the stack
|
|
// trace of the exception in [exceptionVar].
|
|
final stackTraceVar = VariableDeclaration(
|
|
null,
|
|
type: InterfaceType(coreTypes.stackTraceClass, Nullability.nonNullable),
|
|
isSynthesized: true,
|
|
);
|
|
|
|
final body = visitDelimited(stmt.body);
|
|
var finalizer = visitDelimited(stmt.finalizer);
|
|
|
|
// Add a use of `continuationVar` in finally so that it will be added to
|
|
// the context
|
|
if (finalizer is Block) {
|
|
finalizer.statements.add(
|
|
ExpressionStatement(VariableGet(continuationVar))..parent = finalizer,
|
|
);
|
|
finalizer.statements.add(
|
|
ExpressionStatement(VariableGet(exceptionVar))..parent = finalizer,
|
|
);
|
|
finalizer.statements.add(
|
|
ExpressionStatement(VariableGet(stackTraceVar))..parent = finalizer,
|
|
);
|
|
} else {
|
|
finalizer = Block([
|
|
finalizer,
|
|
ExpressionStatement(VariableGet(continuationVar)),
|
|
ExpressionStatement(VariableGet(exceptionVar)),
|
|
ExpressionStatement(VariableGet(stackTraceVar)),
|
|
]);
|
|
}
|
|
|
|
return Block([
|
|
VariableStatement(continuationVar),
|
|
VariableStatement(exceptionVar),
|
|
VariableStatement(stackTraceVar),
|
|
TryFinally(body, finalizer),
|
|
]);
|
|
}
|
|
|
|
@override
|
|
TreeNode visitLegacyVariableStatement(LegacyVariableStatement stmt) {
|
|
final initializer = stmt.variable.initializer;
|
|
if (initializer != null) {
|
|
stmt.variable.initializer = expressionTransformer.rewrite(
|
|
initializer,
|
|
statements,
|
|
)..parent = stmt.variable;
|
|
}
|
|
return stmt;
|
|
}
|
|
|
|
@override
|
|
TreeNode visitWhileStatement(WhileStatement stmt) {
|
|
final Statement body = visitDelimited(stmt.body);
|
|
final List<Statement> effects = [];
|
|
final Expression cond = expressionTransformer.rewrite(
|
|
stmt.condition,
|
|
effects,
|
|
);
|
|
if (effects.isEmpty) {
|
|
stmt.condition = cond..parent = stmt;
|
|
stmt.body = body..parent = stmt;
|
|
return stmt;
|
|
} else {
|
|
// The condition rewrote to a non-empty sequence of statements S* and
|
|
// value V. Rewrite the loop to:
|
|
//
|
|
// L: while (true) {
|
|
// S*
|
|
// if (V) {
|
|
// [body]
|
|
// else {
|
|
// break L;
|
|
// }
|
|
// }
|
|
final LabeledStatement labeled = LabeledStatement(stmt);
|
|
stmt.condition = BoolLiteral(true)..parent = stmt;
|
|
effects.add(IfStatement(cond, body, BreakStatement(labeled)));
|
|
stmt.body = Block(effects)..parent = stmt;
|
|
return labeled;
|
|
}
|
|
}
|
|
|
|
@override
|
|
TreeNode visitYieldStatement(YieldStatement stmt) {
|
|
stmt.expression = expressionTransformer.rewrite(stmt.expression, statements)
|
|
..parent = stmt;
|
|
return stmt;
|
|
}
|
|
|
|
@override
|
|
TreeNode defaultStatement(Statement stmt) =>
|
|
throw 'Unhandled statement: $stmt (${stmt.location})';
|
|
|
|
@override
|
|
TreeNode defaultExpression(Expression expr) {
|
|
// This visits initializer expressions, annotations etc.
|
|
final List<Statement> effects = [];
|
|
final Expression transformedExpr = expressionTransformer.rewrite(
|
|
expr,
|
|
effects,
|
|
);
|
|
if (effects.isEmpty) {
|
|
return transformedExpr;
|
|
} else {
|
|
return BlockExpression(Block(effects), expr);
|
|
}
|
|
}
|
|
}
|
|
|
|
class _ExpressionTransformer extends Transformer {
|
|
/// Whether we have seen an await to the right in the expression tree.
|
|
///
|
|
/// Subexpressions are visited right-to-left in the reverse of evaluation
|
|
/// order.
|
|
///
|
|
/// On entry to an expression's visit method, [seenAwait] indicates whether a
|
|
/// sibling to the right contains an await. If so the expression will be
|
|
/// named in a temporary variable because it is potentially live across an
|
|
/// await.
|
|
///
|
|
/// On exit from an expression's visit method, [seenAwait] indicates whether
|
|
/// the expression itself or a sibling to the right contains an await.
|
|
bool seenAwait = false;
|
|
|
|
/// The (reverse order) sequence of statements that have been emitted.
|
|
///
|
|
/// Transformation of an expression produces a transformed expression and a
|
|
/// sequence of statements which are assignments to local variables, calls to
|
|
/// helper functions, and yield points. Only the yield points need to be a
|
|
/// statements, and they are statements so an implementation does not have to
|
|
/// handle unnamed expression intermediate live across yield points.
|
|
///
|
|
/// The visit methods return the transformed expression and build a sequence
|
|
/// of statements by emitting statements into this list. This list is built
|
|
/// in reverse because children are visited right-to-left.
|
|
///
|
|
/// If an expression should be named it is named before visiting its children
|
|
/// so the naming assignment appears in the list before all statements
|
|
/// implementing the translation of the children.
|
|
///
|
|
/// Children that are conditionally evaluated, such as some parts of logical
|
|
/// and conditional expressions, must be delimited so that they do not emit
|
|
/// unguarded statements into [statements]. This is implemented by setting
|
|
/// [statements] to a fresh empty list before transforming those children.
|
|
List<Statement> statements = <Statement>[];
|
|
|
|
/// The number of currently live named intermediate values.
|
|
///
|
|
/// This index is used to allocate names to temporary values. Because
|
|
/// children are visited right-to-left, names are assigned in reverse order
|
|
/// of index.
|
|
///
|
|
/// When an assignment is emitted into [statements] to name an expression
|
|
/// before visiting its children, the index is not immediately reserved
|
|
/// because a child can freely use the same name as its parent. In practice,
|
|
/// this will be the rightmost named child.
|
|
///
|
|
/// After visiting the children of a named expression, [nameIndex] is set to
|
|
/// indicate one more live value (the value of the expression) than before
|
|
/// visiting the expression.
|
|
///
|
|
/// After visiting the children of an expression that is not named,
|
|
/// [nameIndex] may still account for names of subexpressions.
|
|
int nameIndex = 0;
|
|
|
|
/// Variables created for temporaries.
|
|
final List<VariableDeclaration> variables = <VariableDeclaration>[];
|
|
|
|
final _AwaitTransformer _statementTransformer;
|
|
|
|
final StatefulStaticTypeContext staticTypeContext;
|
|
|
|
final CoreTypes coreTypes;
|
|
|
|
_ExpressionTransformer(
|
|
this._statementTransformer,
|
|
this.staticTypeContext,
|
|
this.coreTypes,
|
|
);
|
|
|
|
// Helpers
|
|
|
|
/// Name an expression by emitting an assignment to a temporary variable.
|
|
Expression name(Expression expr) {
|
|
final DartType type = expr.getStaticType(staticTypeContext);
|
|
final VariableDeclaration temp = allocateTemporary(nameIndex, type);
|
|
statements.add(ExpressionStatement(VariableSet(temp, expr)));
|
|
return castVariableGet(temp, type);
|
|
}
|
|
|
|
VariableDeclaration allocateTemporary(
|
|
int index, [
|
|
DartType type = const DynamicType(),
|
|
]) {
|
|
if (variables.length > index) {
|
|
// Re-using a temporary. Re-type it to dynamic if we detect reuse with
|
|
// different type.
|
|
if (variables[index].type != const DynamicType() &&
|
|
variables[index].type != type) {
|
|
variables[index].type = const DynamicType();
|
|
}
|
|
return variables[index];
|
|
}
|
|
for (var i = variables.length; i <= index; i++) {
|
|
variables.add(VariableDeclaration(":async_temporary_$i", type: type));
|
|
}
|
|
return variables[index];
|
|
}
|
|
|
|
/// Casts a [VariableGet] with `as dynamic` if its type is not `dynamic`.
|
|
Expression castVariableGet(VariableDeclaration variable, DartType type) {
|
|
Expression expr = VariableGet(variable);
|
|
if (type != const DynamicType()) {
|
|
expr = AsExpression(expr, DynamicType());
|
|
}
|
|
return expr;
|
|
}
|
|
|
|
// Expressions
|
|
|
|
/// Rewrite a top-level expression (top-level wrt. a statement). This is the
|
|
/// entry-point from [_AwaitTransformer].
|
|
///
|
|
/// Rewriting an expression produces a sequence of statements and an
|
|
/// expression. The sequence of statements are added to the given list. Pass
|
|
/// an empty list if the rewritten expression should be delimited from the
|
|
/// surrounding context.
|
|
//
|
|
// TODO (omersa): We should be able to maintain the state for temporaries
|
|
// (`nameIndex`, `variables`) in a separate class and create a new expression
|
|
// transformer every time we transform a top-level expression. Would that
|
|
// make the code clearer?
|
|
Expression rewrite(Expression expression, List<Statement> outer) {
|
|
assert(statements.isEmpty);
|
|
final saved = seenAwait;
|
|
seenAwait = false;
|
|
final Expression result = transform(expression);
|
|
outer.addAll(statements.reversed);
|
|
statements.clear();
|
|
seenAwait = seenAwait || saved;
|
|
return result;
|
|
}
|
|
|
|
@override
|
|
TreeNode defaultExpression(Expression expr) =>
|
|
throw 'Unhandled expression: $expr (${expr.location})';
|
|
|
|
@override
|
|
TreeNode visitFunctionExpression(FunctionExpression expr) {
|
|
expr.transformChildren(this);
|
|
return expr;
|
|
}
|
|
|
|
// Simple literals. These are pure expressions so they can be evaluated after
|
|
// an await to their right.
|
|
@override
|
|
TreeNode visitSymbolLiteral(SymbolLiteral expr) => expr;
|
|
|
|
@override
|
|
TreeNode visitTypeLiteral(TypeLiteral expr) => expr;
|
|
|
|
@override
|
|
TreeNode visitThisExpression(ThisExpression expr) => expr;
|
|
|
|
@override
|
|
TreeNode visitStringLiteral(StringLiteral expr) => expr;
|
|
|
|
@override
|
|
TreeNode visitIntLiteral(IntLiteral expr) => expr;
|
|
|
|
@override
|
|
TreeNode visitDoubleLiteral(DoubleLiteral expr) => expr;
|
|
|
|
@override
|
|
TreeNode visitBoolLiteral(BoolLiteral expr) => expr;
|
|
|
|
@override
|
|
TreeNode visitNullLiteral(NullLiteral expr) => expr;
|
|
|
|
@override
|
|
TreeNode visitConstantExpression(ConstantExpression expr) => expr;
|
|
|
|
@override
|
|
TreeNode visitCheckLibraryIsLoaded(CheckLibraryIsLoaded expr) => expr;
|
|
|
|
@override
|
|
TreeNode visitLoadLibrary(LoadLibrary expr) => expr;
|
|
|
|
/// Transform expressions with no child expressions.
|
|
Expression nullary(Expression expr) {
|
|
if (seenAwait) {
|
|
expr = name(expr);
|
|
++nameIndex;
|
|
}
|
|
return expr;
|
|
}
|
|
|
|
@override
|
|
TreeNode visitSuperPropertyGet(SuperPropertyGet expr) => nullary(expr);
|
|
|
|
@override
|
|
TreeNode visitStaticGet(StaticGet expr) => nullary(expr);
|
|
|
|
@override
|
|
TreeNode visitStaticTearOff(StaticTearOff expr) => nullary(expr);
|
|
|
|
@override
|
|
TreeNode visitRethrow(Rethrow expr) => nullary(expr);
|
|
|
|
@override
|
|
TreeNode visitFileUriExpression(FileUriExpression expr) => unary(expr);
|
|
|
|
@override
|
|
TreeNode visitVariableGet(VariableGet expr) {
|
|
Expression result = expr;
|
|
// Getting a final or const variable is not an effect so it can be
|
|
// evaluated after an await to its right.
|
|
if (seenAwait && !expr.variable.isFinal && !expr.variable.isConst) {
|
|
result = name(expr);
|
|
++nameIndex;
|
|
}
|
|
return result;
|
|
}
|
|
|
|
/// Transform an expression given an action to transform the children. For
|
|
/// this purposes of the await transformer the children should generally be
|
|
/// translated from right to left, in the reverse of evaluation order.
|
|
Expression transformTreeNode(
|
|
Expression expr,
|
|
void Function() action, {
|
|
bool alwaysName = false,
|
|
}) {
|
|
final bool shouldName = alwaysName || seenAwait;
|
|
|
|
// 1. If there is an await in a sibling to the right, emit an assignment to
|
|
// a temporary variable before transforming the children.
|
|
final Expression result = shouldName ? name(expr) : expr;
|
|
|
|
// 2. Remember the number of live temporaries before transforming the
|
|
// children.
|
|
final int index = nameIndex;
|
|
|
|
// 3. Transform the children. Initially they do not have an await in a
|
|
// sibling to their right.
|
|
seenAwait = false;
|
|
action();
|
|
|
|
// 4. If the expression was named then the variables used for children are
|
|
// no longer live but the variable used for the expression is. On the other
|
|
// hand, a sibling to the left (yet to be processed) cannot reuse any of
|
|
// the variables used here, as the assignments in the children (here) would
|
|
// overwrite assignments in the siblings to the left, possibly before the
|
|
// use of the overwritten values.
|
|
if (shouldName) {
|
|
if (index + 1 > nameIndex) {
|
|
nameIndex = index + 1;
|
|
}
|
|
seenAwait = true;
|
|
}
|
|
|
|
return result;
|
|
}
|
|
|
|
/// Transform expressions with one child expression.
|
|
Expression unary(Expression expr) {
|
|
return transformTreeNode(expr, () {
|
|
expr.transformChildren(this);
|
|
});
|
|
}
|
|
|
|
@override
|
|
TreeNode visitInvalidExpression(InvalidExpression expr) => unary(expr);
|
|
|
|
@override
|
|
TreeNode visitVariableSet(VariableSet expr) => unary(expr);
|
|
|
|
@override
|
|
TreeNode visitInstanceGet(InstanceGet expr) => unary(expr);
|
|
|
|
@override
|
|
TreeNode visitDynamicGet(DynamicGet expr) => unary(expr);
|
|
|
|
@override
|
|
TreeNode visitInstanceTearOff(InstanceTearOff expr) => unary(expr);
|
|
|
|
@override
|
|
TreeNode visitSuperPropertySet(SuperPropertySet expr) => unary(expr);
|
|
|
|
@override
|
|
TreeNode visitStaticSet(StaticSet expr) => unary(expr);
|
|
|
|
@override
|
|
TreeNode visitNot(Not expr) => unary(expr);
|
|
|
|
@override
|
|
TreeNode visitIsExpression(IsExpression expr) => unary(expr);
|
|
|
|
@override
|
|
TreeNode visitAsExpression(AsExpression expr) => unary(expr);
|
|
|
|
@override
|
|
TreeNode visitThrow(Throw expr) => unary(expr);
|
|
|
|
@override
|
|
TreeNode visitEqualsNull(EqualsNull expr) => unary(expr);
|
|
|
|
@override
|
|
TreeNode visitRecordIndexGet(RecordIndexGet expr) => unary(expr);
|
|
|
|
@override
|
|
TreeNode visitRecordNameGet(RecordNameGet expr) => unary(expr);
|
|
|
|
@override
|
|
TreeNode visitNullCheck(NullCheck expr) => unary(expr);
|
|
|
|
@override
|
|
TreeNode visitInstantiation(Instantiation expr) => unary(expr);
|
|
|
|
@override
|
|
TreeNode visitInstanceSet(InstanceSet expr) {
|
|
return transformTreeNode(expr, () {
|
|
expr.value = transform(expr.value)..parent = expr;
|
|
expr.receiver = transform(expr.receiver)..parent = expr;
|
|
});
|
|
}
|
|
|
|
@override
|
|
TreeNode visitDynamicSet(DynamicSet expr) {
|
|
return transformTreeNode(expr, () {
|
|
expr.value = transform(expr.value)..parent = expr;
|
|
expr.receiver = transform(expr.receiver)..parent = expr;
|
|
});
|
|
}
|
|
|
|
@override
|
|
TreeNode visitArguments(Arguments args) {
|
|
for (final named in args.named.reversed) {
|
|
named.value = transform(named.value)..parent = named;
|
|
}
|
|
final positional = args.positional;
|
|
for (var i = positional.length - 1; i >= 0; --i) {
|
|
positional[i] = transform(positional[i])..parent = args;
|
|
}
|
|
return args;
|
|
}
|
|
|
|
@override
|
|
TreeNode visitInstanceInvocation(InstanceInvocation expr) {
|
|
return transformTreeNode(expr, () {
|
|
visitArguments(expr.arguments);
|
|
expr.receiver = transform(expr.receiver)..parent = expr;
|
|
});
|
|
}
|
|
|
|
@override
|
|
TreeNode visitLocalFunctionInvocation(LocalFunctionInvocation expr) {
|
|
return transformTreeNode(expr, () {
|
|
visitArguments(expr.arguments);
|
|
});
|
|
}
|
|
|
|
@override
|
|
TreeNode visitDynamicInvocation(DynamicInvocation expr) {
|
|
return transformTreeNode(expr, () {
|
|
visitArguments(expr.arguments);
|
|
expr.receiver = transform(expr.receiver)..parent = expr;
|
|
});
|
|
}
|
|
|
|
@override
|
|
TreeNode visitFunctionInvocation(FunctionInvocation expr) {
|
|
return transformTreeNode(expr, () {
|
|
visitArguments(expr.arguments);
|
|
expr.receiver = transform(expr.receiver)..parent = expr;
|
|
});
|
|
}
|
|
|
|
@override
|
|
TreeNode visitEqualsCall(EqualsCall expr) {
|
|
return transformTreeNode(expr, () {
|
|
expr.right = transform(expr.right)..parent = expr;
|
|
expr.left = transform(expr.left)..parent = expr;
|
|
});
|
|
}
|
|
|
|
@override
|
|
TreeNode visitSuperMethodInvocation(SuperMethodInvocation expr) {
|
|
return transformTreeNode(expr, () {
|
|
visitArguments(expr.arguments);
|
|
});
|
|
}
|
|
|
|
@override
|
|
TreeNode visitStaticInvocation(StaticInvocation expr) {
|
|
return transformTreeNode(expr, () {
|
|
visitArguments(expr.arguments);
|
|
});
|
|
}
|
|
|
|
@override
|
|
TreeNode visitConstructorInvocation(ConstructorInvocation expr) {
|
|
return transformTreeNode(expr, () {
|
|
visitArguments(expr.arguments);
|
|
});
|
|
}
|
|
|
|
@override
|
|
TreeNode visitStringConcatenation(StringConcatenation expr) {
|
|
return transformTreeNode(expr, () {
|
|
final expressions = expr.expressions;
|
|
for (var i = expressions.length - 1; i >= 0; --i) {
|
|
expressions[i] = transform(expressions[i])..parent = expr;
|
|
}
|
|
});
|
|
}
|
|
|
|
@override
|
|
TreeNode visitListLiteral(ListLiteral expr) {
|
|
return transformTreeNode(expr, () {
|
|
final expressions = expr.expressions;
|
|
for (var i = expressions.length - 1; i >= 0; --i) {
|
|
expressions[i] = transform(expr.expressions[i])..parent = expr;
|
|
}
|
|
});
|
|
}
|
|
|
|
@override
|
|
TreeNode visitMapLiteral(MapLiteral expr) {
|
|
return transformTreeNode(expr, () {
|
|
for (final entry in expr.entries.reversed) {
|
|
entry.value = transform(entry.value)..parent = entry;
|
|
entry.key = transform(entry.key)..parent = entry;
|
|
}
|
|
});
|
|
}
|
|
|
|
@override
|
|
TreeNode visitSetLiteral(SetLiteral expr) {
|
|
return transformTreeNode(expr, () {
|
|
final expressions = expr.expressions;
|
|
for (var i = expressions.length - 1; i >= 0; --i) {
|
|
expressions[i] = transform(expr.expressions[i])..parent = expr;
|
|
}
|
|
});
|
|
}
|
|
|
|
@override
|
|
TreeNode visitRecordLiteral(RecordLiteral expr) {
|
|
return transformTreeNode(expr, () {
|
|
final named = expr.named;
|
|
for (var i = named.length - 1; i >= 0; --i) {
|
|
named[i] = transform(expr.named[i])..parent = expr;
|
|
}
|
|
|
|
final positional = expr.positional;
|
|
for (var i = positional.length - 1; i >= 0; --i) {
|
|
positional[i] = transform(expr.positional[i])..parent = expr;
|
|
}
|
|
});
|
|
}
|
|
|
|
// Expressions with control flow
|
|
|
|
/// Perform an action with a given list of statements so that it cannot emit
|
|
/// statements into the 'outer' list.
|
|
Expression delimit(Expression Function() action, List<Statement> inner) {
|
|
final outer = statements;
|
|
statements = inner;
|
|
final result = action();
|
|
statements = outer;
|
|
return result;
|
|
}
|
|
|
|
/// Make a [Block] from a reversed list of [Statement]s by reverting the
|
|
/// statements.
|
|
Block blockOf(List<Statement> reversedStatements) {
|
|
return Block(reversedStatements.reversed.toList());
|
|
}
|
|
|
|
@override
|
|
TreeNode visitLogicalExpression(LogicalExpression expr) {
|
|
final bool shouldName = seenAwait;
|
|
|
|
// Right is delimited because it is conditionally evaluated.
|
|
final List<Statement> rightStatements = [];
|
|
seenAwait = false;
|
|
expr.right = delimit(() => transform(expr.right), rightStatements)
|
|
..parent = expr;
|
|
final bool rightAwait = seenAwait;
|
|
|
|
if (rightStatements.isEmpty) {
|
|
// Easy case: right did not emit any statements.
|
|
seenAwait = shouldName;
|
|
return transformTreeNode(expr, () {
|
|
expr.left = transform(expr.left)..parent = expr;
|
|
seenAwait = seenAwait || rightAwait;
|
|
});
|
|
}
|
|
|
|
// If right has emitted statements we will produce a temporary t and emit
|
|
// for && (there is an analogous case for ||):
|
|
//
|
|
// t = false;
|
|
// if ([left]) {
|
|
// t = [right];
|
|
// }
|
|
|
|
// Recall that statements are emitted in reverse order, so first emit the
|
|
// if statement, then the assignment of [left] == true, and then translate
|
|
// left so any statements it emits occur after in the accumulated list
|
|
// (that is, so they occur before in the corresponding block).
|
|
final Block rightBody = blockOf(rightStatements);
|
|
final InterfaceType type = staticTypeContext.typeEnvironment.coreTypes
|
|
.boolRawType(staticTypeContext.nonNullable);
|
|
final VariableDeclaration result = allocateTemporary(nameIndex, type);
|
|
rightBody.addStatement(
|
|
ExpressionStatement(VariableSet(result, expr.right)),
|
|
);
|
|
final Statement then;
|
|
final Statement? otherwise;
|
|
if (expr.operatorEnum == LogicalExpressionOperator.AND) {
|
|
then = rightBody;
|
|
otherwise = null;
|
|
} else {
|
|
then = EmptyStatement();
|
|
otherwise = rightBody;
|
|
}
|
|
final ifStatement = IfStatement(expr.left, then, otherwise);
|
|
statements.add(ifStatement);
|
|
|
|
seenAwait = false;
|
|
ifStatement.condition = transform(expr.left)..parent = ifStatement;
|
|
statements.add(
|
|
ExpressionStatement(
|
|
VariableSet(
|
|
result,
|
|
BoolLiteral(expr.operatorEnum == LogicalExpressionOperator.OR),
|
|
),
|
|
),
|
|
);
|
|
|
|
nameIndex += 1;
|
|
seenAwait = seenAwait || rightAwait;
|
|
return VariableGet(result);
|
|
}
|
|
|
|
@override
|
|
TreeNode visitConditionalExpression(ConditionalExpression expr) {
|
|
// Then and otherwise are delimited because they are conditionally
|
|
// evaluated.
|
|
final bool shouldName = seenAwait;
|
|
|
|
final int savedNameIndex = nameIndex;
|
|
|
|
final thenStatements = <Statement>[];
|
|
seenAwait = false;
|
|
expr.then = delimit(() => transform(expr.then), thenStatements)
|
|
..parent = expr;
|
|
final thenAwait = seenAwait;
|
|
|
|
final thenNameIndex = nameIndex;
|
|
nameIndex = savedNameIndex;
|
|
|
|
final List<Statement> otherwiseStatements = [];
|
|
seenAwait = false;
|
|
expr.otherwise = delimit(
|
|
() => transform(expr.otherwise),
|
|
otherwiseStatements,
|
|
)..parent = expr;
|
|
final otherwiseAwait = seenAwait;
|
|
|
|
// Only one side of this branch will get executed at a time, so just make
|
|
// sure we have enough temps for either, not both at the same time.
|
|
if (thenNameIndex > nameIndex) {
|
|
nameIndex = thenNameIndex;
|
|
}
|
|
|
|
if (thenStatements.isEmpty && otherwiseStatements.isEmpty) {
|
|
// Easy case: neither then nor otherwise emitted any statements.
|
|
seenAwait = shouldName;
|
|
return transformTreeNode(expr, () {
|
|
expr.condition = transform(expr.condition)..parent = expr;
|
|
seenAwait = seenAwait || thenAwait || otherwiseAwait;
|
|
});
|
|
}
|
|
|
|
// If `then` or `otherwise` has emitted statements we will produce a
|
|
// temporary t and emit:
|
|
//
|
|
// if ([condition]) {
|
|
// t = [left];
|
|
// } else {
|
|
// t = [right];
|
|
// }
|
|
final result = allocateTemporary(nameIndex, expr.staticType);
|
|
final thenBody = blockOf(thenStatements);
|
|
final otherwiseBody = blockOf(otherwiseStatements);
|
|
thenBody.addStatement(ExpressionStatement(VariableSet(result, expr.then)));
|
|
otherwiseBody.addStatement(
|
|
ExpressionStatement(VariableSet(result, expr.otherwise)),
|
|
);
|
|
final branch = IfStatement(expr.condition, thenBody, otherwiseBody);
|
|
statements.add(branch);
|
|
|
|
seenAwait = false;
|
|
branch.condition = transform(branch.condition)..parent = branch;
|
|
|
|
nameIndex += 1;
|
|
seenAwait = seenAwait || thenAwait || otherwiseAwait;
|
|
return castVariableGet(result, expr.staticType);
|
|
}
|
|
|
|
// Await expression
|
|
|
|
@override
|
|
TreeNode visitAwaitExpression(AwaitExpression expr) {
|
|
// TODO (omersa): Only name if the await is not already in assignment RHS
|
|
return transformTreeNode(expr, () {
|
|
expr.transformChildren(this);
|
|
}, alwaysName: true);
|
|
}
|
|
|
|
// Block expressions
|
|
|
|
@override
|
|
TreeNode visitBlockExpression(BlockExpression expr) {
|
|
return transformTreeNode(expr, () {
|
|
expr.value = transform(expr.value)..parent = expr;
|
|
final List<Statement> body = <Statement>[];
|
|
for (final Statement stmt in expr.body.statements.reversed) {
|
|
final Statement? translation = _rewriteStatement(stmt);
|
|
if (translation != null) {
|
|
body.add(translation);
|
|
}
|
|
}
|
|
expr.body = Block(body.reversed.toList())..parent = expr;
|
|
});
|
|
}
|
|
|
|
@override
|
|
TreeNode visitLet(Let expr) {
|
|
final body = transform(expr.body);
|
|
final VariableDeclaration variable = expr.variable;
|
|
if (seenAwait) {
|
|
// There is an await in the body of `let var x = initializer in body` or
|
|
// to its right. We will produce the sequence of statements:
|
|
//
|
|
// <initializer's statements>
|
|
// var x = <initializer's value>
|
|
// <body's statements>
|
|
//
|
|
// and return the body's value.
|
|
statements.add(VariableStatement(variable));
|
|
var index = nameIndex;
|
|
seenAwait = false;
|
|
variable.initializer = transform(variable.initializer!)
|
|
..parent = variable;
|
|
// Temporaries used in the initializer or the body are not live but the
|
|
// temporary used for the body is.
|
|
if (index + 1 > nameIndex) {
|
|
nameIndex = index + 1;
|
|
}
|
|
seenAwait = true;
|
|
return body;
|
|
} else {
|
|
// The body in `let x = initializer in body` did not contain an await.
|
|
// We can leave a let expression.
|
|
return transformTreeNode(expr, () {
|
|
// The body has already been translated.
|
|
expr.body = body..parent = expr;
|
|
variable.initializer = transform(variable.initializer!)
|
|
..parent = variable;
|
|
});
|
|
}
|
|
}
|
|
|
|
@override
|
|
TreeNode visitFunctionNode(FunctionNode node) {
|
|
var nestedRewriter = _AwaitTransformer(staticTypeContext, coreTypes);
|
|
return nestedRewriter.transform(node);
|
|
}
|
|
|
|
/// This method translates a statement nested in an expression (e.g., in a
|
|
/// block expression). It produces a translated statement, a list of
|
|
/// statements which are side effects necessary for any await, and a flag
|
|
/// indicating whether there was an await in the statement or to its right.
|
|
/// The translated statement can be null in the case where there was already
|
|
/// an await to the right.
|
|
Statement? _rewriteStatement(Statement stmt) {
|
|
// The translation is accumulating two lists of statements, an inner list
|
|
// which is a reversed list of effects needed for the current expression
|
|
// and an outer list which represents the block containing the current
|
|
// statement. We need to preserve both of those from side effects.
|
|
final List<Statement> savedInner = statements;
|
|
final List<Statement> savedOuter = _statementTransformer.statements;
|
|
statements = <Statement>[];
|
|
_statementTransformer.statements = <Statement>[];
|
|
stmt = _statementTransformer.transform(stmt);
|
|
|
|
final List<Statement> results = _statementTransformer.statements;
|
|
results.add(stmt);
|
|
|
|
statements = savedInner;
|
|
_statementTransformer.statements = savedOuter;
|
|
if (!seenAwait && results.length == 1) {
|
|
return results.first;
|
|
}
|
|
statements.addAll(results.reversed);
|
|
return null;
|
|
}
|
|
|
|
@override
|
|
TreeNode defaultStatement(Statement stmt) {
|
|
throw UnsupportedError(
|
|
"Use _rewriteStatement to transform statement: $stmt",
|
|
);
|
|
}
|
|
}
|