macro_input_core/
fields.rs

1use crate::Def;
2#[cfg(feature = "legacy")]
3use macro_compose::{Collector, Lint};
4#[cfg(feature = "legacy")]
5use quote::ToTokens;
6use syn::Attribute;
7#[cfg(feature = "legacy")]
8use syn::{Error, Meta, NestedMeta, Path};
9
10/// `Defs` is a collection of [`Def`]s
11/// # Example
12/// ```
13/// # use macro_input_core as macro_input;
14/// use macro_input::{DefaultValue, Def, Defs};
15///
16/// const BAR_FIELD: Def = Def::new("foo", "bar", false, DefaultValue::Bool(None));
17/// const BAZ_FIELD: Def = Def::new("foo", "baz", false, DefaultValue::Str(None));
18/// const FOO_FIELDS: &[&Def] = &[&BAR_FIELD, &BAZ_FIELD];
19/// const FOO_FIELD_DEFS: Defs = Defs::new(FOO_FIELDS);
20/// ```
21pub struct Defs<'a> {
22    defs: &'a [&'a Def<'a>],
23}
24
25impl<'a> Defs<'a> {
26    /// create a new collection of [`Def`]s from a slice
27    #[must_use]
28    pub const fn new(defs: &'a [&'a Def<'a>]) -> Self {
29        Defs { defs }
30    }
31
32    /// return an empty collection of [`Def`]s
33    #[must_use]
34    pub const fn empty() -> &'static Defs<'static> {
35        const EMPTY: Defs<'static> = Defs { defs: &[] };
36        &EMPTY
37    }
38
39    /// strip away the attributes for all fields
40    ///
41    /// This is useful for attribute macros because rust has no way of knowing which attributes were used.
42    /// ```
43    /// # use macro_input_core as macro_input;
44    /// use macro_input::{DefaultValue, Def, Defs};
45    /// use syn::{parse_quote, Attribute};
46    ///
47    /// // construct some attributes
48    /// let attr1: Attribute = parse_quote!(#[foo(bar = false)]);
49    /// let attr2: Attribute = parse_quote!(#[foo(baz = "baz")]);
50    /// let attr3: Attribute = parse_quote!(#[foo(qux = "qux", quux = 3)]);
51    /// let attr4: Attribute = parse_quote!(#[foo(quuz = 'b')]);
52    /// let attr5: Attribute = parse_quote!(#[some(thing = "value")]);
53    /// let mut attrs = vec![attr1, attr2, attr3, attr4, attr5];
54    ///
55    /// // strip away all mentions of the fields bar, baz and qux in foo
56    /// const BAR_FIELD: Def = Def::new("foo", "bar", false, DefaultValue::Bool(None));
57    /// const BAZ_FIELD: Def = Def::new("foo", "baz", false, DefaultValue::Str(None));
58    /// const QUX_FIELD: Def = Def::new("foo", "qux", false, DefaultValue::Str(None));
59    /// const FOO_FIELDS: &[&Def] = &[&BAR_FIELD, &BAZ_FIELD, &QUX_FIELD];
60    /// const FOO_FIELD_DEFS: Defs = Defs::new(FOO_FIELDS);
61    /// FOO_FIELD_DEFS.strip(&mut attrs);
62    ///
63    /// // the Vec no longer contains
64    /// // #[foo(bar = false)], #[foo(baz = "baz")] and #[foo(qux = "qux")]
65    /// // but
66    /// // #[foo(quux = 3)], #[foo(quuz = 'b')], #[some(thing = "value")]
67    /// // remain
68    /// let attr1: Attribute = parse_quote!(#[foo(quux = 3)]);
69    /// let attr2: Attribute = parse_quote!(#[foo(quuz = 'b')]);
70    /// let attr3: Attribute = parse_quote!(#[some(thing = "value")]);
71    /// assert_eq!(attrs, vec![attr1, attr2, attr3]);
72    /// ```
73    pub fn strip(&self, attrs: &mut Vec<Attribute>) {
74        for def in self.defs {
75            def.strip(attrs);
76        }
77    }
78
79    #[cfg(feature = "legacy")]
80    fn has_path(&self, path: &Path) -> bool {
81        for def in self.defs.iter() {
82            if path.is_ident(def.path) {
83                return true;
84            }
85        }
86
87        false
88    }
89}
90
91impl<'a> From<&'a [&'a Def<'a>]> for Defs<'a> {
92    fn from(defs: &'a [&'a Def<'a>]) -> Self {
93        Defs::new(defs)
94    }
95}
96
97#[cfg(feature = "legacy")]
98impl Lint<Vec<Attribute>> for Defs<'_> {
99    fn lint(&self, input: &Vec<Attribute>, c: &mut Collector) {
100        for def in self.defs.iter() {
101            def.lint(input, c);
102        }
103
104        for attr in input.iter() {
105            let meta = attr.parse_meta().unwrap();
106            let path = meta.path();
107            if self.has_path(path) {
108                match &meta {
109                    Meta::List(list) => {
110                        for meta in list.nested.iter() {
111                            match meta {
112                                NestedMeta::Meta(meta) => {
113                                    match meta {
114                                        Meta::NameValue(_) | Meta::Path(_) => {}
115                                        _ => {
116                                            c.error(Error::new_spanned(
117                                                meta,
118                                                "expected name-and-value or path meta",
119                                            ));
120                                        }
121                                    }
122
123                                    let is_part_of_defs = |def: &&Def| {
124                                        path.is_ident(&def.path) && meta.path().is_ident(&def.name)
125                                    };
126                                    if !self.defs.iter().any(is_part_of_defs) {
127                                        c.error(Error::new_spanned(
128                                            meta,
129                                            format!(
130                                                "unrecognized attribute: {}::{}",
131                                                path.to_token_stream(),
132                                                meta.path().to_token_stream()
133                                            ),
134                                        ));
135                                    }
136                                }
137                                NestedMeta::Lit(l) => {
138                                    c.error(Error::new_spanned(l, "expected meta"));
139                                }
140                            }
141                        }
142                    }
143                    _ => c.error(Error::new_spanned(meta, "expected a list meta")),
144                }
145            }
146        }
147    }
148}