From 4c92be12e67522df215fe344858362f3c021d1ef Mon Sep 17 00:00:00 2001 From: Stephane Raux Date: Fri, 3 Jun 2022 00:22:53 -0500 Subject: [PATCH 1/9] Add note about Rust version --- enum-iterator/README.md | 4 ++++ enum-iterator/src/lib.rs | 4 ++++ 2 files changed, 8 insertions(+) diff --git a/enum-iterator/README.md b/enum-iterator/README.md index 8013095..54b0c51 100644 --- a/enum-iterator/README.md +++ b/enum-iterator/README.md @@ -53,6 +53,10 @@ assert_eq!(first::(), Some(Foo { a: false, b: 0 })); assert_eq!(last::(), Some(Foo { a: true, b: 255 })); ``` +# Rust version +This crate tracks stable Rust. Minor releases may require a newer Rust version. Patch releases +must not require a newer Rust version. + # Contribute All contributions shall be licensed under the [0BSD license](https://spdx.org/licenses/0BSD.html). diff --git a/enum-iterator/src/lib.rs b/enum-iterator/src/lib.rs index c1ea094..4690977 100644 --- a/enum-iterator/src/lib.rs +++ b/enum-iterator/src/lib.rs @@ -53,6 +53,10 @@ //! assert_eq!(last::(), Some(Foo { a: true, b: 255 })); //! ``` //! +//! # Rust version +//! This crate tracks stable Rust. Minor releases may require a newer Rust version. Patch releases +//! must not require a newer Rust version. +//! //! # Contribute //! All contributions shall be licensed under the [0BSD license](https://spdx.org/licenses/0BSD.html). From 4694c68f4a0b97470e03104df77c4135ee847fc5 Mon Sep 17 00:00:00 2001 From: Stephane Raux Date: Fri, 17 Jun 2022 19:04:13 -0500 Subject: [PATCH 2/9] Address clippy warnings in generated code --- enum-iterator-derive/src/lib.rs | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/enum-iterator-derive/src/lib.rs b/enum-iterator-derive/src/lib.rs index cc1e574..32b2af1 100644 --- a/enum-iterator-derive/src/lib.rs +++ b/enum-iterator-derive/src/lib.rs @@ -80,6 +80,7 @@ fn derive_for_struct( }; let tokens = quote! { impl #impl_generics ::enum_iterator::Sequence for #ty #ty_generics #where_clause { + #[allow(clippy::identity_op)] const CARDINALITY: usize = #cardinality; fn next(&self) -> ::core::option::Option { @@ -130,6 +131,7 @@ fn derive_for_enum( }; let tokens = quote! { impl #impl_generics ::enum_iterator::Sequence for #ty #ty_generics #where_clause { + #[allow(clippy::identity_op)] const CARDINALITY: usize = #cardinality; fn next(&self) -> ::core::option::Option { @@ -141,11 +143,11 @@ fn derive_for_enum( } fn first() -> ::core::option::Option { - ::core::option::Option::Some(#first) + #first } fn last() -> ::core::option::Option { - ::core::option::Option::Some(#last) + #last } } }; @@ -209,7 +211,7 @@ where ::core::option::Option::None #( .or_else(|| ::core::option::Option::Some(#inits)) - )*? + )* } } @@ -241,7 +243,7 @@ where #ty::#id { #assignments } => { #tuple .map(|(#(#bindings,)*)| #ty::#id { #assignments }) - .or_else(|| ::core::option::Option::Some(#next)) + .or_else(|| #next) } } }, From ad31808328e55e4a02c116d03048488f3a762e3b Mon Sep 17 00:00:00 2001 From: Stephane Raux Date: Fri, 17 Jun 2022 21:22:48 -0500 Subject: [PATCH 3/9] Bump dependencies --- enum-iterator-derive/Cargo.toml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/enum-iterator-derive/Cargo.toml b/enum-iterator-derive/Cargo.toml index 5251156..d74157f 100644 --- a/enum-iterator-derive/Cargo.toml +++ b/enum-iterator-derive/Cargo.toml @@ -14,6 +14,6 @@ keywords = ["enum", "variants", "iterator", "enumerate", "cardinality"] proc-macro = true [dependencies] -proc-macro2 = "1" -quote = "1" -syn = { version = "1", features = ["extra-traits"] } +proc-macro2 = "1.0.39" +quote = "1.0.18" +syn = { version = "1.0.96", features = ["extra-traits"] } From 46b561c278601f5b1b370a779d528d2d1d9a781d Mon Sep 17 00:00:00 2001 From: Stephane Raux Date: Fri, 17 Jun 2022 22:46:07 -0500 Subject: [PATCH 4/9] Bump versions --- enum-iterator-derive/Cargo.toml | 2 +- enum-iterator/Cargo.toml | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/enum-iterator-derive/Cargo.toml b/enum-iterator-derive/Cargo.toml index d74157f..1656987 100644 --- a/enum-iterator-derive/Cargo.toml +++ b/enum-iterator-derive/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "enum-iterator-derive" -version = "1.0.0" +version = "1.0.1" authors = ["Stephane Raux "] edition = "2021" description = "Procedural macro to derive Sequence" diff --git a/enum-iterator/Cargo.toml b/enum-iterator/Cargo.toml index 3ce7ace..09852e7 100644 --- a/enum-iterator/Cargo.toml +++ b/enum-iterator/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "enum-iterator" -version = "1.1.1" +version = "1.1.2" authors = ["Stephane Raux "] edition = "2021" description = "Tools to iterate over all values of a type (e.g. all variants of an enumeration)" @@ -11,4 +11,4 @@ documentation = "https://docs.rs/enum-iterator" keywords = ["enum", "variants", "iterator", "enumerate", "cardinality"] [dependencies] -enum-iterator-derive = {path = "../enum-iterator-derive", version = "1"} +enum-iterator-derive = { path = "../enum-iterator-derive", version = "1.0.1" } From acdef034c2d2d870d041758662b0b95c76a509ec Mon Sep 17 00:00:00 2001 From: Stephane Raux Date: Mon, 4 Jul 2022 14:55:49 -0500 Subject: [PATCH 5/9] Dereference matched value This ensures patterns are exhaustive even in the case of an enum without variants. Resolves #16 --- enum-iterator-derive/src/lib.rs | 20 ++++++++++++++++++-- enum-iterator/tests/derive.rs | 18 ++++++++++++++++++ 2 files changed, 36 insertions(+), 2 deletions(-) diff --git a/enum-iterator-derive/src/lib.rs b/enum-iterator-derive/src/lib.rs index 32b2af1..c13e0d8 100644 --- a/enum-iterator-derive/src/lib.rs +++ b/enum-iterator-derive/src/lib.rs @@ -236,11 +236,12 @@ where |(variant, next_variants)| { let next = init_enum(ty, next_variants, direction); let id = &variant.ident; + let destructuring = field_bindings(&variant.fields); let assignments = field_assignments(&variant.fields); let bindings = bindings().take(variant.fields.len()).collect::>(); let tuple = advance_tuple(&bindings, direction); quote! { - #ty::#id { #assignments } => { + #ty::#id { #destructuring } => { #tuple .map(|(#(#bindings,)*)| #ty::#id { #assignments }) .or_else(|| #next) @@ -249,7 +250,7 @@ where }, ); quote! { - match self { + match *self { #(#arms,)* } } @@ -308,6 +309,21 @@ where .collect() } +fn field_bindings<'a, I>(fields: I) -> TokenStream +where + I: IntoIterator, +{ + fields + .into_iter() + .enumerate() + .zip(bindings()) + .map(|((i, field), binding)| { + let field_id = field_id(field, i); + quote! { #field_id: ref #binding, } + }) + .collect() +} + fn bindings() -> impl Iterator { (0..).map(|i| Ident::new(&format!("x{i}"), Span::call_site())) } diff --git a/enum-iterator/tests/derive.rs b/enum-iterator/tests/derive.rs index 6454bf8..73b6e60 100644 --- a/enum-iterator/tests/derive.rs +++ b/enum-iterator/tests/derive.rs @@ -145,3 +145,21 @@ fn reverse_all_works() { }; assert_eq!(reverse_all::().collect::>(), expected); } + +#[derive(Debug, PartialEq, Sequence)] +enum Empty {} + +#[test] +fn empty_cadinality_is_zero() { + assert_eq!(cardinality::(), 0); +} + +#[test] +fn all_values_of_empty_are_yielded() { + assert_eq!(all::().collect::>(), Vec::new()); +} + +#[test] +fn all_values_of_empty_are_yielded_in_reverse() { + assert_eq!(reverse_all::().collect::>(), Vec::new()); +} From 70fadf252c68ad114f94a56f837a6391613b8157 Mon Sep 17 00:00:00 2001 From: Stephane Raux Date: Mon, 4 Jul 2022 15:02:48 -0500 Subject: [PATCH 6/9] Bump versions --- enum-iterator-derive/Cargo.toml | 2 +- enum-iterator/Cargo.toml | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/enum-iterator-derive/Cargo.toml b/enum-iterator-derive/Cargo.toml index 1656987..2bd158e 100644 --- a/enum-iterator-derive/Cargo.toml +++ b/enum-iterator-derive/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "enum-iterator-derive" -version = "1.0.1" +version = "1.0.2" authors = ["Stephane Raux "] edition = "2021" description = "Procedural macro to derive Sequence" diff --git a/enum-iterator/Cargo.toml b/enum-iterator/Cargo.toml index 09852e7..351ab17 100644 --- a/enum-iterator/Cargo.toml +++ b/enum-iterator/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "enum-iterator" -version = "1.1.2" +version = "1.1.3" authors = ["Stephane Raux "] edition = "2021" description = "Tools to iterate over all values of a type (e.g. all variants of an enumeration)" @@ -11,4 +11,4 @@ documentation = "https://docs.rs/enum-iterator" keywords = ["enum", "variants", "iterator", "enumerate", "cardinality"] [dependencies] -enum-iterator-derive = { path = "../enum-iterator-derive", version = "1.0.1" } +enum-iterator-derive = { path = "../enum-iterator-derive", version = "1.0.2" } From c79d71b3f061b8897d57ae90c996f7e52256e7ae Mon Sep 17 00:00:00 2001 From: Stephane Raux Date: Wed, 17 Aug 2022 14:18:31 -0500 Subject: [PATCH 7/9] Implement `Sequence` for arrays --- enum-iterator/src/lib.rs | 122 ++++++++++++++++++++++++++++++++++++++- 1 file changed, 120 insertions(+), 2 deletions(-) diff --git a/enum-iterator/src/lib.rs b/enum-iterator/src/lib.rs index 4690977..eaf7dff 100644 --- a/enum-iterator/src/lib.rs +++ b/enum-iterator/src/lib.rs @@ -64,7 +64,7 @@ #![deny(warnings)] #![no_std] -use core::iter::FusedIterator; +use core::{iter::FusedIterator, ops::ControlFlow}; pub use enum_iterator_derive::Sequence; @@ -527,6 +527,69 @@ impl Sequence for Option { } } +impl Sequence for [T; N] { + const CARDINALITY: usize = { + let tc = T::CARDINALITY; + let mut c = 1; + let mut i = 0; + loop { + if i == N { + break c; + } + c *= tc; + i += 1; + } + }; + + fn next(&self) -> Option { + advance_for_array(self, T::first) + } + + fn previous(&self) -> Option { + advance_for_array(self, T::last) + } + + fn first() -> Option { + if N == 0 { + Some(core::array::from_fn(|_| unreachable!())) + } else { + let x = T::first()?; + Some(core::array::from_fn(|_| x.clone())) + } + } + + fn last() -> Option { + if N == 0 { + Some(core::array::from_fn(|_| unreachable!())) + } else { + let x = T::last()?; + Some(core::array::from_fn(|_| x.clone())) + } + } +} + +fn advance_for_array(a: &[T; N], reset: R) -> Option<[T; N]> +where + T: Sequence + Clone, + R: Fn() -> Option, +{ + let mut a = a.clone(); + let keep = a.iter_mut().rev().try_fold((), |_, x| match x.next() { + Some(new_x) => { + *x = new_x; + ControlFlow::Break(true) + } + None => match reset() { + Some(new_x) => { + *x = new_x; + ControlFlow::Continue(()) + } + None => ControlFlow::Break(false), + }, + }); + Some(a).filter(|_| matches!(keep, ControlFlow::Break(true))) +} + macro_rules! impl_seq_advance_for_tuple { ( $this:ident, @@ -778,7 +841,7 @@ mod tests { } #[test] - fn check_option_items() { + fn all_bool_option_items_are_yielded() { assert!(all::>().eq([None, Some(false), Some(true)])); } @@ -793,4 +856,59 @@ mod tests { (Some(true), true), ])); } + + #[test] + fn cardinality_of_empty_array_is_one() { + assert_eq!(cardinality::<[u8; 0]>(), 1); + } + + #[test] + fn cardinality_equals_item_count_for_empty_array() { + cardinality_equals_item_count::<[u8; 0]>(); + } + + #[test] + fn cardinality_equals_item_count_for_array() { + cardinality_equals_item_count::<[u8; 3]>(); + } + + #[test] + fn array_items_vary_from_right_to_left() { + assert!(all::<[Option; 2]>().eq([ + [None, None], + [None, Some(false)], + [None, Some(true)], + [Some(false), None], + [Some(false), Some(false)], + [Some(false), Some(true)], + [Some(true), None], + [Some(true), Some(false)], + [Some(true), Some(true)], + ])); + } + + #[test] + fn all_empty_array_items_are_yielded() { + assert!(all::<[bool; 0]>().eq([[]])); + } + + #[test] + fn cardinality_of_empty_infallible_array_is_one() { + assert_eq!(cardinality::<[Infallible; 0]>(), 1); + } + + #[test] + fn cardinality_of_non_empty_infallible_array_is_zero() { + assert_eq!(cardinality::<[Infallible; 1]>(), 0); + } + + #[test] + fn all_empty_infallible_array_items_are_yielded() { + assert!(all::<[Infallible; 0]>().eq([[]])); + } + + #[test] + fn all_non_empty_infallible_array_items_are_yielded() { + assert!(all::<[Infallible; 1]>().next().is_none()); + } } From 3adab1c49764b33988c8bfe04cadc93c88f9fd72 Mon Sep 17 00:00:00 2001 From: Stephane Raux Date: Sat, 20 Aug 2022 10:07:30 -0500 Subject: [PATCH 8/9] Improve enum traversal The previous implementation would lead to a combinatorial explosion in the number of generated lines, making the compilation extremely slow as the number of variants grow. --- enum-iterator-derive/src/lib.rs | 145 +++++++++++++++++++++++--------- 1 file changed, 105 insertions(+), 40 deletions(-) diff --git a/enum-iterator-derive/src/lib.rs b/enum-iterator-derive/src/lib.rs index c13e0d8..bdb309f 100644 --- a/enum-iterator-derive/src/lib.rs +++ b/enum-iterator-derive/src/lib.rs @@ -109,10 +109,8 @@ fn derive_for_enum( variants: &Punctuated, ) -> Result { let cardinality = enum_cardinality(variants); - let first = init_enum(ty, variants, Direction::Forward); - let last = init_enum(ty, variants.iter().rev(), Direction::Backward); let next_body = advance_enum(ty, variants, Direction::Forward); - let previous_body = advance_enum(ty, variants.iter().rev(), Direction::Backward); + let previous_body = advance_enum(ty, variants, Direction::Backward); let (impl_generics, ty_generics, where_clause) = generics.split_for_impl(); let where_clause = if generics.params.is_empty() { where_clause.cloned() @@ -129,6 +127,20 @@ fn derive_for_enum( ); Some(clause) }; + let next_variant_body = next_variant(ty, variants, Direction::Forward); + let previous_variant_body = next_variant(ty, variants, Direction::Backward); + let (first, last) = if variants.is_empty() { + ( + quote! { ::core::option::Option::None }, + quote! { ::core::option::Option::None }, + ) + } else { + let last_index = variants.len() - 1; + ( + quote! { next_variant(0) }, + quote! { previous_variant(#last_index) }, + ) + }; let tokens = quote! { impl #impl_generics ::enum_iterator::Sequence for #ty #ty_generics #where_clause { #[allow(clippy::identity_op)] @@ -150,6 +162,21 @@ fn derive_for_enum( #last } } + + fn next_variant #impl_generics( + mut i: usize, + ) -> ::core::option::Option<#ty #ty_generics> #where_clause { + #next_variant_body + } + + fn previous_variant #impl_generics( + mut i: usize, + ) -> ::core::option::Option<#ty #ty_generics> #where_clause { + #previous_variant_body + } + }; + let tokens = quote! { + const _: () = { #tokens }; }; Ok(tokens) } @@ -196,22 +223,40 @@ fn init_fields(fields: &Fields, direction: Direction) -> TokenStream { .collect::() } -fn init_enum<'a, V>(ty: &Ident, variants: V, direction: Direction) -> TokenStream -where - V: IntoIterator, -{ - let inits = variants.into_iter().map(|variant| { - let id = &variant.ident; - let init = init_fields(&variant.fields, direction); +fn next_variant( + ty: &Ident, + variants: &Punctuated, + direction: Direction, +) -> TokenStream { + let advance = match direction { + Direction::Forward => { + let last_index = variants.len().saturating_sub(1); + quote! { + if i >= #last_index { break ::core::option::Option::None; } else { i+= 1; } + } + } + Direction::Backward => quote! { + if i == 0 { break ::core::option::Option::None; } else { i -= 1; } + }, + }; + let arms = variants.iter().enumerate().map(|(i, v)| { + let id = &v.ident; + let init = init_fields(&v.fields, direction); quote! { - #ty::#id { #init } + #i => ::core::option::Option::Some(#ty::#id { #init }) } }); quote! { - ::core::option::Option::None - #( - .or_else(|| ::core::option::Option::Some(#inits)) - )* + loop { + let next = (|| match i { + #(#arms,)* + _ => ::core::option::Option::None, + })(); + match next { + ::core::option::Option::Some(_) => break next, + ::core::option::Option::None => #advance, + } + } } } @@ -222,33 +267,28 @@ fn advance_struct(ty: &Ident, fields: &Fields, direction: Direction) -> TokenStr quote! { let #ty { #assignments } = self; let (#(#bindings,)*) = #tuple?; - Some(#ty { #assignments }) + ::core::option::Option::Some(#ty { #assignments }) } } -fn advance_enum<'a, V>(ty: &Ident, variants: V, direction: Direction) -> TokenStream -where - V: IntoIterator, - V::IntoIter: Clone, -{ - let mut variants = variants.into_iter(); - let arms = iter::from_fn(|| variants.next().map(|variant| (variant, variants.clone()))).map( - |(variant, next_variants)| { - let next = init_enum(ty, next_variants, direction); - let id = &variant.ident; - let destructuring = field_bindings(&variant.fields); - let assignments = field_assignments(&variant.fields); - let bindings = bindings().take(variant.fields.len()).collect::>(); - let tuple = advance_tuple(&bindings, direction); - quote! { - #ty::#id { #destructuring } => { - #tuple - .map(|(#(#bindings,)*)| #ty::#id { #assignments }) - .or_else(|| #next) - } - } - }, - ); +fn advance_enum( + ty: &Ident, + variants: &Punctuated, + direction: Direction, +) -> TokenStream { + let arms: Vec<_> = match direction { + Direction::Forward => variants + .iter() + .enumerate() + .map(|(i, variant)| advance_enum_arm(ty, direction, i, variant)) + .collect(), + Direction::Backward => variants + .iter() + .enumerate() + .rev() + .map(|(i, variant)| advance_enum_arm(ty, direction, i, variant)) + .collect(), + }; quote! { match *self { #(#arms,)* @@ -256,6 +296,31 @@ where } } +fn advance_enum_arm(ty: &Ident, direction: Direction, i: usize, variant: &Variant) -> TokenStream { + let next = match direction { + Direction::Forward => match i.checked_add(1) { + Some(next_i) => quote! { .or_else(|| next_variant(#next_i)) }, + None => quote! {}, + }, + Direction::Backward => match i.checked_sub(1) { + Some(prev_i) => quote! { .or_else(|| previous_variant(#prev_i)) }, + None => quote! {}, + }, + }; + let id = &variant.ident; + let destructuring = field_bindings(&variant.fields); + let assignments = field_assignments(&variant.fields); + let bindings = bindings().take(variant.fields.len()).collect::>(); + let tuple = advance_tuple(&bindings, direction); + quote! { + #ty::#id { #destructuring } => { + #tuple + .map(|(#(#bindings,)*)| #ty::#id { #assignments }) + #next + } + } +} + fn advance_tuple(bindings: &[Ident], direction: Direction) -> TokenStream { let advance = direction.advance(); let reset = direction.reset(); @@ -287,7 +352,7 @@ fn advance_tuple(bindings: &[Ident], direction: Direction) -> TokenStream { (::core::clone::Clone::clone(#rev_binding_tail), false) }; )* - Some((#(#bindings,)*)).filter(|_| !carry) + ::core::option::Option::Some((#(#bindings,)*)).filter(|_| !carry) }; quote! { (|| { #body })() From e7834031d8caadec08c32e4a7bac72d239dd670a Mon Sep 17 00:00:00 2001 From: Stephane Raux Date: Wed, 17 Aug 2022 18:44:16 -0500 Subject: [PATCH 9/9] Bump versions --- enum-iterator-derive/Cargo.toml | 2 +- enum-iterator/Cargo.toml | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/enum-iterator-derive/Cargo.toml b/enum-iterator-derive/Cargo.toml index 2bd158e..7e02c49 100644 --- a/enum-iterator-derive/Cargo.toml +++ b/enum-iterator-derive/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "enum-iterator-derive" -version = "1.0.2" +version = "1.1.0" authors = ["Stephane Raux "] edition = "2021" description = "Procedural macro to derive Sequence" diff --git a/enum-iterator/Cargo.toml b/enum-iterator/Cargo.toml index 351ab17..63465f5 100644 --- a/enum-iterator/Cargo.toml +++ b/enum-iterator/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "enum-iterator" -version = "1.1.3" +version = "1.2.0" authors = ["Stephane Raux "] edition = "2021" description = "Tools to iterate over all values of a type (e.g. all variants of an enumeration)" @@ -11,4 +11,4 @@ documentation = "https://docs.rs/enum-iterator" keywords = ["enum", "variants", "iterator", "enumerate", "cardinality"] [dependencies] -enum-iterator-derive = { path = "../enum-iterator-derive", version = "1.0.2" } +enum-iterator-derive = { path = "../enum-iterator-derive", version = "1.1.0" }