From e2ec6735b1828e6ab9febf48810ac8238888bc23 Mon Sep 17 00:00:00 2001 From: Arturo Castro Date: Tue, 19 Apr 2022 15:05:51 +0200 Subject: [PATCH 1/8] Iterator over all the enabled options This adds a new function that returns an iterator over all the enabled flags. This implementation takes into account combined flags like the ones described in: https://github.com/bitflags/bitflags/pull/204#issuecomment-950304444 It uses an `impl Iterator` as return type because using an specific Iter type implies using const generics which are not supported in earlier versions of rust and adding a const to the trait in order to express the correct dimension of the array as can be seen in the last solution proposed in #204. This superseeds #204 --- src/lib.rs | 44 ++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 44 insertions(+) diff --git a/src/lib.rs b/src/lib.rs index fe52de57..fefd252d 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -730,6 +730,50 @@ macro_rules! __impl_bitflags { Self::from_bits_truncate(!self.bits) } + /// Returns an iterator over all the flags in this set. + pub fn iter(mut self) -> impl Iterator { + let mut options = [ + $( + #[allow(unused_doc_comments, unused_attributes)] + $(#[$attr $($args)*])* + Self::$Flag, + )* + ]; + 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 + }; + let mut len = NUM_FLAGS; + + $crate::_core::iter::from_fn(move || { + if self.is_empty() || NUM_FLAGS == 0 || len == 0 { + None + }else{ + for pos in 0..len { + let flag = options[pos]; + len -= 1; + options.swap(pos, len); + if self.contains(flag) { + self.remove(flag); + return Some(flag) + } + } + + None + } + }) + } + } impl $crate::_core::ops::BitOr for $BitFlags { From 229f1964e0644e89c8babc418a8a02ea086b20eb Mon Sep 17 00:00:00 2001 From: Arturo Castro Date: Wed, 20 Apr 2022 09:54:01 +0200 Subject: [PATCH 2/8] iterators: add tests --- src/lib.rs | 47 +++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 47 insertions(+) diff --git a/src/lib.rs b/src/lib.rs index 6edfed3d..166af121 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1841,4 +1841,51 @@ 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); + } } From 4ba731a5697eb57d13497d993c134c2b0fb55315 Mon Sep 17 00:00:00 2001 From: Arturo Castro Date: Wed, 20 Apr 2022 14:01:01 +0200 Subject: [PATCH 3/8] iterators: preserve order of flags --- src/lib.rs | 11 +++++------ 1 file changed, 5 insertions(+), 6 deletions(-) diff --git a/src/lib.rs b/src/lib.rs index 166af121..c43b298e 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -753,16 +753,15 @@ macro_rules! __impl_bitflags { num_flags }; - let mut len = NUM_FLAGS; + let mut start = 0; $crate::_core::iter::from_fn(move || { - if self.is_empty() || NUM_FLAGS == 0 || len == 0 { + if self.is_empty() || NUM_FLAGS == 0 || start == NUM_FLAGS { None }else{ - for pos in 0..len { + for pos in start..NUM_FLAGS { let flag = options[pos]; - len -= 1; - options.swap(pos, len); + start += 1; if self.contains(flag) { self.remove(flag); return Some(flag) @@ -1856,8 +1855,8 @@ mod tests { 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().unwrap(), Flags::THREE); assert_eq!(iter.next(), None); let flags = Flags::empty(); From c5a5c15364e46417a9add938459a6f21857f7e7a Mon Sep 17 00:00:00 2001 From: Arturo Castro Date: Wed, 20 Apr 2022 14:02:47 +0200 Subject: [PATCH 4/8] iterator: options array doesn't need to be mut --- src/lib.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/lib.rs b/src/lib.rs index c43b298e..d76d39bd 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -732,7 +732,7 @@ macro_rules! __impl_bitflags { /// Returns an iterator over all the flags in this set. pub fn iter(mut self) -> impl Iterator { - let mut options = [ + let options = [ $( #[allow(unused_doc_comments, unused_attributes)] $(#[$attr $($args)*])* From 46fbbc316275fe1b9d074fbc2defbffc7ba4f253 Mon Sep 17 00:00:00 2001 From: Arturo Castro Date: Wed, 20 Apr 2022 14:06:15 +0200 Subject: [PATCH 5/8] iterators: make options array const --- src/lib.rs | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/src/lib.rs b/src/lib.rs index d76d39bd..341cf214 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -732,13 +732,6 @@ macro_rules! __impl_bitflags { /// Returns an iterator over all the flags in this set. pub fn iter(mut self) -> impl Iterator { - let options = [ - $( - #[allow(unused_doc_comments, unused_attributes)] - $(#[$attr $($args)*])* - Self::$Flag, - )* - ]; const NUM_FLAGS: usize = { #[allow(unused_mut)] let mut num_flags = 0; @@ -753,6 +746,13 @@ macro_rules! __impl_bitflags { num_flags }; + const OPTIONS: [$BitFlags; NUM_FLAGS] = [ + $( + #[allow(unused_doc_comments, unused_attributes)] + $(#[$attr $($args)*])* + $BitFlags::$Flag, + )* + ]; let mut start = 0; $crate::_core::iter::from_fn(move || { @@ -760,7 +760,7 @@ macro_rules! __impl_bitflags { None }else{ for pos in start..NUM_FLAGS { - let flag = options[pos]; + let flag = OPTIONS[pos]; start += 1; if self.contains(flag) { self.remove(flag); From 00058b11af5e05f885b89b186bff880751a3a47c Mon Sep 17 00:00:00 2001 From: Arturo Castro Date: Wed, 20 Apr 2022 14:09:04 +0200 Subject: [PATCH 6/8] iterators: remove unnecesary check --- src/lib.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/lib.rs b/src/lib.rs index 341cf214..1de91cbe 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -756,7 +756,7 @@ macro_rules! __impl_bitflags { let mut start = 0; $crate::_core::iter::from_fn(move || { - if self.is_empty() || NUM_FLAGS == 0 || start == NUM_FLAGS { + if self.is_empty() || NUM_FLAGS == 0 { None }else{ for pos in start..NUM_FLAGS { From 5f753e1767e965c11ca9170f3ff1c75974e1a06b Mon Sep 17 00:00:00 2001 From: Arturo Castro Date: Wed, 20 Apr 2022 14:17:38 +0200 Subject: [PATCH 7/8] iterators: no need to iterate over a range anymore --- src/lib.rs | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/lib.rs b/src/lib.rs index 1de91cbe..32bb486e 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -759,8 +759,7 @@ macro_rules! __impl_bitflags { if self.is_empty() || NUM_FLAGS == 0 { None }else{ - for pos in start..NUM_FLAGS { - let flag = OPTIONS[pos]; + for flag in OPTIONS[start..NUM_FLAGS].iter().copied() { start += 1; if self.contains(flag) { self.remove(flag); From 6656792e7385f6f9952bce84c958db6b543c3857 Mon Sep 17 00:00:00 2001 From: Arturo Castro Date: Tue, 26 Apr 2022 11:20:46 +0200 Subject: [PATCH 8/8] iterator: add cfg to tests --- src/lib.rs | 58 +++++++++++++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 57 insertions(+), 1 deletion(-) diff --git a/src/lib.rs b/src/lib.rs index 32bb486e..ab7656e7 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -772,6 +772,57 @@ macro_rules! __impl_bitflags { }) } + /// Returns an iterator over all the flags names as &'static str in this set. + pub fn iter_names(mut self) -> impl Iterator { + 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 + }; + const OPTIONS: [$BitFlags; NUM_FLAGS] = [ + $( + #[allow(unused_doc_comments, unused_attributes)] + $(#[$attr $($args)*])* + $BitFlags::$Flag, + )* + ]; + #[allow(unused_doc_comments, unused_attributes)] + const OPTIONS_NAMES: [&'static str; NUM_FLAGS] = [ + $( + $(#[$attr $($args)*])* + $crate::_core::stringify!($Flag), + )* + ]; + let mut start = 0; + + $crate::_core::iter::from_fn(move || { + if self.is_empty() || NUM_FLAGS == 0 { + None + }else{ + for (flag, flag_name) in OPTIONS[start..NUM_FLAGS].iter().copied() + .zip(OPTIONS_NAMES[start..NUM_FLAGS].iter().copied()) + { + start += 1; + if self.contains(flag) { + self.remove(flag); + return Some(flag_name) + } + } + + None + } + }) + } + } impl $crate::_core::ops::BitOr for $BitFlags { @@ -1847,15 +1898,20 @@ mod tests { const ONE = 0b001; const TWO = 0b010; const THREE = 0b100; + #[cfg(windows)] + const FOUR_WIN = 0b1000; + #[cfg(unix)] + const FOUR_UNIX = 0b10000; } } let flags = Flags::all(); - assert_eq!(flags.iter().count(), 3); + assert_eq!(flags.iter().count(), 4); 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().unwrap(), Flags::FOUR_UNIX); assert_eq!(iter.next(), None); let flags = Flags::empty();