diff options
| author | 魏曹先生 <1992414357@qq.com> | 2026-05-16 21:55:55 +0800 |
|---|---|---|
| committer | 魏曹先生 <1992414357@qq.com> | 2026-05-16 21:55:55 +0800 |
| commit | 5fe400c006d9d143aa7b977cb13f95e7380754a5 (patch) | |
| tree | 96eca77fa2c47a1b1e6b4713bfefba41555ee9d2 /mingling_macros | |
| parent | 29cdd6fdbe9dd658bbc6b6694b563b046c0d9f41 (diff) | |
Validate single-segment types in attribute macros
Diffstat (limited to 'mingling_macros')
| -rw-r--r-- | mingling_macros/src/chain.rs | 14 | ||||
| -rw-r--r-- | mingling_macros/src/completion.rs | 20 | ||||
| -rw-r--r-- | mingling_macros/src/help.rs | 5 | ||||
| -rw-r--r-- | mingling_macros/src/lib.rs | 29 | ||||
| -rw-r--r-- | mingling_macros/src/renderer.rs | 5 |
5 files changed, 71 insertions, 2 deletions
diff --git a/mingling_macros/src/chain.rs b/mingling_macros/src/chain.rs index 8670f97..134b3c2 100644 --- a/mingling_macros/src/chain.rs +++ b/mingling_macros/src/chain.rs @@ -29,7 +29,19 @@ fn extract_args_info(sig: &Signature) -> syn::Result<(Pat, TypePath, Vec<Resourc FnArg::Typed(PatType { pat, ty, .. }) => { let param_pat = (**pat).clone(); match &**ty { - Type::Path(type_path) => (param_pat, type_path.clone()), + Type::Path(type_path) => { + // Check that the type is a single-segment type (no `::`) + if type_path.path.segments.len() > 1 { + return Err(syn::Error::new( + type_path.span(), + format!( + "The type `{}` in #[chain] function must be a simple single-segment type, e.g. `Empty` instead of `other::Empty`. Qualified paths with `::` are not allowed here.", + quote! { #type_path } + ), + )); + } + (param_pat, type_path.clone()) + } Type::Reference(_) => { return Err(syn::Error::new( ty.span(), diff --git a/mingling_macros/src/completion.rs b/mingling_macros/src/completion.rs index ea57f06..cd4a9ad 100644 --- a/mingling_macros/src/completion.rs +++ b/mingling_macros/src/completion.rs @@ -1,6 +1,7 @@ use proc_macro::TokenStream; use quote::quote; -use syn::{Ident, ItemFn, parse_macro_input}; +use syn::spanned::Spanned; +use syn::{FnArg, Ident, ItemFn, PatType, Type, parse_macro_input}; #[cfg(feature = "comp")] pub fn completion_attr(attr: TokenStream, item: TokenStream) -> TokenStream { @@ -45,6 +46,23 @@ pub fn completion_attr(attr: TokenStream, item: TokenStream) -> TokenStream { .into(); } + // Check that the function parameter type is a single-segment type path (no `::`) + if let Some(arg) = inputs.first() + && let FnArg::Typed(PatType { ty, .. }) = arg + && let Type::Path(type_path) = &**ty + && type_path.path.segments.len() > 1 + { + return syn::Error::new( + type_path.span(), + format!( + "The type `{}` in #[completion] function must be a simple single-segment type, e.g. `HelloEntry` instead of `other::HelloEntry`. Qualified paths with `::` are not allowed here.", + quote! { #type_path } + ), + ) + .to_compile_error() + .into(); + } + // Get the function body let fn_body = &input_fn.block; diff --git a/mingling_macros/src/help.rs b/mingling_macros/src/help.rs index 315483d..c89aad2 100644 --- a/mingling_macros/src/help.rs +++ b/mingling_macros/src/help.rs @@ -69,6 +69,11 @@ pub fn help_attr(item: TokenStream) -> TokenStream { Err(e) => return e.to_compile_error().into(), }; + // Check that the entry type is a single-segment type (no `::`) + if let Some(err_tokens) = crate::check_single_segment_type(&entry_type, "#[help]") { + return err_tokens.into(); + } + // Validate return type if let Err(e) = validate_return_type(&input_fn.sig) { return e.to_compile_error().into(); diff --git a/mingling_macros/src/lib.rs b/mingling_macros/src/lib.rs index 213c397..175c7e5 100644 --- a/mingling_macros/src/lib.rs +++ b/mingling_macros/src/lib.rs @@ -68,6 +68,35 @@ pub(crate) static RENDERERS_EXIST: Lazy<Mutex<BTreeSet<String>>> = pub(crate) static HELP_REQUESTS: Lazy<Mutex<BTreeSet<String>>> = Lazy::new(|| Mutex::new(BTreeSet::new())); +/// Checks that a TypePath is a simple single-segment identifier (no `::` in the path). +/// +/// This is used by `#[renderer]`, `#[help]`, `#[chain]`, and `#[completion]` attribute macros +/// to ensure that the type in the function signature is a bare identifier like `Empty`, +/// not a qualified path like `other::Empty`. +/// +/// Returns `None` if the type is valid, or a `compile_error!` token stream if it contains `::`. +pub(crate) fn check_single_segment_type( + type_path: &syn::TypePath, + attr_name: &str, +) -> Option<proc_macro2::TokenStream> { + if type_path.path.segments.len() > 1 { + let type_str = quote! { #type_path }; + Some(quote! { + compile_error!(concat!( + "The type `", + #type_str, + "` in ", + #attr_name, + " function must be a simple single-segment type, ", + "e.g. `Empty` instead of `other::Empty`. ", + "Qualified paths with `::` are not allowed here." + )); + }) + } else { + None + } +} + /// Creates a `Node` from a dot-separated path string. /// /// Each segment is converted to kebab-case (unless it starts with `_`). diff --git a/mingling_macros/src/renderer.rs b/mingling_macros/src/renderer.rs index 362a4a4..565ffa3 100644 --- a/mingling_macros/src/renderer.rs +++ b/mingling_macros/src/renderer.rs @@ -76,6 +76,11 @@ pub fn renderer_attr(item: TokenStream) -> TokenStream { Err(e) => return e.to_compile_error().into(), }; + // Check that the previous type is a single-segment type (no `::`) + if let Some(err_tokens) = crate::check_single_segment_type(&previous_type, "#[renderer]") { + return err_tokens.into(); + } + // Validate return type if let Err(e) = extract_return_type(&input_fn.sig) { return e.to_compile_error().into(); |
