diff --git a/crates/neon-runtime/src/napi/fun.rs b/crates/neon-runtime/src/napi/fun.rs index f05ab51b5..297cfb7e3 100644 --- a/crates/neon-runtime/src/napi/fun.rs +++ b/crates/neon-runtime/src/napi/fun.rs @@ -32,7 +32,7 @@ pub unsafe fn call( fun: Local, this: Local, argc: i32, - argv: *mut c_void, + argv: *const c_void, ) -> bool { let status = napi::call_function( env, @@ -51,7 +51,7 @@ pub unsafe fn construct( env: Env, fun: Local, argc: i32, - argv: *mut c_void, + argv: *const c_void, ) -> bool { let status = napi::new_instance(env, fun, argc as usize, argv as *const _, out as *mut _); diff --git a/crates/neon-sys/src/lib.rs b/crates/neon-sys/src/lib.rs index d7910b618..149a83787 100644 --- a/crates/neon-sys/src/lib.rs +++ b/crates/neon-sys/src/lib.rs @@ -206,14 +206,14 @@ extern "C" { fun: Local, this: Local, argc: i32, - argv: *mut c_void, + argv: *const c_void, ) -> bool; pub fn Neon_Fun_Construct( out: &mut Local, isolate: Isolate, fun: Local, argc: i32, - argv: *mut c_void, + argv: *const c_void, ) -> bool; pub fn Neon_Mem_SameHandle(h1: Local, h2: Local) -> bool; diff --git a/src/context/mod.rs b/src/context/mod.rs index e0fdb9313..baf9e375c 100644 --- a/src/context/mod.rs +++ b/src/context/mod.rs @@ -94,9 +94,10 @@ //! //! while !done { //! done = cx.execute_scoped(|mut cx| { // temporary scope -//! let args: Vec> = vec![]; -//! let obj = next.call(&mut cx, iterator, args)? // temporary object -//! .downcast_or_throw::(&mut cx)?; +//! let obj: Handle = next // temporary object +//! .call_with(&cx) +//! .this(iterator) +//! .apply(&mut cx)?; //! let number = obj.get(&mut cx, "value")? // temporary number //! .downcast_or_throw::(&mut cx)? //! .value(&mut cx); diff --git a/src/object/class/mod.rs b/src/object/class/mod.rs index dff27a59e..8a12b054b 100644 --- a/src/object/class/mod.rs +++ b/src/object/class/mod.rs @@ -11,7 +11,7 @@ use crate::context::{Context, Lock}; use crate::handle::{Handle, Managed}; use crate::object::{Object, This}; use crate::result::{JsResult, NeonResult, Throw}; -use crate::types::internal::{Callback, ValueInternal}; +use crate::types::private::{Callback, ValueInternal}; use crate::types::{build, JsFunction, JsValue, Value}; use neon_runtime; use neon_runtime::raw; diff --git a/src/types/binary.rs b/src/types/binary.rs index a0e7d1d6b..58a5f5f9d 100644 --- a/src/types/binary.rs +++ b/src/types/binary.rs @@ -8,7 +8,7 @@ use crate::context::{Context, Lock}; use crate::handle::Handle; use crate::handle::Managed; use crate::result::JsResult; -use crate::types::internal::ValueInternal; +use crate::types::private::ValueInternal; use crate::types::{build, Object, Value}; use neon_runtime; use neon_runtime::raw; diff --git a/src/types/boxed.rs b/src/types/boxed.rs index d687e2064..867e81157 100644 --- a/src/types/boxed.rs +++ b/src/types/boxed.rs @@ -8,7 +8,7 @@ use crate::context::internal::Env; use crate::context::{Context, FinalizeContext}; use crate::handle::{Handle, Managed}; use crate::object::Object; -use crate::types::internal::ValueInternal; +use crate::types::private::ValueInternal; use crate::types::Value; type BoxAny = Box; diff --git a/src/types/date.rs b/src/types/date.rs index 76c636f6e..208dc88e1 100644 --- a/src/types/date.rs +++ b/src/types/date.rs @@ -1,4 +1,5 @@ -use super::{Value, ValueInternal}; +use super::private::ValueInternal; +use super::Value; use crate::context::internal::Env; use crate::context::Context; use crate::handle::{Handle, Managed}; diff --git a/src/types/error.rs b/src/types/error.rs index 3b89589af..3d3416644 100644 --- a/src/types/error.rs +++ b/src/types/error.rs @@ -8,7 +8,7 @@ use neon_runtime::raw; use crate::context::internal::Env; use crate::context::Context; use crate::result::{NeonResult, Throw}; -use crate::types::internal::ValueInternal; +use crate::types::private::ValueInternal; use crate::types::utf8::Utf8; use crate::types::{build, Handle, Managed, Object, Value}; diff --git a/src/types/function/mod.rs b/src/types/function/mod.rs new file mode 100644 index 000000000..e02d70740 --- /dev/null +++ b/src/types/function/mod.rs @@ -0,0 +1,215 @@ +//! Types and traits for working with JavaScript functions. + +use crate::context::Context; +use crate::handle::Handle; +use crate::result::{JsResult, NeonResult}; +use crate::types::{JsFunction, JsObject, JsValue, Value}; + +pub(crate) mod private; + +/// A builder for making a JavaScript function call like `parseInt("42")`. +/// +/// The builder methods make it convenient to assemble the call from parts: +/// ``` +/// # use neon::prelude::*; +/// # fn foo(mut cx: FunctionContext) -> JsResult { +/// # let global = cx.global(); +/// # let parse_int = global.get(&mut cx, "parseInt")?; +/// # let parse_int: Handle = parse_int.downcast_or_throw(&mut cx)?; +/// let x: Handle = parse_int +/// .call_with(&cx) +/// .arg(cx.string("42")) +/// .apply(&mut cx)?; +/// # Ok(x) +/// # } +/// ``` +#[derive(Clone)] +pub struct CallOptions<'a> { + pub(crate) callee: Handle<'a, JsFunction>, + pub(crate) this: Option>, + pub(crate) args: private::ArgsVec<'a>, +} + +impl<'a> CallOptions<'a> { + /// Set the value of `this` for the function call. + pub fn this(&mut self, this: Handle<'a, V>) -> &mut Self { + self.this = Some(this.upcast()); + self + } + + /// Add an argument to the arguments list. + pub fn arg(&mut self, arg: Handle<'a, V>) -> &mut Self { + self.args.push(arg.upcast()); + self + } + + /// Add multiple arguments to the arguments list. + pub fn args>(&mut self, args: A) -> &mut Self { + args.append(&mut self.args); + self + } + + /// Make the function call. If the function returns without throwing, the result value + /// is downcast to the type `V`, throwing a `TypeError` if the downcast fails. + pub fn apply<'b, V: Value, C: Context<'b>>(&self, cx: &mut C) -> JsResult<'b, V> { + let this = self.this.unwrap_or_else(|| cx.null().upcast()); + let v: Handle = self.callee.do_call(cx, this, &self.args)?; + v.downcast_or_throw(cx) + } + + /// Make the function call for side effect, discarding the result value. This method is + /// preferable to [`apply()`](CallOptions::apply) when the result value isn't needed, + /// since it doesn't require specifying a result type. + pub fn exec<'b, C: Context<'b>>(&self, cx: &mut C) -> NeonResult<()> { + let this = self.this.unwrap_or_else(|| cx.null().upcast()); + self.callee.do_call(cx, this, &self.args)?; + Ok(()) + } +} + +/// A builder for making a JavaScript constructor call like `new Array(16)`. +/// +/// The builder methods make it convenient to assemble the call from parts: +/// ``` +/// # use neon::prelude::*; +/// # fn foo(mut cx: FunctionContext) -> JsResult { +/// # let global = cx.global(); +/// # let url = global.get(&mut cx, "URL")?; +/// # let url: Handle = url.downcast_or_throw(&mut cx)?; +/// let obj = url +/// .construct_with(&cx) +/// .arg(cx.string("https://neon-bindings.com")) +/// .apply(&mut cx)?; +/// # Ok(obj) +/// # } +/// ``` +#[derive(Clone)] +pub struct ConstructOptions<'a> { + pub(crate) callee: Handle<'a, JsFunction>, + pub(crate) args: private::ArgsVec<'a>, +} + +impl<'a> ConstructOptions<'a> { + /// Add an argument to the arguments list. + pub fn arg(&mut self, arg: Handle<'a, V>) -> &mut Self { + self.args.push(arg.upcast()); + self + } + + /// Add multiple arguments to the arguments list. + pub fn args>(&mut self, args: A) -> &mut Self { + args.append(&mut self.args); + self + } + + /// Make the constructor call. If the function returns without throwing, returns + /// the resulting object. + pub fn apply<'b, C: Context<'b>>(&self, cx: &mut C) -> JsResult<'b, JsObject> { + self.callee.do_construct(cx, &self.args) + } +} + +/// The trait for specifying arguments in a [`CallOptions`](CallOptions) +/// or [`ConstructOptions`](ConstructOptions). This trait is sealed and cannot +/// be implemented by types outside of the Neon crate. +/// +/// **Note:** This trait is implemented for tuples of up to 32 JavaScript values, +/// but for the sake of brevity, only tuples up to size 8 are shown in this documentation. +pub trait Arguments<'a>: private::ArgumentsInternal<'a> {} + +impl<'a> private::ArgumentsInternal<'a> for () { + fn append(self, _args: &mut private::ArgsVec<'a>) {} +} + +impl<'a> Arguments<'a> for () {} + +macro_rules! impl_arguments { + { + [ $(($tprefix:ident, $vprefix:ident), )* ]; + []; + } => {}; + + { + [ $(($tprefix:ident, $vprefix:ident), )* ]; + [ $(#[$attr1:meta])? ($tname1:ident, $vname1:ident), $($(#[$attrs:meta])? ($tnames:ident, $vnames:ident), )* ]; + } => { + $(#[$attr1])? + impl<'a, $($tprefix: Value, )* $tname1: Value> private::ArgumentsInternal<'a> for ($(Handle<'a, $tprefix>, )* Handle<'a, $tname1>, ) { + fn append(self, args: &mut private::ArgsVec<'a>) { + let ($($vprefix, )* $vname1, ) = self; + $(args.push($vprefix.upcast());)* + args.push($vname1.upcast()); + } + } + + $(#[$attr1])? + impl<'a, $($tprefix: Value, )* $tname1: Value> Arguments<'a> for ($(Handle<'a, $tprefix>, )* Handle<'a, $tname1>, ) {} + + impl_arguments! { + [ $(($tprefix, $vprefix), )* ($tname1, $vname1), ]; + [ $($(#[$attrs])? ($tnames, $vnames), )* ]; + } + }; +} + +impl_arguments! { + []; + [ + (V1, v1), + (V2, v2), + (V3, v3), + (V4, v4), + (V5, v5), + (V6, v6), + (V7, v7), + (V8, v8), + #[doc(hidden)] + (V9, v9), + #[doc(hidden)] + (V10, v10), + #[doc(hidden)] + (V11, v11), + #[doc(hidden)] + (V12, v12), + #[doc(hidden)] + (V13, v13), + #[doc(hidden)] + (V14, v14), + #[doc(hidden)] + (V15, v15), + #[doc(hidden)] + (V16, v16), + #[doc(hidden)] + (V17, v17), + #[doc(hidden)] + (V18, v18), + #[doc(hidden)] + (V19, v19), + #[doc(hidden)] + (V20, v20), + #[doc(hidden)] + (V21, v21), + #[doc(hidden)] + (V22, v22), + #[doc(hidden)] + (V23, v23), + #[doc(hidden)] + (V24, v24), + #[doc(hidden)] + (V25, v25), + #[doc(hidden)] + (V26, v26), + #[doc(hidden)] + (V27, v27), + #[doc(hidden)] + (V28, v28), + #[doc(hidden)] + (V29, v29), + #[doc(hidden)] + (V30, v30), + #[doc(hidden)] + (V31, v31), + #[doc(hidden)] + (V32, v32), + ]; +} diff --git a/src/types/function/private.rs b/src/types/function/private.rs new file mode 100644 index 000000000..6b6945b19 --- /dev/null +++ b/src/types/function/private.rs @@ -0,0 +1,11 @@ +use crate::handle::Handle; +use crate::types::JsValue; + +use smallvec::SmallVec; + +pub type ArgsVec<'a> = SmallVec<[Handle<'a, JsValue>; 8]>; + +/// This type marks the `Arguments` trait as sealed. +pub trait ArgumentsInternal<'a> { + fn append(self, args: &mut ArgsVec<'a>); +} diff --git a/src/types/mod.rs b/src/types/mod.rs index 82ade11c9..3cb848a86 100644 --- a/src/types/mod.rs +++ b/src/types/mod.rs @@ -81,10 +81,13 @@ pub(crate) mod boxed; pub(crate) mod date; pub(crate) mod error; -pub(crate) mod internal; +pub(crate) mod private; pub(crate) mod utf8; -use self::internal::{FunctionCallback, ValueInternal}; +pub mod function; + +use self::function::{CallOptions, ConstructOptions}; +use self::private::{Callback, FunctionCallback}; use self::utf8::Utf8; use crate::context::internal::Env; use crate::context::{Context, FunctionContext}; @@ -92,10 +95,9 @@ use crate::handle::internal::SuperType; use crate::handle::{Handle, Managed}; use crate::object::{Object, This}; use crate::result::{JsResult, JsResultExt, NeonResult, Throw}; -use crate::types::internal::Callback; use neon_runtime; use neon_runtime::raw; -use smallvec::SmallVec; +use smallvec::{smallvec, SmallVec}; use std::fmt; use std::fmt::Debug; use std::marker::PhantomData; @@ -135,7 +137,7 @@ impl SuperType for JsObject { } /// The trait shared by all JavaScript values. -pub trait Value: ValueInternal { +pub trait Value: private::ValueInternal { fn to_string<'a, C: Context<'a>>(self, cx: &mut C) -> JsResult<'a, JsString> { let env = cx.env(); build(env, |out| unsafe { @@ -165,7 +167,7 @@ impl Managed for JsValue { } } -impl ValueInternal for JsValue { +impl private::ValueInternal for JsValue { fn name() -> String { "any".to_string() } @@ -251,7 +253,7 @@ unsafe impl This for JsUndefined { } } -impl ValueInternal for JsUndefined { +impl private::ValueInternal for JsUndefined { fn name() -> String { "undefined".to_string() } @@ -298,7 +300,7 @@ impl Managed for JsNull { } } -impl ValueInternal for JsNull { +impl private::ValueInternal for JsNull { fn name() -> String { "null".to_string() } @@ -350,7 +352,7 @@ impl Managed for JsBoolean { } } -impl ValueInternal for JsBoolean { +impl private::ValueInternal for JsBoolean { fn name() -> String { "boolean".to_string() } @@ -399,7 +401,7 @@ impl Managed for JsString { } } -impl ValueInternal for JsString { +impl private::ValueInternal for JsString { fn name() -> String { "string".to_string() } @@ -520,7 +522,7 @@ impl Managed for JsNumber { } } -impl ValueInternal for JsNumber { +impl private::ValueInternal for JsNumber { fn name() -> String { "number".to_string() } @@ -559,7 +561,7 @@ unsafe impl This for JsObject { } } -impl ValueInternal for JsObject { +impl private::ValueInternal for JsObject { fn name() -> String { "object".to_string() } @@ -659,7 +661,7 @@ impl Managed for JsArray { } } -impl ValueInternal for JsArray { +impl private::ValueInternal for JsArray { fn name() -> String { "Array".to_string() } @@ -672,6 +674,83 @@ impl ValueInternal for JsArray { impl Object for JsArray {} /// A JavaScript function object. +/// +/// A `JsFunction` may come from an existing JavaScript function, for example +/// by extracting it from the property of another object such as the +/// [global object](crate::context::Context::global), or it may be defined in Rust +/// with [`JsFunction::new()`](JsFunction::new). +/// +/// ## Calling functions +/// +/// Neon provides a convenient syntax for calling JavaScript functions with the +/// [`call_with()`](JsFunction::call_with) method, which produces a [`CallOptions`](CallOptions) +/// struct that can be used to provide the function arguments (and optionally, the binding for +/// `this`) before calling the function: +/// ``` +/// # use neon::prelude::*; +/// # fn foo(mut cx: FunctionContext) -> JsResult { +/// # let global = cx.global(); +/// // Extract the parseInt function from the global object +/// let parse_int: Handle = global +/// .get(&mut cx, "parseInt")? +/// .downcast_or_throw(&mut cx)?; +/// +/// // Call parseInt("42") +/// let x: Handle = parse_int +/// .call_with(&mut cx) +/// .arg(cx.string("42")) +/// .apply(&mut cx)?; +/// # Ok(x) +/// # } +/// ``` +/// +/// ## Calling functions as constructors +/// +/// A `JsFunction` can be called as a constructor (like `new Array(16)` or +/// `new URL("https://neon-bindings.com")`) with the +/// [`construct_with()`](JsFunction::construct_with) method: +/// ``` +/// # use neon::prelude::*; +/// # fn foo(mut cx: FunctionContext) -> JsResult { +/// # let global = cx.global(); +/// // Extract the URL constructor from the global object +/// let url: Handle = global +/// .get(&mut cx, "URL")? +/// .downcast_or_throw(&mut cx)?; +/// +/// // Call new URL("https://neon-bindings.com") +/// let obj = url +/// .construct_with(&cx) +/// .arg(cx.string("https://neon-bindings.com")) +/// .apply(&mut cx)?; +/// # Ok(obj) +/// # } +/// ``` +/// +/// ## Defining functions +/// +/// JavaScript functions can be defined in Rust with the +/// [`JsFunction::new()`](JsFunction::new) constructor, which takes +/// a Rust implementation function and produces a JavaScript function. +/// +/// ``` +/// # use neon::prelude::*; +/// // A function implementation that adds 1 to its first argument +/// fn add1(mut cx: FunctionContext) -> JsResult { +/// let x: Handle = cx.argument(0)?; +/// # #[cfg(feature = "legacy-runtime")] +/// # let v = x.value(); +/// # #[cfg(feature = "napi-1")] +/// let v = x.value(&mut cx); +/// Ok(cx.number(v + 1.0)) +/// } +/// +/// # fn foo(mut cx: FunctionContext) -> JsResult { +/// // Define a new JsFunction implemented with the add1 function +/// let f = JsFunction::new(&mut cx, add1)?; +/// # Ok(f) +/// # } +/// ``` #[repr(C)] #[derive(Clone, Copy)] pub struct JsFunction { @@ -684,19 +763,19 @@ impl Object for JsFunction {} // Maximum number of function arguments in V8. const V8_ARGC_LIMIT: usize = 65535; -unsafe fn prepare_call<'a, 'b, C: Context<'a>, A>( +fn prepare_call<'a, 'b, C: Context<'a>, A>( cx: &mut C, - args: &mut [Handle<'b, A>], -) -> NeonResult<(i32, *mut c_void)> + args: &[Handle<'b, A>], +) -> NeonResult<(i32, *const c_void)> where A: Value + 'b, { - let argv = args.as_mut_ptr(); + let argv = args.as_ptr(); let argc = args.len(); if argc > V8_ARGC_LIMIT { return cx.throw_range_error("too many arguments"); } - Ok((argc as i32, argv as *mut c_void)) + Ok((argc as i32, argv as *const c_void)) } impl JsFunction { @@ -719,6 +798,36 @@ impl JsFunction { } impl JsFunction { + fn do_call<'a, 'b: 'a, C, T, A>( + self, + cx: &mut C, + this: Handle<'a, T>, + args: &[Handle<'a, A>], + ) -> JsResult<'b, JsValue> + where + C: Context<'b>, + T: Value, + A: Value, + { + let (argc, argv) = prepare_call(cx, args)?; + let env = cx.env().to_raw(); + build(cx.env(), |out| unsafe { + neon_runtime::fun::call(out, env, self.to_raw(), this.to_raw(), argc, argv) + }) + } + + fn do_construct<'a, 'b: 'a, C, A>(self, cx: &mut C, args: &[Handle<'a, A>]) -> JsResult<'b, CL> + where + C: Context<'b>, + A: Value, + { + let (argc, argv) = prepare_call(cx, args)?; + let env = cx.env().to_raw(); + build(cx.env(), |out| unsafe { + neon_runtime::fun::construct(out, env, self.to_raw(), argc, argv) + }) + } + pub fn call<'a, 'b, C: Context<'a>, T, A, AS>( self, cx: &mut C, @@ -730,12 +839,8 @@ impl JsFunction { A: Value + 'b, AS: IntoIterator>, { - let mut args = args.into_iter().collect::>(); - let (argc, argv) = unsafe { prepare_call(cx, &mut args) }?; - let env = cx.env().to_raw(); - build(cx.env(), |out| unsafe { - neon_runtime::fun::call(out, env, self.to_raw(), this.to_raw(), argc, argv) - }) + let args = args.into_iter().collect::>(); + self.do_call(cx, this, &args) } pub fn construct<'a, 'b, C: Context<'a>, A, AS>(self, cx: &mut C, args: AS) -> JsResult<'a, CL> @@ -743,12 +848,28 @@ impl JsFunction { A: Value + 'b, AS: IntoIterator>, { - let mut args = args.into_iter().collect::>(); - let (argc, argv) = unsafe { prepare_call(cx, &mut args) }?; - let env = cx.env().to_raw(); - build(cx.env(), |out| unsafe { - neon_runtime::fun::construct(out, env, self.to_raw(), argc, argv) - }) + let args = args.into_iter().collect::>(); + self.do_construct(cx, &args) + } +} + +impl JsFunction { + /// Create a [`CallOptions`](function::CallOptions) for calling this function. + pub fn call_with<'a, C: Context<'a>>(self, _cx: &C) -> CallOptions<'a> { + CallOptions { + this: None, + callee: Handle::new_internal(self), + args: smallvec![], + } + } + + /// Create a [`ConstructOptions`](function::ConstructOptions) for calling this function + /// as a constructor. + pub fn construct_with<'a, C: Context<'a>>(self, _cx: &C) -> ConstructOptions<'a> { + ConstructOptions { + callee: Handle::new_internal(self), + args: smallvec![], + } } } @@ -767,7 +888,7 @@ impl Managed for JsFunction { } } -impl ValueInternal for JsFunction { +impl private::ValueInternal for JsFunction { fn name() -> String { "function".to_string() } diff --git a/src/types/internal.rs b/src/types/private.rs similarity index 100% rename from src/types/internal.rs rename to src/types/private.rs diff --git a/test/dynamic/native/src/js/eventhandler.rs b/test/dynamic/native/src/js/eventhandler.rs index df612a6ae..7be224717 100644 --- a/test/dynamic/native/src/js/eventhandler.rs +++ b/test/dynamic/native/src/js/eventhandler.rs @@ -111,8 +111,11 @@ declare_types! { if let Some(cb) = cb { thread::spawn(move || { cb.schedule_with(move |cx, this, callback| { - let args : Vec> = vec![cx.string("number").upcast()]; - let result = callback.call(cx, this, args); + let result: JsResult = callback + .call_with(cx) + .this(this) + .arg(cx.string("number")) + .apply(cx); let cmd = match result { Ok(v) => { if let Ok(number) = v.downcast::() { @@ -127,8 +130,7 @@ declare_types! { }, Err(e) => format!("threw {}", e) }; - let args : Vec> = vec![cx.string(cmd).upcast()]; - let _result = callback.call(cx, this, args); + let _result = callback.call_with(cx).this(this).arg(cx.string(cmd)).exec(cx); }); }); } diff --git a/test/napi/lib/functions.js b/test/napi/lib/functions.js index 1db8ec417..39733fafe 100644 --- a/test/napi/lib/functions.js +++ b/test/napi/lib/functions.js @@ -14,6 +14,34 @@ describe('JsFunction', function() { assert.equal(addon.call_js_function(function(x) { return x + 1 }), 17); }); + it('call a JsFunction with zero args', function() { + assert.equal(addon.call_js_function_with_zero_args(), -Infinity); + }); + + it('call a JsFunction with one arg', function() { + assert.equal(addon.call_js_function_with_one_arg(), 1.0); + }); + + it('call a JsFunction with two args', function() { + assert.equal(addon.call_js_function_with_two_args(), 2.0); + }); + + it('call a JsFunction with three args', function() { + assert.equal(addon.call_js_function_with_three_args(), 3.0); + }); + + it('call a JsFunction with four args', function() { + assert.equal(addon.call_js_function_with_four_args(), 4.0); + }); + + it('call a JsFunction with a custom this', function() { + assert.equal(addon.call_js_function_with_custom_this(function() { return this }).secret, 42); + }); + + it('call a JsFunction with a heterogeneously typed tuple', function() { + assert.deepEqual(addon.call_js_function_with_heterogeneous_tuple(), [1, "hello", true]); + }); + it('new a JsFunction', function () { assert.equal(addon.construct_js_function(Date), 1970); }); diff --git a/test/napi/src/js/functions.rs b/test/napi/src/js/functions.rs index 5a8bd819c..d5bee0d28 100644 --- a/test/napi/src/js/functions.rs +++ b/test/napi/src/js/functions.rs @@ -11,27 +11,95 @@ pub fn return_js_function(mut cx: FunctionContext) -> JsResult { } pub fn call_js_function(mut cx: FunctionContext) -> JsResult { - let f = cx.argument::(0)?; - let args: Vec> = vec![cx.number(16.0)]; - let null = cx.null(); - f.call(&mut cx, null, args)? - .downcast::(&mut cx) - .or_throw(&mut cx) + cx.argument::(0)? + .call_with(&cx) + .this(cx.null()) + .arg(cx.number(16.0)) + .apply(&mut cx) +} + +fn get_math_max<'a>(cx: &mut FunctionContext<'a>) -> JsResult<'a, JsFunction> { + let math = cx + .global() + .get(cx, "Math")? + .downcast_or_throw::(cx)?; + let max = math + .get(cx, "max")? + .downcast_or_throw::(cx)?; + Ok(max) +} + +pub fn call_js_function_with_zero_args(mut cx: FunctionContext) -> JsResult { + get_math_max(&mut cx)?.call_with(&cx).apply(&mut cx) +} + +pub fn call_js_function_with_one_arg(mut cx: FunctionContext) -> JsResult { + get_math_max(&mut cx)? + .call_with(&cx) + .arg(cx.number(1.0)) + .apply(&mut cx) +} + +pub fn call_js_function_with_two_args(mut cx: FunctionContext) -> JsResult { + get_math_max(&mut cx)? + .call_with(&cx) + .arg(cx.number(1.0)) + .arg(cx.number(2.0)) + .apply(&mut cx) +} + +pub fn call_js_function_with_three_args(mut cx: FunctionContext) -> JsResult { + get_math_max(&mut cx)? + .call_with(&cx) + .arg(cx.number(1.0)) + .arg(cx.number(2.0)) + .arg(cx.number(3.0)) + .apply(&mut cx) +} + +pub fn call_js_function_with_four_args(mut cx: FunctionContext) -> JsResult { + get_math_max(&mut cx)? + .call_with(&cx) + .arg(cx.number(1.0)) + .arg(cx.number(2.0)) + .arg(cx.number(3.0)) + .arg(cx.number(4.0)) + .apply(&mut cx) +} + +pub fn call_js_function_with_custom_this(mut cx: FunctionContext) -> JsResult { + let custom_this = cx.empty_object(); + let secret = cx.number(42.0); + custom_this.set(&mut cx, "secret", secret)?; + cx.argument::(0)? + .call_with(&cx) + .this(custom_this) + .apply(&mut cx) +} + +pub fn call_js_function_with_heterogeneous_tuple(mut cx: FunctionContext) -> JsResult { + cx.global() + .get(&mut cx, "Array")? + .downcast_or_throw::(&mut cx)? + .call_with(&cx) + .args((cx.number(1.0), cx.string("hello"), cx.boolean(true))) + .apply(&mut cx) } pub fn construct_js_function(mut cx: FunctionContext) -> JsResult { - let f = cx.argument::(0)?; - let zero = cx.number(0.0); - let o = f.construct(&mut cx, vec![zero])?; + let o = cx + .argument::(0)? + .construct_with(&cx) + .arg(cx.number(0.0)) + .apply(&mut cx)?; let get_utc_full_year_method = o .get(&mut cx, "getUTCFullYear")? .downcast::(&mut cx) .or_throw(&mut cx)?; - let args: Vec> = vec![]; get_utc_full_year_method - .call(&mut cx, o.upcast::(), args)? - .downcast::(&mut cx) - .or_throw(&mut cx) + .call_with(&cx) + .this(o) + .apply(&mut cx) } trait CheckArgument<'a> { @@ -124,11 +192,7 @@ pub fn throw_and_catch(mut cx: FunctionContext) -> JsResult { pub fn call_and_catch(mut cx: FunctionContext) -> JsResult { let f: Handle = cx.argument(0)?; Ok(cx - .try_catch(|cx| { - let global = cx.global(); - let args: Vec> = vec![]; - f.call(cx, global, args) - }) + .try_catch(|cx| f.call_with(cx).this(cx.global()).apply(cx)) .unwrap_or_else(|err| err)) } diff --git a/test/napi/src/js/threads.rs b/test/napi/src/js/threads.rs index e9c5f968c..e69897a45 100644 --- a/test/napi/src/js/threads.rs +++ b/test/napi/src/js/threads.rs @@ -16,15 +16,7 @@ pub fn thread_callback(mut cx: FunctionContext) -> JsResult { let channel = cx.channel(); std::thread::spawn(move || { - channel.send(move |mut cx| { - let callback = callback.into_inner(&mut cx); - let this = cx.undefined(); - let args = Vec::>::new(); - - callback.call(&mut cx, this, args)?; - - Ok(()) - }) + channel.send(move |mut cx| callback.into_inner(&mut cx).call_with(&cx).exec(&mut cx)) }); Ok(cx.undefined()) @@ -41,13 +33,11 @@ pub fn multi_threaded_callback(mut cx: FunctionContext) -> JsResult std::thread::spawn(move || { channel.send(move |mut cx| { - let callback = callback.into_inner(&mut cx); - let this = cx.undefined(); - let args = vec![cx.number(i as f64)]; - - callback.call(&mut cx, this, args)?; - - Ok(()) + callback + .into_inner(&mut cx) + .call_with(&cx) + .arg(cx.number(i as f64)) + .exec(&mut cx) }) }); } @@ -74,13 +64,11 @@ impl AsyncGreeter { std::thread::spawn(move || { channel.send(|mut cx| { - let callback = callback.into_inner(&mut cx); - let this = cx.undefined(); - let args = vec![cx.string(greeting)]; - - callback.call(&mut cx, this, args)?; - - Ok(()) + callback + .into_inner(&mut cx) + .call_with(&cx) + .arg(cx.string(greeting)) + .exec(&mut cx) }) }); @@ -95,10 +83,7 @@ impl Finalize for AsyncGreeter { } = self; if let Some(shutdown) = shutdown { - let shutdown = shutdown.into_inner(cx); - let this = cx.undefined(); - let args = Vec::>::new(); - let _ = shutdown.call(cx, this, args); + let _ = shutdown.into_inner(cx).call_with(cx).exec(cx); } callback.drop(cx); @@ -159,13 +144,11 @@ pub fn drop_global_queue(mut cx: FunctionContext) -> JsResult { fn drop(&mut self) { if let Some(callback) = self.callback.take() { self.channel.send(|mut cx| { - let callback = callback.into_inner(&mut cx); - let this = cx.undefined(); - let args = vec![cx.undefined()]; - - callback.call(&mut cx, this, args)?; - - Ok(()) + callback + .into_inner(&mut cx) + .call_with(&cx) + .arg(cx.undefined()) + .exec(&mut cx) }); } } diff --git a/test/napi/src/lib.rs b/test/napi/src/lib.rs index f081c07a1..ff081280e 100644 --- a/test/napi/src/lib.rs +++ b/test/napi/src/lib.rs @@ -143,6 +143,34 @@ fn main(mut cx: ModuleContext) -> NeonResult<()> { cx.export_function("return_js_function", return_js_function)?; cx.export_function("call_js_function", call_js_function)?; + cx.export_function( + "call_js_function_with_zero_args", + call_js_function_with_zero_args, + )?; + cx.export_function( + "call_js_function_with_one_arg", + call_js_function_with_one_arg, + )?; + cx.export_function( + "call_js_function_with_two_args", + call_js_function_with_two_args, + )?; + cx.export_function( + "call_js_function_with_three_args", + call_js_function_with_three_args, + )?; + cx.export_function( + "call_js_function_with_four_args", + call_js_function_with_four_args, + )?; + cx.export_function( + "call_js_function_with_custom_this", + call_js_function_with_custom_this, + )?; + cx.export_function( + "call_js_function_with_heterogeneous_tuple", + call_js_function_with_heterogeneous_tuple, + )?; cx.export_function("construct_js_function", construct_js_function)?; cx.export_function("num_arguments", num_arguments)?; cx.export_function("return_this", return_this)?;