Skip to content

Commit

Permalink
Merge pull request #829 from neon-bindings/arguments-options-api
Browse files Browse the repository at this point in the history
Ergonomic function-calling API
  • Loading branch information
dherman authored Dec 3, 2021
2 parents afca75e + b7617ed commit dae093f
Show file tree
Hide file tree
Showing 17 changed files with 610 additions and 67 deletions.
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

0 comments on commit dae093f

Please sign in to comment.