evitable_syn_meta_ext/
from_meta.rs

1use crate::quote::ToTokens;
2use proc_macro2::TokenStream;
3use std::rc::Rc;
4use std::sync::atomic::AtomicBool;
5use std::sync::Arc;
6use syn::spanned::Spanned;
7use syn::{Ident, Lit, Path};
8
9use super::error::{Error, Result};
10use super::{Meta, MetaValue, NestedMeta};
11
12pub trait FromMeta: Sized {
13  fn from_nested_meta(item: &NestedMeta) -> Result<Self> {
14    (match item {
15      NestedMeta::Literal(lit) => Self::from_lit(lit),
16      NestedMeta::Meta(mi) => match mi {
17        Meta::Path(p) => Self::from_path(p),
18        Meta::NameValue(_) => Err(Error::unexpected_type("name value").with_span(item)),
19        Meta::List(_) => Err(Error::unexpected_type("list").with_span(item)),
20      },
21    })
22    .map_err(|e| e.with_span(item))
23  }
24
25  /// Create an instance from a `syn::Meta` by dispatching to the format-appropriate
26  /// trait function. This generally should not be overridden by implementers.
27  ///
28  /// # Error Spans
29  /// If this method is overridden and can introduce errors that weren't passed up from
30  /// other `from_meta` calls, the override must call `with_span` on the error using the
31  /// `item` to make sure that the emitted diagnostic points to the correct location in
32  /// source code.
33  fn from_meta(item: &Meta) -> Result<Self> {
34    (match item {
35      Meta::Path(_) => Self::from_empty(),
36      Meta::List(value) => {
37        Self::from_list(value.nested.iter().collect::<Vec<&NestedMeta>>().as_ref())
38      }
39      Meta::NameValue(value) => Self::from_value(&value.val),
40    })
41    .map_err(|e| e.with_span(item))
42  }
43
44  /// Create an instance from the presence of the word in the attribute with no
45  /// additional options specified.
46  fn from_empty() -> Result<Self> {
47    Err(Error::unsupported_format("empty"))
48  }
49
50  /// Create an instance from a list of nested meta items.
51  #[allow(unused_variables)]
52  fn from_list(items: &[&NestedMeta]) -> Result<Self> {
53    match items.len() {
54      0 => Self::from_empty(),
55      1 => Self::from_nested_meta(&items[0]),
56      _ => Err(Error::unsupported_format("list")),
57    }
58  }
59
60  /// Create an instance from a literal value of either `foo = "bar"` or `foo("bar")`.
61  /// This dispatches to the appropriate method based on the type of literal encountered,
62  /// and generally should not be overridden by implementers.
63  ///
64  /// # Error Spans
65  /// If this method is overridden, the override must make sure to add `value`'s span
66  /// information to the returned error by calling `with_span(value)` on the `Error` instance.
67  fn from_value(value: &MetaValue) -> Result<Self> {
68    (match value {
69      MetaValue::Path(path) => Self::from_path(path),
70      MetaValue::Literal(lit) => Self::from_lit(lit),
71    })
72    .map_err(|e| e.with_span(value))
73  }
74
75  fn from_lit(lit: &Lit) -> Result<Self> {
76    (match lit {
77      Lit::Bool(b) => Self::from_bool(b.value, b),
78      Lit::Str(s) => Self::from_string(&s.value(), s),
79      Lit::Char(c) => Self::from_char(c.value(), c),
80      Lit::Int(i) => Self::from_int(i.base10_parse()?, i),
81      _ => Err(Error::unexpected_lit_type(lit)),
82    })
83    .map_err(|e| e.with_span(lit))
84  }
85
86  /// Create an instance from a char literal in a value position.
87  #[allow(unused_variables)]
88  fn from_char<S: Spanned>(value: char, span: &S) -> Result<Self> {
89    Err(Error::unexpected_type("char").with_span(span))
90  }
91
92  /// Create an instance from a int literal in a value position.
93  #[allow(unused_variables)]
94  fn from_int<S: Spanned>(value: u64, span: &S) -> Result<Self> {
95    Err(Error::unexpected_type("int").with_span(span))
96  }
97
98  /// Create an instance from a char literal in a value position.
99  #[allow(unused_variables)]
100  fn from_path(value: &Path) -> Result<Self> {
101    if value.leading_colon.is_none()
102      && value.segments.len() == 1
103      && value.segments[0].arguments == syn::PathArguments::None
104    {
105      Self::from_ident(&value.segments[0].ident)
106    } else {
107      Err(Error::unexpected_type("path"))
108    }
109  }
110
111  /// Create an instance from a string literal in a value position.
112  #[allow(unused_variables)]
113  fn from_string<S: Spanned>(value: &str, span: &S) -> Result<Self> {
114    Err(Error::unexpected_type("string").with_span(span))
115  }
116
117  /// Create an instance from a bool literal in a value position.
118  #[allow(unused_variables)]
119  fn from_bool<S: Spanned>(value: bool, span: &S) -> Result<Self> {
120    Err(Error::unexpected_type("bool").with_span(span))
121  }
122
123  #[allow(unused_variables)]
124  fn from_ident(value: &Ident) -> Result<Self> {
125    Err(Error::unexpected_type("ident"))
126  }
127}
128
129// FromMeta impls for std and syn types.
130
131impl FromMeta for () {
132  fn from_empty() -> Result<Self> {
133    Ok(())
134  }
135}
136
137impl FromMeta for bool {
138  fn from_empty() -> Result<Self> {
139    Ok(true)
140  }
141
142  fn from_bool<S: Spanned>(value: bool, _: &S) -> Result<Self> {
143    Ok(value)
144  }
145
146  fn from_string<S: Spanned>(value: &str, span: &S) -> Result<Self> {
147    value
148      .parse()
149      .map_err(|_| Error::unknown_value(value).with_span(span))
150  }
151}
152
153impl FromMeta for AtomicBool {
154  fn from_meta(mi: &Meta) -> Result<Self> {
155    FromMeta::from_meta(mi)
156      .map(AtomicBool::new)
157      .map_err(|e| e.with_span(mi))
158  }
159}
160
161impl FromMeta for String {
162  fn from_string<S: Spanned>(s: &str, _span: &S) -> Result<Self> {
163    Ok(s.to_string())
164  }
165
166  fn from_path(p: &Path) -> Result<Self> {
167    let mut ss = TokenStream::new();
168    p.to_tokens(&mut ss);
169    Ok(format!("{}", ss))
170  }
171}
172
173/// Generate an impl of `FromMeta` that will accept strings which parse to numbers or
174/// integer literals.
175macro_rules! from_meta_num {
176  ($ty:ident) => {
177    impl FromMeta for $ty {
178      fn from_string<S: Spanned>(s: &str, span: &S) -> Result<Self> {
179        s.parse()
180          .map_err(|_| Error::unknown_value(s).with_span(span))
181      }
182
183      fn from_lit(value: &Lit) -> Result<Self> {
184        (match value {
185          Lit::Str(s) => Self::from_string(&s.value(), s),
186          Lit::Int(s) => Ok(s.base10_parse()?),
187          v => Err(Error::unexpected_lit_type(value).with_span(&v)),
188        })
189        .map_err(|e| e.with_span(value))
190      }
191    }
192  };
193}
194
195from_meta_num!(u8);
196from_meta_num!(u16);
197from_meta_num!(u32);
198from_meta_num!(u64);
199from_meta_num!(usize);
200from_meta_num!(i8);
201from_meta_num!(i16);
202from_meta_num!(i32);
203from_meta_num!(i64);
204from_meta_num!(isize);
205
206/// Generate an impl of `FromMeta` that will accept strings which parse to floats or
207/// float literals.
208macro_rules! from_meta_float {
209  ($ty:ident) => {
210    impl FromMeta for $ty {
211      fn from_string<S: Spanned>(s: &str, span: &S) -> Result<Self> {
212        s.parse()
213          .map_err(|_| Error::unknown_value(s).with_span(span))
214      }
215
216      fn from_lit(value: &Lit) -> Result<Self> {
217        (match value {
218          Lit::Str(s) => Self::from_string(&s.value(), s),
219          Lit::Float(s) => Ok(s.base10_parse()?),
220          v => Err(Error::unexpected_lit_type(value).with_span(&v)),
221        })
222        .map_err(|e| e.with_span(value))
223      }
224    }
225  };
226}
227
228from_meta_float!(f32);
229from_meta_float!(f64);
230
231/// Parsing support for identifiers. This attempts to preserve span information
232/// when available, but also supports parsing strings with the call site as the
233/// emitted span.
234impl FromMeta for syn::Ident {
235  fn from_ident(value: &Ident) -> Result<Self> {
236    Ok(value.clone())
237  }
238
239  fn from_string<S: Spanned>(value: &str, span: &S) -> Result<Self> {
240    Ok(syn::Ident::new(value, span.span()))
241  }
242}
243
244/// Parsing support for paths. This attempts to preserve span information when available,
245/// but also supports parsing strings with the call site as the emitted span.
246impl FromMeta for syn::Path {
247  fn from_path(path: &Path) -> Result<Self> {
248    Ok(path.clone())
249  }
250
251  fn from_string<S: Spanned>(value: &str, span: &S) -> Result<Self> {
252    syn::parse_str(value).map_err(|_| Error::unknown_value(value).with_span(span))
253  }
254
255  fn from_lit(value: &Lit) -> Result<Self> {
256    if let Lit::Str(ref path_str) = *value {
257      path_str
258        .parse()
259        .map_err(|_| Error::unknown_lit_str_value(path_str).with_span(value))
260    } else {
261      Err(Error::unexpected_lit_type(value).with_span(value))
262    }
263  }
264}
265
266impl FromMeta for syn::Lit {
267  fn from_lit(value: &Lit) -> Result<Self> {
268    Ok(value.clone())
269  }
270}
271
272macro_rules! from_meta_lit {
273  ($impl_ty:path, $lit_variant:path) => {
274    impl FromMeta for $impl_ty {
275      fn from_lit(value: &Lit) -> Result<Self> {
276        if let $lit_variant(ref value) = *value {
277          Ok(value.clone())
278        } else {
279          Err(Error::unexpected_lit_type(value).with_span(value))
280        }
281      }
282    }
283  };
284}
285
286from_meta_lit!(syn::LitInt, Lit::Int);
287from_meta_lit!(syn::LitFloat, Lit::Float);
288from_meta_lit!(syn::LitStr, Lit::Str);
289from_meta_lit!(syn::LitByte, Lit::Byte);
290from_meta_lit!(syn::LitByteStr, Lit::ByteStr);
291from_meta_lit!(syn::LitChar, Lit::Char);
292from_meta_lit!(syn::LitBool, Lit::Bool);
293from_meta_lit!(proc_macro2::Literal, Lit::Verbatim);
294
295impl FromMeta for Meta {
296  fn from_meta(value: &Meta) -> Result<Self> {
297    Ok(value.clone())
298  }
299}
300
301impl FromMeta for ident_case::RenameRule {
302  fn from_string<S: Spanned>(value: &str, span: &S) -> Result<Self> {
303    value
304      .parse()
305      .map_err(|_| Error::unknown_value(value).with_span(span))
306  }
307}
308
309impl<T: FromMeta> FromMeta for Option<T> {
310  fn from_meta(item: &Meta) -> Result<Self> {
311    FromMeta::from_meta(item).map(Some)
312  }
313}
314
315impl<T: FromMeta> FromMeta for Box<T> {
316  fn from_meta(item: &Meta) -> Result<Self> {
317    FromMeta::from_meta(item).map(Box::new)
318  }
319}
320
321impl<T: FromMeta> FromMeta for Result<T> {
322  fn from_meta(item: &Meta) -> Result<Self> {
323    Ok(FromMeta::from_meta(item))
324  }
325}
326
327/// Parses the meta-item, and in case of error preserves a copy of the input for
328/// later analysis.
329impl<T: FromMeta> FromMeta for ::std::result::Result<T, Meta> {
330  fn from_meta(item: &Meta) -> Result<Self> {
331    T::from_meta(item)
332      .map(Ok)
333      .or_else(|_| Ok(Err(item.clone())))
334  }
335}
336
337impl<T: FromMeta> FromMeta for Rc<T> {
338  fn from_meta(item: &Meta) -> Result<Self> {
339    FromMeta::from_meta(item).map(Rc::new)
340  }
341}
342
343impl<T: FromMeta> FromMeta for Arc<T> {
344  fn from_meta(item: &Meta) -> Result<Self> {
345    FromMeta::from_meta(item).map(Arc::new)
346  }
347}
348
349impl<T: FromMeta> FromMeta for Vec<T> {
350  fn from_empty() -> Result<Self> {
351    Ok(Vec::new())
352  }
353
354  fn from_list(items: &[&NestedMeta]) -> Result<Self> {
355    let mut ret = Vec::with_capacity(items.len());
356    for item in items {
357      ret.push(T::from_nested_meta(item)?);
358    }
359
360    Ok(ret)
361  }
362
363  fn from_value(val: &MetaValue) -> Result<Self> {
364    let mut ret = Vec::with_capacity(1);
365    ret.push(T::from_value(val)?);
366    Ok(ret)
367  }
368}
369
370/// Tests for `FromMeta` implementations. Wherever the word `ignore` appears in test input,
371/// it should not be considered by the parsing.
372#[cfg(test)]
373mod tests {
374  use proc_macro2::TokenStream;
375  use syn;
376
377  use super::Meta;
378  use super::{FromMeta, Result};
379  use crate::AttrExt;
380
381  /// parse a string as a syn::Meta instance.
382  fn pm(tokens: TokenStream) -> ::std::result::Result<Meta, String> {
383    let attribute: syn::Attribute = parse_quote!(#[#tokens]);
384    attribute.meta().map_err(|_| "Unable to parse".into())
385  }
386
387  fn fm<T: FromMeta>(tokens: TokenStream) -> T {
388    FromMeta::from_meta(&pm(tokens).expect("Tests should pass well-formed input"))
389      .expect("Tests should pass valid input")
390  }
391
392  #[test]
393  fn unit_succeeds() {
394    assert_eq!(fm::<()>(quote!(ignore)), ());
395  }
396
397  #[test]
398  fn bool_succeeds() {
399    // word format
400    assert_eq!(fm::<bool>(quote!(ignore)), true);
401
402    // bool literal
403    assert_eq!(fm::<bool>(quote!(ignore = true)), true);
404    assert_eq!(fm::<bool>(quote!(ignore = false)), false);
405
406    // string literals
407    assert_eq!(fm::<bool>(quote!(ignore = "true")), true);
408    assert_eq!(fm::<bool>(quote!(ignore = "false")), false);
409  }
410
411  #[test]
412  fn string_succeeds() {
413    // cooked form
414    assert_eq!(&fm::<String>(quote!(ignore = "world")), "world");
415
416    // raw form
417    assert_eq!(&fm::<String>(quote!(ignore = r#"world"#)), "world");
418  }
419
420  #[test]
421  fn number_succeeds() {
422    assert_eq!(fm::<u8>(quote!(ignore = "2")), 2u8);
423    assert_eq!(fm::<i16>(quote!(ignore = "-25")), -25i16);
424    assert_eq!(fm::<f64>(quote!(ignore = "1.4e10")), 1.4e10);
425  }
426
427  #[test]
428  fn int_without_quotes() {
429    assert_eq!(fm::<u8>(quote!(ignore = 2)), 2u8);
430    assert_eq!(fm::<u16>(quote!(ignore = 255)), 255u16);
431    assert_eq!(fm::<u32>(quote!(ignore = 5000)), 5000u32);
432
433    // Check that we aren't tripped up by incorrect suffixes
434    assert_eq!(fm::<u32>(quote!(ignore = 5000i32)), 5000u32);
435  }
436
437  #[test]
438  fn float_without_quotes() {
439    assert_eq!(fm::<f32>(quote!(ignore = 2.)), 2.0f32);
440    assert_eq!(fm::<f32>(quote!(ignore = 2.0)), 2.0f32);
441    assert_eq!(fm::<f64>(quote!(ignore = 1.4e10)), 1.4e10f64);
442  }
443
444  #[test]
445  fn meta_succeeds() {
446    assert_eq!(
447      fm::<Meta>(quote!(hello(world, today))),
448      pm(quote!(hello(world, today))).unwrap()
449    );
450  }
451
452  /// Tests that fallible parsing will always produce an outer `Ok` (from `fm`),
453  /// and will accurately preserve the inner contents.
454  #[test]
455  fn result_succeeds() {
456    fm::<Result<()>>(quote!(ignore)).unwrap();
457    fm::<Result<()>>(quote!(ignore(world))).unwrap_err();
458  }
459}