Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Implement the array-grouping proposal #3420

Merged
merged 1 commit into from
Oct 25, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
121 changes: 118 additions & 3 deletions boa_engine/src/builtins/map/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -54,7 +54,7 @@ impl IntrinsicObject for Map {
.name(js_string!("entries"))
.build();

BuiltInBuilder::from_standard_constructor::<Self>(realm)
let obj = BuiltInBuilder::from_standard_constructor::<Self>(realm)
.static_accessor(
JsSymbol::species(),
Some(get_species),
Expand Down Expand Up @@ -89,8 +89,12 @@ impl IntrinsicObject for Map {
Some(get_size),
None,
Attribute::CONFIGURABLE,
)
.build();
);

#[cfg(feature = "experimental")]
let obj = { obj.static_method(Self::group_by, js_string!("groupBy"), 2) };

obj.build();
}

fn get(intrinsics: &Intrinsics) -> JsObject {
Expand Down Expand Up @@ -521,6 +525,117 @@ impl Map {
// 2. Return ? CreateMapIterator(M, value).
MapIterator::create_map_iterator(this, PropertyNameKind::Value, context)
}

/// [`Map.groupBy ( items, callbackfn )`][spec]
///
/// [spec]: https://tc39.es/proposal-array-grouping/#sec-map.groupby
#[cfg(feature = "experimental")]
pub(crate) fn group_by(
_: &JsValue,
args: &[JsValue],
context: &mut Context<'_>,
) -> JsResult<JsValue> {
use std::hash::BuildHasherDefault;

use indexmap::IndexMap;
use rustc_hash::FxHasher;

use crate::builtins::{iterable::if_abrupt_close_iterator, Array, Number};

let items = args.get_or_undefined(0);
let callback = args.get_or_undefined(1);
// 1. Let groups be ? GroupBy(items, callbackfn, zero).

// `GroupBy`
// https://tc39.es/proposal-array-grouping/#sec-group-by
// inlined to change the key type.

// 1. Perform ? RequireObjectCoercible(items).
items.require_object_coercible()?;

// 2. If IsCallable(callbackfn) is false, throw a TypeError exception.
let callback = callback.as_callable().ok_or_else(|| {
JsNativeError::typ().with_message("callback must be a callable object")
})?;

// 3. Let groups be a new empty List.
let mut groups: IndexMap<JsValue, Vec<JsValue>, BuildHasherDefault<FxHasher>> =
IndexMap::default();

// 4. Let iteratorRecord be ? GetIterator(items).
let mut iterator = items.get_iterator(context, None, None)?;

// 5. Let k be 0.
let mut k = 0u64;

// 6. Repeat,
loop {
// a. If k ≥ 2^53 - 1, then
if k >= Number::MAX_SAFE_INTEGER as u64 {
// i. Let error be ThrowCompletion(a newly created TypeError object).
let error = JsNativeError::typ()
.with_message("exceeded maximum safe integer")
.into();

// ii. Return ? IteratorClose(iteratorRecord, error).
return iterator.close(Err(error), context);
}

// b. Let next be ? IteratorStep(iteratorRecord).
let done = iterator.step(context)?;

// c. If next is false, then
if done {
// i. Return groups.
break;
}

// d. Let value be ? IteratorValue(next).
let value = iterator.value(context)?;

// e. Let key be Completion(Call(callbackfn, undefined, « value, 𝔽(k) »)).
let key = callback.call(&JsValue::undefined(), &[value.clone(), k.into()], context);

// f. IfAbruptCloseIterator(key, iteratorRecord).
let mut key = if_abrupt_close_iterator!(key, iterator, context);

// h. Else,
// i. Assert: keyCoercion is zero.
// ii. If key is -0𝔽, set key to +0𝔽.
if key.as_number() == Some(-0.0) {
key = 0.into();
}

// i. Perform AddValueToKeyedGroup(groups, key, value).
groups.entry(key).or_default().push(value);

// j. Set k to k + 1.
k += 1;
}

// 2. Let map be ! Construct(%Map%).
let mut map = OrderedMap::new();

// 3. For each Record { [[Key]], [[Elements]] } g of groups, do
for (key, elements) in groups {
// a. Let elements be CreateArrayFromList(g.[[Elements]]).
let elements = Array::create_array_from_list(elements, context);

// b. Let entry be the Record { [[Key]]: g.[[Key]], [[Value]]: elements }.
// c. Append entry to map.[[MapData]].
map.insert(key, elements.into());
}

let proto = context.intrinsics().constructors().map().prototype();

// 4. Return map.
Ok(JsObject::from_proto_and_data_with_shared_shape(
context.root_shape(),
proto,
ObjectData::map(map),
)
.into())
}
}

