mirror of
https://github.com/RGBCube/serenity
synced 2025-07-23 20:57:41 +00:00
LibJS: Implement IteratorClose with Completions and align to the spec
This commit is contained in:
parent
04b4307b3d
commit
ec54a7b5b0
8 changed files with 97 additions and 79 deletions
|
@ -123,9 +123,8 @@ JS_DEFINE_OLD_NATIVE_FUNCTION(ArrayConstructor::from)
|
||||||
size_t k = 0;
|
size_t k = 0;
|
||||||
while (true) {
|
while (true) {
|
||||||
if (k >= MAX_ARRAY_LIKE_INDEX) {
|
if (k >= MAX_ARRAY_LIKE_INDEX) {
|
||||||
vm.throw_exception<TypeError>(global_object, ErrorType::ArrayMaxSize);
|
auto error = vm.throw_completion<TypeError>(global_object, ErrorType::ArrayMaxSize);
|
||||||
iterator_close(*iterator);
|
return TRY_OR_DISCARD(iterator_close(*iterator, move(error)));
|
||||||
return {};
|
|
||||||
}
|
}
|
||||||
|
|
||||||
auto* next = TRY_OR_DISCARD(iterator_step(global_object, *iterator));
|
auto* next = TRY_OR_DISCARD(iterator_step(global_object, *iterator));
|
||||||
|
@ -139,20 +138,16 @@ JS_DEFINE_OLD_NATIVE_FUNCTION(ArrayConstructor::from)
|
||||||
Value mapped_value;
|
Value mapped_value;
|
||||||
if (map_fn) {
|
if (map_fn) {
|
||||||
auto mapped_value_or_error = vm.call(*map_fn, this_arg, next_value, Value(k));
|
auto mapped_value_or_error = vm.call(*map_fn, this_arg, next_value, Value(k));
|
||||||
if (mapped_value_or_error.is_error()) {
|
if (mapped_value_or_error.is_error())
|
||||||
iterator_close(*iterator);
|
return TRY_OR_DISCARD(iterator_close(*iterator, mapped_value_or_error.release_error()));
|
||||||
return {};
|
|
||||||
}
|
|
||||||
mapped_value = mapped_value_or_error.release_value();
|
mapped_value = mapped_value_or_error.release_value();
|
||||||
} else {
|
} else {
|
||||||
mapped_value = next_value;
|
mapped_value = next_value;
|
||||||
}
|
}
|
||||||
|
|
||||||
auto result_or_error = array_object.create_data_property_or_throw(k, mapped_value);
|
auto result_or_error = array_object.create_data_property_or_throw(k, mapped_value);
|
||||||
if (result_or_error.is_error()) {
|
if (result_or_error.is_error())
|
||||||
iterator_close(*iterator);
|
return TRY_OR_DISCARD(iterator_close(*iterator, result_or_error.release_error()));
|
||||||
return {};
|
|
||||||
}
|
|
||||||
|
|
||||||
++k;
|
++k;
|
||||||
}
|
}
|
||||||
|
|
|
@ -297,11 +297,10 @@ ThrowCompletionOr<Vector<String>> string_list_from_iterable(GlobalObject& global
|
||||||
// ii. If Type(nextValue) is not String, then
|
// ii. If Type(nextValue) is not String, then
|
||||||
if (!next_value.is_string()) {
|
if (!next_value.is_string()) {
|
||||||
// 1. Let error be ThrowCompletion(a newly created TypeError object).
|
// 1. Let error be ThrowCompletion(a newly created TypeError object).
|
||||||
auto completion = vm.throw_completion<TypeError>(global_object, ErrorType::NotAString, next_value);
|
auto error = vm.throw_completion<TypeError>(global_object, ErrorType::NotAString, next_value);
|
||||||
|
|
||||||
// 2. Return ? IteratorClose(iteratorRecord, error).
|
// 2. Return ? IteratorClose(iteratorRecord, error).
|
||||||
iterator_close(*iterator_record);
|
return iterator_close(*iterator_record, move(error));
|
||||||
return completion;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// iii. Append nextValue to the end of the List list.
|
// iii. Append nextValue to the end of the List list.
|
||||||
|
|
|
@ -9,6 +9,7 @@
|
||||||
#include <LibJS/Runtime/FunctionObject.h>
|
#include <LibJS/Runtime/FunctionObject.h>
|
||||||
#include <LibJS/Runtime/GlobalObject.h>
|
#include <LibJS/Runtime/GlobalObject.h>
|
||||||
#include <LibJS/Runtime/IteratorOperations.h>
|
#include <LibJS/Runtime/IteratorOperations.h>
|
||||||
|
#include <LibJS/Runtime/TemporaryClearException.h>
|
||||||
|
|
||||||
namespace JS {
|
namespace JS {
|
||||||
|
|
||||||
|
@ -87,45 +88,52 @@ ThrowCompletionOr<Object*> iterator_step(GlobalObject& global_object, Object& it
|
||||||
}
|
}
|
||||||
|
|
||||||
// 7.4.6 IteratorClose ( iteratorRecord, completion ), https://tc39.es/ecma262/#sec-iteratorclose
|
// 7.4.6 IteratorClose ( iteratorRecord, completion ), https://tc39.es/ecma262/#sec-iteratorclose
|
||||||
void iterator_close(Object& iterator)
|
Completion iterator_close(Object& iterator, Completion completion)
|
||||||
{
|
{
|
||||||
auto& vm = iterator.vm();
|
auto& vm = iterator.vm();
|
||||||
auto& global_object = iterator.global_object();
|
auto& global_object = iterator.global_object();
|
||||||
|
|
||||||
// Emulates `completion` behavior
|
// The callers of iterator_close() are often in an exceptional state.
|
||||||
auto* completion_exception = vm.exception();
|
// Temporarily clear that exception for invocation(s) to Call.
|
||||||
vm.clear_exception();
|
TemporaryClearException clear_exception(vm);
|
||||||
auto unwind_until = vm.unwind_until();
|
|
||||||
auto unwind_until_label = vm.unwind_until_label();
|
|
||||||
vm.stop_unwind();
|
|
||||||
auto restore_completion = [&]() {
|
|
||||||
if (completion_exception)
|
|
||||||
vm.set_exception(*completion_exception);
|
|
||||||
if (unwind_until != ScopeType::None)
|
|
||||||
vm.unwind(unwind_until, unwind_until_label);
|
|
||||||
};
|
|
||||||
|
|
||||||
auto return_method_or_error = Value(&iterator).get_method(global_object, vm.names.return_);
|
// 3. Let innerResult be GetMethod(iterator, "return").
|
||||||
Value result;
|
auto inner_result_or_error = Value(&iterator).get_method(global_object, vm.names.return_);
|
||||||
if (!return_method_or_error.is_error()) { // If innerResult.[[Type]] is normal, then
|
Value inner_result;
|
||||||
auto return_method = return_method_or_error.release_value();
|
|
||||||
|
// 4. If innerResult.[[Type]] is normal, then
|
||||||
|
if (!inner_result_or_error.is_error()) {
|
||||||
|
// a. Let return be innerResult.[[Value]].
|
||||||
|
auto* return_method = inner_result_or_error.release_value();
|
||||||
|
|
||||||
|
// b. If return is undefined, return Completion(completion).
|
||||||
if (!return_method)
|
if (!return_method)
|
||||||
return restore_completion(); // If return is undefined, return Completion(completion).
|
return completion;
|
||||||
|
|
||||||
|
vm.stop_unwind();
|
||||||
|
|
||||||
|
// c. Set innerResult to Call(return, iterator).
|
||||||
auto result_or_error = vm.call(*return_method, &iterator);
|
auto result_or_error = vm.call(*return_method, &iterator);
|
||||||
if (result_or_error.is_error())
|
if (result_or_error.is_error())
|
||||||
return_method_or_error = result_or_error.release_error();
|
inner_result_or_error = result_or_error.release_error();
|
||||||
else
|
else
|
||||||
result = result_or_error.release_value();
|
inner_result = result_or_error.release_value();
|
||||||
}
|
}
|
||||||
if (completion_exception)
|
|
||||||
return restore_completion(); // If completion.[[Type]] is throw, return Completion(completion).
|
// 5. If completion.[[Type]] is throw, return Completion(completion).
|
||||||
if (return_method_or_error.is_error())
|
if (completion.is_error())
|
||||||
return; // If innerResult.[[Type]] is throw, return Completion(innerResult).
|
return completion;
|
||||||
if (!result.is_object()) {
|
|
||||||
vm.throw_exception<TypeError>(global_object, ErrorType::IterableReturnBadReturn);
|
// 6. If innerResult.[[Type]] is throw, return Completion(innerResult).
|
||||||
return; // If Type(innerResult.[[Value]]) is not Object, throw a TypeError exception.
|
if (inner_result_or_error.is_error())
|
||||||
}
|
return inner_result_or_error.release_error();
|
||||||
restore_completion(); // Return Completion(completion).
|
|
||||||
|
// 7. If Type(innerResult.[[Value]]) is not Object, throw a TypeError exception.
|
||||||
|
if (!inner_result.is_object())
|
||||||
|
return vm.throw_completion<TypeError>(global_object, ErrorType::IterableReturnBadReturn);
|
||||||
|
|
||||||
|
// 8. Return Completion(completion).
|
||||||
|
return completion;
|
||||||
}
|
}
|
||||||
|
|
||||||
// 7.4.8 CreateIterResultObject ( value, done ), https://tc39.es/ecma262/#sec-createiterresultobject
|
// 7.4.8 CreateIterResultObject ( value, done ), https://tc39.es/ecma262/#sec-createiterresultobject
|
||||||
|
@ -165,10 +173,8 @@ Completion get_iterator_values(GlobalObject& global_object, Value iterable, Iter
|
||||||
|
|
||||||
auto next_value = TRY(iterator_value(global_object, *next_object));
|
auto next_value = TRY(iterator_value(global_object, *next_object));
|
||||||
|
|
||||||
if (auto completion = callback(next_value); completion.has_value()) {
|
if (auto completion = callback(next_value); completion.has_value())
|
||||||
iterator_close(*iterator);
|
return iterator_close(*iterator, completion.release_value());
|
||||||
return completion.release_value();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -25,7 +25,7 @@ ThrowCompletionOr<Object*> iterator_next(Object& iterator, Value value = {});
|
||||||
ThrowCompletionOr<Object*> iterator_step(GlobalObject&, Object& iterator);
|
ThrowCompletionOr<Object*> iterator_step(GlobalObject&, Object& iterator);
|
||||||
ThrowCompletionOr<bool> iterator_complete(GlobalObject&, Object& iterator_result);
|
ThrowCompletionOr<bool> iterator_complete(GlobalObject&, Object& iterator_result);
|
||||||
ThrowCompletionOr<Value> iterator_value(GlobalObject&, Object& iterator_result);
|
ThrowCompletionOr<Value> iterator_value(GlobalObject&, Object& iterator_result);
|
||||||
void iterator_close(Object& iterator);
|
Completion iterator_close(Object& iterator, Completion completion);
|
||||||
Object* create_iterator_result_object(GlobalObject&, Value value, bool done);
|
Object* create_iterator_result_object(GlobalObject&, Value value, bool done);
|
||||||
MarkedValueList iterable_to_list(GlobalObject&, Value iterable, Value method = {});
|
MarkedValueList iterable_to_list(GlobalObject&, Value iterable, Value method = {});
|
||||||
|
|
||||||
|
|
|
@ -302,11 +302,16 @@ JS_DEFINE_OLD_NATIVE_FUNCTION(PromiseConstructor::all)
|
||||||
auto* iterator_record = iterator_record_or_error.release_value();
|
auto* iterator_record = iterator_record_or_error.release_value();
|
||||||
|
|
||||||
auto result = perform_promise_all(global_object, *iterator_record, constructor, promise_capability, promise_resolve);
|
auto result = perform_promise_all(global_object, *iterator_record, constructor, promise_capability, promise_resolve);
|
||||||
if (vm.exception()) {
|
if (auto* exception = vm.exception()) {
|
||||||
if (!iterator_record_is_complete(global_object, *iterator_record))
|
// FIXME: Once perform_promise_all returns a throw completion, pass that to iterator_close() instead.
|
||||||
iterator_close(*iterator_record);
|
auto result_error = throw_completion(exception->value());
|
||||||
|
|
||||||
auto abrupt = if_abrupt_reject_promise(global_object, result, promise_capability);
|
if (!iterator_record_is_complete(global_object, *iterator_record)) {
|
||||||
|
TemporaryClearException clear_exception(vm); // iterator_close() may invoke vm.call(), which VERIFYs no exception.
|
||||||
|
result_error = iterator_close(*iterator_record, move(result_error));
|
||||||
|
}
|
||||||
|
|
||||||
|
auto abrupt = if_abrupt_reject_promise(global_object, result_error, promise_capability);
|
||||||
return abrupt.value();
|
return abrupt.value();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -332,11 +337,16 @@ JS_DEFINE_OLD_NATIVE_FUNCTION(PromiseConstructor::all_settled)
|
||||||
auto* iterator_record = iterator_record_or_error.release_value();
|
auto* iterator_record = iterator_record_or_error.release_value();
|
||||||
|
|
||||||
auto result = perform_promise_all_settled(global_object, *iterator_record, constructor, promise_capability, promise_resolve);
|
auto result = perform_promise_all_settled(global_object, *iterator_record, constructor, promise_capability, promise_resolve);
|
||||||
if (vm.exception()) {
|
if (auto* exception = vm.exception()) {
|
||||||
if (!iterator_record_is_complete(global_object, *iterator_record))
|
// FIXME: Once perform_promise_all_settled returns a throw completion, pass that to iterator_close() instead.
|
||||||
iterator_close(*iterator_record);
|
auto result_error = throw_completion(exception->value());
|
||||||
|
|
||||||
auto abrupt = if_abrupt_reject_promise(global_object, result, promise_capability);
|
if (!iterator_record_is_complete(global_object, *iterator_record)) {
|
||||||
|
TemporaryClearException clear_exception(vm); // iterator_close() may invoke vm.call(), which VERIFYs no exception.
|
||||||
|
result_error = iterator_close(*iterator_record, move(result_error));
|
||||||
|
}
|
||||||
|
|
||||||
|
auto abrupt = if_abrupt_reject_promise(global_object, result_error, promise_capability);
|
||||||
return abrupt.value();
|
return abrupt.value();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -362,11 +372,16 @@ JS_DEFINE_OLD_NATIVE_FUNCTION(PromiseConstructor::any)
|
||||||
auto* iterator_record = iterator_record_or_error.release_value();
|
auto* iterator_record = iterator_record_or_error.release_value();
|
||||||
|
|
||||||
auto result = perform_promise_any(global_object, *iterator_record, constructor, promise_capability, promise_resolve);
|
auto result = perform_promise_any(global_object, *iterator_record, constructor, promise_capability, promise_resolve);
|
||||||
if (vm.exception()) {
|
if (auto* exception = vm.exception()) {
|
||||||
if (!iterator_record_is_complete(global_object, *iterator_record))
|
// FIXME: Once perform_promise_any returns a throw completion, pass that to iterator_close() instead.
|
||||||
iterator_close(*iterator_record);
|
auto result_error = throw_completion(exception->value());
|
||||||
|
|
||||||
auto abrupt = if_abrupt_reject_promise(global_object, result, promise_capability);
|
if (!iterator_record_is_complete(global_object, *iterator_record)) {
|
||||||
|
TemporaryClearException clear_exception(vm); // iterator_close() may invoke vm.call(), which VERIFYs no exception.
|
||||||
|
result_error = iterator_close(*iterator_record, move(result_error));
|
||||||
|
}
|
||||||
|
|
||||||
|
auto abrupt = if_abrupt_reject_promise(global_object, result_error, promise_capability);
|
||||||
return abrupt.value();
|
return abrupt.value();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -392,11 +407,16 @@ JS_DEFINE_OLD_NATIVE_FUNCTION(PromiseConstructor::race)
|
||||||
auto* iterator_record = iterator_record_or_error.release_value();
|
auto* iterator_record = iterator_record_or_error.release_value();
|
||||||
|
|
||||||
auto result = perform_promise_race(global_object, *iterator_record, constructor, promise_capability, promise_resolve);
|
auto result = perform_promise_race(global_object, *iterator_record, constructor, promise_capability, promise_resolve);
|
||||||
if (vm.exception()) {
|
if (auto* exception = vm.exception()) {
|
||||||
if (!iterator_record_is_complete(global_object, *iterator_record))
|
// FIXME: Once perform_promise_race returns a throw completion, pass that to iterator_close() instead.
|
||||||
iterator_close(*iterator_record);
|
auto result_error = throw_completion(exception->value());
|
||||||
|
|
||||||
auto abrupt = if_abrupt_reject_promise(global_object, result, promise_capability);
|
if (!iterator_record_is_complete(global_object, *iterator_record)) {
|
||||||
|
TemporaryClearException clear_exception(vm);
|
||||||
|
result_error = iterator_close(*iterator_record, move(result_error)); // iterator_close() may invoke vm.call(), which VERIFYs no exception.
|
||||||
|
}
|
||||||
|
|
||||||
|
auto abrupt = if_abrupt_reject_promise(global_object, result_error, promise_capability);
|
||||||
return abrupt.value();
|
return abrupt.value();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -63,8 +63,7 @@ ThrowCompletionOr<MarkedValueList> iterable_to_list_of_type(GlobalObject& global
|
||||||
// 1. Let completion be ThrowCompletion(a newly created TypeError object).
|
// 1. Let completion be ThrowCompletion(a newly created TypeError object).
|
||||||
auto completion = vm.throw_completion<TypeError>(global_object, ErrorType::IterableToListOfTypeInvalidValue, next_value.to_string_without_side_effects());
|
auto completion = vm.throw_completion<TypeError>(global_object, ErrorType::IterableToListOfTypeInvalidValue, next_value.to_string_without_side_effects());
|
||||||
// 2. Return ? IteratorClose(iteratorRecord, completion).
|
// 2. Return ? IteratorClose(iteratorRecord, completion).
|
||||||
iterator_close(*iterator_record);
|
return iterator_close(*iterator_record, move(completion));
|
||||||
return completion;
|
|
||||||
}
|
}
|
||||||
// iii. Append nextValue to the end of the List values.
|
// iii. Append nextValue to the end of the List values.
|
||||||
values.append(next_value);
|
values.append(next_value);
|
||||||
|
|
|
@ -513,31 +513,28 @@ JS_DEFINE_OLD_NATIVE_FUNCTION(CalendarPrototype::fields)
|
||||||
// ii. If Type(nextValue) is not String, then
|
// ii. If Type(nextValue) is not String, then
|
||||||
if (!next_value.is_string()) {
|
if (!next_value.is_string()) {
|
||||||
// 1. Let completion be ThrowCompletion(a newly created TypeError object).
|
// 1. Let completion be ThrowCompletion(a newly created TypeError object).
|
||||||
vm.throw_exception<TypeError>(global_object, ErrorType::TemporalInvalidCalendarFieldValue, next_value.to_string_without_side_effects());
|
auto completion = vm.throw_completion<TypeError>(global_object, ErrorType::TemporalInvalidCalendarFieldValue, next_value.to_string_without_side_effects());
|
||||||
|
|
||||||
// 2. Return ? IteratorClose(iteratorRecord, completion).
|
// 2. Return ? IteratorClose(iteratorRecord, completion).
|
||||||
iterator_close(*iterator_record);
|
return TRY_OR_DISCARD(iterator_close(*iterator_record, move(completion)));
|
||||||
return {};
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// iii. If fieldNames contains nextValue, then
|
// iii. If fieldNames contains nextValue, then
|
||||||
if (field_names.contains_slow(next_value)) {
|
if (field_names.contains_slow(next_value)) {
|
||||||
// 1. Let completion be ThrowCompletion(a newly created RangeError object).
|
// 1. Let completion be ThrowCompletion(a newly created RangeError object).
|
||||||
vm.throw_exception<RangeError>(global_object, ErrorType::TemporalDuplicateCalendarField, next_value.as_string().string());
|
auto completion = vm.throw_completion<RangeError>(global_object, ErrorType::TemporalDuplicateCalendarField, next_value.as_string().string());
|
||||||
|
|
||||||
// 2. Return ? IteratorClose(iteratorRecord, completion).
|
// 2. Return ? IteratorClose(iteratorRecord, completion).
|
||||||
iterator_close(*iterator_record);
|
return TRY_OR_DISCARD(iterator_close(*iterator_record, move(completion)));
|
||||||
return {};
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// iv. If nextValue is not one of "year", "month", "monthCode", "day", "hour", "minute", "second", "millisecond", "microsecond", "nanosecond", then
|
// iv. If nextValue is not one of "year", "month", "monthCode", "day", "hour", "minute", "second", "millisecond", "microsecond", "nanosecond", then
|
||||||
if (!next_value.as_string().string().is_one_of("year"sv, "month"sv, "monthCode"sv, "day"sv, "hour"sv, "minute"sv, "second"sv, "millisecond"sv, "microsecond"sv, "nanosecond"sv)) {
|
if (!next_value.as_string().string().is_one_of("year"sv, "month"sv, "monthCode"sv, "day"sv, "hour"sv, "minute"sv, "second"sv, "millisecond"sv, "microsecond"sv, "nanosecond"sv)) {
|
||||||
// 1. Let completion be ThrowCompletion(a newly created RangeError object).
|
// 1. Let completion be ThrowCompletion(a newly created RangeError object).
|
||||||
vm.throw_exception<RangeError>(global_object, ErrorType::TemporalInvalidCalendarFieldName, next_value.as_string().string());
|
auto completion = vm.throw_completion<RangeError>(global_object, ErrorType::TemporalInvalidCalendarFieldName, next_value.as_string().string());
|
||||||
|
|
||||||
// 2. Return ? IteratorClose(iteratorRecord, completion).
|
// 2. Return ? IteratorClose(iteratorRecord, completion).
|
||||||
iterator_close(*iterator_record);
|
return TRY_OR_DISCARD(iterator_close(*iterator_record, move(completion)));
|
||||||
return {};
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// v. Append nextValue to the end of the List fieldNames.
|
// v. Append nextValue to the end of the List fieldNames.
|
||||||
|
|
|
@ -202,11 +202,13 @@ ThrowCompletionOr<void> VM::binding_initialization(NonnullRefPtr<BindingPattern>
|
||||||
auto result = iterator_binding_initialization(*target, iterator, iterator_done, environment, global_object);
|
auto result = iterator_binding_initialization(*target, iterator, iterator_done, environment, global_object);
|
||||||
|
|
||||||
if (!iterator_done) {
|
if (!iterator_done) {
|
||||||
// FIXME: Iterator close should take result and potentially return that. This logic should achieve the same until that is possible.
|
// iterator_close() always returns a Completion, which ThrowCompletionOr will interpret as a throw
|
||||||
iterator_close(*iterator);
|
// completion. So only return the result of iterator_close() if it is indeed a throw completion.
|
||||||
if (auto* thrown_exception = exception())
|
auto completion = result.is_throw_completion() ? result.release_error() : normal_completion({});
|
||||||
return JS::throw_completion(thrown_exception->value());
|
if (completion = iterator_close(*iterator, move(completion)); completion.is_error())
|
||||||
|
return completion.release_error();
|
||||||
}
|
}
|
||||||
|
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue