Skip to main content

bitflags_lite/
flags.rs

1use core::any::type_name;
2use core::borrow::Borrow;
3use core::cmp::Ordering;
4use core::fmt;
5use core::hash::{Hash, Hasher};
6use core::iter::{FromIterator, IntoIterator};
7use core::marker::PhantomData;
8use core::ops::{
9    BitAnd, BitAndAssign, BitOr, BitOrAssign, BitXor, BitXorAssign, Not, Shl, Sub, SubAssign,
10};
11use core::str::FromStr;
12
13use crate::{BitFlag, BitFlagsIter, BitFlagsSpec, NameMatcher, util};
14
15/// A type-safe set of flags stored in an integer `T`.
16///
17/// The second type parameter `U` is a tag type that distinguishes flag sets
18/// that share the same storage type.
19/// When `U` implements `BitFlagsSpec`, it defines which bits are valid.
20///
21/// `BitFlags` is `#[repr(transparent)]` over `T`, so it has the same layout as
22/// `T`.
23///
24/// # Invalid bits
25///
26/// A `BitFlags` value is not guaranteed to contain only valid bits.
27/// [`BitFlags::from_bits_retain`] can create a value with any bit pattern of
28/// `T`, and raw-bit operations such as [`BitFlags::insert`],
29/// [`BitFlags::toggle`], [`BitFlags::union`], and bitwise operators preserve
30/// invalid bits. Use [`BitFlags::truncate`], [`BitFlags::try_from_bits`], or
31/// [`BitFlags::from_bits_truncate`] when a value must be restricted to
32/// [`BitFlagsSpec::VALID_BITS_MASK`].
33///
34/// [`BitFlags::bits`] returns the raw stored bits, including any invalid bits
35/// that are present.
36///
37/// # Examples
38///
39/// ```
40/// use bitflags_lite::{BitFlags, BitFlagsSpec, NameMatcher};
41///
42/// struct PermissionsTag;
43///
44/// impl BitFlagsSpec<u32> for PermissionsTag {
45///     const VALID_BITS_MASK: u32 = 0b111;
46///     const FLAG_NAMES: &[&[&str]] = &[&["read"], &["write"], &["exec", "execute"]];
47///     const COMBINED_NAMES: &[(u32, &str)] = &[(0, "none"), (0b111, "rwx")];
48///     const DEFAULT_NAME_MATCHER: NameMatcher = NameMatcher::Exact;
49/// }
50///
51/// type Permissions = BitFlags<u32, PermissionsTag>;
52///
53/// const READ: Permissions = Permissions::try_from_bits(1 << 0).unwrap();
54/// const WRITE: Permissions = Permissions::try_from_bits(1 << 1).unwrap();
55/// const EXEC: Permissions = Permissions::try_from_bits(1 << 2).unwrap();
56///
57/// let rw = READ | WRITE;
58/// assert!(rw.contains(READ));
59/// assert!(rw.contains(WRITE));
60/// assert!(!rw.contains(EXEC));
61/// ```
62///
63/// # Method summary
64///
65/// - [`empty()`](Self::empty): Return a set with no bits set.
66/// - [`all_valid()`](Self::all_valid): Return a set with all valid bits set.
67/// - [`from_bits_retain()`](Self::from_bits_retain): Convert from raw bits
68///   without validation.
69/// - [`from_bits_truncate()`](Self::from_bits_truncate): Convert from raw bits,
70///   clearing invalid bits.
71/// - [`try_from_bits()`](Self::try_from_bits): Convert from raw bits, rejecting
72///   invalid bits.
73/// - [`from_bit_retain()`](Self::from_bit_retain): Return a set with only the
74///   bit at a given index, without validation.
75/// - [`from_bit_truncate()`](Self::from_bit_truncate): Return a set with only
76///   the bit at a given index, clearing it if invalid.
77/// - [`try_from_bit()`](Self::try_from_bit): Return a set with only the bit at
78///   a given index, rejecting it if invalid.
79/// - [`from_flag()`](Self::from_flag): Convert from a single [`BitFlag`].
80/// - [`try_from_str()`](Self::try_from_str): Parse from a separated string.
81/// - [`bits()`](Self::bits): Return the raw stored bits.
82/// - [`bits_mut()`](Self::bits_mut): Return a mutable reference to the raw
83///   bits.
84/// - [`into_bits()`](Self::into_bits): Consume the set and return the raw bits.
85/// - [`iter()`](Self::iter): Return an iterator over the set bits.
86/// - [`is_empty()`](Self::is_empty): Return `true` if no bits are set.
87/// - [`is_valid()`](Self::is_valid): Return `true` if all set bits are valid
88///   for the tag.
89/// - [`intersects()`](Self::intersects): Return `true` if any bits are shared.
90/// - [`contains()`](Self::contains): Return `true` if all bits of another set
91///   are present.
92/// - [`insert()`](Self::insert): Add all bits of another set.
93/// - [`remove()`](Self::remove): Remove all bits of another set.
94/// - [`toggle()`](Self::toggle): Flip all bits of another set.
95/// - [`set()`](Self::set): Insert or remove bits based on a boolean.
96/// - [`clear()`](Self::clear): Remove all bits, valid and invalid.
97/// - [`truncate()`](Self::truncate): Remove all invalid bits.
98/// - [`intersection()`](Self::intersection): Return the bits set in both sets.
99/// - [`union()`](Self::union): Return the bits set in either set.
100/// - [`difference()`](Self::difference): Return the bits set in `self` but not
101///   in `other`.
102/// - [`symmetric_difference()`](Self::symmetric_difference): Return the bits
103///   set in exactly one set.
104/// - [`complement()`](Self::complement): Return the valid bits not set in
105///   `self`.
106#[repr(transparent)]
107pub struct BitFlags<T, U>(
108    T,
109    // PhantomData<fn() -> *const U> is Send + Sync and covariant over `U`,
110    // and `U` is not required to be sized as pattern uses only its pointer.
111    // See: https://doc.rust-lang.org/nomicon/phantom-data.html#table-of-phantomdata-patterns
112    PhantomData<fn() -> *const U>,
113)
114where
115    U: ?Sized;
116
117/// Serde visitor for deserializing [`BitFlags`] from a string.
118///
119/// The accepted format is the same as [`BitFlags`]'s
120/// [`FromStr`](core::str::FromStr) implementation: an empty string, a
121/// `|`-separated list of named flags, and/or `0x`-prefixed hexadecimal flags.
122/// Named flags are matched according to
123/// [`BitFlagsSpec::DEFAULT_NAME_MATCHER`].
124///
125/// Deserialization does not validate the parsed bits against
126/// [`BitFlagsSpec::VALID_BITS_MASK`].
127/// The resulting value may contain invalid bits.
128#[cfg(feature = "serde")]
129struct BitFlagsVisitor<T, U>(PhantomData<T>, PhantomData<fn() -> *const U>)
130where
131    U: ?Sized;
132
133impl<T, U> BitFlags<T, U>
134where
135    U: ?Sized,
136{
137    /// Creates a flag set with no bits set (zero bits).
138    ///
139    /// This is equivalent to `from_bits_retain(0)`.
140    #[inline]
141    #[must_use]
142    pub const fn empty() -> Self
143    where
144        T: util::IntOpsExt,
145    {
146        Self(T::ZERO, PhantomData)
147    }
148
149    /// Creates a flag set containing all valid bits for this tag.
150    ///
151    /// This returns exactly [`BitFlagsSpec::VALID_BITS_MASK`]. Invalid bits
152    /// are never set.
153    ///
154    /// The method is named `all_valid` rather than `all` because a
155    /// `BitFlags` value can contain invalid bits after
156    /// [`BitFlags::from_bits_retain`].
157    /// In that case, "all" would be ambiguous: all bits of the storage
158    /// type, all bits currently set, or all valid bits?
159    ///
160    /// `all_valid` makes the meaning explicit.
161    #[doc(alias = "all")]
162    #[inline]
163    #[must_use]
164    pub const fn all_valid() -> Self
165    where
166        T: 'static,
167        U: BitFlagsSpec<T>,
168    {
169        Self(U::VALID_BITS_MASK, PhantomData)
170    }
171
172    /// Creates a flag set from raw bits without validation.
173    ///
174    /// The resulting value may contain bits not present in
175    /// [`BitFlagsSpec::VALID_BITS_MASK`].
176    /// Such invalid bits are preserved by this constructor and by
177    /// raw-bit operations unless an operation clears them, such as
178    /// [`BitFlags::truncate`], [`BitFlags::complement`],
179    /// [`BitFlags::from_bits_truncate`], or [`Not`](core::ops::Not).
180    #[inline]
181    #[must_use]
182    pub const fn from_bits_retain(bits: T) -> Self {
183        Self(bits, PhantomData)
184    }
185
186    /// Returns the raw bits stored in this flag set.
187    ///
188    /// The returned value may contain invalid bits if this value was
189    /// created with [`BitFlags::from_bits_retain`] and has not since
190    /// been passed through an operation that clears invalid bits, such
191    /// as [`BitFlags::truncate`] or [`BitFlags::complement`].
192    #[inline]
193    #[must_use]
194    pub const fn bits(&self) -> T
195    where
196        T: Copy,
197    {
198        self.0
199    }
200
201    /// Returns a mutable reference to the raw bits stored in this flag set.
202    ///
203    /// Mutating the returned value can introduce invalid bits.
204    /// Use [`BitFlags::truncate`] afterwards if the value must contain only
205    /// valid bits.
206    #[inline]
207    #[must_use]
208    pub const fn bits_mut(&mut self) -> &mut T {
209        &mut self.0
210    }
211
212    /// Consumes this flag set and returns the raw bits.
213    ///
214    /// This is the consuming counterpart to [`BitFlags::bits`].
215    /// Like [`BitFlags::bits`], the returned value may contain bits not present
216    /// in [`BitFlagsSpec::VALID_BITS_MASK`].
217    ///
218    /// This method does not require `T: Copy`, so it is available for storage
219    /// types that are not `Copy`.
220    #[inline]
221    #[must_use]
222    pub fn into_bits(self) -> T {
223        self.0
224    }
225
226    /// Returns an iterator over the individual set bits in this flag set.
227    ///
228    /// The iterator yields [`BitFlag`] values from the least significant bit to
229    /// the most significant bit.
230    /// It includes invalid bits if they are present.
231    #[inline]
232    pub const fn iter(self) -> BitFlagsIter<T, U>
233    where
234        T: Copy + Not<Output = T> + BitAndAssign<T> + util::IntOpsExt,
235    {
236        BitFlagsIter::new(self)
237    }
238
239    /// Parses a flag set from a string.
240    ///
241    /// The input may be empty, which returns [`BitFlags::empty`].
242    /// Otherwise it is split on given `separator`, and each part is trimmed and
243    /// matched against the names in [`BitFlagsSpec::FLAG_NAMES`].
244    /// Match behavior is specified by `name_matcher`.
245    /// A part may also be a hexadecimal flag with a `0x` prefix.
246    ///
247    /// This method does not validate or truncate the parsed bits against
248    /// [`BitFlagsSpec::VALID_BITS_MASK`].
249    /// The returned value may contain invalid bits.
250    ///
251    /// Note that if `separator` is an empty string, the method would split into
252    /// individual characters, which is almost certainly not intended.
253    ///
254    /// # Errors
255    ///
256    /// Returns [`BitFlagsParseError`] if `input` is not a `separator`-separated
257    /// list of named flags or hexadecimal flags.
258    #[inline]
259    pub fn try_from_str(
260        input: &str,
261        separator: &'static str,
262        name_matcher: NameMatcher,
263    ) -> Result<Self, BitFlagsParseError>
264    where
265        T: 'static + Copy + BitOrAssign<T> + util::IntOpsExt,
266        U: BitFlagsSpec<T>,
267    {
268        if input.trim().is_empty() {
269            return Ok(Self::empty());
270        }
271
272        let mut this = Self::empty();
273        let input = input.split(separator);
274        'a: for input in input {
275            let input = input.trim();
276
277            let flag = BitFlag::from_name_opt(input, name_matcher);
278            if let Some(flag) = flag {
279                this |= flag;
280                continue;
281            }
282
283            for (flags, name) in U::COMBINED_NAMES {
284                if input == *name {
285                    this |= Self::from_bits_retain(*flags);
286                    continue 'a;
287                }
288            }
289
290            if let Some(maybe_hex_flag) = input.strip_prefix("0x") {
291                // `from_str_radix` allows optional `+` prefix.
292                // Parsing here should prohibit it.
293                if !maybe_hex_flag.starts_with('+') {
294                    let bits = T::from_str_radix(maybe_hex_flag, 16);
295                    if let Ok(bits) = bits {
296                        this |= Self::from_bits_retain(bits);
297                        continue;
298                    }
299                }
300            }
301
302            #[cfg(feature = "alloc")]
303            let given = alloc::string::ToString::to_string(input);
304            #[cfg(not(feature = "alloc"))]
305            let given = ();
306            let expected = U::FLAG_NAMES;
307            return Err(BitFlagsParseError {
308                given,
309                separator,
310                expected,
311            });
312        }
313
314        Ok(this)
315    }
316}
317
318// Const traits are not stable yet, so the const methods are implemented for
319// each integer type separately via a macro.
320// See: https://github.com/rust-lang/rust/issues/143874
321macro_rules! impl_bit_flags {
322    ($ty:ty) => {
323        impl<U> BitFlags<$ty, U>
324        where
325            U: ?Sized,
326        {
327            /// Creates a flag set from a single [`BitFlag`].
328            ///
329            /// The bit index is not validated against
330            /// [`BitFlagsSpec::VALID_BITS_MASK`].
331            /// The resulting value may contain an invalid bit.
332            ///
333            /// If the flag's index is outside the width of `T`, the result is
334            /// [`BitFlags::empty`].
335            #[inline]
336            #[must_use]
337            pub const fn from_flag(flag: BitFlag<$ty, U>) -> Self {
338                Self::from_bit_retain(flag.bit())
339            }
340
341            /// Creates a flag set with only the bit at index `bit` set.
342            ///
343            /// The bit index is not validated against
344            /// [`BitFlagsSpec::VALID_BITS_MASK`].
345            /// The resulting value may contain an invalid bit.
346            /// Use [`BitFlags::try_from_bit`] or
347            /// [`BitFlags::from_bits_truncate`] when validation or truncation
348            /// is required.
349            ///
350            /// If `bit` is outside the width of `T`, the result is
351            /// [`BitFlags::empty`].
352            #[inline]
353            #[must_use]
354            pub const fn from_bit_retain(bit: u8) -> Self {
355                #[expect(clippy::as_conversions, reason = "supported in const functions")]
356                let bit = bit as u32;
357                if bit < <$ty>::BITS {
358                    Self::from_bits_retain(1 << bit)
359                } else {
360                    Self::empty()
361                }
362            }
363
364            /// Creates a flag set with only the bit at index `bit` set,
365            /// clearing the bit if it is not valid for the tag.
366            ///
367            /// This is the bit-index counterpart to
368            /// [`BitFlags::from_bits_truncate`].
369            /// If `bit` is outside the width of `T`, or if the corresponding
370            /// bit is not present in [`BitFlagsSpec::VALID_BITS_MASK`], the
371            /// result is [`BitFlags::empty`].
372            ///
373            /// The result always contains either exactly one bit or no bits.
374            ///
375            /// Use [`BitFlags::try_from_bit`] when the caller needs to
376            /// distinguish a valid index from an invalid one.
377            #[inline]
378            #[must_use]
379            pub const fn from_bit_truncate(bit: u8) -> Self
380            where
381                U: BitFlagsSpec<$ty>,
382            {
383                #[expect(clippy::as_conversions, reason = "supported in const functions")]
384                let bit = bit as u32;
385                if bit < <$ty>::BITS {
386                    Self::from_bits_truncate(1 << bit)
387                } else {
388                    Self::empty()
389                }
390            }
391
392            /// Creates a flag set from bit index `bit` if that bit is valid.
393            ///
394            /// Returns `None` if `bit` is outside the width of `T` or if the
395            /// resulting bit is not present in
396            /// [`BitFlagsSpec::VALID_BITS_MASK`].
397            #[inline]
398            #[must_use]
399            pub const fn try_from_bit(bit: u8) -> Option<Self>
400            where
401                U: BitFlagsSpec<$ty>,
402            {
403                #[expect(clippy::as_conversions, reason = "supported in const functions")]
404                let bit = bit as u32;
405                if bit < <$ty>::BITS {
406                    Self::try_from_bits(1 << bit)
407                } else {
408                    None
409                }
410            }
411
412            /// Creates a flag set from raw bits if all set bits are valid.
413            ///
414            /// Returns `None` if `bits` contains any bit not present in
415            /// [`BitFlagsSpec::VALID_BITS_MASK`].
416            ///
417            /// Use [`BitFlags::from_bits_retain`] to keep invalid bits, or
418            /// [`BitFlags::from_bits_truncate`] to clear them.
419            #[doc(alias = "from_bits")]
420            #[inline]
421            #[must_use]
422            pub const fn try_from_bits(bits: $ty) -> Option<Self>
423            where
424                U: BitFlagsSpec<$ty>,
425            {
426                if bits & !U::VALID_BITS_MASK == 0 {
427                    Some(Self(bits, PhantomData))
428                } else {
429                    None
430                }
431            }
432
433            /// Creates a flag set from raw bits, clearing any bits not present in
434            /// [`BitFlagsSpec::VALID_BITS_MASK`].
435            ///
436            /// This always succeeds and never stores invalid bits.
437            #[inline]
438            #[must_use]
439            pub const fn from_bits_truncate(bits: $ty) -> Self
440            where
441                U: BitFlagsSpec<$ty>,
442            {
443                Self(bits & U::VALID_BITS_MASK, PhantomData)
444            }
445
446            /// Returns `true` if no bits are set.
447            ///
448            /// This checks the raw stored bits.
449            /// A value containing only invalid bits is not empty.
450            #[inline]
451            #[must_use]
452            pub const fn is_empty(&self) -> bool {
453                self.0 == 0
454            }
455
456            /// Returns `true` if all set bits in `self` are valid for this tag.
457            ///
458            /// A `BitFlags` value can contain bits not present in
459            /// [`BitFlagsSpec::VALID_BITS_MASK`] after construction with
460            /// [`BitFlags::from_bits_retain`] or after raw-bit operations such as
461            /// [`BitFlags::insert`], [`BitFlags::toggle`], and [`BitFlags::union`].
462            /// This method returns `false` for such values.
463            ///
464            /// Use [`BitFlags::truncate`] or [`BitFlags::from_bits_truncate`] to
465            /// restrict a value to its valid bits.
466            #[doc(alias = "is_all")]
467            #[inline]
468            #[must_use]
469            pub const fn is_valid(&self) -> bool
470            where
471                U: BitFlagsSpec<$ty>,
472            {
473                self.bits() & !U::VALID_BITS_MASK == 0
474            }
475
476            /// Returns `true` if `self` and `other` have at least one set bit in
477            /// common.
478            ///
479            /// This compares raw bits.
480            /// Invalid bits participate in the comparison if they are present.
481            #[inline]
482            #[must_use]
483            pub const fn intersects(&self, other: Self) -> bool {
484                (self.0 & other.0) != 0
485            }
486
487            /// Returns `true` if all bits set in `other` are also set in `self`.
488            ///
489            /// This compares raw bits.
490            /// Invalid bits participate in the comparison if they are present.
491            #[inline]
492            #[must_use]
493            pub const fn contains(&self, other: Self) -> bool {
494                (self.0 & other.0) == other.0
495            }
496
497            /// Inserts all bits from `other`.
498            ///
499            /// This is a raw-bit operation.
500            /// It does not validate or clear invalid bits.
501            /// Inserting `other` can introduce invalid bits into `self`.
502            #[inline]
503            pub const fn insert(&mut self, other: Self) {
504                self.0 |= other.0;
505            }
506
507            /// Removes all bits from `self`, including any invalid bits.
508            ///
509            /// This is equivalent to assigning [`BitFlags::empty`] to `self`.
510            #[inline]
511            pub const fn clear(&mut self) {
512                self.0 = 0;
513            }
514
515            /// Clears all bits not present in
516            /// [`BitFlagsSpec::VALID_BITS_MASK`].
517            ///
518            /// This leaves `self` unchanged if it already contains only valid
519            /// bits.
520            #[inline]
521            pub const fn truncate(&mut self)
522            where
523                U: BitFlagsSpec<$ty>,
524            {
525                self.0 &= U::VALID_BITS_MASK
526            }
527
528            /// Removes all bits from `other`.
529            ///
530            /// This is a raw-bit operation.
531            /// It cannot introduce new bits, but it does not clear any other
532            /// invalid bits that may already be present.
533            #[inline]
534            pub const fn remove(&mut self, other: Self) {
535                self.0 &= !other.0;
536            }
537
538            /// Toggles all bits from `other`.
539            ///
540            /// This is a raw-bit operation.
541            /// It does not validate or clear invalid bits.
542            /// Toggling `other` can introduce invalid bits into `self`.
543            #[inline]
544            pub const fn toggle(&mut self, other: Self) {
545                self.0 ^= other.0;
546            }
547
548            /// Inserts `other` if `value` is `true`, or removes `other` if
549            /// `value` is `false`.
550            ///
551            /// This is a raw-bit operation and follows the same invalid-bit
552            /// behavior as [`BitFlags::insert`] and [`BitFlags::remove`].
553            #[inline]
554            pub const fn set(&mut self, other: Self, value: bool) {
555                if value {
556                    self.insert(other)
557                } else {
558                    self.remove(other)
559                }
560            }
561
562            /// Returns the intersection of `self` and `other`.
563            ///
564            /// This is a raw-bit operation.
565            /// Invalid bits in either operand are preserved when they are set
566            /// in both operands.
567            #[inline]
568            #[must_use]
569            pub const fn intersection(self, other: Self) -> Self {
570                Self(self.0 & other.0, PhantomData)
571            }
572
573            /// Returns the union of `self` and `other`.
574            ///
575            /// This is a raw-bit operation.
576            /// Invalid bits in either operand are preserved.
577            #[inline]
578            #[must_use]
579            pub const fn union(self, other: Self) -> Self {
580                Self(self.0 | other.0, PhantomData)
581            }
582
583            /// Returns the difference of `self` and `other`.
584            ///
585            /// The result contains bits that are set in `self` but not in
586            /// `other`.
587            /// This is a raw-bit operation.
588            /// Invalid bits already present in `self` are preserved unless they
589            /// are also set in `other`.
590            #[inline]
591            #[must_use]
592            pub const fn difference(self, other: Self) -> Self {
593                Self(self.0 & !other.0, PhantomData)
594            }
595
596            /// Returns the symmetric difference of `self` and `other`.
597            ///
598            /// The result contains bits that are set in exactly one of `self`
599            /// or `other`.
600            /// This is a raw-bit operation.
601            /// Invalid bits participate and may be preserved.
602            #[inline]
603            #[must_use]
604            pub const fn symmetric_difference(self, other: Self) -> Self {
605                Self(self.0 ^ other.0, PhantomData)
606            }
607
608            /// Returns the complement of `self`.
609            ///
610            /// All valid bits not set in `self` become set, and all invalid
611            /// bits become unset.
612            /// The result always contains only valid bits.
613            #[inline]
614            #[must_use]
615            pub const fn complement(self) -> Self
616            where
617                U: BitFlagsSpec<$ty>,
618            {
619                Self(!self.0 & U::VALID_BITS_MASK, PhantomData)
620            }
621        }
622
623        impl<U> From<BitFlags<$ty, U>> for $ty
624        where
625            U: ?Sized,
626        {
627            #[inline]
628            fn from(flags: BitFlags<$ty, U>) -> Self {
629                flags.0
630            }
631        }
632    };
633}
634
635impl_bit_flags!(u8);
636impl_bit_flags!(u16);
637impl_bit_flags!(u32);
638impl_bit_flags!(u64);
639impl_bit_flags!(u128);
640impl_bit_flags!(usize);
641
642impl_bit_flags!(i8);
643impl_bit_flags!(i16);
644impl_bit_flags!(i32);
645impl_bit_flags!(i64);
646impl_bit_flags!(i128);
647impl_bit_flags!(isize);
648
649impl<T, U> Copy for BitFlags<T, U>
650where
651    T: Copy,
652    U: ?Sized,
653{
654}
655
656impl<T, U> Clone for BitFlags<T, U>
657where
658    T: Clone,
659    U: ?Sized,
660{
661    #[inline]
662    fn clone(&self) -> Self {
663        Self(self.0.clone(), PhantomData)
664    }
665}
666
667impl<T, U> Eq for BitFlags<T, U>
668where
669    T: Eq,
670    U: ?Sized,
671{
672}
673
674impl<T, U> PartialEq for BitFlags<T, U>
675where
676    T: PartialEq,
677    U: ?Sized,
678{
679    #[inline]
680    fn eq(&self, other: &Self) -> bool {
681        self.0 == other.0
682    }
683}
684
685impl<T, U> PartialEq<BitFlag<T, U>> for BitFlags<T, U>
686where
687    T: Copy + PartialEq + util::IntOpsExt,
688    U: ?Sized,
689{
690    #[inline]
691    fn eq(&self, other: &BitFlag<T, U>) -> bool {
692        self.eq(&Self::from(*other))
693    }
694}
695
696impl<T, U> PartialEq<BitFlags<T, U>> for BitFlag<T, U>
697where
698    T: Copy + PartialEq + util::IntOpsExt,
699    U: ?Sized,
700{
701    #[inline]
702    fn eq(&self, other: &BitFlags<T, U>) -> bool {
703        BitFlags::from(*self).eq(other)
704    }
705}
706
707impl<T, U> Ord for BitFlags<T, U>
708where
709    T: Ord,
710    U: ?Sized,
711{
712    #[inline]
713    fn cmp(&self, other: &Self) -> Ordering {
714        self.0.cmp(&other.0)
715    }
716}
717
718impl<T, U> PartialOrd for BitFlags<T, U>
719where
720    T: PartialOrd,
721    U: ?Sized,
722{
723    #[inline]
724    fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
725        self.0.partial_cmp(&other.0)
726    }
727}
728
729impl<T, U> PartialOrd<BitFlag<T, U>> for BitFlags<T, U>
730where
731    T: Copy + PartialOrd + util::IntOpsExt,
732    U: ?Sized,
733{
734    #[inline]
735    fn partial_cmp(&self, other: &BitFlag<T, U>) -> Option<Ordering> {
736        self.partial_cmp(&Self::from(*other))
737    }
738}
739
740impl<T, U> PartialOrd<BitFlags<T, U>> for BitFlag<T, U>
741where
742    T: Copy + PartialOrd + util::IntOpsExt,
743    U: ?Sized,
744{
745    #[inline]
746    fn partial_cmp(&self, other: &BitFlags<T, U>) -> Option<Ordering> {
747        BitFlags::from(*self).partial_cmp(other)
748    }
749}
750
751impl<T, U> Hash for BitFlags<T, U>
752where
753    T: Hash,
754    U: ?Sized,
755{
756    #[inline]
757    fn hash<H: Hasher>(&self, state: &mut H) {
758        self.0.hash(state);
759    }
760}
761
762impl<T, U> Default for BitFlags<T, U>
763where
764    T: Default,
765    U: ?Sized,
766{
767    #[inline]
768    fn default() -> Self {
769        Self(T::default(), PhantomData)
770    }
771}
772
773impl<T, U> From<BitFlag<T, U>> for BitFlags<T, U>
774where
775    T: util::IntOpsExt,
776    U: ?Sized,
777{
778    #[inline]
779    fn from(flag: BitFlag<T, U>) -> Self {
780        let bit = flag.bit();
781        T::ONE
782            .checked_shl(u32::from(bit))
783            .map_or_else(Self::empty, Self::from_bits_retain)
784    }
785}
786
787impl<T, U> AsRef<T> for BitFlags<T, U>
788where
789    U: ?Sized,
790{
791    #[inline]
792    fn as_ref(&self) -> &T {
793        &self.0
794    }
795}
796
797impl<T, U> Borrow<T> for BitFlags<T, U>
798where
799    U: ?Sized,
800{
801    #[inline]
802    fn borrow(&self) -> &T {
803        &self.0
804    }
805}
806
807impl<T, U> BitAnd<Self> for BitFlags<T, U>
808where
809    T: BitAnd<T, Output = T>,
810    U: ?Sized,
811{
812    type Output = Self;
813
814    #[inline]
815    fn bitand(self, rhs: Self) -> Self::Output {
816        Self(self.0 & rhs.0, PhantomData)
817    }
818}
819
820impl<T, U> BitAnd<BitFlag<T, U>> for BitFlags<T, U>
821where
822    T: BitAnd<T, Output = T> + util::IntOpsExt,
823    U: ?Sized,
824{
825    type Output = Self;
826
827    #[inline]
828    fn bitand(self, rhs: BitFlag<T, U>) -> Self::Output {
829        self.bitand(Self::from(rhs))
830    }
831}
832
833impl<T, U> BitAnd<BitFlags<T, U>> for BitFlag<T, U>
834where
835    T: BitAnd<T, Output = T> + util::IntOpsExt,
836    U: ?Sized,
837{
838    type Output = BitFlags<T, U>;
839
840    #[inline]
841    fn bitand(self, rhs: BitFlags<T, U>) -> Self::Output {
842        BitFlags::from(self).bitand(rhs)
843    }
844}
845
846impl<T, U> BitAndAssign<Self> for BitFlags<T, U>
847where
848    T: BitAndAssign<T>,
849    U: ?Sized,
850{
851    #[inline]
852    fn bitand_assign(&mut self, rhs: Self) {
853        self.0 &= rhs.0;
854    }
855}
856
857impl<T, U> BitAndAssign<BitFlag<T, U>> for BitFlags<T, U>
858where
859    T: BitAndAssign<T> + util::IntOpsExt,
860    U: ?Sized,
861{
862    #[inline]
863    fn bitand_assign(&mut self, rhs: BitFlag<T, U>) {
864        self.bitand_assign(Self::from(rhs));
865    }
866}
867
868impl<T, U> BitOr<Self> for BitFlags<T, U>
869where
870    T: BitOr<T, Output = T>,
871    U: ?Sized,
872{
873    type Output = Self;
874
875    #[inline]
876    fn bitor(self, rhs: Self) -> Self::Output {
877        Self(self.0 | rhs.0, PhantomData)
878    }
879}
880
881impl<T, U> BitOr<BitFlag<T, U>> for BitFlags<T, U>
882where
883    T: BitOr<T, Output = T> + util::IntOpsExt,
884    U: ?Sized,
885{
886    type Output = Self;
887
888    #[inline]
889    fn bitor(self, rhs: BitFlag<T, U>) -> Self::Output {
890        self.bitor(Self::from(rhs))
891    }
892}
893
894impl<T, U> BitOr<BitFlags<T, U>> for BitFlag<T, U>
895where
896    T: BitOr<T, Output = T> + util::IntOpsExt,
897    U: ?Sized,
898{
899    type Output = BitFlags<T, U>;
900
901    #[inline]
902    fn bitor(self, rhs: BitFlags<T, U>) -> Self::Output {
903        BitFlags::from(self).bitor(rhs)
904    }
905}
906
907impl<T, U> BitOrAssign<Self> for BitFlags<T, U>
908where
909    T: BitOrAssign<T>,
910    U: ?Sized,
911{
912    #[inline]
913    fn bitor_assign(&mut self, rhs: Self) {
914        self.0 |= rhs.0;
915    }
916}
917
918impl<T, U> BitOrAssign<BitFlag<T, U>> for BitFlags<T, U>
919where
920    T: BitOrAssign<T> + util::IntOpsExt,
921    U: ?Sized,
922{
923    #[inline]
924    fn bitor_assign(&mut self, rhs: BitFlag<T, U>) {
925        self.bitor_assign(Self::from(rhs));
926    }
927}
928
929impl<T, U> BitXor<Self> for BitFlags<T, U>
930where
931    T: BitXor<T, Output = T>,
932    U: ?Sized,
933{
934    type Output = Self;
935
936    #[inline]
937    fn bitxor(self, rhs: Self) -> Self::Output {
938        Self(self.0 ^ rhs.0, PhantomData)
939    }
940}
941
942impl<T, U> BitXor<BitFlag<T, U>> for BitFlags<T, U>
943where
944    T: BitXor<T, Output = T> + util::IntOpsExt,
945    U: ?Sized,
946{
947    type Output = Self;
948
949    #[inline]
950    fn bitxor(self, rhs: BitFlag<T, U>) -> Self::Output {
951        self.bitxor(Self::from(rhs))
952    }
953}
954
955impl<T, U> BitXor<BitFlags<T, U>> for BitFlag<T, U>
956where
957    T: BitXor<T, Output = T> + util::IntOpsExt,
958    U: ?Sized,
959{
960    type Output = BitFlags<T, U>;
961
962    #[inline]
963    fn bitxor(self, rhs: BitFlags<T, U>) -> Self::Output {
964        BitFlags::from(self).bitxor(rhs)
965    }
966}
967
968impl<T, U> BitXorAssign<Self> for BitFlags<T, U>
969where
970    T: BitXorAssign<T>,
971    U: ?Sized,
972{
973    #[inline]
974    fn bitxor_assign(&mut self, rhs: Self) {
975        self.0 ^= rhs.0;
976    }
977}
978
979impl<T, U> BitXorAssign<BitFlag<T, U>> for BitFlags<T, U>
980where
981    T: BitXorAssign<T> + util::IntOpsExt,
982    U: ?Sized,
983{
984    #[inline]
985    fn bitxor_assign(&mut self, rhs: BitFlag<T, U>) {
986        self.bitxor_assign(Self::from(rhs));
987    }
988}
989
990impl<T, U> Sub<Self> for BitFlags<T, U>
991where
992    T: Not<Output = T> + BitAnd<T, Output = T>,
993    U: ?Sized,
994{
995    type Output = Self;
996
997    #[inline]
998    fn sub(self, rhs: Self) -> Self::Output {
999        Self(self.0 & !rhs.0, PhantomData)
1000    }
1001}
1002
1003impl<T, U> Sub<BitFlag<T, U>> for BitFlags<T, U>
1004where
1005    T: Not<Output = T> + BitAnd<T, Output = T> + util::IntOpsExt,
1006    U: ?Sized,
1007{
1008    type Output = Self;
1009
1010    #[inline]
1011    fn sub(self, rhs: BitFlag<T, U>) -> Self::Output {
1012        self.sub(Self::from(rhs))
1013    }
1014}
1015
1016impl<T, U> Sub<BitFlags<T, U>> for BitFlag<T, U>
1017where
1018    T: Not<Output = T> + BitAnd<T, Output = T> + util::IntOpsExt,
1019    U: ?Sized,
1020{
1021    type Output = BitFlags<T, U>;
1022
1023    #[inline]
1024    fn sub(self, rhs: BitFlags<T, U>) -> Self::Output {
1025        BitFlags::from(self).sub(rhs)
1026    }
1027}
1028
1029impl<T, U> SubAssign<Self> for BitFlags<T, U>
1030where
1031    T: Not<Output = T> + BitAndAssign<T>,
1032    U: ?Sized,
1033{
1034    #[inline]
1035    fn sub_assign(&mut self, rhs: Self) {
1036        self.0 &= !rhs.0;
1037    }
1038}
1039
1040impl<T, U> SubAssign<BitFlag<T, U>> for BitFlags<T, U>
1041where
1042    T: Not<Output = T> + BitAndAssign<T> + util::IntOpsExt,
1043    U: ?Sized,
1044{
1045    #[inline]
1046    fn sub_assign(&mut self, rhs: BitFlag<T, U>) {
1047        self.sub_assign(Self::from(rhs));
1048    }
1049}
1050
1051impl<T, U> Not for BitFlags<T, U>
1052where
1053    T: 'static + Not<Output = T> + BitAnd<T, Output = T>,
1054    U: ?Sized + BitFlagsSpec<T>,
1055{
1056    type Output = Self;
1057
1058    #[inline]
1059    fn not(self) -> Self::Output {
1060        Self(!self.0 & U::VALID_BITS_MASK, PhantomData)
1061    }
1062}
1063
1064impl<T, U> fmt::Display for BitFlags<T, U>
1065where
1066    T: 'static
1067        + Copy
1068        + PartialEq
1069        + Not<Output = T>
1070        + BitAnd<T, Output = T>
1071        + Shl<usize, Output = T>
1072        + fmt::LowerHex
1073        + util::IntOpsExt,
1074    U: ?Sized + BitFlagsSpec<T>,
1075{
1076    #[expect(clippy::missing_inline_in_public_items, reason = "bloats binary size")]
1077    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
1078        let mut separator = "";
1079        let mut this = self.0;
1080        for (i, names) in U::FLAG_NAMES.iter().enumerate() {
1081            let Some(i) = u32::try_from(i).ok() else {
1082                break; // `FLAG_NAMES` with more than `u32::MAX` entries.
1083            };
1084            let Some(mask) = T::ONE.checked_shl(i) else {
1085                break;
1086            };
1087            if let Some(name) = names.first() {
1088                let new_this = this & !mask;
1089                if this != new_this {
1090                    write!(f, "{separator}{name}")?;
1091                    separator = " | ";
1092                }
1093                this = new_this;
1094            }
1095        }
1096        if this != T::ZERO {
1097            write!(f, "{separator}{this:#x}")?;
1098        }
1099        Ok(())
1100    }
1101}
1102
1103impl<T, U> fmt::Debug for BitFlags<T, U>
1104where
1105    T: fmt::LowerHex,
1106    U: ?Sized,
1107{
1108    #[expect(clippy::missing_inline_in_public_items, reason = "bloats binary size")]
1109    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
1110        write!(
1111            f,
1112            "BitFlags({:#x}, PhantomData<{}>)",
1113            self.0,
1114            type_name::<U>()
1115        )
1116    }
1117}
1118
1119impl<T, U> fmt::Binary for BitFlags<T, U>
1120where
1121    T: fmt::Binary,
1122    U: ?Sized,
1123{
1124    #[inline]
1125    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
1126        fmt::Binary::fmt(&self.0, f)
1127    }
1128}
1129
1130impl<T, U> fmt::Octal for BitFlags<T, U>
1131where
1132    T: fmt::Octal,
1133    U: ?Sized,
1134{
1135    #[inline]
1136    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
1137        fmt::Octal::fmt(&self.0, f)
1138    }
1139}
1140
1141impl<T, U> fmt::LowerHex for BitFlags<T, U>
1142where
1143    T: fmt::LowerHex,
1144    U: ?Sized,
1145{
1146    #[inline]
1147    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
1148        fmt::LowerHex::fmt(&self.0, f)
1149    }
1150}
1151
1152impl<T, U> fmt::UpperHex for BitFlags<T, U>
1153where
1154    T: fmt::UpperHex,
1155    U: ?Sized,
1156{
1157    #[inline]
1158    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
1159        fmt::UpperHex::fmt(&self.0, f)
1160    }
1161}
1162
1163impl<T, U> FromStr for BitFlags<T, U>
1164where
1165    T: 'static + Copy + BitOrAssign<T> + util::IntOpsExt,
1166    U: ?Sized + BitFlagsSpec<T>,
1167{
1168    type Err = BitFlagsParseError;
1169
1170    #[inline]
1171    fn from_str(input: &str) -> Result<Self, Self::Err> {
1172        Self::try_from_str(input, "|", U::DEFAULT_NAME_MATCHER)
1173    }
1174}
1175
1176impl<T, U> IntoIterator for BitFlags<T, U>
1177where
1178    T: Copy + Not<Output = T> + BitAndAssign<T> + util::IntOpsExt,
1179    U: ?Sized,
1180{
1181    type IntoIter = BitFlagsIter<T, U>;
1182    type Item = BitFlag<T, U>;
1183
1184    #[inline]
1185    fn into_iter(self) -> Self::IntoIter {
1186        BitFlagsIter::new(self)
1187    }
1188}
1189
1190impl<T, U> IntoIterator for &BitFlags<T, U>
1191where
1192    T: Copy + Not<Output = T> + BitAndAssign<T> + util::IntOpsExt,
1193    U: ?Sized,
1194{
1195    type IntoIter = BitFlagsIter<T, U>;
1196    type Item = BitFlag<T, U>;
1197
1198    #[inline]
1199    fn into_iter(self) -> Self::IntoIter {
1200        BitFlagsIter::new(*self)
1201    }
1202}
1203
1204#[expect(
1205    clippy::into_iter_without_iter,
1206    reason = "`iter` and `iter_mut` are the same"
1207)]
1208impl<T, U> IntoIterator for &mut BitFlags<T, U>
1209where
1210    T: Copy + Not<Output = T> + BitAndAssign<T> + util::IntOpsExt,
1211    U: ?Sized,
1212{
1213    type IntoIter = BitFlagsIter<T, U>;
1214    type Item = BitFlag<T, U>;
1215
1216    #[inline]
1217    fn into_iter(self) -> Self::IntoIter {
1218        BitFlagsIter::new(*self)
1219    }
1220}
1221
1222impl<T, U> Extend<Self> for BitFlags<T, U>
1223where
1224    T: BitOrAssign<T>,
1225    U: ?Sized,
1226{
1227    #[inline]
1228    fn extend<I>(&mut self, iter: I)
1229    where
1230        I: IntoIterator<Item = Self>,
1231    {
1232        for other in iter {
1233            self.0 |= other.0;
1234        }
1235    }
1236}
1237
1238impl<T, U> Extend<BitFlag<T, U>> for BitFlags<T, U>
1239where
1240    T: BitOrAssign<T> + util::IntOpsExt,
1241    U: ?Sized,
1242{
1243    #[inline]
1244    fn extend<I>(&mut self, iter: I)
1245    where
1246        I: IntoIterator<Item = BitFlag<T, U>>,
1247    {
1248        for other in iter {
1249            *self |= Self::from(other);
1250        }
1251    }
1252}
1253
1254impl<T, U> FromIterator<Self> for BitFlags<T, U>
1255where
1256    T: BitOrAssign<T> + util::IntOpsExt,
1257    U: ?Sized,
1258{
1259    #[inline]
1260    fn from_iter<I>(iter: I) -> Self
1261    where
1262        I: IntoIterator<Item = Self>,
1263    {
1264        let mut this = Self(T::ZERO, PhantomData);
1265        for other in iter {
1266            this.0 |= other.0;
1267        }
1268        this
1269    }
1270}
1271
1272impl<T, U> FromIterator<BitFlag<T, U>> for BitFlags<T, U>
1273where
1274    T: BitOrAssign<T> + util::IntOpsExt,
1275    U: ?Sized,
1276{
1277    #[inline]
1278    fn from_iter<I>(iter: I) -> Self
1279    where
1280        I: IntoIterator<Item = BitFlag<T, U>>,
1281    {
1282        let mut this = Self(T::ZERO, PhantomData);
1283        for other in iter {
1284            this |= Self::from(other);
1285        }
1286        this
1287    }
1288}
1289
1290#[cfg(feature = "arbitrary")]
1291#[cfg_attr(docsrs, doc(cfg(feature = "arbitrary")))]
1292impl<'a, T, U> arbitrary::Arbitrary<'a> for BitFlags<T, U>
1293where
1294    T: arbitrary::Arbitrary<'a>,
1295    U: ?Sized,
1296{
1297    #[inline]
1298    fn arbitrary(u: &mut arbitrary::Unstructured<'a>) -> arbitrary::Result<Self> {
1299        T::arbitrary(u).map(|u| Self(u, PhantomData))
1300    }
1301
1302    #[inline]
1303    fn size_hint(depth: usize) -> (usize, Option<usize>) {
1304        T::size_hint(depth)
1305    }
1306}
1307
1308#[cfg(feature = "bytemuck")]
1309#[cfg_attr(docsrs, doc(cfg(feature = "bytemuck")))]
1310// SAFETY: All fields are valid when their memory is entirely zeroed out.
1311unsafe impl<T, U> bytemuck::Zeroable for BitFlags<T, U>
1312where
1313    T: bytemuck::Zeroable,
1314    U: ?Sized,
1315{
1316}
1317
1318#[cfg(feature = "bytemuck")]
1319#[cfg_attr(docsrs, doc(cfg(feature = "bytemuck")))]
1320// SAFETY: The type is Copy, Zeroable, has no padding bytes, and any
1321// bit pattern of the correct size is a valid instance of this type.
1322unsafe impl<T, U> bytemuck::Pod for BitFlags<T, U>
1323where
1324    T: bytemuck::Pod,
1325    U: 'static + ?Sized,
1326{
1327}
1328
1329#[cfg(feature = "serde")]
1330#[cfg_attr(docsrs, doc(cfg(feature = "serde")))]
1331impl<T, U> serde::Serialize for BitFlags<T, U>
1332where
1333    T: 'static
1334        + Copy
1335        + PartialEq
1336        + Not<Output = T>
1337        + BitAnd<T, Output = T>
1338        + Shl<usize, Output = T>
1339        + fmt::LowerHex
1340        + util::IntOpsExt,
1341    U: ?Sized + BitFlagsSpec<T>,
1342{
1343    #[inline]
1344    fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
1345    where
1346        S: serde::Serializer,
1347    {
1348        // Replicates the behavior of `bitflags`-based implementation.
1349        serializer.collect_str(&self)
1350    }
1351}
1352
1353#[cfg(feature = "serde")]
1354#[cfg_attr(docsrs, doc(cfg(feature = "serde")))]
1355impl<'de, T, U> serde::Deserialize<'de> for BitFlags<T, U>
1356where
1357    T: 'static + Copy + BitOrAssign<T> + util::IntOpsExt,
1358    U: ?Sized + BitFlagsSpec<T>,
1359{
1360    #[inline]
1361    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
1362    where
1363        D: serde::Deserializer<'de>,
1364    {
1365        deserializer.deserialize_str(BitFlagsVisitor::<T, U>(PhantomData, PhantomData))
1366    }
1367}
1368
1369#[cfg(feature = "serde")]
1370#[cfg_attr(docsrs, doc(cfg(feature = "serde")))]
1371impl<T, U> serde::de::Visitor<'_> for BitFlagsVisitor<T, U>
1372where
1373    T: 'static + Copy + BitOrAssign<T> + util::IntOpsExt,
1374    U: ?Sized + BitFlagsSpec<T>,
1375{
1376    type Value = BitFlags<T, U>;
1377
1378    fn expecting(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
1379        fmt_expected(f, "|", U::FLAG_NAMES)
1380    }
1381
1382    #[inline]
1383    fn visit_str<E>(self, value: &str) -> Result<Self::Value, E>
1384    where
1385        E: serde::de::Error,
1386    {
1387        Self::Value::from_str(value).map_err(E::custom)
1388    }
1389}
1390
1391/// An error encountered while parsing flags from text.
1392#[cfg_attr(
1393    not(feature = "alloc"),
1394    expect(
1395        missing_copy_implementations,
1396        reason = "the payload is feature-dependent"
1397    )
1398)]
1399#[derive(Clone, Debug)]
1400pub struct BitFlagsParseError {
1401    #[cfg(not(feature = "alloc"))]
1402    given: (),
1403    #[cfg(feature = "alloc")]
1404    given: alloc::string::String,
1405
1406    separator: &'static str,
1407    expected: &'static [&'static [&'static str]],
1408}
1409
1410impl fmt::Display for BitFlagsParseError {
1411    #[expect(clippy::missing_inline_in_public_items, reason = "bloats binary size")]
1412    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
1413        let named_flags = self
1414            .expected
1415            .iter()
1416            .filter(|variants| !variants.is_empty())
1417            .count();
1418
1419        if named_flags == 0 {
1420            write!(f, "unrecognized hex flag, ")?;
1421        } else {
1422            write!(f, "unrecognized named flag or hex flag, ")?;
1423        }
1424        #[cfg(feature = "alloc")]
1425        write!(f, "given: \"{}\", expected ", self.given)?;
1426        #[cfg(not(feature = "alloc"))]
1427        write!(f, "expected ")?;
1428        fmt_expected(f, self.separator, self.expected)?;
1429
1430        #[cfg(not(feature = "alloc"))]
1431        let () = self.given;
1432
1433        Ok(())
1434    }
1435}
1436
1437impl core::error::Error for BitFlagsParseError {}
1438
1439/// Writes the expected flag format for [`BitFlagsParseError`] and serde
1440/// visitors.
1441#[inline]
1442fn fmt_expected(
1443    f: &mut fmt::Formatter<'_>,
1444    separator: &'static str,
1445    expected: &'static [&'static [&'static str]],
1446) -> fmt::Result {
1447    let named_flags = expected
1448        .iter()
1449        .filter(|variants| !variants.is_empty())
1450        .count();
1451
1452    if named_flags == 0 {
1453        write!(f, "a valid hex flag with \"0x\" prefix")?;
1454    } else {
1455        write!(f, "\"\", or a \"{separator}\"-separated set of")?;
1456
1457        let mut separator = " ";
1458        for names in expected {
1459            if let Some(name) = names.first() {
1460                write!(f, "{separator}\"{name}\"")?;
1461                separator = ", ";
1462            }
1463        }
1464        write!(f, ", or a valid hex flag with \"0x\" prefix")?;
1465    }
1466    Ok(())
1467}