mingling_macros 0.1.0

Macros for the mingling library
Documentation
//! Renderer Attribute Macro Implementation
//!
//! This module provides the `#[renderer]` attribute macro for automatically
//! generating structs that implement the `Renderer` trait from functions.

use proc_macro::TokenStream;
use quote::quote;
use syn::spanned::Spanned;
use syn::{FnArg, ItemFn, Pat, PatType, ReturnType, Signature, Type, TypePath, parse_macro_input};

/// Extracts the previous type and parameter name from function arguments
fn extract_previous_info(sig: &Signature) -> syn::Result<(Pat, TypePath)> {
    // The function should have exactly one parameter
    if sig.inputs.len() != 1 {
        return Err(syn::Error::new(
            sig.inputs.span(),
            "Renderer function must have exactly one parameter (the previous type)",
        ));
    }

    // First and only parameter is the previous type
    let arg = &sig.inputs[0];
    match arg {
        FnArg::Typed(PatType { pat, ty, .. }) => {
            // Extract the pattern (parameter name)
            let param_pat = (**pat).clone();

            // Extract the type
            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",
        )),
    }
}

/// Extracts the return type from the function signature
fn extract_return_type(sig: &Signature) -> syn::Result<()> {
    // Renderer functions should return () or have no return type
    match &sig.output {
        ReturnType::Type(_, ty) => {
            // Check if it's ()
            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 {
    // Parse the function item
    let input_fn = parse_macro_input!(item as ItemFn);

    // Validate the function is not async
    if input_fn.sig.asyncness.is_some() {
        return syn::Error::new(input_fn.sig.span(), "Renderer function cannot be async")
            .to_compile_error()
            .into();
    }

    // Extract the previous type and parameter name from function arguments
    let (prev_param, previous_type) = match extract_previous_info(&input_fn.sig) {
        Ok(info) => info,
        Err(e) => return e.to_compile_error().into(),
    };

    // Validate return type
    if let Err(e) = extract_return_type(&input_fn.sig) {
        return e.to_compile_error().into();
    }

    // Get the function body
    let fn_body = &input_fn.block;

    // Get function attributes (excluding the renderer attribute)
    let mut fn_attrs = input_fn.attrs.clone();

    // Remove any #[renderer(...)] attributes to avoid infinite recursion
    fn_attrs.retain(|attr| !attr.path().is_ident("renderer"));

    // Get function visibility
    let vis = &input_fn.vis;

    // Get function name
    let fn_name = &input_fn.sig.ident;

    // Generate struct name from function name using pascal_case
    let pascal_case_name = just_fmt::pascal_case!(fn_name.to_string());
    let struct_name = syn::Ident::new(&pascal_case_name, fn_name.span());

    // Register the renderer in the global list
    let renderer_entry = quote! {
        #struct_name => #previous_type,
    };
    let renderer_exist_entry = quote! {
        id if id == std::any::TypeId::of::<#previous_type>() => true,
    };

    let mut renderers = crate::RENDERERS.lock().unwrap();
    let mut renderer_exist = crate::RENDERERS_EXIST.lock().unwrap();
    let renderer_entry_str = renderer_entry.to_string();
    let renderer_exist_entry_str = renderer_exist_entry.to_string();
    if !renderers.contains(&renderer_entry_str) {
        renderers.push(renderer_entry_str);
    }
    if !renderer_exist.contains(&renderer_exist_entry_str) {
        renderer_exist.push(renderer_exist_entry_str);
    }

    // Generate the struct and implementation
    // We need to create a wrapper function that adds the r parameter
    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) {
                // Create a local wrapper function that includes r parameter
                // This allows r_println! to access r
                #[allow(non_snake_case)]
                fn render_wrapper(#prev_param: #previous_type, r: &mut ::mingling::RenderResult) {
                    #fn_body
                }

                // Call the wrapper function
                render_wrapper(#prev_param, r);
            }
        }

        // Keep the original function for internal use (without r parameter)
        #(#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()
}