use std::{fmt, iter::FromIterator};
use crate::ast;
pub trait AsShape {
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()
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum Shape {
Named,
Tuple,
Unit,
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
}
}
#[derive(Debug, Clone, Default)]
pub struct ShapeSet {
newtype: bool,
named: bool,
tuple: bool,
unit: bool,
}
impl ShapeSet {
pub fn new(items: impl IntoIterator<Item = Shape>) -> Self {
items.into_iter().collect()
}
pub fn insert_all(&mut self) {
self.insert(Shape::Named);
self.insert(Shape::Newtype);
self.insert(Shape::Tuple);
self.insert(Shape::Unit);
}
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,
}
}
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,
}
}
pub fn contains(&self, fields: &impl AsShape) -> bool {
self.contains_shape(fields.as_shape())
}
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");
};
}
}