use proc_macro::TokenStream;
use proc_macro2::Span;
use quote::quote;
use quote::ToTokens;
use syn::{FnArg::Typed, Ident, ItemFn, ReturnType::Type};
use crate::EXPORTNUM;
pub fn patchable(fn_item: ItemFn, modpath: Option<String>) -> TokenStream {
let (fargs, output_type, mut fn_name, sigtext, mut item) = gather_info(fn_item);
if !cfg!(feature = "allow-main") && !cfg!(feature = "redirect-main") && fn_name == "main" {
fn_name.span().unwrap().error("Attempted to set main as patchable")
.note("calling main.hotpatch() would cause a deadlock")
.help("enable the 'allow-main' feature if you're using #[main] or #[start]")
.help("enable the 'redirect-main' feature if you actually want main to be patchable (requires unsafe and nightly, read the docs on force functions)")
.emit();
return TokenStream::new();
}
let vis = item.vis.clone();
let mut docitem = item.clone();
docitem.attrs.append(
&mut syn::parse2::<syn::ItemStruct>(quote! {
#[cfg(doc)]
struct Dummy {}
})
.unwrap()
.attrs,
);
let item_name = fn_name.clone();
fn_name = Ident::new("__hotpatch_internal_fn_mangle_name", Span::call_site());
item.sig.ident = fn_name.clone();
let redirected_main = if cfg!(feature = "redirect-main") && item_name == "main" {
quote! {
#[main]
fn __hotpatch_redirect_main() -> #output_type {
main()
}
}
} else {
quote! {}
};
let mname = match modpath {
Some(mpath) => (quote! {concat!("::", #mpath)}),
None => {
quote! {
concat!(module_path!(), "::", stringify!(#item_name))
}
}
};
TokenStream::from(quote! {
#docitem
#[cfg(not(doc))]
#[allow(non_upper_case_globals)]
#vis static #item_name: hotpatch::Patchable<dyn Fn#fargs -> #output_type + Send + Sync + 'static> = hotpatch::Patchable::__new(
|| {
#[inline(always)]
#item
hotpatch::Patchable::__new_internal(Box::new(#fn_name) as Box<dyn Fn#fargs -> #output_type + Send + Sync + 'static>,
#mname,
#sigtext)
});
#redirected_main
})
}
pub fn patch(fn_item: ItemFn, modpath: Option<String>) -> TokenStream {
let (fargs, output_type, fn_name, sigtext, mut item) = gather_info(fn_item);
let exnum;
{
let mut r = EXPORTNUM.write().unwrap();
exnum = *r;
*r += 1;
}
item.attrs.append(
&mut syn::parse2::<syn::ItemStruct>(quote! {
struct Dummy {}
})
.unwrap()
.attrs,
);
let hotpatch_name = Ident::new(&format!("__HOTPATCH_EXPORT_{}", exnum), Span::call_site());
let mname = match modpath {
Some(mpath) => (quote! {concat!("::", #mpath)}),
None => {
quote! {
concat!(module_path!(), "::", stringify!(#fn_name))
}
}
};
TokenStream::from(quote! {
#item
#[doc(hidden)]
#[no_mangle]
pub static #hotpatch_name: hotpatch::HotpatchExport<fn#fargs -> #output_type> =
hotpatch::HotpatchExport::__new(#fn_name,
#mname,
#sigtext);
})
}
fn gather_info(item: ItemFn) -> (syn::Type, syn::Type, Ident, String, ItemFn) {
let fn_name = item.sig.ident.clone();
let output_type = if let Type(_, t) = &item.sig.output {
*(t.clone())
} else {
syn::parse2::<syn::Type>(quote! {
()
})
.unwrap()
};
let mut ts = proc_macro2::TokenStream::new();
output_type.to_tokens(&mut ts);
let sigtext = format!(
"fn({}) -> {}",
item.sig
.inputs
.clone()
.into_iter()
.map(|input| {
if let syn::FnArg::Typed(t) = input {
let mut ts = proc_macro2::TokenStream::new();
t.ty.to_tokens(&mut ts);
ts.to_string()
} else {
todo!() }
})
.collect::<Vec<String>>()
.join(", "),
ts
);
let mut args = vec![];
for i in 0..item.sig.inputs.len() {
if let Typed(arg) = &item.sig.inputs[i] {
args.push(arg.ty.clone());
}
}
let fargs = syn::parse2::<syn::Type>(if args.len() == 0 {
quote! {
()
}
} else {
quote! {
(#(#args),*,)
}
})
.unwrap();
(fargs, output_type, fn_name, sigtext, item)
}