use quote::quote; use syn::spanned::Spanned; #[proc_macro] pub fn init(input: proc_macro::TokenStream) -> proc_macro::TokenStream { init_impl(input.into()) .unwrap_or_else(|e| e.to_compile_error()) .into() } fn init_impl(input: proc_macro2::TokenStream) -> syn::Result { let input: syn::Ident = syn::parse2(input)?; let code = quote! { #[no_mangle] pub unsafe extern "C" fn napi_register_module_v1(env: node_api::sys::napi_env, exports: node_api::sys::napi_value) -> node_api::sys::napi_value { let env = node_api::Env::from_raw(env); let exports = node_api::Value::from_raw(env, exports); let result = std::panic::catch_unwind(|| #input(env, exports)); let result = match result { Ok(result) => result, Err(panic_info) => { env.throw_error("A panic occurred."); return std::ptr::null_mut(); }, }; let exports = match result { Ok(exports) => exports, Err(error) => { if !env.is_exception_pending() { env.throw_error(&format!("{}", error)); } return std::ptr::null_mut(); } }; exports.raw() } }; Ok(code) } #[proc_macro_attribute] pub fn function( _attr: proc_macro::TokenStream, input: proc_macro::TokenStream, ) -> proc_macro::TokenStream { function_impl(input.into()) .unwrap_or_else(|e| e.to_compile_error()) .into() } fn function_impl(input: proc_macro2::TokenStream) -> syn::Result { let input: syn::ItemFn = syn::parse2(input)?; let visibility = &input.vis; let ident = &input.sig.ident; let impl_inputs = input.sig.inputs.iter().skip(1); let impl_output = &input.sig.output; let impl_block = &input.block; let args = input .sig .inputs .iter() .skip(1) .map(|input| { let input = match input { syn::FnArg::Typed(arg) => arg, syn::FnArg::Receiver(_) => { return Err(syn::Error::new( input.span(), "receiver arg is not allowed here", )) } }; let ident = match &*input.pat { syn::Pat::Ident(pat_ident) => &pat_ident.ident, _ => return Err(syn::Error::new(input.pat.span(), "invalid pattern")), }; Ok(ident) }) .collect::>>()?; let args_count = args.len(); let from_node_api_statements = args .iter() .enumerate() .map(|(i, ident)| { let code = quote! { let #ident = argv[#i]; let #ident = node_api::Value::from_raw(env, #ident); let #ident = node_api::FromNodeAPI::from_node_api(#ident)?; }; Ok(code) }) .collect::>>()?; let code = quote! { #visibility unsafe extern "C" fn #ident(env: node_api::sys::napi_env, info: node_api::sys::napi_callback_info) -> node_api::sys::napi_value { fn function_impl<'a>(env: node_api::Env<'a>, #(#impl_inputs),*) #impl_output #impl_block let env = node_api::Env::from_raw(env); let result = std::panic::catch_unwind(|| -> node_api::Result<_> { let mut argc = #args_count; let mut argv: [node_api::sys::napi_value; #args_count] = [std::ptr::null_mut(); #args_count]; let status = node_api::sys::napi_get_cb_info( env.raw(), info, &mut argc as *mut usize, argv.as_mut_ptr() as *mut node_api::sys::napi_value, std::ptr::null_mut(), std::ptr::null_mut() ); if status != node_api::sys::napi_status::napi_ok { return Err(node_api::Error::from_last_node_api_error(env.raw(), status).into()); } if argc != #args_count { return Err(node_api::Error::message("invalid number of arguments").into()); } #(#from_node_api_statements)* let output = function_impl(env, #(#args),*).map_err(|error| node_api::Error::message(error.to_string()))?; let output = node_api::IntoNodeApi::into_node_api(output, env)?; Ok(output) }); let result = match result { Ok(result) => result, Err(_) => { env.throw_error("A panic occurred."); return std::ptr::null_mut(); }, }; let output = match result { Ok(output) => output, Err(error) => { if !env.is_exception_pending() { env.throw_error(&format!("{}", error)); } return std::ptr::null_mut(); } }; output.raw() } }; Ok(code) }