diff options
Diffstat (limited to 'mingling_macros/src')
| -rw-r--r-- | mingling_macros/src/attr/chain.rs | 73 | ||||
| -rw-r--r-- | mingling_macros/src/attr/completion.rs | 39 | ||||
| -rw-r--r-- | mingling_macros/src/attr/help.rs | 41 | ||||
| -rw-r--r-- | mingling_macros/src/attr/renderer.rs | 47 |
4 files changed, 140 insertions, 60 deletions
diff --git a/mingling_macros/src/attr/chain.rs b/mingling_macros/src/attr/chain.rs index 26bb137..dbd87dd 100644 --- a/mingling_macros/src/attr/chain.rs +++ b/mingling_macros/src/attr/chain.rs @@ -34,16 +34,14 @@ fn validate_return_type(sig: &Signature) -> Result<(), proc_macro2::TokenStream> /// Builds the `proc` function implementation inside the generated `Chain` impl. /// -/// The user's function body is inlined directly, and its result is converted -/// via `.into()` to `ChainProcess<ProgramType>`. -#[allow(unused_variables)] +/// Instead of inlining the user's body, the trait method calls the original +/// function by name, with resources injected from the application context. fn generate_proc_fn( + fn_name: &Ident, has_resources: bool, resources: &[ResourceInjection], program_type: &proc_macro2::TokenStream, previous_type: &TypePath, - prev_param: &Pat, - fn_body_stmts: &[syn::Stmt], is_async_fn: bool, is_unit_return: bool, origin_return_type: &proc_macro2::TokenStream, @@ -51,10 +49,52 @@ fn generate_proc_fn( let immut_resource_stmts = generate_immut_resource_bindings(resources.iter(), program_type); let mut_resources: Vec<_> = resources.iter().filter(|r| r.is_mut).collect(); + // Use a fixed parameter name `prev` for the trait method, regardless of + // the user's original parameter name (which may be `_` and cannot be + // referenced in expression position). + let fixed_prev: Pat = syn::parse_quote!(prev); + + // Build the call to the original function with resource arguments injected. + // The variable names come from the resource injection bindings + // (immut bindings are `let #name = …`, mut closures receive `|#name: &mut T|`), + // which match the original function's parameter names. + let resource_args: Vec<_> = resources + .iter() + .map(|res| { + let var_name = &res.var_name; + quote! { #var_name } + }) + .collect(); + + let fn_call = if has_resources { + let call = quote! { #fn_name(#fixed_prev, #(#resource_args),*) }; + if is_async_fn { + quote! { #call.await } + } else { + call + } + } else { + let call = quote! { #fn_name(#fixed_prev) }; + if is_async_fn { + quote! { #call.await } + } else { + call + } + }; + + // Convert the function call to a syn::Stmt so existing wrapping functions can use it + let fn_call_expr: syn::Expr = syn::parse_quote! { #fn_call }; + let fn_call_stmt = syn::Stmt::Expr(fn_call_expr, None); + let wrapped_body = if is_async_fn && !mut_resources.is_empty() { - wrap_body_with_mut_resources_async(fn_body_stmts, &mut_resources, program_type) + wrap_body_with_mut_resources_async(&[fn_call_stmt], &mut_resources, program_type) } else { - wrap_body_with_mut_resources(fn_body_stmts, &mut_resources, program_type, is_unit_return) + wrap_body_with_mut_resources( + &[fn_call_stmt], + &mut_resources, + program_type, + is_unit_return, + ) }; let proc_body = if is_unit_return { @@ -82,11 +122,6 @@ fn generate_proc_fn( } else { quote! { #wrapped_body } }; - // Convert the body result to `ChainProcess` using the user-declared - // return type as the source type for a fully-qualified `Into` call. - // This works for both: - // - `-> Next` / `-> ChainProcess`: identity `From<T> for T` - // - `-> PackType`: `Into<ChainProcess>` from pack!/derive quote! { let __chain_result = { #body }; <#origin_return_type as ::std::convert::Into< @@ -98,7 +133,7 @@ fn generate_proc_fn( #[cfg(feature = "async")] { quote! { - async fn proc(#prev_param: #previous_type) -> ::mingling::ChainProcess<#program_type> { + async fn proc(#fixed_prev: #previous_type) -> ::mingling::ChainProcess<#program_type> { #proc_body } } @@ -107,7 +142,7 @@ fn generate_proc_fn( #[cfg(not(feature = "async"))] { quote! { - fn proc(#prev_param: #previous_type) -> ::mingling::ChainProcess<#program_type> { + fn proc(#fixed_prev: #previous_type) -> ::mingling::ChainProcess<#program_type> { #proc_body } } @@ -192,13 +227,12 @@ pub(crate) fn chain_attr(attr: TokenStream, item: TokenStream) -> TokenStream { } // Extract the previous type, parameter name, and resource injection params - let (prev_param, previous_type, resources) = match extract_args_info(&input_fn.sig) { + let (_, previous_type, resources) = match extract_args_info(&input_fn.sig) { Ok(info) => info, Err(e) => return e.to_compile_error().into(), }; // Prepare building blocks - let fn_body = &input_fn.block; let mut fn_attrs = input_fn.attrs.clone(); fn_attrs.retain(|attr| !attr.path().is_ident("chain")); let vis = &input_fn.vis; @@ -223,12 +257,11 @@ pub(crate) fn chain_attr(attr: TokenStream, item: TokenStream) -> TokenStream { // Generate the `proc` function for the Chain impl let proc_fn = generate_proc_fn( + fn_name, has_resources, &resources, &program_type, &previous_type, - &prev_param, - &fn_body.stmts, #[cfg(feature = "async")] is_async_fn, #[cfg(not(feature = "async"))] @@ -237,11 +270,9 @@ pub(crate) fn chain_attr(attr: TokenStream, item: TokenStream) -> TokenStream { &origin_return_type, ); - // Preserve the original function untouched, with dead_code allowed - // since it may only be called through the Chain trait dispatch. + // Preserve the original function untouched // Note: do NOT add `#vis` here — `input_fn` (ItemFn) already contains its own visibility. let original_fn = quote! { - #[allow(dead_code)] #(#fn_attrs)* #input_fn }; diff --git a/mingling_macros/src/attr/completion.rs b/mingling_macros/src/attr/completion.rs index e917d7d..3ced091 100644 --- a/mingling_macros/src/attr/completion.rs +++ b/mingling_macros/src/attr/completion.rs @@ -47,11 +47,8 @@ pub(crate) fn completion_attr(attr: TokenStream, item: TokenStream) -> TokenStre // Extract the first param pattern and type for the ctx parameter let first_arg = &inputs[0]; - let (ctx_pat, _ctx_type) = match first_arg { - FnArg::Typed(PatType { pat, ty, .. }) => { - let param_pat = (**pat).clone(); - (param_pat, (**ty).clone()) - } + let _ctx_type = match first_arg { + FnArg::Typed(PatType { ty, .. }) => (**ty).clone(), FnArg::Receiver(_) => { return syn::Error::new( first_arg.span(), @@ -61,6 +58,7 @@ pub(crate) fn completion_attr(attr: TokenStream, item: TokenStream) -> TokenStre .into(); } }; + let fixed_ctx: Pat = syn::parse_quote!(ctx); // Extract resources from params 2 through N, skipping ctx let resources = match extract_resources_from_args(sig, 1) { @@ -70,7 +68,6 @@ pub(crate) fn completion_attr(attr: TokenStream, item: TokenStream) -> TokenStre // Get the function body let fn_body = &input_fn.block; - let fn_body_stmts = &fn_body.stmts; // Get function attributes excluding the completion attribute let mut fn_attrs = input_fn.attrs.clone(); @@ -96,12 +93,26 @@ pub(crate) fn completion_attr(attr: TokenStream, item: TokenStream) -> TokenStre // Generate immutable resource bindings let immut_resource_stmts = generate_immut_resource_bindings(resources.iter(), &program_type); - // Build the comp method body with resource injection - // Use modify_res for mutable resources same pattern as renderer.rs - let wrapped_body = if mut_resources.is_empty() { - quote! { #(#fn_body_stmts)* } + // Build the call to the original function with resource arguments injected + let resource_args: Vec<_> = resources + .iter() + .map(|res| { + let var_name = &res.var_name; + quote! { #var_name } + }) + .collect(); + + let fn_call = if has_resources { + quote! { #fn_name(#fixed_ctx, #(#resource_args),*) } + } else { + quote! { #fn_name(#fixed_ctx) } + }; + + // Wrap the function call with modify_res for mutable resources + let inner_call = if mut_resources.is_empty() { + fn_call } else { - let mut wrapped = quote! { #(#fn_body_stmts)* }; + let mut wrapped = fn_call; for res in mut_resources.iter().rev() { let var_name = &res.var_name; let inner_type = &res.inner_type; @@ -117,10 +128,10 @@ pub(crate) fn completion_attr(attr: TokenStream, item: TokenStream) -> TokenStre let comp_body = if has_resources { quote! { #(#immut_resource_stmts)* - #wrapped_body + #inner_call } } else { - quote! { #(#fn_body_stmts)* } + quote! { #inner_call } }; // Generate the struct and implementation @@ -135,7 +146,7 @@ pub(crate) fn completion_attr(attr: TokenStream, item: TokenStream) -> TokenStre impl ::mingling::Completion for #struct_name { type Previous = #previous_type_path; - fn comp(#ctx_pat: &::mingling::ShellContext) #output { + fn comp(#fixed_ctx: &::mingling::ShellContext) #output { #comp_body } } diff --git a/mingling_macros/src/attr/help.rs b/mingling_macros/src/attr/help.rs index 6defae4..aa7bc88 100644 --- a/mingling_macros/src/attr/help.rs +++ b/mingling_macros/src/attr/help.rs @@ -1,7 +1,7 @@ use proc_macro::TokenStream; use quote::{ToTokens, quote}; use syn::spanned::Spanned; -use syn::{Ident, ItemFn, ReturnType, Signature, TypePath, parse_macro_input}; +use syn::{Ident, ItemFn, Pat, ReturnType, Signature, TypePath, parse_macro_input}; use crate::get_global_set; use crate::res_injection::{extract_args_info, generate_immut_resource_bindings}; @@ -25,8 +25,8 @@ pub(crate) fn help_attr(item: TokenStream) -> TokenStream { .into(); } - // Extract the entry type, parameter name, and resource injection params - let (prev_param, entry_type, resources) = match extract_args_info(&input_fn.sig) { + // Extract the entry type and resource injection params + let (_, entry_type, resources) = match extract_args_info(&input_fn.sig) { Ok(info) => info, Err(e) => return e.to_compile_error().into(), }; @@ -66,13 +66,31 @@ pub(crate) fn help_attr(item: TokenStream) -> TokenStream { // Generate immutable resource bindings let immut_resource_stmts = generate_immut_resource_bindings(resources.iter(), &program_type); - // Build the render_help body with resource injection - // Use modify_res for mutable resources same pattern as renderer.rs + // Build the call to the original function with resource arguments injected + let resource_args: Vec<_> = resources + .iter() + .map(|res| { + let var_name = &res.var_name; + quote! { #var_name } + }) + .collect(); + + // Use a fixed parameter name `prev` for the trait method, regardless of + // the user's original parameter name (which may be `_` and cannot be + // referenced in expression position). + let fixed_prev: Pat = syn::parse_quote!(prev); + + let fn_call = if has_resources { + quote! { #fn_name(#fixed_prev, #(#resource_args),*) } + } else { + quote! { #fn_name(#fixed_prev) } + }; - let wrapped_body = if mut_resources.is_empty() { - quote! { #(#fn_body_stmts)* } + // Wrap the function call with modify_res for mutable resources + let inner_call = if mut_resources.is_empty() { + fn_call } else { - let mut wrapped = quote! { #(#fn_body_stmts)* }; + let mut wrapped = fn_call; for res in mut_resources.iter().rev() { let var_name = &res.var_name; let inner_type = &res.inner_type; @@ -88,10 +106,10 @@ pub(crate) fn help_attr(item: TokenStream) -> TokenStream { let help_render_body = if has_resources { quote! { #(#immut_resource_stmts)* - #wrapped_body + #inner_call } } else { - quote! { #(#fn_body_stmts)* } + quote! { #inner_call } }; // Register the help request mapping @@ -128,7 +146,7 @@ pub(crate) fn help_attr(item: TokenStream) -> TokenStream { impl ::mingling::HelpRequest for #struct_name { type Entry = #entry_type; - fn render_help(#prev_param: Self::Entry) -> ::mingling::RenderResult { + fn render_help(#fixed_prev: Self::Entry) -> ::mingling::RenderResult { let __help_result = { #help_render_body }; ::std::convert::Into::into(__help_result) } @@ -137,7 +155,6 @@ pub(crate) fn help_attr(item: TokenStream) -> TokenStream { ::mingling::macros::register_help!(#entry_type, #struct_name); // Keep the original function unchanged - #[allow(dead_code)] #(#fn_attrs)* #vis fn #fn_name(#original_inputs) -> #original_return_type { #(#fn_body_stmts)* diff --git a/mingling_macros/src/attr/renderer.rs b/mingling_macros/src/attr/renderer.rs index c7cbb0b..828dc00 100644 --- a/mingling_macros/src/attr/renderer.rs +++ b/mingling_macros/src/attr/renderer.rs @@ -1,7 +1,7 @@ use proc_macro::TokenStream; use quote::{ToTokens, quote}; use syn::spanned::Spanned; -use syn::{ItemFn, ReturnType, Signature, TypePath, parse_macro_input}; +use syn::{ItemFn, Pat, ReturnType, Signature, TypePath, parse_macro_input}; use crate::get_global_set; use crate::res_injection::{extract_args_info, generate_immut_resource_bindings}; @@ -31,8 +31,8 @@ pub(crate) fn renderer_attr(attr: TokenStream, item: TokenStream) -> TokenStream .into(); } - // Extract the previous type, parameter name, and resource injection params - let (prev_param, previous_type, resources) = match extract_args_info(&input_fn.sig) { + // Extract the previous type and resource injection params + let (_, previous_type, resources) = match extract_args_info(&input_fn.sig) { Ok(info) => info, Err(e) => return e.to_compile_error().into(), }; @@ -69,31 +69,53 @@ pub(crate) fn renderer_attr(attr: TokenStream, item: TokenStream) -> TokenStream let immut_resource_stmts = generate_immut_resource_bindings(resources.iter(), program_type); let mut_resources: Vec<_> = resources.iter().filter(|r| r.is_mut).collect(); - let inner_body_with_resources = if has_mut_resources { - let mut wrapped = quote! { #(#fn_body_stmts)* }; + // Build the call to the original function with resource arguments injected + let resource_args: Vec<_> = resources + .iter() + .map(|res| { + let var_name = &res.var_name; + quote! { #var_name } + }) + .collect(); + + // Use a fixed parameter name `prev` for the trait method, regardless of + // the user's original parameter name (which may be `_` and cannot be + // referenced in expression position). + let fixed_prev: Pat = syn::parse_quote!(prev); + + let fn_call = if has_resources { + quote! { #fn_name(#fixed_prev, #(#resource_args),*) } + } else { + quote! { #fn_name(#fixed_prev) } + }; + + // Wrap the function call with modify_res for mutable resources + let inner_call = if has_mut_resources { + let mut wrapped = fn_call; for res in mut_resources.iter().rev() { let var_name = &res.var_name; let inner_type = &res.inner_type; wrapped = quote! { - ::mingling::this::<#program_type>().modify_res(|#var_name: &mut #inner_type| { + ::mingling::this::<#program_type>() + .modify_res(|#var_name: &mut #inner_type| { #wrapped }) }; } wrapped } else { - quote! { #(#fn_body_stmts)* } + fn_call }; - // Build the Renderer::render body with resource injection - // The user's body now directly creates and returns a RenderResult. + // Build the Renderer::render body with resource injection. + // The trait method injects resources and calls the original function. let render_fn_body = if has_resources { quote! { #(#immut_resource_stmts)* - #inner_body_with_resources + #inner_call } } else { - quote! { #inner_body_with_resources } + quote! { #inner_call } }; // The original function preserves the user's exact signature and body. @@ -112,14 +134,13 @@ pub(crate) fn renderer_attr(attr: TokenStream, item: TokenStream) -> TokenStream impl ::mingling::Renderer for #struct_name { type Previous = #previous_type; - fn render(#prev_param: Self::Previous) -> ::mingling::RenderResult { + fn render(#fixed_prev: Self::Previous) -> ::mingling::RenderResult { let __renderer_result = { #render_fn_body }; ::std::convert::Into::into(__renderer_result) } } // Keep the original function unchanged - #[allow(dead_code)] #(#fn_attrs)* #vis fn #fn_name(#original_inputs) -> #original_return_type { #(#fn_body_stmts)* |
