summaryrefslogtreecommitdiff
path: root/Userland/Libraries
diff options
context:
space:
mode:
authorTimothy Flynn <trflynn89@pm.me>2021-10-20 13:36:14 -0400
committerLinus Groh <mail@linusgroh.de>2021-10-21 00:26:45 +0100
commitec54a7b5b09f7f86c99c247477b29267a274991b (patch)
tree3da224b62570ae2a232a63b29cc9086d9757fbbb /Userland/Libraries
parent04b4307b3d57a4c48abfa19a22d4745b23760d78 (diff)
downloadserenity-ec54a7b5b09f7f86c99c247477b29267a274991b.zip
LibJS: Implement IteratorClose with Completions and align to the spec
Diffstat (limited to 'Userland/Libraries')
-rw-r--r--Userland/Libraries/LibJS/Runtime/ArrayConstructor.cpp17
-rw-r--r--Userland/Libraries/LibJS/Runtime/Intl/ListFormat.cpp5
-rw-r--r--Userland/Libraries/LibJS/Runtime/IteratorOperations.cpp74
-rw-r--r--Userland/Libraries/LibJS/Runtime/IteratorOperations.h2
-rw-r--r--Userland/Libraries/LibJS/Runtime/PromiseConstructor.cpp52
-rw-r--r--Userland/Libraries/LibJS/Runtime/Temporal/AbstractOperations.cpp3
-rw-r--r--Userland/Libraries/LibJS/Runtime/Temporal/CalendarPrototype.cpp15
-rw-r--r--Userland/Libraries/LibJS/Runtime/VM.cpp10
8 files changed, 98 insertions, 80 deletions
diff --git a/Userland/Libraries/LibJS/Runtime/ArrayConstructor.cpp b/Userland/Libraries/LibJS/Runtime/ArrayConstructor.cpp
index 32bc5740cb..670b4bea60 100644
--- a/Userland/Libraries/LibJS/Runtime/ArrayConstructor.cpp
+++ b/Userland/Libraries/LibJS/Runtime/ArrayConstructor.cpp
@@ -123,9 +123,8 @@ JS_DEFINE_OLD_NATIVE_FUNCTION(ArrayConstructor::from)
size_t k = 0;
while (true) {
if (k >= MAX_ARRAY_LIKE_INDEX) {
- vm.throw_exception<TypeError>(global_object, ErrorType::ArrayMaxSize);
- iterator_close(*iterator);
- return {};
+ auto error = vm.throw_completion<TypeError>(global_object, ErrorType::ArrayMaxSize);
+ return TRY_OR_DISCARD(iterator_close(*iterator, move(error)));
}
auto* next = TRY_OR_DISCARD(iterator_step(global_object, *iterator));
@@ -139,20 +138,16 @@ JS_DEFINE_OLD_NATIVE_FUNCTION(ArrayConstructor::from)
Value mapped_value;
if (map_fn) {
auto mapped_value_or_error = vm.call(*map_fn, this_arg, next_value, Value(k));
- if (mapped_value_or_error.is_error()) {
- iterator_close(*iterator);
- return {};
- }
+ if (mapped_value_or_error.is_error())
+ return TRY_OR_DISCARD(iterator_close(*iterator, mapped_value_or_error.release_error()));
mapped_value = mapped_value_or_error.release_value();
} else {
mapped_value = next_value;
}
auto result_or_error = array_object.create_data_property_or_throw(k, mapped_value);
- if (result_or_error.is_error()) {
- iterator_close(*iterator);
- return {};
- }
+ if (result_or_error.is_error())
+ return TRY_OR_DISCARD(iterator_close(*iterator, result_or_error.release_error()));
++k;
}
diff --git a/Userland/Libraries/LibJS/Runtime/Intl/ListFormat.cpp b/Userland/Libraries/LibJS/Runtime/Intl/ListFormat.cpp
index c44267031f..03a56dfe84 100644
--- a/Userland/Libraries/LibJS/Runtime/Intl/ListFormat.cpp
+++ b/Userland/Libraries/LibJS/Runtime/Intl/ListFormat.cpp
@@ -297,11 +297,10 @@ ThrowCompletionOr<Vector<String>> string_list_from_iterable(GlobalObject& global
// ii. If Type(nextValue) is not String, then
if (!next_value.is_string()) {
// 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).
- iterator_close(*iterator_record);
- return completion;
+ return iterator_close(*iterator_record, move(error));
}
// iii. Append nextValue to the end of the List list.
diff --git a/Userland/Libraries/LibJS/Runtime/IteratorOperations.cpp b/Userland/Libraries/LibJS/Runtime/IteratorOperations.cpp
index 6271189b29..a0a1a894ed 100644
--- a/Userland/Libraries/LibJS/Runtime/IteratorOperations.cpp
+++ b/Userland/Libraries/LibJS/Runtime/IteratorOperations.cpp
@@ -9,6 +9,7 @@
#include <LibJS/Runtime/FunctionObject.h>
#include <LibJS/Runtime/GlobalObject.h>
#include <LibJS/Runtime/IteratorOperations.h>
+#include <LibJS/Runtime/TemporaryClearException.h>
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
-void iterator_close(Object& iterator)
+Completion iterator_close(Object& iterator, Completion completion)
{
auto& vm = iterator.vm();
auto& global_object = iterator.global_object();
- // Emulates `completion` behavior
- auto* completion_exception = vm.exception();
- vm.clear_exception();
- 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_);
- Value result;
- if (!return_method_or_error.is_error()) { // If innerResult.[[Type]] is normal, then
- auto return_method = return_method_or_error.release_value();
+ // The callers of iterator_close() are often in an exceptional state.
+ // Temporarily clear that exception for invocation(s) to Call.
+ TemporaryClearException clear_exception(vm);
+
+ // 3. Let innerResult be GetMethod(iterator, "return").
+ auto inner_result_or_error = Value(&iterator).get_method(global_object, vm.names.return_);
+ Value inner_result;
+
+ // 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)
- 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);
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
- 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).
- if (return_method_or_error.is_error())
- return; // If innerResult.[[Type]] is throw, return Completion(innerResult).
- if (!result.is_object()) {
- vm.throw_exception<TypeError>(global_object, ErrorType::IterableReturnBadReturn);
- return; // If Type(innerResult.[[Value]]) is not Object, throw a TypeError exception.
- }
- restore_completion(); // Return Completion(completion).
+
+ // 5. If completion.[[Type]] is throw, return Completion(completion).
+ if (completion.is_error())
+ return completion;
+
+ // 6. If innerResult.[[Type]] is throw, return Completion(innerResult).
+ if (inner_result_or_error.is_error())
+ return inner_result_or_error.release_error();
+
+ // 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
@@ -165,10 +173,8 @@ Completion get_iterator_values(GlobalObject& global_object, Value iterable, Iter
auto next_value = TRY(iterator_value(global_object, *next_object));
- if (auto completion = callback(next_value); completion.has_value()) {
- iterator_close(*iterator);
- return completion.release_value();
- }
+ if (auto completion = callback(next_value); completion.has_value())
+ return iterator_close(*iterator, completion.release_value());
}
}
diff --git a/Userland/Libraries/LibJS/Runtime/IteratorOperations.h b/Userland/Libraries/LibJS/Runtime/IteratorOperations.h
index 8338983bf7..385b275250 100644
--- a/Userland/Libraries/LibJS/Runtime/IteratorOperations.h
+++ b/Userland/Libraries/LibJS/Runtime/IteratorOperations.h
@@ -25,7 +25,7 @@ ThrowCompletionOr<Object*> iterator_next(Object& iterator, Value value = {});
ThrowCompletionOr<Object*> iterator_step(GlobalObject&, Object& iterator);
ThrowCompletionOr<bool> iterator_complete(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);
MarkedValueList iterable_to_list(GlobalObject&, Value iterable, Value method = {});
diff --git a/Userland/Libraries/LibJS/Runtime/PromiseConstructor.cpp b/Userland/Libraries/LibJS/Runtime/PromiseConstructor.cpp
index 3a9feb3b1b..5272d60231 100644
--- a/Userland/Libraries/LibJS/Runtime/PromiseConstructor.cpp
+++ b/Userland/Libraries/LibJS/Runtime/PromiseConstructor.cpp
@@ -302,11 +302,16 @@ JS_DEFINE_OLD_NATIVE_FUNCTION(PromiseConstructor::all)
auto* iterator_record = iterator_record_or_error.release_value();
auto result = perform_promise_all(global_object, *iterator_record, constructor, promise_capability, promise_resolve);
- if (vm.exception()) {
- if (!iterator_record_is_complete(global_object, *iterator_record))
- iterator_close(*iterator_record);
+ if (auto* exception = vm.exception()) {
+ // FIXME: Once perform_promise_all returns a throw completion, pass that to iterator_close() instead.
+ auto result_error = throw_completion(exception->value());
+
+ 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, promise_capability);
+ auto abrupt = if_abrupt_reject_promise(global_object, result_error, promise_capability);
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 result = perform_promise_all_settled(global_object, *iterator_record, constructor, promise_capability, promise_resolve);
- if (vm.exception()) {
- if (!iterator_record_is_complete(global_object, *iterator_record))
- iterator_close(*iterator_record);
+ if (auto* exception = vm.exception()) {
+ // FIXME: Once perform_promise_all_settled returns a throw completion, pass that to iterator_close() instead.
+ auto result_error = throw_completion(exception->value());
+
+ 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, promise_capability);
+ auto abrupt = if_abrupt_reject_promise(global_object, result_error, promise_capability);
return abrupt.value();
}
@@ -362,11 +372,16 @@ JS_DEFINE_OLD_NATIVE_FUNCTION(PromiseConstructor::any)
auto* iterator_record = iterator_record_or_error.release_value();
auto result = perform_promise_any(global_object, *iterator_record, constructor, promise_capability, promise_resolve);
- if (vm.exception()) {
- if (!iterator_record_is_complete(global_object, *iterator_record))
- iterator_close(*iterator_record);
+ if (auto* exception = vm.exception()) {
+ // FIXME: Once perform_promise_any returns a throw completion, pass that to iterator_close() instead.
+ auto result_error = throw_completion(exception->value());
+
+ 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, promise_capability);
+ auto abrupt = if_abrupt_reject_promise(global_object, result_error, promise_capability);
return abrupt.value();
}
@@ -392,11 +407,16 @@ JS_DEFINE_OLD_NATIVE_FUNCTION(PromiseConstructor::race)
auto* iterator_record = iterator_record_or_error.release_value();
auto result = perform_promise_race(global_object, *iterator_record, constructor, promise_capability, promise_resolve);
- if (vm.exception()) {
- if (!iterator_record_is_complete(global_object, *iterator_record))
- iterator_close(*iterator_record);
+ if (auto* exception = vm.exception()) {
+ // FIXME: Once perform_promise_race returns a throw completion, pass that to iterator_close() instead.
+ auto result_error = throw_completion(exception->value());
+
+ 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, promise_capability);
+ auto abrupt = if_abrupt_reject_promise(global_object, result_error, promise_capability);
return abrupt.value();
}
diff --git a/Userland/Libraries/LibJS/Runtime/Temporal/AbstractOperations.cpp b/Userland/Libraries/LibJS/Runtime/Temporal/AbstractOperations.cpp
index ecc42e189e..f2a3c302ec 100644
--- a/Userland/Libraries/LibJS/Runtime/Temporal/AbstractOperations.cpp
+++ b/Userland/Libraries/LibJS/Runtime/Temporal/AbstractOperations.cpp
@@ -63,8 +63,7 @@ ThrowCompletionOr<MarkedValueList> iterable_to_list_of_type(GlobalObject& global
// 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());
// 2. Return ? IteratorClose(iteratorRecord, completion).
- iterator_close(*iterator_record);
- return completion;
+ return iterator_close(*iterator_record, move(completion));
}
// iii. Append nextValue to the end of the List values.
values.append(next_value);
diff --git a/Userland/Libraries/LibJS/Runtime/Temporal/CalendarPrototype.cpp b/Userland/Libraries/LibJS/Runtime/Temporal/CalendarPrototype.cpp
index 8b3ce39613..36bdab819c 100644
--- a/Userland/Libraries/LibJS/Runtime/Temporal/CalendarPrototype.cpp
+++ b/Userland/Libraries/LibJS/Runtime/Temporal/CalendarPrototype.cpp
@@ -513,31 +513,28 @@ JS_DEFINE_OLD_NATIVE_FUNCTION(CalendarPrototype::fields)
// ii. If Type(nextValue) is not String, then
if (!next_value.is_string()) {
// 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).
- iterator_close(*iterator_record);
- return {};
+ return TRY_OR_DISCARD(iterator_close(*iterator_record, move(completion)));
}
// iii. If fieldNames contains nextValue, then
if (field_names.contains_slow(next_value)) {
// 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).
- iterator_close(*iterator_record);
- return {};
+ return TRY_OR_DISCARD(iterator_close(*iterator_record, move(completion)));
}
// 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)) {
// 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).
- iterator_close(*iterator_record);
- return {};
+ return TRY_OR_DISCARD(iterator_close(*iterator_record, move(completion)));
}
// v. Append nextValue to the end of the List fieldNames.
diff --git a/Userland/Libraries/LibJS/Runtime/VM.cpp b/Userland/Libraries/LibJS/Runtime/VM.cpp
index 5e8dc951a8..6e3e3d6a2e 100644
--- a/Userland/Libraries/LibJS/Runtime/VM.cpp
+++ b/Userland/Libraries/LibJS/Runtime/VM.cpp
@@ -202,11 +202,13 @@ ThrowCompletionOr<void> VM::binding_initialization(NonnullRefPtr<BindingPattern>
auto result = iterator_binding_initialization(*target, iterator, iterator_done, environment, global_object);
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(*iterator);
- if (auto* thrown_exception = exception())
- return JS::throw_completion(thrown_exception->value());
+ // iterator_close() always returns a Completion, which ThrowCompletionOr will interpret as a throw
+ // completion. So only return the result of iterator_close() if it is indeed a throw completion.
+ auto completion = result.is_throw_completion() ? result.release_error() : normal_completion({});
+ if (completion = iterator_close(*iterator, move(completion)); completion.is_error())
+ return completion.release_error();
}
+
return result;
}
}