use proc_macro::TokenStream;
use quote::{ToTokens, quote};
use syn::spanned::Spanned;
use syn::{FnArg, ItemFn, Pat, PatType, ReturnType, Signature, Type, TypePath, parse_macro_input};
fn extract_previous_info(sig: &Signature) -> syn::Result<(Pat, TypePath)> {
if sig.inputs.len() != 1 {
return Err(syn::Error::new(
sig.inputs.span(),
"Renderer function must have exactly one parameter (the previous type)",
));
}
let arg = &sig.inputs[0];
match arg {
FnArg::Typed(PatType { pat, ty, .. }) => {
let param_pat = (**pat).clone();
match &**ty {
Type::Path(type_path) => Ok((param_pat, type_path.clone())),
_ => Err(syn::Error::new(
ty.span(),
"Parameter type must be a type path",
)),
}
}
FnArg::Receiver(_) => Err(syn::Error::new(
arg.span(),
"Renderer function cannot have self parameter",
)),
}
}
fn extract_return_type(sig: &Signature) -> syn::Result<()> {
match &sig.output {
ReturnType::Type(_, ty) => {
match &**ty {
Type::Tuple(tuple) if tuple.elems.is_empty() => Ok(()),
_ => Err(syn::Error::new(
ty.span(),
"Renderer function must return () or have no return type",
)),
}
}
ReturnType::Default => Ok(()),
}
}
pub fn renderer_attr(item: TokenStream) -> TokenStream {
let input_fn = parse_macro_input!(item as ItemFn);
if input_fn.sig.asyncness.is_some() {
return syn::Error::new(input_fn.sig.span(), "Renderer function cannot be async")
.to_compile_error()
.into();
}
let (prev_param, previous_type) = match extract_previous_info(&input_fn.sig) {
Ok(info) => info,
Err(e) => return e.to_compile_error().into(),
};
if let Err(e) = extract_return_type(&input_fn.sig) {
return e.to_compile_error().into();
}
let fn_body = &input_fn.block;
let mut fn_attrs = input_fn.attrs.clone();
fn_attrs.retain(|attr| !attr.path().is_ident("renderer"));
let vis = &input_fn.vis;
let fn_name = &input_fn.sig.ident;
let pascal_case_name = just_fmt::pascal_case!(fn_name.to_string());
let struct_name = syn::Ident::new(&pascal_case_name, fn_name.span());
let renderer_entry = build_renderer_entry(&struct_name, &previous_type);
let renderer_exist_entry = build_renderer_exist_entry(&previous_type);
#[cfg(feature = "general_renderer")]
let general_renderer_entry = build_general_renderer_entry(&previous_type);
let mut renderers = crate::RENDERERS.lock().unwrap();
let mut renderer_exist = crate::RENDERERS_EXIST.lock().unwrap();
let mut packed_types = crate::PACKED_TYPES.lock().unwrap();
#[cfg(feature = "general_renderer")]
let mut general_renderers = crate::GENERAL_RENDERERS.lock().unwrap();
let renderer_entry_str = renderer_entry.to_string();
let renderer_exist_entry_str = renderer_exist_entry.to_string();
let previous_type_str = previous_type.to_token_stream().to_string();
#[cfg(feature = "general_renderer")]
let general_renderer_entry_str = general_renderer_entry.to_string();
renderers.insert(renderer_entry_str);
renderer_exist.insert(renderer_exist_entry_str);
packed_types.insert(previous_type_str);
#[cfg(feature = "general_renderer")]
general_renderers.insert(general_renderer_entry_str);
let expanded = quote! {
#(#fn_attrs)*
#vis struct #struct_name;
impl ::mingling::Renderer for #struct_name {
type Previous = #previous_type;
fn render(#prev_param: Self::Previous, r: &mut ::mingling::RenderResult) {
#[allow(non_snake_case)]
fn render_wrapper(#prev_param: #previous_type, r: &mut ::mingling::RenderResult) {
#fn_body
}
render_wrapper(#prev_param, r);
}
}
#(#fn_attrs)*
#vis fn #fn_name(#prev_param: #previous_type) {
let mut dummy_r = ::mingling::RenderResult::default();
let r = &mut dummy_r;
#fn_body
}
};
expanded.into()
}
pub fn build_renderer_entry(
struct_name: &syn::Ident,
previous_type: &TypePath,
) -> proc_macro2::TokenStream {
quote! {
#struct_name => #previous_type,
}
}
pub fn build_renderer_exist_entry(previous_type: &TypePath) -> proc_macro2::TokenStream {
quote! {
Self::#previous_type => true,
}
}
#[cfg(feature = "general_renderer")]
pub fn build_general_renderer_entry(previous_type: &TypePath) -> proc_macro2::TokenStream {
quote! {
Self::#previous_type => {
let raw = unsafe { any.restore::<#previous_type>().unwrap_unchecked() };
let mut r = ::mingling::RenderResult::default();
::mingling::GeneralRenderer::render(&raw, setting, &mut r)?;
Ok(r)
}
}
}