diff --git a/napi-derive/src/lib.rs b/napi-derive/src/lib.rs index d85f43ac..88a18f02 100644 --- a/napi-derive/src/lib.rs +++ b/napi-derive/src/lib.rs @@ -96,7 +96,7 @@ pub fn js_function(attr: TokenStream, input: TokenStream) -> TokenStream { use std::ptr; use std::panic::{self, AssertUnwindSafe}; use std::ffi::CString; - use napi::{Env, Error, Status, NapiValue, CallContext}; + use napi::{Env, Error, Status, NapiValue, NapiRaw, CallContext}; let mut argc = #arg_len_span as usize; let mut raw_args: [napi::sys::napi_value; #arg_len_span] = [ptr::null_mut(); #arg_len_span]; let mut raw_this = ptr::null_mut(); @@ -145,7 +145,7 @@ pub fn contextless_function(_attr: TokenStream, input: TokenStream) -> TokenStre use std::ptr; use std::panic::{self, AssertUnwindSafe}; use std::ffi::CString; - use napi::{Env, NapiValue, Error, Status}; + use napi::{Env, NapiValue, NapiRaw, Error, Status}; let ctx = unsafe { Env::from_raw(raw_env) }; #execute_js_function @@ -227,7 +227,7 @@ pub fn module_exports(_attr: TokenStream, input: TokenStream) -> TokenStream { ) -> napi::sys::napi_value { use std::ffi::CString; use std::ptr; - use napi::{Env, JsObject, NapiValue}; + use napi::{Env, JsObject, NapiValue, NapiRaw}; #[cfg(all(feature = "tokio_rt", feature = "napi4"))] use napi::shutdown_tokio_rt; diff --git a/napi/src/async_work.rs b/napi/src/async_work.rs index 9f6b3c55..31994af9 100644 --- a/napi/src/async_work.rs +++ b/napi/src/async_work.rs @@ -2,7 +2,11 @@ use std::mem; use std::os::raw::{c_char, c_void}; use std::ptr; -use crate::{check_status, js_values::NapiValue, sys, Env, JsError, JsObject, Result, Task}; +use crate::{ + check_status, + js_values::{NapiRaw, NapiValue}, + sys, Env, JsError, JsObject, Result, Task, +}; struct AsyncWork { inner_task: T, diff --git a/napi/src/js_values/bigint.rs b/napi/src/js_values/bigint.rs index 6a034fd2..993054f1 100644 --- a/napi/src/js_values/bigint.rs +++ b/napi/src/js_values/bigint.rs @@ -123,11 +123,19 @@ impl JsBigint { } } -impl NapiValue for JsBigint { +impl NapiRaw for JsBigint { unsafe fn raw(&self) -> sys::napi_value { self.raw.value } +} +impl<'env> NapiRaw for &'env JsBigint { + unsafe fn raw(&self) -> sys::napi_value { + self.raw.value + } +} + +impl NapiValue for JsBigint { unsafe fn from_raw(env: sys::napi_env, value: sys::napi_value) -> Result { let mut word_count = 0usize; check_status!(sys::napi_get_value_bigint_words( diff --git a/napi/src/js_values/either.rs b/napi/src/js_values/either.rs index d781372e..2fed39b8 100644 --- a/napi/src/js_values/either.rs +++ b/napi/src/js_values/either.rs @@ -1,4 +1,4 @@ -use crate::{sys, JsUndefined, NapiValue, Result}; +use crate::{sys, JsUndefined, NapiRaw, NapiValue, Result}; #[derive(Debug, Clone, Copy)] pub enum Either { @@ -25,7 +25,9 @@ impl NapiValue for Either { unsafe fn from_raw_unchecked(env: sys::napi_env, value: sys::napi_value) -> Either { Self::from_raw(env, value).unwrap() } +} +impl NapiRaw for Either { unsafe fn raw(&self) -> sys::napi_value { match self { Either::A(v) => v.raw(), diff --git a/napi/src/js_values/escapable_handle_scope.rs b/napi/src/js_values/escapable_handle_scope.rs index b345dc2c..473f0879 100644 --- a/napi/src/js_values/escapable_handle_scope.rs +++ b/napi/src/js_values/escapable_handle_scope.rs @@ -2,7 +2,7 @@ use std::ops::Deref; use std::ptr; use crate::check_status; -use crate::{sys, Env, NapiValue, Result}; +use crate::{sys, Env, NapiRaw, NapiValue, Result}; pub struct EscapableHandleScope { handle_scope: sys::napi_escapable_handle_scope, @@ -16,7 +16,7 @@ impl EscapableHandleScope { check_status!(unsafe { sys::napi_open_escapable_handle_scope(env, &mut handle_scope) })?; let mut result = ptr::null_mut(); check_status!(unsafe { - sys::napi_escape_handle(env, handle_scope, NapiValue::raw(&value), &mut result) + sys::napi_escape_handle(env, handle_scope, NapiRaw::raw(&value), &mut result) })?; Ok(Self { value, diff --git a/napi/src/js_values/function.rs b/napi/src/js_values/function.rs index 22c2b1df..699a17b9 100644 --- a/napi/src/js_values/function.rs +++ b/napi/src/js_values/function.rs @@ -2,7 +2,7 @@ use std::ptr; use super::Value; use crate::check_status; -use crate::{sys, Env, Error, JsObject, JsUnknown, NapiValue, Result, Status}; +use crate::{sys, Env, Error, JsObject, JsUnknown, NapiRaw, NapiValue, Result, Status}; pub struct JsFunction(pub(crate) Value); @@ -23,7 +23,10 @@ pub struct JsFunction(pub(crate) Value); impl JsFunction { /// [napi_call_function](https://nodejs.org/api/n-api.html#n_api_napi_call_function) #[inline] - pub fn call(&self, this: Option<&JsObject>, args: &[JsUnknown]) -> Result { + pub fn call(&self, this: Option<&JsObject>, args: &[V]) -> Result + where + V: NapiRaw, + { let raw_this = this .map(|v| unsafe { v.raw() }) .or_else(|| { @@ -35,7 +38,7 @@ impl JsFunction { .ok_or_else(|| Error::new(Status::GenericFailure, "Get raw this failed".to_owned()))?; let raw_args = args .iter() - .map(|arg| arg.0.value) + .map(|arg| unsafe { arg.raw() }) .collect::>(); let mut return_value = ptr::null_mut(); check_status!(unsafe { @@ -52,6 +55,34 @@ impl JsFunction { unsafe { JsUnknown::from_raw(self.0.env, return_value) } } + /// [napi_call_function](https://nodejs.org/api/n-api.html#n_api_napi_call_function) + /// The same with `call`, but without arguments + #[inline] + pub fn call_without_args(&self, this: Option<&JsObject>) -> Result { + let raw_this = this + .map(|v| unsafe { v.raw() }) + .or_else(|| { + unsafe { Env::from_raw(self.0.env) } + .get_undefined() + .ok() + .map(|u| unsafe { u.raw() }) + }) + .ok_or_else(|| Error::new(Status::GenericFailure, "Get raw this failed".to_owned()))?; + let mut return_value = ptr::null_mut(); + check_status!(unsafe { + sys::napi_call_function( + self.0.env, + raw_this, + self.0.value, + 0, + ptr::null_mut(), + &mut return_value, + ) + })?; + + unsafe { JsUnknown::from_raw(self.0.env, return_value) } + } + /// https://nodejs.org/api/n-api.html#n_api_napi_new_instance /// /// This method is used to instantiate a new `JavaScript` value using a given `JsFunction` that represents the constructor for the object. diff --git a/napi/src/js_values/mod.rs b/napi/src/js_values/mod.rs index bf740c1b..07cf9251 100644 --- a/napi/src/js_values/mod.rs +++ b/napi/src/js_values/mod.rs @@ -101,10 +101,6 @@ macro_rules! impl_napi_value_trait { } } - unsafe fn raw(&self) -> sys::napi_value { - self.0.value - } - unsafe fn from_raw_unchecked(env: sys::napi_env, value: sys::napi_value) -> $js_value { $js_value(Value { env, @@ -114,6 +110,18 @@ macro_rules! impl_napi_value_trait { } } + impl NapiRaw for $js_value { + unsafe fn raw(&self) -> sys::napi_value { + self.0.value + } + } + + impl<'env> NapiRaw for &'env $js_value { + unsafe fn raw(&self) -> sys::napi_value { + self.0.value + } + } + impl TryFrom for $js_value { type Error = Error; fn try_from(value: JsUnknown) -> Result<$js_value> { @@ -221,7 +229,10 @@ macro_rules! impl_js_value_methods { } #[inline] - pub fn instanceof(&self, constructor: Constructor) -> Result { + pub fn instanceof(&self, constructor: Constructor) -> Result + where + Constructor: NapiRaw, + { let mut result = false; check_status!(unsafe { sys::napi_instanceof(self.0.env, self.0.value, constructor.raw(), &mut result) @@ -238,7 +249,7 @@ macro_rules! impl_object_methods { #[inline] pub fn set_property(&mut self, key: JsString, value: V) -> Result<()> where - V: NapiValue, + V: NapiRaw, { check_status!(unsafe { sys::napi_set_property(self.0.env, self.0.value, key.0.value, value.raw()) @@ -246,9 +257,9 @@ macro_rules! impl_object_methods { } #[inline] - pub fn get_property(&self, key: &K) -> Result + pub fn get_property(&self, key: K) -> Result where - K: NapiValue, + K: NapiRaw, T: NapiValue, { let mut raw_value = ptr::null_mut(); @@ -259,9 +270,9 @@ macro_rules! impl_object_methods { } #[inline] - pub fn get_property_unchecked(&self, key: &K) -> Result + pub fn get_property_unchecked(&self, key: K) -> Result where - K: NapiValue, + K: NapiRaw, T: NapiValue, { let mut raw_value = ptr::null_mut(); @@ -274,7 +285,7 @@ macro_rules! impl_object_methods { #[inline] pub fn set_named_property(&mut self, name: &str, value: T) -> Result<()> where - T: NapiValue, + T: NapiRaw, { let key = CString::new(name)?; check_status!(unsafe { @@ -341,7 +352,7 @@ macro_rules! impl_object_methods { #[inline] pub fn delete_property(&mut self, name: S) -> Result where - S: NapiValue, + S: NapiRaw, { let mut result = false; check_status!(unsafe { @@ -381,7 +392,7 @@ macro_rules! impl_object_methods { #[inline] pub fn has_own_property_js(&self, key: K) -> Result where - K: NapiValue, + K: NapiRaw, { let mut result = false; check_status!(unsafe { @@ -407,7 +418,7 @@ macro_rules! impl_object_methods { #[inline] pub fn has_property_js(&self, name: K) -> Result where - K: NapiValue, + K: NapiRaw, { let mut result = false; check_status!(unsafe { @@ -473,7 +484,7 @@ macro_rules! impl_object_methods { #[inline] pub fn set_element(&mut self, index: u32, value: T) -> Result<()> where - T: NapiValue, + T: NapiRaw, { check_status!(unsafe { sys::napi_set_element(self.0.env, self.0.value, index, value.raw()) @@ -565,15 +576,17 @@ macro_rules! impl_object_methods { }; } -pub trait NapiValue: Sized { +pub trait NapiRaw { + #[allow(clippy::missing_safety_doc)] + unsafe fn raw(&self) -> sys::napi_value; +} + +pub trait NapiValue: Sized + NapiRaw { #[allow(clippy::missing_safety_doc)] unsafe fn from_raw(env: sys::napi_env, value: sys::napi_value) -> Result; #[allow(clippy::missing_safety_doc)] unsafe fn from_raw_unchecked(env: sys::napi_env, value: sys::napi_value) -> Self; - - #[allow(clippy::missing_safety_doc)] - unsafe fn raw(&self) -> sys::napi_value; } impl_js_value_methods!(JsUnknown); @@ -640,7 +653,17 @@ impl NapiValue for JsUnknown { value_type: Unknown, }) } +} +impl NapiRaw for JsUnknown { + /// get raw js value ptr + #[inline] + unsafe fn raw(&self) -> sys::napi_value { + self.0.value + } +} + +impl<'env> NapiRaw for &'env JsUnknown { /// get raw js value ptr #[inline] unsafe fn raw(&self) -> sys::napi_value { diff --git a/test_module/__test__/function.spec.ts b/test_module/__test__/function.spec.ts index 7843dcd5..0dd4f4ec 100644 --- a/test_module/__test__/function.spec.ts +++ b/test_module/__test__/function.spec.ts @@ -8,6 +8,12 @@ test('should call the function', (t) => { }) }) +test('should call function with ref args', (t) => { + bindings.testCallFunctionWithRefArguments((arg1: string, arg2: string) => { + t.is(`${arg1} ${arg2}`, 'hello world') + }) +}) + test('should set "this" properly', (t) => { const obj = {} bindings.testCallFunctionWithThis.call(obj, function (this: typeof obj) { diff --git a/test_module/src/function.rs b/test_module/src/function.rs index 271d94cc..1582128d 100644 --- a/test_module/src/function.rs +++ b/test_module/src/function.rs @@ -11,18 +11,33 @@ pub fn call_function(ctx: CallContext) -> Result { ctx.env.get_null() } +#[js_function(1)] +pub fn call_function_with_ref_arguments(ctx: CallContext) -> Result { + let js_func = ctx.get::(0)?; + let js_string_hello = ctx.env.create_string("hello".as_ref())?; + let js_string_world = ctx.env.create_string("world".as_ref())?; + + js_func.call(None, &[&js_string_hello, &js_string_world])?; + + ctx.env.get_null() +} + #[js_function(1)] pub fn call_function_with_this(ctx: CallContext) -> Result { let js_this: JsObject = ctx.this_unchecked(); let js_func = ctx.get::(0)?; - js_func.call(Some(&js_this), &[])?; + js_func.call_without_args(Some(&js_this))?; ctx.env.get_null() } pub fn register_js(exports: &mut JsObject) -> Result<()> { exports.create_named_method("testCallFunction", call_function)?; + exports.create_named_method( + "testCallFunctionWithRefArguments", + call_function_with_ref_arguments, + )?; exports.create_named_method("testCallFunctionWithThis", call_function_with_this)?; Ok(()) }