Przeglądaj źródła

LibJS: Implement Iterator.prototype.constructor according to spec

The spec allows setting a constructor on non built-in Iterator objects.

This is a normative change in the Iterator Helpers proposal. See:
https://github.com/tc39/proposal-iterator-helpers/commit/30b3501
Timothy Flynn 1 rok temu
rodzic
commit
fb228a3d85

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

@@ -306,6 +306,8 @@ JS_ENUMERATE_TYPED_ARRAYS
             initialize_constructor(vm, vm.names.Boolean, *m_##snake_namespace##snake_name##_constructor, m_##snake_namespace##snake_name##_prototype);   \
         else if constexpr (IsSame<Namespace::ConstructorName, FunctionConstructor>)                                                                      \
             initialize_constructor(vm, vm.names.Function, *m_##snake_namespace##snake_name##_constructor, m_##snake_namespace##snake_name##_prototype);  \
+        else if constexpr (IsSame<Namespace::ConstructorName, IteratorConstructor>)                                                                      \
+            initialize_constructor(vm, vm.names.Iterator, *m_##snake_namespace##snake_name##_constructor, nullptr);                                      \
         else if constexpr (IsSame<Namespace::ConstructorName, NumberConstructor>)                                                                        \
             initialize_constructor(vm, vm.names.Number, *m_##snake_namespace##snake_name##_constructor, m_##snake_namespace##snake_name##_prototype);    \
         else if constexpr (IsSame<Namespace::ConstructorName, RegExpConstructor>)                                                                        \

+ 25 - 0
Userland/Libraries/LibJS/Runtime/IteratorPrototype.cpp

@@ -10,6 +10,7 @@
 #include <LibJS/Runtime/FunctionObject.h>
 #include <LibJS/Runtime/GlobalObject.h>
 #include <LibJS/Runtime/Iterator.h>
+#include <LibJS/Runtime/IteratorConstructor.h>
 #include <LibJS/Runtime/IteratorHelper.h>
 #include <LibJS/Runtime/IteratorPrototype.h>
 #include <LibJS/Runtime/ValueInlines.h>
@@ -43,6 +44,9 @@ void IteratorPrototype::initialize(Realm& realm)
     define_native_function(realm, vm.names.every, every, 1, attr);
     define_native_function(realm, vm.names.find, find, 1, attr);
 
+    // 3.1.3.1 Iterator.prototype.constructor, https://tc39.es/proposal-iterator-helpers/#sec-iteratorprototype.constructor
+    define_native_accessor(realm, vm.names.constructor, constructor_getter, constructor_setter, Attribute::Configurable);
+
     // 3.1.3.13 Iterator.prototype [ @@toStringTag ], https://tc39.es/proposal-iterator-helpers/#sec-iteratorprototype-@@tostringtag
     define_native_accessor(realm, vm.well_known_symbol_to_string_tag(), to_string_tag_getter, to_string_tag_setter, Attribute::Configurable);
 }
@@ -54,6 +58,27 @@ JS_DEFINE_NATIVE_FUNCTION(IteratorPrototype::symbol_iterator)
     return vm.this_value();
 }
 
+// 3.1.3.1.1 get Iterator.prototype.constructor, https://tc39.es/proposal-iterator-helpers/#sec-get-iteratorprototype-constructor
+JS_DEFINE_NATIVE_FUNCTION(IteratorPrototype::constructor_getter)
+{
+    auto& realm = *vm.current_realm();
+
+    // 1. Return %Iterator%.
+    return realm.intrinsics().iterator_constructor();
+}
+
+// 3.1.3.1.2 set Iterator.prototype.constructor, https://tc39.es/proposal-iterator-helpers/#sec-set-iteratorprototype-constructor
+JS_DEFINE_NATIVE_FUNCTION(IteratorPrototype::constructor_setter)
+{
+    auto& realm = *vm.current_realm();
+
+    // 1. Perform ? SetterThatIgnoresPrototypeProperties(this value, %Iterator.prototype%, "constructor", v).
+    TRY(setter_that_ignores_prototype_properties(vm, vm.this_value(), realm.intrinsics().iterator_prototype(), vm.names.constructor, vm.argument(0)));
+
+    // 2. Return undefined.
+    return js_undefined();
+}
+
 // 3.1.3.2 Iterator.prototype.map ( mapper ), https://tc39.es/proposal-iterator-helpers/#sec-iteratorprototype.map
 JS_DEFINE_NATIVE_FUNCTION(IteratorPrototype::map)
 {

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

@@ -22,6 +22,9 @@ public:
 private:
     IteratorPrototype(Realm&);
 
+    JS_DECLARE_NATIVE_FUNCTION(constructor_getter);
+    JS_DECLARE_NATIVE_FUNCTION(constructor_setter);
+
     JS_DECLARE_NATIVE_FUNCTION(symbol_iterator);
     JS_DECLARE_NATIVE_FUNCTION(map);
     JS_DECLARE_NATIVE_FUNCTION(filter);

+ 33 - 0
Userland/Libraries/LibJS/Tests/builtins/Iterator/Iterator.prototype.constructor.js

@@ -0,0 +1,33 @@
+const sentinel = "whf :^)";
+
+describe("errors", () => {
+    test("setter called on non-object", () => {
+        let { get, set } = Object.getOwnPropertyDescriptor(Iterator.prototype, "constructor");
+
+        expect(() => {
+            set.call(undefined, sentinel);
+        }).toThrowWithMessage(TypeError, "undefined is not an object");
+    });
+
+    test("cannot set the built-in Iterator's constructor", () => {
+        expect(() => {
+            Iterator.prototype.constructor = sentinel;
+        }).toThrowWithMessage(
+            TypeError,
+            "Cannot write to non-writable property '[object IteratorPrototype]'"
+        );
+    });
+});
+
+describe("correct behavior", () => {
+    test("basic functionality", () => {
+        expect(Iterator.prototype.constructor).toBe(Iterator);
+    });
+
+    test("constructor setter", () => {
+        let Proto = Object.create(Iterator.prototype);
+        Proto.constructor = sentinel;
+
+        expect(Proto.constructor).toBe(sentinel);
+    });
+});