| // 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/class_hierarchy.dart'; |
| import 'package:kernel/core_types.dart'; |
| import 'package:kernel/target/targets.dart'; |
| import 'package:kernel/type_environment.dart'; |
| import 'package:kernel/type_algebra.dart'; |
| |
| void transformLibraries(List<Library> libraries, CoreTypes coreTypes, |
| ClassHierarchy hierarchy, DiagnosticReporter diagnosticReporter) { |
| final transformer = |
| _WasmTransformer(coreTypes, hierarchy, diagnosticReporter); |
| libraries.forEach(transformer.visitLibrary); |
| } |
| |
| void transformProcedure( |
| Procedure procedure, CoreTypes coreTypes, ClassHierarchy hierarchy) { |
| final transformer = _WasmTransformer(coreTypes, hierarchy, null); |
| procedure.accept(transformer); |
| } |
| |
| class _WasmTransformer extends Transformer { |
| final TypeEnvironment env; |
| final DiagnosticReporter? diagnosticReporter; |
| |
| Member? _currentMember; |
| StaticTypeContext? _cachedTypeContext; |
| final Library _coreLibrary; |
| final InterfaceType _nonNullableTypeType; |
| final List<_AsyncStarFrame> _asyncStarFrames = []; |
| bool _enclosingIsAsyncStar = false; |
| late final controllerNullableObjectType = InterfaceType( |
| coreTypes.index.getClass('dart:async', 'StreamController'), |
| Nullability.nonNullable, |
| [coreTypes.objectNullableRawType]); |
| late final completerBoolType = InterfaceType( |
| coreTypes.index.getClass('dart:async', 'Completer'), |
| Nullability.nonNullable, |
| [coreTypes.boolNonNullableRawType]); |
| |
| StaticTypeContext get typeContext => |
| _cachedTypeContext ??= StaticTypeContext(_currentMember!, env); |
| |
| CoreTypes get coreTypes => env.coreTypes; |
| |
| _WasmTransformer( |
| CoreTypes coreTypes, ClassHierarchy hierarchy, this.diagnosticReporter) |
| : env = TypeEnvironment(coreTypes, hierarchy), |
| _nonNullableTypeType = coreTypes.index |
| .getClass('dart:core', '_Type') |
| .getThisType(coreTypes, Nullability.nonNullable), |
| _coreLibrary = coreTypes.index.getLibrary('dart:core'); |
| |
| @override |
| defaultMember(Member node) { |
| _currentMember = node; |
| _cachedTypeContext = null; |
| |
| final result = super.defaultMember(node); |
| |
| _currentMember = null; |
| _cachedTypeContext = null; |
| return result; |
| } |
| |
| /// Checks to see if it is safe to reuse `super._typeArguments`. |
| bool canReuseSuperMethod(Class cls) { |
| // We search for the first non-abstract super in [cls]'s inheritance chain |
| // to see if we can reuse its `_typeArguments` method. |
| Class classIter = cls; |
| late Supertype supertype; |
| while (classIter.supertype != null) { |
| Supertype supertypeIter = classIter.supertype!; |
| Class superclass = supertypeIter.classNode; |
| if (!superclass.isAbstract) { |
| supertype = supertypeIter; |
| break; |
| } |
| classIter = classIter.supertype!.classNode; |
| } |
| |
| // We can reuse a superclass' `_typeArguments` method if the subclass and |
| // the superclass have the exact same type parameters in the exact same |
| // order. |
| if (cls.typeParameters.length != supertype.typeArguments.length) { |
| return false; |
| } |
| for (int i = 0; i < cls.typeParameters.length; i++) { |
| TypeParameter parameter = cls.typeParameters[i]; |
| DartType arg = supertype.typeArguments[i]; |
| if (arg is! TypeParameterType || arg.parameter != parameter) { |
| return false; |
| } |
| } |
| return true; |
| } |
| |
| @override |
| TreeNode visitClass(Class cls) { |
| // For every concrete class whose type parameters do not match the type |
| // parameters of it's super class we embed a special virtual function |
| // `_getTypeArguments`. When generating code for `_getTypeArguments`, we |
| // read the `TypeParameter`s off the instantiated object and generate a |
| // `List<Type>` to pass to `_getRuntimeType` which then returns a reified |
| // `Type` object. |
| if (!cls.isAbstract && |
| cls != coreTypes.objectClass && |
| !canReuseSuperMethod(cls)) { |
| Procedure getTypeArguments = Procedure( |
| Name("_typeArguments", _coreLibrary), |
| ProcedureKind.Getter, |
| FunctionNode( |
| null, |
| returnType: InterfaceType(coreTypes.listClass, |
| Nullability.nonNullable, [_nonNullableTypeType]), |
| ), |
| isExternal: true, |
| fileUri: cls.fileUri) |
| ..isNonNullableByDefault = true; |
| cls.addProcedure(getTypeArguments); |
| } |
| return super.visitClass(cls); |
| } |
| |
| TreeNode _lowerForIn(ForInStatement stmt) { |
| // Transform |
| // |
| // for ({var/final} T <variable> in <iterable>) { ... } |
| // |
| // Into |
| // |
| // { |
| // final Iterator<T> #forIterator = <iterable>.iterator; |
| // for (; #forIterator.moveNext() ;) { |
| // {var/final} T variable = #forIterator.current; |
| // ... |
| // } |
| // } |
| // } |
| // |
| // and: |
| // |
| // await for ({var/final} T <variable> in <stream>) { ... } |
| // |
| // Into |
| // |
| // { |
| // final StreamIterator<T> #forIterator = StreamIterator(<stream>); |
| // bool #jumpSentinel = false; |
| // try { |
| // for (; jumpSentinel = await #forIterator.moveNext() ;) { |
| // {var/final} T variable = #forIterator.current; |
| // ... |
| // } |
| // } finally { |
| // if (#jumpSentinel) { |
| // await #forIterator.cancel(); |
| // } |
| // } |
| // } |
| |
| // 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(typeContext); |
| if (iterableType is InvalidType) { |
| return ExpressionStatement( |
| InvalidExpression('Invalid iterable type in for-in')); |
| } |
| |
| final isAsync = stmt.isAsync; |
| late final Class iteratorClass; |
| late final Procedure iteratorMoveNext; |
| late final Member iteratorCurrent; |
| if (isAsync) { |
| iteratorClass = coreTypes.streamIteratorClass; |
| iteratorMoveNext = coreTypes.streamIteratorMoveNext; |
| iteratorCurrent = coreTypes.streamIteratorCurrent; |
| } else { |
| iteratorClass = coreTypes.iteratorClass; |
| iteratorMoveNext = coreTypes.iteratorMoveNext; |
| iteratorCurrent = coreTypes.iteratorGetCurrent; |
| } |
| |
| final DartType elementType = stmt.getElementType(typeContext); |
| final iteratorType = |
| InterfaceType(iteratorClass, Nullability.nonNullable, [elementType]); |
| |
| late final Expression iteratorInitializer; |
| if (isAsync) { |
| iteratorInitializer = ConstructorInvocation( |
| coreTypes.streamIteratorDefaultConstructor, |
| Arguments([iterable], types: [elementType])); |
| } else { |
| iteratorInitializer = InstanceGet( |
| InstanceAccessKind.Instance, iterable, Name('iterator'), |
| interfaceTarget: coreTypes.iterableGetIterator, |
| resultType: iteratorType); |
| } |
| |
| final iterator = VariableDeclaration("#forIterator", |
| initializer: iteratorInitializer..fileOffset = iterable.fileOffset, |
| type: iteratorType) |
| ..fileOffset = iterable.fileOffset; |
| |
| // Only used when `isAsync` is true. |
| final jumpSentinel = VariableDeclaration("#jumpSentinel", |
| initializer: ConstantExpression(BoolConstant(false)), |
| type: InterfaceType(coreTypes.boolClass, Nullability.nonNullable)); |
| |
| final condition = InstanceInvocation(InstanceAccessKind.Instance, |
| VariableGet(iterator), Name('moveNext'), Arguments(const []), |
| interfaceTarget: iteratorMoveNext, |
| functionType: iteratorMoveNext.getterType as FunctionType) |
| ..fileOffset = iterable.fileOffset; |
| |
| final variable = stmt.variable |
| ..initializer = (InstanceGet( |
| InstanceAccessKind.Instance, VariableGet(iterator), Name('current'), |
| interfaceTarget: iteratorCurrent, resultType: elementType) |
| ..fileOffset = stmt.bodyOffset); |
| |
| Block body = Block([variable, stmt.body])..fileOffset = stmt.fileOffset; |
| |
| Statement forStatement = ForStatement( |
| const [], |
| isAsync |
| ? VariableSet(jumpSentinel, AwaitExpression(condition)) |
| : condition, |
| const [], |
| body); |
| |
| // Wrap the body with a try / finally to cancel the stream on breaking out |
| // of the loop. |
| if (isAsync) { |
| forStatement = TryFinally( |
| Block([forStatement]), |
| Block([ |
| IfStatement( |
| VariableGet(jumpSentinel), |
| ExpressionStatement(AwaitExpression(InstanceInvocation( |
| InstanceAccessKind.Instance, |
| VariableGet(iterator), |
| Name('cancel'), |
| Arguments(const []), |
| interfaceTarget: coreTypes.streamIteratorCancel, |
| functionType: coreTypes.streamIteratorCancel.getterType |
| as FunctionType))), |
| null) |
| ]), |
| ); |
| } |
| |
| return Block([iterator, if (isAsync) jumpSentinel, forStatement]) |
| .accept<TreeNode>(this); |
| } |
| |
| @override |
| TreeNode visitForInStatement(ForInStatement stmt) { |
| return _lowerForIn(stmt); |
| } |
| |
| StaticInvocation _completerBoolInitializer() => StaticInvocation( |
| coreTypes.index.getProcedure('dart:async', 'Completer', ''), |
| Arguments([], types: [coreTypes.boolNonNullableRawType])); |
| |
| InstanceInvocation _addToController( |
| VariableDeclaration controller, Expression expression, int fileOffset) { |
| Procedure controllerAdd = |
| coreTypes.index.getProcedure('dart:async', 'StreamController', 'add'); |
| FunctionType controllerAddType = |
| Substitution.fromInterfaceType(controllerNullableObjectType) |
| .substituteType(controllerAdd.function |
| .computeThisFunctionType(Nullability.nonNullable)) |
| as FunctionType; |
| return InstanceInvocation(InstanceAccessKind.Instance, |
| VariableGet(controller), Name('add'), Arguments([expression]), |
| interfaceTarget: controllerAdd, functionType: controllerAddType) |
| ..fileOffset = fileOffset; |
| } |
| |
| InstanceInvocation _addCompleterToController(VariableDeclaration controller, |
| VariableDeclaration completer, int fileOffset) => |
| _addToController(controller, VariableGet(completer), fileOffset); |
| |
| AwaitExpression _awaitCompleterFuture( |
| VariableDeclaration completer, int fileOffset) { |
| Procedure completerFuture = |
| coreTypes.index.getProcedure('dart:async', 'Completer', 'get:future'); |
| FunctionType completerFutureType = |
| Substitution.fromInterfaceType(completerBoolType).substituteType( |
| completerFuture.function |
| .computeThisFunctionType(Nullability.nonNullable)) |
| as FunctionType; |
| return AwaitExpression(InstanceGet( |
| InstanceAccessKind.Instance, VariableGet(completer), Name('future'), |
| interfaceTarget: completerFuture, resultType: completerFutureType) |
| ..fileOffset = fileOffset); |
| } |
| |
| TreeNode _lowerAsyncStar(FunctionNode functionNode) { |
| // TODO(joshualitt): This lowering is mostly reasonable, but if possible we |
| // should try and figure out a way to remove the even / odd dance. That |
| // said, this will be replaced by an intrinsic implementation ASAP so it may |
| // not be worth spending anymore time on this(aside from bug fixes). |
| // |
| // Transform |
| // |
| // Stream<T> foo() async* { |
| // ... |
| // yield i; |
| // ... |
| // yield* bar; |
| // ... |
| // } |
| // |
| // Into |
| // |
| // Stream<T> foo() { |
| // StreamController<Object?> #controller = StreamController<Object?>(); |
| // Future<void> Function() #body = () async { |
| // Completer<bool> #completer = Completer<bool>(); |
| // #controller.add(#completer); |
| // await #completer.future; |
| // ... |
| // #controller.add(i); |
| // #completer = Completer<bool>(); |
| // #controller.add(#completer) |
| // await #completer.future; |
| // ... |
| // await for (var i in bar) { |
| // #controller.add(i); |
| // #completer = Completer<bool>(); |
| // #controller.add(#completer) |
| // await #completer.future; |
| // } |
| // ... |
| // #controller.close(); |
| // }; |
| // bool isEven = false; |
| // bool isFirst = true; |
| // #controller.add(null); |
| // return #controller.stream.asyncMap((value) async { |
| // if (isFirst) { |
| // #body(); |
| // return null; |
| // } |
| // if (value is Completer<Bool>) { |
| // value.complete(true); |
| // } |
| // return value; |
| // }).where((value) { |
| // if (isFirst) { |
| // isFirst = false; |
| // return false; |
| // } |
| // bool keep = isEven; |
| // isEven = !isEven; |
| // return keep; |
| // }).cast<T>(); |
| // } |
| int fileOffset = functionNode.fileOffset; |
| |
| // Initialize `#controller`. |
| final controllerInitializer = StaticInvocation( |
| coreTypes.index.getProcedure('dart:async', 'StreamController', ''), |
| Arguments([], types: [coreTypes.objectNullableRawType])); |
| final controller = VariableDeclaration('#controller', |
| initializer: controllerInitializer..fileOffset = fileOffset, |
| type: controllerNullableObjectType) |
| ..fileOffset = fileOffset; |
| |
| // Initialize `#completer`. |
| final completer = VariableDeclaration('#completer', |
| initializer: _completerBoolInitializer()..fileOffset = fileOffset, |
| type: completerBoolType) |
| ..fileOffset = fileOffset; |
| |
| // Close `#controller`. |
| Procedure controllerCloseProc = |
| coreTypes.index.getProcedure('dart:async', 'StreamController', 'close'); |
| FunctionType controllerCloseType = |
| Substitution.fromInterfaceType(controllerNullableObjectType) |
| .substituteType(controllerCloseProc.function |
| .computeThisFunctionType(Nullability.nonNullable)) |
| as FunctionType; |
| final callControllerClose = InstanceInvocation(InstanceAccessKind.Instance, |
| VariableGet(controller), Name('close'), Arguments([]), |
| interfaceTarget: controllerCloseProc, |
| functionType: controllerCloseType); |
| |
| // Create a frame so yield statements within the body can access the right |
| // controller / completer. |
| _asyncStarFrames.add(_AsyncStarFrame(controller, completer)); |
| |
| // Visit the body to transform any yields. We will re-visit after |
| // transformation just to ensure everything we've added will also be |
| // lowered. |
| Statement? transformedBody = |
| functionNode.body?.accept<TreeNode>(this) as Statement?; |
| _asyncStarFrames.removeLast(); |
| |
| // Locally declare body function. |
| final bodyFunction = FunctionNode( |
| Block([ |
| completer, |
| ExpressionStatement( |
| _addCompleterToController(controller, completer, fileOffset)), |
| ExpressionStatement(_awaitCompleterFuture(completer, fileOffset)), |
| if (transformedBody != null) transformedBody, |
| ExpressionStatement(callControllerClose), |
| ]), |
| futureValueType: const VoidType(), |
| returnType: InterfaceType( |
| coreTypes.futureClass, Nullability.nonNullable, [const VoidType()]), |
| asyncMarker: AsyncMarker.Async, |
| dartAsyncMarker: AsyncMarker.Async); |
| final bodyInitializer = FunctionExpression(bodyFunction); |
| FunctionType bodyFunctionType = |
| bodyFunction.computeThisFunctionType(Nullability.nonNullable); |
| final body = VariableDeclaration('#body', |
| initializer: bodyInitializer..fileOffset = fileOffset, |
| type: bodyFunctionType) |
| ..fileOffset = fileOffset; |
| |
| // Invoke body. |
| final invokeBody = FunctionInvocation( |
| FunctionAccessKind.FunctionType, VariableGet(body), Arguments([]), |
| functionType: bodyFunctionType); |
| |
| // Create a 'counting' sentinel to let us know which values to filter. |
| final isEven = VariableDeclaration('#isEven', |
| initializer: ConstantExpression(BoolConstant(false)) |
| ..fileOffset = fileOffset, |
| type: coreTypes.boolNonNullableRawType) |
| ..fileOffset = fileOffset; |
| final isFirst = VariableDeclaration('#isFirst', |
| initializer: ConstantExpression(BoolConstant(true)) |
| ..fileOffset = fileOffset, |
| type: coreTypes.boolNonNullableRawType) |
| ..fileOffset = fileOffset; |
| |
| // Get `controller.stream` |
| Procedure controllerStream = coreTypes.index |
| .getProcedure('dart:async', 'StreamController', 'get:stream'); |
| FunctionType controllerStreamType = |
| Substitution.fromInterfaceType(controllerNullableObjectType) |
| .substituteType(controllerStream.function |
| .computeThisFunctionType(Nullability.nonNullable)) |
| as FunctionType; |
| final getControllerStream = InstanceGet( |
| InstanceAccessKind.Instance, VariableGet(controller), Name('stream'), |
| interfaceTarget: controllerStream, resultType: controllerStreamType); |
| |
| // Prepare `completerPrePass` to issue a round of completions to our hidden |
| // completers. |
| Procedure completerComplete = |
| coreTypes.index.getProcedure('dart:async', 'Completer', 'complete'); |
| FunctionType completerCompleteType = |
| Substitution.fromInterfaceType(completerBoolType).substituteType( |
| completerComplete.function |
| .computeThisFunctionType(Nullability.nonNullable)) |
| as FunctionType; |
| final completerPrePassArg = |
| VariableDeclaration('value', type: coreTypes.objectNullableRawType); |
| final completerPrePass = FunctionExpression(FunctionNode( |
| Block([ |
| IfStatement( |
| VariableGet(isFirst), |
| Block([ |
| ExpressionStatement(invokeBody), |
| ReturnStatement(ConstantExpression(NullConstant())), |
| ]), |
| null), |
| IfStatement( |
| IsExpression(VariableGet(completerPrePassArg), completerBoolType), |
| ExpressionStatement(InstanceInvocation( |
| InstanceAccessKind.Instance, |
| VariableGet(completerPrePassArg), |
| Name('complete'), |
| Arguments([ConstantExpression(BoolConstant(true))]), |
| interfaceTarget: completerComplete, |
| functionType: completerCompleteType)), |
| null), |
| ReturnStatement(VariableGet(completerPrePassArg)), |
| ]), |
| positionalParameters: [completerPrePassArg], |
| returnType: FutureOrType( |
| coreTypes.objectNullableRawType, Nullability.nonNullable), |
| asyncMarker: AsyncMarker.Async, |
| dartAsyncMarker: AsyncMarker.Async, |
| futureValueType: coreTypes.objectNullableRawType, |
| )); |
| |
| // Call `asyncMap`. |
| Procedure asyncMap = |
| coreTypes.index.getProcedure('dart:async', 'Stream', 'asyncMap'); |
| final streamType = InterfaceType(coreTypes.streamClass, |
| Nullability.nonNullable, [coreTypes.objectNullableRawType]); |
| final asyncMapType = FunctionType([ |
| FunctionType([ |
| coreTypes.objectNullableRawType |
| ], FutureOrType(coreTypes.objectNullableRawType, Nullability.nonNullable), |
| Nullability.nonNullable, requiredParameterCount: 1) |
| ], streamType, Nullability.nonNullable, requiredParameterCount: 1); |
| final callAsyncMap = InstanceInvocation( |
| InstanceAccessKind.Instance, |
| getControllerStream, |
| Name('asyncMap'), |
| Arguments([completerPrePass], types: [coreTypes.objectNullableRawType]), |
| interfaceTarget: asyncMap, |
| functionType: asyncMapType); |
| |
| // Call `where`. |
| final whereFilterArg = |
| VariableDeclaration('value', type: coreTypes.objectNullableRawType); |
| final whereKeep = VariableDeclaration('keep', |
| initializer: VariableGet(isEven), |
| type: coreTypes.boolNonNullableRawType); |
| |
| final whereFilter = FunctionExpression(FunctionNode( |
| Block([ |
| IfStatement( |
| VariableGet(isFirst), |
| Block([ |
| ExpressionStatement(VariableSet( |
| isFirst, ConstantExpression(BoolConstant(false)))), |
| ReturnStatement(ConstantExpression(BoolConstant(false))) |
| ]), |
| null), |
| whereKeep, |
| ExpressionStatement(VariableSet(isEven, Not(VariableGet(isEven)))), |
| ReturnStatement(VariableGet(whereKeep)), |
| ]), |
| positionalParameters: [whereFilterArg], |
| returnType: coreTypes.objectNullableRawType)); |
| |
| Procedure whereProc = |
| coreTypes.index.getProcedure('dart:async', 'Stream', 'where'); |
| FunctionType whereProcType = Substitution.fromInterfaceType(streamType) |
| .substituteType(whereProc.function |
| .computeThisFunctionType(Nullability.nonNullable)) as FunctionType; |
| final callWhere = InstanceInvocation(InstanceAccessKind.Instance, |
| callAsyncMap, Name('where'), Arguments([whereFilter]), |
| interfaceTarget: whereProc, functionType: whereProcType); |
| |
| // Finally call cast |
| DartType typeArgument; |
| if (functionNode.returnType is InterfaceType) { |
| typeArgument = |
| (functionNode.returnType as InterfaceType).typeArguments.single; |
| } else { |
| typeArgument = const DynamicType(); |
| } |
| Procedure castProc = |
| coreTypes.index.getProcedure('dart:async', 'Stream', 'cast'); |
| final returnStreamType = InterfaceType( |
| coreTypes.streamClass, typeArgument.nullability, [typeArgument]); |
| final castProcType = FunctionType( |
| [], returnStreamType, Nullability.nonNullable, |
| requiredParameterCount: 1); |
| final castToExpectedType = InstanceInvocation(InstanceAccessKind.Instance, |
| callWhere, Name('cast'), Arguments([], types: [typeArgument]), |
| interfaceTarget: castProc, functionType: castProcType); |
| return FunctionNode( |
| Block([ |
| controller, |
| body, |
| isFirst, |
| isEven, |
| ExpressionStatement(_addToController( |
| controller, ConstantExpression(NullConstant()), fileOffset)), |
| ReturnStatement(castToExpectedType), |
| ]), |
| typeParameters: functionNode.typeParameters, |
| positionalParameters: functionNode.positionalParameters, |
| namedParameters: functionNode.namedParameters, |
| requiredParameterCount: functionNode.requiredParameterCount, |
| returnType: functionNode.returnType, |
| asyncMarker: AsyncMarker.Sync, |
| dartAsyncMarker: AsyncMarker.Sync); |
| } |
| |
| @override |
| TreeNode visitYieldStatement(YieldStatement yield) { |
| // We currently ignore yields in 'sync*'. |
| if (!_enclosingIsAsyncStar) { |
| return super.visitYieldStatement(yield); |
| } |
| int fileOffset = yield.fileOffset; |
| _AsyncStarFrame frame = _asyncStarFrames.last; |
| VariableDeclaration controller = frame.controller; |
| VariableDeclaration completer = frame.completer; |
| bool isYieldStar = yield.isYieldStar; |
| |
| // If [isYieldStar] then we need to create an `await for` loop to wrap the |
| // yields. |
| DartType yieldExpressionType = yield.expression.getStaticType(typeContext); |
| VariableDeclaration? awaitForVar; |
| if (isYieldStar) { |
| DartType awaitVarType = const DynamicType(); |
| if (yieldExpressionType is InterfaceType) { |
| Class cls = yieldExpressionType.className.asClass; |
| if (cls == coreTypes.streamClass) { |
| awaitVarType = yieldExpressionType.typeArguments.single; |
| } |
| } |
| awaitForVar = VariableDeclaration('#awaitForVar', type: awaitVarType) |
| ..fileOffset = fileOffset; |
| } |
| |
| final yieldBody = Block([ |
| ExpressionStatement(_addToController( |
| controller, |
| isYieldStar ? VariableGet(awaitForVar!) : yield.expression, |
| fileOffset)), |
| ExpressionStatement(VariableSet(completer, _completerBoolInitializer())), |
| ExpressionStatement( |
| _addCompleterToController(controller, completer, fileOffset)), |
| ExpressionStatement(_awaitCompleterFuture(completer, fileOffset)), |
| ]); |
| if (isYieldStar) { |
| // If this is a yield* then wrap the yield in an `await for`. |
| ForInStatement awaitForIn = ForInStatement( |
| awaitForVar!, yield.expression, yieldBody, |
| isAsync: true); |
| return awaitForIn.accept<TreeNode>(this); |
| } else { |
| return yieldBody.accept<TreeNode>(this); |
| } |
| } |
| |
| @override |
| TreeNode visitFunctionNode(FunctionNode functionNode) { |
| if (functionNode.dartAsyncMarker == AsyncMarker.AsyncStar) { |
| _enclosingIsAsyncStar = true; |
| functionNode = _lowerAsyncStar(functionNode) as FunctionNode; |
| _enclosingIsAsyncStar = false; |
| return super.visitFunctionNode(functionNode); |
| } else { |
| bool previousEnclosing = _enclosingIsAsyncStar; |
| TreeNode result = super.visitFunctionNode(functionNode); |
| _enclosingIsAsyncStar = previousEnclosing; |
| return result; |
| } |
| } |
| } |
| |
| class _AsyncStarFrame { |
| final VariableDeclaration controller; |
| final VariableDeclaration completer; |
| |
| _AsyncStarFrame(this.controller, this.completer); |
| } |