1#![doc = include_str!("../README.md")]
2
3use darling::FromMeta;
4use darling::ast::NestedMeta;
5use proc_macro::TokenStream;
6use quote::ToTokens as _;
7use quote::format_ident;
8use quote::quote;
9use syn::Ident;
10
11#[proc_macro_attribute]
12pub fn nameth(attr: TokenStream, tokens: TokenStream) -> TokenStream {
13 let attr_args = match NestedMeta::parse_meta_list(attr.into()) {
14 Ok(args) => args,
15 Err(error) => {
16 return TokenStream::from(darling::Error::from(error).write_errors());
17 }
18 };
19 let attr_args = match NamedMacroArgs::from_list(&attr_args) {
20 Ok(args) => args,
21 Err(error) => {
22 return TokenStream::from(error.write_errors());
23 }
24 };
25
26 let item: syn::Item = match syn::parse(tokens.clone()) {
27 Ok(item) => item,
28 Err(err) => return err.into_compile_error().into(),
29 };
30
31 let crate_name = attr_args.crate_override.as_deref().unwrap_or("nameth");
32 let crate_name = format_ident!("{}", crate_name);
33
34 let name = match item {
35 syn::Item::Struct(item_struct) => process_struct(&crate_name, item_struct),
36 syn::Item::Enum(item_enum) => process_enum(&crate_name, item_enum),
37 syn::Item::Fn(item_fn) => process_fn(item_fn),
38 _ => return quote! { compile_error!("Unexpected item kind"); }.into(),
39 };
40
41 if attr_args.debug {
42 println!("\nGenerated:\n{name}\n");
43 }
44
45 let mut tokens = tokens;
46 tokens.extend(name);
47 return tokens;
48}
49
50fn process_struct(crate_name: &Ident, item_struct: syn::ItemStruct) -> TokenStream {
51 let syn::ItemStruct {
52 ident, generics, ..
53 } = item_struct;
54 let name = ident.to_string();
55 let type_name_const = format_ident!("{}", ident_to_upper_snake_case(&name));
56 let without_defaults = without_defaults(&generics);
57 let param_names_only = param_names_only(&generics);
58 quote! {
59 impl #without_defaults #crate_name::NamedType for #ident #param_names_only {
60 fn type_name() -> &'static str {
61 return #name;
62 }
63 }
64 static #type_name_const: &str = #name;
65 }
66 .into()
67}
68
69fn process_enum(crate_name: &Ident, item_enum: syn::ItemEnum) -> TokenStream {
70 let syn::ItemEnum {
71 ident,
72 generics,
73 variants,
74 ..
75 } = item_enum;
76 let cases: Vec<_> = variants
77 .iter()
78 .map(|variant| {
79 let ident = &variant.ident;
80 let name = ident.to_string();
81 quote! { Self::#ident { .. } => #name, }
82 })
83 .collect();
84
85 let without_defaults = without_defaults(&generics);
86 let param_names_only = param_names_only(&generics);
87 let name = ident.to_string();
88 let type_name_const = format_ident!("{}", ident_to_upper_snake_case(&name));
89 quote! {
90 impl #without_defaults #crate_name::NamedType for #ident #param_names_only {
91 fn type_name() -> &'static str {
92 return #name;
93 }
94 }
95 impl #without_defaults #crate_name::NamedEnumValues for #ident #param_names_only {
96 fn name(&self) -> &'static str {
97 match self {
98 #(#cases)*
99 }
100 }
101 }
102 static #type_name_const: &str = #name;
103 }
104 .into()
105}
106
107fn process_fn(item_fn: syn::ItemFn) -> TokenStream {
108 let name = item_fn.sig.ident.to_string();
109 let vis = item_fn.vis;
110 let ident = format_ident!("{}", name.to_uppercase());
111 quote! { #vis const #ident : &'static str = #name; }.into()
112}
113
114#[derive(Debug, FromMeta)]
115struct NamedMacroArgs {
116 #[darling(default)]
117 debug: bool,
118
119 #[darling(default)]
120 crate_override: Option<String>,
121}
122
123fn param_names_only(generics: &syn::Generics) -> proc_macro2::TokenStream {
124 let syn::Generics {
125 lt_token: Some(lt_token),
126 params,
127 gt_token: Some(gt_token),
128 where_clause: _,
129 } = generics
130 else {
131 return quote!();
132 };
133 syn::AngleBracketedGenericArguments {
134 colon2_token: None,
135 lt_token: lt_token.to_owned(),
136 args: params
137 .into_iter()
138 .map(|param| match param {
139 syn::GenericParam::Lifetime(x) => {
140 syn::GenericArgument::Lifetime(x.lifetime.clone())
141 }
142 syn::GenericParam::Type(syn::TypeParam { ident, .. }) => {
143 syn::GenericArgument::Type(syn::parse2(quote! { #ident }).unwrap())
144 }
145 syn::GenericParam::Const(syn::ConstParam { ident, .. }) => {
146 syn::GenericArgument::Const(syn::parse2(quote! { #ident }).unwrap())
147 }
148 })
149 .collect(),
150 gt_token: gt_token.to_owned(),
151 }
152 .into_token_stream()
153}
154
155fn without_defaults(generics: &syn::Generics) -> syn::Generics {
156 let mut generics = generics.clone();
157 for param in &mut generics.params {
158 match param {
159 syn::GenericParam::Lifetime(syn::LifetimeParam { attrs, .. }) => {
160 *attrs = vec![];
161 }
162 syn::GenericParam::Type(syn::TypeParam {
163 attrs,
164 eq_token,
165 default,
166 ..
167 }) => {
168 *attrs = vec![];
169 *eq_token = None;
170 *default = None;
171 }
172 syn::GenericParam::Const(syn::ConstParam {
173 attrs,
174 eq_token,
175 default,
176 ..
177 }) => {
178 *attrs = vec![];
179 *eq_token = None;
180 *default = None;
181 }
182 }
183 }
184 generics
185}
186
187fn ident_to_upper_snake_case(name: impl std::fmt::Display + Copy) -> String {
188 let name = name.to_string();
189 let mut result = String::default();
190 let mut last_is_upper = false;
191 for c in name.chars() {
192 if !last_is_upper && c.is_uppercase() {
193 last_is_upper = true;
194 if !result.is_empty() {
195 result.push('_');
196 }
197 result.push(c);
198 } else {
199 last_is_upper = false;
200 result.push_str(&c.to_uppercase().to_string());
201 }
202 }
203 return result;
204}
205
206#[cfg(test)]
207mod tests {
208 #[test]
209 fn ident_to_upper_snake_case() {
210 assert_eq!(
211 "FILE_SYSTEM_IO",
212 super::ident_to_upper_snake_case("FileSystemIO")
213 );
214 }
215}