diff --git a/crates/neon/src/context/mod.rs b/crates/neon/src/context/mod.rs index 1c4ae2aa3..d15cd96ac 100644 --- a/crates/neon/src/context/mod.rs +++ b/crates/neon/src/context/mod.rs @@ -45,10 +45,9 @@ //! # use neon::prelude::*; //! fn log(cx: &mut Cx, msg: &str) -> NeonResult<()> { //! cx.global::("console")? -//! .call_method_with(cx, "log")? -//! .arg(cx.string(msg)) -//! .exec(cx)?; -//! +//! .method(cx, "log")? +//! .arg(msg)? +//! .exec()?; //! Ok(()) //! } //! @@ -107,22 +106,18 @@ //! # fn iterate(mut cx: FunctionContext) -> JsResult { //! let iterator = cx.argument::(0)?; // iterator object //! let next: Handle = // iterator's `next` method -//! iterator.get(&mut cx, "next")?; +//! iterator.prop(&mut cx, "next").get()?; //! let mut numbers = vec![]; // results vector //! let mut done = false; // loop controller //! //! while !done { //! done = cx.execute_scoped(|mut cx| { // temporary scope //! let obj: Handle = next // temporary object -//! .call_with(&cx) -//! .this(iterator) -//! .apply(&mut cx)?; -//! let number: Handle = // temporary number -//! obj.get(&mut cx, "value")?; -//! numbers.push(number.value(&mut cx)); -//! let done: Handle = // temporary boolean -//! obj.get(&mut cx, "done")?; -//! Ok(done.value(&mut cx)) +//! .bind(&mut cx) +//! .this(iterator)? +//! .call()?; +//! numbers.push(obj.prop(&mut cx, "value").get()?); // temporary number +//! obj.prop(&mut cx, "done").get() // temporary boolean //! })?; //! } //! # Ok(cx.undefined()) @@ -505,7 +500,7 @@ pub trait Context<'a>: ContextInternal<'a> { /// # let v: Handle = /// { /// let global = cx.global_object(); - /// global.get(cx, name) + /// global.prop(cx, name).get() /// } /// # ?; /// # Ok(v) diff --git a/crates/neon/src/event/channel.rs b/crates/neon/src/event/channel.rs index cc2872f29..15782ddac 100644 --- a/crates/neon/src/event/channel.rs +++ b/crates/neon/src/event/channel.rs @@ -75,13 +75,11 @@ type Callback = Box; /// // loop. This _will_ block the event loop while executing. /// channel.send(move |mut cx| { /// let callback = callback.into_inner(&mut cx); -/// let this = cx.undefined(); -/// let args = vec![ -/// cx.null().upcast::(), -/// cx.number(result).upcast(), -/// ]; /// -/// callback.call(&mut cx, this, args)?; +/// callback +/// .bind(&mut cx) +/// .args(((), result))? +/// .exec()?; /// /// Ok(()) /// }); diff --git a/crates/neon/src/event/mod.rs b/crates/neon/src/event/mod.rs index c5a594f0f..e5125650f 100644 --- a/crates/neon/src/event/mod.rs +++ b/crates/neon/src/event/mod.rs @@ -78,31 +78,28 @@ //! // loop. This _will_ block the event loop while executing. //! channel.send(move |mut cx| { //! let callback = callback.into_inner(&mut cx); -//! let this = cx.undefined(); -//! let args = match result { +//! +//! match result { //! Ok(psd) => { //! // Extract data from the parsed file. -//! let width = cx.number(psd.width()); -//! let height = cx.number(psd.height()); -//! -//! // Save the data in a result object. -//! let obj = cx.empty_object(); -//! obj.set(&mut cx, "width", width)?; -//! obj.set(&mut cx, "height", height)?; -//! vec![ -//! cx.null().upcast::(), -//! obj.upcast(), -//! ] +//! let obj = cx.empty_object() +//! .prop(&mut cx, "width").set(psd.width())? +//! .prop("height").set(psd.height())? +//! .this(); +//! +//! callback +//! .bind(&mut cx) +//! .args(((), obj))? +//! .exec()?; //! } //! Err(err) => { -//! let err = cx.string(err.to_string()); -//! vec![ -//! err.upcast::(), -//! ] +//! use neon::types::extract::Error; +//! callback +//! .bind(&mut cx) +//! .arg(Error::from(err))? +//! .exec()?; //! } -//! }; -//! -//! callback.call(&mut cx, this, args)?; +//! } //! //! Ok(()) //! }); diff --git a/crates/neon/src/handle/mod.rs b/crates/neon/src/handle/mod.rs index 41654b253..569326e36 100644 --- a/crates/neon/src/handle/mod.rs +++ b/crates/neon/src/handle/mod.rs @@ -27,22 +27,18 @@ //! //! ## Example //! -//! This Neon function takes an object as its argument, extracts two properties, -//! `width` and `height`, and multiplies them together as numbers. Each JavaScript -//! value in the calculation is stored locally in a `Handle`. +//! This Neon function takes an object as its argument, extracts an object property, +//! `homeAddress`, and then extracts a string property, `zipCode` from that second +//! object. Each JavaScript value in the calculation is stored locally in a `Handle`. //! //! ``` //! # use neon::prelude::*; -//! fn area(mut cx: FunctionContext) -> JsResult { -//! let rect: Handle = cx.argument(0)?; -//! -//! let width: Handle = rect.get(&mut cx, "width")?; -//! let w: f64 = width.value(&mut cx); -//! -//! let height: Handle = rect.get(&mut cx, "height")?; -//! let h: f64 = height.value(&mut cx); -//! -//! Ok(cx.number(w * h)) +//! # use neon::export; +//! #[export] +//! fn customer_zip_code<'cx>(cx: &mut FunctionContext<'cx>, customer: Handle<'cx, JsObject>) -> JsResult<'cx, JsString> { +//! let home_address: Handle = customer.prop(cx, "homeAddress").get()?; +//! let zip_code: Handle = home_address.prop(cx, "zipCode").get()?; +//! Ok(zip_code) //! } //! ``` diff --git a/crates/neon/src/object/mod.rs b/crates/neon/src/object/mod.rs index 319e2d650..ecf142d1c 100644 --- a/crates/neon/src/object/mod.rs +++ b/crates/neon/src/object/mod.rs @@ -16,35 +16,43 @@ //! //! ``` //! # use neon::prelude::*; -//! fn set_and_check<'a>( -//! cx: &mut impl Context<'a>, -//! obj: Handle<'a, JsObject> -//! ) -> JsResult<'a, JsValue> { -//! let value = cx.string("hello!"); +//! fn set_and_check<'cx>( +//! cx: &mut Cx<'cx>, +//! obj: Handle<'cx, JsObject> +//! ) -> JsResult<'cx, JsValue> { //! // set property "17" with integer shorthand -//! obj.set(cx, 17, value)?; +//! obj.prop(cx, 17).set("hello")?; //! // get property "17" with string shorthand //! // returns the same value ("hello!") -//! obj.get(cx, "17") +//! obj.prop(cx, "17").get() //! } //! ``` //! //! [hierarchy]: crate::types#the-javascript-type-hierarchy //! [symbol]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Symbol +use smallvec::smallvec; + use crate::{ - context::Context, + context::{internal::ContextInternal, Context, Cx}, handle::{Handle, Root}, result::{NeonResult, Throw}, sys::{self, raw}, - types::{build, function::CallOptions, utf8::Utf8, JsFunction, JsUndefined, JsValue, Value}, + types::{ + build, + extract::{TryFromJs, TryIntoJs}, + function::{BindOptions, CallOptions}, + private::ValueInternal, + utf8::Utf8, + JsFunction, JsUndefined, JsValue, Value, + }, }; #[cfg(feature = "napi-6")] use crate::{result::JsResult, types::JsArray}; /// A property key in a JavaScript object. -pub trait PropertyKey { +pub trait PropertyKey: Copy { unsafe fn get_from<'c, C: Context<'c>>( self, cx: &mut C, @@ -134,10 +142,160 @@ impl<'a> PropertyKey for &'a str { } } +/// A builder for accessing an object property. +/// +/// The builder methods make it convenient to get and set properties +/// as well as to bind and call methods. +/// ``` +/// # use neon::prelude::*; +/// # fn foo(mut cx: FunctionContext) -> JsResult { +/// # let obj: Handle = cx.argument(0)?; +/// let x: f64 = obj +/// .prop(&mut cx, "x") +/// .get()?; +/// +/// obj.prop(&mut cx, "y") +/// .set(x)?; +/// +/// let s: String = obj.method(&mut cx, "toString")?.call()?; +/// # Ok(cx.string(s)) +/// # } +/// ``` +pub struct PropOptions<'a, 'cx, O, K> +where + 'cx: 'a, + O: Object, + K: PropertyKey, +{ + pub(crate) cx: &'a mut Cx<'cx>, + pub(crate) this: Handle<'cx, O>, + pub(crate) key: K, +} + +impl<'a, 'cx, O, K> PropOptions<'a, 'cx, O, K> +where + 'cx: 'a, + O: Object, + K: PropertyKey, +{ + /// Returns the original object from which the property was accessed. + pub fn this(&self) -> Handle<'cx, O> { + self.this + } + + /// Updates the property key. + /// + /// This method is useful for chaining multiple property assignments: + /// + /// ``` + /// # use neon::prelude::*; + /// # fn foo(mut cx: FunctionContext) -> JsResult { + /// let obj = cx.empty_object() + /// .prop(&mut cx, "x") + /// .set(1)? + /// .prop("y") + /// .set(2)? + /// .prop("color") + /// .set("blue")? + /// .this(); + /// # Ok(obj) + /// # } + /// ``` + pub fn prop(&mut self, key: K) -> &mut Self { + self.key = key; + self + } + + /// Gets the property from the object and attempts to convert it to a Rust value. + /// + /// May throw an exception either during accessing the property or converting the + /// result type. + pub fn get>(&mut self) -> NeonResult { + let v = self.this.get_value(self.cx, self.key)?; + R::from_js(self.cx, v) + } + + /// Sets the property on the object to a value converted from Rust. + /// + /// May throw an exception either during converting the value or setting the property. + pub fn set>(&mut self, v: V) -> NeonResult<&mut Self> { + let v = v.try_into_js(self.cx)?; + self.this.set(self.cx, self.key, v)?; + Ok(self) + } + + /// Sets the property on the object to a value computed from a closure. + /// + /// May throw an exception either during converting the value or setting the property. + pub fn set_with(&mut self, f: F) -> NeonResult<&mut Self> + where + R: TryIntoJs<'cx>, + F: FnOnce(&mut Cx<'cx>) -> R, + { + let v = f(self.cx).try_into_js(self.cx)?; + self.this.set(self.cx, self.key, v)?; + Ok(self) + } + + /// Gets the property from the object as a method and binds `this` to the object. + /// + /// May throw an exception when accessing the property. + /// + /// Defers checking that the method is callable until call time. + pub fn bind(&'a mut self) -> NeonResult> { + let callee: Handle = self.this.get(self.cx, self.key)?; + let this = Some(self.this.upcast()); + Ok(BindOptions { + cx: self.cx, + callee, + this, + args: smallvec![], + }) + } +} + /// The trait of all object types. pub trait Object: Value { - /// Gets a property from a JavaScript object that may be `undefined` and - /// attempts to downcast the value if it existed. + /// Create a [`PropOptions`] for accessing a property. + /// + /// # Safety + /// + /// Because `cx` is a mutable reference, Neon guarantees it + /// is the context with the shortest possible lifetime, so + /// replacing the lifetime `'self` with `'cx` cannot extend + /// the lifetime of the property beyond the lifetime of the + /// object. + fn prop<'a, 'cx: 'a, K: PropertyKey>( + &self, + cx: &'a mut Cx<'cx>, + key: K, + ) -> PropOptions<'a, 'cx, Self, K> { + let this: Handle<'_, Self> = + Handle::new_internal(unsafe { ValueInternal::from_local(cx.env(), self.to_local()) }); + PropOptions { cx, this, key } + } + + /// Gets a property from the object as a method and binds `this` to the object. + /// + /// May throw an exception either from accessing the property. + /// + /// Defers checking that the method is callable until call time. + fn method<'a, 'cx: 'a, K: PropertyKey>( + &self, + cx: &'a mut Cx<'cx>, + key: K, + ) -> NeonResult> { + let callee: Handle = self.prop(cx, key).get()?; + let this = Some(self.as_value(cx)); + Ok(BindOptions { + cx, + callee, + this, + args: smallvec![], + }) + } + + #[deprecated(since = "TBD", note = "use `Object::prop()` instead")] fn get_opt<'a, V: Value, C: Context<'a>, K: PropertyKey>( &self, cx: &mut C, @@ -152,10 +310,7 @@ pub trait Object: Value { v.downcast_or_throw(cx).map(Some) } - /// Gets a property from a JavaScript object as a [`JsValue`]. - /// - /// If a [`getter`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Functions/get) - /// is defined on the object, it will be called. + #[deprecated(since = "TBD", note = "use `Object::prop()` instead")] fn get_value<'a, C: Context<'a>, K: PropertyKey>( &self, cx: &mut C, @@ -166,10 +321,7 @@ pub trait Object: Value { }) } - /// Gets a property from a JavaScript object and attempts to downcast as a specific type. - /// Equivalent to calling `obj.get_value(&mut cx)?.downcast_or_throw(&mut cx)`. - /// - /// Throws an exception if the value is a different type. + #[deprecated(since = "TBD", note = "use `Object::prop()` instead")] fn get<'a, V: Value, C: Context<'a>, K: PropertyKey>( &self, cx: &mut C, @@ -214,6 +366,7 @@ pub trait Object: Value { } } + #[deprecated(since = "TBD", note = "use `Object::prop()` instead")] fn set<'a, C: Context<'a>, K: PropertyKey, W: Value>( &self, cx: &mut C, @@ -234,6 +387,7 @@ pub trait Object: Value { Root::new(cx, self) } + #[deprecated(since = "TBD", note = "use `Object::method()` instead")] fn call_method_with<'a, C, K>(&self, cx: &mut C, method: K) -> NeonResult> where C: Context<'a>, diff --git a/crates/neon/src/result/mod.rs b/crates/neon/src/result/mod.rs index 581995192..c65da4709 100644 --- a/crates/neon/src/result/mod.rs +++ b/crates/neon/src/result/mod.rs @@ -26,8 +26,7 @@ //! # use neon::prelude::*; //! fn get_message(mut cx: FunctionContext) -> JsResult { //! let obj: Handle = cx.argument(0)?; -//! let prop: Handle = obj.get(&mut cx, "message")?; -//! Ok(prop) +//! obj.prop(&mut cx, "message").get() //! } //! ``` //! diff --git a/crates/neon/src/sys/fun.rs b/crates/neon/src/sys/fun.rs index e4a709d39..9d9d461c1 100644 --- a/crates/neon/src/sys/fun.rs +++ b/crates/neon/src/sys/fun.rs @@ -83,34 +83,14 @@ where callback(env, info) } -pub unsafe fn call( - out: &mut Local, - env: Env, - fun: Local, - this: Local, - argc: i32, - argv: *const c_void, -) -> bool { - let status = napi::call_function( - env, - this, - fun, - argc as usize, - argv as *const _, - out as *mut _, - ); - - status == napi::Status::Ok -} - pub unsafe fn construct( out: &mut Local, env: Env, fun: Local, - argc: i32, + argc: usize, argv: *const c_void, ) -> bool { - let status = napi::new_instance(env, fun, argc as usize, argv as *const _, out as *mut _); + let status = napi::new_instance(env, fun, argc, argv as *const _, out as *mut _); status == napi::Status::Ok } diff --git a/crates/neon/src/thread/mod.rs b/crates/neon/src/thread/mod.rs index f4d7ad1e3..ca8153558 100644 --- a/crates/neon/src/thread/mod.rs +++ b/crates/neon/src/thread/mod.rs @@ -16,11 +16,12 @@ //! pub fn thread_id(cx: &mut Cx) -> NeonResult { //! THREAD_ID.get_or_try_init(cx, |cx| { //! let require: Handle = cx.global("require")?; -//! let worker: Handle = require.call_with(cx) -//! .arg(cx.string("node:worker_threads")) -//! .apply(cx)?; -//! let thread_id: Handle = worker.get(cx, "threadId")?; -//! Ok(thread_id.value(cx) as u32) +//! let worker: Handle = require +//! .bind(cx) +//! .arg("node:worker_threads")? +//! .call()?; +//! let thread_id: f64 = worker.prop(cx, "threadId").get()?; +//! Ok(thread_id as u32) //! }).cloned() //! } //! ``` diff --git a/crates/neon/src/types_impl/boxed.rs b/crates/neon/src/types_impl/boxed.rs index ed73fce17..3b63bb70a 100644 --- a/crates/neon/src/types_impl/boxed.rs +++ b/crates/neon/src/types_impl/boxed.rs @@ -293,18 +293,10 @@ impl Deref for JsBox { /// /// impl Finalize for Point { /// fn finalize<'a, C: Context<'a>>(self, cx: &mut C) { -/// let global = cx.global_object(); -/// let emit: Handle = global -/// .get(cx, "emit") -/// .unwrap(); -/// -/// let args = vec![ -/// cx.string("gc_point").upcast::(), -/// cx.number(self.0).upcast(), -/// cx.number(self.1).upcast(), -/// ]; -/// -/// emit.call(cx, global, args).unwrap(); +/// cx.global_object() +/// .method(cx.cx_mut(), "emit").unwrap() +/// .args(("gc_point", self.0, self.1)).unwrap() +/// .exec().unwrap(); /// } /// } /// ``` diff --git a/crates/neon/src/types_impl/error.rs b/crates/neon/src/types_impl/error.rs index 98fc404b8..7e723085a 100644 --- a/crates/neon/src/types_impl/error.rs +++ b/crates/neon/src/types_impl/error.rs @@ -24,11 +24,8 @@ use crate::{ /// let err = cx.type_error("expected a number, found a string")?; /// /// // Add some custom diagnostic properties to the error: -/// let expected = cx.string("number"); -/// err.set(&mut cx, "expected", expected)?; -/// -/// let found = cx.string("string"); -/// err.set(&mut cx, "found", found)?; +/// err.prop(&mut cx, "expected").set("number")?; +/// err.prop(&mut cx, "found").set("string")?; /// /// // Throw the error: /// cx.throw(err)?; diff --git a/crates/neon/src/types_impl/extract/with.rs b/crates/neon/src/types_impl/extract/with.rs index 0f631fe8a..bfce5c03f 100644 --- a/crates/neon/src/types_impl/extract/with.rs +++ b/crates/neon/src/types_impl/extract/with.rs @@ -20,9 +20,9 @@ use crate::{context::Cx, result::JsResult, types::extract::TryIntoJs}; /// /// With(move |cx| -> NeonResult<_> { /// cx.global::("console")? -/// .call_method_with(cx, "log")? -/// .arg(cx.string(log)) -/// .exec(cx)?; +/// .method(cx, "log")? +/// .arg(&log)? +/// .exec()?; /// /// Ok(sum) /// }) diff --git a/crates/neon/src/types_impl/function/mod.rs b/crates/neon/src/types_impl/function/mod.rs index d5c52523e..bd3b8e0a4 100644 --- a/crates/neon/src/types_impl/function/mod.rs +++ b/crates/neon/src/types_impl/function/mod.rs @@ -3,15 +3,106 @@ use smallvec::smallvec; use crate::{ - context::Context, + context::{Context, Cx}, handle::Handle, object::Object, result::{JsResult, NeonResult}, - types::{JsFunction, JsObject, JsValue, Value}, + types::{ + extract::{TryFromJs, TryIntoJs, With}, + private::ValueInternal, + 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 parse_int: Handle = cx.global("parseInt")?; +/// let x: f64 = parse_int +/// .bind(&mut cx) +/// .arg("42")? +/// .call()?; +/// # Ok(cx.number(x)) +/// # } +/// ``` +pub struct BindOptions<'a, 'cx: 'a> { + pub(crate) cx: &'a mut Cx<'cx>, + pub(crate) callee: Handle<'cx, JsValue>, + pub(crate) this: Option>, + pub(crate) args: private::ArgsVec<'cx>, +} + +impl<'a, 'cx: 'a> BindOptions<'a, 'cx> { + /// Set the value of `this` for the function call. + pub fn this>(&mut self, this: T) -> NeonResult<&mut Self> { + let v = this.try_into_js(self.cx)?; + self.this = Some(v.upcast()); + Ok(self) + } + + /// Replaces the arguments list with the given arguments. + pub fn args>(&mut self, a: A) -> NeonResult<&mut Self> { + self.args = a.try_into_args_vec(self.cx)?; + Ok(self) + } + + /// Replaces the arguments list with a list computed from a closure. + pub fn args_with(&mut self, f: F) -> NeonResult<&mut Self> + where + R: TryIntoArguments<'cx>, + F: FnOnce(&mut Cx<'cx>) -> R, + { + self.args = f(self.cx).try_into_args_vec(self.cx)?; + Ok(self) + } + + /// Add an argument to the arguments list. + pub fn arg>(&mut self, a: A) -> NeonResult<&mut Self> { + let v = a.try_into_js(self.cx)?; + self.args.push(v.upcast()); + Ok(self) + } + + /// Add an argument to the arguments list, computed from a closure. + pub fn arg_with(&mut self, f: F) -> NeonResult<&mut Self> + where + R: TryIntoJs<'cx>, + F: FnOnce(&mut Cx<'cx>) -> R, + { + let v = f(self.cx).try_into_js(self.cx)?; + self.args.push(v.upcast()); + Ok(self) + } + + /// Make the function call. If the function returns without throwing, the result value + /// is converted to a Rust value with `TryFromJs::from_js`. + pub fn call>(&mut self) -> NeonResult { + let this = self.this.unwrap_or_else(|| self.cx.undefined().upcast()); + let v: Handle = unsafe { self.callee.try_call(self.cx, this, &self.args)? }; + R::from_js(self.cx, v) + } + + /// Make the function call as a constructor. If the function returns without throwing, the + /// result value is converted to a Rust value with `TryFromJs::from_js`. + pub fn construct>(&mut self) -> NeonResult { + let v: Handle = unsafe { self.callee.try_construct(self.cx, &self.args)? }; + R::from_js(self.cx, v) + } + + /// Make the function call for side effect, discarding the result value. This method is + /// preferable to [`call()`](BindOptions::call) when the result value isn't needed, + /// since it doesn't require specifying a result type. + pub fn exec(&mut self) -> NeonResult<()> { + let _ignore: Handle = self.call()?; + Ok(()) + } +} + /// A builder for making a JavaScript function call like `parseInt("42")`. /// /// The builder methods make it convenient to assemble the call from parts: @@ -26,6 +117,7 @@ pub(crate) mod private; /// # Ok(x) /// # } /// ``` +#[deprecated(since = "TBD", note = "use `JsFunction::bind()` instead")] #[derive(Clone)] pub struct CallOptions<'a> { pub(crate) callee: Handle<'a, JsFunction>, @@ -84,6 +176,7 @@ impl<'a> CallOptions<'a> { /// # Ok(obj) /// # } /// ``` +#[deprecated(since = "TBD", note = "use `JsFunction::bind()` instead")] #[derive(Clone)] pub struct ConstructOptions<'a> { pub(crate) callee: Handle<'a, JsFunction>, @@ -111,6 +204,110 @@ impl<'a> ConstructOptions<'a> { } } +/// The trait for specifying values to be converted into arguments for a function call. +/// 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 TryIntoArguments<'cx>: private::TryIntoArgumentsInternal<'cx> {} + +impl<'cx> private::TryIntoArgumentsInternal<'cx> for () { + fn try_into_args_vec(self, _cx: &mut Cx<'cx>) -> NeonResult> { + Ok(smallvec![]) + } +} + +impl<'cx, F, O> private::TryIntoArgumentsInternal<'cx> for With +where + F: FnOnce(&mut Cx) -> O, + O: private::TryIntoArgumentsInternal<'cx>, +{ + fn try_into_args_vec(self, cx: &mut Cx<'cx>) -> NeonResult> { + (self.0)(cx).try_into_args_vec(cx) + } +} + +impl<'cx, F, O> TryIntoArguments<'cx> for With +where + F: FnOnce(&mut Cx) -> O, + O: TryIntoArguments<'cx>, +{ +} + +impl<'cx, T, E> private::TryIntoArgumentsInternal<'cx> for Result +where + T: private::TryIntoArgumentsInternal<'cx>, + E: TryIntoJs<'cx>, +{ + fn try_into_args_vec(self, cx: &mut Cx<'cx>) -> NeonResult> { + match self { + Ok(v) => v.try_into_args_vec(cx), + Err(err) => err.try_into_js(cx).and_then(|err| cx.throw(err)), + } + } +} + +impl<'cx, T, E> TryIntoArguments<'cx> for Result +where + T: TryIntoArguments<'cx>, + E: TryIntoJs<'cx>, +{ +} + +macro_rules! impl_into_arguments_expand { + { + $(#[$attrs:meta])? + [ $($prefix:ident ),* ]; + []; + } => {}; + + { + $(#[$attrs:meta])? + [ $($prefix:ident),* ]; + [ $head:ident $(, $tail:ident)* ]; + } => { + $(#[$attrs])? + impl<'cx, $($prefix: TryIntoJs<'cx> + 'cx, )* $head: TryIntoJs<'cx> + 'cx> private::TryIntoArgumentsInternal<'cx> for ($($prefix, )* $head, ) { + #[allow(non_snake_case)] + fn try_into_args_vec(self, cx: &mut Cx<'cx>) -> NeonResult> { + let ($($prefix, )* $head, ) = self; + Ok(smallvec![ $($prefix.try_into_js(cx)?.upcast(),)* $head.try_into_js(cx)?.upcast() ]) + } + } + + $(#[$attrs])? + impl<'cx, $($prefix: TryIntoJs<'cx> + 'cx, )* $head: TryIntoJs<'cx> + 'cx> TryIntoArguments<'cx> for ($($prefix, )* $head, ) {} + + impl_into_arguments_expand! { + $(#[$attrs])? + [ $($prefix, )* $head ]; + [ $($tail),* ]; + } + } +} + +macro_rules! impl_into_arguments { + { + [ $($show:ident),* ]; + [ $($hide:ident),* ]; + } => { + impl_into_arguments_expand! { []; [ $($show),* ]; } + impl_into_arguments_expand! { #[doc(hidden)] [ $($show),* ]; [ $($hide),* ]; } + } +} + +impl_into_arguments! { + // Tuples up to length 8 are included in the docs. + [V1, V2, V3, V4, V5, V6, V7, V8]; + + // Tuples up to length 32 are not included in the docs. + [ + V9, V10, V11, V12, V13, V14, V15, V16, + V17, V18, V19, V20, V21, V22, V23, V24, + V25, V26, V27, V28, V29, V30, V31, V32 + ]; +} + /// The trait for specifying arguments for a function call. This trait is sealed and cannot /// be implemented by types outside of the Neon crate. /// @@ -126,92 +323,56 @@ impl<'a> private::ArgumentsInternal<'a> for () { impl<'a> Arguments<'a> for () {} -macro_rules! impl_arguments { +macro_rules! impl_arguments_expand { { - [ $(($tprefix:ident, $vprefix:ident), )* ]; + $(#[$attrs:meta])? + [ $($prefix:ident),* ]; []; } => {}; { - [ $(($tprefix:ident, $vprefix:ident), )* ]; - [ $(#[$attr1:meta])? ($tname1:ident, $vname1:ident), $($(#[$attrs:meta])? ($tnames:ident, $vnames:ident), )* ]; + $(#[$attrs:meta])? + [ $($prefix:ident),* ]; + [ $head:ident $(, $tail:ident)* ]; } => { - $(#[$attr1])? - impl<'a, $($tprefix: Value, )* $tname1: Value> private::ArgumentsInternal<'a> for ($(Handle<'a, $tprefix>, )* Handle<'a, $tname1>, ) { + $(#[$attrs])? + impl<'a, $($prefix: Value, )* $head: Value> private::ArgumentsInternal<'a> for ($(Handle<'a, $prefix>, )* Handle<'a, $head>, ) { + #[allow(non_snake_case)] fn into_args_vec(self) -> private::ArgsVec<'a> { - let ($($vprefix, )* $vname1, ) = self; - smallvec![$($vprefix.upcast(),)* $vname1.upcast()] + let ($($prefix, )* $head, ) = self; + smallvec![$($prefix.upcast(),)* $head.upcast()] } } - $(#[$attr1])? - impl<'a, $($tprefix: Value, )* $tname1: Value> Arguments<'a> for ($(Handle<'a, $tprefix>, )* Handle<'a, $tname1>, ) {} + $(#[$attrs])? + impl<'a, $($prefix: Value, )* $head: Value> Arguments<'a> for ($(Handle<'a, $prefix>, )* Handle<'a, $head>, ) {} - impl_arguments! { - [ $(($tprefix, $vprefix), )* ($tname1, $vname1), ]; - [ $($(#[$attrs])? ($tnames, $vnames), )* ]; + impl_arguments_expand! { + $(#[$attrs])? + [ $($prefix, )* $head ]; + [ $($tail),* ]; } - }; - } + }; +} + +macro_rules! impl_arguments { + { + [ $($show:ident),* ]; + [ $($hide:ident),* ]; + } => { + impl_arguments_expand! { []; [ $($show),* ]; } + impl_arguments_expand! { #[doc(hidden)] [ $($show),* ]; [ $($hide),* ]; } + } +} impl_arguments! { - []; + // Tuples up to length 8 are included in the docs. + [V1, V2, V3, V4, V5, V6, V7, V8]; + + // Tuples up to length 32 are not included in the docs. [ - (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), + V9, V10, V11, V12, V13, V14, V15, V16, + V17, V18, V19, V20, V21, V22, V23, V24, + V25, V26, V27, V28, V29, V30, V31, V32 ]; } diff --git a/crates/neon/src/types_impl/function/private.rs b/crates/neon/src/types_impl/function/private.rs index 21a776191..c0b079ec1 100644 --- a/crates/neon/src/types_impl/function/private.rs +++ b/crates/neon/src/types_impl/function/private.rs @@ -1,9 +1,14 @@ use smallvec::SmallVec; -use crate::{handle::Handle, types::JsValue}; +use crate::{context::Cx, handle::Handle, result::NeonResult, types::JsValue}; pub type ArgsVec<'a> = SmallVec<[Handle<'a, JsValue>; 8]>; +/// This type marks the `TryIntoArguments` trait as sealed. +pub trait TryIntoArgumentsInternal<'cx> { + fn try_into_args_vec(self, cx: &mut Cx<'cx>) -> NeonResult>; +} + /// This type marks the `Arguments` trait as sealed. pub trait ArgumentsInternal<'a> { fn into_args_vec(self) -> ArgsVec<'a>; diff --git a/crates/neon/src/types_impl/mod.rs b/crates/neon/src/types_impl/mod.rs index 45837cfb3..b0a91713c 100644 --- a/crates/neon/src/types_impl/mod.rs +++ b/crates/neon/src/types_impl/mod.rs @@ -18,13 +18,13 @@ pub(crate) mod utf8; use std::{ any, fmt::{self, Debug}, - os::raw::c_void, }; +use private::prepare_call; use smallvec::smallvec; use crate::{ - context::{internal::Env, Context, FunctionContext}, + context::{internal::Env, Context, Cx, FunctionContext}, handle::{ internal::{SuperType, TransparentNoCopyWrapper}, Handle, @@ -33,7 +33,7 @@ use crate::{ result::{JsResult, NeonResult, ResultExt, Throw}, sys::{self, raw}, types::{ - function::{CallOptions, ConstructOptions}, + function::{BindOptions, CallOptions, ConstructOptions}, private::ValueInternal, utf8::Utf8, }, @@ -208,7 +208,7 @@ impl JsValue { /// let undefined = cx.undefined(); /// /// // Call console.log(undefined): -/// console.call_method_with(&mut cx, "log")?.arg(undefined).exec(&mut cx)?; +/// console.method(&mut cx, "log")?.arg(undefined)?.exec()?; /// # Ok(undefined) /// # } /// ``` @@ -273,11 +273,12 @@ impl ValueInternal for JsUndefined { /// ``` /// # use neon::prelude::*; /// # fn test(mut cx: FunctionContext) -> JsResult { +/// let null = cx.null(); /// cx.global::("console")? -/// .call_method_with(&mut cx, "log")? -/// .arg(cx.null()) -/// .exec(&mut cx)?; -/// # Ok(cx.null()) +/// .method(&mut cx, "log")? +/// .arg(null)? +/// .exec()?; +/// # Ok(null) /// # } /// ``` #[derive(Debug)] @@ -341,16 +342,12 @@ impl ValueInternal for JsNull { /// ``` /// # use neon::prelude::*; /// # fn test(mut cx: FunctionContext) -> JsResult { -/// // Extract the console.log function: -/// let console: Handle = cx.global("console")?; -/// let log: Handle = console.get(&mut cx, "log")?; -/// /// // The two Boolean values: /// let t = cx.boolean(true); /// let f = cx.boolean(false); /// /// // Call console.log(true, false): -/// log.call_with(&cx).arg(t).arg(f).exec(&mut cx)?; +/// cx.global::("console")?.method(&mut cx, "log")?.args((t, f))?.exec()?; /// # Ok(cx.undefined()) /// # } /// ``` @@ -418,15 +415,11 @@ impl ValueInternal for JsBoolean { /// ``` /// # use neon::prelude::*; /// # fn test(mut cx: FunctionContext) -> JsResult { -/// // Extract the console.log function: -/// let console: Handle = cx.global("console")?; -/// let log: Handle = console.get(&mut cx, "log")?; -/// /// // Create a string: /// let s = cx.string("hello 🥹"); /// /// // Call console.log(s): -/// log.call_with(&cx).arg(s).exec(&mut cx)?; +/// cx.global::("console")?.method(&mut cx, "log")?.arg(s)?.exec()?; /// # Ok(cx.undefined()) /// # } /// ``` @@ -695,15 +688,11 @@ impl JsString { /// ``` /// # use neon::prelude::*; /// # fn test(mut cx: FunctionContext) -> JsResult { -/// // Extract the console.log function: -/// let console: Handle = cx.global("console")?; -/// let log: Handle = console.get(&mut cx, "log")?; -/// /// // Create a number: /// let n = cx.number(17.0); /// /// // Call console.log(n): -/// log.call_with(&cx).arg(n).exec(&mut cx)?; +/// cx.global::("console")?.method(&mut cx, "log")?.arg(n)?.exec()?; /// # Ok(cx.undefined()) /// # } /// ``` @@ -771,21 +760,16 @@ impl ValueInternal for JsNumber { /// ``` /// # use neon::prelude::*; /// # fn test(mut cx: FunctionContext) -> JsResult { -/// // Extract the console.log function: -/// let console: Handle = cx.global("console")?; -/// let log: Handle = console.get(&mut cx, "log")?; -/// /// // Create an object: -/// let obj = cx.empty_object(); -/// -/// let name = cx.string("Neon"); -/// obj.set(&mut cx, "name", name)?; -/// -/// let url = cx.string("https://neon-bindings.com"); -/// obj.set(&mut cx, "url", url)?; +/// let obj = cx.empty_object() +/// .prop(&mut cx, "name") +/// .set("Neon")? +/// .prop("url") +/// .set("https://neon-bindings.com")? +/// .this(); /// /// // Call console.log(obj): -/// log.call_with(&cx).arg(obj).exec(&mut cx)?; +/// cx.global::("console")?.method(&mut cx, "log")?.arg(obj)?.exec()?; /// # Ok(cx.undefined()) /// # } /// ``` @@ -860,13 +844,9 @@ impl JsObject { /// // Create a new empty array: /// let a: Handle = cx.empty_array(); /// -/// // Create some new values to push onto the array: -/// let n = cx.number(17); -/// let s = cx.string("hello"); -/// -/// // Push the elements onto the array: -/// a.set(&mut cx, 0, n)?; -/// a.set(&mut cx, 1, s)?; +/// // Push some values onto the array: +/// a.prop(&mut cx, 0).set(17)?; +/// a.prop(&mut cx, 1).set("hello")?; /// # Ok(a) /// # } /// ``` @@ -979,7 +959,7 @@ impl Object for JsArray {} /// ## Calling functions /// /// Neon provides a convenient syntax for calling JavaScript functions with the -/// [`call_with()`](JsFunction::call_with) method, which produces a [`CallOptions`](CallOptions) +/// [`bind()`](JsFunction::bind) method, which produces a [`BindOptions`](BindOptions) /// struct that can be used to provide the function arguments (and optionally, the binding for /// `this`) before calling the function: /// ``` @@ -990,9 +970,9 @@ impl Object for JsArray {} /// /// // Call parseInt("42") /// let x: Handle = parse_int -/// .call_with(&mut cx) -/// .arg(cx.string("42")) -/// .apply(&mut cx)?; +/// .bind(&mut cx) +/// .arg("42")? +/// .call()?; /// # Ok(x) /// # } /// ``` @@ -1001,7 +981,7 @@ impl Object for JsArray {} /// /// 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: +/// [`construct()`](BindOptions::construct) method: /// ``` /// # use neon::prelude::*; /// # fn foo(mut cx: FunctionContext) -> JsResult { @@ -1010,9 +990,9 @@ impl Object for JsArray {} /// /// // Call new URL("https://neon-bindings.com") /// let obj = url -/// .construct_with(&cx) -/// .arg(cx.string("https://neon-bindings.com")) -/// .apply(&mut cx)?; +/// .bind(&mut cx) +/// .arg("https://neon-bindings.com")? +/// .construct()?; /// # Ok(obj) /// # } /// ``` @@ -1044,24 +1024,6 @@ pub struct JsFunction { 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>>( - cx: &mut C, - args: &[Handle<'b, JsValue>], -) -> NeonResult<(i32, *const c_void)> { - // Note: This cast is only save because `Handle<'_, JsValue>` is - // guaranteed to have the same layout as a pointer because `Handle` - // and `JsValue` are both `repr(C)` newtypes. - let argv = args.as_ptr().cast(); - let argc = args.len(); - if argc > V8_ARGC_LIMIT { - return cx.throw_range_error("too many arguments"); - } - Ok((argc as i32, argv)) -} - impl JsFunction { #[cfg(not(feature = "napi-5"))] /// Returns a new `JsFunction` implemented by `f`. @@ -1158,6 +1120,7 @@ impl JsFunction { /// Calls this function. /// /// **See also:** [`JsFunction::call_with`]. + #[deprecated(since = "TBD", note = "use `JsFunction::bind` instead")] pub fn call<'a, 'b, C: Context<'a>, T, AS>( &self, cx: &mut C, @@ -1168,16 +1131,13 @@ impl JsFunction { T: Value, AS: AsRef<[Handle<'b, JsValue>]>, { - let (argc, argv) = unsafe { prepare_call(cx, args.as_ref()) }?; - let env = cx.env().to_raw(); - build(cx.env(), |out| unsafe { - sys::fun::call(out, env, self.to_local(), this.to_local(), argc, argv) - }) + unsafe { self.try_call(cx, this, args) } } /// Calls this function for side effect, discarding its result. /// /// **See also:** [`JsFunction::call_with`]. + #[deprecated(since = "TBD", note = "use `JsFunction::bind` instead")] pub fn exec<'a, 'b, C: Context<'a>, T, AS>( &self, cx: &mut C, @@ -1195,6 +1155,7 @@ impl JsFunction { /// Calls this function as a constructor. /// /// **See also:** [`JsFunction::construct_with`]. + #[deprecated(since = "TBD", note = "use `JsFunction::bind` instead")] pub fn construct<'a, 'b, C: Context<'a>, AS>( &self, cx: &mut C, @@ -1211,8 +1172,35 @@ impl JsFunction { } } +impl JsFunction { + /// Create a [`BindOptions`] builder for calling this function. + /// + /// The builder methods make it convenient to assemble the call from parts: + /// ``` + /// # use neon::prelude::*; + /// # fn foo(mut cx: FunctionContext) -> JsResult { + /// # let parse_int: Handle = cx.global("parseInt")?; + /// let x: f64 = parse_int + /// .bind(&mut cx) + /// .arg("42")? + /// .call()?; + /// # Ok(cx.number(x)) + /// # } + /// ``` + pub fn bind<'a, 'cx: 'a>(&self, cx: &'a mut Cx<'cx>) -> BindOptions<'a, 'cx> { + let callee = self.as_value(cx); + BindOptions { + cx, + callee, + this: None, + args: smallvec![], + } + } +} + impl JsFunction { /// Create a [`CallOptions`](function::CallOptions) for calling this function. + #[deprecated(since = "TBD", note = "use `JsFunction::bind` instead")] pub fn call_with<'a, C: Context<'a>>(&self, _cx: &C) -> CallOptions<'a> { CallOptions { this: None, @@ -1227,6 +1215,7 @@ impl JsFunction { /// Create a [`ConstructOptions`](function::ConstructOptions) for calling this function /// as a constructor. + #[deprecated(since = "TBD", note = "use `JsFunction::bind` instead")] pub fn construct_with<'a, C: Context<'a>>(&self, _cx: &C) -> ConstructOptions<'a> { ConstructOptions { // # Safety diff --git a/crates/neon/src/types_impl/private.rs b/crates/neon/src/types_impl/private.rs index 774dd0bcb..e213530c0 100644 --- a/crates/neon/src/types_impl/private.rs +++ b/crates/neon/src/types_impl/private.rs @@ -1,10 +1,33 @@ +use std::{ffi::c_void, mem::MaybeUninit}; + use crate::{ - context::internal::Env, + context::{internal::Env, Context}, handle::{internal::TransparentNoCopyWrapper, Handle}, - sys::raw, + result::{JsResult, NeonResult, Throw}, + sys::{self, bindings as napi, raw}, types::Value, }; +use super::JsValue; + +// Maximum number of function arguments in V8. +const V8_ARGC_LIMIT: usize = 65535; + +pub(crate) unsafe fn prepare_call<'a, 'b, C: Context<'a>>( + cx: &mut C, + args: &[Handle<'b, JsValue>], +) -> NeonResult<(usize, *const c_void)> { + // Note: This cast is only save because `Handle<'_, JsValue>` is + // guaranteed to have the same layout as a pointer because `Handle` + // and `JsValue` are both `repr(C)` newtypes. + let argv = args.as_ptr().cast(); + let argc = args.len(); + if argc > V8_ARGC_LIMIT { + return cx.throw_range_error("too many arguments"); + } + Ok((argc, argv)) +} + pub trait ValueInternal: TransparentNoCopyWrapper + 'static { fn name() -> &'static str; @@ -29,4 +52,79 @@ pub trait ValueInternal: TransparentNoCopyWrapper + 'static { // # Safety // JavaScript value must be of type `Self` unsafe fn from_local(env: Env, h: raw::Local) -> Self; + + unsafe fn try_call<'a, 'b, C: Context<'a>, T, AS>( + &self, + cx: &mut C, + this: Handle<'b, T>, + args: AS, + ) -> JsResult<'a, JsValue> + where + T: Value, + AS: AsRef<[Handle<'b, JsValue>]>, + { + let callee = self.to_local(); + let (argc, argv) = unsafe { prepare_call(cx, args.as_ref()) }?; + let env = cx.env(); + let mut result: MaybeUninit = MaybeUninit::zeroed(); + + let status = napi::call_function( + env.to_raw(), + this.to_local(), + callee, + argc, + argv.cast(), + result.as_mut_ptr(), + ); + + check_call_status(cx, callee, status)?; + + Ok(Handle::new_internal(JsValue::from_local( + env, + result.assume_init(), + ))) + } + + unsafe fn try_construct<'a, 'b, C: Context<'a>, AS>( + &self, + cx: &mut C, + args: AS, + ) -> JsResult<'a, JsValue> + where + AS: AsRef<[Handle<'b, JsValue>]>, + { + let callee = self.to_local(); + let (argc, argv) = unsafe { prepare_call(cx, args.as_ref()) }?; + let env = cx.env(); + let mut result: MaybeUninit = MaybeUninit::zeroed(); + let status = + napi::new_instance(env.to_raw(), callee, argc, argv.cast(), result.as_mut_ptr()); + + check_call_status(cx, callee, status)?; + + Ok(Handle::new_internal(JsValue::from_local( + env, + result.assume_init(), + ))) + } +} + +unsafe fn check_call_status<'a, C: Context<'a>>( + cx: &mut C, + callee: raw::Local, + status: sys::Status, +) -> NeonResult<()> { + match status { + sys::Status::InvalidArg if !sys::tag::is_function(cx.env().to_raw(), callee) => { + return cx.throw_error("not a function"); + } + sys::Status::PendingException => { + return Err(Throw::new()); + } + status => { + assert_eq!(status, sys::Status::Ok); + } + } + + Ok(()) } diff --git a/crates/neon/src/types_impl/promise.rs b/crates/neon/src/types_impl/promise.rs index 1d79735e5..af5de8e62 100644 --- a/crates/neon/src/types_impl/promise.rs +++ b/crates/neon/src/types_impl/promise.rs @@ -123,10 +123,12 @@ const BOUNDARY: FailureBoundary = FailureBoundary { /// .promise(|mut cx, (indices, kinds)| { /// let indices = JsUint32Array::from_slice(&mut cx, &indices)?; /// let kinds = JsUint8Array::from_slice(&mut cx, &kinds)?; -/// let result = cx.empty_object(); -/// result.set(&mut cx, "indices", indices)?; -/// result.set(&mut cx, "kinds", kinds)?; -/// Ok(result) +/// Ok(cx.empty_object() +/// .prop(&mut cx, "indices") +/// .set(indices)? +/// .prop("kinds") +/// .set(kinds)? +/// .this()) /// }); /// /// Ok(promise) diff --git a/test/napi/lib/functions.js b/test/napi/lib/functions.js index bf5077c97..5012b74fb 100644 --- a/test/napi/lib/functions.js +++ b/test/napi/lib/functions.js @@ -1,6 +1,21 @@ var addon = require(".."); var assert = require("chai").assert; +const STRICT = function () { + "use strict"; + return this; +}; +const SLOPPY = Function("return this;"); + +function isStrict(f) { + try { + f.caller; + return false; + } catch (e) { + return true; + } +} + describe("JsFunction", function () { it("return a JsFunction built in Rust", function () { assert.isFunction(addon.return_js_function()); @@ -28,6 +43,87 @@ describe("JsFunction", function () { ); }); + it("call a JsFunction built in JS with .bind().apply()", function () { + assert.equal( + addon.call_js_function_with_bind(function (a, b, c, d, e) { + return a * b * c * d * e; + }), + 1 * 2 * 3 * 4 * 5 + ); + }); + + it("call a JsFunction build in JS with .bind and .args_with", function () { + assert.equal( + addon.call_js_function_with_bind_and_args_with(function (a, b, c) { + return a + b + c; + }), + 1 + 2 + 3 + ); + }); + + it("call a JsFunction build in JS with .bind and .args and With", function () { + assert.equal( + addon.call_js_function_with_bind_and_args_and_with(function (a, b, c) { + return a + b + c; + }), + 1 + 2 + 3 + ); + }); + + it("call parseInt with .bind().apply()", function () { + assert.equal(addon.call_parse_int_with_bind(), 42); + }); + + it("call a JsFunction built in JS with .bind and .exec", function () { + let local = 41; + addon.call_js_function_with_bind_and_exec(function (x) { + local += x; + }); + assert.equal(local, 42); + }); + + it("call a JsFunction built in JS as a constructor with .bind and .construct", function () { + function MyClass(number, string) { + this.number = number; + this.string = string; + } + + const obj = addon.call_js_constructor_with_bind(MyClass); + + assert.instanceOf(obj, MyClass); + assert.equal(obj.number, 42); + assert.equal(obj.string, "hello"); + }); + + it("bind a JsFunction to an object", function () { + const result = addon.bind_js_function_to_object(function () { + return this.prop; + }); + + assert.equal(result, 42); + }); + + it("bind a strict JsFunction to a number", function () { + assert.isTrue(isStrict(STRICT)); + + // strict mode functions are allowed to have a primitive this binding + const result = addon.bind_js_function_to_number(STRICT); + + assert.strictEqual(result, 42); + }); + + it("bind a sloppy JsFunction to a primitive", function () { + assert.isFalse(isStrict(SLOPPY)); + + // legacy JS functions (aka "sloppy mode") replace primitive this bindings + // with object wrappers, so 42 will get wrapped as new Number(42) + const result = addon.bind_js_function_to_number(SLOPPY); + + assert.instanceOf(result, Number); + assert.strictEqual(typeof result, "object"); + assert.strictEqual(result.valueOf(), 42); + }); + it("call a JsFunction with zero args", function () { assert.equal(addon.call_js_function_with_zero_args(), -Infinity); }); diff --git a/test/napi/lib/objects.js b/test/napi/lib/objects.js index 705fde8b7..1fe346862 100644 --- a/test/napi/lib/objects.js +++ b/test/napi/lib/objects.js @@ -124,4 +124,47 @@ describe("JsObject", function () { assert.strictEqual(addon.call_symbol_method(obj, sym), "hello"); }); + + it("extracts an object property with .prop()", function () { + const obj = { number: 3.141593 }; + + assert.strictEqual(addon.get_property_with_prop(obj), 3.141593); + }); + + it("sets an object property with .prop()", function () { + const obj = { number: 3.141593 }; + + addon.set_property_with_prop(obj); + + assert.strictEqual(obj.number, 42); + }); + + it("calls a method with .prop()", function () { + const obj = { + name: "Diana Prince", + setName(name) { + this.name = name; + }, + toString() { + return `[object ${this.name}]`; + }, + }; + + assert.strictEqual(obj.toString(), "[object Diana Prince]"); + assert.strictEqual( + addon.call_methods_with_prop(obj), + "[object Wonder Woman]" + ); + assert.strictEqual(obj.toString(), "[object Wonder Woman]"); + }); + + it("throws a TypeError when calling a non-method with .prop()", function () { + const obj = { + number: 42, + }; + + assert.throws(() => { + addon.call_non_method_with_prop(obj); + }, /not a function/); + }); }); diff --git a/test/napi/src/js/functions.rs b/test/napi/src/js/functions.rs index 042d0de9b..2abcab8b2 100644 --- a/test/napi/src/js/functions.rs +++ b/test/napi/src/js/functions.rs @@ -1,4 +1,4 @@ -use neon::prelude::*; +use neon::{prelude::*, types::extract::With}; fn add1(mut cx: FunctionContext) -> JsResult { let x = cx.argument::(0)?.value(&mut cx); @@ -26,6 +26,65 @@ pub fn call_js_function_idiomatically(mut cx: FunctionContext) -> JsResult JsResult { + let n: f64 = cx + .argument::(0)? + .bind(&mut cx) + .args((1, 2, 3))? + .arg(4)? + .arg_with(|cx| cx.number(5))? + .call()?; + Ok(cx.number(n)) +} + +pub fn call_js_function_with_bind_and_args_with(mut cx: FunctionContext) -> JsResult { + let n: f64 = cx + .argument::(0)? + .bind(&mut cx) + .args_with(|_| (1, 2, 3))? + .call()?; + Ok(cx.number(n)) +} + +pub fn call_js_function_with_bind_and_args_and_with(mut cx: FunctionContext) -> JsResult { + let n: f64 = cx + .argument::(0)? + .bind(&mut cx) + .args(With(|_| (1, 2, 3)))? + .call()?; + Ok(cx.number(n)) +} + +pub fn call_parse_int_with_bind(mut cx: FunctionContext) -> JsResult { + let parse_int: Handle = cx.global("parseInt")?; + let x: f64 = parse_int.bind(&mut cx).arg("41")?.call()?; + Ok(cx.number(x + 1.0)) +} + +pub fn call_js_function_with_bind_and_exec(mut cx: FunctionContext) -> JsResult { + cx.argument::(0)?.bind(&mut cx).arg(1)?.exec()?; + Ok(cx.undefined()) +} + +pub fn call_js_constructor_with_bind(mut cx: FunctionContext) -> JsResult { + cx.argument::(0)? + .bind(&mut cx) + .args((42, "hello"))? + .construct() +} + +pub fn bind_js_function_to_object(mut cx: FunctionContext) -> JsResult { + let f = cx.argument::(0)?; + let obj = cx.empty_object(); + obj.prop(&mut cx, "prop").set(42)?; + f.bind(&mut cx).this(obj)?.call() +} + +pub fn bind_js_function_to_number(mut cx: FunctionContext) -> JsResult { + let f = cx.argument::(0)?; + f.bind(&mut cx).this(42)?.call() +} + fn get_math_max<'a>(cx: &mut FunctionContext<'a>) -> JsResult<'a, JsFunction> { let math: Handle = cx.global("Math")?; let max: Handle = math.get(cx, "max")?; diff --git a/test/napi/src/js/objects.rs b/test/napi/src/js/objects.rs index 5e9013b6e..32b1f1815 100644 --- a/test/napi/src/js/objects.rs +++ b/test/napi/src/js/objects.rs @@ -95,3 +95,30 @@ pub fn call_symbol_method(mut cx: FunctionContext) -> JsResult { let sym: Handle = cx.argument::(1)?; obj.call_method_with(&mut cx, sym)?.apply(&mut cx) } + +pub fn get_property_with_prop(mut cx: FunctionContext) -> JsResult { + let obj: Handle = cx.argument::(0)?; + let n: f64 = obj.prop(&mut cx, "number").get()?; + Ok(cx.number(n)) +} + +pub fn set_property_with_prop(mut cx: FunctionContext) -> JsResult { + let obj: Handle = cx.argument::(0)?; + obj.prop(&mut cx, "number").set(42)?; + Ok(cx.undefined()) +} + +pub fn call_methods_with_prop(mut cx: FunctionContext) -> JsResult { + let obj: Handle = cx.argument::(0)?; + obj.prop(&mut cx, "setName") + .bind()? + .arg("Wonder Woman")? + .call()?; + obj.prop(&mut cx, "toString").bind()?.call() +} + +pub fn call_non_method_with_prop(mut cx: FunctionContext) -> JsResult { + let obj: Handle = cx.argument::(0)?; + obj.prop(&mut cx, "number").bind()?.call()?; + Ok(cx.undefined()) +} diff --git a/test/napi/src/lib.rs b/test/napi/src/lib.rs index 11890f5a2..3568e7842 100644 --- a/test/napi/src/lib.rs +++ b/test/napi/src/lib.rs @@ -153,6 +153,26 @@ fn main(mut cx: ModuleContext) -> NeonResult<()> { "call_js_function_idiomatically", call_js_function_idiomatically, )?; + cx.export_function("call_js_function_with_bind", call_js_function_with_bind)?; + cx.export_function( + "call_js_function_with_bind_and_args_with", + call_js_function_with_bind_and_args_with, + )?; + cx.export_function( + "call_js_function_with_bind_and_args_and_with", + call_js_function_with_bind_and_args_and_with, + )?; + cx.export_function("call_parse_int_with_bind", call_parse_int_with_bind)?; + cx.export_function( + "call_js_function_with_bind_and_exec", + call_js_function_with_bind_and_exec, + )?; + cx.export_function( + "call_js_constructor_with_bind", + call_js_constructor_with_bind, + )?; + cx.export_function("bind_js_function_to_object", bind_js_function_to_object)?; + cx.export_function("bind_js_function_to_number", bind_js_function_to_number)?; cx.export_function( "call_js_function_with_zero_args", call_js_function_with_zero_args, @@ -296,6 +316,10 @@ fn main(mut cx: ModuleContext) -> NeonResult<()> { cx.export_function("call_nullary_method", call_nullary_method)?; cx.export_function("call_unary_method", call_unary_method)?; cx.export_function("call_symbol_method", call_symbol_method)?; + cx.export_function("get_property_with_prop", get_property_with_prop)?; + cx.export_function("set_property_with_prop", set_property_with_prop)?; + cx.export_function("call_methods_with_prop", call_methods_with_prop)?; + cx.export_function("call_non_method_with_prop", call_non_method_with_prop)?; cx.export_function("create_date", create_date)?; cx.export_function("get_date_value", get_date_value)?;