blob: f9576ed033c6a756b4ab2c18da2f1c21cc699870 [file] [log] [blame]
// Copyright (c) 2022, the Dart project authors. Please see the AUTHORS file
// for details. All rights reserved. Use of this source code is governed by a
// BSD-style license that can be found in the LICENSE file.
import 'package:kernel/ast.dart';
import 'package:kernel/core_types.dart' show CoreTypes;
import 'package:kernel/type_algebra.dart' show Substitution;
import 'package:kernel/type_environment.dart' show StaticTypeContext;
class ForInVariables {
static const stream = ':stream';
static const forIterator = ':for-iterator';
static const syncForIterator = ':sync-for-iterator';
}
/// VM-specific desugaring of for-in loops.
class ForInLowering {
final CoreTypes coreTypes;
final bool productMode;
ForInLowering(this.coreTypes, {required this.productMode});
Statement transformForInStatement(ForInStatement stmt,
FunctionNode? enclosingFunction, StaticTypeContext staticTypeContext) {
if (stmt.isAsync) {
if (enclosingFunction == null ||
(enclosingFunction.asyncMarker != AsyncMarker.Async &&
enclosingFunction.asyncMarker != AsyncMarker.AsyncStar)) {
return stmt;
}
// 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();
// }
// }
final valueVariable = stmt.variable;
final streamVariable = new VariableDeclaration(ForInVariables.stream,
initializer: stmt.iterable,
type: stmt.iterable.getStaticType(staticTypeContext));
final streamIteratorType = new InterfaceType(
coreTypes.streamIteratorClass,
staticTypeContext.nullable,
[valueVariable.type]);
final forIteratorVariable = VariableDeclaration(
ForInVariables.forIterator,
initializer: new ConstructorInvocation(
coreTypes.streamIteratorDefaultConstructor,
new Arguments(<Expression>[new VariableGet(streamVariable)],
types: [valueVariable.type])),
type: streamIteratorType);
// await :for-iterator.moveNext()
final condition = new AwaitExpression(new InstanceInvocation(
InstanceAccessKind.Instance,
VariableGet(forIteratorVariable),
coreTypes.streamIteratorMoveNext.name,
new Arguments([]),
interfaceTarget: coreTypes.streamIteratorMoveNext,
functionType:
coreTypes.streamIteratorMoveNext.getterType as FunctionType))
..fileOffset = stmt.fileOffset;
Expression whileCondition;
if (productMode) {
whileCondition = condition;
} else {
// _asyncStarMoveNextHelper(:stream)
final asyncStarMoveNextCall = new StaticInvocation(
coreTypes.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),
coreTypes.streamIteratorCurrent.name,
interfaceTarget: coreTypes.streamIteratorCurrent,
resultType: valueVariable.type)
..fileOffset = stmt.bodyOffset;
valueVariable.initializer!.parent = valueVariable;
final whileBody = new Block(<Statement>[valueVariable, stmt.body]);
final tryBody = new WhileStatement(whileCondition, whileBody);
// if (:for-iterator._subscription != null) await :for-iterator.cancel();
final DartType subscriptionType =
Substitution.fromInterfaceType(streamIteratorType)
.substituteType(coreTypes.streamIteratorSubscription.getterType);
final tryFinalizer = new IfStatement(
new Not(new EqualsNull(new InstanceGet(
InstanceAccessKind.Instance,
VariableGet(forIteratorVariable),
coreTypes.streamIteratorSubscription.name,
interfaceTarget: coreTypes.streamIteratorSubscription,
resultType: subscriptionType))),
new ExpressionStatement(new AwaitExpression(new InstanceInvocation(
InstanceAccessKind.Instance,
VariableGet(forIteratorVariable),
coreTypes.streamIteratorCancel.name,
new Arguments(<Expression>[]),
interfaceTarget: coreTypes.streamIteratorCancel,
functionType:
coreTypes.streamIteratorCancel.getterType as FunctionType))),
null);
final tryFinally = new TryFinally(tryBody, tryFinalizer);
final block = new Block(
<Statement>[streamVariable, forIteratorVariable, tryFinally]);
return block;
}
// 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;
// ...
// }
// }
// }
// 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(ForInVariables.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);
variable.initializer!.parent = variable;
final Block body = Block([variable, stmt.body])
..fileOffset = stmt.bodyOffset;
return Block([syncForIterator, ForStatement([], condition, [], body)]);
}
}