6e989dfe35
This change replaces generation of old invocation AST nodes (such as PropertyGet, PropertySet and MethodInvocation) in async transformers with new nodes (InstanceGet, InstanceSet, InstanceInvocation, LocalFunctionInvocation). The old nodes will be deleted eventually. TEST=existing tests Issue: https://github.com/dart-lang/sdk/issues/45340 Change-Id: I8c2ead9509cfd2def2f75f9d82dc13b2a9490fdf Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/202801 Commit-Queue: Alexander Markov <alexmarkov@google.com> Reviewed-by: Clement Skau <cskau@google.com> Reviewed-by: Aske Simon Christensen <askesc@google.com>
1539 lines
56 KiB
Dart
1539 lines
56 KiB
Dart
// Copyright (c) 2016, the Dart project authors. Please see the AUTHORS file
|
|
// for details. All rights reserved. Use of this source code is governed by a
|
|
// BSD-style license that can be found in the LICENSE file.
|
|
|
|
library kernel.transformations.continuation;
|
|
|
|
import 'dart:math' as math;
|
|
|
|
import '../ast.dart';
|
|
import '../core_types.dart';
|
|
import '../type_algebra.dart' show Substitution;
|
|
import '../type_environment.dart';
|
|
|
|
import 'async.dart';
|
|
|
|
class ContinuationVariables {
|
|
static const awaitJumpVar = ':await_jump_var';
|
|
static const asyncFuture = ':async_future';
|
|
static const isSync = ":is_sync";
|
|
static const awaitContextVar = ':await_ctx_var';
|
|
static const asyncCompleter = ':async_completer';
|
|
static const asyncOp = ':async_op';
|
|
static const asyncOpThen = ':async_op_then';
|
|
static const asyncOpError = ':async_op_error';
|
|
static const controller = ':controller';
|
|
static const controllerStreamVar = ':controller_stream';
|
|
static const forIterator = ':for-iterator';
|
|
static const returnValue = ':return_value';
|
|
static const stream = ':stream';
|
|
static const syncForIterator = ':sync-for-iterator';
|
|
static const syncOpGen = ':sync_op_gen';
|
|
static const syncOp = ':sync_op';
|
|
// sync_op(..) parameter.
|
|
static const iteratorParam = ':iterator';
|
|
// (a)sync_op(..) parameters.
|
|
static const exceptionParam = ':exception';
|
|
static const stackTraceParam = ':stack_trace';
|
|
|
|
static const savedTryContextVarPrefix = ':saved_try_context_var';
|
|
static const exceptionVarPrefix = ':exception';
|
|
static const stackTraceVarPrefix = ':stack_trace';
|
|
|
|
static String savedTryContextVar(int depth) =>
|
|
'$savedTryContextVarPrefix$depth';
|
|
static String exceptionVar(int depth) => '$exceptionVarPrefix$depth';
|
|
static String stackTraceVar(int depth) => '$stackTraceVarPrefix$depth';
|
|
}
|
|
|
|
void transformLibraries(
|
|
TypeEnvironment typeEnvironment, List<Library> libraries,
|
|
{required bool productMode}) {
|
|
var helper =
|
|
new HelperNodes.fromCoreTypes(typeEnvironment.coreTypes, productMode);
|
|
var rewriter = new RecursiveContinuationRewriter(
|
|
helper, new StatefulStaticTypeContext.stacked(typeEnvironment));
|
|
for (var library in libraries) {
|
|
rewriter.rewriteLibrary(library);
|
|
}
|
|
}
|
|
|
|
Component transformComponent(
|
|
TypeEnvironment typeEnvironment, Component component,
|
|
{required bool productMode}) {
|
|
var helper =
|
|
new HelperNodes.fromCoreTypes(typeEnvironment.coreTypes, productMode);
|
|
var rewriter = new RecursiveContinuationRewriter(
|
|
helper, new StatefulStaticTypeContext.stacked(typeEnvironment));
|
|
return rewriter.rewriteComponent(component);
|
|
}
|
|
|
|
Procedure transformProcedure(
|
|
TypeEnvironment typeEnvironment, Procedure procedure,
|
|
{required bool productMode}) {
|
|
var helper =
|
|
new HelperNodes.fromCoreTypes(typeEnvironment.coreTypes, productMode);
|
|
var rewriter = new RecursiveContinuationRewriter(
|
|
helper, new StatefulStaticTypeContext.stacked(typeEnvironment));
|
|
return rewriter.transform(procedure);
|
|
}
|
|
|
|
class RecursiveContinuationRewriter extends RemovingTransformer {
|
|
final HelperNodes helper;
|
|
|
|
final VariableDeclaration awaitJumpVariable = new VariableDeclaration(
|
|
ContinuationVariables.awaitJumpVar,
|
|
initializer: new IntLiteral(0));
|
|
final VariableDeclaration awaitContextVariable =
|
|
new VariableDeclaration(ContinuationVariables.awaitContextVar);
|
|
|
|
StatefulStaticTypeContext staticTypeContext;
|
|
|
|
RecursiveContinuationRewriter(this.helper, this.staticTypeContext);
|
|
|
|
Component rewriteComponent(Component node) {
|
|
return transform(node);
|
|
}
|
|
|
|
Library rewriteLibrary(Library node) {
|
|
return transform(node);
|
|
}
|
|
|
|
visitField(Field node, TreeNode? removalSentinel) {
|
|
staticTypeContext.enterMember(node);
|
|
final result = super.visitField(node, removalSentinel);
|
|
staticTypeContext.leaveMember(node);
|
|
return result;
|
|
}
|
|
|
|
visitConstructor(Constructor node, TreeNode? removalSentinel) {
|
|
staticTypeContext.enterMember(node);
|
|
final result = super.visitConstructor(node, removalSentinel);
|
|
staticTypeContext.leaveMember(node);
|
|
return result;
|
|
}
|
|
|
|
@override
|
|
visitProcedure(Procedure node, TreeNode? removalSentinel) {
|
|
staticTypeContext.enterMember(node);
|
|
final result =
|
|
node.isAbstract ? node : super.visitProcedure(node, removalSentinel);
|
|
staticTypeContext.leaveMember(node);
|
|
return result;
|
|
}
|
|
|
|
@override
|
|
visitLibrary(Library node, TreeNode? removalSentinel) {
|
|
staticTypeContext.enterLibrary(node);
|
|
Library result = super.visitLibrary(node, removalSentinel) as Library;
|
|
staticTypeContext.leaveLibrary(node);
|
|
return result;
|
|
}
|
|
|
|
@override
|
|
visitFunctionNode(FunctionNode node, TreeNode? removalSentinel) {
|
|
switch (node.asyncMarker) {
|
|
case AsyncMarker.Sync:
|
|
case AsyncMarker.SyncYielding:
|
|
node.transformOrRemoveChildren(
|
|
new RecursiveContinuationRewriter(helper, staticTypeContext));
|
|
return node;
|
|
case AsyncMarker.SyncStar:
|
|
return new SyncStarFunctionRewriter(helper, node, staticTypeContext)
|
|
.rewrite();
|
|
case AsyncMarker.Async:
|
|
return new AsyncFunctionRewriter(helper, node, staticTypeContext)
|
|
.rewrite();
|
|
case AsyncMarker.AsyncStar:
|
|
return new AsyncStarFunctionRewriter(helper, node, staticTypeContext)
|
|
.rewrite();
|
|
}
|
|
}
|
|
|
|
@override
|
|
TreeNode visitForInStatement(ForInStatement stmt, TreeNode? removalSentinel) {
|
|
if (stmt.isAsync) {
|
|
return super.visitForInStatement(stmt, removalSentinel);
|
|
}
|
|
|
|
// Transform
|
|
//
|
|
// for ({var/final} T <variable> in <iterable>) { ... }
|
|
//
|
|
// Into
|
|
//
|
|
// {
|
|
// final Iterator<T> :sync-for-iterator = <iterable>.iterator;
|
|
// for (; :sync-for-iterator.moveNext() ;) {
|
|
// {var/final} T variable = :sync-for-iterator.current;
|
|
// ...
|
|
// }
|
|
// }
|
|
// }
|
|
final CoreTypes coreTypes = staticTypeContext.typeEnvironment.coreTypes;
|
|
|
|
// The CFE might invoke this transformation despite the program having
|
|
// compile-time errors. So we will not transform this [stmt] if the
|
|
// `stmt.iterable` is an invalid expression or has an invalid type and
|
|
// instead eliminate the entire for-in and replace it with a invalid
|
|
// expression statement.
|
|
final iterable = stmt.iterable;
|
|
final iterableType = iterable.getStaticType(staticTypeContext);
|
|
if (iterableType is InvalidType) {
|
|
return ExpressionStatement(
|
|
InvalidExpression('Invalid iterable type in for-in'));
|
|
}
|
|
|
|
// The NNBD sdk declares that Iterable.get:iterator returns a non-nullable
|
|
// `Iterator<E>`.
|
|
assert(const [
|
|
Nullability.nonNullable,
|
|
Nullability.legacy
|
|
].contains(coreTypes.iterableGetIterator.function.returnType.nullability));
|
|
|
|
final DartType elementType = stmt.getElementType(staticTypeContext);
|
|
final iteratorType = InterfaceType(
|
|
coreTypes.iteratorClass, staticTypeContext.nonNullable, [elementType]);
|
|
|
|
final syncForIterator = VariableDeclaration(
|
|
ContinuationVariables.syncForIterator,
|
|
initializer: InstanceGet(InstanceAccessKind.Instance, iterable,
|
|
coreTypes.iterableGetIterator.name,
|
|
interfaceTarget: coreTypes.iterableGetIterator,
|
|
resultType: iteratorType)
|
|
..fileOffset = iterable.fileOffset,
|
|
type: iteratorType)
|
|
..fileOffset = iterable.fileOffset;
|
|
|
|
final condition = InstanceInvocation(
|
|
InstanceAccessKind.Instance,
|
|
VariableGet(syncForIterator),
|
|
coreTypes.iteratorMoveNext.name,
|
|
Arguments([]),
|
|
interfaceTarget: coreTypes.iteratorMoveNext,
|
|
functionType: coreTypes.iteratorMoveNext.getterType as FunctionType)
|
|
..fileOffset = iterable.fileOffset;
|
|
|
|
final variable = stmt.variable
|
|
..initializer = (InstanceGet(InstanceAccessKind.Instance,
|
|
VariableGet(syncForIterator), coreTypes.iteratorGetCurrent.name,
|
|
interfaceTarget: coreTypes.iteratorGetCurrent,
|
|
resultType: elementType)
|
|
..fileOffset = stmt.bodyOffset);
|
|
|
|
final Block body = Block([variable, stmt.body]);
|
|
|
|
return transform(
|
|
Block([syncForIterator, ForStatement([], condition, [], body)]));
|
|
}
|
|
}
|
|
|
|
abstract class ContinuationRewriterBase extends RecursiveContinuationRewriter {
|
|
final FunctionNode enclosingFunction;
|
|
|
|
int currentTryDepth = 0; // Nesting depth for try-blocks.
|
|
int currentCatchDepth = 0; // Nesting depth for catch-blocks.
|
|
int capturedTryDepth = 0; // Deepest yield point within a try-block.
|
|
int capturedCatchDepth = 0; // Deepest yield point within a catch-block.
|
|
|
|
ContinuationRewriterBase(HelperNodes helper, this.enclosingFunction,
|
|
StatefulStaticTypeContext staticTypeContext)
|
|
: super(helper, staticTypeContext);
|
|
|
|
/// Given a container [type], which is an instantiation of the given
|
|
/// [containerClass] extract its element type.
|
|
///
|
|
/// This is used to extract element type from Future<T>, Iterable<T> and
|
|
/// Stream<T> instantiations.
|
|
///
|
|
/// If instantiation is not valid (has more than 1 type argument) then
|
|
/// this function returns [InvalidType].
|
|
static DartType elementTypeFrom(Class containerClass, DartType type) {
|
|
if (type is InterfaceType) {
|
|
if (type.classNode == containerClass) {
|
|
if (type.typeArguments.length == 0) {
|
|
return const DynamicType();
|
|
} else if (type.typeArguments.length == 1) {
|
|
return type.typeArguments[0];
|
|
} else {
|
|
return const InvalidType();
|
|
}
|
|
}
|
|
}
|
|
return const DynamicType();
|
|
}
|
|
|
|
static DartType elementTypeFromFutureOr(DartType type) {
|
|
if (type is FutureOrType) {
|
|
return type.typeArgument;
|
|
}
|
|
return const DynamicType();
|
|
}
|
|
|
|
DartType elementTypeFromReturnType(Class expected) =>
|
|
elementTypeFrom(expected, enclosingFunction.returnType);
|
|
|
|
DartType elementTypeFromAsyncReturnType() =>
|
|
elementTypeFromFutureOr(enclosingFunction.returnType);
|
|
|
|
Statement createContinuationPoint([Expression? value]) {
|
|
if (value == null) value = new NullLiteral();
|
|
capturedTryDepth = math.max(capturedTryDepth, currentTryDepth);
|
|
capturedCatchDepth = math.max(capturedCatchDepth, currentCatchDepth);
|
|
return new YieldStatement(value, isNative: true);
|
|
}
|
|
|
|
TreeNode visitTryCatch(TryCatch node, TreeNode? removalSentinel) {
|
|
// ignore: unnecessary_null_comparison
|
|
if (node.body != null) {
|
|
++currentTryDepth;
|
|
node.body = transform(node.body);
|
|
node.body.parent = node;
|
|
--currentTryDepth;
|
|
}
|
|
|
|
++currentCatchDepth;
|
|
transformCatchList(node.catches, node);
|
|
--currentCatchDepth;
|
|
return node;
|
|
}
|
|
|
|
TreeNode visitTryFinally(TryFinally node, TreeNode? removalSentinel) {
|
|
// ignore: unnecessary_null_comparison
|
|
if (node.body != null) {
|
|
++currentTryDepth;
|
|
node.body = transform(node.body);
|
|
node.body.parent = node;
|
|
--currentTryDepth;
|
|
}
|
|
// ignore: unnecessary_null_comparison
|
|
if (node.finalizer != null) {
|
|
++currentCatchDepth;
|
|
node.finalizer = transform(node.finalizer);
|
|
node.finalizer.parent = node;
|
|
--currentCatchDepth;
|
|
}
|
|
return node;
|
|
}
|
|
|
|
Iterable<VariableDeclaration> createCapturedTryVariables() =>
|
|
new Iterable.generate(
|
|
capturedTryDepth,
|
|
(depth) => new VariableDeclaration(
|
|
ContinuationVariables.savedTryContextVar(depth)));
|
|
|
|
Iterable<VariableDeclaration> createCapturedCatchVariables() =>
|
|
new Iterable.generate(capturedCatchDepth).expand((depth) => [
|
|
new VariableDeclaration(ContinuationVariables.exceptionVar(depth)),
|
|
new VariableDeclaration(ContinuationVariables.stackTraceVar(depth)),
|
|
]);
|
|
|
|
List<VariableDeclaration> variableDeclarations() {
|
|
awaitJumpVariable.type = staticTypeContext.typeEnvironment.coreTypes
|
|
.intRawType(staticTypeContext.nonNullable);
|
|
return [awaitJumpVariable, awaitContextVariable]
|
|
..addAll(createCapturedTryVariables())
|
|
..addAll(createCapturedCatchVariables());
|
|
}
|
|
}
|
|
|
|
// Transformer that rewrites all variable references to a given function's
|
|
// parameters.
|
|
// This allows us to e.g. "shadow" the original parameter variables with copies
|
|
// unique to given sub-closure to prevent shared variables being overwritten.
|
|
class ShadowRewriter extends Transformer {
|
|
final FunctionNode enclosingFunction;
|
|
Map<VariableDeclaration, VariableDeclaration?> _shadowedParameters = {};
|
|
|
|
ShadowRewriter(this.enclosingFunction) {
|
|
for (final parameter in enclosingFunction.positionalParameters
|
|
.followedBy(enclosingFunction.namedParameters)) {
|
|
// Put in placeholers so we can allocate new variables lazily- i.e. only
|
|
// if they're later referenced.
|
|
_shadowedParameters[parameter] = null;
|
|
}
|
|
}
|
|
|
|
// Return all used parameters.
|
|
Iterable<VariableDeclaration> get shadowedParameters =>
|
|
_shadowedParameters.values.whereType<VariableDeclaration>();
|
|
|
|
VariableDeclaration _rewrite(VariableDeclaration variable) {
|
|
if (_shadowedParameters.containsKey(variable)) {
|
|
// Fill in placeholder.
|
|
VariableDeclaration? placeholder = _shadowedParameters[variable];
|
|
if (placeholder == null) {
|
|
placeholder = _shadowedParameters[variable] = VariableDeclaration(
|
|
variable.name,
|
|
type: variable.type,
|
|
initializer: VariableGet(variable),
|
|
);
|
|
}
|
|
variable = placeholder;
|
|
}
|
|
return variable;
|
|
}
|
|
|
|
@override
|
|
TreeNode visitVariableGet(VariableGet node) {
|
|
node = super.visitVariableGet(node) as VariableGet;
|
|
return node..variable = _rewrite(node.variable);
|
|
}
|
|
|
|
@override
|
|
TreeNode visitVariableSet(VariableSet node) {
|
|
node = super.visitVariableSet(node) as VariableSet;
|
|
return node..variable = _rewrite(node.variable);
|
|
}
|
|
}
|
|
|
|
class SyncStarFunctionRewriter extends ContinuationRewriterBase {
|
|
final VariableDeclaration iteratorParameter;
|
|
|
|
SyncStarFunctionRewriter(HelperNodes helper, FunctionNode enclosingFunction,
|
|
StatefulStaticTypeContext staticTypeContext)
|
|
: iteratorParameter =
|
|
VariableDeclaration(ContinuationVariables.iteratorParam)
|
|
..type = InterfaceType(
|
|
helper.syncIteratorClass, staticTypeContext.nullable, [
|
|
// Note: This is dynamic since nested iterators (of potentially
|
|
// different type) are handled by shared internal synthetic
|
|
// code.
|
|
const DynamicType(),
|
|
]),
|
|
super(helper, enclosingFunction, staticTypeContext);
|
|
|
|
FunctionNode rewrite() {
|
|
// We need to preserve the original parameters passed to the sync* function
|
|
// because each iteration should start from those parameters. To achieve
|
|
// this we shadow the original parameters with new variables (which are
|
|
// initialised to the original parameter values) and rewrite
|
|
// the body to use these variables instead.
|
|
final shadowRewriter = ShadowRewriter(enclosingFunction);
|
|
enclosingFunction.body = shadowRewriter.transform(enclosingFunction.body!)
|
|
..parent = enclosingFunction;
|
|
|
|
// TODO(cskau): Figure out why inlining this below causes segfaults.
|
|
// Maybe related to http://dartbug.com/41596 ?
|
|
final syncOpFN = FunctionNode(buildClosureBody(),
|
|
positionalParameters: [
|
|
iteratorParameter,
|
|
new VariableDeclaration(ContinuationVariables.exceptionParam),
|
|
new VariableDeclaration(ContinuationVariables.stackTraceParam),
|
|
],
|
|
requiredParameterCount: 3,
|
|
// Note: SyncYielding functions have no Dart equivalent. Since they are
|
|
// synchronous, we use Sync. (Note also that the Dart VM backend uses
|
|
// the Dart async marker to decide if functions are debuggable.)
|
|
asyncMarker: AsyncMarker.SyncYielding,
|
|
dartAsyncMarker: AsyncMarker.Sync,
|
|
returnType: helper.coreTypes.boolLegacyRawType)
|
|
..fileOffset = enclosingFunction.fileOffset
|
|
..fileEndOffset = enclosingFunction.fileEndOffset;
|
|
final syncOpType =
|
|
syncOpFN.computeThisFunctionType(staticTypeContext.nonNullable);
|
|
|
|
final syncOpGenVariable = VariableDeclaration(
|
|
ContinuationVariables.syncOpGen,
|
|
type: FunctionType([], syncOpType, staticTypeContext.nonNullable));
|
|
|
|
final syncOpVariable = VariableDeclaration(ContinuationVariables.syncOp);
|
|
final syncOpDecl = FunctionDeclaration(syncOpVariable, syncOpFN);
|
|
|
|
enclosingFunction.body = Block([
|
|
// :sync_op_gen() {
|
|
// :await_jump_var;
|
|
// :await_ctx_var;
|
|
// bool sync_op(:iterator, e, st) yielding {
|
|
// modified <node.body> ...
|
|
// };
|
|
// return sync_op;
|
|
// }
|
|
FunctionDeclaration(
|
|
syncOpGenVariable,
|
|
FunctionNode(
|
|
Block([
|
|
// :await_jump_var, :await_ctx_var.
|
|
...variableDeclarations(),
|
|
// Shadow any used function parameters with local copies.
|
|
...shadowRewriter.shadowedParameters,
|
|
// :sync_op(..) { .. }
|
|
syncOpDecl,
|
|
// return sync_op;
|
|
ReturnStatement(VariableGet(syncOpVariable)),
|
|
]),
|
|
returnType: syncOpType)),
|
|
|
|
// return _SyncIterable<T>(:sync_op_gen);
|
|
ReturnStatement(ConstructorInvocation(
|
|
helper.syncIterableConstructor,
|
|
Arguments([
|
|
VariableGet(syncOpGenVariable)
|
|
], types: [
|
|
ContinuationRewriterBase.elementTypeFrom(
|
|
helper.iterableClass, enclosingFunction.returnType)
|
|
]))),
|
|
])
|
|
..parent = enclosingFunction;
|
|
enclosingFunction.asyncMarker = AsyncMarker.Sync;
|
|
|
|
return enclosingFunction;
|
|
}
|
|
|
|
Statement buildClosureBody() {
|
|
// The body will insert calls to
|
|
// :iterator.current_=
|
|
// :iterator.isYieldEach=
|
|
// and return `true` as long as it did something and `false` when it's done.
|
|
return new Block(<Statement>[
|
|
transform(enclosingFunction.body!),
|
|
new ReturnStatement(new BoolLiteral(false))
|
|
..fileOffset = enclosingFunction.fileEndOffset
|
|
]);
|
|
}
|
|
|
|
visitYieldStatement(YieldStatement node, TreeNode? removalSentinel) {
|
|
Expression transformedExpression = transform(node.expression);
|
|
|
|
var statements = <Statement>[];
|
|
if (node.isYieldStar) {
|
|
statements.add(new ExpressionStatement(new InstanceSet(
|
|
InstanceAccessKind.Instance,
|
|
VariableGet(iteratorParameter),
|
|
helper.syncIteratorYieldEachIterable.name,
|
|
transformedExpression,
|
|
interfaceTarget: helper.syncIteratorYieldEachIterable)));
|
|
} else {
|
|
statements.add(new ExpressionStatement(new InstanceSet(
|
|
InstanceAccessKind.Instance,
|
|
VariableGet(iteratorParameter),
|
|
helper.syncIteratorCurrent.name,
|
|
transformedExpression,
|
|
interfaceTarget: helper.syncIteratorCurrent)));
|
|
}
|
|
|
|
statements.add(createContinuationPoint(new BoolLiteral(true))
|
|
..fileOffset = node.fileOffset);
|
|
return new Block(statements);
|
|
}
|
|
|
|
TreeNode visitReturnStatement(
|
|
ReturnStatement node, TreeNode? removalSentinel) {
|
|
// sync* functions cannot return a value.
|
|
assert(node.expression == null || node.expression is NullLiteral);
|
|
node.expression = new BoolLiteral(false)..parent = node;
|
|
return node;
|
|
}
|
|
}
|
|
|
|
abstract class AsyncRewriterBase extends ContinuationRewriterBase {
|
|
// :async_op has type ([dynamic result, dynamic e, StackTrace? s]) -> dynamic
|
|
final VariableDeclaration nestedClosureVariable;
|
|
|
|
// :async_op_then has type (dynamic result) -> dynamic
|
|
final VariableDeclaration thenContinuationVariable;
|
|
|
|
// :async_op_error has type (Object e, StackTrace s) -> dynamic
|
|
final VariableDeclaration catchErrorContinuationVariable;
|
|
|
|
LabeledStatement? labeledBody;
|
|
|
|
ExpressionLifter? expressionRewriter;
|
|
|
|
AsyncRewriterBase(HelperNodes helper, FunctionNode enclosingFunction,
|
|
StatefulStaticTypeContext staticTypeContext)
|
|
: nestedClosureVariable = VariableDeclaration(
|
|
ContinuationVariables.asyncOp,
|
|
type: FunctionType([
|
|
const DynamicType(),
|
|
const DynamicType(),
|
|
helper.coreTypes.stackTraceRawType(staticTypeContext.nullable),
|
|
], const DynamicType(), staticTypeContext.nonNullable,
|
|
requiredParameterCount: 0)),
|
|
thenContinuationVariable = VariableDeclaration(
|
|
ContinuationVariables.asyncOpThen,
|
|
type: FunctionType(const [const DynamicType()], const DynamicType(),
|
|
staticTypeContext.nonNullable)),
|
|
catchErrorContinuationVariable =
|
|
VariableDeclaration(ContinuationVariables.asyncOpError,
|
|
type: FunctionType([
|
|
helper.coreTypes.objectRawType(staticTypeContext.nonNullable),
|
|
helper.coreTypes
|
|
.stackTraceRawType(staticTypeContext.nonNullable),
|
|
], const DynamicType(), staticTypeContext.nonNullable)),
|
|
super(helper, enclosingFunction, staticTypeContext) {}
|
|
|
|
void setupAsyncContinuations(List<Statement> statements) {
|
|
expressionRewriter = new ExpressionLifter(this);
|
|
|
|
// var :async_op_then;
|
|
statements.add(thenContinuationVariable);
|
|
|
|
// var :async_op_error;
|
|
statements.add(catchErrorContinuationVariable);
|
|
|
|
// :async_op([:result, :exception, :stack_trace]) {
|
|
// modified <node.body>;
|
|
// }
|
|
final parameters = <VariableDeclaration>[
|
|
expressionRewriter!.asyncResult,
|
|
new VariableDeclaration(ContinuationVariables.exceptionParam),
|
|
new VariableDeclaration(ContinuationVariables.stackTraceParam),
|
|
];
|
|
|
|
// Note: SyncYielding functions have no Dart equivalent. Since they are
|
|
// synchronous, we use Sync. (Note also that the Dart VM backend uses the
|
|
// Dart async marker to decide if functions are debuggable.)
|
|
final function = new FunctionNode(buildWrappedBody(),
|
|
positionalParameters: parameters,
|
|
requiredParameterCount: 0,
|
|
asyncMarker: AsyncMarker.SyncYielding,
|
|
dartAsyncMarker: AsyncMarker.Sync)
|
|
..fileOffset = enclosingFunction.fileOffset
|
|
..fileEndOffset = enclosingFunction.fileEndOffset;
|
|
|
|
// The await expression lifter might have created a number of
|
|
// [VariableDeclarations].
|
|
// TODO(kustermann): If we didn't need any variables we should not emit
|
|
// these.
|
|
statements.addAll(variableDeclarations());
|
|
statements.addAll(expressionRewriter!.variables);
|
|
|
|
// Now add the closure function itself.
|
|
final closureFunction =
|
|
new FunctionDeclaration(nestedClosureVariable, function)
|
|
..fileOffset = enclosingFunction.parent!.fileOffset;
|
|
statements.add(closureFunction);
|
|
|
|
// :async_op_then = _asyncThenWrapperHelper(asyncBody);
|
|
final boundThenClosure = new StaticInvocation(helper.asyncThenWrapper,
|
|
new Arguments(<Expression>[new VariableGet(nestedClosureVariable)]));
|
|
final thenClosureVariableAssign = new ExpressionStatement(
|
|
new VariableSet(thenContinuationVariable, boundThenClosure));
|
|
statements.add(thenClosureVariableAssign);
|
|
|
|
// :async_op_error = _asyncErrorWrapperHelper(asyncBody);
|
|
final boundCatchErrorClosure = new StaticInvocation(
|
|
helper.asyncErrorWrapper,
|
|
new Arguments(<Expression>[new VariableGet(nestedClosureVariable)]));
|
|
final catchErrorClosureVariableAssign = new ExpressionStatement(
|
|
new VariableSet(
|
|
catchErrorContinuationVariable, boundCatchErrorClosure));
|
|
statements.add(catchErrorClosureVariableAssign);
|
|
}
|
|
|
|
Statement buildWrappedBody() {
|
|
++currentTryDepth;
|
|
labeledBody = new LabeledStatement(null);
|
|
labeledBody!.body = visitDelimited(enclosingFunction.body!)
|
|
..parent = labeledBody;
|
|
--currentTryDepth;
|
|
|
|
var exceptionVariable = VariableDeclaration('exception');
|
|
var stackTraceVariable = VariableDeclaration('stack_trace',
|
|
type:
|
|
helper.coreTypes.stackTraceRawType(staticTypeContext.nonNullable));
|
|
|
|
return new TryCatch(
|
|
buildReturn(labeledBody!),
|
|
<Catch>[
|
|
new Catch(
|
|
exceptionVariable,
|
|
new Block(<Statement>[
|
|
buildCatchBody(exceptionVariable, stackTraceVariable)
|
|
]),
|
|
stackTrace: stackTraceVariable)
|
|
],
|
|
isSynthetic: true,
|
|
);
|
|
}
|
|
|
|
Statement buildCatchBody(VariableDeclaration exceptionVariable,
|
|
VariableDeclaration stackTraceVariable);
|
|
|
|
Statement buildReturn(Statement body);
|
|
|
|
List<Statement> statements = <Statement>[];
|
|
|
|
TreeNode visitExpressionStatement(
|
|
ExpressionStatement stmt, TreeNode? removalSentinel) {
|
|
stmt.expression = expressionRewriter!.rewrite(stmt.expression, statements)
|
|
..parent = stmt;
|
|
statements.add(stmt);
|
|
return removalSentinel ?? EmptyStatement();
|
|
}
|
|
|
|
TreeNode visitBlock(Block stmt, TreeNode? removalSentinel) {
|
|
var saved = statements;
|
|
statements = <Statement>[];
|
|
for (var statement in stmt.statements) {
|
|
transform(statement);
|
|
}
|
|
saved.add(new Block(statements));
|
|
statements = saved;
|
|
return removalSentinel ?? EmptyStatement();
|
|
}
|
|
|
|
TreeNode visitEmptyStatement(EmptyStatement stmt, TreeNode? removalSentinel) {
|
|
statements.add(stmt);
|
|
return removalSentinel ?? EmptyStatement();
|
|
}
|
|
|
|
TreeNode visitAssertBlock(AssertBlock stmt, TreeNode? removalSentinel) {
|
|
var saved = statements;
|
|
statements = <Statement>[];
|
|
for (var statement in stmt.statements) {
|
|
transform(statement);
|
|
}
|
|
saved.add(new Block(statements));
|
|
statements = saved;
|
|
return removalSentinel ?? EmptyStatement();
|
|
}
|
|
|
|
TreeNode visitAssertStatement(
|
|
AssertStatement stmt, TreeNode? removalSentinel) {
|
|
var condEffects = <Statement>[];
|
|
var cond = expressionRewriter!.rewrite(stmt.condition, condEffects);
|
|
if (stmt.message == 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.
|
|
statements.add(
|
|
condEffects.isEmpty ? stmt : new AssertBlock(condEffects..add(stmt)));
|
|
return removalSentinel ?? EmptyStatement();
|
|
}
|
|
|
|
// The translation depends on the translation of the message, by cases.
|
|
Statement result;
|
|
var msgEffects = <Statement>[];
|
|
stmt.message = expressionRewriter!.rewrite(stmt.message!, 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;
|
|
result = 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 = new BoolLiteral(false)..parent = stmt;
|
|
result = new AssertBlock([
|
|
new IfStatement(
|
|
cond, new EmptyStatement(), new 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 = new BoolLiteral(false)..parent = stmt;
|
|
condEffects.add(new IfStatement(
|
|
cond, new EmptyStatement(), new Block(msgEffects..add(stmt))));
|
|
}
|
|
result = new AssertBlock(condEffects);
|
|
}
|
|
statements.add(result);
|
|
return removalSentinel ?? EmptyStatement();
|
|
}
|
|
|
|
Statement visitDelimited(Statement stmt) {
|
|
var saved = statements;
|
|
statements = <Statement>[];
|
|
transform(stmt);
|
|
Statement result =
|
|
statements.length == 1 ? statements.first : new Block(statements);
|
|
statements = saved;
|
|
return result;
|
|
}
|
|
|
|
TreeNode visitLabeledStatement(
|
|
LabeledStatement stmt, TreeNode? removalSentinel) {
|
|
stmt.body = visitDelimited(stmt.body)..parent = stmt;
|
|
statements.add(stmt);
|
|
return removalSentinel ?? EmptyStatement();
|
|
}
|
|
|
|
TreeNode visitBreakStatement(BreakStatement stmt, TreeNode? removalSentinel) {
|
|
statements.add(stmt);
|
|
return removalSentinel ?? EmptyStatement();
|
|
}
|
|
|
|
TreeNode visitWhileStatement(WhileStatement stmt, TreeNode? removalSentinel) {
|
|
Statement body = visitDelimited(stmt.body);
|
|
List<Statement> effects = <Statement>[];
|
|
Expression cond = expressionRewriter!.rewrite(stmt.condition, effects);
|
|
if (effects.isEmpty) {
|
|
stmt.condition = cond..parent = stmt;
|
|
stmt.body = body..parent = stmt;
|
|
statements.add(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;
|
|
// }
|
|
// }
|
|
LabeledStatement labeled = new LabeledStatement(stmt);
|
|
stmt.condition = new BoolLiteral(true)..parent = stmt;
|
|
effects.add(new IfStatement(cond, body, new BreakStatement(labeled)));
|
|
stmt.body = new Block(effects)..parent = stmt;
|
|
statements.add(labeled);
|
|
}
|
|
return removalSentinel ?? EmptyStatement();
|
|
}
|
|
|
|
TreeNode visitDoStatement(DoStatement stmt, TreeNode? removalSentinel) {
|
|
Statement body = visitDelimited(stmt.body);
|
|
List<Statement> effects = <Statement>[];
|
|
stmt.condition = expressionRewriter!.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.
|
|
Block block = body is Block ? body : body = new Block(<Statement>[body]);
|
|
for (var effect in effects) {
|
|
block.statements.add(effect);
|
|
effect.parent = body;
|
|
}
|
|
}
|
|
stmt.body = body..parent = stmt;
|
|
statements.add(stmt);
|
|
return removalSentinel ?? EmptyStatement();
|
|
}
|
|
|
|
TreeNode visitForStatement(ForStatement stmt, TreeNode? removalSentinel) {
|
|
// 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 =
|
|
new List<List<Statement>>.generate(length, (int i) {
|
|
VariableDeclaration decl = stmt.variables[i];
|
|
List<Statement> statements = <Statement>[];
|
|
if (decl.initializer != null) {
|
|
decl.initializer = expressionRewriter!
|
|
.rewrite(decl.initializer!, statements)
|
|
..parent = decl;
|
|
}
|
|
isSimple = isSimple && statements.isEmpty;
|
|
return statements;
|
|
});
|
|
|
|
length = stmt.updates.length;
|
|
List<List<Statement>> updateEffects =
|
|
new List<List<Statement>>.generate(length, (int i) {
|
|
List<Statement> statements = <Statement>[];
|
|
stmt.updates[i] = expressionRewriter!.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 = expressionRewriter!.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;
|
|
statements.add(stmt);
|
|
} else {
|
|
LabeledStatement labeled = new LabeledStatement(stmt);
|
|
// No condition in a for loop is the same as true.
|
|
stmt.condition = null;
|
|
condEffects
|
|
.add(new IfStatement(cond!, body, new BreakStatement(labeled)));
|
|
stmt.body = new Block(condEffects)..parent = stmt;
|
|
statements.add(labeled);
|
|
}
|
|
return removalSentinel ?? EmptyStatement();
|
|
}
|
|
|
|
// 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'.
|
|
// TODO(kmillikin) bool type for first.
|
|
List<VariableDeclaration> temps = <VariableDeclaration>[
|
|
new VariableDeclaration.forValue(new BoolLiteral(true), isFinal: false)
|
|
];
|
|
List<Statement> loopBody = <Statement>[];
|
|
List<Statement> initializers = <Statement>[
|
|
new ExpressionStatement(
|
|
new VariableSet(temps.first, new BoolLiteral(false)))
|
|
];
|
|
List<Statement> updates = <Statement>[];
|
|
List<Statement> newBody = <Statement>[body];
|
|
for (int i = 0; i < stmt.variables.length; ++i) {
|
|
VariableDeclaration decl = stmt.variables[i];
|
|
temps.add(new VariableDeclaration(null, type: decl.type));
|
|
loopBody.add(decl);
|
|
if (decl.initializer != null) {
|
|
initializers.addAll(initEffects[i]);
|
|
initializers.add(
|
|
new ExpressionStatement(new VariableSet(decl, decl.initializer!)));
|
|
decl.initializer = null;
|
|
}
|
|
updates.add(new ExpressionStatement(
|
|
new VariableSet(decl, new VariableGet(temps.last))));
|
|
newBody.add(new ExpressionStatement(
|
|
new VariableSet(temps.last, new VariableGet(decl))));
|
|
}
|
|
// Add the updates to their guarded list of statements.
|
|
for (int i = 0; i < stmt.updates.length; ++i) {
|
|
updates.addAll(updateEffects[i]);
|
|
updates.add(new ExpressionStatement(stmt.updates[i]));
|
|
}
|
|
// Initializers or updates could be empty.
|
|
loopBody.add(new IfStatement(new VariableGet(temps.first),
|
|
new Block(initializers), new Block(updates)));
|
|
|
|
LabeledStatement labeled = new LabeledStatement(null);
|
|
if (cond != null) {
|
|
loopBody.addAll(condEffects!);
|
|
} else {
|
|
cond = new BoolLiteral(true);
|
|
}
|
|
loopBody.add(
|
|
new IfStatement(cond, new Block(newBody), new BreakStatement(labeled)));
|
|
labeled.body =
|
|
new WhileStatement(new BoolLiteral(true), new Block(loopBody))
|
|
..parent = labeled;
|
|
statements.add(new Block(<Statement>[]
|
|
..addAll(temps)
|
|
..add(labeled)));
|
|
return removalSentinel ?? EmptyStatement();
|
|
}
|
|
|
|
TreeNode visitForInStatement(ForInStatement stmt, TreeNode? removalSentinel) {
|
|
if (stmt.isAsync) {
|
|
// Transform
|
|
//
|
|
// await for (T variable in <stream-expression>) { ... }
|
|
//
|
|
// To (in product mode):
|
|
//
|
|
// {
|
|
// :stream = <stream-expression>;
|
|
// _StreamIterator<T> :for-iterator = new _StreamIterator<T>(:stream);
|
|
// try {
|
|
// while (await :for-iterator.moveNext()) {
|
|
// T <variable> = :for-iterator.current;
|
|
// ...
|
|
// }
|
|
// } finally {
|
|
// if (:for-iterator._subscription != null)
|
|
// await :for-iterator.cancel();
|
|
// }
|
|
// }
|
|
//
|
|
// Or (in non-product mode):
|
|
//
|
|
// {
|
|
// :stream = <stream-expression>;
|
|
// _StreamIterator<T> :for-iterator = new _StreamIterator<T>(:stream);
|
|
// try {
|
|
// while (let _ = _asyncStarMoveNextHelper(:stream) in
|
|
// await :for-iterator.moveNext()) {
|
|
// T <variable> = :for-iterator.current;
|
|
// ...
|
|
// }
|
|
// } finally {
|
|
// if (:for-iterator._subscription != null)
|
|
// await :for-iterator.cancel();
|
|
// }
|
|
// }
|
|
var valueVariable = stmt.variable;
|
|
|
|
var streamVariable = new VariableDeclaration(ContinuationVariables.stream,
|
|
initializer: stmt.iterable,
|
|
type: stmt.iterable.getStaticType(staticTypeContext));
|
|
|
|
final streamIteratorType = new InterfaceType(helper.streamIteratorClass,
|
|
staticTypeContext.nullable, [valueVariable.type]);
|
|
var forIteratorVariable = VariableDeclaration(
|
|
ContinuationVariables.forIterator,
|
|
initializer: new ConstructorInvocation(
|
|
helper.streamIteratorConstructor,
|
|
new Arguments(<Expression>[new VariableGet(streamVariable)],
|
|
types: [valueVariable.type])),
|
|
type: streamIteratorType);
|
|
|
|
// await :for-iterator.moveNext()
|
|
var condition = new AwaitExpression(new InstanceInvocation(
|
|
InstanceAccessKind.Instance,
|
|
VariableGet(forIteratorVariable),
|
|
helper.streamIteratorMoveNext.name,
|
|
new Arguments([]),
|
|
interfaceTarget: helper.streamIteratorMoveNext,
|
|
functionType:
|
|
helper.streamIteratorMoveNext.getterType as FunctionType))
|
|
..fileOffset = stmt.fileOffset;
|
|
|
|
Expression whileCondition;
|
|
if (helper.productMode) {
|
|
whileCondition = condition;
|
|
} else {
|
|
// _asyncStarMoveNextHelper(:stream)
|
|
var asyncStarMoveNextCall = new StaticInvocation(
|
|
helper.asyncStarMoveNextHelper,
|
|
new Arguments([new VariableGet(streamVariable)]))
|
|
..fileOffset = stmt.fileOffset;
|
|
|
|
// let _ = asyncStarMoveNextCall in (condition)
|
|
whileCondition = new Let(
|
|
new VariableDeclaration(null, initializer: asyncStarMoveNextCall),
|
|
condition);
|
|
}
|
|
|
|
// T <variable> = :for-iterator.current;
|
|
valueVariable.initializer = new InstanceGet(InstanceAccessKind.Instance,
|
|
VariableGet(forIteratorVariable), helper.streamIteratorCurrent.name,
|
|
interfaceTarget: helper.streamIteratorCurrent,
|
|
resultType: valueVariable.type)
|
|
..fileOffset = stmt.bodyOffset;
|
|
valueVariable.initializer!.parent = valueVariable;
|
|
|
|
var whileBody = new Block(<Statement>[valueVariable, stmt.body]);
|
|
var tryBody = new WhileStatement(whileCondition, whileBody);
|
|
|
|
// if (:for-iterator._subscription != null) await :for-iterator.cancel();
|
|
final DartType subscriptionType =
|
|
Substitution.fromInterfaceType(streamIteratorType).substituteType(
|
|
helper.coreTypes.streamIteratorSubscription.getterType);
|
|
var tryFinalizer = new IfStatement(
|
|
new Not(new EqualsNull(new InstanceGet(
|
|
InstanceAccessKind.Instance,
|
|
VariableGet(forIteratorVariable),
|
|
helper.coreTypes.streamIteratorSubscription.name,
|
|
interfaceTarget: helper.coreTypes.streamIteratorSubscription,
|
|
resultType: subscriptionType))),
|
|
new ExpressionStatement(new AwaitExpression(new InstanceInvocation(
|
|
InstanceAccessKind.Instance,
|
|
VariableGet(forIteratorVariable),
|
|
helper.streamIteratorCancel.name,
|
|
new Arguments(<Expression>[]),
|
|
interfaceTarget: helper.streamIteratorCancel,
|
|
functionType:
|
|
helper.streamIteratorCancel.getterType as FunctionType))),
|
|
null);
|
|
|
|
var tryFinally = new TryFinally(tryBody, tryFinalizer);
|
|
|
|
var block = new Block(
|
|
<Statement>[streamVariable, forIteratorVariable, tryFinally]);
|
|
transform<Statement>(block);
|
|
return removalSentinel ?? EmptyStatement();
|
|
} else {
|
|
super.visitForInStatement(stmt, removalSentinel);
|
|
return removalSentinel ?? EmptyStatement();
|
|
}
|
|
}
|
|
|
|
TreeNode visitSwitchStatement(
|
|
SwitchStatement stmt, TreeNode? removalSentinel) {
|
|
stmt.expression = expressionRewriter!.rewrite(stmt.expression, statements)
|
|
..parent = stmt;
|
|
for (var 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;
|
|
}
|
|
statements.add(stmt);
|
|
return removalSentinel ?? EmptyStatement();
|
|
}
|
|
|
|
TreeNode visitContinueSwitchStatement(
|
|
ContinueSwitchStatement stmt, TreeNode? removalSentinel) {
|
|
statements.add(stmt);
|
|
return removalSentinel ?? EmptyStatement();
|
|
}
|
|
|
|
TreeNode visitIfStatement(IfStatement stmt, TreeNode? removalSentinel) {
|
|
stmt.condition = expressionRewriter!.rewrite(stmt.condition, statements)
|
|
..parent = stmt;
|
|
stmt.then = visitDelimited(stmt.then)..parent = stmt;
|
|
if (stmt.otherwise != null) {
|
|
stmt.otherwise = visitDelimited(stmt.otherwise!)..parent = stmt;
|
|
}
|
|
statements.add(stmt);
|
|
return removalSentinel ?? EmptyStatement();
|
|
}
|
|
|
|
TreeNode visitTryCatch(TryCatch stmt, TreeNode? removalSentinel) {
|
|
++currentTryDepth;
|
|
stmt.body = visitDelimited(stmt.body)..parent = stmt;
|
|
--currentTryDepth;
|
|
|
|
++currentCatchDepth;
|
|
for (var clause in stmt.catches) {
|
|
clause.body = visitDelimited(clause.body)..parent = clause;
|
|
}
|
|
--currentCatchDepth;
|
|
statements.add(stmt);
|
|
return removalSentinel ?? EmptyStatement();
|
|
}
|
|
|
|
TreeNode visitTryFinally(TryFinally stmt, TreeNode? removalSentinel) {
|
|
++currentTryDepth;
|
|
stmt.body = visitDelimited(stmt.body)..parent = stmt;
|
|
--currentTryDepth;
|
|
++currentCatchDepth;
|
|
stmt.finalizer = visitDelimited(stmt.finalizer)..parent = stmt;
|
|
--currentCatchDepth;
|
|
statements.add(stmt);
|
|
return removalSentinel ?? EmptyStatement();
|
|
}
|
|
|
|
TreeNode visitYieldStatement(YieldStatement stmt, TreeNode? removalSentinel) {
|
|
stmt.expression = expressionRewriter!.rewrite(stmt.expression, statements)
|
|
..parent = stmt;
|
|
statements.add(stmt);
|
|
return removalSentinel ?? EmptyStatement();
|
|
}
|
|
|
|
TreeNode visitVariableDeclaration(
|
|
VariableDeclaration stmt, TreeNode? removalSentinel) {
|
|
if (stmt.initializer != null) {
|
|
stmt.initializer = expressionRewriter!
|
|
.rewrite(stmt.initializer!, statements)
|
|
..parent = stmt;
|
|
}
|
|
statements.add(stmt);
|
|
return removalSentinel ?? EmptyStatement();
|
|
}
|
|
|
|
TreeNode visitFunctionDeclaration(
|
|
FunctionDeclaration stmt, TreeNode? removalSentinel) {
|
|
stmt.function = transform(stmt.function)..parent = stmt;
|
|
statements.add(stmt);
|
|
return removalSentinel ?? EmptyStatement();
|
|
}
|
|
|
|
defaultExpression(TreeNode node, TreeNode? removalSentinel) =>
|
|
throw 'unreachable $node';
|
|
}
|
|
|
|
class AsyncStarFunctionRewriter extends AsyncRewriterBase {
|
|
VariableDeclaration? controllerVariable;
|
|
|
|
AsyncStarFunctionRewriter(HelperNodes helper, FunctionNode enclosingFunction,
|
|
StatefulStaticTypeContext staticTypeContext)
|
|
: super(helper, enclosingFunction, staticTypeContext);
|
|
|
|
FunctionNode rewrite() {
|
|
var statements = <Statement>[];
|
|
|
|
final elementType = elementTypeFromReturnType(helper.streamClass);
|
|
|
|
// _AsyncStarStreamController<T> :controller;
|
|
controllerVariable = new VariableDeclaration(
|
|
ContinuationVariables.controller,
|
|
type: new InterfaceType(helper.asyncStarStreamControllerClass,
|
|
staticTypeContext.nullable, [elementType]));
|
|
statements.add(controllerVariable!);
|
|
|
|
// dynamic :controller_stream;
|
|
VariableDeclaration controllerStreamVariable =
|
|
new VariableDeclaration(ContinuationVariables.controllerStreamVar);
|
|
statements.add(controllerStreamVariable);
|
|
|
|
setupAsyncContinuations(statements);
|
|
|
|
// :controller = new _AsyncStarStreamController<T>(:async_op);
|
|
var arguments = new Arguments(
|
|
<Expression>[new VariableGet(nestedClosureVariable)],
|
|
types: [elementType]);
|
|
var buildController = new ConstructorInvocation(
|
|
helper.asyncStarStreamControllerConstructor, arguments)
|
|
..fileOffset = enclosingFunction.fileOffset;
|
|
var setController = new ExpressionStatement(
|
|
new VariableSet(controllerVariable!, buildController));
|
|
statements.add(setController);
|
|
|
|
// :controller_stream = :controller.stream;
|
|
var completerGet = new VariableGet(controllerVariable!);
|
|
statements.add(new ExpressionStatement(new VariableSet(
|
|
controllerStreamVariable,
|
|
new InstanceGet(InstanceAccessKind.Instance, completerGet,
|
|
helper.asyncStarStreamControllerStream.name,
|
|
interfaceTarget: helper.asyncStarStreamControllerStream,
|
|
resultType: Substitution.fromInterfaceType(
|
|
controllerVariable!.type as InterfaceType)
|
|
.substituteType(
|
|
helper.asyncStarStreamControllerStream.getterType)))));
|
|
|
|
// return :controller_stream;
|
|
var returnStatement =
|
|
new ReturnStatement(new VariableGet(controllerStreamVariable));
|
|
statements.add(returnStatement);
|
|
|
|
enclosingFunction.body = new Block(statements)..parent = enclosingFunction;
|
|
enclosingFunction.asyncMarker = AsyncMarker.Sync;
|
|
return enclosingFunction;
|
|
}
|
|
|
|
Statement buildWrappedBody() {
|
|
++currentTryDepth;
|
|
Statement body = super.buildWrappedBody();
|
|
--currentTryDepth;
|
|
|
|
var finallyBody = new ExpressionStatement(new InstanceInvocation(
|
|
InstanceAccessKind.Instance,
|
|
new VariableGet(controllerVariable!),
|
|
helper.asyncStarStreamControllerClose.name,
|
|
new Arguments([]),
|
|
interfaceTarget: helper.asyncStarStreamControllerClose,
|
|
functionType:
|
|
helper.asyncStarStreamControllerClose.getterType as FunctionType));
|
|
|
|
var tryFinally = new TryFinally(body, new Block(<Statement>[finallyBody]));
|
|
return tryFinally;
|
|
}
|
|
|
|
Statement buildCatchBody(VariableDeclaration exceptionVariable,
|
|
VariableDeclaration stackTraceVariable) {
|
|
return new ExpressionStatement(new InstanceInvocation(
|
|
InstanceAccessKind.Instance,
|
|
new VariableGet(controllerVariable!),
|
|
helper.asyncStarStreamControllerAddError.name,
|
|
new Arguments(<Expression>[
|
|
new VariableGet(exceptionVariable),
|
|
new VariableGet(stackTraceVariable)
|
|
]),
|
|
interfaceTarget: helper.asyncStarStreamControllerAddError,
|
|
functionType: helper.asyncStarStreamControllerAddError.getterType
|
|
as FunctionType));
|
|
}
|
|
|
|
Statement buildReturn(Statement body) {
|
|
// Async* functions cannot return a value. The returns from the function
|
|
// have been translated into breaks from the labeled body.
|
|
return new Block(<Statement>[
|
|
body,
|
|
new ReturnStatement()..fileOffset = enclosingFunction.fileEndOffset,
|
|
]);
|
|
}
|
|
|
|
TreeNode visitYieldStatement(YieldStatement stmt, TreeNode? removalSentinel) {
|
|
Expression expr = expressionRewriter!.rewrite(stmt.expression, statements);
|
|
|
|
final Procedure addMethod = stmt.isYieldStar
|
|
? helper.asyncStarStreamControllerAddStream
|
|
: helper.asyncStarStreamControllerAdd;
|
|
final FunctionType addMethodFunctionType = Substitution.fromInterfaceType(
|
|
controllerVariable!.type as InterfaceType)
|
|
.substituteType(addMethod.getterType) as FunctionType;
|
|
var addExpression = new InstanceInvocation(
|
|
InstanceAccessKind.Instance,
|
|
new VariableGet(controllerVariable!),
|
|
addMethod.name,
|
|
new Arguments(<Expression>[expr]),
|
|
interfaceTarget: addMethod,
|
|
functionType: addMethodFunctionType)
|
|
..fileOffset = stmt.fileOffset;
|
|
|
|
statements.add(new IfStatement(
|
|
addExpression,
|
|
new ReturnStatement(new NullLiteral()),
|
|
createContinuationPoint()..fileOffset = stmt.fileOffset));
|
|
return removalSentinel ?? EmptyStatement();
|
|
}
|
|
|
|
TreeNode visitReturnStatement(
|
|
ReturnStatement node, TreeNode? removalSentinel) {
|
|
// Async* functions cannot return a value.
|
|
assert(node.expression == null || node.expression is NullLiteral);
|
|
statements
|
|
.add(new BreakStatement(labeledBody!)..fileOffset = node.fileOffset);
|
|
return removalSentinel ?? EmptyStatement();
|
|
}
|
|
}
|
|
|
|
class AsyncFunctionRewriter extends AsyncRewriterBase {
|
|
VariableDeclaration? returnVariable;
|
|
VariableDeclaration? asyncFutureVariable;
|
|
VariableDeclaration? isSyncVariable;
|
|
|
|
AsyncFunctionRewriter(HelperNodes helper, FunctionNode enclosingFunction,
|
|
StatefulStaticTypeContext staticTypeContext)
|
|
: super(helper, enclosingFunction, staticTypeContext);
|
|
|
|
FunctionNode rewrite() {
|
|
var statements = <Statement>[];
|
|
|
|
// The original function return type should be Future<T> or FutureOr<T>
|
|
// because the function is async. If it was, we make a Completer<T>,
|
|
// otherwise we make a malformed type. In a "Future<T> foo() async {}"
|
|
// function the body can either return a "T" or a "Future<T>" => a
|
|
// "FutureOr<T>".
|
|
DartType valueType = elementTypeFromReturnType(helper.futureClass);
|
|
if (valueType == const DynamicType()) {
|
|
valueType = elementTypeFromAsyncReturnType();
|
|
}
|
|
final DartType returnType =
|
|
FutureOrType(valueType, staticTypeContext.nullable);
|
|
final futureTypeArguments = <DartType>[valueType];
|
|
|
|
final futureType = InterfaceType(helper.futureImplClass,
|
|
staticTypeContext.nonNullable, futureTypeArguments);
|
|
|
|
// final _Future<T> :async_future = _Future<T>();
|
|
asyncFutureVariable = VariableDeclaration(ContinuationVariables.asyncFuture,
|
|
initializer: ConstructorInvocation(helper.futureImplConstructor,
|
|
Arguments([], types: futureTypeArguments))
|
|
..fileOffset = enclosingFunction.body?.fileOffset ?? -1,
|
|
isFinal: true,
|
|
type: futureType);
|
|
statements.add(asyncFutureVariable!);
|
|
|
|
// bool :is_sync = false;
|
|
isSyncVariable = VariableDeclaration(ContinuationVariables.isSync,
|
|
initializer: BoolLiteral(false),
|
|
type: helper.coreTypes.boolLegacyRawType);
|
|
statements.add(isSyncVariable!);
|
|
|
|
// asy::FutureOr<dynamic>* :return_value;
|
|
returnVariable = VariableDeclaration(ContinuationVariables.returnValue,
|
|
type: returnType);
|
|
statements.add(returnVariable!);
|
|
|
|
setupAsyncContinuations(statements);
|
|
|
|
// :async_op();
|
|
final startStatement = ExpressionStatement(LocalFunctionInvocation(
|
|
nestedClosureVariable, Arguments([]),
|
|
functionType: FunctionType(
|
|
[], const DynamicType(), staticTypeContext.nonNullable))
|
|
..fileOffset = enclosingFunction.fileOffset);
|
|
statements.add(startStatement);
|
|
|
|
// :is_sync = true;
|
|
final setIsSync =
|
|
ExpressionStatement(VariableSet(isSyncVariable!, BoolLiteral(true)));
|
|
statements.add(setIsSync);
|
|
|
|
// return :async_future;
|
|
statements.add(ReturnStatement(VariableGet(asyncFutureVariable!)));
|
|
|
|
enclosingFunction.body = Block(statements)..parent = enclosingFunction;
|
|
enclosingFunction.asyncMarker = AsyncMarker.Sync;
|
|
return enclosingFunction;
|
|
}
|
|
|
|
// :async_op's try-catch catch body:
|
|
Statement buildCatchBody(exceptionVariable, stackTraceVariable) {
|
|
// _completeOnAsyncError(_future, e, st, :is_sync)
|
|
return ExpressionStatement(StaticInvocation(
|
|
helper.completeOnAsyncError,
|
|
Arguments([
|
|
VariableGet(asyncFutureVariable!),
|
|
VariableGet(exceptionVariable),
|
|
VariableGet(stackTraceVariable),
|
|
VariableGet(isSyncVariable!)
|
|
])));
|
|
}
|
|
|
|
// :async_op's try-catch try body:
|
|
Statement buildReturn(Statement body) {
|
|
// Returns from the body have all been translated into assignments to the
|
|
// return value variable followed by a break from the labeled body.
|
|
|
|
// .. body ..
|
|
// _completeOnAsyncReturn(_future, returnVariable, :is_sync)
|
|
// return;
|
|
return Block(<Statement>[
|
|
body,
|
|
ExpressionStatement(StaticInvocation(
|
|
helper.completeOnAsyncReturn,
|
|
Arguments([
|
|
VariableGet(asyncFutureVariable!),
|
|
VariableGet(returnVariable!),
|
|
VariableGet(isSyncVariable!)
|
|
]))),
|
|
ReturnStatement()..fileOffset = enclosingFunction.fileEndOffset
|
|
]);
|
|
}
|
|
|
|
visitReturnStatement(ReturnStatement node, TreeNode? removalSentinel) {
|
|
var expr = node.expression == null
|
|
? new NullLiteral()
|
|
: expressionRewriter!.rewrite(node.expression!, statements);
|
|
statements.add(new ExpressionStatement(
|
|
new VariableSet(returnVariable!, expr)..fileOffset = node.fileOffset));
|
|
statements.add(new BreakStatement(labeledBody!));
|
|
return removalSentinel ?? EmptyStatement();
|
|
}
|
|
}
|
|
|
|
class HelperNodes {
|
|
final Procedure asyncErrorWrapper;
|
|
final Library asyncLibrary;
|
|
final Procedure asyncStarStreamControllerAdd;
|
|
final Procedure asyncStarStreamControllerAddError;
|
|
final Procedure asyncStarStreamControllerAddStream;
|
|
final Class asyncStarStreamControllerClass;
|
|
final Procedure asyncStarStreamControllerClose;
|
|
final Constructor asyncStarStreamControllerConstructor;
|
|
final Member asyncStarStreamControllerStream;
|
|
final Procedure asyncStarMoveNextHelper;
|
|
final Procedure asyncThenWrapper;
|
|
final Procedure awaitHelper;
|
|
final Procedure completeOnAsyncReturn;
|
|
final Procedure completeOnAsyncError;
|
|
final Library coreLibrary;
|
|
final CoreTypes coreTypes;
|
|
final Class futureClass;
|
|
final Class futureOrClass;
|
|
final Class futureImplClass;
|
|
final Constructor futureImplConstructor;
|
|
final Class iterableClass;
|
|
final Class streamClass;
|
|
final Procedure streamIteratorCancel;
|
|
final Class streamIteratorClass;
|
|
final Constructor streamIteratorConstructor;
|
|
final Member streamIteratorCurrent;
|
|
final Procedure streamIteratorMoveNext;
|
|
final Constructor syncIterableConstructor;
|
|
final Class syncIteratorClass;
|
|
final Member syncIteratorCurrent;
|
|
final Member syncIteratorYieldEachIterable;
|
|
final Class boolClass;
|
|
final Procedure unsafeCast;
|
|
|
|
bool productMode;
|
|
|
|
HelperNodes._(
|
|
this.asyncErrorWrapper,
|
|
this.asyncLibrary,
|
|
this.asyncStarStreamControllerAdd,
|
|
this.asyncStarStreamControllerAddError,
|
|
this.asyncStarStreamControllerAddStream,
|
|
this.asyncStarStreamControllerClass,
|
|
this.asyncStarStreamControllerClose,
|
|
this.asyncStarStreamControllerConstructor,
|
|
this.asyncStarStreamControllerStream,
|
|
this.asyncStarMoveNextHelper,
|
|
this.asyncThenWrapper,
|
|
this.awaitHelper,
|
|
this.completeOnAsyncReturn,
|
|
this.completeOnAsyncError,
|
|
this.coreLibrary,
|
|
this.coreTypes,
|
|
this.futureClass,
|
|
this.futureOrClass,
|
|
this.futureImplClass,
|
|
this.futureImplConstructor,
|
|
this.iterableClass,
|
|
this.streamClass,
|
|
this.streamIteratorCancel,
|
|
this.streamIteratorClass,
|
|
this.streamIteratorConstructor,
|
|
this.streamIteratorCurrent,
|
|
this.streamIteratorMoveNext,
|
|
this.syncIterableConstructor,
|
|
this.syncIteratorClass,
|
|
this.syncIteratorCurrent,
|
|
this.syncIteratorYieldEachIterable,
|
|
this.boolClass,
|
|
this.productMode,
|
|
this.unsafeCast);
|
|
|
|
factory HelperNodes.fromCoreTypes(CoreTypes coreTypes, bool productMode) {
|
|
return new HelperNodes._(
|
|
coreTypes.asyncErrorWrapperHelperProcedure,
|
|
coreTypes.asyncLibrary,
|
|
coreTypes.asyncStarStreamControllerAdd,
|
|
coreTypes.asyncStarStreamControllerAddError,
|
|
coreTypes.asyncStarStreamControllerAddStream,
|
|
coreTypes.asyncStarStreamControllerClass,
|
|
coreTypes.asyncStarStreamControllerClose,
|
|
coreTypes.asyncStarStreamControllerDefaultConstructor,
|
|
coreTypes.asyncStarStreamControllerStream,
|
|
coreTypes.asyncStarMoveNextHelper,
|
|
coreTypes.asyncThenWrapperHelperProcedure,
|
|
coreTypes.awaitHelperProcedure,
|
|
coreTypes.completeOnAsyncReturn,
|
|
coreTypes.completeOnAsyncError,
|
|
coreTypes.coreLibrary,
|
|
coreTypes,
|
|
coreTypes.futureClass,
|
|
coreTypes.deprecatedFutureOrClass,
|
|
coreTypes.futureImplClass,
|
|
coreTypes.futureImplConstructor,
|
|
coreTypes.iterableClass,
|
|
coreTypes.streamClass,
|
|
coreTypes.streamIteratorCancel,
|
|
coreTypes.streamIteratorClass,
|
|
coreTypes.streamIteratorDefaultConstructor,
|
|
coreTypes.streamIteratorCurrent,
|
|
coreTypes.streamIteratorMoveNext,
|
|
coreTypes.syncIterableDefaultConstructor,
|
|
coreTypes.syncIteratorClass,
|
|
coreTypes.syncIteratorCurrent,
|
|
coreTypes.syncIteratorYieldEachIterable,
|
|
coreTypes.boolClass,
|
|
productMode,
|
|
coreTypes.index.getTopLevelMember('dart:_internal', 'unsafeCast')
|
|
as Procedure);
|
|
}
|
|
}
|