aboutsummaryrefslogtreecommitdiff
path: root/mingling_macros/src
diff options
context:
space:
mode:
Diffstat (limited to 'mingling_macros/src')
-rw-r--r--mingling_macros/src/chain.rs141
-rw-r--r--mingling_macros/src/lib.rs11
2 files changed, 112 insertions, 40 deletions
diff --git a/mingling_macros/src/chain.rs b/mingling_macros/src/chain.rs
index 02bbc6f..5ec5635 100644
--- a/mingling_macros/src/chain.rs
+++ b/mingling_macros/src/chain.rs
@@ -154,29 +154,47 @@ fn parse_chain_attr_args(attr: TokenStream) -> (proc_macro2::TokenStream, bool)
}
/// Validates that the return type of the function is `Next`.
-fn validate_return_type_is_next_process(sig: &Signature) -> Result<(), proc_macro2::TokenStream> {
+/// Checks whether the return type is `()` (unit).
+fn is_unit_return_type(sig: &Signature) -> bool {
+ match &sig.output {
+ ReturnType::Type(_, ty) => match &**ty {
+ Type::Tuple(tuple) => tuple.elems.is_empty(),
+ _ => false,
+ },
+ ReturnType::Default => true,
+ }
+}
+
+fn validate_return_type(sig: &Signature) -> Result<(), proc_macro2::TokenStream> {
+ // If return type is `()`, it's valid (no Next required)
+ 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`")
- .to_compile_error(),
- );
+ 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`")
- .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`)",
+ "Chain function must specify a return type (must be `Next` or `()`)",
)
.to_compile_error());
}
@@ -258,48 +276,73 @@ fn generate_proc_fn(
fn_name: &Ident,
fn_body_stmts: &[syn::Stmt],
is_async_fn: bool,
+ is_unit_return: bool,
) -> 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 wrapped_body = wrap_body_with_mut_resources(fn_body_stmts, &mut_resources, program_type);
- #[cfg(feature = "async")]
- {
+ 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::EmptyResult::new(()).to_chain() },
+ None,
+ ));
+ // Box::leak to get a &'static [syn::Stmt]
+ Box::leak(Box::new(stmts))
+ } else {
+ fn_body_stmts
+ };
+
+ let wrapped_body = wrap_body_with_mut_resources(body_stmts, &mut_resources, program_type);
+
+ // When the function returns `()`, wrap the result with EmptyResult
+ let call_or_wrapped = if is_unit_return {
if has_resources {
quote! {
- async fn proc(#prev_param: #previous_type) -> ::mingling::ChainProcess<#program_type> {
- #(#immut_resource_stmts)*
- #wrapped_body
- }
+ #(#immut_resource_stmts)*
+ #wrapped_body
}
} else {
let call = if is_async_fn {
- quote! { #fn_name(#prev_param).await.into() }
+ quote! { #fn_name(#prev_param).await; }
} else {
- quote! { #fn_name(#prev_param).into() }
+ quote! { #fn_name(#prev_param); }
};
quote! {
- async fn proc(#prev_param: #previous_type) -> ::mingling::ChainProcess<#program_type> {
- #call
- }
+ #call
+ crate::EmptyResult::new(()).to_chain()
+ }
+ }
+ } else if has_resources {
+ quote! {
+ #(#immut_resource_stmts)*
+ #wrapped_body
+ }
+ } else {
+ let call = if is_async_fn {
+ quote! { #fn_name(#prev_param).await.into() }
+ } else {
+ quote! { #fn_name(#prev_param).into() }
+ };
+ quote! {
+ #call
+ }
+ };
+
+ #[cfg(feature = "async")]
+ {
+ quote! {
+ async fn proc(#prev_param: #previous_type) -> ::mingling::ChainProcess<#program_type> {
+ #call_or_wrapped
}
}
}
#[cfg(not(feature = "async"))]
{
- if has_resources {
- quote! {
- fn proc(#prev_param: #previous_type) -> ::mingling::ChainProcess<#program_type> {
- #(#immut_resource_stmts)*
- #wrapped_body
- }
- }
- } else {
- quote! {
- fn proc(#prev_param: #previous_type) -> ::mingling::ChainProcess<#program_type> {
- #fn_name(#prev_param).into()
- }
+ quote! {
+ fn proc(#prev_param: #previous_type) -> ::mingling::ChainProcess<#program_type> {
+ #call_or_wrapped
}
}
}
@@ -316,7 +359,22 @@ fn generate_original_fn(
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::EmptyResult::new(()).to_chain()
+ }
+ }
+ } else {
+ quote! { #fn_body }
+ };
+
#[cfg(feature = "async")]
{
let async_kw = if is_async_fn {
@@ -326,8 +384,8 @@ fn generate_original_fn(
};
quote! {
#(#fn_attrs)*
- #vis #async_kw fn #fn_name(#inputs) -> impl Into<::mingling::ChainProcess<#program_type>> {
- #fn_body
+ #vis #async_kw fn #fn_name(#inputs) -> #return_type {
+ #body
}
}
}
@@ -336,8 +394,8 @@ fn generate_original_fn(
{
quote! {
#(#fn_attrs)*
- #vis fn #fn_name(#inputs) -> impl Into<::mingling::ChainProcess<#program_type>> {
- #fn_body
+ #vis fn #fn_name(#inputs) -> #return_type {
+ #body
}
}
}
@@ -426,8 +484,11 @@ pub fn chain_attr(attr: TokenStream, item: TokenStream) -> TokenStream {
}
}
+ // Check if return type is unit
+ let is_unit_return = is_unit_return_type(&input_fn.sig);
+
// Validate return type
- if let Err(err) = validate_return_type_is_next_process(&input_fn.sig) {
+ if let Err(err) = validate_return_type(&input_fn.sig) {
return err.into();
}
@@ -482,6 +543,7 @@ pub fn chain_attr(attr: TokenStream, item: TokenStream) -> TokenStream {
is_async_fn,
#[cfg(not(feature = "async"))]
false,
+ is_unit_return,
);
// Generate the original function
@@ -496,6 +558,7 @@ pub fn chain_attr(attr: TokenStream, item: TokenStream) -> TokenStream {
#[cfg(not(feature = "async"))]
false,
&program_type,
+ is_unit_return,
);
// Assemble the final output
diff --git a/mingling_macros/src/lib.rs b/mingling_macros/src/lib.rs
index 85b89a3..760a6ef 100644
--- a/mingling_macros/src/lib.rs
+++ b/mingling_macros/src/lib.rs
@@ -1138,13 +1138,15 @@ pub fn register_renderer(input: TokenStream) -> TokenStream {
/// Internal macro used by `gen_program!` to generate fallback types.
///
-/// This macro generates two fallback wrapper types that are essential
+/// This macro generates the fallback wrapper types that are essential
/// for error handling in the Mingling pipeline:
///
/// - **`RendererNotFound`** — Wraps a `String` (the name of the missing renderer).
/// Used when no matching renderer is found for a given output type.
/// - **`DispatcherNotFound`** — Wraps `Vec<String>` (the unrecognized command args).
/// Used when no matching dispatcher is found for user input.
+/// - **`EmptyResult`** — 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.
@@ -1165,6 +1167,7 @@ pub fn register_renderer(input: TokenStream) -> TokenStream {
/// ```rust,ignore
/// pack!(ProgramName, RendererNotFound = String);
/// pack!(ProgramName, DispatcherNotFound = Vec<String>);
+/// pack!(ProgramName, EmptyResult = ());
/// ```
#[proc_macro]
pub fn program_fallback_gen(input: TokenStream) -> TokenStream {
@@ -1173,6 +1176,7 @@ pub fn program_fallback_gen(input: TokenStream) -> TokenStream {
let expanded = quote! {
::mingling::macros::pack!(#name, RendererNotFound = String);
::mingling::macros::pack!(#name, DispatcherNotFound = Vec<String>);
+ ::mingling::macros::pack!(#name, EmptyResult = ());
};
TokenStream::from(expanded)
}
@@ -1213,6 +1217,7 @@ pub fn program_fallback_gen(input: TokenStream) -> TokenStream {
///
/// impl ProgramCollect for MyProgram {
/// type Enum = MyProgram;
+/// type EmptyResult = EmptyResult;
/// fn render(any, r) { /* dispatches to all registered renderers */ }
/// fn do_chain(any) -> ChainProcess { /* dispatches to all registered chain steps */ }
/// fn render_help(any, r) { /* dispatches to all registered help handlers */ }
@@ -1386,12 +1391,16 @@ pub fn program_final_gen(input: TokenStream) -> TokenStream {
type Enum = #name;
type DispatcherNotFound = DispatcherNotFound;
type RendererNotFound = RendererNotFound;
+ type EmptyResult = EmptyResult;
fn build_renderer_not_found(member_id: Self::Enum) -> ::mingling::AnyOutput<Self::Enum> {
::mingling::AnyOutput::new(RendererNotFound::new(member_id.to_string()))
}
fn build_dispatcher_not_found(args: Vec<String>) -> ::mingling::AnyOutput<Self::Enum> {
::mingling::AnyOutput::new(DispatcherNotFound::new(args))
}
+ fn build_empty_result() -> ::mingling::AnyOutput<Self::Enum> {
+ ::mingling::AnyOutput::new(EmptyResult::new(()))
+ }
::mingling::__dispatch_program_renderers!(
#(#renderer_tokens)*
);