// Copyright (c) 2012, 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. // Patch file for the dart:async library. import 'dart:_async_status_codes' as async_status_codes; import 'dart:_js_helper' show notNull, ReifyFunctionTypes; import 'dart:_internal' show patch, unsafeCast; import 'dart:_isolate_helper' show TimerImpl; import 'dart:_foreign_helper' show JS, JS_RAW_EXCEPTION, JSExportName, RAW_DART_FUNCTION_REF; import 'dart:_runtime' as dart; @patch void _trySetStackTrace(Object error, StackTrace stackTrace) { if (error is Error) { dart.trySetStackTrace(error, stackTrace); } } /// This function adapts ES6 generators to implement Dart's async/await. /// /// It's designed to interact with Dart's Future and follow Dart async/await /// semantics. /// /// See https://github.com/dart-lang/sdk/issues/27315 for ideas on reconciling /// Dart's Future and ES6 Promise. At that point we should use native JS /// async/await. /// /// Inspired by `co`: https://github.com/tj/co/blob/master/index.js, which is a /// stepping stone for ES async/await. @JSExportName('async') _async(Function() initGenerator) { var iter; late Object? Function(Object?) onValue; late Object Function(Object, StackTrace?) onError; onAwait(Object? value) { _Future f; if (value is _Future) { f = value; } else if (value is Future) { f = _Future(); f._chainForeignFuture(value); } else { f = _Future.value(value); } f = JS('', '#', f._thenAwait(onValue, onError)); return f; } onValue = (value) { var iteratorResult = JS('', '#.next(#)', iter, value); value = JS('', '#.value', iteratorResult); return JS('!', '#.done', iteratorResult) ? value : onAwait(value); }; // If the awaited Future throws, we want to convert this to an exception // thrown from the `yield` point, as if it was thrown there. // // If the exception is not caught inside `gen`, it will emerge here, which // will send it to anyone listening on this async function's Future. // // In essence, we are giving the code inside the generator a chance to // use try-catch-finally. onError = (value, stackTrace) { var iteratorResult = JS( '', '#.throw(#)', iter, dart.createErrorWithStack(value, stackTrace), ); value = JS('', '#.value', iteratorResult); return JS('!', '#.done', iteratorResult) ? value : onAwait(value); }; var zone = Zone.current; if (!identical(zone, _rootZone)) { onValue = zone.registerUnaryCallback(onValue); onError = zone.registerBinaryCallback(onError); } var asyncFuture = _Future(); // This will be set to true once we've yielded to the event loop. // // Before we've done that, we need to complete the future asynchronously to // match dart2js/VM. See https://github.com/dart-lang/sdk/issues/33330 // // Once we've yielded to the event loop we can complete synchronously. // Other implementations call this `isSync` to indicate that. bool isRunningAsEvent = false; runBody() { try { iter = JS('', '#[Symbol.iterator]()', initGenerator()); var iteratorValue = JS('', '#.next(null)', iter); var value = JS('', '#.value', iteratorValue); if (JS('!', '#.done', iteratorValue)) { // TODO(jmesserly): this is a workaround for ignored cast failures. // Remove it once we've fixed those. We should be able to call: // // if (isRunningAsEvent) { // asyncFuture._complete(value); // } else { // asyncFuture._asyncComplete(value); // } // // But if the user code returns `Future` instead of // `Future`, that function won't recognize it as a future and will // instead treat it as a completed value. if (value is Future) { if (value is _Future) { _Future._chainCoreFuture(value, asyncFuture, isRunningAsEvent); } else { asyncFuture._chainForeignFuture(value); } } else if (isRunningAsEvent) { asyncFuture._completeWithValue(JS('', '#', value)); } else { asyncFuture._asyncComplete(JS('', '#', value)); } } else { _Future._chainCoreFuture(onAwait(value), asyncFuture, true); } } catch (e, s) { if (isRunningAsEvent) { _completeWithErrorCallback(asyncFuture, e, s); } else { _asyncCompleteWithErrorCallback(asyncFuture, e, s); } } } if (dart.startAsyncSynchronously) { runBody(); isRunningAsEvent = true; } else { isRunningAsEvent = true; scheduleMicrotask(runBody); } return asyncFuture; } /// Checks that the value being awaited is a Future of the expected type and /// if not the value is wrapped in a new Future. /// /// Calls to the method are generated from the compiler when it detects a type /// check is required on the expression in an await. /// /// Closes a soundness hole where null could leak from an awaited Future. @JSExportName('awaitWithTypeCheck') _awaitWithTypeCheck(Object? value) => value is T ? value : _Future.value(value); @patch class _AsyncRun { @patch static void _scheduleImmediate(void Function() callback) { _scheduleImmediateClosure(callback); } // Lazily initialized. static final _scheduleImmediateClosure = _initializeScheduleImmediate(); static void Function(void Function()) _initializeScheduleImmediate() { // d8 support, see preambles/d8.js for the definition of `scheduleImmediate`. // // TODO(jmesserly): do we need this? It's only for our d8 stack trace test. if (JS('', '#.scheduleImmediate', dart.global_) != null) { return _scheduleImmediateJSOverride; } return _scheduleImmediateWithPromise; } @ReifyFunctionTypes(false) static void _scheduleImmediateJSOverride(void Function() callback) { JS('void', '#.scheduleImmediate(#)', dart.global_, () { callback(); }); } @ReifyFunctionTypes(false) static void _scheduleImmediateWithPromise(void Function() callback) { JS('', '#.Promise.resolve(null).then(#)', dart.global_, () { callback(); }); } } @patch class Timer { @patch static Timer _createTimer(Duration duration, void Function() callback) { int milliseconds = duration.inMilliseconds; if (milliseconds < 0) milliseconds = 0; return TimerImpl(milliseconds, callback); } @patch static Timer _createPeriodicTimer( Duration duration, void callback(Timer timer), ) { int milliseconds = duration.inMilliseconds; if (milliseconds < 0) milliseconds = 0; return TimerImpl.periodic(milliseconds, callback); } } /// Used by the compiler to implement `async*` functions. /// /// This is inspired by _AsyncStarStreamController in dart-lang/sdk's /// runtime/lib/core_patch.dart /// /// Given input like: /// /// foo() async* { /// yield 1; /// yield* bar(); /// print(await baz()); /// } /// /// This compiles to: /// /// function foo() { /// return new (AsyncStarImplOfT()).new(function*(stream) { /// if (stream.add(1)) return; /// yield; /// if (stream.addStream(bar()) return; /// yield; /// print(yield baz()); /// }); /// } /// class _AsyncStarImpl { late StreamController controller; Object Function(_AsyncStarImpl) initGenerator; @notNull bool isSuspendedAtYieldStar = false; @notNull bool onListenReceived = false; @notNull bool isScheduled = false; @notNull bool isSuspendedAtYield = false; /// Whether we're suspended at an `await`. @notNull bool isSuspendedAtAwait = false; Completer? cancellationCompleter; late Object jsIterator; Null Function(Object, StackTrace)? _handleErrorCallback; void Function([Object?])? _runBodyCallback; _AsyncStarImpl(this.initGenerator) { controller = StreamController( onListen: JS('!', 'this.onListen.bind(this)'), onResume: JS('!', 'this.onResume.bind(this)'), onCancel: JS('!', 'this.onCancel.bind(this)'), ); jsIterator = JS('!', '#[Symbol.iterator]()', initGenerator(this)); } /// The stream produced by this `async*` function. Stream get stream => controller.stream; /// Returns the callback used for error handling. /// /// This callback throws the error back into the user code, at the appropriate /// location (e.g. `await` `yield` or `yield*`). This gives user code a chance /// to handle it try-catch. If they do not handle, the error gets routed to /// the [stream] as an error via [addError]. /// /// As a performance optimization, this callback is only bound once to the /// current [Zone]. This works because a single subscription stream should /// always be running in its original zone. An `async*` method will always /// save/restore the zone that was active when `listen()` was first called, /// similar to a stream. This follows from section 16.14 of the Dart 4th /// edition spec: /// /// > If `f` is marked `async*` (9), then a fresh instance `s` implementing /// > the built-in class `Stream` is associated with the invocation and /// > immediately returned. When `s` is listened to, execution of the body of /// > `f` will begin. /// Null Function(Object, StackTrace) get handleError { if (_handleErrorCallback == null) { _handleErrorCallback = (error, StackTrace stackTrace) { try { JS( '', '#.throw(#)', jsIterator, dart.createErrorWithStack(error, stackTrace), ); } catch (e, newStack) { // The generator didn't catch the error, or it threw a new one. // Make sure to propagate the new error. addError(e, newStack); } }; var zone = Zone.current; if (!identical(zone, Zone.root)) { _handleErrorCallback = zone.bindBinaryCallback(_handleErrorCallback!); } } return _handleErrorCallback!; } void scheduleGenerator() { // TODO(jmesserly): is this isPaused check in the right place? Assuming the // async* Stream yields, then is paused (by other code), the body will // already be scheduled. This will cause at least one more iteration to // run (adding another data item to the Stream) before actually pausing. // It could be fixed by moving the `isPaused` check inside `runBody`. if (isScheduled || controller.isPaused || isSuspendedAtYieldStar) { return; } isScheduled = true; // Capture the current zone. See comment on [handleError] for more // information about this optimization. var zone = Zone.current; if (_runBodyCallback == null) { _runBodyCallback = JS('!', '#.bind(this)', runBody); if (!identical(zone, Zone.root)) { var registered = zone.registerUnaryCallback(_runBodyCallback!); _runBodyCallback = ([arg]) => zone.runUnaryGuarded(registered, arg); } } zone.scheduleMicrotask(_runBodyCallback!); } void runBody(awaitValue) { isScheduled = false; isSuspendedAtYield = false; isSuspendedAtAwait = false; Object iterResult; try { iterResult = JS('', '#.next(#)', jsIterator, awaitValue); } catch (e, s) { addError(e, s); return; } if (JS('!', '#.done', iterResult)) { close(); return; } // If we're suspended at a yield/yield*, we're done for now. if (isSuspendedAtYield || isSuspendedAtYieldStar) return; // Handle `await`: if we get a value passed to `yield` it means we are // waiting on this Future. Make sure to prevent scheduling, and pass the // value back as the result of the `yield`. // // TODO(jmesserly): is the timing here correct? The assumption here is // that we should schedule `await` in `async*` the same as in `async`. isSuspendedAtAwait = true; FutureOr value = JS('', '#.value', iterResult); // TODO(jmesserly): this logic was copied from `async` function impl. _Future f; if (value is _Future) { f = value; } else if (value is Future) { f = _Future(); f._chainForeignFuture(value); } else { f = _Future.value(value); } f._thenAwait(_runBodyCallback!, handleError); } /// Adds element to [stream] and returns true if the caller should terminate /// execution of the generator. /// /// This is called from generated code like this: /// /// if (controller.add(1)) return; /// yield; // // TODO(hausner): Per spec, the generator should be suspended before exiting // when the stream is closed. We could add a getter like this: // // get isCancelled => controller.hasListener; // // The generator would translate a 'yield e' statement to // // controller.add(1); // suspend; // this is `yield` in JS. // if (controller.isCancelled) return; bool add(T event) { if (!onListenReceived) _fatal("yield before stream is listened to"); if (isSuspendedAtYield) _fatal("unexpected yield"); // If stream is cancelled, tell caller to exit the async generator. if (!controller.hasListener) { return true; } controller.add(event); scheduleGenerator(); isSuspendedAtYield = true; return false; } /// Adds the elements of [stream] into this [controller]'s stream, and returns /// true if the caller should terminate execution of the generator. /// /// The generator will be scheduled again when all of the elements of the /// added stream have been consumed. bool addStream(Stream stream) { if (!onListenReceived) _fatal("yield* before stream is listened to"); // If stream is cancelled, tell caller to exit the async generator. if (!controller.hasListener) return true; isSuspendedAtYieldStar = true; var whenDoneAdding = controller.addStream(stream, cancelOnError: false); whenDoneAdding.then((_) { isSuspendedAtYieldStar = false; scheduleGenerator(); if (!isScheduled) isSuspendedAtYield = true; }, onError: handleError); return false; } void addError(Object error, StackTrace stackTrace) { ArgumentError.checkNotNull(error, "error"); var completer = cancellationCompleter; if (completer != null && !completer.isCompleted) { // If the stream has been cancelled, complete the cancellation future // with the error. completer.completeError(error, stackTrace); } else if (controller.hasListener) { controller.addError(error, stackTrace); } // No need to schedule the generator body here. This code is only // called from the catch clause of the implicit try-catch-finally // around the generator body. That is, we are on the error path out // of the generator and do not need to run the generator again. close(); } void close() { var completer = cancellationCompleter; if (completer != null && !completer.isCompleted) { // If the stream has been cancelled, complete the cancellation future // with the error. completer.complete(); } controller.close(); } onListen() { assert(!onListenReceived); onListenReceived = true; scheduleGenerator(); } onResume() { if (isSuspendedAtYield) { scheduleGenerator(); } } onCancel() { if (controller.isClosed) { return null; } if (cancellationCompleter == null) { cancellationCompleter = Completer(); // Only resume the generator if it is suspended at a yield. // Cancellation does not affect an async generator that is // suspended at an await. if (isSuspendedAtYield) { scheduleGenerator(); } } return cancellationCompleter!.future; } _fatal(String message) => throw StateError(message); } class _AsyncAwaitCompleter implements Completer { final _future = _Future(); bool isSync; int hotRestartIteration = dart.hotRestartIteration; _AsyncAwaitCompleter() : isSync = false; void complete([FutureOr? value]) { // All paths require that if value is null, null as T succeeds. value = (value == null) ? value as T : value; if (!isSync) { _future._asyncComplete(value); } else if (value is Future) { assert(!_future._isComplete); _future._chainFuture(value); } else { _future._completeWithValue(value); } } void completeError(Object e, [StackTrace? st]) { st ??= AsyncError.defaultStackTrace(e); if (isSync) { _future._completeError(e, st); } else { _future._asyncCompleteError(e, st); } } Future get future => _future; bool get isCompleted => !_future._mayComplete; } /// Creates a Completer for an `async` function. /// /// Used as part of the runtime support for the async/await transformation. All /// calls to this method are generated by the compiler. Completer _makeAsyncAwaitCompleter() { return _AsyncAwaitCompleter(); } /// Initiates the computation of an `async` function and starts the body /// synchronously. /// /// Used as part of the runtime support for the async/await transformation. All /// calls to this method are generated by the compiler. /// /// This function sets up the first call into the transformed [bodyFunction]. /// Independently, it takes the [completer] and returns the future of the /// completer for convenience of the transformed code. Future _asyncStartSync( _WrappedAsyncBody bodyFunction, _AsyncAwaitCompleter completer, ) { bodyFunction(async_status_codes.SUCCESS, null); completer.isSync = true; return completer.future; } /// Performs the `await` operation of an `async` function. /// /// Used as part of the runtime support for the async/await transformation. All /// calls to this method are generated by the compiler. /// /// Arranges for [bodyFunction] to be called when the future or value [object] /// is completed with a code [async_status_codes.SUCCESS] or /// [async_status_codes.ERROR] depending on the success of the future. void _asyncAwait( Object? object, _WrappedAsyncBody bodyFunction, _AsyncAwaitCompleter completer, ) { _awaitOnObject(object, bodyFunction); } /// Completes the future of an `async` function. /// /// Used as part of the runtime support for the async/await transformation. All /// calls to this method are generated by the compiler. /// /// This function is used when the `async` function returns (explicitly or /// implicitly). void _asyncReturn(Object? object, _AsyncAwaitCompleter completer) { completer.complete(object); } /// Completes the future of an `async` function with an error. /// /// Used as part of the runtime support for the async/await transformation. All /// calls to this method are generated by the compiler. /// /// This function is used when the `async` function re-throws an exception. void _asyncRethrow(Object? object, _AsyncAwaitCompleter completer) { // The error is a js-error. completer.completeError(dart.getThrown(object)!, dart.stackTrace(object)); } /// Awaits on the given [object]. /// /// If the [object] is a Future, registers on it, otherwise wraps it into a /// future first. /// /// The [bodyFunction] argument is the continuation that should be invoked /// when the future completes. void _awaitOnObject(Object? object, _WrappedAsyncBody bodyFunction) { FutureOr Function(dynamic) thenCallback = (result) => bodyFunction(async_status_codes.SUCCESS, result); Function errorCallback = (dynamic error, StackTrace stackTrace) { final wrappedException = dart.createErrorWithStack(error, stackTrace); bodyFunction(async_status_codes.ERROR, wrappedException); }; if (object is _Future) { // We can skip the zone registration, since the bodyFunction is already // registered (see [_wrapJsFunctionForAsync]). object._thenAwait(thenCallback, errorCallback); } else if (object is Future) { object.then(thenCallback, onError: errorCallback); } else { _Future future = _Future().._setValue(object); // We can skip the zone registration, since the bodyFunction is already // registered (see [_wrapJsFunctionForAsync]). future._thenAwait(thenCallback, errorCallback); } } typedef _WrappedAsyncBody = void Function(int errorCode, Object? result); /// Wraps a JS function generated by the compiler with boiler plate to handle /// errors and re-entry logic. /// /// Used as part of the runtime support for the async/await transformation. All /// calls to this method are generated by the compiler. _WrappedAsyncBody _wrapJsFunctionForAsync(dynamic /* js function */ function) { var protected = JS( '', """ (function (fn, ERROR) { // Invokes [function] with [errorCode] and [result]. // // If (and as long as) the invocation throws, calls [function] again, // with an error-code. return function(errorCode, result) { while (true) { try { fn(errorCode, result); break; } catch (error) { result = error; errorCode = ERROR; } } } })(#, #)""", function, async_status_codes.ERROR, ); return Zone.current.registerBinaryCallback((int errorCode, dynamic result) { JS('', '#(#, #)', protected, errorCode, result); }); } /// Implements the runtime support for async* functions. /// /// Called by the transformed function for each original return, await, yield, /// yield* and before starting the function. /// /// When the async* function wants to return it calls this function with /// [bodyFunctionOrErrorCode] == [async_status_codes.SUCCESS], the /// asyncStarHelper takes this as signal to close the stream. /// /// When the async* function wants to signal that an uncaught error was thrown, /// it calls this function with /// [bodyFunctionOrErrorCode] == [async_status_codes.ERROR], the streamHelper /// takes this as signal to addError [object] to the [controller] and close it. /// /// If the async* function wants to do a yield or yield*, it calls this function /// with [object] being an [_IterationMarker]. /// /// In the case of a yield or yield*, if the stream subscription has been /// canceled, schedules [bodyFunctionOrErrorCode] to be called with /// [async_status_codes.STREAM_WAS_CANCELED]. /// /// If [object] is a single-yield [_IterationMarker], adds the value of the /// [_IterationMarker] to the stream. If the stream subscription has been /// paused, return early. Otherwise schedule the helper function to be /// executed again. /// /// If [object] is a yield-star [_IterationMarker], starts listening to the /// yielded stream, and adds all events and errors to our own controller (taking /// care if the subscription has been paused or canceled) - when the sub-stream /// is done, schedules [bodyFunctionOrErrorCode] again. /// /// If the async* function wants to do an await it calls this function with /// [object] not an [_IterationMarker]. /// /// If [object] is not a [Future], it is wrapped in a `Future.value`. /// The [bodyFunctionOrErrorCode] is called on completion of the future /// (see [_awaitOnObject]). /// /// All calls to this method are generated by the compiler. void _asyncStarHelper( Object? object, dynamic /* int | _WrappedAsyncBody */ bodyFunctionOrErrorCode, _AsyncStarStreamController controller, ) { if (identical(bodyFunctionOrErrorCode, async_status_codes.SUCCESS)) { // This happens on return from the async* function. if (controller.isCanceled) { controller.cancelationFuture!._completeWithValue(null); } else { controller.close(); } return; } else if (identical(bodyFunctionOrErrorCode, async_status_codes.ERROR)) { // The error is a js-error. if (controller.isCanceled) { controller.cancelationFuture!._completeError( dart.getThrown(object)!, dart.stackTrace(object), ); } else { controller.addError(dart.getThrown(object)!, dart.stackTrace(object)); controller.close(); } return; } _WrappedAsyncBody bodyFunction = bodyFunctionOrErrorCode; if (object is _IterationMarker) { if (controller.isCanceled) { bodyFunction(async_status_codes.STREAM_WAS_CANCELED, null); return; } if (object.state == _IterationMarker.YIELD_SINGLE) { controller.add(object.value); scheduleMicrotask(() { if (controller.isPaused) { // We only suspend the thread inside the microtask in order to allow // listeners on the output stream to pause in response to the just // output value, and have the stream immediately stop producing. controller.isSuspended = true; return; } bodyFunction( controller.isCanceled ? async_status_codes.STREAM_WAS_CANCELED : async_status_codes.SUCCESS, null, ); }); return; } else if (object.state == _IterationMarker.YIELD_STAR) { Stream stream = object.value; // Errors of [stream] are passed though to the main stream. (see // [AsyncStreamController.addStream]). controller.addStream(stream).then((_) { // No need to check for pause because to get here the stream either // completed normally or was cancelled. The stream cannot be paused // after either of these states. int errorCode = controller.isCanceled ? async_status_codes.STREAM_WAS_CANCELED : async_status_codes.SUCCESS; bodyFunction(errorCode, null); }); return; } } _awaitOnObject(object, bodyFunction); } /// Gets the stream out of an async controller. /// /// Used as part of the runtime support for the async/await transformation. All /// calls to this method are generated by the compiler. Stream _streamOfController(_AsyncStarStreamController controller) { return controller.stream; } /// A wrapper around a [StreamController] that keeps track of the state of /// the execution of an async* function. /// /// It can be in 1 of 3 states: /// /// - running/scheduled /// - suspended /// - canceled /// /// If yielding while the subscription is paused it will become suspended. And /// only resume after the subscription is resumed or canceled. class _AsyncStarStreamController { late StreamController controller; Stream get stream => controller.stream; /// True when the async* function has yielded while being paused. /// /// When true execution will only resume after a `onResume` or `onCancel` /// event. bool isSuspended = false; bool get isPaused => controller.isPaused; _Future? cancelationFuture = null; /// True after the StreamSubscription has been cancelled. /// /// When this is true, errors thrown from the async* body should go to the /// [cancelationFuture] instead of adding them to [controller], and /// returning from the async function should complete [cancelationFuture]. bool get isCanceled => cancelationFuture != null; add(event) => controller.add(event); Future addStream(Stream stream) { return controller.addStream(stream, cancelOnError: false); } addError(error, stackTrace) => controller.addError(error, stackTrace); close() => controller.close(); _AsyncStarStreamController(_WrappedAsyncBody body) { _resumeBody() { scheduleMicrotask(() { body(async_status_codes.SUCCESS, null); }); } controller = StreamController( onListen: () { _resumeBody(); }, onResume: () { // Only schedule again if the async* function actually is suspended. // Resume directly instead of scheduling, so that the sequence // `pause-resume-pause` will result in one extra event produced. if (isSuspended) { isSuspended = false; _resumeBody(); } }, onCancel: () { // If the async* is finished we ignore cancel events. if (!controller.isClosed) { cancelationFuture = _Future(); if (isSuspended) { // Resume the suspended async* function to run finalizers. isSuspended = false; scheduleMicrotask(() { body(async_status_codes.STREAM_WAS_CANCELED, null); }); } return cancelationFuture; } }, ); } } /// Creates a stream controller for an `async*` function. /// /// Used as part of the runtime support for the async/await transformation. _makeAsyncStarStreamController(_WrappedAsyncBody body) { return _AsyncStarStreamController(body); } class _IterationMarker { static const YIELD_SINGLE = 0; static const YIELD_STAR = 1; static const ITERATION_ENDED = 2; static const UNCAUGHT_ERROR = 3; final value; final int state; const _IterationMarker._(this.state, this.value); static yieldStar(Object /* Iterable or Stream */ values) { return _IterationMarker._(YIELD_STAR, values); } static endOfIteration() { return const _IterationMarker._(ITERATION_ENDED, null); } static yieldSingle(Object value) { return _IterationMarker._(YIELD_SINGLE, value); } static uncaughtError(Object error) { return _IterationMarker._(UNCAUGHT_ERROR, error); } toString() => "IterationMarker($state, $value)"; } /// _SyncStarIterator handles stepping a sync* generator body state machine. /// /// It also handles the stepping over 'nested' iterators to flatten yield* /// statements. For non-sync* iterators, [_nestedIterator] contains the /// iterator. We delegate to [_nestedIterator] when it is not `null`. /// /// For nested sync* iterators, this [Iterator] acts on behalf of the innermost /// nested sync* iterator. The current state machine is suspended on a stack /// until the inner state machine ends. class _SyncStarIterator implements Iterator { /// The state machine for the innermost _SyncStarIterator. Object? _body; /// The current value, unless iterating a non-sync* nested iterator. T? _current = null; /// Value passed back from state machine for uncaught exceptions. Object? _datum; /// This is the nested iterator when iterating a yield* of a non-sync iterator. Iterator? _nestedIterator = null; /// Stack of suspended state machines when iterating a yield* of a sync* /// iterator. List? _suspendedBodies = null; _SyncStarIterator(this._body); T get current { return _current as T; } _resumeBody(int errorCode, Object? errorValue) { final body = _body; while (true) { try { return JS('', '#(#, #, #)', body, this, errorCode, errorValue); } catch (error) { errorValue = JS_RAW_EXCEPTION(); errorCode = async_status_codes.ERROR; } } } bool moveNext() { Object? errorValue; int errorCode = async_status_codes.SUCCESS; while (true) { final nestedIterator = _nestedIterator; if (nestedIterator != null) { try { if (nestedIterator.moveNext()) { _current = nestedIterator.current; return true; } else { _nestedIterator = null; } } catch (error) { errorValue = error; errorCode = async_status_codes.ERROR; _nestedIterator = null; } } var value = _resumeBody(errorCode, errorValue); if (async_status_codes.SYNC_STAR_YIELD == value) { // The state-machine has assgned the value to _current. return true; } if (async_status_codes.SYNC_STAR_DONE == value) { _current = null; var suspendedBodies = _suspendedBodies; if (suspendedBodies == null || suspendedBodies.isEmpty) { // Overwrite the body with a stub for an empty iterable. If [moveNext] // is called 'too many' times, it continues to return `false`. _body = RAW_DART_FUNCTION_REF(_terminatedBody); return false; } // Resume the innermost suspended iterator. _body = suspendedBodies.removeLast(); errorCode = async_status_codes.SUCCESS; errorValue = null; continue; } if (async_status_codes.SYNC_STAR_YIELD_STAR == value) { // The call to _yieldStar has modified the state. errorCode = async_status_codes.SUCCESS; errorValue = null; continue; } if (async_status_codes.SYNC_STAR_UNCAUGHT_EXCEPTION == value) { errorValue = _datum; _datum = null; var suspendedBodies = _suspendedBodies; if (suspendedBodies == null || suspendedBodies.isEmpty) { _current = null; // Overwrite the body with a stub for an empty iterable. If [moveNext] // is called after the exception propagates out of the `yield*` stack, // it will return `false`. _body = RAW_DART_FUNCTION_REF(_terminatedBody); // This is a wrapped exception, so we use JavaScript throw to throw // it. JS('', 'throw #', errorValue); // The above is not seen as terminating, so we need this return: return false; // unreachable } // Resume the innermost suspended iterator. _body = suspendedBodies.removeLast(); errorCode = async_status_codes.ERROR; continue; } throw StateError('sync*'); } } static _terminatedBody(_1, _2, _3) => async_status_codes.SYNC_STAR_DONE; // Called from generated code. int _yieldStar(Iterable iterable) { if (iterable is _SyncStarIterable) { // Promotion fails, so we need this zero-cost 'cast'. _SyncStarIterable syncStarIterable = JS('', '#', iterable); _SyncStarIterator inner = syncStarIterable.iterator; // Suspend the current state machine and start acting on behalf of // the nested state machine. // // TODO(sra): Recognize "tail yield*" statements and avoid // suspending the current body when all it will do is step without // effect to ITERATION_ENDED. (_suspendedBodies ??= []).add(_body); _body = inner._body; return async_status_codes.SYNC_STAR_YIELD_STAR; } else { _nestedIterator = iterable.iterator; return async_status_codes.SYNC_STAR_YIELD_STAR; } } } /// Creates an Iterable for a `sync*` function. /// /// Used as part of the runtime support for the async/await transformation. _SyncStarIterable _makeSyncStarIterable(body) { return _SyncStarIterable(body); } /// An Iterable corresponding to a sync* method. /// /// Each invocation of a sync* method will return a new instance of this class. class _SyncStarIterable extends Iterable { /// This is a function that will return a helper function that does the /// iteration of the sync*. /// /// Each invocation should give a body with fresh state. final dynamic /* js function */ _outerHelper; _SyncStarIterable(this._outerHelper); _SyncStarIterator get iterator => _SyncStarIterator(JS('', '#()', _outerHelper)); } /// Wraps an `await`ed expression in [Future.value] if needed. /// /// If an expression `e` has a static type of `S`, then `await e` must first /// check if the runtime type of `e` is `Future`. If it is, `e` can /// be `await`ed directly. Otherwise, we must `await Future.value(e)`. Here, [T] /// is expected to be `flatten(S)`. /// /// It suffices to use `_Future.value` rather than `Future.value` - see the /// comments on https://github.com/dart-lang/sdk/issues/50601. Future _wrapAwaitedExpression(Object? e) => e is Future ? e : _Future.value(unsafeCast(e));