diff options
Diffstat (limited to 'mingling_macros/src/lib.rs')
| -rw-r--r-- | mingling_macros/src/lib.rs | 839 |
1 files changed, 120 insertions, 719 deletions
diff --git a/mingling_macros/src/lib.rs b/mingling_macros/src/lib.rs index d0f603a..a85648f 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) | //! @@ -141,40 +141,48 @@ //! } //! ``` -use proc_macro::TokenStream; +#[cfg(feature = "extra_macros")] use quote::quote; + +#[cfg(feature = "extra_macros")] +use syn::parse_macro_input; + +use proc_macro::TokenStream; use std::collections::BTreeSet; use std::sync::Mutex; use std::sync::OnceLock; -use syn::parse_macro_input; -mod chain; +mod attr; +mod derive; +mod func; +mod systems; + +mod extensions; +mod utils; + +// Bring all sub-modules into scope at the old paths so that existing +// references (e.g. `chain::chain_attr`, `renderer::renderer_attr`) +// continue to work without any `use`-path changes. #[cfg(feature = "comp")] -mod completion; -#[cfg(feature = "dispatch_tree")] -mod dispatch_tree_gen; -mod dispatcher; +use attr::completion; #[cfg(feature = "clap")] -mod dispatcher_clap; +use attr::dispatcher_clap; #[cfg(feature = "extra_macros")] -mod entry; -mod enum_tag; +use attr::program_setup; +use attr::{chain, help, renderer}; +use derive::{enum_tag, grouped}; #[cfg(feature = "extra_macros")] -mod group_impl; -mod groupped; -mod help; -mod node; -mod pack; +use func::entry; #[cfg(feature = "extra_macros")] -mod pack_err; +pub(crate) use func::group as group_impl; #[cfg(feature = "extra_macros")] -mod program_setup; -mod renderer; -mod res_injection; -#[cfg(feature = "structural_renderer")] -mod structural_data; +use func::pack_err; #[cfg(feature = "comp")] -mod suggest; +use func::suggest; +use func::{dispatcher, node, pack}; +use systems::res_injection; +#[cfg(feature = "structural_renderer")] +pub(crate) use systems::structural_data; pub(crate) fn default_program_path() -> proc_macro2::TokenStream { quote::quote! { crate::ThisProgram } @@ -262,44 +270,6 @@ fn entry_has_variant(entry: &str, variant_name: &str) -> bool { entry.contains(&format!(":: {variant_name} =>")) } -/// 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 -/// the type's simple name as an enum variant. -/// -/// # Syntax -/// -/// ```rust,ignore -/// group!(std::io::Error); -/// group!(ParseIntError); -/// ``` -/// -/// The type is registered under the default program (`crate::ThisProgram`). -/// -/// # How it works -/// -/// 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 -/// - 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!`. -/// -/// # Example -/// -/// ```rust,ignore -/// use mingling::macros::group; -/// -/// // Register std::io::Error as a group member -/// group!(std::io::Error); -/// ``` -/// -/// After expansion, the type can be used in chains and renderers like any -/// `#[derive(Groupped)]` type. -/// -/// This macro is only available with the `extra_macros` feature. #[cfg(feature = "extra_macros")] #[proc_macro] pub fn group(input: TokenStream) -> TokenStream { @@ -402,7 +372,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 +408,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 +431,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 +448,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 +491,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 +517,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 +530,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 +588,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,10 +842,16 @@ 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 { + // Extension point: if attr contains extension identifiers like `routeify`, + // re-dispatch as #[ext1] #[ext2] #[chain] fn ... + if let Some(redispatch) = extensions::try_redispatch_simple(attr.clone(), &item, "chain") { + return redispatch; + } chain::chain_attr(attr, item) } @@ -938,6 +919,9 @@ pub fn chain(attr: TokenStream, item: TokenStream) -> TokenStream { /// ``` #[proc_macro_attribute] pub fn renderer(attr: TokenStream, item: TokenStream) -> TokenStream { + if let Some(redispatch) = extensions::try_redispatch_simple(attr.clone(), &item, "renderer") { + return redispatch; + } renderer::renderer_attr(attr, item) } @@ -992,6 +976,9 @@ pub fn renderer(attr: TokenStream, item: TokenStream) -> TokenStream { #[cfg(feature = "comp")] #[proc_macro_attribute] pub fn completion(attr: TokenStream, item: TokenStream) -> TokenStream { + if let Some(redispatch) = extensions::try_redispatch_completion(attr.clone(), &item) { + return redispatch; + } completion::completion_attr(attr, item) } @@ -1234,7 +1221,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 @@ -1245,14 +1232,38 @@ pub fn register_dispatcher(input: TokenStream) -> TokenStream { /// /// [`BasicProgramSetup`]: https://docs.rs/mingling/latest/mingling/setup/struct.BasicProgramSetup.html #[proc_macro_attribute] -pub fn help(_attr: TokenStream, item: TokenStream) -> TokenStream { +pub fn help(attr: TokenStream, item: TokenStream) -> TokenStream { + if let Some(redispatch) = extensions::try_redispatch_simple(attr.clone(), &item, "help") { + return redispatch; + } help::help_attr(item) } -/// Derive macro for automatically implementing the `Groupped` trait on a struct. +/// Extension attribute macro that transforms `expr?` into `route!(expr)`. +/// +/// Designed for use with `#[chain(routeify, ...)]` to enable concise error +/// routing in chain functions using the `?` operator syntax. /// -/// The `#[derive(Groupped)]` macro: -/// 1. Implements `Groupped<crate::ThisProgram>`. +/// # Example +/// +/// ```rust,ignore +/// #[chain(routeify)] +/// fn handle_calc(args: EntryCalculate) -> Next { +/// let a = args.pick(&arg![f32]).to_result()?; +/// let op = args.pick(&arg![Operator]).to_result()?; +/// StateCalculate { number_a: a, operator: op, ... }.to_chain() +/// } +/// ``` +#[cfg(feature = "extra_macros")] +#[proc_macro_attribute] +pub fn routeify(attr: TokenStream, item: TokenStream) -> TokenStream { + extensions::routeify::routeify_impl(attr, item) +} + +/// Derive macro for automatically implementing the `Grouped` trait on a struct. +/// +/// 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 +1271,7 @@ pub fn help(_attr: TokenStream, item: TokenStream) -> TokenStream { /// # Syntax /// /// ```rust,ignore -/// #[derive(Groupped)] +/// #[derive(Grouped)] /// struct MyStruct { /// // ... /// } @@ -1269,9 +1280,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 +1290,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 +1361,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 +1382,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. @@ -1435,119 +1446,20 @@ pub fn derive_groupped_serialize(input: TokenStream) -> TokenStream { /// gen_program!(); /// ``` #[proc_macro] -pub fn gen_program(_input: TokenStream) -> TokenStream { - #[cfg(feature = "comp")] - let comp_gen = quote! { - ::mingling::macros::program_comp_gen!(); - }; - - #[cfg(not(feature = "comp"))] - let comp_gen = quote! {}; - - TokenStream::from(quote! { - pub type Next = ::mingling::ChainProcess<crate::ThisProgram>; - - #comp_gen - ::mingling::macros::program_fallback_gen!(); - ::mingling::macros::program_final_gen!(); - }) +pub fn gen_program(input: TokenStream) -> TokenStream { + func::gen_program::gen_program_impl(input) } -/// Internal macro used by `gen_program!` to generate completion infrastructure. -/// -/// **This macro is only available with the `comp` feature.** -/// -/// This is an internal macro and should not be called directly by user code. -/// It generates a completion dispatcher, the `CompletionContext` type, and -/// the execution/render logic for shell completion. -/// -/// The generated module `__completion_gen` contains: -/// - A `__comp` dispatcher that routes completion requests -/// - A `__exec_completion` chain that processes `CompletionContext` into `CompletionSuggest` -/// - A `__render_completion` renderer that outputs completion suggestions -#[proc_macro] #[cfg(feature = "comp")] -pub fn program_comp_gen(_input: TokenStream) -> TokenStream { - #[cfg(feature = "async")] - let fn_exec_comp = quote! { - #[doc(hidden)] - #[::mingling::macros::chain] - pub async fn __exec_completion(prev: CompletionContext) -> Next { - use ::mingling::Groupped; - - let read_ctx = ::mingling::ShellContext::try_from(prev.inner); - match read_ctx { - Ok(ctx) => { - let suggest = ::mingling::CompletionHelper::exec_completion::<crate::ThisProgram>(&ctx); - crate::CompletionSuggest::new((ctx, suggest)).to_render() - } - Err(_) => std::process::exit(1), - } - } - }; - - #[cfg(not(feature = "async"))] - let fn_exec_comp = quote! { - #[doc(hidden)] - #[::mingling::macros::chain] - pub fn __exec_completion(prev: CompletionContext) -> Next { - use ::mingling::Groupped; - - let read_ctx = ::mingling::ShellContext::try_from(prev.inner); - match read_ctx { - Ok(ctx) => { - let suggest = ::mingling::CompletionHelper::exec_completion::<crate::ThisProgram>(&ctx); - crate::CompletionSuggest::new((ctx, suggest)).to_render() - } - Err(_) => std::process::exit(1), - } - } - }; - - #[cfg(feature = "dispatch_tree")] - let internal_dispatcher_comp = quote! { - use __internal_completion_mod::__internal_dispatcher_comp; - }; - - #[cfg(not(feature = "dispatch_tree"))] - let internal_dispatcher_comp = quote! {}; - - let comp_dispatcher = quote! { - #[doc(hidden)] - mod __internal_completion_mod { - use ::mingling::Groupped; - ::mingling::macros::dispatcher!("__comp", CMDCompletion => CompletionContext); - ::mingling::macros::pack!( - CompletionSuggest = (::mingling::ShellContext, ::mingling::Suggest) - ); - } - #internal_dispatcher_comp - use __internal_completion_mod::CompletionContext; - use __internal_completion_mod::CompletionSuggest; - pub use __internal_completion_mod::CMDCompletion; - - #fn_exec_comp - - ::mingling::macros::register_type!(CompletionContext); - - #[allow(unused)] - #[doc(hidden)] - #[::mingling::macros::renderer] - pub fn __render_completion(prev: CompletionSuggest) -> ::mingling::RenderResult { - let result = ::mingling::RenderResult::default(); - let (ctx, suggest) = prev.inner; - ::mingling::CompletionHelper::render_suggest::<crate::ThisProgram>(ctx, suggest); - result - } - }; - - TokenStream::from(comp_dispatcher) +#[proc_macro] +pub fn program_comp_gen(input: TokenStream) -> TokenStream { + func::gen_program::program_comp_gen_impl(input) } /// 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. @@ -1566,115 +1478,29 @@ pub fn program_comp_gen(_input: TokenStream) -> TokenStream { /// Panics if the global `PACKED_TYPES` mutex is poisoned. #[proc_macro] pub fn register_type(input: TokenStream) -> TokenStream { - let type_ident = parse_macro_input!(input as syn::Ident); - let entry_str = type_ident.to_string(); - - get_global_set(&PACKED_TYPES) - .lock() - .unwrap() - .insert(entry_str); - - TokenStream::new() + func::gen_program::register_type_impl(input) } -/// Registers a chain mapping from a previous type to a chain struct. -/// -/// This macro is called internally by `#[chain]`(macro.chain.html) and is -/// generally not needed in user code. It inserts entries into the global -/// `CHAINS` and `CHAINS_EXIST` registries. -/// -/// # Syntax -/// -/// ```rust,ignore -/// register_chain!(PreviousType, ChainStruct); -/// ``` -/// -/// The `PreviousType` is the input type of the chain step, and `ChainStruct` -/// is the generated struct that implements the `Chain` trait. #[proc_macro] pub fn register_chain(input: TokenStream) -> TokenStream { - chain::register_chain(input) + func::gen_program::register_chain_impl(input) } -/// Registers a renderer mapping from a type to a renderer struct. -/// -/// This macro is called internally by `#[renderer]`(macro.renderer.html) and is -/// generally not needed in user code. It inserts entries into the global -/// `RENDERERS`, `RENDERERS_EXIST` and (with `structural_renderer` feature) -/// `STRUCTURAL_RENDERERS` registries. -/// -/// # Syntax -/// -/// ```rust,ignore -/// register_renderer!(PreviousType, RendererStruct); -/// ``` -/// -/// The `PreviousType` is the input type of the renderer, and `RendererStruct` -/// is the generated struct that implements the `Renderer` trait. #[proc_macro] pub fn register_renderer(input: TokenStream) -> TokenStream { - renderer::register_renderer(input) + func::gen_program::register_renderer_impl(input) } -/// Internal macro used by `gen_program!` to generate fallback types. -/// -/// This macro generates the fallback wrapper types that are essential -/// for error handling in the Mingling pipeline: -/// -/// - **`ErrorRendererNotFound`** — Wraps a `String` (the name of the missing renderer). -/// Used when no matching renderer is found for a given output type. -/// - **`ErrorDispatcherNotFound`** — Wraps `Vec<String>` (the unrecognized command args). -/// Used when no matching dispatcher is found for user input. -/// - **`ResultEmpty`** — 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. -/// -/// This macro is called automatically by `gen_program!` and should not -/// be called directly by user code. -/// -/// # Syntax -/// -/// ```rust,ignore -/// // Called internally by gen_program!: -/// program_fallback_gen!(); -/// ``` -/// -/// # Generated code equivalent -/// -/// ```rust,ignore -/// pack!(ErrorRendererNotFound = String); -/// pack!(ErrorDispatcherNotFound = Vec<String>); -/// pack!(ResultEmpty = ()); -/// ``` #[proc_macro] -pub fn program_fallback_gen(_input: TokenStream) -> TokenStream { - #[cfg(feature = "structural_renderer")] - let pack_empty = quote! { - #[derive(::serde::Serialize, ::mingling::StructuralData, ::mingling::Groupped, Default)] - pub struct ResultEmpty; - }; - - #[cfg(not(feature = "structural_renderer"))] - let pack_empty = quote! { - #[derive(::mingling::Groupped, Default)] - pub struct ResultEmpty; - }; - - let expanded = quote! { - ::mingling::macros::pack!(ErrorRendererNotFound = String); - ::mingling::macros::pack!(ErrorDispatcherNotFound = Vec<String>); - #pack_empty - }; - TokenStream::from(expanded) +pub fn program_fallback_gen(input: TokenStream) -> TokenStream { + func::gen_program::program_fallback_gen_impl(input) } /// Internal macro used by `gen_program!` to generate the final program enum /// 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 @@ -1719,434 +1545,9 @@ pub fn program_fallback_gen(_input: TokenStream) -> TokenStream { /// pub fn new() -> Program<MyProgram> { Program::new() } /// } /// ``` -/// -/// # Panics -/// -// Feature detection: baked into the proc-macro binary at compile time -#[cfg(feature = "async")] -const ASYNC_ENABLED: bool = true; -#[cfg(not(feature = "async"))] -const ASYNC_ENABLED: bool = false; - -/// Parses an entry of the format `StructName => EnumVariant,` into a pair of idents. -fn parse_entry_pair(entry: &proc_macro2::TokenStream) -> (proc_macro2::Ident, proc_macro2::Ident) { - let s = entry.to_string(); - let arrow_idx = s - .find("=>") - .unwrap_or_else(|| panic!("Entry missing '=>': {s}")); - let struct_str = s[..arrow_idx].trim(); - let variant_str = s[arrow_idx + 2..].trim().trim_end_matches(',').trim(); - let struct_ident = proc_macro2::Ident::new(struct_str, proc_macro2::Span::call_site()); - let variant_ident = proc_macro2::Ident::new(variant_str, proc_macro2::Span::call_site()); - (struct_ident, variant_ident) -} - -/// Loads the pathf type mapping from `$OUT_DIR/{crate}/type_using.rs`. -/// Always compiled; returns empty map when pathf feature is not enabled. -fn load_pathf_map() -> std::collections::HashMap<String, String> { - if !cfg!(feature = "pathf") { - return std::collections::HashMap::new(); - } - let out_dir = std::env::var("OUT_DIR").ok(); - let crate_name = std::env::var("CARGO_PKG_NAME").ok(); - match (out_dir, crate_name) { - (Some(dir), Some(name)) => { - let path = std::path::Path::new(&dir).join(&name).join("type_using.rs"); - match std::fs::read_to_string(&path) { - Ok(content) => content - .lines() - .filter_map(|line| { - let line = line.trim(); - if let Some(rest) = line.strip_prefix("use ") { - let path = rest.strip_suffix(';').unwrap_or(rest); - if let Some((_mod, type_name)) = path.rsplit_once("::") { - return Some((type_name.to_string(), path.to_string())); - } - } - None - }) - .collect(), - Err(_) => std::collections::HashMap::new(), - } - } - _ => std::collections::HashMap::new(), - } -} - -/// Resolves a type name to its full path token stream using the pathf mapping. -pub(crate) fn resolve_type( - name: &str, - map: &std::collections::HashMap<String, String>, -) -> proc_macro2::TokenStream { - if let Some(full_path) = map.get(name) { - syn::parse_str::<proc_macro2::TokenStream>(full_path).unwrap_or_else(|_| { - let ident = proc_macro2::Ident::new(name, proc_macro2::Span::call_site()); - quote! { #ident } - }) - } else { - let ident = proc_macro2::Ident::new(name, proc_macro2::Span::call_site()); - quote! { #ident } - } -} - -/// Panics if any of the global registries (`PACKED_TYPES`, `RENDERERS`, `CHAINS`, etc.) -/// are poisoned. #[proc_macro] -#[allow(clippy::too_many_lines)] -pub fn program_final_gen(_input: TokenStream) -> TokenStream { - let name = syn::Ident::new("ThisProgram", proc_macro2::Span::call_site()); - - let packed_types = get_global_set(&PACKED_TYPES).lock().unwrap().clone(); - - let renderers = get_global_set(&RENDERERS).lock().unwrap().clone(); - let chains = get_global_set(&CHAINS).lock().unwrap().clone(); - let renderer_exist = get_global_set(&RENDERERS_EXIST).lock().unwrap().clone(); - let chain_exist = get_global_set(&CHAINS_EXIST).lock().unwrap().clone(); - - #[cfg(feature = "structural_renderer")] - let structural_renderers = get_global_set(&STRUCTURAL_RENDERERS) - .lock() - .unwrap() - .clone(); - - #[cfg(feature = "comp")] - let completions = get_global_set(&COMPLETIONS).lock().unwrap().clone(); - - let packed_types: Vec<proc_macro2::TokenStream> = packed_types - .iter() - .map(|s| syn::parse_str::<proc_macro2::TokenStream>(s).unwrap()) - .collect(); - - let renderer_tokens: Vec<proc_macro2::TokenStream> = renderers - .iter() - .map(|s| syn::parse_str::<proc_macro2::TokenStream>(s).unwrap()) - .collect(); - - let chain_tokens: Vec<proc_macro2::TokenStream> = chains - .iter() - .map(|s| syn::parse_str::<proc_macro2::TokenStream>(s).unwrap()) - .collect(); - - let renderer_exist_tokens: Vec<proc_macro2::TokenStream> = renderer_exist - .iter() - .map(|s| syn::parse_str::<proc_macro2::TokenStream>(s).unwrap()) - .collect(); - - let chain_exist_tokens: Vec<proc_macro2::TokenStream> = chain_exist - .iter() - .map(|s| syn::parse_str::<proc_macro2::TokenStream>(s).unwrap()) - .collect(); - - let pathf_map: std::collections::HashMap<String, String> = if cfg!(feature = "pathf") { - load_pathf_map() - } else { - std::collections::HashMap::new() - }; - - let pathf_uses: Vec<proc_macro2::TokenStream> = if cfg!(feature = "pathf") { - pathf_map - .values() - .map(|path| format!("use {};", path).parse().unwrap_or_default()) - .collect() - } else { - Vec::new() - }; - - #[cfg(feature = "structural_renderer")] - let structural_renderer_tokens: Vec<proc_macro2::TokenStream> = structural_renderers - .iter() - .map(|s| syn::parse_str::<proc_macro2::TokenStream>(s).unwrap()) - .collect(); - - #[cfg(feature = "structural_renderer")] - let structural_render = quote! { - fn structural_render( - any: ::mingling::AnyOutput<Self::Enum>, - setting: &::mingling::StructuralRendererSetting, - ) -> Result<::mingling::RenderResult, ::mingling::error::StructuralRendererSerializeError> { - #[allow(unused_imports)] - #(#pathf_uses)* - match any.member_id { - #(#structural_renderer_tokens)* - _ => { - // Non-structural types: render ResultEmpty (which implements - // StructuralData + Serialize) instead of producing nothing. - let mut r = ::mingling::RenderResult::default(); - ::mingling::StructuralRenderer::render(&ResultEmpty, setting, &mut r)?; - Ok(r) - } - } - } - }; - - #[cfg(not(feature = "structural_renderer"))] - let structural_render = quote! {}; - - #[cfg(feature = "dispatch_tree")] - let compile_time_dispatchers: Vec<String> = get_global_set(&COMPILE_TIME_DISPATCHERS) - .lock() - .unwrap() - .clone() - .iter() - .cloned() - .collect(); - - #[cfg(feature = "dispatch_tree")] - let dispatch_tree_nodes = { - let entries: Vec<(String, String, String)> = compile_time_dispatchers - .iter() - .filter_map(|entry| { - let parts: Vec<&str> = entry.split(':').collect(); - if parts.len() == 3 { - Some(( - parts[0].to_string(), - parts[1].to_string(), - parts[2].to_string(), - )) - } else { - None - } - }) - .collect(); - - let get_nodes_fn = dispatch_tree_gen::gen_get_nodes(&entries, &pathf_map); - let dispatch_trie_fn = dispatch_tree_gen::gen_dispatch_args_trie(&entries, &pathf_map); - - quote! { - #get_nodes_fn - #dispatch_trie_fn - } - }; - - #[cfg(not(feature = "dispatch_tree"))] - let dispatch_tree_nodes = quote! {}; - - #[cfg(feature = "comp")] - let completion_tokens: Vec<proc_macro2::TokenStream> = completions - .iter() - .map(|s| syn::parse_str::<proc_macro2::TokenStream>(s).unwrap()) - .collect(); - - #[cfg(feature = "comp")] - let comp = quote! { - fn do_comp(any: &::mingling::AnyOutput<Self::Enum>, ctx: &::mingling::ShellContext) -> ::mingling::Suggest { - #[allow(unused_imports)] - #(#pathf_uses)* - match any.member_id { - #(#completion_tokens)* - _ => ::mingling::Suggest::FileCompletion, - } - } - }; - - #[cfg(not(feature = "comp"))] - let comp = quote! {}; - - // Build render function arms from stored entries - let render_fn = - if renderer_tokens.is_empty() { - quote! { - fn render(_any: ::mingling::AnyOutput<Self::Enum>) -> ::mingling::RenderResult { - ::mingling::RenderResult::default() - } - } - } else { - let render_arms: Vec<_> = renderer_tokens.iter().map(|entry| { - let (struct_ident, variant_ident) = parse_entry_pair(entry); - let downcast_ty = resolve_type(&variant_ident.to_string(), &pathf_map); - let resolved_struct = resolve_type(&struct_ident.to_string(), &pathf_map); - quote! { - Self::#variant_ident => { - // SAFETY: The `type_id` check ensures that `any` contains a value of type `#variant_ident`, - // so downcasting to `#variant_ident` is safe. - let value = unsafe { any.downcast::<#downcast_ty>().unwrap_unchecked() }; - <#resolved_struct as ::mingling::Renderer>::render(value) - } - } - }).collect(); - quote! { - fn render(any: ::mingling::AnyOutput<Self::Enum>) -> ::mingling::RenderResult { - match any.member_id { - #(#render_arms)* - _ => ::mingling::RenderResult::default(), - } - } - } - }; - - // Build do_chain function (async and sync versions) - let chain_arms_async: Vec<_> = chain_tokens.iter().map(|entry| { - let (struct_ident, variant_ident) = parse_entry_pair(entry); - let downcast_ty = resolve_type(&variant_ident.to_string(), &pathf_map); - let resolved_struct = resolve_type(&struct_ident.to_string(), &pathf_map); - quote! { - Self::#variant_ident => { - // SAFETY: The `type_id` check ensures that `any` contains a value of type `#variant_ident`, - // so downcasting to `#variant_ident` is safe. - let value = unsafe { any.downcast::<#downcast_ty>().unwrap_unchecked() }; - let fut = async { <#resolved_struct as ::mingling::Chain<Self::Enum>>::proc(value).await }; - ::std::boxed::Box::pin(fut) - } - } - }).collect(); - - let chain_arms_sync: Vec<_> = chain_tokens - .iter() - .map(|entry| { - let (struct_ident, variant_ident) = parse_entry_pair(entry); - let downcast_ty = resolve_type(&variant_ident.to_string(), &pathf_map); - let resolved_struct = resolve_type(&struct_ident.to_string(), &pathf_map); - quote! { - Self::#variant_ident => { - // SAFETY: The `type_id` check ensures that `any` contains a value of type `#variant_ident`, - // so downcasting to `#variant_ident` is safe. - let value = unsafe { any.downcast::<#downcast_ty>().unwrap_unchecked() }; - <#resolved_struct as ::mingling::Chain<Self::Enum>>::proc(value) - } - } - }) - .collect(); - - let do_chain_fn = if chain_tokens.is_empty() { - quote! { - fn do_chain(_any: ::mingling::AnyOutput<Self::Enum>) -> ::mingling::ChainProcess<Self::Enum> { - ::core::panic!("No chain found for type id") - } - } - } else if ASYNC_ENABLED { - quote! { - fn do_chain( - any: ::mingling::AnyOutput<Self::Enum>, - ) -> ::std::pin::Pin<::std::boxed::Box<dyn ::std::future::Future<Output = ::mingling::ChainProcess<Self::Enum>> + ::std::marker::Send>> { - match any.member_id { - #(#chain_arms_async)* - _ => ::core::panic!("No chain found for type id: {:?}", any.type_id), - } - } - } - } else { - quote! { - fn do_chain( - any: ::mingling::AnyOutput<Self::Enum>, - ) -> ::mingling::ChainProcess<Self::Enum> { - match any.member_id { - #(#chain_arms_sync)* - _ => ::core::panic!("No chain found for type id: {:?}", any.type_id), - } - } - } - }; - - let help_tokens: Vec<proc_macro2::TokenStream> = get_global_set(&HELP_REQUESTS) - .lock() - .unwrap() - .clone() - .iter() - .map(|s| syn::parse_str::<proc_macro2::TokenStream>(s).unwrap()) - .collect(); - - let num_variants = packed_types.len(); - let repr_type = if u8::try_from(num_variants).is_ok() { - quote! { u8 } - } else if u16::try_from(num_variants).is_ok() { - quote! { u16 } - } else if u32::try_from(num_variants).is_ok() { - quote! { u32 } - } else { - quote! { u128 } - }; - - let expanded = quote! { - #[derive(Debug, PartialEq, Eq, Clone)] - #[repr(#repr_type)] - #[allow(nonstandard_style)] - pub enum #name { - #(#packed_types),* - } - - impl ::std::fmt::Display for #name { - fn fmt(&self, f: &mut ::std::fmt::Formatter<'_>) -> ::std::fmt::Result { - match self { - #(#name::#packed_types => write!(f, stringify!(#packed_types)),)* - } - } - } - - impl ::mingling::ProgramCollect for #name { - type Enum = #name; - type ErrorDispatcherNotFound = ErrorDispatcherNotFound; - type ErrorRendererNotFound = ErrorRendererNotFound; - type ResultEmpty = ResultEmpty; - fn build_renderer_not_found(member_id: Self::Enum) -> ::mingling::AnyOutput<Self::Enum> { - ::mingling::AnyOutput::new(ErrorRendererNotFound::new(member_id.to_string())) - } - fn build_dispatcher_not_found(args: Vec<String>) -> ::mingling::AnyOutput<Self::Enum> { - ::mingling::AnyOutput::new(ErrorDispatcherNotFound::new(args)) - } - fn build_empty_result() -> ::mingling::AnyOutput<Self::Enum> { - ::mingling::AnyOutput::new(ResultEmpty) - } - #render_fn - #do_chain_fn - fn render_help(any: ::mingling::AnyOutput<Self::Enum>) -> ::mingling::RenderResult { - #[allow(unused_imports)] - #(#pathf_uses)* - match any.member_id { - #(#help_tokens)* - _ => ::mingling::RenderResult::default(), - } - } - fn has_renderer(any: &::mingling::AnyOutput<Self::Enum>) -> bool { - match any.member_id { - #(#renderer_exist_tokens)* - _ => false - } - } - fn has_chain(any: &::mingling::AnyOutput<Self::Enum>) -> bool { - match any.member_id { - #(#chain_exist_tokens)* - _ => false - } - } - #dispatch_tree_nodes - #structural_render - #comp - } - - impl #name { - /// Creates a new `Program<#name>` instance with default configuration. - pub fn new() -> ::mingling::Program<#name> { - ::mingling::Program::new() - } - - /// Returns a static reference to the global `Program<#name>` singleton. - pub fn this() -> &'static ::mingling::Program<#name> { - &::mingling::this::<#name>() - } - } - }; - - // Clear all global registries to prevent stale state in Rust Analyzer - get_global_set(&PACKED_TYPES).lock().unwrap().clear(); - get_global_set(&CHAINS).lock().unwrap().clear(); - get_global_set(&CHAINS_EXIST).lock().unwrap().clear(); - get_global_set(&RENDERERS).lock().unwrap().clear(); - get_global_set(&RENDERERS_EXIST).lock().unwrap().clear(); - get_global_set(&HELP_REQUESTS).lock().unwrap().clear(); - #[cfg(feature = "comp")] - get_global_set(&COMPLETIONS).lock().unwrap().clear(); - #[cfg(feature = "dispatch_tree")] - get_global_set(&COMPILE_TIME_DISPATCHERS) - .lock() - .unwrap() - .clear(); - #[cfg(feature = "structural_renderer")] - get_global_set(&STRUCTURAL_RENDERERS) - .lock() - .unwrap() - .clear(); - - TokenStream::from(expanded) +pub fn program_final_gen(input: TokenStream) -> TokenStream { + func::gen_program::program_final_gen_impl(input) } /// Builds a `Suggest` instance with inline suggestion items. |
