use proc_macro2; use syn; use syn::spanned::Spanned; /// Represent the `derivative` attributes on the input type (`struct`/`enum`). #[derive(Debug, Default)] pub struct Input { /// Whether `Clone` is present and its specific attributes. pub clone: Option, /// Whether `Copy` is present and its specific attributes. pub copy: Option, /// Whether `Debug` is present and its specific attributes. pub debug: Option, /// Whether `Default` is present and its specific attributes. pub default: Option, /// Whether `Eq` is present and its specific attributes. pub eq: Option, /// Whether `Hash` is present and its specific attributes. pub hash: Option, /// Whether `PartialEq` is present and its specific attributes. pub partial_eq: Option, /// Whether `PartialOrd` is present and its specific attributes. pub partial_ord: Option, /// Whether `Ord` is present and its specific attributes. pub ord: Option, pub is_packed: bool, } #[derive(Debug, Default)] /// Represent the `derivative` attributes on a field. pub struct Field { /// The parameters for `Clone`. clone: FieldClone, /// The parameters for `Copy`. copy_bound: Option>, /// The parameters for `Debug`. debug: FieldDebug, /// The parameters for `Default`. default: FieldDefault, /// The parameters for `Eq`. eq_bound: Option>, /// The parameters for `Hash`. hash: FieldHash, /// The parameters for `PartialEq`. partial_eq: FieldPartialEq, /// The parameters for `PartialOrd`. partial_ord: FieldPartialOrd, /// The parameters for `Ord`. ord: FieldOrd, } #[derive(Debug, Default)] /// Represent the `derivative(Clone(…))` attributes on an input. pub struct InputClone { /// The `bound` attribute if present and the corresponding bounds. bounds: Option>, /// Whether the implementation should have an explicit `clone_from`. pub clone_from: bool, } #[derive(Debug, Default)] /// Represent the `derivative(Clone(…))` attributes on an input. pub struct InputCopy { /// The `bound` attribute if present and the corresponding bounds. bounds: Option>, } #[derive(Debug, Default)] /// Represent the `derivative(Debug(…))` attributes on an input. pub struct InputDebug { /// The `bound` attribute if present and the corresponding bounds. bounds: Option>, /// Whether the type is marked `transparent`. pub transparent: bool, } #[derive(Debug, Default)] /// Represent the `derivative(Default(…))` attributes on an input. pub struct InputDefault { /// The `bound` attribute if present and the corresponding bounds. bounds: Option>, /// Whether the type is marked with `new`. pub new: bool, } #[derive(Debug, Default)] /// Represent the `derivative(Eq(…))` attributes on an input. pub struct InputEq { /// The `bound` attribute if present and the corresponding bounds. bounds: Option>, } #[derive(Debug, Default)] /// Represent the `derivative(Hash(…))` attributes on an input. pub struct InputHash { /// The `bound` attribute if present and the corresponding bounds. bounds: Option>, } #[derive(Debug, Default)] /// Represent the `derivative(PartialEq(…))` attributes on an input. pub struct InputPartialEq { /// The `bound` attribute if present and the corresponding bounds. bounds: Option>, } #[derive(Debug, Default)] /// Represent the `derivative(PartialOrd(…))` attributes on an input. pub struct InputPartialOrd { /// The `bound` attribute if present and the corresponding bounds. bounds: Option>, /// Allow `derivative(PartialOrd)` on enums: on_enum: bool, } #[derive(Debug, Default)] /// Represent the `derivative(Ord(…))` attributes on an input. pub struct InputOrd { /// The `bound` attribute if present and the corresponding bounds. bounds: Option>, /// Allow `derivative(Ord)` on enums: on_enum: bool, } #[derive(Debug, Default)] /// Represents the `derivative(Clone(…))` attributes on a field. pub struct FieldClone { /// The `bound` attribute if present and the corresponding bounds. bounds: Option>, /// The `clone_with` attribute if present and the path to the cloning function. clone_with: Option, } #[derive(Debug, Default)] /// Represents the `derivative(Debug(…))` attributes on a field. pub struct FieldDebug { /// The `bound` attribute if present and the corresponding bounds. bounds: Option>, /// The `format_with` attribute if present and the path to the formatting function. format_with: Option, /// Whether the field is to be ignored from output. ignore: bool, } #[derive(Debug, Default)] /// Represent the `derivative(Default(…))` attributes on a field. pub struct FieldDefault { /// The `bound` attribute if present and the corresponding bounds. bounds: Option>, /// The default value for the field if present. pub value: Option, } #[derive(Debug, Default)] /// Represents the `derivative(Hash(…))` attributes on a field. pub struct FieldHash { /// The `bound` attribute if present and the corresponding bounds. bounds: Option>, /// The `hash_with` attribute if present and the path to the hashing function. hash_with: Option, /// Whether the field is to be ignored when hashing. ignore: bool, } #[derive(Debug, Default)] /// Represent the `derivative(PartialEq(…))` attributes on a field. pub struct FieldPartialEq { /// The `bound` attribute if present and the corresponding bounds. bounds: Option>, /// The `compare_with` attribute if present and the path to the comparison function. compare_with: Option, /// Whether the field is to be ignored when comparing. ignore: bool, } #[derive(Debug, Default)] /// Represent the `derivative(PartialOrd(…))` attributes on a field. pub struct FieldPartialOrd { /// The `bound` attribute if present and the corresponding bounds. bounds: Option>, /// The `compare_with` attribute if present and the path to the comparison function. compare_with: Option, /// Whether the field is to be ignored when comparing. ignore: bool, } #[derive(Debug, Default)] /// Represent the `derivative(Ord(…))` attributes on a field. pub struct FieldOrd { /// The `bound` attribute if present and the corresponding bounds. bounds: Option>, /// The `compare_with` attribute if present and the path to the comparison function. compare_with: Option, /// Whether the field is to be ignored when comparing. ignore: bool, } macro_rules! for_all_attr { ($errors:ident; for ($name:ident, $value:ident) in $attrs:expr; $($body:tt)*) => { for meta_items in $attrs.iter() { let meta_items = derivative_attribute(meta_items, $errors); if let Some(meta_items) = meta_items { for meta_item in meta_items.iter() { let meta_item = read_items(meta_item, $errors); let MetaItem($name, $value) = try!(meta_item); match $name.to_string().as_ref() { $($body)* } } } } }; } macro_rules! match_attributes { ($errors:ident for $trait:expr; let Some($name:ident) = $unwrapped:expr; for $value:ident in $values:expr; $($body:tt)* ) => { let mut $name = $unwrapped.take().unwrap_or_default(); match_attributes! { $errors for $trait; for $value in $values; $($body)* } $unwrapped = Some($name); }; ($errors:ident for $trait:expr; for $value:ident in $values:expr; $($body:tt)* ) => { for (name, $value) in $values { match name { Some(ident) => { match ident.to_string().as_ref() { $($body)* unknown => { let message = format!("Unknown attribute `{}` for trait `{}`", unknown, $trait); $errors.extend(quote_spanned! {ident.span()=> compile_error!(#message); }); } } } None => { let value = $value.expect("Expected value to be passed"); match value.value().as_ref() { $($body)* unknown => { let message = format!("Unknown attribute `{}` for trait `{}`", unknown, $trait); let span = value.span(); $errors.extend(quote_spanned! {span=> compile_error!(#message); }); } } } } } }; } impl Input { /// Parse the `derivative` attributes on a type. #[allow(clippy::cognitive_complexity)] // mostly macros pub fn from_ast( attrs: &[syn::Attribute], errors: &mut proc_macro2::TokenStream, ) -> Result { let mut input = Input { is_packed: attrs.iter().any(has_repr_packed_attr), ..Default::default() }; for_all_attr! { errors; for (name, values) in attrs; "Clone" => { match_attributes! { errors for "Clone"; let Some(clone) = input.clone; for value in values; "bound" => parse_bound(&mut clone.bounds, value, errors), "clone_from" => { clone.clone_from = parse_boolean_meta_item(value, true, "clone_from", errors); } } } "Copy" => { match_attributes! { errors for "Copy"; let Some(copy) = input.copy; for value in values; "bound" => parse_bound(&mut copy.bounds, value, errors), } } "Debug" => { match_attributes! { errors for "Debug"; let Some(debug) = input.debug; for value in values; "bound" => parse_bound(&mut debug.bounds, value, errors), "transparent" => { debug.transparent = parse_boolean_meta_item(value, true, "transparent", errors); } } } "Default" => { match_attributes! { errors for "Default"; let Some(default) = input.default; for value in values; "bound" => parse_bound(&mut default.bounds, value, errors), "new" => { default.new = parse_boolean_meta_item(value, true, "new", errors); } } } "Eq" => { match_attributes! { errors for "Eq"; let Some(eq) = input.eq; for value in values; "bound" => parse_bound(&mut eq.bounds, value, errors), } } "Hash" => { match_attributes! { errors for "Hash"; let Some(hash) = input.hash; for value in values; "bound" => parse_bound(&mut hash.bounds, value, errors), } } "PartialEq" => { match_attributes! { errors for "PartialEq"; let Some(partial_eq) = input.partial_eq; for value in values; "bound" => parse_bound(&mut partial_eq.bounds, value, errors), "feature_allow_slow_enum" => (), // backward compatibility, now unnecessary } } "PartialOrd" => { match_attributes! { errors for "PartialOrd"; let Some(partial_ord) = input.partial_ord; for value in values; "bound" => parse_bound(&mut partial_ord.bounds, value, errors), "feature_allow_slow_enum" => { partial_ord.on_enum = parse_boolean_meta_item(value, true, "feature_allow_slow_enum", errors); } } } "Ord" => { match_attributes! { errors for "Ord"; let Some(ord) = input.ord; for value in values; "bound" => parse_bound(&mut ord.bounds, value, errors), "feature_allow_slow_enum" => { ord.on_enum = parse_boolean_meta_item(value, true, "feature_allow_slow_enum", errors); } } } unknown => { let message = format!("deriving `{}` is not supported by derivative", unknown); errors.extend(quote_spanned! {name.span()=> compile_error!(#message); }); } } Ok(input) } pub fn clone_bound(&self) -> Option<&[syn::WherePredicate]> { self.clone .as_ref() .and_then(|d| d.bounds.as_ref().map(Vec::as_slice)) } pub fn clone_from(&self) -> bool { self.clone.as_ref().map_or(false, |d| d.clone_from) } pub fn copy_bound(&self) -> Option<&[syn::WherePredicate]> { self.copy .as_ref() .and_then(|d| d.bounds.as_ref().map(Vec::as_slice)) } pub fn debug_bound(&self) -> Option<&[syn::WherePredicate]> { self.debug .as_ref() .and_then(|d| d.bounds.as_ref().map(Vec::as_slice)) } pub fn debug_transparent(&self) -> bool { self.debug.as_ref().map_or(false, |d| d.transparent) } pub fn default_bound(&self) -> Option<&[syn::WherePredicate]> { self.default .as_ref() .and_then(|d| d.bounds.as_ref().map(Vec::as_slice)) } pub fn eq_bound(&self) -> Option<&[syn::WherePredicate]> { self.eq .as_ref() .and_then(|d| d.bounds.as_ref().map(Vec::as_slice)) } pub fn hash_bound(&self) -> Option<&[syn::WherePredicate]> { self.hash .as_ref() .and_then(|d| d.bounds.as_ref().map(Vec::as_slice)) } pub fn partial_eq_bound(&self) -> Option<&[syn::WherePredicate]> { self.partial_eq .as_ref() .and_then(|d| d.bounds.as_ref().map(Vec::as_slice)) } pub fn partial_ord_bound(&self) -> Option<&[syn::WherePredicate]> { self.partial_ord .as_ref() .and_then(|d| d.bounds.as_ref().map(Vec::as_slice)) } pub fn ord_bound(&self) -> Option<&[syn::WherePredicate]> { self.ord .as_ref() .and_then(|d| d.bounds.as_ref().map(Vec::as_slice)) } pub fn partial_ord_on_enum(&self) -> bool { self.partial_ord.as_ref().map_or(false, |d| d.on_enum) } pub fn ord_on_enum(&self) -> bool { self.ord.as_ref().map_or(false, |d| d.on_enum) } } impl Field { /// Parse the `derivative` attributes on a type. #[allow(clippy::cognitive_complexity)] // mostly macros pub fn from_ast( field: &syn::Field, errors: &mut proc_macro2::TokenStream, ) -> Result { let mut out = Field::default(); for_all_attr! { errors; for (name, values) in field.attrs; "Clone" => { match_attributes! { errors for "Clone"; for value in values; "bound" => parse_bound(&mut out.clone.bounds, value, errors), "clone_with" => { let path = value.expect("`clone_with` needs a value"); out.clone.clone_with = parse_str_lit(&path, errors).ok(); } } } "Debug" => { match_attributes! { errors for "Debug"; for value in values; "bound" => parse_bound(&mut out.debug.bounds, value, errors), "format_with" => { let path = value.expect("`format_with` needs a value"); out.debug.format_with = parse_str_lit(&path, errors).ok(); } "ignore" => { out.debug.ignore = parse_boolean_meta_item(value, true, "ignore", errors); } } } "Default" => { match_attributes! { errors for "Default"; for value in values; "bound" => parse_bound(&mut out.default.bounds, value, errors), "value" => { let value = value.expect("`value` needs a value"); out.default.value = parse_str_lit(&value, errors).ok(); } } } "Eq" => { match_attributes! { errors for "Eq"; for value in values; "bound" => parse_bound(&mut out.eq_bound, value, errors), } } "Hash" => { match_attributes! { errors for "Hash"; for value in values; "bound" => parse_bound(&mut out.hash.bounds, value, errors), "hash_with" => { let path = value.expect("`hash_with` needs a value"); out.hash.hash_with = parse_str_lit(&path, errors).ok(); } "ignore" => { out.hash.ignore = parse_boolean_meta_item(value, true, "ignore", errors); } } } "PartialEq" => { match_attributes! { errors for "PartialEq"; for value in values; "bound" => parse_bound(&mut out.partial_eq.bounds, value, errors), "compare_with" => { let path = value.expect("`compare_with` needs a value"); out.partial_eq.compare_with = parse_str_lit(&path, errors).ok(); } "ignore" => { out.partial_eq.ignore = parse_boolean_meta_item(value, true, "ignore", errors); } } } "PartialOrd" => { match_attributes! { errors for "PartialOrd"; for value in values; "bound" => parse_bound(&mut out.partial_ord.bounds, value, errors), "compare_with" => { let path = value.expect("`compare_with` needs a value"); out.partial_ord.compare_with = parse_str_lit(&path, errors).ok(); } "ignore" => { out.partial_ord.ignore = parse_boolean_meta_item(value, true, "ignore", errors); } } } "Ord" => { match_attributes! { errors for "Ord"; for value in values; "bound" => parse_bound(&mut out.ord.bounds, value, errors), "compare_with" => { let path = value.expect("`compare_with` needs a value"); out.ord.compare_with = parse_str_lit(&path, errors).ok(); } "ignore" => { out.ord.ignore = parse_boolean_meta_item(value, true, "ignore", errors); } } } unknown => { let message = format!("deriving `{}` is not supported by derivative", unknown); errors.extend(quote_spanned! {name.span()=> compile_error!(#message); }); } } Ok(out) } pub fn clone_bound(&self) -> Option<&[syn::WherePredicate]> { self.clone.bounds.as_ref().map(Vec::as_slice) } pub fn clone_with(&self) -> Option<&syn::Path> { self.clone.clone_with.as_ref() } pub fn copy_bound(&self) -> Option<&[syn::WherePredicate]> { self.copy_bound.as_ref().map(Vec::as_slice) } pub fn debug_bound(&self) -> Option<&[syn::WherePredicate]> { self.debug.bounds.as_ref().map(Vec::as_slice) } pub fn debug_format_with(&self) -> Option<&syn::Path> { self.debug.format_with.as_ref() } pub fn ignore_debug(&self) -> bool { self.debug.ignore } pub fn ignore_hash(&self) -> bool { self.hash.ignore } pub fn default_bound(&self) -> Option<&[syn::WherePredicate]> { self.default.bounds.as_ref().map(Vec::as_slice) } pub fn default_value(&self) -> Option<&proc_macro2::TokenStream> { self.default.value.as_ref() } pub fn eq_bound(&self) -> Option<&[syn::WherePredicate]> { self.eq_bound.as_ref().map(Vec::as_slice) } pub fn hash_bound(&self) -> Option<&[syn::WherePredicate]> { self.hash.bounds.as_ref().map(Vec::as_slice) } pub fn hash_with(&self) -> Option<&syn::Path> { self.hash.hash_with.as_ref() } pub fn partial_eq_bound(&self) -> Option<&[syn::WherePredicate]> { self.partial_eq.bounds.as_ref().map(Vec::as_slice) } pub fn partial_ord_bound(&self) -> Option<&[syn::WherePredicate]> { self.partial_ord.bounds.as_ref().map(Vec::as_slice) } pub fn ord_bound(&self) -> Option<&[syn::WherePredicate]> { self.ord.bounds.as_ref().map(Vec::as_slice) } pub fn partial_eq_compare_with(&self) -> Option<&syn::Path> { self.partial_eq.compare_with.as_ref() } pub fn partial_ord_compare_with(&self) -> Option<&syn::Path> { self.partial_ord.compare_with.as_ref() } pub fn ord_compare_with(&self) -> Option<&syn::Path> { self.ord.compare_with.as_ref() } pub fn ignore_partial_eq(&self) -> bool { self.partial_eq.ignore } pub fn ignore_partial_ord(&self) -> bool { self.partial_ord.ignore } pub fn ignore_ord(&self) -> bool { self.ord.ignore } } /// Represent an attribute. /// /// We only have a limited set of possible attributes: /// /// * `#[derivative(Debug)]` is represented as `(Debug, [])`; /// * `#[derivative(Debug="foo")]` is represented as `(Debug, [(None, Some("foo"))])`; /// * `#[derivative(Debug(foo="bar")]` is represented as `(Debug, [(Some(foo), Some("bar"))])`. struct MetaItem<'a>( &'a syn::Ident, Vec<(Option<&'a syn::Ident>, Option<&'a syn::LitStr>)>, ); /// Parse an arbitrary item for our limited `MetaItem` subset. fn read_items<'a>(item: &'a syn::NestedMeta, errors: &mut proc_macro2::TokenStream) -> Result, ()> { let item = match *item { syn::NestedMeta::Meta(ref item) => item, syn::NestedMeta::Lit(ref lit) => { errors.extend(quote_spanned! {lit.span()=> compile_error!("expected meta-item but found literal"); }); return Err(()); } }; match *item { syn::Meta::Path(ref path) => match path.get_ident() { Some(name) => Ok(MetaItem(name, Vec::new())), None => { errors.extend(quote_spanned! {path.span()=> compile_error!("expected derivative attribute to be a string, but found a path"); }); Err(()) } }, syn::Meta::List(syn::MetaList { ref path, nested: ref values, .. }) => { let values = values .iter() .map(|value| { if let syn::NestedMeta::Meta(syn::Meta::NameValue(syn::MetaNameValue { ref path, lit: ref value, .. })) = *value { let (name, value) = ensure_str_lit(&path, &value, errors)?; Ok((Some(name), Some(value))) } else { errors.extend(quote_spanned! {value.span()=> compile_error!("expected named value"); }); Err(()) } }) .collect::>()?; let name = match path.get_ident() { Some(name) => name, None => { errors.extend(quote_spanned! {path.span()=> compile_error!("expected derivative attribute to be a string, but found a path"); }); return Err(()); } }; Ok(MetaItem(name, values)) } syn::Meta::NameValue(syn::MetaNameValue { ref path, lit: ref value, .. }) => { let (name, value) = ensure_str_lit(&path, &value, errors)?; Ok(MetaItem(name, vec![(None, Some(value))])) } } } /// Filter the `derivative` items from an attribute. fn derivative_attribute( attribute: &syn::Attribute, errors: &mut proc_macro2::TokenStream, ) -> Option> { if !attribute.path.is_ident("derivative") { return None; } match attribute.parse_meta() { Ok(syn::Meta::List(meta_list)) => Some(meta_list.nested), Ok(_) => None, Err(e) => { let message = format!("invalid attribute: {}", e); errors.extend(quote_spanned! {e.span()=> compile_error!(#message); }); None } } } /// Parse an item value as a boolean. Accepted values are the string literal `"true"` and /// `"false"`. The `default` parameter specifies what the value of the boolean is when only its /// name is specified (eg. `Debug="ignore"` is equivalent to `Debug(ignore="true")`). The `name` /// parameter is used for error reporting. fn parse_boolean_meta_item( item: Option<&syn::LitStr>, default: bool, name: &str, errors: &mut proc_macro2::TokenStream, ) -> bool { if let Some(item) = item.as_ref() { match item.value().as_ref() { "true" => true, "false" => false, val => { if val == name { true } else { let message = format!( r#"expected `"true"` or `"false"` for `{}`, got `{}`"#, name, val ); errors.extend(quote_spanned! {item.span()=> compile_error!(#message); }); default } } } } else { default } } /// Parse a `bound` item. fn parse_bound( opt_bounds: &mut Option>, value: Option<&syn::LitStr>, errors: &mut proc_macro2::TokenStream, ) { let bound = value.expect("`bound` needs a value"); let bound_value = bound.value(); *opt_bounds = if !bound_value.is_empty() { let where_string = syn::LitStr::new(&format!("where {}", bound_value), bound.span()); let bounds = parse_str_lit::(&where_string, errors) .map(|wh| wh.predicates.into_iter().collect()); match bounds { Ok(bounds) => Some(bounds), Err(_) => { errors.extend(quote_spanned! {where_string.span()=> compile_error!("could not parse bound"); }); None } } } else { Some(vec![]) }; } fn parse_str_lit(value: &syn::LitStr, errors: &mut proc_macro2::TokenStream) -> Result where T: syn::parse::Parse, { match value.parse() { Ok(value) => Ok(value), Err(e) => { let message = format!("could not parse string literal: {}", e); errors.extend(quote_spanned! {value.span()=> compile_error!(#message); }); Err(()) } } } fn ensure_str_lit<'a>( attr_path: &'a syn::Path, lit: &'a syn::Lit, errors: &mut proc_macro2::TokenStream, ) -> Result<(&'a syn::Ident, &'a syn::LitStr), ()> { let attr_name = match attr_path.get_ident() { Some(attr_name) => attr_name, None => { errors.extend(quote_spanned! {attr_path.span()=> compile_error!("expected derivative attribute to be a string, but found a path"); }); return Err(()); } }; if let syn::Lit::Str(ref lit) = *lit { Ok((attr_name, lit)) } else { let message = format!( "expected derivative {} attribute to be a string: `{} = \"...\"`", attr_name, attr_name ); errors.extend(quote_spanned! {lit.span()=> compile_error!(#message); }); Err(()) } } pub fn has_repr_packed_attr(attr: &syn::Attribute) -> bool { if let Ok(attr) = attr.parse_meta() { if attr.path().get_ident().map(|i| i == "repr") == Some(true) { if let syn::Meta::List(items) = attr { for item in items.nested { if let syn::NestedMeta::Meta(item) = item { if item.path().get_ident().map(|i| i == "packed") == Some(true) { return true; } } } } } } false }