use std::cell::RefCell;
use std::collections::hash_map::{Entry, HashMap};
use std::hash::BuildHasher;
use std::rc::Rc;
use std::sync::atomic::AtomicBool;
use std::sync::Arc;
use ident_case;
use syn::{self, Expr, Lit, Meta, NestedMeta};
use {Error, Result};
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))
}
fn from_meta(item: &Meta) -> Result<Self> {
(match *item {
Meta::Path(_) => Self::from_word(),
Meta::List(ref value) => Self::from_list(
&value
.nested
.iter()
.cloned()
.collect::<Vec<syn::NestedMeta>>()[..],
),
Meta::NameValue(ref value) => Self::from_value(&value.lit),
})
.map_err(|e| e.with_span(item))
}
fn from_word() -> Result<Self> {
Err(Error::unsupported_format("word"))
}
#[allow(unused_variables)]
fn from_list(items: &[NestedMeta]) -> Result<Self> {
Err(Error::unsupported_format("list"))
}
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()),
_ => Err(Error::unexpected_lit_type(value)),
})
.map_err(|e| e.with_span(value))
}
#[allow(unused_variables)]
fn from_char(value: char) -> Result<Self> {
Err(Error::unexpected_type("char"))
}
#[allow(unused_variables)]
fn from_string(value: &str) -> Result<Self> {
Err(Error::unexpected_type("string"))
}
#[allow(unused_variables)]
fn from_bool(value: bool) -> Result<Self> {
Err(Error::unexpected_type("bool"))
}
}
impl FromMeta for () {
fn from_word() -> Result<Self> {
Ok(())
}
}
impl FromMeta for bool {
fn from_word() -> Result<Self> {
Ok(true)
}
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 String {
fn from_string(s: &str) -> Result<Self> {
Ok(s.to_string())
}
}
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!(usize);
from_meta_num!(i8);
from_meta_num!(i16);
from_meta_num!(i32);
from_meta_num!(i64);
from_meta_num!(isize);
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);
impl FromMeta for syn::Ident {
fn from_string(value: &str) -> Result<Self> {
Ok(syn::Ident::new(value, ::proc_macro2::Span::call_site()))
}
fn from_value(value: &Lit) -> Result<Self> {
if let Lit::Str(ref ident) = *value {
ident
.parse()
.map_err(|_| Error::unknown_lit_str_value(ident))
} else {
Err(Error::unexpected_lit_type(value))
}
}
}
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))
}
}
}
impl FromMeta for syn::ExprArray {
fn from_value(value: &Lit) -> Result<Self> {
if let Lit::Str(ref ident) = *value {
ident
.parse::<syn::ExprArray>()
.map_err(|_| Error::unknown_lit_str_value(ident))
} else {
Err(Error::unexpected_lit_type(value))
}
}
}
macro_rules! from_numeric_array {
($ty:ident) => {
impl FromMeta for Vec<$ty> {
fn from_value(value: &Lit) -> Result<Self> {
let expr_array = syn::ExprArray::from_value(value)?;
let v =
expr_array
.elems
.iter()
.map(|expr| match expr {
Expr::Lit(lit) => $ty::from_value(&lit.lit),
_ => Err(Error::custom("Expected array of unsigned integers")
.with_span(expr)),
})
.collect::<Result<Vec<$ty>>>();
v
}
}
};
}
from_numeric_array!(u8);
from_numeric_array!(u16);
from_numeric_array!(u32);
from_numeric_array!(u64);
from_numeric_array!(usize);
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: &Lit) -> Result<Self> {
if let Lit::Str(ref path_str) = *value {
path_str
.parse()
.map_err(|_| Error::unknown_lit_str_value(path_str))
} else {
Err(Error::unexpected_lit_type(value))
}
}
}
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))
}
}
}
};
}
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 syn::WhereClause {
fn from_string(value: &str) -> Result<Self> {
syn::parse_str(value).map_err(|_| Error::unknown_value(value))
}
}
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())
}
}
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_meta(item: &Meta) -> Result<Self> {
FromMeta::from_meta(item).map(Some)
}
}
impl<T: FromMeta> FromMeta for Box<T> {
fn from_meta(item: &Meta) -> Result<Self> {
FromMeta::from_meta(item).map(Box::new)
}
}
impl<T: FromMeta> FromMeta for Result<T> {
fn from_meta(item: &Meta) -> Result<Self> {
Ok(FromMeta::from_meta(item))
}
}
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_meta(item: &Meta) -> Result<Self> {
FromMeta::from_meta(item).map(Rc::new)
}
}
impl<T: FromMeta> FromMeta for Arc<T> {
fn from_meta(item: &Meta) -> Result<Self> {
FromMeta::from_meta(item).map(Arc::new)
}
}
impl<T: FromMeta> FromMeta for RefCell<T> {
fn from_meta(item: &Meta) -> Result<Self> {
FromMeta::from_meta(item).map(RefCell::new)
}
}
impl<V: FromMeta, S: BuildHasher + Default> FromMeta for HashMap<String, V, S> {
fn from_list(nested: &[syn::NestedMeta]) -> Result<Self> {
let mut map = HashMap::with_capacity_and_hasher(nested.len(), Default::default());
for item in nested {
if let syn::NestedMeta::Meta(ref inner) = *item {
let path = inner.path();
let name = path
.segments
.iter()
.map(|s| s.ident.to_string())
.collect::<Vec<String>>()
.join("::");
match map.entry(name) {
Entry::Occupied(_) => {
return Err(Error::duplicate_field_path(&path).with_span(inner));
}
Entry::Vacant(entry) => {
entry.insert(FromMeta::from_meta(inner).map_err(|e| e.at_path(&path))?);
}
}
}
}
Ok(map)
}
}
#[cfg(test)]
mod tests {
use proc_macro2::TokenStream;
use syn;
use {Error, FromMeta, Result};
fn pm(tokens: TokenStream) -> ::std::result::Result<syn::Meta, String> {
let attribute: syn::Attribute = parse_quote!(#[#tokens]);
attribute.parse_meta().map_err(|_| "Unable to parse".into())
}
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() {
assert_eq!(fm::<()>(quote!(ignore)), ());
}
#[test]
fn bool_succeeds() {
assert_eq!(fm::<bool>(quote!(ignore)), true);
assert_eq!(fm::<bool>(quote!(ignore = true)), true);
assert_eq!(fm::<bool>(quote!(ignore = false)), false);
assert_eq!(fm::<bool>(quote!(ignore = "true")), true);
assert_eq!(fm::<bool>(quote!(ignore = "false")), false);
}
#[test]
fn string_succeeds() {
assert_eq!(&fm::<String>(quote!(ignore = "world")), "world");
assert_eq!(&fm::<String>(quote!(ignore = r#"world"#)), "world");
}
#[test]
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);
assert_eq!(fm::<u32>(quote!(ignore = 5000i32)), 5000u32);
}
#[test]
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
);
}
#[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 darling_result_succeeds() {
fm::<Result<()>>(quote!(ignore)).unwrap();
fm::<Result<()>>(quote!(ignore(world))).unwrap_err();
}
#[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_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]
);
}
}