From d94103fd7e76dd86cd64c23f00c818165fb7dc22 Mon Sep 17 00:00:00 2001 From: 魏曹先生 <1992414357@qq.com> Date: Tue, 23 Jun 2026 00:23:00 +0800 Subject: Replace macro_rules dispatch with proc-macro generation Generate `render()` and `do_chain()` match dispatch directly in `program_final_gen`, using a compile-time `ASYNC_ENABLED` constant to select the correct sync/async signature. Removes the `__dispatch_program_renderers!` and `__dispatch_program_chains!` macros from `mingling_core`. --- CHANGELOG.md | 2 + mingling_core/src/program.rs | 73 ---------------------------- mingling_macros/src/lib.rs | 113 ++++++++++++++++++++++++++++++++++++++++--- 3 files changed, 109 insertions(+), 79 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index b9b7bd7..637b074 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -72,6 +72,8 @@ 2. **\[core:comp\]** Changed the completion system's node filtering to exclude all hidden nodes (names starting with `_`) instead of only the specific `__comp` node. This makes the completion script generation more general — any node prefixed with an underscore is now treated as internal/hidden and excluded from suggestions. +3. **\[macros\]** Consolidated `__dispatch_program_renderers!` and `__dispatch_program_chains!` from `macro_rules!` into the `program_final_gen` proc-macro (`mingling_macros/src/lib.rs`), removing them from `mingling_core/src/program.rs`. The `render()` and `do_chain()` match dispatch is now generated directly by the proc-macro, using a compile-time `ASYNC_ENABLED` constant (via `#[cfg(feature = "async")]`) to select the correct sync/async signature at proc-macro compilation time, replacing the previous `#[cfg]`-gated `macro_rules!` dispatch that relied on per-crate feature resolution. + #### Features: 1. **\[core\]** Added the `unpack_chain_process!` macro for ergonomically extracting the inner value from a `ChainProcess` result. diff --git a/mingling_core/src/program.rs b/mingling_core/src/program.rs index 12dc9cc..a1b803e 100644 --- a/mingling_core/src/program.rs +++ b/mingling_core/src/program.rs @@ -169,79 +169,6 @@ where } } -#[macro_export] -#[doc(hidden)] -macro_rules! __dispatch_program_renderers { - ( - $( $render_ty:ty => $prev_ty:ident, )* - ) => { - fn render(any: mingling::AnyOutput, __renderer_inner_result: &mut mingling::RenderResult) { - match any.member_id { - $( - Self::$prev_ty => { - // SAFETY: The `type_id` check ensures that `any` contains a value of type `$prev_ty`, - // so downcasting to `$prev_ty` is safe. - let value = unsafe { any.downcast::<$prev_ty>().unwrap_unchecked() }; - <$render_ty as mingling::Renderer>::render(value, __renderer_inner_result); - } - )* - _ => (), - } - } - }; -} - -#[macro_export] -#[doc(hidden)] -#[cfg(feature = "async")] -macro_rules! __dispatch_program_chains { - ( - $( $chain_ty:ty => $chain_prev:ident, )* - ) => { - fn do_chain( - any: mingling::AnyOutput, - ) -> std::pin::Pin> + Send>> { - match any.member_id { - $( - Self::$chain_prev => { - // SAFETY: The `type_id` check ensures that `any` contains a value of type `$chain_prev`, - // so downcasting to `$chain_prev` is safe. - let value = unsafe { any.downcast::<$chain_prev>().unwrap_unchecked() }; - let fut = async { <$chain_ty as mingling::Chain>::proc(value).await }; - Box::pin(fut) - } - )* - _ => panic!("No chain found for type id: {:?}", any.type_id), - } - } - }; -} - -#[macro_export] -#[doc(hidden)] -#[cfg(not(feature = "async"))] -macro_rules! __dispatch_program_chains { - ( - $( $chain_ty:ty => $chain_prev:ident, )* - ) => { - fn do_chain( - any: mingling::AnyOutput, - ) -> mingling::ChainProcess { - match any.member_id { - $( - Self::$chain_prev => { - // SAFETY: The `type_id` check ensures that `any` contains a value of type `$chain_prev`, - // so downcasting to `$chain_prev` is safe. - let value = unsafe { any.downcast::<$chain_prev>().unwrap_unchecked() }; - <$chain_ty as mingling::Chain>::proc(value) - } - )* - _ => panic!("No chain found for type id: {:?}", any.type_id), - } - } - }; -} - /// Get all registered dispatcher names from the program #[allow(unused_variables)] #[must_use] diff --git a/mingling_macros/src/lib.rs b/mingling_macros/src/lib.rs index 3b33f09..c6b94c3 100644 --- a/mingling_macros/src/lib.rs +++ b/mingling_macros/src/lib.rs @@ -1624,6 +1624,25 @@ pub fn program_fallback_gen(_input: TokenStream) -> TokenStream { /// /// # Panics /// +// Feature detection: baked into the proc-macro binary at compile time +#[cfg(feature = "async")] +const ASYNC_ENABLED: bool = true; +#[cfg(not(feature = "async"))] +const ASYNC_ENABLED: bool = false; + +/// Parses an entry of the format `StructName => EnumVariant,` into a pair of idents. +fn parse_entry_pair(entry: &proc_macro2::TokenStream) -> (proc_macro2::Ident, proc_macro2::Ident) { + let s = entry.to_string(); + let arrow_idx = s + .find("=>") + .unwrap_or_else(|| panic!("Entry missing '=>': {s}")); + let struct_str = s[..arrow_idx].trim(); + let variant_str = s[arrow_idx + 2..].trim().trim_end_matches(','); + let struct_ident = proc_macro2::Ident::new(struct_str, proc_macro2::Span::call_site()); + let variant_ident = proc_macro2::Ident::new(variant_str, proc_macro2::Span::call_site()); + (struct_ident, variant_ident) +} + /// Panics if any of the global registries (`PACKED_TYPES`, `RENDERERS`, `CHAINS`, etc.) /// are poisoned. #[proc_macro] @@ -1749,6 +1768,92 @@ pub fn program_final_gen(_input: TokenStream) -> TokenStream { #[cfg(not(feature = "comp"))] let comp = quote! {}; + // Build render function arms from stored entries + let render_fn = if renderer_tokens.is_empty() { + quote! { + fn render(_any: ::mingling::AnyOutput, _renderer_inner_result: &mut ::mingling::RenderResult) {} + } + } else { + let render_arms: Vec<_> = renderer_tokens.iter().map(|entry| { + let (struct_ident, variant_ident) = parse_entry_pair(entry); + quote! { + Self::#variant_ident => { + // SAFETY: The `type_id` check ensures that `any` contains a value of type `#variant_ident`, + // so downcasting to `#variant_ident` is safe. + let value = unsafe { any.downcast::<#variant_ident>().unwrap_unchecked() }; + <#struct_ident as ::mingling::Renderer>::render(value, __renderer_inner_result); + } + } + }).collect(); + quote! { + fn render(any: ::mingling::AnyOutput, __renderer_inner_result: &mut ::mingling::RenderResult) { + match any.member_id { + #(#render_arms)* + _ => (), + } + } + } + }; + + // Build do_chain function (async and sync versions) + let chain_arms_async: Vec<_> = chain_tokens.iter().map(|entry| { + let (struct_ident, variant_ident) = parse_entry_pair(entry); + quote! { + Self::#variant_ident => { + // SAFETY: The `type_id` check ensures that `any` contains a value of type `#variant_ident`, + // so downcasting to `#variant_ident` is safe. + let value = unsafe { any.downcast::<#variant_ident>().unwrap_unchecked() }; + let fut = async { <#struct_ident as ::mingling::Chain>::proc(value).await }; + ::std::boxed::Box::pin(fut) + } + } + }).collect(); + + let chain_arms_sync: Vec<_> = chain_tokens + .iter() + .map(|entry| { + let (struct_ident, variant_ident) = parse_entry_pair(entry); + quote! { + Self::#variant_ident => { + // SAFETY: The `type_id` check ensures that `any` contains a value of type `#variant_ident`, + // so downcasting to `#variant_ident` is safe. + let value = unsafe { any.downcast::<#variant_ident>().unwrap_unchecked() }; + <#struct_ident as ::mingling::Chain>::proc(value) + } + } + }) + .collect(); + + let do_chain_fn = if chain_tokens.is_empty() { + quote! { + fn do_chain(_any: ::mingling::AnyOutput) -> ::mingling::ChainProcess { + ::core::panic!("No chain found for type id") + } + } + } else if ASYNC_ENABLED { + quote! { + fn do_chain( + any: ::mingling::AnyOutput, + ) -> ::std::pin::Pin<::std::boxed::Box> + ::std::marker::Send>> { + match any.member_id { + #(#chain_arms_async)* + _ => ::core::panic!("No chain found for type id: {:?}", any.type_id), + } + } + } + } else { + quote! { + fn do_chain( + any: ::mingling::AnyOutput, + ) -> ::mingling::ChainProcess { + match any.member_id { + #(#chain_arms_sync)* + _ => ::core::panic!("No chain found for type id: {:?}", any.type_id), + } + } + } + }; + let help_tokens: Vec = get_global_set(&HELP_REQUESTS) .lock() .unwrap() @@ -1798,12 +1903,8 @@ pub fn program_final_gen(_input: TokenStream) -> TokenStream { fn build_empty_result() -> ::mingling::AnyOutput { ::mingling::AnyOutput::new(ResultEmpty::new(())) } - ::mingling::__dispatch_program_renderers!( - #(#renderer_tokens)* - ); - ::mingling::__dispatch_program_chains!( - #(#chain_tokens)* - ); + #render_fn + #do_chain_fn fn render_help(any: ::mingling::AnyOutput, __renderer_inner_result: &mut ::mingling::RenderResult) { match any.member_id { #(#help_tokens)* -- cgit