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

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
126 changes: 126 additions & 0 deletions crates/neon/src/types_impl/extract/container.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,126 @@
use std::{
cell::{Ref, RefCell, RefMut},
rc::Rc,
sync::Arc,
};

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

use super::error::TypeExpected;

impl<'cx, T: 'static> TryFromJs<'cx> for &'cx RefCell<T> {
type Error = TypeExpected<JsBox<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(TypeExpected::new())),
}
}
}

impl<'cx, T: 'static> TryFromJs<'cx> for Ref<'cx, T> {
type Error = TypeExpected<JsBox<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) => match JsBox::deref(&v).try_borrow() {
Ok(r) => Ok(Ok(r)),
Err(_) => cx.throw_error("RefCell is already mutably borrowed"),
},
Err(_) => Ok(Err(TypeExpected::new())),
}
}
}

impl<'cx, T: 'static> TryFromJs<'cx> for RefMut<'cx, T> {
type Error = TypeExpected<JsBox<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) => match JsBox::deref(&v).try_borrow_mut() {
Ok(r) => Ok(Ok(r)),
Err(_) => cx.throw_error("RefCell is already borrowed"),
},
Err(_) => Ok(Err(TypeExpected::new())),
}
}
}

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 = TypeExpected<JsBox<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(TypeExpected::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 = TypeExpected<JsBox<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(TypeExpected::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))
}
}
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.

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

describe("Container type extractors", 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("can produce and modify a RefCell", function () {
const cell = addon.createStringRefCell("new");
addon.writeStringRefCell(cell, "modified");
assert.strictEqual(addon.readStringRefCell(cell), "modified");
});

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

it("fail 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 neon::types_impl::boxed::JsBox<core::cell::RefCell<alloc::string::String>>"
);
}
});

it("dynamically fail when borrowing a mutably borrowed RefCell", function () {
const cell = addon.createStringRefCell("hello");
try {
addon.borrowMutAndThen(cell, () => {
addon.stringRefConcat(cell, " world");
});
assert.fail("should have thrown");
} catch (err) {
assert.instanceOf(err, Error);
assert.include(err.message, "already mutably borrowed");
}
});

it("dynamically fail when modifying a borrowed RefCell", function () {
const cell = addon.createStringRefCell("hello");
try {
addon.borrowAndThen(cell, () => {
addon.writeStringRef(cell, "world");
});
assert.fail("should have thrown");
} catch (err) {
assert.instanceOf(err, Error);
assert.include(err.message, "already borrowed");
}
});

it("can produce and consume an Rc", function () {
const cell = addon.createStringRc("my sekret mesij");
const s = addon.readStringRc(cell);
assert.strictEqual(s, "my sekret mesij");
});

it("can produce and consume an Arc", function () {
const cell = addon.createStringArc("my sekret mesij");
const s = addon.readStringArc(cell);
assert.strictEqual(s, "my sekret mesij");
});
});
80 changes: 80 additions & 0 deletions test/napi/src/js/container.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,80 @@
use neon::prelude::*;

use std::{
cell::{Ref, RefCell, RefMut},
rc::Rc,
sync::Arc,
};

#[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 write_string_ref_cell(s: &RefCell<String>, value: String) {
*s.borrow_mut() = value;
}

#[neon::export]
fn string_ref_cell_concat(lhs: &RefCell<String>, rhs: String) -> String {
lhs.borrow().clone() + &rhs
}

#[neon::export]
fn string_ref_concat(lhs: Ref<String>, rhs: String) -> String {
lhs.clone() + &rhs
}

#[neon::export]
fn write_string_ref(mut s: RefMut<String>, value: String) {
*s = value;
}

#[neon::export]
fn borrow_and_then<'cx>(
cx: &mut Cx<'cx>,
cell: &RefCell<String>,
f: Handle<JsFunction>,
) -> JsResult<'cx, JsString> {
let s = cell.borrow();
f.bind(cx).exec()?;
Ok(cx.string(s.clone()))
}

#[neon::export]
fn borrow_mut_and_then<'cx>(
cx: &mut Cx<'cx>,
cell: &RefCell<String>,
f: Handle<JsFunction>,
) -> JsResult<'cx, JsString> {
let mut s = cell.borrow_mut();
f.bind(cx).exec()?;
*s = "overwritten".to_string();
Ok(cx.string(s.clone()))
}

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

#[neon::export]
fn read_string_rc(s: Rc<String>) -> String {
(*s).clone()
}

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

#[neon::export]
fn read_string_arc(s: Arc<String>) -> String {
(*s).clone()
}
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