From: Lymia Aluysia Date: Sat, 4 Apr 2020 22:19:05 +0000 (-0700) Subject: Documentation improvements. X-Git-Url: https://git.lizzy.rs/?a=commitdiff_plain;h=9bd8edccf3e21aaa16b3b7510453056b8fa4e0d8;p=enumset.git Documentation improvements. --- diff --git a/enumset/src/lib.rs b/enumset/src/lib.rs index 36431e0..9c5bb46 100644 --- a/enumset/src/lib.rs +++ b/enumset/src/lib.rs @@ -18,6 +18,8 @@ //! } //! ``` //! +//! 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, @@ -60,7 +62,7 @@ //! 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::*; @@ -103,14 +105,24 @@ pub mod __internal { /// 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(set: EnumSet, ser: S) -> Result where Self: EnumSetType; + /// Deserializes the `EnumSet`. #[cfg(feature = "serde")] fn deserialize<'de, D: serde::Deserializer<'de>>(de: D) -> Result, D::Error> where Self: EnumSetType; @@ -123,6 +135,8 @@ use crate::__internal::EnumSetTypePrivate; 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 + AsPrimitive + AsPrimitive + AsPrimitive + @@ -161,10 +175,13 @@ use crate::private::EnumSetTypeRepr; /// 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)]` @@ -173,8 +190,12 @@ use crate::private::EnumSetTypeRepr; /// 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 /// @@ -213,23 +234,29 @@ pub unsafe trait EnumSetType: Copy + Eq + EnumSetTypePrivate { } /// 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 { #[doc(hidden)] @@ -386,6 +413,7 @@ impl EnumSet { } } +/// Helper macro for generating conversion functions. macro_rules! conversion_impls { ( $(for_num!( @@ -397,7 +425,7 @@ macro_rules! conversion_impls { impl EnumSet {$( #[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."] @@ -407,7 +435,7 @@ macro_rules! conversion_impls { #[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`."] @@ -417,7 +445,7 @@ macro_rules! conversion_impls { #[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."] @@ -427,7 +455,7 @@ macro_rules! conversion_impls { #[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.") @@ -435,7 +463,7 @@ macro_rules! conversion_impls { #[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 { let bits = ::$from_fn(bits); @@ -458,7 +486,6 @@ macro_rules! conversion_impls { )*} } } - 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); diff --git a/enumset_derive/src/lib.rs b/enumset_derive/src/lib.rs index ab98343..83ac138 100644 --- a/enumset_derive/src/lib.rs +++ b/enumset_derive/src/lib.rs @@ -31,27 +31,42 @@ struct EnumsetAttrs { /// 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, + /// The numeric type to serialize the enum as. explicit_serde_repr: Option, + /// 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, + /// 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, - used_discriminators: HashSet, + /// A list of variant discriminants that are already in use. + used_discriminants: HashSet, + /// 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 { @@ -66,14 +81,17 @@ 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" => { @@ -92,10 +110,12 @@ impl EnumSetInfo { _ => 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.{}", @@ -124,6 +144,7 @@ impl EnumSetInfo { } } + // Validate the discriminant. let discriminant = self.cur_discrim; if discriminant >= 128 { let message = if self.variants.len() <= 127 { @@ -133,11 +154,11 @@ impl EnumSetInfo { }; 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; @@ -147,14 +168,16 @@ impl EnumSetInfo { 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, @@ -174,6 +197,7 @@ impl EnumSetInfo { Ok(()) } + /// Computes the underlying type used to store the enumset. fn enumset_repr(&self) -> SynTokenStream { if self.max_discrim <= 7 { quote! { u8 } @@ -189,6 +213,7 @@ impl EnumSetInfo { 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 { @@ -198,6 +223,7 @@ impl EnumSetInfo { } } + /// Returns a bitmask of all variants in the set. fn all_variants(&self) -> u128 { let mut accum = 0u128; for variant in &self.variants { @@ -208,6 +234,7 @@ impl EnumSetInfo { } } +/// Generates the actual `EnumSetType` impl. fn enum_set_type_impl(info: EnumSetInfo) -> SynTokenStream { let name = &info.name; let enumset = match &info.crate_name { @@ -376,7 +403,8 @@ fn enum_set_type_impl(info: EnumSetInfo) -> SynTokenStream { 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 { @@ -431,7 +459,20 @@ fn enum_set_type_impl(info: EnumSetInfo) -> SynTokenStream { } } -fn derive_enum_set_type_impl(input: DeriveInput, attrs: EnumsetAttrs) -> Result { +/// 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 { if !input.generics.params.is_empty() { error( input.generics.span(), @@ -453,17 +494,4 @@ fn derive_enum_set_type_impl(input: DeriveInput, attrs: EnumsetAttrs) -> Result< } 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