diff --git a/enumflags/Cargo.toml b/enumflags/Cargo.toml index e2248d9..aab66c2 100644 --- a/enumflags/Cargo.toml +++ b/enumflags/Cargo.toml @@ -12,3 +12,6 @@ documentation = "https://docs.rs/enumflags2" [dependencies] enumflags2_derive = { version = "0.6.0", path = "../enumflags_derive" } serde = { version = "^1.0.0", default-features = false, optional = true } + +[features] +std = [] diff --git a/enumflags/src/fallible.rs b/enumflags/src/fallible.rs new file mode 100644 index 0000000..2520d7b --- /dev/null +++ b/enumflags/src/fallible.rs @@ -0,0 +1,62 @@ +use core::convert::TryFrom; +use core::fmt; +use super::BitFlags; +use super::_internal::RawBitFlags; + +macro_rules! impl_try_from { + ($($ty:ty),*) => { + $( + impl TryFrom<$ty> for BitFlags + where + T: RawBitFlags, + { + type Error = FromBitsError; + + fn try_from(bits: T::Type) -> Result { + let flags = Self::from_bits_truncate(bits); + if flags.bits() == bits { + Ok(flags) + } else { + Err(FromBitsError { + flags, + invalid: bits & !flags.bits(), + }) + } + } + } + )* + }; +} + +impl_try_from! { + u8, u16, u32, u64, usize +} + +#[derive(Debug, Copy, Clone)] +pub struct FromBitsError { + flags: BitFlags, + invalid: T::Type, +} + +impl FromBitsError { + pub fn truncate(self) -> BitFlags { + self.flags + } + + pub fn invalid_bits(self) -> T::Type { + self.invalid + } +} + +impl fmt::Display for FromBitsError { + fn fmt(&self, fmt: &mut fmt::Formatter) -> fmt::Result { + write!(fmt, "Invalid bits for {:?}: {:#b}", self.flags, self.invalid) + } +} + +#[cfg(feature = "std")] +impl std::error::Error for FromBitsError { + fn description(&self) -> &str { + "invalid bitflags representation" + } +} diff --git a/enumflags/src/formatting.rs b/enumflags/src/formatting.rs index 9ef801c..ca2c951 100644 --- a/enumflags/src/formatting.rs +++ b/enumflags/src/formatting.rs @@ -4,7 +4,6 @@ use crate::{BitFlags, _internal::RawBitFlags}; impl fmt::Debug for BitFlags where T: RawBitFlags + fmt::Debug, - T::Type: fmt::Binary + fmt::Debug, { fn fmt(&self, fmt: &mut fmt::Formatter) -> fmt::Result { let name = T::bitflags_type_name(); diff --git a/enumflags/src/lib.rs b/enumflags/src/lib.rs index 4d905e1..802478b 100644 --- a/enumflags/src/lib.rs +++ b/enumflags/src/lib.rs @@ -44,10 +44,11 @@ //! ## Optional Feature Flags //! //! - [`serde`](https://serde.rs/) implements `Serialize` and `Deserialize` for `BitFlags`. +//! - `std` implements `std::error::Error` for `FromBitsError`. #![warn(missing_docs)] -#![cfg_attr(not(test), no_std)] +#![cfg_attr(all(not(test), not(feature = "std")), no_std)] -#[cfg(test)] +#[cfg(any(test, feature = "std"))] extern crate core; use core::{cmp, ops}; use core::iter::FromIterator; @@ -89,6 +90,7 @@ pub mod _internal { use core::ops::{BitAnd, BitOr, BitXor, Not}; use core::cmp::PartialOrd; + use core::fmt; pub trait BitFlagNum : Default @@ -97,6 +99,8 @@ pub mod _internal { + BitXor + Not + PartialOrd + + fmt::Debug + + fmt::Binary + Copy + Clone { } @@ -116,6 +120,10 @@ pub mod _internal { // Internal debug formatting implementations mod formatting; +// impl TryFrom for BitFlags +mod fallible; +pub use fallible::FromBitsError; + use _internal::RawBitFlags; /// Represents a set of flags of some type `T`. @@ -148,7 +156,7 @@ where impl From for BitFlags { fn from(t: T) -> BitFlags { - BitFlags { val: t.bits() } + Self::from_flag(t) } } @@ -201,6 +209,10 @@ where } } + pub fn from_flag(t: T) -> Self { + BitFlags { val: t.bits() } + } + /// Truncates flags that are illegal pub fn from_bits_truncate(bits: T::Type) -> Self { unsafe { BitFlags::new(bits & T::all()) } diff --git a/test_suite/tests/formatting.rs b/test_suite/tests/formatting.rs index 708268b..8f9358e 100644 --- a/test_suite/tests/formatting.rs +++ b/test_suite/tests/formatting.rs @@ -72,3 +72,13 @@ fn format() { "0x0F" ); } + +#[test] +fn debug_generic() { + use enumflags2::{BitFlags, _internal::RawBitFlags}; + + #[derive(Debug)] + struct Debug(BitFlags); + + let _ = format!("{:?}", Debug(BitFlags::::all())); +}