blob: ad89d1fa5dc305bf2adf61ad07cde095837702b7 [file] [log] [blame]
// 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);
}
@override
TreeNode visitField(Field node, TreeNode? removalSentinel) {
staticTypeContext.enterMember(node);
final result = super.visitField(node, removalSentinel);
staticTypeContext.leaveMember(node);
return result;
}
@override
TreeNode visitConstructor(Constructor node, TreeNode? removalSentinel) {
staticTypeContext.enterMember(node);
final result = super.visitConstructor(node, removalSentinel);
staticTypeContext.leaveMember(node);
return result;
}
@override
TreeNode visitProcedure(Procedure node, TreeNode? removalSentinel) {
staticTypeContext.enterMember(node);
final result =
node.isAbstract ? node : super.visitProcedure(node, removalSentinel);
staticTypeContext.leaveMember(node);
return result;
}
@override
TreeNode visitLibrary(Library node, TreeNode? removalSentinel) {
staticTypeContext.enterLibrary(node);
Library result = super.visitLibrary(node, removalSentinel) as Library;
staticTypeContext.leaveLibrary(node);
return result;
}
@override
TreeNode 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])
..fileOffset = stmt.bodyOffset;
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);
}
@override
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;
}
@override
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)
..fileOffset = enclosingFunction.fileOffset;
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))
..fileOffset = enclosingFunction.fileOffset,
// 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
]);
}
@override
TreeNode 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);
}
@override
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>[];
@override
TreeNode visitExpressionStatement(
ExpressionStatement stmt, TreeNode? removalSentinel) {
stmt.expression = expressionRewriter!.rewrite(stmt.expression, statements)
..parent = stmt;
statements.add(stmt);
return removalSentinel ?? EmptyStatement();
}
@override
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();
}
@override
TreeNode visitEmptyStatement(EmptyStatement stmt, TreeNode? removalSentinel) {
statements.add(stmt);
return removalSentinel ?? EmptyStatement();
}
@override
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();
}
@override
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;
}
@override
TreeNode visitLabeledStatement(
LabeledStatement stmt, TreeNode? removalSentinel) {
stmt.body = visitDelimited(stmt.body)..parent = stmt;
statements.add(stmt);
return removalSentinel ?? EmptyStatement();
}
@override
TreeNode visitBreakStatement(BreakStatement stmt, TreeNode? removalSentinel) {
statements.add(stmt);
return removalSentinel ?? EmptyStatement();
}
@override
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();
}
@override
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();
}
@override
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();
}
@override
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();
}
}
@override
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();
}
@override
TreeNode visitContinueSwitchStatement(
ContinueSwitchStatement stmt, TreeNode? removalSentinel) {
statements.add(stmt);
return removalSentinel ?? EmptyStatement();
}
@override
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();
}
@override
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();
}
@override
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();
}
@override
TreeNode visitYieldStatement(YieldStatement stmt, TreeNode? removalSentinel) {
stmt.expression = expressionRewriter!.rewrite(stmt.expression, statements)
..parent = stmt;
statements.add(stmt);
return removalSentinel ?? EmptyStatement();
}
@override
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();
}
@override
TreeNode visitFunctionDeclaration(
FunctionDeclaration stmt, TreeNode? removalSentinel) {
stmt.function = transform(stmt.function)..parent = stmt;
statements.add(stmt);
return removalSentinel ?? EmptyStatement();
}
@override
TreeNode 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;
}
@override
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;
}
@override
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));
}
@override
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,
]);
}
@override
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();
}
@override
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:
@override
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:
@override
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
]);
}
@override
TreeNode 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);
}
}