Skip to content

Commit 3adab1c

Browse files
committed
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.
1 parent c79d71b commit 3adab1c

File tree

1 file changed

+105
-40
lines changed
  • enum-iterator-derive/src

1 file changed

+105
-40
lines changed

enum-iterator-derive/src/lib.rs

Lines changed: 105 additions & 40 deletions
Original file line numberDiff line numberDiff line change
@@ -109,10 +109,8 @@ fn derive_for_enum(
109109
variants: &Punctuated<Variant, Comma>,
110110
) -> Result<TokenStream, syn::Error> {
111111
let cardinality = enum_cardinality(variants);
112-
let first = init_enum(ty, variants, Direction::Forward);
113-
let last = init_enum(ty, variants.iter().rev(), Direction::Backward);
114112
let next_body = advance_enum(ty, variants, Direction::Forward);
115-
let previous_body = advance_enum(ty, variants.iter().rev(), Direction::Backward);
113+
let previous_body = advance_enum(ty, variants, Direction::Backward);
116114
let (impl_generics, ty_generics, where_clause) = generics.split_for_impl();
117115
let where_clause = if generics.params.is_empty() {
118116
where_clause.cloned()
@@ -129,6 +127,20 @@ fn derive_for_enum(
129127
);
130128
Some(clause)
131129
};
130+
let next_variant_body = next_variant(ty, variants, Direction::Forward);
131+
let previous_variant_body = next_variant(ty, variants, Direction::Backward);
132+
let (first, last) = if variants.is_empty() {
133+
(
134+
quote! { ::core::option::Option::None },
135+
quote! { ::core::option::Option::None },
136+
)
137+
} else {
138+
let last_index = variants.len() - 1;
139+
(
140+
quote! { next_variant(0) },
141+
quote! { previous_variant(#last_index) },
142+
)
143+
};
132144
let tokens = quote! {
133145
impl #impl_generics ::enum_iterator::Sequence for #ty #ty_generics #where_clause {
134146
#[allow(clippy::identity_op)]
@@ -150,6 +162,21 @@ fn derive_for_enum(
150162
#last
151163
}
152164
}
165+
166+
fn next_variant #impl_generics(
167+
mut i: usize,
168+
) -> ::core::option::Option<#ty #ty_generics> #where_clause {
169+
#next_variant_body
170+
}
171+
172+
fn previous_variant #impl_generics(
173+
mut i: usize,
174+
) -> ::core::option::Option<#ty #ty_generics> #where_clause {
175+
#previous_variant_body
176+
}
177+
};
178+
let tokens = quote! {
179+
const _: () = { #tokens };
153180
};
154181
Ok(tokens)
155182
}
@@ -196,22 +223,40 @@ fn init_fields(fields: &Fields, direction: Direction) -> TokenStream {
196223
.collect::<TokenStream>()
197224
}
198225

