1use itertools::Itertools;
2use proc_macro::TokenStream;
3use proc_macro2::{Ident, Span, TokenTree};
4use quote::{quote, ToTokens};
5use syn::{
6 parse_macro_input, parse_quote, Attribute, Data, DeriveInput, Expr, ExprLit, Fields,
7 FieldsNamed, Generics, Lit, Meta, Type, TypeGenerics, Visibility, WhereClause,
8};
9
10#[proc_macro_derive(FieldnameAccess, attributes(fieldname_enum, fieldname))]
98pub fn fieldname_accessor(inp: TokenStream) -> TokenStream {
99 let inp = parse_macro_input!(inp as DeriveInput);
100 let structure = match inp.data {
101 Data::Struct(ref s) => s,
102 Data::Union(_) => {
103 panic!("FieldnameAccess cannot be used with unions")
104 }
105 Data::Enum(_) => {
106 panic!("FieldnameAccess cannot be used with enums")
107 }
108 };
109 let DeriveInput {
110 ident: struct_ident,
111 vis: visibility,
112 generics,
113 ..
114 } = inp;
115
116 let field_lifetime: syn::GenericParam = parse_quote!('field);
117 let (impl_generics, ty_generics, where_clauses) = generics.split_for_impl();
118
119 let mut enum_generics = generics.clone();
120 enum_generics.params.push(field_lifetime.clone());
121
122 let fields = match &structure.fields {
123 Fields::Named(FieldsNamed { named: x, .. }) => x.to_owned(),
124 Fields::Unnamed(_) | Fields::Unit => {
125 panic!("Nameless fields are not supported")
126 }
127 };
128
129 let field_map = fields
130 .into_iter()
131 .map(|field| {
132 let field_type = field.ty;
133 let field_name = field.ident.expect("Nameless fields are not supported");
134 let variant_ident = if let Some(name) = retrieve_fieldname(&field.attrs) {
135 name
136 } else {
137 let type_str = generate_variant_name(&field_type);
138 Ident::new(&type_str, Span::call_site())
139 };
140 (field_name, field_type, variant_ident)
141 })
142 .collect::<Vec<_>>();
143 let field_list = field_map.iter().map(|(name, _, _)| name.to_string());
144 let field_count = field_map.len();
145
146 let (derive, derive_mut) = if let Some(derives) = retrieve_derives(&inp.attrs, "derive_all") {
147 (Some(derives.clone()), Some(derives))
148 } else {
149 let derive = retrieve_derives(&inp.attrs, "derive");
150 let derive_mut = retrieve_derives(&inp.attrs, "derive_mut");
151 (derive, derive_mut)
152 };
153
154 let value_enum_ident = retrieve_enum_name(&inp.attrs).unwrap_or(Ident::new(
155 &format!("{}Field", struct_ident),
156 Span::call_site(),
157 ));
158 let value_enum_ident_mut = Ident::new(&format!("{}Mut", value_enum_ident), Span::call_site());
159
160 let value_variants = generate_enum_variants(&field_map, false);
161 let value_variants_mut = generate_enum_variants(&field_map, true);
162
163 let match_arms = generate_match_arms(&field_map, &value_enum_ident, false);
164 let match_arms_mut = generate_match_arms(&field_map, &value_enum_ident_mut, true);
165
166 let iter_impl = generate_iter_impl(
167 &visibility,
168 &value_enum_ident,
169 &struct_ident,
170 &ty_generics,
171 &where_clauses,
172 &enum_generics,
173 &field_lifetime,
174 );
175
176 let tokens = quote! {
177 #derive
179 #visibility enum #value_enum_ident #enum_generics {
180 #(#value_variants,)*
181 }
182
183 #derive_mut
185 #visibility enum #value_enum_ident_mut #enum_generics {
186 #(#value_variants_mut,)*
187 }
188
189 #iter_impl
190
191 impl #impl_generics #struct_ident #ty_generics #where_clauses {
192 const FIELDS: [&'static str; #field_count] = [#(#field_list),*];
194
195 #visibility fn field<#field_lifetime>(&#field_lifetime self, fieldname: &str) -> Option<#value_enum_ident #enum_generics> {
197 match fieldname {
198 #(#match_arms,)*
199 _ => None
200 }
201 }
202 #visibility fn field_mut<#field_lifetime>(&#field_lifetime mut self, fieldname: &str) -> Option<#value_enum_ident_mut #enum_generics> {
204 match fieldname {
205 #(#match_arms_mut,)*
206 _ => None
207 }
208 }
209 }
210 };
211 tokens.into()
212}
213
214fn generate_variant_name(ty: &syn::Type) -> String {
215 let type_str = ty.to_token_stream().to_string();
216 shorten_type(type_str)
217}
218
219fn generate_iter_impl(
220 vis: &Visibility,
221 value_enum_ident: &Ident,
222 struct_ident: &Ident,
223 struct_generics: &TypeGenerics,
224 where_clauses: &Option<&WhereClause>,
225 enum_generics: &Generics,
226 enum_lt: &syn::GenericParam,
227) -> proc_macro2::TokenStream {
228 let iter_ident = Ident::new(&format!("{}FieldIter", value_enum_ident), Span::call_site());
229
230 let struct_generic_turbofish = struct_generics.as_turbofish();
231 let struct_ident_turbofish = quote! { #struct_ident #struct_generic_turbofish };
232
233 quote! {
234 #vis struct #iter_ident #enum_generics #where_clauses {
235 idx: usize,
236 inner: &#enum_lt #struct_ident #struct_generics
237 }
238
239 impl #struct_generics #struct_ident #struct_generics #where_clauses {
240 pub fn field_iter<#enum_lt>(&#enum_lt self) -> #iter_ident #enum_generics {
241 #iter_ident {
242 idx: 0,
243 inner: self
244 }
245 }
246 }
247
248 impl #enum_generics Iterator for #iter_ident #enum_generics #where_clauses {
249 type Item = (&'static str, #value_enum_ident #enum_generics);
250
251 fn next(&mut self) -> Option<Self::Item> {
252 (self.idx != #struct_ident_turbofish::FIELDS.len()).then(|| {
253 let field_name = #struct_ident_turbofish::FIELDS[self.idx];
254 self.idx += 1;
255 (field_name, self.inner.field(field_name).unwrap())
256 })
257 }
258 }
259 }
260}
261
262fn shorten_type(type_str: String) -> String {
263 let mut short_type = type_str
264 .chars()
265 .skip_while(|c| !c.is_uppercase())
266 .peekable();
267 if short_type.peek().is_some() {
268 let mut complex_type_str = String::new();
269 while let Some(c) = short_type.next() {
270 if c.is_ascii_alphanumeric() {
271 complex_type_str.push(c);
272 }
273 if c == '<' {
274 complex_type_str += &shorten_type(short_type.collect());
275 break;
276 }
277 }
278 complex_type_str
279 } else {
280 let cleaned_str = type_str
281 .chars()
282 .filter(|c| c.is_ascii_alphanumeric())
283 .collect::<String>();
284 cleaned_str[0..1].to_uppercase() + &cleaned_str[1..]
285 }
286}
287
288fn generate_enum_variants(
289 field_map: &[(Ident, syn::Type, Ident)],
290 is_mut: bool,
291) -> Vec<proc_macro2::TokenStream> {
292 field_map
293 .iter()
294 .unique_by(|(_, _, variant_ident)| variant_ident)
295 .map(|(_, field_type, variant_ident)| {
296 if is_mut {
297 quote! {
298 #variant_ident(&'field mut #field_type)
299 }
300 } else {
301 quote! {
302 #variant_ident(&'field #field_type)
303 }
304 }
305 })
306 .collect()
307}
308
309fn generate_match_arms(
310 field_map: &[(Ident, Type, Ident)],
311 value_enum_ident: &Ident,
312 is_mut: bool,
313) -> Vec<proc_macro2::TokenStream> {
314 field_map
315 .iter()
316 .map(|(field_name, _, variant_ident)| {
317 let field_name_str = field_name.to_string();
318 if is_mut {
319 quote! {
320 #field_name_str => Some(#value_enum_ident::#variant_ident(&mut self.#field_name))
321 }
322 } else {
323 quote! {
324 #field_name_str => Some(#value_enum_ident::#variant_ident(&self.#field_name))
325 }
326 }
327 })
328 .collect()
329}
330
331fn retrieve_enum_name(attrs: &[Attribute]) -> Option<Ident> {
332 if let Some(TokenTree::Literal(lit)) = get_fieldname_enum_val(attrs, "name") {
333 let lit = lit.to_string();
334 Some(Ident::new(&lit[1..lit.len() - 1], Span::call_site()))
335 } else {
336 None
337 }
338}
339
340fn retrieve_derives(attrs: &[Attribute], derive_group: &str) -> Option<proc_macro2::TokenStream> {
341 if let Some(TokenTree::Group(group)) = get_fieldname_enum_val(attrs, derive_group) {
342 let token_stream = group.stream();
343 Some(quote!(#[derive(#token_stream)]))
344 } else {
345 None
346 }
347}
348
349fn retrieve_fieldname(attrs: &[Attribute]) -> Option<Ident> {
350 attrs.iter().find_map(|attr| match &attr.meta {
351 Meta::NameValue(meta_name_value) => {
352 let fieldname_enum_attr = meta_name_value.path.segments.first()?;
353 if fieldname_enum_attr.ident != "fieldname" {
354 return None;
355 }
356 if let Expr::Lit(ExprLit {
357 lit: Lit::Str(ref str),
358 ..
359 }) = meta_name_value.value
360 {
361 Some(Ident::new(&str.value(), Span::call_site()))
362 } else {
363 None
364 }
365 }
366
367 _ => None,
368 })
369}
370
371fn get_fieldname_enum_val(attrs: &[Attribute], attr_name: &str) -> Option<TokenTree> {
372 attrs.iter().find_map(|attr| match &attr.meta {
373 Meta::List(meta_list) => {
374 let fieldname_enum_attr = meta_list.path.segments.first()?;
375 if fieldname_enum_attr.ident != "fieldname_enum" {
376 return None;
377 }
378 meta_list
379 .tokens
380 .clone()
381 .into_iter()
382 .skip_while(|token| token.to_string() != attr_name)
383 .nth(2)
384 }
385 _ => None,
386 })
387}