/// * `#[enumset(crate_name = "enumset2")]` may be used to change the name of the `enumset` crate
/// used in the generated code. When the `std` feature is enabled, enumset parses `Cargo.toml`
/// to determine the name of the crate, and this flag is unnecessary.
+/// * `#[enumset(repr = "u8")]` may be used to specify the in-memory representation of `EnumSet`s
+/// of this enum type. The effects of this are described in [the `EnumSet` documentation under
+/// “FFI, Safety and `repr`”][EnumSet#ffi-safety-and-repr]. Allowed types are `u8`, `u16`, `u32`,
+/// `u64` and `u128`. If this is not used, then the derive macro will choose a type to best fit
+/// the enum, but there are no guarantees about which type will be chosen.
///
/// When the `serde` feature is used, the following features may also be specified. These options
/// may be used (with no effect) when building without the feature enabled:
/// [`#[derive(EnumSetType)]`](./derive.EnumSetType.html).
pub unsafe trait EnumSetType: Copy + Eq + EnumSetTypePrivate { }
+/// An [`EnumSetType`] for which [`EnumSet`]s have a guaranteed in-memory representation.
+///
+/// An implementation of this trait is generated by using
+/// [`#[derive(EnumSetType)]`](./derive.EnumSetType.html) with the annotation
+/// `#[enumset(repr = "…")]`, where `…` is `u8`, `u16`, `u32`, `u64` or `u128`.
+///
+/// For any type `T` that implements this trait, the in-memory representation of `EnumSet<T>`
+/// is guaranteed to be `Repr`. This guarantee is useful for FFI. See [the `EnumSet` documentation
+/// under “FFI, Safety and `repr`”][EnumSet#ffi-safety-and-repr] for an example.
+pub unsafe trait EnumSetTypeWithRepr: EnumSetType + EnumSetTypePrivate<Repr = <Self as EnumSetTypeWithRepr>::Repr> {
+ /// The guaranteed representation.
+ type Repr: EnumSetTypeRepr;
+}
+
/// An efficient set type for enums.
///
/// It is implemented using a bitset stored using the smallest integer that can fit all bits
/// 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`]. Note that this is a breaking change.
+///
+/// # FFI, Safety and `repr`
+///
+/// If an enum type `T` is annotated with [`#[enumset(repr = "R")]`][derive@EnumSetType#options], then
+/// several things happen:
+///
+/// * `T` will implement <code>[EnumSetTypeWithRepr]<Repr = R></code> in addition to
+/// [`EnumSetType`].
+/// * The `EnumSet` methods with `repr` in their name, such as [`as_repr`][EnumSet::as_repr] and
+/// [`from_repr`][EnumSet::from_repr], will be available for `EnumSet<T>`.
+/// * The in-memory representation of `EnumSet<T>` is guaranteed to be `R`.
+///
+/// That last guarantee makes it sound to send `EnumSet<T>` across an FFI boundary. For example:
+///
+/// ```
+/// # use enumset::*;
+/// #
+/// # mod ffi_impl {
+/// # // This example “foreign” function is actually written in Rust, but for the sake
+/// # // of example, we'll pretend it's written in C.
+/// # #[no_mangle]
+/// # extern "C" fn some_foreign_function(set: u32) -> u32 {
+/// # set & 0b100
+/// # }
+/// # }
+/// #
+/// extern "C" {
+/// // This function is written in C like:
+/// // uint32_t some_foreign_function(uint32_t set) { … }
+/// fn some_foreign_function(set: EnumSet<MyEnum>) -> EnumSet<MyEnum>;
+/// }
+///
+/// #[derive(Debug, EnumSetType)]
+/// #[enumset(repr = "u32")]
+/// enum MyEnum { A, B, C }
+///
+/// let set: EnumSet<MyEnum> = enum_set!(MyEnum::A | MyEnum::C);
+///
+/// let new_set: EnumSet<MyEnum> = unsafe { some_foreign_function(set) };
+/// assert_eq!(new_set, enum_set!(MyEnum::C));
+/// ```
+///
+/// When an `EnumSet<T>` is received via FFI, all bits that don't correspond to an enum variant
+/// of `T` must be set to 0. Behavior is **undefined** if any of these bits are set to 1.
#[derive(Copy, Clone, PartialEq, Eq)]
#[repr(transparent)]
pub struct EnumSet<T: EnumSetType> {
pub fn iter(&self) -> EnumSetIter<T> {
EnumSetIter::new(*self)
}
+
+ /// Returns a `T::Repr` representing the elements of this set.
+ ///
+ /// Unlike the other `as_*` methods, this method is zero-cost and guaranteed not to fail,
+ /// panic or truncate any bits.
+ ///
+ /// In order to use this method, the definition of `T` must have the `#[enumset(repr = "…")]`
+ /// annotation.
+ #[inline(always)]
+ pub fn as_repr(&self) -> <T as EnumSetTypeWithRepr>::Repr
+ where
+ T: EnumSetTypeWithRepr,
+ {
+ self.__priv_repr
+ }
+
+ /// Constructs a bitset from a `T::Repr` without checking for invalid bits.
+ ///
+ /// Unlike the other `from_*` methods, this method is zero-cost and guaranteed not to fail,
+ /// panic or truncate any bits, provided the conditions under “Safety” are upheld.
+ ///
+ /// In order to use this method, the definition of `T` must have the `#[enumset(repr = "…")]`
+ /// annotation.
+ ///
+ /// # Safety
+ ///
+ /// All bits in the provided parameter `bits` that don't correspond to an enum variant of
+ /// `T` must be set to 0. Behavior is **undefined** if any of these bits are set to 1.
+ #[inline(always)]
+ pub unsafe fn from_repr_unchecked(bits: <T as EnumSetTypeWithRepr>::Repr) -> Self
+ where
+ T: EnumSetTypeWithRepr,
+ {
+ Self { __priv_repr: bits }
+ }
+
+ /// Constructs a bitset from a `T::Repr`.
+ ///
+ /// If a bit that doesn't correspond to an enum variant is set, this
+ /// method will panic.
+ ///
+ /// In order to use this method, the definition of `T` must have the `#[enumset(repr = "…")]`
+ /// annotation.
+ #[inline(always)]
+ pub fn from_repr(bits: <T as EnumSetTypeWithRepr>::Repr) -> Self
+ where
+ T: EnumSetTypeWithRepr,
+ {
+ Self::try_from_repr(bits).expect("Bitset contains invalid variants.")
+ }
+
+ /// Attempts to constructs a bitset from a `T::Repr`.
+ ///
+ /// If a bit that doesn't correspond to an enum variant is set, this
+ /// method will return `None`.
+ ///
+ /// In order to use this method, the definition of `T` must have the `#[enumset(repr = "…")]`
+ /// annotation.
+ #[inline(always)]
+ pub fn try_from_repr(bits: <T as EnumSetTypeWithRepr>::Repr) -> Option<Self>
+ where
+ T: EnumSetTypeWithRepr,
+ {
+ let mask = Self::all().__priv_repr;
+ if bits.and_not(mask).is_empty() {
+ Some(EnumSet { __priv_repr: bits })
+ } else {
+ None
+ }
+ }
+
+ /// Constructs a bitset from a `T::Repr`, ignoring invalid variants.
+ ///
+ /// In order to use this method, the definition of `T` must have the `#[enumset(repr = "…")]`
+ /// annotation.
+ #[inline(always)]
+ pub fn from_repr_truncated(bits: <T as EnumSetTypeWithRepr>::Repr) -> Self
+ where
+ T: EnumSetTypeWithRepr,
+ {
+ let mask = Self::all().as_repr();
+ let bits = bits & mask;
+ EnumSet { __priv_repr: bits }
+ }
}
/// Helper macro for generating conversion functions.
}
-fn main() { }
\ No newline at end of file
+#[derive(EnumSetType)]
+#[enumset(repr = "u16")]
+enum BadMemRepr {
+ Variant = 16,
+}
+
+#[derive(EnumSetType)]
+enum OkayEnumButCantUseFromRepr {
+ Variant,
+}
+
+fn main() {
+ EnumSet::<OkayEnumButCantUseFromRepr>::from_repr(1);
+}
error: `#[derive(EnumSetType)]` currently only supports discriminants up to 127.
- --> $DIR/variants.rs:5:5
+ --> tests/compile-fail/variants.rs:5:5
|
5 | Variant = 128,
| ^^^^^^^^^^^^^
error: Enum set discriminants must be `u32`s. (larger discrimiants are still unsupported with reprs that allow them.)
- --> $DIR/variants.rs:11:15
+ --> tests/compile-fail/variants.rs:11:15
|
11 | Variant = 0x100000000,
| ^^^^^^^^^^^
error: `#[derive(EnumSetType)]` currently only supports enums up to 128 variants.
- --> $DIR/variants.rs:22:95
+ --> tests/compile-fail/variants.rs:22:95
|
22 | _113, _114, _115, _116, _117, _118, _119, _120, _121, _122, _123, _124, _125, _126, _127, _128,
| ^^^^
error: Enum set discriminants must be `u32`s.
- --> $DIR/variants.rs:27:5
+ --> tests/compile-fail/variants.rs:27:5
|
27 | Variant = -1,
| ^^^^^^^^^^^^
error: `#[derive(EnumSetType)]` can only be used on fieldless enums.
- --> $DIR/variants.rs:38:5
+ --> tests/compile-fail/variants.rs:38:5
|
38 | Variant(u32),
| ^^^^^^^^^^^^
error: serialize_repr cannot be smaller than bitset.
- --> $DIR/variants.rs:41:10
+ --> tests/compile-fail/variants.rs:41:10
|
41 | #[derive(EnumSetType)]
| ^^^^^^^^^^^
= note: this error originates in the derive macro `EnumSetType` (in Nightly builds, run with -Z macro-backtrace for more info)
error: `#[derive(EnumSetType)]` may only be used on enums
- --> $DIR/variants.rs:48:1
+ --> tests/compile-fail/variants.rs:48:1
|
48 | / struct BadItemType {
49 | |
50 | | }
| |_^
+
+error: repr cannot be smaller than bitset.
+ --> tests/compile-fail/variants.rs:52:10
+ |
+52 | #[derive(EnumSetType)]
+ | ^^^^^^^^^^^
+ |
+ = note: this error originates in the derive macro `EnumSetType` (in Nightly builds, run with -Z macro-backtrace for more info)
+
+error[E0277]: the trait bound `OkayEnumButCantUseFromRepr: EnumSetTypeWithRepr` is not satisfied
+ --> tests/compile-fail/variants.rs:64:5
+ |
+64 | EnumSet::<OkayEnumButCantUseFromRepr>::from_repr(1);
+ | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ the trait `EnumSetTypeWithRepr` is not implemented for `OkayEnumButCantUseFromRepr`
+ |
+note: required by a bound in `enumset::EnumSet::<T>::from_repr`
+ --> src/lib.rs
+ |
+ | T: EnumSetTypeWithRepr,
+ | ^^^^^^^^^^^^^^^^^^^ required by this bound in `enumset::EnumSet::<T>::from_repr`
+
+error[E0277]: the trait bound `OkayEnumButCantUseFromRepr: EnumSetTypeWithRepr` is not satisfied
+ --> tests/compile-fail/variants.rs:64:54
+ |
+64 | EnumSet::<OkayEnumButCantUseFromRepr>::from_repr(1);
+ | ^ the trait `EnumSetTypeWithRepr` is not implemented for `OkayEnumButCantUseFromRepr`
--- /dev/null
+use enumset::*;
+
+#[derive(EnumSetType, Debug)]
+#[enumset(repr = "u16")]
+enum ReprEnum {
+ A, B, C, D, E, F, G, H,
+}
+
+#[test]
+fn test() {
+ let mut set = EnumSet::<ReprEnum>::new();
+ set.insert(ReprEnum::B);
+ set.insert(ReprEnum::F);
+
+ let repr: u16 = set.as_repr();
+ assert_eq!(
+ (1 << 1) | (1 << 5),
+ repr,
+ );
+
+ let set2 = unsafe { EnumSet::<ReprEnum>::from_repr_unchecked(repr) };
+ assert_eq!(set, set2);
+}
use darling::*;
use proc_macro::TokenStream;
use proc_macro2::{TokenStream as SynTokenStream, Literal, Span};
-use std::collections::HashSet;
+use std::{collections::HashSet, fmt::Display};
use syn::{*, Result, Error};
use syn::spanned::Spanned;
use quote::*;
/// Helper function for emitting compile errors.
-fn error<T>(span: Span, message: &str) -> Result<T> {
+fn error<T>(span: Span, message: impl Display) -> Result<T> {
Err(Error::new(span, message))
}
struct EnumsetAttrs {
no_ops: bool,
no_super_impls: bool,
+ #[darling(default)]
+ repr: Option<String>,
serialize_as_list: bool,
serialize_deny_unknown: bool,
#[darling(default)]
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.
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,
error(Span::call_site(), "serialize_repr cannot be smaller than bitset.")?;
}
}
+ // 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 {
+ let is_overflowed = match explicit_mem_repr.to_string().as_str() {
+ "u8" => self.max_discrim >= 8,
+ "u16" => self.max_discrim >= 16,
+ "u32" => self.max_discrim >= 32,
+ "u64" => self.max_discrim >= 64,
+ "u128" => self.max_discrim >= 128,
+ _ => error(
+ Span::call_site(),
+ "Only `u8`, `u16`, `u32`, `u64` and `u128` are supported for repr."
+ )?,
+ };
+ if is_overflowed {
+ error(Span::call_site(), "repr cannot be smaller than bitset.")?;
+ }
+ }
Ok(())
}
/// Computes the underlying type used to store the enumset.
fn enumset_repr(&self) -> SynTokenStream {
- if self.max_discrim <= 7 {
+ 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 }
}
};
+ 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::__internal::EnumSetTypePrivate for #name {
type Repr = #repr;
unsafe impl #enumset::EnumSetType for #name { }
+ #impl_with_repr
#super_impls
impl #name {