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 if let Ok(meta) = attr.parse_args_with(
55 syn::punctuated::Punctuated::<Meta, syn::Token![,]>::parse_terminated,
56 ) {
57 for item in meta {
58 if let Meta::NameValue(MetaNameValue {
59 path,
60 value: Expr::Lit(expr_lit),
61 ..
62 }) = item
63 {
64 if path.is_ident("name") {
65 if let Lit::Str(name) = expr_lit.lit {
66 typename = quote! { #name };
67 } else {
68 return TokenStream::from(
69 syn::Error::new_spanned(path, "invalid name")
70 .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
88 let mut generics_with_bounds = input.generics.clone();
90 if has_generics {
91 for param in generics_with_bounds.type_params_mut() {
92 param.bounds.push(syn::parse_quote!(typeuri::Named));
93 }
94 }
95 let (impl_generics_with_bounds, _, _) = generics_with_bounds.split_for_impl();
96
97 let (typename_impl, typehash_impl) = if has_generics {
99 let placeholders = vec!["{}"; type_params.len()].join(", ");
101 let placeholders_format_string = format!("<{}>", placeholders);
102 let format_string = quote! { concat!(std::module_path!(), "::", stringify!(#struct_name), #placeholders_format_string) };
103
104 let type_param_idents: Vec<_> = type_params.iter().map(|p| &p.ident).collect();
105 (
106 quote! {
107 typeuri::intern_typename!(Self, #format_string, #(#type_param_idents),*)
108 },
109 quote! {
110 typeuri::cityhasher::hash(Self::typename())
111 },
112 )
113 } else {
114 (
115 typename,
116 quote! {
117 static TYPEHASH: std::sync::LazyLock<u64> = std::sync::LazyLock::new(|| {
118 typeuri::cityhasher::hash(<#struct_name as typeuri::Named>::typename())
119 });
120 *TYPEHASH
121 },
122 )
123 };
124
125 let arm_impl = match &input.data {
127 Data::Enum(DataEnum { variants, .. }) => {
128 let match_arms = variants.iter().map(|v| {
129 let variant_name = &v.ident;
130 let variant_str = variant_name.to_string();
131 match &v.fields {
132 Fields::Unit => quote! { Self::#variant_name => Some(#variant_str) },
133 Fields::Unnamed(_) => quote! { Self::#variant_name(..) => Some(#variant_str) },
134 Fields::Named(_) => quote! { Self::#variant_name { .. } => Some(#variant_str) },
135 }
136 });
137 quote! {
138 fn arm(&self) -> Option<&'static str> {
139 match self {
140 #(#match_arms,)*
141 }
142 }
143 }
144 }
145 _ => quote! {},
146 };
147
148 let (_, ty_generics, where_clause) = input.generics.split_for_impl();
149
150 let expanded = quote! {
151 impl #impl_generics_with_bounds typeuri::Named for #struct_name #ty_generics #where_clause {
152 fn typename() -> &'static str { #typename_impl }
153 fn typehash() -> u64 { #typehash_impl }
154 #arm_impl
155 }
156 };
157
158 TokenStream::from(expanded)
159}