Skip to content

Commit

Permalink
add BindOptions::construct()
Browse files Browse the repository at this point in the history
  • Loading branch information
dherman committed Sep 28, 2024
1 parent 2158281 commit b5a76b8
Show file tree
Hide file tree
Showing 6 changed files with 93 additions and 14 deletions.
4 changes: 1 addition & 3 deletions crates/neon/src/object/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -285,9 +285,7 @@ pub trait Object: Value {
cx: &'a mut Cx<'cx>,
key: K,
) -> NeonResult<BindOptions<'a, 'cx>> {
let callee: Handle<JsValue> = self
.prop(cx, key)
.get()?;
let callee: Handle<JsValue> = self.prop(cx, key).get()?;
let this = Some(self.as_value(cx));
Ok(BindOptions {
cx,
Expand Down
7 changes: 7 additions & 0 deletions crates/neon/src/types_impl/function/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -87,6 +87,13 @@ impl<'a, 'cx: 'a> BindOptions<'a, 'cx> {
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<R: TryFromJs<'cx>>(&mut self) -> NeonResult<R> {
let v: Handle<JsValue> = 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.
Expand Down
55 changes: 44 additions & 11 deletions crates/neon/src/types_impl/private.rs
Original file line number Diff line number Diff line change
Expand Up @@ -77,21 +77,54 @@ pub trait ValueInternal: TransparentNoCopyWrapper + 'static {
result.as_mut_ptr(),
);

match status {
sys::Status::InvalidArg if !sys::tag::is_function(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);
}
}
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<raw::Local> = 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(())
}
21 changes: 21 additions & 0 deletions test/napi/lib/functions.js
Original file line number Diff line number Diff line change
Expand Up @@ -74,6 +74,27 @@ describe("JsFunction", 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;
Expand Down
12 changes: 12 additions & 0 deletions test/napi/src/js/functions.rs
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,18 @@ pub fn call_parse_int_with_bind(mut cx: FunctionContext) -> JsResult<JsNumber> {
Ok(cx.number(x + 1.0))
}

pub fn call_js_function_with_bind_and_exec(mut cx: FunctionContext) -> JsResult<JsUndefined> {
cx.argument::<JsFunction>(0)?.bind(&mut cx).arg(1)?.exec()?;
Ok(cx.undefined())
}

pub fn call_js_constructor_with_bind(mut cx: FunctionContext) -> JsResult<JsObject> {
cx.argument::<JsFunction>(0)?
.bind(&mut cx)
.args((42, "hello"))?
.construct()
}

pub fn bind_js_function_to_object(mut cx: FunctionContext) -> JsResult<JsValue> {
let f = cx.argument::<JsFunction>(0)?;
let obj = cx.empty_object();
Expand Down
8 changes: 8 additions & 0 deletions test/napi/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -163,6 +163,14 @@ fn main(mut cx: ModuleContext) -> NeonResult<()> {
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(
Expand Down

0 comments on commit b5a76b8

Please sign in to comment.