LibJS: Implement Iterator.from and the WrapForValidIteratorPrototype

Iterator.from creates an Iterator from either an existing iterator or
an iterator-like object. In the latter case, it sets the prototype of
the returned iterator to WrapForValidIteratorPrototype to wrap around
the iterator-like object's iteration methods.
This commit is contained in:
Timothy Flynn 2023-06-24 11:27:30 -04:00 committed by Andreas Kling
parent 5736b53013
commit d9d245faa7
Notes: sideshowbarker 2024-07-18 03:35:30 +09:00
11 changed files with 299 additions and 0 deletions

View file

@ -257,6 +257,7 @@ set(SOURCES
Runtime/WeakSet.cpp
Runtime/WeakSetConstructor.cpp
Runtime/WeakSetPrototype.cpp
Runtime/WrapForValidIteratorPrototype.cpp
Runtime/WrappedFunction.cpp
Script.cpp
SourceCode.cpp

View file

@ -229,6 +229,7 @@ class AsyncFromSyncIteratorPrototype;
class AsyncGenerator;
class AsyncGeneratorPrototype;
class GeneratorPrototype;
class WrapForValidIteratorPrototype;
class TypedArrayConstructor;
class TypedArrayPrototype;

View file

@ -126,6 +126,7 @@
#include <LibJS/Runtime/WeakRefPrototype.h>
#include <LibJS/Runtime/WeakSetConstructor.h>
#include <LibJS/Runtime/WeakSetPrototype.h>
#include <LibJS/Runtime/WrapForValidIteratorPrototype.h>
namespace JS {
@ -199,6 +200,7 @@ ThrowCompletionOr<void> Intrinsics::initialize_intrinsics(Realm& realm)
m_async_generator_prototype = heap().allocate<AsyncGeneratorPrototype>(realm, realm).release_allocated_value_but_fixme_should_propagate_errors();
m_generator_prototype = heap().allocate<GeneratorPrototype>(realm, realm).release_allocated_value_but_fixme_should_propagate_errors();
m_intl_segments_prototype = heap().allocate<Intl::SegmentsPrototype>(realm, realm).release_allocated_value_but_fixme_should_propagate_errors();
m_wrap_for_valid_iterator_prototype = heap().allocate<WrapForValidIteratorPrototype>(realm, realm).release_allocated_value_but_fixme_should_propagate_errors();
// These must be initialized before allocating...
// - AggregateErrorPrototype, which uses ErrorPrototype as its prototype
@ -356,6 +358,7 @@ void Intrinsics::visit_edges(Visitor& visitor)
visitor.visit(m_async_generator_prototype);
visitor.visit(m_generator_prototype);
visitor.visit(m_intl_segments_prototype);
visitor.visit(m_wrap_for_valid_iterator_prototype);
visitor.visit(m_eval_function);
visitor.visit(m_is_finite_function);
visitor.visit(m_is_nan_function);

View file

@ -29,6 +29,7 @@ public:
NonnullGCPtr<Object> async_from_sync_iterator_prototype() { return *m_async_from_sync_iterator_prototype; }
NonnullGCPtr<Object> async_generator_prototype() { return *m_async_generator_prototype; }
NonnullGCPtr<Object> generator_prototype() { return *m_generator_prototype; }
NonnullGCPtr<Object> wrap_for_valid_iterator_prototype() { return *m_wrap_for_valid_iterator_prototype; }
// Alias for the AsyncGenerator Prototype Object used by the spec (%AsyncGeneratorFunction.prototype.prototype%)
NonnullGCPtr<Object> async_generator_function_prototype_prototype() { return *m_async_generator_prototype; }
@ -128,6 +129,7 @@ private:
GCPtr<Object> m_async_from_sync_iterator_prototype;
GCPtr<Object> m_async_generator_prototype;
GCPtr<Object> m_generator_prototype;
GCPtr<Object> m_wrap_for_valid_iterator_prototype;
// Not included in JS_ENUMERATE_INTL_OBJECTS due to missing distinct constructor
GCPtr<Object> m_intl_segments_prototype;

View file

@ -4,7 +4,9 @@
* SPDX-License-Identifier: BSD-2-Clause
*/
#include <LibJS/Runtime/AbstractOperations.h>
#include <LibJS/Runtime/Iterator.h>
#include <LibJS/Runtime/VM.h>
namespace JS {
@ -24,4 +26,47 @@ Iterator::Iterator(Object& prototype)
{
}
// 2.1.1 GetIteratorDirect ( obj ), https://tc39.es/proposal-iterator-helpers/#sec-getiteratorflattenable
ThrowCompletionOr<IteratorRecord> get_iterator_direct(VM& vm, Object& object)
{
// 1. Let nextMethod be ? Get(obj, "next").
auto next_method = TRY(object.get(vm.names.next));
// 2. Let iteratorRecord be Record { [[Iterator]]: obj, [[NextMethod]]: nextMethod, [[Done]]: false }.
IteratorRecord iterator_record { .iterator = object, .next_method = next_method, .done = false };
// 3. Return iteratorRecord.
return iterator_record;
}
ThrowCompletionOr<IteratorRecord> get_iterator_flattenable(VM& vm, Value object)
{
// 1. If obj is not an Object, throw a TypeError exception.
if (!object.is_object())
return vm.throw_completion<TypeError>(ErrorType::NotAnObject, "obj"sv);
// 2. Let method be ? GetMethod(obj, @@iterator).
auto method = TRY(object.get_method(vm, vm.well_known_symbol_iterator()));
Value iterator;
// 3. If method is undefined, then
if (!method) {
// a. Let iterator be obj.
iterator = object;
}
// 4. Else,
else {
// a. Let iterator be ? Call(method, obj).
iterator = TRY(call(vm, method, object));
}
// 5. If iterator is not an Object, throw a TypeError exception.
if (!iterator.is_object())
return vm.throw_completion<TypeError>(ErrorType::NotAnObject, "iterator"sv);
// 6. Return ? GetIteratorDirect(iterator).
return TRY(get_iterator_direct(vm, iterator.as_object()));
}
}

View file

@ -35,4 +35,7 @@ private:
IteratorRecord m_iterated; // [[Iterated]]
};
ThrowCompletionOr<IteratorRecord> get_iterator_direct(VM&, Object&);
ThrowCompletionOr<IteratorRecord> get_iterator_flattenable(VM&, Value);
}

