Skip to content

Commit 5fd3a5c

Browse files
committed
Auto merge of #89916 - the8472:advance_by-avoid-err-0, r=dtolnay
Fix Iterator::advance_by contract inconsistency The `advance_by(n)` docs state that in the error case `Err(k)` that k is always less than n. It also states that `advance_by(0)` may return `Err(0)` to indicate an exhausted iterator. These statements are inconsistent. Since only one implementation (Skip) actually made use of that I changed it to return Ok(()) in that case too. While adding some tests I also found a bug in `Take::advance_back_by`.
2 parents 0881b3a + 3f9b26d commit 5fd3a5c

File tree

11 files changed

+69
-22
lines changed

11 files changed

+69
-22
lines changed

library/alloc/tests/vec.rs

+3
Original file line numberDiff line numberDiff line change
@@ -985,6 +985,9 @@ fn test_into_iter_advance_by() {
985985

986986
assert_eq!(i.advance_by(usize::MAX), Err(0));
987987

988+
i.advance_by(0).unwrap();
989+
i.advance_back_by(0).unwrap();
990+
988991
assert_eq!(i.len(), 0);
989992
}
990993

library/core/src/iter/adapters/skip.rs

+2-2
Original file line numberDiff line numberDiff line change
@@ -119,8 +119,8 @@ where
119119
#[rustc_inherit_overflow_checks]
120120
fn advance_by(&mut self, n: usize) -> Result<(), usize> {
121121
let mut rem = n;
122-
123122
let step_one = self.n.saturating_add(rem);
123+
124124
match self.iter.advance_by(step_one) {
125125
Ok(_) => {
126126
rem -= step_one - self.n;
@@ -129,7 +129,7 @@ where
129129
Err(advanced) => {
130130
let advanced_without_skip = advanced.saturating_sub(self.n);
131131
self.n = self.n.saturating_sub(advanced);
132-
return Err(advanced_without_skip);
132+
return if n == 0 { Ok(()) } else { Err(advanced_without_skip) };
133133
}
134134
}
135135

library/core/src/iter/adapters/take.rs

+15-14
Original file line numberDiff line numberDiff line change
@@ -215,21 +215,22 @@ where
215215
}
216216

217217
#[inline]
218+
#[rustc_inherit_overflow_checks]
218219
fn advance_back_by(&mut self, n: usize) -> Result<(), usize> {
219-
let inner_len = self.iter.len();
220-
let len = self.n;
221-
let remainder = len.saturating_sub(n);
222-
let to_advance = inner_len - remainder;
223-
match self.iter.advance_back_by(to_advance) {
224-
Ok(_) => {
225-
self.n = remainder;
226-
if n > len {
227-
return Err(len);
228-
}
229-
return Ok(());
230-
}
231-
_ => panic!("ExactSizeIterator contract violation"),
232-
}
220+
// The amount by which the inner iterator needs to be shortened for it to be
221+
// at most as long as the take() amount.
222+
let trim_inner = self.iter.len().saturating_sub(self.n);
223+
// The amount we need to advance inner to fulfill the caller's request.
224+
// take(), advance_by() and len() all can be at most usize, so we don't have to worry
225+
// about having to advance more than usize::MAX here.
226+
let advance_by = trim_inner.saturating_add(n);
227+
228+
let advanced = match self.iter.advance_back_by(advance_by) {
229+
Ok(_) => advance_by - trim_inner,
230+
Err(advanced) => advanced - trim_inner,
231+
};
232+
self.n -= advanced;
233+
return if advanced < n { Err(advanced) } else { Ok(()) };
233234
}
234235
}
235236

library/core/src/iter/traits/double_ended.rs

