1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
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()
}
|