1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
#![doc = include_str!("../README.md")]

extern crate proc_macro;
use proc_macro::TokenStream;

use quote::quote;
use syn::punctuated::Punctuated;
use syn::token::Comma;
use syn::{
  parse_macro_input, AttrStyle, Data, DeriveInput, Fields, Meta,
  NestedMeta,
};

const ERR_MSG: &str =
  "Derive(FieldNamesAsArray) only applicable to named structs";

#[proc_macro_derive(
  FieldNamesAsArray,
  attributes(field_names_as_array)
)]
pub fn derive_field_names_as_array(
  input: TokenStream,
) -> TokenStream {
  let input = parse_macro_input!(input as DeriveInput);

  let name = &input.ident;
  let vis = &input.vis;
  let (impl_generics, type_generics, where_clause) =
    &input.generics.split_for_impl();

  let field_names: Punctuated<String, Comma> = match input.data {
    Data::Struct(data_struct) => match data_struct.fields {
      Fields::Named(fields) => fields
        .named
        .into_iter()
        .filter_map(|f| {
          for attr in f.attrs.iter() {
            match attr.style {
              AttrStyle::Outer => {}
              _ => continue,
            }

            let attr_name = attr
              .path
              .segments
              .iter()
              .last()
              .cloned()
              .expect("attribute is badly formatted");

            if attr_name.ident != "field_names_as_array" {
              continue;
            }

            let meta = attr
              .parse_meta()
              .expect("cannot parse attribute to meta");

            let list = match meta {
              Meta::List(l) => l,
              _ => panic!("field_names_as_array needs an argument"),
            };

            let arg = list
              .nested
              .iter()
              .next()
              .expect("argument list cannot be empty");

            match arg {
              NestedMeta::Meta(m) => match m.path().get_ident() {
                Some(i) if i == "skip" => return None,
                _ => panic!("unknown argument"),
              },
              _ => panic!("badly formatted argument"),
            }
          }

          Some(f.ident.unwrap().to_string())
        })
        .collect(),
      _ => panic!("{}", ERR_MSG),
    },
    _ => panic!("{}", ERR_MSG),
  };

  let result = quote! {
    impl #impl_generics #name #type_generics #where_clause {
      #[doc=concat!("Generated array of field names for `", stringify!(#name #type_generics), "`.")]
      #vis const FIELD_NAMES_AS_ARRAY: &'static [&'static str] =
        &[#field_names];
    }
  };

  TokenStream::from(result)
}