Commit fe9bdabe authored by legendecas's avatar legendecas Committed by V8 LUCI CQ

[builtins] implement array grouping

The Array Grouping proposal [1] reached Stage 3 in December 2021 TC39.

[1] https://github.com/tc39/proposal-array-grouping/

Bug: v8:12499
Change-Id: I05b4838d915ab1b0cf8126aa30a3e48f47b9ee59
Reviewed-on: https://chromium-review.googlesource.com/c/v8/v8/+/3366630Reviewed-by: 's avatarShu-yu Guo <syg@chromium.org>
Commit-Queue: Chengzhong Wu <legendecas@gmail.com>
Cr-Commit-Position: refs/heads/main@{#78794}
parent 1fc5f92a
......@@ -17,6 +17,7 @@
#include "src/objects/hash-table-inl.h"
#include "src/objects/js-array-buffer-inl.h"
#include "src/objects/js-array-inl.h"
#include "src/objects/js-collection-inl.h"
#include "src/objects/lookup.h"
#include "src/objects/objects-inl.h"
#include "src/objects/prototype.h"
......@@ -1542,5 +1543,309 @@ BUILTIN(ArrayConcat) {
return Slow_ArrayConcat(&args, species, isolate);
}
namespace {
// https://tc39.es/proposal-array-grouping/#sec-add-value-to-keyed-group
// Each keyed group is an array list.
inline Handle<OrderedHashMap> AddValueToKeyedGroup(
Isolate* isolate, Handle<OrderedHashMap> groups, Handle<Object> key,
Handle<Object> value) {
InternalIndex entry = groups->FindEntry(isolate, *key);
if (!entry.is_found()) {
Handle<ArrayList> array = ArrayList::New(isolate, 1);
array = ArrayList::Add(isolate, array, value);
return OrderedHashMap::Add(isolate, groups, key, array).ToHandleChecked();
}
Handle<ArrayList> array =
Handle<ArrayList>(ArrayList::cast(groups->ValueAt(entry)), isolate);
array = ArrayList::Add(isolate, array, value);
groups->SetEntry(entry, *key, *array);
return groups;
}
inline ElementsKind DeduceKeyedGroupElementsKind(ElementsKind kind) {
// The keyed groups are array lists with fast elements.
// Double elements are stored as HeapNumbers in the keyed group elements
// so that we don't need to cast all the keyed groups when switching from
// fast path to the generic path.
// TODO(v8:12499) add unboxed double elements support
switch (kind) {
case ElementsKind::PACKED_SMI_ELEMENTS: {
return ElementsKind::PACKED_SMI_ELEMENTS;
}
default: {
return ElementsKind::PACKED_ELEMENTS;
}
}
}
inline bool IsFastArray(Handle<JSReceiver> object) {
Isolate* isolate = object->GetIsolate();
if (isolate->force_slow_path()) return false;
if (!object->IsJSArray()) return false;
Handle<JSArray> array = Handle<JSArray>::cast(object);
if (!array->HasFastElements(isolate)) return false;
Context context = isolate->context();
if (array->map().prototype() !=
context.get(Context::INITIAL_ARRAY_PROTOTYPE_INDEX)) {
return false;
}
return Protectors::IsNoElementsIntact(isolate);
}
inline bool CheckArrayMapNotModified(Handle<JSArray> array,
Handle<Map> original_map) {
if (array->map() != *original_map) {
return false;
}
return Protectors::IsNoElementsIntact(array->GetIsolate());
}
enum class GroupByMode { kToObject, kToMap };
template <GroupByMode mode>
inline MaybeHandle<OrderedHashMap> GenericArrayGroupBy(
Isolate* isolate, Handle<JSReceiver> O, Handle<Object> callbackfn,
Handle<OrderedHashMap> groups, double initialK, double len) {
// 6. Repeat, while k < len
for (double k = initialK; k < len; ++k) {
// 6a. Let Pk be ! ToString(𝔽(k)).
Handle<Name> Pk;
ASSIGN_RETURN_ON_EXCEPTION(
isolate, Pk, Object::ToName(isolate, isolate->factory()->NewNumber(k)),
OrderedHashMap);
// 6b. Let kValue be ? Get(O, Pk).
Handle<Object> kValue;
ASSIGN_RETURN_ON_EXCEPTION(isolate, kValue,
Object::GetPropertyOrElement(isolate, O, Pk),
OrderedHashMap);
// Common steps for ArrayPrototypeGroupBy and ArrayPrototypeGroupByToMap
// 6c. Let key be ? Call(callbackfn, thisArg, « kValue, 𝔽(k), O »).
Handle<Object> propertyKey;
Handle<Object> argv[] = {kValue, isolate->factory()->NewNumber(k), O};
ASSIGN_RETURN_ON_EXCEPTION(isolate, propertyKey,
Execution::Call(isolate, callbackfn, O, 3, argv),
OrderedHashMap);
if (mode == GroupByMode::kToMap) {
// 6d. If key is -0𝔽, set key to +0𝔽.
if (propertyKey->IsMinusZero()) {
propertyKey = Handle<Smi>(Smi::FromInt(0), isolate);
}
} else {
// 6c. Let propertyKey be ? ToPropertyKey(? Call(callbackfn, thisArg, «
// kValue, 𝔽(k), O »)).
Handle<Name> propertyKeyName;
ASSIGN_RETURN_ON_EXCEPTION(isolate, propertyKeyName,
Object::ToName(isolate, propertyKey),
OrderedHashMap);
propertyKey = isolate->factory()->InternalizeName(propertyKeyName);
}
// 6e. Perform ! AddValueToKeyedGroup(groups, propertyKey, kValue).
groups = AddValueToKeyedGroup(isolate, groups, propertyKey, kValue);
// 6f. Set k to k + 1.
// done by the loop.
}
return groups;
}
template <GroupByMode mode>
inline MaybeHandle<OrderedHashMap> FastArrayGroupBy(
Isolate* isolate, Handle<JSArray> array, Handle<Object> callbackfn,
Handle<OrderedHashMap> groups, double len) {
Handle<Map> original_map = Handle<Map>(array->map(), isolate);
uint32_t uint_len = static_cast<uint32_t>(len);
ElementsAccessor* accessor = array->GetElementsAccessor();
// 4. Let k be 0.
// 6. Repeat, while k < len
for (InternalIndex k : InternalIndex::Range(uint_len)) {
if (!CheckArrayMapNotModified(array, original_map)) {
return GenericArrayGroupBy<mode>(isolate, array, callbackfn, groups,
k.as_uint32(), len);
}
// 6a. Let Pk be ! ToString(𝔽(k)).
// 6b. Let kValue be ? Get(O, Pk).
Handle<Object> kValue = accessor->Get(array, k);
if (kValue->IsTheHole()) {
kValue = isolate->factory()->undefined_value();
}
// Common steps for ArrayPrototypeGroupBy and ArrayPrototypeGroupByToMap
// 6c. Let key be ? Call(callbackfn, thisArg, « kValue, 𝔽(k), O »).
Handle<Object> propertyKey;
Handle<Object> argv[] = {
kValue, isolate->factory()->NewNumber(k.as_uint32()), array};
ASSIGN_RETURN_ON_EXCEPTION(
isolate, propertyKey,
Execution::Call(isolate, callbackfn, array, 3, argv), OrderedHashMap);
if (mode == GroupByMode::kToMap) {
// 6d. If key is -0𝔽, set key to +0𝔽.
if (propertyKey->IsMinusZero()) {
propertyKey = Handle<Smi>(Smi::FromInt(0), isolate);
}
} else {
// 6c. Let propertyKey be ? ToPropertyKey(? Call(callbackfn, thisArg, «
// kValue, 𝔽(k), O »)).
Handle<Name> propertyKeyName;
ASSIGN_RETURN_ON_EXCEPTION(isolate, propertyKeyName,
Object::ToName(isolate, propertyKey),
OrderedHashMap);
propertyKey = isolate->factory()->InternalizeName(propertyKeyName);
}
// 6e. Perform ! AddValueToKeyedGroup(groups, propertyKey, kValue).
groups = AddValueToKeyedGroup(isolate, groups, propertyKey, kValue);
// 6f. Set k to k + 1.
// done by the loop.
}
return groups;
}
} // namespace
// https://tc39.es/proposal-array-grouping/#sec-array.prototype.groupby
BUILTIN(ArrayPrototypeGroupBy) {
const char* const kMethodName = "Array.prototype.groupBy";
HandleScope scope(isolate);
Handle<JSReceiver> O;
// 1. Let O be ? ToObject(this value).
ASSIGN_RETURN_FAILURE_ON_EXCEPTION(
isolate, O, Object::ToObject(isolate, args.receiver(), kMethodName));
// 2. Let len be ? LengthOfArrayLike(O).
double len;
MAYBE_ASSIGN_RETURN_FAILURE_ON_EXCEPTION(isolate, len,
GetLengthProperty(isolate, O));
// 3. If IsCallable(callbackfn) is false, throw a TypeError exception.
Handle<Object> callbackfn = args.atOrUndefined(isolate, 1);
if (!callbackfn->IsCallable()) {
THROW_NEW_ERROR_RETURN_FAILURE(
isolate, NewTypeError(MessageTemplate::kCalledNonCallable, callbackfn));
}
// 5. Let groups be a new empty List.
Handle<OrderedHashMap> groups = isolate->factory()->NewOrderedHashMap();
// Elements kind of the array for grouped elements kind deduction.
ElementsKind elements_kind = ElementsKind::NO_ELEMENTS;
if (IsFastArray(O)) {
Handle<JSArray> array = Handle<JSArray>::cast(O);
ASSIGN_RETURN_FAILURE_ON_EXCEPTION(
isolate, groups,
FastArrayGroupBy<GroupByMode::kToObject>(isolate, array, callbackfn,
groups, len));
// Get array's elements kind after called into javascript.
elements_kind = array->GetElementsKind();
} else {
// 4. Let k be 0.
ASSIGN_RETURN_FAILURE_ON_EXCEPTION(
isolate, groups,
GenericArrayGroupBy<GroupByMode::kToObject>(isolate, O, callbackfn,
groups, 0, len));
}
// 7. Let obj be ! OrdinaryObjectCreate(null).
Handle<JSObject> obj = isolate->factory()->NewJSObjectWithNullProto();
ElementsKind result_elements_kind =
DeduceKeyedGroupElementsKind(elements_kind);
// 8. For each Record { [[Key]], [[Elements]] } g of groups, do
for (InternalIndex entry : groups->IterateEntries()) {
Handle<Name> key = Handle<Name>(Name::cast(groups->KeyAt(entry)), isolate);
// 8a. Let elements be ! CreateArrayFromList(g.[[Elements]]).
Handle<ArrayList> array_list =
Handle<ArrayList>(ArrayList::cast(groups->ValueAt(entry)), isolate);
Handle<FixedArray> elements = ArrayList::Elements(isolate, array_list);
Handle<JSArray> array = isolate->factory()->NewJSArrayWithElements(
elements, result_elements_kind, array_list->Length());
// 8b. Perform ! CreateDataPropertyOrThrow(obj, g.[[Key]], elements).
JSReceiver::CreateDataProperty(isolate, obj, key, array,
Just(kThrowOnError))
.Check();
}
// 9. Return obj.
return *obj;
}
// https://tc39.es/proposal-array-grouping/#sec-array.prototype.groupbymap
BUILTIN(ArrayPrototypeGroupByToMap) {
const char* const kMethodName = "Array.prototype.groupByToMap";
HandleScope scope(isolate);
Handle<JSReceiver> O;
// 1. Let O be ? ToObject(this value).
ASSIGN_RETURN_FAILURE_ON_EXCEPTION(
isolate, O, Object::ToObject(isolate, args.receiver(), kMethodName));
// 2. Let len be ? LengthOfArrayLike(O).
double len;
MAYBE_ASSIGN_RETURN_FAILURE_ON_EXCEPTION(isolate, len,
GetLengthProperty(isolate, O));
// 3. If IsCallable(callbackfn) is false, throw a TypeError exception.
Handle<Object> callbackfn = args.atOrUndefined(isolate, 1);
if (!callbackfn->IsCallable()) {
THROW_NEW_ERROR_RETURN_FAILURE(
isolate, NewTypeError(MessageTemplate::kCalledNonCallable, callbackfn));
}
// 5. Let groups be a new empty List.
Handle<OrderedHashMap> groups = isolate->factory()->NewOrderedHashMap();
// Elements kind of the array for grouped elements kind deduction.
ElementsKind elements_kind = ElementsKind::NO_ELEMENTS;
if (IsFastArray(O)) {
Handle<JSArray> array = Handle<JSArray>::cast(O);
ASSIGN_RETURN_FAILURE_ON_EXCEPTION(
isolate, groups,
FastArrayGroupBy<GroupByMode::kToMap>(isolate, array, callbackfn,
groups, len));
// Get array's elements kind after called into javascript.
elements_kind = array->GetElementsKind();
} else {
// 4. Let k be 0.
ASSIGN_RETURN_FAILURE_ON_EXCEPTION(
isolate, groups,
GenericArrayGroupBy<GroupByMode::kToMap>(isolate, O, callbackfn, groups,
0, len));
}
// 7. Let map be ! Construct(%Map%).
Handle<JSMap> map = isolate->factory()->NewJSMap();
Handle<OrderedHashMap> map_table = isolate->factory()->NewOrderedHashMap();
ElementsKind result_elements_kind =
DeduceKeyedGroupElementsKind(elements_kind);
// 8. For each Record { [[Key]], [[Elements]] } g of groups, do
for (InternalIndex entry : groups->IterateEntries()) {
Handle<Object> key = Handle<Object>(groups->KeyAt(entry), isolate);
// 8a. Let elements be ! CreateArrayFromList(g.[[Elements]]).
Handle<ArrayList> array_list =
Handle<ArrayList>(ArrayList::cast(groups->ValueAt(entry)), isolate);
Handle<FixedArray> elements = ArrayList::Elements(isolate, array_list);
Handle<JSArray> array = isolate->factory()->NewJSArrayWithElements(
elements, result_elements_kind, array_list->Length());
// 8b. Let entry be the Record { [[Key]]: g.[[Key]], [[Value]]: elements }.
// 8c. Append entry as the last element of map.[[MapData]].
map_table =
OrderedHashMap::Add(isolate, map_table, key, array).ToHandleChecked();
}
map->set_table(*map_table);
// 9. Return map.
return *map;
}
} // namespace internal
} // namespace v8
......@@ -385,6 +385,9 @@ namespace internal {
/* ES6 #sec-array.prototype.pop */ \
CPP(ArrayPop) \
TFJ(ArrayPrototypePop, kDontAdaptArgumentsSentinel) \
/* ES6 #sec-array.prototype.groupby */ \
CPP(ArrayPrototypeGroupBy) \
CPP(ArrayPrototypeGroupByToMap) \
/* ES6 #sec-array.prototype.push */ \
CPP(ArrayPush) \
TFJ(ArrayPrototypePush, kDontAdaptArgumentsSentinel) \
......
......@@ -307,7 +307,8 @@ DEFINE_BOOL(harmony_shipping, true, "enable all shipped harmony features")
V(harmony_rab_gsab, \
"harmony ResizableArrayBuffer / GrowableSharedArrayBuffer") \
V(harmony_temporal, "Temporal") \
V(harmony_shadow_realm, "harmony ShadowRealm")
V(harmony_shadow_realm, "harmony ShadowRealm") \
V(harmony_array_grouping, "harmony array grouping")
#ifdef V8_INTL_SUPPORT
#define HARMONY_INPROGRESS(V) HARMONY_INPROGRESS_BASE(V)
......
......@@ -4465,6 +4465,28 @@ void Genesis::InitializeGlobal_harmony_array_find_last() {
}
}
void Genesis::InitializeGlobal_harmony_array_grouping() {
if (!FLAG_harmony_array_grouping) return;
Handle<JSFunction> array_function(native_context()->array_function(),
isolate());
Handle<JSObject> array_prototype(
JSObject::cast(array_function->instance_prototype()), isolate());
SimpleInstallFunction(isolate_, array_prototype, "groupBy",
Builtin::kArrayPrototypeGroupBy, 1, false);
SimpleInstallFunction(isolate_, array_prototype, "groupByToMap",
Builtin::kArrayPrototypeGroupByToMap, 1, false);
Handle<JSObject> unscopables = Handle<JSObject>::cast(
JSObject::GetProperty(isolate(), array_prototype,
isolate()->factory()->unscopables_symbol())
.ToHandleChecked());
InstallTrueValuedProperty(isolate_, unscopables, "groupBy");
InstallTrueValuedProperty(isolate_, unscopables, "groupByToMap");
}
void Genesis::InitializeGlobal_harmony_object_has_own() {
if (!FLAG_harmony_object_has_own) return;
......
......@@ -406,6 +406,13 @@ MaybeHandle<OrderedHashMap> OrderedHashMap::Add(Isolate* isolate,
return table;
}
void OrderedHashMap::SetEntry(InternalIndex entry, Object key, Object value) {
DisallowGarbageCollection no_gc;
int index = EntryToIndex(entry);
this->set(index, key);
this->set(index + kValueOffset, value);
}
template <typename IsolateT>
InternalIndex OrderedNameDictionary::FindEntry(IsolateT* isolate, Object key) {
DisallowGarbageCollection no_gc;
......
......@@ -331,6 +331,9 @@ class V8_EXPORT_PRIVATE OrderedHashMap
int new_capacity);
static MaybeHandle<OrderedHashMap> Rehash(Isolate* isolate,
Handle<OrderedHashMap> table);
void SetEntry(InternalIndex entry, Object key, Object value);
Object ValueAt(InternalIndex entry);
// This takes and returns raw Address values containing tagged Object
......
// Copyright 2022 the V8 project authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
// Flags: --harmony-array-grouping
assertEquals(Array.prototype[Symbol.unscopables].groupBy, true);
var array = [-0, 1, 0, 2];
var groupBy = () => {
let result = array.groupBy(v => v > 0);
result = Array.from(Object.entries(result));
return result;
}
// entry order matters
assertEquals(groupBy(), [
['false', [-0, 0]],
['true', [1, 2]],
]);
Object.defineProperty(array, 4, {
enumerable: true,
configurable: true,
writable: true,
value: 3,
});
assertEquals(groupBy(), [
['false', [-0, 0]],
['true', [1, 2, 3]],
]);
Object.defineProperty(array, 5, {
enumerable: true,
configurable: true,
get: () => 4,
});
var result = groupBy();
assertEquals(result, [
['false', [-0, 0]],
['true', [1, 2, 3, 4]],
]);
assertSame(result[0][1][0], -0);
// fairly large result array
var length = 20000;
var array = new Array(length);
for (var idx = 0; idx < length; idx++) {
array[idx] = idx;
}
var groupBy = () => {
let result = array.groupBy(v => v % 2);
result = Array.from(Object.entries(result));
return result;
}
var result = groupBy();
assertEquals(result, [
['0', array.filter(v => v % 2 === 0)],
['1', array.filter(v => v % 2 === 1)],
]);
// check array changed by callbackfn
var array = [-0, 0, 1, 2];
groupBy = () => {
let result = array.groupBy((v, idx) => {
if (idx === 1) {
array[2] = {a: 'b'};
}
return v > 0;
});
result = Array.from(Object.entries(result));
return result;
}
assertEquals(groupBy(), [
['false', [-0, 0, {a: 'b'}]],
['true', [2]],
]);
// check array with holes
var array = [1, , 2, , 3, , 4];
var groupBy = () => {
let result = array.groupBy(v => v % 2 === 0 ? 'even' : 'not_even');
result = Array.from(Object.entries(result));
return result;
};
function checkNoHoles(arr) {
for (let idx = 0; idx < arr.length; idx++) {
assertTrue(Object.getOwnPropertyDescriptor(arr, idx) !== undefined);
}
}
var result = groupBy();
assertEquals(result, [
['not_even', [1, undefined, undefined, 3, undefined]],
['even', [2, 4]],
]);
checkNoHoles(result[0][1]);
checkNoHoles(result[1][1]);
var array = [1, undefined, 2, undefined, 3, undefined, 4];
result = groupBy();
assertEquals(result, [
['not_even', [1, undefined, undefined, 3, undefined]],
['even', [2, 4]],
]);
checkNoHoles(result[0][1]);
checkNoHoles(result[1][1]);
// array like objects
var arrayLikeObjects = [
{
'0': -1,
'1': 1,
'2': 2,
length: 3,
},
(function () { return arguments })(-1, 1, 2),
Int8Array.from([-1, 1, 2]),
Float32Array.from([-1, 1, 2]),
];
var groupBy = () => {
let result = Array.prototype.groupBy.call(array, v => v > 0);
result = Array.from(Object.entries(result));
return result;
};
for (var array of arrayLikeObjects) {
assertEquals(groupBy(), [
['false', [-1]],
['true', [1, 2]],
]);
}
// check proto elements
var array = [,];
var groupBy = () => {
let result = array.groupBy(v => v);
result = Array.from(Object.entries(result));
return result;
}
assertEquals(groupBy(), [
['undefined', [,]],
]);
array.__proto__.push(6);
assertEquals(groupBy(), [
['6', [6]],
]);
// callbackfn throws
var array = [-0, 1, 0, 2];
assertThrows(
() => array.groupBy(() => { throw new Error('foobar'); }),
Error,
'foobar'
);
// ToPropertyKey throws
var array = [-0, 1, 0, 2];
assertThrows(
() => array.groupBy(() => {
return {
toString() {
throw new Error('foobar');
},
};
}),
Error,
'foobar'
);
// callbackfn is not callable
var array = [-0, 1, 0, 2];
assertThrows(
() => array.groupBy('foobar'),
TypeError,
);
// Copyright 2022 the V8 project authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
// Flags: --harmony-array-grouping
assertEquals(Array.prototype[Symbol.unscopables].groupByToMap, true);
var array = [-0, 1, 0, 2];
var groupByToMap = () => {
let result = array.groupByToMap(v => v > 0);
result = Array.from(result.entries());
return result;
}
// entry order matters
assertEquals(groupByToMap(), [
[false, [-0, 0]],
[true, [1, 2]],
]);
Object.defineProperty(array, 4, {
enumerable: true,
configurable: true,
writable: true,
value: 3,
});
assertEquals(groupByToMap(), [
[false, [-0, 0]],
[true, [1, 2, 3]],
]);
Object.defineProperty(array, 5, {
enumerable: true,
configurable: true,
get: () => 4,
});
var result = groupByToMap();
assertEquals(result, [
[false, [-0, 0]],
[true, [1, 2, 3, 4]],
]);
assertSame(result[0][1][0], -0);
// fairly large result array
var length = 20000;
var array = new Array(length);
for (var idx = 0; idx < length; idx++) {
array[idx] = idx;
}
var groupByToMap = () => {
let result = array.groupByToMap(v => v % 2);
result = Array.from(result.entries());
return result;
}
var result = groupByToMap();
assertEquals(result, [
[0, array.filter(v => v % 2 === 0)],
[1, array.filter(v => v % 2 === 1)],
]);
// check section groupByToMap 6.d
var array = [-0, 0];
var result = array.groupByToMap(v => v);
assertEquals(result.get(0), [-0, 0]);
// check array changed by callbackfn
var array = [-0, 0, 1, 2];
var groupByToMap = () => {
let result = array.groupByToMap((v, idx) => {
if (idx === 1) {
array[2] = {a: 'b'};
}
return v > 0;
});
result = Array.from(result.entries());
return result;
}
assertEquals(groupByToMap(), [
[false, [-0, 0, {a: 'b'}]],
[true, [2]],
]);
// check array with holes
var array = [1, , 2, , 3, , 4];
var groupByToMap = () => {
let result = array.groupByToMap(v => v % 2 === 0 ? 'even' : 'not_even');
result = Array.from(result.entries());
return result;
};
function checkNoHoles(arr) {
for (let idx = 0; idx < arr.length; idx++) {
assertTrue(Object.getOwnPropertyDescriptor(arr, idx) !== undefined);
}
}
var result = groupByToMap();
assertEquals(result, [
['not_even', [1, undefined, undefined, 3, undefined]],
['even', [2, 4]],
]);
checkNoHoles(result[0][1]);
checkNoHoles(result[1][1]);
var array = [1, undefined, 2, undefined, 3, undefined, 4];
result = groupByToMap();
assertEquals(result, [
['not_even', [1, undefined, undefined, 3, undefined]],
['even', [2, 4]],
]);
checkNoHoles(result[0][1]);
checkNoHoles(result[1][1]);
// array like objects
var arrayLikeObjects = [
{
'0': -1,
'1': 1,
'2': 2,
length: 3,
},
(function () { return arguments })(-1, 1, 2),
Int8Array.from([-1, 1, 2]),
Float32Array.from([-1, 1, 2]),
];
var groupByToMap = () => {
let result = Array.prototype.groupByToMap.call(array, v => v > 0);
result = Array.from(result.entries());
return result;
};
for (var array of arrayLikeObjects) {
assertEquals(groupByToMap(), [
[false, [-1]],
[true, [1, 2]],
]);
}
// check proto elements
var array = [,];
var groupByToMap = () => {
let result = array.groupByToMap(v => v);
result = Array.from(result.entries());
return result;
}
assertEquals(groupByToMap(), [
[undefined, [,]],
]);
array.__proto__.push(6);
assertEquals(groupByToMap(), [
[6, [6]],
]);
// callbackfn throws
var array = [-0, 1, 0, 2];
assertThrows(
() => array.groupByToMap(() => { throw new Error('foobar'); }),
Error,
'foobar'
);
// callbackfn is not callable
var array = [-0, 1, 0, 2];
assertThrows(
() => array.groupByToMap('foobar'),
TypeError,
);
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment