mirror of
				https://github.com/RGBCube/serenity
				synced 2025-10-31 18:42:43 +00:00 
			
		
		
		
	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:
		
							parent
							
								
									5736b53013
								
							
						
					
					
						commit
						d9d245faa7
					
				
					 11 changed files with 299 additions and 0 deletions
				
			
		|  | @ -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 | ||||
|  |  | |||
|  | @ -229,6 +229,7 @@ class AsyncFromSyncIteratorPrototype; | |||
| class AsyncGenerator; | ||||
| class AsyncGeneratorPrototype; | ||||
| class GeneratorPrototype; | ||||
| class WrapForValidIteratorPrototype; | ||||
| 
 | ||||
| class TypedArrayConstructor; | ||||
| class TypedArrayPrototype; | ||||
|  |  | |||
|  | @ -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); | ||||
|  |  | |||
|  | @ -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; | ||||
|  |  | |||
|  | @ -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())); | ||||
| } | ||||
| 
 | ||||
| } | ||||
|  |  | |||
|  | @ -35,4 +35,7 @@ private: | |||
|     IteratorRecord m_iterated; // [[Iterated]]
 | ||||
| }; | ||||
| 
 | ||||
| ThrowCompletionOr<IteratorRecord> get_iterator_direct(VM&, Object&); | ||||
| ThrowCompletionOr<IteratorRecord> get_iterator_flattenable(VM&, Value); | ||||
| 
 | ||||
| } | ||||
|  |  | |||
|  | @ -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; | ||||
| } | ||||
| 
 | ||||
| } | ||||
|  |  | |||
|  | @ -24,6 +24,8 @@ private: | |||
|     explicit IteratorConstructor(Realm&); | ||||
| 
 | ||||
|     virtual bool has_constructor() const override { return true; } | ||||
| 
 | ||||
|     JS_DECLARE_NATIVE_FUNCTION(from); | ||||
| }; | ||||
| 
 | ||||
| } | ||||
|  |  | |||
|  | @ -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)); | ||||
| } | ||||
| 
 | ||||
| } | ||||
|  | @ -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_); | ||||
| }; | ||||
| 
 | ||||
| } | ||||
|  | @ -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(); | ||||
|     }); | ||||
| }); | ||||
		Loading…
	
	Add table
		Add a link
		
	
		Reference in a new issue
	
	 Timothy Flynn
						Timothy Flynn