diff options
| -rw-r--r-- | CHANGELOG.md | 31 | ||||
| -rw-r--r-- | dev_tools/src/bin/ci.rs | 9 | ||||
| -rw-r--r-- | mingling_macros/src/chain.rs | 6 | ||||
| -rw-r--r-- | mingling_macros/src/completion.rs | 29 | ||||
| -rw-r--r-- | mingling_macros/src/help.rs | 5 | ||||
| -rw-r--r-- | mingling_macros/src/lib.rs | 29 | ||||
| -rw-r--r-- | mingling_macros/src/renderer.rs | 14 | ||||
| -rw-r--r-- | mingling_macros/src/res_injection.rs | 16 |
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(), |
