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
This commit is contained in:
bors 2020-03-18 03:08:52 +00:00
commit d939f708d9
3 changed files with 90 additions and 9 deletions

View File

@ -1,7 +1,7 @@
use crate::fmt;
use crate::ops::Try;
use super::super::{DoubleEndedIterator, FusedIterator, Iterator};
use super::super::{DoubleEndedIterator, Fuse, FusedIterator, Iterator};
use super::Map;
/// An iterator that maps each element to an iterator, and yields the elements
@ -239,14 +239,17 @@ where
/// this type.
#[derive(Clone, Debug)]
struct FlattenCompat<I, U> {
iter: I,
iter: Fuse<I>,
frontiter: Option<U>,
backiter: Option<U>,
}
impl<I, U> FlattenCompat<I, U> {
impl<I, U> FlattenCompat<I, U>
where
I: Iterator,
{
/// Adapts an iterator by flattening it, for use in `flatten()` and `flat_map()`.
fn new(iter: I) -> FlattenCompat<I, U> {
FlattenCompat { iter, frontiter: None, backiter: None }
FlattenCompat { iter: iter.fuse(), frontiter: None, backiter: None }
}
}
@ -261,8 +264,9 @@ where
fn next(&mut self) -> Option<U::Item> {
loop {
if let Some(ref mut inner) = self.frontiter {
if let elt @ Some(_) = inner.next() {
return elt;
match inner.next() {
None => self.frontiter = None,
elt @ Some(_) => return elt,
}
}
match self.iter.next() {
@ -348,8 +352,9 @@ where
fn next_back(&mut self) -> Option<U::Item> {
loop {
if let Some(ref mut inner) = self.backiter {
if let elt @ Some(_) = inner.next_back() {
return elt;
match inner.next_back() {
None => self.backiter = None,
elt @ Some(_) => return elt,
}
}
match self.iter.next_back() {

View File

@ -1480,7 +1480,11 @@ where
{
#[inline]
fn next_back(&mut self) -> Option<Self::Item> {
self.iter.next_back().or_else(|| self.peeked.take().and_then(|x| x))
match self.peeked.as_mut() {
Some(v @ Some(_)) => self.iter.next_back().or_else(|| v.take()),
Some(None) => None,
None => self.iter.next_back(),
}
}
#[inline]

View File

@ -1,3 +1,5 @@
// ignore-tidy-filelength
use core::cell::Cell;
use core::convert::TryFrom;
use core::iter::*;
@ -2940,3 +2942,73 @@ fn test_partition() {
check(xs, |&x| x < 3, 3); // small
check(xs, |&x| x > 6, 3); // large
}
/// An iterator that panics whenever `next` or next_back` is called
/// after `None` has already been returned. This does not violate
/// `Iterator`'s contract. Used to test that iterator adaptors don't
/// poll their inner iterators after exhausting them.
struct NonFused<I> {
iter: I,
done: bool,
}
impl<I> NonFused<I> {
fn new(iter: I) -> Self {
Self { iter, done: false }
}
}
impl<I> Iterator for NonFused<I>
where
I: Iterator,
{
type Item = I::Item;
fn next(&mut self) -> Option<Self::Item> {
assert!(!self.done, "this iterator has already returned None");
self.iter.next().or_else(|| {
self.done = true;
None
})
}
}
impl<I> DoubleEndedIterator for NonFused<I>
where
I: DoubleEndedIterator,
{
fn next_back(&mut self) -> Option<Self::Item> {
assert!(!self.done, "this iterator has already returned None");
self.iter.next_back().or_else(|| {
self.done = true;
None
})
}
}
#[test]
fn test_peekable_non_fused() {
let mut iter = NonFused::new(empty::<i32>()).peekable();
assert_eq!(iter.peek(), None);
assert_eq!(iter.next_back(), None);
}
#[test]
fn test_flatten_non_fused_outer() {
let mut iter = NonFused::new(once(0..2)).flatten();
assert_eq!(iter.next_back(), Some(1));
assert_eq!(iter.next(), Some(0));
assert_eq!(iter.next(), None);
}
#[test]
fn test_flatten_non_fused_inner() {
let mut iter = once(0..1).chain(once(1..3)).flat_map(NonFused::new);
assert_eq!(iter.next_back(), Some(2));
assert_eq!(iter.next(), Some(0));
assert_eq!(iter.next(), Some(1));
assert_eq!(iter.next(), None);
}