LibJS: Update specification steps for the Set Methods proposal

It is now Stage 4 and has been merged into the main ECMA-262 spec:
a78d504
This commit is contained in:
Timothy Flynn 2024-07-09 17:18:09 -04:00 committed by Andreas Kling
commit 2dbd71d54b
Notes: sideshowbarker 2024-07-16 23:51:07 +09:00
4 changed files with 432 additions and 408 deletions

View file

@ -4,7 +4,9 @@
* SPDX-License-Identifier: BSD-2-Clause
*/
#include <LibJS/Runtime/KeyedCollections.h>
#include <LibJS/Runtime/Set.h>
#include <LibJS/Runtime/ValueInlines.h>
namespace JS {
@ -43,4 +45,60 @@ void Set::visit_edges(Cell::Visitor& visitor)
visitor.visit(m_values);
}
// 24.2.1.2 GetSetRecord ( obj ), https://tc39.es/ecma262/#sec-getsetrecord
ThrowCompletionOr<SetRecord> get_set_record(VM& vm, Value value)
{
// 1. If obj is not an Object, throw a TypeError exception.
if (!value.is_object())
return vm.throw_completion<TypeError>(ErrorType::NotAnObject, value.to_string_without_side_effects());
auto const& object = value.as_object();
// 2. Let rawSize be ? Get(obj, "size").
auto raw_size = TRY(object.get(vm.names.size));
// 3. Let numSize be ? ToNumber(rawSize).
auto number_size = TRY(raw_size.to_number(vm));
// 4. NOTE: If rawSize is undefined, then numSize will be NaN.
// 5. If numSize is NaN, throw a TypeError exception.
if (number_size.is_nan())
return vm.throw_completion<TypeError>(ErrorType::NumberIsNaN, "size"sv);
// 6. Let intSize be ! ToIntegerOrInfinity(numSize).
auto integer_size = MUST(number_size.to_integer_or_infinity(vm));
// 7. If intSize < 0, throw a RangeError exception.
if (integer_size < 0)
return vm.throw_completion<RangeError>(ErrorType::NumberIsNegative, "size"sv);
// 8. Let has be ? Get(obj, "has").
auto has = TRY(object.get(vm.names.has));
// 9. If IsCallable(has) is false, throw a TypeError exception.
if (!has.is_function())
return vm.throw_completion<TypeError>(ErrorType::NotAFunction, has.to_string_without_side_effects());
// 10. Let keys be ? Get(obj, "keys").
auto keys = TRY(object.get(vm.names.keys));
// 11. If IsCallable(keys) is false, throw a TypeError exception.
if (!keys.is_function())
return vm.throw_completion<TypeError>(ErrorType::NotAFunction, keys.to_string_without_side_effects());
// 12. Return a new Set Record { [[SetObject]]: obj, [[Size]]: intSize, [[Has]]: has, [[Keys]]: keys }.
return SetRecord { .set_object = object, .size = integer_size, .has = has.as_function(), .keys = keys.as_function() };
}
// 24.2.1.3 SetDataHas ( setData, value ), https://tc39.es/ecma262/#sec-setdatahas
bool set_data_has(NonnullGCPtr<Set> set_data, Value value)
{
// NOTE: We do not need to implement SetDataIndex, as we do not implement the use of empty slots in Set. But we do
// need to match its behavior of always canonicalizing the provided value.
value = canonicalize_keyed_collection_key(value);
// 1. If SetDataIndex(setData, value) is not-found, return false.
// 2. Return true.
return set_data->set_has(value);
}
}

View file

@ -47,4 +47,15 @@ private:
GCPtr<Map> m_values;
};
// 24.2.1.1 Set Records, https://tc39.es/ecma262/#sec-set-records
struct SetRecord {
NonnullGCPtr<Object const> set_object; // [[SetObject]]
double size { 0 }; // [[Size]
NonnullGCPtr<FunctionObject> has; // [[Has]]
NonnullGCPtr<FunctionObject> keys; // [[Keys]]
};
ThrowCompletionOr<SetRecord> get_set_record(VM&, Value);
bool set_data_has(NonnullGCPtr<Set>, Value);
}

View file

