diff --git a/crates/neon/src/types_impl/boxed.rs b/crates/neon/src/types_impl/boxed.rs index eaac6de51..5283bf456 100644 --- a/crates/neon/src/types_impl/boxed.rs +++ b/crates/neon/src/types_impl/boxed.rs @@ -234,14 +234,10 @@ impl ValueInternal for JsBox { /// until the application terminates, only that its lifetime is indefinite. impl JsBox { /// Constructs a new `JsBox` containing `value`. - pub fn new<'a, C>(cx: &mut C, value: T) -> Handle<'a, JsBox> - where - C: Context<'a>, - T: 'static, - { + pub fn new<'cx, C: Context<'cx>>(cx: &mut C, value: T) -> Handle<'cx, JsBox> { // This function will execute immediately before the `JsBox` is garbage collected. // It unwraps the `napi_external`, downcasts the `BoxAny` and moves the type - // out of the `Box`. Lastly, it calls the trait method `Finalize::fianlize` of the + // out of the `Box`. Lastly, it calls the trait method `Finalize::finalize` of the // contained value `T`. fn finalizer(env: raw::Env, data: BoxAny) { let data = *data.downcast::().unwrap(); @@ -250,12 +246,29 @@ impl JsBox { Cx::with_context(env, move |mut cx| data.finalize(&mut cx)); } + Self::create_external(cx, value, finalizer::) + } +} + +impl JsBox { + pub(crate) fn manually_finalize<'cx>(cx: &mut Cx<'cx>, value: T) -> Handle<'cx, JsBox> { + fn finalizer(_env: raw::Env, _data: BoxAny) {} + + Self::create_external(cx, value, finalizer) + } + + fn create_external<'cx, C: Context<'cx>>( + cx: &mut C, + value: T, + finalizer: fn(raw::Env, BoxAny), + ) -> Handle<'cx, JsBox> { 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::) }; + let local = unsafe { external::create(cx.env().to_raw(), v, finalizer) }; - Handle::new_internal(Self(JsBoxInner { local, raw_data })) + Handle::new_internal(JsBox(JsBoxInner { local, raw_data })) } } diff --git a/crates/neon/src/types_impl/extract/container.rs b/crates/neon/src/types_impl/extract/container.rs new file mode 100644 index 000000000..e31678e7d --- /dev/null +++ b/crates/neon/src/types_impl/extract/container.rs @@ -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 { + type Error = TypeExpected>>; + + fn try_from_js( + cx: &mut Cx<'cx>, + v: Handle<'cx, JsValue>, + ) -> NeonResult> { + match v.downcast::>, _>(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>>; + + fn try_from_js( + cx: &mut Cx<'cx>, + v: Handle<'cx, JsValue>, + ) -> NeonResult> { + match v.downcast::>, _>(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>>; + + fn try_from_js( + cx: &mut Cx<'cx>, + v: Handle<'cx, JsValue>, + ) -> NeonResult> { + match v.downcast::>, _>(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 +where + T: 'static, +{ + type Value = JsBox>; + + 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 { + type Error = TypeExpected>>; + + fn try_from_js( + cx: &mut Cx<'cx>, + v: Handle<'cx, JsValue>, + ) -> NeonResult> { + match v.downcast::>, _>(cx) { + Ok(v) => Ok(Ok(Rc::clone(&v))), + Err(_) => Ok(Err(TypeExpected::new())), + } + } +} + +impl<'cx, T> TryIntoJs<'cx> for Rc +where + T: 'static, +{ + type Value = JsBox>; + + 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 { + type Error = TypeExpected>>; + + fn try_from_js( + cx: &mut Cx<'cx>, + v: Handle<'cx, JsValue>, + ) -> NeonResult> { + match v.downcast::>, _>(cx) { + Ok(v) => Ok(Ok(Arc::clone(&v))), + Err(_) => Ok(Err(TypeExpected::new())), + } + } +} + +impl<'cx, T> TryIntoJs<'cx> for Arc +where + T: 'static, +{ + type Value = JsBox>; + + fn try_into_js(self, cx: &mut Cx<'cx>) -> JsResult<'cx, Self::Value> { + Ok(JsBox::manually_finalize(cx, self)) + } +} diff --git a/crates/neon/src/types_impl/extract/mod.rs b/crates/neon/src/types_impl/extract/mod.rs index 4813cb8f1..5014fc43c 100644 --- a/crates/neon/src/types_impl/extract/mod.rs +++ b/crates/neon/src/types_impl/extract/mod.rs @@ -126,6 +126,7 @@ pub mod json; mod boxed; mod buffer; +mod container; mod either; mod error; mod private; diff --git a/crates/neon/src/types_impl/extract/private.rs b/crates/neon/src/types_impl/extract/private.rs index b795d9b38..211b5fe99 100644 --- a/crates/neon/src/types_impl/extract/private.rs +++ b/crates/neon/src/types_impl/extract/private.rs @@ -1,3 +1,9 @@ +use std::{ + cell::{Ref, RefCell, RefMut}, + rc::Rc, + sync::Arc, +}; + use crate::{ context::FunctionContext, handle::{Handle, Root}, @@ -45,4 +51,16 @@ impl Sealed for Result {} impl<'cx, T> Sealed for Box where T: TryIntoJs<'cx> {} +impl Sealed for RefCell {} + +impl Sealed for &RefCell {} + +impl Sealed for Arc {} + +impl Sealed for Rc {} + +impl Sealed for Ref<'_, T> {} + +impl Sealed for RefMut<'_, T> {} + impl_sealed!(u8, u16, u32, i8, i16, i32, f32, f64, bool, String, Date, Throw, Error,); diff --git a/package-lock.json b/package-lock.json index 97c4532b5..b6617da57 100644 --- a/package-lock.json +++ b/package-lock.json @@ -6066,7 +6066,7 @@ } }, "pkgs/create-neon": { - "version": "0.5.2", + "version": "0.6.0", "license": "MIT", "dependencies": { "@neon-rs/manifest": "^0.2.1", diff --git a/test/napi/lib/container.js b/test/napi/lib/container.js new file mode 100644 index 000000000..5f0627ad3 --- /dev/null +++ b/test/napi/lib/container.js @@ -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 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>" + ); + } + }); + + 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"); + }); +}); diff --git a/test/napi/src/js/container.rs b/test/napi/src/js/container.rs new file mode 100644 index 000000000..da45feecb --- /dev/null +++ b/test/napi/src/js/container.rs @@ -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 { + RefCell::new(s) +} + +#[neon::export] +fn read_string_ref_cell(s: &RefCell) -> String { + s.borrow().clone() +} + +#[neon::export] +fn write_string_ref_cell(s: &RefCell, value: String) { + *s.borrow_mut() = value; +} + +#[neon::export] +fn string_ref_cell_concat(lhs: &RefCell, rhs: String) -> String { + lhs.borrow().clone() + &rhs +} + +#[neon::export] +fn string_ref_concat(lhs: Ref, rhs: String) -> String { + lhs.clone() + &rhs +} + +#[neon::export] +fn write_string_ref(mut s: RefMut, value: String) { + *s = value; +} + +#[neon::export] +fn borrow_and_then<'cx>( + cx: &mut Cx<'cx>, + cell: &RefCell, + f: Handle, +) -> 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, + f: Handle, +) -> 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 { + Rc::new(s) +} + +#[neon::export] +fn read_string_rc(s: Rc) -> String { + (*s).clone() +} + +#[neon::export] +fn create_string_arc(s: String) -> Arc { + Arc::new(s) +} + +#[neon::export] +fn read_string_arc(s: Arc) -> String { + (*s).clone() +} diff --git a/test/napi/src/lib.rs b/test/napi/src/lib.rs index 3568e7842..df5835077 100644 --- a/test/napi/src/lib.rs +++ b/test/napi/src/lib.rs @@ -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;