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

Add more utility traits and funtions to boa_interop #3773

Merged
merged 14 commits into from
Apr 9, 2024
Merged
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| {
hansl marked this conversation as resolved.
Show resolved Hide resolved
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
Loading