Skip to content

Commit 4ea3594

Browse files
committed
LibJS: Re-implement the Array Grouping proposal as static methods
Closes #19495.
1 parent 4e7e878 commit 4ea3594

File tree

10 files changed

+220
-276
lines changed

10 files changed

+220
-276
lines changed

Userland/Libraries/LibJS/Runtime/AbstractOperations.h

Lines changed: 112 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@
1414
#include <LibJS/Runtime/CanonicalIndex.h>
1515
#include <LibJS/Runtime/FunctionObject.h>
1616
#include <LibJS/Runtime/GlobalObject.h>
17+
#include <LibJS/Runtime/IteratorOperations.h>
1718
#include <LibJS/Runtime/PrivateEnvironment.h>
1819
#include <LibJS/Runtime/Value.h>
1920

@@ -180,6 +181,117 @@ Vector<T> merge_lists(Vector<T> const& a, Vector<T> const& b)
180181
return merged;
181182
}
182183

184+
// 4.2 AddValueToKeyedGroup ( groups, key, value ), https://tc39.es/proposal-array-grouping/#sec-add-value-to-keyed-group
185+
template<typename GroupsType, typename KeyType>
186+
void add_value_to_keyed_group(VM& vm, GroupsType& groups, KeyType key, Value value)
187+
{
188+
// 1. For each Record { [[Key]], [[Elements]] } g of groups, do
189+
// a. If SameValue(g.[[Key]], key) is true, then
190+
// NOTE: This is performed in KeyedGroupTraits::equals for groupToMap and Traits<JS::PropertyKey>::equals for group.
191+
auto existing_elements_iterator = groups.find(key);
192+
if (existing_elements_iterator != groups.end()) {
193+
// i. Assert: exactly one element of groups meets this criteria.
194+
// NOTE: This is done on insertion into the hash map, as only `set` tells us if we overrode an entry.
195+
196+
// ii. Append value as the last element of g.[[Elements]].
197+
existing_elements_iterator->value.append(value);
198+
199+
// iii. Return unused.
200+
return;
201+
}
202+
203+
// 2. Let group be the Record { [[Key]]: key, [[Elements]]: « value » }.
204+
MarkedVector<Value> new_elements { vm.heap() };
205+
new_elements.append(value);
206+
207+
// 3. Append group as the last element of groups.
208+
auto result = groups.set(key, move(new_elements));
209+
VERIFY(result == AK::HashSetResult::InsertedNewEntry);
210+
211+
// 4. Return unused.
212+
}
213+
214+
// 4.1 GroupBy ( items, callbackfn, keyCoercion ), https://tc39.es/proposal-array-grouping/#sec-group-by
215+
template<typename GroupsType, typename KeyType>
216+
ThrowCompletionOr<GroupsType> group_by(VM& vm, Value items, Value callback_function)
217+
{
218+
// 1. Perform ? RequireObjectCoercible(items).
219+
TRY(require_object_coercible(vm, items));
220+
221+
// 2. If IsCallable(callbackfn) is false, throw a TypeError exception.
222+
if (!callback_function.is_function())
223+
return vm.throw_completion<TypeError>(ErrorType::NotAFunction, TRY_OR_THROW_OOM(vm, callback_function.to_string_without_side_effects()));
224+
225+
// 3. Let groups be a new empty List.
226+
GroupsType groups;
227+
228+
// 4. Let iteratorRecord be ? GetIterator(items).
229+
auto iterator_record = TRY(get_iterator(vm, items));
230+
231+
// 5. Let k be 0.
232+
u64 k = 0;
233+
234+
// 6. Repeat,
235+
while (true) {
236+
// a. If k ≥ 2^53 - 1, then
237+
if (k >= MAX_ARRAY_LIKE_INDEX) {
238+
// i. Let error be ThrowCompletion(a newly created TypeError object).
239+
auto error = vm.throw_completion<TypeError>(ErrorType::ArrayMaxSize);
240+
241+
// ii. Return ? IteratorClose(iteratorRecord, error).
242+
return iterator_close(vm, iterator_record, move(error));
243+
}
244+
245+
// b. Let next be ? IteratorStep(iteratorRecord).
246+
auto next = TRY(iterator_step(vm, iterator_record));
247+
248+
// c. If next is false, then
249+
if (!next) {
250+
// i. Return groups.
251+
return ThrowCompletionOr<GroupsType> { move(groups) };
252+
}
253+
254+
// d. Let value be ? IteratorValue(next).
255+
auto value = TRY(iterator_value(vm, *next));
256+
257+
// e. Let key be Completion(Call(callbackfn, undefined, « value, 𝔽(k) »)).
258+
auto key = call(vm, callback_function, js_undefined(), value, Value(k));
259+
260+
// f. IfAbruptCloseIterator(key, iteratorRecord).
261+
if (key.is_error())
262+
return Completion { *TRY(iterator_close(vm, iterator_record, key.release_error())) };
263+
264+
// g. If keyCoercion is property, then
265+
if constexpr (IsSame<KeyType, PropertyKey>) {
266+
// i. Set key to Completion(ToPropertyKey(key)).
267+
auto property_key = key.value().to_property_key(vm);
268+
269+
// ii. IfAbruptCloseIterator(key, iteratorRecord).
270+
if (property_key.is_error())
271+
return Completion { *TRY(iterator_close(vm, iterator_record, key.release_error())) };
272+
273+
add_value_to_keyed_group(vm, groups, property_key.release_value(), value);
274+
}
275+
// h. Else,
276+
else {
277+
// i. Assert: keyCoercion is zero.
278+
static_assert(IsSame<KeyType, void>);
279+
280+
// ii. If key is -0𝔽, set key to +0𝔽.
281+
if (key.value().is_negative_zero())
282+
key = Value(0);
283+
284+
add_value_to_keyed_group(vm, groups, make_handle(key.release_value()), value);
285+
}
286+
287+
// i. Perform AddValueToKeyedGroup(groups, key, value).
288+
// NOTE: This is dependent on the `key_coercion` template parameter and thus done separately in the branches above.
289+
290+
// j. Set k to k + 1.
291+
++k;
292+
}
293+
}
294+
183295
// x modulo y, https://tc39.es/ecma262/#eqn-modulo
184296
template<Arithmetic T, Arithmetic U>
185297
auto modulo(T x, U y)

