use core::any::type_name;
use core::cmp::Ordering;
use core::fmt;
use core::hash::{Hash, Hasher};
use core::marker::PhantomData;
use core::ops::{BitAnd, BitOr, BitXor, Not};
use core::str::FromStr;
use crate::{BitFlags, BitFlagsSpec, NameMatcher, util};
#[repr(transparent)]
pub struct BitFlag<T, U>(u8, PhantomData<fn() -> T>, PhantomData<fn() -> *const U>)
where
U: ?Sized;
#[cfg(feature = "serde")]
struct BitFlagVisitor<T, U>(PhantomData<T>, PhantomData<fn() -> *const U>)
where
U: ?Sized;
impl<T, U> BitFlag<T, U>
where
U: ?Sized,
{
#[inline]
#[must_use]
pub const fn from_bit_retain(i: u8) -> Self {
Self(i, PhantomData, PhantomData)
}
#[inline]
#[must_use]
pub const fn bit(&self) -> u8 {
self.0
}
#[inline]
#[must_use]
pub const fn name(&self) -> Option<&'static str>
where
U: BitFlagsSpec<T>,
{
let names_and_aliases = self.name_and_aliases();
if names_and_aliases.is_empty() {
None
} else {
Some(names_and_aliases[0])
}
}
#[inline]
#[must_use]
pub const fn name_and_aliases(&self) -> &'static [&'static str]
where
U: BitFlagsSpec<T>,
{
#[expect(clippy::as_conversions, reason = "supported in const functions")]
let bit = self.0 as usize;
if bit < U::FLAG_NAMES.len() {
U::FLAG_NAMES[bit]
} else {
&[]
}
}
#[inline]
pub fn try_from_str(input: &str, name_matcher: NameMatcher) -> Result<Self, BitFlagParseError>
where
T: util::IntOpsExt,
U: BitFlagsSpec<T>,
{
if let Some(flag) = Self::from_str_opt(input, name_matcher) {
return Ok(flag);
}
#[cfg(feature = "alloc")]
let given = alloc::string::ToString::to_string(input);
#[cfg(not(feature = "alloc"))]
let given = ();
let expected = U::FLAG_NAMES;
Err(BitFlagParseError { given, expected })
}
#[inline]
#[must_use]
pub fn from_str_opt(input: &str, name_matcher: NameMatcher) -> Option<Self>
where
T: util::IntOpsExt,
U: BitFlagsSpec<T>,
{
if let Some(flag) = Self::from_name_opt(input, name_matcher) {
return Some(flag);
}
if let Some(flag) = Self::from_hex_opt(input) {
return Some(flag);
}
None
}
#[inline]
pub fn try_from_name(
input: &str,
name_matcher: NameMatcher,
) -> Result<Self, BitFlagParseNameError>
where
T: util::IntOpsExt,
U: BitFlagsSpec<T>,
{
if let Some(flag) = Self::from_name_opt(input, name_matcher) {
return Ok(flag);
}
#[cfg(feature = "alloc")]
let given = alloc::string::ToString::to_string(input);
#[cfg(not(feature = "alloc"))]
let given = ();
let expected = U::FLAG_NAMES;
Err(BitFlagParseNameError { given, expected })
}
#[inline]
#[must_use]
pub fn from_name_opt(input: &str, name_matcher: NameMatcher) -> Option<Self>
where
T: util::IntOpsExt,
U: BitFlagsSpec<T>,
{
for (i, variants) in U::FLAG_NAMES.iter().enumerate().take(usize::from(T::BITS)) {
let Ok(bit) = u8::try_from(i) else {
#[cfg(debug_assertions)]
unreachable!("`i < T::BITS <= u8::MAX`, so this is unreachable");
#[cfg(not(debug_assertions))]
break;
};
let bit_flag = Self::from_bit_retain(bit);
for &expected in *variants {
let is_matched = name_matcher.matches(input, expected);
if is_matched {
return Some(bit_flag);
}
}
}
None
}
#[inline]
pub fn try_from_hex(input: &str) -> Result<Self, BitFlagParseHexError>
where
T: util::IntOpsExt,
{
if let Some(flag) = Self::from_hex_opt(input) {
return Ok(flag);
}
#[cfg(feature = "alloc")]
let given = alloc::string::ToString::to_string(input);
#[cfg(not(feature = "alloc"))]
let given = ();
Err(BitFlagParseHexError { given })
}
#[inline]
#[must_use]
pub fn from_hex_opt(input: &str) -> Option<Self>
where
T: util::IntOpsExt,
{
let input = input.strip_prefix("0x")?;
if input.starts_with('+') {
return None;
}
let bits = T::from_str_radix(input, 16).ok()?;
if bits.count_ones() != 1 {
return None;
}
let bit = bits.lowest_one()?;
let bit = u8::try_from(bit).ok()?;
(bit < T::BITS).then(|| Self::from_bit_retain(bit))
}
}
macro_rules! impl_bit_flag {
($ty:ty) => {
impl<U> BitFlag<$ty, U>
where
U: ?Sized,
{
#[inline]
#[must_use]
pub const fn try_from_flags(flags: BitFlags<$ty, U>) -> Option<Self>
where
U: BitFlagsSpec<$ty>,
{
Self::try_from_bits(flags.bits())
}
#[inline]
#[must_use]
pub const fn try_from_bit(i: u8) -> Option<Self>
where
U: BitFlagsSpec<$ty>,
{
const MAX_BITS: usize = size_of::<$ty>() * 8;
#[expect(clippy::as_conversions, reason = "supported in const functions")]
if (i as usize) < MAX_BITS && ((1 << (i as u32)) & !U::VALID_BITS_MASK) == 0 {
Some(Self(i, PhantomData, PhantomData))
} else {
None
}
}
#[inline]
#[must_use]
pub const fn try_from_bits(bits: $ty) -> Option<Self>
where
U: BitFlagsSpec<$ty>,
{
if bits.count_ones() == 1
&& let Some(bit) = bits.lowest_one()
{
#[expect(
clippy::as_conversions,
clippy::cast_possible_truncation,
reason = "supported in const functions"
)]
Self::try_from_bit(bit as u8)
} else {
None
}
}
#[inline]
#[must_use]
pub const fn try_from_bits_retain(bits: $ty) -> Option<Self> {
if bits.count_ones() == 1
&& let Some(bit) = bits.lowest_one()
{
#[expect(
clippy::as_conversions,
clippy::cast_possible_truncation,
reason = "supported in const functions"
)]
Some(Self::from_bit_retain(bit as u8))
} else {
None
}
}
#[inline]
#[must_use]
pub const fn is_valid(&self) -> bool
where
U: BitFlagsSpec<$ty>,
{
#[expect(clippy::as_conversions, reason = "supported in const functions")]
((self.0 as u32) < <$ty>::BITS && BitFlags::<$ty, U>::from_flag(*self).is_valid())
}
#[inline]
#[must_use]
pub const fn mask(&self) -> Option<$ty> {
#[expect(clippy::as_conversions, reason = "supported in const functions")]
let bit = self.0 as u32;
if bit < <$ty>::BITS {
Some(1 << bit)
} else {
None
}
}
#[inline]
#[must_use]
pub const fn intersection(self, other: Self) -> BitFlags<$ty, U> {
BitFlags::<$ty, U>::from_flag(self)
.intersection(BitFlags::<$ty, U>::from_flag(other))
}
#[inline]
#[must_use]
pub const fn union(self, other: Self) -> BitFlags<$ty, U> {
BitFlags::<$ty, U>::from_flag(self).union(BitFlags::<$ty, U>::from_flag(other))
}
#[inline]
#[must_use]
pub const fn difference(self, other: Self) -> BitFlags<$ty, U> {
BitFlags::<$ty, U>::from_flag(self).difference(BitFlags::<$ty, U>::from_flag(other))
}
#[inline]
#[must_use]
pub const fn symmetric_difference(self, other: Self) -> BitFlags<$ty, U> {
BitFlags::<$ty, U>::from_flag(self)
.symmetric_difference(BitFlags::<$ty, U>::from_flag(other))
}
#[inline]
#[must_use]
pub const fn complement(self) -> BitFlags<$ty, U>
where
U: BitFlagsSpec<$ty>,
{
BitFlags::<$ty, U>::from_flag(self).complement()
}
}
};
}
impl_bit_flag!(u8);
impl_bit_flag!(u16);
impl_bit_flag!(u32);
impl_bit_flag!(u64);
impl_bit_flag!(u128);
impl_bit_flag!(usize);
impl_bit_flag!(i8);
impl_bit_flag!(i16);
impl_bit_flag!(i32);
impl_bit_flag!(i64);
impl_bit_flag!(i128);
impl_bit_flag!(isize);
impl<T, U> Copy for BitFlag<T, U> where U: ?Sized {}
impl<T, U> Clone for BitFlag<T, U>
where
U: ?Sized,
{
#[inline]
fn clone(&self) -> Self {
*self
}
}
impl<T, U> Eq for BitFlag<T, U> where U: ?Sized {}
impl<T, U> PartialEq for BitFlag<T, U>
where
U: ?Sized,
{
#[inline]
fn eq(&self, other: &Self) -> bool {
self.0 == other.0
}
}
impl<T, U> Ord for BitFlag<T, U>
where
U: ?Sized,
{
#[inline]
fn cmp(&self, other: &Self) -> Ordering {
self.0.cmp(&other.0)
}
}
impl<T, U> PartialOrd for BitFlag<T, U>
where
U: ?Sized,
{
#[inline]
fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
Some(self.cmp(other))
}
}
impl<T, U> Hash for BitFlag<T, U>
where
U: ?Sized,
{
#[inline]
fn hash<H: Hasher>(&self, state: &mut H) {
self.0.hash(state);
}
}
impl<T, U> TryFrom<BitFlags<T, U>> for BitFlag<T, U>
where
T: Copy + PartialEq + Not<Output = T> + BitAnd<T, Output = T> + util::IntOpsExt,
U: BitFlagsSpec<T>,
{
type Error = BitFlagFromFlagsError;
#[inline]
fn try_from(flags: BitFlags<T, U>) -> Result<Self, Self::Error> {
let bits = flags.bits();
if bits.count_ones() == 1
&& let Some(bit) = bits.lowest_one()
&& let Ok(bit) = u8::try_from(bit)
&& let Some(mask) = T::ONE.checked_shl(u32::from(bit))
&& (mask & !U::VALID_BITS_MASK) == T::ZERO
{
Ok(Self::from_bit_retain(bit))
} else {
Err(BitFlagFromFlagsError)
}
}
}
impl<T, U> BitAnd<Self> for BitFlag<T, U>
where
T: BitAnd<T, Output = T> + util::IntOpsExt,
U: ?Sized,
{
type Output = BitFlags<T, U>;
#[inline]
fn bitand(self, rhs: Self) -> Self::Output {
BitFlags::from(self) & BitFlags::from(rhs)
}
}
impl<T, U> BitOr<Self> for BitFlag<T, U>
where
T: BitOr<T, Output = T> + util::IntOpsExt,
U: ?Sized,
{
type Output = BitFlags<T, U>;
#[inline]
fn bitor(self, rhs: Self) -> Self::Output {
BitFlags::from(self) | BitFlags::from(rhs)
}
}
impl<T, U> BitXor<Self> for BitFlag<T, U>
where
T: BitXor<T, Output = T> + util::IntOpsExt,
U: ?Sized,
{
type Output = BitFlags<T, U>;
#[inline]
fn bitxor(self, rhs: Self) -> Self::Output {
BitFlags::from(self) ^ BitFlags::from(rhs)
}
}
impl<T, U> Not for BitFlag<T, U>
where
T: Not<Output = T> + BitAnd<T, Output = T> + util::IntOpsExt,
U: ?Sized + BitFlagsSpec<T>,
{
type Output = BitFlags<T, U>;
#[inline]
fn not(self) -> Self::Output {
!BitFlags::from(self)
}
}
impl<T, U> fmt::Display for BitFlag<T, U>
where
U: ?Sized + BitFlagsSpec<T>,
{
#[expect(clippy::missing_inline_in_public_items, reason = "bloats binary size")]
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
let bit = usize::from(self.0);
if let Some(names) = U::FLAG_NAMES.get(bit)
&& let Some(name) = names.first()
{
write!(f, "{name}")
} else {
let leading = 1 << (bit % 4);
let num_zeroes = bit / 4;
write!(f, "0x{leading}{:0<num_zeroes$}", "")
}
}
}
impl<T, U> fmt::Debug for BitFlag<T, U>
where
U: ?Sized,
{
#[expect(clippy::missing_inline_in_public_items, reason = "bloats binary size")]
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(
f,
"BitFlag(1 << {}, PhantomData<({}, {})>)",
self.0,
type_name::<T>(),
type_name::<U>(),
)
}
}
impl<T, U> fmt::Binary for BitFlag<T, U>
where
U: ?Sized,
{
#[inline]
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
if f.alternate() {
write!(f, "0b")?;
}
let bit = usize::from(self.0);
write!(f, "1{:0<bit$}", "")
}
}
impl<T, U> fmt::Octal for BitFlag<T, U>
where
U: ?Sized,
{
#[inline]
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
if f.alternate() {
write!(f, "0o")?;
}
let bit = usize::from(self.0);
let leading = 1 << (bit % 3);
let num_zeroes = bit / 3;
write!(f, "{leading}{:0<num_zeroes$}", "")
}
}
impl<T, U> fmt::LowerHex for BitFlag<T, U>
where
U: ?Sized,
{
#[inline]
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
if f.alternate() {
write!(f, "0x")?;
}
let bit = usize::from(self.0);
let leading = 1 << (bit % 4);
let num_zeroes = bit / 4;
write!(f, "{leading}{:0<num_zeroes$}", "")
}
}
impl<T, U> fmt::UpperHex for BitFlag<T, U>
where
U: ?Sized,
{
#[inline]
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
if f.alternate() {
write!(f, "0x")?;
}
let bit = usize::from(self.0);
let leading = 1 << (bit % 4);
let num_zeroes = bit / 4;
write!(f, "{leading}{:0<num_zeroes$}", "")
}
}
impl<T, U> FromStr for BitFlag<T, U>
where
T: util::IntOpsExt,
U: ?Sized + BitFlagsSpec<T>,
{
type Err = BitFlagParseError;
#[inline]
fn from_str(input: &str) -> Result<Self, Self::Err> {
Self::try_from_str(input, U::DEFAULT_NAME_MATCHER)
}
}
#[cfg(feature = "serde")]
#[cfg_attr(docsrs, doc(cfg(feature = "serde")))]
impl<T, U> serde::Serialize for BitFlag<T, U>
where
U: ?Sized + BitFlagsSpec<T>,
{
#[inline]
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
where
S: serde::Serializer,
{
serializer.collect_str(&self)
}
}
#[cfg(feature = "arbitrary")]
#[cfg_attr(docsrs, doc(cfg(feature = "arbitrary")))]
impl<'a, T, U> arbitrary::Arbitrary<'a> for BitFlag<T, U>
where
U: ?Sized,
{
#[inline]
fn arbitrary(u: &mut arbitrary::Unstructured<'a>) -> arbitrary::Result<Self> {
u8::arbitrary(u).map(|u| Self(u, PhantomData, PhantomData))
}
#[inline]
fn size_hint(depth: usize) -> (usize, Option<usize>) {
u8::size_hint(depth)
}
}
#[cfg(feature = "bytemuck")]
#[cfg_attr(docsrs, doc(cfg(feature = "bytemuck")))]
unsafe impl<T, U> bytemuck::Zeroable for BitFlag<T, U> where U: ?Sized {}
#[cfg(feature = "bytemuck")]
#[cfg_attr(docsrs, doc(cfg(feature = "bytemuck")))]
unsafe impl<T, U> bytemuck::Pod for BitFlag<T, U>
where
T: 'static,
U: 'static + ?Sized,
{
}
#[cfg(feature = "serde")]
#[cfg_attr(docsrs, doc(cfg(feature = "serde")))]
impl<'de, T, U> serde::Deserialize<'de> for BitFlag<T, U>
where
T: util::IntOpsExt,
U: ?Sized + BitFlagsSpec<T>,
{
#[inline]
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
where
D: serde::Deserializer<'de>,
{
deserializer.deserialize_str(BitFlagVisitor::<T, U>(PhantomData, PhantomData))
}
}
#[cfg(feature = "serde")]
#[cfg_attr(docsrs, doc(cfg(feature = "serde")))]
impl<T, U> serde::de::Visitor<'_> for BitFlagVisitor<T, U>
where
T: util::IntOpsExt,
U: ?Sized + BitFlagsSpec<T>,
{
type Value = BitFlag<T, U>;
fn expecting(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
fmt_expected(f, U::FLAG_NAMES)
}
#[inline]
fn visit_str<E>(self, value: &str) -> Result<Self::Value, E>
where
E: serde::de::Error,
{
Self::Value::from_str(value).map_err(E::custom)
}
}
#[cfg_attr(
not(feature = "alloc"),
expect(
missing_copy_implementations,
reason = "the payload is feature-dependent"
)
)]
#[derive(Clone, Debug)]
pub struct BitFlagParseError {
#[cfg(not(feature = "alloc"))]
given: (),
#[cfg(feature = "alloc")]
given: alloc::string::String,
expected: &'static [&'static [&'static str]],
}
impl fmt::Display for BitFlagParseError {
#[expect(clippy::missing_inline_in_public_items, reason = "bloats binary size")]
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
let named_flags = self
.expected
.iter()
.filter(|variants| !variants.is_empty())
.count();
if named_flags == 0 {
write!(f, "unrecognized hex flag, ")?;
} else {
write!(f, "unrecognized named flag or hex flag, ")?;
}
#[cfg(feature = "alloc")]
write!(f, "given: \"{}\", expected ", self.given)?;
#[cfg(not(feature = "alloc"))]
write!(f, "expected ")?;
fmt_expected(f, self.expected)?;
#[cfg(not(feature = "alloc"))]
let () = self.given;
Ok(())
}
}
impl core::error::Error for BitFlagParseError {}
#[inline]
fn fmt_expected(
f: &mut fmt::Formatter<'_>,
expected: &'static [&'static [&'static str]],
) -> fmt::Result {
let named_flags = expected
.iter()
.filter(|variants| !variants.is_empty())
.count();
if named_flags == 0 {
write!(f, "a valid one-bit hex flag with \"0x\" prefix")?;
} else {
fmt_expected_name(f, expected)?;
write!(f, ", or a valid one-bit hex flag with \"0x\" prefix")?;
}
Ok(())
}
#[cfg_attr(
not(feature = "alloc"),
expect(
missing_copy_implementations,
reason = "the payload is feature-dependent"
)
)]
#[derive(Clone, Debug)]
pub struct BitFlagParseNameError {
#[cfg(not(feature = "alloc"))]
given: (),
#[cfg(feature = "alloc")]
given: alloc::string::String,
expected: &'static [&'static [&'static str]],
}
impl fmt::Display for BitFlagParseNameError {
#[expect(clippy::missing_inline_in_public_items, reason = "bloats binary size")]
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
let named_flags = self
.expected
.iter()
.filter(|variants| !variants.is_empty())
.count();
if named_flags == 0 {
write!(f, "does not support named flags")?;
} else {
write!(f, "unrecognized named flag, ")?;
#[cfg(feature = "alloc")]
write!(f, "given: \"{}\", expected ", self.given)?;
#[cfg(not(feature = "alloc"))]
write!(f, "expected ")?;
fmt_expected(f, self.expected)?;
}
#[cfg(not(feature = "alloc"))]
let () = self.given;
Ok(())
}
}
impl core::error::Error for BitFlagParseNameError {}
#[inline]
fn fmt_expected_name(
f: &mut fmt::Formatter<'_>,
expected: &'static [&'static [&'static str]],
) -> fmt::Result {
let mut separator = " ";
write!(f, "one of")?;
for names in expected {
if let Some(name) = names.first() {
write!(f, "{separator}\"{name}\"")?;
separator = ", ";
}
}
Ok(())
}
#[cfg_attr(
not(feature = "alloc"),
expect(
missing_copy_implementations,
reason = "the payload is feature-dependent"
)
)]
#[derive(Clone, Debug)]
pub struct BitFlagParseHexError {
#[cfg(not(feature = "alloc"))]
given: (),
#[cfg(feature = "alloc")]
given: alloc::string::String,
}
impl fmt::Display for BitFlagParseHexError {
#[expect(clippy::missing_inline_in_public_items, reason = "bloats binary size")]
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "unrecognized hex flag")?;
#[cfg(feature = "alloc")]
write!(f, ", given: \"{}\"", self.given)?;
write!(f, ", expected a valid one-bit hex flag with \"0x\" prefix")?;
#[cfg(not(feature = "alloc"))]
let () = self.given;
Ok(())
}
}
impl core::error::Error for BitFlagParseHexError {}
#[derive(Copy, Clone, Eq, PartialEq, Ord, PartialOrd, Hash, Default, Debug)]
pub struct BitFlagFromFlagsError;
impl fmt::Display for BitFlagFromFlagsError {
#[expect(clippy::missing_inline_in_public_items, reason = "bloats binary size")]
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "flags do not contain exactly one valid bit")
}
}
impl core::error::Error for BitFlagFromFlagsError {}