macro_input_core/
field.rs

1use crate::{convert::FromMeta, DefaultValue};
2#[cfg(feature = "legacy")]
3use macro_compose::{Collector, Context, Lint};
4use proc_macro2::Span;
5use quote::{format_ident, ToTokens};
6use std::{iter::FromIterator, mem::replace};
7use syn::{
8    parse::Parse, parse2, parse_quote, punctuated::Punctuated, Attribute, Error, Lit, Meta,
9    NestedMeta, Result,
10};
11
12/// a field definition
13/// # Example
14/// ```
15/// # use macro_input_core as macro_input;
16/// use macro_input::{DefaultValue, Def};
17///
18/// const BAR_FIELD: Def = Def::new("foo", "bar", false, DefaultValue::Bool(None));
19/// const BAZ_FIELD: Def = Def::new("foo", "baz", false, DefaultValue::Str(None));
20/// ```
21pub struct Def<'a> {
22    /// the path/namespace of the field
23    pub path: &'a str,
24    /// the name of the field
25    pub name: &'a str,
26    /// whether or not this field is required
27    pub required: bool,
28    /// the typed default value
29    pub default: DefaultValue,
30}
31
32impl<'a> Def<'a> {
33    /// create a new field definition
34    #[must_use]
35    pub const fn new(path: &'a str, name: &'a str, required: bool, default: DefaultValue) -> Self {
36        Def {
37            path,
38            name,
39            required,
40            default,
41        }
42    }
43
44    /// strip away the attributes for this field
45    ///
46    /// This is useful for attribute macros because rust has no way of knowing which attributes were used.
47    /// ```
48    /// # use macro_input_core as macro_input;
49    /// use macro_input::{DefaultValue, Def};
50    /// use syn::{parse_quote, Attribute};
51    ///
52    /// // construct some attributes
53    /// let foo_attr: Attribute = parse_quote!(#[foo(bar = false)]);
54    /// let other_attr: Attribute = parse_quote!(#[some(thing = "value")]);
55    /// let mut attrs = vec![foo_attr, other_attr.clone()];
56    ///
57    /// // strip away all mentions of the field bar in foo
58    /// const BAR_FIELD: Def = Def::new("foo", "bar", false, DefaultValue::Bool(None));
59    /// BAR_FIELD.strip(&mut attrs);
60    ///
61    /// // the Vec no longer contains #[foo(bar = false)] but #[some(thing = "value")] remains
62    /// assert_eq!(attrs, vec![other_attr]);
63    /// ```
64    pub fn strip(&self, attrs: &mut Vec<Attribute>) {
65        let data = replace(attrs, Vec::new());
66        attrs.extend(data.into_iter().filter_map(|mut a| {
67            if self.strip_from_attribute(&mut a) {
68                None
69            } else {
70                Some(a)
71            }
72        }));
73    }
74
75    /// strip the attribute and return whether it was empty
76    fn strip_from_attribute(&self, attr: &mut Attribute) -> bool {
77        let mut meta = if let Ok(meta) = attr.parse_meta() {
78            meta
79        } else {
80            return false;
81        };
82
83        // check the path
84        if !meta.path().is_ident(self.path) {
85            return false;
86        }
87
88        match &mut meta {
89            Meta::List(list) => {
90                let new_punctuated = list
91                    .nested
92                    .iter()
93                    .filter(|meta| {
94                        if let NestedMeta::Meta(meta) = meta {
95                            !meta.path().is_ident(self.name)
96                        } else {
97                            true
98                        }
99                    })
100                    .cloned();
101                list.nested = Punctuated::from_iter(new_punctuated);
102
103                let empty = list.nested.is_empty();
104                *attr = parse_quote!(#[#list]);
105                empty
106            }
107            Meta::Path(_) => true,
108            _ => false,
109        }
110    }
111
112    /// try to find the meta that has the value for this field
113    ///
114    /// # Errors
115    /// may return the error if the field is required but not found
116    pub fn get_meta(&self, attrs: &[Attribute]) -> Result<Option<Meta>> {
117        for attr in attrs.iter() {
118            let meta = attr.parse_meta()?;
119            if meta.path().is_ident(self.path) {
120                if let Meta::List(list) = meta {
121                    for meta in list.nested.iter() {
122                        if let NestedMeta::Meta(meta) = meta {
123                            if meta.path().is_ident(self.name) {
124                                return Ok(Some(meta.clone()));
125                            }
126                        }
127                    }
128                }
129            }
130        }
131
132        if self.required {
133            return Err(Error::new(
134                Span::call_site(),
135                format!(
136                    "attribute for required field not found: {}::{}",
137                    self.path, self.name
138                ),
139            ));
140        }
141
142        // construct a default meta
143        if let Some(lit) = self.default.as_lit() {
144            let name = format_ident!("{}", self.path);
145
146            return Ok(Some(parse_quote!(#name = #lit)));
147        }
148
149        Ok(None)
150    }
151
152    /// try to find the literal that has the value for this field
153    ///
154    /// # Errors
155    /// may return the error if the field is required but not found
156    pub fn get_lit(&self, attrs: &[Attribute]) -> Result<Option<Lit>> {
157        Ok(self.get_meta(attrs)?.and_then(|m| match m {
158            Meta::NameValue(nvm) => Some(nvm.lit),
159            _ => None,
160        }))
161    }
162
163    /// try to parse the literal that has the value for this field
164    ///
165    /// # Errors
166    /// may return the error if parsing fails
167    pub fn get<L: Parse>(&self, attrs: &[Attribute]) -> Result<Option<L>> {
168        self.get_lit(attrs)?
169            .map(|lit| {
170                let tokens = lit.to_token_stream();
171                parse2(tokens)
172            })
173            .transpose()
174    }
175
176    /// try to extract the value from the literal that has the value for this field
177    ///
178    /// ```
179    /// # use macro_input_core as macro_input;
180    /// use macro_input::{DefaultValue, Def};
181    /// use syn::{parse_quote, Attribute};
182    ///
183    /// # fn main() -> syn::Result<()> {
184    /// let attr = parse_quote!(#[foo(bar = false)]);
185    /// const BAR_FIELD: Def = Def::new("foo", "bar", false, DefaultValue::Bool(None));
186    /// let value = BAR_FIELD.get_value::<bool>(&[attr])?;
187    /// assert_eq!(value, false);
188    /// # Ok(())
189    /// # }
190    /// ```
191    ///
192    /// # Errors
193    /// may return an error if the field doesn't exist or has a value of the wrong type
194    pub fn get_value<V: FromMeta>(&self, attrs: &[Attribute]) -> Result<V> {
195        self.get_meta(attrs).and_then(FromMeta::from)
196    }
197}
198
199#[cfg(feature = "legacy")]
200impl Lint<Vec<Attribute>> for Def<'_> {
201    fn lint(&self, input: &Vec<Attribute>, c: &mut Collector) {
202        let mut found = false;
203
204        for attr in input.iter() {
205            let meta = attr.parse_meta().unwrap();
206            if meta.path().is_ident(self.path) {
207                if let Meta::List(list) = meta {
208                    for meta in list.nested.iter() {
209                        if let NestedMeta::Meta(meta) = meta {
210                            if meta.path().is_ident(self.name) {
211                                match meta {
212                                    Meta::NameValue(meta) => {
213                                        if found {
214                                            c.error(Error::new_spanned(
215                                                meta,
216                                                format!("dupplicate {} attribute", self.path),
217                                            ));
218                                        } else {
219                                            found = true;
220                                        }
221
222                                        let some_lit = Some(&meta.lit);
223                                        let mut subcontext = Context::new_by_ref(c, &some_lit);
224                                        subcontext.lint(
225                                            &self
226                                                .default
227                                                .ty(!self.required
228                                                    && !self.default.has_default_data()),
229                                        );
230                                    }
231                                    Meta::Path(_) => {
232                                        if found {
233                                            c.error(Error::new_spanned(
234                                                meta,
235                                                format!("dupplicate {} attribute", self.path),
236                                            ));
237                                        } else {
238                                            found = true;
239                                        }
240
241                                        let mut subcontext = Context::new_by_ref(c, &None);
242                                        subcontext.lint(
243                                            &self
244                                                .default
245                                                .ty(!self.required
246                                                    && !self.default.has_default_data()),
247                                        );
248                                    }
249                                    _ => {
250                                        c.error(Error::new_spanned(&meta, "unexpected meta list"));
251                                    }
252                                }
253                            }
254                        }
255                    }
256                }
257            }
258        }
259
260        if !found && self.required {
261            c.error(Error::new(
262                Span::call_site(),
263                format!("missing required {} attribute", self.name),
264            ));
265        }
266    }
267}