Skip to content
New issue

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

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

Already on GitHub? # to your account

Type extractors for common container types #1091

Merged
merged 16 commits into from
Feb 3, 2025
Merged
17 changes: 17 additions & 0 deletions crates/neon/src/types_impl/boxed.rs
Original file line number Diff line number Diff line change
Expand Up @@ -259,6 +259,23 @@ impl<T: Finalize + 'static> JsBox<T> {
}
}

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>,
T: 'static,
{
fn finalizer(_env: raw::Env, _data: BoxAny) {}

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) };

Handle::new_internal(Self(JsBoxInner { local, raw_data }))
}
}

impl<T: 'static> JsBox<T> {
/// Gets a reference to the inner value of a [`JsBox`]. This method is similar to
/// [dereferencing](JsBox::deref) a `JsBox` (e.g., `&*boxed`), but the lifetime
Expand Down
150 changes: 150 additions & 0 deletions crates/neon/src/types_impl/extract/container.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,150 @@
use std::{
cell::{Ref, RefCell, RefMut},
rc::Rc,
sync::Arc,
};

use crate::{
context::Cx,
handle::Handle,
result::{JsResult, NeonResult},
types::{
extract::{TryFromJs, TryIntoJs},
JsBox, JsValue,
},
};

use super::error::{RefCellError, RustTypeExpected};

pub trait Container {
fn container_name() -> &'static str;
}

impl<T> Container for RefCell<T> {
fn container_name() -> &'static str {
"std::cell::RefCell"
}
}

impl<T> Container for Rc<T> {
fn container_name() -> &'static str {
"std::rc::Rc"
}
}

impl<T> Container for Arc<T> {
fn container_name() -> &'static str {
"std::sync::Arc"
}
}

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

fn try_from_js(
cx: &mut Cx<'cx>,
v: Handle<'cx, JsValue>,
) -> NeonResult<Result<Self, Self::Error>> {
match v.downcast::<JsBox<RefCell<T>>, _>(cx) {
Ok(v) => Ok(Ok(JsBox::deref(&v))),
Err(_) => Ok(Err(RustTypeExpected::new())),
}
}
}

impl<'cx, T: 'static> TryFromJs<'cx> for Ref<'cx, T> {
type Error = RefCellError;

fn try_from_js(
cx: &mut Cx<'cx>,
v: Handle<'cx, JsValue>,
) -> NeonResult<Result<Self, Self::Error>> {
match v.downcast::<JsBox<RefCell<T>>, _>(cx) {
Ok(v) => {
let cell = JsBox::deref(&v);
Ok(cell.try_borrow().map_err(|_| RefCellError::Borrowed))
}
Err(_) => Ok(Err(RefCellError::WrongType)),
}
}
}

impl<'cx, T: 'static> TryFromJs<'cx> for RefMut<'cx, T> {
type Error = RefCellError;

fn try_from_js(
cx: &mut Cx<'cx>,
v: Handle<'cx, JsValue>,
) -> NeonResult<Result<Self, Self::Error>> {
match v.downcast::<JsBox<RefCell<T>>, _>(cx) {
Ok(v) => {
let cell = JsBox::deref(&v);
Ok(cell
.try_borrow_mut()
.map_err(|_| RefCellError::MutablyBorrowed))
}
Err(_) => Ok(Err(RefCellError::WrongType)),
}
}
}

impl<'cx, T> TryIntoJs<'cx> for RefCell<T>
where
T: 'static,
{
type Value = JsBox<RefCell<T>>;

fn try_into_js(self, cx: &mut Cx<'cx>) -> JsResult<'cx, Self::Value> {
Ok(JsBox::manually_finalize(cx, self))
}
}

impl<'cx, T: 'static> TryFromJs<'cx> for Rc<T> {
type Error = RustTypeExpected<Rc<T>>;

fn try_from_js(
cx: &mut Cx<'cx>,
v: Handle<'cx, JsValue>,
) -> NeonResult<Result<Self, Self::Error>> {
match v.downcast::<JsBox<Rc<T>>, _>(cx) {
Ok(v) => Ok(Ok(JsBox::deref(&v).clone())),
Err(_) => Ok(Err(RustTypeExpected::new())),
}
}
}

impl<'cx, T> TryIntoJs<'cx> for Rc<T>
where
T: 'static,
{
type Value = JsBox<Rc<T>>;

fn try_into_js(self, cx: &mut Cx<'cx>) -> JsResult<'cx, Self::Value> {
Ok(JsBox::manually_finalize(cx, self))
}
}

impl<'cx, T: 'static> TryFromJs<'cx> for Arc<T> {
type Error = RustTypeExpected<Arc<T>>;

fn try_from_js(
cx: &mut Cx<'cx>,
v: Handle<'cx, JsValue>,
) -> NeonResult<Result<Self, Self::Error>> {
match v.downcast::<JsBox<Arc<T>>, _>(cx) {
Ok(v) => Ok(Ok(JsBox::deref(&v).clone())),
Err(_) => Ok(Err(RustTypeExpected::new())),
}
}
}

