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

TryFromJs from JsMap for HashMap & BtreeMap #3998

Open
wants to merge 5 commits into
base: main
Choose a base branch
from

Conversation

Nikita-str
Copy link
Contributor

Seems like TryFromJs didn't convert correctly from JsMap into HashMap / BtreeMap

This PR fix it

Example
Next code

use boa_engine::{value::TryFromJs, Context, JsResult, Source};
use std::collections::HashMap;

fn main() -> JsResult<()> {
    let js_code = "new Map([['a', 2], ['b', 3]])";
    let mut context = Context::default();

    let js_value = context.eval(Source::from_bytes(js_code))?;
    println!("{}", js_value.display());

    let map = HashMap::<String, i32>::try_from_js(&js_value, &mut context)?;
    println!("{map:?}");
    Ok(())
}

before this changes will return:

Map { "a" → 2, "b" → 3 }
{}

and after changes returns:

Map { "a" → 2, "b" → 3 }
{"a": 2, "b": 3}

Notes
I don't find more fast way(than the one used in for_each_elem_in_js_map) to get (key, value)'s from JsMap.
Maybe there is a way?
Or maybe I should write it (for example by function like next but that just return Option<(JsValue, JsValue)>)?

Copy link
Member

@jedel1043 jedel1043 left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thank you! I have a small suggestion to simplify things.

Comment on lines 113 to 124
let next = iter.next(context)?;
let Some(iter_obj) = next.as_object() else {
return unexp_obj_err();
};

let done = iter_obj.get(js_str!("done"), context)?;
let Some(done) = done.as_boolean() else {
return unexp_obj_err();
};
if done {
break;
}
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Here I think you can use IteratorResult to do all the checks for you. We'll probably want to make next return that instead of the raw JsValue, but constructing it manually should be fine for now.

Comment on lines 99 to 109
fn for_each_elem_in_js_map<F>(js_map: &JsMap, mut f: F, context: &mut Context) -> JsResult<()>
where
F: FnMut(JsValue, JsValue, &mut Context) -> JsResult<()>,
{
let unexp_obj_err = || {
JsResult::Err(
JsNativeError::typ()
.with_message("MapIterator return unexpected object")
.into(),
)
};
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

On second thought, this looks like something that should replace JsMap::for_each, right? I think it would be a lot more ergonomic instead of our current API.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

~Yes, but I actually not sure how to do it via JsMp::for_each because you should to create an JsFunction and how I can see it can be done via NativeFunctionPointer but you can't capture in such function outer map Rust object (HashMap/BTreeMap). And if you use closure that will capture the map, then it seems like you can't create JsFunction.

Actually we don't want to create extra js objects here (like JsFunction), and therefor maybe it makes sense to create an function in JsMap that will do for_each but for Rust fn (for F: FnMut), and maybe make it public only in the crate.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, I was more talking about substituting JsMap::for_each with this new function, since it seems that passing JsFunction directly is a lot more painful overall.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done

Is riust_for_each acceptable name?

Just for sureness, is this equivalent to the next:

let map = map
    .downcast_ref::<OrderedMap<JsValue>>()
    .expect("checked that `this` was a map");

map.iter().try_for_each(|(k, v)|{
    let args = [v.clone(), k.clone(), this.clone()];
    callback.call(this_arg, &args, context).map(|_|())
})?;
Ok(JsValue::undefined())

If they are not the same, then seems like I incorrectly impl the fn rust_for each

If they the same, maybe simplify it?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Not really sure, but I think we have pretty good coverage. You can try replacing it to see if there are any regressions on the test262 suite.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No, they are not the same because JsMap can be changed in for_each and it will panic because of mut and non-mut borrowing at the same time

Copy link

codecov bot commented Sep 23, 2024

Codecov Report

Attention: Patch coverage is 75.00000% with 9 lines in your changes missing coverage. Please review.

Project coverage is 52.83%. Comparing base (6ddc2b4) to head (b74a521).
Report is 264 commits behind head on main.

Files with missing lines Patch % Lines
core/engine/src/builtins/map/mod.rs 58.82% 7 Missing ⚠️
...e/src/value/conversions/try_from_js/collections.rs 87.50% 2 Missing ⚠️
Additional details and impacted files
@@            Coverage Diff             @@
##             main    #3998      +/-   ##
==========================================
+ Coverage   47.24%   52.83%   +5.58%     
==========================================
  Files         476      480       +4     
  Lines       46892    46709     -183     
==========================================
+ Hits        22154    24677    +2523     
+ Misses      24738    22032    -2706     

☔ View full report in Codecov by Sentry.
📢 Have feedback on the report? Share it here.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

2 participants