]> git.lizzy.rs Git - enumset.git/blobdiff - enumset_derive/src/lib.rs
Opps, used feature from too new a Rust version.
[enumset.git] / enumset_derive / src / lib.rs
index 5f63479fe0fedd42bfb12c6469c2c9d33e16051f..88ac367e0ad914cf317bc046bebc9a51e1c655e2 100644 (file)
-#![recursion_limit="256"]
-#![cfg_attr(feature = "nightly", feature(proc_macro_diagnostic))]
+#![recursion_limit = "256"]
 
-extern crate syn;
 extern crate proc_macro;
-extern crate proc_macro2;
-extern crate quote;
 
+use darling::*;
 use proc_macro::TokenStream;
-use proc_macro2::{TokenStream as SynTokenStream, Literal};
-use syn::*;
-use syn::export::Span;
-use syn::spanned::Spanned;
+use proc_macro2::{Literal, Span, TokenStream as SynTokenStream};
 use quote::*;
+use std::{collections::HashSet, fmt::Display};
+use syn::spanned::Spanned;
+use syn::{Error, Result, *};
+
+/// Helper function for emitting compile errors.
+fn error<T>(span: Span, message: impl Display) -> Result<T> {
+    Err(Error::new(span, message))
+}
 
-#[cfg(feature = "nightly")]
-fn error(span: Span, data: &str) -> TokenStream {
-    span.unstable().error(data).emit();
-    TokenStream::new()
+/// Decodes the custom attributes for our custom derive.
+#[derive(FromDeriveInput, Default)]
+#[darling(attributes(enumset), default)]
+struct EnumsetAttrs {
+    no_ops: bool,
+    no_super_impls: bool,
+    #[darling(default)]
+    repr: Option<String>,
+    serialize_as_list: bool,
+    serialize_deny_unknown: bool,
+    #[darling(default)]
+    serialize_repr: Option<String>,
+    #[darling(default)]
+    crate_name: Option<String>,
 }
 
-#[cfg(not(feature = "nightly"))]
-fn error(_: Span, data: &str) -> TokenStream {
-    panic!("{}", data)
+/// An variant in the enum set type.
+struct EnumSetValue {
+    /// The name of the variant.
+    name: Ident,
+    /// The discriminant of the variant.
+    variant_repr: u32,
 }
 
-fn enum_set_type_impl(
-    name: &Ident, all_variants: u128, repr: Ident,
-    no_ops: bool, no_derives: bool, _serialize_as_list: bool,
-) -> SynTokenStream {
-    let typed_enumset = quote!(::enumset::EnumSet<#name>);
-    let core = quote!(::enumset::internal::core);
+/// Stores information about the enum set type.
+#[allow(dead_code)]
+struct EnumSetInfo {
+    /// The name of the enum.
+    name: Ident,
+    /// The crate name to use.
+    crate_name: Option<Ident>,
+    /// The numeric type to represent the `EnumSet` as in memory.
+    explicit_mem_repr: Option<Ident>,
+    /// The numeric type to serialize the enum as.
+    explicit_serde_repr: Option<Ident>,
+    /// Whether the underlying repr of the enum supports negative values.
+    has_signed_repr: bool,
+    /// Whether the underlying repr of the enum supports values higher than 2^32.
+    has_large_repr: bool,
+    /// A list of variants in the enum.
+    variants: Vec<EnumSetValue>,
+
+    /// The highest encountered variant discriminant.
+    max_discrim: u32,
+    /// The current variant discriminant. Used to track, e.g. `A=10,B,C`.
+    cur_discrim: u32,
+    /// A list of variant names that are already in use.
+    used_variant_names: HashSet<String>,
+    /// A list of variant discriminants that are already in use.
+    used_discriminants: HashSet<u32>,
+
+    /// Avoid generating operator overloads on the enum type.
+    no_ops: bool,
+    /// Avoid generating implementations for `Clone`, `Copy`, `Eq`, and `PartialEq`.
+    no_super_impls: bool,
+    /// Serialize the enum as a list.
+    serialize_as_list: bool,
+    /// Disallow unknown bits while deserializing the enum.
+    serialize_deny_unknown: bool,
+}
+impl EnumSetInfo {
+    fn new(input: &DeriveInput, attrs: EnumsetAttrs) -> EnumSetInfo {
+        EnumSetInfo {
+            name: input.ident.clone(),
+            crate_name: attrs.crate_name.map(|x| Ident::new(&x, Span::call_site())),
+            explicit_mem_repr: attrs.repr.map(|x| Ident::new(&x, Span::call_site())),
+            explicit_serde_repr: attrs
+                .serialize_repr
+                .map(|x| Ident::new(&x, Span::call_site())),
+            has_signed_repr: false,
+            has_large_repr: false,
+            variants: Vec::new(),
+            max_discrim: 0,
+            cur_discrim: 0,
+            used_variant_names: HashSet::new(),
+            used_discriminants: HashSet::new(),
+            no_ops: attrs.no_ops,
+            no_super_impls: attrs.no_super_impls,
+            serialize_as_list: attrs.serialize_as_list,
+            serialize_deny_unknown: attrs.serialize_deny_unknown,
+        }
+    }
+
+    /// Sets an explicit repr for the enumset.
+    fn push_explicit_repr(&mut self, attr_span: Span, repr: &str) -> Result<()> {
+        // Check whether the repr is supported, and if so, set some flags for better error
+        // messages later on.
+        match repr {
+            "Rust" | "C" | "u8" | "u16" | "u32" => Ok(()),
+            "usize" | "u64" | "u128" => {
+                self.has_large_repr = true;
+                Ok(())
+            }
+            "i8" | "i16" | "i32" => {
+                self.has_signed_repr = true;
+                Ok(())
+            }
+            "isize" | "i64" | "i128" => {
+                self.has_signed_repr = true;
+                self.has_large_repr = true;
+                Ok(())
+            }
+            _ => error(attr_span, "Unsupported repr."),
+        }
+    }
+    /// Adds a variant to the enumset.
+    fn push_variant(&mut self, variant: &Variant) -> Result<()> {
+        if self.used_variant_names.contains(&variant.ident.to_string()) {
+            error(variant.span(), "Duplicated variant name.")
+        } else if let Fields::Unit = variant.fields {
+            // Parse the discriminant.
+            if let Some((_, expr)) = &variant.discriminant {
+                let discriminant_fail_message = format!(
+                    "Enum set discriminants must be `u32`s.{}",
+                    if self.has_signed_repr || self.has_large_repr {
+                        format!(
+                            " ({} discrimiants are still unsupported with reprs that allow them.)",
+                            if self.has_large_repr {
+                                "larger"
+                            } else if self.has_signed_repr {
+                                "negative"
+                            } else {
+                                "larger or negative"
+                            }
+                        )
+                    } else {
+                        String::new()
+                    },
+                );
+                if let Expr::Lit(ExprLit { lit: Lit::Int(i), .. }) = expr {
+                    match i.base10_parse() {
+                        Ok(val) => self.cur_discrim = val,
+                        Err(_) => error(expr.span(), &discriminant_fail_message)?,
+                    }
+                } else {
+                    error(variant.span(), &discriminant_fail_message)?;
+                }
+            }
+
+            // Validate the discriminant.
+            let discriminant = self.cur_discrim;
+            if discriminant >= 128 {
+                let message = if self.variants.len() <= 127 {
+                    "`#[derive(EnumSetType)]` currently only supports discriminants up to 127."
+                } else {
+                    "`#[derive(EnumSetType)]` currently only supports enums up to 128 variants."
+                };
+                error(variant.span(), message)?;
+            }
+            if self.used_discriminants.contains(&discriminant) {
+                error(variant.span(), "Duplicated enum discriminant.")?;
+            }
+
+            // Add the variant to the info.
+            self.cur_discrim += 1;
+            if discriminant > self.max_discrim {
+                self.max_discrim = discriminant;
+            }
+            self.variants
+                .push(EnumSetValue { name: variant.ident.clone(), variant_repr: discriminant });
+            self.used_variant_names.insert(variant.ident.to_string());
+            self.used_discriminants.insert(discriminant);
+
+            Ok(())
+        } else {
+            error(variant.span(), "`#[derive(EnumSetType)]` can only be used on fieldless enums.")
+        }
+    }
+    /// Validate the enumset type.
+    fn validate(&self) -> Result<()> {
+        fn do_check(ty: &str, max_discrim: u32, what: &str) -> Result<()> {
+            let is_overflowed = match ty {
+                "u8" => max_discrim >= 8,
+                "u16" => max_discrim >= 16,
+                "u32" => max_discrim >= 32,
+                "u64" => max_discrim >= 64,
+                "u128" => max_discrim >= 128,
+                _ => error(
+                    Span::call_site(),
+                    format!(
+                        "Only `u8`, `u16`, `u32`, `u64` and `u128` are supported for {}.",
+                        what
+                    ),
+                )?,
+            };
+            if is_overflowed {
+                error(Span::call_site(), format!("{} cannot be smaller than bitset.", what))?;
+            }
+            Ok(())
+        }
+
+        // Check if all bits of the bitset can fit in the serialization representation.
+        if let Some(explicit_serde_repr) = &self.explicit_serde_repr {
+            do_check(&explicit_serde_repr.to_string(), self.max_discrim, "serialize_repr")?;
+        }
+
+        // Check if all bits of the bitset can fit in the memory representation, if one was given.
+        if let Some(explicit_mem_repr) = &self.explicit_mem_repr {
+            do_check(&explicit_mem_repr.to_string(), self.max_discrim, "repr")?;
+        }
+        Ok(())
+    }
+
+    /// Computes the underlying type used to store the enumset.
+    fn enumset_repr(&self) -> SynTokenStream {
+        if let Some(explicit_mem_repr) = &self.explicit_mem_repr {
+            explicit_mem_repr.to_token_stream()
+        } else if self.max_discrim <= 7 {
+            quote! { u8 }
+        } else if self.max_discrim <= 15 {
+            quote! { u16 }
+        } else if self.max_discrim <= 31 {
+            quote! { u32 }
+        } else if self.max_discrim <= 63 {
+            quote! { u64 }
+        } else if self.max_discrim <= 127 {
+            quote! { u128 }
+        } else {
+            panic!("max_variant > 127?")
+        }
+    }
+    /// Computes the underlying type used to serialize the enumset.
     #[cfg(feature = "serde")]
-    let serde = quote!(::enumset::internal::serde);
+    fn serde_repr(&self) -> SynTokenStream {
+        if let Some(serde_repr) = &self.explicit_serde_repr {
+            quote! { #serde_repr }
+        } else {
+            self.enumset_repr()
+        }
+    }
+
+    /// Returns a bitmask of all variants in the set.
+    fn all_variants(&self) -> u128 {
+        let mut accum = 0u128;
+        for variant in &self.variants {
+            assert!(variant.variant_repr <= 127);
+            accum |= 1u128 << variant.variant_repr as u128;
+        }
+        accum
+    }
+}
+
+/// Generates the actual `EnumSetType` impl.
+fn enum_set_type_impl(info: EnumSetInfo) -> SynTokenStream {
+    let name = &info.name;
+
+    let enumset = match &info.crate_name {
+        Some(crate_name) => quote!(::#crate_name),
+        None => {
+            #[cfg(feature = "proc-macro-crate")]
+            {
+                use proc_macro_crate::FoundCrate;
+
+                let crate_name = proc_macro_crate::crate_name("enumset");
+                match crate_name {
+                    Ok(FoundCrate::Name(name)) => {
+                        let ident = Ident::new(&name, Span::call_site());
+                        quote!(::#ident)
+                    }
+                    _ => quote!(::enumset),
+                }
+            }
+
+            #[cfg(not(feature = "proc-macro-crate"))]
+            {
+                quote!(::enumset)
+            }
+        }
+    };
+    let typed_enumset = quote!(#enumset::EnumSet<#name>);
+    let core = quote!(#enumset::__internal::core_export);
 
-    // proc_macro2 does not support creating u128 literals.
-    let all_variants = Literal::u128_unsuffixed(all_variants);
+    let repr = info.enumset_repr();
+    let all_variants = Literal::u128_unsuffixed(info.all_variants());
 
-    let ops = if no_ops {
+    let ops = if info.no_ops {
         quote! {}
     } else {
         quote! {
             impl <O : Into<#typed_enumset>> #core::ops::Sub<O> for #name {
                 type Output = #typed_enumset;
                 fn sub(self, other: O) -> Self::Output {
-                    ::enumset::EnumSet::only(self) - other.into()
+                    #enumset::EnumSet::only(self) - other.into()
                 }
             }
             impl <O : Into<#typed_enumset>> #core::ops::BitAnd<O> for #name {
                 type Output = #typed_enumset;
                 fn bitand(self, other: O) -> Self::Output {
-                    ::enumset::EnumSet::only(self) & other.into()
+                    #enumset::EnumSet::only(self) & other.into()
                 }
             }
             impl <O : Into<#typed_enumset>> #core::ops::BitOr<O> for #name {
                 type Output = #typed_enumset;
                 fn bitor(self, other: O) -> Self::Output {
-                    ::enumset::EnumSet::only(self) | other.into()
+                    #enumset::EnumSet::only(self) | other.into()
                 }
             }
             impl <O : Into<#typed_enumset>> #core::ops::BitXor<O> for #name {
                 type Output = #typed_enumset;
                 fn bitxor(self, other: O) -> Self::Output {
-                    ::enumset::EnumSet::only(self) ^ other.into()
+                    #enumset::EnumSet::only(self) ^ other.into()
                 }
             }
             impl #core::ops::Not for #name {
                 type Output = #typed_enumset;
                 fn not(self) -> Self::Output {
-                    !::enumset::EnumSet::only(self)
+                    !#enumset::EnumSet::only(self)
                 }
             }
             impl #core::cmp::PartialEq<#typed_enumset> for #name {
                 fn eq(&self, other: &#typed_enumset) -> bool {
-                    ::enumset::EnumSet::only(*self) == *other
-                }
-            }
-        }
-    };
-
-    let derives = if no_derives {
-        quote! {}
-    } else {
-        quote! {
-            impl #core::cmp::PartialOrd for #name {
-                fn partial_cmp(&self, other: &Self) -> #core::option::Option<#core::cmp::Ordering> {
-                    (*self as u8).partial_cmp(&(*other as u8))
-                }
-            }
-            impl #core::cmp::Ord for #name {
-                fn cmp(&self, other: &Self) -> #core::cmp::Ordering {
-                    (*self as u8).cmp(&(*other as u8))
-                }
-            }
-            impl #core::cmp::PartialEq for #name {
-                fn eq(&self, other: &Self) -> bool {
-                    (*self as u8) == (*other as u8)
+                    #enumset::EnumSet::only(*self) == *other
                 }
             }
-            impl #core::cmp::Eq for #name { }
-            impl #core::hash::Hash for #name {
-                fn hash<H: #core::hash::Hasher>(&self, state: &mut H) {
-                    state.write_u8(*self as u8)
-                }
-            }
-            impl #core::clone::Clone for #name {
-                fn clone(&self) -> Self {
-                    *self
-                }
-            }
-            impl #core::marker::Copy for #name { }
         }
     };
 
     #[cfg(feature = "serde")]
