1use std::iter::FusedIterator;
2
3use crate::ffi::{self, Py_ssize_t};
4#[cfg(feature = "experimental-inspect")]
5use crate::inspect::types::TypeInfo;
6use crate::internal_tricks::get_ssize_index;
7use crate::types::PyList;
8use crate::types::PySequence;
9use crate::{
10 exceptions, FromPyObject, IntoPy, Py, PyAny, PyErr, PyObject, PyResult, Python, ToPyObject,
11};
12
13#[inline]
14#[track_caller]
15fn new_from_iter(
16 py: Python<'_>,
17 elements: &mut dyn ExactSizeIterator<Item = PyObject>,
18) -> Py<PyTuple> {
19 unsafe {
20 // PyTuple_New checks for overflow but has a bad error message, so we check ourselves
21 let len: Py_ssize_t = elements
22 .len()
23 .try_into()
24 .expect("out of range integral type conversion attempted on `elements.len()`");
25
26 let ptr = ffi::PyTuple_New(len);
27
28 // - Panics if the ptr is null
29 // - Cleans up the tuple if `convert` or the asserts panic
30 let tup: Py<PyTuple> = Py::from_owned_ptr(py, ptr);
31
32 let mut counter: Py_ssize_t = 0;
33
34 for obj in elements.take(len as usize) {
35 #[cfg(not(any(Py_LIMITED_API, PyPy)))]
36 ffi::PyTuple_SET_ITEM(ptr, counter, obj.into_ptr());
37 #[cfg(any(Py_LIMITED_API, PyPy))]
38 ffi::PyTuple_SetItem(ptr, counter, obj.into_ptr());
39 counter += 1;
40 }
41
42 assert!(elements.next().is_none(), "Attempted to create PyTuple but `elements` was larger than reported by its `ExactSizeIterator` implementation.");
43 assert_eq!(len, counter, "Attempted to create PyTuple but `elements` was smaller than reported by its `ExactSizeIterator` implementation.");
44
45 tup
46 }
47}
48
49/// Represents a Python `tuple` object.
50///
51/// This type is immutable.
52#[repr(transparent)]
53pub struct PyTuple(PyAny);
54
55pyobject_native_type_core!(PyTuple, pyobject_native_static_type_object!(ffi::PyTuple_Type), #checkfunction=ffi::PyTuple_Check);
56
57impl PyTuple {
58 /// Constructs a new tuple with the given elements.
59 ///
60 /// If you want to create a [`PyTuple`] with elements of different or unknown types, or from an
61 /// iterable that doesn't implement [`ExactSizeIterator`], create a Rust tuple with the given
62 /// elements and convert it at once using `into_py`.
63 ///
64 /// # Examples
65 ///
66 /// ```rust
67 /// use pyo3::prelude::*;
68 /// use pyo3::types::PyTuple;
69 ///
70 /// # fn main() {
71 /// Python::with_gil(|py| {
72 /// let elements: Vec<i32> = vec![0, 1, 2, 3, 4, 5];
73 /// let tuple: &PyTuple = PyTuple::new(py, elements);
74 /// assert_eq!(format!("{:?}", tuple), "(0, 1, 2, 3, 4, 5)");
75 /// });
76 /// # }
77 /// ```
78 ///
79 /// # Panics
80 ///
81 /// This function will panic if `element`'s [`ExactSizeIterator`] implementation is incorrect.
82 /// All standard library structures implement this trait correctly, if they do, so calling this
83 /// function using [`Vec`]`<T>` or `&[T]` will always succeed.
84 #[track_caller]
85 pub fn new<T, U>(
86 py: Python<'_>,
87 elements: impl IntoIterator<Item = T, IntoIter = U>,
88 ) -> &PyTuple
89 where
90 T: ToPyObject,
91 U: ExactSizeIterator<Item = T>,
92 {
93 let mut elements = elements.into_iter().map(|e| e.to_object(py));
94 let tup = new_from_iter(py, &mut elements);
95 tup.into_ref(py)
96 }
97
98 /// Constructs an empty tuple (on the Python side, a singleton object).
99 pub fn empty(py: Python<'_>) -> &PyTuple {
100 unsafe { py.from_owned_ptr(ffi::PyTuple_New(0)) }
101 }
102
103 /// Gets the length of the tuple.
104 pub fn len(&self) -> usize {
105 unsafe {
106 #[cfg(not(any(Py_LIMITED_API, PyPy)))]
107 let size = ffi::PyTuple_GET_SIZE(self.as_ptr());
108 #[cfg(any(Py_LIMITED_API, PyPy))]
109 let size = ffi::PyTuple_Size(self.as_ptr());
110 // non-negative Py_ssize_t should always fit into Rust uint
111 size as usize
112 }
113 }
114
115 /// Checks if the tuple is empty.
116 pub fn is_empty(&self) -> bool {
117 self.len() == 0
118 }
119
120 /// Returns `self` cast as a `PySequence`.
121 pub fn as_sequence(&self) -> &PySequence {
122 unsafe { self.downcast_unchecked() }
123 }
124
125 /// Takes the slice `self[low:high]` and returns it as a new tuple.
126 ///
127 /// Indices must be nonnegative, and out-of-range indices are clipped to
128 /// `self.len()`.
129 pub fn get_slice(&self, low: usize, high: usize) -> &PyTuple {
130 unsafe {
131 self.py().from_owned_ptr(ffi::PyTuple_GetSlice(
132 self.as_ptr(),
133 get_ssize_index(low),
134 get_ssize_index(high),
135 ))
136 }
137 }
138
139 /// Gets the tuple item at the specified index.
140 /// # Example
141 /// ```
142 /// use pyo3::{prelude::*, types::PyTuple};
143 ///
144 /// # fn main() -> PyResult<()> {
145 /// Python::with_gil(|py| -> PyResult<()> {
146 /// let ob = (1, 2, 3).to_object(py);
147 /// let tuple: &PyTuple = ob.downcast(py).unwrap();
148 /// let obj = tuple.get_item(0);
149 /// assert_eq!(obj.unwrap().extract::<i32>().unwrap(), 1);
150 /// Ok(())
151 /// })
152 /// # }
153 /// ```
154 pub fn get_item(&self, index: usize) -> PyResult<&PyAny> {
155 unsafe {
156 let item = ffi::PyTuple_GetItem(self.as_ptr(), index as Py_ssize_t);
157 self.py().from_borrowed_ptr_or_err(item)
158 }
159 }
160
161 /// Gets the tuple item at the specified index. Undefined behavior on bad index. Use with caution.
162 ///
163 /// # Safety
164 ///
165 /// Caller must verify that the index is within the bounds of the tuple.
166 #[cfg(not(any(Py_LIMITED_API, PyPy)))]
167 pub unsafe fn get_item_unchecked(&self, index: usize) -> &PyAny {
168 let item = ffi::PyTuple_GET_ITEM(self.as_ptr(), index as Py_ssize_t);
169 self.py().from_borrowed_ptr(item)
170 }
171
172 /// Returns `self` as a slice of objects.
173 #[cfg(not(Py_LIMITED_API))]
174 pub fn as_slice(&self) -> &[&PyAny] {
175 // This is safe because &PyAny has the same memory layout as *mut ffi::PyObject,
176 // and because tuples are immutable.
177 unsafe {
178 let ptr = self.as_ptr() as *mut ffi::PyTupleObject;
179 let slice = std::slice::from_raw_parts((*ptr).ob_item.as_ptr(), self.len());
180 &*(slice as *const [*mut ffi::PyObject] as *const [&PyAny])
181 }
182 }
183
184 /// Determines if self contains `value`.
185 ///
186 /// This is equivalent to the Python expression `value in self`.
187 #[inline]
188 pub fn contains<V>(&self, value: V) -> PyResult<bool>
189 where
190 V: ToPyObject,
191 {
192 self.as_sequence().contains(value)
193 }
194
195 /// Returns the first index `i` for which `self[i] == value`.
196 ///
197 /// This is equivalent to the Python expression `self.index(value)`.
198 #[inline]
199 pub fn index<V>(&self, value: V) -> PyResult<usize>
200 where
201 V: ToPyObject,
202 {
203 self.as_sequence().index(value)
204 }
205
206 /// Returns an iterator over the tuple items.
207 pub fn iter(&self) -> PyTupleIterator<'_> {
208 PyTupleIterator {
209 tuple: self,
210 index: 0,
211 length: self.len(),
212 }
213 }
214
215 /// Return a new list containing the contents of this tuple; equivalent to the Python expression `list(tuple)`.
216 ///
217 /// This method is equivalent to `self.as_sequence().to_list()` and faster than `PyList::new(py, self)`.
218 pub fn to_list(&self) -> &PyList {
219 self.as_sequence()
220 .to_list()
221 .expect("failed to convert tuple to list")
222 }
223}
224
225index_impls!(PyTuple, "tuple", PyTuple::len, PyTuple::get_slice);
226
227/// Used by `PyTuple::iter()`.
228pub struct PyTupleIterator<'a> {
229 tuple: &'a PyTuple,
230 index: usize,
231 length: usize,
232}
233
234impl<'a> PyTupleIterator<'a> {
235 unsafe fn get_item(&self, index: usize) -> &'a PyAny {
236 #[cfg(any(Py_LIMITED_API, PyPy))]
237 let item = self.tuple.get_item(index).expect("tuple.get failed");
238 #[cfg(not(any(Py_LIMITED_API, PyPy)))]
239 let item: &PyAny = self.tuple.get_item_unchecked(index);
240 item
241 }
242}
243
244impl<'a> Iterator for PyTupleIterator<'a> {
245 type Item = &'a PyAny;
246
247 #[inline]
248 fn next(&mut self) -> Option<Self::Item> {
249 if self.index < self.length {
250 let item: &PyAny = unsafe { self.get_item(self.index) };
251 self.index += 1;
252 Some(item)
253 } else {
254 None
255 }
256 }
257
258 #[inline]
259 fn size_hint(&self) -> (usize, Option<usize>) {
260 let len: usize = self.len();
261 (len, Some(len))
262 }
263}
264
265impl<'a> DoubleEndedIterator for PyTupleIterator<'a> {
266 #[inline]
267 fn next_back(&mut self) -> Option<Self::Item> {
268 if self.index < self.length {
269 let item: &PyAny = unsafe { self.get_item(self.length - 1) };
270 self.length -= 1;
271 Some(item)
272 } else {
273 None
274 }
275 }
276}
277
278impl<'a> ExactSizeIterator for PyTupleIterator<'a> {
279 fn len(&self) -> usize {
280 self.length.saturating_sub(self.index)
281 }
282}
283
284impl FusedIterator for PyTupleIterator<'_> {}
285
286impl<'a> IntoIterator for &'a PyTuple {
287 type Item = &'a PyAny;
288 type IntoIter = PyTupleIterator<'a>;
289
290 fn into_iter(self) -> Self::IntoIter {
291 self.iter()
292 }
293}
294
295#[cold]
296fn wrong_tuple_length(t: &PyTuple, expected_length: usize) -> PyErr {
297 let msg: String = format!(
298 "expected tuple of length {}, but got tuple of length {}",
299 expected_length,
300 t.len()
301 );
302 exceptions::PyValueError::new_err(args:msg)
303}
304
305macro_rules! tuple_conversion ({$length:expr,$(($refN:ident, $n:tt, $T:ident)),+} => {
306 impl <$($T: ToPyObject),+> ToPyObject for ($($T,)+) {
307 fn to_object(&self, py: Python<'_>) -> PyObject {
308 array_into_tuple(py, [$(self.$n.to_object(py)),+]).into()
309 }
310 }
311 impl <$($T: IntoPy<PyObject>),+> IntoPy<PyObject> for ($($T,)+) {
312 fn into_py(self, py: Python<'_>) -> PyObject {
313 array_into_tuple(py, [$(self.$n.into_py(py)),+]).into()
314 }
315
316 #[cfg(feature = "experimental-inspect")]
317fn type_output() -> TypeInfo {
318 TypeInfo::Tuple(Some(vec![$( $T::type_output() ),+]))
319 }
320 }
321
322 impl <$($T: IntoPy<PyObject>),+> IntoPy<Py<PyTuple>> for ($($T,)+) {
323 fn into_py(self, py: Python<'_>) -> Py<PyTuple> {
324 array_into_tuple(py, [$(self.$n.into_py(py)),+])
325 }
326
327 #[cfg(feature = "experimental-inspect")]
328 fn type_output() -> TypeInfo {
329 TypeInfo::Tuple(Some(vec![$( $T::type_output() ),+]))
330 }
331 }
332
333 impl<'s, $($T: FromPyObject<'s>),+> FromPyObject<'s> for ($($T,)+) {
334 fn extract(obj: &'s PyAny) -> PyResult<Self>
335 {
336 let t: &PyTuple = obj.downcast()?;
337 if t.len() == $length {
338 #[cfg(any(Py_LIMITED_API, PyPy))]
339 return Ok(($(t.get_item($n)?.extract::<$T>()?,)+));
340
341 #[cfg(not(any(Py_LIMITED_API, PyPy)))]
342 unsafe {return Ok(($(t.get_item_unchecked($n).extract::<$T>()?,)+));}
343 } else {
344 Err(wrong_tuple_length(t, $length))
345 }
346 }
347
348 #[cfg(feature = "experimental-inspect")]
349fn type_input() -> TypeInfo {
350 TypeInfo::Tuple(Some(vec![$( $T::type_input() ),+]))
351 }
352 }
353});
354
355fn array_into_tuple<const N: usize>(py: Python<'_>, array: [PyObject; N]) -> Py<PyTuple> {
356 unsafe {
357 let ptr: *mut PyObject = ffi::PyTuple_New(N.try_into().expect(msg:"0 < N <= 12"));
358 let tup: Py = Py::from_owned_ptr(py, ptr);
359 for (index: usize, obj: Py) in array.into_iter().enumerate() {
360 #[cfg(not(any(Py_LIMITED_API, PyPy)))]
361 ffi::PyTuple_SET_ITEM(op:ptr, i:index as ffi::Py_ssize_t, v:obj.into_ptr());
362 #[cfg(any(Py_LIMITED_API, PyPy))]
363 ffi::PyTuple_SetItem(ptr, index as ffi::Py_ssize_t, obj.into_ptr());
364 }
365 tup
366 }
367}
368
369tuple_conversion!(1, (ref0, 0, T0));
370tuple_conversion!(2, (ref0, 0, T0), (ref1, 1, T1));
371tuple_conversion!(3, (ref0, 0, T0), (ref1, 1, T1), (ref2, 2, T2));
372tuple_conversion!(
373 4,
374 (ref0, 0, T0),
375 (ref1, 1, T1),
376 (ref2, 2, T2),
377 (ref3, 3, T3)
378);
379tuple_conversion!(
380 5,
381 (ref0, 0, T0),
382 (ref1, 1, T1),
383 (ref2, 2, T2),
384 (ref3, 3, T3),
385 (ref4, 4, T4)
386);
387tuple_conversion!(
388 6,
389 (ref0, 0, T0),
390 (ref1, 1, T1),
391 (ref2, 2, T2),
392 (ref3, 3, T3),
393 (ref4, 4, T4),
394 (ref5, 5, T5)
395);
396tuple_conversion!(
397 7,
398 (ref0, 0, T0),
399 (ref1, 1, T1),
400 (ref2, 2, T2),
401 (ref3, 3, T3),
402 (ref4, 4, T4),
403 (ref5, 5, T5),
404 (ref6, 6, T6)
405);
406tuple_conversion!(
407 8,
408 (ref0, 0, T0),
409 (ref1, 1, T1),
410 (ref2, 2, T2),
411 (ref3, 3, T3),
412 (ref4, 4, T4),
413 (ref5, 5, T5),
414 (ref6, 6, T6),
415 (ref7, 7, T7)
416);
417tuple_conversion!(
418 9,
419 (ref0, 0, T0),
420 (ref1, 1, T1),
421 (ref2, 2, T2),
422 (ref3, 3, T3),
423 (ref4, 4, T4),
424 (ref5, 5, T5),
425 (ref6, 6, T6),
426 (ref7, 7, T7),
427 (ref8, 8, T8)
428);
429tuple_conversion!(
430 10,
431 (ref0, 0, T0),
432 (ref1, 1, T1),
433 (ref2, 2, T2),
434 (ref3, 3, T3),
435 (ref4, 4, T4),
436 (ref5, 5, T5),
437 (ref6, 6, T6),
438 (ref7, 7, T7),
439 (ref8, 8, T8),
440 (ref9, 9, T9)
441);
442tuple_conversion!(
443 11,
444 (ref0, 0, T0),
445 (ref1, 1, T1),
446 (ref2, 2, T2),
447 (ref3, 3, T3),
448 (ref4, 4, T4),
449 (ref5, 5, T5),
450 (ref6, 6, T6),
451 (ref7, 7, T7),
452 (ref8, 8, T8),
453 (ref9, 9, T9),
454 (ref10, 10, T10)
455);
456
457tuple_conversion!(
458 12,
459 (ref0, 0, T0),
460 (ref1, 1, T1),
461 (ref2, 2, T2),
462 (ref3, 3, T3),
463 (ref4, 4, T4),
464 (ref5, 5, T5),
465 (ref6, 6, T6),
466 (ref7, 7, T7),
467 (ref8, 8, T8),
468 (ref9, 9, T9),
469 (ref10, 10, T10),
470 (ref11, 11, T11)
471);
472
473#[cfg(test)]
474mod tests {
475 use crate::types::{PyAny, PyList, PyTuple};
476 use crate::{Python, ToPyObject};
477 use std::collections::HashSet;
478
479 #[test]
480 fn test_new() {
481 Python::with_gil(|py| {
482 let ob = PyTuple::new(py, [1, 2, 3]);
483 assert_eq!(3, ob.len());
484 let ob: &PyAny = ob.into();
485 assert_eq!((1, 2, 3), ob.extract().unwrap());
486
487 let mut map = HashSet::new();
488 map.insert(1);
489 map.insert(2);
490 PyTuple::new(py, map);
491 });
492 }
493
494 #[test]
495 fn test_len() {
496 Python::with_gil(|py| {
497 let ob = (1, 2, 3).to_object(py);
498 let tuple: &PyTuple = ob.downcast(py).unwrap();
499 assert_eq!(3, tuple.len());
500 let ob: &PyAny = tuple.into();
501 assert_eq!((1, 2, 3), ob.extract().unwrap());
502 });
503 }
504
505 #[test]
506 fn test_slice() {
507 Python::with_gil(|py| {
508 let tup = PyTuple::new(py, [2, 3, 5, 7]);
509 let slice = tup.get_slice(1, 3);
510 assert_eq!(2, slice.len());
511 let slice = tup.get_slice(1, 7);
512 assert_eq!(3, slice.len());
513 });
514 }
515
516 #[test]
517 fn test_iter() {
518 Python::with_gil(|py| {
519 let ob = (1, 2, 3).to_object(py);
520 let tuple: &PyTuple = ob.downcast(py).unwrap();
521 assert_eq!(3, tuple.len());
522 let mut iter = tuple.iter();
523
524 assert_eq!(iter.size_hint(), (3, Some(3)));
525
526 assert_eq!(1_i32, iter.next().unwrap().extract::<'_, i32>().unwrap());
527 assert_eq!(iter.size_hint(), (2, Some(2)));
528
529 assert_eq!(2_i32, iter.next().unwrap().extract::<'_, i32>().unwrap());
530 assert_eq!(iter.size_hint(), (1, Some(1)));
531
532 assert_eq!(3_i32, iter.next().unwrap().extract::<'_, i32>().unwrap());
533 assert_eq!(iter.size_hint(), (0, Some(0)));
534
535 assert!(iter.next().is_none());
536 assert!(iter.next().is_none());
537 });
538 }
539
540 #[test]
541 fn test_iter_rev() {
542 Python::with_gil(|py| {
543 let ob = (1, 2, 3).to_object(py);
544 let tuple: &PyTuple = ob.downcast(py).unwrap();
545 assert_eq!(3, tuple.len());
546 let mut iter = tuple.iter().rev();
547
548 assert_eq!(iter.size_hint(), (3, Some(3)));
549
550 assert_eq!(3_i32, iter.next().unwrap().extract::<'_, i32>().unwrap());
551 assert_eq!(iter.size_hint(), (2, Some(2)));
552
553 assert_eq!(2_i32, iter.next().unwrap().extract::<'_, i32>().unwrap());
554 assert_eq!(iter.size_hint(), (1, Some(1)));
555
556 assert_eq!(1_i32, iter.next().unwrap().extract::<'_, i32>().unwrap());
557 assert_eq!(iter.size_hint(), (0, Some(0)));
558
559 assert!(iter.next().is_none());
560 assert!(iter.next().is_none());
561 });
562 }
563
564 #[test]
565 fn test_into_iter() {
566 Python::with_gil(|py| {
567 let ob = (1, 2, 3).to_object(py);
568 let tuple: &PyTuple = ob.downcast(py).unwrap();
569 assert_eq!(3, tuple.len());
570
571 for (i, item) in tuple.iter().enumerate() {
572 assert_eq!(i + 1, item.extract::<'_, usize>().unwrap());
573 }
574 });
575 }
576
577 #[test]
578 #[cfg(not(Py_LIMITED_API))]
579 fn test_as_slice() {
580 Python::with_gil(|py| {
581 let ob = (1, 2, 3).to_object(py);
582 let tuple: &PyTuple = ob.downcast(py).unwrap();
583
584 let slice = tuple.as_slice();
585 assert_eq!(3, slice.len());
586 assert_eq!(1_i32, slice[0].extract::<'_, i32>().unwrap());
587 assert_eq!(2_i32, slice[1].extract::<'_, i32>().unwrap());
588 assert_eq!(3_i32, slice[2].extract::<'_, i32>().unwrap());
589 });
590 }
591
592 #[test]
593 fn test_tuple_lengths_up_to_12() {
594 Python::with_gil(|py| {
595 let t0 = (0,).to_object(py);
596 let t1 = (0, 1).to_object(py);
597 let t2 = (0, 1, 2).to_object(py);
598 let t3 = (0, 1, 2, 3).to_object(py);
599 let t4 = (0, 1, 2, 3, 4).to_object(py);
600 let t5 = (0, 1, 2, 3, 4, 5).to_object(py);
601 let t6 = (0, 1, 2, 3, 4, 5, 6).to_object(py);
602 let t7 = (0, 1, 2, 3, 4, 5, 6, 7).to_object(py);
603 let t8 = (0, 1, 2, 3, 4, 5, 6, 7, 8).to_object(py);
604 let t9 = (0, 1, 2, 3, 4, 5, 6, 7, 8, 9).to_object(py);
605 let t10 = (0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10).to_object(py);
606 let t11 = (0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11).to_object(py);
607
608 assert_eq!(t0.extract::<(i32,)>(py).unwrap(), (0,));
609 assert_eq!(t1.extract::<(i32, i32)>(py).unwrap(), (0, 1,));
610 assert_eq!(t2.extract::<(i32, i32, i32)>(py).unwrap(), (0, 1, 2,));
611 assert_eq!(
612 t3.extract::<(i32, i32, i32, i32,)>(py).unwrap(),
613 (0, 1, 2, 3,)
614 );
615 assert_eq!(
616 t4.extract::<(i32, i32, i32, i32, i32,)>(py).unwrap(),
617 (0, 1, 2, 3, 4,)
618 );
619 assert_eq!(
620 t5.extract::<(i32, i32, i32, i32, i32, i32,)>(py).unwrap(),
621 (0, 1, 2, 3, 4, 5,)
622 );
623 assert_eq!(
624 t6.extract::<(i32, i32, i32, i32, i32, i32, i32,)>(py)
625 .unwrap(),
626 (0, 1, 2, 3, 4, 5, 6,)
627 );
628 assert_eq!(
629 t7.extract::<(i32, i32, i32, i32, i32, i32, i32, i32,)>(py)
630 .unwrap(),
631 (0, 1, 2, 3, 4, 5, 6, 7,)
632 );
633 assert_eq!(
634 t8.extract::<(i32, i32, i32, i32, i32, i32, i32, i32, i32,)>(py)
635 .unwrap(),
636 (0, 1, 2, 3, 4, 5, 6, 7, 8,)
637 );
638 assert_eq!(
639 t9.extract::<(i32, i32, i32, i32, i32, i32, i32, i32, i32, i32,)>(py)
640 .unwrap(),
641 (0, 1, 2, 3, 4, 5, 6, 7, 8, 9,)
642 );
643 assert_eq!(
644 t10.extract::<(i32, i32, i32, i32, i32, i32, i32, i32, i32, i32, i32,)>(py)
645 .unwrap(),
646 (0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10,)
647 );
648 assert_eq!(
649 t11.extract::<(i32, i32, i32, i32, i32, i32, i32, i32, i32, i32, i32, i32,)>(py)
650 .unwrap(),
651 (0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11,)
652 );
653 })
654 }
655
656 #[test]
657 fn test_tuple_get_item_invalid_index() {
658 Python::with_gil(|py| {
659 let ob = (1, 2, 3).to_object(py);
660 let tuple: &PyTuple = ob.downcast(py).unwrap();
661 let obj = tuple.get_item(5);
662 assert!(obj.is_err());
663 assert_eq!(
664 obj.unwrap_err().to_string(),
665 "IndexError: tuple index out of range"
666 );
667 });
668 }
669
670 #[test]
671 fn test_tuple_get_item_sanity() {
672 Python::with_gil(|py| {
673 let ob = (1, 2, 3).to_object(py);
674 let tuple: &PyTuple = ob.downcast(py).unwrap();
675 let obj = tuple.get_item(0);
676 assert_eq!(obj.unwrap().extract::<i32>().unwrap(), 1);
677 });
678 }
679
680 #[cfg(not(any(Py_LIMITED_API, PyPy)))]
681 #[test]
682 fn test_tuple_get_item_unchecked_sanity() {
683 Python::with_gil(|py| {
684 let ob = (1, 2, 3).to_object(py);
685 let tuple: &PyTuple = ob.downcast(py).unwrap();
686 let obj = unsafe { tuple.get_item_unchecked(0) };
687 assert_eq!(obj.extract::<i32>().unwrap(), 1);
688 });
689 }
690
691 #[test]
692 fn test_tuple_index_trait() {
693 Python::with_gil(|py| {
694 let ob = (1, 2, 3).to_object(py);
695 let tuple: &PyTuple = ob.downcast(py).unwrap();
696 assert_eq!(1, tuple[0].extract::<i32>().unwrap());
697 assert_eq!(2, tuple[1].extract::<i32>().unwrap());
698 assert_eq!(3, tuple[2].extract::<i32>().unwrap());
699 });
700 }
701
702 #[test]
703 #[should_panic]
704 fn test_tuple_index_trait_panic() {
705 Python::with_gil(|py| {
706 let ob = (1, 2, 3).to_object(py);
707 let tuple: &PyTuple = ob.downcast(py).unwrap();
708 let _ = &tuple[7];
709 });
710 }
711
712 #[test]
713 fn test_tuple_index_trait_ranges() {
714 Python::with_gil(|py| {
715 let ob = (1, 2, 3).to_object(py);
716 let tuple: &PyTuple = ob.downcast(py).unwrap();
717 assert_eq!(vec![2, 3], tuple[1..3].extract::<Vec<i32>>().unwrap());
718 assert_eq!(
719 Vec::<i32>::new(),
720 tuple[3..3].extract::<Vec<i32>>().unwrap()
721 );
722 assert_eq!(vec![2, 3], tuple[1..].extract::<Vec<i32>>().unwrap());
723 assert_eq!(Vec::<i32>::new(), tuple[3..].extract::<Vec<i32>>().unwrap());
724 assert_eq!(vec![1, 2, 3], tuple[..].extract::<Vec<i32>>().unwrap());
725 assert_eq!(vec![2, 3], tuple[1..=2].extract::<Vec<i32>>().unwrap());
726 assert_eq!(vec![1, 2], tuple[..2].extract::<Vec<i32>>().unwrap());
727 assert_eq!(vec![1, 2], tuple[..=1].extract::<Vec<i32>>().unwrap());
728 })
729 }
730
731 #[test]
732 #[should_panic = "range start index 5 out of range for tuple of length 3"]
733 fn test_tuple_index_trait_range_panic_start() {
734 Python::with_gil(|py| {
735 let ob = (1, 2, 3).to_object(py);
736 let tuple: &PyTuple = ob.downcast(py).unwrap();
737 tuple[5..10].extract::<Vec<i32>>().unwrap();
738 })
739 }
740
741 #[test]
742 #[should_panic = "range end index 10 out of range for tuple of length 3"]
743 fn test_tuple_index_trait_range_panic_end() {
744 Python::with_gil(|py| {
745 let ob = (1, 2, 3).to_object(py);
746 let tuple: &PyTuple = ob.downcast(py).unwrap();
747 tuple[1..10].extract::<Vec<i32>>().unwrap();
748 })
749 }
750
751 #[test]
752 #[should_panic = "slice index starts at 2 but ends at 1"]
753 fn test_tuple_index_trait_range_panic_wrong_order() {
754 Python::with_gil(|py| {
755 let ob = (1, 2, 3).to_object(py);
756 let tuple: &PyTuple = ob.downcast(py).unwrap();
757 #[allow(clippy::reversed_empty_ranges)]
758 tuple[2..1].extract::<Vec<i32>>().unwrap();
759 })
760 }
761
762 #[test]
763 #[should_panic = "range start index 8 out of range for tuple of length 3"]
764 fn test_tuple_index_trait_range_from_panic() {
765 Python::with_gil(|py| {
766 let ob = (1, 2, 3).to_object(py);
767 let tuple: &PyTuple = ob.downcast(py).unwrap();
768 tuple[8..].extract::<Vec<i32>>().unwrap();
769 })
770 }
771
772 #[test]
773 fn test_tuple_contains() {
774 Python::with_gil(|py| {
775 let ob = (1, 1, 2, 3, 5, 8).to_object(py);
776 let tuple: &PyTuple = ob.downcast(py).unwrap();
777 assert_eq!(6, tuple.len());
778
779 let bad_needle = 7i32.to_object(py);
780 assert!(!tuple.contains(&bad_needle).unwrap());
781
782 let good_needle = 8i32.to_object(py);
783 assert!(tuple.contains(&good_needle).unwrap());
784
785 let type_coerced_needle = 8f32.to_object(py);
786 assert!(tuple.contains(&type_coerced_needle).unwrap());
787 });
788 }
789
790 #[test]
791 fn test_tuple_index() {
792 Python::with_gil(|py| {
793 let ob = (1, 1, 2, 3, 5, 8).to_object(py);
794 let tuple: &PyTuple = ob.downcast(py).unwrap();
795 assert_eq!(0, tuple.index(1i32).unwrap());
796 assert_eq!(2, tuple.index(2i32).unwrap());
797 assert_eq!(3, tuple.index(3i32).unwrap());
798 assert_eq!(4, tuple.index(5i32).unwrap());
799 assert_eq!(5, tuple.index(8i32).unwrap());
800 assert!(tuple.index(42i32).is_err());
801 });
802 }
803
804 use std::ops::Range;
805
806 // An iterator that lies about its `ExactSizeIterator` implementation.
807 // See https://github.com/PyO3/pyo3/issues/2118
808 struct FaultyIter(Range<usize>, usize);
809
810 impl Iterator for FaultyIter {
811 type Item = usize;
812
813 fn next(&mut self) -> Option<Self::Item> {
814 self.0.next()
815 }
816 }
817
818 impl ExactSizeIterator for FaultyIter {
819 fn len(&self) -> usize {
820 self.1
821 }
822 }
823
824 #[test]
825 #[should_panic(
826 expected = "Attempted to create PyTuple but `elements` was larger than reported by its `ExactSizeIterator` implementation."
827 )]
828 fn too_long_iterator() {
829 Python::with_gil(|py| {
830 let iter = FaultyIter(0..usize::MAX, 73);
831 let _tuple = PyTuple::new(py, iter);
832 })
833 }
834
835 #[test]
836 #[should_panic(
837 expected = "Attempted to create PyTuple but `elements` was smaller than reported by its `ExactSizeIterator` implementation."
838 )]
839 fn too_short_iterator() {
840 Python::with_gil(|py| {
841 let iter = FaultyIter(0..35, 73);
842 let _tuple = PyTuple::new(py, iter);
843 })
844 }
845
846 #[test]
847 #[should_panic(
848 expected = "out of range integral type conversion attempted on `elements.len()`"
849 )]
850 fn overflowing_size() {
851 Python::with_gil(|py| {
852 let iter = FaultyIter(0..0, usize::MAX);
853
854 let _tuple = PyTuple::new(py, iter);
855 })
856 }
857
858 #[cfg(feature = "macros")]
859 #[test]
860 fn bad_clone_mem_leaks() {
861 use crate::{IntoPy, Py};
862 use std::sync::atomic::{AtomicUsize, Ordering::SeqCst};
863
864 static NEEDS_DESTRUCTING_COUNT: AtomicUsize = AtomicUsize::new(0);
865
866 #[crate::pyclass]
867 #[pyo3(crate = "crate")]
868 struct Bad(usize);
869
870 impl Clone for Bad {
871 fn clone(&self) -> Self {
872 // This panic should not lead to a memory leak
873 assert_ne!(self.0, 42);
874 NEEDS_DESTRUCTING_COUNT.fetch_add(1, SeqCst);
875
876 Bad(self.0)
877 }
878 }
879
880 impl Drop for Bad {
881 fn drop(&mut self) {
882 NEEDS_DESTRUCTING_COUNT.fetch_sub(1, SeqCst);
883 }
884 }
885
886 impl ToPyObject for Bad {
887 fn to_object(&self, py: Python<'_>) -> Py<PyAny> {
888 self.to_owned().into_py(py)
889 }
890 }
891
892 struct FaultyIter(Range<usize>, usize);
893
894 impl Iterator for FaultyIter {
895 type Item = Bad;
896
897 fn next(&mut self) -> Option<Self::Item> {
898 self.0.next().map(|i| {
899 NEEDS_DESTRUCTING_COUNT.fetch_add(1, SeqCst);
900 Bad(i)
901 })
902 }
903 }
904
905 impl ExactSizeIterator for FaultyIter {
906 fn len(&self) -> usize {
907 self.1
908 }
909 }
910
911 Python::with_gil(|py| {
912 std::panic::catch_unwind(|| {
913 let iter = FaultyIter(0..50, 50);
914 let _tuple = PyTuple::new(py, iter);
915 })
916 .unwrap_err();
917 });
918
919 assert_eq!(
920 NEEDS_DESTRUCTING_COUNT.load(SeqCst),
921 0,
922 "Some destructors did not run"
923 );
924 }
925
926 #[cfg(feature = "macros")]
927 #[test]
928 fn bad_clone_mem_leaks_2() {
929 use crate::{IntoPy, Py};
930 use std::sync::atomic::{AtomicUsize, Ordering::SeqCst};
931
932 static NEEDS_DESTRUCTING_COUNT: AtomicUsize = AtomicUsize::new(0);
933
934 #[crate::pyclass]
935 #[pyo3(crate = "crate")]
936 struct Bad(usize);
937
938 impl Clone for Bad {
939 fn clone(&self) -> Self {
940 // This panic should not lead to a memory leak
941 assert_ne!(self.0, 3);
942 NEEDS_DESTRUCTING_COUNT.fetch_add(1, SeqCst);
943
944 Bad(self.0)
945 }
946 }
947
948 impl Drop for Bad {
949 fn drop(&mut self) {
950 NEEDS_DESTRUCTING_COUNT.fetch_sub(1, SeqCst);
951 }
952 }
953
954 impl ToPyObject for Bad {
955 fn to_object(&self, py: Python<'_>) -> Py<PyAny> {
956 self.to_owned().into_py(py)
957 }
958 }
959
960 let s = (Bad(1), Bad(2), Bad(3), Bad(4));
961 NEEDS_DESTRUCTING_COUNT.store(4, SeqCst);
962 Python::with_gil(|py| {
963 std::panic::catch_unwind(|| {
964 let _tuple: Py<PyAny> = s.to_object(py);
965 })
966 .unwrap_err();
967 });
968 drop(s);
969
970 assert_eq!(
971 NEEDS_DESTRUCTING_COUNT.load(SeqCst),
972 0,
973 "Some destructors did not run"
974 );
975 }
976
977 #[test]
978 fn test_tuple_to_list() {
979 Python::with_gil(|py| {
980 let tuple = PyTuple::new(py, vec![1, 2, 3]);
981 let list = tuple.to_list();
982 let list_expected = PyList::new(py, vec![1, 2, 3]);
983 assert!(list.eq(list_expected).unwrap());
984 })
985 }
986}
987