aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
author魏曹先生 <1992414357@qq.com>2026-07-19 05:26:21 +0800
committer魏曹先生 <1992414357@qq.com>2026-07-19 05:26:21 +0800
commit1c220b52903f05df1246802c24250300038cadb7 (patch)
tree125bb8cdd2dd2ff665890b835eca88930c343752
parent961efae6bbd1e3d574a382c7c7f1a2c2267a7186 (diff)
feat(macros:chain): accept any return type in chain functions
-rw-r--r--CHANGELOG.md27
-rw-r--r--mingling_macros/src/chain.rs54
2 files changed, 48 insertions, 33 deletions
diff --git a/CHANGELOG.md b/CHANGELOG.md
index ea4b737..6c70d3d 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -184,6 +184,33 @@ None
These methods complement the existing read-only `get_args(&self)` method, providing full control over argument mutation and ownership.
+9. **[`macros:chain`]** Relaxed the `#[chain]` return type validation. Previously, `#[chain]` functions were restricted to returning `Next`, `ChainProcess<ThisProgram>`, `()`, or omitting the return type. Now, any return type is accepted, and the generated `proc` function performs an explicit `Into<ChainProcess<ThisProgram>>` conversion using a fully-qualified turbofish based on the user-declared return type.
+
+ This means `#[chain]` functions can now return any pack type directly, without needing an explicit `.into()` call in the function body:
+
+ ```rust
+ // Before — required explicit .into()
+ #[chain]
+ fn handle_greet(args: EntryGreet) -> Next {
+ let name = /* ... */;
+ ResultGreeting::new(name).into()
+ }
+
+ // After — return any pack type directly
+ #[chain]
+ fn handle_greet(args: EntryGreet) -> ResultGreeting {
+ let name = /* ... */;
+ ResultGreeting::new(name)
+ }
+ ```
+
+ The generated `proc` function now wraps the body result in `<UserReturnType as Into<ChainProcess<ThisProgram>>>::into(...)`, which:
+ - Works for `Next` / `ChainProcess` via the identity `From<T> for T` implementation.
+ - Works for any pack type (`ResultGreeting`, etc.) via the `.into()` conversion generated by `pack!` / `#[derive(Groupped)]`.
+ - Works for `()` via the `From<()>` implementation on `ChainProcess`.
+
+ The return type validation has been removed entirely — any valid Rust return type is accepted. If the type does not implement `Into<ChainProcess<ThisProgram>>`, a standard Rust compilation error will be produced at the call site.
+
#### **BREAKING CHANGES** (API CHANGES):
1. **[`macros:renderer`]** **[`macros:help`]** Removed `r_println!` and `r_print!` macros. The `#[renderer]` and `#[help]` macros no longer implicitly inject an internal `RenderResult` variable or provide `r_println!` / `r_print!` macros.
diff --git a/mingling_macros/src/chain.rs b/mingling_macros/src/chain.rs
index 5044227..566e782 100644
--- a/mingling_macros/src/chain.rs
+++ b/mingling_macros/src/chain.rs
@@ -20,41 +20,16 @@ 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.
@@ -71,6 +46,7 @@ fn generate_proc_fn(
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();
@@ -106,11 +82,16 @@ 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).
+ // Convert the body result to `ChainProcess` using the user-declared
+ // return type as the source type for a fully-qualified `Into` call.
+ // This works for both:
+ // - `-> Next` / `-> ChainProcess`: identity `From<T> for T`
+ // - `-> PackType`: `Into<ChainProcess>` from pack!/derive
quote! {
- let __chain_result: ::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)
}
};
@@ -234,6 +215,12 @@ 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(
has_resources,
@@ -247,6 +234,7 @@ pub fn chain_attr(attr: TokenStream, item: TokenStream) -> TokenStream {
#[cfg(not(feature = "async"))]
false,
is_unit_return,
+ &origin_return_type,
);
// Preserve the original function untouched, with dead_code allowed