Skip to content

Commit b1728fa

Browse files
authored
Type extractors for common container types (#1091)
Implement type extractors for common container types in the standard library, so that they don't required explicit Boxed wrapping with JsBox in userland. Container type implementations included in this commit: - 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
1 parent 7f4feb1 commit b1728fa

File tree

8 files changed

+322
-9
lines changed

8 files changed

+322
-9
lines changed

crates/neon/src/types_impl/boxed.rs

+21-8
Original file line numberDiff line numberDiff line change
@@ -234,14 +234,10 @@ impl<T: 'static> ValueInternal for JsBox<T> {
234234
/// until the application terminates, only that its lifetime is indefinite.
235235
impl<T: Finalize + 'static> JsBox<T> {
236236
/// Constructs a new `JsBox` containing `value`.
237-
pub fn new<'a, C>(cx: &mut C, value: T) -> Handle<'a, JsBox<T>>
238-
where
239-
C: Context<'a>,
240-
T: 'static,
241-
{
237+
pub fn new<'cx, C: Context<'cx>>(cx: &mut C, value: T) -> Handle<'cx, JsBox<T>> {
242238
// This function will execute immediately before the `JsBox` is garbage collected.
243239
// It unwraps the `napi_external`, downcasts the `BoxAny` and moves the type
244-
// out of the `Box`. Lastly, it calls the trait method `Finalize::fianlize` of the
240+
// out of the `Box`. Lastly, it calls the trait method `Finalize::finalize` of the
245241
// contained value `T`.
246242
fn finalizer<U: Finalize + 'static>(env: raw::Env, data: BoxAny) {
247243
let data = *data.downcast::<U>().unwrap();
@@ -250,12 +246,29 @@ impl<T: Finalize + 'static> JsBox<T> {
250246
Cx::with_context(env, move |mut cx| data.finalize(&mut cx));
251247
}
252248

249+
Self::create_external(cx, value, finalizer::<T>)
250+
}
251+
}
252+
253+
impl<T: 'static> JsBox<T> {
254+
pub(crate) fn manually_finalize<'cx>(cx: &mut Cx<'cx>, value: T) -> Handle<'cx, JsBox<T>> {
255+
fn finalizer(_env: raw::Env, _data: BoxAny) {}
256+
257+
Self::create_external(cx, value, finalizer)
258+
}
259+
260+
fn create_external<'cx, C: Context<'cx>>(
261+
cx: &mut C,
262+
value: T,
263+
finalizer: fn(raw::Env, BoxAny),
264+
) -> Handle<'cx, JsBox<T>> {
253265
let v = Box::new(value) as BoxAny;
266+
254267
// Since this value was just constructed, we know it is `T`
255268
let raw_data = &*v as *const dyn Any as *const T;
256-
let local = unsafe { external::create(cx.env().to_raw(), v, finalizer::<T>) };
269+
let local = unsafe { external::create(cx.env().to_raw(), v, finalizer) };
257270

258-
Handle::new_internal(Self(JsBoxInner { local, raw_data }))
271+
Handle::new_internal(JsBox(JsBoxInner { local, raw_data }))
259272
}
260273
}
261274

