LibJS: Implement Iterator.prototype [ @@toStringTag ] according to spec

The spec allows setting the prototype on non built-in Iterator objects.

This is a normative change in the Iterator Helpers proposal. See:
30b3501
This commit is contained in:
Timothy Flynn 2024-07-15 20:38:28 -04:00 committed by Andreas Kling
commit 734e37442d
Notes: sideshowbarker 2024-07-18 23:45:47 +09:00
5 changed files with 94 additions and 5 deletions

View file

@ -353,6 +353,42 @@ ThrowCompletionOr<MarkedVector<Value>> iterator_to_list(VM& vm, IteratorRecord&
}
}
// 2.1 SetterThatIgnoresPrototypeProperties ( this, home, p, v ), https://tc39.es/proposal-iterator-helpers/#sec-SetterThatIgnoresPrototypeProperties
ThrowCompletionOr<void> setter_that_ignores_prototype_properties(VM& vm, Value this_, Object const& home, PropertyKey const& property, Value value)
{
// 1. If this is not an Object, then
if (!this_.is_object()) {
// a. Throw a TypeError exception.
return vm.throw_completion<TypeError>(ErrorType::NotAnObject, this_);
}
auto& this_object = this_.as_object();
// 2. If this is home, then
if (&this_object == &home) {
// a. NOTE: Throwing here emulates assignment to a non-writable data property on the home object in strict mode code.
// b. Throw a TypeError exception.
return vm.throw_completion<TypeError>(ErrorType::DescWriteNonWritable, this_);
}
// 3. Let desc be ? this.[[GetOwnProperty]](p).
auto desc = TRY(this_object.internal_get_own_property(property));
// 4. If desc is undefined, then
if (!desc.has_value()) {
// a. Perform ? CreateDataPropertyOrThrow(this, p, v).
TRY(this_object.create_data_property_or_throw(property, value));
}
// 5. Else,
else {
// a. Perform ? Set(this, p, v, true).
TRY(this_object.set(property, value, Object::ShouldThrowExceptions::Yes));
}
// 6. Return unused.
return {};
}
// Non-standard
Completion get_iterator_values(VM& vm, Value iterable, IteratorValueCallback callback)
{

View file

@ -83,6 +83,7 @@ Completion iterator_close(VM&, IteratorRecord const&, Completion);
Completion async_iterator_close(VM&, IteratorRecord const&, Completion);
NonnullGCPtr<Object> create_iterator_result_object(VM&, Value, bool done);
ThrowCompletionOr<MarkedVector<Value>> iterator_to_list(VM&, IteratorRecord&);
ThrowCompletionOr<void> setter_that_ignores_prototype_properties(VM&, Value this_, Object const& home, PropertyKey const& property, Value value);
using IteratorValueCallback = Function<Optional<Completion>(Value)>;
Completion get_iterator_values(VM&, Value iterable, IteratorValueCallback callback);

View file

@ -29,9 +29,6 @@ void IteratorPrototype::initialize(Realm& realm)
auto& vm = this->vm();
Base::initialize(realm);
// 3.1.3.13 Iterator.prototype [ @@toStringTag ], https://tc39.es/proposal-iterator-helpers/#sec-iteratorprototype-@@tostringtag
define_direct_property(vm.well_known_symbol_to_string_tag(), PrimitiveString::create(vm, "Iterator"_string), Attribute::Configurable | Attribute::Writable);
u8 attr = Attribute::Writable | Attribute::Configurable;
define_native_function(realm, vm.well_known_symbol_iterator(), symbol_iterator, 0, attr);
define_native_function(realm, vm.names.map, map, 1, attr);
@ -45,6 +42,9 @@ void IteratorPrototype::initialize(Realm& realm)
define_native_function(realm, vm.names.some, some, 1, attr);
define_native_function(realm, vm.names.every, every, 1, attr);
define_native_function(realm, vm.names.find, find, 1, attr);
// 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);
}
// 27.1.2.1 %IteratorPrototype% [ @@iterator ] ( ), https://tc39.es/ecma262/#sec-%iteratorprototype%-@@iterator
@ -749,4 +749,23 @@ JS_DEFINE_NATIVE_FUNCTION(IteratorPrototype::find)
}
}
// 3.1.3.13.1 get Iterator.prototype [ @@toStringTag ], https://tc39.es/proposal-iterator-helpers/#sec-get-iteratorprototype-@@tostringtag
JS_DEFINE_NATIVE_FUNCTION(IteratorPrototype::to_string_tag_getter)
{
// 1. Return "Iterator".
return PrimitiveString::create(vm, vm.names.Iterator.as_string());
}
// 3.1.3.13.2 set Iterator.prototype [ @@toStringTag ], https://tc39.es/proposal-iterator-helpers/#sec-set-iteratorprototype-@@tostringtag
JS_DEFINE_NATIVE_FUNCTION(IteratorPrototype::to_string_tag_setter)
{
auto& realm = *vm.current_realm();
// 1. Perform ? SetterThatIgnoresPrototypeProperties(this value, %Iterator.prototype%, %Symbol.toStringTag%, v).
TRY(setter_that_ignores_prototype_properties(vm, vm.this_value(), realm.intrinsics().iterator_prototype(), vm.well_known_symbol_to_string_tag(), vm.argument(0)));
// 2. Return undefined.
return js_undefined();
}
}

View file

@ -34,6 +34,9 @@ private:
JS_DECLARE_NATIVE_FUNCTION(some);
JS_DECLARE_NATIVE_FUNCTION(every);
JS_DECLARE_NATIVE_FUNCTION(find);
JS_DECLARE_NATIVE_FUNCTION(to_string_tag_getter);
JS_DECLARE_NATIVE_FUNCTION(to_string_tag_setter);
};
}

View file

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