macrotk_core/
meta.rs

1use syn::parse::{Parse, ParseStream};
2use syn::spanned::Spanned;
3use syn::punctuated::Punctuated;
4use syn::{Error, Path, Token, Lit, LitStr};
5
6use proc_macro2::Span;
7
8/// Types that can be parsed from a [`Meta`] list.
9pub trait FromMeta: Sized {
10    fn from_meta(a: &MetaValue) -> Result<Self, Error>;
11}
12
13/// A meta item.
14#[derive(Clone)]
15pub enum MetaValue {
16    Path(Path),
17    NameValue(MetaNameValue),
18    List(MetaList),
19    Lit(Lit),
20}
21
22impl MetaValue {
23    /// Tries to take the value as a [`Path`], returning an error if it isn't
24    /// a path.
25    pub fn path(&self) -> Result<&Path, Error> {
26        match self {
27            Self::Path(p) => Ok(p),
28            Self::NameValue(nv) => Err(
29                Error::new(
30                    nv.eq.span(),
31                    "unexpected =; expected a naked path",
32                )
33            ),
34            Self::List(list) => Err(
35                Error::new(
36                    list.paren.map(|p| p.span).unwrap_or_else(Span::call_site),
37                    "unexpected (...); expected a naked path",
38                )
39            ),
40            Self::Lit(lit) => Err(
41                Error::new(
42                    lit.span(),
43                    "expected a naked path",
44                )
45            ),
46        }
47    }
48
49    /// Tries to take the value as a [`MetaNameValue`], returning an error if
50    /// it isn't one.
51    pub fn name_value(&self) -> Result<&MetaNameValue, Error> {
52        match self {
53            Self::NameValue(nv) => Ok(nv),
54            Self::Path(p) => Err(
55                Error::new(
56                    p.span(),
57                    "expected =",
58                )
59            ),
60            Self::List(list) => Err(
61                Error::new(
62                    list.paren.map(|p| p.span).unwrap_or_else(Span::call_site),
63                    "expected =",
64                )
65            ),
66            Self::Lit(lit) => Err(
67                Error::new(
68                    lit.span(),
69                    "unexpected literal; expected =",
70                )
71            ),
72        }
73    }
74
75    /// Tries to take the value as a [`Lit`], returning an error if it isn't
76    /// a literal.
77    pub fn lit(&self) -> Result<&Lit, Error> {
78        match self {
79            Self::Lit(lit) => Ok(lit),
80            Self::Path(p) => Err(
81                Error::new(
82                    p.span(),
83                    "expected a literal",
84                )
85            ),
86            Self::List(list) => Err(
87                Error::new(
88                    list.name.span(),
89                    "expected a literal",
90                )
91            ),
92            Self::NameValue(nv) => Err(
93                Error::new(
94                    nv.name.span(),
95                    "expected a literal",
96                )
97            ),
98        }
99    }
100
101    /// Tries to take the value as a [`List`], returning an error if it isn't
102    /// a list.
103    pub fn list(&self) -> Result<&MetaList, Error> {
104        match self {
105            Self::List(list) => Ok(list),
106            Self::Path(p) => Err(
107                Error::new(
108                    p.span(),
109                    "expected a list",
110                )
111            ),
112            Self::NameValue(nv) => Err(
113                Error::new(
114                    nv.eq.span(),
115                    "unexpected =; expected a list",
116                )
117            ),
118            Self::Lit(lit) => Err(
119                Error::new(
120                    lit.span(),
121                    "expected a list",
122                )
123            ),
124        }
125    }
126
127    /// Attempts to get the value of the `MetaValue`.
128    ///
129    /// As the various variants that `MetaValue` provides:
130    /// * `MetaValue::Lit(lit)`: `lit` is the value.
131    /// * `MetaValue::NameValue(nv)`: `nv.value` is the value.
132    /// * Otherwise, a proper error is generated.
133    pub fn value(&self) -> Result<&Lit, Error> {
134        match self {
135            Self::Lit(lit) => Ok(lit),
136            Self::NameValue(nv) => Ok(&nv.value),
137            Self::Path(p) => Err(
138                Error::new(
139                    p.span(),
140                    "expected a literal",
141                )
142            ),
143            Self::List(list) => Err(
144                Error::new(
145                    list.paren.map(|p| p.span).unwrap_or_else(Span::call_site),
146                    "expected a literal",
147                )
148            ),
149        }
150    }
151
152    pub fn name(&self) -> Option<&syn::Ident> {
153        let path = match self {
154            Self::Path(p) => p,
155            Self::List(list) => list.name.as_ref()?,
156            Self::NameValue(nv) => &nv.name,
157            _ => return None,
158        };
159
160        path.segments.last().map(|l| &l.ident)
161    }
162}
163
164impl From<Lit> for MetaValue {
165    fn from(l: Lit) -> MetaValue {
166        MetaValue::Lit(l)
167    }
168}
169
170impl From<MetaList> for MetaValue {
171    fn from(l: MetaList) -> MetaValue {
172        MetaValue::List(l)
173    }
174}
175
176impl Parse for MetaValue {
177    fn parse(p: ParseStream) -> Result<MetaValue, Error> {
178        if let Ok(name) = p.parse::<Path>() {
179            // check if we have an eq or a paren in front
180            if p.peek(syn::token::Paren) {
181                // this is a list
182                let list;
183                Ok(MetaValue::List(
184                    MetaList {
185                        name: Some(name),
186                        paren: Some(syn::parenthesized!(list in p)),
187                        list: list.parse_terminated(Self::parse)?,
188                    }
189                ))
190            } else if p.peek(Token![=]) {
191                // this is a name-value pair
192                Ok(MetaValue::NameValue(
193                    MetaNameValue {
194                        name,
195                        eq: p.parse()?,
196                        value: p.parse()?,
197                    }
198                ))
199            } else {
200                // this is a path
201                Ok(MetaValue::Path(name))
202            }
203        } else {
204            // parse literal
205            p.parse::<Lit>().map(Into::into)
206        }
207    }
208}
209
210/// A meta name-value pair.
211#[derive(Clone)]
212pub struct MetaNameValue {
213    pub name: Path,
214    pub eq: Token![=],
215    pub value: Lit,
216}
217
218/// A meta list.
219///
220/// Use this as the "entrypoint" of your attribute proc macro.
221#[derive(Clone, Default)]
222pub struct MetaList {
223    /// Can be `None` if this is the root list.
224    pub name: Option<Path>,
225    /// Can be `None` if this is the root list.
226    pub paren: Option<syn::token::Paren>,
227    pub list: Punctuated<MetaValue, Token![,]>,
228}
229
230impl MetaList {
231    /// Gets a type by name.
232    ///
233    /// This considers both list types ([`MetaList`]) and name-value pairs
234    /// ([`MetaNameValue`]).
235    pub fn get<T>(&self, name: &str) -> Option<Result<T, Error>> 
236    where T:
237        FromMeta,
238    {
239        let item = self.list.iter()
240            .filter(|meta| meta.name().map(|n| n == name).unwrap_or(false))
241            .next()?;
242
243        // try to convert the type
244        Some(T::from_meta(&item))
245    }
246
247    pub fn parse_root_attr(p: ParseStream) -> Result<MetaList, Error> {
248        Ok(
249            MetaList {
250                name: None,
251                paren: None,
252                list: p.call(Punctuated::parse_terminated)?,
253            }
254        )
255    }
256}
257
258/// Helper type for parsing attribute token streams in an attribute proc
259/// macro.
260pub struct Meta<T>(pub T);
261
262impl<T> Meta<T> {
263    pub fn into_inner(self) -> T {
264        self.0
265    }
266}
267
268impl<T> std::ops::Deref for Meta<T> {
269    type Target = T;
270
271    fn deref(&self) -> &T {
272        &self.0
273    }
274}
275
276impl<T> Parse for Meta<T> 
277where T:
278    FromMeta,
279{
280    fn parse(p: ParseStream) -> Result<Meta<T>, Error> {
281        if p.is_empty() {
282            // use empty list
283            T::from_meta(&MetaList::default().into())
284                .map(|t| Meta(t))
285        } else {
286            p.call(MetaList::parse_root_attr)
287                .map(Into::into)
288                .and_then(|meta| T::from_meta(&meta))
289                .map(|t| Meta(t))
290        }
291    }
292}
293
294// some basic impls
295impl FromMeta for LitStr {
296    fn from_meta(meta: &MetaValue) -> Result<LitStr, Error> {
297        let value = meta.value()?;
298
299        match &value {
300            Lit::Str(lit) => Ok(lit.clone()),
301            _ => Err(
302                Error::new(
303                    value.span(),
304                    "expected a string literal",
305                )
306            ),
307        }
308    }
309}
310
311// other impls
312impl<T> FromMeta for Option<T>
313where T:
314    FromMeta,
315{
316    fn from_meta(p: &MetaValue) -> Result<Option<T>, Error> {
317        Ok(Some(T::from_meta(p)?))
318    }
319}
320