Userland/Libraries/LibJS/Runtime/ArrayPrototype.cpp

Lines changed: 0 additions & 162 deletions
Original file line numberDiff line numberDiff line change
@@ -53,8 +53,6 @@ ThrowCompletionOr<void> ArrayPrototype::initialize(Realm& realm)
5353
define_native_function(realm, vm.names.flat, flat, 0, attr);
5454
define_native_function(realm, vm.names.flatMap, flat_map, 1, attr);
5555
define_native_function(realm, vm.names.forEach, for_each, 1, attr);
56-
define_native_function(realm, vm.names.group, group, 1, attr);
57-
define_native_function(realm, vm.names.groupToMap, group_to_map, 1, attr);
5856
define_native_function(realm, vm.names.includes, includes, 1, attr);
5957
define_native_function(realm, vm.names.indexOf, index_of, 1, attr);
6058
define_native_function(realm, vm.names.join, join, 1, attr);
@@ -87,7 +85,6 @@ ThrowCompletionOr<void> ArrayPrototype::initialize(Realm& realm)
8785
define_direct_property(vm.well_known_symbol_iterator(), get_without_side_effects(vm.names.values), attr);
8886

8987
// 23.1.3.41 Array.prototype [ @@unscopables ], https://tc39.es/ecma262/#sec-array.prototype-@@unscopables
90-
// With array grouping proposal, https://tc39.es/proposal-array-grouping/#sec-array.prototype-@@unscopables
9188
auto unscopable_list = Object::create(realm, nullptr);
9289
MUST(unscopable_list->create_data_property_or_throw(vm.names.at, Value(true)));
9390
MUST(unscopable_list->create_data_property_or_throw(vm.names.copyWithin, Value(true)));
@@ -99,8 +96,6 @@ ThrowCompletionOr<void> ArrayPrototype::initialize(Realm& realm)
9996
MUST(unscopable_list->create_data_property_or_throw(vm.names.findLastIndex, Value(true)));
10097
MUST(unscopable_list->create_data_property_or_throw(vm.names.flat, Value(true)));
10198
MUST(unscopable_list->create_data_property_or_throw(vm.names.flatMap, Value(true)));
102-
MUST(unscopable_list->create_data_property_or_throw(vm.names.group, Value(true)));
103-
MUST(unscopable_list->create_data_property_or_throw(vm.names.groupToMap, Value(true)));
10499
MUST(unscopable_list->create_data_property_or_throw(vm.names.includes, Value(true)));
105100
MUST(unscopable_list->create_data_property_or_throw(vm.names.keys, Value(true)));
106101
MUST(unscopable_list->create_data_property_or_throw(vm.names.toReversed, Value(true)));
@@ -723,163 +718,6 @@ JS_DEFINE_NATIVE_FUNCTION(ArrayPrototype::for_each)
723718
return js_undefined();
724719
}
725720