Original file line numberDiff line numberDiff line change
@@ -0,0 +1,126 @@
1+
use std::{
2+
cell::{Ref, RefCell, RefMut},
3+
rc::Rc,
4+
sync::Arc,
5+
};
6+
7+
use crate::{
8+
context::{Context, Cx},
9+
handle::Handle,
10+
result::{JsResult, NeonResult},
11+
types::{
12+
extract::{TryFromJs, TryIntoJs},
13+
JsBox, JsValue,
14+
},
15+
};
16+
17+
use super::error::TypeExpected;
18+
19+
impl<'cx, T: 'static> TryFromJs<'cx> for &'cx RefCell<T> {
20+
type Error = TypeExpected<JsBox<RefCell<T>>>;
21+
22+
fn try_from_js(
23+
cx: &mut Cx<'cx>,
24+
v: Handle<'cx, JsValue>,
25+
) -> NeonResult<Result<Self, Self::Error>> {
26+
match v.downcast::<JsBox<RefCell<T>>, _>(cx) {
27+
Ok(v) => Ok(Ok(JsBox::deref(&v))),
28+
Err(_) => Ok(Err(TypeExpected::new())),
29+
}
30+
}
31+
}
32+
33+
impl<'cx, T: 'static> TryFromJs<'cx> for Ref<'cx, T> {
34+
type Error = TypeExpected<JsBox<RefCell<T>>>;
35+
36+
fn try_from_js(
37+
cx: &mut Cx<'cx>,
38+
v: Handle<'cx, JsValue>,
39+
) -> NeonResult<Result<Self, Self::Error>> {
40+
match v.downcast::<JsBox<RefCell<T>>, _>(cx) {
41+
Ok(v) => match JsBox::deref(&v).try_borrow() {
42+
Ok(r) => Ok(Ok(r)),
43+
Err(_) => cx.throw_error("RefCell is already mutably borrowed"),
44+
},
45+
Err(_) => Ok(Err(TypeExpected::new())),
46+
}
47+
}
48+
}
49+
50+
impl<'cx, T: 'static> TryFromJs<'cx> for RefMut<'cx, T> {
51+
type Error = TypeExpected<JsBox<RefCell<T>>>;
52+
53+
fn try_from_js(
54+
cx: &mut Cx<'cx>,
55+
v: Handle<'cx, JsValue>,
56+
) -> NeonResult<Result<Self, Self::Error>> {
57+
match v.downcast::<JsBox<RefCell<T>>, _>(cx) {
58+
Ok(v) => match JsBox::deref(&v).try_borrow_mut() {
59+
Ok(r) => Ok(Ok(r)),
60+
Err(_) => cx.throw_error("RefCell is already borrowed"),
61+
},
62+
Err(_) => Ok(Err(TypeExpected::new())),
63+
}
64+
}
65+
}
66+
67+
impl<'cx, T> TryIntoJs<'cx> for RefCell<T>
68+
where
69+
T: 'static,
70+
{
71+
type Value = JsBox<RefCell<T>>;
72+
73+
fn try_into_js(self, cx: &mut Cx<'cx>) -> JsResult<'cx, Self::Value> {
74+
Ok(JsBox::manually_finalize(cx, self))
75+
}
76+
}
77+
78+
impl<'cx, T: 'static> TryFromJs<'cx> for Rc<T> {
79+
type Error = TypeExpected<JsBox<Rc<T>>>;
80+
81+
fn try_from_js(
82+
cx: &mut Cx<'cx>,
83+
v: Handle<'cx, JsValue>,
84+
) -> NeonResult<Result<Self, Self::Error>> {
85+
match v.downcast::<JsBox<Rc<T>>, _>(cx) {
86+
Ok(v) => Ok(Ok(Rc::clone(&v))),
87+
Err(_) => Ok(Err(TypeExpected::new())),
88+
}
89+
}
90+
}
91+
92+
impl<'cx, T> TryIntoJs<'cx> for Rc<T>
93+
where
94+
T: 'static,
95+
{
96+
type Value = JsBox<Rc<T>>;
97+
98+
fn try_into_js(self, cx: &mut Cx<'cx>) -> JsResult<'cx, Self::Value> {
99+
Ok(JsBox::manually_finalize(cx, self))
100+
}
101+
}
102+
103+
impl<'cx, T: 'static> TryFromJs<'cx> for Arc<T> {
104+
type Error = TypeExpected<JsBox<Arc<T>>>;
105+
106+
fn try_from_js(
107+
cx: &mut Cx<'cx>,
108+
v: Handle<'cx, JsValue>,
109+
) -> NeonResult<Result<Self, Self::Error>> {
110+
match v.downcast::<JsBox<Arc<T>>, _>(cx) {
111+
Ok(v) => Ok(Ok(Arc::clone(&v))),
112+
Err(_) => Ok(Err(TypeExpected::new())),
113+
}
114+
}
115+
}
116+
117+
impl<'cx, T> TryIntoJs<'cx> for Arc<T>
118+
where
119+
T: 'static,
120+
{
121+
type Value = JsBox<Arc<T>>;
122+
123+
fn try_into_js(self, cx: &mut Cx<'cx>) -> JsResult<'cx, Self::Value> {
124+
Ok(JsBox::manually_finalize(cx, self))
125+
}
126+
}

crates/neon/src/types_impl/extract/mod.rs

+1
Original file line numberDiff line numberDiff line change
@@ -126,6 +126,7 @@ pub mod json;
126126

127127
mod boxed;
128128
mod buffer;
129+
mod container;
129130
mod either;
130131
mod error;
131132
mod private;

crates/neon/src/types_impl/extract/private.rs

+18
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,9 @@
1+
use std::{
2+
cell::{Ref, RefCell, RefMut},
3+
rc::Rc,
4+
sync::Arc,
5+
};
6+
17
use crate::{
28
context::FunctionContext,
39
handle::{Handle, Root},
@@ -45,4 +51,16 @@ impl<T, E> Sealed for Result<T, E> {}
4551

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

54+
impl<T> Sealed for RefCell<T> {}
55+
56+
impl<T> Sealed for &RefCell<T> {}
57+
58+
impl<T> Sealed for Arc<T> {}
59+
60+
impl<T> Sealed for Rc<T> {}
61+
62+
impl<T> Sealed for Ref<'_, T> {}
63+
64+
impl<T> Sealed for RefMut<'_, T> {}
65+
4866
impl_sealed!(u8, u16, u32, i8, i16, i32, f32, f64, bool, String, Date, Throw, Error,);

package-lock.json

+1-1
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

test/napi/lib/container.js

+74
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,74 @@
1+
const addon = require("..");
2+
const { expect } = require("chai");
3+
const assert = require("chai").assert;
4+
5+
describe("Container type extractors", function () {
6+
it("can produce and consume a RefCell", function () {
7+
const cell = addon.createStringRefCell("my sekret mesij");
8+
const s = addon.readStringRefCell(cell);
9+
assert.strictEqual(s, "my sekret mesij");
10+
});
11+
12+
it("can produce and modify a RefCell", function () {
13+
const cell = addon.createStringRefCell("new");
14+
addon.writeStringRefCell(cell, "modified");
15+
assert.strictEqual(addon.readStringRefCell(cell), "modified");
16+
});
17+
18+
it("can concatenate a RefCell<String> with a String", function () {
19+
const cell = addon.createStringRefCell("hello");
20+
const s = addon.stringRefCellConcat(cell, " world");
21+
assert.strictEqual(s, "hello world");
22+
});
23+
24+
it("fail with a type error when not given a RefCell", function () {
25+
try {
26+
addon.stringRefCellConcat("hello", " world");
27+
assert.fail("should have thrown");
28+
} catch (err) {
29+
assert.instanceOf(err, TypeError);
30+
assert.strictEqual(
31+
err.message,
32+
"expected neon::types_impl::boxed::JsBox<core::cell::RefCell<alloc::string::String>>"
33+
);
34+
}
35+
});
36+
37+
it("dynamically fail when borrowing a mutably borrowed RefCell", function () {
38+
const cell = addon.createStringRefCell("hello");
39+
try {
40+
addon.borrowMutAndThen(cell, () => {
41+
addon.stringRefConcat(cell, " world");
42+
});
43+
assert.fail("should have thrown");
44+
} catch (err) {
45+
assert.instanceOf(err, Error);
46+
assert.include(err.message, "already mutably borrowed");
47+
}
48+
});
49+
50+
it("dynamically fail when modifying a borrowed RefCell", function () {
51+
const cell = addon.createStringRefCell("hello");
52+
try {
53+
addon.borrowAndThen(cell, () => {
54+
addon.writeStringRef(cell, "world");
55+
});
56+
assert.fail("should have thrown");
57+
} catch (err) {
58+
assert.instanceOf(err, Error);
59+
assert.include(err.message, "already borrowed");
60+
}
61+
});
62+
63+
it("can produce and consume an Rc", function () {
64+
const cell = addon.createStringRc("my sekret mesij");
65+
const s = addon.readStringRc(cell);
66+
assert.strictEqual(s, "my sekret mesij");
67+
});
68+
69+
it("can produce and consume an Arc", function () {
70+
const cell = addon.createStringArc("my sekret mesij");
71+
const s = addon.readStringArc(cell);
72+
assert.strictEqual(s, "my sekret mesij");
73+
});
74+
});

test/napi/src/js/container.rs

+80
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,80 @@
1+
use neon::prelude::*;
2+
3+
use std::{
4+
cell::{Ref, RefCell, RefMut},
5+
rc::Rc,
6+
sync::Arc,
7+
};
8+
9+
#[neon::export]
10+
fn create_string_ref_cell(s: String) -> RefCell<String> {
11+
RefCell::new(s)
12+
}
13+
14+
#[neon::export]
15+
fn read_string_ref_cell(s: &RefCell<String>) -> String {
16+
s.borrow().clone()
17+
}
18+
19+
#[neon::export]
20+
fn write_string_ref_cell(s: &RefCell<String>, value: String) {
21+
*s.borrow_mut() = value;
22+
}
23+
24+
#[neon::export]
25+
fn string_ref_cell_concat(lhs: &RefCell<String>, rhs: String) -> String {
26+
lhs.borrow().clone() + &rhs
27+
}
28+
29+
#[neon::export]
30+
fn string_ref_concat(lhs: Ref<String>, rhs: String) -> String {
31+
lhs.clone() + &rhs
32+
}
33+
34+
#[neon::export]
35+
fn write_string_ref(mut s: RefMut<String>, value: String) {
36+
*s = value;
37+
}
38+
39+
#[neon::export]
40+
fn borrow_and_then<'cx>(
41+
cx: &mut Cx<'cx>,
42+
cell: &RefCell<String>,
43+
f: Handle<JsFunction>,
44+
) -> JsResult<'cx, JsString> {
45+
let s = cell.borrow();
46+
f.bind(cx).exec()?;
47+
Ok(cx.string(s.clone()))
48+
}
49+
50+
#[neon::export]
51+
fn borrow_mut_and_then<'cx>(
52+
cx: &mut Cx<'cx>,
53+
cell: &RefCell<String>,
54+
f: Handle<JsFunction>,
55+
) -> JsResult<'cx, JsString> {
56+
let mut s = cell.borrow_mut();
57+
f.bind(cx).exec()?;
58+
*s = "overwritten".to_string();
59+
Ok(cx.string(s.clone()))
60+
}
61+
62+
#[neon::export]
63+
fn create_string_rc(s: String) -> Rc<String> {
64+
Rc::new(s)
65+
}
66+
67+
#[neon::export]
68+
fn read_string_rc(s: Rc<String>) -> String {
69+
(*s).clone()
70+
}
71+
72+
#[neon::export]
73+
fn create_string_arc(s: String) -> Arc<String> {
74+
Arc::new(s)
75+
}
76+
77+
#[neon::export]
78+
fn read_string_arc(s: Arc<String>) -> String {
79+
(*s).clone()
80+
}

test/napi/src/lib.rs

+1
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ mod js {
1212
pub mod bigint;
1313
pub mod boxed;
1414
pub mod coercions;
15+
pub mod container;
1516
pub mod date;
1617
pub mod errors;
1718
pub mod export;

0 commit comments

Comments
 (0)