//! }
//! ```
//!
+//! For more information on more advanced use cases, see the documentation for [`EnumSetType`].
+//!
//! # Working with EnumSets
//!
//! EnumSets can be constructed via [`EnumSet::new()`] like a normal set. In addition,
//! assert_eq!(CONST_SET, Enum::A | Enum::B);
//! ```
//!
-//! Mutable operations on the [`EnumSet`] otherwise work basically as expected:
+//! Mutable operations on the [`EnumSet`] otherwise similarly to Rust's builtin sets:
//!
//! ```rust
//! # use enumset::*;
/// The actual members of EnumSetType. Put here to avoid polluting global namespaces.
pub unsafe trait EnumSetTypePrivate {
+ /// The underlying type used to store the bitset.
type Repr: EnumSetTypeRepr;
+ /// A mask of bits that are valid in the bitset.
const ALL_BITS: Self::Repr;
+
+ /// Converts an enum of this type into its bit position.
fn enum_into_u32(self) -> u32;
+ /// Converts a bit position into an enum value.
unsafe fn enum_from_u32(val: u32) -> Self;
+ /// Serializes the `EnumSet`.
+ ///
+ /// This and `deserialize` are part of the `EnumSetType` trait so the procedural derive
+ /// can control how `EnumSet` is serialized.
#[cfg(feature = "serde")]
fn serialize<S: serde::Serializer>(set: EnumSet<Self>, ser: S) -> Result<S::Ok, S::Error>
where Self: EnumSetType;
+ /// Deserializes the `EnumSet`.
#[cfg(feature = "serde")]
fn deserialize<'de, D: serde::Deserializer<'de>>(de: D) -> Result<EnumSet<Self>, D::Error>
where Self: EnumSetType;
mod private {
use super::*;
+ /// A trait marking valid underlying bitset storage types and providing the
+ /// operations `EnumSet` and related types use.
pub trait EnumSetTypeRepr :
PrimInt + WrappingSub + CheckedShl + Debug + Hash + FromPrimitive + ToPrimitive +
AsPrimitive<u8> + AsPrimitive<u16> + AsPrimitive<u32> + AsPrimitive<u64> +
/// The trait used to define enum types that may be used with [`EnumSet`].
///
/// This trait should be implemented using `#[derive(EnumSetType)]`. Its internal structure is
-/// not currently stable, and may change at any time.
+/// not stable, and may change at any time.
///
/// # Custom Derive
///
+/// Any C-like enum is supported, as long as there are no more than 128 variants in the enum,
+/// and no variant discriminator is larger than 127.
+///
/// The custom derive for [`EnumSetType`] automatically creates implementations of [`PartialEq`],
/// [`Sub`], [`BitAnd`], [`BitOr`], [`BitXor`], and [`Not`] allowing the enum to be used as
/// if it were an [`EnumSet`] in expressions. This can be disabled by adding an `#[enumset(no_ops)]`
/// The custom derive for `EnumSetType` automatically implements [`Copy`], [`Clone`], [`Eq`], and
/// [`PartialEq`] on the enum. These are required for the [`EnumSet`] to function.
///
-/// Any C-like enum is supported, as long as there are no more than 128 variants in the enum,
-/// and no variant discriminator is larger than 127.
+/// In addition, if you have renamed the `enumset` crate in your crate, you can use the
+/// `#[enumset(crate_name = "enumset2")]` attribute to tell the custom derive to use that name
+/// instead.
+///
+/// Attributes controlling the serialization of an `EnumSet` are documented in
+/// [its documentation](./struct.EnumSet.html#serialization).
///
/// # Examples
///
/// An efficient set type for enums.
///
/// It is implemented using a bitset stored using the smallest integer that can fit all bits
-/// in the underlying enum.
+/// in the underlying enum. In general, an enum variant with a numeric value of `n` is stored in
+/// the nth least significant bit (corresponding with a mask of, e.g. `1 << enum as u32`).
///
/// # Serialization
///
-/// By default, `EnumSet`s are serialized as an unsigned integer of the same width as used to store
-/// it in memory.
+/// When the `serde` feature is enabled, `EnumSet`s can be serialized and deserialized using
+/// the `serde` crate. The exact serialization format can be controlled with additional attributes
+/// on the enum type. These attributes are valid regardless of whether the `serde` feature
+/// is enabled.
///
-/// Unknown bits are ignored, and are simply dropped. To override this behavior, you can add a
-/// `#[enumset(serialize_deny_unknown)]` annotation to your enum.
+/// By default, `EnumSet`s serialize by directly writing out the underlying bitset as an integer
+/// of the smallest type that can fit in the underlying enum. You can add a
+/// `#[enumset(serialize_repr = "u8")]` attribute to your enum to control the integer type used
+/// for serialization. This can be important for avoiding unintentional breaking changes when
+/// `EnumSet`s are serialized with formats like `bincode`.
///
-/// You can add a `#[enumset(serialize_repr = "u8")]` annotation to your enum to manually set
-/// the number width the `EnumSet` is serialized as. Only unsigned integer types may be used. This
-/// can be used to avoid breaking changes in certain serialization formats (such as `bincode`).
+/// By default, unknown bits are ignored and silently removed from the bitset. To override this
+/// behavior, you can add a `#[enumset(serialize_deny_unknown)]` attribute. This will cause
+/// deserialization to fail if an invalid bit is set.
///
-/// In addition, the `#[enumset(serialize_as_list)]` annotation causes the `EnumSet` to be
+/// In addition, the `#[enumset(serialize_as_list)]` attribute causes the `EnumSet` to be
/// instead serialized as a list of enum variants. This requires your enum type implement
-/// [`Serialize`] and [`Deserialize`].
+/// [`Serialize`] and [`Deserialize`]. Note that this is a breaking change
#[derive(Copy, Clone, PartialEq, Eq)]
pub struct EnumSet<T: EnumSetType> {
#[doc(hidden)]
}
}
+/// Helper macro for generating conversion functions.
macro_rules! conversion_impls {
(
$(for_num!(
impl <T : EnumSetType> EnumSet<T> {$(
#[doc = "Returns a `"]
#[doc = $underlying_str]
- #[doc = "` representing the elements of this set. \n\nIf the underlying bitset will \
+ #[doc = "` representing the elements of this set.\n\nIf the underlying bitset will \
not fit in a `"]
#[doc = $underlying_str]
#[doc = "`, this method will panic."]
#[doc = "Tries to return a `"]
#[doc = $underlying_str]
- #[doc = "` representing the elements of this set. \n\nIf the underlying bitset will \
+ #[doc = "` representing the elements of this set.\n\nIf the underlying bitset will \
not fit in a `"]
#[doc = $underlying_str]
#[doc = "`, this method will instead return `None`."]
#[doc = "Returns a truncated `"]
#[doc = $underlying_str]
- #[doc = "` representing the elements of this set. \n\nIf the underlying bitset will \
+ #[doc = "` representing the elements of this set.\n\nIf the underlying bitset will \
not fit in a `"]
#[doc = $underlying_str]
#[doc = "`, this method will truncate any bits that don't fit."]
#[doc = "Constructs a bitset from a `"]
#[doc = $underlying_str]
- #[doc = "`. \n\nIf a bit that doesn't correspond to an enum variant is set, this \
+ #[doc = "`.\n\nIf a bit that doesn't correspond to an enum variant is set, this \
method will panic."]
pub fn $from(bits: $underlying) -> Self {
Self::$try_from(bits).expect("Bitset contains invalid variants.")
#[doc = "Attempts to constructs a bitset from a `"]
#[doc = $underlying_str]
- #[doc = "`. \n\nIf a bit that doesn't correspond to an enum variant is set, this \
+ #[doc = "`.\n\nIf a bit that doesn't correspond to an enum variant is set, this \
method will return `None`."]
pub fn $try_from(bits: $underlying) -> Option<Self> {
let bits = <T::Repr as FromPrimitive>::$from_fn(bits);
)*}
}
}
-
conversion_impls! {
for_num!(u8, "u8", from_u8, to_u8,
from_u8 try_from_u8 from_u8_truncated as_u8 try_as_u8 as_u8_truncated);
/// An variant in the enum set type.
struct EnumSetValue {
+ /// The name of the variant.
name: Ident,
+ /// The discriminant of the variant.
variant_repr: u32,
}
/// 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 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>,
- used_discriminators: HashSet<u32>,
+ /// 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,
+ /// Serialize the enum as a list.
serialize_as_list: bool,
+ /// Disallow unknown bits while deserializing the enum.
serialize_deny_unknown: bool,
}
impl EnumSetInfo {
max_discrim: 0,
cur_discrim: 0,
used_variant_names: HashSet::new(),
- used_discriminators: HashSet::new(),
+ used_discriminants: HashSet::new(),
no_ops: attrs.no_ops,
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" => {
_ => 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.{}",
}
}
+ // Validate the discriminant.
let discriminant = self.cur_discrim;
if discriminant >= 128 {
let message = if self.variants.len() <= 127 {
};
error(variant.span(), message)?;
}
-
- if self.used_discriminators.contains(&discriminant) {
+ 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;
variant_repr: discriminant,
});
self.used_variant_names.insert(variant.ident.to_string());
- self.used_discriminators.insert(discriminant);
+ 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<()> {
+ // Check if all bits of the bitset can fit in the serialization representation.
if let Some(explicit_serde_repr) = &self.explicit_serde_repr {
let is_overflowed = match explicit_serde_repr.to_string().as_str() {
"u8" => self.max_discrim >= 8,
Ok(())
}
+ /// Computes the underlying type used to store the enumset.
fn enumset_repr(&self) -> SynTokenStream {
if self.max_discrim <= 7 {
quote! { u8 }
panic!("max_variant > 127?")
}
}
+ /// Computes the underlying type used to serialize the enumset.
#[cfg(feature = "serde")]
fn serde_repr(&self) -> SynTokenStream {
if let Some(serde_repr) = &self.explicit_serde_repr {
}
}
+ /// Returns a bitmask of all variants in the set.
fn all_variants(&self) -> u128 {
let mut accum = 0u128;
for variant in &self.variants {
}
}
+/// Generates the actual `EnumSetType` impl.
fn enum_set_type_impl(info: EnumSetInfo) -> SynTokenStream {
let name = &info.name;
let enumset = match &info.crate_name {
self as u32
}
unsafe fn enum_from_u32(val: u32) -> Self {
- // We put these in const fields so they aren't generated even on -O0
+ // 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 {
}
}
-fn derive_enum_set_type_impl(input: DeriveInput, attrs: EnumsetAttrs) -> Result<TokenStream> {
+/// A wrapper that parses the input enum.
+#[proc_macro_derive(EnumSetType, attributes(enumset))]
+pub fn derive_enum_set_type(input: TokenStream) -> TokenStream {
+ let input: DeriveInput = parse_macro_input!(input);
+ 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(),
} else {
error(input.span(), "`#[derive(EnumSetType)]` may only be used on enums")
}
-}
-
-#[proc_macro_derive(EnumSetType, attributes(enumset))]
-pub fn derive_enum_set_type(input: TokenStream) -> TokenStream {
- let input: DeriveInput = parse_macro_input!(input);
- let attrs: EnumsetAttrs = match EnumsetAttrs::from_derive_input(&input) {
- Ok(attrs) => attrs,
- Err(e) => return e.write_errors().into(),
- };
- match derive_enum_set_type_impl(input, attrs) {
- Ok(v) => v,
- Err(e) => e.to_compile_error().into(),
- }
}
\ No newline at end of file