From 1df1fc664c4a4b12807a49ef2f1a0effda6ce064 Mon Sep 17 00:00:00 2001 From: 魏曹先生 <1992414357@qq.com> Date: Tue, 23 Jun 2026 00:40:05 +0800 Subject: Add compile-time duplicate variant detection Add duplicate variant checks for chain, renderer, help, and completion registrations to produce a clear compile error instead of silently generating unreachable code --- mingling_macros/src/chain.rs | 14 ++++++++++++++ mingling_macros/src/completion.rs | 12 ++++++++++++ mingling_macros/src/help.rs | 33 +++++++++++++++++++++++++++++---- mingling_macros/src/lib.rs | 31 +++++++++++++++++++++++++++++++ mingling_macros/src/renderer.rs | 32 ++++++++++++++++++++++++++------ 5 files changed, 112 insertions(+), 10 deletions(-) (limited to 'mingling_macros') diff --git a/mingling_macros/src/chain.rs b/mingling_macros/src/chain.rs index eaf43fb..a678858 100644 --- a/mingling_macros/src/chain.rs +++ b/mingling_macros/src/chain.rs @@ -432,6 +432,20 @@ pub fn register_chain(input: TokenStream) -> TokenStream { let chain_entry_str = chain_entry.to_string(); let chain_exist_entry_str = chain_exist_entry.to_string(); + // Check for duplicate variant before inserting + let variant_name = previous_type + .path + .segments + .last() + .unwrap() + .ident + .to_string(); + if let Err(err) = + crate::check_duplicate_variant(&chains, &variant_name, "chain", previous_type.span()) + { + return err.into(); + } + chains.insert(chain_entry_str); chain_exist.insert(chain_exist_entry_str); diff --git a/mingling_macros/src/completion.rs b/mingling_macros/src/completion.rs index 452c418..c4870d9 100644 --- a/mingling_macros/src/completion.rs +++ b/mingling_macros/src/completion.rs @@ -153,6 +153,18 @@ pub fn completion_attr(attr: TokenStream, item: TokenStream) -> TokenStream { let mut completions = get_global_set(&crate::COMPLETIONS).lock().unwrap(); let completion_str = completion_entry.to_string(); + + // Check for duplicate variant before inserting + let variant_name = previous_type_ident.to_string(); + if let Err(err) = crate::check_duplicate_variant( + &completions, + &variant_name, + "completion", + previous_type_path.span(), + ) { + return err.into(); + } + completions.insert(completion_str); expanded.into() diff --git a/mingling_macros/src/help.rs b/mingling_macros/src/help.rs index e904b16..e1095e6 100644 --- a/mingling_macros/src/help.rs +++ b/mingling_macros/src/help.rs @@ -105,6 +105,18 @@ pub fn help_attr(item: TokenStream) -> TokenStream { // Register the help request mapping let help_entry = build_help_entry(&struct_name, &entry_type); let entry_str = help_entry.to_string(); + + // Check for duplicate variant before inserting + let variant_name = entry_type.path.segments.last().unwrap().ident.to_string(); + { + let helps = get_global_set(&crate::HELP_REQUESTS).lock().unwrap(); + if let Err(err) = + crate::check_duplicate_variant(&helps, &variant_name, "help", entry_type.span()) + { + return err.into(); + } + } + get_global_set(&crate::HELP_REQUESTS) .lock() .unwrap() @@ -185,10 +197,23 @@ pub fn register_help(input: TokenStream) -> TokenStream { // Register the help request mapping let help_entry = build_help_entry(&struct_name, &entry_type); let entry_str = help_entry.to_string(); - get_global_set(&crate::HELP_REQUESTS) - .lock() - .unwrap() - .insert(entry_str); + + // 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, &variant_name, "help", entry_type.span()) + { + return err.into(); + } + + helps.insert(entry_str); quote! {}.into() } diff --git a/mingling_macros/src/lib.rs b/mingling_macros/src/lib.rs index c6b94c3..5bc73a4 100644 --- a/mingling_macros/src/lib.rs +++ b/mingling_macros/src/lib.rs @@ -195,6 +195,37 @@ pub(crate) static CHAINS_EXIST: Registry = OnceLock::new(); pub(crate) static RENDERERS_EXIST: Registry = OnceLock::new(); pub(crate) static HELP_REQUESTS: Registry = OnceLock::new(); +/// Checks if a variant name already exists in a registered set. +/// Returns a `compile_error` token stream if a duplicate is found. +pub(crate) fn check_duplicate_variant( + set: &std::collections::BTreeSet, + variant_name: &str, + kind: &str, + error_span: proc_macro2::Span, +) -> Result<(), proc_macro2::TokenStream> { + for existing in set.iter() { + if entry_has_variant(existing, variant_name) { + return Err(syn::Error::new( + error_span, + format!( + "duplicate {kind} registration for `{variant_name}`: a {kind} with this type already exists" + ), + ) + .to_compile_error()); + } + } + Ok(()) +} + +/// Checks if a stored entry string contains the given variant name. +/// Handles both "StructName => Variant," and "Self::Variant => ..." formats. +fn entry_has_variant(entry: &str, variant_name: &str) -> bool { + entry.contains(&format!("=> {variant_name},")) + || entry.contains(&format!("=> {variant_name} ")) + || entry.contains(&format!("=> {variant_name}")) + || entry.contains(&format!(":: {variant_name} =>")) +} + /// Registers an outside-type as a member of a program group without modifying its definition. /// /// This macro allows you to use outside-types from external crates (like `std::io::Error`) diff --git a/mingling_macros/src/renderer.rs b/mingling_macros/src/renderer.rs index c4b2786..79128ef 100644 --- a/mingling_macros/src/renderer.rs +++ b/mingling_macros/src/renderer.rs @@ -234,18 +234,38 @@ pub fn register_renderer(input: TokenStream) -> TokenStream { #[cfg(feature = "general_renderer")] let general_renderer_entry = build_general_renderer_entry(&previous_type); - let mut renderers = get_global_set(&crate::RENDERERS).lock().unwrap(); - let mut renderer_exist = get_global_set(&crate::RENDERERS_EXIST).lock().unwrap(); - - #[cfg(feature = "general_renderer")] - let mut general_renderers = get_global_set(&crate::GENERAL_RENDERERS).lock().unwrap(); - let renderer_entry_str = renderer_entry.to_string(); let renderer_exist_entry_str = renderer_exist_entry.to_string(); #[cfg(feature = "general_renderer")] let general_renderer_entry_str = general_renderer_entry.to_string(); + // Check for duplicate variant before acquiring other locks + let variant_name = previous_type + .path + .segments + .last() + .unwrap() + .ident + .to_string(); + { + let renderers = get_global_set(&crate::RENDERERS).lock().unwrap(); + if let Err(err) = crate::check_duplicate_variant( + &renderers, + &variant_name, + "renderer", + previous_type.span(), + ) { + return err.into(); + } + } // renderers lock released here + + let mut renderers = get_global_set(&crate::RENDERERS).lock().unwrap(); + let mut renderer_exist = get_global_set(&crate::RENDERERS_EXIST).lock().unwrap(); + + #[cfg(feature = "general_renderer")] + let mut general_renderers = get_global_set(&crate::GENERAL_RENDERERS).lock().unwrap(); + renderers.insert(renderer_entry_str); renderer_exist.insert(renderer_exist_entry_str); -- cgit