diff options
| author | 魏曹先生 <1992414357@qq.com> | 2026-07-20 08:13:49 +0800 |
|---|---|---|
| committer | 魏曹先生 <1992414357@qq.com> | 2026-07-20 08:13:49 +0800 |
| commit | 9dc737acec1eab40ef65bef8e6c729a4e5e06401 (patch) | |
| tree | 05484fd697e7d2a047d8f525d6ede55c88010934 /mingling_macros | |
| parent | a6697111573952903b3d9d0659ffe1af23432c3c (diff) | |
feat(macros): preserve user function as standalone item in codegen
Refactor #[chain], #[renderer], #[help], and #[completion] macros to
keep
the original user function as a named item instead of inlining its body.
Trait method implementations now call the original function by name,
injecting resources from the application context.
This improves debugging (stack traces reference user function names),
error messages, and macro expansion clarity.
Diffstat (limited to 'mingling_macros')
| -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)* |
