Skip to content

Commit

Permalink
Readd the safe version for Fn which also implements Copy
Browse files Browse the repository at this point in the history
  • Loading branch information
hansl committed Apr 1, 2024
1 parent 76e0cce commit b20cef0
Show file tree
Hide file tree
Showing 2 changed files with 92 additions and 38 deletions.
63 changes: 55 additions & 8 deletions core/interop/src/into_js_function_impls.rs
Original file line number Diff line number Diff line change
@@ -1,24 +1,26 @@
//! Implementations of the `IntoJsFunction` trait for various function signatures.
use crate::{IntoJsFunction, TryFromJsArgument};
use boa_engine::{Context, JsValue, NativeFunction};
use std::cell::RefCell;

use boa_engine::{Context, JsValue, NativeFunction};

use crate::{IntoJsFunction, IntoJsFunctionUnsafe, TryFromJsArgument};

/// 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> IntoJsFunction<($($t,)*), R> for T
unsafe impl<$($t,)* R, T> IntoJsFunctionUnsafe<($($t,)*), R> for T
where
$($t: TryFromJsArgument + 'static,)*
R: Into<JsValue>,
T: FnMut($($t,)*) -> R + 'static,
{
#[allow(unused_variables)]
fn into_js_function(self, _context: &mut Context) -> NativeFunction {
unsafe fn into_js_function_unsafe(self, _context: &mut Context) -> NativeFunction {
let s = RefCell::new(self);
unsafe {
NativeFunction::from_closure(move |this, args, ctx| {
Expand All @@ -33,14 +35,14 @@ macro_rules! impl_into_js_function {
}
}

impl<$($t,)* R, T> IntoJsFunction<($($t,)* ContextArgToken), R> for T
unsafe impl<$($t,)* R, T> IntoJsFunctionUnsafe<($($t,)* ContextArgToken), R> for T
where
$($t: TryFromJsArgument + 'static,)*
R: Into<JsValue>,
T: FnMut($($t,)* &mut Context) -> R + 'static,
{
#[allow(unused_variables)]
fn into_js_function(self, _context: &mut Context) -> NativeFunction {
unsafe fn into_js_function_unsafe(self, _context: &mut Context) -> NativeFunction {
let s = RefCell::new(self);
unsafe {
NativeFunction::from_closure(move |this, args, ctx| {
Expand All @@ -54,15 +56,60 @@ macro_rules! impl_into_js_function {
}
}
}

// Safe versions for `Fn(..) -> ...`.
impl<$($t,)* R, T> IntoJsFunction<($($t,)*), R> for T
where
$($t: TryFromJsArgument + 'static,)*
R: Into<JsValue>,
T: Fn($($t,)*) -> R + 'static + Copy,
{
#[allow(unused_variables)]
fn into_js_function(self, _context: &mut Context) -> NativeFunction {
let s = 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( $($id),* );
Ok(r.into())
})
}
}
}

impl<$($t,)* R, T> IntoJsFunction<($($t,)* ContextArgToken), R> for T
where
$($t: TryFromJsArgument + 'static,)*
R: Into<JsValue>,
T: Fn($($t,)* &mut Context) -> R + 'static + Copy,
{
#[allow(unused_variables)]
fn into_js_function(self, _context: &mut Context) -> NativeFunction {
let s = 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( $($id,)* ctx);
Ok(r.into())
})
}
}
}
};
}

impl<R, T> IntoJsFunction<(), R> for T
unsafe impl<R, T> IntoJsFunctionUnsafe<(), R> for T
where
R: Into<JsValue>,
T: FnMut() -> R + 'static,
{
fn into_js_function(self, _context: &mut Context) -> NativeFunction {
unsafe fn into_js_function_unsafe(self, _context: &mut Context) -> NativeFunction {
let s = RefCell::new(self);
unsafe {
NativeFunction::from_closure(move |_this, _args, _ctx| {
Expand Down
67 changes: 37 additions & 30 deletions core/interop/src/lib.rs
Original file line number Diff line number Diff line change
@@ -1,7 +1,5 @@
//! Interop utilities between Boa and its host.
use std::cell::RefCell;

use boa_engine::module::SyntheticModuleInitializer;
use boa_engine::value::TryFromJs;
use boa_engine::{Context, JsResult, JsString, JsValue, Module, NativeFunction};
Expand Down Expand Up @@ -48,12 +46,12 @@ impl<T: IntoIterator<Item = (JsString, NativeFunction)> + Clone> IntoJsModule fo
/// # use boa_interop::IntoJsFunctionUnsafe;
/// # let mut context = Context::default();
/// let f = |a: i32, b: i32| a + b;
/// let f = unsafe { f.into_js_function(&mut context) };
/// let f = unsafe { f.into_js_function_unsafe(&mut context) };
/// let result = f.call(&JsValue::undefined(), &[JsValue::from(1), JsValue::from(2)], &mut context).unwrap();
/// assert_eq!(result, JsValue::new(3));
/// ```
///
/// Since the `IntoJsFunction` trait is implemented for `FnMut`, you can
/// Since the `IntoJsFunctionUnsafe` trait is implemented for `FnMut`, you can
/// also use closures directly:
/// ```
/// # use boa_engine::{Context, JsValue, NativeFunction};
Expand All @@ -69,7 +67,7 @@ impl<T: IntoIterator<Item = (JsString, NativeFunction)> + Clone> IntoJsModule fo
/// let x = x.clone();
/// move |a: i32| *x.borrow_mut() += a
/// };
/// let f = f.into_js_function(&mut context);
/// let f = unsafe { f.into_js_function_unsafe(&mut context) };
/// f.call(&JsValue::undefined(), &[JsValue::from(1)], &mut context).unwrap();
/// f.call(&JsValue::undefined(), &[JsValue::from(4)], &mut context).unwrap();
/// assert_eq!(*x.borrow(), 5);
Expand All @@ -78,25 +76,32 @@ impl<T: IntoIterator<Item = (JsString, NativeFunction)> + Clone> IntoJsModule fo
/// # Safety
/// For this trait to be implemented safely, the implementing type must not contain any
/// garbage collected objects (from [`boa_gc`]).
pub unsafe trait IntoJsFunctionUnsafe {
pub unsafe trait IntoJsFunctionUnsafe<Args, Ret> {
/// Converts the type into a JS function.
///
/// # Safety
/// This function is unsafe to ensure the callee knows the risks of using this trait.
/// The implementing type must not contain any garbage collected objects.
unsafe fn into_js_function(self, context: &mut Context) -> NativeFunction;
unsafe fn into_js_function_unsafe(self, context: &mut Context) -> NativeFunction;
}

unsafe impl<T: FnMut() + 'static> IntoJsFunctionUnsafe for T {
unsafe fn into_js_function(self, _context: &mut Context) -> NativeFunction {
let cell = RefCell::new(self);
unsafe {
NativeFunction::from_closure(move |_, _, _| {
cell.borrow_mut()();
Ok(JsValue::undefined())
})
}
}
/// The safe equivalent of the IntoJsFunctionUnsafe trait.
/// This can only be used on closures that have the `Copy` trait.
///
/// Since this function is implemented for `Fn(...)` closures, we can use
/// it directly when defining a function:
/// ```
/// # use boa_engine::{Context, JsValue, NativeFunction};
/// # use boa_interop::IntoJsFunction;
/// # let mut context = Context::default();
/// let f = |a: i32, b: i32| a + b;
/// let f = f.into_js_function(&mut context);
/// let result = f.call(&JsValue::undefined(), &[JsValue::from(1), JsValue::from(2)], &mut context).unwrap();
/// assert_eq!(result, JsValue::new(3));
/// ```
pub trait IntoJsFunction<Args, Ret>: Copy {
/// Converts the type into a JS function.
fn into_js_function(self, context: &mut Context) -> NativeFunction;
}

/// Create a Rust value from a JS argument. This trait is used to
Expand Down Expand Up @@ -230,11 +235,11 @@ pub fn into_js_module() {
result
}
}
.into_js_function(&mut context),
.into_js_function_unsafe(&mut context),
),
(
js_string!("bar"),
IntoJsFunctionUnsafe::into_js_function(
IntoJsFunctionUnsafe::into_js_function_unsafe(
{
let counter = bar_count.clone();
move |i: i32| {
Expand All @@ -246,20 +251,22 @@ pub fn into_js_module() {
),
(
js_string!("dad"),
{
let counter = dad_count.clone();
move |args: JsRest, context: &mut Context| {
*counter.borrow_mut() += args
.into_iter()
.map(|i| i.try_js_into::<i32>(context).unwrap())
.sum::<i32>();
}
}
.into_js_function(&mut context),
IntoJsFunctionUnsafe::into_js_function_unsafe(
{
let counter = dad_count.clone();
move |args: JsRest, context: &mut Context| {
*counter.borrow_mut() += args
.into_iter()
.map(|i| i.try_js_into::<i32>(context).unwrap())
.sum::<i32>();
}
},
&mut context,
),
),
(
js_string!("send"),
IntoJsFunctionUnsafe::into_js_function(
IntoJsFunctionUnsafe::into_js_function_unsafe(
{
let result = result.clone();
move |value: JsValue| {
Expand Down

0 comments on commit b20cef0

Please sign in to comment.