-3
Original file line numberDiff line numberDiff line change
@@ -106,9 +106,6 @@ pub trait DoubleEndedIterator: Iterator {
106106
/// Calling `advance_back_by(0)` can do meaningful work, for example [`Flatten`] can advance its
107107
/// outer iterator until it finds an inner iterator that is not empty, which then often
108108
/// allows it to return a more accurate `size_hint()` than in its initial state.
109-
/// `advance_back_by(0)` may either return `Ok()` or `Err(0)`. The former conveys no information
110-
/// whether the iterator is or is not exhausted, the latter can be treated as if [`next_back`]
111-
/// had returned `None`. Replacing a `Err(0)` with `Ok` is only correct for `n = 0`.
112109
///
113110
/// [`advance_by`]: Iterator::advance_by
114111
/// [`Flatten`]: crate::iter::Flatten

library/core/src/iter/traits/iterator.rs

-3
Original file line numberDiff line numberDiff line change
@@ -249,9 +249,6 @@ pub trait Iterator {
249249
/// Calling `advance_by(0)` can do meaningful work, for example [`Flatten`]
250250
/// can advance its outer iterator until it finds an inner iterator that is not empty, which
251251
/// then often allows it to return a more accurate `size_hint()` than in its initial state.
252-
/// `advance_by(0)` may either return `Ok()` or `Err(0)`. The former conveys no information
253-
/// whether the iterator is or is not exhausted, the latter can be treated as if [`next`]
254-
/// had returned `None`. Replacing a `Err(0)` with `Ok` is only correct for `n = 0`.
255252
///
256253
/// [`Flatten`]: crate::iter::Flatten
257254
/// [`next`]: Iterator::next

library/core/tests/iter/adapters/chain.rs

+8
Original file line numberDiff line numberDiff line change
@@ -34,21 +34,25 @@ fn test_iterator_chain_advance_by() {
3434
iter.advance_by(i).unwrap();
3535
assert_eq!(iter.next(), Some(&xs[i]));
3636
assert_eq!(iter.advance_by(100), Err(len - i - 1));
37+
iter.advance_by(0).unwrap();
3738
}
3839

3940
for i in 0..ys.len() {
4041
let mut iter = Unfuse::new(xs).chain(Unfuse::new(ys));
4142
iter.advance_by(xs.len() + i).unwrap();
4243
assert_eq!(iter.next(), Some(&ys[i]));
4344
assert_eq!(iter.advance_by(100), Err(ys.len() - i - 1));
45+
iter.advance_by(0).unwrap();
4446
}
4547

4648
let mut iter = xs.iter().chain(ys);
4749
iter.advance_by(len).unwrap();
4850
assert_eq!(iter.next(), None);
51+
iter.advance_by(0).unwrap();
4952

5053
let mut iter = xs.iter().chain(ys);
5154
assert_eq!(iter.advance_by(len + 1), Err(len));
55+
iter.advance_by(0).unwrap();
5256
}
5357

5458
test_chain(&[], &[]);
@@ -67,21 +71,25 @@ fn test_iterator_chain_advance_back_by() {
6771
iter.advance_back_by(i).unwrap();
6872
assert_eq!(iter.next_back(), Some(&ys[ys.len() - i - 1]));
6973
assert_eq!(iter.advance_back_by(100), Err(len - i - 1));
74+
iter.advance_back_by(0).unwrap();
7075
}
7176

7277
for i in 0..xs.len() {
7378
let mut iter = Unfuse::new(xs).chain(Unfuse::new(ys));
7479
iter.advance_back_by(ys.len() + i).unwrap();
7580
assert_eq!(iter.next_back(), Some(&xs[xs.len() - i - 1]));
7681
assert_eq!(iter.advance_back_by(100), Err(xs.len() - i - 1));
82+
iter.advance_back_by(0).unwrap();
7783
}
7884

7985
let mut iter = xs.iter().chain(ys);
8086
iter.advance_back_by(len).unwrap();
8187
assert_eq!(iter.next_back(), None);
88+
iter.advance_back_by(0).unwrap();
8289

8390
let mut iter = xs.iter().chain(ys);
8491
assert_eq!(iter.advance_back_by(len + 1), Err(len));
92+
iter.advance_back_by(0).unwrap();
8593
}
8694

8795
test_chain(&[], &[]);

library/core/tests/iter/adapters/flatten.rs

