aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
author魏曹先生 <1992414357@qq.com>2026-06-22 20:21:41 +0800
committer魏曹先生 <1992414357@qq.com>2026-06-22 20:21:41 +0800
commitd7c9ad94113cca2f782666e37a0aa4fb7b8d7d86 (patch)
tree323dfc74a274463500ac52c7bb7b83029b771411
parent232f31c6649e6348a5b0b64362f185f7f4db1dc0 (diff)
Support qualified type paths in four macros
-rw-r--r--CHANGELOG.md31
-rw-r--r--dev_tools/src/bin/ci.rs9
-rw-r--r--mingling_macros/src/chain.rs6
-rw-r--r--mingling_macros/src/completion.rs29
-rw-r--r--mingling_macros/src/help.rs5
-rw-r--r--mingling_macros/src/lib.rs29
-rw-r--r--mingling_macros/src/renderer.rs14
-rw-r--r--mingling_macros/src/res_injection.rs16
8 files changed, 41 insertions, 98 deletions
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 314c56a..2a0f5b5 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -59,6 +59,13 @@
4. **\[macros:dispatcher_clap\]** Added `dispatch_tree` feature integration for `#[dispatcher_clap]`. When the `dispatch_tree` feature is enabled, `#[dispatcher_clap]` will now automatically register the dispatcher and entry in the dispatch tree via `register_dispatcher!`, matching the behavior already present in the `dispatcher!` macro. When the feature is disabled, no additional code is generated.
+5. **\[macros\]** The four macros `#[chain]`, `#[renderer]`, `#[help]`, and `#[completion]` now support using fully qualified type paths with `::` (e.g. `crate::EntryFine`) as type inputs. Previously, these macros required types to be bare single-segment idents (e.g. `EntryFine`), rejecting reasonable paths like `crate::EntryFine`. Specific changes:
+
+ - `res_injection::extract_args_info` (shared by `#[chain]` and `#[renderer]`): Removed the single-segment validation for the first parameter type
+ - `#[renderer]` / `#[help]`: Removed respective `check_single_segment_type` calls
+ - `#[completion]`: Attribute parameter parsing changed from `Ident` to `TypePath`, supporting `#[completion(crate::EntryFine)]`
+ - Fixed code generation in `build_chain_arm`, `build_chain_exist_arm`, `build_renderer_entry`, `build_renderer_exist_entry`, `build_general_renderer_entry`, and completion entry: `Self::#variant` match arms now only take the last segment ident of the type path (e.g. `Self::EntryFine`), rather than concatenating the full path directly (which would generate invalid syntax like `Self::crate::EntryFine`), while `downcast::<T>()` and `type Previous = T` still use the full path to ensure correct type resolution
+
#### Optimizations:
1. **\[core:flag\]** Refactored the `special_argument!` and `special_arguments!` macros to replace index‑based `while` loops with iterator `position` and `drain`, improving both performance and readability.
@@ -198,17 +205,17 @@ This macro is only available with the `extra_macros` feature.
The following explicit syntaxes are **removed**:
-| Macro | Removed syntax |
-|---|---|
-| `pack!` | `pack!(MyProgram, Type = Inner)` → only `pack!(Type = Inner)` |
-| `group!` | `group!(MyProgram, Type)` → only `group!(Type)` |
-| `#[derive(Groupped)]` | `#[group(MyProgram)]` attribute |
-| `#[chain]` | `#[chain(MyProgram)]` argument |
-| `#[renderer]` | `#[renderer(MyProgram)]` argument |
-| `dispatcher!` | `dispatcher!(MyProgram, "cmd", CMD => Entry)` |
-| `#[dispatcher_clap]` | `#[dispatcher_clap(MyProgram, "cmd", Disp)]` |
-| `#[program_setup]` | `#[program_setup(MyProgram)]` argument |
-| `gen_program!` | `gen_program!(MyProgram)` → only `gen_program!()` |
+| Macro | Removed syntax |
+| --------------------- | ------------------------------------------------------------- |
+| `pack!` | `pack!(MyProgram, Type = Inner)` → only `pack!(Type = Inner)` |
+| `group!` | `group!(MyProgram, Type)` → only `group!(Type)` |
+| `#[derive(Groupped)]` | `#[group(MyProgram)]` attribute |
+| `#[chain]` | `#[chain(MyProgram)]` argument |
+| `#[renderer]` | `#[renderer(MyProgram)]` argument |
+| `dispatcher!` | `dispatcher!(MyProgram, "cmd", CMD => Entry)` |
+| `#[dispatcher_clap]` | `#[dispatcher_clap(MyProgram, "cmd", Disp)]` |
+| `#[program_setup]` | `#[program_setup(MyProgram)]` argument |
+| `gen_program!` | `gen_program!(MyProgram)` → only `gen_program!()` |
> **Tradeoff Rationale** — Removing explicit program names is a sacrifice of flexibility in exchange for reduced development and maintenance complexity. In practice, no use case has emerged that genuinely requires a custom program name — all real-world programs can be expressed with the default `ThisProgram`. Keeping the parameter in every macro would add ongoing documentation, testing, and cognitive overhead that is not justified by current needs.
@@ -243,7 +250,7 @@ use mingling::{res::ResExitCode, res::ResREPL};
AnyOutput::new(self).route_renderer()
}
}
-
+
// After (provided by Groupped trait default methods):
// just ensure Groupped is implemented — to_chain() and to_render()
// are automatically available
diff --git a/dev_tools/src/bin/ci.rs b/dev_tools/src/bin/ci.rs
index c0afd2d..d1071ab 100644
--- a/dev_tools/src/bin/ci.rs
+++ b/dev_tools/src/bin/ci.rs
@@ -13,10 +13,11 @@ fn print_help() {
println!("Usage: ci [options]");
println!();
println!("Options:");
- println!(" -h, --help Print this help message");
- println!(" -y Auto-confirm temporary commits");
- println!(" --test-docs Run documentation tests (build, clippy, test)");
- println!(" --test-codes Test examples and documentation code blocks");
+ println!(" -h, --help Print this help message");
+ println!(" -y Auto-confirm temporary commits");
+ println!(" --refresh-docs Refresh documentation files");
+ println!(" --test-docs Run documentation tests (build, clippy, test)");
+ println!(" --test-codes Test examples and documentation code blocks");
println!();
println!("If no specific options are given, all checks are run.");
}
diff --git a/mingling_macros/src/chain.rs b/mingling_macros/src/chain.rs
index fb5999a..eaf43fb 100644
--- a/mingling_macros/src/chain.rs
+++ b/mingling_macros/src/chain.rs
@@ -377,15 +377,17 @@ 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 {
+ let enum_variant = &previous_type.path.segments.last().unwrap().ident;
quote! {
- #struct_name => #previous_type,
+ #struct_name => #enum_variant,
}
}
/// Builds a match arm for chain existence check
pub fn build_chain_exist_arm(previous_type: &TypePath) -> proc_macro2::TokenStream {
+ let enum_variant = &previous_type.path.segments.last().unwrap().ident;
quote! {
- Self::#previous_type => true,
+ Self::#enum_variant => true,
}
}
diff --git a/mingling_macros/src/completion.rs b/mingling_macros/src/completion.rs
index ba0de2e..720ac49 100644
--- a/mingling_macros/src/completion.rs
+++ b/mingling_macros/src/completion.rs
@@ -1,14 +1,13 @@
use proc_macro::TokenStream;
use quote::quote;
-use syn::spanned::Spanned;
-use syn::{FnArg, Ident, ItemFn, PatType, Type, parse_macro_input};
+use syn::{Ident, ItemFn, TypePath, parse_macro_input};
#[cfg(feature = "comp")]
pub fn completion_attr(attr: TokenStream, item: TokenStream) -> TokenStream {
- // Parse the attribute arguments (e.g., HelloEntry from #[completion(HelloEntry)])
+ // Parse the attribute arguments (e.g., HelloEntry or crate::EntryFine from #[completion(crate::EntryFine)])
use crate::get_global_set;
- let previous_type_ident = if attr.is_empty() {
+ let previous_type_path: TypePath = if attr.is_empty() {
return syn::Error::new(
proc_macro2::Span::call_site(),
"completion attribute requires a previous type argument, e.g. #[completion(HelloEntry)]",
@@ -16,8 +15,9 @@ pub fn completion_attr(attr: TokenStream, item: TokenStream) -> TokenStream {
.to_compile_error()
.into();
} else {
- parse_macro_input!(attr as Ident)
+ parse_macro_input!(attr as TypePath)
};
+ let previous_type_ident = &previous_type_path.path.segments.last().unwrap().ident;
// Parse the function item
let input_fn = parse_macro_input!(item as ItemFn);
@@ -48,23 +48,6 @@ pub fn completion_attr(attr: TokenStream, item: TokenStream) -> TokenStream {
.into();
}
- // Check that the function parameter type is a single-segment type path (no `::`)
- if let Some(arg) = inputs.first()
- && let FnArg::Typed(PatType { ty, .. }) = arg
- && let Type::Path(type_path) = &**ty
- && type_path.path.segments.len() > 1
- {
- return syn::Error::new(
- type_path.span(),
- format!(
- "The type `{}` in #[completion] function must be a simple single-segment type, e.g. `HelloEntry` instead of `other::HelloEntry`. Qualified paths with `::` are not allowed here.",
- quote! { #type_path }
- ),
- )
- .to_compile_error()
- .into();
- }
-
// Get the function body
let fn_body = &input_fn.block;
@@ -93,7 +76,7 @@ pub fn completion_attr(attr: TokenStream, item: TokenStream) -> TokenStream {
#vis struct #struct_name;
impl ::mingling::Completion for #struct_name {
- type Previous = #previous_type_ident;
+ type Previous = #previous_type_path;
fn comp(#inputs) #output {
#fn_body
diff --git a/mingling_macros/src/help.rs b/mingling_macros/src/help.rs
index 2721bf5..e9e91cf 100644
--- a/mingling_macros/src/help.rs
+++ b/mingling_macros/src/help.rs
@@ -71,11 +71,6 @@ pub fn help_attr(item: TokenStream) -> TokenStream {
Err(e) => return e.to_compile_error().into(),
};
- // Check that the entry type is a single-segment type (no `::`)
- if let Some(err_tokens) = crate::check_single_segment_type(&entry_type, "#[help]") {
- return err_tokens.into();
- }
-
// Validate return type
if let Err(e) = validate_return_type(&input_fn.sig) {
return e.to_compile_error().into();
diff --git a/mingling_macros/src/lib.rs b/mingling_macros/src/lib.rs
index e774335..204d59c 100644
--- a/mingling_macros/src/lib.rs
+++ b/mingling_macros/src/lib.rs
@@ -195,35 +195,6 @@ pub(crate) static CHAINS_EXIST: Registry = OnceLock::new();
pub(crate) static RENDERERS_EXIST: Registry = OnceLock::new();
pub(crate) static HELP_REQUESTS: Registry = OnceLock::new();
-/// Checks that a `TypePath` is a simple single-segment identifier (no `::` in the path).
-///
-/// This is used by `#[renderer]`, `#[help]`, `#[chain]`, and `#[completion]` attribute macros
-/// to ensure that the type in the function signature is a bare identifier like `Empty`,
-/// not a qualified path like `other::Empty`.
-///
-/// Returns `None` if the type is valid, or a `compile_error!` token stream if it contains `::`.
-pub(crate) fn check_single_segment_type(
- type_path: &syn::TypePath,
- attr_name: &str,
-) -> Option<proc_macro2::TokenStream> {
- if type_path.path.segments.len() > 1 {
- let type_str = quote! { #type_path };
- Some(quote! {
- compile_error!(concat!(
- "The type `",
- #type_str,
- "` in ",
- #attr_name,
- " function must be a simple single-segment type, ",
- "e.g. `Empty` instead of `other::Empty`. ",
- "Qualified paths with `::` are not allowed here."
- ));
- })
- } else {
- None
- }
-}
-
/// 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`)
diff --git a/mingling_macros/src/renderer.rs b/mingling_macros/src/renderer.rs
index ac82799..c4b2786 100644
--- a/mingling_macros/src/renderer.rs
+++ b/mingling_macros/src/renderer.rs
@@ -44,11 +44,6 @@ pub fn renderer_attr(attr: TokenStream, item: TokenStream) -> TokenStream {
Err(e) => return e.to_compile_error().into(),
};
- // Check that the previous type is a single-segment type (no `::`)
- if let Some(err_tokens) = crate::check_single_segment_type(&previous_type, "#[renderer]") {
- return err_tokens.into();
- }
-
// Validate return type – now returns Some(type) if custom type, None if ()
let return_type = extract_return_type(&input_fn.sig);
@@ -174,23 +169,26 @@ pub fn build_renderer_entry(
struct_name: &syn::Ident,
previous_type: &TypePath,
) -> proc_macro2::TokenStream {
+ let enum_variant = &previous_type.path.segments.last().unwrap().ident;
quote! {
- #struct_name => #previous_type,
+ #struct_name => #enum_variant,
}
}
/// Builds the renderer existence check entry
pub fn build_renderer_exist_entry(previous_type: &TypePath) -> proc_macro2::TokenStream {
+ let enum_variant = &previous_type.path.segments.last().unwrap().ident;
quote! {
- Self::#previous_type => true,
+ Self::#enum_variant => true,
}
}
/// Builds the general renderer entry
#[cfg(feature = "general_renderer")]
pub fn build_general_renderer_entry(previous_type: &TypePath) -> proc_macro2::TokenStream {
+ let enum_variant = &previous_type.path.segments.last().unwrap().ident;
quote! {
- Self::#previous_type => {
+ Self::#enum_variant => {
// SAFETY: Only types that match will enter this branch for forced conversion,
// and `AnyOutput::new` ensures the type implements serde::Serialize
let raw = unsafe { any.restore::<#previous_type>().unwrap_unchecked() };
diff --git a/mingling_macros/src/res_injection.rs b/mingling_macros/src/res_injection.rs
index f2280e3..4f0be88 100644
--- a/mingling_macros/src/res_injection.rs
+++ b/mingling_macros/src/res_injection.rs
@@ -30,21 +30,7 @@ pub(crate) fn extract_args_info(
FnArg::Typed(PatType { pat, ty, .. }) => {
let param_pat = (**pat).clone();
match &**ty {
- Type::Path(type_path) => {
- // Check that the type is a single-segment type (no `::`)
- if type_path.path.segments.len() > 1 {
- return Err(syn::Error::new(
- type_path.span(),
- format!(
- "The type `{}` must be a simple single-segment type, \
- e.g. `Empty` instead of `other::Empty`. \
- Qualified paths with `::` are not allowed here.",
- quote! { #type_path }
- ),
- ));
- }
- (param_pat, type_path.clone())
- }
+ Type::Path(type_path) => (param_pat, type_path.clone()),
Type::Reference(_) => {
return Err(syn::Error::new(
ty.span(),