Skip to main content

bitflags_lite/
flag.rs

1use core::any::type_name;
2use core::cmp::Ordering;
3use core::fmt;
4use core::hash::{Hash, Hasher};
5use core::marker::PhantomData;
6use core::ops::{BitAnd, BitOr, BitXor, Not};
7use core::str::FromStr;
8
9use crate::{BitFlags, BitFlagsSpec, NameMatcher, util};
10
11/// A single named or indexed bit in a [`BitFlags`] set.
12///
13/// A `BitFlag` stores a bit index and is tied to the same storage type `T` and
14/// tag type `U` as the corresponding [`BitFlags<T, U>`].
15/// It is primarily returned by [`BitFlagsIter`] and can be combined with other
16/// flags or converted into a [`BitFlags`] set.
17///
18/// # Formatting
19///
20/// [`Display`](fmt::Display) writes the flag's name if one is defined in
21/// [`BitFlagsSpec::FLAG_NAMES`], and its mask (`1 << bit`) in lowercase
22/// hexadecimal with a `0x` prefix otherwise.
23/// The mask is computed from the bit index regardless of whether the index is
24/// in range for `T`.
25///
26/// [`Debug`](fmt::Debug) writes the bit index and the type parameters.
27///
28/// [`Binary`](fmt::Binary), [`Octal`](fmt::Octal),
29/// [`LowerHex`](fmt::LowerHex), and [`UpperHex`](fmt::UpperHex) write the
30/// mask in the corresponding numeric base.
31///
32/// # Method summary
33///
34/// - [`from_bit_retain()`](Self::from_bit_retain): Return a flag for a bit
35///   index, without validation.
36/// - [`try_from_bit()`](Self::try_from_bit): Return a flag for a bit index,
37///   rejecting it if invalid.
38/// - [`try_from_bits()`](Self::try_from_bits): Return a flag from a single-bit
39///   mask, rejecting it if invalid.
40/// - [`try_from_bits_retain()`](Self::try_from_bits_retain): Return a flag from
41///   a single-bit mask, without validation.
42/// - [`try_from_flags()`](Self::try_from_flags): Return a flag from a
43///   [`BitFlags`] with exactly one valid bit.
44/// - [`try_from_str()`](Self::try_from_str): Parse from a name or a hex flag.
45/// - [`from_str_opt()`](Self::from_str_opt): Parse from a name or a hex flag,
46///   returning `Option`.
47/// - [`try_from_name()`](Self::try_from_name): Parse from a name only.
48/// - [`from_name_opt()`](Self::from_name_opt): Parse from a name only,
49///   returning `Option`.
50/// - [`try_from_hex()`](Self::try_from_hex): Parse from a hex flag only.
51/// - [`from_hex_opt()`](Self::from_hex_opt): Parse from a hex flag only,
52///   returning `Option`.
53/// - [`bit()`](Self::bit): Return the zero-based bit index.
54/// - [`mask()`](Self::mask): Return the single-bit mask, or `None` if out of
55///   range.
56/// - [`name()`](Self::name): Return the primary name, if any.
57/// - [`name_and_aliases()`](Self::name_and_aliases): Return all names and
58///   aliases.
59/// - [`is_valid()`](Self::is_valid): Return `true` if the bit is valid for the
60///   tag.
61/// - [`intersection()`](Self::intersection): Return the bits set in both flags.
62/// - [`union()`](Self::union): Return the bits set in either flag.
63/// - [`difference()`](Self::difference): Return the bits set in `self` but not
64///   in `other`.
65/// - [`symmetric_difference()`](Self::symmetric_difference): Return the bits
66///   set in exactly one flag.
67/// - [`complement()`](Self::complement): Return the valid bits not set in
68///   `self`.
69///
70/// [`BitFlagsIter`]: struct.BitFlagsIter.html
71#[repr(transparent)]
72pub struct BitFlag<T, U>(u8, PhantomData<fn() -> T>, PhantomData<fn() -> *const U>)
73where
74    U: ?Sized;
75
76/// Serde visitor for deserializing [`BitFlag`] from a string.
77#[cfg(feature = "serde")]
78struct BitFlagVisitor<T, U>(PhantomData<T>, PhantomData<fn() -> *const U>)
79where
80    U: ?Sized;
81
82impl<T, U> BitFlag<T, U>
83where
84    U: ?Sized,
85{
86    /// Creates a bit flag for bit index `i`.
87    ///
88    /// The index is not checked against the number of bits in `T`, against
89    /// [`BitFlagsSpec::VALID_BITS_MASK`], or against
90    /// [`BitFlagsSpec::FLAG_NAMES`].
91    /// A `BitFlag` created with this method may refer to a bit that is out of
92    /// range for `T` or invalid for the tag.
93    /// Use [`BitFlag::try_from_bit`] or [`BitFlag::is_valid`] when validation
94    /// is required.
95    #[inline]
96    #[must_use]
97    pub const fn from_bit_retain(i: u8) -> Self {
98        Self(i, PhantomData, PhantomData)
99    }
100
101    /// Returns the zero-based bit index stored in this flag.
102    #[inline]
103    #[must_use]
104    pub const fn bit(&self) -> u8 {
105        self.0
106    }
107
108    /// Returns the primary name of this bit, if one is defined in
109    /// [`BitFlagsSpec::FLAG_NAMES`].
110    ///
111    /// The primary name is the first entry in the inner slice for this bit.
112    /// Returns `None` if the bit has no name.
113    #[inline]
114    #[must_use]
115    pub const fn name(&self) -> Option<&'static str>
116    where
117        U: BitFlagsSpec<T>,
118    {
119        let names_and_aliases = self.name_and_aliases();
120        if names_and_aliases.is_empty() {
121            None
122        } else {
123            Some(names_and_aliases[0])
124        }
125    }
126
127    /// Returns all names and aliases for this bit.
128    ///
129    /// The returned slice is the entry at this bit's index in
130    /// [`BitFlagsSpec::FLAG_NAMES`], or an empty slice if the index is out of
131    /// range or the bit has no names.
132    #[inline]
133    #[must_use]
134    pub const fn name_and_aliases(&self) -> &'static [&'static str]
135    where
136        U: BitFlagsSpec<T>,
137    {
138        #[expect(clippy::as_conversions, reason = "supported in const functions")]
139        let bit = self.0 as usize;
140        if bit < U::FLAG_NAMES.len() {
141            U::FLAG_NAMES[bit]
142        } else {
143            &[]
144        }
145    }
146
147    /// Parses a single flag from `input` using `name_matcher`.
148    ///
149    /// This is equivalent to [`BitFlag::try_from_str`], but returns a
150    /// [`BitFlagParseError`] describing the input and expected names on
151    /// failure.
152    ///
153    /// # Errors
154    ///
155    /// Returns [`BitFlagParseError`] if `input` is neither a recognized named
156    /// flag nor a hexadecimal flag of the form `0x...` with exactly one bit
157    /// set.
158    #[inline]
159    pub fn try_from_str(input: &str, name_matcher: NameMatcher) -> Result<Self, BitFlagParseError>
160    where
161        T: util::IntOpsExt,
162        U: BitFlagsSpec<T>,
163    {
164        if let Some(flag) = Self::from_str_opt(input, name_matcher) {
165            return Ok(flag);
166        }
167
168        #[cfg(feature = "alloc")]
169        let given = alloc::string::ToString::to_string(input);
170        #[cfg(not(feature = "alloc"))]
171        let given = ();
172        let expected = U::FLAG_NAMES;
173
174        Err(BitFlagParseError { given, expected })
175    }
176
177    /// Parses a single flag from `input` using `name_matcher`.
178    ///
179    /// The input is first matched against the names and aliases in
180    /// [`BitFlagsSpec::FLAG_NAMES`]. If no name matches, it is parsed as a
181    /// hexadecimal flag with a `0x` prefix that must have exactly one bit set.
182    ///
183    /// Returns `None` if `input` is not recognized.
184    #[inline]
185    #[must_use]
186    pub fn from_str_opt(input: &str, name_matcher: NameMatcher) -> Option<Self>
187    where
188        T: util::IntOpsExt,
189        U: BitFlagsSpec<T>,
190    {
191        if let Some(flag) = Self::from_name_opt(input, name_matcher) {
192            return Some(flag);
193        }
194        if let Some(flag) = Self::from_hex_opt(input) {
195            return Some(flag);
196        }
197
198        None
199    }
200
201    /// Parses a named flag from `input` using `name_matcher`.
202    ///
203    /// This is equivalent to [`BitFlag::try_from_name`], but returns a
204    /// [`BitFlagParseNameError`] describing the input and expected names on
205    /// failure.
206    ///
207    /// # Errors
208    ///
209    /// Returns [`BitFlagParseNameError`] if `input` does not match any name or
210    /// alias in [`BitFlagsSpec::FLAG_NAMES`].
211    #[inline]
212    pub fn try_from_name(
213        input: &str,
214        name_matcher: NameMatcher,
215    ) -> Result<Self, BitFlagParseNameError>
216    where
217        T: util::IntOpsExt,
218        U: BitFlagsSpec<T>,
219    {
220        if let Some(flag) = Self::from_name_opt(input, name_matcher) {
221            return Ok(flag);
222        }
223
224        #[cfg(feature = "alloc")]
225        let given = alloc::string::ToString::to_string(input);
226        #[cfg(not(feature = "alloc"))]
227        let given = ();
228        let expected = U::FLAG_NAMES;
229
230        Err(BitFlagParseNameError { given, expected })
231    }
232
233    /// Parses a named flag from `input` using `name_matcher`.
234    ///
235    /// The input is matched against all names and aliases in
236    /// [`BitFlagsSpec::FLAG_NAMES`].
237    /// Returns `None` if no name matches.
238    #[inline]
239    #[must_use]
240    pub fn from_name_opt(input: &str, name_matcher: NameMatcher) -> Option<Self>
241    where
242        T: util::IntOpsExt,
243        U: BitFlagsSpec<T>,
244    {
245        for (i, variants) in U::FLAG_NAMES.iter().enumerate().take(usize::from(T::BITS)) {
246            let Ok(bit) = u8::try_from(i) else {
247                #[cfg(debug_assertions)]
248                unreachable!("`i < T::BITS <= u8::MAX`, so this is unreachable");
249                #[cfg(not(debug_assertions))]
250                break;
251            };
252            let bit_flag = Self::from_bit_retain(bit);
253            for &expected in *variants {
254                let is_matched = name_matcher.matches(input, expected);
255                if is_matched {
256                    return Some(bit_flag);
257                }
258            }
259        }
260
261        None
262    }
263
264    /// Parses a hexadecimal flag from `input`.
265    ///
266    /// This is equivalent to [`BitFlag::try_from_hex`], but returns a
267    /// [`BitFlagParseHexError`] describing the input on failure.
268    ///
269    /// # Errors
270    ///
271    /// Returns [`BitFlagParseHexError`] if `input` is not a valid `0x`-prefixed
272    /// hexadecimal flag with exactly one bit set.
273    #[inline]
274    pub fn try_from_hex(input: &str) -> Result<Self, BitFlagParseHexError>
275    where
276        T: util::IntOpsExt,
277    {
278        if let Some(flag) = Self::from_hex_opt(input) {
279            return Ok(flag);
280        }
281
282        #[cfg(feature = "alloc")]
283        let given = alloc::string::ToString::to_string(input);
284        #[cfg(not(feature = "alloc"))]
285        let given = ();
286
287        Err(BitFlagParseHexError { given })
288    }
289
290    /// Parses a hexadecimal flag from `input`.
291    ///
292    /// The input must have a `0x` prefix and represent a value with exactly one
293    /// bit set.
294    /// A leading `+` is not accepted.
295    /// Returns `None` if `input` is not a valid hexadecimal flag.
296    #[inline]
297    #[must_use]
298    pub fn from_hex_opt(input: &str) -> Option<Self>
299    where
300        T: util::IntOpsExt,
301    {
302        let input = input.strip_prefix("0x")?;
303        if input.starts_with('+') {
304            return None;
305        }
306        let bits = T::from_str_radix(input, 16).ok()?;
307
308        if bits.count_ones() != 1 {
309            return None;
310        }
311        let bit = bits.lowest_one()?;
312        let bit = u8::try_from(bit).ok()?;
313        (bit < T::BITS).then(|| Self::from_bit_retain(bit))
314    }
315}
316
317// Const traits are not stable yet, so the const methods are implemented for
318// each integer type separately via a macro.
319// See: https://github.com/rust-lang/rust/issues/143874
320macro_rules! impl_bit_flag {
321    ($ty:ty) => {
322        impl<U> BitFlag<$ty, U>
323        where
324            U: ?Sized,
325        {
326            /// Creates a bit flag from a [`BitFlags`] set.
327            ///
328            /// Returns `Some` if `flags` contains exactly one set bit and that
329            /// bit is valid according to [`BitFlagsSpec::VALID_BITS_MASK`].
330            ///
331            /// Returns `None` if `flags` is empty, has more than one bit set,
332            /// or the single set bit is not valid.
333            ///
334            /// This is equivalent to [`BitFlag::try_from_bits`] on
335            /// `flags.bits()`.
336            #[inline]
337            #[must_use]
338            pub const fn try_from_flags(flags: BitFlags<$ty, U>) -> Option<Self>
339            where
340                U: BitFlagsSpec<$ty>,
341            {
342                Self::try_from_bits(flags.bits())
343            }
344
345            /// Creates a validated bit flag for bit index `i`.
346            ///
347            /// Returns `None` if `i` is not set in
348            /// [`BitFlagsSpec::VALID_BITS_MASK`].
349            ///
350            /// The index `i` must be less than the number of bits in the
351            /// storage type.
352            #[inline]
353            #[must_use]
354            pub const fn try_from_bit(i: u8) -> Option<Self>
355            where
356                U: BitFlagsSpec<$ty>,
357            {
358                const MAX_BITS: usize = size_of::<$ty>() * 8;
359
360                #[expect(clippy::as_conversions, reason = "supported in const functions")]
361                if (i as usize) < MAX_BITS && ((1 << (i as u32)) & !U::VALID_BITS_MASK) == 0 {
362                    Some(Self(i, PhantomData, PhantomData))
363                } else {
364                    None
365                }
366            }
367
368            /// Creates a validated bit flag from `bits` if exactly one bit is
369            /// set and that bit is valid according to
370            /// [`BitFlagsSpec::VALID_BITS_MASK`].
371            ///
372            /// Returns `None` if `bits` is zero, has more than one bit set, or
373            /// the single set bit is not valid.
374            #[inline]
375            #[must_use]
376            pub const fn try_from_bits(bits: $ty) -> Option<Self>
377            where
378                U: BitFlagsSpec<$ty>,
379            {
380                if bits.count_ones() == 1
381                    && let Some(bit) = bits.lowest_one()
382                {
383                    #[expect(
384                        clippy::as_conversions,
385                        clippy::cast_possible_truncation,
386                        reason = "supported in const functions"
387                    )]
388                    Self::try_from_bit(bit as u8)
389                } else {
390                    None
391                }
392            }
393
394            /// Creates a single-bit flag from `bits` if exactly one bit is set.
395            ///
396            /// The single set bit is not checked against
397            /// [`BitFlagsSpec::VALID_BITS_MASK`].
398            ///
399            /// Returns `None` if `bits` is `0` or has more than one bit set.
400            #[inline]
401            #[must_use]
402            pub const fn try_from_bits_retain(bits: $ty) -> Option<Self> {
403                if bits.count_ones() == 1
404                    && let Some(bit) = bits.lowest_one()
405                {
406                    #[expect(
407                        clippy::as_conversions,
408                        clippy::cast_possible_truncation,
409                        reason = "supported in const functions"
410                    )]
411                    Some(Self::from_bit_retain(bit as u8))
412                } else {
413                    None
414                }
415            }
416
417            /// Returns `true` if this flag refers to a valid bit.
418            ///
419            /// A flag is valid when its bit index is within the width of `T` and the
420            /// corresponding bit is present in [`BitFlagsSpec::VALID_BITS_MASK`].
421            ///
422            /// [`BitFlag::from_bit_retain`] does not validate the bit index, so a
423            /// `BitFlag` constructed through it may be invalid.
424            #[inline]
425            #[must_use]
426            pub const fn is_valid(&self) -> bool
427            where
428                U: BitFlagsSpec<$ty>,
429            {
430                #[expect(clippy::as_conversions, reason = "supported in const functions")]
431                ((self.0 as u32) < <$ty>::BITS && BitFlags::<$ty, U>::from_flag(*self).is_valid())
432            }
433
434            /// Returns the single-bit mask for this flag, or `None` if the bit index is
435            /// outside the width of `T`.
436            ///
437            /// The mask is `1 << bit`. It is not checked against
438            /// [`BitFlagsSpec::VALID_BITS_MASK`]; use [`BitFlag::is_valid`] when tag
439            /// validity is required.
440            #[inline]
441            #[must_use]
442            pub const fn mask(&self) -> Option<$ty> {
443                #[expect(clippy::as_conversions, reason = "supported in const functions")]
444                let bit = self.0 as u32;
445                if bit < <$ty>::BITS {
446                    Some(1 << bit)
447                } else {
448                    None
449                }
450            }
451
452            /// Returns the intersection of `self` and `other`.
453            ///
454            /// This is a raw-bit operation.
455            /// Invalid bits in either operand are preserved when they are set
456            /// in both operands.
457            #[inline]
458            #[must_use]
459            pub const fn intersection(self, other: Self) -> BitFlags<$ty, U> {
460                BitFlags::<$ty, U>::from_flag(self)
461                    .intersection(BitFlags::<$ty, U>::from_flag(other))
462            }
463
464            /// Returns the union of `self` and `other`.
465            ///
466            /// This is a raw-bit operation.
467            /// Invalid bits in either operand are preserved.
468            #[inline]
469            #[must_use]
470            pub const fn union(self, other: Self) -> BitFlags<$ty, U> {
471                BitFlags::<$ty, U>::from_flag(self).union(BitFlags::<$ty, U>::from_flag(other))
472            }
473
474            /// Returns the difference of `self` and `other`.
475            ///
476            /// The result contains bits that are set in `self` but not in
477            /// `other`.
478            /// This is a raw-bit operation.
479            /// Invalid bits already present in `self` are preserved unless they
480            /// are also set in `other`.
481            #[inline]
482            #[must_use]
483            pub const fn difference(self, other: Self) -> BitFlags<$ty, U> {
484                BitFlags::<$ty, U>::from_flag(self).difference(BitFlags::<$ty, U>::from_flag(other))
485            }
486
487            /// Returns the symmetric difference of `self` and `other`.
488            ///
489            /// The result contains bits that are set in exactly one of `self`
490            /// or `other`.
491            /// This is a raw-bit operation.
492            /// Invalid bits participate and may be preserved.
493            #[inline]
494            #[must_use]
495            pub const fn symmetric_difference(self, other: Self) -> BitFlags<$ty, U> {
496                BitFlags::<$ty, U>::from_flag(self)
497                    .symmetric_difference(BitFlags::<$ty, U>::from_flag(other))
498            }
499
500            /// Returns the complement of `self`.
501            ///
502            /// All valid bits not set in `self` become set, and all invalid
503            /// bits become unset.
504            /// The result always contains only valid bits.
505            #[inline]
506            #[must_use]
507            pub const fn complement(self) -> BitFlags<$ty, U>
508            where
509                U: BitFlagsSpec<$ty>,
510            {
511                BitFlags::<$ty, U>::from_flag(self).complement()
512            }
513        }
514    };
515}
516
517impl_bit_flag!(u8);
518impl_bit_flag!(u16);
519impl_bit_flag!(u32);
520impl_bit_flag!(u64);
521impl_bit_flag!(u128);
522impl_bit_flag!(usize);
523
524impl_bit_flag!(i8);
525impl_bit_flag!(i16);
526impl_bit_flag!(i32);
527impl_bit_flag!(i64);
528impl_bit_flag!(i128);
529impl_bit_flag!(isize);
530
531impl<T, U> Copy for BitFlag<T, U> where U: ?Sized {}
532
533impl<T, U> Clone for BitFlag<T, U>
534where
535    U: ?Sized,
536{
537    #[inline]
538    fn clone(&self) -> Self {
539        *self
540    }
541}
542
543impl<T, U> Eq for BitFlag<T, U> where U: ?Sized {}
544
545impl<T, U> PartialEq for BitFlag<T, U>
546where
547    U: ?Sized,
548{
549    #[inline]
550    fn eq(&self, other: &Self) -> bool {
551        self.0 == other.0
552    }
553}
554
555impl<T, U> Ord for BitFlag<T, U>
556where
557    U: ?Sized,
558{
559    #[inline]
560    fn cmp(&self, other: &Self) -> Ordering {
561        self.0.cmp(&other.0)
562    }
563}
564
565impl<T, U> PartialOrd for BitFlag<T, U>
566where
567    U: ?Sized,
568{
569    #[inline]
570    fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
571        Some(self.cmp(other))
572    }
573}
574
575impl<T, U> Hash for BitFlag<T, U>
576where
577    U: ?Sized,
578{
579    #[inline]
580    fn hash<H: Hasher>(&self, state: &mut H) {
581        self.0.hash(state);
582    }
583}
584
585/// Converts a flag set into a single-bit flag.
586///
587/// This fails unless `flags` contains exactly one set bit and that bit is
588/// valid for the tag. See [`BitFlag::try_from_flags`].
589impl<T, U> TryFrom<BitFlags<T, U>> for BitFlag<T, U>
590where
591    T: Copy + PartialEq + Not<Output = T> + BitAnd<T, Output = T> + util::IntOpsExt,
592    U: BitFlagsSpec<T>,
593{
594    type Error = BitFlagFromFlagsError;
595
596    #[inline]
597    fn try_from(flags: BitFlags<T, U>) -> Result<Self, Self::Error> {
598        let bits = flags.bits();
599        if bits.count_ones() == 1
600            && let Some(bit) = bits.lowest_one()
601            && let Ok(bit) = u8::try_from(bit)
602            && let Some(mask) = T::ONE.checked_shl(u32::from(bit))
603            && (mask & !U::VALID_BITS_MASK) == T::ZERO
604        {
605            Ok(Self::from_bit_retain(bit))
606        } else {
607            Err(BitFlagFromFlagsError)
608        }
609    }
610}
611
612impl<T, U> BitAnd<Self> for BitFlag<T, U>
613where
614    T: BitAnd<T, Output = T> + util::IntOpsExt,
615    U: ?Sized,
616{
617    type Output = BitFlags<T, U>;
618
619    #[inline]
620    fn bitand(self, rhs: Self) -> Self::Output {
621        BitFlags::from(self) & BitFlags::from(rhs)
622    }
623}
624
625impl<T, U> BitOr<Self> for BitFlag<T, U>
626where
627    T: BitOr<T, Output = T> + util::IntOpsExt,
628    U: ?Sized,
629{
630    type Output = BitFlags<T, U>;
631
632    #[inline]
633    fn bitor(self, rhs: Self) -> Self::Output {
634        BitFlags::from(self) | BitFlags::from(rhs)
635    }
636}
637
638impl<T, U> BitXor<Self> for BitFlag<T, U>
639where
640    T: BitXor<T, Output = T> + util::IntOpsExt,
641    U: ?Sized,
642{
643    type Output = BitFlags<T, U>;
644
645    #[inline]
646    fn bitxor(self, rhs: Self) -> Self::Output {
647        BitFlags::from(self) ^ BitFlags::from(rhs)
648    }
649}
650
651impl<T, U> Not for BitFlag<T, U>
652where
653    T: Not<Output = T> + BitAnd<T, Output = T> + util::IntOpsExt,
654    U: ?Sized + BitFlagsSpec<T>,
655{
656    type Output = BitFlags<T, U>;
657
658    #[inline]
659    fn not(self) -> Self::Output {
660        !BitFlags::from(self)
661    }
662}
663
664/// Formats this flag as its name if one is defined for its bit, or as its
665/// mask (`1 << bit`) in lowercase hexadecimal with a `0x` prefix otherwise.
666///
667/// The mask is computed from the bit index regardless of whether the index
668/// is in range for `T`. See [`BitFlag::is_valid`].
669impl<T, U> fmt::Display for BitFlag<T, U>
670where
671    U: ?Sized + BitFlagsSpec<T>,
672{
673    #[expect(clippy::missing_inline_in_public_items, reason = "bloats binary size")]
674    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
675        let bit = usize::from(self.0);
676        if let Some(names) = U::FLAG_NAMES.get(bit)
677            && let Some(name) = names.first()
678        {
679            write!(f, "{name}")
680        } else {
681            let leading = 1 << (bit % 4);
682            let num_zeroes = bit / 4;
683            write!(f, "0x{leading}{:0<num_zeroes$}", "")
684        }
685    }
686}
687
688/// Formats this flag's bit index and type parameters.
689///
690/// Use [`Display`](fmt::Display) to format the flag's mask or name instead.
691impl<T, U> fmt::Debug for BitFlag<T, U>
692where
693    U: ?Sized,
694{
695    #[expect(clippy::missing_inline_in_public_items, reason = "bloats binary size")]
696    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
697        write!(
698            f,
699            "BitFlag(1 << {}, PhantomData<({}, {})>)",
700            self.0,
701            type_name::<T>(),
702            type_name::<U>(),
703        )
704    }
705}
706
707/// Formats this flag's mask (`1 << bit`) in binary.
708impl<T, U> fmt::Binary for BitFlag<T, U>
709where
710    U: ?Sized,
711{
712    #[inline]
713    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
714        if f.alternate() {
715            write!(f, "0b")?;
716        }
717        let bit = usize::from(self.0);
718        write!(f, "1{:0<bit$}", "")
719    }
720}
721
722/// Formats this flag's mask (`1 << bit`) in octal.
723impl<T, U> fmt::Octal for BitFlag<T, U>
724where
725    U: ?Sized,
726{
727    #[inline]
728    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
729        if f.alternate() {
730            write!(f, "0o")?;
731        }
732        let bit = usize::from(self.0);
733        let leading = 1 << (bit % 3);
734        let num_zeroes = bit / 3;
735        write!(f, "{leading}{:0<num_zeroes$}", "")
736    }
737}
738
739/// Formats this flag's mask (`1 << bit`) in lowercase hexadecimal.
740impl<T, U> fmt::LowerHex for BitFlag<T, U>
741where
742    U: ?Sized,
743{
744    #[inline]
745    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
746        if f.alternate() {
747            write!(f, "0x")?;
748        }
749        let bit = usize::from(self.0);
750        let leading = 1 << (bit % 4);
751        let num_zeroes = bit / 4;
752        write!(f, "{leading}{:0<num_zeroes$}", "")
753    }
754}
755
756/// Formats this flag's mask (`1 << bit`) in uppercase hexadecimal.
757impl<T, U> fmt::UpperHex for BitFlag<T, U>
758where
759    U: ?Sized,
760{
761    #[inline]
762    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
763        if f.alternate() {
764            write!(f, "0x")?;
765        }
766        let bit = usize::from(self.0);
767        let leading = 1 << (bit % 4);
768        let num_zeroes = bit / 4;
769        write!(f, "{leading}{:0<num_zeroes$}", "")
770    }
771}
772
773impl<T, U> FromStr for BitFlag<T, U>
774where
775    T: util::IntOpsExt,
776    U: ?Sized + BitFlagsSpec<T>,
777{
778    type Err = BitFlagParseError;
779
780    #[inline]
781    fn from_str(input: &str) -> Result<Self, Self::Err> {
782        Self::try_from_str(input, U::DEFAULT_NAME_MATCHER)
783    }
784}
785
786#[cfg(feature = "serde")]
787#[cfg_attr(docsrs, doc(cfg(feature = "serde")))]
788impl<T, U> serde::Serialize for BitFlag<T, U>
789where
790    U: ?Sized + BitFlagsSpec<T>,
791{
792    #[inline]
793    fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
794    where
795        S: serde::Serializer,
796    {
797        // Replicates the behavior of `bitflags`-based implementation.
798        serializer.collect_str(&self)
799    }
800}
801
802#[cfg(feature = "arbitrary")]
803#[cfg_attr(docsrs, doc(cfg(feature = "arbitrary")))]
804impl<'a, T, U> arbitrary::Arbitrary<'a> for BitFlag<T, U>
805where
806    U: ?Sized,
807{
808    #[inline]
809    fn arbitrary(u: &mut arbitrary::Unstructured<'a>) -> arbitrary::Result<Self> {
810        u8::arbitrary(u).map(|u| Self(u, PhantomData, PhantomData))
811    }
812
813    #[inline]
814    fn size_hint(depth: usize) -> (usize, Option<usize>) {
815        u8::size_hint(depth)
816    }
817}
818
819#[cfg(feature = "bytemuck")]
820#[cfg_attr(docsrs, doc(cfg(feature = "bytemuck")))]
821// SAFETY: All fields are valid when their memory is entirely zeroed out.
822unsafe impl<T, U> bytemuck::Zeroable for BitFlag<T, U> where U: ?Sized {}
823
824#[cfg(feature = "bytemuck")]
825#[cfg_attr(docsrs, doc(cfg(feature = "bytemuck")))]
826// SAFETY: The type is Copy, Zeroable, has no padding bytes, and any
827// bit pattern of the correct size is a valid instance of this type.
828unsafe impl<T, U> bytemuck::Pod for BitFlag<T, U>
829where
830    T: 'static,
831    U: 'static + ?Sized,
832{
833}
834
835#[cfg(feature = "serde")]
836#[cfg_attr(docsrs, doc(cfg(feature = "serde")))]
837impl<'de, T, U> serde::Deserialize<'de> for BitFlag<T, U>
838where
839    T: util::IntOpsExt,
840    U: ?Sized + BitFlagsSpec<T>,
841{
842    #[inline]
843    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
844    where
845        D: serde::Deserializer<'de>,
846    {
847        deserializer.deserialize_str(BitFlagVisitor::<T, U>(PhantomData, PhantomData))
848    }
849}
850
851#[cfg(feature = "serde")]
852#[cfg_attr(docsrs, doc(cfg(feature = "serde")))]
853impl<T, U> serde::de::Visitor<'_> for BitFlagVisitor<T, U>
854where
855    T: util::IntOpsExt,
856    U: ?Sized + BitFlagsSpec<T>,
857{
858    type Value = BitFlag<T, U>;
859
860    fn expecting(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
861        fmt_expected(f, U::FLAG_NAMES)
862    }
863
864    #[inline]
865    fn visit_str<E>(self, value: &str) -> Result<Self::Value, E>
866    where
867        E: serde::de::Error,
868    {
869        Self::Value::from_str(value).map_err(E::custom)
870    }
871}
872
873/// An error encountered while parsing flags from text.
874#[cfg_attr(
875    not(feature = "alloc"),
876    expect(
877        missing_copy_implementations,
878        reason = "the payload is feature-dependent"
879    )
880)]
881#[derive(Clone, Debug)]
882pub struct BitFlagParseError {
883    #[cfg(not(feature = "alloc"))]
884    given: (),
885    #[cfg(feature = "alloc")]
886    given: alloc::string::String,
887
888    expected: &'static [&'static [&'static str]],
889}
890
891impl fmt::Display for BitFlagParseError {
892    #[expect(clippy::missing_inline_in_public_items, reason = "bloats binary size")]
893    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
894        let named_flags = self
895            .expected
896            .iter()
897            .filter(|variants| !variants.is_empty())
898            .count();
899
900        if named_flags == 0 {
901            write!(f, "unrecognized hex flag, ")?;
902        } else {
903            write!(f, "unrecognized named flag or hex flag, ")?;
904        }
905        #[cfg(feature = "alloc")]
906        write!(f, "given: \"{}\", expected ", self.given)?;
907        #[cfg(not(feature = "alloc"))]
908        write!(f, "expected ")?;
909        fmt_expected(f, self.expected)?;
910
911        #[cfg(not(feature = "alloc"))]
912        let () = self.given;
913
914        Ok(())
915    }
916}
917
918impl core::error::Error for BitFlagParseError {}
919
920/// Writes the expected flag format for [`BitFlagParseError`] and serde
921/// visitors.
922#[inline]
923fn fmt_expected(
924    f: &mut fmt::Formatter<'_>,
925    expected: &'static [&'static [&'static str]],
926) -> fmt::Result {
927    let named_flags = expected
928        .iter()
929        .filter(|variants| !variants.is_empty())
930        .count();
931
932    if named_flags == 0 {
933        write!(f, "a valid one-bit hex flag with \"0x\" prefix")?;
934    } else {
935        fmt_expected_name(f, expected)?;
936        write!(f, ", or a valid one-bit hex flag with \"0x\" prefix")?;
937    }
938    Ok(())
939}
940
941/// An error returned when parsing a named flag fails.
942#[cfg_attr(
943    not(feature = "alloc"),
944    expect(
945        missing_copy_implementations,
946        reason = "the payload is feature-dependent"
947    )
948)]
949#[derive(Clone, Debug)]
950pub struct BitFlagParseNameError {
951    #[cfg(not(feature = "alloc"))]
952    given: (),
953    #[cfg(feature = "alloc")]
954    given: alloc::string::String,
955
956    expected: &'static [&'static [&'static str]],
957}
958
959impl fmt::Display for BitFlagParseNameError {
960    #[expect(clippy::missing_inline_in_public_items, reason = "bloats binary size")]
961    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
962        let named_flags = self
963            .expected
964            .iter()
965            .filter(|variants| !variants.is_empty())
966            .count();
967
968        if named_flags == 0 {
969            write!(f, "does not support named flags")?;
970        } else {
971            write!(f, "unrecognized named flag, ")?;
972            #[cfg(feature = "alloc")]
973            write!(f, "given: \"{}\", expected ", self.given)?;
974            #[cfg(not(feature = "alloc"))]
975            write!(f, "expected ")?;
976            fmt_expected(f, self.expected)?;
977        }
978
979        #[cfg(not(feature = "alloc"))]
980        let () = self.given;
981
982        Ok(())
983    }
984}
985
986impl core::error::Error for BitFlagParseNameError {}
987
988/// Writes the expected named-flag format.
989#[inline]
990fn fmt_expected_name(
991    f: &mut fmt::Formatter<'_>,
992    expected: &'static [&'static [&'static str]],
993) -> fmt::Result {
994    let mut separator = " ";
995
996    write!(f, "one of")?;
997    for names in expected {
998        if let Some(name) = names.first() {
999            write!(f, "{separator}\"{name}\"")?;
1000            separator = ", ";
1001        }
1002    }
1003    Ok(())
1004}
1005
1006/// An error returned when parsing a hexadecimal flag fails.
1007#[cfg_attr(
1008    not(feature = "alloc"),
1009    expect(
1010        missing_copy_implementations,
1011        reason = "the payload is feature-dependent"
1012    )
1013)]
1014#[derive(Clone, Debug)]
1015pub struct BitFlagParseHexError {
1016    #[cfg(not(feature = "alloc"))]
1017    given: (),
1018    #[cfg(feature = "alloc")]
1019    given: alloc::string::String,
1020}
1021
1022impl fmt::Display for BitFlagParseHexError {
1023    #[expect(clippy::missing_inline_in_public_items, reason = "bloats binary size")]
1024    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
1025        write!(f, "unrecognized hex flag")?;
1026
1027        #[cfg(feature = "alloc")]
1028        write!(f, ", given: \"{}\"", self.given)?;
1029        write!(f, ", expected a valid one-bit hex flag with \"0x\" prefix")?;
1030
1031        #[cfg(not(feature = "alloc"))]
1032        let () = self.given;
1033
1034        Ok(())
1035    }
1036}
1037
1038impl core::error::Error for BitFlagParseHexError {}
1039
1040/// An error returned when converting [`BitFlags`] into a [`BitFlag`] fails.
1041///
1042/// The conversion fails if the flag set contains zero or more than one set
1043/// bit, or if the single set bit is not valid for the tag.
1044#[derive(Copy, Clone, Eq, PartialEq, Ord, PartialOrd, Hash, Default, Debug)]
1045pub struct BitFlagFromFlagsError;
1046
1047impl fmt::Display for BitFlagFromFlagsError {
1048    #[expect(clippy::missing_inline_in_public_items, reason = "bloats binary size")]
1049    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
1050        write!(f, "flags do not contain exactly one valid bit")
1051    }
1052}
1053
1054impl core::error::Error for BitFlagFromFlagsError {}