]> git.lizzy.rs Git - enumset.git/commitdiff
Add `repr` option to derive.
authorahcodedthat <83854662+ahcodedthat@users.noreply.github.com>
Tue, 12 Apr 2022 06:55:20 +0000 (23:55 -0700)
committerahcodedthat <83854662+ahcodedthat@users.noreply.github.com>
Tue, 12 Apr 2022 06:55:20 +0000 (23:55 -0700)
enumset/src/lib.rs
enumset/tests/compile-fail/variants.rs
enumset/tests/compile-fail/variants.stderr
enumset/tests/repr.rs [new file with mode: 0644]
enumset_derive/src/lib.rs

index e36c22b0018c5279ad66075a1c2bd46337f5eacc..384f458166578149549eb4a20e1a7eedf9af0276 100644 (file)
@@ -166,6 +166,11 @@ use crate::repr::EnumSetTypeRepr;
 /// * `#[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:
@@ -221,6 +226,20 @@ pub use enumset_derive::EnumSetType;
 /// [`#[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
@@ -260,6 +279,50 @@ pub unsafe trait EnumSetType: Copy + Eq + EnumSetTypePrivate { }
 /// 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]&lt;Repr = R&gt;</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> {
@@ -428,6 +491,90 @@ impl <T: EnumSetType> EnumSet<T> {
     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.
index bfa9c11993e518206a90b4df56a59d9d34a01f9f..2b76a40897635a816bcff3d035d99372ac0ec7f3 100644 (file)
@@ -49,4 +49,17 @@ struct BadItemType {
 
 }
 
-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);
+}
index 259842a78bef6bd117baecbab38e096b9472a29b..0425ae533a8c6bf2d9deb2fb09a86ed190f00a4f 100644 (file)
@@ -1,35 +1,35 @@
 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)]
    |          ^^^^^^^^^^^
@@ -37,9 +37,35 @@ error: serialize_repr cannot be smaller than bitset.
    = 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`
diff --git a/enumset/tests/repr.rs b/enumset/tests/repr.rs
new file mode 100644 (file)
index 0000000..c7d9d7b
--- /dev/null
@@ -0,0 +1,23 @@
+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);
+}
index 76e98f7f849f680bb7c5a4308224722f6c5e8404..7e117f0cc9aa6dc625ae68c3e8d6f94f0b439103 100644 (file)
@@ -5,13 +5,13 @@ extern crate proc_macro;
 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))
 }
 
@@ -21,6 +21,8 @@ fn error<T>(span: Span, message: &str) -> Result<T> {
 struct EnumsetAttrs {
     no_ops: bool,
     no_super_impls: bool,
+    #[darling(default)]
+    repr: Option<String>,
     serialize_as_list: bool,
     serialize_deny_unknown: bool,
     #[darling(default)]
@@ -44,6 +46,8 @@ struct EnumSetInfo {
     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.
@@ -76,6 +80,7 @@ impl 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,
@@ -197,12 +202,31 @@ impl EnumSetInfo {
                 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 }
@@ -482,6 +506,16 @@ fn enum_set_type_impl(info: EnumSetInfo) -> SynTokenStream {
         }
     };
 
+    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;
@@ -492,6 +526,7 @@ fn enum_set_type_impl(info: EnumSetInfo) -> SynTokenStream {
 
         unsafe impl #enumset::EnumSetType for #name { }
 
+        #impl_with_repr
         #super_impls
 
         impl #name {