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

Type extractors for common container types #1091

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

Conversation

dherman
Copy link
Collaborator

@dherman dherman commented Feb 1, 2025

Implement type extractors for common container types in the standard library, so that they don't required explicit Boxed wrapping with JsBox in userland.

Implemented so far:

  • RefCell
    • impl TryIntoJs for RefCell
    • impl TryFromJs for &RefCell
    • impl TryFromJs for Ref
    • impl TryFromJs for RefMut
  • Rc
    • impl TryIntoJs for Rc
    • impl TryFromJs for Rc
  • Arc
    • impl TryIntoJs for Arc
    • impl TryFromJs for Arc

- impl TryIntoJs for RefCell
- impl TryFromJs for &RefCell
Copy link

codecov bot commented Feb 1, 2025

Codecov Report

Attention: Patch coverage is 90.90909% with 6 lines in your changes missing coverage. Please review.

Project coverage is 83.42%. Comparing base (7f4feb1) to head (8ae43c0).

Files with missing lines Patch % Lines
crates/neon/src/types_impl/extract/container.rs 88.67% 6 Missing ⚠️
Additional details and impacted files
@@            Coverage Diff             @@
##             main    #1091      +/-   ##
==========================================
+ Coverage   83.05%   83.42%   +0.36%     
==========================================
  Files          73       74       +1     
  Lines        5472     5538      +66     
  Branches     5472     5538      +66     
==========================================
+ Hits         4545     4620      +75     
+ Misses        810      803       -7     
+ Partials      117      115       -2     

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

- impl TryIntoJs for Rc
- impl TryFromJs for Rc
- impl TryIntoJs for Arc
- impl TryFromJs for Arc
- impl TryIntoJs for Mutex
- impl TryFromJs for &Mutex
- impl TryFromJs for MutexGuard

also a couple more RefCell conveniences:
- impl TryFromJs for Ref
- impl TryFromJs for RefMut
}

impl<'cx, T: 'static> TryFromJs<'cx> for &'cx RefCell<T> {
type Error = RustTypeExpected<RefCell<T>>;
Copy link
Member

Choose a reason for hiding this comment

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

What do you think of using TypeExpected<JsBox<T>> to reduce the API surface area a bit? Since these types are still JsBox under the hood, I think it's okay to expose the error as that.

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

That may be a good idea also so people can understand what the actual runtime representation is. (I think it would be TypeExpected<JsBox<RefCell<T>>>, right?)

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

@kjvalencik A related design call I'd love your input on: right now the dynamic borrow errors are explicitly handled with try_borrow and try_borrow_mut and turned into a Neon-specific error type called RefCellError. An alternative is we could just let them panic and keep the API surface to just TypeExpected. Do you have a preference?

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

I also dislike the fact that RefCellError::WrongType overlaps with TypeExpected.

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

Yeah I really don't see much tangible benefit to the extra types. The panic message is basically the same. I'll remove it for now and if you disagree we can talk about putting the type back.

Copy link
Member

@kjvalencik kjvalencik Feb 2, 2025

Choose a reason for hiding this comment

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

Yes, TypeExpected<JsBox<RefCell<T>>> would be the type I would expect.

For Ref and RefMut, I don't like letting it panic. We'll catch the unwind and convert it to an exception, but it might not be a clear error message (also, the overhead of the unwind/catch).

I think it's okay to let throw an exception. If this behavior is unwanted, the author can go to &'cx RefCell as an intermediary and then borrow. If they are going directly to Ref or RefMut, they are already opting into some shorthand syntax.

However, I think it's better for the try_from_js to call the try_ methods on RefCell and on error, manually throw with cx.throw_error(..) and a clear error message.

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

Oh I think I see what you're suggesting: instead of creating a new Error type, just explicitly throw an error with a custom message directly in the TryFromJs implementation. That's reasonable. I'm not sure it makes a huge difference but I'm OK with it.

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

OK I pushed this middle ground -- it uses try_borrow() and try_borrow_mut() but explicitly calls cx.throw_error() instead of creating a custom type, so it can use TypeExpected for the other case. This feels like a good choice to me but see what you think.

- just use TypeExpected for type errors
- just use panic for dynamic borrow errors
- eliminate RustTypeExpected and RefCellError types completely
- eliminate Container trait
…comments, but just throw instead of a custom error type
@dherman dherman marked this pull request as ready for review February 2, 2025 23:07
@dherman dherman requested a review from kjvalencik February 2, 2025 23:07
let v = Box::new(value) as BoxAny;
// Since this value was just constructed, we know it is `T`
let raw_data = &*v as *const dyn Any as *const T;
let local = unsafe { external::create(cx.env().to_raw(), v, finalizer) };
Copy link
Member

Choose a reason for hiding this comment

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

Could we refactor most of this into a shared function called by each of the constructors? It would be nice to avoid duplicating the unsafe.

impl<T: 'static> JsBox<T> {
pub(crate) fn manually_finalize<'a, C>(cx: &mut C, value: T) -> Handle<'a, JsBox<T>>
where
C: Context<'a>,
Copy link
Member

Choose a reason for hiding this comment

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

FYI, since Cx was added, we don't need to be generic over Context, unless we are trying for consistency with an existing public API.

v: Handle<'cx, JsValue>,
) -> NeonResult<Result<Self, Self::Error>> {
match v.downcast::<JsBox<Rc<T>>, _>(cx) {
Ok(v) => Ok(Ok(JsBox::deref(&v).clone())),
Copy link
Member

Choose a reason for hiding this comment

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

Nit, this could use deref coercion. I might write it as:

Rc::clone(&v)


impl<'a, T> Sealed for Ref<'a, T> {}

impl<'a, T> Sealed for RefMut<'a, T> {}
Copy link
Member

Choose a reason for hiding this comment

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

Clippy: These two impls have unnecessary lifetimes.

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