use quote::quote;
use syn::{ItemFn, ReturnType, Type};
use crate::types::get_rc_inner_type;
#[derive(Clone, Copy, Debug, PartialEq)]
pub enum FastApiType {
Void,
Bool,
I32,
U32,
I64,
U64,
F32,
F64,
}
impl FastApiType {
pub fn quote_ctype(&self) -> proc_macro2::TokenStream {
match self {
FastApiType::Void => quote!(v8::fast_api::Type::Void.as_info()),
FastApiType::Bool => quote!(v8::fast_api::Type::Bool.as_info()),
FastApiType::I32 => quote!(v8::fast_api::Type::Int32.as_info()),
FastApiType::U32 => quote!(v8::fast_api::Type::Uint32.as_info()),
FastApiType::I64 => quote!(v8::fast_api::Type::Int64.as_info()),
FastApiType::U64 => quote!(v8::fast_api::Type::Uint64.as_info()),
FastApiType::F32 => quote!(v8::fast_api::Type::Float32.as_info()),
FastApiType::F64 => quote!(v8::fast_api::Type::Float64.as_info()),
}
}
pub fn quote_rust_type(&self) -> proc_macro2::TokenStream {
match self {
FastApiType::Void => quote!(()),
FastApiType::Bool => quote!(bool),
FastApiType::I32 => quote!(i32),
FastApiType::U32 => quote!(u32),
FastApiType::I64 => quote!(i64),
FastApiType::U64 => quote!(u64),
FastApiType::F32 => quote!(f32),
FastApiType::F64 => quote!(f64),
}
}
}
pub fn get_fast_api_type(ty: &Type) -> Option<FastApiType> {
if let Type::Path(type_path) = ty {
if let Some(segment) = type_path.path.segments.last() {
let ident = segment.ident.to_string();
return match ident.as_str() {
"bool" => Some(FastApiType::Bool),
"i32" => Some(FastApiType::I32),
"u32" => Some(FastApiType::U32),
"i64" => Some(FastApiType::I64),
"u64" => Some(FastApiType::U64),
"f32" => Some(FastApiType::F32),
"f64" => Some(FastApiType::F64),
_ => None,
};
}
}
if let Type::Tuple(tuple) = ty {
if tuple.elems.is_empty() {
return Some(FastApiType::Void);
}
}
None
}
pub fn get_fast_api_return_type(ret: &ReturnType) -> Option<FastApiType> {
match ret {
ReturnType::Default => Some(FastApiType::Void),
ReturnType::Type(_, ty) => get_fast_api_type(ty),
}
}
#[allow(clippy::too_many_arguments)]
pub fn generate_fast_api_code(
input_fn: &ItemFn,
fn_name: &syn::Ident,
wrapper_name: &syn::Ident,
params: &[(syn::Ident, Box<Type>)],
has_scope: bool,
has_state: bool,
state_type: &Option<Type>,
state_extraction: &proc_macro2::TokenStream,
arg_extractions: &[proc_macro2::TokenStream],
call_and_return: &proc_macro2::TokenStream,
) -> proc_macro2::TokenStream {
let fast_fn_name = syn::Ident::new(&format!("{}_v8_fast", fn_name), fn_name.span());
let template_fn_name = syn::Ident::new(&format!("{}_v8_template", fn_name), fn_name.span());
let cfunction_name = syn::Ident::new(
&format!("{}_V8_FAST_CALL", fn_name.to_string().to_uppercase()),
fn_name.span(),
);
let cfunction_info_name = syn::Ident::new(
&format!("{}_V8_FAST_CALL_INFO", fn_name.to_string().to_uppercase()),
fn_name.span(),
);
let mut fast_param_types: Vec<FastApiType> = Vec::new();
let mut all_fast_compatible = true;
for (_, ty) in params {
if let Some(fast_type) = get_fast_api_type(ty) {
fast_param_types.push(fast_type);
} else {
all_fast_compatible = false;
break;
}
}
let fast_return_type = get_fast_api_return_type(&input_fn.sig.output);
if !all_fast_compatible || fast_return_type.is_none() {
return quote! {
#input_fn
pub fn #wrapper_name(
scope: &mut v8::PinScope,
args: v8::FunctionCallbackArguments,
mut rv: v8::ReturnValue,
) {
#state_extraction
#(#arg_extractions)*
#call_and_return
}
};
}
if has_scope {
return quote! {
#input_fn
pub fn #wrapper_name(
scope: &mut v8::PinScope,
args: v8::FunctionCallbackArguments,
mut rv: v8::ReturnValue,
) {
#state_extraction
#(#arg_extractions)*
#call_and_return
}
};
}
let fast_return = fast_return_type.unwrap();
if has_state {
if let Some(state_ty) = state_type {
return generate_fast_api_with_state(
input_fn,
fn_name,
wrapper_name,
&fast_fn_name,
&template_fn_name,
&cfunction_name,
&cfunction_info_name,
params,
&fast_param_types,
fast_return,
state_ty,
arg_extractions,
call_and_return,
);
} else {
return quote! {
#input_fn
compile_error!("Function has 'state' parameter but no state type specified. Use #[glue_v8::method(fast, state = YourStateType)]");
};
}
}
generate_fast_api_pure(
input_fn,
fn_name,
wrapper_name,
&fast_fn_name,
&template_fn_name,
&cfunction_name,
&cfunction_info_name,
params,
&fast_param_types,
fast_return,
state_extraction,
arg_extractions,
call_and_return,
)
}
#[allow(clippy::too_many_arguments)]
fn generate_fast_api_pure(
input_fn: &ItemFn,
fn_name: &syn::Ident,
wrapper_name: &syn::Ident,
fast_fn_name: &syn::Ident,
template_fn_name: &syn::Ident,
cfunction_name: &syn::Ident,
cfunction_info_name: &syn::Ident,
params: &[(syn::Ident, Box<Type>)],
fast_param_types: &[FastApiType],
fast_return: FastApiType,
state_extraction: &proc_macro2::TokenStream,
arg_extractions: &[proc_macro2::TokenStream],
call_and_return: &proc_macro2::TokenStream,
) -> proc_macro2::TokenStream {
let receiver_ctype = quote!(v8::fast_api::Type::V8Value.as_info());
let arg_ctypes: Vec<_> = fast_param_types.iter().map(|t| t.quote_ctype()).collect();
let return_ctype = fast_return.quote_ctype();
let fast_params: Vec<_> = params
.iter()
.enumerate()
.map(|(idx, (name, _))| {
let rust_type = fast_param_types[idx].quote_rust_type();
quote!(#name: #rust_type)
})
.collect();
let fast_return_rust = fast_return.quote_rust_type();
let call_args_for_fast: Vec<_> = params.iter().map(|(name, _)| quote!(#name)).collect();
quote! {
#input_fn
pub fn #wrapper_name(
scope: &mut v8::PinScope,
args: v8::FunctionCallbackArguments,
mut rv: v8::ReturnValue,
) {
#state_extraction
#(#arg_extractions)*
#call_and_return
}
extern "C" fn #fast_fn_name(
_recv: v8::Local<v8::Value>,
#(#fast_params,)*
_options: *mut v8::fast_api::FastApiCallbackOptions,
) -> #fast_return_rust {
#fn_name(#(#call_args_for_fast),*)
}
const #cfunction_info_name: v8::fast_api::CFunctionInfo = v8::fast_api::CFunctionInfo::new(
#return_ctype,
&[#receiver_ctype, #(#arg_ctypes),*],
v8::fast_api::Int64Representation::BigInt,
);
pub const #cfunction_name: v8::fast_api::CFunction = v8::fast_api::CFunction::new(
#fast_fn_name as *const std::ffi::c_void,
&#cfunction_info_name,
);
pub fn #template_fn_name<'s>(
scope: &mut v8::PinScope<'s, '_>,
data: Option<v8::Local<'s, v8::Value>>,
) -> v8::Local<'s, v8::FunctionTemplate> {
v8::FunctionTemplate::builder(#wrapper_name)
.data(data.unwrap_or_else(|| v8::undefined(scope).into()))
.build_fast(scope, &[#cfunction_name])
}
}
}
#[allow(clippy::too_many_arguments)]
fn generate_fast_api_with_state(
input_fn: &ItemFn,
fn_name: &syn::Ident,
wrapper_name: &syn::Ident,
fast_fn_name: &syn::Ident,
template_fn_name: &syn::Ident,
cfunction_name: &syn::Ident,
cfunction_info_name: &syn::Ident,
params: &[(syn::Ident, Box<Type>)],
fast_param_types: &[FastApiType],
fast_return: FastApiType,
state_type: &Type,
arg_extractions: &[proc_macro2::TokenStream],
call_and_return: &proc_macro2::TokenStream,
) -> proc_macro2::TokenStream {
let receiver_ctype = quote!(v8::fast_api::Type::V8Value.as_info());
let arg_ctypes: Vec<_> = fast_param_types.iter().map(|t| t.quote_ctype()).collect();
let options_ctype = quote!(v8::fast_api::Type::CallbackOptions.as_info());
let return_ctype = fast_return.quote_ctype();
let fast_params: Vec<_> = params
.iter()
.enumerate()
.map(|(idx, (name, _))| {
let rust_type = fast_param_types[idx].quote_rust_type();
quote!(#name: #rust_type)
})
.collect();
let fast_return_rust = fast_return.quote_rust_type();
let call_args_for_fast: Vec<_> = params.iter().map(|(name, _)| quote!(#name)).collect();
let inner_state_type = if let Some(inner) = get_rc_inner_type(state_type) {
inner.clone()
} else {
state_type.clone()
};
let state_ty_str = quote!(#state_type).to_string();
quote! {
#input_fn
pub fn #wrapper_name(
scope: &mut v8::PinScope,
args: v8::FunctionCallbackArguments,
mut rv: v8::ReturnValue,
) {
let state: #state_type = unsafe {
let data = args.data();
if data.is_undefined() || data.is_null() {
let msg = v8::String::new(scope, concat!("internal error: state data not set for ", #state_ty_str)).unwrap();
let err = v8::Exception::error(scope, msg);
scope.throw_exception(err);
return;
}
let external = v8::Local::<v8::External>::try_from(data).unwrap();
let ptr = external.value() as *const #inner_state_type;
std::rc::Rc::clone(&*std::mem::ManuallyDrop::new(std::rc::Rc::from_raw(ptr)))
};
#(#arg_extractions)*
#call_and_return
}
extern "C" fn #fast_fn_name<'s>(
_recv: v8::Local<v8::Value>,
#(#fast_params,)*
options: *mut v8::fast_api::FastApiCallbackOptions<'s>,
) -> #fast_return_rust {
let state: &#inner_state_type = unsafe {
let options = &*options;
let external = v8::Local::<v8::External>::cast_unchecked(options.data);
&*(external.value() as *const #inner_state_type)
};
let state: std::mem::ManuallyDrop<std::rc::Rc<#inner_state_type>> = unsafe {
std::mem::ManuallyDrop::new(std::rc::Rc::from_raw(state))
};
#fn_name(&state, #(#call_args_for_fast),*)
}
const #cfunction_info_name: v8::fast_api::CFunctionInfo = v8::fast_api::CFunctionInfo::new(
#return_ctype,
&[#receiver_ctype, #(#arg_ctypes,)* #options_ctype],
v8::fast_api::Int64Representation::BigInt,
);
pub const #cfunction_name: v8::fast_api::CFunction = v8::fast_api::CFunction::new(
#fast_fn_name as *const std::ffi::c_void,
&#cfunction_info_name,
);
pub fn #template_fn_name<'s>(
scope: &mut v8::PinScope<'s, '_>,
state: &std::rc::Rc<#inner_state_type>,
) -> v8::Local<'s, v8::FunctionTemplate> {
let ptr = std::rc::Rc::as_ptr(state);
let external = v8::External::new(scope, ptr as *mut std::ffi::c_void);
v8::FunctionTemplate::builder(#wrapper_name)
.data(external.into())
.build_fast(scope, &[#cfunction_name])
}
}
}