use quote::quote; use syn::spanned::Spanned; use syn::{FnArg, Ident, Pat, PatType, Signature, Type, TypePath}; /// Extracted information about a resource injection parameter pub(crate) struct ResourceInjection { pub(crate) var_name: Ident, pub(crate) full_type: Type, pub(crate) inner_type: TypePath, pub(crate) is_ref: bool, pub(crate) is_mut: bool, } /// Extracts the previous type and parameter name from function arguments, /// and collects resource injection parameters from the 2nd argument onward. #[allow(clippy::too_many_lines)] pub(crate) fn extract_args_info( sig: &Signature, ) -> syn::Result<(Pat, TypePath, Vec)> { if sig.inputs.is_empty() { return Err(syn::Error::new( sig.span(), "Function must have at least one parameter", )); } // First parameter: required, the previous type (must be owned, not a reference) let first_arg = &sig.inputs[0]; let (prev_param, previous_type) = match first_arg { FnArg::Typed(PatType { pat, ty, .. }) => { let param_pat = (**pat).clone(); match &**ty { Type::Path(type_path) => { // Check that the type is a single-segment type (no `::`) if type_path.path.segments.len() > 1 { return Err(syn::Error::new( type_path.span(), format!( "The type `{}` must be a simple single-segment type, \ e.g. `Empty` instead of `other::Empty`. \ Qualified paths with `::` are not allowed here.", quote! { #type_path } ), )); } (param_pat, type_path.clone()) } Type::Reference(_) => { return Err(syn::Error::new( ty.span(), "The first parameter (previous type) must be taken by move, \ not by reference. \ Use `prev: SomeEntry` instead of `prev: &SomeEntry`.", )); } _ => { return Err(syn::Error::new( ty.span(), "First parameter type must be a type path", )); } } } FnArg::Receiver(_) => { return Err(syn::Error::new( first_arg.span(), "Function cannot have self parameter", )); } }; // 2nd to Nth parameters: optional, for resource injection let mut resources = Vec::new(); for arg in sig.inputs.iter().skip(1) { match arg { FnArg::Typed(PatType { pat, ty, .. }) => { // Extract the variable name – must be a simple identifier let var_name = match &**pat { Pat::Ident(pat_ident) => pat_ident.ident.clone(), _ => { return Err(syn::Error::new( pat.span(), "Resource injection parameter must be a simple identifier (e.g., `age: &Age`)", )); } }; let full_type = *(*ty).clone(); // Try to extract inner type for reference patterns like `&Age` -> `Age` // and `&mut Age` -> `Age` let (inner_type, is_ref, is_mut) = match &full_type { Type::Reference(ref_type) => match &*ref_type.elem { Type::Path(type_path) => { let is_mut = ref_type.mutability.is_some(); (type_path.clone(), true, is_mut) } _ => { return Err(syn::Error::new( ty.span(), "Reference resource type must be a type path (e.g., `age: &Age`)", )); } }, Type::Path(_) => { return Err(syn::Error::new( ty.span(), "Resource injection parameter must be a reference (`&T` or `&mut T`), \ not an owned value. Use `age: &Age` instead of `age: Age`.", )); } _ => { return Err(syn::Error::new( ty.span(), "Resource injection type must be a type path or reference to one \ (e.g., `age: Age` or `age: &Age`)", )); } }; resources.push(ResourceInjection { var_name, full_type, inner_type, is_ref, is_mut, }); } FnArg::Receiver(_) => { return Err(syn::Error::new( arg.span(), "Resource injection parameter cannot be self", )); } } } Ok((prev_param, previous_type, resources)) } /// Generates `let` binding statements for immutable resource injection parameters. /// /// Each immutable reference parameter gets a `_binding` variable that holds the /// `res_or_default` result, then a shadowing `let` that borrows from it via `.as_ref()`. pub(crate) fn generate_immut_resource_bindings<'a>( resources: impl Iterator, program_type: &proc_macro2::TokenStream, ) -> Vec { resources .filter(|r| !r.is_mut) .map(|res| { let var_binding_name = syn::Ident::new( &format!("__{}_binding", &res.var_name.to_string()), res.var_name.span(), ); let var_name = &res.var_name; let full_type = &res.full_type; let inner_type = &res.inner_type; if res.is_ref { quote! { let #var_binding_name = ::mingling::this::<#program_type>() .res_or_default::<#inner_type>(); let #var_name: #full_type = #var_binding_name.as_ref(); } } else { quote! { let #var_name: #full_type = ::mingling::this::<#program_type>() .res_or_default::<#full_type>(); } } }) .collect() } /// Wraps the function body in nested `__modify_res_and_return_route` closures for /// each mutable resource parameter. The innermost closure gets the original body, /// and each mutable parameter wraps outward from last to first. pub(crate) fn wrap_body_with_mut_resources( fn_body_stmts: &[syn::Stmt], mut_resources: &[&ResourceInjection], program_type: &proc_macro2::TokenStream, ) -> proc_macro2::TokenStream { let mut wrapped = quote! { #(#fn_body_stmts)* }; for res in mut_resources { let var_name = &res.var_name; let inner_type = &res.inner_type; wrapped = quote! { ::mingling::this::<#program_type>().__modify_res_and_return_route(|#var_name: &mut #inner_type| { #wrapped }).into() }; } wrapped }