Skip to content

Commit eae5df8

Browse files
Allow non-ExactSizeIterator in PyList::new
1 parent 341b1e7 commit eae5df8

File tree

1 file changed

+32
-54
lines changed

1 file changed

+32
-54
lines changed

src/types/list.rs

Lines changed: 32 additions & 54 deletions
Original file line numberDiff line numberDiff line change
@@ -36,43 +36,6 @@ impl crate::impl_::pyclass::PyClassBaseType for PyList {
3636
crate::impl_::pycell::PyVariableClassObject<T>;
3737
}
3838

39-
#[inline]
40-
#[track_caller]
41-
fn try_new_from_iter<'py>(
42-
py: Python<'py>,
43-
mut elements: impl ExactSizeIterator<Item = PyResult<Bound<'py, PyAny>>>,
44-
) -> PyResult<Bound<'py, PyList>> {
45-
unsafe {
46-
// PyList_New checks for overflow but has a bad error message, so we check ourselves
47-
let len: Py_ssize_t = elements
48-
.len()
49-
.try_into()
50-
.expect("out of range integral type conversion attempted on `elements.len()`");
51-
52-
let ptr = ffi::PyList_New(len);
53-
54-
// We create the `Bound` pointer here for two reasons:
55-
// - panics if the ptr is null
56-
// - its Drop cleans up the list if user code or the asserts panic.
57-
let list = ptr.assume_owned(py).cast_into_unchecked();
58-
59-
let count = (&mut elements)
60-
.take(len as usize)
61-
.try_fold(0, |count, item| {
62-
#[cfg(not(Py_LIMITED_API))]
63-
ffi::PyList_SET_ITEM(ptr, count, item?.into_ptr());
64-
#[cfg(Py_LIMITED_API)]
65-
ffi::PyList_SetItem(ptr, count, item?.into_ptr());
66-
Ok::<_, PyErr>(count + 1)
67-
})?;
68-
69-
assert!(elements.next().is_none(), "Attempted to create PyList but `elements` was larger than reported by its `ExactSizeIterator` implementation.");
70-
assert_eq!(len, count, "Attempted to create PyList but `elements` was smaller than reported by its `ExactSizeIterator` implementation.");
71-
72-
Ok(list)
73-
}
74-
}
75-
7639
impl PyList {
7740
/// Constructs a new list with the given elements.
7841
///
@@ -101,16 +64,38 @@ impl PyList {
10164
/// All standard library structures implement this trait correctly, if they do, so calling this
10265
/// function with (for example) [`Vec`]`<T>` or `&[T]` will always succeed.
10366
#[track_caller]
104-
pub fn new<'py, T, U>(
67+
pub fn new<'py, T>(
10568
py: Python<'py>,
106-
elements: impl IntoIterator<Item = T, IntoIter = U>,
69+
elements: impl IntoIterator<Item = T>,
10770
) -> PyResult<Bound<'py, PyList>>
10871
where
10972
T: IntoPyObject<'py>,
110-
U: ExactSizeIterator<Item = T>,
11173
{
112-
let iter = elements.into_iter().map(|e| e.into_bound_py_any(py));
113-
try_new_from_iter(py, iter)
74+
let mut elements = elements.into_iter().map(|e| e.into_bound_py_any(py));
75+
let (min_len, _) = elements.size_hint();
76+
77+
// PyList_New checks for overflow but has a bad error message, so we check ourselves
78+
let len: Py_ssize_t = min_len
79+
.try_into()
80+
.expect("out of range integral type conversion attempted on `elements.len()`");
81+
82+
let list = unsafe { ffi::PyList_New(len).assume_owned(py).cast_into_unchecked() };
83+
84+
let count = (&mut elements)
85+
.take(len as usize)
86+
.try_fold(0, |count, item| unsafe {
87+
#[cfg(not(Py_LIMITED_API))]
88+
ffi::PyList_SET_ITEM(list.as_ptr(), count, item?.into_ptr());
89+
#[cfg(Py_LIMITED_API)]
90+
ffi::PyList_SetItem(list.as_ptr(), count, item?.into_ptr());
91+
Ok::<_, PyErr>(count + 1)
92+
})?;
93+
94+
assert_eq!(len, count, "Attempted to create PyList but `elements` was smaller than reported by its `size_hint` implementation.");
95+
96+
elements.try_for_each(|item| list.append(item?))?;
97+
98+
Ok(list)
11499
}
115100

116101
/// Constructs a new empty list.
@@ -1516,6 +1501,10 @@ mod tests {
15161501
fn next(&mut self) -> Option<Self::Item> {
15171502
self.0.next()
15181503
}
1504+
1505+
fn size_hint(&self) -> (usize, Option<usize>) {
1506+
(self.1, Some(self.1))
1507+
}
15191508
}
15201509

15211510
impl ExactSizeIterator for FaultyIter {
@@ -1526,18 +1515,7 @@ mod tests {
15261515

15271516
#[test]
15281517
#[should_panic(
1529-
expected = "Attempted to create PyList but `elements` was larger than reported by its `ExactSizeIterator` implementation."
1530-
)]
1531-
fn too_long_iterator() {
1532-
Python::attach(|py| {
1533-
let iter = FaultyIter(0..usize::MAX, 73);
1534-
let _list = PyList::new(py, iter).unwrap();
1535-
})
1536-
}
1537-
1538-
#[test]
1539-
#[should_panic(
1540-
expected = "Attempted to create PyList but `elements` was smaller than reported by its `ExactSizeIterator` implementation."
1518+
expected = "Attempted to create PyList but `elements` was smaller than reported by its `size_hint` implementation."
15411519
)]
15421520
fn too_short_iterator() {
15431521
Python::attach(|py| {

0 commit comments

Comments
 (0)