diff --git a/Userland/Libraries/LibJS/Runtime/Set.cpp b/Userland/Libraries/LibJS/Runtime/Set.cpp index 39b45af240b..4ddd57067c3 100644 --- a/Userland/Libraries/LibJS/Runtime/Set.cpp +++ b/Userland/Libraries/LibJS/Runtime/Set.cpp @@ -4,7 +4,9 @@ * SPDX-License-Identifier: BSD-2-Clause */ +#include #include +#include 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 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(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(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(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(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(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_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); +} + } diff --git a/Userland/Libraries/LibJS/Runtime/Set.h b/Userland/Libraries/LibJS/Runtime/Set.h index d570fa82569..e7150be17a4 100644 --- a/Userland/Libraries/LibJS/Runtime/Set.h +++ b/Userland/Libraries/LibJS/Runtime/Set.h @@ -47,4 +47,15 @@ private: GCPtr m_values; }; +// 24.2.1.1 Set Records, https://tc39.es/ecma262/#sec-set-records +struct SetRecord { + NonnullGCPtr set_object; // [[SetObject]] + double size { 0 }; // [[Size] + NonnullGCPtr has; // [[Has]] + NonnullGCPtr keys; // [[Keys]] +}; + +ThrowCompletionOr get_set_record(VM&, Value); +bool set_data_has(NonnullGCPtr, Value); + } diff --git a/Userland/Libraries/LibJS/Runtime/SetPrototype.cpp b/Userland/Libraries/LibJS/Runtime/SetPrototype.cpp index 0cdbabb13b0..86936cb54ee 100644 --- a/Userland/Libraries/LibJS/Runtime/SetPrototype.cpp +++ b/Userland/Libraries/LibJS/Runtime/SetPrototype.cpp @@ -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 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 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 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 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 set; // [[Set]] - double size { 0 }; // [[Size] - NonnullGCPtr has; // [[Has]] - NonnullGCPtr keys; // [[Keys]] -}; - -// 9 GetSetRecord ( obj ), https://tc39.es/proposal-set-methods/#sec-getsetrecord -static ThrowCompletionOr 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(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(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(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(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(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 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 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); } } diff --git a/Userland/Libraries/LibJS/Runtime/SetPrototype.h b/Userland/Libraries/LibJS/Runtime/SetPrototype.h index 1284240fba1..49992c07d29 100644 --- a/Userland/Libraries/LibJS/Runtime/SetPrototype.h +++ b/Userland/Libraries/LibJS/Runtime/SetPrototype.h @@ -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); }; }