@ -138,21 +138,19 @@ impl AsyncGenerator {
. with_message ( "generator resumed on non generator object" )
. into ( )
} ) ;
let generator_object = if_abrupt_reject_promise ! ( result , promise_capability , context ) ;
let result : JsResult < _ > = generator_object . downcast_mu t ::< Self > ( ) . ok_or_else ( | | {
let generator = if_abrupt_reject_promise ! ( result , promise_capability , context ) ;
let result : JsResult < _ > = generator . clone ( ) . downcast ::< Self > ( ) . map_err ( | _ | {
JsNativeError ::typ ( )
. with_message ( "generator resumed on non generator object" )
. into ( )
} ) ;
let mut generator = if_abrupt_reject_promise ! ( result , promise_capability , context ) ;
let generator = if_abrupt_reject_promise ! ( result , promise_capability , context ) ;
// 5. Let state be generator.[[AsyncGeneratorState]].
let state = generator . state ;
let state = generator . borrow ( ) . data . state ;
// 6. If state is completed, then
if state = = AsyncGeneratorState ::Completed {
drop ( generator ) ;
// a. Let iteratorResult be CreateIterResultObject(undefined, true).
let iterator_result = create_iter_result_object ( JsValue ::undefined ( ) , true , context ) ;
@ -170,28 +168,7 @@ impl AsyncGenerator {
let completion = CompletionRecord ::Normal ( args . get_or_undefined ( 0 ) . clone ( ) ) ;
// 8. Perform AsyncGeneratorEnqueue(generator, completion, promiseCapability).
generator . enqueue ( completion . clone ( ) , promise_capability . clone ( ) ) ;
// 9. If state is either suspendedStart or suspendedYield, then
if state = = AsyncGeneratorState ::SuspendedStart
| | state = = AsyncGeneratorState ::SuspendedYield
{
// a. Perform AsyncGeneratorResume(generator, completion).
let generator_context = generator
. context
. take ( )
. expect ( "generator context cannot be empty here" ) ;
drop ( generator ) ;
Self ::resume (
generator_object ,
state ,
generator_context ,
completion ,
context ,
) ;
}
Self ::enqueue ( & generator , completion , promise_capability . clone ( ) , context ) ;
// 11. Return promiseCapability.[[Promise]].
Ok ( promise_capability . promise ( ) . clone ( ) . into ( ) )
@ -226,49 +203,19 @@ impl AsyncGenerator {
. into ( )
} ) ;
let generator_object = if_abrupt_reject_promise ! ( result , promise_capability , context ) ;
let result : JsResult < _ > = generator_object . downcast_mu t ::< Self > ( ) . ok_or_else ( | | {
let result : JsResult < _ > = generator_object . clone ( ) . downcast ::< Self > ( ) . map_err ( | _ | {
JsNativeError ::typ ( )
. with_message ( "generator resumed on non generator object" )
. into ( )
} ) ;
let mut generator = if_abrupt_reject_promise ! ( result , promise_capability , context ) ;
let generator = if_abrupt_reject_promise ! ( result , promise_capability , context ) ;
// 5. Let completion be Completion Record { [[Type]]: return, [[Value]]: value, [[Target]]: empty }.
let return_value = args . get_or_undefined ( 0 ) . clone ( ) ;
let completion = CompletionRecord ::Return ( return_value . clone ( ) ) ;
let completion = CompletionRecord ::Return ( return_value ) ;
// 6. Perform AsyncGeneratorEnqueue(generator, completion, promiseCapability).
generator . enqueue ( completion . clone ( ) , promise_capability . clone ( ) ) ;
// 7. Let state be generator.[[AsyncGeneratorState]].
let state = generator . state ;
// 8. If state is either suspendedStart or completed, then
if state = = AsyncGeneratorState ::SuspendedStart | | state = = AsyncGeneratorState ::Completed {
// a. Set generator.[[AsyncGeneratorState]] to awaiting-return.
generator . state = AsyncGeneratorState ::AwaitingReturn ;
// b. Perform ! AsyncGeneratorAwaitReturn(generator).
drop ( generator ) ;
Self ::await_return ( generator_object . clone ( ) , return_value , context ) ;
}
// 9. Else if state is suspendedYield, then
else if state = = AsyncGeneratorState ::SuspendedYield {
// a. Perform AsyncGeneratorResume(generator, completion).
let generator_context = generator
. context
. take ( )
. expect ( "generator context cannot be empty here" ) ;
drop ( generator ) ;
Self ::resume (
generator_object ,
state ,
generator_context ,
completion ,
context ,
) ;
}
Self ::enqueue ( & generator , completion , promise_capability . clone ( ) , context ) ;
// 11. Return promiseCapability.[[Promise]].
Ok ( promise_capability . promise ( ) . clone ( ) . into ( ) )
@ -303,30 +250,31 @@ impl AsyncGenerator {
. into ( )
} ) ;
let generator_object = if_abrupt_reject_promise ! ( result , promise_capability , context ) ;
let result : JsResult < _ > = generator_object . downcast_mu t ::< Self > ( ) . ok_or_else ( | | {
let result : JsResult < _ > = generator_object . clone ( ) . downcast ::< Self > ( ) . map_err ( | _ | {
JsNativeError ::typ ( )
. with_message ( "generator resumed on non generator object" )
. into ( )
} ) ;
let mut generator = if_abrupt_reject_promise ! ( result , promise_capability , context ) ;
let generator = if_abrupt_reject_promise ! ( result , promise_capability , context ) ;
let mut gen = generator . borrow_mut ( ) ;
// 5. Let state be generator.[[AsyncGeneratorState]].
let mut state = generator . state ;
let mut state = gen . data . state ;
// 6. If state is suspendedStart, then
if state = = AsyncGeneratorState ::SuspendedStart {
// a. Set generator.[[AsyncGeneratorState]] to completed.
generator . state = AsyncGeneratorState ::Completed ;
generator . context = None ;
gen . data . state = AsyncGeneratorState ::Completed ;
gen . data . context = None ;
// b. Set state to completed.
state = AsyncGeneratorState ::Completed ;
}
drop ( gen ) ;
// 7. If state is completed, then
if state = = AsyncGeneratorState ::Completed {
drop ( generator ) ;
// a. Perform ! Call(promiseCapability.[[Reject]], undefined, « exception »).
promise_capability
. reject ( )
@ -346,25 +294,12 @@ impl AsyncGenerator {
CompletionRecord ::Throw ( JsError ::from_opaque ( args . get_or_undefined ( 0 ) . clone ( ) ) ) ;
// 9. Perform AsyncGeneratorEnqueue(generator, completion, promiseCapability).
generator . enqueue ( completion . clone ( ) , promise_capability . clone ( ) ) ;
// 10. If state is suspendedYield, then
if state = = AsyncGeneratorState ::SuspendedYield {
let generator_context = generator
. context
. take ( )
. expect ( "generator context cannot be empty here" ) ;
drop ( generator ) ;
// a. Perform AsyncGeneratorResume(generator, completion).
Self ::resume (
generator_object ,
state ,
generator_context ,
completion ,
Self ::enqueue (
& generator ,
completion . clone ( ) ,
promise_capability . clone ( ) ,
context ,
) ;
}
// 12. Return promiseCapability.[[Promise]].
Ok ( promise_capability . promise ( ) . clone ( ) . into ( ) )
@ -377,10 +312,12 @@ impl AsyncGenerator {
///
/// [spec]: https://tc39.es/ecma262/#sec-asyncgeneratorenqueue
pub ( crate ) fn enqueue (
& mut self ,
generator : & JsObject < AsyncGenerator > ,
completion : CompletionRecord ,
promise_capability : PromiseCapability ,
context : & mut Context ,
) {
let mut gen = generator . borrow_mut ( ) ;
// 1. Let request be AsyncGeneratorRequest { [[Completion]]: completion, [[Capability]]: promiseCapability }.
let request = AsyncGeneratorRequest {
completion ,
@ -388,7 +325,14 @@ impl AsyncGenerator {
} ;
// 2. Append request to the end of generator.[[AsyncGeneratorQueue]].
self . queue . push_back ( request ) ;
gen . data . queue . push_back ( request ) ;
// Patch that mirrors https://262.ecma-international.org/12.0/#sec-asyncgeneratorenqueue
// This resolves the return bug.
if gen . data . state ! = AsyncGeneratorState ::Executing {
drop ( gen ) ;
AsyncGenerator ::resume_next ( generator , context ) ;
}
}
/// `AsyncGeneratorCompleteStep ( generator, completion, done [ , realm ] )`
@ -453,67 +397,17 @@ impl AsyncGenerator {
}
}
/// `AsyncGeneratorResume ( generator, completion )`
///
/// More information:
/// - [ECMAScript reference][spec]
///
/// [spec]: https://tc39.es/ecma262/#sec-asyncgeneratorresume
pub ( crate ) fn resume (
generator : & JsObject ,
state : AsyncGeneratorState ,
mut generator_context : GeneratorContext ,
completion : CompletionRecord ,
context : & mut Context ,
) {
// 1. Assert: generator.[[AsyncGeneratorState]] is either suspendedStart or suspendedYield.
assert! (
state = = AsyncGeneratorState ::SuspendedStart
| | state = = AsyncGeneratorState ::SuspendedYield
) ;
// 2. Let genContext be generator.[[AsyncGeneratorContext]].
// 3. Let callerContext be the running execution context.
// 4. Suspend callerContext.
// 5. Set generator.[[AsyncGeneratorState]] to executing.
generator
. downcast_mut ::< Self > ( )
. expect ( "already checked before" )
. state = AsyncGeneratorState ::Executing ;
let ( value , resume_kind ) = match completion {
CompletionRecord ::Normal ( val ) = > ( val , GeneratorResumeKind ::Normal ) ,
CompletionRecord ::Return ( val ) = > ( val , GeneratorResumeKind ::Return ) ,
CompletionRecord ::Throw ( err ) = > ( err . to_opaque ( context ) , GeneratorResumeKind ::Throw ) ,
} ;
// 6. Push genContext onto the execution context stack; genContext is now the running execution context.
let result = generator_context . resume ( Some ( value ) , resume_kind , context ) ;
// 7. Resume the suspended evaluation of genContext using completion as the result of the operation that suspended it. Let result be the Completion Record returned by the resumed computation.
generator
. downcast_mut ::< Self > ( )
. expect ( "already checked before" )
. context = Some ( generator_context ) ;
// 8. Assert: result is never an abrupt completion.
assert! ( ! result . is_throw_completion ( ) ) ;
// 9. Assert: When we return here, genContext has already been removed from the execution context stack and
// callerContext is the currently running execution context.
// 10. Return unused.
}
/// `AsyncGeneratorAwaitReturn ( generator )`
///
/// More information:
/// - [ECMAScript reference][spec]
///
/// [spec]: https://tc39.es/ecma262/#sec-asyncgeneratorawaitreturn
pub ( crate ) fn await_return ( generator : JsObject , value : JsValue , context : & mut Context ) {
pub ( crate ) fn await_return (
generator : JsObject < AsyncGenerator > ,
value : JsValue ,
context : & mut Context ,
) {
// 1. Let queue be generator.[[AsyncGeneratorQueue]].
// 2. Assert: queue is not empty.
// 3. Let next be the first element of queue.
@ -532,15 +426,14 @@ impl AsyncGenerator {
let promise = match promise_completion {
Ok ( value ) = > value ,
Err ( value ) = > {
let mut gen = generator
. downcast_mut ::< Self > ( )
. expect ( "already checked before" ) ;
gen . state = AsyncGeneratorState ::Completed ;
gen . context = None ;
let next = gen . queue . pop_front ( ) . expect ( "queue must not be empty" ) ;
drop ( gen ) ;
let next = {
let mut gen = generator . borrow_mut ( ) ;
gen . data . state = AsyncGeneratorState ::Completed ;
gen . data . context = None ;
gen . data . queue . pop_front ( ) . expect ( "queue must not be empty" )
} ;
Self ::complete_step ( & next , Err ( value ) , true , None , context ) ;
Self ::d rain_qu eue( & generator , context ) ;
Self ::res um e_next ( & generator , context ) ;
return ;
}
} ;
@ -552,15 +445,13 @@ impl AsyncGenerator {
NativeFunction ::from_copy_closure_with_captures (
| _this , args , generator , context | {
let next = {
let mut gen = generator
. downcast_mut ::< Self > ( )
. expect ( "already checked before" ) ;
let mut gen = generator . borrow_mut ( ) ;
// a. Set generator.[[AsyncGeneratorState]] to completed.
gen . state = AsyncGeneratorState ::Completed ;
gen . context = None ;
gen . data . state = AsyncGeneratorState ::Completed ;
gen . data . context = None ;
gen . queue . pop_front ( ) . expect ( "must have one entry" )
gen . data . queue . pop_front ( ) . expect ( "must have one entry" )
} ;
// b. Let result be NormalCompletion(value).
@ -570,7 +461,7 @@ impl AsyncGenerator {
Self ::complete_step ( & next , result , true , None , context ) ;
// d. Perform AsyncGeneratorDrainQueue(generator).
Self ::d rain_qu eue( generator , context ) ;
Self ::res um e_next ( generator , context ) ;
// e. Return undefined.
Ok ( JsValue ::undefined ( ) )
@ -588,24 +479,24 @@ impl AsyncGenerator {
context . realm ( ) ,
NativeFunction ::from_copy_closure_with_captures (
| _this , args , generator , context | {
let mut gen = generator
. downcast_mut ::< Self > ( )
. expect ( "already checked before" ) ;
let next = {
let mut gen = generator . borrow_mut ( ) ;
// a. Set generator.[[AsyncGeneratorState]] to completed.
gen . state = AsyncGeneratorState ::Completed ;
gen . context = None ;
gen . data . state = AsyncGeneratorState ::Completed ;
gen . data . context = None ;
gen . data . queue . pop_front ( ) . expect ( "must have one entry" )
} ;
// b. Let result be ThrowCompletion(reason).
let result = Err ( JsError ::from_opaque ( args . get_or_undefined ( 0 ) . clone ( ) ) ) ;
// c. Perform AsyncGeneratorCompleteStep(generator, result, true).
let next = gen . queue . pop_front ( ) . expect ( "must have one entry" ) ;
drop ( gen ) ;
Self ::complete_step ( & next , result , true , None , context ) ;
// d. Perform AsyncGeneratorDrainQueue(generator).
Self ::d rain_qu eue( generator , context ) ;
Self ::res um e_next ( generator , context ) ;
// e. Return undefined.
Ok ( JsValue ::undefined ( ) )
@ -627,64 +518,135 @@ impl AsyncGenerator {
) ;
}
/// `AsyncGeneratorDrainQueue ( generator )`
/// [`AsyncGeneratorResumeNext ( generator )`][spec]
///
/// More information:
/// - [ECMAScript reference][spec]
///
/// [spec]: https://tc39.es/ecma262/#sec-asyncgeneratordrainqueue
pub ( crate ) fn drain_queue ( generator : & JsObject , context : & mut Context ) {
let mut gen = generator
. downcast_mut ::< Self > ( )
. expect ( "already checked before" ) ;
// 1. Assert: generator.[[AsyncGeneratorState]] is completed.
assert_eq! ( gen . state , AsyncGeneratorState ::Completed ) ;
// 2. Let queue be generator.[[AsyncGeneratorQueue]].
let queue = & mut gen . queue ;
/// [spec]: https://262.ecma-international.org/12.0/#sec-asyncgeneratorresumenext
pub ( crate ) fn resume_next ( generator : & JsObject < AsyncGenerator > , context : & mut Context ) {
// 1. Assert: generator is an AsyncGenerator instance.
let mut gen = generator . borrow_mut ( ) ;
// 2. Let state be generator.[[AsyncGeneratorState]].
match gen . data . state {
// 3. Assert: state is not executing.
AsyncGeneratorState ::Executing = > panic! ( "3. Assert: state is not executing." ) ,
// 4. If state is awaiting-return, return undefined.
AsyncGeneratorState ::AwaitingReturn = > return ,
_ = > { }
}
// 3. If queue is empty, return unused.
if queue . is_empty ( ) {
// 5. Let queue be generator.[[AsyncGeneratorQueue]].
// 6. If queue is an empty List, return undefined.
// 7. Let next be the value of the first element of queue.
// 8. Assert: next is an AsyncGeneratorRequest record.
let Some ( next ) = gen . data . queue . front ( ) else {
return ;
} ;
// 9. Let completion be next.[[Completion]].
let completion = & next . completion ;
match ( completion , gen . data . state ) {
// 11. Else if state is completed, return ! AsyncGeneratorResolve(generator, undefined, true).
( CompletionRecord ::Normal ( _ ) , s ) = > {
if s = = AsyncGeneratorState ::Completed {
let next = gen
. data
. queue
. pop_front ( )
. expect ( "already have a reference to the front" ) ;
drop ( gen ) ;
AsyncGenerator ::complete_step (
& next ,
Ok ( JsValue ::undefined ( ) ) ,
true ,
None ,
context ,
) ;
return AsyncGenerator ::resume_next ( generator , context ) ;
}
// 4. Let done be false.
// 5. Repeat, while done is false,
loop {
// a. Let next be the first element of queue.
let next = queue . front ( ) . expect ( "must have entry" ) ;
// b. Let completion be Completion(next.[[Completion]]).
match next . completion . clone ( ) {
// c. If completion.[[Type]] is return, then
CompletionRecord ::Return ( val ) = > {
// i. Set generator.[[AsyncGeneratorState]] to awaiting-return.
gen . state = AsyncGeneratorState ::AwaitingReturn ;
}
// b. If state is completed, then
// i. If completion.[[Type]] is return, then
(
CompletionRecord ::Return ( val ) ,
AsyncGeneratorState ::SuspendedStart | AsyncGeneratorState ::Completed ,
) = > {
let val = val . clone ( ) ;
// 1. Set generator.[[AsyncGeneratorState]] to awaiting-return.
gen . data . state = AsyncGeneratorState ::AwaitingReturn ;
drop ( gen ) ;
// ii. Perform ! AsyncGeneratorAwaitReturn(generator).
Self ::await_return ( generator . clone ( ) , val , context ) ;
// Steps 2-11 are superseeded by `AsyncGeneratorAwaitReturn`
AsyncGenerator ::await_return ( generator . clone ( ) , val , context ) ;
// iii. Set done to true.
break ;
}
// d. Else,
completion = > {
// i. If completion.[[Type]] is normal, then
// 1. Set completion to NormalCompletion(undefined).
let completion = completion . consume ( ) . map ( | _ | JsValue ::undefined ( ) ) ;
// ii. Perform AsyncGeneratorCompleteStep(generator, completion, true).
let next = queue . pop_front ( ) . expect ( "must have entry" ) ;
Self ::complete_step ( & next , completion , true , None , context ) ;
// iii. If queue is empty, set done to true.
if queue . is_empty ( ) {
break ;
}
// 12. Return undefined.
return ;
}
// ii. Else,
(
CompletionRecord ::Throw ( e ) ,
AsyncGeneratorState ::SuspendedStart | AsyncGeneratorState ::Completed ,
) = > {
let e = e . clone ( ) ;
// 1. Assert: completion.[[Type]] is throw.
// 2. Perform ! AsyncGeneratorReject(generator, completion.[[Value]]).
gen . data . state = AsyncGeneratorState ::Completed ;
let next = gen
. data
. queue
. pop_front ( )
. expect ( "already have a reference to the front" ) ;
drop ( gen ) ;
AsyncGenerator ::complete_step ( & next , Err ( e ) , true , None , context ) ;
// 3. Return undefined.
return AsyncGenerator ::resume_next ( generator , context ) ;
}
_ = > { }
}
// 12. Assert: state is either suspendedStart or suspendedYield.
assert! ( matches! (
gen . data . state ,
AsyncGeneratorState ::SuspendedStart | AsyncGeneratorState ::SuspendedYield
) ) ;
let completion = completion . clone ( ) ;
// 16. Set generator.[[AsyncGeneratorState]] to executing.
gen . data . state = AsyncGeneratorState ::Executing ;
// 13. Let genContext be generator.[[AsyncGeneratorContext]].
let mut generator_context = gen
. data
. context
. take ( )
. expect ( "generator context cannot be empty here" ) ;
drop ( gen ) ;
let ( value , resume_kind ) = match completion {
CompletionRecord ::Normal ( val ) = > ( val , GeneratorResumeKind ::Normal ) ,
CompletionRecord ::Return ( val ) = > ( val , GeneratorResumeKind ::Return ) ,
CompletionRecord ::Throw ( err ) = > ( err . to_opaque ( context ) , GeneratorResumeKind ::Throw ) ,
} ;
// 14. Let callerContext be the running execution context.
// 15. Suspend callerContext.
// 17. Push genContext onto the execution context stack; genContext is now the running execution context.
// 18. Resume the suspended evaluation of genContext using completion as the result of the operation that suspended it.
// Let result be the completion record returned by the resumed computation.
let result = generator_context . resume ( Some ( value ) , resume_kind , context ) ;
// 19. Assert: result is never an abrupt completion.
assert! ( ! matches! ( result , CompletionRecord ::Throw ( _ ) ) ) ;
generator
. borrow_mut ( )
. data
. context
. get_or_insert ( generator_context ) ;
// 20. Assert: When we return here, genContext has already been removed from the execution context stack and
// callerContext is the currently running execution context.
// 21. Return undefined.
}
}