Skip to content

Commit b60b103

Browse files
authored
TryFromJs from JsMap for HashMap & BtreeMap (#3998)
* `TryFromJs` from `JsMap` for `HashMap` & `BtreeMap` * fix `clippy` warn * use `IteratorResult` instead of `as_object` * `JsMap` impl `rust_for_each` * fix: initial `JsMap` can be changed in `for_each` * better naming
1 parent d8ec97c commit b60b103

File tree

4 files changed

+109
-0
lines changed

4 files changed

+109
-0
lines changed

core/engine/src/builtins/map/mod.rs

Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -503,6 +503,53 @@ impl Map {
503503
}
504504
}
505505

506+
/// Call `f` for each `(key, value)` in the `Map`.
507+
///
508+
/// Can not be used in [`Self::for_each`] because in that case will be
509+
/// incorrect order for next steps of the algo:
510+
/// ```txt
511+
/// 2. Perform ? RequireInternalSlot(M, [[MapData]]).
512+
/// 3. If IsCallable(callbackfn) is false, throw a TypeError exception.
513+
/// ```
514+
pub(crate) fn for_each_native<F>(this: &JsValue, mut f: F) -> JsResult<()>
515+
where
516+
F: FnMut(JsValue, JsValue) -> JsResult<()>,
517+
{
518+
// See `Self::for_each` for comments on the algo.
519+
520+
let map = this
521+
.as_object()
522+
.filter(|obj| obj.is::<OrderedMap<JsValue>>())
523+
.ok_or_else(|| JsNativeError::typ().with_message("`this` is not a Map"))?;
524+
525+
let _lock = map
526+
.downcast_mut::<OrderedMap<JsValue>>()
527+
.expect("checked that `this` was a map")
528+
.lock(map.clone());
529+
530+
let mut index = 0;
531+
loop {
532+
let (k, v) = {
533+
let map = map
534+
.downcast_ref::<OrderedMap<JsValue>>()
535+
.expect("checked that `this` was a map");
536+
537+
if index < map.full_len() {
538+
if let Some((k, v)) = map.get_index(index) {
539+
(k.clone(), v.clone())
540+
} else {
541+
continue;
542+
}
543+
} else {
544+
return Ok(());
545+
}
546+
};
547+
548+
f(k, v)?;
549+
index += 1;
550+
}
551+
}
552+
506553
/// `Map.prototype.values()`
507554
///
508555
/// Returns a new Iterator object that contains the values for each element in the Map object in insertion order.

core/engine/src/object/builtins/jsmap.rs

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -402,6 +402,16 @@ impl JsMap {
402402
)
403403
}
404404

405+
/// Executes the provided callback function for each key-value pair within the [`JsMap`].
406+
#[inline]
407+
pub fn for_each_native<F>(&self, f: F) -> JsResult<()>
408+
where
409+
F: FnMut(JsValue, JsValue) -> JsResult<()>,
410+
{
411+
let this = self.inner.clone().into();
412+
Map::for_each_native(&this, f)
413+
}
414+
405415
/// Returns a new [`JsMapIterator`] object that yields the `value` for each element within the [`JsMap`] in insertion order.
406416
#[inline]
407417
pub fn values(&self, context: &mut Context) -> JsResult<JsMapIterator> {

core/engine/src/value/conversions/try_from_js.rs

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -565,3 +565,26 @@ fn value_into_map() {
565565
}),
566566
]);
567567
}
568+
569+
#[test]
570+
fn js_map_into_rust_map() -> JsResult<()> {
571+
use boa_engine::Source;
572+
use std::collections::{BTreeMap, HashMap};
573+
574+
let js_code = "new Map([['a', 1], ['b', 3], ['aboba', 42024]])";
575+
let mut context = Context::default();
576+
577+
let js_value = context.eval(Source::from_bytes(js_code))?;
578+
579+
let hash_map = HashMap::<String, i32>::try_from_js(&js_value, &mut context)?;
580+
let btree_map = BTreeMap::<String, i32>::try_from_js(&js_value, &mut context)?;
581+
582+
let expect = [("a".into(), 1), ("aboba".into(), 42024), ("b".into(), 3)];
583+
584+
let expected_hash_map: HashMap<String, _> = expect.iter().cloned().collect();
585+
assert_eq!(expected_hash_map, hash_map);
586+
587+
let expected_btree_map: BTreeMap<String, _> = expect.iter().cloned().collect();
588+
assert_eq!(expected_btree_map, btree_map);
589+
Ok(())
590+
}

core/engine/src/value/conversions/try_from_js/collections.rs

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
use std::collections::{BTreeMap, HashMap};
44
use std::hash::Hash;
55

6+
use crate::object::JsMap;
67
use crate::value::TryFromJs;
78
use crate::{Context, JsNativeError, JsResult, JsValue};
89

@@ -18,6 +19,20 @@ where
1819
.into());
1920
};
2021

22+
// JsMap case
23+
if let Ok(js_map) = JsMap::from_object(object.clone()) {
24+
let mut map = Self::default();
25+
js_map.for_each_native(|key, value| {
26+
map.insert(
27+
K::try_from_js(&key, context)?,
28+
V::try_from_js(&value, context)?,
29+
);
30+
Ok(())
31+
})?;
32+
return Ok(map);
33+
}
34+
35+
// key-valued JsObject case:
2136
let keys = object.__own_property_keys__(context)?;
2237

2338
keys.into_iter()
@@ -47,6 +62,20 @@ where
4762
.into());
4863
};
4964

65+
// JsMap case
66+
if let Ok(js_map) = JsMap::from_object(object.clone()) {
67+
let mut map = Self::default();
68+
js_map.for_each_native(|key, value| {
69+
map.insert(
70+
K::try_from_js(&key, context)?,
71+
V::try_from_js(&value, context)?,
72+
);
73+
Ok(())
74+
})?;
75+
return Ok(map);
76+
}
77+
78+
// key-valued JsObject case:
5079
let keys = object.__own_property_keys__(context)?;
5180

5281
keys.into_iter()

0 commit comments

Comments
 (0)