aboutsummaryrefslogtreecommitdiff
path: root/mingling_macros/src
diff options
context:
space:
mode:
Diffstat (limited to 'mingling_macros/src')
-rw-r--r--mingling_macros/src/attr.rs1
-rw-r--r--mingling_macros/src/attr/help.rs57
-rw-r--r--mingling_macros/src/attr/mlint.rs7
-rw-r--r--mingling_macros/src/derive.rs2
-rw-r--r--mingling_macros/src/derive/structural_data.rs26
-rw-r--r--mingling_macros/src/func.rs27
-rw-r--r--mingling_macros/src/func/dispatcher.rs74
-rw-r--r--mingling_macros/src/func/empty_result.rs11
-rw-r--r--mingling_macros/src/func/gen_program.rs591
-rw-r--r--mingling_macros/src/func/group_structural.rs124
-rw-r--r--mingling_macros/src/func/pack_err.rs102
-rw-r--r--mingling_macros/src/func/pack_err_structural.rs119
-rw-r--r--mingling_macros/src/func/pack_structural.rs168
-rw-r--r--mingling_macros/src/func/program_comp_gen.rs80
-rw-r--r--mingling_macros/src/func/program_fallback_gen.rs23
-rw-r--r--mingling_macros/src/func/program_final_gen.rs465
-rw-r--r--mingling_macros/src/func/r_append.rs29
-rw-r--r--mingling_macros/src/func/r_eprint.rs7
-rw-r--r--mingling_macros/src/func/r_eprintln.rs7
-rw-r--r--mingling_macros/src/func/r_print.rs52
-rw-r--r--mingling_macros/src/func/r_println.rs7
-rw-r--r--mingling_macros/src/func/register_chain.rs7
-rw-r--r--mingling_macros/src/func/register_dispatcher.rs75
-rw-r--r--mingling_macros/src/func/register_help.rs71
-rw-r--r--mingling_macros/src/func/register_renderer.rs7
-rw-r--r--mingling_macros/src/func/register_type.rs17
-rw-r--r--mingling_macros/src/func/render_route.rs15
-rw-r--r--mingling_macros/src/func/route.rs16
-rw-r--r--mingling_macros/src/func/suggest.rs21
-rw-r--r--mingling_macros/src/func/suggest_enum.rs24
-rw-r--r--mingling_macros/src/lib.rs72
-rw-r--r--mingling_macros/src/systems/dispatch_tree_gen.rs2
-rw-r--r--mingling_macros/src/systems/structural_data.rs350
33 files changed, 1380 insertions, 1276 deletions
diff --git a/mingling_macros/src/attr.rs b/mingling_macros/src/attr.rs
index e4cd826..ed7f58b 100644
--- a/mingling_macros/src/attr.rs
+++ b/mingling_macros/src/attr.rs
@@ -4,6 +4,7 @@ pub(crate) mod completion;
#[cfg(feature = "clap")]
pub(crate) mod dispatcher_clap;
pub(crate) mod help;
+pub(crate) mod mlint;
#[cfg(feature = "extra_macros")]
pub(crate) mod program_setup;
pub(crate) mod renderer;
diff --git a/mingling_macros/src/attr/help.rs b/mingling_macros/src/attr/help.rs
index aa7bc88..b4ad989 100644
--- a/mingling_macros/src/attr/help.rs
+++ b/mingling_macros/src/attr/help.rs
@@ -1,5 +1,5 @@
use proc_macro::TokenStream;
-use quote::{ToTokens, quote};
+use quote::quote;
use syn::spanned::Spanned;
use syn::{Ident, ItemFn, Pat, ReturnType, Signature, TypePath, parse_macro_input};
@@ -154,7 +154,7 @@ pub(crate) fn help_attr(item: TokenStream) -> TokenStream {
::mingling::macros::register_help!(#entry_type, #struct_name);
- // Keep the original function unchanged
+ // Keep the original function unchanged
#(#fn_attrs)*
#vis fn #fn_name(#original_inputs) -> #original_return_type {
#(#fn_body_stmts)*
@@ -176,56 +176,3 @@ fn build_help_entry(struct_name: &Ident, entry_type: &TypePath) -> proc_macro2::
}
}
}
-
-pub(crate) fn register_help(input: TokenStream) -> TokenStream {
- // Parse the input as a comma-separated list of arguments
- let input_parsed = syn::parse_macro_input!(input with syn::punctuated::Punctuated<syn::Expr, syn::Token![,]>::parse_terminated);
-
- // Check if there are exactly two elements
- if input_parsed.len() != 2 {
- return syn::Error::new(
- input_parsed.span(),
- "Expected exactly two comma-separated arguments: `EntryType, StructName`",
- )
- .to_compile_error()
- .into();
- }
-
- // Extract the two elements
- let entry_type_expr = &input_parsed[0];
- let struct_name_expr = &input_parsed[1];
-
- // Convert expressions to TypePath and Ident
- let entry_type = match syn::parse2::<TypePath>(entry_type_expr.to_token_stream()) {
- Ok(ty) => ty,
- Err(e) => return e.to_compile_error().into(),
- };
-
- let struct_name = match syn::parse2::<syn::Ident>(struct_name_expr.to_token_stream()) {
- Ok(ident) => ident,
- Err(e) => return e.to_compile_error().into(),
- };
-
- // Register the help request mapping
- let help_entry = build_help_entry(&struct_name, &entry_type);
- let entry_str = help_entry.to_string();
-
- // Check if entry was already pre-inserted by `#[help]` attribute
- let mut helps = get_global_set(&crate::HELP_REQUESTS).lock().unwrap();
- if helps.contains(&entry_str) {
- // Already registered by `#[help]`, no duplicate check needed
- return quote! {}.into();
- }
-
- // Check for duplicate variant (different struct, same type)
- let variant_name = entry_type.path.segments.last().unwrap().ident.to_string();
- if let Err(err) =
- crate::check_duplicate_variant(&helps, &entry_str, &variant_name, "help", entry_type.span())
- {
- return err.into();
- }
-
- helps.insert(entry_str);
-
- quote! {}.into()
-}
diff --git a/mingling_macros/src/attr/mlint.rs b/mingling_macros/src/attr/mlint.rs
new file mode 100644
index 0000000..176ef0a
--- /dev/null
+++ b/mingling_macros/src/attr/mlint.rs
@@ -0,0 +1,7 @@
+use proc_macro::TokenStream;
+
+/// Marker attribute for the Mingling lint system.
+/// All it does is pass the item through unchanged.
+pub(crate) fn mlint(_attr: TokenStream, item: TokenStream) -> TokenStream {
+ item
+}
diff --git a/mingling_macros/src/derive.rs b/mingling_macros/src/derive.rs
index ffa405b..666a36e 100644
--- a/mingling_macros/src/derive.rs
+++ b/mingling_macros/src/derive.rs
@@ -1,2 +1,4 @@
pub(crate) mod enum_tag;
pub(crate) mod grouped;
+#[cfg(feature = "structural_renderer")]
+pub(crate) mod structural_data;
diff --git a/mingling_macros/src/derive/structural_data.rs b/mingling_macros/src/derive/structural_data.rs
new file mode 100644
index 0000000..acb2f28
--- /dev/null
+++ b/mingling_macros/src/derive/structural_data.rs
@@ -0,0 +1,26 @@
+use proc_macro::TokenStream;
+use quote::quote;
+use syn::{DeriveInput, parse_macro_input};
+
+use crate::get_global_set;
+
+/// Derive macro for `StructuralData`.
+pub(crate) fn derive_structural_data(input: TokenStream) -> TokenStream {
+ let input = parse_macro_input!(input as DeriveInput);
+ let type_name = input.ident;
+
+ // Register in STRUCTURED_TYPES
+ let type_name_str = type_name.to_string();
+ get_global_set(&crate::STRUCTURED_TYPES)
+ .lock()
+ .unwrap()
+ .insert(type_name_str);
+
+ // Generate BOTH the sealed impl AND the StructuralData impl.
+ let expanded = quote! {
+ impl ::mingling::__private::StructuralDataSealed<crate::ThisProgram> for #type_name {}
+ impl ::mingling::__private::StructuralData<crate::ThisProgram> for #type_name {}
+ };
+
+ expanded.into()
+}
diff --git a/mingling_macros/src/func.rs b/mingling_macros/src/func.rs
index 33bb094..d836b38 100644
--- a/mingling_macros/src/func.rs
+++ b/mingling_macros/src/func.rs
@@ -1,13 +1,40 @@
pub(crate) mod dispatcher;
#[cfg(feature = "extra_macros")]
+pub(crate) mod empty_result;
+#[cfg(feature = "extra_macros")]
pub(crate) mod entry;
pub(crate) mod gen_program;
#[cfg(feature = "extra_macros")]
pub(crate) mod group;
+#[cfg(all(feature = "structural_renderer", feature = "extra_macros"))]
+pub(crate) mod group_structural;
pub(crate) mod node;
pub(crate) mod pack;
#[cfg(feature = "extra_macros")]
pub(crate) mod pack_err;
+#[cfg(all(feature = "structural_renderer", feature = "extra_macros"))]
+pub(crate) mod pack_err_structural;
+#[cfg(feature = "structural_renderer")]
+pub(crate) mod pack_structural;
+#[cfg(feature = "comp")]
+pub(crate) mod program_comp_gen;
+pub(crate) mod program_fallback_gen;
+pub(crate) mod program_final_gen;
+pub(crate) mod r_append;
+pub(crate) mod r_eprint;
+pub(crate) mod r_eprintln;
pub(crate) mod r_print;
+pub(crate) mod r_println;
+pub(crate) mod register_chain;
+pub(crate) mod register_dispatcher;
+pub(crate) mod register_help;
+pub(crate) mod register_renderer;
+pub(crate) mod register_type;
+#[cfg(feature = "extra_macros")]
+pub(crate) mod render_route;
+#[cfg(feature = "extra_macros")]
+pub(crate) mod route;
#[cfg(feature = "comp")]
pub(crate) mod suggest;
+#[cfg(feature = "comp")]
+pub(crate) mod suggest_enum;
diff --git a/mingling_macros/src/func/dispatcher.rs b/mingling_macros/src/func/dispatcher.rs
index a61dd26..4e7c42f 100644
--- a/mingling_macros/src/func/dispatcher.rs
+++ b/mingling_macros/src/func/dispatcher.rs
@@ -1,13 +1,8 @@
-#[cfg(feature = "dispatch_tree")]
-use just_fmt::snake_case;
use proc_macro::TokenStream;
use proc_macro2::TokenStream as TokenStream2;
use quote::quote;
use syn::parse::{Parse, ParseStream};
-use syn::{Attribute, Ident, LitStr, Result as SynResult, Token};
-
-#[cfg(feature = "dispatch_tree")]
-use crate::COMPILE_TIME_DISPATCHERS;
+use syn::{Attribute, Ident, LitStr, Token};
enum DispatcherChainInput {
Default {
@@ -25,7 +20,7 @@ enum DispatcherChainInput {
}
impl Parse for DispatcherChainInput {
- fn parse(input: ParseStream) -> SynResult<Self> {
+ fn parse(input: ParseStream) -> syn::Result<Self> {
// Collect outer attributes for the CMD struct
let cmd_attrs = input.call(Attribute::parse_outer)?;
@@ -184,76 +179,13 @@ fn get_dispatch_tree_entry(
quote! {}
}
-#[cfg(feature = "dispatch_tree")]
-/// Input format: ("node.name", DispatcherType, EntryName)
-struct RegisterDispatcherInput {
- node_name: syn::LitStr,
- dispatcher_type: Ident,
- entry_name: Ident,
-}
-
-#[cfg(feature = "dispatch_tree")]
-impl Parse for RegisterDispatcherInput {
- fn parse(input: ParseStream) -> SynResult<Self> {
- let node_name = input.parse()?;
- input.parse::<Token![,]>()?;
- let dispatcher_type = input.parse()?;
- input.parse::<Token![,]>()?;
- let entry_name = input.parse()?;
- Ok(RegisterDispatcherInput {
- node_name,
- dispatcher_type,
- entry_name,
- })
- }
-}
-
-#[cfg(feature = "dispatch_tree")]
-pub(crate) fn register_dispatcher(input: TokenStream) -> TokenStream {
- let RegisterDispatcherInput {
- node_name,
- dispatcher_type,
- entry_name,
- } = syn::parse_macro_input!(input as RegisterDispatcherInput);
-
- let node_name_str = node_name.value();
- let static_name = format!(
- "__internal_dispatcher_{}",
- snake_case!(node_name_str.clone())
- );
- let static_ident = Ident::new(&static_name, proc_macro2::Span::call_site());
-
- // Register node info in the global collection at compile time
- // Format: "node.name:DispatcherType:EntryName"
- crate::get_global_set(&COMPILE_TIME_DISPATCHERS)
- .lock()
- .unwrap()
- .insert(format!(
- "{}:{}:{}",
- node_name_str, dispatcher_type, entry_name
- ));
-
- let expanded = quote! {
- #[doc(hidden)]
- #[allow(nonstandard_style)]
- pub static #static_ident: #dispatcher_type = #dispatcher_type;
- };
-
- expanded.into()
-}
-
-#[cfg(not(feature = "dispatch_tree"))]
-pub(crate) fn register_dispatcher(_input: TokenStream) -> TokenStream {
- quote! {}.into()
-}
-
/// Converts a dotted command name (e.g. "remote.add") to `PascalCase` (e.g. "`RemoteAdd`").
///
/// Each segment is split by `.`, the first character of each segment is uppercased,
/// and the segments are joined. This is used by the abbreviated `dispatcher!` syntax
/// (when `Command => Entry` is omitted) to auto-derive struct names.
#[cfg(feature = "extra_macros")]
-fn dotted_to_pascal_case(s: &str) -> String {
+pub(crate) fn dotted_to_pascal_case(s: &str) -> String {
s.split('.')
.map(|segment| {
let mut chars = segment.chars();
diff --git a/mingling_macros/src/func/empty_result.rs b/mingling_macros/src/func/empty_result.rs
new file mode 100644
index 0000000..9b5c78c
--- /dev/null
+++ b/mingling_macros/src/func/empty_result.rs
@@ -0,0 +1,11 @@
+use proc_macro::TokenStream;
+use quote::quote;
+
+/// Creates an empty result value wrapped in `ChainProcess` for early return
+/// from a chain function.
+pub(crate) fn empty_result(_input: TokenStream) -> TokenStream {
+ let expanded = quote! {
+ <crate::ResultEmpty as ::mingling::Routable::<crate::ThisProgram>>::to_chain(crate::ResultEmpty)
+ };
+ TokenStream::from(expanded)
+}
diff --git a/mingling_macros/src/func/gen_program.rs b/mingling_macros/src/func/gen_program.rs
index db82504..c5358bc 100644
--- a/mingling_macros/src/func/gen_program.rs
+++ b/mingling_macros/src/func/gen_program.rs
@@ -1,90 +1,11 @@
-use std::collections::HashMap;
-
use proc_macro::TokenStream;
use quote::quote;
-use syn::parse_macro_input;
-
-use crate::CHAINS;
-use crate::CHAINS_EXIST;
-#[cfg(feature = "dispatch_tree")]
-use crate::COMPILE_TIME_DISPATCHERS;
-#[cfg(feature = "comp")]
-use crate::COMPLETIONS;
-use crate::HELP_REQUESTS;
-use crate::PACKED_TYPES;
-use crate::RENDERERS;
-use crate::RENDERERS_EXIST;
-#[cfg(feature = "structural_renderer")]
-use crate::STRUCTURAL_RENDERERS;
-use crate::attr::{chain, renderer};
-use crate::get_global_set;
-#[cfg(feature = "dispatch_tree")]
-use crate::systems::dispatch_tree_gen;
-
-#[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 None when pathf feature is not enabled,
-/// or when the file does not exist / fails to load.
-fn load_pathf_map() -> Option<std::collections::HashMap<String, String>> {
- if !cfg!(feature = "pathf") {
- return None;
- }
- let out_dir = std::env::var("OUT_DIR").ok()?;
- let crate_name = std::env::var("CARGO_PKG_NAME").ok()?;
- let path = std::path::Path::new(&out_dir)
- .join(&crate_name)
- .join("type_using.rs");
- let content = std::fs::read_to_string(&path).ok()?;
- Some(
- 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(),
- )
-}
-
-/// 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 }
- }
-}
+/// Entry point for `gen_program!()`.
+///
+/// Generates the `Next` type alias, `Routable` impl for `ChainProcess`,
+/// and delegates to `program_comp_gen!()`, `program_fallback_gen!()`,
+/// and `program_final_gen!()`.
pub(crate) fn gen_program_impl(_input: TokenStream) -> TokenStream {
#[cfg(feature = "comp")]
let comp_gen = quote! {
@@ -124,505 +45,3 @@ pub(crate) fn gen_program_impl(_input: TokenStream) -> TokenStream {
::mingling::macros::program_final_gen!();
})
}
-
-#[cfg(feature = "comp")]
-pub(crate) fn program_comp_gen_impl(_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::Grouped;
-
- let read_ctx = ::mingling::ShellContext::try_from(prev.inner);
- match read_ctx {
- Ok(ctx) => {
- let suggest = ::mingling::CompletionHelper::exec_completion::<crate::ThisProgram>(&ctx);
- ::mingling::Routable::<crate::ThisProgram>::to_render(crate::CompletionSuggest::new((ctx, suggest)))
- }
- 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::Grouped;
-
- let read_ctx = ::mingling::ShellContext::try_from(prev.inner);
- match read_ctx {
- Ok(ctx) => {
- let suggest = ::mingling::CompletionHelper::exec_completion::<crate::ThisProgram>(&ctx);
- ::mingling::Routable::<crate::ThisProgram>::to_render(crate::CompletionSuggest::new((ctx, suggest)))
- }
- 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::Grouped;
- ::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)
-}
-
-pub(crate) fn register_type_impl(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()
-}
-
-pub(crate) fn register_chain_impl(input: TokenStream) -> TokenStream {
- chain::register_chain(input)
-}
-
-pub(crate) fn register_renderer_impl(input: TokenStream) -> TokenStream {
- renderer::register_renderer(input)
-}
-
-pub(crate) fn program_fallback_gen_impl(_input: TokenStream) -> TokenStream {
- #[cfg(feature = "structural_renderer")]
- let pack_empty = quote! {
- #[derive(::serde::Serialize, ::mingling::StructuralData, ::mingling::Grouped, Default)]
- pub struct ResultEmpty;
- };
-
- #[cfg(not(feature = "structural_renderer"))]
- let pack_empty = quote! {
- #[derive(::mingling::Grouped, Default)]
- pub struct ResultEmpty;
- };
-
- let expanded = quote! {
- ::mingling::macros::pack!(ErrorRendererNotFound = String);
- ::mingling::macros::pack!(ErrorDispatcherNotFound = Vec<String>);
- #pack_empty
- };
- TokenStream::from(expanded)
-}
-
-#[allow(clippy::too_many_lines)]
-pub(crate) fn program_final_gen_impl(_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: Option<HashMap<String, String>> = load_pathf_map();
-
- #[cfg(feature = "pathf")]
- let pathf_hint: proc_macro2::TokenStream = if pathf_map.is_none() {
- quote! {
- compile_error!(
-"Cannot load type mapping computed by `pathf`.
-If not yet configured, execute `mingling::build::analyze_and_build_type_mapping()` in your `build.rs`.
-
-fn main() {
- // Require features: [\"builds\", \"pathf\"]
- mingling::build::analyze_and_build_type_mapping().unwrap();
- ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^\\__ Write to `build.rs`
-
-}
- ");
- }
- } else {
- quote! {}
- };
-
- #[cfg(not(feature = "pathf"))]
- let pathf_hint: proc_macro2::TokenStream = quote! {};
-
- let pathf_map: HashMap<String, String> = if cfg!(feature = "pathf") {
- pathf_map.unwrap_or_default()
- } else {
- 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! {
- #pathf_hint
-
- #[derive(Debug, PartialEq, Eq, Clone, Copy)]
- #[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)
-}
diff --git a/mingling_macros/src/func/group_structural.rs b/mingling_macros/src/func/group_structural.rs
new file mode 100644
index 0000000..2bd2f83
--- /dev/null
+++ b/mingling_macros/src/func/group_structural.rs
@@ -0,0 +1,124 @@
+use proc_macro::TokenStream;
+use quote::quote;
+use syn::{Ident, TypePath};
+
+use crate::get_global_set;
+
+/// `group_structural!` — like `group!` but also marks the type as supporting
+/// structured output via `StructuralData`.
+pub(crate) fn group_structural(input: TokenStream) -> TokenStream {
+ // Parse the same input as group!
+ let input_parsed = syn::parse_macro_input!(input as GroupStructuralInput);
+
+ let is_aliased = matches!(&input_parsed, GroupStructuralInput::Aliased { .. });
+
+ let (type_path, type_name, alias_stmt) = match &input_parsed {
+ GroupStructuralInput::Plain(type_path) => {
+ let name = type_path
+ .path
+ .segments
+ .last()
+ .expect("TypePath must have at least one segment")
+ .ident
+ .clone();
+ (type_path.clone(), name, quote! {})
+ }
+ GroupStructuralInput::Aliased { alias, type_path } => {
+ let alias_stmt = quote! {
+ pub(crate) type #alias = #type_path;
+ };
+ (type_path.clone(), alias.clone(), alias_stmt)
+ }
+ };
+
+ let type_name_str = type_name.to_string();
+
+ // Register in STRUCTURED_TYPES
+ get_global_set(&crate::STRUCTURED_TYPES)
+ .lock()
+ .unwrap()
+ .insert(type_name_str);
+
+ let program_path = crate::default_program_path();
+
+ // Generate unique module name
+ let segments: Vec<String> = type_path
+ .path
+ .segments
+ .iter()
+ .map(|seg| seg.ident.to_string().to_lowercase())
+ .collect();
+ let module_name = Ident::new(
+ &format!("internal_group_{}", segments.join("_")),
+ proc_macro2::Span::call_site(),
+ );
+
+ // Generate the appropriate `use` statement for the original type
+ let type_use = if type_path.path.segments.len() > 1 {
+ quote! { #[allow(unused_imports)] use #type_path; }
+ } else {
+ let ident = type_path
+ .path
+ .segments
+ .last()
+ .expect("TypePath must have at least one segment")
+ .ident
+ .clone();
+ quote! { #[allow(unused_imports)] use super::#ident; }
+ };
+
+ let alias_use = if is_aliased {
+ quote! { use super::#type_name; }
+ } else {
+ quote! {}
+ };
+
+ let expanded = quote! {
+ #alias_stmt
+ #[allow(non_camel_case_types)]
+ mod #module_name {
+ use #program_path as __MinglingProgram;
+ #type_use
+ #alias_use
+
+ /// SAFETY: This is an internal implementation of the `pack_structural!` macro,
+ /// guaranteeing that the enum value registered by the `register_type!` macro
+ /// is exactly the same as the actual return value,
+ /// which can be confirmed via the `Ident` in the `quote!` block.
+ unsafe impl ::mingling::Grouped<__MinglingProgram> for #type_name {
+ fn member_id() -> __MinglingProgram {
+ __MinglingProgram::#type_name
+ }
+ }
+
+ impl ::mingling::__private::StructuralDataSealed<crate::ThisProgram> for #type_name {}
+ impl ::mingling::__private::StructuralData<crate::ThisProgram> for #type_name {}
+
+ ::mingling::macros::register_type!(#type_name);
+ }
+ };
+
+ expanded.into()
+}
+
+/// Input for `group_structural!` — same format as `group!`.
+enum GroupStructuralInput {
+ Plain(TypePath),
+ Aliased { alias: Ident, type_path: TypePath },
+}
+
+impl syn::parse::Parse for GroupStructuralInput {
+ fn parse(input: syn::parse::ParseStream) -> syn::Result<Self> {
+ let fork = input.fork();
+ let _first: Ident = fork.parse()?;
+ if fork.peek(syn::Token![=]) {
+ let alias: Ident = input.parse()?;
+ let _eq: syn::Token![=] = input.parse()?;
+ let type_path: TypePath = input.parse()?;
+ Ok(GroupStructuralInput::Aliased { alias, type_path })
+ } else {
+ let type_path: TypePath = input.parse()?;
+ Ok(GroupStructuralInput::Plain(type_path))
+ }
+ }
+}
diff --git a/mingling_macros/src/func/pack_err.rs b/mingling_macros/src/func/pack_err.rs
index 2a318bc..8b224b0 100644
--- a/mingling_macros/src/func/pack_err.rs
+++ b/mingling_macros/src/func/pack_err.rs
@@ -105,105 +105,3 @@ pub(crate) fn pack_err(input: TokenStream) -> TokenStream {
}
}
}
-
-/// `pack_err_structural!` — like `pack_err!` but also marks the type as
-/// supporting structured output via `StructuralData`.
-///
-/// # Syntax
-///
-/// ```rust,ignore
-/// pack_err_structural!(ErrorNotFound);
-/// pack_err_structural!(ErrorNotDir = PathBuf);
-/// ```
-///
-/// This is equivalent to:
-/// ```rust,ignore
-/// pack_err!(ErrorNotFound);
-/// impl ::mingling::__private::StructuralDataSealed for ErrorNotFound {}
-/// impl ::mingling::__private::StructuralData for ErrorNotFound {}
-/// ```
-#[cfg(feature = "structural_renderer")]
-pub(crate) fn pack_err_structural(input: TokenStream) -> TokenStream {
- let parsed = parse_macro_input!(input as PackErrInput);
-
- let type_name = match &parsed {
- PackErrInput::Simple { type_name } => type_name.clone(),
- PackErrInput::Typed { type_name, .. } => type_name.clone(),
- };
-
- // Register in STRUCTURED_TYPES
- let type_name_str = type_name.to_string();
- crate::get_global_set(&crate::STRUCTURED_TYPES)
- .lock()
- .unwrap()
- .insert(type_name_str);
-
- let structural_data = quote! {
- impl ::mingling::__private::StructuralDataSealed<crate::ThisProgram> for #type_name {}
- impl ::mingling::__private::StructuralData<crate::ThisProgram> for #type_name {}
- };
-
- // Generate the struct + impls (same as pack_err! but with Serialize derive + sealed)
- match parsed {
- PackErrInput::Simple { type_name } => {
- let name_str = type_name.to_string();
- let snake_name = snake_case!(&name_str);
-
- let expanded = quote! {
- #[derive(::mingling::Grouped, ::serde::Serialize)]
- pub struct #type_name {
- /// The snake_case name of this error, automatically set at compile time.
- pub name: String,
- }
-
- impl ::std::default::Default for #type_name {
- fn default() -> Self {
- Self {
- name: #snake_name.into(),
- }
- }
- }
-
- ::mingling::macros::register_type!(#type_name);
-
- #structural_data
- };
-
- expanded.into()
- }
- PackErrInput::Typed {
- type_name,
- inner_type,
- } => {
- let name_str = type_name.to_string();
- let snake_name = snake_case!(&name_str);
-
- let expanded = quote! {
- #[derive(::mingling::Grouped, ::serde::Serialize)]
- pub struct #type_name {
- /// The snake_case name of this error, automatically set at compile time.
- pub name: String,
- /// Additional context info for this error.
- pub info: #inner_type,
- }
-
- impl #type_name {
- /// Creates a new error with the given info.
- /// The `name` field is automatically set to the snake_case of the struct name.
- pub fn new(info: #inner_type) -> Self {
- Self {
- name: #snake_name.into(),
- info,
- }
- }
- }
-
- ::mingling::macros::register_type!(#type_name);
-
- #structural_data
- };
-
- expanded.into()
- }
- }
-}
diff --git a/mingling_macros/src/func/pack_err_structural.rs b/mingling_macros/src/func/pack_err_structural.rs
new file mode 100644
index 0000000..7d3a6f8
--- /dev/null
+++ b/mingling_macros/src/func/pack_err_structural.rs
@@ -0,0 +1,119 @@
+use just_fmt::snake_case;
+use proc_macro::TokenStream;
+use quote::quote;
+use syn::{Ident, Token, Type, parse_macro_input};
+
+/// `pack_err_structural!` — like `pack_err!` but also marks the type as
+/// supporting structured output via `StructuralData`.
+pub(crate) fn pack_err_structural(input: TokenStream) -> TokenStream {
+ let parsed = parse_macro_input!(input as PackErrInput);
+
+ let type_name = match &parsed {
+ PackErrInput::Simple { type_name } => type_name.clone(),
+ PackErrInput::Typed { type_name, .. } => type_name.clone(),
+ };
+
+ // Register in STRUCTURED_TYPES
+ let type_name_str = type_name.to_string();
+ crate::get_global_set(&crate::STRUCTURED_TYPES)
+ .lock()
+ .unwrap()
+ .insert(type_name_str);
+
+ let structural_data = quote! {
+ impl ::mingling::__private::StructuralDataSealed<crate::ThisProgram> for #type_name {}
+ impl ::mingling::__private::StructuralData<crate::ThisProgram> for #type_name {}
+ };
+
+ // Generate the struct + impls (same as pack_err! but with Serialize derive + sealed)
+ match parsed {
+ PackErrInput::Simple { type_name } => {
+ let name_str = type_name.to_string();
+ let snake_name = snake_case!(&name_str);
+
+ let expanded = quote! {
+ #[derive(::mingling::Grouped, ::serde::Serialize)]
+ pub struct #type_name {
+ /// The snake_case name of this error, automatically set at compile time.
+ pub name: String,
+ }
+
+ impl ::std::default::Default for #type_name {
+ fn default() -> Self {
+ Self {
+ name: #snake_name.into(),
+ }
+ }
+ }
+
+ ::mingling::macros::register_type!(#type_name);
+
+ #structural_data
+ };
+
+ expanded.into()
+ }
+ PackErrInput::Typed {
+ type_name,
+ inner_type,
+ } => {
+ let name_str = type_name.to_string();
+ let snake_name = snake_case!(&name_str);
+
+ let expanded = quote! {
+ #[derive(::mingling::Grouped, ::serde::Serialize)]
+ pub struct #type_name {
+ /// The snake_case name of this error, automatically set at compile time.
+ pub name: String,
+ /// Additional context info for this error.
+ pub info: #inner_type,
+ }
+
+ impl #type_name {
+ /// Creates a new error with the given info.
+ /// The `name` field is automatically set to the snake_case of the struct name.
+ pub fn new(info: #inner_type) -> Self {
+ Self {
+ name: #snake_name.into(),
+ info,
+ }
+ }
+ }
+
+ ::mingling::macros::register_type!(#type_name);
+
+ #structural_data
+ };
+
+ expanded.into()
+ }
+ }
+}
+
+// Re-use pack_err's input parser
+enum PackErrInput {
+ Simple {
+ type_name: Ident,
+ },
+ Typed {
+ type_name: Ident,
+ inner_type: Box<Type>,
+ },
+}
+
+impl syn::parse::Parse for PackErrInput {
+ fn parse(input: syn::parse::ParseStream) -> syn::Result<Self> {
+ let type_name: Ident = input.parse()?;
+
+ if input.peek(Token![=]) {
+ input.parse::<Token![=]>()?;
+ let inner_type: Type = input.parse()?;
+ Ok(PackErrInput::Typed {
+ type_name,
+ inner_type: Box::new(inner_type),
+ })
+ } else {
+ Ok(PackErrInput::Simple { type_name })
+ }
+ }
+}
diff --git a/mingling_macros/src/func/pack_structural.rs b/mingling_macros/src/func/pack_structural.rs
new file mode 100644
index 0000000..9399959
--- /dev/null
+++ b/mingling_macros/src/func/pack_structural.rs
@@ -0,0 +1,168 @@
+use proc_macro::TokenStream;
+use quote::quote;
+use syn::Ident;
+
+use crate::get_global_set;
+
+/// `pack_structural!` — like `pack!` but also marks the type as supporting
+/// structured output via `StructuralData`.
+pub(crate) fn pack_structural(input: TokenStream) -> TokenStream {
+ // Parse same input format as `pack!`
+ let input_parsed = syn::parse_macro_input!(input as PackStructuralInput);
+ let type_name = input_parsed.type_name;
+ let inner_type = input_parsed.inner_type;
+ let attrs = input_parsed.attrs;
+ let program_path = crate::default_program_path();
+
+ // Register in STRUCTURED_TYPES
+ let type_name_str = type_name.to_string();
+ get_global_set(&crate::STRUCTURED_TYPES)
+ .lock()
+ .unwrap()
+ .insert(type_name_str);
+
+ // Struct definition (with Serialize derive, same as pack! under structural_renderer)
+ #[cfg(not(feature = "structural_renderer"))]
+ let struct_def = quote! {
+ #(#attrs)*
+ pub struct #type_name {
+ pub inner: #inner_type,
+ }
+ };
+
+ #[cfg(feature = "structural_renderer")]
+ let struct_def = quote! {
+ #(#attrs)*
+ #[derive(serde::Serialize)]
+ pub struct #type_name {
+ pub inner: #inner_type,
+ }
+ };
+
+ // Helper impls (same as pack!)
+ let new_impl = quote! {
+ impl #type_name {
+ pub fn new(inner: #inner_type) -> Self {
+ Self { inner }
+ }
+ }
+ };
+
+ let from_into_impl = quote! {
+ impl From<#inner_type> for #type_name {
+ fn from(inner: #inner_type) -> Self {
+ Self::new(inner)
+ }
+ }
+ impl From<#type_name> for #inner_type {
+ fn from(wrapper: #type_name) -> #inner_type {
+ wrapper.inner
+ }
+ }
+ };
+
+ let as_ref_impl = quote! {
+ impl ::std::convert::AsRef<#inner_type> for #type_name {
+ fn as_ref(&self) -> &#inner_type {
+ &self.inner
+ }
+ }
+ impl ::std::convert::AsMut<#inner_type> for #type_name {
+ fn as_mut(&mut self) -> &mut #inner_type {
+ &mut self.inner
+ }
+ }
+ };
+
+ let deref_impl = quote! {
+ impl ::std::ops::Deref for #type_name {
+ type Target = #inner_type;
+ fn deref(&self) -> &Self::Target {
+ &self.inner
+ }
+ }
+ impl ::std::ops::DerefMut for #type_name {
+ fn deref_mut(&mut self) -> &mut Self::Target {
+ &mut self.inner
+ }
+ }
+ };
+
+ let default_impl = quote! {
+ impl ::std::default::Default for #type_name
+ where
+ #inner_type: ::std::default::Default,
+ {
+ fn default() -> Self {
+ Self::new(::std::default::Default::default())
+ }
+ }
+ };
+
+ let register_impl = quote! {
+ ::mingling::macros::register_type!(#type_name);
+ };
+
+ // StructuralData impl + sealed + registration
+ let structural_impl = quote! {
+ impl ::mingling::__private::StructuralDataSealed<crate::ThisProgram> for #type_name {}
+ impl ::mingling::__private::StructuralData<crate::ThisProgram> for #type_name {}
+ };
+
+ let expanded = quote! {
+ #struct_def
+
+ #new_impl
+ #from_into_impl
+ #as_ref_impl
+ #deref_impl
+ #default_impl
+ #register_impl
+ #structural_impl
+
+ impl Into<::mingling::AnyOutput<#program_path>> for #type_name {
+ fn into(self) -> ::mingling::AnyOutput<#program_path> {
+ ::mingling::AnyOutput::new(self)
+ }
+ }
+
+ impl Into<::mingling::ChainProcess<#program_path>> for #type_name {
+ fn into(self) -> ::mingling::ChainProcess<#program_path> {
+ ::mingling::AnyOutput::new(self).route_chain()
+ }
+ }
+
+ /// SAFETY: This is an internal implementation of the `pack_structural!` macro,
+ /// guaranteeing that the enum value registered by the `register_type!` macro
+ /// is exactly the same as the actual return value,
+ /// which can be confirmed via the `Ident` in the `quote!` block.
+ unsafe impl ::mingling::Grouped<#program_path> for #type_name {
+ fn member_id() -> #program_path {
+ #program_path::#type_name
+ }
+ }
+ };
+
+ expanded.into()
+}
+
+/// Input for `pack_structural!` — same format as `pack!`.
+struct PackStructuralInput {
+ attrs: Vec<syn::Attribute>,
+ type_name: Ident,
+ inner_type: syn::Type,
+}
+
+impl syn::parse::Parse for PackStructuralInput {
+ fn parse(input: syn::parse::ParseStream) -> syn::Result<Self> {
+ let attrs = input.call(syn::Attribute::parse_outer)?;
+ let type_name: Ident = input.parse()?;
+ input.parse::<syn::Token![=]>()?;
+ let inner_type: syn::Type = input.parse()?;
+ Ok(PackStructuralInput {
+ attrs,
+ type_name,
+ inner_type,
+ })
+ }
+}
diff --git a/mingling_macros/src/func/program_comp_gen.rs b/mingling_macros/src/func/program_comp_gen.rs
new file mode 100644
index 0000000..b6b2546
--- /dev/null
+++ b/mingling_macros/src/func/program_comp_gen.rs
@@ -0,0 +1,80 @@
+use proc_macro::TokenStream;
+use quote::quote;
+
+#[cfg(feature = "comp")]
+pub(crate) fn program_comp_gen_impl(_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::Grouped;
+
+ let read_ctx = ::mingling::ShellContext::try_from(prev.inner);
+ match read_ctx {
+ Ok(ctx) => {
+ let suggest = ::mingling::CompletionHelper::exec_completion::<crate::ThisProgram>(&ctx);
+ ::mingling::Routable::<crate::ThisProgram>::to_render(crate::CompletionSuggest::new((ctx, suggest)))
+ }
+ 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::Grouped;
+
+ let read_ctx = ::mingling::ShellContext::try_from(prev.inner);
+ match read_ctx {
+ Ok(ctx) => {
+ let suggest = ::mingling::CompletionHelper::exec_completion::<crate::ThisProgram>(&ctx);
+ ::mingling::Routable::<crate::ThisProgram>::to_render(crate::CompletionSuggest::new((ctx, suggest)))
+ }
+ 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::Grouped;
+ ::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)
+}
diff --git a/mingling_macros/src/func/program_fallback_gen.rs b/mingling_macros/src/func/program_fallback_gen.rs
new file mode 100644
index 0000000..3d095e5
--- /dev/null
+++ b/mingling_macros/src/func/program_fallback_gen.rs
@@ -0,0 +1,23 @@
+use proc_macro::TokenStream;
+use quote::quote;
+
+pub(crate) fn program_fallback_gen_impl(_input: TokenStream) -> TokenStream {
+ #[cfg(feature = "structural_renderer")]
+ let pack_empty = quote! {
+ #[derive(::serde::Serialize, ::mingling::StructuralData, ::mingling::Grouped, Default)]
+ pub struct ResultEmpty;
+ };
+
+ #[cfg(not(feature = "structural_renderer"))]
+ let pack_empty = quote! {
+ #[derive(::mingling::Grouped, Default)]
+ pub struct ResultEmpty;
+ };
+
+ let expanded = quote! {
+ ::mingling::macros::pack!(ErrorRendererNotFound = String);
+ ::mingling::macros::pack!(ErrorDispatcherNotFound = Vec<String>);
+ #pack_empty
+ };
+ TokenStream::from(expanded)
+}
diff --git a/mingling_macros/src/func/program_final_gen.rs b/mingling_macros/src/func/program_final_gen.rs
new file mode 100644
index 0000000..d3af3b3
--- /dev/null
+++ b/mingling_macros/src/func/program_final_gen.rs
@@ -0,0 +1,465 @@
+use std::collections::HashMap;
+
+use proc_macro::TokenStream;
+use quote::quote;
+
+use crate::CHAINS;
+use crate::CHAINS_EXIST;
+#[cfg(feature = "dispatch_tree")]
+use crate::COMPILE_TIME_DISPATCHERS;
+#[cfg(feature = "comp")]
+use crate::COMPLETIONS;
+use crate::HELP_REQUESTS;
+use crate::PACKED_TYPES;
+use crate::RENDERERS;
+use crate::RENDERERS_EXIST;
+#[cfg(feature = "structural_renderer")]
+use crate::STRUCTURAL_RENDERERS;
+use crate::get_global_set;
+#[cfg(feature = "dispatch_tree")]
+use crate::systems::dispatch_tree_gen;
+
+#[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`.
+fn load_pathf_map() -> Option<std::collections::HashMap<String, String>> {
+ if !cfg!(feature = "pathf") {
+ return None;
+ }
+ let out_dir = std::env::var("OUT_DIR").ok()?;
+ let crate_name = std::env::var("CARGO_PKG_NAME").ok()?;
+ let path = std::path::Path::new(&out_dir)
+ .join(&crate_name)
+ .join("type_using.rs");
+ let content = std::fs::read_to_string(&path).ok()?;
+ Some(
+ 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(),
+ )
+}
+
+/// 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 }
+ }
+}
+
+#[allow(clippy::too_many_lines)]
+pub(crate) fn program_final_gen_impl(_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: Option<HashMap<String, String>> = load_pathf_map();
+
+ #[cfg(feature = "pathf")]
+ let pathf_hint: proc_macro2::TokenStream = if pathf_map.is_none() {
+ quote! {
+ compile_error!(
+"Cannot load type mapping computed by `pathf`.
+If not yet configured, execute `mingling::build::analyze_and_build_type_mapping()` in your `build.rs`.
+
+fn main() {
+ // Require features: [\"builds\", \"pathf\"]
+ mingling::build::analyze_and_build_type_mapping().unwrap();
+ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^\\__ Write to `build.rs`
+
+}
+ ");
+ }
+ } else {
+ quote! {}
+ };
+
+ #[cfg(not(feature = "pathf"))]
+ let pathf_hint: proc_macro2::TokenStream = quote! {};
+
+ let pathf_map: HashMap<String, String> = if cfg!(feature = "pathf") {
+ pathf_map.unwrap_or_default()
+ } else {
+ 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! {
+ #pathf_hint
+
+ #[derive(Debug, PartialEq, Eq, Clone, Copy)]
+ #[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)
+}
diff --git a/mingling_macros/src/func/r_append.rs b/mingling_macros/src/func/r_append.rs
new file mode 100644
index 0000000..247da27
--- /dev/null
+++ b/mingling_macros/src/func/r_append.rs
@@ -0,0 +1,29 @@
+use proc_macro::TokenStream;
+use quote::quote;
+
+use crate::func::r_print::AppendInput;
+
+pub(crate) fn r_append(input: TokenStream) -> TokenStream {
+ let parsed: AppendInput = match syn::parse(input) {
+ Ok(p) => p,
+ Err(e) => return e.to_compile_error().into(),
+ };
+
+ let dst_ident = parsed.dst.clone();
+ let src_tokens = parsed.src;
+
+ let expanded = match dst_ident {
+ Some(dst) => {
+ quote! {
+ #dst.append_other(#src_tokens);
+ }
+ }
+ None => {
+ quote! {
+ __render_result_buffer.append_other(#src_tokens);
+ }
+ }
+ };
+
+ expanded.into()
+}
diff --git a/mingling_macros/src/func/r_eprint.rs b/mingling_macros/src/func/r_eprint.rs
new file mode 100644
index 0000000..97023b0
--- /dev/null
+++ b/mingling_macros/src/func/r_eprint.rs
@@ -0,0 +1,7 @@
+use proc_macro::TokenStream;
+
+use crate::func::r_print::expand_print;
+
+pub(crate) fn r_eprint(input: TokenStream) -> TokenStream {
+ expand_print(input, "eprint")
+}
diff --git a/mingling_macros/src/func/r_eprintln.rs b/mingling_macros/src/func/r_eprintln.rs
new file mode 100644
index 0000000..6bb86b8
--- /dev/null
+++ b/mingling_macros/src/func/r_eprintln.rs
@@ -0,0 +1,7 @@
+use proc_macro::TokenStream;
+
+use crate::func::r_print::expand_print;
+
+pub(crate) fn r_eprintln(input: TokenStream) -> TokenStream {
+ expand_print(input, "eprintln")
+}
diff --git a/mingling_macros/src/func/r_print.rs b/mingling_macros/src/func/r_print.rs
index 86ea52f..20f15b8 100644
--- a/mingling_macros/src/func/r_print.rs
+++ b/mingling_macros/src/func/r_print.rs
@@ -9,7 +9,7 @@ use syn::{Ident, Token};
/// Two forms:
/// - `(ident, format_args...)` — explicit buffer
/// - `(format_args...)` — implicit `__render_result_buffer`
-enum PrintInput {
+pub(crate) enum PrintInput {
Explicit { dst: Ident, args: TokenStream2 },
Implicit { args: TokenStream2 },
}
@@ -29,7 +29,7 @@ impl Parse for PrintInput {
}
}
-fn expand_print(input: TokenStream, method: &str) -> TokenStream {
+pub(crate) fn expand_print(input: TokenStream, method: &str) -> TokenStream {
let parsed: PrintInput = match syn::parse(input) {
Ok(p) => p,
Err(e) => return e.to_compile_error().into(),
@@ -53,30 +53,18 @@ fn expand_print(input: TokenStream, method: &str) -> TokenStream {
expanded.into()
}
-pub(crate) fn r_println(input: TokenStream) -> TokenStream {
- expand_print(input, "println")
-}
-
pub(crate) fn r_print(input: TokenStream) -> TokenStream {
expand_print(input, "print")
}
-pub(crate) fn r_eprintln(input: TokenStream) -> TokenStream {
- expand_print(input, "eprintln")
-}
-
-pub(crate) fn r_eprint(input: TokenStream) -> TokenStream {
- expand_print(input, "eprint")
-}
-
/// Parsed input for `r_append!`.
///
/// Two forms:
/// - `(dst, src)` — explicit buffer and source
/// - `(src)` — implicit `__render_result_buffer`
-struct AppendInput {
- dst: Option<Ident>,
- src: proc_macro2::TokenStream,
+pub(crate) struct AppendInput {
+ pub(crate) dst: Option<Ident>,
+ pub(crate) src: proc_macro2::TokenStream,
}
impl Parse for AppendInput {
@@ -95,33 +83,3 @@ impl Parse for AppendInput {
}
}
}
-
-/// `r_append!` macro: appends the contents of another `RenderResult` to this one.
-///
-/// Two forms:
-/// - `r_append!(dst, src)` — appends `src` into `dst`
-/// - `r_append!(src)` — appends `src` into `__render_result_buffer`
-pub(crate) fn r_append(input: TokenStream) -> TokenStream {
- let parsed: AppendInput = match syn::parse(input) {
- Ok(p) => p,
- Err(e) => return e.to_compile_error().into(),
- };
-
- let dst_ident = parsed.dst.clone();
- let src_tokens = parsed.src;
-
- let expanded = match dst_ident {
- Some(dst) => {
- quote! {
- #dst.append_other(#src_tokens);
- }
- }
- None => {
- quote! {
- __render_result_buffer.append_other(#src_tokens);
- }
- }
- };
-
- expanded.into()
-}
diff --git a/mingling_macros/src/func/r_println.rs b/mingling_macros/src/func/r_println.rs
new file mode 100644
index 0000000..2ba915a
--- /dev/null
+++ b/mingling_macros/src/func/r_println.rs
@@ -0,0 +1,7 @@
+use proc_macro::TokenStream;
+
+use crate::func::r_print::expand_print;
+
+pub(crate) fn r_println(input: TokenStream) -> TokenStream {
+ expand_print(input, "println")
+}
diff --git a/mingling_macros/src/func/register_chain.rs b/mingling_macros/src/func/register_chain.rs
new file mode 100644
index 0000000..4cd139b
--- /dev/null
+++ b/mingling_macros/src/func/register_chain.rs
@@ -0,0 +1,7 @@
+use proc_macro::TokenStream;
+
+use crate::attr::chain;
+
+pub(crate) fn register_chain_impl(input: TokenStream) -> TokenStream {
+ chain::register_chain(input)
+}
diff --git a/mingling_macros/src/func/register_dispatcher.rs b/mingling_macros/src/func/register_dispatcher.rs
new file mode 100644
index 0000000..0b3fbdf
--- /dev/null
+++ b/mingling_macros/src/func/register_dispatcher.rs
@@ -0,0 +1,75 @@
+#[cfg(feature = "dispatch_tree")]
+use just_fmt::snake_case;
+use proc_macro::TokenStream;
+use quote::quote;
+#[cfg(feature = "dispatch_tree")]
+use syn::parse::{Parse, ParseStream};
+#[cfg(feature = "dispatch_tree")]
+use syn::{Ident, LitStr, Result as SynResult, Token};
+
+#[cfg(feature = "dispatch_tree")]
+use crate::COMPILE_TIME_DISPATCHERS;
+#[cfg(feature = "dispatch_tree")]
+use crate::get_global_set;
+
+#[cfg(feature = "dispatch_tree")]
+struct RegisterDispatcherInput {
+ node_name: LitStr,
+ dispatcher_type: Ident,
+ entry_name: Ident,
+}
+
+#[cfg(feature = "dispatch_tree")]
+impl Parse for RegisterDispatcherInput {
+ fn parse(input: ParseStream) -> SynResult<Self> {
+ let node_name: LitStr = input.parse()?;
+ input.parse::<Token![,]>()?;
+ let dispatcher_type: Ident = input.parse()?;
+ input.parse::<Token![,]>()?;
+ let entry_name: Ident = input.parse()?;
+ Ok(RegisterDispatcherInput {
+ node_name,
+ dispatcher_type,
+ entry_name,
+ })
+ }
+}
+
+#[cfg(feature = "dispatch_tree")]
+pub(crate) fn register_dispatcher(input: TokenStream) -> TokenStream {
+ let RegisterDispatcherInput {
+ node_name,
+ dispatcher_type,
+ entry_name,
+ } = syn::parse_macro_input!(input as RegisterDispatcherInput);
+
+ let node_name_str = node_name.value();
+ let static_name = format!(
+ "__internal_dispatcher_{}",
+ snake_case!(node_name_str.clone())
+ );
+ let static_ident = Ident::new(&static_name, proc_macro2::Span::call_site());
+
+ // Register node info in the global collection at compile time
+ // Format: "node.name:DispatcherType:EntryName"
+ get_global_set(&COMPILE_TIME_DISPATCHERS)
+ .lock()
+ .unwrap()
+ .insert(format!(
+ "{}:{}:{}",
+ node_name_str, dispatcher_type, entry_name
+ ));
+
+ let expanded = quote! {
+ #[doc(hidden)]
+ #[allow(nonstandard_style)]
+ pub static #static_ident: #dispatcher_type = #dispatcher_type;
+ };
+
+ expanded.into()
+}
+
+#[cfg(not(feature = "dispatch_tree"))]
+pub(crate) fn register_dispatcher(_input: TokenStream) -> TokenStream {
+ quote! {}.into()
+}
diff --git a/mingling_macros/src/func/register_help.rs b/mingling_macros/src/func/register_help.rs
new file mode 100644
index 0000000..e715244
--- /dev/null
+++ b/mingling_macros/src/func/register_help.rs
@@ -0,0 +1,71 @@
+use proc_macro::TokenStream;
+use quote::ToTokens;
+use syn::TypePath;
+use syn::spanned::Spanned;
+
+use crate::get_global_set;
+
+pub(crate) fn register_help(input: TokenStream) -> TokenStream {
+ // Parse the input as a comma-separated list of arguments
+ let input_parsed = syn::parse_macro_input!(input with syn::punctuated::Punctuated<syn::Expr, syn::Token![,]>::parse_terminated);
+
+ // Check if there are exactly two elements
+ if input_parsed.len() != 2 {
+ return syn::Error::new(
+ input_parsed.span(),
+ "Expected exactly two comma-separated arguments: `EntryType, StructName`",
+ )
+ .to_compile_error()
+ .into();
+ }
+
+ // Extract the two elements
+ let entry_type_expr = &input_parsed[0];
+ let struct_name_expr = &input_parsed[1];
+
+ // Convert expressions to TypePath and Ident
+ let entry_type = match syn::parse2::<TypePath>(entry_type_expr.to_token_stream()) {
+ Ok(ty) => ty,
+ Err(e) => return e.to_compile_error().into(),
+ };
+
+ let struct_name = match syn::parse2::<syn::Ident>(struct_name_expr.to_token_stream()) {
+ Ok(ident) => ident,
+ Err(e) => return e.to_compile_error().into(),
+ };
+
+ // Register the help request mapping
+ let help_entry = build_help_entry(&struct_name, &entry_type);
+ let entry_str = help_entry.to_string();
+
+ // Check if entry was already pre-inserted by `#[help]` attribute
+ let mut helps = get_global_set(&crate::HELP_REQUESTS).lock().unwrap();
+ if helps.contains(&entry_str) {
+ // Already registered by `#[help]`, no duplicate check needed
+ return quote::quote! {}.into();
+ }
+
+ // Check for duplicate variant (different struct, same type)
+ let variant_name = entry_type.path.segments.last().unwrap().ident.to_string();
+ if let Err(err) =
+ crate::check_duplicate_variant(&helps, &entry_str, &variant_name, "help", entry_type.span())
+ {
+ return err.into();
+ }
+
+ helps.insert(entry_str);
+
+ quote::quote! {}.into()
+}
+
+fn build_help_entry(struct_name: &syn::Ident, entry_type: &TypePath) -> proc_macro2::TokenStream {
+ let enum_variant = entry_type.path.segments.last().unwrap().ident.clone();
+ quote::quote! {
+ Self::#enum_variant => {
+ // SAFETY: The member_id check ensures that `any` contains a value of type `#entry_type`,
+ // so downcasting to `#entry_type` is safe.
+ let value = unsafe { any.downcast::<#entry_type>().unwrap_unchecked() };
+ <#struct_name as ::mingling::HelpRequest>::render_help(value)
+ }
+ }
+}
diff --git a/mingling_macros/src/func/register_renderer.rs b/mingling_macros/src/func/register_renderer.rs
new file mode 100644
index 0000000..c5324d9
--- /dev/null
+++ b/mingling_macros/src/func/register_renderer.rs
@@ -0,0 +1,7 @@
+use proc_macro::TokenStream;
+
+use crate::attr::renderer;
+
+pub(crate) fn register_renderer_impl(input: TokenStream) -> TokenStream {
+ renderer::register_renderer(input)
+}
diff --git a/mingling_macros/src/func/register_type.rs b/mingling_macros/src/func/register_type.rs
new file mode 100644
index 0000000..593a2ec
--- /dev/null
+++ b/mingling_macros/src/func/register_type.rs
@@ -0,0 +1,17 @@
+use proc_macro::TokenStream;
+use syn::parse_macro_input;
+
+use crate::PACKED_TYPES;
+use crate::get_global_set;
+
+pub(crate) fn register_type_impl(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()
+}
diff --git a/mingling_macros/src/func/render_route.rs b/mingling_macros/src/func/render_route.rs
new file mode 100644
index 0000000..8f390e6
--- /dev/null
+++ b/mingling_macros/src/func/render_route.rs
@@ -0,0 +1,15 @@
+use proc_macro::TokenStream;
+use quote::quote;
+use syn::parse_macro_input;
+
+/// Routes errors to the rendering pipeline instead of the chain pipeline.
+pub(crate) fn render_route(input: TokenStream) -> TokenStream {
+ let expr = parse_macro_input!(input as syn::Expr);
+ let expanded = quote! {
+ match #expr {
+ Ok(r) => r,
+ Err(e) => return <crate::ThisProgram as ::mingling::ProgramCollect>::render(::mingling::AnyOutput::new(e)),
+ }
+ };
+ TokenStream::from(expanded)
+}
diff --git a/mingling_macros/src/func/route.rs b/mingling_macros/src/func/route.rs
new file mode 100644
index 0000000..b338fb0
--- /dev/null
+++ b/mingling_macros/src/func/route.rs
@@ -0,0 +1,16 @@
+use proc_macro::TokenStream;
+use quote::quote;
+use syn::parse_macro_input;
+
+/// Routes execution depending on a condition — early-returns the error from a `Result`,
+/// converting the `Ok` branch to the next chain process value.
+pub(crate) fn route(input: TokenStream) -> TokenStream {
+ let expr = parse_macro_input!(input as syn::Expr);
+ let expanded = quote! {
+ match #expr {
+ Ok(r) => r,
+ Err(e) => return ::mingling::Routable::to_chain(e),
+ }
+ };
+ TokenStream::from(expanded)
+}
diff --git a/mingling_macros/src/func/suggest.rs b/mingling_macros/src/func/suggest.rs
index 0f2026f..85f1afe 100644
--- a/mingling_macros/src/func/suggest.rs
+++ b/mingling_macros/src/func/suggest.rs
@@ -70,24 +70,3 @@ pub(crate) fn suggest(input: TokenStream) -> TokenStream {
expanded.into()
}
-
-pub(crate) fn suggest_enum(input: TokenStream) -> TokenStream {
- let enum_type = parse_macro_input!(input as syn::Type);
-
- let expanded = quote! {{
- let mut enum_suggest = ::mingling::Suggest::new();
- for (name, desc) in <#enum_type>::enums() {
- if desc.is_empty() {
- enum_suggest.insert(::mingling::SuggestItem::new(name.to_string()));
- } else {
- enum_suggest.insert(::mingling::SuggestItem::new_with_desc(
- name.to_string(),
- desc.to_string(),
- ));
- }
- }
- enum_suggest
- }};
-
- expanded.into()
-}
diff --git a/mingling_macros/src/func/suggest_enum.rs b/mingling_macros/src/func/suggest_enum.rs
new file mode 100644
index 0000000..c8e9db0
--- /dev/null
+++ b/mingling_macros/src/func/suggest_enum.rs
@@ -0,0 +1,24 @@
+use proc_macro::TokenStream;
+use quote::quote;
+use syn::parse_macro_input;
+
+pub(crate) fn suggest_enum(input: TokenStream) -> TokenStream {
+ let enum_type = parse_macro_input!(input as syn::Type);
+
+ let expanded = quote! {{
+ let mut enum_suggest = ::mingling::Suggest::new();
+ for (name, desc) in <#enum_type>::enums() {
+ if desc.is_empty() {
+ enum_suggest.insert(::mingling::SuggestItem::new(name.to_string()));
+ } else {
+ enum_suggest.insert(::mingling::SuggestItem::new_with_desc(
+ name.to_string(),
+ desc.to_string(),
+ ));
+ }
+ }
+ enum_suggest
+ }};
+
+ expanded.into()
+}
diff --git a/mingling_macros/src/lib.rs b/mingling_macros/src/lib.rs
index ca3261e..27563a4 100644
--- a/mingling_macros/src/lib.rs
+++ b/mingling_macros/src/lib.rs
@@ -143,12 +143,6 @@
#![deny(missing_docs)]
-#[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;
@@ -183,9 +177,6 @@ use func::pack_err;
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 }
}
@@ -322,7 +313,7 @@ pub fn group(input: TokenStream) -> TokenStream {
#[cfg(all(feature = "structural_renderer", feature = "extra_macros"))]
#[proc_macro]
pub fn group_structural(input: TokenStream) -> TokenStream {
- structural_data::group_structural(input)
+ func::group_structural::group_structural(input)
}
/// Creates a `Node` from a dot-separated path string.
@@ -435,7 +426,7 @@ pub fn pack(input: TokenStream) -> TokenStream {
#[cfg(feature = "structural_renderer")]
#[proc_macro]
pub fn pack_structural(input: TokenStream) -> TokenStream {
- structural_data::pack_structural(input)
+ func::pack_structural::pack_structural(input)
}
/// Creates an error struct with a `name: String` field and optional `info: Type` field.
@@ -520,7 +511,7 @@ pub fn pack_err(input: TokenStream) -> TokenStream {
#[cfg(all(feature = "structural_renderer", feature = "extra_macros"))]
#[proc_macro]
pub fn pack_err_structural(input: TokenStream) -> TokenStream {
- pack_err::pack_err_structural(input)
+ func::pack_err_structural::pack_err_structural(input)
}
/// Early-returns the error from a `Result`, converting the `Ok` branch to the
@@ -580,14 +571,7 @@ pub fn pack_err_structural(input: TokenStream) -> TokenStream {
#[cfg(feature = "extra_macros")]
#[proc_macro]
pub fn route(input: TokenStream) -> TokenStream {
- let expr = parse_macro_input!(input as syn::Expr);
- let expanded = quote! {
- match #expr {
- Ok(r) => r,
- Err(e) => return ::mingling::Routable::to_chain(e),
- }
- };
- TokenStream::from(expanded)
+ func::route::route(input)
}
/// Routes errors to the rendering pipeline instead of the chain pipeline.
@@ -630,14 +614,7 @@ pub fn route(input: TokenStream) -> TokenStream {
#[cfg(feature = "extra_macros")]
#[proc_macro]
pub fn render_route(input: TokenStream) -> TokenStream {
- let expr = parse_macro_input!(input as syn::Expr);
- let expanded = quote! {
- match #expr {
- Ok(r) => r,
- Err(e) => return <crate::ThisProgram as ::mingling::ProgramCollect>::render(::mingling::AnyOutput::new(e)),
- }
- };
- TokenStream::from(expanded)
+ func::render_route::render_route(input)
}
/// Creates an empty result value wrapped in `ChainProcess` for early return
@@ -690,11 +667,8 @@ pub fn render_route(input: TokenStream) -> TokenStream {
/// [`ChainProcess`]: https://docs.rs/mingling/latest/mingling/enum.ChainProcess.html
#[cfg(feature = "extra_macros")]
#[proc_macro]
-pub fn empty_result(_input: TokenStream) -> TokenStream {
- let expanded = quote! {
- <crate::ResultEmpty as ::mingling::Routable::<crate::ThisProgram>>::to_chain(crate::ResultEmpty)
- };
- TokenStream::from(expanded)
+pub fn empty_result(input: TokenStream) -> TokenStream {
+ func::empty_result::empty_result(input)
}
/// Creates a `Dispatcher` implementation for a subcommand.
@@ -1233,7 +1207,7 @@ pub fn entry(input: TokenStream) -> TokenStream {
/// enum variant for `EntryType` to the help rendering logic in `HelpStruct`.
#[proc_macro]
pub fn register_help(input: TokenStream) -> TokenStream {
- help::register_help(input)
+ func::register_help::register_help(input)
}
/// Registers a dispatcher at compile time for the `dispatch_tree` feature.
@@ -1262,7 +1236,7 @@ pub fn register_help(input: TokenStream) -> TokenStream {
/// - `dispatch_tree_gen` module — The trie generation logic.
#[proc_macro]
pub fn register_dispatcher(input: TokenStream) -> TokenStream {
- dispatcher::register_dispatcher(input)
+ func::register_dispatcher::register_dispatcher(input)
}
/// Declares a help rendering function for an entry type.
@@ -1358,8 +1332,8 @@ pub fn help(attr: TokenStream, item: TokenStream) -> TokenStream {
/// fn some_item() {}
/// ```
#[proc_macro_attribute]
-pub fn mlint(_attr: TokenStream, item: TokenStream) -> TokenStream {
- item
+pub fn mlint(attr: TokenStream, item: TokenStream) -> TokenStream {
+ attr::mlint::mlint(attr, item)
}
/// Extension attribute macro that transforms `expr?` into `route!(expr)`.
@@ -1475,7 +1449,7 @@ pub fn buffer(attr: TokenStream, item: TokenStream) -> TokenStream {
/// ```
#[proc_macro]
pub fn r_println(input: TokenStream) -> TokenStream {
- func::r_print::r_println(input)
+ func::r_println::r_println(input)
}
/// Prints text to a `RenderResult` buffer, without a trailing newline.
@@ -1537,7 +1511,7 @@ pub fn r_print(input: TokenStream) -> TokenStream {
/// ```
#[proc_macro]
pub fn r_eprintln(input: TokenStream) -> TokenStream {
- func::r_print::r_eprintln(input)
+ func::r_eprintln::r_eprintln(input)
}
/// Prints text to a `RenderResult` buffer (standard error style), without a trailing newline.
@@ -1570,7 +1544,7 @@ pub fn r_eprintln(input: TokenStream) -> TokenStream {
/// ```
#[proc_macro]
pub fn r_eprint(input: TokenStream) -> TokenStream {
- func::r_print::r_eprint(input)
+ func::r_eprint::r_eprint(input)
}
/// Appends the contents of one `RenderResult` to another.
@@ -1600,7 +1574,7 @@ pub fn r_eprint(input: TokenStream) -> TokenStream {
/// ```
#[proc_macro]
pub fn r_append(input: TokenStream) -> TokenStream {
- func::r_print::r_append(input)
+ func::r_append::r_append(input)
}
/// Derive macro for automatically implementing the `Grouped` trait on a struct.
@@ -1701,7 +1675,7 @@ pub fn derive_enum_tag(input: TokenStream) -> TokenStream {
#[cfg(feature = "structural_renderer")]
#[proc_macro_derive(StructuralData)]
pub fn derive_structural_data(input: TokenStream) -> TokenStream {
- structural_data::derive_structural_data(input)
+ derive::structural_data::derive_structural_data(input)
}
/// Derive macro for implementing both `Grouped` and `serde::Serialize` on a struct.
@@ -1818,7 +1792,7 @@ pub fn gen_program(input: TokenStream) -> TokenStream {
#[cfg(feature = "comp")]
#[proc_macro]
pub fn program_comp_gen(input: TokenStream) -> TokenStream {
- func::gen_program::program_comp_gen_impl(input)
+ func::program_comp_gen::program_comp_gen_impl(input)
}
/// Registers a type into the global packed types registry for inclusion in
@@ -1843,7 +1817,7 @@ 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 {
- func::gen_program::register_type_impl(input)
+ func::register_type::register_type_impl(input)
}
/// Registers a chain mapping function into the global chain registry.
@@ -1861,7 +1835,7 @@ pub fn register_type(input: TokenStream) -> TokenStream {
/// Panics if the global `CHAINS` mutex is poisoned.
#[proc_macro]
pub fn register_chain(input: TokenStream) -> TokenStream {
- func::gen_program::register_chain_impl(input)
+ func::register_chain::register_chain_impl(input)
}
/// Registers a renderer mapping function into the global renderer registry.
@@ -1879,7 +1853,7 @@ pub fn register_chain(input: TokenStream) -> TokenStream {
/// Panics if the global `RENDERERS` mutex is poisoned.
#[proc_macro]
pub fn register_renderer(input: TokenStream) -> TokenStream {
- func::gen_program::register_renderer_impl(input)
+ func::register_renderer::register_renderer_impl(input)
}
/// Internal macro used by `gen_program!` to generate the fallback types for
@@ -1889,7 +1863,7 @@ pub fn register_renderer(input: TokenStream) -> TokenStream {
/// be called directly by user code.
#[proc_macro]
pub fn program_fallback_gen(input: TokenStream) -> TokenStream {
- func::gen_program::program_fallback_gen_impl(input)
+ func::program_fallback_gen::program_fallback_gen_impl(input)
}
/// Internal macro used by `gen_program!` to generate the final program enum
@@ -1943,7 +1917,7 @@ pub fn program_fallback_gen(input: TokenStream) -> TokenStream {
/// ```
#[proc_macro]
pub fn program_final_gen(input: TokenStream) -> TokenStream {
- func::gen_program::program_final_gen_impl(input)
+ func::program_final_gen::program_final_gen_impl(input)
}
/// Builds a `Suggest` instance with inline suggestion items.
@@ -2064,5 +2038,5 @@ pub fn suggest(input: TokenStream) -> TokenStream {
#[cfg(feature = "comp")]
#[proc_macro]
pub fn suggest_enum(input: TokenStream) -> TokenStream {
- suggest::suggest_enum(input)
+ func::suggest_enum::suggest_enum(input)
}
diff --git a/mingling_macros/src/systems/dispatch_tree_gen.rs b/mingling_macros/src/systems/dispatch_tree_gen.rs
index 7383421..9eeb637 100644
--- a/mingling_macros/src/systems/dispatch_tree_gen.rs
+++ b/mingling_macros/src/systems/dispatch_tree_gen.rs
@@ -4,7 +4,7 @@ use just_fmt::snake_case;
use proc_macro2::TokenStream;
use quote::quote;
-use crate::func::gen_program::resolve_type;
+use crate::func::program_final_gen::resolve_type;
/// Generate the `get_nodes()` function body for a ProgramCollect impl.
/// If `pathf_map` is non-empty, resolves internal dispatcher statics using full paths.
diff --git a/mingling_macros/src/systems/structural_data.rs b/mingling_macros/src/systems/structural_data.rs
index 46d7cf8..512d318 100644
--- a/mingling_macros/src/systems/structural_data.rs
+++ b/mingling_macros/src/systems/structural_data.rs
@@ -1,344 +1,6 @@
-#![allow(dead_code)]
-
-use proc_macro::TokenStream;
-use quote::quote;
-use syn::{DeriveInput, Ident, TypePath, parse_macro_input};
-
-use crate::get_global_set;
-
-/// Derive macro for `StructuralData`.
-///
-/// This marks a type as eligible for structured output (JSON / YAML / TOML / RON).
-/// The type must also implement `serde::Serialize` — the generated `impl StructuralData`
-/// will fail to compile if `Serialize` is not in scope or implemented.
-///
-/// Also registers the type name in the global `STRUCTURED_TYPES` registry so that
-/// the `structural_render` match arm is generated by `gen_program!()`.
-pub(crate) fn derive_structural_data(input: TokenStream) -> TokenStream {
- let input = parse_macro_input!(input as DeriveInput);
- let type_name = input.ident;
-
- // Register in STRUCTURED_TYPES
- let type_name_str = type_name.to_string();
- get_global_set(&crate::STRUCTURED_TYPES)
- .lock()
- .unwrap()
- .insert(type_name_str);
-
- // Generate BOTH the sealed impl AND the StructuralData impl.
- // Users cannot implement StructuralDataSealed manually (it's #[doc(hidden)]),
- // so the only way to get StructuralData is through this derive macro.
- let expanded = quote! {
- impl ::mingling::__private::StructuralDataSealed<crate::ThisProgram> for #type_name {}
- impl ::mingling::__private::StructuralData<crate::ThisProgram> for #type_name {}
- };
-
- expanded.into()
-}
-
-/// `pack_structural!` — like `pack!` but also marks the type as supporting
-/// structured output via `StructuralData`.
-///
-/// # Syntax
-///
-/// ```rust,ignore
-/// pack_structural!(Info = (String, i32));
-/// ```
-///
-/// This is equivalent to:
-/// ```rust,ignore
-/// pack!(Info = (String, i32));
-/// impl ::mingling::StructuralData for Info {}
-/// ```
-pub(crate) fn pack_structural(input: TokenStream) -> TokenStream {
- // Parse same input format as `pack!`
- let input_parsed = syn::parse_macro_input!(input as PackStructuralInput);
- let type_name = input_parsed.type_name;
- let inner_type = input_parsed.inner_type;
- let attrs = input_parsed.attrs;
- let program_path = crate::default_program_path();
-
- // Register in STRUCTURED_TYPES
- let type_name_str = type_name.to_string();
- get_global_set(&crate::STRUCTURED_TYPES)
- .lock()
- .unwrap()
- .insert(type_name_str);
-
- // Struct definition (with Serialize derive, same as pack! under structural_renderer)
- #[cfg(not(feature = "structural_renderer"))]
- let struct_def = quote! {
- #(#attrs)*
- pub struct #type_name {
- pub inner: #inner_type,
- }
- };
-
- #[cfg(feature = "structural_renderer")]
- let struct_def = quote! {
- #(#attrs)*
- #[derive(serde::Serialize)]
- pub struct #type_name {
- pub inner: #inner_type,
- }
- };
-
- // Helper impls (same as pack!)
- let new_impl = quote! {
- impl #type_name {
- pub fn new(inner: #inner_type) -> Self {
- Self { inner }
- }
- }
- };
-
- let from_into_impl = quote! {
- impl From<#inner_type> for #type_name {
- fn from(inner: #inner_type) -> Self {
- Self::new(inner)
- }
- }
- impl From<#type_name> for #inner_type {
- fn from(wrapper: #type_name) -> #inner_type {
- wrapper.inner
- }
- }
- };
-
- let as_ref_impl = quote! {
- impl ::std::convert::AsRef<#inner_type> for #type_name {
- fn as_ref(&self) -> &#inner_type {
- &self.inner
- }
- }
- impl ::std::convert::AsMut<#inner_type> for #type_name {
- fn as_mut(&mut self) -> &mut #inner_type {
- &mut self.inner
- }
- }
- };
-
- let deref_impl = quote! {
- impl ::std::ops::Deref for #type_name {
- type Target = #inner_type;
- fn deref(&self) -> &Self::Target {
- &self.inner
- }
- }
- impl ::std::ops::DerefMut for #type_name {
- fn deref_mut(&mut self) -> &mut Self::Target {
- &mut self.inner
- }
- }
- };
-
- let default_impl = quote! {
- impl ::std::default::Default for #type_name
- where
- #inner_type: ::std::default::Default,
- {
- fn default() -> Self {
- Self::new(::std::default::Default::default())
- }
- }
- };
-
- let register_impl = quote! {
- ::mingling::macros::register_type!(#type_name);
- };
-
- // StructuralData impl + sealed + registration
- let structural_impl = quote! {
- impl ::mingling::__private::StructuralDataSealed<crate::ThisProgram> for #type_name {}
- impl ::mingling::__private::StructuralData<crate::ThisProgram> for #type_name {}
- };
-
- let expanded = quote! {
- #struct_def
-
- #new_impl
- #from_into_impl
- #as_ref_impl
- #deref_impl
- #default_impl
- #register_impl
- #structural_impl
-
- impl Into<::mingling::AnyOutput<#program_path>> for #type_name {
- fn into(self) -> ::mingling::AnyOutput<#program_path> {
- ::mingling::AnyOutput::new(self)
- }
- }
-
- impl Into<::mingling::ChainProcess<#program_path>> for #type_name {
- fn into(self) -> ::mingling::ChainProcess<#program_path> {
- ::mingling::AnyOutput::new(self).route_chain()
- }
- }
-
- /// SAFETY: This is an internal implementation of the `pack_structural!` macro,
- /// guaranteeing that the enum value registered by the `register_type!` macro
- /// is exactly the same as the actual return value,
- /// which can be confirmed via the `Ident` in the `quote!` block.
- unsafe impl ::mingling::Grouped<#program_path> for #type_name {
- fn member_id() -> #program_path {
- #program_path::#type_name
- }
- }
- };
-
- expanded.into()
-}
-
-/// Input for `pack_structural!` — same format as `pack!`.
-struct PackStructuralInput {
- attrs: Vec<syn::Attribute>,
- type_name: Ident,
- inner_type: syn::Type,
-}
-
-impl syn::parse::Parse for PackStructuralInput {
- fn parse(input: syn::parse::ParseStream) -> syn::Result<Self> {
- let attrs = input.call(syn::Attribute::parse_outer)?;
- let type_name: Ident = input.parse()?;
- input.parse::<syn::Token![=]>()?;
- let inner_type: syn::Type = input.parse()?;
- Ok(PackStructuralInput {
- attrs,
- type_name,
- inner_type,
- })
- }
-}
-
-/// `group_structural!` — like `group!` but also marks the type as supporting
-/// structured output via `StructuralData`.
-///
-/// # Syntax
-///
-/// ```rust,ignore
-/// group_structural!(Info = (String, i32));
-/// ```
-///
-/// This is equivalent to:
-/// ```rust,ignore
-/// group!(Info = (String, i32));
-/// impl ::mingling::StructuralData for Info {}
-/// ```
-pub(crate) fn group_structural(input: TokenStream) -> TokenStream {
- // Parse the same input as group!
- let input_parsed = syn::parse_macro_input!(input as GroupStructuralInput);
-
- let is_aliased = matches!(&input_parsed, GroupStructuralInput::Aliased { .. });
-
- let (type_path, type_name, alias_stmt) = match &input_parsed {
- GroupStructuralInput::Plain(type_path) => {
- let name = type_path
- .path
- .segments
- .last()
- .expect("TypePath must have at least one segment")
- .ident
- .clone();
- (type_path.clone(), name, quote! {})
- }
- GroupStructuralInput::Aliased { alias, type_path } => {
- let alias_stmt = quote! {
- pub(crate) type #alias = #type_path;
- };
- (type_path.clone(), alias.clone(), alias_stmt)
- }
- };
-
- let type_name_str = type_name.to_string();
-
- // Register in STRUCTURED_TYPES
- get_global_set(&crate::STRUCTURED_TYPES)
- .lock()
- .unwrap()
- .insert(type_name_str);
-
- let program_path = crate::default_program_path();
-
- // Generate unique module name
- let segments: Vec<String> = type_path
- .path
- .segments
- .iter()
- .map(|seg| seg.ident.to_string().to_lowercase())
- .collect();
- let module_name = Ident::new(
- &format!("internal_group_{}", segments.join("_")),
- proc_macro2::Span::call_site(),
- );
-
- // Generate the appropriate `use` statement for the original type
- // (consistent with gen_type_use in group_impl.rs)
- let type_use = if type_path.path.segments.len() > 1 {
- quote! { #[allow(unused_imports)] use #type_path; }
- } else {
- let ident = type_path
- .path
- .segments
- .last()
- .expect("TypePath must have at least one segment")
- .ident
- .clone();
- quote! { #[allow(unused_imports)] use super::#ident; }
- };
-
- let alias_use = if is_aliased {
- quote! { use super::#type_name; }
- } else {
- quote! {}
- };
-
- let expanded = quote! {
- #alias_stmt
- #[allow(non_camel_case_types)]
- mod #module_name {
- use #program_path as __MinglingProgram;
- #type_use
- #alias_use
-
- /// SAFETY: This is an internal implementation of the `pack_structural!` macro,
- /// guaranteeing that the enum value registered by the `register_type!` macro
- /// is exactly the same as the actual return value,
- /// which can be confirmed via the `Ident` in the `quote!` block.
- unsafe impl ::mingling::Grouped<__MinglingProgram> for #type_name {
- fn member_id() -> __MinglingProgram {
- __MinglingProgram::#type_name
- }
- }
-
- impl ::mingling::__private::StructuralDataSealed<crate::ThisProgram> for #type_name {}
- impl ::mingling::__private::StructuralData<crate::ThisProgram> for #type_name {}
-
- ::mingling::macros::register_type!(#type_name);
- }
- };
-
- expanded.into()
-}
-
-/// Input for `group_structural!` — same format as `group!`.
-enum GroupStructuralInput {
- Plain(TypePath),
- Aliased { alias: Ident, type_path: TypePath },
-}
-
-impl syn::parse::Parse for GroupStructuralInput {
- fn parse(input: syn::parse::ParseStream) -> syn::Result<Self> {
- let fork = input.fork();
- let _first: Ident = fork.parse()?;
- if fork.peek(syn::Token![=]) {
- let alias: Ident = input.parse()?;
- let _eq: syn::Token![=] = input.parse()?;
- let type_path: TypePath = input.parse()?;
- Ok(GroupStructuralInput::Aliased { alias, type_path })
- } else {
- let type_path: TypePath = input.parse()?;
- Ok(GroupStructuralInput::Plain(type_path))
- }
- }
-}
+//! Legacy structural data module.
+//!
+//! Functions have been moved to:
+//! - `derive::structural_data` — `derive_structural_data`
+//! - `func::pack_structural` — `pack_structural`
+//! - `func::group_structural` — `group_structural`