struct_field_names_as_array_derive/
lib.rs1extern crate proc_macro;
10use proc_macro::TokenStream;
11
12use quote::quote;
13use syn::punctuated::Punctuated;
14use syn::token::Comma;
15use syn::{
16 parse_macro_input, Data, DataStruct, DeriveInput, Error, Field, Fields, FieldsNamed, Result,
17};
18
19mod attrs;
20
21use attrs::{ContainerAttributes, FieldAttributes, ParseAttributes};
22
23#[allow(clippy::missing_panics_doc)]
24#[proc_macro_derive(FieldNamesAsArray, attributes(field_names_as_array))]
25pub fn derive_field_names_as_array(input: TokenStream) -> TokenStream {
26 let input = parse_macro_input!(input as DeriveInput);
27
28 let name = &input.ident;
29
30 let (impl_generics, type_generics, where_clause) = &input.generics.split_for_impl();
31
32 let container_attributes =
33 ContainerAttributes::parse_attributes("field_names_as_array", &input.attrs).unwrap();
34
35 let Data::Struct(DataStruct {
36 fields: Fields::Named(FieldsNamed { named, .. }),
37 ..
38 }) = input.data
39 else {
40 panic!("Derive(FieldNamesAsArray) only applicable to named structs");
41 };
42
43 let field_names = field_names(named, &container_attributes).unwrap();
44
45 let len = field_names.len();
46
47 TokenStream::from(quote! {
48 impl #impl_generics ::struct_field_names_as_array::FieldNamesAsArray<#len> for #name #type_generics #where_clause {
49 #[doc=concat!("Generated array of field names for `", stringify!(#name #type_generics), "`.")]
50 const FIELD_NAMES_AS_ARRAY: [&'static str; #len] = [#(#field_names),*];
51 }
52 })
53}
54
55#[allow(clippy::missing_panics_doc)]
56#[proc_macro_derive(FieldNamesAsSlice, attributes(field_names_as_slice))]
57pub fn derive_field_names_as_slice(input: TokenStream) -> TokenStream {
58 let input = parse_macro_input!(input as DeriveInput);
59
60 let name = &input.ident;
61
62 let (impl_generics, type_generics, where_clause) = &input.generics.split_for_impl();
63
64 let container_attributes =
65 ContainerAttributes::parse_attributes("field_names_as_slice", &input.attrs).unwrap();
66
67 let Data::Struct(DataStruct {
68 fields: Fields::Named(FieldsNamed { named, .. }),
69 ..
70 }) = input.data
71 else {
72 panic!("Derive(FieldNamesAsSlice) only applicable to named structs");
73 };
74
75 let field_names = field_names(named, &container_attributes).unwrap();
76
77 TokenStream::from(quote! {
78 impl #impl_generics ::struct_field_names_as_array::FieldNamesAsSlice for #name #type_generics #where_clause {
79 #[doc=concat!("Generated slice of field names for `", stringify!(#name #type_generics), "`.")]
80 const FIELD_NAMES_AS_SLICE: &'static [&'static str] = &[#(#field_names),*];
81 }
82 })
83}
84
85fn field_names(
86 fields: Punctuated<Field, Comma>,
87 container_attributes: &ContainerAttributes,
88) -> Result<Vec<String>> {
89 let mut res = Vec::new();
90
91 for field in fields {
92 let field_attributes =
93 FieldAttributes::parse_attributes(container_attributes.attribute(), &field.attrs)?;
94
95 let Some(field) = field.ident else {
96 return Err(Error::new_spanned(field, "field must be a named field"));
97 };
98
99 let field = container_attributes.apply_to_field(&field.to_string());
100
101 if let Some(field) = field_attributes.apply_to_field(&field) {
102 res.push(field);
103 }
104 }
105
106 Ok(res)
107}