diff options
Diffstat (limited to 'mingling_macros/src')
| -rw-r--r-- | mingling_macros/src/chain.rs | 216 | ||||
| -rw-r--r-- | mingling_macros/src/dispatcher.rs | 2 | ||||
| -rw-r--r-- | mingling_macros/src/group_impl.rs | 4 | ||||
| -rw-r--r-- | mingling_macros/src/grouped.rs (renamed from mingling_macros/src/groupped.rs) | 12 | ||||
| -rw-r--r-- | mingling_macros/src/help.rs | 45 | ||||
| -rw-r--r-- | mingling_macros/src/lib.rs | 120 | ||||
| -rw-r--r-- | mingling_macros/src/pack.rs | 2 | ||||
| -rw-r--r-- | mingling_macros/src/pack_err.rs | 8 | ||||
| -rw-r--r-- | mingling_macros/src/renderer.rs | 47 | ||||
| -rw-r--r-- | mingling_macros/src/res_injection.rs | 25 | ||||
| -rw-r--r-- | mingling_macros/src/structural_data.rs | 4 |
11 files changed, 190 insertions, 295 deletions
diff --git a/mingling_macros/src/chain.rs b/mingling_macros/src/chain.rs index 5f72422..ef31854 100644 --- a/mingling_macros/src/chain.rs +++ b/mingling_macros/src/chain.rs @@ -9,8 +9,7 @@ use quote::{ToTokens, quote}; use syn::spanned::Spanned; use syn::{Ident, ItemFn, Pat, ReturnType, Signature, Type, TypePath, parse_macro_input}; -/// Validates that the return type of the function is `Next`. -/// Checks whether the return type is `()` (unit). +/// Checks whether the return type is `()` fn is_unit_return_type(sig: &Signature) -> bool { match &sig.output { ReturnType::Type(_, ty) => match &**ty { @@ -21,48 +20,22 @@ fn is_unit_return_type(sig: &Signature) -> bool { } } +/// 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> { - // If return type is `()`, it's valid (no Next required) + // `()` 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(); - if last_segment.ident != "Next" { - return Err(syn::Error::new( - ty.span(), - "Chain function must return `Next` or `()`", - ) - .to_compile_error()); - } - } - _ => { - return Err(syn::Error::new( - ty.span(), - "Chain function must return `Next` or `()`", - ) - .to_compile_error()); - } - }, - ReturnType::Default => { - return Err(syn::Error::new( - sig.span(), - "Chain function must specify a return type (must be `Next` or `()`)", - ) - .to_compile_error()); - } - } Ok(()) } -/// Builds the `proc` function implementation that serves as the actual chain -/// entry point inside the generated `Chain` impl. +/// Builds the `proc` function implementation inside the generated `Chain` impl. /// -/// * Without resources: delegates directly to the original function. -/// * With resources: inlines the body and prepends resource bindings. +/// The user's function body is inlined directly, and its result is converted +/// via `.into()` to `ChainProcess<ProgramType>`. #[allow(unused_variables)] fn generate_proc_fn( has_resources: bool, @@ -70,67 +43,55 @@ fn generate_proc_fn( program_type: &proc_macro2::TokenStream, previous_type: &TypePath, prev_param: &Pat, - fn_name: &Ident, 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(); - let body_stmts: &[syn::Stmt] = if is_unit_return && has_resources { - let mut stmts = fn_body_stmts.to_vec(); - stmts.push(syn::Stmt::Expr( - syn::parse_quote! { - <crate::ResultEmpty as ::mingling::Groupped::<crate::ThisProgram>> - ::to_chain(crate::ResultEmpty) - }, - None, - )); - // Box::leak to get a &'static [syn::Stmt] - Box::leak(Box::new(stmts)) - } else { - fn_body_stmts - }; - let wrapped_body = if is_async_fn && !mut_resources.is_empty() { - wrap_body_with_mut_resources_async(body_stmts, &mut_resources, program_type) + wrap_body_with_mut_resources_async(fn_body_stmts, &mut_resources, program_type) } else { - wrap_body_with_mut_resources(body_stmts, &mut_resources, program_type) + wrap_body_with_mut_resources(fn_body_stmts, &mut_resources, program_type, is_unit_return) }; - // When the function returns `()`, wrap the result with ResultEmpty - let call_or_wrapped = if is_unit_return { - if has_resources { + let proc_body = if is_unit_return { + let body_with_ending = if has_resources { quote! { #(#immut_resource_stmts)* - #wrapped_body + #wrapped_body; + <crate::ResultEmpty as ::mingling::Grouped::<crate::ThisProgram>> + ::to_chain(crate::ResultEmpty) } } else { - let call = if is_async_fn { - quote! { #fn_name(#prev_param).await; } - } else { - quote! { #fn_name(#prev_param); } - }; quote! { - #call - <crate::ResultEmpty as ::mingling::Groupped::<crate::ThisProgram>> + #wrapped_body; + <crate::ResultEmpty as ::mingling::Grouped::<crate::ThisProgram>> ::to_chain(crate::ResultEmpty) } - } - } else if has_resources { - quote! { - #(#immut_resource_stmts)* - #wrapped_body - } + }; + quote! { #body_with_ending } } else { - let call = if is_async_fn { - quote! { #fn_name(#prev_param).await.into() } + let body = if has_resources { + quote! { + #(#immut_resource_stmts)* + #wrapped_body + } } else { - quote! { #fn_name(#prev_param).into() } + quote! { #wrapped_body } }; + // Convert the body result to `ChainProcess` using the user-declared + // return type as the source type for a fully-qualified `Into` call. + // This works for both: + // - `-> Next` / `-> ChainProcess`: identity `From<T> for T` + // - `-> PackType`: `Into<ChainProcess>` from pack!/derive quote! { - #call + let __chain_result = { #body }; + <#origin_return_type as ::std::convert::Into< + ::mingling::ChainProcess<#program_type> + >>::into(__chain_result) } }; @@ -138,7 +99,7 @@ fn generate_proc_fn( { quote! { async fn proc(#prev_param: #previous_type) -> ::mingling::ChainProcess<#program_type> { - #call_or_wrapped + #proc_body } } } @@ -147,73 +108,14 @@ fn generate_proc_fn( { quote! { fn proc(#prev_param: #previous_type) -> ::mingling::ChainProcess<#program_type> { - #call_or_wrapped - } - } - } -} - -/// Generates the original function signature (kept for backwards compatibility / -/// internal use), with its return type changed to `impl Into<ChainProcess<..>>`. -#[allow(unused_variables)] -fn generate_original_fn( - fn_attrs: &[syn::Attribute], - vis: &syn::Visibility, - fn_name: &Ident, - inputs: &syn::punctuated::Punctuated<syn::FnArg, syn::Token![,]>, - fn_body: &syn::Block, - is_async_fn: bool, - program_type: &proc_macro2::TokenStream, - is_unit_return: bool, -) -> proc_macro2::TokenStream { - // Both unit and Next return types need to produce `impl Into<ChainProcess<ProgramType>>` - let return_type = quote! { impl Into<::mingling::ChainProcess<#program_type>> }; - - let body = if is_unit_return { - quote! { - { - #fn_body - <crate::ResultEmpty as ::mingling::Groupped::<crate::ThisProgram>>::to_chain(crate::ResultEmpty) - } - } - } else { - quote! { - { - let _: crate::Next; - let _: Next; - #fn_body - } - } - }; - - #[cfg(feature = "async")] - { - let async_kw = if is_async_fn { - quote! { async } - } else { - quote! {} - }; - quote! { - #(#fn_attrs)* - #vis #async_kw fn #fn_name(#inputs) -> #return_type { - #body - } - } - } - - #[cfg(not(feature = "async"))] - { - quote! { - #(#fn_attrs)* - #vis fn #fn_name(#inputs) -> #return_type { - #body + #proc_body } } } } /// Assembles the final expanded output: hidden struct, `register_chain!` invocation, -/// `Chain` impl with the `proc` method, and the original function. +/// `Chain` impl with the `proc` method, and the preserved original function. fn generate_struct_and_impl( fn_attrs: &[syn::Attribute], vis: &syn::Visibility, @@ -222,7 +124,7 @@ fn generate_struct_and_impl( previous_type_str: &proc_macro2::TokenStream, program_type: &proc_macro2::TokenStream, proc_fn: &proc_macro2::TokenStream, - origin_proc_fn: &proc_macro2::TokenStream, + original_fn: &proc_macro2::TokenStream, ) -> proc_macro2::TokenStream { quote! { #(#fn_attrs)* @@ -238,8 +140,8 @@ fn generate_struct_and_impl( #proc_fn } - // Keep the original function for internal use - #origin_proc_fn + // Keep the original function unchanged + #original_fn } } @@ -296,8 +198,6 @@ pub fn chain_attr(attr: TokenStream, item: TokenStream) -> TokenStream { }; // Prepare building blocks - let sig = &input_fn.sig; - let inputs = &sig.inputs; let fn_body = &input_fn.block; let mut fn_attrs = input_fn.attrs.clone(); fn_attrs.retain(|attr| !attr.path().is_ident("chain")); @@ -315,36 +215,36 @@ pub fn chain_attr(attr: TokenStream, item: TokenStream) -> TokenStream { // Always use the default crate-defined program path let program_type = crate::default_program_path(); - // Generate the `proc` function + // 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( has_resources, &resources, &program_type, &previous_type, &prev_param, - fn_name, &fn_body.stmts, #[cfg(feature = "async")] is_async_fn, #[cfg(not(feature = "async"))] false, is_unit_return, + &origin_return_type, ); - // Generate the original function - let origin_proc_fn = generate_original_fn( - &fn_attrs, - vis, - fn_name, - inputs, - fn_body, - #[cfg(feature = "async")] - is_async_fn, - #[cfg(not(feature = "async"))] - false, - &program_type, - is_unit_return, - ); + // Preserve the original function untouched, with dead_code allowed + // since it may only be called through the Chain trait dispatch. + // 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 + }; // Assemble the final output let previous_type_str = quote! { #previous_type }; @@ -356,7 +256,7 @@ pub fn chain_attr(attr: TokenStream, item: TokenStream) -> TokenStream { &previous_type_str, &program_type, &proc_fn, - &origin_proc_fn, + &original_fn, ); expanded.into() diff --git a/mingling_macros/src/dispatcher.rs b/mingling_macros/src/dispatcher.rs index 2a7c850..3698ede 100644 --- a/mingling_macros/src/dispatcher.rs +++ b/mingling_macros/src/dispatcher.rs @@ -134,7 +134,7 @@ 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; + use ::mingling::Grouped; #pack::new(args).to_chain() } fn clone_dispatcher(&self) -> Box<dyn ::mingling::Dispatcher<#program_type>> { diff --git a/mingling_macros/src/group_impl.rs b/mingling_macros/src/group_impl.rs index 59da9dd..cac2734 100644 --- a/mingling_macros/src/group_impl.rs +++ b/mingling_macros/src/group_impl.rs @@ -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/groupped.rs b/mingling_macros/src/grouped.rs index 8aee003..9014c37 100644 --- a/mingling_macros/src/groupped.rs +++ b/mingling_macros/src/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 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/help.rs b/mingling_macros/src/help.rs index 1903d07..6e0d12e 100644 --- a/mingling_macros/src/help.rs +++ b/mingling_macros/src/help.rs @@ -1,34 +1,16 @@ use proc_macro::TokenStream; use quote::{ToTokens, quote}; use syn::spanned::Spanned; -use syn::{Ident, ItemFn, ReturnType, Type, TypePath, parse_macro_input}; +use syn::{Ident, ItemFn, ReturnType, Signature, TypePath, parse_macro_input}; use crate::get_global_set; use crate::res_injection::{extract_args_info, generate_immut_resource_bindings}; -/// Validates that the function returns `::mingling::RenderResult`. -fn validate_render_result_return(sig: &syn::Signature) -> syn::Result<()> { +/// Extracts the user's return type, returning `None` for no return type. +fn extract_user_return_type(sig: &Signature) -> Option<proc_macro2::TokenStream> { match &sig.output { - ReturnType::Type(_, ty) => match &**ty { - Type::Path(type_path) => { - let last_seg = type_path.path.segments.last().map(|s| s.ident.to_string()); - match last_seg.as_deref() { - Some("RenderResult") => Ok(()), - _ => Err(syn::Error::new( - ty.span(), - "Help function must return `RenderResult`", - )), - } - } - _ => Err(syn::Error::new( - ty.span(), - "Help function must return `RenderResult`", - )), - }, - ReturnType::Default => Err(syn::Error::new( - sig.span(), - "Help function must have a return type `-> RenderResult`", - )), + ReturnType::Type(_, ty) => Some(quote! { #ty }), + ReturnType::Default => None, } } @@ -49,10 +31,8 @@ pub fn help_attr(item: TokenStream) -> TokenStream { Err(e) => return e.to_compile_error().into(), }; - // Validate return type is RenderResult - if let Err(e) = validate_render_result_return(&input_fn.sig) { - return e.to_compile_error().into(); - } + // Determine the user's return type for preserving the original function + let user_return_type = extract_user_return_type(&input_fn.sig); // Get the function body let fn_body = &input_fn.block; @@ -69,8 +49,8 @@ pub fn help_attr(item: TokenStream) -> TokenStream { let fn_name = &input_fn.sig.ident; // Get original inputs to keep the original function - let original_inputs = input_fn.sig.inputs.clone(); + let original_return_type = user_return_type.clone().unwrap_or(quote! { () }); // Generate internal name using snake_case let internal_name = format!( @@ -149,17 +129,18 @@ pub fn help_attr(item: TokenStream) -> TokenStream { type Entry = #entry_type; fn render_help(#prev_param: Self::Entry) -> ::mingling::RenderResult { - #help_render_body + let __help_result = { #help_render_body }; + ::std::convert::Into::into(__help_result) } } ::mingling::macros::register_help!(#entry_type, #struct_name); // Keep the original function unchanged - + #[allow(dead_code)] #(#fn_attrs)* - #vis fn #fn_name(#original_inputs) -> ::mingling::RenderResult { - #fn_body + #vis fn #fn_name(#original_inputs) -> #original_return_type { + #(#fn_body_stmts)* } }; diff --git a/mingling_macros/src/lib.rs b/mingling_macros/src/lib.rs index d0f603a..ea33577 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) | //! @@ -161,7 +161,7 @@ mod entry; mod enum_tag; #[cfg(feature = "extra_macros")] mod group_impl; -mod groupped; +mod grouped; mod help; mod node; mod pack; @@ -265,7 +265,7 @@ fn entry_has_variant(entry: &str, variant_name: &str) -> bool { /// Registers an outside-type as a member of a program group without modifying its 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 +/// within the Mingling framework by generating a `Grouped` implementation and registering /// the type's simple name as an enum variant. /// /// # Syntax @@ -281,11 +281,11 @@ fn entry_has_variant(entry: &str, variant_name: &str) -> bool { /// /// 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 +/// - An `impl Grouped<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!`. +/// program enum, just like `#[derive(Grouped)]` or `pack!`. /// /// # Example /// @@ -297,7 +297,7 @@ fn entry_has_variant(entry: &str, variant_name: &str) -> bool { /// ``` /// /// After expansion, the type can be used in chains and renderers like any -/// `#[derive(Groupped)]` type. +/// `#[derive(Grouped)]` type. /// /// This macro is only available with the `extra_macros` feature. #[cfg(feature = "extra_macros")] @@ -402,7 +402,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 +438,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 +461,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 +478,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 +521,25 @@ 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. /// /// # Example /// @@ -542,7 +547,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 +560,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 +618,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::Grouped::<crate::ThisProgram>>::to_chain(crate::ResultEmpty) }; TokenStream::from(expanded) } @@ -867,7 +872,8 @@ pub fn dispatcher(input: TokenStream) -> TokenStream { /// /// - The function must have at least **one** parameter (the previous type in the chain). /// - The first parameter must be taken **by move**. -/// - The function must return `Next` (the type alias generated by `gen_program!`, which equals `ChainProcess<ProgramName>`). +/// - The function may return `Next`, `ChainProcess<ProgramName>`, `()`, or omit the return type. +/// - The original function signature is preserved unchanged. /// - 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 { @@ -1234,7 +1240,7 @@ pub fn register_dispatcher(input: TokenStream) -> TokenStream { /// /// - The function must have exactly one parameter (the entry type to provide help for). /// - The parameter type must be a single-segment type path (e.g., `MyEntry`, not `other::MyEntry`). -/// - The function must return `RenderResult`. +/// - The function may return `RenderResult`, `()`, or any type that implements `Into<RenderResult>`. /// - The function cannot be async. /// /// # See also @@ -1249,10 +1255,10 @@ pub fn help(_attr: TokenStream, item: TokenStream) -> TokenStream { help::help_attr(item) } -/// Derive macro for automatically implementing the `Groupped` trait on a struct. +/// Derive macro for automatically implementing the `Grouped` trait on a struct. /// -/// The `#[derive(Groupped)]` macro: -/// 1. Implements `Groupped<crate::ThisProgram>`. +/// 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. @@ -1260,7 +1266,7 @@ pub fn help(_attr: TokenStream, item: TokenStream) -> TokenStream { /// # Syntax /// /// ```rust,ignore -/// #[derive(Groupped)] +/// #[derive(Grouped)] /// struct MyStruct { /// // ... /// } @@ -1269,9 +1275,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, /// } @@ -1279,9 +1285,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 @@ -1350,18 +1356,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, @@ -1371,19 +1377,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. @@ -1445,8 +1451,30 @@ pub fn gen_program(_input: TokenStream) -> TokenStream { 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!(); @@ -1473,7 +1501,7 @@ pub fn program_comp_gen(_input: TokenStream) -> TokenStream { #[doc(hidden)] #[::mingling::macros::chain] pub async fn __exec_completion(prev: CompletionContext) -> Next { - use ::mingling::Groupped; + use ::mingling::Grouped; let read_ctx = ::mingling::ShellContext::try_from(prev.inner); match read_ctx { @@ -1491,7 +1519,7 @@ pub fn program_comp_gen(_input: TokenStream) -> TokenStream { #[doc(hidden)] #[::mingling::macros::chain] pub fn __exec_completion(prev: CompletionContext) -> Next { - use ::mingling::Groupped; + use ::mingling::Grouped; let read_ctx = ::mingling::ShellContext::try_from(prev.inner); match read_ctx { @@ -1515,7 +1543,7 @@ pub fn program_comp_gen(_input: TokenStream) -> TokenStream { let comp_dispatcher = quote! { #[doc(hidden)] mod __internal_completion_mod { - use ::mingling::Groupped; + use ::mingling::Grouped; ::mingling::macros::dispatcher!("__comp", CMDCompletion => CompletionContext); ::mingling::macros::pack!( CompletionSuggest = (::mingling::ShellContext, ::mingling::Suggest) @@ -1547,7 +1575,7 @@ pub fn program_comp_gen(_input: TokenStream) -> TokenStream { /// 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. @@ -1652,13 +1680,13 @@ pub fn register_renderer(input: TokenStream) -> TokenStream { pub fn program_fallback_gen(_input: TokenStream) -> TokenStream { #[cfg(feature = "structural_renderer")] let pack_empty = quote! { - #[derive(::serde::Serialize, ::mingling::StructuralData, ::mingling::Groupped, Default)] + #[derive(::serde::Serialize, ::mingling::StructuralData, ::mingling::Grouped, Default)] pub struct ResultEmpty; }; #[cfg(not(feature = "structural_renderer"))] let pack_empty = quote! { - #[derive(::mingling::Groupped, Default)] + #[derive(::mingling::Grouped, Default)] pub struct ResultEmpty; }; @@ -1674,7 +1702,7 @@ pub fn program_fallback_gen(_input: TokenStream) -> TokenStream { /// 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 diff --git a/mingling_macros/src/pack.rs b/mingling_macros/src/pack.rs index 7f05232..0af1b4c 100644 --- a/mingling_macros/src/pack.rs +++ b/mingling_macros/src/pack.rs @@ -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/pack_err.rs index ba7cf17..a747aa9 100644 --- a/mingling_macros/src/pack_err.rs +++ b/mingling_macros/src/pack_err.rs @@ -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! { @@ -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/renderer.rs b/mingling_macros/src/renderer.rs index f47511c..d124ec9 100644 --- a/mingling_macros/src/renderer.rs +++ b/mingling_macros/src/renderer.rs @@ -1,38 +1,16 @@ use proc_macro::TokenStream; use quote::{ToTokens, quote}; use syn::spanned::Spanned; -use syn::{ItemFn, ReturnType, Signature, Type, TypePath, parse_macro_input}; +use syn::{ItemFn, ReturnType, Signature, TypePath, parse_macro_input}; use crate::get_global_set; use crate::res_injection::{extract_args_info, generate_immut_resource_bindings}; -/// Validates that the function returns `::mingling::RenderResult`. -fn validate_render_result_return(sig: &Signature) -> syn::Result<()> { +/// Extracts the user's return type, returning `None` for no return type. +fn extract_user_return_type(sig: &Signature) -> Option<proc_macro2::TokenStream> { match &sig.output { - ReturnType::Type(_, ty) => { - // Check if the return type is RenderResult - match &**ty { - Type::Path(type_path) => { - let segments = &type_path.path.segments; - let last_seg = segments.last().map(|s| s.ident.to_string()); - match last_seg.as_deref() { - Some("RenderResult") => Ok(()), - _ => Err(syn::Error::new( - ty.span(), - "Renderer function must return `RenderResult`", - )), - } - } - _ => Err(syn::Error::new( - ty.span(), - "Renderer function must return `RenderResult`", - )), - } - } - ReturnType::Default => Err(syn::Error::new( - sig.span(), - "Renderer function must have a return type `-> RenderResult`", - )), + ReturnType::Type(_, ty) => Some(quote! { #ty }), + ReturnType::Default => None, } } @@ -59,10 +37,8 @@ pub fn renderer_attr(attr: TokenStream, item: TokenStream) -> TokenStream { Err(e) => return e.to_compile_error().into(), }; - // Validate that the function returns RenderResult - if let Err(e) = validate_render_result_return(&input_fn.sig) { - return e.to_compile_error().into(); - } + // Determine the user's return type and whether it needs to be converted to RenderResult + let user_return_type = extract_user_return_type(&input_fn.sig); // Get function body statements let fn_body_stmts: Vec<syn::Stmt> = input_fn.block.stmts.clone(); @@ -123,10 +99,7 @@ pub fn renderer_attr(attr: TokenStream, item: TokenStream) -> TokenStream { // The original function preserves the user's exact signature and body. // Resource parameters are passed directly by the caller, NOT injected from context. let original_inputs = input_fn.sig.inputs.clone(); - let original_return_type = match &input_fn.sig.output { - ReturnType::Type(_, ty) => quote! { #ty }, - ReturnType::Default => unreachable!("Already validated that return type is RenderResult"), - }; + let original_return_type = user_return_type.clone().unwrap_or(quote! { () }); let expanded = quote! { #(#fn_attrs)* @@ -140,11 +113,13 @@ pub fn renderer_attr(attr: TokenStream, item: TokenStream) -> TokenStream { type Previous = #previous_type; fn render(#prev_param: Self::Previous) -> ::mingling::RenderResult { - #render_fn_body + 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)* diff --git a/mingling_macros/src/res_injection.rs b/mingling_macros/src/res_injection.rs index 09da889..606b9a6 100644 --- a/mingling_macros/src/res_injection.rs +++ b/mingling_macros/src/res_injection.rs @@ -163,8 +163,10 @@ fn mut_res_binding_name(var_name: &Ident) -> Ident { syn::Ident::new(&format!("__{}_binding", var_name), var_name.span()) } -/// Wraps the function body in nested `__modify_res_and_return_route` closures for -/// each mutable resource parameter (sync version). +/// Wraps the function body in mutable resource closures (sync version). +/// +/// For unit return types: uses `modify_res` (closure returns `()`). +/// For non-unit return types: uses `__modify_res_and_return_route` (closure returns `ChainProcess<C>`). /// /// The innermost closure gets the original body, and each mutable parameter wraps /// outward from last to first. @@ -172,6 +174,7 @@ pub(crate) fn wrap_body_with_mut_resources( fn_body_stmts: &[syn::Stmt], mut_resources: &[&ResourceInjection], program_type: &proc_macro2::TokenStream, + is_unit_return: bool, ) -> proc_macro2::TokenStream { let mut wrapped = quote! { #(#fn_body_stmts)* @@ -180,11 +183,19 @@ pub(crate) fn wrap_body_with_mut_resources( for res in mut_resources { let var_name = &res.var_name; let inner_type = &res.inner_type; - wrapped = quote! { - ::mingling::this::<#program_type>().__modify_res_and_return_route(|#var_name: &mut #inner_type| { - #wrapped - }).into() - }; + if is_unit_return { + wrapped = quote! { + ::mingling::this::<#program_type>().modify_res(|#var_name: &mut #inner_type| { + #wrapped + }) + }; + } else { + wrapped = quote! { + ::mingling::this::<#program_type>().__modify_res_and_return_route(|#var_name: &mut #inner_type| { + #wrapped + }) + }; + } } wrapped diff --git a/mingling_macros/src/structural_data.rs b/mingling_macros/src/structural_data.rs index 5350d7e..74bcf09 100644 --- a/mingling_macros/src/structural_data.rs +++ b/mingling_macros/src/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 } |
