nameth_macro/
lib.rs

1#![doc = include_str!("../README.md")]
2
3use darling::FromMeta;
4use darling::ast::NestedMeta;
5use proc_macro::TokenStream;
6use quote::format_ident;
7use quote::quote;
8use syn::Ident;
9
10#[proc_macro_attribute]
11pub fn nameth(attr: TokenStream, tokens: TokenStream) -> TokenStream {
12    let attr_args = match NestedMeta::parse_meta_list(attr.into()) {
13        Ok(args) => args,
14        Err(error) => {
15            return TokenStream::from(darling::Error::from(error).write_errors());
16        }
17    };
18    let attr_args = match NamedMacroArgs::from_list(&attr_args) {
19        Ok(args) => args,
20        Err(error) => {
21            return TokenStream::from(error.write_errors());
22        }
23    };
24
25    let item: syn::Item = match syn::parse(tokens.clone()) {
26        Ok(item) => item,
27        Err(err) => return err.into_compile_error().into(),
28    };
29
30    let crate_name = attr_args.crate_override.as_deref().unwrap_or("nameth");
31    let crate_name = format_ident!("{}", crate_name);
32
33    let name = match item {
34        syn::Item::Struct(item_struct) => process_struct(&crate_name, item_struct),
35        syn::Item::Enum(item_enum) => process_enum(&crate_name, item_enum),
36        syn::Item::Fn(item_fn) => process_fn(item_fn),
37        _ => return quote! { compile_error!("Unexpected item kind"); }.into(),
38    };
39
40    if attr_args.debug {
41        println!("\nGenerated:\n{name}\n");
42    }
43
44    let mut tokens = tokens;
45    tokens.extend(name);
46    return tokens;
47}
48
49fn process_struct(crate_name: &Ident, item_struct: syn::ItemStruct) -> TokenStream {
50    let syn::ItemStruct {
51        ident, generics, ..
52    } = item_struct;
53    let name = ident.to_string();
54    let without_defaults = without_defaults(&generics);
55    let param_names_only = param_names_only(&generics);
56    quote! {
57        impl #without_defaults #crate_name::NamedType for #ident #param_names_only {
58            fn type_name() -> &'static str {
59                return #name;
60            }
61        }
62    }
63    .into()
64}
65
66fn process_enum(crate_name: &Ident, item_enum: syn::ItemEnum) -> TokenStream {
67    let syn::ItemEnum {
68        ident,
69        generics,
70        variants,
71        ..
72    } = item_enum;
73    let cases: Vec<_> = variants
74        .iter()
75        .map(|variant| {
76            let ident = &variant.ident;
77            let name = ident.to_string();
78            quote! { Self::#ident { .. } => #name, }
79        })
80        .collect();
81
82    let without_defaults = without_defaults(&generics);
83    let param_names_only = param_names_only(&generics);
84    let name = ident.to_string();
85    quote! {
86        impl #without_defaults #crate_name::NamedType for #ident #param_names_only {
87            fn type_name() -> &'static str {
88                return #name;
89            }
90        }
91        impl #without_defaults #crate_name::NamedEnumValues for #ident #param_names_only {
92            fn name(&self) -> &'static str {
93                match self {
94                    #(#cases)*
95                }
96            }
97        }
98    }
99    .into()
100}
101
102fn process_fn(item_fn: syn::ItemFn) -> TokenStream {
103    let name = item_fn.sig.ident.to_string();
104    let vis = item_fn.vis;
105    let ident = format_ident!("{}", name.to_uppercase());
106    quote! { #vis const #ident : &'static str = #name; }.into()
107}
108
109#[derive(Debug, FromMeta)]
110struct NamedMacroArgs {
111    #[darling(default)]
112    debug: bool,
113
114    #[darling(default)]
115    crate_override: Option<String>,
116}
117
118fn param_names_only(generics: &syn::Generics) -> syn::Generics {
119    let mut generics = without_defaults(generics);
120    for param in &mut generics.params {
121        match param {
122            syn::GenericParam::Lifetime(syn::LifetimeParam {
123                colon_token,
124                bounds,
125                ..
126            }) => {
127                *colon_token = None;
128                *bounds = syn::punctuated::Punctuated::default()
129            }
130            syn::GenericParam::Type(syn::TypeParam {
131                colon_token,
132                bounds,
133                ..
134            }) => {
135                *colon_token = None;
136                *bounds = syn::punctuated::Punctuated::default();
137            }
138            syn::GenericParam::Const(syn::ConstParam {
139                eq_token, default, ..
140            }) => {
141                *eq_token = None;
142                *default = None;
143            }
144        }
145    }
146    generics
147}
148
149fn without_defaults(generics: &syn::Generics) -> syn::Generics {
150    let mut generics = generics.clone();
151    for param in &mut generics.params {
152        match param {
153            syn::GenericParam::Lifetime(syn::LifetimeParam { attrs, .. }) => {
154                *attrs = vec![];
155            }
156            syn::GenericParam::Type(syn::TypeParam {
157                attrs,
158                eq_token,
159                default,
160                ..
161            }) => {
162                *attrs = vec![];
163                *eq_token = None;
164                *default = None;
165            }
166            syn::GenericParam::Const(syn::ConstParam { attrs, .. }) => {
167                *attrs = vec![];
168            }
169        }
170    }
171    generics
172}