| use std::{fmt, iter::FromIterator}; |
| |
| use crate::ast; |
| |
| /// Get the "shape" of a fields container, such as a struct or variant. |
| pub trait AsShape { |
| /// Get the "shape" of a fields container. |
| fn as_shape(&self) -> Shape; |
| } |
| |
| impl<T> AsShape for ast::Fields<T> { |
| fn as_shape(&self) -> Shape { |
| match self.style { |
| ast::Style::Tuple if self.fields.len() == 1 => Shape::Newtype, |
| ast::Style::Tuple => Shape::Tuple, |
| ast::Style::Struct => Shape::Named, |
| ast::Style::Unit => Shape::Unit, |
| } |
| } |
| } |
| |
| impl AsShape for syn::Fields { |
| fn as_shape(&self) -> Shape { |
| match self { |
| syn::Fields::Named(fields) => fields.as_shape(), |
| syn::Fields::Unnamed(fields) => fields.as_shape(), |
| syn::Fields::Unit => Shape::Unit, |
| } |
| } |
| } |
| |
| impl AsShape for syn::FieldsNamed { |
| fn as_shape(&self) -> Shape { |
| Shape::Named |
| } |
| } |
| |
| impl AsShape for syn::FieldsUnnamed { |
| fn as_shape(&self) -> Shape { |
| if self.unnamed.len() == 1 { |
| Shape::Newtype |
| } else { |
| Shape::Tuple |
| } |
| } |
| } |
| |
| impl AsShape for syn::DataStruct { |
| fn as_shape(&self) -> Shape { |
| self.fields.as_shape() |
| } |
| } |
| |
| impl AsShape for syn::Variant { |
| fn as_shape(&self) -> Shape { |
| self.fields.as_shape() |
| } |
| } |
| |
| /// Description of how fields in a struct or variant are syntactically laid out. |
| #[derive(Debug, Clone, Copy, PartialEq, Eq)] |
| pub enum Shape { |
| /// A set of named fields, e.g. `{ field: String }`. |
| Named, |
| /// A list of unnamed fields, e.g. `(String, u64)`. |
| Tuple, |
| /// No fields, e.g. `struct Example;` |
| Unit, |
| /// A special case of [`Tuple`](Shape#variant.Tuple) with exactly one field, e.g. `(String)`. |
| Newtype, |
| } |
| |
| impl Shape { |
| pub fn description(&self) -> &'static str { |
| match self { |
| Shape::Named => "named fields", |
| Shape::Tuple => "unnamed fields", |
| Shape::Unit => "no fields", |
| Shape::Newtype => "one unnamed field", |
| } |
| } |
| } |
| |
| impl fmt::Display for Shape { |
| fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { |
| write!(f, "{}", self.description()) |
| } |
| } |
| |
| impl AsShape for Shape { |
| fn as_shape(&self) -> Shape { |
| *self |
| } |
| } |
| |
| /// A set of [`Shape`] values, which correctly handles the relationship between |
| /// [newtype](Shape#variant.Newtype) and [tuple](Shape#variant.Tuple) shapes. |
| /// |
| /// # Example |
| /// ```rust |
| /// # use darling_core::util::{Shape, ShapeSet}; |
| /// let shape_set = ShapeSet::new(vec![Shape::Tuple]); |
| /// |
| /// // This is correct, because all newtypes are single-field tuples. |
| /// assert!(shape_set.contains(&Shape::Newtype)); |
| /// ``` |
| #[derive(Debug, Clone, Default)] |
| pub struct ShapeSet { |
| newtype: bool, |
| named: bool, |
| tuple: bool, |
| unit: bool, |
| } |
| |
| impl ShapeSet { |
| /// Create a new `ShapeSet` which includes the specified items. |
| /// |
| /// # Exampe |
| /// ```rust |
| /// # use darling_core::util::{Shape, ShapeSet}; |
| /// let shape_set = ShapeSet::new(vec![Shape::Named, Shape::Newtype]); |
| /// assert!(shape_set.contains(&Shape::Newtype)); |
| /// ``` |
| pub fn new(items: impl IntoIterator<Item = Shape>) -> Self { |
| items.into_iter().collect() |
| } |
| |
| /// Insert all possible shapes into the set. |
| /// |
| /// This is equivalent to calling [`insert`](ShapeSet#method.insert) with every value of [`Shape`]. |
| /// |
| /// # Example |
| /// ```rust |
| /// # use darling_core::util::{Shape, ShapeSet}; |
| /// let mut shape_set = ShapeSet::default(); |
| /// shape_set.insert_all(); |
| /// assert!(shape_set.contains(&Shape::Named)); |
| /// ``` |
| pub fn insert_all(&mut self) { |
| self.insert(Shape::Named); |
| self.insert(Shape::Newtype); |
| self.insert(Shape::Tuple); |
| self.insert(Shape::Unit); |
| } |
| |
| /// Insert a shape into the set, so that the set will match that shape |
| pub fn insert(&mut self, shape: Shape) { |
| match shape { |
| Shape::Named => self.named = true, |
| Shape::Tuple => self.tuple = true, |
| Shape::Unit => self.unit = true, |
| Shape::Newtype => self.newtype = true, |
| } |
| } |
| |
| /// Whether this set is empty. |
| pub fn is_empty(&self) -> bool { |
| !self.named && !self.newtype && !self.tuple && !self.unit |
| } |
| |
| fn contains_shape(&self, shape: Shape) -> bool { |
| match shape { |
| Shape::Named => self.named, |
| Shape::Tuple => self.tuple, |
| Shape::Unit => self.unit, |
| Shape::Newtype => self.newtype || self.tuple, |
| } |
| } |
| |
| /// Check if a fields container's shape is in this set. |
| pub fn contains(&self, fields: &impl AsShape) -> bool { |
| self.contains_shape(fields.as_shape()) |
| } |
| |
| /// Check if a field container's shape is in this set of shapes, and produce |
| /// an [`Error`](crate::Error) if it does not. |
| pub fn check(&self, fields: &impl AsShape) -> crate::Result<()> { |
| let shape = fields.as_shape(); |
| |
| if self.contains_shape(shape) { |
| Ok(()) |
| } else { |
| Err(crate::Error::unsupported_shape_with_expected( |
| shape.description(), |
| self, |
| )) |
| } |
| } |
| |
| fn to_vec(&self) -> Vec<Shape> { |
| let mut shapes = Vec::with_capacity(3); |
| |
| if self.named { |
| shapes.push(Shape::Named); |
| } |
| |
| if self.tuple || self.newtype { |
| shapes.push(if self.tuple { |
| Shape::Tuple |
| } else { |
| Shape::Newtype |
| }); |
| } |
| |
| if self.unit { |
| shapes.push(Shape::Unit) |
| } |
| |
| shapes |
| } |
| } |
| |
| impl fmt::Display for ShapeSet { |
| fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { |
| let shapes = self.to_vec(); |
| |
| match shapes.len() { |
| 0 => write!(f, "nothing"), |
| 1 => write!(f, "{}", shapes[0]), |
| 2 => write!(f, "{} or {}", shapes[0], shapes[1]), |
| 3 => write!(f, "{}, {}, or {}", shapes[0], shapes[1], shapes[2]), |
| _ => unreachable!(), |
| } |
| } |
| } |
| |
| impl FromIterator<Shape> for ShapeSet { |
| fn from_iter<T: IntoIterator<Item = Shape>>(iter: T) -> Self { |
| let mut output = ShapeSet::default(); |
| for shape in iter.into_iter() { |
| output.insert(shape); |
| } |
| |
| output |
| } |
| } |
| |
| #[cfg(test)] |
| mod tests { |
| use syn::parse_quote; |
| |
| use super::*; |
| |
| #[test] |
| fn any_accepts_anything() { |
| let mut filter = ShapeSet::default(); |
| filter.insert_all(); |
| let unit_struct: syn::DeriveInput = syn::parse_quote! { |
| struct Example; |
| }; |
| if let syn::Data::Struct(data) = unit_struct.data { |
| assert!(filter.contains(&data)); |
| } else { |
| panic!("Struct not parsed as struct"); |
| }; |
| } |
| |
| #[test] |
| fn tuple_accepts_newtype() { |
| let filter = ShapeSet::new(vec![Shape::Tuple]); |
| let newtype_struct: syn::DeriveInput = parse_quote! { |
| struct Example(String); |
| }; |
| |
| if let syn::Data::Struct(data) = newtype_struct.data { |
| assert!(filter.contains(&data)); |
| } else { |
| panic!("Struct not parsed as struct"); |
| }; |
| } |
| |
| #[test] |
| fn newtype_rejects_tuple() { |
| let filter = ShapeSet::new(vec![Shape::Newtype]); |
| let tuple_struct: syn::DeriveInput = parse_quote! { |
| struct Example(String, u64); |
| }; |
| |
| if let syn::Data::Struct(data) = tuple_struct.data { |
| assert!(!filter.contains(&data)); |
| } else { |
| panic!("Struct not parsed as struct"); |
| }; |
| } |
| } |