-    let expecting_str = format!("a list of {}", name);
+    let serde = quote!(#enumset::__internal::serde);
 
     #[cfg(feature = "serde")]
-    let serde_ops = if _serialize_as_list {
+    let serde_ops = if info.serialize_as_list {
+        let expecting_str = format!("a list of {}", name);
         quote! {
             fn serialize<S: #serde::Serializer>(
-                set: ::enumset::EnumSet<#name>, ser: S,
+                set: #enumset::EnumSet<#name>, ser: S,
             ) -> #core::result::Result<S::Ok, S::Error> {
                 use #serde::ser::SerializeSeq;
                 let mut seq = ser.serialize_seq(#core::prelude::v1::Some(set.len()))?;
@@ -130,10 +351,10 @@ fn enum_set_type_impl(
             }
             fn deserialize<'de, D: #serde::Deserializer<'de>>(
                 de: D,
-            ) -> #core::result::Result<::enumset::EnumSet<#name>, D::Error> {
+            ) -> #core::result::Result<#enumset::EnumSet<#name>, D::Error> {
                 struct Visitor;
                 impl <'de> #serde::de::Visitor<'de> for Visitor {
-                    type Value = ::enumset::EnumSet<#name>;
+                    type Value = #enumset::EnumSet<#name>;
                     fn expecting(
                         &self, formatter: &mut #core::fmt::Formatter,
                     ) -> #core::fmt::Result {
@@ -141,8 +362,10 @@ fn enum_set_type_impl(
                     }
                     fn visit_seq<A>(
                         mut self, mut seq: A,
-                    ) -> Result<Self::Value, A::Error> where A: #serde::de::SeqAccess<'de> {
-                        let mut accum = ::enumset::EnumSet::<#name>::new();
+                    ) -> #core::result::Result<Self::Value, A::Error> where
+                        A: #serde::de::SeqAccess<'de>
+                    {
+                        let mut accum = #enumset::EnumSet::<#name>::new();
                         while let #core::prelude::v1::Some(val) = seq.next_element::<#name>()? {
                             accum |= val;
                         }
@@ -153,147 +376,215 @@ fn enum_set_type_impl(
             }
         }
     } else {
+        let serialize_repr = info.serde_repr();
+        let check_unknown = if info.serialize_deny_unknown {
+            quote! {
+                if value & !#all_variants != 0 {
+                    use #serde::de::Error;
+                    return #core::prelude::v1::Err(
+                        D::Error::custom("enumset contains unknown bits")
+                    )
+                }
+            }
+        } else {
+            quote! {}
+        };
         quote! {
             fn serialize<S: #serde::Serializer>(
-                set: ::enumset::EnumSet<#name>, ser: S,
+                set: #enumset::EnumSet<#name>, ser: S,
             ) -> #core::result::Result<S::Ok, S::Error> {
