aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
author魏曹先生 <1992414357@qq.com>2026-05-16 21:55:55 +0800
committer魏曹先生 <1992414357@qq.com>2026-05-16 21:55:55 +0800
commit5fe400c006d9d143aa7b977cb13f95e7380754a5 (patch)
tree96eca77fa2c47a1b1e6b4713bfefba41555ee9d2
parent29cdd6fdbe9dd658bbc6b6694b563b046c0d9f41 (diff)
Validate single-segment types in attribute macros
-rw-r--r--mingling_macros/src/chain.rs14
-rw-r--r--mingling_macros/src/completion.rs20
-rw-r--r--mingling_macros/src/help.rs5
-rw-r--r--mingling_macros/src/lib.rs29
-rw-r--r--mingling_macros/src/renderer.rs5
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();