diff options
Diffstat (limited to 'mingling_macros')
| -rw-r--r-- | mingling_macros/src/attr.rs | 9 | ||||
| -rw-r--r-- | mingling_macros/src/attr/chain.rs (renamed from mingling_macros/src/chain.rs) | 132 | ||||
| -rw-r--r-- | mingling_macros/src/attr/completion.rs (renamed from mingling_macros/src/completion.rs) | 41 | ||||
| -rw-r--r-- | mingling_macros/src/attr/dispatcher_clap.rs (renamed from mingling_macros/src/dispatcher_clap.rs) | 16 | ||||
| -rw-r--r-- | mingling_macros/src/attr/help.rs (renamed from mingling_macros/src/help.rs) | 45 | ||||
| -rw-r--r-- | mingling_macros/src/attr/program_setup.rs (renamed from mingling_macros/src/program_setup.rs) | 2 | ||||
| -rw-r--r-- | mingling_macros/src/attr/renderer.rs (renamed from mingling_macros/src/renderer.rs) | 59 | ||||
| -rw-r--r-- | mingling_macros/src/derive.rs | 2 | ||||
| -rw-r--r-- | mingling_macros/src/derive/enum_tag.rs (renamed from mingling_macros/src/enum_tag.rs) | 2 | ||||
| -rw-r--r-- | mingling_macros/src/derive/grouped.rs (renamed from mingling_macros/src/groupped.rs) | 12 | ||||
| -rw-r--r-- | mingling_macros/src/extensions.rs | 119 | ||||
| -rw-r--r-- | mingling_macros/src/extensions/buffer.rs | 95 | ||||
| -rw-r--r-- | mingling_macros/src/extensions/routeify.rs | 46 | ||||
| -rw-r--r-- | mingling_macros/src/func.rs | 13 | ||||
| -rw-r--r-- | mingling_macros/src/func/dispatcher.rs (renamed from mingling_macros/src/dispatcher.rs) | 10 | ||||
| -rw-r--r-- | mingling_macros/src/func/entry.rs (renamed from mingling_macros/src/entry.rs) | 2 | ||||
| -rw-r--r-- | mingling_macros/src/func/gen_program.rs | 628 | ||||
| -rw-r--r-- | mingling_macros/src/func/group.rs (renamed from mingling_macros/src/group_impl.rs) | 6 | ||||
| -rw-r--r-- | mingling_macros/src/func/node.rs (renamed from mingling_macros/src/node.rs) | 2 | ||||
| -rw-r--r-- | mingling_macros/src/func/pack.rs (renamed from mingling_macros/src/pack.rs) | 4 | ||||
| -rw-r--r-- | mingling_macros/src/func/pack_err.rs (renamed from mingling_macros/src/pack_err.rs) | 12 | ||||
| -rw-r--r-- | mingling_macros/src/func/r_print.rs | 62 | ||||
| -rw-r--r-- | mingling_macros/src/func/suggest.rs (renamed from mingling_macros/src/suggest.rs) | 4 | ||||
| -rw-r--r-- | mingling_macros/src/lib.rs | 985 | ||||
| -rw-r--r-- | mingling_macros/src/systems.rs | 5 | ||||
| -rw-r--r-- | mingling_macros/src/systems/dispatch_tree_gen.rs (renamed from mingling_macros/src/dispatch_tree_gen.rs) | 6 | ||||
| -rw-r--r-- | mingling_macros/src/systems/res_injection.rs (renamed from mingling_macros/src/res_injection.rs) | 0 | ||||
| -rw-r--r-- | mingling_macros/src/systems/structural_data.rs (renamed from mingling_macros/src/structural_data.rs) | 4 | ||||
| -rw-r--r-- | mingling_macros/src/utils.rs | 1 |
29 files changed, 1491 insertions, 833 deletions
diff --git a/mingling_macros/src/attr.rs b/mingling_macros/src/attr.rs new file mode 100644 index 0000000..e4cd826 --- /dev/null +++ b/mingling_macros/src/attr.rs @@ -0,0 +1,9 @@ +pub(crate) mod chain; +#[cfg(feature = "comp")] +pub(crate) mod completion; +#[cfg(feature = "clap")] +pub(crate) mod dispatcher_clap; +pub(crate) mod help; +#[cfg(feature = "extra_macros")] +pub(crate) mod program_setup; +pub(crate) mod renderer; diff --git a/mingling_macros/src/chain.rs b/mingling_macros/src/attr/chain.rs index 5044227..dbd87dd 100644 --- a/mingling_macros/src/chain.rs +++ b/mingling_macros/src/attr/chain.rs @@ -20,65 +20,81 @@ fn is_unit_return_type(sig: &Signature) -> bool { } } -/// Validates that the return type is `Next`, `ChainProcess<ThisProgram>`, `()`, or omitted. +/// Validates that the return type is acceptable. +/// Accepts `()`, `Next`, `ChainProcess<...>`, or any type that can +/// be converted to `ChainProcess` via `.into()` (i.e. any pack type). fn validate_return_type(sig: &Signature) -> Result<(), proc_macro2::TokenStream> { // `()` or omitted is always valid if is_unit_return_type(sig) { return Ok(()); } - match &sig.output { - ReturnType::Type(_, ty) => match &**ty { - Type::Path(type_path) => { - let last_segment = type_path.path.segments.last().unwrap(); - let ident_str = last_segment.ident.to_string(); - if ident_str == "Next" || ident_str == "ChainProcess" { - return Ok(()); - } - Err(syn::Error::new( - ty.span(), - "Chain function must return `Next`, `ChainProcess<ThisProgram>`, `()`, or omit the return type", - ) - .to_compile_error()) - } - _ => Err(syn::Error::new( - ty.span(), - "Chain function must return `Next`, `ChainProcess<ThisProgram>`, `()`, or omit the return type", - ) - .to_compile_error()), - }, - ReturnType::Default => { - Err(syn::Error::new( - sig.span(), - "Chain function must specify a return type (must be `Next`, `ChainProcess<ThisProgram>`, or `()`)", - ) - .to_compile_error()) - } - } + Ok(()) } /// 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, ) -> proc_macro2::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(); + // 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 { @@ -86,13 +102,13 @@ fn generate_proc_fn( quote! { #(#immut_resource_stmts)* #wrapped_body; - <crate::ResultEmpty as ::mingling::Groupped::<crate::ThisProgram>> + <crate::ResultEmpty as ::mingling::Routable::<crate::ThisProgram>> ::to_chain(crate::ResultEmpty) } } else { quote! { #wrapped_body; - <crate::ResultEmpty as ::mingling::Groupped::<crate::ThisProgram>> + <crate::ResultEmpty as ::mingling::Routable::<crate::ThisProgram>> ::to_chain(crate::ResultEmpty) } }; @@ -106,18 +122,18 @@ fn generate_proc_fn( } else { quote! { #wrapped_body } }; - // Use a let-binding with explicit type annotation so that the inner - // body's `.into()` calls resolve correctly (same as the original function). quote! { - let __chain_result: ::mingling::ChainProcess<#program_type> = { #body }; - __chain_result + let __chain_result = { #body }; + <#origin_return_type as ::std::convert::Into< + ::mingling::ChainProcess<#program_type> + >>::into(__chain_result) } }; #[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 } } @@ -126,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 } } @@ -177,7 +193,7 @@ fn reject_async(sig: &Signature) -> Result<(), proc_macro2::TokenStream> { Ok(()) } -pub fn chain_attr(attr: TokenStream, item: TokenStream) -> TokenStream { +pub(crate) fn chain_attr(attr: TokenStream, item: TokenStream) -> TokenStream { // Reject non-empty attribute arguments; #[chain] must be bare if !attr.is_empty() { return syn::Error::new( @@ -211,13 +227,12 @@ pub 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; @@ -234,26 +249,30 @@ pub fn chain_attr(attr: TokenStream, item: TokenStream) -> TokenStream { // Always use the default crate-defined program path let program_type = crate::default_program_path(); + // Extract the user's return type for the explicit Into turbofish + let origin_return_type = match &input_fn.sig.output { + ReturnType::Type(_, ty) => quote! { #ty }, + ReturnType::Default => quote! { () }, + }; + // 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"))] false, is_unit_return, + &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 }; @@ -275,7 +294,10 @@ pub fn chain_attr(attr: TokenStream, item: TokenStream) -> TokenStream { } /// Builds a match arm for chain mapping -pub fn build_chain_arm(struct_name: &Ident, previous_type: &TypePath) -> proc_macro2::TokenStream { +pub(crate) fn build_chain_arm( + struct_name: &Ident, + previous_type: &TypePath, +) -> proc_macro2::TokenStream { let enum_variant = &previous_type.path.segments.last().unwrap().ident; quote! { #struct_name => #enum_variant, @@ -283,14 +305,14 @@ pub fn build_chain_arm(struct_name: &Ident, previous_type: &TypePath) -> proc_ma } /// Builds a match arm for chain existence check -pub fn build_chain_exist_arm(previous_type: &TypePath) -> proc_macro2::TokenStream { +pub(crate) fn build_chain_exist_arm(previous_type: &TypePath) -> proc_macro2::TokenStream { let enum_variant = &previous_type.path.segments.last().unwrap().ident; quote! { Self::#enum_variant => true, } } -pub fn register_chain(input: TokenStream) -> TokenStream { +pub(crate) fn register_chain(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); diff --git a/mingling_macros/src/completion.rs b/mingling_macros/src/attr/completion.rs index ae01462..3ced091 100644 --- a/mingling_macros/src/completion.rs +++ b/mingling_macros/src/attr/completion.rs @@ -5,7 +5,7 @@ use syn::spanned::Spanned; use syn::{FnArg, Ident, ItemFn, Pat, PatType, Type, TypePath, parse_macro_input}; #[cfg(feature = "comp")] -pub fn completion_attr(attr: TokenStream, item: TokenStream) -> TokenStream { +pub(crate) fn completion_attr(attr: TokenStream, item: TokenStream) -> TokenStream { // Parse the attribute arguments such as HelloEntry or crate::EntryFine from #[completion(crate::EntryFine)] use crate::get_global_set; let previous_type_path: TypePath = if attr.is_empty() { @@ -47,11 +47,8 @@ pub fn completion_attr(attr: TokenStream, item: TokenStream) -> TokenStream { // 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 fn completion_attr(attr: TokenStream, item: TokenStream) -> TokenStream { .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 fn completion_attr(attr: TokenStream, item: TokenStream) -> TokenStream { // 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 fn completion_attr(attr: TokenStream, item: TokenStream) -> TokenStream { // 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 fn completion_attr(attr: TokenStream, item: TokenStream) -> TokenStream { 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 fn completion_attr(attr: TokenStream, item: TokenStream) -> TokenStream { 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/dispatcher_clap.rs b/mingling_macros/src/attr/dispatcher_clap.rs index 0945e31..40f7d47 100644 --- a/mingling_macros/src/dispatcher_clap.rs +++ b/mingling_macros/src/attr/dispatcher_clap.rs @@ -93,7 +93,7 @@ impl Parse for DispatcherClapInput { } #[cfg(feature = "clap")] -pub fn dispatcher_clap_attr(attr: TokenStream, item: TokenStream) -> TokenStream { +pub(crate) fn dispatcher_clap_attr(attr: TokenStream, item: TokenStream) -> TokenStream { let attr_input = parse_macro_input!(attr as DispatcherClapInput); let input_struct = parse_macro_input!(item as ItemStruct); let struct_name = &input_struct.ident; @@ -108,23 +108,23 @@ pub fn dispatcher_clap_attr(attr: TokenStream, item: TokenStream) -> TokenStream let begin_body = if let Some(ref error_struct) = options.error_struct { quote! { if ::mingling::this::<#program_path>().user_context.help { - return #struct_name::default().to_chain(); + return ::mingling::Routable::<#program_path>::to_chain(#struct_name::default()); } match <#struct_name as ::clap::Parser>::try_parse_from(clap_args) { - Ok(parsed) => parsed.to_chain(), + Ok(parsed) => ::mingling::Routable::<#program_path>::to_chain(parsed), Err(e) => { - return #error_struct::new(format!("{}", e.render().ansi())).to_render() + return ::mingling::Routable::<#program_path>::to_render(#error_struct::new(format!("{}", e.render().ansi()))) }, } } } else { quote! { if ::mingling::this::<#program_path>().user_context.help { - return #struct_name::default().to_chain(); + return ::mingling::Routable::<#program_path>::to_chain(#struct_name::default()); } let parsed = <#struct_name as ::clap::Parser>::try_parse_from(clap_args) .unwrap_or_else(|e| e.exit()); - parsed.to_chain() + ::mingling::Routable::<#program_path>::to_chain(parsed) } }; @@ -144,7 +144,7 @@ pub fn dispatcher_clap_attr(attr: TokenStream, item: TokenStream) -> TokenStream Some(quote! { #[allow(non_snake_case)] #[::mingling::macros::help] - pub fn #help_fn_name(_prev: #struct_name) -> ::mingling::RenderResult { + pub(crate) fn #help_fn_name(_prev: #struct_name) -> ::mingling::RenderResult { use std::io::Write; use clap::ColorChoice; @@ -189,7 +189,7 @@ pub fn dispatcher_clap_attr(attr: TokenStream, item: TokenStream) -> TokenStream // Generate the dispatcher struct #[doc(hidden)] #[derive(Default)] - pub struct #dispatcher_struct; + pub(crate) struct #dispatcher_struct; impl ::mingling::Dispatcher<#program_path> for #dispatcher_struct { fn node(&self) -> ::mingling::Node { diff --git a/mingling_macros/src/help.rs b/mingling_macros/src/attr/help.rs index 6e0d12e..aa7bc88 100644 --- a/mingling_macros/src/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}; @@ -14,7 +14,7 @@ fn extract_user_return_type(sig: &Signature) -> Option<proc_macro2::TokenStream> } } -pub fn help_attr(item: TokenStream) -> TokenStream { +pub(crate) fn help_attr(item: TokenStream) -> TokenStream { // Parse the function item let input_fn = parse_macro_input!(item as ItemFn); @@ -25,8 +25,8 @@ pub 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 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 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 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 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)* @@ -160,7 +177,7 @@ fn build_help_entry(struct_name: &Ident, entry_type: &TypePath) -> proc_macro2:: } } -pub fn register_help(input: TokenStream) -> TokenStream { +pub(crate) fn register_help(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); diff --git a/mingling_macros/src/program_setup.rs b/mingling_macros/src/attr/program_setup.rs index 7fd9d16..dee5a1c 100644 --- a/mingling_macros/src/program_setup.rs +++ b/mingling_macros/src/attr/program_setup.rs @@ -47,7 +47,7 @@ fn extract_return_type(sig: &Signature) -> syn::Result<()> { } } -pub fn setup_attr(attr: TokenStream, item: TokenStream) -> TokenStream { +pub(crate) fn setup_attr(attr: TokenStream, item: TokenStream) -> TokenStream { // #[program_setup] takes no arguments; always use the default program path let _ = attr; let program_path = crate::default_program_path(); diff --git a/mingling_macros/src/renderer.rs b/mingling_macros/src/attr/renderer.rs index d124ec9..828dc00 100644 --- a/mingling_macros/src/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}; @@ -15,7 +15,7 @@ fn extract_user_return_type(sig: &Signature) -> Option<proc_macro2::TokenStream> } #[allow(clippy::too_many_lines)] -pub fn renderer_attr(attr: TokenStream, item: TokenStream) -> TokenStream { +pub(crate) fn renderer_attr(attr: TokenStream, item: TokenStream) -> TokenStream { // #[renderer] takes no arguments; always use the default program path let _ = attr; let program_path = crate::default_program_path(); @@ -31,8 +31,8 @@ pub 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 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 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)* @@ -130,7 +151,7 @@ pub fn renderer_attr(attr: TokenStream, item: TokenStream) -> TokenStream { } /// Builds the renderer entry for the global renderers list -pub fn build_renderer_entry( +pub(crate) fn build_renderer_entry( struct_name: &syn::Ident, previous_type: &TypePath, ) -> proc_macro2::TokenStream { @@ -141,7 +162,7 @@ pub fn build_renderer_entry( } /// Builds the renderer existence check entry -pub fn build_renderer_exist_entry(previous_type: &TypePath) -> proc_macro2::TokenStream { +pub(crate) fn build_renderer_exist_entry(previous_type: &TypePath) -> proc_macro2::TokenStream { let enum_variant = &previous_type.path.segments.last().unwrap().ident; quote! { Self::#enum_variant => true, @@ -150,7 +171,9 @@ pub fn build_renderer_exist_entry(previous_type: &TypePath) -> proc_macro2::Toke /// Builds the structural renderer entry #[cfg(feature = "structural_renderer")] -pub fn build_structural_renderer_entry(previous_type: &TypePath) -> proc_macro2::TokenStream { +pub(crate) fn build_structural_renderer_entry( + previous_type: &TypePath, +) -> proc_macro2::TokenStream { let enum_variant = &previous_type.path.segments.last().unwrap().ident; quote! { Self::#enum_variant => { @@ -164,7 +187,7 @@ pub fn build_structural_renderer_entry(previous_type: &TypePath) -> proc_macro2: } } -pub fn register_renderer(input: TokenStream) -> TokenStream { +pub(crate) 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); diff --git a/mingling_macros/src/derive.rs b/mingling_macros/src/derive.rs new file mode 100644 index 0000000..ffa405b --- /dev/null +++ b/mingling_macros/src/derive.rs @@ -0,0 +1,2 @@ +pub(crate) mod enum_tag; +pub(crate) mod grouped; diff --git a/mingling_macros/src/enum_tag.rs b/mingling_macros/src/derive/enum_tag.rs index 6277b69..a7f71f0 100644 --- a/mingling_macros/src/enum_tag.rs +++ b/mingling_macros/src/derive/enum_tag.rs @@ -4,7 +4,7 @@ use syn::{ Attribute, Data, DeriveInput, Error, Fields, Ident, LitStr, Result, Variant, parse_macro_input, }; -pub fn derive_enum_tag(input: TokenStream) -> TokenStream { +pub(crate) fn derive_enum_tag(input: TokenStream) -> TokenStream { let input = parse_macro_input!(input as DeriveInput); match derive_enum_tag_impl(input) { diff --git a/mingling_macros/src/groupped.rs b/mingling_macros/src/derive/grouped.rs index 8aee003..307aab6 100644 --- a/mingling_macros/src/groupped.rs +++ b/mingling_macros/src/derive/grouped.rs @@ -2,7 +2,7 @@ use proc_macro::TokenStream; use quote::quote; use syn::{DeriveInput, Ident, parse_macro_input}; -pub fn derive_groupped(input: TokenStream) -> TokenStream { +pub(crate) fn derive_grouped(input: TokenStream) -> TokenStream { // Parse the input struct/enum let input = parse_macro_input!(input as DeriveInput); let struct_name = input.ident; @@ -12,11 +12,11 @@ pub fn derive_groupped(input: TokenStream) -> TokenStream { let any_output_convert_impls = proc_macro2::TokenStream::from(build_any_output_convert_impls(&struct_name, &group_ident)); - // Generate the Groupped trait implementation + // Generate the Grouped trait implementation let expanded = quote! { ::mingling::macros::register_type!(#struct_name); - impl ::mingling::Groupped<#group_ident> for #struct_name { + impl ::mingling::Grouped<#group_ident> for #struct_name { fn member_id() -> #group_ident { #group_ident::#struct_name } @@ -29,7 +29,7 @@ pub fn derive_groupped(input: TokenStream) -> TokenStream { } #[cfg(feature = "structural_renderer")] -pub fn derive_groupped_serialize(input: TokenStream) -> TokenStream { +pub fn derive_grouped_serialize(input: TokenStream) -> TokenStream { // Parse the input struct/enum let input_parsed = parse_macro_input!(input as DeriveInput); let struct_name = input_parsed.ident.clone(); @@ -39,14 +39,14 @@ pub fn derive_groupped_serialize(input: TokenStream) -> TokenStream { let any_output_convert_impls = proc_macro2::TokenStream::from(build_any_output_convert_impls(&struct_name, &group_ident)); - // Generate both Serialize and Groupped implementations + // Generate both Serialize and Grouped implementations let expanded = quote! { #[derive(serde::Serialize)] #input_parsed ::mingling::macros::register_type!(#struct_name); - impl ::mingling::Groupped<#group_ident> for #struct_name { + impl ::mingling::Grouped<#group_ident> for #struct_name { fn member_id() -> #group_ident { #group_ident::#struct_name } diff --git a/mingling_macros/src/extensions.rs b/mingling_macros/src/extensions.rs new file mode 100644 index 0000000..022761b --- /dev/null +++ b/mingling_macros/src/extensions.rs @@ -0,0 +1,119 @@ +//! Extension point mechanism for Mingling attribute macros. +//! +//! This module provides a way for attribute macros like `#[chain]`, `#[renderer]`, +//! `#[help]`, and `#[completion]` to accept extension identifiers that are +//! applied as outer attributes before the bare macro. + +use proc_macro::TokenStream; +use quote::quote; +use syn::parse::{Parse, ParseStream}; +use syn::{Ident, Token}; + +/// Extension: `#[routeify]` — transforms `expr?` into `route!(expr)`. +#[cfg(feature = "extra_macros")] +pub(crate) mod routeify; + +/// Extension: `#[buffer]` — wraps a unit-returning function to return `RenderResult`. +pub(crate) mod buffer; + +/// Parsed extensions from an attribute macro like `#[chain(routeify, other_ext)]`. +pub(crate) struct Extensions { + pub(crate) exts: Vec<Ident>, +} + +impl Parse for Extensions { + fn parse(input: ParseStream) -> syn::Result<Self> { + let mut exts = Vec::new(); + while !input.is_empty() { + let ident: Ident = input.parse()?; + exts.push(ident); + if input.peek(Token![,]) { + let _ = input.parse::<Token![,]>(); + } + } + Ok(Extensions { exts }) + } +} + +/// Parsed extensions for `#[completion(EntryType, routeify, ...)]`. +#[cfg(feature = "comp")] +pub(crate) struct CompletionExt { + pub(crate) entry_type: proc_macro2::TokenStream, + pub(crate) exts: Vec<Ident>, +} + +#[cfg(feature = "comp")] +impl Parse for CompletionExt { + fn parse(input: ParseStream) -> syn::Result<Self> { + let entry_type: proc_macro2::TokenStream = input.parse()?; + let mut exts = Vec::new(); + while !input.is_empty() { + let _ = input.parse::<Token![,]>(); + if input.is_empty() { + break; + } + let ident: Ident = input.parse()?; + exts.push(ident); + } + Ok(CompletionExt { entry_type, exts }) + } +} + +/// Generates a re-dispatch token stream for attribute macros that take **no fixed arguments** +/// (chain, renderer, help). +pub(crate) fn try_redispatch_simple( + attr: TokenStream, + item: &TokenStream, + bare_attr_name: &str, +) -> Option<TokenStream> { + if attr.is_empty() { + return None; + } + + let exts: Extensions = syn::parse(attr).ok()?; + if exts.exts.is_empty() { + return None; + } + + let bare = Ident::new(bare_attr_name, proc_macro2::Span::call_site()); + let exts = &exts.exts; + let item = proc_macro2::TokenStream::from(item.clone()); + + Some( + quote! { + #(#[#exts])* + #[#bare] + #item + } + .into(), + ) +} + +/// Generates a re-dispatch token stream for `#[completion(EntryType, ...)]`. +#[cfg(feature = "comp")] +pub(crate) fn try_redispatch_completion( + attr: TokenStream, + item: &TokenStream, +) -> Option<TokenStream> { + if attr.is_empty() { + return None; + } + + let parsed: CompletionExt = syn::parse(attr).ok()?; + if parsed.exts.is_empty() { + return None; + } + + let entry_type = &parsed.entry_type; + let exts = &parsed.exts; + let item = proc_macro2::TokenStream::from(item.clone()); + + Some( + quote! { + #(#[#exts])* + #[::mingling::macros::completion(#entry_type)] + #item + } + .into(), + ) +} diff --git a/mingling_macros/src/extensions/buffer.rs b/mingling_macros/src/extensions/buffer.rs new file mode 100644 index 0000000..27eb612 --- /dev/null +++ b/mingling_macros/src/extensions/buffer.rs @@ -0,0 +1,95 @@ +use proc_macro::TokenStream; +use quote::quote; +use syn::spanned::Spanned; +use syn::{ItemFn, ReturnType, Type, parse_macro_input}; + +/// Checks whether the return type is unit `()`. +fn is_unit_return_type(sig: &syn::Signature) -> bool { + match &sig.output { + ReturnType::Type(_, ty) => match &**ty { + Type::Tuple(tuple) => tuple.elems.is_empty(), + _ => false, + }, + ReturnType::Default => true, + } +} + +/// The `#[buffer]` attribute macro. +/// +/// Wraps a unit-returning function to produce a `::mingling::RenderResult` by +/// injecting a local `__render_result_buffer` variable. Inside the function +/// body, the `r_print!` / `r_println!` macros can write into the buffer. +/// +/// # Example +/// +/// ```rust,ignore +/// use mingling::macros::{buffer, r_println}; +/// +/// #[buffer] +/// fn render_greeting(prev: Greeting) { +/// r_println!("Hello, {}!", *prev); +/// } +/// ``` +/// +/// Expands to: +/// +/// ```rust,ignore +/// fn render_greeting(prev: Greeting) -> mingling::RenderResult { +/// let mut __render_result_buffer = mingling::RenderResult::new(); +/// { +/// r_println!("Hello, {}!", *prev); +/// } +/// __render_result_buffer +/// } +/// ``` +pub(crate) fn buffer_impl(attr: TokenStream, item: TokenStream) -> TokenStream { + // Reject non-empty attribute arguments; #[buffer] must be bare + if !attr.is_empty() { + return syn::Error::new( + attr.into_iter().next().unwrap().span().into(), + "#[buffer] does not accept arguments", + ) + .to_compile_error() + .into(); + } + + // Parse the function item + let input_fn = parse_macro_input!(item as ItemFn); + + // Validate the function is not async + if input_fn.sig.asyncness.is_some() { + return syn::Error::new(input_fn.sig.span(), "Buffer function cannot be async") + .to_compile_error() + .into(); + } + + // Validate return type is unit + if !is_unit_return_type(&input_fn.sig) { + return syn::Error::new( + input_fn.sig.span(), + "#[buffer] function must not have a return value (must return `()`)", + ) + .to_compile_error() + .into(); + } + + // Get function attributes (excluding the buffer attribute) + let mut fn_attrs = input_fn.attrs.clone(); + fn_attrs.retain(|attr| !attr.path().is_ident("buffer")); + + let vis = &input_fn.vis; + let fn_name = &input_fn.sig.ident; + let inputs = &input_fn.sig.inputs; + let fn_body = &input_fn.block; + + let expanded = quote! { + #(#fn_attrs)* + #vis fn #fn_name(#inputs) -> ::mingling::RenderResult { + let mut __render_result_buffer = ::mingling::RenderResult::new(); + #fn_body + __render_result_buffer + } + }; + + expanded.into() +} diff --git a/mingling_macros/src/extensions/routeify.rs b/mingling_macros/src/extensions/routeify.rs new file mode 100644 index 0000000..7cded54 --- /dev/null +++ b/mingling_macros/src/extensions/routeify.rs @@ -0,0 +1,46 @@ +//! The `#[routeify]` extension — transforms `expr?` into `route!(expr)`. +//! +//! Designed as an extension for the Mingling attribute macro system, intended +//! to be used with `#[chain(routeify)]` or standalone as `#[routeify]`. +//! +//! # How it works +//! +//! The macro parses the function AST and replaces every `Expr::Try` node with an +//! equivalent `route!(expr)` invocation. + +use proc_macro::TokenStream; +use quote::ToTokens; +use syn::spanned::Spanned; +use syn::visit_mut::VisitMut; +use syn::{Expr, ItemFn, parse_macro_input}; + +struct RouteifyTransform; + +impl VisitMut for RouteifyTransform { + fn visit_expr_mut(&mut self, expr: &mut Expr) { + syn::visit_mut::visit_expr_mut(self, expr); + + if let Expr::Try(try_expr) = expr { + let inner = &*try_expr.expr; + let inner_tokens = inner.to_token_stream(); + + // Set the span of the generated `route` ident to the `?` token's span, + // so that rust-analyzer resolves the `?` position to the `route!` macro + // instead of the standard Try trait, showing the route macro's docs on hover. + let q_span = try_expr.question_token.span(); + let route_ident = proc_macro2::Ident::new("route", q_span); + + if let Ok(macro_expr) = syn::parse2::<Expr>(quote::quote! { + ::mingling::macros::#route_ident!(#inner_tokens) + }) { + *expr = macro_expr; + } + } + } +} + +pub(crate) fn routeify_impl(_attr: TokenStream, item: TokenStream) -> TokenStream { + let mut input_fn = parse_macro_input!(item as ItemFn); + RouteifyTransform.visit_item_fn_mut(&mut input_fn); + input_fn.to_token_stream().into() +} diff --git a/mingling_macros/src/func.rs b/mingling_macros/src/func.rs new file mode 100644 index 0000000..33bb094 --- /dev/null +++ b/mingling_macros/src/func.rs @@ -0,0 +1,13 @@ +pub(crate) mod dispatcher; +#[cfg(feature = "extra_macros")] +pub(crate) mod entry; +pub(crate) mod gen_program; +#[cfg(feature = "extra_macros")] +pub(crate) mod group; +pub(crate) mod node; +pub(crate) mod pack; +#[cfg(feature = "extra_macros")] +pub(crate) mod pack_err; +pub(crate) mod r_print; +#[cfg(feature = "comp")] +pub(crate) mod suggest; diff --git a/mingling_macros/src/dispatcher.rs b/mingling_macros/src/func/dispatcher.rs index 2a7c850..a61dd26 100644 --- a/mingling_macros/src/dispatcher.rs +++ b/mingling_macros/src/func/dispatcher.rs @@ -75,7 +75,7 @@ impl Parse for DispatcherChainInput { // are nearly identical and could benefit from refactoring into common helper functions. #[allow(clippy::too_many_lines)] -pub fn dispatcher(input: TokenStream) -> TokenStream { +pub(crate) fn dispatcher(input: TokenStream) -> TokenStream { // Parse the input let dispatcher_input = syn::parse_macro_input!(input as DispatcherChainInput); @@ -134,8 +134,8 @@ pub fn dispatcher(input: TokenStream) -> TokenStream { ::mingling::macros::node!(#command_name_str) } fn begin(&self, args: Vec<String>) -> ::mingling::ChainProcess<#program_type> { - use ::mingling::Groupped; - #pack::new(args).to_chain() + use ::mingling::Grouped; + ::mingling::Routable::to_chain(#pack::new(args)) } fn clone_dispatcher(&self) -> Box<dyn ::mingling::Dispatcher<#program_type>> { Box::new(#command_struct) @@ -209,7 +209,7 @@ impl Parse for RegisterDispatcherInput { } #[cfg(feature = "dispatch_tree")] -pub fn register_dispatcher(input: TokenStream) -> TokenStream { +pub(crate) fn register_dispatcher(input: TokenStream) -> TokenStream { let RegisterDispatcherInput { node_name, dispatcher_type, @@ -243,7 +243,7 @@ pub fn register_dispatcher(input: TokenStream) -> TokenStream { } #[cfg(not(feature = "dispatch_tree"))] -pub fn register_dispatcher(_input: TokenStream) -> TokenStream { +pub(crate) fn register_dispatcher(_input: TokenStream) -> TokenStream { quote! {}.into() } diff --git a/mingling_macros/src/entry.rs b/mingling_macros/src/func/entry.rs index 2ac5d6b..35209e5 100644 --- a/mingling_macros/src/entry.rs +++ b/mingling_macros/src/func/entry.rs @@ -39,7 +39,7 @@ fn parse_strings(input: &syn::parse::ParseBuffer) -> syn::Result<Vec<String>> { Ok(strings) } -pub fn entry(input: TokenStream) -> TokenStream { +pub(crate) fn entry(input: TokenStream) -> TokenStream { let parsed = parse_macro_input!(input as EntryInput); let strings = match &parsed { diff --git a/mingling_macros/src/func/gen_program.rs b/mingling_macros/src/func/gen_program.rs new file mode 100644 index 0000000..de4da06 --- /dev/null +++ b/mingling_macros/src/func/gen_program.rs @@ -0,0 +1,628 @@ +use std::collections::HashMap; + +use proc_macro::TokenStream; +use quote::quote; +use syn::parse_macro_input; + +use crate::CHAINS; +use crate::CHAINS_EXIST; +#[cfg(feature = "dispatch_tree")] +use crate::COMPILE_TIME_DISPATCHERS; +#[cfg(feature = "comp")] +use crate::COMPLETIONS; +use crate::HELP_REQUESTS; +use crate::PACKED_TYPES; +use crate::RENDERERS; +use crate::RENDERERS_EXIST; +#[cfg(feature = "structural_renderer")] +use crate::STRUCTURAL_RENDERERS; +use crate::attr::{chain, renderer}; +use crate::get_global_set; +#[cfg(feature = "dispatch_tree")] +use crate::systems::dispatch_tree_gen; + +#[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(',').trim(); + 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) +} + +/// Loads the pathf type mapping from `$OUT_DIR/{crate}/type_using.rs`. +/// Always compiled; returns None when pathf feature is not enabled, +/// or when the file does not exist / fails to load. +fn load_pathf_map() -> Option<std::collections::HashMap<String, String>> { + if !cfg!(feature = "pathf") { + return None; + } + let out_dir = std::env::var("OUT_DIR").ok()?; + let crate_name = std::env::var("CARGO_PKG_NAME").ok()?; + let path = std::path::Path::new(&out_dir) + .join(&crate_name) + .join("type_using.rs"); + let content = std::fs::read_to_string(&path).ok()?; + Some( + content + .lines() + .filter_map(|line| { + let line = line.trim(); + if let Some(rest) = line.strip_prefix("use ") { + let path = rest.strip_suffix(';').unwrap_or(rest); + if let Some((_mod, type_name)) = path.rsplit_once("::") { + return Some((type_name.to_string(), path.to_string())); + } + } + None + }) + .collect(), + ) +} + +/// Resolves a type name to its full path token stream using the pathf mapping. +pub(crate) fn resolve_type( + name: &str, + map: &std::collections::HashMap<String, String>, +) -> proc_macro2::TokenStream { + if let Some(full_path) = map.get(name) { + syn::parse_str::<proc_macro2::TokenStream>(full_path).unwrap_or_else(|_| { + let ident = proc_macro2::Ident::new(name, proc_macro2::Span::call_site()); + quote! { #ident } + }) + } else { + let ident = proc_macro2::Ident::new(name, proc_macro2::Span::call_site()); + quote! { #ident } + } +} + +pub(crate) fn gen_program_impl(_input: TokenStream) -> TokenStream { + #[cfg(feature = "comp")] + let comp_gen = quote! { + ::mingling::macros::program_comp_gen!(); + }; + + #[cfg(not(feature = "comp"))] + let comp_gen = quote! {}; + + TokenStream::from(quote! { + /// Alias for the current program type `crate::ThisProgram` + pub type Next = ::mingling::ChainProcess<crate::ThisProgram>; + + impl ::mingling::Routable<crate::ThisProgram> for ::mingling::ChainProcess<crate::ThisProgram> + { + fn to_chain(self) -> ::mingling::ChainProcess<crate::ThisProgram> { + match self { + ::mingling::ChainProcess::Ok((any, _)) => { + ::mingling::ChainProcess::Ok((any, mingling::NextProcess::Chain)) + } + other => other, + } + } + + fn to_render(self) -> ::mingling::ChainProcess<crate::ThisProgram> { + match self { + ::mingling::ChainProcess::Ok((any, _)) => { + ::mingling::ChainProcess::Ok((any, mingling::NextProcess::Renderer)) + } + other => other, + } + } + } + + #comp_gen + ::mingling::macros::program_fallback_gen!(); + ::mingling::macros::program_final_gen!(); + }) +} + +#[cfg(feature = "comp")] +pub(crate) fn program_comp_gen_impl(_input: TokenStream) -> TokenStream { + #[cfg(feature = "async")] + let fn_exec_comp = quote! { + #[doc(hidden)] + #[::mingling::macros::chain] + pub async fn __exec_completion(prev: CompletionContext) -> Next { + use ::mingling::Grouped; + + let read_ctx = ::mingling::ShellContext::try_from(prev.inner); + match read_ctx { + Ok(ctx) => { + let suggest = ::mingling::CompletionHelper::exec_completion::<crate::ThisProgram>(&ctx); + ::mingling::Routable::<crate::ThisProgram>::to_render(crate::CompletionSuggest::new((ctx, suggest))) + } + Err(_) => std::process::exit(1), + } + } + }; + + #[cfg(not(feature = "async"))] + let fn_exec_comp = quote! { + #[doc(hidden)] + #[::mingling::macros::chain] + pub fn __exec_completion(prev: CompletionContext) -> Next { + use ::mingling::Grouped; + + let read_ctx = ::mingling::ShellContext::try_from(prev.inner); + match read_ctx { + Ok(ctx) => { + let suggest = ::mingling::CompletionHelper::exec_completion::<crate::ThisProgram>(&ctx); + ::mingling::Routable::<crate::ThisProgram>::to_render(crate::CompletionSuggest::new((ctx, suggest))) + } + Err(_) => std::process::exit(1), + } + } + }; + + #[cfg(feature = "dispatch_tree")] + let internal_dispatcher_comp = quote! { + use __internal_completion_mod::__internal_dispatcher_comp; + }; + + #[cfg(not(feature = "dispatch_tree"))] + let internal_dispatcher_comp = quote! {}; + + let comp_dispatcher = quote! { + #[doc(hidden)] + mod __internal_completion_mod { + use ::mingling::Grouped; + ::mingling::macros::dispatcher!("__comp", CMDCompletion => CompletionContext); + ::mingling::macros::pack!( + CompletionSuggest = (::mingling::ShellContext, ::mingling::Suggest) + ); + } + #internal_dispatcher_comp + use __internal_completion_mod::CompletionContext; + use __internal_completion_mod::CompletionSuggest; + pub use __internal_completion_mod::CMDCompletion; + + #fn_exec_comp + + ::mingling::macros::register_type!(CompletionContext); + + #[allow(unused)] + #[doc(hidden)] + #[::mingling::macros::renderer] + pub fn __render_completion(prev: CompletionSuggest) -> ::mingling::RenderResult { + let result = ::mingling::RenderResult::default(); + let (ctx, suggest) = prev.inner; + ::mingling::CompletionHelper::render_suggest::<crate::ThisProgram>(ctx, suggest); + result + } + }; + + TokenStream::from(comp_dispatcher) +} + +pub(crate) fn register_type_impl(input: TokenStream) -> TokenStream { + let type_ident = parse_macro_input!(input as syn::Ident); + let entry_str = type_ident.to_string(); + + get_global_set(&PACKED_TYPES) + .lock() + .unwrap() + .insert(entry_str); + + TokenStream::new() +} + +pub(crate) fn register_chain_impl(input: TokenStream) -> TokenStream { + chain::register_chain(input) +} + +pub(crate) fn register_renderer_impl(input: TokenStream) -> TokenStream { + renderer::register_renderer(input) +} + +pub(crate) fn program_fallback_gen_impl(_input: TokenStream) -> TokenStream { + #[cfg(feature = "structural_renderer")] + let pack_empty = quote! { + #[derive(::serde::Serialize, ::mingling::StructuralData, ::mingling::Grouped, Default)] + pub struct ResultEmpty; + }; + + #[cfg(not(feature = "structural_renderer"))] + let pack_empty = quote! { + #[derive(::mingling::Grouped, Default)] + pub struct ResultEmpty; + }; + + let expanded = quote! { + ::mingling::macros::pack!(ErrorRendererNotFound = String); + ::mingling::macros::pack!(ErrorDispatcherNotFound = Vec<String>); + #pack_empty + }; + TokenStream::from(expanded) +} + +#[allow(clippy::too_many_lines)] +pub(crate) fn program_final_gen_impl(_input: TokenStream) -> TokenStream { + let name = syn::Ident::new("ThisProgram", proc_macro2::Span::call_site()); + + let packed_types = get_global_set(&PACKED_TYPES).lock().unwrap().clone(); + + let renderers = get_global_set(&RENDERERS).lock().unwrap().clone(); + let chains = get_global_set(&CHAINS).lock().unwrap().clone(); + let renderer_exist = get_global_set(&RENDERERS_EXIST).lock().unwrap().clone(); + let chain_exist = get_global_set(&CHAINS_EXIST).lock().unwrap().clone(); + + #[cfg(feature = "structural_renderer")] + let structural_renderers = get_global_set(&STRUCTURAL_RENDERERS) + .lock() + .unwrap() + .clone(); + + #[cfg(feature = "comp")] + let completions = get_global_set(&COMPLETIONS).lock().unwrap().clone(); + + let packed_types: Vec<proc_macro2::TokenStream> = packed_types + .iter() + .map(|s| syn::parse_str::<proc_macro2::TokenStream>(s).unwrap()) + .collect(); + + let renderer_tokens: Vec<proc_macro2::TokenStream> = renderers + .iter() + .map(|s| syn::parse_str::<proc_macro2::TokenStream>(s).unwrap()) + .collect(); + + let chain_tokens: Vec<proc_macro2::TokenStream> = chains + .iter() + .map(|s| syn::parse_str::<proc_macro2::TokenStream>(s).unwrap()) + .collect(); + + let renderer_exist_tokens: Vec<proc_macro2::TokenStream> = renderer_exist + .iter() + .map(|s| syn::parse_str::<proc_macro2::TokenStream>(s).unwrap()) + .collect(); + + let chain_exist_tokens: Vec<proc_macro2::TokenStream> = chain_exist + .iter() + .map(|s| syn::parse_str::<proc_macro2::TokenStream>(s).unwrap()) + .collect(); + + let pathf_map: Option<HashMap<String, String>> = load_pathf_map(); + + #[cfg(feature = "pathf")] + let pathf_hint: proc_macro2::TokenStream = if pathf_map.is_none() { + quote! { + compile_error!( +"Cannot load type mapping computed by `pathf`. +If not yet configured, execute `mingling::build::analyze_and_build_type_mapping()` in your `build.rs`. + +fn main() { + // Require features: [\"builds\", \"pathf\"] + mingling::build::analyze_and_build_type_mapping().unwrap(); + ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^\\__ Write to `build.rs` + +} + "); + } + } else { + quote! {} + }; + + #[cfg(not(feature = "pathf"))] + let pathf_hint: proc_macro2::TokenStream = quote! {}; + + let pathf_map: HashMap<String, String> = if cfg!(feature = "pathf") { + pathf_map.unwrap_or_default() + } else { + HashMap::new() + }; + + let pathf_uses: Vec<proc_macro2::TokenStream> = if cfg!(feature = "pathf") { + pathf_map + .values() + .map(|path| format!("use {};", path).parse().unwrap_or_default()) + .collect() + } else { + Vec::new() + }; + + #[cfg(feature = "structural_renderer")] + let structural_renderer_tokens: Vec<proc_macro2::TokenStream> = structural_renderers + .iter() + .map(|s| syn::parse_str::<proc_macro2::TokenStream>(s).unwrap()) + .collect(); + + #[cfg(feature = "structural_renderer")] + let structural_render = quote! { + fn structural_render( + any: ::mingling::AnyOutput<Self::Enum>, + setting: &::mingling::StructuralRendererSetting, + ) -> Result<::mingling::RenderResult, ::mingling::error::StructuralRendererSerializeError> { + #[allow(unused_imports)] + #(#pathf_uses)* + match any.member_id { + #(#structural_renderer_tokens)* + _ => { + // Non-structural types: render ResultEmpty (which implements + // StructuralData + Serialize) instead of producing nothing. + let mut r = ::mingling::RenderResult::default(); + ::mingling::StructuralRenderer::render(&ResultEmpty, setting, &mut r)?; + Ok(r) + } + } + } + }; + + #[cfg(not(feature = "structural_renderer"))] + let structural_render = quote! {}; + + #[cfg(feature = "dispatch_tree")] + let compile_time_dispatchers: Vec<String> = get_global_set(&COMPILE_TIME_DISPATCHERS) + .lock() + .unwrap() + .clone() + .iter() + .cloned() + .collect(); + + #[cfg(feature = "dispatch_tree")] + let dispatch_tree_nodes = { + let entries: Vec<(String, String, String)> = compile_time_dispatchers + .iter() + .filter_map(|entry| { + let parts: Vec<&str> = entry.split(':').collect(); + if parts.len() == 3 { + Some(( + parts[0].to_string(), + parts[1].to_string(), + parts[2].to_string(), + )) + } else { + None + } + }) + .collect(); + + let get_nodes_fn = dispatch_tree_gen::gen_get_nodes(&entries, &pathf_map); + let dispatch_trie_fn = dispatch_tree_gen::gen_dispatch_args_trie(&entries, &pathf_map); + + quote! { + #get_nodes_fn + #dispatch_trie_fn + } + }; + + #[cfg(not(feature = "dispatch_tree"))] + let dispatch_tree_nodes = quote! {}; + + #[cfg(feature = "comp")] + let completion_tokens: Vec<proc_macro2::TokenStream> = completions + .iter() + .map(|s| syn::parse_str::<proc_macro2::TokenStream>(s).unwrap()) + .collect(); + + #[cfg(feature = "comp")] + let comp = quote! { + fn do_comp(any: &::mingling::AnyOutput<Self::Enum>, ctx: &::mingling::ShellContext) -> ::mingling::Suggest { + #[allow(unused_imports)] + #(#pathf_uses)* + match any.member_id { + #(#completion_tokens)* + _ => ::mingling::Suggest::FileCompletion, + } + } + }; + + #[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<Self::Enum>) -> ::mingling::RenderResult { + ::mingling::RenderResult::default() + } + } + } else { + let render_arms: Vec<_> = renderer_tokens.iter().map(|entry| { + let (struct_ident, variant_ident) = parse_entry_pair(entry); + let downcast_ty = resolve_type(&variant_ident.to_string(), &pathf_map); + let resolved_struct = resolve_type(&struct_ident.to_string(), &pathf_map); + 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::<#downcast_ty>().unwrap_unchecked() }; + <#resolved_struct as ::mingling::Renderer>::render(value) + } + } + }).collect(); + quote! { + fn render(any: ::mingling::AnyOutput<Self::Enum>) -> ::mingling::RenderResult { + match any.member_id { + #(#render_arms)* + _ => ::mingling::RenderResult::default(), + } + } + } + }; + + // 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); + let downcast_ty = resolve_type(&variant_ident.to_string(), &pathf_map); + let resolved_struct = resolve_type(&struct_ident.to_string(), &pathf_map); + 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::<#downcast_ty>().unwrap_unchecked() }; + let fut = async { <#resolved_struct as ::mingling::Chain<Self::Enum>>::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); + let downcast_ty = resolve_type(&variant_ident.to_string(), &pathf_map); + let resolved_struct = resolve_type(&struct_ident.to_string(), &pathf_map); + 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::<#downcast_ty>().unwrap_unchecked() }; + <#resolved_struct as ::mingling::Chain<Self::Enum>>::proc(value) + } + } + }) + .collect(); + + let do_chain_fn = if chain_tokens.is_empty() { + quote! { + fn do_chain(_any: ::mingling::AnyOutput<Self::Enum>) -> ::mingling::ChainProcess<Self::Enum> { + ::core::panic!("No chain found for type id") + } + } + } else if ASYNC_ENABLED { + quote! { + fn do_chain( + any: ::mingling::AnyOutput<Self::Enum>, + ) -> ::std::pin::Pin<::std::boxed::Box<dyn ::std::future::Future<Output = ::mingling::ChainProcess<Self::Enum>> + ::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<Self::Enum>, + ) -> ::mingling::ChainProcess<Self::Enum> { + match any.member_id { + #(#chain_arms_sync)* + _ => ::core::panic!("No chain found for type id: {:?}", any.type_id), + } + } + } + }; + + let help_tokens: Vec<proc_macro2::TokenStream> = get_global_set(&HELP_REQUESTS) + .lock() + .unwrap() + .clone() + .iter() + .map(|s| syn::parse_str::<proc_macro2::TokenStream>(s).unwrap()) + .collect(); + + let num_variants = packed_types.len(); + let repr_type = if u8::try_from(num_variants).is_ok() { + quote! { u8 } + } else if u16::try_from(num_variants).is_ok() { + quote! { u16 } + } else if u32::try_from(num_variants).is_ok() { + quote! { u32 } + } else { + quote! { u128 } + }; + + let expanded = quote! { + #pathf_hint + + #[derive(Debug, PartialEq, Eq, Clone)] + #[repr(#repr_type)] + #[allow(nonstandard_style)] + pub enum #name { + #(#packed_types),* + } + + impl ::std::fmt::Display for #name { + fn fmt(&self, f: &mut ::std::fmt::Formatter<'_>) -> ::std::fmt::Result { + match self { + #(#name::#packed_types => write!(f, stringify!(#packed_types)),)* + } + } + } + + impl ::mingling::ProgramCollect for #name { + type Enum = #name; + type ErrorDispatcherNotFound = ErrorDispatcherNotFound; + type ErrorRendererNotFound = ErrorRendererNotFound; + type ResultEmpty = ResultEmpty; + fn build_renderer_not_found(member_id: Self::Enum) -> ::mingling::AnyOutput<Self::Enum> { + ::mingling::AnyOutput::new(ErrorRendererNotFound::new(member_id.to_string())) + } + fn build_dispatcher_not_found(args: Vec<String>) -> ::mingling::AnyOutput<Self::Enum> { + ::mingling::AnyOutput::new(ErrorDispatcherNotFound::new(args)) + } + fn build_empty_result() -> ::mingling::AnyOutput<Self::Enum> { + ::mingling::AnyOutput::new(ResultEmpty) + } + #render_fn + #do_chain_fn + fn render_help(any: ::mingling::AnyOutput<Self::Enum>) -> ::mingling::RenderResult { + #[allow(unused_imports)] + #(#pathf_uses)* + match any.member_id { + #(#help_tokens)* + _ => ::mingling::RenderResult::default(), + } + } + fn has_renderer(any: &::mingling::AnyOutput<Self::Enum>) -> bool { + match any.member_id { + #(#renderer_exist_tokens)* + _ => false + } + } + fn has_chain(any: &::mingling::AnyOutput<Self::Enum>) -> bool { + match any.member_id { + #(#chain_exist_tokens)* + _ => false + } + } + #dispatch_tree_nodes + #structural_render + #comp + } + + impl #name { + /// Creates a new `Program<#name>` instance with default configuration. + pub fn new() -> ::mingling::Program<#name> { + ::mingling::Program::new() + } + + /// Returns a static reference to the global `Program<#name>` singleton. + pub fn this() -> &'static ::mingling::Program<#name> { + &::mingling::this::<#name>() + } + } + }; + + // Clear all global registries to prevent stale state in Rust Analyzer + get_global_set(&PACKED_TYPES).lock().unwrap().clear(); + get_global_set(&CHAINS).lock().unwrap().clear(); + get_global_set(&CHAINS_EXIST).lock().unwrap().clear(); + get_global_set(&RENDERERS).lock().unwrap().clear(); + get_global_set(&RENDERERS_EXIST).lock().unwrap().clear(); + get_global_set(&HELP_REQUESTS).lock().unwrap().clear(); + #[cfg(feature = "comp")] + get_global_set(&COMPLETIONS).lock().unwrap().clear(); + #[cfg(feature = "dispatch_tree")] + get_global_set(&COMPILE_TIME_DISPATCHERS) + .lock() + .unwrap() + .clear(); + #[cfg(feature = "structural_renderer")] + get_global_set(&STRUCTURAL_RENDERERS) + .lock() + .unwrap() + .clear(); + + TokenStream::from(expanded) +} diff --git a/mingling_macros/src/group_impl.rs b/mingling_macros/src/func/group.rs index 59da9dd..b865913 100644 --- a/mingling_macros/src/group_impl.rs +++ b/mingling_macros/src/func/group.rs @@ -91,7 +91,7 @@ fn gen_type_use(type_path: &TypePath) -> proc_macro2::TokenStream { } } -pub fn group_macro(input: TokenStream) -> TokenStream { +pub(crate) fn group_macro(input: TokenStream) -> TokenStream { let input = syn::parse_macro_input!(input as GroupInput); let is_aliased = matches!(input, GroupInput::Aliased { .. }); @@ -124,7 +124,7 @@ pub fn group_macro(input: TokenStream) -> TokenStream { quote! {} }; - // Generate the module with the Groupped implementation + // Generate the module with the Grouped implementation let expanded = quote! { #alias_stmt #[allow(non_camel_case_types)] @@ -133,7 +133,7 @@ pub fn group_macro(input: TokenStream) -> TokenStream { #type_use #alias_use - impl ::mingling::Groupped<__MinglingProgram> for #type_name { + impl ::mingling::Grouped<__MinglingProgram> for #type_name { fn member_id() -> __MinglingProgram { __MinglingProgram::#type_name } diff --git a/mingling_macros/src/node.rs b/mingling_macros/src/func/node.rs index b3a61c6..1b944a1 100644 --- a/mingling_macros/src/node.rs +++ b/mingling_macros/src/func/node.rs @@ -18,7 +18,7 @@ impl Parse for NodeInput { } } -pub fn node(input: TokenStream) -> TokenStream { +pub(crate) fn node(input: TokenStream) -> TokenStream { // Parse the input as a string literal let input_parsed = syn::parse_macro_input!(input as NodeInput); let path_str = input_parsed.path.value(); diff --git a/mingling_macros/src/pack.rs b/mingling_macros/src/func/pack.rs index 7f05232..a1a7e6b 100644 --- a/mingling_macros/src/pack.rs +++ b/mingling_macros/src/func/pack.rs @@ -25,7 +25,7 @@ impl Parse for PackInput { } #[allow(clippy::too_many_lines)] -pub fn pack(input: TokenStream) -> TokenStream { +pub(crate) fn pack(input: TokenStream) -> TokenStream { let pack_input = syn::parse_macro_input!(input as PackInput); let group_name = crate::default_program_path(); @@ -138,7 +138,7 @@ pub fn pack(input: TokenStream) -> TokenStream { } } - impl ::mingling::Groupped<#group_name> for #type_name { + impl ::mingling::Grouped<#group_name> for #type_name { fn member_id() -> #group_name { #group_name::#type_name } diff --git a/mingling_macros/src/pack_err.rs b/mingling_macros/src/func/pack_err.rs index ba7cf17..36e550a 100644 --- a/mingling_macros/src/pack_err.rs +++ b/mingling_macros/src/func/pack_err.rs @@ -31,7 +31,7 @@ impl syn::parse::Parse for PackErrInput { } #[allow(clippy::too_many_lines)] -pub fn pack_err(input: TokenStream) -> TokenStream { +pub(crate) fn pack_err(input: TokenStream) -> TokenStream { let parsed = parse_macro_input!(input as PackErrInput); match parsed { @@ -42,7 +42,7 @@ pub fn pack_err(input: TokenStream) -> TokenStream { // Note: No longer derives Serialize under structural_renderer. // Use pack_err_structural for structured output support. let derive = quote! { - #[derive(::mingling::Groupped)] + #[derive(::mingling::Grouped)] }; let expanded = quote! { @@ -75,7 +75,7 @@ pub fn pack_err(input: TokenStream) -> TokenStream { // Note: No longer derives Serialize under structural_renderer. // Use pack_err_structural for structured output support. let derive = quote! { - #[derive(::mingling::Groupped)] + #[derive(::mingling::Grouped)] }; let expanded = quote! { @@ -123,7 +123,7 @@ pub fn pack_err(input: TokenStream) -> TokenStream { /// impl ::mingling::__private::StructuralData for ErrorNotFound {} /// ``` #[cfg(feature = "structural_renderer")] -pub fn pack_err_structural(input: TokenStream) -> TokenStream { +pub(crate) fn pack_err_structural(input: TokenStream) -> TokenStream { let parsed = parse_macro_input!(input as PackErrInput); let type_name = match &parsed { @@ -150,7 +150,7 @@ pub fn pack_err_structural(input: TokenStream) -> TokenStream { let snake_name = snake_case!(&name_str); let expanded = quote! { - #[derive(::mingling::Groupped, ::serde::Serialize)] + #[derive(::mingling::Grouped, ::serde::Serialize)] pub struct #type_name { /// The snake_case name of this error, automatically set at compile time. pub name: String, @@ -179,7 +179,7 @@ pub fn pack_err_structural(input: TokenStream) -> TokenStream { let snake_name = snake_case!(&name_str); let expanded = quote! { - #[derive(::mingling::Groupped, ::serde::Serialize)] + #[derive(::mingling::Grouped, ::serde::Serialize)] pub struct #type_name { /// The snake_case name of this error, automatically set at compile time. pub name: String, diff --git a/mingling_macros/src/func/r_print.rs b/mingling_macros/src/func/r_print.rs new file mode 100644 index 0000000..e81b544 --- /dev/null +++ b/mingling_macros/src/func/r_print.rs @@ -0,0 +1,62 @@ +use proc_macro::TokenStream; +use proc_macro2::TokenStream as TokenStream2; +use quote::quote; +use syn::parse::{Parse, ParseStream}; +use syn::{Ident, Token}; + +/// Parsed input for `r_println!` and `r_print!`. +/// +/// Two forms: +/// - `(ident, format_args...)` — explicit buffer +/// - `(format_args...)` — implicit `__render_result_buffer` +enum PrintInput { + Explicit { dst: Ident, args: TokenStream2 }, + Implicit { args: TokenStream2 }, +} + +impl Parse for PrintInput { + fn parse(input: ParseStream) -> syn::Result<Self> { + // Peek: if the next token is an ident followed by a comma, it's the explicit form + if input.peek(Ident) && input.peek2(Token![,]) { + let dst: Ident = input.parse()?; + let _comma: Token![,] = input.parse()?; + let args: TokenStream2 = input.parse()?; + Ok(PrintInput::Explicit { dst, args }) + } else { + let args: TokenStream2 = input.parse()?; + Ok(PrintInput::Implicit { args }) + } + } +} + +fn expand_print(input: TokenStream, method: &str) -> TokenStream { + let parsed: PrintInput = match syn::parse(input) { + Ok(p) => p, + Err(e) => return e.to_compile_error().into(), + }; + + let method_ident = Ident::new(method, proc_macro2::Span::call_site()); + + let expanded = match parsed { + PrintInput::Explicit { dst, args } => { + quote! { + #dst.#method_ident(format!(#args)) + } + } + PrintInput::Implicit { args } => { + quote! { + __render_result_buffer.#method_ident(format!(#args)) + } + } + }; + + expanded.into() +} + +pub(crate) fn r_println(input: TokenStream) -> TokenStream { + expand_print(input, "println") +} + +pub(crate) fn r_print(input: TokenStream) -> TokenStream { + expand_print(input, "print") +} diff --git a/mingling_macros/src/suggest.rs b/mingling_macros/src/func/suggest.rs index d3ab446..0f2026f 100644 --- a/mingling_macros/src/suggest.rs +++ b/mingling_macros/src/func/suggest.rs @@ -35,7 +35,7 @@ impl Parse for SuggestItem { } #[cfg(feature = "comp")] -pub fn suggest(input: TokenStream) -> TokenStream { +pub(crate) fn suggest(input: TokenStream) -> TokenStream { let input = parse_macro_input!(input as SuggestInput); let mut items = Vec::new(); @@ -71,7 +71,7 @@ pub fn suggest(input: TokenStream) -> TokenStream { expanded.into() } -pub fn suggest_enum(input: TokenStream) -> TokenStream { +pub(crate) fn suggest_enum(input: TokenStream) -> TokenStream { let enum_type = parse_macro_input!(input as syn::Type); let expanded = quote! {{ diff --git a/mingling_macros/src/lib.rs b/mingling_macros/src/lib.rs index 9419f39..56ed999 100644 --- a/mingling_macros/src/lib.rs +++ b/mingling_macros/src/lib.rs @@ -13,7 +13,7 @@ //! ┌──────────────────────────────────────────────────────────────────┐ //! │ Phase 1: Declaration │ //! │ │ -//! │ dispatcher! pack! node! #[derive(Groupped)] │ +//! │ dispatcher! pack! node! #[derive(Grouped)] │ //! │ │ │ │ │ │ //! │ V V V V │ //! │ Declares Wraps a Builds Makes a type │ @@ -55,7 +55,7 @@ //! | `pack_err!` | Creates an error struct with automatic `name` field | //! | `pack_err_structural!` | Like `pack_err!` but also derives `StructuralData` for structured output | //! | `entry!` | Creates a packed entry from string literals | -//! | [`#[derive(Groupped)]`](derive@Groupped) | Makes a type recognizable by the framework's type registry | +//! | [`#[derive(Grouped)]`](derive@Grouped) | Makes a type recognizable by the framework's type registry | //! | `#[derive(StructuralData)]` | Marks a type as eligible for structured output (JSON/YAML/etc.) | //! | [`#[derive(EnumTag)]`](derive@EnumTag) | Adds enum variant metadata (name, description) | //! @@ -141,40 +141,50 @@ //! } //! ``` -use proc_macro::TokenStream; +#![deny(missing_docs)] + +#[cfg(feature = "extra_macros")] use quote::quote; + +#[cfg(feature = "extra_macros")] +use syn::parse_macro_input; + +use proc_macro::TokenStream; use std::collections::BTreeSet; use std::sync::Mutex; use std::sync::OnceLock; -use syn::parse_macro_input; -mod chain; +mod attr; +mod derive; +mod func; +mod systems; + +mod extensions; +mod utils; + +// Bring all sub-modules into scope at the old paths so that existing +// references (e.g. `chain::chain_attr`, `renderer::renderer_attr`) +// continue to work without any `use`-path changes. #[cfg(feature = "comp")] -mod completion; -#[cfg(feature = "dispatch_tree")] -mod dispatch_tree_gen; -mod dispatcher; +use attr::completion; #[cfg(feature = "clap")] -mod dispatcher_clap; +use attr::dispatcher_clap; #[cfg(feature = "extra_macros")] -mod entry; -mod enum_tag; +use attr::program_setup; +use attr::{chain, help, renderer}; +use derive::{enum_tag, grouped}; #[cfg(feature = "extra_macros")] -mod group_impl; -mod groupped; -mod help; -mod node; -mod pack; +use func::entry; #[cfg(feature = "extra_macros")] -mod pack_err; +pub(crate) use func::group as group_impl; #[cfg(feature = "extra_macros")] -mod program_setup; -mod renderer; -mod res_injection; -#[cfg(feature = "structural_renderer")] -mod structural_data; +use func::pack_err; #[cfg(feature = "comp")] -mod suggest; +use func::suggest; +use func::{dispatcher, node, pack}; +use systems::res_injection; +#[cfg(feature = "structural_renderer")] +pub(crate) use systems::structural_data; pub(crate) fn default_program_path() -> proc_macro2::TokenStream { quote::quote! { crate::ThisProgram } @@ -262,44 +272,36 @@ fn entry_has_variant(entry: &str, variant_name: &str) -> bool { entry.contains(&format!(":: {variant_name} =>")) } -/// Registers an outside-type as a member of a program group without modifying its definition. +/// Registers an outside type (from `std` or other crates) as a type recognizable +/// by the Mingling framework, without modifying the original type definition. /// -/// This macro allows you to use outside-types from external crates (like `std::io::Error`) -/// within the Mingling framework by generating a `Groupped` implementation and registering -/// the type's simple name as an enum variant. +/// This macro generates a newtype wrapper around the given type that implements +/// `Grouped`, `Into<AnyOutput>`, `Into<ChainProcess>`, and the `Routable` trait, +/// making the outside type usable in `#[chain]` and `#[renderer]` functions. /// /// # Syntax /// /// ```rust,ignore -/// group!(std::io::Error); +/// // Simple form — creates a wrapper named after the type's last segment: /// group!(ParseIntError); -/// ``` /// -/// The type is registered under the default program (`crate::ThisProgram`). -/// -/// # How it works -/// -/// The macro generates a module containing: -/// - A `use` import for the program path and the outside-type -/// - An `impl Groupped<Program>` for the outside-type -/// - A `register_type!` call with the type's simple name -/// -/// The type's simple name (e.g. `Error`) is used as the enum variant in the generated -/// program enum, just like `#[derive(Groupped)]` or `pack!`. +/// // Aliased form — creates a wrapper with a custom name: +/// group!(ErrorIo = std::io::Error); +/// ``` /// /// # Example /// -/// ```rust,ignore -/// use mingling::macros::group; -/// -/// // Register std::io::Error as a group member -/// group!(std::io::Error); +/// See the full example in the crate documentation or run: +/// ```bash +/// cargo run --example example-outside-type -- parse 42 +/// cargo run --example example-outside-type -- parse hello +/// cargo run --example example-outside-type -- error /// ``` /// -/// After expansion, the type can be used in chains and renderers like any -/// `#[derive(Groupped)]` type. +/// # Requirements /// -/// This macro is only available with the `extra_macros` feature. +/// - The type must be accessible at the call site (imported or fully qualified). +/// - The alias name (if provided) must not conflict with existing types. #[cfg(feature = "extra_macros")] #[proc_macro] pub fn group(input: TokenStream) -> TokenStream { @@ -402,7 +404,7 @@ pub fn node(input: TokenStream) -> TokenStream { /// - `AsRef<String>`, `AsMut<String>` /// - `Default` if `String: Default` /// - `Into<AnyOutput<ThisProgram>>`, `Into<ChainProcess<ThisProgram>>` -/// - Implements `Groupped<ThisProgram>` with `member_id()` returning the enum variant +/// - Implements `Grouped<ThisProgram>` with `member_id()` returning the enum variant /// /// The struct is also registered via `register_type!` so that `gen_program!` /// can include it in the program enum. @@ -438,7 +440,7 @@ pub fn pack_structural(input: TokenStream) -> TokenStream { /// Creates an error struct with a `name: String` field and optional `info: Type` field. /// -/// This macro provides a concise way to define error types that implement `Groupped` +/// This macro provides a concise way to define error types that implement `Grouped` /// and are registered for inclusion in the program enum. /// /// The `name` field is automatically set to the snake_case version of the struct name @@ -461,7 +463,7 @@ pub fn pack_structural(input: TokenStream) -> TokenStream { /// For `pack_err!(ErrorNotFound)`: /// /// ```rust,ignore -/// #[derive(::mingling::Groupped)] +/// #[derive(::mingling::Grouped)] /// pub struct ErrorNotFound { /// name: String, /// } @@ -478,7 +480,7 @@ pub fn pack_structural(input: TokenStream) -> TokenStream { /// For `pack_err!(ErrorNotDir = PathBuf)`: /// /// ```rust,ignore -/// #[derive(::mingling::Groupped)] +/// #[derive(::mingling::Grouped)] /// pub struct ErrorNotDir { /// name: String, /// info: PathBuf, @@ -521,20 +523,47 @@ pub fn pack_err_structural(input: TokenStream) -> TokenStream { pack_err::pack_err_structural(input) } -/// Early-returns an error from a `Result`, converting the `Ok` branch to a -/// `ChainProcess`. +/// Early-returns the error from a `Result`, converting the `Ok` branch to the +/// next chain process value. /// /// This macro is equivalent to: /// ```rust,ignore /// match expr { /// Ok(r) => r, -/// Err(e) => return ::mingling::Groupped::to_chain(e), +/// Err(e) => return ::mingling::Routable::to_chain(e), /// } /// ``` /// -/// It is useful inside chain functions where you have a `Result<SomeType, SomeType>` -/// where both types implement `Groupped` and want to propagate the error case -/// as an early return via `Groupped::to_chain()`. +/// It is useful inside chain functions where you have a `Result<SuccessType, ErrorType>` +/// where both types implement `Routable` and you want to propagate the error case +/// as an early return via `Routable::to_chain()`. +/// +/// The key difference from a simple `?` operator is that `route!` converts **both** +/// the success and error types into the chain process — the `Ok` value is unwrapped +/// directly, while the `Err` value is converted via `Routable::to_chain()` and +/// returned early. +/// +/// ## Interaction with `#[routeify]` +/// +/// The [`#[routeify]`](attr.routeify.html) attribute macro automatically replaces +/// every `expr?` inside a function with `route!(expr)`. This means you can use the +/// familiar `?` syntax in chain functions instead of writing `route!(...)` +/// explicitly: +/// +/// ```rust,ignore +/// use mingling::macros::chain; +/// +/// #[chain(routeify)] +/// fn process(prev: SomeEntry) -> Next { +/// // `?` here expands to `route!(...)` → this macro → the match block +/// let value = some_fallible_call()?; +/// value.to_chain() +/// } +/// ``` +/// +/// Because `#[routeify]` maps the span of `?` to this macro, hovering over `?` in +/// a `#[routeify]` function will display this documentation — explaining what +/// the `?` actually expands to. /// /// # Example /// @@ -542,7 +571,7 @@ pub fn pack_err_structural(input: TokenStream) -> TokenStream { /// use mingling::macros::{chain, route}; /// /// #[chain] -/// fn process(prev: SomeEntry) -> ChainProcess<ThisProgram> { +/// fn process(prev: SomeEntry) -> Next { /// let value = route!(current_dir().map_err(|e| ErrorEntry::new(e.to_string_lossy().to_string()))); /// // value is the PathBuf from current_dir() /// value.to_chain() @@ -555,7 +584,7 @@ pub fn route(input: TokenStream) -> TokenStream { let expanded = quote! { match #expr { Ok(r) => r, - Err(e) => return ::mingling::Groupped::to_chain(e), + Err(e) => return ::mingling::Routable::to_chain(e), } }; TokenStream::from(expanded) @@ -613,7 +642,7 @@ pub fn route(input: TokenStream) -> TokenStream { #[proc_macro] pub fn empty_result(_input: TokenStream) -> TokenStream { let expanded = quote! { - <crate::ResultEmpty as ::mingling::Groupped::<crate::ThisProgram>>::to_chain(crate::ResultEmpty) + <crate::ResultEmpty as ::mingling::Routable::<crate::ThisProgram>>::to_chain(crate::ResultEmpty) }; TokenStream::from(expanded) } @@ -872,6 +901,11 @@ pub fn dispatcher(input: TokenStream) -> TokenStream { /// - With the `async` feature, async functions are supported; without it, async functions are rejected. #[proc_macro_attribute] pub fn chain(attr: TokenStream, item: TokenStream) -> TokenStream { + // Extension point: if attr contains extension identifiers like `routeify`, + // re-dispatch as #[ext1] #[ext2] #[chain] fn ... + if let Some(redispatch) = extensions::try_redispatch_simple(attr.clone(), &item, "chain") { + return redispatch; + } chain::chain_attr(attr, item) } @@ -939,6 +973,9 @@ pub fn chain(attr: TokenStream, item: TokenStream) -> TokenStream { /// ``` #[proc_macro_attribute] pub fn renderer(attr: TokenStream, item: TokenStream) -> TokenStream { + if let Some(redispatch) = extensions::try_redispatch_simple(attr.clone(), &item, "renderer") { + return redispatch; + } renderer::renderer_attr(attr, item) } @@ -993,6 +1030,9 @@ pub fn renderer(attr: TokenStream, item: TokenStream) -> TokenStream { #[cfg(feature = "comp")] #[proc_macro_attribute] pub fn completion(attr: TokenStream, item: TokenStream) -> TokenStream { + if let Some(redispatch) = extensions::try_redispatch_completion(attr.clone(), &item) { + return redispatch; + } completion::completion_attr(attr, item) } @@ -1246,14 +1286,136 @@ pub fn register_dispatcher(input: TokenStream) -> TokenStream { /// /// [`BasicProgramSetup`]: https://docs.rs/mingling/latest/mingling/setup/struct.BasicProgramSetup.html #[proc_macro_attribute] -pub fn help(_attr: TokenStream, item: TokenStream) -> TokenStream { +pub fn help(attr: TokenStream, item: TokenStream) -> TokenStream { + if let Some(redispatch) = extensions::try_redispatch_simple(attr.clone(), &item, "help") { + return redispatch; + } help::help_attr(item) } -/// Derive macro for automatically implementing the `Groupped` trait on a struct. +/// Extension attribute macro that transforms `expr?` into `route!(expr)`. +/// +/// Designed for use with `#[chain(routeify, ...)]` to enable concise error +/// routing in chain functions using the `?` operator syntax. +/// +/// # Example +/// +/// ```rust,ignore +/// #[chain(routeify)] +/// fn handle_calc(args: EntryCalculate) -> Next { +/// let a = args.pick(&arg![f32]).to_result()?; +/// let op = args.pick(&arg![Operator]).to_result()?; +/// StateCalculate { number_a: a, operator: op, ... }.to_chain() +/// } +/// ``` +#[cfg(feature = "extra_macros")] +#[proc_macro_attribute] +pub fn routeify(attr: TokenStream, item: TokenStream) -> TokenStream { + extensions::routeify::routeify_impl(attr, item) +} + +/// Wraps a unit-returning function to produce a `RenderResult`. +/// +/// The `#[buffer]` attribute macro injects a local `__render_result_buffer` +/// variable of type `::mingling::RenderResult` and changes the function's +/// return type to `::mingling::RenderResult`. Inside the body, use the +/// `r_print!` and `r_println!` macros to write into the buffer. +/// +/// # Example +/// +/// ```rust,ignore +/// use mingling::macros::{buffer, r_println}; +/// +/// #[buffer] +/// fn render_my_type(prev: MyType) { +/// r_println!("Value: {:?}", *prev); +/// } +/// ``` /// -/// The `#[derive(Groupped)]` macro: -/// 1. Implements `Groupped<crate::ThisProgram>`. +/// This expands to: +/// +/// ```rust,ignore +/// fn render_my_type(prev: MyType) -> mingling::RenderResult { +/// let mut __render_result_buffer = mingling::RenderResult::new(); +/// { +/// r_println!("Value: {:?}", *prev); +/// } +/// __render_result_buffer +/// } +/// ``` +/// +/// # Requirements +/// +/// - The function must return `()` (unit). +/// - The function cannot be async. +#[proc_macro_attribute] +pub fn buffer(attr: TokenStream, item: TokenStream) -> TokenStream { + extensions::buffer::buffer_impl(attr, item) +} + +/// Prints text to a `RenderResult` buffer, with a trailing newline. +/// +/// # Implicit buffer (inside `#[buffer]` functions) +/// +/// ```rust,ignore +/// use mingling::macros::{buffer, r_println}; +/// +/// #[buffer] +/// fn render() { +/// r_println!("Hello, {}!", name); +/// } +/// ``` +/// +/// # Explicit buffer +/// +/// Pass a `RenderResult` variable as the first argument: +/// +/// ```rust,ignore +/// use mingling::macros::r_println; +/// use mingling::RenderResult; +/// +/// let mut r = RenderResult::new(); +/// r_println!(r, "value: {}", 42); +/// assert_eq!(&*r, "value: 42\n"); +/// ``` +#[proc_macro] +pub fn r_println(input: TokenStream) -> TokenStream { + func::r_print::r_println(input) +} + +/// Prints text to a `RenderResult` buffer, without a trailing newline. +/// +/// # Implicit buffer (inside `#[buffer]` functions) +/// +/// ```rust,ignore +/// use mingling::macros::{buffer, r_print}; +/// +/// #[buffer] +/// fn render() { +/// r_print!("Hello, "); +/// r_println!("world!"); +/// } +/// ``` +/// +/// # Explicit buffer +/// +/// ```rust,ignore +/// use mingling::macros::r_print; +/// use mingling::RenderResult; +/// +/// let mut r = RenderResult::new(); +/// r_print!(r, "value: {}", 42); +/// assert_eq!(&*r, "value: 42"); +/// ``` +#[proc_macro] +pub fn r_print(input: TokenStream) -> TokenStream { + func::r_print::r_print(input) +} + +/// Derive macro for automatically implementing the `Grouped` trait on a struct. +/// +/// The `#[derive(Grouped)]` macro: +/// 1. Implements `Grouped<crate::ThisProgram>`. /// 2. Registers the type via `register_type!` so it's included in the program enum. /// 3. Generates `Into<AnyOutput<Group>>` and `Into<ChainProcess<Group>>` conversions. /// 4. Adds `to_chain()` and `to_render()` methods to the struct. @@ -1261,7 +1423,7 @@ pub fn help(_attr: TokenStream, item: TokenStream) -> TokenStream { /// # Syntax /// /// ```rust,ignore -/// #[derive(Groupped)] +/// #[derive(Grouped)] /// struct MyStruct { /// // ... /// } @@ -1270,9 +1432,9 @@ pub fn help(_attr: TokenStream, item: TokenStream) -> TokenStream { /// # Example /// /// ```rust,ignore -/// use mingling::macros::Groupped; +/// use mingling::macros::Grouped; /// -/// #[derive(Groupped)] +/// #[derive(Grouped)] /// struct Greeting { /// name: String, /// } @@ -1280,9 +1442,9 @@ pub fn help(_attr: TokenStream, item: TokenStream) -> TokenStream { /// /// This is equivalent to using `pack!` but works with custom structs that /// have named fields. For simple wrappers, prefer `pack!`. -#[proc_macro_derive(Groupped, attributes(group))] -pub fn derive_groupped(input: TokenStream) -> TokenStream { - groupped::derive_groupped(input) +#[proc_macro_derive(Grouped, attributes(group))] +pub fn derive_grouped(input: TokenStream) -> TokenStream { + grouped::derive_grouped(input) } /// Derive macro for automatically implementing the `EnumTag` trait on an enum @@ -1351,18 +1513,18 @@ pub fn derive_structural_data(input: TokenStream) -> TokenStream { structural_data::derive_structural_data(input) } -/// Derive macro for implementing both `Groupped` and `serde::Serialize` on a struct. +/// Derive macro for implementing both `Grouped` and `serde::Serialize` on a struct. /// /// **This macro is only available with the `structural_renderer` feature.** /// -/// This is identical to `#[derive(Groupped)]` but also adds `#[derive(serde::Serialize)]` +/// This is identical to `#[derive(Grouped)]` but also adds `#[derive(serde::Serialize)]` /// to the struct, which is required for the structural renderer to serialize output /// to formats like JSON, YAML, TOML, or RON. /// /// # Syntax /// /// ```rust,ignore -/// #[derive(GrouppedSerialize)] +/// #[derive(GroupedSerialize)] /// struct Info { /// name: String, /// age: i32, @@ -1372,19 +1534,19 @@ pub fn derive_structural_data(input: TokenStream) -> TokenStream { /// # Example /// /// ```rust,ignore -/// use mingling::GrouppedSerialize; +/// use mingling::GroupedSerialize; /// use serde::Serialize; /// -/// #[derive(GrouppedSerialize)] +/// #[derive(GroupedSerialize)] /// struct Info { /// name: String, /// age: i32, /// } /// ``` #[cfg(feature = "structural_renderer")] -#[proc_macro_derive(GrouppedSerialize, attributes(group))] -pub fn derive_groupped_serialize(input: TokenStream) -> TokenStream { - groupped::derive_groupped_serialize(input) +#[proc_macro_derive(GroupedSerialize, attributes(group))] +pub fn derive_grouped_serialize(input: TokenStream) -> TokenStream { + grouped::derive_grouped_serialize(input) } /// Generates the program enum and all collected types, chains, and renderers. @@ -1436,119 +1598,42 @@ pub fn derive_groupped_serialize(input: TokenStream) -> TokenStream { /// gen_program!(); /// ``` #[proc_macro] -pub fn gen_program(_input: TokenStream) -> TokenStream { - #[cfg(feature = "comp")] - let comp_gen = quote! { - ::mingling::macros::program_comp_gen!(); - }; - - #[cfg(not(feature = "comp"))] - let comp_gen = quote! {}; - - TokenStream::from(quote! { - pub type Next = ::mingling::ChainProcess<crate::ThisProgram>; - - #comp_gen - ::mingling::macros::program_fallback_gen!(); - ::mingling::macros::program_final_gen!(); - }) +pub fn gen_program(input: TokenStream) -> TokenStream { + func::gen_program::gen_program_impl(input) } -/// Internal macro used by `gen_program!` to generate completion infrastructure. +/// Internal macro used by `gen_program!` to generate the completion infrastructure for +/// shell completion support. /// /// **This macro is only available with the `comp` feature.** /// -/// This is an internal macro and should not be called directly by user code. -/// It generates a completion dispatcher, the `CompletionContext` type, and -/// the execution/render logic for shell completion. -/// -/// The generated module `__completion_gen` contains: -/// - A `__comp` dispatcher that routes completion requests -/// - A `__exec_completion` chain that processes `CompletionContext` into `CompletionSuggest` -/// - A `__render_completion` renderer that outputs completion suggestions -#[proc_macro] +/// The `program_comp_gen!` macro generates: +/// 1. A hidden `__internal_completion_mod` module containing: +/// - A `CompletionContext` packed type (wrapping `ShellContext`), dispatched via `"__comp"`. +/// - A `CompletionSuggest` packed type (wrapping `(ShellContext, Suggest)`). +/// - An internal dispatcher (`CMDCompletion`) for the `"__comp"` command path. +/// 2. An internal chain function `__exec_completion` that: +/// - Reads a `ShellContext` from the packed `CompletionContext`. +/// - Calls `CompletionHelper::exec_completion::<ThisProgram>(&ctx)` to generate suggestions. +/// - Routes the result to the completion renderer via `CompletionSuggest`. +/// 3. An internal renderer `__render_completion` that renders the suggestions via +/// `CompletionHelper::render_suggest`. +/// +/// When the `dispatch_tree` feature is enabled, it also imports the internal dispatcher +/// from the generated module into the parent scope for trie-based dispatch. +/// +/// This macro is called automatically by `gen_program!` and should not be called +/// directly by user code. #[cfg(feature = "comp")] -pub fn program_comp_gen(_input: TokenStream) -> TokenStream { - #[cfg(feature = "async")] - let fn_exec_comp = quote! { - #[doc(hidden)] - #[::mingling::macros::chain] - pub async fn __exec_completion(prev: CompletionContext) -> Next { - use ::mingling::Groupped; - - let read_ctx = ::mingling::ShellContext::try_from(prev.inner); - match read_ctx { - Ok(ctx) => { - let suggest = ::mingling::CompletionHelper::exec_completion::<crate::ThisProgram>(&ctx); - crate::CompletionSuggest::new((ctx, suggest)).to_render() - } - Err(_) => std::process::exit(1), - } - } - }; - - #[cfg(not(feature = "async"))] - let fn_exec_comp = quote! { - #[doc(hidden)] - #[::mingling::macros::chain] - pub fn __exec_completion(prev: CompletionContext) -> Next { - use ::mingling::Groupped; - - let read_ctx = ::mingling::ShellContext::try_from(prev.inner); - match read_ctx { - Ok(ctx) => { - let suggest = ::mingling::CompletionHelper::exec_completion::<crate::ThisProgram>(&ctx); - crate::CompletionSuggest::new((ctx, suggest)).to_render() - } - Err(_) => std::process::exit(1), - } - } - }; - - #[cfg(feature = "dispatch_tree")] - let internal_dispatcher_comp = quote! { - use __internal_completion_mod::__internal_dispatcher_comp; - }; - - #[cfg(not(feature = "dispatch_tree"))] - let internal_dispatcher_comp = quote! {}; - - let comp_dispatcher = quote! { - #[doc(hidden)] - mod __internal_completion_mod { - use ::mingling::Groupped; - ::mingling::macros::dispatcher!("__comp", CMDCompletion => CompletionContext); - ::mingling::macros::pack!( - CompletionSuggest = (::mingling::ShellContext, ::mingling::Suggest) - ); - } - #internal_dispatcher_comp - use __internal_completion_mod::CompletionContext; - use __internal_completion_mod::CompletionSuggest; - pub use __internal_completion_mod::CMDCompletion; - - #fn_exec_comp - - ::mingling::macros::register_type!(CompletionContext); - - #[allow(unused)] - #[doc(hidden)] - #[::mingling::macros::renderer] - pub fn __render_completion(prev: CompletionSuggest) -> ::mingling::RenderResult { - let result = ::mingling::RenderResult::default(); - let (ctx, suggest) = prev.inner; - ::mingling::CompletionHelper::render_suggest::<crate::ThisProgram>(ctx, suggest); - result - } - }; - - TokenStream::from(comp_dispatcher) +#[proc_macro] +pub fn program_comp_gen(input: TokenStream) -> TokenStream { + func::gen_program::program_comp_gen_impl(input) } /// Registers a type into the global packed types registry for inclusion in /// the program enum generated by `gen_program!`. /// -/// This macro is called internally by `pack!` and `#[derive(Groupped)]`(`macro.derive_groupped.html`) +/// This macro is called internally by `pack!` and `#[derive(Grouped)]`(`macro.derive_grouped.html`) /// and is generally not needed in user code. However, it can be used for manual /// registration if you are implementing custom type registration outside of /// the standard macros. @@ -1567,115 +1652,60 @@ pub fn program_comp_gen(_input: TokenStream) -> TokenStream { /// Panics if the global `PACKED_TYPES` mutex is poisoned. #[proc_macro] pub fn register_type(input: TokenStream) -> TokenStream { - let type_ident = parse_macro_input!(input as syn::Ident); - let entry_str = type_ident.to_string(); - - get_global_set(&PACKED_TYPES) - .lock() - .unwrap() - .insert(entry_str); - - TokenStream::new() + func::gen_program::register_type_impl(input) } -/// Registers a chain mapping from a previous type to a chain struct. +/// Registers a chain mapping function into the global chain registry. /// -/// This macro is called internally by `#[chain]`(macro.chain.html) and is -/// generally not needed in user code. It inserts entries into the global -/// `CHAINS` and `CHAINS_EXIST` registries. +/// This macro is called internally by `#[chain]` and is generally not needed +/// in user code. Each call stores a string entry containing the source-to-target +/// type mapping, which is later consumed by `gen_program!` to generate the +/// `has_chain` and `do_chain` dispatch logic in `ProgramCollect`. /// -/// # Syntax +/// The entry string format is a match arm: the source variant maps to a call +/// that converts the value into a `ChainProcess` via the destination type. /// -/// ```rust,ignore -/// register_chain!(PreviousType, ChainStruct); -/// ``` +/// # Panics /// -/// The `PreviousType` is the input type of the chain step, and `ChainStruct` -/// is the generated struct that implements the `Chain` trait. +/// Panics if the global `CHAINS` mutex is poisoned. #[proc_macro] pub fn register_chain(input: TokenStream) -> TokenStream { - chain::register_chain(input) + func::gen_program::register_chain_impl(input) } -/// Registers a renderer mapping from a type to a renderer struct. +/// Registers a renderer mapping function into the global renderer registry. /// -/// This macro is called internally by `#[renderer]`(macro.renderer.html) and is -/// generally not needed in user code. It inserts entries into the global -/// `RENDERERS`, `RENDERERS_EXIST` and (with `structural_renderer` feature) -/// `STRUCTURAL_RENDERERS` registries. +/// This macro is called internally by `#[renderer]` and is generally not +/// needed in user code. Each call stores a string entry containing the +/// type-to-render mapping, which is later consumed by `gen_program!` to +/// generate the `has_renderer` and `render` dispatch logic in `ProgramCollect`. /// -/// # Syntax +/// The entry string format is a match arm: the type variant maps to a call +/// of the registered renderer function that produces a `RenderResult`. /// -/// ```rust,ignore -/// register_renderer!(PreviousType, RendererStruct); -/// ``` +/// # Panics /// -/// The `PreviousType` is the input type of the renderer, and `RendererStruct` -/// is the generated struct that implements the `Renderer` trait. +/// Panics if the global `RENDERERS` mutex is poisoned. #[proc_macro] pub fn register_renderer(input: TokenStream) -> TokenStream { - renderer::register_renderer(input) + func::gen_program::register_renderer_impl(input) } -/// Internal macro used by `gen_program!` to generate fallback types. -/// -/// This macro generates the fallback wrapper types that are essential -/// for error handling in the Mingling pipeline: -/// -/// - **`ErrorRendererNotFound`** — Wraps a `String` (the name of the missing renderer). -/// Used when no matching renderer is found for a given output type. -/// - **`ErrorDispatcherNotFound`** — Wraps `Vec<String>` (the unrecognized command args). -/// Used when no matching dispatcher is found for user input. -/// - **`ResultEmpty`** — Wraps `()` (the unit type). -/// Used when the chain returns an empty result. -/// -/// Users can (and should) write `#[renderer]` functions for these types -/// to provide meaningful error messages. +/// Internal macro used by `gen_program!` to generate the fallback types for +/// error cases when no dispatcher or renderer is found. /// /// This macro is called automatically by `gen_program!` and should not /// be called directly by user code. -/// -/// # Syntax -/// -/// ```rust,ignore -/// // Called internally by gen_program!: -/// program_fallback_gen!(); -/// ``` -/// -/// # Generated code equivalent -/// -/// ```rust,ignore -/// pack!(ErrorRendererNotFound = String); -/// pack!(ErrorDispatcherNotFound = Vec<String>); -/// pack!(ResultEmpty = ()); -/// ``` #[proc_macro] -pub fn program_fallback_gen(_input: TokenStream) -> TokenStream { - #[cfg(feature = "structural_renderer")] - let pack_empty = quote! { - #[derive(::serde::Serialize, ::mingling::StructuralData, ::mingling::Groupped, Default)] - pub struct ResultEmpty; - }; - - #[cfg(not(feature = "structural_renderer"))] - let pack_empty = quote! { - #[derive(::mingling::Groupped, Default)] - pub struct ResultEmpty; - }; - - let expanded = quote! { - ::mingling::macros::pack!(ErrorRendererNotFound = String); - ::mingling::macros::pack!(ErrorDispatcherNotFound = Vec<String>); - #pack_empty - }; - TokenStream::from(expanded) +pub fn program_fallback_gen(input: TokenStream) -> TokenStream { + func::gen_program::program_fallback_gen_impl(input) } /// Internal macro used by `gen_program!` to generate the final program enum /// and its `ProgramCollect` implementation. /// /// This is the core code generation macro that: -/// 1. Collects all registered types (from `pack!`, `#[derive(Groupped)]`, etc.) and +/// 1. Collects all registered types (from `pack!`, `#[derive(Grouped)]`, etc.) and /// creates an enum with each type as a variant. /// 2. Generates the `Display` implementation for the enum. /// 3. Generates the `ProgramCollect` implementation that dispatches to all @@ -1720,434 +1750,9 @@ pub fn program_fallback_gen(_input: TokenStream) -> TokenStream { /// pub fn new() -> Program<MyProgram> { Program::new() } /// } /// ``` -/// -/// # 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(',').trim(); - 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) -} - -/// Loads the pathf type mapping from `$OUT_DIR/{crate}/type_using.rs`. -/// Always compiled; returns empty map when pathf feature is not enabled. -fn load_pathf_map() -> std::collections::HashMap<String, String> { - if !cfg!(feature = "pathf") { - return std::collections::HashMap::new(); - } - let out_dir = std::env::var("OUT_DIR").ok(); - let crate_name = std::env::var("CARGO_PKG_NAME").ok(); - match (out_dir, crate_name) { - (Some(dir), Some(name)) => { - let path = std::path::Path::new(&dir).join(&name).join("type_using.rs"); - match std::fs::read_to_string(&path) { - Ok(content) => content - .lines() - .filter_map(|line| { - let line = line.trim(); - if let Some(rest) = line.strip_prefix("use ") { - let path = rest.strip_suffix(';').unwrap_or(rest); - if let Some((_mod, type_name)) = path.rsplit_once("::") { - return Some((type_name.to_string(), path.to_string())); - } - } - None - }) - .collect(), - Err(_) => std::collections::HashMap::new(), - } - } - _ => std::collections::HashMap::new(), - } -} - -/// Resolves a type name to its full path token stream using the pathf mapping. -pub(crate) fn resolve_type( - name: &str, - map: &std::collections::HashMap<String, String>, -) -> proc_macro2::TokenStream { - if let Some(full_path) = map.get(name) { - syn::parse_str::<proc_macro2::TokenStream>(full_path).unwrap_or_else(|_| { - let ident = proc_macro2::Ident::new(name, proc_macro2::Span::call_site()); - quote! { #ident } - }) - } else { - let ident = proc_macro2::Ident::new(name, proc_macro2::Span::call_site()); - quote! { #ident } - } -} - -/// Panics if any of the global registries (`PACKED_TYPES`, `RENDERERS`, `CHAINS`, etc.) -/// are poisoned. #[proc_macro] -#[allow(clippy::too_many_lines)] -pub fn program_final_gen(_input: TokenStream) -> TokenStream { - let name = syn::Ident::new("ThisProgram", proc_macro2::Span::call_site()); - - let packed_types = get_global_set(&PACKED_TYPES).lock().unwrap().clone(); - - let renderers = get_global_set(&RENDERERS).lock().unwrap().clone(); - let chains = get_global_set(&CHAINS).lock().unwrap().clone(); - let renderer_exist = get_global_set(&RENDERERS_EXIST).lock().unwrap().clone(); - let chain_exist = get_global_set(&CHAINS_EXIST).lock().unwrap().clone(); - - #[cfg(feature = "structural_renderer")] - let structural_renderers = get_global_set(&STRUCTURAL_RENDERERS) - .lock() - .unwrap() - .clone(); - - #[cfg(feature = "comp")] - let completions = get_global_set(&COMPLETIONS).lock().unwrap().clone(); - - let packed_types: Vec<proc_macro2::TokenStream> = packed_types - .iter() - .map(|s| syn::parse_str::<proc_macro2::TokenStream>(s).unwrap()) - .collect(); - - let renderer_tokens: Vec<proc_macro2::TokenStream> = renderers - .iter() - .map(|s| syn::parse_str::<proc_macro2::TokenStream>(s).unwrap()) - .collect(); - - let chain_tokens: Vec<proc_macro2::TokenStream> = chains - .iter() - .map(|s| syn::parse_str::<proc_macro2::TokenStream>(s).unwrap()) - .collect(); - - let renderer_exist_tokens: Vec<proc_macro2::TokenStream> = renderer_exist - .iter() - .map(|s| syn::parse_str::<proc_macro2::TokenStream>(s).unwrap()) - .collect(); - - let chain_exist_tokens: Vec<proc_macro2::TokenStream> = chain_exist - .iter() - .map(|s| syn::parse_str::<proc_macro2::TokenStream>(s).unwrap()) - .collect(); - - let pathf_map: std::collections::HashMap<String, String> = if cfg!(feature = "pathf") { - load_pathf_map() - } else { - std::collections::HashMap::new() - }; - - let pathf_uses: Vec<proc_macro2::TokenStream> = if cfg!(feature = "pathf") { - pathf_map - .values() - .map(|path| format!("use {};", path).parse().unwrap_or_default()) - .collect() - } else { - Vec::new() - }; - - #[cfg(feature = "structural_renderer")] - let structural_renderer_tokens: Vec<proc_macro2::TokenStream> = structural_renderers - .iter() - .map(|s| syn::parse_str::<proc_macro2::TokenStream>(s).unwrap()) - .collect(); - - #[cfg(feature = "structural_renderer")] - let structural_render = quote! { - fn structural_render( - any: ::mingling::AnyOutput<Self::Enum>, - setting: &::mingling::StructuralRendererSetting, - ) -> Result<::mingling::RenderResult, ::mingling::error::StructuralRendererSerializeError> { - #[allow(unused_imports)] - #(#pathf_uses)* - match any.member_id { - #(#structural_renderer_tokens)* - _ => { - // Non-structural types: render ResultEmpty (which implements - // StructuralData + Serialize) instead of producing nothing. - let mut r = ::mingling::RenderResult::default(); - ::mingling::StructuralRenderer::render(&ResultEmpty, setting, &mut r)?; - Ok(r) - } - } - } - }; - - #[cfg(not(feature = "structural_renderer"))] - let structural_render = quote! {}; - - #[cfg(feature = "dispatch_tree")] - let compile_time_dispatchers: Vec<String> = get_global_set(&COMPILE_TIME_DISPATCHERS) - .lock() - .unwrap() - .clone() - .iter() - .cloned() - .collect(); - - #[cfg(feature = "dispatch_tree")] - let dispatch_tree_nodes = { - let entries: Vec<(String, String, String)> = compile_time_dispatchers - .iter() - .filter_map(|entry| { - let parts: Vec<&str> = entry.split(':').collect(); - if parts.len() == 3 { - Some(( - parts[0].to_string(), - parts[1].to_string(), - parts[2].to_string(), - )) - } else { - None - } - }) - .collect(); - - let get_nodes_fn = dispatch_tree_gen::gen_get_nodes(&entries, &pathf_map); - let dispatch_trie_fn = dispatch_tree_gen::gen_dispatch_args_trie(&entries, &pathf_map); - - quote! { - #get_nodes_fn - #dispatch_trie_fn - } - }; - - #[cfg(not(feature = "dispatch_tree"))] - let dispatch_tree_nodes = quote! {}; - - #[cfg(feature = "comp")] - let completion_tokens: Vec<proc_macro2::TokenStream> = completions - .iter() - .map(|s| syn::parse_str::<proc_macro2::TokenStream>(s).unwrap()) - .collect(); - - #[cfg(feature = "comp")] - let comp = quote! { - fn do_comp(any: &::mingling::AnyOutput<Self::Enum>, ctx: &::mingling::ShellContext) -> ::mingling::Suggest { - #[allow(unused_imports)] - #(#pathf_uses)* - match any.member_id { - #(#completion_tokens)* - _ => ::mingling::Suggest::FileCompletion, - } - } - }; - - #[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<Self::Enum>) -> ::mingling::RenderResult { - ::mingling::RenderResult::default() - } - } - } else { - let render_arms: Vec<_> = renderer_tokens.iter().map(|entry| { - let (struct_ident, variant_ident) = parse_entry_pair(entry); - let downcast_ty = resolve_type(&variant_ident.to_string(), &pathf_map); - let resolved_struct = resolve_type(&struct_ident.to_string(), &pathf_map); - 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::<#downcast_ty>().unwrap_unchecked() }; - <#resolved_struct as ::mingling::Renderer>::render(value) - } - } - }).collect(); - quote! { - fn render(any: ::mingling::AnyOutput<Self::Enum>) -> ::mingling::RenderResult { - match any.member_id { - #(#render_arms)* - _ => ::mingling::RenderResult::default(), - } - } - } - }; - - // 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); - let downcast_ty = resolve_type(&variant_ident.to_string(), &pathf_map); - let resolved_struct = resolve_type(&struct_ident.to_string(), &pathf_map); - 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::<#downcast_ty>().unwrap_unchecked() }; - let fut = async { <#resolved_struct as ::mingling::Chain<Self::Enum>>::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); - let downcast_ty = resolve_type(&variant_ident.to_string(), &pathf_map); - let resolved_struct = resolve_type(&struct_ident.to_string(), &pathf_map); - 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::<#downcast_ty>().unwrap_unchecked() }; - <#resolved_struct as ::mingling::Chain<Self::Enum>>::proc(value) - } - } - }) - .collect(); - - let do_chain_fn = if chain_tokens.is_empty() { - quote! { - fn do_chain(_any: ::mingling::AnyOutput<Self::Enum>) -> ::mingling::ChainProcess<Self::Enum> { - ::core::panic!("No chain found for type id") - } - } - } else if ASYNC_ENABLED { - quote! { - fn do_chain( - any: ::mingling::AnyOutput<Self::Enum>, - ) -> ::std::pin::Pin<::std::boxed::Box<dyn ::std::future::Future<Output = ::mingling::ChainProcess<Self::Enum>> + ::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<Self::Enum>, - ) -> ::mingling::ChainProcess<Self::Enum> { - match any.member_id { - #(#chain_arms_sync)* - _ => ::core::panic!("No chain found for type id: {:?}", any.type_id), - } - } - } - }; - - let help_tokens: Vec<proc_macro2::TokenStream> = get_global_set(&HELP_REQUESTS) - .lock() - .unwrap() - .clone() - .iter() - .map(|s| syn::parse_str::<proc_macro2::TokenStream>(s).unwrap()) - .collect(); - - let num_variants = packed_types.len(); - let repr_type = if u8::try_from(num_variants).is_ok() { - quote! { u8 } - } else if u16::try_from(num_variants).is_ok() { - quote! { u16 } - } else if u32::try_from(num_variants).is_ok() { - quote! { u32 } - } else { - quote! { u128 } - }; - - let expanded = quote! { - #[derive(Debug, PartialEq, Eq, Clone)] - #[repr(#repr_type)] - #[allow(nonstandard_style)] - pub enum #name { - #(#packed_types),* - } - - impl ::std::fmt::Display for #name { - fn fmt(&self, f: &mut ::std::fmt::Formatter<'_>) -> ::std::fmt::Result { - match self { - #(#name::#packed_types => write!(f, stringify!(#packed_types)),)* - } - } - } - - impl ::mingling::ProgramCollect for #name { - type Enum = #name; - type ErrorDispatcherNotFound = ErrorDispatcherNotFound; - type ErrorRendererNotFound = ErrorRendererNotFound; - type ResultEmpty = ResultEmpty; - fn build_renderer_not_found(member_id: Self::Enum) -> ::mingling::AnyOutput<Self::Enum> { - ::mingling::AnyOutput::new(ErrorRendererNotFound::new(member_id.to_string())) - } - fn build_dispatcher_not_found(args: Vec<String>) -> ::mingling::AnyOutput<Self::Enum> { - ::mingling::AnyOutput::new(ErrorDispatcherNotFound::new(args)) - } - fn build_empty_result() -> ::mingling::AnyOutput<Self::Enum> { - ::mingling::AnyOutput::new(ResultEmpty) - } - #render_fn - #do_chain_fn - fn render_help(any: ::mingling::AnyOutput<Self::Enum>) -> ::mingling::RenderResult { - #[allow(unused_imports)] - #(#pathf_uses)* - match any.member_id { - #(#help_tokens)* - _ => ::mingling::RenderResult::default(), - } - } - fn has_renderer(any: &::mingling::AnyOutput<Self::Enum>) -> bool { - match any.member_id { - #(#renderer_exist_tokens)* - _ => false - } - } - fn has_chain(any: &::mingling::AnyOutput<Self::Enum>) -> bool { - match any.member_id { - #(#chain_exist_tokens)* - _ => false - } - } - #dispatch_tree_nodes - #structural_render - #comp - } - - impl #name { - /// Creates a new `Program<#name>` instance with default configuration. - pub fn new() -> ::mingling::Program<#name> { - ::mingling::Program::new() - } - - /// Returns a static reference to the global `Program<#name>` singleton. - pub fn this() -> &'static ::mingling::Program<#name> { - &::mingling::this::<#name>() - } - } - }; - - // Clear all global registries to prevent stale state in Rust Analyzer - get_global_set(&PACKED_TYPES).lock().unwrap().clear(); - get_global_set(&CHAINS).lock().unwrap().clear(); - get_global_set(&CHAINS_EXIST).lock().unwrap().clear(); - get_global_set(&RENDERERS).lock().unwrap().clear(); - get_global_set(&RENDERERS_EXIST).lock().unwrap().clear(); - get_global_set(&HELP_REQUESTS).lock().unwrap().clear(); - #[cfg(feature = "comp")] - get_global_set(&COMPLETIONS).lock().unwrap().clear(); - #[cfg(feature = "dispatch_tree")] - get_global_set(&COMPILE_TIME_DISPATCHERS) - .lock() - .unwrap() - .clear(); - #[cfg(feature = "structural_renderer")] - get_global_set(&STRUCTURAL_RENDERERS) - .lock() - .unwrap() - .clear(); - - TokenStream::from(expanded) +pub fn program_final_gen(input: TokenStream) -> TokenStream { + func::gen_program::program_final_gen_impl(input) } /// Builds a `Suggest` instance with inline suggestion items. diff --git a/mingling_macros/src/systems.rs b/mingling_macros/src/systems.rs new file mode 100644 index 0000000..53e58c5 --- /dev/null +++ b/mingling_macros/src/systems.rs @@ -0,0 +1,5 @@ +#[cfg(feature = "dispatch_tree")] +pub(crate) mod dispatch_tree_gen; +pub(crate) mod res_injection; +#[cfg(feature = "structural_renderer")] +pub(crate) mod structural_data; diff --git a/mingling_macros/src/dispatch_tree_gen.rs b/mingling_macros/src/systems/dispatch_tree_gen.rs index b66e2f4..7383421 100644 --- a/mingling_macros/src/dispatch_tree_gen.rs +++ b/mingling_macros/src/systems/dispatch_tree_gen.rs @@ -4,11 +4,11 @@ use just_fmt::snake_case; use proc_macro2::TokenStream; use quote::quote; -use crate::resolve_type; +use crate::func::gen_program::resolve_type; /// Generate the `get_nodes()` function body for a ProgramCollect impl. /// If `pathf_map` is non-empty, resolves internal dispatcher statics using full paths. -pub fn gen_get_nodes( +pub(crate) fn gen_get_nodes( entries: &[(String, String, String)], pathf_map: &HashMap<String, String>, ) -> TokenStream { @@ -40,7 +40,7 @@ pub fn gen_get_nodes( /// Single-node groups use `starts_with`; multi-node groups recurse with `nth()` match. /// /// If `pathf_map` is non-empty, resolves dispatcher types using full paths. -pub fn gen_dispatch_args_trie( +pub(crate) fn gen_dispatch_args_trie( entries: &[(String, String, String)], pathf_map: &HashMap<String, String>, ) -> TokenStream { diff --git a/mingling_macros/src/res_injection.rs b/mingling_macros/src/systems/res_injection.rs index 606b9a6..606b9a6 100644 --- a/mingling_macros/src/res_injection.rs +++ b/mingling_macros/src/systems/res_injection.rs diff --git a/mingling_macros/src/structural_data.rs b/mingling_macros/src/systems/structural_data.rs index 5350d7e..74bcf09 100644 --- a/mingling_macros/src/structural_data.rs +++ b/mingling_macros/src/systems/structural_data.rs @@ -176,7 +176,7 @@ pub(crate) fn pack_structural(input: TokenStream) -> TokenStream { } } - impl ::mingling::Groupped<#program_path> for #type_name { + impl ::mingling::Grouped<#program_path> for #type_name { fn member_id() -> #program_path { #program_path::#type_name } @@ -297,7 +297,7 @@ pub(crate) fn group_structural(input: TokenStream) -> TokenStream { #type_use #alias_use - impl ::mingling::Groupped<__MinglingProgram> for #type_name { + impl ::mingling::Grouped<__MinglingProgram> for #type_name { fn member_id() -> __MinglingProgram { __MinglingProgram::#type_name } diff --git a/mingling_macros/src/utils.rs b/mingling_macros/src/utils.rs new file mode 100644 index 0000000..49d0e7a --- /dev/null +++ b/mingling_macros/src/utils.rs @@ -0,0 +1 @@ +// Shared utilities for the macro crate. |