@ -32,25 +32,25 @@ void SetPrototype::initialize(Realm& realm)
define_native_function(realm, vm.names.add, add, 1, attr);
define_native_function(realm, vm.names.clear, clear, 0, attr);
define_native_function(realm, vm.names.delete_, delete_, 1, attr);
define_native_function(realm, vm.names.difference, difference, 1, attr);
define_native_function(realm, vm.names.entries, entries, 0, attr);
define_native_function(realm, vm.names.forEach, for_each, 1, attr);
define_native_function(realm, vm.names.has, has, 1, attr);
define_native_function(realm, vm.names.values, values, 0, attr);
define_native_function(realm, vm.names.union_, union_, 1, attr);
define_native_function(realm, vm.names.intersection, intersection, 1, attr);
define_native_function(realm, vm.names.difference, difference, 1, attr);
define_native_function(realm, vm.names.symmetricDifference, symmetric_difference, 1, attr);
define_native_function(realm, vm.names.isDisjointFrom, is_disjoint_from, 1, attr);
define_native_function(realm, vm.names.isSubsetOf, is_subset_of, 1, attr);
define_native_function(realm, vm.names.isSupersetOf, is_superset_of, 1, attr);
define_native_function(realm, vm.names.isDisjointFrom, is_disjoint_from, 1, attr);
define_native_accessor(realm, vm.names.size, size_getter, {}, Attribute::Configurable);
define_native_function(realm, vm.names.symmetricDifference, symmetric_difference, 1, attr);
define_native_function(realm, vm.names.union_, union_, 1, attr);
define_native_function(realm, vm.names.values, values, 0, attr);
define_direct_property(vm.names.keys, get_without_side_effects(vm.names.values), attr);
// 24.2.3.11 Set.prototype [ @@iterator ] ( ), https://tc39.es/ecma262/#sec-set.prototype-@@iterator
// 24.2.3.18 Set.prototype [ @@iterator ] ( ), https://tc39.es/ecma262/#sec-set.prototype-@@iterator
define_direct_property(vm.well_known_symbol_iterator(), get_without_side_effects(vm.names.values), attr);
// 24.2.3.12 Set.prototype [ @@toStringTag ], https://tc39.es/ecma262/#sec-set.prototype-@@tostringtag
// 24.2.3.19 Set.prototype [ @@toStringTag ], https://tc39.es/ecma262/#sec-set.prototype-@@tostringtag
define_direct_property(vm.well_known_symbol_to_string_tag(), PrimitiveString::create(vm, vm.names.Set.as_string()), Attribute::Configurable);
}
@ -111,7 +111,75 @@ JS_DEFINE_NATIVE_FUNCTION(SetPrototype::delete_)
return Value(set->set_remove(value));
}
// 24.2.3.5 Set.prototype.entries ( ), https://tc39.es/ecma262/#sec-set.prototype.entries
// 24.2.4.5 Set.prototype.difference ( other ), https://tc39.es/ecma262/#sec-set.prototype.difference
JS_DEFINE_NATIVE_FUNCTION(SetPrototype::difference)
{
// 1. Let O be the this value.
// 2. Perform ? RequireInternalSlot(O, [[SetData]]).
auto set = TRY(typed_this_object(vm));
// 3. Let otherRec be ? GetSetRecord(other).
auto other_record = TRY(get_set_record(vm, vm.argument(0)));
// 4. Let resultSetData be a copy of O.[[SetData]].
auto result = set->copy();
// 5. If SetDataSize(O.[[SetData]]) ≤ otherRec.[[Size]], then
if (set->set_size() <= other_record.size) {
// a. Let thisSize be the number of elements in O.[[SetData]].
// b. Let index be 0.
// c. Repeat, while index < thisSize,
for (auto const& element : *set) {
// i. Let e be resultSetData[index].
// ii. If e is not EMPTY, then
// 1. Let inOther be ToBoolean(? Call(otherRec.[[Has]], otherRec.[[SetObject]], « e »)).
auto in_other = TRY(call(vm, *other_record.has, other_record.set_object, element.key)).to_boolean();
// 2. If inOther is true, then
if (in_other) {
// a. Set resultSetData[index] to EMPTY.
result->set_remove(element.key);
}
// iii. Set index to index + 1.
}
}
// 6. Else,
else {
// a. Let keysIter be ? GetIteratorFromMethod(otherRec.[[SetObject]], otherRec.[[Keys]]).
auto keys_iterator = TRY(get_iterator_from_method(vm, other_record.set_object, other_record.keys));
// b. Let next be NOT-STARTED.
Optional<Value> next;
// c. Repeat, while next is not DONE,
do {
// i. Set next to ? IteratorStepValue(keysIter).
next = TRY(iterator_step_value(vm, keys_iterator));
// ii. If next is not DONE, then
if (next.has_value()) {
// 1. Set next to CanonicalizeKeyedCollectionKey(next).
next = canonicalize_keyed_collection_key(*next);
// 2. Let valueIndex be SetDataIndex(resultSetData, next).
// 3. If valueIndex is not NOT-FOUND, then
if (result->set_has(*next)) {
// a. Set resultSetData[valueIndex] to EMPTY.
result->set_remove(*next);
}
}
} while (next.has_value());
}
// 7. Let result be OrdinaryObjectCreate(%Set.prototype%, « [[SetData]] »).
// 8. Set result.[[SetData]] to resultSetData.
// 9. Return result.
return result;
}
// 24.2.3.6 Set.prototype.entries ( ), https://tc39.es/ecma262/#sec-set.prototype.entries
JS_DEFINE_NATIVE_FUNCTION(SetPrototype::entries)
{
auto& realm = *vm.current_realm();
@ -123,7 +191,7 @@ JS_DEFINE_NATIVE_FUNCTION(SetPrototype::entries)
return SetIterator::create(realm, set, Object::PropertyKind::KeyAndValue);
}
// 24.2.3.6 Set.prototype.forEach ( callbackfn [ , thisArg ] ), https://tc39.es/ecma262/#sec-set.prototype.foreach
// 24.2.3.7 Set.prototype.forEach ( callbackfn [ , thisArg ] ), https://tc39.es/ecma262/#sec-set.prototype.foreach
JS_DEFINE_NATIVE_FUNCTION(SetPrototype::for_each)
{
auto callback_fn = vm.argument(0);
@ -159,7 +227,7 @@ JS_DEFINE_NATIVE_FUNCTION(SetPrototype::for_each)
return js_undefined();
}
// 24.2.3.7 Set.prototype.has ( value ), https://tc39.es/ecma262/#sec-set.prototype.has
// 24.2.3.8 Set.prototype.has ( value ), https://tc39.es/ecma262/#sec-set.prototype.has
JS_DEFINE_NATIVE_FUNCTION(SetPrototype::has)
{
auto value = vm.argument(0);
@ -177,20 +245,229 @@ JS_DEFINE_NATIVE_FUNCTION(SetPrototype::has)
return Value(set->set_has(value));
}
// 24.2.3.10 Set.prototype.values ( ), https://tc39.es/ecma262/#sec-set.prototype.values
JS_DEFINE_NATIVE_FUNCTION(SetPrototype::values)
// 24.2.4.9 Set.prototype.intersection ( other ), https://tc39.es/ecma262/#sec-set.prototype.intersection
JS_DEFINE_NATIVE_FUNCTION(SetPrototype::intersection)
{
auto& realm = *vm.current_realm();
// 1. Let S be the this value.
// NOTE: CreateSetIterator checks the presence of a [[SetData]] slot, so we can do this here.
// 1. Let O be the this value.
// 2. Perform ? RequireInternalSlot(O, [[SetData]]).
auto set = TRY(typed_this_object(vm));
// 2. Return ? CreateSetIterator(S, value).
return SetIterator::create(realm, set, Object::PropertyKind::Value);
// 3. Let otherRec be ? GetSetRecord(other).
auto other_record = TRY(get_set_record(vm, vm.argument(0)));
// 4. Let resultSetData be a new empty List.
auto result = Set::create(realm);
// 5. If SetDataSize(O.[[SetData]]) ≤ otherRec.[[Size]], then
if (set->set_size() <= other_record.size) {
// a. Let thisSize be the number of elements in O.[[SetData]].
// b. Let index be 0.
// c. Repeat, while index < thisSize,
for (auto const& element : *set) {
// i. Let e be O.[[SetData]][index].
// ii. Set index to index + 1.
// iii. If e is not empty, then
// 1. Let inOther be ToBoolean(? Call(otherRec.[[Has]], otherRec.[[SetObject]], « e »)).
auto in_other = TRY(call(vm, *other_record.has, other_record.set_object, element.key)).to_boolean();
// 2. If inOther is true, then
if (in_other) {
// a. NOTE: It is possible for earlier calls to otherRec.[[Has]] to remove and re-add an element of O.[[SetData]], which can cause the same element to be visited twice during this iteration.
// b. If SetDataHas(resultSetData, e) is false, then
if (!set_data_has(result, element.key)) {
// i. Append e to resultSetData.
result->set_add(element.key);
}
}
// 3. NOTE: The number of elements in O.[[SetData]] may have increased during execution of otherRec.[[Has]].
// 4. Set thisSize to the number of elements in O.[[SetData]].
}
}
// 6. Else,
else {
// a. Let keysIter be ? GetIteratorFromMethod(otherRec.[[SetObject]], otherRec.[[Keys]]).
auto keys_iterator = TRY(get_iterator_from_method(vm, other_record.set_object, other_record.keys));
// b. Let next be NOT-STARTED.
Optional<Value> next;
// c. Repeat, while next is not DONE,
do {
// i. Set next to ? IteratorStepValue(keysIter).
next = TRY(iterator_step_value(vm, keys_iterator));
// ii. If next is not DONE, then
if (next.has_value()) {
// 1. Set next to CanonicalizeKeyedCollectionKey(next).
next = canonicalize_keyed_collection_key(*next);
// 2. Let inThis be SetDataHas(O.[[SetData]], next).
auto in_this = set_data_has(set, *next);
// 3. If inThis is true, then
if (in_this) {
// a. NOTE: Because other is an arbitrary object, it is possible for its "keys" iterator to produce the same value more than once.
// b. If SetDataHas(resultSetData, next) is false, then
if (!set_data_has(result, *next)) {
// i. Append next to resultSetData.
result->set_add(*next);
}
}
}
} while (next.has_value());
}
// 7. Let result be OrdinaryObjectCreate(%Set.prototype%, « [[SetData]] »).
// 8. Set result.[[SetData]] to resultSetData.
// 9. Return result.
return result;
}
// 24.2.3.9 get Set.prototype.size, https://tc39.es/ecma262/#sec-get-set.prototype.size
// 24.2.4.10 Set.prototype.isDisjointFrom ( other ), https://tc39.es/ecma262/#sec-set.prototype.isdisjointfrom
JS_DEFINE_NATIVE_FUNCTION(SetPrototype::is_disjoint_from)
{
// 1. Let O be the this value.
// 2. Perform ? RequireInternalSlot(O, [[SetData]]).
auto set = TRY(typed_this_object(vm));
// 3. Let otherRec be ? GetSetRecord(other).
auto other_record = TRY(get_set_record(vm, vm.argument(0)));
// 4. If SetDataSize(O.[[SetData]]) ≤ otherRec.[[Size]], then
if (set->set_size() <= other_record.size) {
// a. Let thisSize be the number of elements in O.[[SetData]].
// b. Let index be 0.
// c. Repeat, while index < thisSize,
for (auto const& element : *set) {
// i. Let e be O.[[SetData]][index].
// ii. Set index to index + 1.
// iii. If e is not empty, then
// 1. Let inOther be ToBoolean(? Call(otherRec.[[Has]], otherRec.[[SetObject]], « e »)).
auto in_other = TRY(call(vm, *other_record.has, other_record.set_object, element.key)).to_boolean();
// 2. If inOther is true, return false.
if (in_other)
return false;
// 3. NOTE: The number of elements in O.[[SetData]] may have increased during execution of otherRec.[[Has]].
// 4. Set thisSize to the number of elements in O.[[SetData]].
}
}
// 5. Else,
else {
// a. Let keysIter be ? GetIteratorFromMethod(otherRec.[[SetObject]], otherRec.[[Keys]]).
auto keys_iterator = TRY(get_iterator_from_method(vm, other_record.set_object, other_record.keys));
// b. Let next be NOT-STARTED.
Optional<Value> next;
// c. Repeat, while next is not DONE,
do {
// i. Set next to ? IteratorStepValue(keysIter).
next = TRY(iterator_step_value(vm, keys_iterator));
// ii. If next is not DONE, then
if (next.has_value()) {
// 1. If SetDataHas(O.[[SetData]], next) is true, then
if (set_data_has(set, *next)) {
// a. Perform ? IteratorClose(keysIter, NormalCompletion(UNUSED)).
TRY(iterator_close(vm, keys_iterator, normal_completion({})));
// b. Return false.
return false;
}
}
} while (next.has_value());
}
// 6. Return true.
return true;
}
// 24.2.4.11 Set.prototype.isSubsetOf ( other ), https://tc39.es/ecma262/#sec-set.prototype.issubsetof
JS_DEFINE_NATIVE_FUNCTION(SetPrototype::is_subset_of)
{
// 1. Let O be the this value.
// 2. Perform ? RequireInternalSlot(O, [[SetData]]).
auto set = TRY(typed_this_object(vm));
// 3. Let otherRec be ? GetSetRecord(other).
auto other_record = TRY(get_set_record(vm, vm.argument(0)));
// 4. If SetDataSize(O.[[SetData]]) > otherRec.[[Size]], return false.
if (set->set_size() > other_record.size)
return false;
// 5. Let thisSize be the number of elements in O.[[SetData]].
// 6. Let index be 0.
// 7. Repeat, while index < thisSize,
for (auto const& element : *set) {
// a. Let e be O.[[SetData]][index].
// b. Set index to index + 1.
// c. If e is not empty, then
// i. Let inOther be ToBoolean(? Call(otherRec.[[Has]], otherRec.[[SetObject]], « e »)).
auto in_other = TRY(call(vm, *other_record.has, other_record.set_object, element.key)).to_boolean();
// ii. If inOther is false, return false.
if (!in_other)
return false;
// iii. NOTE: The number of elements in O.[[SetData]] may have increased during execution of otherRec.[[Has]].
// iv. Set thisSize to the number of elements in O.[[SetData]].
}
// 8. Return true.
return true;
}
// 24.2.4.12 Set.prototype.isSupersetOf ( other ), https://tc39.es/ecma262/#sec-set.prototype.issupersetof
JS_DEFINE_NATIVE_FUNCTION(SetPrototype::is_superset_of)
{
// 1. Let O be the this value.
// 2. Perform ? RequireInternalSlot(O, [[SetData]]).
auto set = TRY(typed_this_object(vm));
// 3. Let otherRec be ? GetSetRecord(other).
auto other_record = TRY(get_set_record(vm, vm.argument(0)));
// 4. If SetDataSize(O.[[SetData]]) < otherRec.[[Size]], return false.
if (set->set_size() < other_record.size)
return false;
// 5. Let keysIter be ? GetIteratorFromMethod(otherRec.[[SetObject]], otherRec.[[Keys]]).
auto keys_iterator = TRY(get_iterator_from_method(vm, other_record.set_object, other_record.keys));
// 6. Let next be NOT-STARTED.
Optional<Value> next;
// 7. Repeat, while next is not DONE,
do {
// a. Set next to ? IteratorStepValue(keysIter).
next = TRY(iterator_step_value(vm, keys_iterator));
// b. If next is not DONE, then
if (next.has_value()) {
// i. If SetDataHas(O.[[SetData]], next) is false, then
if (!set_data_has(set, *next)) {
// 1. Perform ? IteratorClose(keysIter, NormalCompletion(UNUSED)).
TRY(iterator_close(vm, keys_iterator, normal_completion({})));
// 2. Return false.
return false;
}
}
} while (next.has_value());
// 8. Return true.
return true;
}
// 24.2.3.14 get Set.prototype.size, https://tc39.es/ecma262/#sec-get-set.prototype.size
JS_DEFINE_NATIVE_FUNCTION(SetPrototype::size_getter)
{
// 1. Let S be the this value.
@ -206,255 +483,7 @@ JS_DEFINE_NATIVE_FUNCTION(SetPrototype::size_getter)
return Value(count);
}
// 8 Set Records, https://tc39.es/proposal-set-methods/#sec-set-records
struct SetRecord {
NonnullGCPtr<Object const> set; // [[Set]]
double size { 0 }; // [[Size]
NonnullGCPtr<FunctionObject> has; // [[Has]]
NonnullGCPtr<FunctionObject> keys; // [[Keys]]
};
// 9 GetSetRecord ( obj ), https://tc39.es/proposal-set-methods/#sec-getsetrecord
static ThrowCompletionOr<SetRecord> get_set_record(VM& vm, Value value)
{
// 1. If obj is not an Object, throw a TypeError exception.
if (!value.is_object())
return vm.throw_completion<TypeError>(ErrorType::NotAnObject, value.to_string_without_side_effects());
auto const& object = value.as_object();
// 2. Let rawSize be ? Get(obj, "size").
auto raw_size = TRY(object.get(vm.names.size));
// 3. Let numSize be ? ToNumber(rawSize).
auto number_size = TRY(raw_size.to_number(vm));
// 4. NOTE: If rawSize is undefined, then numSize will be NaN.
// 5. If numSize is NaN, throw a TypeError exception.
if (number_size.is_nan())
return vm.throw_completion<TypeError>(ErrorType::NumberIsNaN, "size"sv);
// 6. Let intSize be ! ToIntegerOrInfinity(numSize).
auto integer_size = MUST(number_size.to_integer_or_infinity(vm));
// 7. If intSize < 0, throw a RangeError exception.
if (integer_size < 0)
return vm.throw_completion<RangeError>(ErrorType::NumberIsNegative, "size"sv);
// 8. Let has be ? Get(obj, "has").
auto has = TRY(object.get(vm.names.has));
// 9. If IsCallable(has) is false, throw a TypeError exception.
if (!has.is_function())
return vm.throw_completion<TypeError>(ErrorType::NotAFunction, has.to_string_without_side_effects());
// 10. Let keys be ? Get(obj, "keys").
auto keys = TRY(object.get(vm.names.keys));
// 11. If IsCallable(keys) is false, throw a TypeError exception.
if (!keys.is_function())
return vm.throw_completion<TypeError>(ErrorType::NotAFunction, keys.to_string_without_side_effects());
// 12. Return a new Set Record { [[Set]]: obj, [[Size]]: intSize, [[Has]]: has, [[Keys]]: keys }.
return SetRecord { .set = object, .size = integer_size, .has = has.as_function(), .keys = keys.as_function() };
}
// 1 Set.prototype.union ( other ), https://tc39.es/proposal-set-methods/#sec-set.prototype.union
JS_DEFINE_NATIVE_FUNCTION(SetPrototype::union_)
{
// 1. Let O be the this value.
// 2. Perform ? RequireInternalSlot(O, [[SetData]]).
auto set = TRY(typed_this_object(vm));
// 3. Let otherRec be ? GetSetRecord(other).
auto other_record = TRY(get_set_record(vm, vm.argument(0)));
// 4. Let keysIter be ? GetIteratorFromMethod(otherRec.[[Set]], otherRec.[[Keys]]).
auto keys_iterator = TRY(get_iterator_from_method(vm, other_record.set, other_record.keys));
// 5. Let resultSetData be a copy of O.[[SetData]].
auto result = set->copy();
// 6. Let next be true.
auto next = true;
// 7. Repeat, while next is not false,
while (next) {
// a. Set next to ? IteratorStep(keysIter).
auto iterator_result = TRY(iterator_step(vm, keys_iterator));
next = iterator_result;
// b. If next is not false, then
if (next) {
// i. Let nextValue be ? IteratorValue(next).
auto next_value = TRY(iterator_value(vm, *iterator_result));
// ii. If nextValue is -0𝔽, set nextValue to +0𝔽.
if (next_value.is_negative_zero())
next_value = Value(0);
// iii. If SetDataHas(resultSetData, nextValue) is false, then
// 1. Append nextValue to resultSetData.
result->set_add(next_value);
}
}
// 8. Let result be OrdinaryObjectCreate(%Set.prototype%, « [[SetData]] »).
// 9. Set result.[[SetData]] to resultSetData.
// 10. Return result.
return result;
}
// 2 Set.prototype.intersection ( other ), https://tc39.es/proposal-set-methods/#sec-set.prototype.intersection
JS_DEFINE_NATIVE_FUNCTION(SetPrototype::intersection)
{
auto& realm = *vm.current_realm();
// 1. Let O be the this value.
// 2. Perform ? RequireInternalSlot(O, [[SetData]]).
auto set = TRY(typed_this_object(vm));
// 3. Let otherRec be ? GetSetRecord(other).
auto other_record = TRY(get_set_record(vm, vm.argument(0)));
// 4. Let resultSetData be a new empty List.
auto result = Set::create(realm);
// 5. Let thisSize be the number of elements in O.[[SetData]].
auto this_size = set->set_size();
// 6. If thisSize ≤ otherRec.[[Size]], then
if (this_size <= other_record.size) {
// a. For each element e of O.[[SetData]], do
for (auto& element : *set) {
// i. If e is not empty, then
// 1. Let inOther be ToBoolean(? Call(otherRec.[[Has]], otherRec.[[Set]], « e »)).
auto in_other = TRY(call(vm, *other_record.has, other_record.set, element.key)).to_boolean();
// 2. If inOther is true, then
if (in_other) {
// a. Append e to resultSetData.
result->set_add(element.key);
}
}
}
// 7. Else,
else {
// a. Let keysIter be ? GetIteratorFromMethod(otherRec.[[Set]], otherRec.[[Keys]]).
auto keys_iterator = TRY(get_iterator_from_method(vm, other_record.set, other_record.keys));
// b. Let next be true.
auto next = true;
// c. Repeat, while next is not false,
while (next) {
// i. Set next to ? IteratorStep(keysIter).
auto iterator_result = TRY(iterator_step(vm, keys_iterator));
next = iterator_result;
// ii. If next is not false, then
if (next) {
// 1. Let nextValue be ? IteratorValue(next).
auto next_value = TRY(iterator_value(vm, *iterator_result));
// 2. If nextValue is -0𝔽, set nextValue to +0𝔽.
if (next_value.is_negative_zero())
next_value = Value(0);
// 3. NOTE: Because other is an arbitrary object, it is possible for its "keys" iterator to produce the same value more than once.
// 4. Let alreadyInResult be SetDataHas(resultSetData, nextValue).
// 5. Let inThis be SetDataHas(O.[[SetData]], nextValue).
auto in_this = set->set_has(next_value);
// 6. If alreadyInResult is false and inThis is true, then
if (in_this) {
// a. Append nextValue to resultSetData.
result->set_add(next_value);
}
}
}
// d. NOTE: It is possible for resultSetData not to be a subset of O.[[SetData]] at this point because arbitrary code may have been executed by the iterator, including code which modifies O.[[SetData]].
// e. Sort the elements of resultSetData so that all elements which are also in O.[[SetData]] are ordered as they are in O.[[SetData]], and any additional elements are moved to the end of the list in the same order as they were before sorting resultSetData.
// FIXME: This is not possible with the current underlying m_values implementation
}
// 8. Let result be OrdinaryObjectCreate(%Set.prototype%, « [[SetData]] »).
// 9. Set result.[[SetData]] to resultSetData.
// 10. Return result.
return result;
}
// 3 Set.prototype.difference ( other ), https://tc39.es/proposal-set-methods/#sec-set.prototype.difference
JS_DEFINE_NATIVE_FUNCTION(SetPrototype::difference)
{
// 1. Let O be the this value.
// 2. Perform ? RequireInternalSlot(O, [[SetData]]).
auto set = TRY(typed_this_object(vm));
// 3. Let otherRec be ? GetSetRecord(other).
auto other_record = TRY(get_set_record(vm, vm.argument(0)));
// 4. Let resultSetData be a copy of O.[[SetData]].
auto result = set->copy();
// 5. Let thisSize be the number of elements in O.[[SetData]].
auto this_size = set->set_size();
// 6. If thisSize ≤ otherRec.[[Size]], then
if (this_size <= other_record.size) {
// a. For each element e of resultSetData, do
for (auto& element : *set) {
// i. If e is not empty, then
// 1. Let inOther be ToBoolean(? Call(otherRec.[[Has]], otherRec.[[Set]], « e »)).
auto in_other = TRY(call(vm, *other_record.has, other_record.set, element.key)).to_boolean();
// 2. If inOther is true, then
if (in_other) {
// a. Remove e from resultSetData.
result->set_remove(element.key);
}
}
}
// 7. Else,
else {
// a. Let keysIter be ? GetIteratorFromMethod(otherRec.[[Set]], otherRec.[[Keys]]).
auto keys_iterator = TRY(get_iterator_from_method(vm, other_record.set, other_record.keys));
// b. Let next be true.
auto next = true;
// c. Repeat, while next is not false,
while (next) {
// i. Set next to ? IteratorStep(keysIter).
auto iterator_result = TRY(iterator_step(vm, keys_iterator));
next = iterator_result;
// ii. If next is not false, then
if (next) {
// 1. Let nextValue be ? IteratorValue(next).
auto next_value = TRY(iterator_value(vm, *iterator_result));
// 2. If nextValue is -0𝔽, set nextValue to +0𝔽.
if (next_value.is_negative_zero())
next_value = Value(0);
// 3. If SetDataHas(resultSetData, nextValue) is true, then
if (result->set_has(next_value)) {
// a. Remove nextValue from resultSetData.
result->set_remove(next_value);
}
}
}
}
// 8. Let result be OrdinaryObjectCreate(%Set.prototype%, « [[SetData]] »).
// 9. Set result.[[SetData]] to resultSetData.
// 10. Return result.
return result;
}
// 4 Set.prototype.symmetricDifference ( other ), https://tc39.es/proposal-set-methods/#sec-set.prototype.symmetricdifference
// 24.2.4.15 Set.prototype.symmetricDifference ( other ), https://tc39.es/ecma262/#sec-set.prototype.symmetricdifference
JS_DEFINE_NATIVE_FUNCTION(SetPrototype::symmetric_difference)
{
// 1. Let O be the this value.
@ -464,43 +493,43 @@ JS_DEFINE_NATIVE_FUNCTION(SetPrototype::symmetric_difference)
// 3. Let otherRec be ? GetSetRecord(other).
auto other_record = TRY(get_set_record(vm, vm.argument(0)));
// 4. Let keysIter be ? GetIteratorFromMethod(otherRec.[[Set]], otherRec.[[Keys]]).
auto keys_iterator = TRY(get_iterator_from_method(vm, other_record.set, other_record.keys));
// 4. Let keysIter be ? GetIteratorFromMethod(otherRec.[[SetObject]], otherRec.[[Keys]]).
auto keys_iterator = TRY(get_iterator_from_method(vm, other_record.set_object, other_record.keys));
// 5. Let resultSetData be a copy of O.[[SetData]].
auto result = set->copy();
// 6. Let next be true.
auto next = true;
// 6. Let next be NOT-STARTED.
Optional<Value> next;
// 7. Repeat, while next is not false,
while (next) {
// a. Set next to ? IteratorStep(keysIter).
auto iterator_result = TRY(iterator_step(vm, keys_iterator));
next = iterator_result;
// 7. Repeat, while next is not DONE,
do {
// a. Set next to ? IteratorStepValue(keysIter).
next = TRY(iterator_step_value(vm, keys_iterator));
// b. If next is not false, then
if (next) {
// i. Let nextValue be ? IteratorValue(next).
auto next_value = TRY(iterator_value(vm, *iterator_result));
// b. If next is not DONE, then
if (next.has_value()) {
// i. Set next to CanonicalizeKeyedCollectionKey(next).
next = canonicalize_keyed_collection_key(*next);
// ii. If nextValue is -0𝔽, set nextValue to +0𝔽.
if (next_value.is_negative_zero())
next_value = Value(0);
// ii. Let resultIndex be SetDataIndex(resultSetData, next).
// iii. If resultIndex is not-found, let alreadyInResult be false. Otherwise let alreadyInResult be true.
auto already_in_result = result->set_has(*next);
// iii. Let inResult be SetDataHas(resultSetData, nextValue).
// iv. If SetDataHas(O.[[SetData]], nextValue) is true, then
if (set->set_has(next_value)) {
// 1. If inResult is true, remove nextValue from resultSetData.
result->set_remove(next_value);
// iv. If SetDataHas(O.[[SetData]], next) is true, then
if (set_data_has(set, *next)) {
// 1. If alreadyInResult is true, set resultSetData[resultIndex] to empty.
if (already_in_result)
result->set_remove(*next);
}
// v. Else,
else {
// 1. If inResult is false, append nextValue to resultSetData.
result->set_add(next_value);
// 1. If alreadyInResult is false, append next to resultSetData.
if (!already_in_result)
result->set_add(*next);
}
}
}
} while (next.has_value());
// 8. Let result be OrdinaryObjectCreate(%Set.prototype%, « [[SetData]] »).
// 9. Set result.[[SetData]] to resultSetData.
@ -509,8 +538,8 @@ JS_DEFINE_NATIVE_FUNCTION(SetPrototype::symmetric_difference)
return result;
}
// 5 Set.prototype.isSubsetOf ( other ), https://tc39.es/proposal-set-methods/#sec-set.prototype.issubsetof
JS_DEFINE_NATIVE_FUNCTION(SetPrototype::is_subset_of)
// 24.2.4.16 Set.prototype.union ( other ), https://tc39.es/ecma262/#sec-set.prototype.union
JS_DEFINE_NATIVE_FUNCTION(SetPrototype::union_)
{
// 1. Let O be the this value.
// 2. Perform ? RequireInternalSlot(O, [[SetData]]).
@ -519,124 +548,51 @@ JS_DEFINE_NATIVE_FUNCTION(SetPrototype::is_subset_of)
// 3. Let otherRec be ? GetSetRecord(other).
auto other_record = TRY(get_set_record(vm, vm.argument(0)));
// 4. Let thisSize be the number of elements in O.[[SetData]].
auto this_size = set->set_size();
// 4. Let keysIter be ? GetIteratorFromMethod(otherRec.[[SetObject]], otherRec.[[Keys]]).
auto keys_iterator = TRY(get_iterator_from_method(vm, other_record.set_object, other_record.keys));
// 5. If thisSize > otherRec.[[Size]], return false.
if (this_size > other_record.size)
return false;
// 5. Let resultSetData be a copy of O.[[SetData]].
auto result = set->copy();
// 6. For each element e of O.[[SetData]], do
for (auto& element : *set) {
// a. Let inOther be ToBoolean(? Call(otherRec.[[Has]], otherRec.[[Set]], « e »)).
auto in_other = TRY(call(vm, *other_record.has, other_record.set, element.key)).to_boolean();
// 6. Let next be NOT-STARTED.
Optional<Value> next;
// b. If inOther is false, return false.
if (!in_other)
return false;
}
// 7. Repeat, while next is not DONE,
do {
// a. Set next to ? IteratorStepValue(keysIter).
next = TRY(iterator_step_value(vm, keys_iterator));
// 7. Return true.
return true;
}
// b. If next is not DONE, then
if (next.has_value()) {
// i. Set next to CanonicalizeKeyedCollectionKey(next).
next = canonicalize_keyed_collection_key(*next);
// 6 Set.prototype.isSupersetOf ( other ), https://tc39.es/proposal-set-methods/#sec-set.prototype.issupersetof
JS_DEFINE_NATIVE_FUNCTION(SetPrototype::is_superset_of)
{
// 1. Let O be the this value.
// 2. Perform ? RequireInternalSlot(O, [[SetData]]).
auto set = TRY(typed_this_object(vm));
// 3. Let otherRec be ? GetSetRecord(other).
auto other_record = TRY(get_set_record(vm, vm.argument(0)));
// 4. Let thisSize be the number of elements in O.[[SetData]].
auto this_size = set->set_size();
// 5. If thisSize < otherRec.[[Size]], return false.
if (this_size < other_record.size)
return false;
// 6. Let keysIter be ? GetIteratorFromMethod(otherRec.[[Set]], otherRec.[[Keys]]).
auto keys_iterator = TRY(get_iterator_from_method(vm, other_record.set, other_record.keys));
// 7. Let next be true.
auto next = true;
// 8. Repeat, while next is not false,
while (next) {
// a. Set next to ? IteratorStep(keysIter).
auto iterator_result = TRY(iterator_step(vm, keys_iterator));
next = iterator_result;
// b. If next is not false, then
if (next) {
// i. Let nextValue be ? IteratorValue(next).
auto next_value = TRY(iterator_value(vm, *iterator_result));
// ii. If SetDataHas(O.[[SetData]], nextValue) is false, return false.
if (!set->set_has(next_value))
return false;
}
}
// 9. Return true.
return true;
}
// 7 Set.prototype.isDisjointFrom ( other ), https://tc39.es/proposal-set-methods/#sec-set.prototype.isdisjointfrom
JS_DEFINE_NATIVE_FUNCTION(SetPrototype::is_disjoint_from)
{
// 1. Let O be the this value.
// 2. Perform ? RequireInternalSlot(O, [[SetData]]).
auto set = TRY(typed_this_object(vm));
// 3. Let otherRec be ? GetSetRecord(other).
auto other_record = TRY(get_set_record(vm, vm.argument(0)));
// 4. Let thisSize be the number of elements in O.[[SetData]].
auto this_size = set->set_size();
// 5. If thisSize ≤ otherRec.[[Size]], then
if (this_size <= other_record.size) {
// a. For each element e of O.[[SetData]], do
for (auto& element : *set) {
// i. If e is not empty, then
// 1. Let inOther be ToBoolean(? Call(otherRec.[[Has]], otherRec.[[Set]], « e »)).
auto in_other = TRY(call(vm, *other_record.has, other_record.set, element.key)).to_boolean();
// 2. If inOther is true, return false.
if (in_other)
return false;
}
}
// 6. Else,
else {
// a. Let keysIter be ? GetIteratorFromMethod(otherRec.[[Set]], otherRec.[[Keys]]).
auto keys_iterator = TRY(get_iterator_from_method(vm, other_record.set, other_record.keys));
// b. Let next be true.
auto next = true;
// c. Repeat, while next is not false,
while (next) {
// i. Set next to ? IteratorStep(keysIter).
auto iterator_result = TRY(iterator_step(vm, keys_iterator));
next = iterator_result;
// ii. If next is not false, then
if (next) {
// 1. Let nextValue be ? IteratorValue(next).
auto next_value = TRY(iterator_value(vm, *iterator_result));
// 2. If SetDataHas(O.[[SetData]], nextValue) is true, return false.
if (set->set_has(next_value))
return false;
// ii. If SetDataHas(resultSetData, next) is false, then
if (!set_data_has(result, *next)) {
// 1. Append next to resultSetData.
result->set_add(*next);
}
}
}
} while (next.has_value());
// 7. Return true.
return true;
// 8. Let result be OrdinaryObjectCreate(%Set.prototype%, « [[SetData]] »).
// 9. Set result.[[SetData]] to resultSetData.
// 10. Return result.
return result;
}
// 24.2.3.17 Set.prototype.values ( ), https://tc39.es/ecma262/#sec-set.prototype.values
JS_DEFINE_NATIVE_FUNCTION(SetPrototype::values)
{
auto& realm = *vm.current_realm();
// 1. Let S be the this value.
// NOTE: CreateSetIterator checks the presence of a [[SetData]] slot, so we can do this here.
auto set = TRY(typed_this_object(vm));
// 2. Return ? CreateSetIterator(S, value).
return SetIterator::create(realm, set, Object::PropertyKind::Value);
}
}

View file

@ -25,19 +25,18 @@ private:
JS_DECLARE_NATIVE_FUNCTION(add);
JS_DECLARE_NATIVE_FUNCTION(clear);
JS_DECLARE_NATIVE_FUNCTION(delete_);
JS_DECLARE_NATIVE_FUNCTION(difference);
JS_DECLARE_NATIVE_FUNCTION(entries);
JS_DECLARE_NATIVE_FUNCTION(for_each);
JS_DECLARE_NATIVE_FUNCTION(has);
JS_DECLARE_NATIVE_FUNCTION(values);
JS_DECLARE_NATIVE_FUNCTION(union_);
JS_DECLARE_NATIVE_FUNCTION(intersection);
JS_DECLARE_NATIVE_FUNCTION(difference);
JS_DECLARE_NATIVE_FUNCTION(symmetric_difference);
JS_DECLARE_NATIVE_FUNCTION(is_disjoint_from);
JS_DECLARE_NATIVE_FUNCTION(is_subset_of);
JS_DECLARE_NATIVE_FUNCTION(is_superset_of);
JS_DECLARE_NATIVE_FUNCTION(is_disjoint_from);
JS_DECLARE_NATIVE_FUNCTION(size_getter);
JS_DECLARE_NATIVE_FUNCTION(symmetric_difference);
JS_DECLARE_NATIVE_FUNCTION(union_);
JS_DECLARE_NATIVE_FUNCTION(values);
};
}