LibJS/Bytecode: Implement async generators
This commit is contained in:
parent
d4e30710e7
commit
d1cb78c411
Notes:
sideshowbarker
2024-07-17 01:00:06 +09:00
Author: https://github.com/Lubrsi Commit: https://github.com/SerenityOS/serenity/commit/d1cb78c411 Pull-request: https://github.com/SerenityOS/serenity/pull/19934 Reviewed-by: https://github.com/alimpfard Reviewed-by: https://github.com/linusg ✅
9 changed files with 1290 additions and 71 deletions
|
@ -1587,6 +1587,78 @@ Bytecode::CodeGenerationErrorOr<void> ReturnStatement::generate_bytecode(Bytecod
|
|||
return {};
|
||||
}
|
||||
|
||||
static void get_received_completion_type_and_value(Bytecode::Generator& generator, Bytecode::Register received_completion_register, Bytecode::Register received_completion_type_register, Bytecode::Register received_completion_value_register, Bytecode::IdentifierTableIndex type_identifier, Bytecode::IdentifierTableIndex value_identifier)
|
||||
{
|
||||
// The accumulator is set to an object, for example: { "type": 1 (normal), value: 1337 }
|
||||
generator.emit<Bytecode::Op::Store>(received_completion_register);
|
||||
|
||||
generator.emit_get_by_id(type_identifier);
|
||||
generator.emit<Bytecode::Op::Store>(received_completion_type_register);
|
||||
|
||||
generator.emit<Bytecode::Op::Load>(received_completion_register);
|
||||
generator.emit_get_by_id(value_identifier);
|
||||
generator.emit<Bytecode::Op::Store>(received_completion_value_register);
|
||||
}
|
||||
|
||||
static void generate_await(Bytecode::Generator& generator, Bytecode::Register received_completion_register, Bytecode::Register received_completion_type_register, Bytecode::Register received_completion_value_register, Bytecode::IdentifierTableIndex type_identifier, Bytecode::IdentifierTableIndex value_identifier);
|
||||
|
||||
enum class AwaitBeforeYield {
|
||||
No,
|
||||
Yes,
|
||||
};
|
||||
|
||||
static void generate_yield(Bytecode::Generator& generator, Bytecode::Label continuation_label, Bytecode::Register received_completion_register, Bytecode::Register received_completion_type_register, Bytecode::Register received_completion_value_register, Bytecode::IdentifierTableIndex type_identifier, Bytecode::IdentifierTableIndex value_identifier, AwaitBeforeYield await_before_yield)
|
||||
{
|
||||
if (!generator.is_in_async_generator_function()) {
|
||||
generator.emit<Bytecode::Op::Yield>(Bytecode::Label { continuation_label });
|
||||
return;
|
||||
}
|
||||
|
||||
if (await_before_yield == AwaitBeforeYield::Yes)
|
||||
generate_await(generator, received_completion_register, received_completion_type_register, received_completion_value_register, type_identifier, value_identifier);
|
||||
|
||||
auto& unwrap_yield_resumption_block = generator.make_block();
|
||||
generator.emit<Bytecode::Op::Yield>(Bytecode::Label { unwrap_yield_resumption_block });
|
||||
generator.switch_to_basic_block(unwrap_yield_resumption_block);
|
||||
get_received_completion_type_and_value(generator, received_completion_register, received_completion_type_register, received_completion_value_register, type_identifier, value_identifier);
|
||||
|
||||
// 27.6.3.7 AsyncGeneratorUnwrapYieldResumption ( resumptionValue ), https://tc39.es/ecma262/#sec-asyncgeneratorunwrapyieldresumption
|
||||
// 1. If resumptionValue.[[Type]] is not return, return ? resumptionValue.
|
||||
auto& load_completion_and_jump_to_continuation_label_block = generator.make_block();
|
||||
auto& resumption_value_type_is_return_block = generator.make_block();
|
||||
generator.emit<Bytecode::Op::LoadImmediate>(Value(to_underlying(Completion::Type::Return)));
|
||||
generator.emit<Bytecode::Op::StrictlyInequals>(received_completion_type_register);
|
||||
generator.emit<Bytecode::Op::JumpConditional>(
|
||||
Bytecode::Label { load_completion_and_jump_to_continuation_label_block },
|
||||
Bytecode::Label { resumption_value_type_is_return_block });
|
||||
|
||||
generator.switch_to_basic_block(resumption_value_type_is_return_block);
|
||||
|
||||
// 2. Let awaited be Completion(Await(resumptionValue.[[Value]])).
|
||||
generator.emit<Bytecode::Op::Load>(received_completion_value_register);
|
||||
generate_await(generator, received_completion_register, received_completion_type_register, received_completion_value_register, type_identifier, value_identifier);
|
||||
|
||||
// 3. If awaited.[[Type]] is throw, return ? awaited.
|
||||
auto& awaited_type_is_normal_block = generator.make_block();
|
||||
generator.emit<Bytecode::Op::LoadImmediate>(Value(to_underlying(Completion::Type::Throw)));
|
||||
generator.emit<Bytecode::Op::StrictlyEquals>(received_completion_type_register);
|
||||
generator.emit<Bytecode::Op::JumpConditional>(
|
||||
Bytecode::Label { load_completion_and_jump_to_continuation_label_block },
|
||||
Bytecode::Label { awaited_type_is_normal_block });
|
||||
|
||||
// 4. Assert: awaited.[[Type]] is normal.
|
||||
generator.switch_to_basic_block(awaited_type_is_normal_block);
|
||||
|
||||
// 5. Return Completion Record { [[Type]]: return, [[Value]]: awaited.[[Value]], [[Target]]: empty }.
|
||||
generator.emit<Bytecode::Op::LoadImmediate>(Value(to_underlying(Completion::Type::Return)));
|
||||
generator.emit<Bytecode::Op::PutById>(received_completion_register, type_identifier);
|
||||
generator.emit<Bytecode::Op::Jump>(Bytecode::Label { load_completion_and_jump_to_continuation_label_block });
|
||||
|
||||
generator.switch_to_basic_block(load_completion_and_jump_to_continuation_label_block);
|
||||
generator.emit<Bytecode::Op::Load>(received_completion_register);
|
||||
generator.emit<Bytecode::Op::Jump>(continuation_label);
|
||||
}
|
||||
|
||||
Bytecode::CodeGenerationErrorOr<void> YieldExpression::generate_bytecode(Bytecode::Generator& generator) const
|
||||
{
|
||||
VERIFY(generator.is_in_generator_function());
|
||||
|
@ -1598,21 +1670,10 @@ Bytecode::CodeGenerationErrorOr<void> YieldExpression::generate_bytecode(Bytecod
|
|||
auto type_identifier = generator.intern_identifier("type");
|
||||
auto value_identifier = generator.intern_identifier("value");
|
||||
|
||||
auto get_received_completion_type_and_value = [&]() {
|
||||
// The accumulator is set to an object, for example: { "type": 1 (normal), value: 1337 }
|
||||
generator.emit<Bytecode::Op::Store>(received_completion_register);
|
||||
|
||||
generator.emit_get_by_id(type_identifier);
|
||||
generator.emit<Bytecode::Op::Store>(received_completion_type_register);
|
||||
|
||||
generator.emit<Bytecode::Op::Load>(received_completion_register);
|
||||
generator.emit_get_by_id(value_identifier);
|
||||
generator.emit<Bytecode::Op::Store>(received_completion_value_register);
|
||||
};
|
||||
|
||||
if (m_is_yield_from) {
|
||||
// 15.5.5 Runtime Semantics: Evaluation, https://tc39.es/ecma262/#sec-generator-function-definitions-runtime-semantics-evaluation
|
||||
// FIXME: 1. Let generatorKind be GetGeneratorKind().
|
||||
// 1. Let generatorKind be GetGeneratorKind().
|
||||
// NOTE: is_in_async_generator_function differentiates the generator kind.
|
||||
|
||||
// 2. Let exprRef be ? Evaluation of AssignmentExpression.
|
||||
// 3. Let value be ? GetValue(exprRef).
|
||||
|
@ -1620,9 +1681,9 @@ Bytecode::CodeGenerationErrorOr<void> YieldExpression::generate_bytecode(Bytecod
|
|||
TRY(m_argument->generate_bytecode(generator));
|
||||
|
||||
// 4. Let iteratorRecord be ? GetIterator(value, generatorKind).
|
||||
// FIXME: Consider generatorKind.
|
||||
auto iterator_record_register = generator.allocate_register();
|
||||
generator.emit<Bytecode::Op::GetIterator>();
|
||||
auto iterator_hint = generator.is_in_async_generator_function() ? IteratorHint::Async : IteratorHint::Sync;
|
||||
generator.emit<Bytecode::Op::GetIterator>(iterator_hint);
|
||||
generator.emit<Bytecode::Op::Store>(iterator_record_register);
|
||||
|
||||
// 5. Let iterator be iteratorRecord.[[Iterator]].
|
||||
|
@ -1670,7 +1731,9 @@ Bytecode::CodeGenerationErrorOr<void> YieldExpression::generate_bytecode(Bytecod
|
|||
generator.emit_with_extra_register_slots<Bytecode::Op::NewArray>(2, AK::Array { received_completion_value_register, received_completion_value_register });
|
||||
generator.emit<Bytecode::Op::CallWithArgumentArray>(Bytecode::Op::CallType::Call, next_method_register, iterator_register);
|
||||
|
||||
// FIXME: ii. If generatorKind is async, set innerResult to ? Await(innerResult).
|
||||
// ii. If generatorKind is async, set innerResult to ? Await(innerResult).
|
||||
if (generator.is_in_async_generator_function())
|
||||
generate_await(generator, received_completion_register, received_completion_type_register, received_completion_value_register, type_identifier, value_identifier);
|
||||
|
||||
// iii. If innerResult is not an Object, throw a TypeError exception.
|
||||
generator.emit<Bytecode::Op::ThrowIfNotObject>();
|
||||
|
@ -1697,15 +1760,15 @@ Bytecode::CodeGenerationErrorOr<void> YieldExpression::generate_bytecode(Bytecod
|
|||
|
||||
generator.switch_to_basic_block(type_is_normal_not_done_block);
|
||||
|
||||
// FIXME: vi. If generatorKind is async, set received to Completion(AsyncGeneratorYield(? IteratorValue(innerResult))).
|
||||
// vi. If generatorKind is async, set received to Completion(AsyncGeneratorYield(? IteratorValue(innerResult))).
|
||||
// vii. Else, set received to Completion(GeneratorYield(innerResult)).
|
||||
// FIXME: Else,
|
||||
generator.emit<Bytecode::Op::Load>(inner_result_register);
|
||||
|
||||
// FIXME: Yield currently only accepts a Value, not an object conforming to the IteratorResult interface, so we have to do an observable lookup of `value` here.
|
||||
// This only matters for non-async generators.
|
||||
generator.emit<Bytecode::Op::IteratorResultValue>();
|
||||
|
||||
generator.emit<Bytecode::Op::Yield>(Bytecode::Label { continuation_block });
|
||||
generate_yield(generator, Bytecode::Label { continuation_block }, received_completion_register, received_completion_type_register, received_completion_value_register, type_identifier, value_identifier, AwaitBeforeYield::No);
|
||||
|
||||
// b. Else if received.[[Type]] is throw, then
|
||||
generator.switch_to_basic_block(is_type_throw_block);
|
||||
|
@ -1742,7 +1805,9 @@ Bytecode::CodeGenerationErrorOr<void> YieldExpression::generate_bytecode(Bytecod
|
|||
generator.emit_with_extra_register_slots<Bytecode::Op::NewArray>(2, AK::Array { received_completion_value_register, received_completion_value_register });
|
||||
generator.emit<Bytecode::Op::CallWithArgumentArray>(Bytecode::Op::CallType::Call, throw_method_register, iterator_register);
|
||||
|
||||
// FIXME: 2. If generatorKind is async, set innerResult to ? Await(innerResult).
|
||||
// 2. If generatorKind is async, set innerResult to ? Await(innerResult).
|
||||
if (generator.is_in_async_generator_function())
|
||||
generate_await(generator, received_completion_register, received_completion_type_register, received_completion_value_register, type_identifier, value_identifier);
|
||||
|
||||
// 3. NOTE: Exceptions from the inner iterator throw method are propagated. Normal completions from an inner throw method are processed similarly to an inner next.
|
||||
// 4. If innerResult is not an Object, throw a TypeError exception.
|
||||
|
@ -1768,26 +1833,31 @@ Bytecode::CodeGenerationErrorOr<void> YieldExpression::generate_bytecode(Bytecod
|
|||
|
||||
generator.switch_to_basic_block(type_is_throw_not_done_block);
|
||||
|
||||
// FIXME: 7. If generatorKind is async, set received to Completion(AsyncGeneratorYield(? IteratorValue(innerResult))).
|
||||
// 7. If generatorKind is async, set received to Completion(AsyncGeneratorYield(? IteratorValue(innerResult))).
|
||||
// 8. Else, set received to Completion(GeneratorYield(innerResult)).
|
||||
// FIXME: Else,
|
||||
generator.emit<Bytecode::Op::Load>(inner_result_register);
|
||||
|
||||
// FIXME: Yield currently only accepts a Value, not an object conforming to the IteratorResult interface, so we have to do an observable lookup of `value` here.
|
||||
// This only matters for non-async generators.
|
||||
generator.emit<Bytecode::Op::IteratorResultValue>();
|
||||
|
||||
generator.emit<Bytecode::Op::Yield>(Bytecode::Label { continuation_block });
|
||||
generate_yield(generator, Bytecode::Label { continuation_block }, received_completion_register, received_completion_type_register, received_completion_value_register, type_identifier, value_identifier, AwaitBeforeYield::No);
|
||||
|
||||
generator.switch_to_basic_block(throw_method_is_undefined_block);
|
||||
|
||||
// 1. NOTE: If iterator does not have a throw method, this throw is going to terminate the yield* loop. But first we need to give iterator a chance to clean up.
|
||||
|
||||
// 2. Let closeCompletion be Completion Record { [[Type]]: normal, [[Value]]: empty, [[Target]]: empty }.
|
||||
// FIXME: 3. If generatorKind is async, perform ? AsyncIteratorClose(iteratorRecord, closeCompletion).
|
||||
// 4. Else, perform ? IteratorClose(iteratorRecord, closeCompletion).
|
||||
// FIXME: Else,
|
||||
// 3. If generatorKind is async, perform ? AsyncIteratorClose(iteratorRecord, closeCompletion).
|
||||
generator.emit<Bytecode::Op::Load>(iterator_record_register);
|
||||
generator.emit<Bytecode::Op::IteratorClose>(Completion::Type::Normal, Optional<Value> {});
|
||||
if (generator.is_in_async_generator_function()) {
|
||||
// FIXME: This performs `await` outside of the generator!
|
||||
generator.emit<Bytecode::Op::AsyncIteratorClose>(Completion::Type::Normal, Optional<Value> {});
|
||||
}
|
||||
// 4. Else, perform ? IteratorClose(iteratorRecord, closeCompletion).
|
||||
else {
|
||||
generator.emit<Bytecode::Op::IteratorClose>(Completion::Type::Normal, Optional<Value> {});
|
||||
}
|
||||
|
||||
// 5. NOTE: The next step throws a TypeError to indicate that there was a yield* protocol violation: iterator does not have a throw method.
|
||||
// 6. Throw a TypeError exception.
|
||||
|
@ -1817,10 +1887,13 @@ Bytecode::CodeGenerationErrorOr<void> YieldExpression::generate_bytecode(Bytecod
|
|||
|
||||
generator.switch_to_basic_block(return_is_undefined_block);
|
||||
|
||||
// FIXME: 1. If generatorKind is async, set received.[[Value]] to ? Await(received.[[Value]]).
|
||||
// 1. If generatorKind is async, set received.[[Value]] to ? Await(received.[[Value]]).
|
||||
generator.emit<Bytecode::Op::Load>(received_completion_value_register);
|
||||
if (generator.is_in_async_generator_function())
|
||||
generate_await(generator, received_completion_register, received_completion_type_register, received_completion_value_register, type_identifier, value_identifier);
|
||||
|
||||
// 2. Return ? received.
|
||||
// NOTE: This will always be a return completion.
|
||||
generator.emit<Bytecode::Op::Load>(received_completion_value_register);
|
||||
generator.perform_needed_unwinds<Bytecode::Op::Yield>();
|
||||
generator.emit<Bytecode::Op::Yield>(nullptr);
|
||||
|
||||
|
@ -1830,7 +1903,9 @@ Bytecode::CodeGenerationErrorOr<void> YieldExpression::generate_bytecode(Bytecod
|
|||
generator.emit_with_extra_register_slots<Bytecode::Op::NewArray>(2, AK::Array { received_completion_value_register, received_completion_value_register });
|
||||
generator.emit<Bytecode::Op::CallWithArgumentArray>(Bytecode::Op::CallType::Call, return_method_register, iterator_register);
|
||||
|
||||
// FIXME: v. If generatorKind is async, set innerReturnResult to ? Await(innerReturnResult).
|
||||
// v. If generatorKind is async, set innerReturnResult to ? Await(innerReturnResult).
|
||||
if (generator.is_in_async_generator_function())
|
||||
generate_await(generator, received_completion_register, received_completion_type_register, received_completion_value_register, type_identifier, value_identifier);
|
||||
|
||||
// vi. If innerReturnResult is not an Object, throw a TypeError exception.
|
||||
generator.emit<Bytecode::Op::ThrowIfNotObject>();
|
||||
|
@ -1860,18 +1935,18 @@ Bytecode::CodeGenerationErrorOr<void> YieldExpression::generate_bytecode(Bytecod
|
|||
|
||||
generator.switch_to_basic_block(type_is_return_not_done_block);
|
||||
|
||||
// FIXME: ix. If generatorKind is async, set received to Completion(AsyncGeneratorYield(? IteratorValue(innerReturnResult))).
|
||||
// ix. If generatorKind is async, set received to Completion(AsyncGeneratorYield(? IteratorValue(innerReturnResult))).
|
||||
// x. Else, set received to Completion(GeneratorYield(innerReturnResult)).
|
||||
// FIXME: Else,
|
||||
generator.emit<Bytecode::Op::Load>(inner_return_result_register);
|
||||
|
||||
// FIXME: Yield currently only accepts a Value, not an object conforming to the IteratorResult interface, so we have to do an observable lookup of `value` here.
|
||||
// This only matters for non-async generators.
|
||||
generator.emit<Bytecode::Op::IteratorResultValue>();
|
||||
|
||||
generator.emit<Bytecode::Op::Yield>(Bytecode::Label { continuation_block });
|
||||
generate_yield(generator, Bytecode::Label { continuation_block }, received_completion_register, received_completion_type_register, received_completion_value_register, type_identifier, value_identifier, AwaitBeforeYield::No);
|
||||
|
||||
generator.switch_to_basic_block(continuation_block);
|
||||
get_received_completion_type_and_value();
|
||||
get_received_completion_type_and_value(generator, received_completion_register, received_completion_type_register, received_completion_value_register, type_identifier, value_identifier);
|
||||
generator.emit<Bytecode::Op::Jump>(Bytecode::Label { loop_block });
|
||||
|
||||
generator.switch_to_basic_block(loop_end_block);
|
||||
|
@ -1884,9 +1959,9 @@ Bytecode::CodeGenerationErrorOr<void> YieldExpression::generate_bytecode(Bytecod
|
|||
generator.emit<Bytecode::Op::LoadImmediate>(js_undefined());
|
||||
|
||||
auto& continuation_block = generator.make_block();
|
||||
generator.emit<Bytecode::Op::Yield>(Bytecode::Label { continuation_block });
|
||||
generate_yield(generator, Bytecode::Label { continuation_block }, received_completion_register, received_completion_type_register, received_completion_value_register, type_identifier, value_identifier, AwaitBeforeYield::Yes);
|
||||
generator.switch_to_basic_block(continuation_block);
|
||||
get_received_completion_type_and_value();
|
||||
get_received_completion_type_and_value(generator, received_completion_register, received_completion_type_register, received_completion_value_register, type_identifier, value_identifier);
|
||||
|
||||
auto& normal_completion_continuation_block = generator.make_block();
|
||||
auto& throw_completion_continuation_block = generator.make_block();
|
||||
|
@ -2419,32 +2494,15 @@ Bytecode::CodeGenerationErrorOr<void> ThisExpression::generate_bytecode(Bytecode
|
|||
return {};
|
||||
}
|
||||
|
||||
static void generate_await(Bytecode::Generator& generator)
|
||||
static void generate_await(Bytecode::Generator& generator, Bytecode::Register received_completion_register, Bytecode::Register received_completion_type_register, Bytecode::Register received_completion_value_register, Bytecode::IdentifierTableIndex type_identifier, Bytecode::IdentifierTableIndex value_identifier)
|
||||
{
|
||||
VERIFY(generator.is_in_async_function());
|
||||
|
||||
// Transform `await expr` to `yield expr`, see AsyncFunctionDriverWrapper
|
||||
// For that we just need to copy most of the code from YieldExpression
|
||||
auto received_completion_register = generator.allocate_register();
|
||||
auto received_completion_type_register = generator.allocate_register();
|
||||
auto received_completion_value_register = generator.allocate_register();
|
||||
|
||||
auto type_identifier = generator.intern_identifier("type");
|
||||
auto value_identifier = generator.intern_identifier("value");
|
||||
|
||||
auto& continuation_block = generator.make_block();
|
||||
generator.emit<Bytecode::Op::Yield>(Bytecode::Label { continuation_block });
|
||||
generator.emit<Bytecode::Op::Await>(Bytecode::Label { continuation_block });
|
||||
generator.switch_to_basic_block(continuation_block);
|
||||
|
||||
// The accumulator is set to an object, for example: { "type": 1 (normal), value: 1337 }
|
||||
generator.emit<Bytecode::Op::Store>(received_completion_register);
|
||||
|
||||
generator.emit_get_by_id(type_identifier);
|
||||
generator.emit<Bytecode::Op::Store>(received_completion_type_register);
|
||||
|
||||
generator.emit<Bytecode::Op::Load>(received_completion_register);
|
||||
generator.emit_get_by_id(value_identifier);
|
||||
generator.emit<Bytecode::Op::Store>(received_completion_value_register);
|
||||
get_received_completion_type_and_value(generator, received_completion_register, received_completion_type_register, received_completion_value_register, type_identifier, value_identifier);
|
||||
|
||||
auto& normal_completion_continuation_block = generator.make_block();
|
||||
auto& throw_value_block = generator.make_block();
|
||||
|
@ -2455,7 +2513,7 @@ static void generate_await(Bytecode::Generator& generator)
|
|||
Bytecode::Label { normal_completion_continuation_block },
|
||||
Bytecode::Label { throw_value_block });
|
||||
|
||||
// Simplification: The only abrupt completion we receive from AsyncFunctionDriverWrapper is Type::Throw
|
||||
// Simplification: The only abrupt completion we receive from AsyncFunctionDriverWrapper or AsyncGenerator is Type::Throw
|
||||
// So we do not need to account for the Type::Return path
|
||||
generator.switch_to_basic_block(throw_value_block);
|
||||
generator.emit<Bytecode::Op::Load>(received_completion_value_register);
|
||||
|
@ -2469,7 +2527,15 @@ static void generate_await(Bytecode::Generator& generator)
|
|||
Bytecode::CodeGenerationErrorOr<void> AwaitExpression::generate_bytecode(Bytecode::Generator& generator) const
|
||||
{
|
||||
TRY(m_argument->generate_bytecode(generator));
|
||||
generate_await(generator);
|
||||
|
||||
auto received_completion_register = generator.allocate_register();
|
||||
auto received_completion_type_register = generator.allocate_register();
|
||||
auto received_completion_value_register = generator.allocate_register();
|
||||
|
||||
auto type_identifier = generator.intern_identifier("type");
|
||||
auto value_identifier = generator.intern_identifier("value");
|
||||
|
||||
generate_await(generator, received_completion_register, received_completion_type_register, received_completion_value_register, type_identifier, value_identifier);
|
||||
return {};
|
||||
}
|
||||
|
||||
|
@ -2651,8 +2717,16 @@ static Bytecode::CodeGenerationErrorOr<void> for_in_of_body_evaluation(Bytecode:
|
|||
generator.emit<Bytecode::Op::IteratorNext>();
|
||||
|
||||
// b. If iteratorKind is async, set nextResult to ? Await(nextResult).
|
||||
if (iterator_kind == IteratorHint::Async)
|
||||
generate_await(generator);
|
||||
if (iterator_kind == IteratorHint::Async) {
|
||||
auto received_completion_register = generator.allocate_register();
|
||||
auto received_completion_type_register = generator.allocate_register();
|
||||
auto received_completion_value_register = generator.allocate_register();
|
||||
|
||||
auto type_identifier = generator.intern_identifier("type");
|
||||
auto value_identifier = generator.intern_identifier("value");
|
||||
|
||||
generate_await(generator, received_completion_register, received_completion_type_register, received_completion_value_register, type_identifier, value_identifier);
|
||||
}
|
||||
|
||||
// c. If Type(nextResult) is not Object, throw a TypeError exception.
|
||||
generator.emit<Bytecode::Op::ThrowIfNotObject>();
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
/*
|
||||
* Copyright (c) 2022, Linus Groh <linusg@serenityos.org>
|
||||
* Copyright (c) 2023, Luke Wilde <lukew@serenityos.org>
|
||||
*
|
||||
* SPDX-License-Identifier: BSD-2-Clause
|
||||
*/
|
||||
|
@ -7,12 +8,28 @@
|
|||
#include <LibJS/Runtime/AsyncGenerator.h>
|
||||
#include <LibJS/Runtime/AsyncGeneratorPrototype.h>
|
||||
#include <LibJS/Runtime/AsyncGeneratorRequest.h>
|
||||
#include <LibJS/Runtime/ECMAScriptFunctionObject.h>
|
||||
#include <LibJS/Runtime/GlobalObject.h>
|
||||
#include <LibJS/Runtime/PromiseConstructor.h>
|
||||
|
||||
namespace JS {
|
||||
|
||||
AsyncGenerator::AsyncGenerator(Object& prototype)
|
||||
ThrowCompletionOr<NonnullGCPtr<AsyncGenerator>> AsyncGenerator::create(Realm& realm, Value initial_value, ECMAScriptFunctionObject* generating_function, ExecutionContext execution_context, Bytecode::RegisterWindow frame)
|
||||
{
|
||||
auto& vm = realm.vm();
|
||||
// This is "g1.prototype" in figure-2 (https://tc39.es/ecma262/img/figure-2.png)
|
||||
auto generating_function_prototype = TRY(generating_function->get(vm.names.prototype));
|
||||
auto generating_function_prototype_object = TRY(generating_function_prototype.to_object(vm));
|
||||
auto object = MUST_OR_THROW_OOM(realm.heap().allocate<AsyncGenerator>(realm, realm, generating_function_prototype_object, move(execution_context)));
|
||||
object->m_generating_function = generating_function;
|
||||
object->m_frame = move(frame);
|
||||
object->m_previous_value = initial_value;
|
||||
return object;
|
||||
}
|
||||
|
||||
AsyncGenerator::AsyncGenerator(Realm&, Object& prototype, ExecutionContext context)
|
||||
: Object(ConstructWithPrototypeTag::Tag, prototype)
|
||||
, m_async_generator_context(move(context))
|
||||
{
|
||||
}
|
||||
|
||||
|
@ -24,6 +41,496 @@ void AsyncGenerator::visit_edges(Cell::Visitor& visitor)
|
|||
visitor.visit(*request.completion.value());
|
||||
visitor.visit(request.capability);
|
||||
}
|
||||
m_async_generator_context.visit_edges(visitor);
|
||||
visitor.visit(m_generating_function);
|
||||
visitor.visit(m_previous_value);
|
||||
if (m_frame.has_value())
|
||||
m_frame->visit_edges(visitor);
|
||||
visitor.visit(m_current_promise);
|
||||
}
|
||||
|
||||
// 27.6.3.4 AsyncGeneratorEnqueue ( generator, completion, promiseCapability ), https://tc39.es/ecma262/#sec-asyncgeneratorenqueue
|
||||
void AsyncGenerator::async_generator_enqueue(Completion completion, NonnullGCPtr<PromiseCapability> promise_capability)
|
||||
{
|
||||
// 1. Let request be AsyncGeneratorRequest { [[Completion]]: completion, [[Capability]]: promiseCapability }.
|
||||
auto request = AsyncGeneratorRequest { .completion = move(completion), .capability = promise_capability };
|
||||
|
||||
// 2. Append request to generator.[[AsyncGeneratorQueue]].
|
||||
m_async_generator_queue.append(move(request));
|
||||
|
||||
// 3. Return unused.
|
||||
}
|
||||
|
||||
void AsyncGenerator::set_async_generator_state(Badge<AsyncGeneratorPrototype>, AsyncGenerator::State value)
|
||||
{
|
||||
m_async_generator_state = value;
|
||||
}
|
||||
|
||||
// 27.7.5.3 Await ( value ), https://tc39.es/ecma262/#await
|
||||
ThrowCompletionOr<void> AsyncGenerator::await(Value value)
|
||||
{
|
||||
auto& vm = this->vm();
|
||||
auto& realm = *vm.current_realm();
|
||||
|
||||
// 1. Let asyncContext be the running execution context.
|
||||
auto& async_context = vm.running_execution_context();
|
||||
|
||||
// 2. Let promise be ? PromiseResolve(%Promise%, value).
|
||||
auto* promise_object = TRY(promise_resolve(vm, realm.intrinsics().promise_constructor(), value));
|
||||
|
||||
// 3. Let fulfilledClosure be a new Abstract Closure with parameters (v) that captures asyncContext and performs the
|
||||
// following steps when called:
|
||||
auto fulfilled_closure = [this, &async_context](VM& vm) -> ThrowCompletionOr<Value> {
|
||||
auto value = vm.argument(0);
|
||||
|
||||
// a. Let prevContext be the running execution context.
|
||||
auto& prev_context = vm.running_execution_context();
|
||||
|
||||
// FIXME: b. Suspend prevContext.
|
||||
|
||||
// c. Push asyncContext onto the execution context stack; asyncContext is now the running execution context.
|
||||
TRY(vm.push_execution_context(async_context, {}));
|
||||
|
||||
// d. Resume the suspended evaluation of asyncContext using NormalCompletion(v) as the result of the operation that
|
||||
// suspended it.
|
||||
execute(vm, normal_completion(value));
|
||||
|
||||
// e. Assert: When we reach this step, asyncContext has already been removed from the execution context stack and
|
||||
// prevContext is the currently running execution context.
|
||||
VERIFY(&vm.running_execution_context() == &prev_context);
|
||||
|
||||
// f. Return undefined.
|
||||
return js_undefined();
|
||||
};
|
||||
|
||||
// 4. Let onFulfilled be CreateBuiltinFunction(fulfilledClosure, 1, "", « »).
|
||||
auto on_fulfilled = NativeFunction::create(realm, move(fulfilled_closure), 1, "");
|
||||
|
||||
// 5. Let rejectedClosure be a new Abstract Closure with parameters (reason) that captures asyncContext and performs the
|
||||
// following steps when called:
|
||||
auto rejected_closure = [this, &async_context](VM& vm) -> ThrowCompletionOr<Value> {
|
||||
auto reason = vm.argument(0);
|
||||
|
||||
// a. Let prevContext be the running execution context.
|
||||
auto& prev_context = vm.running_execution_context();
|
||||
|
||||
// FIXME: b. Suspend prevContext.
|
||||
|
||||
// c. Push asyncContext onto the execution context stack; asyncContext is now the running execution context.
|
||||
TRY(vm.push_execution_context(async_context, {}));
|
||||
|
||||
// d. Resume the suspended evaluation of asyncContext using ThrowCompletion(reason) as the result of the operation that
|
||||
// suspended it.
|
||||
execute(vm, throw_completion(reason));
|
||||
|
||||
// e. Assert: When we reach this step, asyncContext has already been removed from the execution context stack and
|
||||
// prevContext is the currently running execution context.
|
||||
VERIFY(&vm.running_execution_context() == &prev_context);
|
||||
|
||||
// f. Return undefined.
|
||||
return js_undefined();
|
||||
};
|
||||
|
||||
// 6. Let onRejected be CreateBuiltinFunction(rejectedClosure, 1, "", « »).
|
||||
auto on_rejected = NativeFunction::create(realm, move(rejected_closure), 1, "");
|
||||
|
||||
// 7. Perform PerformPromiseThen(promise, onFulfilled, onRejected).
|
||||
m_current_promise = verify_cast<Promise>(promise_object);
|
||||
m_current_promise->perform_then(on_fulfilled, on_rejected, {});
|
||||
|
||||
// 8. Remove asyncContext from the execution context stack and restore the execution context that is at the top of the
|
||||
// execution context stack as the running execution context.
|
||||
vm.pop_execution_context();
|
||||
|
||||
// NOTE: None of these are necessary. 10-12 are handled by step d of the above lambdas.
|
||||
// 9. Let callerContext be the running execution context.
|
||||
// 10. Resume callerContext passing empty. If asyncContext is ever resumed again, let completion be the Completion Record with which it is resumed.
|
||||
// 11. Assert: If control reaches here, then asyncContext is the running execution context again.
|
||||
// 12. Return completion.
|
||||
return {};
|
||||
}
|
||||
|
||||
void AsyncGenerator::execute(VM& vm, Completion completion)
|
||||
{
|
||||
while (true) {
|
||||
// Loosely based on step 4 of https://tc39.es/ecma262/#sec-asyncgeneratorstart
|
||||
VERIFY(completion.value().has_value());
|
||||
|
||||
auto generated_value = [](Value value) -> Value {
|
||||
if (value.is_object())
|
||||
return value.as_object().get_without_side_effects("result");
|
||||
return value.is_empty() ? js_undefined() : value;
|
||||
};
|
||||
|
||||
auto generated_continuation = [&](Value value) -> Bytecode::BasicBlock const* {
|
||||
if (value.is_object()) {
|
||||
auto number_value = value.as_object().get_without_side_effects("continuation");
|
||||
return reinterpret_cast<Bytecode::BasicBlock const*>(static_cast<u64>(number_value.as_double()));
|
||||
}
|
||||
return nullptr;
|
||||
};
|
||||
|
||||
auto generated_is_await = [](Value value) -> bool {
|
||||
if (value.is_object())
|
||||
return value.as_object().get_without_side_effects("isAwait").as_bool();
|
||||
return false;
|
||||
};
|
||||
|
||||
auto& realm = *vm.current_realm();
|
||||
auto completion_object = Object::create(realm, nullptr);
|
||||
completion_object->define_direct_property(vm.names.type, Value(to_underlying(completion.type())), default_attributes);
|
||||
completion_object->define_direct_property(vm.names.value, completion.value().value(), default_attributes);
|
||||
|
||||
auto& bytecode_interpreter = vm.bytecode_interpreter();
|
||||
|
||||
auto const* next_block = generated_continuation(m_previous_value);
|
||||
|
||||
// We should never enter `execute` again after the generator is complete.
|
||||
VERIFY(next_block);
|
||||
|
||||
VERIFY(!m_generating_function->bytecode_executable()->basic_blocks.find_if([next_block](auto& block) { return block == next_block; }).is_end());
|
||||
|
||||
Bytecode::RegisterWindow* frame = nullptr;
|
||||
if (m_frame.has_value())
|
||||
frame = &m_frame.value();
|
||||
|
||||
if (frame)
|
||||
frame->registers[0] = completion_object;
|
||||
else
|
||||
bytecode_interpreter.accumulator() = completion_object;
|
||||
|
||||
auto next_result = bytecode_interpreter.run_and_return_frame(realm, *m_generating_function->bytecode_executable(), next_block, frame);
|
||||
|
||||
if (!m_frame.has_value())
|
||||
m_frame = move(*next_result.frame);
|
||||
|
||||
auto result_value = move(next_result.value);
|
||||
if (!result_value.is_throw_completion()) {
|
||||
m_previous_value = result_value.release_value();
|
||||
auto value = generated_value(m_previous_value);
|
||||
bool is_await = generated_is_await(m_previous_value);
|
||||
|
||||
if (is_await) {
|
||||
auto await_result = this->await(value);
|
||||
if (await_result.is_throw_completion()) {
|
||||
completion = await_result.release_error();
|
||||
continue;
|
||||
}
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
bool done = result_value.is_throw_completion() || generated_continuation(m_previous_value) == nullptr;
|
||||
if (!done) {
|
||||
// 27.6.3.8 AsyncGeneratorYield ( value ), https://tc39.es/ecma262/#sec-asyncgeneratoryield
|
||||
// 1. Let genContext be the running execution context.
|
||||
// 2. Assert: genContext is the execution context of a generator.
|
||||
// 3. Let generator be the value of the Generator component of genContext.
|
||||
// 4. Assert: GetGeneratorKind() is async.
|
||||
// NOTE: genContext is `m_async_generator_context`, generator is `this`.
|
||||
|
||||
// 5. Let completion be NormalCompletion(value).
|
||||
auto value = generated_value(m_previous_value);
|
||||
auto yield_completion = normal_completion(value);
|
||||
|
||||
// 6. Assert: The execution context stack has at least two elements.
|
||||
VERIFY(vm.execution_context_stack().size() >= 2);
|
||||
|
||||
// 7. Let previousContext be the second to top element of the execution context stack.
|
||||
auto& previous_context = vm.execution_context_stack().at(vm.execution_context_stack().size() - 2);
|
||||
|
||||
// 8. Let previousRealm be previousContext's Realm.
|
||||
auto previous_realm = previous_context->realm;
|
||||
|
||||
// 9. Perform AsyncGeneratorCompleteStep(generator, completion, false, previousRealm).
|
||||
complete_step(yield_completion, false, previous_realm.ptr());
|
||||
|
||||
// 10. Let queue be generator.[[AsyncGeneratorQueue]].
|
||||
auto& queue = m_async_generator_queue;
|
||||
|
||||
// 11. If queue is not empty, then
|
||||
if (!queue.is_empty()) {
|
||||
// a. NOTE: Execution continues without suspending the generator.
|
||||
// b. Let toYield be the first element of queue.
|
||||
auto& to_yield = queue.first();
|
||||
|
||||
// c. Let resumptionValue be Completion(toYield.[[Completion]]).
|
||||
completion = Completion(to_yield.completion);
|
||||
|
||||
// d. Return ? AsyncGeneratorUnwrapYieldResumption(resumptionValue).
|
||||
// NOTE: AsyncGeneratorUnwrapYieldResumption is performed inside the continuation block inside the generator,
|
||||
// so we just need to enter the generator again.
|
||||
continue;
|
||||
}
|
||||
// 12. Else,
|
||||
else {
|
||||
// a. Set generator.[[AsyncGeneratorState]] to suspendedYield.
|
||||
m_async_generator_state = State::SuspendedYield;
|
||||
|
||||
// b. Remove genContext from the execution context stack and restore the execution context that is at the top of the
|
||||
// execution context stack as the running execution context.
|
||||
vm.pop_execution_context();
|
||||
|
||||
// c. Let callerContext be the running execution context.
|
||||
// d. Resume callerContext passing undefined. If genContext is ever resumed again, let resumptionValue be the Completion Record with which it is resumed.
|
||||
// e. Assert: If control reaches here, then genContext is the running execution context again.
|
||||
// f. Return ? AsyncGeneratorUnwrapYieldResumption(resumptionValue).
|
||||
// NOTE: e-f are performed whenever someone calls `execute` again.
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
// 27.6.3.2 AsyncGeneratorStart ( generator, generatorBody ), https://tc39.es/ecma262/#sec-asyncgeneratorstart
|
||||
// 4.e. Assert: If we return here, the async generator either threw an exception or performed either an implicit or explicit return.
|
||||
// 4.f. Remove acGenContext from the execution context stack and restore the execution context that is at the top of the execution context stack as the running execution context.
|
||||
vm.pop_execution_context();
|
||||
|
||||
// 4.g. Set acGenerator.[[AsyncGeneratorState]] to completed.
|
||||
m_async_generator_state = State::Completed;
|
||||
|
||||
// 4.h. If result.[[Type]] is normal, set result to NormalCompletion(undefined).
|
||||
// 4.i. If result.[[Type]] is return, set result to NormalCompletion(result.[[Value]]).
|
||||
Completion result;
|
||||
if (!result_value.is_throw_completion()) {
|
||||
result = normal_completion(generated_value(m_previous_value));
|
||||
} else {
|
||||
result = result_value.release_error();
|
||||
}
|
||||
|
||||
// 4.j. Perform AsyncGeneratorCompleteStep(acGenerator, result, true).
|
||||
complete_step(result, true);
|
||||
|
||||
// 4.k. Perform AsyncGeneratorDrainQueue(acGenerator).
|
||||
drain_queue();
|
||||
|
||||
// 4.l. Return undefined.
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
// 27.6.3.6 AsyncGeneratorResume ( generator, completion ), https://tc39.es/ecma262/#sec-asyncgeneratorresume
|
||||
ThrowCompletionOr<void> AsyncGenerator::resume(VM& vm, Completion completion)
|
||||
{
|
||||
// 1. Assert: generator.[[AsyncGeneratorState]] is either suspendedStart or suspendedYield.
|
||||
VERIFY(m_async_generator_state == State::SuspendedStart || m_async_generator_state == State::SuspendedYield);
|
||||
|
||||
// 2. Let genContext be generator.[[AsyncGeneratorContext]].
|
||||
auto& generator_context = m_async_generator_context;
|
||||
|
||||
// 3. Let callerContext be the running execution context.
|
||||
auto const& caller_context = vm.running_execution_context();
|
||||
|
||||
// FIXME: 4. Suspend callerContext.
|
||||
|
||||
// 5. Set generator.[[AsyncGeneratorState]] to executing.
|
||||
m_async_generator_state = State::Executing;
|
||||
|
||||
// 6. Push genContext onto the execution context stack; genContext is now the running execution context.
|
||||
TRY(vm.push_execution_context(generator_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.
|
||||
// 8. Assert: result is never an abrupt completion.
|
||||
execute(vm, 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.
|
||||
VERIFY(&vm.running_execution_context() == &caller_context);
|
||||
|
||||
// 10. Return unused.
|
||||
return {};
|
||||
}
|
||||
|
||||
// 27.6.3.9 AsyncGeneratorAwaitReturn ( generator ), https://tc39.es/ecma262/#sec-asyncgeneratorawaitreturn
|
||||
ThrowCompletionOr<void> AsyncGenerator::await_return()
|
||||
{
|
||||
auto& vm = this->vm();
|
||||
auto& realm = *vm.current_realm();
|
||||
|
||||
// 1. Let queue be generator.[[AsyncGeneratorQueue]].
|
||||
auto& queue = m_async_generator_queue;
|
||||
|
||||
// 2. Assert: queue is not empty.
|
||||
VERIFY(!queue.is_empty());
|
||||
|
||||
// 3. Let next be the first element of queue.
|
||||
auto& next = m_async_generator_queue.first();
|
||||
|
||||
// 4. Let completion be Completion(next.[[Completion]]).
|
||||
auto completion = Completion(next.completion);
|
||||
|
||||
// 5. Assert: completion.[[Type]] is return.
|
||||
VERIFY(completion.type() == Completion::Type::Return);
|
||||
|
||||
// 6. Let promise be ? PromiseResolve(%Promise%, completion.[[Value]]).
|
||||
auto* promise = TRY(promise_resolve(vm, realm.intrinsics().promise_constructor(), completion.value().value()));
|
||||
|
||||
// 7. Let fulfilledClosure be a new Abstract Closure with parameters (value) that captures generator and performs
|
||||
// the following steps when called:
|
||||
auto fulfilled_closure = [this](VM& vm) -> ThrowCompletionOr<Value> {
|
||||
// a. Set generator.[[AsyncGeneratorState]] to completed.
|
||||
m_async_generator_state = State::Completed;
|
||||
|
||||
// b. Let result be NormalCompletion(value).
|
||||
auto result = normal_completion(vm.argument(0));
|
||||
|
||||
// c. Perform AsyncGeneratorCompleteStep(generator, result, true).
|
||||
complete_step(result, true);
|
||||
|
||||
// d. Perform AsyncGeneratorDrainQueue(generator).
|
||||
drain_queue();
|
||||
|
||||
// e. Return undefined.
|
||||
return js_undefined();
|
||||
};
|
||||
|
||||
// 8. Let onFulfilled be CreateBuiltinFunction(fulfilledClosure, 1, "", « »).
|
||||
auto on_fulfilled = NativeFunction::create(realm, move(fulfilled_closure), 1, "");
|
||||
|
||||
// 9. Let rejectedClosure be a new Abstract Closure with parameters (reason) that captures generator and performs
|
||||
// the following steps when called:
|
||||
auto rejected_closure = [this](VM& vm) -> ThrowCompletionOr<Value> {
|
||||
// a. Set generator.[[AsyncGeneratorState]] to completed.
|
||||
m_async_generator_state = State::Completed;
|
||||
|
||||
// b. Let result be ThrowCompletion(reason).
|
||||
auto result = throw_completion(vm.argument(0));
|
||||
|
||||
// c. Perform AsyncGeneratorCompleteStep(generator, result, true).
|
||||
complete_step(result, true);
|
||||
|
||||
// d. Perform AsyncGeneratorDrainQueue(generator).
|
||||
drain_queue();
|
||||
|
||||
// e. Return undefined.
|
||||
return js_undefined();
|
||||
};
|
||||
|
||||
// 10. Let onRejected be CreateBuiltinFunction(rejectedClosure, 1, "", « »).
|
||||
auto on_rejected = NativeFunction::create(realm, move(rejected_closure), 1, "");
|
||||
|
||||
// 11. Perform PerformPromiseThen(promise, onFulfilled, onRejected).
|
||||
// NOTE: await_return should only be called when the generator is in SuspendedStart or Completed state,
|
||||
// so an await shouldn't be running currently, so it should be safe to overwrite m_current_promise.
|
||||
m_current_promise = verify_cast<Promise>(promise);
|
||||
m_current_promise->perform_then(on_fulfilled, on_rejected, {});
|
||||
|
||||
// 12. Return unused.
|
||||
return {};
|
||||
}
|
||||
|
||||
// 27.6.3.5 AsyncGeneratorCompleteStep ( generator, completion, done [ , realm ] ), https://tc39.es/ecma262/#sec-asyncgeneratorcompletestep
|
||||
void AsyncGenerator::complete_step(Completion completion, bool done, Realm* realm)
|
||||
{
|
||||
auto& vm = this->vm();
|
||||
|
||||
// 1. Assert: generator.[[AsyncGeneratorQueue]] is not empty.
|
||||
VERIFY(!m_async_generator_queue.is_empty());
|
||||
|
||||
// 2. Let next be the first element of generator.[[AsyncGeneratorQueue]].
|
||||
// 3. Remove the first element from generator.[[AsyncGeneratorQueue]].
|
||||
auto next = m_async_generator_queue.take_first();
|
||||
|
||||
// 4. Let promiseCapability be next.[[Capability]].
|
||||
auto promise_capability = next.capability;
|
||||
|
||||
// 5. Let value be completion.[[Value]].
|
||||
auto value = completion.value().value();
|
||||
|
||||
// 6. If completion.[[Type]] is throw, then
|
||||
if (completion.type() == Completion::Type::Throw) {
|
||||
// a. Perform ! Call(promiseCapability.[[Reject]], undefined, « value »).
|
||||
MUST(call(vm, *promise_capability->reject(), js_undefined(), value));
|
||||
}
|
||||
// 7. Else,
|
||||
else {
|
||||
// a. Assert: completion.[[Type]] is normal.
|
||||
VERIFY(completion.type() == Completion::Type::Normal);
|
||||
|
||||
GCPtr<Object> iterator_result;
|
||||
|
||||
// b. If realm is present, then
|
||||
if (realm) {
|
||||
// i. Let oldRealm be the running execution context's Realm.
|
||||
auto old_realm = vm.running_execution_context().realm;
|
||||
|
||||
// ii. Set the running execution context's Realm to realm.
|
||||
vm.running_execution_context().realm = realm;
|
||||
|
||||
// iii. Let iteratorResult be CreateIterResultObject(value, done).
|
||||
iterator_result = create_iterator_result_object(vm, value, done);
|
||||
|
||||
// iv. Set the running execution context's Realm to oldRealm.
|
||||
vm.running_execution_context().realm = old_realm;
|
||||
}
|
||||
// c. Else,
|
||||
else {
|
||||
// i. Let iteratorResult be CreateIterResultObject(value, done).
|
||||
iterator_result = create_iterator_result_object(vm, value, done);
|
||||
}
|
||||
|
||||
VERIFY(iterator_result);
|
||||
|
||||
// d. Perform ! Call(promiseCapability.[[Resolve]], undefined, « iteratorResult »).
|
||||
MUST(call(vm, *promise_capability->resolve(), js_undefined(), iterator_result));
|
||||
}
|
||||
|
||||
// 8. Return unused.
|
||||
}
|
||||
|
||||
// 27.6.3.10 AsyncGeneratorDrainQueue ( generator ), https://tc39.es/ecma262/#sec-asyncgeneratordrainqueue
|
||||
void AsyncGenerator::drain_queue()
|
||||
{
|
||||
// 1. Assert: generator.[[AsyncGeneratorState]] is completed.
|
||||
VERIFY(m_async_generator_state == State::Completed);
|
||||
|
||||
// 2. Let queue be generator.[[AsyncGeneratorQueue]].
|
||||
auto& queue = m_async_generator_queue;
|
||||
|
||||
// 3. If queue is empty, return unused.
|
||||
if (queue.is_empty())
|
||||
return;
|
||||
|
||||
// 4. Let done be false.
|
||||
bool done = false;
|
||||
|
||||
// 5. Repeat, while done is false,
|
||||
while (!done) {
|
||||
// a. Let next be the first element of queue.
|
||||
auto& next = m_async_generator_queue.first();
|
||||
|
||||
// b. Let completion be Completion(next.[[Completion]]).
|
||||
auto completion = Completion(next.completion);
|
||||
|
||||
// c. If completion.[[Type]] is return, then
|
||||
if (completion.type() == Completion::Type::Return) {
|
||||
// i. Set generator.[[AsyncGeneratorState]] to awaiting-return.
|
||||
m_async_generator_state = State::AwaitingReturn;
|
||||
|
||||
// ii. Perform ! AsyncGeneratorAwaitReturn(generator).
|
||||
MUST(await_return());
|
||||
|
||||
// iii. Set done to true.
|
||||
done = true;
|
||||
}
|
||||
// d. Else,
|
||||
else {
|
||||
// i. If completion.[[Type]] is normal, then
|
||||
if (completion.type() == Completion::Type::Normal) {
|
||||
// 1. Set completion to NormalCompletion(undefined).
|
||||
completion = normal_completion(js_undefined());
|
||||
}
|
||||
|
||||
// ii. Perform AsyncGeneratorCompleteStep(generator, completion, true).
|
||||
complete_step(completion, true);
|
||||
|
||||
// iii. If queue is empty, set done to true.
|
||||
if (queue.is_empty())
|
||||
done = true;
|
||||
}
|
||||
}
|
||||
|
||||
// 6. Return unused.
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
/*
|
||||
* Copyright (c) 2022, Linus Groh <linusg@serenityos.org>
|
||||
* Copyright (c) 2023, Luke Wilde <lukew@serenityos.org>
|
||||
*
|
||||
* SPDX-License-Identifier: BSD-2-Clause
|
||||
*/
|
||||
|
@ -7,6 +8,7 @@
|
|||
#pragma once
|
||||
|
||||
#include <AK/Variant.h>
|
||||
#include <LibJS/Bytecode/Interpreter.h>
|
||||
#include <LibJS/Runtime/ExecutionContext.h>
|
||||
#include <LibJS/Runtime/Object.h>
|
||||
|
||||
|
@ -25,21 +27,40 @@ public:
|
|||
Completed,
|
||||
};
|
||||
|
||||
static ThrowCompletionOr<NonnullGCPtr<AsyncGenerator>> create(Realm&, Value, ECMAScriptFunctionObject*, ExecutionContext, Bytecode::RegisterWindow);
|
||||
|
||||
virtual ~AsyncGenerator() override = default;
|
||||
|
||||
void async_generator_enqueue(Completion, NonnullGCPtr<PromiseCapability>);
|
||||
ThrowCompletionOr<void> resume(VM&, Completion completion);
|
||||
ThrowCompletionOr<void> await_return();
|
||||
void complete_step(Completion, bool done, Realm* realm = nullptr);
|
||||
void drain_queue();
|
||||
|
||||
State async_generator_state() const { return m_async_generator_state; }
|
||||
void set_async_generator_state(Badge<AsyncGeneratorPrototype>, State value);
|
||||
|
||||
Optional<String> const& generator_brand() const { return m_generator_brand; }
|
||||
|
||||
private:
|
||||
explicit AsyncGenerator(Object& prototype);
|
||||
AsyncGenerator(Realm&, Object& prototype, ExecutionContext);
|
||||
|
||||
virtual void visit_edges(Cell::Visitor&) override;
|
||||
|
||||
void execute(VM&, Completion completion);
|
||||
ThrowCompletionOr<void> await(Value);
|
||||
|
||||
// At the time of constructing an AsyncGenerator, we still need to point to an
|
||||
// execution context on the stack, but later need to 'adopt' it.
|
||||
using ExecutionContextVariant = Variant<ExecutionContext, ExecutionContext*, Empty>;
|
||||
State m_async_generator_state { State::SuspendedStart }; // [[AsyncGeneratorState]]
|
||||
ExecutionContext m_async_generator_context; // [[AsyncGeneratorContext]]
|
||||
Vector<AsyncGeneratorRequest> m_async_generator_queue; // [[AsyncGeneratorQueue]]
|
||||
Optional<String> m_generator_brand; // [[GeneratorBrand]]
|
||||
|
||||
Optional<State> m_async_generator_state; // [[AsyncGeneratorState]]
|
||||
ExecutionContextVariant m_async_generator_context; // [[AsyncGeneratorContext]]
|
||||
Vector<AsyncGeneratorRequest> m_async_generator_queue; // [[AsyncGeneratorQueue]]
|
||||
Optional<DeprecatedString> m_generator_brand; // [[GeneratorBrand]]
|
||||
GCPtr<ECMAScriptFunctionObject> m_generating_function;
|
||||
Value m_previous_value;
|
||||
Optional<Bytecode::RegisterWindow> m_frame;
|
||||
GCPtr<Promise> m_current_promise;
|
||||
};
|
||||
|
||||
}
|
||||
|
|
|
@ -1,10 +1,14 @@
|
|||
/*
|
||||
* Copyright (c) 2022-2023, Linus Groh <linusg@serenityos.org>
|
||||
* Copyright (c) 2023, Luke Wilde <lukew@serenityos.org>
|
||||
*
|
||||
* SPDX-License-Identifier: BSD-2-Clause
|
||||
*/
|
||||
|
||||
#include <LibJS/Runtime/AsyncGeneratorPrototype.h>
|
||||
#include <LibJS/Runtime/IteratorOperations.h>
|
||||
#include <LibJS/Runtime/PromiseCapability.h>
|
||||
#include <LibJS/Runtime/PromiseConstructor.h>
|
||||
|
||||
namespace JS {
|
||||
|
||||
|
@ -18,6 +22,10 @@ ThrowCompletionOr<void> AsyncGeneratorPrototype::initialize(Realm& realm)
|
|||
{
|
||||
auto& vm = this->vm();
|
||||
MUST_OR_THROW_OOM(Base::initialize(realm));
|
||||
u8 attr = Attribute::Writable | Attribute::Configurable;
|
||||
define_native_function(realm, vm.names.next, next, 1, attr);
|
||||
define_native_function(realm, vm.names.return_, return_, 1, attr);
|
||||
define_native_function(realm, vm.names.throw_, throw_, 1, attr);
|
||||
|
||||
// 27.6.1.5 AsyncGenerator.prototype [ @@toStringTag ], https://tc39.es/ecma262/#sec-asyncgenerator-prototype-tostringtag
|
||||
define_direct_property(vm.well_known_symbol_to_string_tag(), MUST_OR_THROW_OOM(PrimitiveString::create(vm, "AsyncGenerator"sv)), Attribute::Configurable);
|
||||
|
@ -25,4 +33,180 @@ ThrowCompletionOr<void> AsyncGeneratorPrototype::initialize(Realm& realm)
|
|||
return {};
|
||||
}
|
||||
|
||||
// 27.6.3.3 AsyncGeneratorValidate ( generator, generatorBrand ), https://tc39.es/ecma262/#sec-asyncgeneratorvalidate
|
||||
static ThrowCompletionOr<NonnullGCPtr<AsyncGenerator>> async_generator_validate(VM& vm, Value generator, Optional<String> generator_brand)
|
||||
{
|
||||
// 1. Perform ? RequireInternalSlot(generator, [[AsyncGeneratorContext]]).
|
||||
// 2. Perform ? RequireInternalSlot(generator, [[AsyncGeneratorState]]).
|
||||
// 3. Perform ? RequireInternalSlot(generator, [[AsyncGeneratorQueue]]).
|
||||
if (!generator.is_object() || !is<AsyncGenerator>(generator.as_object()))
|
||||
return vm.throw_completion<TypeError>(ErrorType::NotAnObjectOfType, "AsyncGenerator");
|
||||
|
||||
auto& async_generator = static_cast<AsyncGenerator&>(generator.as_object());
|
||||
|
||||
// 4. If generator.[[GeneratorBrand]] is not generatorBrand, throw a TypeError exception.
|
||||
if (async_generator.generator_brand() != generator_brand)
|
||||
return vm.throw_completion<TypeError>(ErrorType::GeneratorBrandMismatch, async_generator.generator_brand().value_or("emp"_short_string), generator_brand.value_or("emp"_short_string));
|
||||
|
||||
// 5. Return unused.
|
||||
return async_generator;
|
||||
}
|
||||
|
||||
// 27.6.1.2 AsyncGenerator.prototype.next ( value ), https://tc39.es/ecma262/#sec-asyncgenerator-prototype-next
|
||||
JS_DEFINE_NATIVE_FUNCTION(AsyncGeneratorPrototype::next)
|
||||
{
|
||||
auto& realm = *vm.current_realm();
|
||||
|
||||
// 1. Let generator be the this value.
|
||||
auto generator_this_value = vm.this_value();
|
||||
|
||||
// 2. Let promiseCapability be ! NewPromiseCapability(%Promise%).
|
||||
auto promise_capability = MUST(new_promise_capability(vm, realm.intrinsics().promise_constructor()));
|
||||
|
||||
// 3. Let result be Completion(AsyncGeneratorValidate(generator, empty)).
|
||||
// 4. IfAbruptRejectPromise(result, promiseCapability).
|
||||
auto generator = TRY_OR_REJECT(vm, promise_capability, async_generator_validate(vm, generator_this_value, OptionalNone {}));
|
||||
|
||||
// 5. Let state be generator.[[AsyncGeneratorState]].
|
||||
auto state = generator->async_generator_state();
|
||||
|
||||
// 6. If state is completed, then
|
||||
if (state == AsyncGenerator::State::Completed) {
|
||||
// a. Let iteratorResult be CreateIterResultObject(undefined, true).
|
||||
auto iterator_result = create_iterator_result_object(vm, js_undefined(), true);
|
||||
|
||||
// b. Perform ! Call(promiseCapability.[[Resolve]], undefined, « iteratorResult »).
|
||||
MUST(call(vm, *promise_capability->resolve(), js_undefined(), iterator_result));
|
||||
|
||||
// c. Return promiseCapability.[[Promise]].
|
||||
return promise_capability->promise();
|
||||
}
|
||||
|
||||
// 7. Let completion be NormalCompletion(value).
|
||||
auto completion = normal_completion(vm.argument(0));
|
||||
|
||||
// 8. Perform AsyncGeneratorEnqueue(generator, completion, promiseCapability).
|
||||
generator->async_generator_enqueue(completion, promise_capability);
|
||||
|
||||
// 9. If state is either suspendedStart or suspendedYield, then
|
||||
if (state == AsyncGenerator::State::SuspendedStart || state == AsyncGenerator::State::SuspendedYield) {
|
||||
// a. Perform AsyncGeneratorResume(generator, completion).
|
||||
TRY_OR_REJECT(vm, promise_capability, generator->resume(vm, completion));
|
||||
}
|
||||
// 10. Else,
|
||||
else {
|
||||
// a. Assert: state is either executing or awaiting-return.
|
||||
VERIFY(state == AsyncGenerator::State::Executing || state == AsyncGenerator::State::AwaitingReturn);
|
||||
}
|
||||
|
||||
// 11. Return promiseCapability.[[Promise]].
|
||||
return promise_capability->promise();
|
||||
}
|
||||
|
||||
// 27.6.1.3 AsyncGenerator.prototype.return ( value ), https://tc39.es/ecma262/#sec-asyncgenerator-prototype-return
|
||||
JS_DEFINE_NATIVE_FUNCTION(AsyncGeneratorPrototype::return_)
|
||||
{
|
||||
auto& realm = *vm.current_realm();
|
||||
|
||||
// 1. Let generator be the this value.
|
||||
auto generator_this_value = vm.this_value();
|
||||
|
||||
// 2. Let promiseCapability be ! NewPromiseCapability(%Promise%).
|
||||
auto promise_capability = MUST(new_promise_capability(vm, realm.intrinsics().promise_constructor()));
|
||||
|
||||
// 3. Let result be Completion(AsyncGeneratorValidate(generator, empty)).
|
||||
// 4. IfAbruptRejectPromise(result, promiseCapability).
|
||||
auto generator = TRY_OR_REJECT(vm, promise_capability, async_generator_validate(vm, generator_this_value, OptionalNone {}));
|
||||
|
||||
// 5. Let completion be Completion Record { [[Type]]: return, [[Value]]: value, [[Target]]: empty }.
|
||||
auto completion = Completion(Completion::Type::Return, vm.argument(0), {});
|
||||
|
||||
// 6. Perform AsyncGeneratorEnqueue(generator, completion, promiseCapability).
|
||||
generator->async_generator_enqueue(completion, promise_capability);
|
||||
|
||||
// 7. Let state be generator.[[AsyncGeneratorState]].
|
||||
auto state = generator->async_generator_state();
|
||||
|
||||
// 8. If state is either suspendedStart or completed, then
|
||||
if (state == AsyncGenerator::State::SuspendedStart || state == AsyncGenerator::State::Completed) {
|
||||
// a. Set generator.[[AsyncGeneratorState]] to awaiting-return.
|
||||
generator->set_async_generator_state({}, AsyncGenerator::State::AwaitingReturn);
|
||||
|
||||
// b. Perform ! AsyncGeneratorAwaitReturn(generator).
|
||||
MUST(generator->await_return());
|
||||
}
|
||||
// 9. Else if state is suspendedYield, then
|
||||
else if (state == AsyncGenerator::State::SuspendedYield) {
|
||||
// a. Perform AsyncGeneratorResume(generator, completion).
|
||||
TRY_OR_REJECT(vm, promise_capability, generator->resume(vm, completion));
|
||||
}
|
||||
// 10. Else,
|
||||
else {
|
||||
// a. Assert: state is either executing or awaiting-return.
|
||||
VERIFY(state == AsyncGenerator::State::Executing || state == AsyncGenerator::State::AwaitingReturn);
|
||||
}
|
||||
|
||||
// 11. Return promiseCapability.[[Promise]].
|
||||
return promise_capability->promise();
|
||||
}
|
||||
|
||||
// 27.6.1.4 AsyncGenerator.prototype.throw ( exception ), https://tc39.es/ecma262/#sec-asyncgenerator-prototype-throw
|
||||
JS_DEFINE_NATIVE_FUNCTION(AsyncGeneratorPrototype::throw_)
|
||||
{
|
||||
auto& realm = *vm.current_realm();
|
||||
|
||||
auto exception = vm.argument(0);
|
||||
|
||||
// 1. Let generator be the this value.
|
||||
auto generator_this_value = vm.this_value();
|
||||
|
||||
// 2. Let promiseCapability be ! NewPromiseCapability(%Promise%).
|
||||
auto promise_capability = MUST(new_promise_capability(vm, realm.intrinsics().promise_constructor()));
|
||||
|
||||
// 3. Let result be Completion(AsyncGeneratorValidate(generator, empty)).
|
||||
// 4. IfAbruptRejectPromise(result, promiseCapability).
|
||||
auto generator = TRY_OR_REJECT(vm, promise_capability, async_generator_validate(vm, generator_this_value, OptionalNone {}));
|
||||
|
||||
// 5. Let state be generator.[[AsyncGeneratorState]].
|
||||
auto state = generator->async_generator_state();
|
||||
|
||||
// 6. If state is suspendedStart, then
|
||||
if (state == AsyncGenerator::State::SuspendedStart) {
|
||||
// a. Set generator.[[AsyncGeneratorState]] to completed.
|
||||
generator->set_async_generator_state({}, AsyncGenerator::State::Completed);
|
||||
|
||||
// b. Set state to completed.
|
||||
state = AsyncGenerator::State::Completed;
|
||||
}
|
||||
|
||||
// 7. If state is completed, then
|
||||
if (state == AsyncGenerator::State::Completed) {
|
||||
// a. Perform ! Call(promiseCapability.[[Reject]], undefined, « exception »).
|
||||
MUST(call(vm, *promise_capability->reject(), js_undefined(), exception));
|
||||
|
||||
// b. Return promiseCapability.[[Promise]].
|
||||
return promise_capability->promise();
|
||||
}
|
||||
|
||||
// 8. Let completion be ThrowCompletion(exception).
|
||||
auto completion = throw_completion(exception);
|
||||
|
||||
// 9. Perform AsyncGeneratorEnqueue(generator, completion, promiseCapability).
|
||||
generator->async_generator_enqueue(completion, promise_capability);
|
||||
|
||||
// 10. If state is suspendedYield, then
|
||||
if (state == AsyncGenerator::State::SuspendedYield) {
|
||||
// a. Perform AsyncGeneratorResume(generator, completion).
|
||||
TRY_OR_REJECT(vm, promise_capability, generator->resume(vm, completion));
|
||||
}
|
||||
// 11. Else,
|
||||
else {
|
||||
// a. Assert: state is either executing or awaiting-return.
|
||||
VERIFY(state == AsyncGenerator::State::Executing || state == AsyncGenerator::State::AwaitingReturn);
|
||||
}
|
||||
|
||||
// 12. Return promiseCapability.[[Promise]].
|
||||
return promise_capability->promise();
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
/*
|
||||
* Copyright (c) 2022, Linus Groh <linusg@serenityos.org>
|
||||
* Copyright (c) 2023, Luke Wilde <lukew@serenityos.org>
|
||||
*
|
||||
* SPDX-License-Identifier: BSD-2-Clause
|
||||
*/
|
||||
|
@ -20,6 +21,10 @@ public:
|
|||
|
||||
private:
|
||||
explicit AsyncGeneratorPrototype(Realm&);
|
||||
|
||||
JS_DECLARE_NATIVE_FUNCTION(next);
|
||||
JS_DECLARE_NATIVE_FUNCTION(return_);
|
||||
JS_DECLARE_NATIVE_FUNCTION(throw_);
|
||||
};
|
||||
|
||||
}
|
||||
|
|
|
@ -17,6 +17,7 @@
|
|||
#include <LibJS/Runtime/AbstractOperations.h>
|
||||
#include <LibJS/Runtime/Array.h>
|
||||
#include <LibJS/Runtime/AsyncFunctionDriverWrapper.h>
|
||||
#include <LibJS/Runtime/AsyncGenerator.h>
|
||||
#include <LibJS/Runtime/ECMAScriptFunctionObject.h>
|
||||
#include <LibJS/Runtime/Error.h>
|
||||
#include <LibJS/Runtime/ExecutionContext.h>
|
||||
|
@ -855,9 +856,6 @@ Completion ECMAScriptFunctionObject::ordinary_call_evaluate_body()
|
|||
auto& vm = this->vm();
|
||||
auto& realm = *vm.current_realm();
|
||||
|
||||
if (m_kind == FunctionKind::AsyncGenerator)
|
||||
return vm.throw_completion<InternalError>(ErrorType::NotImplemented, "Async Generator function execution");
|
||||
|
||||
auto* bytecode_interpreter = vm.bytecode_interpreter_if_exists();
|
||||
|
||||
// The bytecode interpreter can execute generator functions while the AST interpreter cannot.
|
||||
|
@ -867,7 +865,7 @@ Completion ECMAScriptFunctionObject::ordinary_call_evaluate_body()
|
|||
// However, this does cause an awkward situation with features not supported in bytecode, where features that work outside of generators with AST
|
||||
// suddenly stop working inside of generators.
|
||||
// This is a stop gap until bytecode mode becomes the default.
|
||||
if (m_kind == FunctionKind::Generator && !bytecode_interpreter) {
|
||||
if ((m_kind == FunctionKind::Generator || m_kind == FunctionKind::AsyncGenerator) && !bytecode_interpreter) {
|
||||
bytecode_interpreter = &vm.bytecode_interpreter();
|
||||
}
|
||||
|
||||
|
@ -920,6 +918,11 @@ Completion ECMAScriptFunctionObject::ordinary_call_evaluate_body()
|
|||
if (m_kind == FunctionKind::Normal)
|
||||
return { Completion::Type::Return, result.value_or(js_undefined()), {} };
|
||||
|
||||
if (m_kind == FunctionKind::AsyncGenerator) {
|
||||
auto async_generator_object = TRY(AsyncGenerator::create(realm, result, this, vm.running_execution_context().copy(), move(*result_and_frame.frame)));
|
||||
return { Completion::Type::Return, async_generator_object, {} };
|
||||
}
|
||||
|
||||
auto generator_object = TRY(GeneratorObject::create(realm, result, this, vm.running_execution_context().copy(), move(*result_and_frame.frame)));
|
||||
|
||||
// NOTE: Async functions are entirely transformed to generator functions, and wrapped in a custom driver that returns a promise
|
||||
|
@ -932,6 +935,8 @@ Completion ECMAScriptFunctionObject::ordinary_call_evaluate_body()
|
|||
} else {
|
||||
if (m_kind == FunctionKind::Generator)
|
||||
return vm.throw_completion<InternalError>(ErrorType::NotImplemented, "Generator function execution in AST interpreter");
|
||||
if (m_kind == FunctionKind::AsyncGenerator)
|
||||
return vm.throw_completion<InternalError>(ErrorType::NotImplemented, "Async generator function execution in AST interpreter");
|
||||
OwnPtr<Interpreter> local_interpreter;
|
||||
Interpreter* ast_interpreter = vm.interpreter_if_exists();
|
||||
|
||||
|
|
|
@ -0,0 +1,145 @@
|
|||
describe("correct behaviour", () => {
|
||||
async function* generatorFunction() {
|
||||
yield 1;
|
||||
await Promise.resolve(2);
|
||||
const b = yield 3;
|
||||
await Promise.resolve(b);
|
||||
yield b + 1;
|
||||
yield Promise.resolve(b + 2);
|
||||
yield* [Promise.resolve(b + 3), Promise.resolve(b + 4), Promise.resolve(b + 5)];
|
||||
return b + 6;
|
||||
}
|
||||
|
||||
test("length is 1", () => {
|
||||
expect(generatorFunction.prototype.next).toHaveLength(1);
|
||||
});
|
||||
|
||||
const generator = generatorFunction();
|
||||
|
||||
function runGenerator(valueToPass, unwrapIteratorResult = true) {
|
||||
let result = null;
|
||||
test(`generator runs valueToPass=${valueToPass}`, () => {
|
||||
const promise = generator.next(valueToPass);
|
||||
promise
|
||||
.then(value => {
|
||||
result = value;
|
||||
})
|
||||
.catch(e => {
|
||||
expect().fail(`Generator threw an unhandled exception: ${e}`);
|
||||
});
|
||||
runQueuedPromiseJobs();
|
||||
expect(result).toBeInstanceOf(Object);
|
||||
expect(Object.getPrototypeOf(result)).toBe(Object.prototype);
|
||||
expect(Object.keys(result)).toEqual(["value", "done"]);
|
||||
});
|
||||
return unwrapIteratorResult ? result.value : result;
|
||||
}
|
||||
|
||||
test("can yield", () => {
|
||||
const firstRunResult = runGenerator("bad1");
|
||||
expect(firstRunResult).toBe(1);
|
||||
});
|
||||
|
||||
test("await does not yield", () => {
|
||||
const secondRunResult = runGenerator("bad2");
|
||||
expect(secondRunResult).toBe(3);
|
||||
});
|
||||
|
||||
test("can pass values via yield", () => {
|
||||
const thirdRunResult = runGenerator(4);
|
||||
expect(thirdRunResult).toBe(5);
|
||||
});
|
||||
|
||||
test("yield implicitly awaits", () => {
|
||||
const fourthRunResult = runGenerator("bad3");
|
||||
expect(fourthRunResult).toBe(6);
|
||||
|
||||
const fifthRunResult = runGenerator("bad4");
|
||||
expect(fifthRunResult).toBe(7);
|
||||
|
||||
const sixthRunResult = runGenerator("bad5");
|
||||
expect(sixthRunResult).toBe(8);
|
||||
|
||||
const seventhRunResult = runGenerator("bad6");
|
||||
expect(seventhRunResult).toBe(9);
|
||||
});
|
||||
|
||||
test("can return a value", () => {
|
||||
const eighthRunResult = runGenerator("bad7", false);
|
||||
expect(eighthRunResult.value).toBe(10);
|
||||
expect(eighthRunResult.done).toBeTrue();
|
||||
});
|
||||
|
||||
test("gets undefined in completed state", () => {
|
||||
const ninethRunResult = runGenerator("bad8", false);
|
||||
expect(ninethRunResult.value).toBeUndefined();
|
||||
expect(ninethRunResult.done).toBeTrue();
|
||||
});
|
||||
|
||||
async function* implicitReturnFunction() {
|
||||
0xbbadbeef;
|
||||
}
|
||||
|
||||
const implicitReturnGenerator = implicitReturnFunction();
|
||||
|
||||
test("gets undefined from implicit return", () => {
|
||||
implicitReturnGenerator
|
||||
.next("bad9")
|
||||
.then(iteratorResult => {
|
||||
expect(iteratorResult.value).toBeUndefined();
|
||||
expect(iteratorResult.done).toBeTrue();
|
||||
})
|
||||
.catch(e => {
|
||||
expect().fail(`Implicit await generator threw an unhandled exception: ${e}`);
|
||||
});
|
||||
runQueuedPromiseJobs();
|
||||
});
|
||||
|
||||
async function* unhandledExceptionFunction() {
|
||||
throw 1337;
|
||||
}
|
||||
|
||||
const unhandledExceptionGenerator = unhandledExceptionFunction();
|
||||
|
||||
test("promise is rejected on unhandled exceptions", () => {
|
||||
unhandledExceptionGenerator
|
||||
.next("bad10")
|
||||
.then(() => {
|
||||
expect().fail(
|
||||
"Unhandled exception generator did NOT throw an unhandled exception."
|
||||
);
|
||||
})
|
||||
.catch(e => {
|
||||
expect(e).toBe(1337);
|
||||
});
|
||||
runQueuedPromiseJobs();
|
||||
});
|
||||
|
||||
test("generator is complete after unhandled exception", () => {
|
||||
unhandledExceptionGenerator
|
||||
.next("bad11")
|
||||
.then(iteratorResult => {
|
||||
expect(iteratorResult.value).toBeUndefined();
|
||||
expect(iteratorResult.done).toBeTrue();
|
||||
})
|
||||
.catch(e => {
|
||||
expect().fail(
|
||||
"Unhandled exception generator threw an unhandled exception in Completed state."
|
||||
);
|
||||
});
|
||||
runQueuedPromiseJobs();
|
||||
});
|
||||
});
|
||||
|
||||
describe("errors", () => {
|
||||
test("this value must be an AsyncGenerator object", () => {
|
||||
async function* generator() {}
|
||||
let rejection = null;
|
||||
generator.prototype.next.call("foo").catch(error => {
|
||||
rejection = error;
|
||||
});
|
||||
runQueuedPromiseJobs();
|
||||
expect(rejection).toBeInstanceOf(TypeError);
|
||||
expect(rejection.message).toBe("Not an object of type AsyncGenerator");
|
||||
});
|
||||
});
|
|
@ -0,0 +1,146 @@
|
|||
describe("correct behavior", () => {
|
||||
async function* emptyGeneratorFunction() {}
|
||||
|
||||
test("length is 1", () => {
|
||||
expect(emptyGeneratorFunction.prototype.return).toHaveLength(1);
|
||||
});
|
||||
|
||||
const emptyGenerator = emptyGeneratorFunction();
|
||||
|
||||
test("return from SuspendedStart", () => {
|
||||
emptyGenerator
|
||||
.return(1337)
|
||||
.then(result => {
|
||||
expect(result.value).toBe(1337);
|
||||
expect(result.done).toBeTrue();
|
||||
})
|
||||
.catch(e => {
|
||||
expect().fail(`Generator threw an unhandled exception: ${e}`);
|
||||
});
|
||||
runQueuedPromiseJobs();
|
||||
});
|
||||
|
||||
test("return from Completed", () => {
|
||||
emptyGenerator
|
||||
.return(123)
|
||||
.then(result => {
|
||||
expect(result.value).toBe(123);
|
||||
expect(result.done).toBeTrue();
|
||||
})
|
||||
.catch(e => {
|
||||
expect().fail(`Generator threw an unhandled exception: ${e}`);
|
||||
});
|
||||
runQueuedPromiseJobs();
|
||||
});
|
||||
|
||||
async function* generatorTwo() {
|
||||
yield 1337;
|
||||
yield 123;
|
||||
}
|
||||
|
||||
const generatorTwoIterator = generatorTwo();
|
||||
|
||||
test("return from SuspendedYield", () => {
|
||||
generatorTwoIterator
|
||||
.next("bad1")
|
||||
.then(result => {
|
||||
expect(result.value).toBe(1337);
|
||||
expect(result.done).toBeFalse();
|
||||
})
|
||||
.catch(e => {
|
||||
expect().fail(`Generator threw an unhandled exception: ${e}`);
|
||||
});
|
||||
runQueuedPromiseJobs();
|
||||
|
||||
generatorTwoIterator
|
||||
.return(999)
|
||||
.then(result => {
|
||||
expect(result.value).toBe(999);
|
||||
expect(result.done).toBeTrue();
|
||||
})
|
||||
.catch(e => {
|
||||
expect().fail(`Generator threw an unhandled exception: ${e}`);
|
||||
});
|
||||
runQueuedPromiseJobs();
|
||||
|
||||
generatorTwoIterator
|
||||
.next("bad2")
|
||||
.then(result => {
|
||||
expect(result.value).toBeUndefined();
|
||||
expect(result.done).toBeTrue();
|
||||
})
|
||||
.catch(e => {
|
||||
expect().fail(`Generator threw an unhandled exception: ${e}`);
|
||||
});
|
||||
runQueuedPromiseJobs();
|
||||
});
|
||||
|
||||
async function* injectedCompletionGenerator() {
|
||||
try {
|
||||
yield 1;
|
||||
} finally {
|
||||
yield 2;
|
||||
}
|
||||
}
|
||||
|
||||
const injectedCompletionGeneratorObject = injectedCompletionGenerator();
|
||||
|
||||
test("return completion is injected into generator", () => {
|
||||
injectedCompletionGeneratorObject
|
||||
.next("bad1")
|
||||
.then(result => {
|
||||
expect(result.value).toBe(1);
|
||||
expect(result.done).toBeFalse();
|
||||
})
|
||||
.catch(e => {
|
||||
expect().fail(`Generator threw an unhandled exception: ${e}`);
|
||||
});
|
||||
runQueuedPromiseJobs();
|
||||
|
||||
injectedCompletionGeneratorObject
|
||||
.return(3)
|
||||
.then(result => {
|
||||
expect(result.value).toBe(2);
|
||||
expect(result.done).toBeFalse();
|
||||
})
|
||||
.catch(e => {
|
||||
expect().fail(`Generator threw an unhandled exception: ${e}`);
|
||||
});
|
||||
runQueuedPromiseJobs();
|
||||
|
||||
injectedCompletionGeneratorObject
|
||||
.next("bad3")
|
||||
.then(result => {
|
||||
expect(result.value).toBe(3);
|
||||
expect(result.done).toBeTrue();
|
||||
})
|
||||
.catch(e => {
|
||||
expect().fail(`Generator threw an unhandled exception: ${e}`);
|
||||
});
|
||||
runQueuedPromiseJobs();
|
||||
|
||||
injectedCompletionGeneratorObject
|
||||
.next("bad4")
|
||||
.then(result => {
|
||||
expect(result.value).toBeUndefined();
|
||||
expect(result.done).toBeTrue();
|
||||
})
|
||||
.catch(e => {
|
||||
expect().fail(`Generator threw an unhandled exception: ${e}`);
|
||||
});
|
||||
runQueuedPromiseJobs();
|
||||
});
|
||||
});
|
||||
|
||||
describe("errors", () => {
|
||||
test("this value must be an AsyncGenerator object", () => {
|
||||
async function* generator() {}
|
||||
let rejection = null;
|
||||
generator.prototype.return.call("foo").catch(error => {
|
||||
rejection = error;
|
||||
});
|
||||
runQueuedPromiseJobs();
|
||||
expect(rejection).toBeInstanceOf(TypeError);
|
||||
expect(rejection.message).toBe("Not an object of type AsyncGenerator");
|
||||
});
|
||||
});
|
|
@ -0,0 +1,132 @@
|
|||
describe("correct behavior", () => {
|
||||
async function* emptyGeneratorFunction() {}
|
||||
|
||||
test("length is 1", () => {
|
||||
expect(emptyGeneratorFunction.prototype.throw).toHaveLength(1);
|
||||
});
|
||||
|
||||
const emptyGenerator = emptyGeneratorFunction();
|
||||
|
||||
test("throw from SuspendedStart", () => {
|
||||
emptyGenerator
|
||||
.throw(1337)
|
||||
.then(() => {
|
||||
expect().fail("Generator did NOT throw an unhandled exception.");
|
||||
})
|
||||
.catch(e => {
|
||||
expect(e).toBe(1337);
|
||||
});
|
||||
runQueuedPromiseJobs();
|
||||
});
|
||||
|
||||
test("throw from Completed", () => {
|
||||
emptyGenerator
|
||||
.throw(123)
|
||||
.then(() => {
|
||||
expect().fail("Generator did NOT throw an unhandled exception.");
|
||||
})
|
||||
.catch(e => {
|
||||
expect(e).toBe(123);
|
||||
});
|
||||
runQueuedPromiseJobs();
|
||||
});
|
||||
|
||||
async function* generatorTwo() {
|
||||
yield 1337;
|
||||
yield 123;
|
||||
}
|
||||
|
||||
const generatorTwoIterator = generatorTwo();
|
||||
|
||||
test("throw from SuspendedYield", () => {
|
||||
generatorTwoIterator
|
||||
.next("bad1")
|
||||
.then(result => {
|
||||
expect(result.value).toBe(1337);
|
||||
expect(result.done).toBeFalse();
|
||||
})
|
||||
.catch(e => {
|
||||
expect().fail(`Generator threw an unhandled exception: ${e}`);
|
||||
});
|
||||
runQueuedPromiseJobs();
|
||||
|
||||
generatorTwoIterator
|
||||
.throw(999)
|
||||
.then(() => {
|
||||
expect().fail("Generator did NOT throw an unhandled exception.");
|
||||
})
|
||||
.catch(e => {
|
||||
expect(e).toBe(999);
|
||||
});
|
||||
runQueuedPromiseJobs();
|
||||
|
||||
generatorTwoIterator
|
||||
.next("bad2")
|
||||
.then(result => {
|
||||
expect(result.value).toBeUndefined();
|
||||
expect(result.done).toBeTrue();
|
||||
})
|
||||
.catch(e => {
|
||||
expect().fail(`Generator threw an unhandled exception: ${e}`);
|
||||
});
|
||||
runQueuedPromiseJobs();
|
||||
});
|
||||
|
||||
async function* injectedCompletionGenerator() {
|
||||
try {
|
||||
yield 1;
|
||||
} catch (e) {
|
||||
yield e;
|
||||
}
|
||||
}
|
||||
|
||||
const injectedCompletionGeneratorObject = injectedCompletionGenerator();
|
||||
|
||||
test("throw completion is injected into generator", () => {
|
||||
injectedCompletionGeneratorObject
|
||||
.next("bad1")
|
||||
.then(result => {
|
||||
expect(result.value).toBe(1);
|
||||
expect(result.done).toBeFalse();
|
||||
})
|
||||
.catch(e => {
|
||||
expect().fail(`Generator threw an unhandled exception: ${e}`);
|
||||
});
|
||||
runQueuedPromiseJobs();
|
||||
|
||||
injectedCompletionGeneratorObject
|
||||
.throw(9999)
|
||||
.then(result => {
|
||||
expect(result.value).toBe(9999);
|
||||
expect(result.done).toBeFalse();
|
||||
})
|
||||
.catch(e => {
|
||||
expect().fail(`Generator threw an unhandled exception: ${e}`);
|
||||
});
|
||||
runQueuedPromiseJobs();
|
||||
|
||||
injectedCompletionGeneratorObject
|
||||
.next("bad2")
|
||||
.then(result => {
|
||||
expect(result.value).toBeUndefined();
|
||||
expect(result.done).toBeTrue();
|
||||
})
|
||||
.catch(e => {
|
||||
expect().fail(`Generator threw an unhandled exception: ${e}`);
|
||||
});
|
||||
runQueuedPromiseJobs();
|
||||
});
|
||||
});
|
||||
|
||||
describe("errors", () => {
|
||||
test("this value must be an AsyncGenerator object", () => {
|
||||
async function* generator() {}
|
||||
let rejection = null;
|
||||
generator.prototype.throw.call("foo").catch(error => {
|
||||
rejection = error;
|
||||
});
|
||||
runQueuedPromiseJobs();
|
||||
expect(rejection).toBeInstanceOf(TypeError);
|
||||
expect(rejection.message).toBe("Not an object of type AsyncGenerator");
|
||||
});
|
||||
});
|
Loading…
Add table
Reference in a new issue