diff options
| author | 魏曹先生 <1992414357@qq.com> | 2026-06-20 01:30:10 +0800 |
|---|---|---|
| committer | 魏曹先生 <1992414357@qq.com> | 2026-06-20 01:30:10 +0800 |
| commit | d3177c386d739a3dc0e06890ad8f14713ef44ee0 (patch) | |
| tree | 98193b025812ee13b1f0f0bb38e1d58fd2558f3d /mingling_macros/src/group_impl.rs | |
| parent | 9d491352d161ee629cc47459537344ba0ea4bb35 (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.rs | 115 |
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() +} |
