1use proc_macro::TokenStream;
12use quote::quote;
13use syn::Data;
14use syn::DataEnum;
15use syn::DeriveInput;
16use syn::Expr;
17use syn::Fields;
18use syn::Lit;
19use syn::Meta;
20use syn::MetaNameValue;
21use syn::parse_macro_input;
22
23#[proc_macro_derive(Named, attributes(named))]
40pub fn derive_named(input: TokenStream) -> TokenStream {
41 let input = parse_macro_input!(input as DeriveInput);
43 let struct_name = &input.ident;
44
45 let mut typename = quote! {
46 concat!(std::module_path!(), "::", stringify!(#struct_name))
47 };
48
49 let type_params: Vec<_> = input.generics.type_params().collect();
50 let has_generics = !type_params.is_empty();
51
52 for attr in &input.attrs {
53 if attr.path().is_ident("named")
54 && let Ok(meta) = attr.parse_args_with(
55 syn::punctuated::Punctuated::<Meta, syn::Token![,]>::parse_terminated,
56 )
57 {
58 for item in meta {
59 if let Meta::NameValue(MetaNameValue {
60 path,
61 value: Expr::Lit(expr_lit),
62 ..
63 }) = item
64 {
65 if path.is_ident("name") {
66 if let Lit::Str(name) = expr_lit.lit {
67 typename = quote! { #name };
68 } else {
69 return TokenStream::from(
70 syn::Error::new_spanned(path, "invalid name").to_compile_error(),
71 );
72 }
73 } else {
74 return TokenStream::from(
75 syn::Error::new_spanned(
76 path,
77 "unsupported attribute (only `name` is supported)",
78 )
79 .to_compile_error(),
80 );
81 }
82 }
83 }
84 }
85 }
86
87 let mut generics_with_bounds = input.generics.clone();
89 if has_generics {
90 for param in generics_with_bounds.type_params_mut() {
91 param.bounds.push(syn::parse_quote!(typeuri::Named));
92 }
93 }
94 let (impl_generics_with_bounds, _, _) = generics_with_bounds.split_for_impl();
95
96 let (typename_impl, typehash_impl) = if has_generics {
98 let placeholders = vec!["{}"; type_params.len()].join(", ");
100 let placeholders_format_string = format!("<{}>", placeholders);
101 let format_string = quote! { concat!(std::module_path!(), "::", stringify!(#struct_name), #placeholders_format_string) };
102
103 let type_param_idents: Vec<_> = type_params.iter().map(|p| &p.ident).collect();
104 (
105 quote! {
106 typeuri::intern_typename!(Self, #format_string, #(#type_param_idents),*)
107 },
108 quote! {
109 typeuri::cityhasher::hash(Self::typename())
110 },
111 )
112 } else {
113 (
114 typename,
115 quote! {
116 static TYPEHASH: std::sync::LazyLock<u64> = std::sync::LazyLock::new(|| {
117 typeuri::cityhasher::hash(<#struct_name as typeuri::Named>::typename())
118 });
119 *TYPEHASH
120 },
121 )
122 };
123
124 let arm_impl = match &input.data {
126 Data::Enum(DataEnum { variants, .. }) => {
127 let match_arms = variants.iter().map(|v| {
128 let variant_name = &v.ident;
129 let variant_str = variant_name.to_string();
130 match &v.fields {
131 Fields::Unit => quote! { Self::#variant_name => Some(#variant_str) },
132 Fields::Unnamed(_) => quote! { Self::#variant_name(..) => Some(#variant_str) },
133 Fields::Named(_) => quote! { Self::#variant_name { .. } => Some(#variant_str) },
134 }
135 });
136 quote! {
137 fn arm(&self) -> Option<&'static str> {
138 match self {
139 #(#match_arms,)*
140 }
141 }
142 }
143 }
144 _ => quote! {},
145 };
146
147 let (_, ty_generics, where_clause) = input.generics.split_for_impl();
148
149 let expanded = quote! {
150 impl #impl_generics_with_bounds typeuri::Named for #struct_name #ty_generics #where_clause {
151 fn typename() -> &'static str { #typename_impl }
152 fn typehash() -> u64 { #typehash_impl }
153 #arm_impl
154 }
155 };
156
157 TokenStream::from(expanded)
158}