struct_field_names_as_array_derive/
lib.rs

1//! Derive macros for the
2//! [struct-field-names-as-array](https://crates.io/crates/struct-field-names-as-array)
3//! crate.
4//!
5//! Please refer to struct-field-names-as-array's
6//! [documentation](https://docs.rs/struct-field-names-as-array)
7//! for more information.
8
9extern 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}