Jelajahi Sumber

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.
Timothy Flynn 2 tahun lalu
induk
melakukan
d9d245faa7

+ 1 - 0
Userland/Libraries/LibJS/CMakeLists.txt

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

+ 1 - 0
Userland/Libraries/LibJS/Forward.h

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

+ 3 - 0
Userland/Libraries/LibJS/Runtime/Intrinsics.cpp

@@ -126,6 +126,7 @@
 #include <LibJS/Runtime/WeakRefPrototype.h>
 #include <LibJS/Runtime/WeakRefPrototype.h>
 #include <LibJS/Runtime/WeakSetConstructor.h>
 #include <LibJS/Runtime/WeakSetConstructor.h>
 #include <LibJS/Runtime/WeakSetPrototype.h>
 #include <LibJS/Runtime/WeakSetPrototype.h>
+#include <LibJS/Runtime/WrapForValidIteratorPrototype.h>
 
 
 namespace JS {
 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_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_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_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...
     // These must be initialized before allocating...
     // - AggregateErrorPrototype, which uses ErrorPrototype as its prototype
     // - 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_async_generator_prototype);
     visitor.visit(m_generator_prototype);
     visitor.visit(m_generator_prototype);
     visitor.visit(m_intl_segments_prototype);
     visitor.visit(m_intl_segments_prototype);
+    visitor.visit(m_wrap_for_valid_iterator_prototype);
     visitor.visit(m_eval_function);
     visitor.visit(m_eval_function);
     visitor.visit(m_is_finite_function);
     visitor.visit(m_is_finite_function);
     visitor.visit(m_is_nan_function);
     visitor.visit(m_is_nan_function);

+ 2 - 0
Userland/Libraries/LibJS/Runtime/Intrinsics.h

@@ -29,6 +29,7 @@ public:
     NonnullGCPtr<Object> async_from_sync_iterator_prototype() { return *m_async_from_sync_iterator_prototype; }
     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> async_generator_prototype() { return *m_async_generator_prototype; }
     NonnullGCPtr<Object> generator_prototype() { return *m_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%)
     // 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; }
     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_from_sync_iterator_prototype;
     GCPtr<Object> m_async_generator_prototype;
     GCPtr<Object> m_async_generator_prototype;
     GCPtr<Object> m_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
     // Not included in JS_ENUMERATE_INTL_OBJECTS due to missing distinct constructor
     GCPtr<Object> m_intl_segments_prototype;
     GCPtr<Object> m_intl_segments_prototype;

+ 45 - 0
Userland/Libraries/LibJS/Runtime/Iterator.cpp

@@ -4,7 +4,9 @@
  * SPDX-License-Identifier: BSD-2-Clause
  * SPDX-License-Identifier: BSD-2-Clause
  */
  */
 
 
+#include <LibJS/Runtime/AbstractOperations.h>
 #include <LibJS/Runtime/Iterator.h>
 #include <LibJS/Runtime/Iterator.h>
+#include <LibJS/Runtime/VM.h>
 
 
 namespace JS {
 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()));
+}
+
 }
 }

+ 3 - 0
Userland/Libraries/LibJS/Runtime/Iterator.h

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

+ 34 - 0
Userland/Libraries/LibJS/Runtime/IteratorConstructor.cpp

@@ -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
     // 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);
     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);
     define_direct_property(vm.names.length, Value(0), Attribute::Configurable);
 
 
     return {};
     return {};
@@ -56,4 +59,35 @@ ThrowCompletionOr<NonnullGCPtr<Object>> IteratorConstructor::construct(FunctionO
     return TRY(ordinary_create_from_constructor<Iterator>(vm, new_target, &Intrinsics::iterator_prototype));
     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;
+}
+
 }
 }

+ 2 - 0
Userland/Libraries/LibJS/Runtime/IteratorConstructor.h

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

+ 71 - 0
Userland/Libraries/LibJS/Runtime/WrapForValidIteratorPrototype.cpp

@@ -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));
+}
+
+}

+ 28 - 0
Userland/Libraries/LibJS/Runtime/WrapForValidIteratorPrototype.h

@@ -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_);
+};
+
+}

+ 109 - 0
Userland/Libraries/LibJS/Tests/builtins/Iterator/Iterator.from.js

@@ -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();
+    });
+});