199-
fn init_enum<'a, V>(ty: &Ident, variants: V, direction: Direction) -> TokenStream
200-
where
201-
V: IntoIterator<Item = &'a Variant>,
202-
{
203-
let inits = variants.into_iter().map(|variant| {
204-
let id = &variant.ident;
205-
let init = init_fields(&variant.fields, direction);
226+
fn next_variant(
227+
ty: &Ident,
228+
variants: &Punctuated<Variant, Comma>,
229+
direction: Direction,
230+
) -> TokenStream {
231+
let advance = match direction {
232+
Direction::Forward => {
233+
let last_index = variants.len().saturating_sub(1);
234+
quote! {
235+
if i >= #last_index { break ::core::option::Option::None; } else { i+= 1; }
236+
}
237+
}
238+
Direction::Backward => quote! {
239+
if i == 0 { break ::core::option::Option::None; } else { i -= 1; }
240+
},
241+
};
242+
let arms = variants.iter().enumerate().map(|(i, v)| {
243+
let id = &v.ident;
244+
let init = init_fields(&v.fields, direction);
206245
quote! {
207-
#ty::#id { #init }
246+
#i => ::core::option::Option::Some(#ty::#id { #init })
208247
}
209248
});
210249
quote! {
211-
::core::option::Option::None
212-
#(
213-
.or_else(|| ::core::option::Option::Some(#inits))
214-
)*
250+
loop {
251+
let next = (|| match i {
252+
#(#arms,)*
253+
_ => ::core::option::Option::None,
254+
})();
255+
match next {
256+
::core::option::Option::Some(_) => break next,
257+
::core::option::Option::None => #advance,
258+
}
259+
}
215260
}
216261
}
217262

@@ -222,40 +267,60 @@ fn advance_struct(ty: &Ident, fields: &Fields, direction: Direction) -> TokenStr
222267
quote! {
223268
let #ty { #assignments } = self;
224269
let (#(#bindings,)*) = #tuple?;
225-
Some(#ty { #assignments })
270+
::core::option::Option::Some(#ty { #assignments })
226271
}
227272
}
228273

229-
fn advance_enum<'a, V>(ty: &Ident, variants: V, direction: Direction) -> TokenStream
230-
where
231-
V: IntoIterator<Item = &'a Variant>,
232-
V::IntoIter: Clone,
233-
{
234-
let mut variants = variants.into_iter();
235-
let arms = iter::from_fn(|| variants.next().map(|variant| (variant, variants.clone()))).map(
236-
|(variant, next_variants)| {
237-
let next = init_enum(ty, next_variants, direction);
238-
let id = &variant.ident;
239-
let destructuring = field_bindings(&variant.fields);
240-
let assignments = field_assignments(&variant.fields);
241-
let bindings = bindings().take(variant.fields.len()).collect::<Vec<_>>();
242-
let tuple = advance_tuple(&bindings, direction);
243-
quote! {
244-
#ty::#id { #destructuring } => {
245-
#tuple
246-
.map(|(#(#bindings,)*)| #ty::#id { #assignments })
247-
.or_else(|| #next)
248-
}
249-
}
250-
},
251-
);
274+
fn advance_enum(
275+
ty: &Ident,
276+
variants: &Punctuated<Variant, Comma>,
277+
direction: Direction,
278+
) -> TokenStream {
279+
let arms: Vec<_> = match direction {
280+
Direction::Forward => variants
281+
.iter()
282+
.enumerate()
283+
.map(|(i, variant)| advance_enum_arm(ty, direction, i, variant))
284+
.collect(),
285+
Direction::Backward => variants
286+
.iter()
287+
.enumerate()
288+
.rev()
289+
.map(|(i, variant)| advance_enum_arm(ty, direction, i, variant))
290+
.collect(),
291+
};
252292
quote! {
253293
match *self {
254294
#(#arms,)*
255295
}
256296
}
257297
}
258298

299+
fn advance_enum_arm(ty: &Ident, direction: Direction, i: usize, variant: &Variant) -> TokenStream {
300+
let next = match direction {
301+
Direction::Forward => match i.checked_add(1) {
302+
Some(next_i) => quote! { .or_else(|| next_variant(#next_i)) },
303+
None => quote! {},
304+
},
305+
Direction::Backward => match i.checked_sub(1) {
306+
Some(prev_i) => quote! { .or_else(|| previous_variant(#prev_i)) },
307+
None => quote! {},
308+
},
309+
};
310+
let id = &variant.ident;
311+
let destructuring = field_bindings(&variant.fields);
312+
let assignments = field_assignments(&variant.fields);
313+
let bindings = bindings().take(variant.fields.len()).collect::<Vec<_>>();
314+
let tuple = advance_tuple(&bindings, direction);
315+
quote! {
316+
#ty::#id { #destructuring } => {
317+
#tuple
318+
.map(|(#(#bindings,)*)| #ty::#id { #assignments })
319+
#next
320+
}
321+
}
322+
}
323+
259324
fn advance_tuple(bindings: &[Ident], direction: Direction) -> TokenStream {
260325
let advance = direction.advance();
261326
let reset = direction.reset();
@@ -287,7 +352,7 @@ fn advance_tuple(bindings: &[Ident], direction: Direction) -> TokenStream {
287352
(::core::clone::Clone::clone(#rev_binding_tail), false)
288353
};
289354
)*
290-
Some((#(#bindings,)*)).filter(|_| !carry)
355+
::core::option::Option::Some((#(#bindings,)*)).filter(|_| !carry)
291356
};
292357
quote! {
293358
(|| { #body })()

0 commit comments

Comments
 (0)