From 6f56d0703689eae8e5649da34da8857848aa060d Mon Sep 17 00:00:00 2001 From: Arturo Castro Date: Tue, 10 Dec 2019 18:22:23 +0100 Subject: [PATCH 1/6] Implement Iterator for bitflagsAdds a new `iter()` method to bitflags that returns an iterator over all the enabled flags.Uses `iter::from_fn` to create the iterator to avoid having to add a new struct to the macro which would require a parameter specifying it's name. --- src/lib.rs | 43 +++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 43 insertions(+) diff --git a/src/lib.rs b/src/lib.rs index 2cb196ab..421738f5 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -702,6 +702,20 @@ macro_rules! __impl_bitflags { self.remove(other); } } + + pub fn iter(&self) -> impl Iterator { + let mut copy = self.clone(); + $crate::_core::iter::from_fn(move || { + if copy.is_empty() { + None + }else{ + let bits = 1 << copy.bits.trailing_zeros() as $T; + let r = $BitFlags { bits }; + copy.remove(r); + Some(r) + } + }) + } } impl $crate::_core::ops::BitOr for $BitFlags { @@ -1428,3 +1442,32 @@ mod tests { assert_eq!(format!("{:?}", Flags::SOME), "SOME"); } } + +#[test] +fn test_iter() { + bitflags! { + struct Flags: u32 { + const ONE = 0b001; + const TWO = 0b010; + const THREE = 0b100; + } + } + + let flags = Flags::all(); + assert_eq!(flags.iter().count(), 3); + let mut iter = flags.iter(); + assert_eq!(iter.next().unwrap(), Flags::ONE); + assert_eq!(iter.next().unwrap(), Flags::TWO); + assert_eq!(iter.next().unwrap(), Flags::THREE); + assert_eq!(iter.next(), None); + + let flags = Flags::empty(); + assert_eq!(flags.iter().count(), 0); + + let flags = (Flags::ONE | Flags::THREE); + assert_eq!(flags.iter().count(), 2); + let mut iter = flags.iter(); + assert_eq!(iter.next().unwrap(), Flags::ONE); + assert_eq!(iter.next().unwrap(), Flags::THREE); + assert_eq!(iter.next(), None); +} \ No newline at end of file From d4cd1ee4665fd378679199462d668d586a143b57 Mon Sep 17 00:00:00 2001 From: Arturo Castro Date: Thu, 18 Jun 2020 17:03:57 +0200 Subject: [PATCH 2/6] iter: add comment --- src/lib.rs | 1 + 1 file changed, 1 insertion(+) diff --git a/src/lib.rs b/src/lib.rs index 421738f5..2d8e6777 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -703,6 +703,7 @@ macro_rules! __impl_bitflags { } } + /// Returns an iterator over every bit set pub fn iter(&self) -> impl Iterator { let mut copy = self.clone(); $crate::_core::iter::from_fn(move || { From 826581c821490a128cbd6dd660b13f00c8eede0f Mon Sep 17 00:00:00 2001 From: Arturo Castro Date: Sun, 16 May 2021 18:01:22 +0200 Subject: [PATCH 3/6] fix warning --- src/lib.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/lib.rs b/src/lib.rs index 2d8e6777..a276821c 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1465,7 +1465,7 @@ fn test_iter() { let flags = Flags::empty(); assert_eq!(flags.iter().count(), 0); - let flags = (Flags::ONE | Flags::THREE); + let flags = Flags::ONE | Flags::THREE; assert_eq!(flags.iter().count(), 2); let mut iter = flags.iter(); assert_eq!(iter.next().unwrap(), Flags::ONE); From 8b10fe14edd76e362b2e129f3be4969f37ac09e7 Mon Sep 17 00:00:00 2001 From: Arturo Castro Date: Tue, 19 Apr 2022 11:53:17 +0200 Subject: [PATCH 4/6] from_bits_(truncate) fail with composite flags When a bitflags type contains composite flags like: ```rs bitflags! { struct Flags: u8 { const A = 0b00000001; const BC = 0b00000110; } } ``` from_bits and from_bits_truncate would not work as expected allowing flags that are not declared. Fixes #275 --- src/lib.rs | 63 +++++++++++++++++++++++++++++++++++++++++++++++++----- 1 file changed, 58 insertions(+), 5 deletions(-) diff --git a/src/lib.rs b/src/lib.rs index fe52de57..0e311d8f 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -559,18 +559,41 @@ macro_rules! __impl_bitflags { /// representation contains bits that do not correspond to a flag. #[inline] pub const fn from_bits(bits: $T) -> $crate::_core::option::Option { - if (bits & !Self::all().bits()) == 0 { - $crate::_core::option::Option::Some(Self { bits }) - } else { - $crate::_core::option::Option::None + if bits == 0 { + return Some(Self{ bits }) } + + $( + #[allow(unused_doc_comments, unused_attributes)] + $(#[$attr $($args)*])* + if bits & Self::$Flag.bits == Self::$Flag.bits { + return Some(Self{ bits }) + } + )* + + None } /// Convert from underlying bit representation, dropping any bits /// that do not correspond to flags. #[inline] pub const fn from_bits_truncate(bits: $T) -> Self { - Self { bits: bits & Self::all().bits } + if bits == 0 { + return Self{ bits } + } + + #[allow(unused_mut)] + let mut truncated = 0; + + $( + #[allow(unused_doc_comments, unused_attributes)] + $(#[$attr $($args)*])* + if bits & Self::$Flag.bits == Self::$Flag.bits { + truncated |= Self::$Flag.bits + } + )* + + Self { bits: truncated } } /// Convert from underlying bit representation, preserving all @@ -1798,3 +1821,33 @@ mod tests { } } } + + + #[test] + fn test_from_bits_edge_cases() { + bitflags! { + struct Flags: u8 { + const A = 0b00000001; + const BC = 0b00000110; + } + } + + + let flags = Flags::from_bits(0b00000100); + assert!(flags.is_none()); + } + + #[test] + fn test_from_bits_truncate_edge_cases() { + bitflags! { + struct Flags: u8 { + const A = 0b00000001; + const BC = 0b00000110; + } + } + + + let flags = Flags::from_bits_truncate(0b00000100); + assert!(flags.is_empty()); + } +} \ No newline at end of file From 6250b31a570976a0af6168ac4c3d59924819cbb1 Mon Sep 17 00:00:00 2001 From: Arturo Castro Date: Tue, 19 Apr 2022 12:21:45 +0200 Subject: [PATCH 5/6] New iterator implementation Uses an array to store all the possible combinations and checks one by one all of them until the flag or the array is empty. Requires a higher version of rust (1.51) since it needs const generics. --- src/bitflags_trait.rs | 2 + src/lib.rs | 105 +++++++++++++++++++++++++++++++++++++++++- 2 files changed, 106 insertions(+), 1 deletion(-) diff --git a/src/bitflags_trait.rs b/src/bitflags_trait.rs index 0ffee465..5862235b 100644 --- a/src/bitflags_trait.rs +++ b/src/bitflags_trait.rs @@ -6,6 +6,8 @@ pub trait ImplementedByBitFlagsMacro {} /// It should not be implemented manually. pub trait BitFlags: ImplementedByBitFlagsMacro { type Bits; + const NUM_FLAGS: usize; + /// Returns an empty set of flags. fn empty() -> Self; /// Returns the set containing all flags. diff --git a/src/lib.rs b/src/lib.rs index 0e311d8f..7c6675ab 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -753,6 +753,17 @@ macro_rules! __impl_bitflags { Self::from_bits_truncate(!self.bits) } + + /// Returns an iterator over all the flags in this set. + pub const fn iter(self) -> $crate::Iter::NUM_FLAGS }> { + $crate::Iter::new(self, [ + $( + #[allow(unused_doc_comments, unused_attributes)] + $(#[$attr $($args)*])* + Self::$Flag, + )* + ]) + } } impl $crate::_core::ops::BitOr for $BitFlags { @@ -855,6 +866,20 @@ macro_rules! __impl_bitflags { impl $crate::BitFlags for $BitFlags { type Bits = $T; + const NUM_FLAGS: usize = { + #[allow(unused_mut)] + let mut num_flags = 0; + + $( + #[allow(unused_doc_comments, unused_attributes)] + $(#[$attr $($args)*])* + { + num_flags += 1; + } + )* + + num_flags + }; fn empty() -> Self { $BitFlags::empty() @@ -1025,6 +1050,39 @@ macro_rules! __impl_bitflags { }; } +pub struct Iter { + bitflags: T, + possible: [T;LEN], + len: usize, +} + +impl Iter { + pub const fn new(bitflags: T, possible: [T;LEN]) -> Iter { + Iter{ bitflags, possible, len: LEN } + } +} + +impl Iterator for Iter { + type Item = T; + + fn next(&mut self) -> Option { + if self.bitflags.is_empty() || LEN == 0 || self.len == 0 { + None + }else{ + for (pos, flag) in self.possible.iter().copied().take(self.len).enumerate() { + if self.bitflags.contains(flag) { + self.bitflags.remove(flag); + self.len -= 1; + self.possible.swap(pos, self.len); + return Some(flag) + } + } + + None + } + } +} + #[cfg(feature = "example_generated")] pub mod example_generated; @@ -1820,8 +1878,53 @@ mod tests { const D = 8; } } -} + #[test] + fn test_iter() { + bitflags! { + struct Flags: u32 { + const ONE = 0b001; + const TWO = 0b010; + const THREE = 0b100; + } + } + + let flags = Flags::all(); + assert_eq!(flags.iter().count(), 3); + let mut iter = flags.iter(); + assert_eq!(iter.next().unwrap(), Flags::ONE); + assert_eq!(iter.next().unwrap(), Flags::THREE); + assert_eq!(iter.next().unwrap(), Flags::TWO); + assert_eq!(iter.next(), None); + + let flags = Flags::empty(); + assert_eq!(flags.iter().count(), 0); + + let flags = Flags::ONE | Flags::THREE; + assert_eq!(flags.iter().count(), 2); + let mut iter = flags.iter(); + assert_eq!(iter.next().unwrap(), Flags::ONE); + assert_eq!(iter.next().unwrap(), Flags::THREE); + assert_eq!(iter.next(), None); + } + + #[test] + fn test_iter_edge_cases() { + bitflags! { + struct Flags: u8 { + const A = 0b00000001; + const BC = 0b00000110; + } + } + + + let flags = Flags::all(); + assert_eq!(flags.iter().count(), 2); + let mut iter = flags.iter(); + assert_eq!(iter.next().unwrap(), Flags::A); + assert_eq!(iter.next().unwrap(), Flags::BC); + assert_eq!(iter.next(), None); + } #[test] fn test_from_bits_edge_cases() { From 5787e707e528018e0e226f889a437d85e1cd02af Mon Sep 17 00:00:00 2001 From: Arturo Castro Date: Tue, 19 Apr 2022 12:50:37 +0200 Subject: [PATCH 6/6] serde tests fail in CI Cause Serialize and Deserialize derives are used from serde:: instead of serde_derive:: --- src/lib.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/lib.rs b/src/lib.rs index fe52de57..061e61d7 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1789,7 +1789,7 @@ mod tests { } bitflags! { - #[derive(serde::Serialize, serde::Deserialize)] + #[derive(serde_derive::Serialize, serde_derive::Deserialize)] struct SerdeFlags: u32 { const A = 1; const B = 2;