aboutsummaryrefslogtreecommitdiff
path: root/mingling_macros/src/group_impl.rs
diff options
context:
space:
mode:
author魏曹先生 <1992414357@qq.com>2026-06-20 01:30:10 +0800
committer魏曹先生 <1992414357@qq.com>2026-06-20 01:30:10 +0800
commitd3177c386d739a3dc0e06890ad8f14713ef44ee0 (patch)
tree98193b025812ee13b1f0f0bb38e1d58fd2558f3d /mingling_macros/src/group_impl.rs
parent9d491352d161ee629cc47459537344ba0ea4bb35 (diff)
Add `group!` macro for registering external types
Diffstat (limited to 'mingling_macros/src/group_impl.rs')
-rw-r--r--mingling_macros/src/group_impl.rs115
1 files changed, 115 insertions, 0 deletions
diff --git a/mingling_macros/src/group_impl.rs b/mingling_macros/src/group_impl.rs
new file mode 100644
index 0000000..1b765f3
--- /dev/null
+++ b/mingling_macros/src/group_impl.rs
@@ -0,0 +1,115 @@
+use proc_macro::TokenStream;
+use quote::quote;
+use syn::parse::{Parse, ParseStream};
+use syn::{Ident, Path, Result as SynResult, Token, TypePath};
+
+/// Input for the `group!` macro
+///
+/// # Syntax
+///
+/// ```rust,ignore
+/// // Explicit mode: specify both program path and type path
+/// group!(crate::ThisProgram, std::io::Error);
+///
+/// // Implicit mode: only type path, uses default `crate::ThisProgram` as program
+/// group!(std::io::Error);
+/// ```
+enum GroupInput {
+ Explicit {
+ program_path: Path,
+ type_path: TypePath,
+ },
+ Implicit {
+ type_path: TypePath,
+ },
+}
+
+impl Parse for GroupInput {
+ fn parse(input: ParseStream) -> SynResult<Self> {
+ // Parse the first path (could be program path or type path)
+ let first_path: Path = input.parse()?;
+
+ // If followed by a comma, it's explicit mode: Path, TypePath
+ if input.peek(Token![,]) {
+ input.parse::<Token![,]>()?;
+ let type_path: TypePath = input.parse()?;
+ Ok(GroupInput::Explicit {
+ program_path: first_path,
+ type_path,
+ })
+ } else {
+ // Otherwise it's implicit mode: just a type path
+ Ok(GroupInput::Implicit {
+ type_path: TypePath {
+ qself: None,
+ path: first_path,
+ },
+ })
+ }
+ }
+}
+
+/// Convert a type path into a valid module name segment
+///
+/// e.g. `std::io::Error` -> `internal_group_std_io_error`
+fn module_name_from_type(type_path: &TypePath) -> Ident {
+ let segments: Vec<String> = type_path
+ .path
+ .segments
+ .iter()
+ .map(|seg| seg.ident.to_string().to_lowercase())
+ .collect();
+ Ident::new(
+ &format!("internal_group_{}", segments.join("_")),
+ proc_macro2::Span::call_site(),
+ )
+}
+
+/// Get the last segment name of a type path (the simple type name)
+///
+/// e.g. `std::io::Error` -> `Error`
+fn type_simple_name(type_path: &TypePath) -> Ident {
+ type_path
+ .path
+ .segments
+ .last()
+ .expect("TypePath must have at least one segment")
+ .ident
+ .clone()
+}
+
+pub fn group_macro(input: TokenStream) -> TokenStream {
+ let input = syn::parse_macro_input!(input as GroupInput);
+
+ let (program_path, type_path) = match input {
+ GroupInput::Explicit {
+ program_path,
+ type_path,
+ } => (quote! { #program_path }, type_path),
+ GroupInput::Implicit { type_path } => (crate::default_program_path(), type_path),
+ };
+
+ // Use the type's simple name as the enum variant identifier
+ let type_name = type_simple_name(&type_path);
+ // Create a unique module name from the full type path
+ let module_name = module_name_from_type(&type_path);
+
+ // Generate the module with the Groupped implementation
+ let expanded = quote! {
+ #[allow(non_camel_case_types)]
+ mod #module_name {
+ use #program_path as __MinglingProgram;
+ use #type_path;
+
+ impl ::mingling::Groupped<__MinglingProgram> for #type_name {
+ fn member_id() -> __MinglingProgram {
+ __MinglingProgram::#type_name
+ }
+ }
+
+ ::mingling::macros::register_type!(#type_name);
+ }
+ };
+
+ expanded.into()
+}