Skip to content

Commit

Permalink
feat(macros): Allow function/constructor callback to get generic para…
Browse files Browse the repository at this point in the history
…meters.

This patch allows `#[function_callback]` and `#[constructor_callback]`
to be defined where generic parameters. This patch adds a test to ensure
it works correctly. Rust will monomorphize the functions automatically,
the macro doesn't have to handle that.
  • Loading branch information
Hywan authored and waywardmonkeys committed Nov 17, 2023
1 parent e04beed commit 9791f15
Show file tree
Hide file tree
Showing 2 changed files with 86 additions and 6 deletions.
20 changes: 14 additions & 6 deletions javascriptcore-macros/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -23,16 +23,20 @@ pub fn function_callback(_attributes: TokenStream, item: TokenStream) -> TokenSt
.expect("#[function_callback] must apply on a valid function");
let function_visibility = &function.vis;
let function_name = &function.sig.ident;
let function_generics = &function.sig.generics.params;
let function_where_clause = &function.sig.generics.where_clause;

quote! {
#function_visibility unsafe extern "C" fn #function_name(
#function_visibility unsafe extern "C" fn #function_name < #function_generics > (
raw_ctx: javascriptcore::sys::JSContextRef,
function: javascriptcore::sys::JSObjectRef,
this_object: javascriptcore::sys::JSObjectRef,
argument_count: usize,
arguments: *const javascriptcore::sys::JSValueRef,
exception: *mut javascriptcore::sys::JSValueRef,
) -> *const javascriptcore::sys::OpaqueJSValue {
) -> *const javascriptcore::sys::OpaqueJSValue
#function_where_clause
{
use core::{mem::ManuallyDrop, option::Option, ops::Not, ptr, result::Result, slice};
use std::vec::Vec;
use javascriptcore::{sys::JSValueRef, JSContext, JSObject, JSValue};
Expand Down Expand Up @@ -80,7 +84,7 @@ pub fn function_callback(_attributes: TokenStream, item: TokenStream) -> TokenSt
) -> Result<JSValue, JSException> = {
#function

#function_name
#function_name ::< #function_generics >
};

// Second, call the original function.
Expand Down Expand Up @@ -130,15 +134,19 @@ pub fn constructor_callback(_attributes: TokenStream, item: TokenStream) -> Toke
.expect("#[constructor_callback] must apply on a valid function");
let constructor_visibility = &constructor.vis;
let constructor_name = &constructor.sig.ident;
let constructor_generics = &constructor.sig.generics.params;
let constructor_where_clause = &constructor.sig.generics.where_clause;

quote! {
#constructor_visibility unsafe extern "C" fn #constructor_name(
#constructor_visibility unsafe extern "C" fn #constructor_name < #constructor_generics >(
raw_ctx: javascriptcore::sys::JSContextRef,
constructor: javascriptcore::sys::JSObjectRef,
argument_count: usize,
arguments: *const javascriptcore::sys::JSValueRef,
exception: *mut javascriptcore::sys::JSValueRef,
) -> *mut javascriptcore::sys::OpaqueJSValue {
) -> *mut javascriptcore::sys::OpaqueJSValue
#constructor_where_clause
{
use core::{mem::ManuallyDrop, option::Option, ops::Not, ptr, result::Result, slice};
use std::vec::Vec;
use javascriptcore::{sys::JSValueRef, JSContext, JSObject, JSValue};
Expand Down Expand Up @@ -172,7 +180,7 @@ pub fn constructor_callback(_attributes: TokenStream, item: TokenStream) -> Toke
) -> Result<JSValue, JSException> = {
#constructor

#constructor_name
#constructor_name ::< #constructor_generics >
};

// Second, call the original constructor.
Expand Down
72 changes: 72 additions & 0 deletions src/value.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1091,6 +1091,78 @@ mod tests {
Ok(())
}

#[test]
fn function_with_macros_and_generics() -> Result<(), JSException> {
use crate as javascriptcore;

let ctx = JSContext::default();

trait Math {
fn double(x: f64) -> f64;
}

struct Double;
struct NoDouble;

impl Math for Double {
fn double(x: f64) -> f64 {
x * 2.
}
}

impl Math for NoDouble {
fn double(x: f64) -> f64 {
x
}
}

#[function_callback]
fn do_double<M>(
ctx: &JSContext,
_function: Option<&JSObject>,
_this_object: Option<&JSObject>,
arguments: &[JSValue],
) -> Result<JSValue, JSException>
where
M: Math,
{
if arguments.len() != 1 {
return Err(JSValue::new_string(ctx, "must receive 1 argument").into());
}

let x = arguments[0].as_number()?;

Ok(JSValue::new_number(ctx, M::double(x)))
}

// Let's try with `Double` as the generic parameter.
let do_math = JSValue::new_function(&ctx, "do_math_with_double", Some(do_double::<Double>));
let do_math_as_object = do_math.as_object()?;

// Correct call.
{
let result =
do_math_as_object.call_as_function(None, &[JSValue::new_number(&ctx, 2.)])?;

assert_eq!(result.as_number()?, 4.);
}

// Let's try with `NoDouble` as the generic parameter.
let do_math =
JSValue::new_function(&ctx, "do_math_with_no_double", Some(do_double::<NoDouble>));
let do_math_as_object = do_math.as_object()?;

// Correct call.
{
let result =
do_math_as_object.call_as_function(None, &[JSValue::new_number(&ctx, 2.)])?;

assert_eq!(result.as_number()?, 2.);
}

Ok(())
}

#[test]
fn json_boolean_true() {
let ctx = JSContext::default();
Expand Down

0 comments on commit 9791f15

Please sign in to comment.