View file

@ -29,6 +29,9 @@ ThrowCompletionOr<void> IteratorConstructor::initialize(Realm& realm)
// 3.1.1.2.1 Iterator.prototype, https://tc39.es/proposal-iterator-helpers/#sec-iterator.prototype
define_direct_property(vm.names.prototype, realm.intrinsics().iterator_prototype(), 0);
u8 attr = Attribute::Writable | Attribute::Configurable;
define_native_function(realm, vm.names.from, from, 1, attr);
define_direct_property(vm.names.length, Value(0), Attribute::Configurable);
return {};
@ -56,4 +59,35 @@ ThrowCompletionOr<NonnullGCPtr<Object>> IteratorConstructor::construct(FunctionO
return TRY(ordinary_create_from_constructor<Iterator>(vm, new_target, &Intrinsics::iterator_prototype));
}
// 3.1.1.2.2 Iterator.from ( O ), https://tc39.es/proposal-iterator-helpers/#sec-iterator.from
JS_DEFINE_NATIVE_FUNCTION(IteratorConstructor::from)
{
auto& realm = *vm.current_realm();
auto object = vm.argument(0);
// 1. If O is a String, set O to ! ToObject(O).
if (object.is_string())
object = MUST_OR_THROW_OOM(object.to_object(vm));
// 2. Let iteratorRecord be ? GetIteratorFlattenable(O).
auto iterator_record = TRY(get_iterator_flattenable(vm, object));
// 3. Let hasInstance be ? OrdinaryHasInstance(%Iterator%, iteratorRecord.[[Iterator]]).
auto has_instance = TRY(ordinary_has_instance(vm, iterator_record.iterator, realm.intrinsics().iterator_constructor()));
// 4. If hasInstance is true, then
if (has_instance.is_boolean() && has_instance.as_bool()) {
// a. Return iteratorRecord.[[Iterator]].
return iterator_record.iterator;
}
// 5. Let wrapper be OrdinaryObjectCreate(%WrapForValidIteratorPrototype%, « [[Iterated]] »).
// 6. Set wrapper.[[Iterated]] to iteratorRecord.
auto wrapper = MUST_OR_THROW_OOM(Iterator::create(realm, realm.intrinsics().wrap_for_valid_iterator_prototype(), move(iterator_record)));
// 7. Return wrapper.
return wrapper;
}
}

View file

@ -24,6 +24,8 @@ private:
explicit IteratorConstructor(Realm&);
virtual bool has_constructor() const override { return true; }
JS_DECLARE_NATIVE_FUNCTION(from);
};
}

View file

