Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Ergonomic function-calling API #829

Merged
merged 8 commits into from
Dec 3, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 4 additions & 3 deletions src/context/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -94,9 +94,10 @@
//!
//! while !done {
//! done = cx.execute_scoped(|mut cx| { // temporary scope
//! let args: Vec<Handle<JsValue>> = vec![];
//! let obj = next.call(&mut cx, iterator, args)? // temporary object
//! .downcast_or_throw::<JsObject, _>(&mut cx)?;
//! let obj: Handle<JsObject> = next // temporary object
//! .call_with(&cx)
//! .this(iterator)
//! .apply(&mut cx)?;
//! let number = obj.get(&mut cx, "value")? // temporary number
//! .downcast_or_throw::<JsNumber, _>(&mut cx)?
//! .value(&mut cx);
Expand Down
2 changes: 1 addition & 1 deletion src/object/class/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down
2 changes: 1 addition & 1 deletion src/types/binary.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ use crate::context::internal::Env;
use crate::context::{Context, Lock};
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;
Expand Down
2 changes: 1 addition & 1 deletion src/types/boxed.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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<dyn Any + Send + 'static>;
Expand Down
2 changes: 1 addition & 1 deletion src/types/buffer/types.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ use neon_runtime::{raw, TypedArrayType};
use crate::context::{internal::Env, Context};
use crate::handle::{Handle, Managed};
use crate::result::{JsResult, Throw};
use crate::types::{internal::ValueInternal, Object, Value};
use crate::types::{private::ValueInternal, Object, Value};

use super::lock::{Ledger, Lock};
use super::{private, BorrowError, Ref, RefMut, TypedArray};
Expand Down
2 changes: 1 addition & 1 deletion src/types/date.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
use super::{Value, ValueInternal};
use super::{private::ValueInternal, Value};
use crate::context::internal::Env;
use crate::context::Context;
use crate::handle::{Handle, Managed};
Expand Down
2 changes: 1 addition & 1 deletion src/types/error.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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};

Expand Down
219 changes: 219 additions & 0 deletions src/types/function/mod.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,219 @@
//! Types and traits for working with JavaScript functions.

use crate::context::Context;
use crate::handle::Handle;
use crate::object::Object;
use crate::result::{JsResult, NeonResult};
use crate::types::{JsFunction, JsObject, JsValue, Value};

use smallvec::smallvec;

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<JsNumber> {
/// # let global = cx.global();
/// # let parse_int = global.get(&mut cx, "parseInt")?;
/// # let parse_int: Handle<JsFunction> = parse_int.downcast_or_throw(&mut cx)?;
/// let x: Handle<JsNumber> = 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<Handle<'a, JsValue>>,
pub(crate) args: private::ArgsVec<'a>,
}

impl<'a> CallOptions<'a> {
/// Set the value of `this` for the function call.
pub fn this<V: Value>(&mut self, this: Handle<'a, V>) -> &mut Self {
self.this = Some(this.upcast());
self
}

/// Add an argument to the arguments list.
pub fn arg<V: Value>(&mut self, arg: Handle<'a, V>) -> &mut Self {
self.args.push(arg.upcast());
self
}

/// Replaces the arguments list with the given arguments.
pub fn args<A: Arguments<'a>>(&mut self, args: A) -> &mut Self {
self.args = args.into_args_vec();
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: 'a, V: Value, C: Context<'b>>(&self, cx: &mut C) -> JsResult<'b, V> {
let this = self.this.unwrap_or_else(|| cx.undefined().upcast());
let v: Handle<JsValue> = self.callee.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: 'a, C: Context<'b>>(&self, cx: &mut C) -> NeonResult<()> {
let this = self.this.unwrap_or_else(|| cx.undefined().upcast());
self.callee.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<JsObject> {
/// # let global = cx.global();
/// # let url = global.get(&mut cx, "URL")?;
/// # let url: Handle<JsFunction> = 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<V: Value>(&mut self, arg: Handle<'a, V>) -> &mut Self {
self.args.push(arg.upcast());
self
}

/// Replaces the arguments list with the given arguments.
pub fn args<A: Arguments<'a>>(&mut self, args: A) -> &mut Self {
self.args = args.into_args_vec();
self
}

/// Make the constructor call. If the function returns without throwing, returns
/// the resulting object.
pub fn apply<'b: 'a, O: Object, C: Context<'b>>(&self, cx: &mut C) -> JsResult<'b, O> {
let v: Handle<JsObject> = self.callee.construct(cx, &self.args)?;
v.downcast_or_throw(cx)
}
}

/// The trait for specifying 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 Arguments<'a>: private::ArgumentsInternal<'a> {}

impl<'a> private::ArgumentsInternal<'a> for () {
fn into_args_vec(self) -> private::ArgsVec<'a> {
smallvec![]
}
}

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 into_args_vec(self) -> private::ArgsVec<'a> {
let ($($vprefix, )* $vname1, ) = self;
smallvec![$($vprefix.upcast(),)* $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),
];
}
11 changes: 11 additions & 0 deletions src/types/function/private.rs
Original file line number Diff line number Diff line change
@@ -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 into_args_vec(self) -> ArgsVec<'a>;
}
Loading