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