-                use #serde::Serialize;
-                <#name as ::enumset::EnumSetType>::Repr::serialize(&set.__enumset_underlying, ser)
+                #serde::Serialize::serialize(&(set.__priv_repr as #serialize_repr), ser)
             }
             fn deserialize<'de, D: #serde::Deserializer<'de>>(
                 de: D,
-            ) -> #core::result::Result<::enumset::EnumSet<#name>, D::Error> {
-                use #serde::Deserialize;
-                #core::prelude::v1::Ok(::enumset::EnumSet {
-                    __enumset_underlying: <#name as ::enumset::EnumSetType>::Repr::deserialize(de)?,
+            ) -> #core::result::Result<#enumset::EnumSet<#name>, D::Error> {
+                let value = <#serialize_repr as #serde::Deserialize>::deserialize(de)?;
+                #check_unknown
+                #core::prelude::v1::Ok(#enumset::EnumSet {
+                    __priv_repr: (value & #all_variants) as #repr,
                 })
             }
         }
     };
 
     #[cfg(not(feature = "serde"))]
-    let serde_ops = quote! { };
+    let serde_ops = quote! {};
+
+    let is_uninhabited = info.variants.is_empty();
+    let is_zst = info.variants.len() == 1;
+    let into_impl = if is_uninhabited {
+        quote! {
+            fn enum_into_u32(self) -> u32 {
+                panic!(concat!(stringify!(#name), " is uninhabited."))
+            }
+            unsafe fn enum_from_u32(val: u32) -> Self {
+                panic!(concat!(stringify!(#name), " is uninhabited."))
+            }
+        }
+    } else if is_zst {
+        let variant = &info.variants[0].name;
+        quote! {
+            fn enum_into_u32(self) -> u32 {
+                self as u32
+            }
+            unsafe fn enum_from_u32(val: u32) -> Self {
+                #name::#variant
+            }
+        }
+    } else {
+        let variant_name: Vec<_> = info.variants.iter().map(|x| &x.name).collect();
+        let variant_value: Vec<_> = info.variants.iter().map(|x| x.variant_repr).collect();
+
+        let const_field: Vec<_> = ["IS_U8", "IS_U16", "IS_U32", "IS_U64", "IS_U128"]
+            .iter()
+            .map(|x| Ident::new(x, Span::call_site()))
+            .collect();
+        let int_type: Vec<_> = ["u8", "u16", "u32", "u64", "u128"]
+            .iter()
+            .map(|x| Ident::new(x, Span::call_site()))
+            .collect();
+
+        quote! {
+            fn enum_into_u32(self) -> u32 {
+                self as u32
+            }
+            unsafe fn enum_from_u32(val: u32) -> Self {
+                // We put these in const fields so the branches they guard aren't generated even
+                // on -O0
+                #(const #const_field: bool =
+                    #core::mem::size_of::<#name>() == #core::mem::size_of::<#int_type>();)*
+                match val {
+                    // Every valid variant value has an explicit branch. If they get optimized out,
+                    // great. If the representation has changed somehow, and they don't, oh well,
+                    // there's still no UB.
+                    #(#variant_value => #name::#variant_name,)*
+                    // Helps hint to the LLVM that this is a transmute. Note that this branch is
+                    // still unreachable.
+                    #(x if #const_field => {
+                        let x = x as #int_type;
+                        *(&x as *const _ as *const #name)
+                    })*
+                    // Default case. Sometimes causes LLVM to generate a table instead of a simple
+                    // transmute, but, oh well.
+                    _ => #core::hint::unreachable_unchecked(),
+                }
+            }
+        }
+    };
+
+    let eq_impl = if is_uninhabited {
+        quote!(panic!(concat!(stringify!(#name), " is uninhabited.")))
+    } else {
+        quote!((*self as u32) == (*other as u32))
+    };
+
+    // used in the enum_set! macro `const fn`s.
+    let self_as_repr_mask = if is_uninhabited {
+        quote! { 0 } // impossible anyway
+    } else {
+        quote! { 1 << self as #repr }
+    };
+
+    let super_impls = if info.no_super_impls {
+        quote! {}
+    } else {
+        quote! {
+            impl #core::cmp::PartialEq for #name {
+                fn eq(&self, other: &Self) -> bool {
+                    #eq_impl
+                }
+            }
+            impl #core::cmp::Eq for #name { }
+            #[allow(clippy::expl_impl_clone_on_copy)]
+            impl #core::clone::Clone for #name {
+                fn clone(&self) -> Self {
+                    *self
+                }
+            }
+            impl #core::marker::Copy for #name { }
+        }
+    };
+
+    let impl_with_repr = if info.explicit_mem_repr.is_some() {
+        quote! {
+            unsafe impl #enumset::EnumSetTypeWithRepr for #name {
+                type Repr = #repr;
+            }
+        }
+    } else {
+        quote! {}
+    };
 
     quote! {
-        unsafe impl ::enumset::EnumSetType for #name {
+        unsafe impl #enumset::__internal::EnumSetTypePrivate for #name {
             type Repr = #repr;
             const ALL_BITS: Self::Repr = #all_variants;
+            #into_impl
+            #serde_ops
+        }
 
-            fn enum_into_u8(self) -> u8 {
-                self as u8
-            }
-            unsafe fn enum_from_u8(val: u8) -> Self {
-                #core::mem::transmute(val)
+        unsafe impl #enumset::EnumSetType for #name { }
+
+        #impl_with_repr
+        #super_impls
+
+        impl #name {
+            /// Creates a new enumset with only this variant.
+            #[deprecated(note = "This method is an internal implementation detail generated by \
+                                 the `enumset` crate's procedural macro. It should not be used \
+                                 directly. Use `EnumSet::only` instead.")]
+            #[doc(hidden)]
+            pub const fn __impl_enumset_internal__const_only(self) -> #enumset::EnumSet<#name> {
+                #enumset::EnumSet { __priv_repr: #self_as_repr_mask }
             }
 
-            #serde_ops
+            /// Creates a new enumset with this variant added.
+            #[deprecated(note = "This method is an internal implementation detail generated by \
+                                 the `enumset` crate's procedural macro. It should not be used \
+                                 directly. Use the `|` operator instead.")]
+            #[doc(hidden)]
+            pub const fn __impl_enumset_internal__const_merge(
+                self, chain: #enumset::EnumSet<#name>,
+            ) -> #enumset::EnumSet<#name> {
+                #enumset::EnumSet { __priv_repr: chain.__priv_repr | #self_as_repr_mask }
+            }
         }
 
         #ops
-        #derives
     }
 }
 
-#[proc_macro_derive(EnumSetType,
-                    attributes(enumset_no_ops, enumset_no_derives, enumset_serialize_as_list))]
+#[proc_macro_derive(EnumSetType, attributes(enumset))]
 pub fn derive_enum_set_type(input: TokenStream) -> TokenStream {
     let input: DeriveInput = parse_macro_input!(input);
-    if let Data::Enum(data) = input.data {
-        if !input.generics.params.is_empty() {
-            error(input.generics.span(),
-                  "`#[derive(EnumSetType)]` cannot be used on enums with type parameters.")
-        } else {
-            let mut all_variants = 0u128;
-            let mut max_variant = 0;
-            let mut current_variant = 0;
-            let mut has_manual_discriminant = false;
-
-            for variant in &data.variants {
-                if let Fields::Unit = variant.fields {
-                    if let Some((_, expr)) = &variant.discriminant {
-                        if let Expr::Lit(ExprLit { lit: Lit::Int(i), .. }) = expr {
-                            current_variant = i.value();
-                            has_manual_discriminant = true;
-                        } else {
-                            return error(variant.span(), "Unrecognized discriminant for variant.")
-                        }
-                    }
-
-                    if current_variant >= 128 {
-                        let message = if has_manual_discriminant {
-                            "`#[derive(EnumSetType)]` only supports enum discriminants up to 127."
-                        } else {
-                            "`#[derive(EnumSetType)]` only supports enums up to 128 variants."
-                        };
-                        return error(variant.span(), message)
-                    }
-
-                    if all_variants & (1 << current_variant) != 0 {
-                        return error(variant.span(),
-                                     &format!("Duplicate enum discriminant: {}", current_variant))
-                    }
-                    all_variants |= 1 << current_variant;
-                    if current_variant > max_variant {
-                        max_variant = current_variant
-                    }
-
-                    current_variant += 1;
-                } else {
-                    return error(variant.span(),
-                                 "`#[derive(EnumSetType)]` can only be used on C-like enums.")
-                }
-            }
-
-            let repr = Ident::new(if max_variant <= 7 {
-                "u8"
-            } else if max_variant <= 15 {
-                "u16"
-            } else if max_variant <= 31 {
-                "u32"
-            } else if max_variant <= 63 {
-                "u64"
-            } else if max_variant <= 127 {
-                "u128"
-            } else {
-                panic!("max_variant > 127?")
-            }, Span::call_site());
-
-            let mut no_ops = false;
-            let mut no_derives = false;
-            let mut serialize_as_list = false;
-
-            for attr in &input.attrs {
-                let span = attr.span();
-                let Attribute { tts, path: Path { segments, ..}, .. } = attr;
-
-                if segments.len() == 1 {
-                    let name = segments[0].ident.to_string();
-                    let mut is_attr = false;
-                    match name.as_str() {
-                        "enumset_no_ops" => {
-                            no_ops = true;
-                            is_attr = true;
-                        }
-                        "enumset_no_derives" => {
-                            no_derives = true;
-                            is_attr = true;
-                        }
-                        "enumset_serialize_as_list" => {
-                            serialize_as_list = true;
-                            is_attr = true;
-                        }
-                        _ => { }
-                    }
-                    if is_attr && !tts.is_empty() {
-                        return error(span, &format!("`#[{}]` takes no arguments.", name))
-                    }
-                }
+    let attrs: EnumsetAttrs = match EnumsetAttrs::from_derive_input(&input) {
+        Ok(attrs) => attrs,
+        Err(e) => return e.write_errors().into(),
+    };
+    match derive_enum_set_type_0(input, attrs) {
+        Ok(v) => v,
+        Err(e) => e.to_compile_error().into(),
+    }
+}
+fn derive_enum_set_type_0(input: DeriveInput, attrs: EnumsetAttrs) -> Result<TokenStream> {
+    if !input.generics.params.is_empty() {
+        error(
+            input.generics.span(),
+            "`#[derive(EnumSetType)]` cannot be used on enums with type parameters.",
+        )
+    } else if let Data::Enum(data) = &input.data {
+        let mut info = EnumSetInfo::new(&input, attrs);
+        for attr in &input.attrs {
+            if attr.path.is_ident(&Ident::new("repr", Span::call_site())) {
+                let meta: Ident = attr.parse_args()?;
+                info.push_explicit_repr(attr.span(), meta.to_string().as_str())?;
             }
-
-            enum_set_type_impl(
-                &input.ident, all_variants, repr, no_ops, no_derives, serialize_as_list,
-            ).into()
         }
+        for variant in &data.variants {
+            info.push_variant(variant)?;
+        }
+        info.validate()?;
+        Ok(enum_set_type_impl(info).into())
     } else {
         error(input.span(), "`#[derive(EnumSetType)]` may only be used on enums")
     }