impl<'cx, T> TryIntoJs<'cx> for Arc<T>
where
T: 'static,
{
type Value = JsBox<Arc<T>>;

fn try_into_js(self, cx: &mut Cx<'cx>) -> JsResult<'cx, Self::Value> {
Ok(JsBox::manually_finalize(cx, self))
}
}
81 changes: 80 additions & 1 deletion crates/neon/src/types_impl/extract/error.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
use std::{convert::Infallible, error, fmt, marker::PhantomData};
use std::{cell::RefCell, convert::Infallible, error, fmt, marker::PhantomData};

use crate::{
context::{Context, Cx},
Expand All @@ -9,6 +9,8 @@ use crate::{
},
};

use super::container::Container;

type BoxError = Box<dyn error::Error + Send + Sync + 'static>;

/// Error returned when a JavaScript value is not the type expected
Expand Down Expand Up @@ -44,6 +46,83 @@ impl<'cx, T: Value> TryIntoJs<'cx> for TypeExpected<T> {

impl<T: Value> private::Sealed for TypeExpected<T> {}

/// Error returned when an implicitly boxed Rust value is not the type expected
pub struct RustTypeExpected<T: Container>(PhantomData<T>);

impl<T: Container> RustTypeExpected<T> {
pub(super) fn new() -> Self {
Self(PhantomData)
}
}

impl<T: Container> fmt::Display for RustTypeExpected<T> {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
write!(f, "expected {}", T::container_name())
}
}

impl<T: Container> fmt::Debug for RustTypeExpected<T> {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
f.debug_tuple("RustTypeExpected")
.field(&T::container_name())
.finish()
}
}

impl<T: Container> error::Error for RustTypeExpected<T> {}

impl<'cx, T: Container + 'static> TryIntoJs<'cx> for RustTypeExpected<T> {
type Value = JsError;

fn try_into_js(self, cx: &mut Cx<'cx>) -> JsResult<'cx, Self::Value> {
JsError::type_error(cx, self.to_string())
}
}

impl<T: Container> private::Sealed for RustTypeExpected<T> {}

pub enum RefCellError {
WrongType,
MutablyBorrowed,
Borrowed,
}

impl fmt::Display for RefCellError {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
match self {
RefCellError::WrongType => write!(f, "expected {}", RefCell::<()>::container_name()),
RefCellError::MutablyBorrowed => write!(f, "std::cell::RefCell is mutably borrowed"),
RefCellError::Borrowed => write!(f, "std::cell::RefCell is borrowed"),
}
}
}

impl fmt::Debug for RefCellError {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
match self {
RefCellError::WrongType => f.debug_tuple("RefCellError::WrongType").finish(),
RefCellError::MutablyBorrowed => {
f.debug_tuple("RefCellError::MutablyBorrowed").finish()
}
RefCellError::Borrowed => f.debug_tuple("RefCellError::Borrowed").finish(),
}
}
}

impl<'cx> TryIntoJs<'cx> for RefCellError {
type Value = JsError;

fn try_into_js(self, cx: &mut Cx<'cx>) -> JsResult<'cx, Self::Value> {
match self {
RefCellError::WrongType => JsError::type_error(cx, self.to_string()),
RefCellError::MutablyBorrowed => JsError::error(cx, self.to_string()),
RefCellError::Borrowed => JsError::error(cx, self.to_string()),
}
}
}

impl private::Sealed for RefCellError {}

impl<'cx> TryIntoJs<'cx> for Infallible {
type Value = JsValue;

Expand Down
1 change: 1 addition & 0 deletions crates/neon/src/types_impl/extract/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -126,6 +126,7 @@ pub mod json;

mod boxed;
mod buffer;
mod container;
mod either;
mod error;
mod private;
Expand Down
18 changes: 18 additions & 0 deletions crates/neon/src/types_impl/extract/private.rs
Original file line number Diff line number Diff line change
@@ -1,3 +1,9 @@
use std::{
cell::{Ref, RefCell, RefMut},
rc::Rc,
sync::Arc,
};

use crate::{
context::FunctionContext,
handle::{Handle, Root},
Expand Down Expand Up @@ -45,4 +51,16 @@ impl<T, E> Sealed for Result<T, E> {}

impl<'cx, T> Sealed for Box<T> where T: TryIntoJs<'cx> {}

impl<T> Sealed for RefCell<T> {}

impl<T> Sealed for &RefCell<T> {}

impl<T> Sealed for Arc<T> {}

impl<T> Sealed for Rc<T> {}

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

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

impl_sealed!(u8, u16, u32, i8, i16, i32, f32, f64, bool, String, Date, Throw, Error,);
2 changes: 1 addition & 1 deletion package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

27 changes: 27 additions & 0 deletions test/napi/lib/container.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
const addon = require("..");
const { expect } = require("chai");
const assert = require("chai").assert;

describe("container", function () {
it("can produce and consume a RefCell", function () {
const cell = addon.createStringRefCell("my sekret mesij");
const s = addon.readStringRefCell(cell);
assert.strictEqual(s, "my sekret mesij");
});

it("concatenates a RefCell<String> with a String", function () {
const cell = addon.createStringRefCell("hello");
const s = addon.stringRefCellConcat(cell, " world");
assert.strictEqual(s, "hello world");
});

it("fails with a type error when not given a RefCell", function () {
try {
addon.stringRefCellConcat("hello", " world");
assert.fail("should have thrown");
} catch (err) {
assert.instanceOf(err, TypeError);
assert.strictEqual(err.message, "expected std::cell::RefCell");
}
});
});
16 changes: 16 additions & 0 deletions test/napi/src/js/container.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
use std::cell::RefCell;

#[neon::export]
fn create_string_ref_cell(s: String) -> RefCell<String> {
RefCell::new(s)
}

#[neon::export]
fn read_string_ref_cell(s: &RefCell<String>) -> String {
s.borrow().clone()
}

#[neon::export]
fn string_ref_cell_concat(lhs: &RefCell<String>, rhs: String) -> String {
lhs.borrow().clone() + &rhs
}
1 change: 1 addition & 0 deletions test/napi/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ mod js {
pub mod bigint;
pub mod boxed;
pub mod coercions;
pub mod container;
pub mod date;
pub mod errors;
pub mod export;
Expand Down