726-
// 2.3 AddValueToKeyedGroup ( groups, key, value ), https://tc39.es/proposal-array-grouping/#sec-add-value-to-keyed-group
727-
template<typename GroupsType, typename KeyType>
728-
static void add_value_to_keyed_group(VM& vm, GroupsType& groups, KeyType key, Value value)
729-
{
730-
// 1. For each Record { [[Key]], [[Elements]] } g of groups, do
731-
// a. If SameValue(g.[[Key]], key) is true, then
732-
// NOTE: This is performed in KeyedGroupTraits::equals for groupToMap and Traits<JS::PropertyKey>::equals for group.
733-
auto existing_elements_iterator = groups.find(key);
734-
if (existing_elements_iterator != groups.end()) {
735-
// i. Assert: exactly one element of groups meets this criteria.
736-
// NOTE: This is done on insertion into the hash map, as only `set` tells us if we overrode an entry.
737-
738-
// ii. Append value as the last element of g.[[Elements]].
739-
existing_elements_iterator->value.append(value);
740-
741-
// iii. Return unused.
742-
return;
743-
}
744-
745-
// 2. Let group be the Record { [[Key]]: key, [[Elements]]: « value » }.
746-
MarkedVector<Value> new_elements { vm.heap() };
747-
new_elements.append(value);
748-
749-
// 3. Append group as the last element of groups.
750-
auto result = groups.set(key, move(new_elements));
751-
VERIFY(result == AK::HashSetResult::InsertedNewEntry);
752-
}
753-
754-
// 2.1 Array.prototype.group ( callbackfn [ , thisArg ] ), https://tc39.es/proposal-array-grouping/#sec-array.prototype.group
755-
JS_DEFINE_NATIVE_FUNCTION(ArrayPrototype::group)
756-
{
757-
auto& realm = *vm.current_realm();
758-
759-
auto callback_function = vm.argument(0);
760-
auto this_arg = vm.argument(1);
761-
762-
// 1. Let O be ? ToObject(this value).
763-
auto this_object = TRY(vm.this_value().to_object(vm));
764-
765-
// 2. Let len be ? LengthOfArrayLike(O).
766-
auto length = TRY(length_of_array_like(vm, this_object));
767-
768-
// 3. If IsCallable(callbackfn) is false, throw a TypeError exception.
769-
if (!callback_function.is_function())
770-
return vm.throw_completion<TypeError>(ErrorType::NotAFunction, TRY_OR_THROW_OOM(vm, callback_function.to_string_without_side_effects()));
771-
772-
// 5. Let groups be a new empty List.
773-
OrderedHashMap<PropertyKey, MarkedVector<Value>> groups;
774-
775-
// 4. Let k be 0.
776-
// 6. Repeat, while k < len
777-
for (size_t index = 0; index < length; ++index) {
778-
// a. Let Pk be ! ToString(𝔽(k)).
779-
auto index_property = PropertyKey { index };
780-
781-
// b. Let kValue be ? Get(O, Pk).
782-
auto k_value = TRY(this_object->get(index_property));
783-
784-
// c. Let propertyKey be ? ToPropertyKey(? Call(callbackfn, thisArg, « kValue, 𝔽(k), O »)).
785-
auto property_key_value = TRY(call(vm, callback_function.as_function(), this_arg, k_value, Value(index), this_object));
786-
auto property_key = TRY(property_key_value.to_property_key(vm));
787-
788-
// d. Perform AddValueToKeyedGroup(groups, propertyKey, kValue).
789-
add_value_to_keyed_group(vm, groups, property_key, k_value);
790-
791-
// e. Set k to k + 1.
792-
}
793-
794-
// 7. Let obj be OrdinaryObjectCreate(null).
795-
auto object = Object::create(realm, nullptr);
796-
797-
// 8. For each Record { [[Key]], [[Elements]] } g of groups, do
798-
for (auto& group : groups) {
799-
// a. Let elements be CreateArrayFromList(g.[[Elements]]).
800-
auto elements = Array::create_from(realm, group.value);
801-
802-
// b. Perform ! CreateDataPropertyOrThrow(obj, g.[[Key]], elements).
803-
MUST(object->create_data_property_or_throw(group.key, elements));
804-
}
805-
806-
// 9. Return obj.
807-
return object;
808-
}
809-
810-
// 2.2 Array.prototype.groupToMap ( callbackfn [ , thisArg ] ), https://tc39.es/proposal-array-grouping/#sec-array.prototype.grouptomap
811-
JS_DEFINE_NATIVE_FUNCTION(ArrayPrototype::group_to_map)
812-
{
813-
auto& realm = *vm.current_realm();
814-
815-
auto callback_function = vm.argument(0);
816-
auto this_arg = vm.argument(1);
817-
818-
// 1. Let O be ? ToObject(this value).
819-
auto this_object = TRY(vm.this_value().to_object(vm));
820-
821-
// 2. Let len be ? LengthOfArrayLike(O).
822-
auto length = TRY(length_of_array_like(vm, this_object));
823-
824-
// 3. If IsCallable(callbackfn) is false, throw a TypeError exception.
825-
if (!callback_function.is_function())
826-
return vm.throw_completion<TypeError>(ErrorType::NotAFunction, TRY_OR_THROW_OOM(vm, callback_function.to_string_without_side_effects()));
827-
828-
struct KeyedGroupTraits : public Traits<Handle<Value>> {
829-
static unsigned hash(Handle<Value> const& value_handle)
830-
{
831-
return ValueTraits::hash(value_handle.value());
832-
}
833-
834-
static bool equals(Handle<Value> const& a, Handle<Value> const& b)
835-
{
836-
// AddValueToKeyedGroup uses SameValue on the keys on Step 1.a.
837-
return same_value(a.value(), b.value());
838-
}
839-
};
840-
841-
// 5. Let groups be a new empty List.
842-
OrderedHashMap<Handle<Value>, MarkedVector<Value>, KeyedGroupTraits> groups;
843-
844-
// 4. Let k be 0.
845-
// 6. Repeat, while k < len
846-
for (size_t index = 0; index < length; ++index) {
847-
// a. Let Pk be ! ToString(𝔽(k)).
848-
auto index_property = PropertyKey { index };
849-
850-
// b. Let kValue be ? Get(O, Pk).
851-
auto k_value = TRY(this_object->get(index_property));
852-
853-
// c. Let key be ? Call(callbackfn, thisArg, « kValue, 𝔽(k), O »).
854-
auto key = TRY(call(vm, callback_function.as_function(), this_arg, k_value, Value(index), this_object));
855-
856-
// d. If key is -0𝔽, set key to +0𝔽.
857-
if (key.is_negative_zero())
858-
key = Value(0);
859-
860-
// e. Perform AddValueToKeyedGroup(groups, key, kValue).
861-
add_value_to_keyed_group(vm, groups, make_handle(key), k_value);
862-
863-
// f. Set k to k + 1.
864-
}
865-
866-
// 7. Let map be ! Construct(%Map%).
867-
auto map = Map::create(realm);
868-
869-
// 8. For each Record { [[Key]], [[Elements]] } g of groups, do
870-
for (auto& group : groups) {
871-
// a. Let elements be CreateArrayFromList(g.[[Elements]]).
872-
auto elements = Array::create_from(realm, group.value);
873-
874-
// b. Let entry be the Record { [[Key]]: g.[[Key]], [[Value]]: elements }.
875-
// c. Append entry as the last element of map.[[MapData]].
876-
map->map_set(group.key.value(), elements);
877-
}
878-
879-
// 9. Return map.
880-
return map;
881-
}
882-
883721
// 23.1.3.16 Array.prototype.includes ( searchElement [ , fromIndex ] ), https://tc39.es/ecma262/#sec-array.prototype.includes
884722
JS_DEFINE_NATIVE_FUNCTION(ArrayPrototype::includes)
885723
{

Userland/Libraries/LibJS/Runtime/CommonPropertyNames.h

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -265,10 +265,10 @@ namespace JS {
265265
P(globalThis) \
266266
P(granularity) \
267267
P(group) \
268+
P(groupBy) \
268269
P(groupCollapsed) \
269270
P(groupEnd) \
270271
P(groups) \
271-
P(groupToMap) \
272272
P(has) \
273273
P(hasIndices) \
274274
P(hasOwn) \

0 commit comments

Comments
 (0)