summaryrefslogtreecommitdiff
path: root/src/from_meta.rs
diff options
context:
space:
mode:
Diffstat (limited to 'src/from_meta.rs')
-rw-r--r--src/from_meta.rs1085
1 files changed, 1085 insertions, 0 deletions
diff --git a/src/from_meta.rs b/src/from_meta.rs
new file mode 100644
index 0000000..7e50e4e
--- /dev/null
+++ b/src/from_meta.rs
@@ -0,0 +1,1085 @@
+use std::borrow::Cow;
+use std::cell::RefCell;
+use std::collections::hash_map::HashMap;
+use std::collections::HashSet;
+use std::hash::BuildHasher;
+use std::rc::Rc;
+use std::sync::atomic::AtomicBool;
+use std::sync::Arc;
+
+use syn::{Expr, Lit, Meta};
+
+use crate::ast::NestedMeta;
+use crate::util::path_to_string;
+use crate::{Error, Result};
+
+/// Create an instance from an item in an attribute declaration.
+///
+/// # Implementing `FromMeta`
+/// * Do not take a dependency on the `ident` of the passed-in meta item. The ident will be set by the field name of the containing struct.
+/// * Implement only the `from_*` methods that you intend to support. The default implementations will return useful errors.
+///
+/// # Provided Implementations
+/// ## bool
+///
+/// * Word with no value specified - becomes `true`.
+/// * As a boolean literal, e.g. `foo = true`.
+/// * As a string literal, e.g. `foo = "true"`.
+///
+/// ## char
+/// * As a char literal, e.g. `foo = '#'`.
+/// * As a string literal consisting of a single character, e.g. `foo = "#"`.
+///
+/// ## String
+/// * As a string literal, e.g. `foo = "hello"`.
+/// * As a raw string literal, e.g. `foo = r#"hello "world""#`.
+///
+/// ## Number
+/// * As a string literal, e.g. `foo = "-25"`.
+/// * As an unquoted positive value, e.g. `foo = 404`. Negative numbers must be in quotation marks.
+///
+/// ## ()
+/// * Word with no value specified, e.g. `foo`. This is best used with `Option`.
+/// See `darling::util::Flag` for a more strongly-typed alternative.
+///
+/// ## Option
+/// * Any format produces `Some`.
+///
+/// ## `Result<T, darling::Error>`
+/// * Allows for fallible parsing; will populate the target field with the result of the
+/// parse attempt.
+pub trait FromMeta: Sized {
+ fn from_nested_meta(item: &NestedMeta) -> Result<Self> {
+ (match *item {
+ NestedMeta::Lit(ref lit) => Self::from_value(lit),
+ NestedMeta::Meta(ref mi) => Self::from_meta(mi),
+ })
+ .map_err(|e| e.with_span(item))
+ }
+
+ /// Create an instance from a `syn::Meta` by dispatching to the format-appropriate
+ /// trait function. This generally should not be overridden by implementers.
+ ///
+ /// # Error Spans
+ /// If this method is overridden and can introduce errors that weren't passed up from
+ /// other `from_meta` calls, the override must call `with_span` on the error using the
+ /// `item` to make sure that the emitted diagnostic points to the correct location in
+ /// source code.
+ fn from_meta(item: &Meta) -> Result<Self> {
+ (match *item {
+ Meta::Path(_) => Self::from_word(),
+ Meta::List(ref value) => {
+ Self::from_list(&NestedMeta::parse_meta_list(value.tokens.clone())?[..])
+ }
+ Meta::NameValue(ref value) => Self::from_expr(&value.value),
+ })
+ .map_err(|e| e.with_span(item))
+ }
+
+ /// When a field is omitted from a parent meta-item, `from_none` is used to attempt
+ /// recovery before a missing field error is generated.
+ ///
+ /// **Most types should not override this method.** `darling` already allows field-level
+ /// missing-field recovery using `#[darling(default)]` and `#[darling(default = "...")]`,
+ /// and users who add a `String` field to their `FromMeta`-deriving struct would be surprised
+ /// if they get back `""` instead of a missing field error when that field is omitted.
+ ///
+ /// The primary use-case for this is `Option<T>` fields gracefully handlling absence without
+ /// needing `#[darling(default)]`.
+ fn from_none() -> Option<Self> {
+ None
+ }
+
+ /// Create an instance from the presence of the word in the attribute with no
+ /// additional options specified.
+ fn from_word() -> Result<Self> {
+ Err(Error::unsupported_format("word"))
+ }
+
+ /// Create an instance from a list of nested meta items.
+ #[allow(unused_variables)]
+ fn from_list(items: &[NestedMeta]) -> Result<Self> {
+ Err(Error::unsupported_format("list"))
+ }
+
+ /// Create an instance from a literal value of either `foo = "bar"` or `foo("bar")`.
+ /// This dispatches to the appropriate method based on the type of literal encountered,
+ /// and generally should not be overridden by implementers.
+ ///
+ /// # Error Spans
+ /// If this method is overridden, the override must make sure to add `value`'s span
+ /// information to the returned error by calling `with_span(value)` on the `Error` instance.
+ fn from_value(value: &Lit) -> Result<Self> {
+ (match *value {
+ Lit::Bool(ref b) => Self::from_bool(b.value),
+ Lit::Str(ref s) => Self::from_string(&s.value()),
+ Lit::Char(ref ch) => Self::from_char(ch.value()),
+ _ => Err(Error::unexpected_lit_type(value)),
+ })
+ .map_err(|e| e.with_span(value))
+ }
+
+ fn from_expr(expr: &Expr) -> Result<Self> {
+ match *expr {
+ Expr::Lit(ref lit) => Self::from_value(&lit.lit),
+ Expr::Group(ref group) => {
+ // syn may generate this invisible group delimiter when the input to the darling
+ // proc macro (specifically, the attributes) are generated by a
+ // macro_rules! (e.g. propagating a macro_rules!'s expr)
+ // Since we want to basically ignore these invisible group delimiters,
+ // we just propagate the call to the inner expression.
+ Self::from_expr(&group.expr)
+ }
+ _ => Err(Error::unexpected_expr_type(expr)),
+ }
+ .map_err(|e| e.with_span(expr))
+ }
+
+ /// Create an instance from a char literal in a value position.
+ #[allow(unused_variables)]
+ fn from_char(value: char) -> Result<Self> {
+ Err(Error::unexpected_type("char"))
+ }
+
+ /// Create an instance from a string literal in a value position.
+ #[allow(unused_variables)]
+ fn from_string(value: &str) -> Result<Self> {
+ Err(Error::unexpected_type("string"))
+ }
+
+ /// Create an instance from a bool literal in a value position.
+ #[allow(unused_variables)]
+ fn from_bool(value: bool) -> Result<Self> {
+ Err(Error::unexpected_type("bool"))
+ }
+}
+
+// FromMeta impls for std and syn types.
+
+impl FromMeta for () {
+ fn from_word() -> Result<Self> {
+ Ok(())
+ }
+}
+
+impl FromMeta for bool {
+ fn from_word() -> Result<Self> {
+ Ok(true)
+ }
+
+ #[allow(clippy::wrong_self_convention)] // false positive
+ fn from_bool(value: bool) -> Result<Self> {
+ Ok(value)
+ }
+
+ fn from_string(value: &str) -> Result<Self> {
+ value.parse().map_err(|_| Error::unknown_value(value))
+ }
+}
+
+impl FromMeta for AtomicBool {
+ fn from_meta(mi: &Meta) -> Result<Self> {
+ FromMeta::from_meta(mi)
+ .map(AtomicBool::new)
+ .map_err(|e| e.with_span(mi))
+ }
+}
+
+impl FromMeta for char {
+ #[allow(clippy::wrong_self_convention)] // false positive
+ fn from_char(value: char) -> Result<Self> {
+ Ok(value)
+ }
+
+ fn from_string(s: &str) -> Result<Self> {
+ let mut chars = s.chars();
+ let char1 = chars.next();
+ let char2 = chars.next();
+
+ if let (Some(char), None) = (char1, char2) {
+ Ok(char)
+ } else {
+ Err(Error::unexpected_type("string"))
+ }
+ }
+}
+
+impl FromMeta for String {
+ fn from_string(s: &str) -> Result<Self> {
+ Ok(s.to_string())
+ }
+}
+
+impl FromMeta for std::path::PathBuf {
+ fn from_string(s: &str) -> Result<Self> {
+ Ok(s.into())
+ }
+}
+
+/// Generate an impl of `FromMeta` that will accept strings which parse to numbers or
+/// integer literals.
+macro_rules! from_meta_num {
+ ($ty:ident) => {
+ impl FromMeta for $ty {
+ fn from_string(s: &str) -> Result<Self> {
+ s.parse().map_err(|_| Error::unknown_value(s))
+ }
+
+ fn from_value(value: &Lit) -> Result<Self> {
+ (match *value {
+ Lit::Str(ref s) => Self::from_string(&s.value()),
+ Lit::Int(ref s) => Ok(s.base10_parse::<$ty>().unwrap()),
+ _ => Err(Error::unexpected_lit_type(value)),
+ })
+ .map_err(|e| e.with_span(value))
+ }
+ }
+ };
+}
+
+from_meta_num!(u8);
+from_meta_num!(u16);
+from_meta_num!(u32);
+from_meta_num!(u64);
+from_meta_num!(u128);
+from_meta_num!(usize);
+from_meta_num!(i8);
+from_meta_num!(i16);
+from_meta_num!(i32);
+from_meta_num!(i64);
+from_meta_num!(i128);
+from_meta_num!(isize);
+
+/// Generate an impl of `FromMeta` that will accept strings which parse to floats or
+/// float literals.
+macro_rules! from_meta_float {
+ ($ty:ident) => {
+ impl FromMeta for $ty {
+ fn from_string(s: &str) -> Result<Self> {
+ s.parse().map_err(|_| Error::unknown_value(s))
+ }
+
+ fn from_value(value: &Lit) -> Result<Self> {
+ (match *value {
+ Lit::Str(ref s) => Self::from_string(&s.value()),
+ Lit::Float(ref s) => Ok(s.base10_parse::<$ty>().unwrap()),
+ _ => Err(Error::unexpected_lit_type(value)),
+ })
+ .map_err(|e| e.with_span(value))
+ }
+ }
+ };
+}
+
+from_meta_float!(f32);
+from_meta_float!(f64);
+
+/// Parsing support for punctuated. This attempts to preserve span information
+/// when available, but also supports parsing strings with the call site as the
+/// emitted span.
+impl<T: syn::parse::Parse, P: syn::parse::Parse> FromMeta for syn::punctuated::Punctuated<T, P> {
+ fn from_value(value: &Lit) -> Result<Self> {
+ if let Lit::Str(ref ident) = *value {
+ ident
+ .parse_with(syn::punctuated::Punctuated::parse_terminated)
+ .map_err(|_| Error::unknown_lit_str_value(ident))
+ } else {
+ Err(Error::unexpected_lit_type(value))
+ }
+ }
+}
+
+/// Support for arbitrary expressions as values in a meta item.
+///
+/// For backwards-compatibility to versions of `darling` based on `syn` 1,
+/// string literals will be "unwrapped" and their contents will be parsed
+/// as an expression.
+///
+/// See [`util::parse_expr`](crate::util::parse_expr) for functions to provide
+/// alternate parsing modes for this type.
+impl FromMeta for syn::Expr {
+ fn from_expr(expr: &Expr) -> Result<Self> {
+ match expr {
+ Expr::Lit(syn::ExprLit {
+ lit: lit @ syn::Lit::Str(_),
+ ..
+ }) => Self::from_value(lit),
+ Expr::Group(group) => Self::from_expr(&group.expr), // see FromMeta::from_expr
+ _ => Ok(expr.clone()),
+ }
+ }
+
+ fn from_string(value: &str) -> Result<Self> {
+ syn::parse_str(value).map_err(|_| Error::unknown_value(value))
+ }
+
+ fn from_value(value: &::syn::Lit) -> Result<Self> {
+ if let ::syn::Lit::Str(ref v) = *value {
+ v.parse::<syn::Expr>()
+ .map_err(|_| Error::unknown_lit_str_value(v))
+ } else {
+ Err(Error::unexpected_lit_type(value))
+ }
+ }
+}
+
+/// Parser for paths that supports both quote-wrapped and bare values.
+impl FromMeta for syn::Path {
+ fn from_string(value: &str) -> Result<Self> {
+ syn::parse_str(value).map_err(|_| Error::unknown_value(value))
+ }
+
+ fn from_value(value: &::syn::Lit) -> Result<Self> {
+ if let ::syn::Lit::Str(ref v) = *value {
+ v.parse().map_err(|_| Error::unknown_lit_str_value(v))
+ } else {
+ Err(Error::unexpected_lit_type(value))
+ }
+ }
+
+ fn from_expr(expr: &Expr) -> Result<Self> {
+ match expr {
+ Expr::Lit(lit) => Self::from_value(&lit.lit),
+ Expr::Path(path) => Ok(path.path.clone()),
+ Expr::Group(group) => Self::from_expr(&group.expr), // see FromMeta::from_expr
+ _ => Err(Error::unexpected_expr_type(expr)),
+ }
+ }
+}
+
+impl FromMeta for syn::Ident {
+ fn from_string(value: &str) -> Result<Self> {
+ syn::parse_str(value).map_err(|_| Error::unknown_value(value))
+ }
+
+ fn from_value(value: &syn::Lit) -> Result<Self> {
+ if let syn::Lit::Str(ref v) = *value {
+ v.parse().map_err(|_| Error::unknown_lit_str_value(v))
+ } else {
+ Err(Error::unexpected_lit_type(value))
+ }
+ }
+
+ fn from_expr(expr: &Expr) -> Result<Self> {
+ match expr {
+ Expr::Lit(lit) => Self::from_value(&lit.lit),
+ // All idents are paths, but not all paths are idents -
+ // the get_ident() method does additional validation to
+ // make sure the path is actually an ident.
+ Expr::Path(path) => match path.path.get_ident() {
+ Some(ident) => Ok(ident.clone()),
+ None => Err(Error::unexpected_expr_type(expr)),
+ },
+ Expr::Group(group) => Self::from_expr(&group.expr), // see FromMeta::from_expr
+ _ => Err(Error::unexpected_expr_type(expr)),
+ }
+ }
+}
+
+/// Adapter for various expression types.
+///
+/// Prior to syn 2.0, darling supported arbitrary expressions as long as they
+/// were wrapped in quotation marks. This was helpful for people writing
+/// libraries that needed expressions, but it now creates an ambiguity when
+/// parsing a meta item.
+///
+/// To address this, the macro supports both formats; if it cannot parse the
+/// item as an expression of the right type and the passed-in expression is
+/// a string literal, it will fall back to parsing the string contents.
+macro_rules! from_syn_expr_type {
+ ($ty:path, $variant:ident) => {
+ impl FromMeta for $ty {
+ fn from_expr(expr: &syn::Expr) -> Result<Self> {
+ match expr {
+ syn::Expr::$variant(body) => Ok(body.clone()),
+ syn::Expr::Lit(expr_lit) => Self::from_value(&expr_lit.lit),
+ syn::Expr::Group(group) => Self::from_expr(&group.expr), // see FromMeta::from_expr
+ _ => Err(Error::unexpected_expr_type(expr)),
+ }
+ }
+
+ fn from_value(value: &::syn::Lit) -> Result<Self> {
+ if let syn::Lit::Str(body) = &value {
+ body.parse::<$ty>()
+ .map_err(|_| Error::unknown_lit_str_value(body))
+ } else {
+ Err(Error::unexpected_lit_type(value))
+ }
+ }
+ }
+ };
+}
+
+from_syn_expr_type!(syn::ExprArray, Array);
+from_syn_expr_type!(syn::ExprPath, Path);
+
+/// Adapter from `syn::parse::Parse` to `FromMeta` for items that cannot
+/// be expressed in a [`syn::MetaNameValue`].
+///
+/// This cannot be a blanket impl, due to the `syn::Lit` family's need to handle non-string values.
+/// Therefore, we use a macro and a lot of impls.
+macro_rules! from_syn_parse {
+ ($ty:path) => {
+ impl FromMeta for $ty {
+ fn from_string(value: &str) -> Result<Self> {
+ syn::parse_str(value).map_err(|_| Error::unknown_value(value))
+ }
+
+ fn from_value(value: &::syn::Lit) -> Result<Self> {
+ if let ::syn::Lit::Str(ref v) = *value {
+ v.parse::<$ty>()
+ .map_err(|_| Error::unknown_lit_str_value(v))
+ } else {
+ Err(Error::unexpected_lit_type(value))
+ }
+ }
+ }
+ };
+}
+
+from_syn_parse!(syn::Type);
+from_syn_parse!(syn::TypeArray);
+from_syn_parse!(syn::TypeBareFn);
+from_syn_parse!(syn::TypeGroup);
+from_syn_parse!(syn::TypeImplTrait);
+from_syn_parse!(syn::TypeInfer);
+from_syn_parse!(syn::TypeMacro);
+from_syn_parse!(syn::TypeNever);
+from_syn_parse!(syn::TypeParam);
+from_syn_parse!(syn::TypeParen);
+from_syn_parse!(syn::TypePath);
+from_syn_parse!(syn::TypePtr);
+from_syn_parse!(syn::TypeReference);
+from_syn_parse!(syn::TypeSlice);
+from_syn_parse!(syn::TypeTraitObject);
+from_syn_parse!(syn::TypeTuple);
+from_syn_parse!(syn::Visibility);
+from_syn_parse!(syn::WhereClause);
+
+macro_rules! from_numeric_array {
+ ($ty:ident) => {
+ /// Parsing an unsigned integer array, i.e. `example = "[1, 2, 3, 4]"`.
+ impl FromMeta for Vec<$ty> {
+ fn from_expr(expr: &syn::Expr) -> Result<Self> {
+ match expr {
+ syn::Expr::Array(expr_array) => expr_array
+ .elems
+ .iter()
+ .map(|expr| {
+ let unexpected = || {
+ Error::custom("Expected array of unsigned integers").with_span(expr)
+ };
+ match expr {
+ Expr::Lit(lit) => $ty::from_value(&lit.lit),
+ Expr::Group(group) => match &*group.expr {
+ Expr::Lit(lit) => $ty::from_value(&lit.lit),
+ _ => Err(unexpected()),
+ },
+ _ => Err(unexpected()),
+ }
+ })
+ .collect::<Result<Vec<$ty>>>(),
+ syn::Expr::Lit(expr_lit) => Self::from_value(&expr_lit.lit),
+ syn::Expr::Group(group) => Self::from_expr(&group.expr), // see FromMeta::from_expr
+ _ => Err(Error::unexpected_expr_type(expr)),
+ }
+ }
+
+ fn from_value(value: &Lit) -> Result<Self> {
+ let expr_array = syn::ExprArray::from_value(value)?;
+ Self::from_expr(&syn::Expr::Array(expr_array))
+ }
+ }
+ };
+}
+
+from_numeric_array!(u8);
+from_numeric_array!(u16);
+from_numeric_array!(u32);
+from_numeric_array!(u64);
+from_numeric_array!(usize);
+
+impl FromMeta for syn::Lit {
+ fn from_value(value: &Lit) -> Result<Self> {
+ Ok(value.clone())
+ }
+}
+
+macro_rules! from_meta_lit {
+ ($impl_ty:path, $lit_variant:path) => {
+ impl FromMeta for $impl_ty {
+ fn from_value(value: &Lit) -> Result<Self> {
+ if let $lit_variant(ref value) = *value {
+ Ok(value.clone())
+ } else {
+ Err(Error::unexpected_lit_type(value))
+ }
+ }
+ }
+
+ impl FromMeta for Vec<$impl_ty> {
+ fn from_list(items: &[NestedMeta]) -> Result<Self> {
+ items
+ .iter()
+ .map(<$impl_ty as FromMeta>::from_nested_meta)
+ .collect()
+ }
+
+ fn from_value(value: &syn::Lit) -> Result<Self> {
+ let expr_array = syn::ExprArray::from_value(value)?;
+ Self::from_expr(&syn::Expr::Array(expr_array))
+ }
+
+ fn from_expr(expr: &syn::Expr) -> Result<Self> {
+ match expr {
+ syn::Expr::Array(expr_array) => expr_array
+ .elems
+ .iter()
+ .map(<$impl_ty as FromMeta>::from_expr)
+ .collect::<Result<Vec<_>>>(),
+ syn::Expr::Lit(expr_lit) => Self::from_value(&expr_lit.lit),
+ syn::Expr::Group(g) => Self::from_expr(&g.expr),
+ _ => Err(Error::unexpected_expr_type(expr)),
+ }
+ }
+ }
+ };
+}
+
+from_meta_lit!(syn::LitInt, Lit::Int);
+from_meta_lit!(syn::LitFloat, Lit::Float);
+from_meta_lit!(syn::LitStr, Lit::Str);
+from_meta_lit!(syn::LitByte, Lit::Byte);
+from_meta_lit!(syn::LitByteStr, Lit::ByteStr);
+from_meta_lit!(syn::LitChar, Lit::Char);
+from_meta_lit!(syn::LitBool, Lit::Bool);
+from_meta_lit!(proc_macro2::Literal, Lit::Verbatim);
+
+impl FromMeta for syn::Meta {
+ fn from_meta(value: &syn::Meta) -> Result<Self> {
+ Ok(value.clone())
+ }
+}
+
+impl FromMeta for Vec<syn::WherePredicate> {
+ fn from_string(value: &str) -> Result<Self> {
+ syn::WhereClause::from_string(&format!("where {}", value))
+ .map(|c| c.predicates.into_iter().collect())
+ }
+
+ fn from_value(value: &Lit) -> Result<Self> {
+ if let syn::Lit::Str(s) = value {
+ syn::WhereClause::from_value(&syn::Lit::Str(syn::LitStr::new(
+ &format!("where {}", s.value()),
+ value.span(),
+ )))
+ .map(|c| c.predicates.into_iter().collect())
+ } else {
+ Err(Error::unexpected_lit_type(value))
+ }
+ }
+}
+
+impl FromMeta for ident_case::RenameRule {
+ fn from_string(value: &str) -> Result<Self> {
+ value.parse().map_err(|_| Error::unknown_value(value))
+ }
+}
+
+impl<T: FromMeta> FromMeta for Option<T> {
+ fn from_none() -> Option<Self> {
+ Some(None)
+ }
+
+ fn from_meta(item: &Meta) -> Result<Self> {
+ FromMeta::from_meta(item).map(Some)
+ }
+}
+
+impl<T: FromMeta> FromMeta for Box<T> {
+ fn from_none() -> Option<Self> {
+ T::from_none().map(Box::new)
+ }
+
+ fn from_meta(item: &Meta) -> Result<Self> {
+ FromMeta::from_meta(item).map(Box::new)
+ }
+}
+
+impl<T: FromMeta> FromMeta for Result<T> {
+ fn from_none() -> Option<Self> {
+ T::from_none().map(Ok)
+ }
+
+ fn from_meta(item: &Meta) -> Result<Self> {
+ Ok(FromMeta::from_meta(item))
+ }
+}
+
+/// Parses the meta-item, and in case of error preserves a copy of the input for
+/// later analysis.
+impl<T: FromMeta> FromMeta for ::std::result::Result<T, Meta> {
+ fn from_meta(item: &Meta) -> Result<Self> {
+ T::from_meta(item)
+ .map(Ok)
+ .or_else(|_| Ok(Err(item.clone())))
+ }
+}
+
+impl<T: FromMeta> FromMeta for Rc<T> {
+ fn from_none() -> Option<Self> {
+ T::from_none().map(Rc::new)
+ }
+
+ fn from_meta(item: &Meta) -> Result<Self> {
+ FromMeta::from_meta(item).map(Rc::new)
+ }
+}
+
+impl<T: FromMeta> FromMeta for Arc<T> {
+ fn from_none() -> Option<Self> {
+ T::from_none().map(Arc::new)
+ }
+
+ fn from_meta(item: &Meta) -> Result<Self> {
+ FromMeta::from_meta(item).map(Arc::new)
+ }
+}
+
+impl<T: FromMeta> FromMeta for RefCell<T> {
+ fn from_none() -> Option<Self> {
+ T::from_none().map(RefCell::new)
+ }
+
+ fn from_meta(item: &Meta) -> Result<Self> {
+ FromMeta::from_meta(item).map(RefCell::new)
+ }
+}
+
+/// Trait to convert from a path into an owned key for a map.
+trait KeyFromPath: Sized {
+ fn from_path(path: &syn::Path) -> Result<Self>;
+ fn to_display(&self) -> Cow<'_, str>;
+}
+
+impl KeyFromPath for String {
+ fn from_path(path: &syn::Path) -> Result<Self> {
+ Ok(path_to_string(path))
+ }
+
+ fn to_display(&self) -> Cow<'_, str> {
+ Cow::Borrowed(self)
+ }
+}
+
+impl KeyFromPath for syn::Path {
+ fn from_path(path: &syn::Path) -> Result<Self> {
+ Ok(path.clone())
+ }
+
+ fn to_display(&self) -> Cow<'_, str> {
+ Cow::Owned(path_to_string(self))
+ }
+}
+
+impl KeyFromPath for syn::Ident {
+ fn from_path(path: &syn::Path) -> Result<Self> {
+ if path.segments.len() == 1
+ && path.leading_colon.is_none()
+ && path.segments[0].arguments.is_empty()
+ {
+ Ok(path.segments[0].ident.clone())
+ } else {
+ Err(Error::custom("Key must be an identifier").with_span(path))
+ }
+ }
+
+ fn to_display(&self) -> Cow<'_, str> {
+ Cow::Owned(self.to_string())
+ }
+}
+
+macro_rules! hash_map {
+ ($key:ty) => {
+ impl<V: FromMeta, S: BuildHasher + Default> FromMeta for HashMap<$key, V, S> {
+ fn from_list(nested: &[NestedMeta]) -> Result<Self> {
+ // Convert the nested meta items into a sequence of (path, value result) result tuples.
+ // An outer Err means no (key, value) structured could be found, while an Err in the
+ // second position of the tuple means that value was rejected by FromMeta.
+ //
+ // We defer key conversion into $key so that we don't lose span information in the case
+ // of String keys; we'll need it for good duplicate key errors later.
+ let pairs = nested
+ .iter()
+ .map(|item| -> Result<(&syn::Path, Result<V>)> {
+ match *item {
+ NestedMeta::Meta(ref inner) => {
+ let path = inner.path();
+ Ok((
+ path,
+ FromMeta::from_meta(inner).map_err(|e| e.at_path(&path)),
+ ))
+ }
+ NestedMeta::Lit(_) => Err(Error::unsupported_format("expression")),
+ }
+ });
+
+ let mut errors = Error::accumulator();
+ // We need to track seen keys separately from the final map, since a seen key with an
+ // Err value won't go into the final map but should trigger a duplicate field error.
+ //
+ // This is a set of $key rather than Path to avoid the possibility that a key type
+ // parses two paths of different values to the same key value.
+ let mut seen_keys = HashSet::with_capacity(nested.len());
+
+ // The map to return in the Ok case. Its size will always be exactly nested.len(),
+ // since otherwise ≥1 field had a problem and the entire map is dropped immediately
+ // when the function returns `Err`.
+ let mut map = HashMap::with_capacity_and_hasher(nested.len(), Default::default());
+
+ for item in pairs {
+ if let Some((path, value)) = errors.handle(item) {
+ let key: $key = match KeyFromPath::from_path(path) {
+ Ok(k) => k,
+ Err(e) => {
+ errors.push(e);
+
+ // Surface value errors even under invalid keys
+ errors.handle(value);
+
+ continue;
+ }
+ };
+
+ let already_seen = seen_keys.contains(&key);
+
+ if already_seen {
+ errors.push(Error::duplicate_field(&key.to_display()).with_span(path));
+ }
+
+ match value {
+ Ok(_) if already_seen => {}
+ Ok(val) => {
+ map.insert(key.clone(), val);
+ }
+ Err(e) => {
+ errors.push(e);
+ }
+ }
+
+ seen_keys.insert(key);
+ }
+ }
+
+ errors.finish_with(map)
+ }
+ }
+ };
+}
+
+// This is done as a macro rather than a blanket impl to avoid breaking backwards compatibility
+// with 0.12.x, while still sharing the same impl.
+hash_map!(String);
+hash_map!(syn::Ident);
+hash_map!(syn::Path);
+
+/// Tests for `FromMeta` implementations. Wherever the word `ignore` appears in test input,
+/// it should not be considered by the parsing.
+#[cfg(test)]
+mod tests {
+ use proc_macro2::TokenStream;
+ use quote::quote;
+ use syn::parse_quote;
+
+ use crate::{Error, FromMeta, Result};
+
+ /// parse a string as a syn::Meta instance.
+ fn pm(tokens: TokenStream) -> ::std::result::Result<syn::Meta, String> {
+ let attribute: syn::Attribute = parse_quote!(#[#tokens]);
+ Ok(attribute.meta)
+ }
+
+ #[track_caller]
+ fn fm<T: FromMeta>(tokens: TokenStream) -> T {
+ FromMeta::from_meta(&pm(tokens).expect("Tests should pass well-formed input"))
+ .expect("Tests should pass valid input")
+ }
+
+ #[test]
+ fn unit_succeeds() {
+ fm::<()>(quote!(ignore));
+ }
+
+ #[test]
+ #[allow(clippy::bool_assert_comparison)]
+ fn bool_succeeds() {
+ // word format
+ assert_eq!(fm::<bool>(quote!(ignore)), true);
+
+ // bool literal
+ assert_eq!(fm::<bool>(quote!(ignore = true)), true);
+ assert_eq!(fm::<bool>(quote!(ignore = false)), false);
+
+ // string literals
+ assert_eq!(fm::<bool>(quote!(ignore = "true")), true);
+ assert_eq!(fm::<bool>(quote!(ignore = "false")), false);
+ }
+
+ #[test]
+ fn char_succeeds() {
+ // char literal
+ assert_eq!(fm::<char>(quote!(ignore = '😬')), '😬');
+
+ // string literal
+ assert_eq!(fm::<char>(quote!(ignore = "😬")), '😬');
+ }
+
+ #[test]
+ fn string_succeeds() {
+ // cooked form
+ assert_eq!(&fm::<String>(quote!(ignore = "world")), "world");
+
+ // raw form
+ assert_eq!(&fm::<String>(quote!(ignore = r#"world"#)), "world");
+ }
+
+ #[test]
+ fn pathbuf_succeeds() {
+ assert_eq!(
+ fm::<std::path::PathBuf>(quote!(ignore = r#"C:\"#)),
+ std::path::PathBuf::from(r#"C:\"#)
+ );
+ }
+
+ #[test]
+ #[allow(clippy::float_cmp)] // we want exact equality
+ fn number_succeeds() {
+ assert_eq!(fm::<u8>(quote!(ignore = "2")), 2u8);
+ assert_eq!(fm::<i16>(quote!(ignore = "-25")), -25i16);
+ assert_eq!(fm::<f64>(quote!(ignore = "1.4e10")), 1.4e10);
+ }
+
+ #[test]
+ fn int_without_quotes() {
+ assert_eq!(fm::<u8>(quote!(ignore = 2)), 2u8);
+ assert_eq!(fm::<u16>(quote!(ignore = 255)), 255u16);
+ assert_eq!(fm::<u32>(quote!(ignore = 5000)), 5000u32);
+
+ // Check that we aren't tripped up by incorrect suffixes
+ assert_eq!(fm::<u32>(quote!(ignore = 5000i32)), 5000u32);
+ }
+
+ #[test]
+ fn negative_int_without_quotes() {
+ assert_eq!(fm::<i8>(quote!(ignore = -2)), -2i8);
+ assert_eq!(fm::<i32>(quote!(ignore = -255)), -255i32);
+ }
+
+ #[test]
+ #[allow(clippy::float_cmp)] // we want exact equality
+ fn float_without_quotes() {
+ assert_eq!(fm::<f32>(quote!(ignore = 2.)), 2.0f32);
+ assert_eq!(fm::<f32>(quote!(ignore = 2.0)), 2.0f32);
+ assert_eq!(fm::<f64>(quote!(ignore = 1.4e10)), 1.4e10f64);
+ }
+
+ #[test]
+ fn meta_succeeds() {
+ use syn::Meta;
+
+ assert_eq!(
+ fm::<Meta>(quote!(hello(world, today))),
+ pm(quote!(hello(world, today))).unwrap()
+ );
+ }
+
+ #[test]
+ fn hash_map_succeeds() {
+ use std::collections::HashMap;
+
+ let comparison = {
+ let mut c = HashMap::new();
+ c.insert("hello".to_string(), true);
+ c.insert("world".to_string(), false);
+ c.insert("there".to_string(), true);
+ c
+ };
+
+ assert_eq!(
+ fm::<HashMap<String, bool>>(quote!(ignore(hello, world = false, there = "true"))),
+ comparison
+ );
+ }
+
+ /// Check that a `HashMap` cannot have duplicate keys, and that the generated error
+ /// is assigned a span to correctly target the diagnostic message.
+ #[test]
+ fn hash_map_duplicate() {
+ use std::collections::HashMap;
+
+ let err: Result<HashMap<String, bool>> =
+ FromMeta::from_meta(&pm(quote!(ignore(hello, hello = false))).unwrap());
+
+ let err = err.expect_err("Duplicate keys in HashMap should error");
+
+ assert!(err.has_span());
+ assert_eq!(err.to_string(), Error::duplicate_field("hello").to_string());
+ }
+
+ #[test]
+ fn hash_map_multiple_errors() {
+ use std::collections::HashMap;
+
+ let err = HashMap::<String, bool>::from_meta(
+ &pm(quote!(ignore(hello, hello = 3, hello = false))).unwrap(),
+ )
+ .expect_err("Duplicates and bad values should error");
+
+ assert_eq!(err.len(), 3);
+ let errors = err.into_iter().collect::<Vec<_>>();
+ assert!(errors[0].has_span());
+ assert!(errors[1].has_span());
+ assert!(errors[2].has_span());
+ }
+
+ #[test]
+ fn hash_map_ident_succeeds() {
+ use std::collections::HashMap;
+ use syn::parse_quote;
+
+ let comparison = {
+ let mut c = HashMap::<syn::Ident, bool>::new();
+ c.insert(parse_quote!(first), true);
+ c.insert(parse_quote!(second), false);
+ c
+ };
+
+ assert_eq!(
+ fm::<HashMap<syn::Ident, bool>>(quote!(ignore(first, second = false))),
+ comparison
+ );
+ }
+
+ #[test]
+ fn hash_map_ident_rejects_non_idents() {
+ use std::collections::HashMap;
+
+ let err: Result<HashMap<syn::Ident, bool>> =
+ FromMeta::from_meta(&pm(quote!(ignore(first, the::second))).unwrap());
+
+ err.unwrap_err();
+ }
+
+ #[test]
+ fn hash_map_path_succeeds() {
+ use std::collections::HashMap;
+ use syn::parse_quote;
+
+ let comparison = {
+ let mut c = HashMap::<syn::Path, bool>::new();
+ c.insert(parse_quote!(first), true);
+ c.insert(parse_quote!(the::second), false);
+ c
+ };
+
+ assert_eq!(
+ fm::<HashMap<syn::Path, bool>>(quote!(ignore(first, the::second = false))),
+ comparison
+ );
+ }
+
+ /// Tests that fallible parsing will always produce an outer `Ok` (from `fm`),
+ /// and will accurately preserve the inner contents.
+ #[test]
+ fn darling_result_succeeds() {
+ fm::<Result<()>>(quote!(ignore)).unwrap();
+ fm::<Result<()>>(quote!(ignore(world))).unwrap_err();
+ }
+
+ /// Test punctuated
+ #[test]
+ fn test_punctuated() {
+ fm::<syn::punctuated::Punctuated<syn::FnArg, syn::token::Comma>>(quote!(
+ ignore = "a: u8, b: Type"
+ ));
+ fm::<syn::punctuated::Punctuated<syn::Expr, syn::token::Comma>>(quote!(ignore = "a, b, c"));
+ }
+
+ #[test]
+ fn test_expr_array() {
+ fm::<syn::ExprArray>(quote!(ignore = "[0x1, 0x2]"));
+ fm::<syn::ExprArray>(quote!(ignore = "[\"Hello World\", \"Test Array\"]"));
+ }
+
+ #[test]
+ fn test_expr() {
+ fm::<syn::Expr>(quote!(ignore = "x + y"));
+ fm::<syn::Expr>(quote!(ignore = "an_object.method_call()"));
+ fm::<syn::Expr>(quote!(ignore = "{ a_statement(); in_a_block }"));
+ }
+
+ #[test]
+ fn test_expr_without_quotes() {
+ fm::<syn::Expr>(quote!(ignore = x + y));
+ fm::<syn::Expr>(quote!(ignore = an_object.method_call()));
+ fm::<syn::Expr>(quote!(
+ ignore = {
+ a_statement();
+ in_a_block
+ }
+ ));
+ }
+
+ #[test]
+ fn test_expr_path() {
+ fm::<syn::ExprPath>(quote!(ignore = "std::mem::replace"));
+ fm::<syn::ExprPath>(quote!(ignore = "x"));
+ fm::<syn::ExprPath>(quote!(ignore = "example::<Test>"));
+ }
+
+ #[test]
+ fn test_expr_path_without_quotes() {
+ fm::<syn::ExprPath>(quote!(ignore = std::mem::replace));
+ fm::<syn::ExprPath>(quote!(ignore = x));
+ fm::<syn::ExprPath>(quote!(ignore = example::<Test>));
+ }
+
+ #[test]
+ fn test_path_without_quotes() {
+ fm::<syn::Path>(quote!(ignore = std::mem::replace));
+ fm::<syn::Path>(quote!(ignore = x));
+ fm::<syn::Path>(quote!(ignore = example::<Test>));
+ }
+
+ #[test]
+ fn test_number_array() {
+ assert_eq!(fm::<Vec<u8>>(quote!(ignore = [16, 0xff])), vec![0x10, 0xff]);
+ assert_eq!(
+ fm::<Vec<u16>>(quote!(ignore = "[32, 0xffff]")),
+ vec![0x20, 0xffff]
+ );
+ assert_eq!(
+ fm::<Vec<u32>>(quote!(ignore = "[48, 0xffffffff]")),
+ vec![0x30, 0xffffffff]
+ );
+ assert_eq!(
+ fm::<Vec<u64>>(quote!(ignore = "[64, 0xffffffffffffffff]")),
+ vec![0x40, 0xffffffffffffffff]
+ );
+ assert_eq!(
+ fm::<Vec<usize>>(quote!(ignore = "[80, 0xffffffff]")),
+ vec![0x50, 0xffffffff]
+ );
+ }
+
+ #[test]
+ fn test_lit_array() {
+ fm::<Vec<syn::LitStr>>(quote!(ignore = "[\"Hello World\", \"Test Array\"]"));
+ fm::<Vec<syn::LitStr>>(quote!(ignore = ["Hello World", "Test Array"]));
+ fm::<Vec<syn::LitChar>>(quote!(ignore = "['a', 'b', 'c']"));
+ fm::<Vec<syn::LitBool>>(quote!(ignore = "[true]"));
+ fm::<Vec<syn::LitStr>>(quote!(ignore = "[]"));
+ fm::<Vec<syn::LitStr>>(quote!(ignore = []));
+ fm::<Vec<syn::LitBool>>(quote!(ignore = [true, false]));
+ }
+}