/// `AddEntriesFromIterable`
Expand Down
114 changes: 111 additions & 3 deletions boa_engine/src/builtins/object/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -56,7 +56,7 @@ impl IntrinsicObject for Object {
.name(js_string!("set __proto__"))
.build();

BuiltInBuilder::from_standard_constructor::<Self>(realm)
let obj = BuiltInBuilder::from_standard_constructor::<Self>(realm)
.inherits(None)
.accessor(
utf16!("__proto__"),
Expand Down Expand Up @@ -131,8 +131,12 @@ impl IntrinsicObject for Object {
1,
)
.static_method(Self::has_own, js_string!("hasOwn"), 2)
.static_method(Self::from_entries, js_string!("fromEntries"), 1)
.build();
.static_method(Self::from_entries, js_string!("fromEntries"), 1);

#[cfg(feature = "experimental")]
let obj = { obj.static_method(Self::group_by, js_string!("groupBy"), 2) };

obj.build();
}

fn get(intrinsics: &Intrinsics) -> JsObject {
Expand Down Expand Up @@ -1334,6 +1338,110 @@ impl Object {
// 6. Return ? AddEntriesFromIterable(obj, iterable, adder).
map::add_entries_from_iterable(&obj, iterable, &adder.into(), context)
}

/// [`Object.groupBy ( items, callbackfn )`][spec]
///
/// [spec]: https://tc39.es/proposal-array-grouping/#sec-object.groupby
#[cfg(feature = "experimental")]
pub(crate) fn group_by(
_: &JsValue,
args: &[JsValue],
context: &mut Context<'_>,
) -> JsResult<JsValue> {
use std::hash::BuildHasherDefault;

use indexmap::IndexMap;
use rustc_hash::FxHasher;

use crate::builtins::{iterable::if_abrupt_close_iterator, Number};

let items = args.get_or_undefined(0);
let callback = args.get_or_undefined(1);
// 1. Let groups be ? GroupBy(items, callbackfn, property).

// `GroupBy`
// https://tc39.es/proposal-array-grouping/#sec-group-by
// inlined to change the key type.

// 1. Perform ? RequireObjectCoercible(items).
items.require_object_coercible()?;

// 2. If IsCallable(callbackfn) is false, throw a TypeError exception.
let callback = callback.as_callable().ok_or_else(|| {
JsNativeError::typ().with_message("callback must be a callable object")
})?;

// 3. Let groups be a new empty List.
let mut groups: IndexMap<PropertyKey, Vec<JsValue>, BuildHasherDefault<FxHasher>> =
IndexMap::default();

// 4. Let iteratorRecord be ? GetIterator(items).
let mut iterator = items.get_iterator(context, None, None)?;

// 5. Let k be 0.
let mut k = 0u64;

// 6. Repeat,
loop {
// a. If k ≥ 2^53 - 1, then
if k >= Number::MAX_SAFE_INTEGER as u64 {
// i. Let error be ThrowCompletion(a newly created TypeError object).
let error = JsNativeError::typ()
.with_message("exceeded maximum safe integer")
.into();

// ii. Return ? IteratorClose(iteratorRecord, error).
return iterator.close(Err(error), context);
}

// b. Let next be ? IteratorStep(iteratorRecord).
let done = iterator.step(context)?;

// c. If next is false, then
if done {
// i. Return groups.
break;
}

// d. Let value be ? IteratorValue(next).
let value = iterator.value(context)?;

// e. Let key be Completion(Call(callbackfn, undefined, « value, 𝔽(k) »)).
let key = callback.call(&JsValue::undefined(), &[value.clone(), k.into()], context);

// f. IfAbruptCloseIterator(key, iteratorRecord).
let key = if_abrupt_close_iterator!(key, iterator, context);

// g. If keyCoercion is property, then
// i. Set key to Completion(ToPropertyKey(key)).
let key = key.to_property_key(context);

// ii. IfAbruptCloseIterator(key, iteratorRecord).
let key = if_abrupt_close_iterator!(key, iterator, context);

// i. Perform AddValueToKeyedGroup(groups, key, value).
groups.entry(key).or_default().push(value);

// j. Set k to k + 1.
k += 1;
}

// 2. Let obj be OrdinaryObjectCreate(null).
let obj = JsObject::with_null_proto();

// 3. For each Record { [[Key]], [[Elements]] } g of groups, do
for (key, elements) in groups {
// a. Let elements be CreateArrayFromList(g.[[Elements]]).
let elements = Array::create_array_from_list(elements, context);

// b. Perform ! CreateDataPropertyOrThrow(obj, g.[[Key]], elements).
obj.create_data_property_or_throw(key, elements, context)
.expect("cannot fail for a newly created object");
}

// 4. Return obj.
Ok(obj.into())
}
}

/// The abstract operation `ObjectDefineProperties`
Expand Down
3 changes: 0 additions & 3 deletions test262_config.toml
Original file line number Diff line number Diff line change
Expand Up @@ -41,9 +41,6 @@ features = [
# https://github.com/tc39/proposal-realms
"ShadowRealm",

# https://github.com/tc39/proposal-array-grouping
"array-grouping",

# https://github.com/tc39/proposal-intl-duration-format
"Intl.DurationFormat",

Expand Down
Loading