Skip to content

Commit

Permalink
Add more utility traits and funtions to boa_interop (#3773)
Browse files Browse the repository at this point in the history
* Add more utility traits and funtions to boa_interop

* cargo clippy

* Readd the safe version for Fn which also implements Copy

* Use a new trait for converting a Rust type to a JsResult<JsValue>

* cargo clippy

* Add a test for returning result and Fn()

* Seal both IntoJsFunction and IntoJsFunctionUnsafe

* Try Borrowing and return a nice error instead of panicking

* Address comments

* Rename into_js_function to into_js_function_copied

* Move TryIntoJsResult to boa_engine

* Move JsRest to be at the end only of arguments and add JsAll

* use from_copy_closure and remove unsafe
  • Loading branch information
hansl authored Apr 9, 2024
1 parent 1825e8b commit 9702501
Show file tree
Hide file tree
Showing 4 changed files with 647 additions and 38 deletions.
16 changes: 16 additions & 0 deletions core/engine/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -141,6 +141,22 @@ pub use prelude::*;
/// The result of a Javascript expression is represented like this so it can succeed (`Ok`) or fail (`Err`)
pub type JsResult<T> = StdResult<T, JsError>;

/// Create a [`JsResult`] from a Rust value. This trait is used to
/// convert Rust types to JS types, including [`JsResult`] of
/// Rust values and [`JsValue`]s.
///
/// This trait is implemented for any that can be converted into a [`JsValue`].
pub trait TryIntoJsResult {
/// Try to convert a Rust value into a `JsResult<JsValue>`.
///
/// # Errors
/// Any parsing errors that may occur during the conversion, or any
/// error that happened during the call to a function.
fn try_into_js_result(self, context: &mut Context) -> JsResult<JsValue>;
}

mod try_into_js_result_impls;

/// A utility trait to make working with function arguments easier.
pub trait JsArgs {
/// Utility function to `get` a parameter from a `[JsValue]` or default to `JsValue::Undefined`
Expand Down
33 changes: 33 additions & 0 deletions core/engine/src/try_into_js_result_impls.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
//! Declare implementations of [`TryIntoJsResult`] trait for various types.
use crate::{Context, JsResult, JsValue, TryIntoJsResult};

impl<T> TryIntoJsResult for T
where
T: Into<JsValue>,
{
fn try_into_js_result(self, _cx: &mut Context) -> JsResult<JsValue> {
Ok(self.into())
}
}

impl<T> TryIntoJsResult for Option<T>
where
T: TryIntoJsResult,
{
fn try_into_js_result(self, cx: &mut Context) -> JsResult<JsValue> {
match self {
Some(value) => value.try_into_js_result(cx),
None => Ok(JsValue::undefined()),
}
}
}

impl<T> TryIntoJsResult for JsResult<T>
where
T: TryIntoJsResult,
{
fn try_into_js_result(self, cx: &mut Context) -> JsResult<JsValue> {
self.and_then(|value| value.try_into_js_result(cx))
}
}
240 changes: 240 additions & 0 deletions core/interop/src/into_js_function_impls.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,240 @@
//! Implementations of the `IntoJsFunction` trait for various function signatures.
use std::cell::RefCell;

use boa_engine::{js_string, Context, JsError, NativeFunction, TryIntoJsResult};

use crate::private::IntoJsFunctionSealed;
use crate::{IntoJsFunctionCopied, JsRest, TryFromJsArgument, UnsafeIntoJsFunction};

/// A token to represent the context argument in the function signature.
/// This should not be used directly and has no external meaning.
#[derive(Debug, Copy, Clone)]
pub struct ContextArgToken;

macro_rules! impl_into_js_function {
($($id: ident: $t: ident),*) => {
impl<$($t,)* R, T> IntoJsFunctionSealed<($($t,)*), R> for T
where
$($t: TryFromJsArgument + 'static,)*
R: TryIntoJsResult,
T: FnMut($($t,)*) -> R + 'static
{}

impl<$($t,)* R, T> IntoJsFunctionSealed<($($t,)* ContextArgToken,), R> for T
where
$($t: TryFromJsArgument + 'static,)*
R: TryIntoJsResult,
T: FnMut($($t,)* &mut Context) -> R + 'static
{}

impl<$($t,)* R, T> IntoJsFunctionSealed<($($t,)* JsRest,), R> for T
where
$($t: TryFromJsArgument + 'static,)*
R: TryIntoJsResult,
T: FnMut($($t,)* JsRest) -> R + 'static
{}

impl<$($t,)* R, T> IntoJsFunctionSealed<($($t,)* JsRest, ContextArgToken), R> for T
where
$($t: TryFromJsArgument + 'static,)*
R: TryIntoJsResult,
T: FnMut($($t,)* JsRest, &mut Context) -> R + 'static
{}

impl<$($t,)* R, T> UnsafeIntoJsFunction<($($t,)*), R> for T
where
$($t: TryFromJsArgument + 'static,)*
R: TryIntoJsResult,
T: FnMut($($t,)*) -> R + 'static,
{
#[allow(unused_variables)]
unsafe fn into_js_function_unsafe(self, _context: &mut Context) -> NativeFunction {
let s = RefCell::new(self);
unsafe {
NativeFunction::from_closure(move |this, args, ctx| {
let rest = args;
$(
let ($id, rest) = $t::try_from_js_argument(this, rest, ctx)?;
)*
match s.try_borrow_mut() {
Ok(mut r) => r( $($id,)* ).try_into_js_result(ctx),
Err(_) => {
Err(JsError::from_opaque(js_string!("recursive calls to this function not supported").into()))
}
}
})
}
}
}

impl<$($t,)* R, T> UnsafeIntoJsFunction<($($t,)* JsRest,), R> for T
where
$($t: TryFromJsArgument + 'static,)*
R: TryIntoJsResult,
T: FnMut($($t,)* JsRest) -> R + 'static,
{
#[allow(unused_variables)]
unsafe fn into_js_function_unsafe(self, _context: &mut Context) -> NativeFunction {
let s = RefCell::new(self);
unsafe {
NativeFunction::from_closure(move |this, args, ctx| {
let rest = args;
$(
let ($id, rest) = $t::try_from_js_argument(this, rest, ctx)?;
)*
match s.try_borrow_mut() {
Ok(mut r) => r( $($id,)* rest.into() ).try_into_js_result(ctx),
Err(_) => {
Err(JsError::from_opaque(js_string!("recursive calls to this function not supported").into()))
}
}
})
}
}
}

impl<$($t,)* R, T> UnsafeIntoJsFunction<($($t,)* ContextArgToken,), R> for T
where
$($t: TryFromJsArgument + 'static,)*
R: TryIntoJsResult,
T: FnMut($($t,)* &mut Context) -> R + 'static,
{
#[allow(unused_variables)]
unsafe fn into_js_function_unsafe(self, _context: &mut Context) -> NativeFunction {
let s = RefCell::new(self);
unsafe {
NativeFunction::from_closure(move |this, args, ctx| {
let rest = args;
$(
let ($id, rest) = $t::try_from_js_argument(this, rest, ctx)?;
)*
let r = s.borrow_mut()( $($id,)* ctx);
r.try_into_js_result(ctx)
})
}
}
}

impl<$($t,)* R, T> UnsafeIntoJsFunction<($($t,)* JsRest, ContextArgToken), R> for T
where
$($t: TryFromJsArgument + 'static,)*
R: TryIntoJsResult,
T: FnMut($($t,)* JsRest, &mut Context) -> R + 'static,
{
#[allow(unused_variables)]
unsafe fn into_js_function_unsafe(self, _context: &mut Context) -> NativeFunction {
let s = RefCell::new(self);
unsafe {
NativeFunction::from_closure(move |this, args, ctx| {
let rest = args;
$(
let ($id, rest) = $t::try_from_js_argument(this, rest, ctx)?;
)*
let r = s.borrow_mut()( $($id,)* rest.into(), ctx);
r.try_into_js_result(ctx)
})
}
}
}

// Safe versions for `Fn(..) -> ...`.
impl<$($t,)* R, T> IntoJsFunctionCopied<($($t,)*), R> for T
where
$($t: TryFromJsArgument + 'static,)*
R: TryIntoJsResult,
T: Fn($($t,)*) -> R + 'static + Copy,
{
#[allow(unused_variables)]
fn into_js_function_copied(self, _context: &mut Context) -> NativeFunction {
let s = self;
NativeFunction::from_copy_closure(move |this, args, ctx| {
let rest = args;
$(
let ($id, rest) = $t::try_from_js_argument(this, rest, ctx)?;
)*
let r = s( $($id,)* );
r.try_into_js_result(ctx)
})
}
}

impl<$($t,)* R, T> IntoJsFunctionCopied<($($t,)* JsRest,), R> for T
where
$($t: TryFromJsArgument + 'static,)*
R: TryIntoJsResult,
T: Fn($($t,)* JsRest) -> R + 'static + Copy,
{
#[allow(unused_variables)]
fn into_js_function_copied(self, _context: &mut Context) -> NativeFunction {
let s = self;
NativeFunction::from_copy_closure(move |this, args, ctx| {
let rest = args;
$(
let ($id, rest) = $t::try_from_js_argument(this, rest, ctx)?;
)*
let r = s( $($id,)* rest.into() );
r.try_into_js_result(ctx)
})
}
}

impl<$($t,)* R, T> IntoJsFunctionCopied<($($t,)* ContextArgToken,), R> for T
where
$($t: TryFromJsArgument + 'static,)*
R: TryIntoJsResult,
T: Fn($($t,)* &mut Context) -> R + 'static + Copy,
{
#[allow(unused_variables)]
fn into_js_function_copied(self, _context: &mut Context) -> NativeFunction {
let s = self;
NativeFunction::from_copy_closure(move |this, args, ctx| {
let rest = args;
$(
let ($id, rest) = $t::try_from_js_argument(this, rest, ctx)?;
)*
let r = s( $($id,)* ctx);
r.try_into_js_result(ctx)
})
}
}

impl<$($t,)* R, T> IntoJsFunctionCopied<($($t,)* JsRest, ContextArgToken), R> for T
where
$($t: TryFromJsArgument + 'static,)*
R: TryIntoJsResult,
T: Fn($($t,)* JsRest, &mut Context) -> R + 'static + Copy,
{
#[allow(unused_variables)]
fn into_js_function_copied(self, _context: &mut Context) -> NativeFunction {
let s = self;
NativeFunction::from_copy_closure(move |this, args, ctx| {
let rest = args;
$(
let ($id, rest) = $t::try_from_js_argument(this, rest, ctx)?;
)*
let r = s( $($id,)* rest.into(), ctx);
r.try_into_js_result(ctx)
})
}
}
};
}

// Currently implemented up to 12 arguments. The empty argument list
// is implemented separately above.
// Consider that JsRest and JsThis are part of this list, but Context
// is not, as it is a special specialization of the template.
impl_into_js_function!();
impl_into_js_function!(a: A);
impl_into_js_function!(a: A, b: B);
impl_into_js_function!(a: A, b: B, c: C);
impl_into_js_function!(a: A, b: B, c: C, d: D);
impl_into_js_function!(a: A, b: B, c: C, d: D, e: E);
impl_into_js_function!(a: A, b: B, c: C, d: D, e: E, f: F);
impl_into_js_function!(a: A, b: B, c: C, d: D, e: E, f: F, g: G);
impl_into_js_function!(a: A, b: B, c: C, d: D, e: E, f: F, g: G, h: H);
impl_into_js_function!(a: A, b: B, c: C, d: D, e: E, f: F, g: G, h: H, i: I);
impl_into_js_function!(a: A, b: B, c: C, d: D, e: E, f: F, g: G, h: H, i: I, j: J);
impl_into_js_function!(a: A, b: B, c: C, d: D, e: E, f: F, g: G, h: H, i: I, j: J, k: K);
impl_into_js_function!(a: A, b: B, c: C, d: D, e: E, f: F, g: G, h: H, i: I, j: J, k: K, l: L);
Loading

0 comments on commit 9702501

Please sign in to comment.