@ -0,0 +1,71 @@
/*
* Copyright (c) 2023, Tim Flynn <trflynn89@serenityos.org>
*
* SPDX-License-Identifier: BSD-2-Clause
*/
#include <LibJS/Runtime/AbstractOperations.h>
#include <LibJS/Runtime/IteratorOperations.h>
#include <LibJS/Runtime/WrapForValidIteratorPrototype.h>
namespace JS {
// 3.1.1.2.2.1 The %WrapForValidIteratorPrototype% Object, https://tc39.es/proposal-iterator-helpers/#sec-wrapforvaliditeratorprototype-object
WrapForValidIteratorPrototype::WrapForValidIteratorPrototype(Realm& realm)
: PrototypeObject(realm.intrinsics().iterator_prototype())
{
}
ThrowCompletionOr<void> WrapForValidIteratorPrototype::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, 0, attr);
define_native_function(realm, vm.names.return_, return_, 0, attr);
return {};
}
// 3.1.1.2.2.1.1 %WrapForValidIteratorPrototype%.next ( ), https://tc39.es/proposal-iterator-helpers/#sec-wrapforvaliditeratorprototype.next
JS_DEFINE_NATIVE_FUNCTION(WrapForValidIteratorPrototype::next)
{
// 1. Let O be this value.
// 2. Perform ? RequireInternalSlot(O, [[Iterated]]).
auto object = TRY(typed_this_object(vm));
// 3. Let iteratorRecord be O.[[Iterated]].
auto const& iterator_record = object->iterated();
// 4. Return ? Call(iteratorRecord.[[NextMethod]], iteratorRecord.[[Iterator]]).
return TRY(call(vm, iterator_record.next_method, iterator_record.iterator));
}
// 3.1.1.2.2.1.2 %WrapForValidIteratorPrototype%.return ( ), https://tc39.es/proposal-iterator-helpers/#sec-wrapforvaliditeratorprototype.return
JS_DEFINE_NATIVE_FUNCTION(WrapForValidIteratorPrototype::return_)
{
// 1. Let O be this value.
// 2. Perform ? RequireInternalSlot(O, [[Iterated]]).
auto object = TRY(typed_this_object(vm));
// 3. Let iterator be O.[[Iterated]].[[Iterator]].
auto iterator = object->iterated().iterator;
// 4. Assert: iterator is an Object.
VERIFY(iterator);
// 5. Let returnMethod be ? GetMethod(iterator, "return").
auto return_method = TRY(Value { iterator }.get_method(vm, vm.names.return_));
// 6. If returnMethod is undefined, then
if (!return_method) {
// a. Return CreateIterResultObject(undefined, true).
return create_iterator_result_object(vm, js_undefined(), true);
}
// 7. Return ? Call(returnMethod, iterator).
return TRY(call(vm, return_method, iterator));
}
}

View file

@ -0,0 +1,28 @@
/*
* Copyright (c) 2023, Tim Flynn <trflynn89@serenityos.org>
*
* SPDX-License-Identifier: BSD-2-Clause
*/
#pragma once
#include <LibJS/Runtime/Completion.h>
#include <LibJS/Runtime/Iterator.h>
#include <LibJS/Runtime/PrototypeObject.h>
namespace JS {
class WrapForValidIteratorPrototype final : public PrototypeObject<WrapForValidIteratorPrototype, Iterator> {
JS_PROTOTYPE_OBJECT(WrapForValidIteratorPrototype, Iterator, Iterator);
public:
virtual ThrowCompletionOr<void> initialize(Realm&) override;
private:
explicit WrapForValidIteratorPrototype(Realm&);
JS_DECLARE_NATIVE_FUNCTION(next);
JS_DECLARE_NATIVE_FUNCTION(return_);
};
}

View file

@ -0,0 +1,109 @@
describe("errors", () => {
test("called with non-Object", () => {
expect(() => {
Iterator.from(Symbol.hasInstance);
}).toThrowWithMessage(TypeError, "obj is not an object");
});
test("@@iterator is not callable", () => {
const iterable = {};
iterable[Symbol.iterator] = 12389;
expect(() => {
Iterator.from(iterable);
}).toThrowWithMessage(TypeError, "12389 is not a function");
});
test("@@iterator throws an exception", () => {
function TestError() {}
const iterable = {};
iterable[Symbol.iterator] = () => {
throw new TestError();
};
expect(() => {
Iterator.from(iterable);
}).toThrow(TestError);
});
test("@@iterator return a non-Object", () => {
const iterable = {};
iterable[Symbol.iterator] = () => {
return Symbol.hasInstance;
};
expect(() => {
Iterator.from(iterable);
}).toThrowWithMessage(TypeError, "iterator is not an object");
});
});
describe("normal behavior", () => {
test("length is 1", () => {
expect(Iterator.from).toHaveLength(1);
});
test("create Iterator from a string", () => {
const iterator = Iterator.from("ab");
let result = iterator.next();
expect(result.value).toBe("a");
expect(result.done).toBeFalse();
result = iterator.next();
expect(result.value).toBe("b");
expect(result.done).toBeFalse();
result = iterator.next();
expect(result.value).toBeUndefined();
expect(result.done).toBeTrue();
});
test("create Iterator from generator", () => {
function* generator() {
yield 1;
yield 2;
}
const iterator = Iterator.from(generator());
let result = iterator.next();
expect(result.value).toBe(1);
expect(result.done).toBeFalse();
result = iterator.next();
expect(result.value).toBe(2);
expect(result.done).toBeFalse();
result = iterator.next();
expect(result.value).toBeUndefined();
expect(result.done).toBeTrue();
});
test("create Iterator from iterator-like object", () => {
class TestIterator {
next() {
if (this.#first) {
this.#first = false;
return { value: 1, done: false };
}
return { value: undefined, done: true };
}
#first = true;
}
const testIterator = new TestIterator();
const iterator = Iterator.from(testIterator);
let result = iterator.next();
expect(result.value).toBe(1);
expect(result.done).toBeFalse();
result = iterator.next();
expect(result.value).toBeUndefined();
expect(result.done).toBeTrue();
});
});