Skip to content

Commit d939f70

Browse files
committed
Auto merge of #68915 - timvermeulen:non_fused_iter, r=Amanieu
Fix bugs in Peekable and Flatten when using non-fused iterators I fixed a couple of bugs with regard to the `Peekable` and `Flatten`/`FlatMap` iterators when the underlying iterator isn't fused. For testing, I also added a `NonFused` iterator wrapper that panics when `next` or `next_back` is called on an iterator that has returned `None` before, which will hopefully make it easier to spot these mistakes in the future. ### Peekable `Peekable::next_back` was implemented as ```rust self.iter.next_back().or_else(|| self.peeked.take().and_then(|x| x)) ``` which is incorrect because when the `peeked` field is `Some(None)`, then `None` has already been returned from the inner iterator and what it returns from `next_back` can no longer be relied upon. `test_peekable_non_fused` tests this. ### Flatten When a `FlattenCompat` instance only has a `backiter` remaining (i.e. `self.frontiter` is `None` and `self.iter` is empty), then `next` will call `self.iter.next()` every time, so the `iter` field needs to be fused. I fixed it by giving it the type `Fuse<I>` instead of `I`, I think this is the only way to fix it. `test_flatten_non_fused_outer` tests this. Furthermore, previously `FlattenCompat::next` did not set `self.frontiter` to `None` after it returned `None`, which is incorrect when the inner iterator type isn't fused. I just delegated it to `try_fold` because that already handles it correctly. `test_flatten_non_fused_inner` tests this. r? @scottmcm
2 parents ae5b641 + 8cf33b0 commit d939f70

File tree

3 files changed

+90
-9
lines changed

3 files changed

+90
-9
lines changed

src/libcore/iter/adapters/flatten.rs

+13-8
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
use crate::fmt;
22
use crate::ops::Try;
33

4-
use super::super::{DoubleEndedIterator, FusedIterator, Iterator};
4+
use super::super::{DoubleEndedIterator, Fuse, FusedIterator, Iterator};
55
use super::Map;
66

77
/// An iterator that maps each element to an iterator, and yields the elements
@@ -239,14 +239,17 @@ where
239239
/// this type.
240240
#[derive(Clone, Debug)]
241241
struct FlattenCompat<I, U> {
242-
iter: I,
242+
iter: Fuse<I>,
243243
frontiter: Option<U>,
244244
backiter: Option<U>,
245245
}
246-
impl<I, U> FlattenCompat<I, U> {
246+
impl<I, U> FlattenCompat<I, U>
247+
where
248+
I: Iterator,
249+
{
247250
/// Adapts an iterator by flattening it, for use in `flatten()` and `flat_map()`.
248251
fn new(iter: I) -> FlattenCompat<I, U> {
249-
FlattenCompat { iter, frontiter: None, backiter: None }
252+
FlattenCompat { iter: iter.fuse(), frontiter: None, backiter: None }
250253
}
251254
}
252255

@@ -261,8 +264,9 @@ where
261264
fn next(&mut self) -> Option<U::Item> {
262265
loop {
263266
if let Some(ref mut inner) = self.frontiter {
264-
if let elt @ Some(_) = inner.next() {
265-
return elt;
267+
match inner.next() {
268+
None => self.frontiter = None,
269+
elt @ Some(_) => return elt,
266270
}
267271
}
268272
match self.iter.next() {
@@ -348,8 +352,9 @@ where
348352
fn next_back(&mut self) -> Option<U::Item> {
349353
loop {
350354
if let Some(ref mut inner) = self.backiter {
351-
if let elt @ Some(_) = inner.next_back() {
352-
return elt;
355+
match inner.next_back() {
356+
None => self.backiter = None,
357+
elt @ Some(_) => return elt,
353358
}
354359
}
355360
match self.iter.next_back() {

src/libcore/iter/adapters/mod.rs

+5-1
Original file line numberDiff line numberDiff line change
@@ -1480,7 +1480,11 @@ where
14801480
{
14811481
#[inline]
14821482
fn next_back(&mut self) -> Option<Self::Item> {
1483-
self.iter.next_back().or_else(|| self.peeked.take().and_then(|x| x))
1483+
match self.peeked.as_mut() {
1484+
Some(v @ Some(_)) => self.iter.next_back().or_else(|| v.take()),
1485+
Some(None) => None,
1486+
None => self.iter.next_back(),
1487+
}
14841488
}
14851489

14861490
#[inline]

src/libcore/tests/iter.rs

+72
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,5 @@
1+
// ignore-tidy-filelength
2+
13
use core::cell::Cell;
24
use core::convert::TryFrom;
35
use core::iter::*;
@@ -2940,3 +2942,73 @@ fn test_partition() {
29402942
check(xs, |&x| x < 3, 3); // small
29412943
check(xs, |&x| x > 6, 3); // large
29422944
}
2945+
2946+
/// An iterator that panics whenever `next` or next_back` is called
2947+
/// after `None` has already been returned. This does not violate
2948+
/// `Iterator`'s contract. Used to test that iterator adaptors don't
2949+
/// poll their inner iterators after exhausting them.
2950+
struct NonFused<I> {
2951+
iter: I,
2952+
done: bool,
2953+
}
2954+
2955+
impl<I> NonFused<I> {
2956+
fn new(iter: I) -> Self {
2957+
Self { iter, done: false }
2958+
}
2959+
}
2960+
2961+
impl<I> Iterator for NonFused<I>
2962+
where
2963+
I: Iterator,
2964+
{
2965+
type Item = I::Item;
2966+
2967+
fn next(&mut self) -> Option<Self::Item> {
2968+
assert!(!self.done, "this iterator has already returned None");
2969+
self.iter.next().or_else(|| {
2970+
self.done = true;
2971+
None
2972+
})
2973+
}
2974+
}
2975+
2976+
impl<I> DoubleEndedIterator for NonFused<I>
2977+
where
2978+
I: DoubleEndedIterator,
2979+
{
2980+
fn next_back(&mut self) -> Option<Self::Item> {
2981+
assert!(!self.done, "this iterator has already returned None");
2982+
self.iter.next_back().or_else(|| {
2983+
self.done = true;
2984+
None
2985+
})
2986+
}
2987+
}
2988+
2989+
#[test]
2990+
fn test_peekable_non_fused() {
2991+
let mut iter = NonFused::new(empty::<i32>()).peekable();
2992+
2993+
assert_eq!(iter.peek(), None);
2994+
assert_eq!(iter.next_back(), None);
2995+
}
2996+
2997+
#[test]
2998+
fn test_flatten_non_fused_outer() {
2999+
let mut iter = NonFused::new(once(0..2)).flatten();
3000+
3001+
assert_eq!(iter.next_back(), Some(1));
3002+
assert_eq!(iter.next(), Some(0));
3003+
assert_eq!(iter.next(), None);
3004+
}
3005+
3006+
#[test]
3007+
fn test_flatten_non_fused_inner() {
3008+
let mut iter = once(0..1).chain(once(1..3)).flat_map(NonFused::new);
3009+
3010+
assert_eq!(iter.next_back(), Some(2));
3011+
assert_eq!(iter.next(), Some(0));
3012+
assert_eq!(iter.next(), Some(1));
3013+
assert_eq!(iter.next(), None);
3014+
}

0 commit comments

Comments
 (0)