+3
Original file line numberDiff line numberDiff line change
@@ -61,6 +61,7 @@ fn test_flatten_try_folds() {
6161
#[test]
6262
fn test_flatten_advance_by() {
6363
let mut it = once(0..10).chain(once(10..30)).chain(once(30..40)).flatten();
64+
6465
it.advance_by(5).unwrap();
6566
assert_eq!(it.next(), Some(5));
6667
it.advance_by(9).unwrap();
@@ -72,6 +73,8 @@ fn test_flatten_advance_by() {
7273

7374
assert_eq!(it.advance_by(usize::MAX), Err(9));
7475
assert_eq!(it.advance_back_by(usize::MAX), Err(0));
76+
it.advance_by(0).unwrap();
77+
it.advance_back_by(0).unwrap();
7578
assert_eq!(it.size_hint(), (0, Some(0)));
7679
}
7780

library/core/tests/iter/adapters/skip.rs

+11
Original file line numberDiff line numberDiff line change
@@ -69,6 +69,17 @@ fn test_iterator_skip_nth() {
6969
assert_eq!(it.nth(0), None);
7070
}
7171

72+
#[test]
73+
fn test_skip_advance_by() {
74+
assert_eq!((0..0).skip(10).advance_by(0), Ok(()));
75+
assert_eq!((0..0).skip(10).advance_by(1), Err(0));
76+
assert_eq!((0u128..(usize::MAX as u128) + 1).skip(usize::MAX).advance_by(usize::MAX), Err(1));
77+
assert_eq!((0u128..u128::MAX).skip(usize::MAX).advance_by(1), Ok(()));
78+
79+
assert_eq!((0..2).skip(1).advance_back_by(10), Err(1));
80+
assert_eq!((0..0).skip(1).advance_back_by(0), Ok(()));
81+
}
82+
7283
#[test]
7384
fn test_iterator_skip_count() {
7485
let xs = [0, 1, 2, 3, 5, 13, 15, 16, 17, 19, 20, 30];

library/core/tests/iter/adapters/take.rs

+22
Original file line numberDiff line numberDiff line change
@@ -73,6 +73,28 @@ fn test_iterator_take_nth_back() {
7373
assert_eq!(it.nth_back(1), None);
7474
}
7575

76+
#[test]
77+
fn test_take_advance_by() {
78+
let mut take = (0..10).take(3);
79+
assert_eq!(take.advance_by(2), Ok(()));
80+
assert_eq!(take.next(), Some(2));
81+
assert_eq!(take.advance_by(1), Err(0));
82+
83+
assert_eq!((0..0).take(10).advance_by(0), Ok(()));
84+
assert_eq!((0..0).take(10).advance_by(1), Err(0));
85+
assert_eq!((0..10).take(4).advance_by(5), Err(4));
86+
87+
let mut take = (0..10).take(3);
88+
assert_eq!(take.advance_back_by(2), Ok(()));
89+
assert_eq!(take.next(), Some(0));
90+
assert_eq!(take.advance_back_by(1), Err(0));
91+
92+
assert_eq!((0..2).take(1).advance_back_by(10), Err(1));
93+
assert_eq!((0..0).take(1).advance_back_by(1), Err(0));
94+
assert_eq!((0..0).take(1).advance_back_by(0), Ok(()));
95+
assert_eq!((0..usize::MAX).take(100).advance_back_by(usize::MAX), Err(100));
96+
}
97+
7698
#[test]
7799
fn test_iterator_take_short() {
78100
let xs = [0, 1, 2, 3];

library/core/tests/iter/range.rs

+3
Original file line numberDiff line numberDiff line change
@@ -300,6 +300,9 @@ fn test_range_advance_by() {
300300

301301
assert_eq!(r.advance_by(usize::MAX), Err(usize::MAX - 2));
302302

303+
r.advance_by(0).unwrap();
304+
r.advance_back_by(0).unwrap();
305+
303306
let mut r = 0u128..u128::MAX;
304307

305308
r.advance_by(usize::MAX).unwrap();

library/core/tests/slice.rs

+2
Original file line numberDiff line numberDiff line change
@@ -154,6 +154,7 @@ fn test_iterator_advance_by() {
154154
assert_eq!(iter.as_slice(), &v[3..]);
155155
iter.advance_by(2).unwrap();
156156
assert_eq!(iter.as_slice(), &[]);
157+
iter.advance_by(0).unwrap();
157158
}
158159

159160
#[test]
@@ -175,6 +176,7 @@ fn test_iterator_advance_back_by() {
175176
assert_eq!(iter.as_slice(), &v[..v.len() - 3]);
176177
iter.advance_back_by(2).unwrap();
177178
assert_eq!(iter.as_slice(), &[]);
179+
iter.advance_back_by(0).unwrap();
178180
}
179181

180182
#[test]

0 commit comments

Comments
 (0)