diff options
Diffstat (limited to 'mingling_macros/src')
| -rw-r--r-- | mingling_macros/src/chain.rs | 199 | ||||
| -rw-r--r-- | mingling_macros/src/lib.rs | 5 | ||||
| -rw-r--r-- | mingling_macros/src/renderer.rs | 132 | ||||
| -rw-r--r-- | mingling_macros/src/res_injection.rs | 197 |
4 files changed, 277 insertions, 256 deletions
diff --git a/mingling_macros/src/chain.rs b/mingling_macros/src/chain.rs index 60e44e9..9666c51 100644 --- a/mingling_macros/src/chain.rs +++ b/mingling_macros/src/chain.rs @@ -1,143 +1,13 @@ #![allow(clippy::too_many_arguments)] +use crate::res_injection::{ + ResourceInjection, extract_args_info, generate_immut_resource_bindings, + wrap_body_with_mut_resources, +}; use proc_macro::TokenStream; use quote::{ToTokens, quote}; use syn::spanned::Spanned; -use syn::{ - FnArg, Ident, ItemFn, Pat, PatType, ReturnType, Signature, Type, TypePath, parse_macro_input, -}; - -/// Extracted information about a resource injection parameter -struct ResourceInjection { - var_name: Ident, - full_type: Type, - inner_type: TypePath, - is_ref: bool, - is_mut: bool, -} - -/// Extracts the previous type and parameter name from function arguments, -fn extract_args_info(sig: &Signature) -> syn::Result<(Pat, TypePath, Vec<ResourceInjection>)> { - if sig.inputs.is_empty() { - return Err(syn::Error::new( - sig.span(), - "Chain function must have at least one parameter", - )); - } - - // First parameter: required, the previous chain 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 `{}` in #[chain] function 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(), - "Chain 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)) -} +use syn::{Ident, ItemFn, Pat, ReturnType, Signature, Type, TypePath, parse_macro_input}; /// Parses the `#[chain(...)]` attribute arguments. /// @@ -202,65 +72,6 @@ fn validate_return_type(sig: &Signature) -> Result<(), proc_macro2::TokenStream> Ok(()) } -/// 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()`. -fn generate_immut_resource_bindings<'a>( - resources: impl Iterator<Item = &'a ResourceInjection>, - program_type: &proc_macro2::TokenStream, -) -> Vec<proc_macro2::TokenStream> { - 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. -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.iter() { - 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 -} - /// Builds the `proc` function implementation that serves as the actual chain /// entry point inside the generated `Chain` impl. /// diff --git a/mingling_macros/src/lib.rs b/mingling_macros/src/lib.rs index 1733470..8c98ba3 100644 --- a/mingling_macros/src/lib.rs +++ b/mingling_macros/src/lib.rs @@ -41,6 +41,7 @@ mod pack; mod program_setup; mod render; mod renderer; +mod res_injection; #[cfg(feature = "comp")] mod suggest; @@ -616,8 +617,8 @@ pub fn chain(attr: TokenStream, item: TokenStream) -> TokenStream { /// } /// ``` #[proc_macro_attribute] -pub fn renderer(_attr: TokenStream, item: TokenStream) -> TokenStream { - renderer::renderer_attr(item) +pub fn renderer(attr: TokenStream, item: TokenStream) -> TokenStream { + renderer::renderer_attr(attr, item) } /// Declares a completion suggestion provider for a command entry type. diff --git a/mingling_macros/src/renderer.rs b/mingling_macros/src/renderer.rs index 39ea594..db04c19 100644 --- a/mingling_macros/src/renderer.rs +++ b/mingling_macros/src/renderer.rs @@ -1,42 +1,12 @@ 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}; +use syn::{ItemFn, ReturnType, Signature, Type, TypePath, parse_macro_input}; use crate::get_global_set; - -/// 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", - )), - } -} +use crate::res_injection::{ + extract_args_info, generate_immut_resource_bindings, wrap_body_with_mut_resources, +}; /// Extracts and returns the return type from the function signature (or None for `()` / no return type). fn extract_return_type(sig: &Signature) -> syn::Result<Option<syn::Type>> { @@ -53,7 +23,11 @@ fn extract_return_type(sig: &Signature) -> syn::Result<Option<syn::Type>> { } } -pub fn renderer_attr(item: TokenStream) -> TokenStream { +pub fn renderer_attr(attr: TokenStream, item: TokenStream) -> TokenStream { + // Parse attribute arguments for program path (e.g. #[renderer(my_crate::Program)]) + let (program_path, _use_crate_prefix) = parse_renderer_attr_args(attr); + let program_type = &program_path; + // Parse the function item let input_fn = parse_macro_input!(item as ItemFn); @@ -64,8 +38,8 @@ pub fn renderer_attr(item: TokenStream) -> TokenStream { .into(); } - // Extract the previous type and parameter name from function arguments - let (prev_param, previous_type) = match extract_previous_info(&input_fn.sig) { + // Extract the previous type, parameter name, and resource injection params + let (prev_param, previous_type, resources) = match extract_args_info(&input_fn.sig) { Ok(info) => info, Err(e) => return e.to_compile_error().into(), }; @@ -81,8 +55,8 @@ pub fn renderer_attr(item: TokenStream) -> TokenStream { Err(e) => return e.to_compile_error().into(), }; - // Get the function body - let fn_body = &input_fn.block; + // Get function body statements + let fn_body_stmts: Vec<syn::Stmt> = input_fn.block.stmts.clone(); // Get function attributes (excluding the renderer attribute) let mut fn_attrs = input_fn.attrs.clone(); @@ -101,6 +75,13 @@ pub fn renderer_attr(item: TokenStream) -> TokenStream { "__internal_renderer_{}", just_fmt::snake_case!(fn_name.to_string()) ); + let struct_name = syn::Ident::new(&internal_name, fn_name.span()); + + let has_resources = !resources.is_empty(); + + // Generate resource bindings for immutable resources + let immut_resource_stmts = generate_immut_resource_bindings(resources.iter(), program_type); + let mut_resources: Vec<_> = resources.iter().filter(|r| r.is_mut).collect(); // Determine public return type and the expression to return dummy_r let (public_return_type, result_return) = match &return_type { @@ -121,10 +102,46 @@ pub fn renderer_attr(item: TokenStream) -> TokenStream { (ret_ty, expr) } }; - let struct_name = syn::Ident::new(&internal_name, fn_name.span()); - // Generate the struct and implementation - // We need to create a wrapper function that adds the r parameter + // Build the Renderer::render body with resource injection + let render_fn_body = if has_resources { + let wrapped_body = + wrap_body_with_mut_resources(&fn_body_stmts, &mut_resources, program_type); + quote! { + #(#immut_resource_stmts)* + #wrapped_body + } + } else { + quote! { #(#fn_body_stmts)* } + }; + + // Build the original function with resource injection (no `.into()` — signature is exactly as user wrote) + let original_fn_body = if has_resources { + let wrapped_body = + wrap_body_with_mut_resources(&fn_body_stmts, &mut_resources, program_type); + quote! { + let mut dummy_r = ::mingling::RenderResult::default(); + { + let __renderer_inner_result = &mut dummy_r; + #(#immut_resource_stmts)* + #wrapped_body + } + #result_return + } + } else { + quote! { + let mut dummy_r = ::mingling::RenderResult::default(); + { + let __renderer_inner_result = &mut dummy_r; + #(#fn_body_stmts)* + } + #result_return + } + }; + + // Keep the original function signature unchanged (same params as user wrote) + let original_inputs = input_fn.sig.inputs.clone(); + let expanded = quote! { #(#fn_attrs)* #[doc(hidden)] @@ -137,33 +154,28 @@ pub fn renderer_attr(item: TokenStream) -> TokenStream { type Previous = #previous_type; fn render(#prev_param: Self::Previous, __renderer_inner_result: &mut ::mingling::RenderResult) { - // Create a local wrapper function that includes `__renderer_inner_result` parameter - // This allows r_println! to access `__renderer_inner_result` - #[allow(non_snake_case)] - fn render_wrapper(#prev_param: #previous_type, __renderer_inner_result: &mut ::mingling::RenderResult) { - #fn_body - } - - // Call the wrapper function - render_wrapper(#prev_param, __renderer_inner_result); + #render_fn_body } } // Keep the original function for internal use (without r parameter) #(#fn_attrs)* - #vis fn #fn_name(#prev_param: impl Into<#previous_type>) -> #public_return_type { - let #prev_param = #prev_param.into(); - let mut dummy_r = ::mingling::RenderResult::default(); - { - let __renderer_inner_result = &mut dummy_r; - #fn_body - } - #result_return + #vis fn #fn_name(#original_inputs) -> #public_return_type { + #original_fn_body } }; expanded.into() } +fn parse_renderer_attr_args(attr: TokenStream) -> (proc_macro2::TokenStream, bool) { + if attr.is_empty() { + (crate::default_program_path(), true) + } else { + let path: syn::Path = + syn::parse(attr).expect("Expected a path argument for #[renderer(path)]"); + (quote! { #path }, false) + } +} /// Builds the renderer entry for the global renderers list pub fn build_renderer_entry( @@ -201,7 +213,7 @@ pub fn register_renderer(input: TokenStream) -> TokenStream { // Parse the input as a comma-separated list of arguments let input_parsed = syn::parse_macro_input!(input with syn::punctuated::Punctuated<syn::Expr, syn::Token![,]>::parse_terminated); - // Check that we have exactly two elements + // Check that there are exactly two elements if input_parsed.len() != 2 { return syn::Error::new( input_parsed.span(), diff --git a/mingling_macros/src/res_injection.rs b/mingling_macros/src/res_injection.rs new file mode 100644 index 0000000..0f180db --- /dev/null +++ b/mingling_macros/src/res_injection.rs @@ -0,0 +1,197 @@ +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. +pub(crate) fn extract_args_info( + sig: &Signature, +) -> syn::Result<(Pat, TypePath, Vec<ResourceInjection>)> { + 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<Item = &'a ResourceInjection>, + program_type: &proc_macro2::TokenStream, +) -> Vec<proc_macro2::TokenStream> { + 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.iter() { + 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 +} |
