pyo3/types/
tuple.rs

1use std::iter::FusedIterator;
2
3use crate::conversion::private;
4use crate::ffi::{self, Py_ssize_t};
5use crate::ffi_ptr_ext::FfiPtrExt;
6#[cfg(feature = "experimental-inspect")]
7use crate::inspect::types::TypeInfo;
8use crate::instance::Borrowed;
9use crate::internal_tricks::get_ssize_index;
10use crate::types::{
11    any::PyAnyMethods, sequence::PySequenceMethods, PyDict, PyList, PySequence, PyString,
12};
13#[cfg(feature = "gil-refs")]
14use crate::PyNativeType;
15use crate::{
16    exceptions, Bound, FromPyObject, IntoPy, Py, PyAny, PyErr, PyObject, PyResult, Python,
17    ToPyObject,
18};
19
20#[inline]
21#[track_caller]
22fn new_from_iter<'py>(
23    py: Python<'py>,
24    elements: &mut dyn ExactSizeIterator<Item = PyObject>,
25) -> Bound<'py, PyTuple> {
26    unsafe {
27        // PyTuple_New checks for overflow but has a bad error message, so we check ourselves
28        let len: Py_ssize_t = elements
29            .len()
30            .try_into()
31            .expect("out of range integral type conversion attempted on `elements.len()`");
32
33        let ptr = ffi::PyTuple_New(len);
34
35        // - Panics if the ptr is null
36        // - Cleans up the tuple if `convert` or the asserts panic
37        let tup = ptr.assume_owned(py).downcast_into_unchecked();
38
39        let mut counter: Py_ssize_t = 0;
40
41        for obj in elements.take(len as usize) {
42            #[cfg(not(any(Py_LIMITED_API, PyPy, GraalPy)))]
43            ffi::PyTuple_SET_ITEM(ptr, counter, obj.into_ptr());
44            #[cfg(any(Py_LIMITED_API, PyPy, GraalPy))]
45            ffi::PyTuple_SetItem(ptr, counter, obj.into_ptr());
46            counter += 1;
47        }
48
49        assert!(elements.next().is_none(), "Attempted to create PyTuple but `elements` was larger than reported by its `ExactSizeIterator` implementation.");
50        assert_eq!(len, counter, "Attempted to create PyTuple but `elements` was smaller than reported by its `ExactSizeIterator` implementation.");
51
52        tup
53    }
54}
55
56/// Represents a Python `tuple` object.
57///
58/// Values of this type are accessed via PyO3's smart pointers, e.g. as
59/// [`Py<PyTuple>`][crate::Py] or [`Bound<'py, PyTuple>`][Bound].
60///
61/// For APIs available on `tuple` objects, see the [`PyTupleMethods`] trait which is implemented for
62/// [`Bound<'py, PyTuple>`][Bound].
63#[repr(transparent)]
64pub struct PyTuple(PyAny);
65
66pyobject_native_type_core!(PyTuple, pyobject_native_static_type_object!(ffi::PyTuple_Type), #checkfunction=ffi::PyTuple_Check);
67
68impl PyTuple {
69    /// Constructs a new tuple with the given elements.
70    ///
71    /// If you want to create a [`PyTuple`] with elements of different or unknown types, or from an
72    /// iterable that doesn't implement [`ExactSizeIterator`], create a Rust tuple with the given
73    /// elements and convert it at once using `into_py`.
74    ///
75    /// # Examples
76    ///
77    /// ```rust
78    /// use pyo3::prelude::*;
79    /// use pyo3::types::PyTuple;
80    ///
81    /// # fn main() {
82    /// Python::with_gil(|py| {
83    ///     let elements: Vec<i32> = vec![0, 1, 2, 3, 4, 5];
84    ///     let tuple = PyTuple::new_bound(py, elements);
85    ///     assert_eq!(format!("{:?}", tuple), "(0, 1, 2, 3, 4, 5)");
86    /// });
87    /// # }
88    /// ```
89    ///
90    /// # Panics
91    ///
92    /// This function will panic if `element`'s [`ExactSizeIterator`] implementation is incorrect.
93    /// All standard library structures implement this trait correctly, if they do, so calling this
94    /// function using [`Vec`]`<T>` or `&[T]` will always succeed.
95    #[track_caller]
96    pub fn new_bound<T, U>(
97        py: Python<'_>,
98        elements: impl IntoIterator<Item = T, IntoIter = U>,
99    ) -> Bound<'_, PyTuple>
100    where
101        T: ToPyObject,
102        U: ExactSizeIterator<Item = T>,
103    {
104        let mut elements = elements.into_iter().map(|e| e.to_object(py));
105        new_from_iter(py, &mut elements)
106    }
107
108    /// Constructs an empty tuple (on the Python side, a singleton object).
109    pub fn empty_bound(py: Python<'_>) -> Bound<'_, PyTuple> {
110        unsafe {
111            ffi::PyTuple_New(0)
112                .assume_owned(py)
113                .downcast_into_unchecked()
114        }
115    }
116}
117
118#[cfg(feature = "gil-refs")]
119impl PyTuple {
120    /// Deprecated form of `PyTuple::new_bound`.
121    #[track_caller]
122    #[deprecated(
123        since = "0.21.0",
124        note = "`PyTuple::new` will be replaced by `PyTuple::new_bound` in a future PyO3 version"
125    )]
126    pub fn new<T, U>(
127        py: Python<'_>,
128        elements: impl IntoIterator<Item = T, IntoIter = U>,
129    ) -> &PyTuple
130    where
131        T: ToPyObject,
132        U: ExactSizeIterator<Item = T>,
133    {
134        Self::new_bound(py, elements).into_gil_ref()
135    }
136
137    /// Deprecated form of `PyTuple::empty_bound`.
138    #[deprecated(
139        since = "0.21.0",
140        note = "`PyTuple::empty` will be replaced by `PyTuple::empty_bound` in a future PyO3 version"
141    )]
142    pub fn empty(py: Python<'_>) -> &PyTuple {
143        Self::empty_bound(py).into_gil_ref()
144    }
145
146    /// Gets the length of the tuple.
147    pub fn len(&self) -> usize {
148        self.as_borrowed().len()
149    }
150
151    /// Checks if the tuple is empty.
152    pub fn is_empty(&self) -> bool {
153        self.as_borrowed().is_empty()
154    }
155
156    /// Returns `self` cast as a `PySequence`.
157    pub fn as_sequence(&self) -> &PySequence {
158        unsafe { self.downcast_unchecked() }
159    }
160
161    /// Takes the slice `self[low:high]` and returns it as a new tuple.
162    ///
163    /// Indices must be nonnegative, and out-of-range indices are clipped to
164    /// `self.len()`.
165    pub fn get_slice(&self, low: usize, high: usize) -> &PyTuple {
166        self.as_borrowed().get_slice(low, high).into_gil_ref()
167    }
168
169    /// Gets the tuple item at the specified index.
170    /// # Example
171    /// ```
172    /// use pyo3::{prelude::*, types::PyTuple};
173    ///
174    /// # fn main() -> PyResult<()> {
175    /// Python::with_gil(|py| -> PyResult<()> {
176    ///     let ob = (1, 2, 3).to_object(py);
177    ///     let tuple = ob.downcast_bound::<PyTuple>(py).unwrap();
178    ///     let obj = tuple.get_item(0);
179    ///     assert_eq!(obj.unwrap().extract::<i32>().unwrap(), 1);
180    ///     Ok(())
181    /// })
182    /// # }
183    /// ```
184    pub fn get_item(&self, index: usize) -> PyResult<&PyAny> {
185        self.as_borrowed()
186            .get_borrowed_item(index)
187            .map(Borrowed::into_gil_ref)
188    }
189
190    /// Gets the tuple item at the specified index. Undefined behavior on bad index. Use with caution.
191    ///
192    /// # Safety
193    ///
194    /// Caller must verify that the index is within the bounds of the tuple.
195    #[cfg(not(any(Py_LIMITED_API, PyPy, GraalPy)))]
196    pub unsafe fn get_item_unchecked(&self, index: usize) -> &PyAny {
197        self.as_borrowed()
198            .get_borrowed_item_unchecked(index)
199            .into_gil_ref()
200    }
201
202    /// Returns `self` as a slice of objects.
203    #[cfg(not(any(Py_LIMITED_API, GraalPy)))]
204    pub fn as_slice(&self) -> &[&PyAny] {
205        // This is safe because &PyAny has the same memory layout as *mut ffi::PyObject,
206        // and because tuples are immutable.
207        unsafe {
208            let ptr = self.as_ptr() as *mut ffi::PyTupleObject;
209            let slice = std::slice::from_raw_parts((*ptr).ob_item.as_ptr(), self.len());
210            &*(slice as *const [*mut ffi::PyObject] as *const [&PyAny])
211        }
212    }
213
214    /// Determines if self contains `value`.
215    ///
216    /// This is equivalent to the Python expression `value in self`.
217    #[inline]
218    pub fn contains<V>(&self, value: V) -> PyResult<bool>
219    where
220        V: ToPyObject,
221    {
222        self.as_borrowed().contains(value)
223    }
224
225    /// Returns the first index `i` for which `self[i] == value`.
226    ///
227    /// This is equivalent to the Python expression `self.index(value)`.
228    #[inline]
229    pub fn index<V>(&self, value: V) -> PyResult<usize>
230    where
231        V: ToPyObject,
232    {
233        self.as_borrowed().index(value)
234    }
235
236    /// Returns an iterator over the tuple items.
237    pub fn iter(&self) -> PyTupleIterator<'_> {
238        PyTupleIterator(BorrowedTupleIterator::new(self.as_borrowed()))
239    }
240
241    /// Return a new list containing the contents of this tuple; equivalent to the Python expression `list(tuple)`.
242    ///
243    /// This method is equivalent to `self.as_sequence().to_list()` and faster than `PyList::new(py, self)`.
244    pub fn to_list(&self) -> &PyList {
245        self.as_borrowed().to_list().into_gil_ref()
246    }
247}
248
249#[cfg(feature = "gil-refs")]
250index_impls!(PyTuple, "tuple", PyTuple::len, PyTuple::get_slice);
251
252/// Implementation of functionality for [`PyTuple`].
253///
254/// These methods are defined for the `Bound<'py, PyTuple>` smart pointer, so to use method call
255/// syntax these methods are separated into a trait, because stable Rust does not yet support
256/// `arbitrary_self_types`.
257#[doc(alias = "PyTuple")]
258pub trait PyTupleMethods<'py>: crate::sealed::Sealed {
259    /// Gets the length of the tuple.
260    fn len(&self) -> usize;
261
262    /// Checks if the tuple is empty.
263    fn is_empty(&self) -> bool;
264
265    /// Returns `self` cast as a `PySequence`.
266    fn as_sequence(&self) -> &Bound<'py, PySequence>;
267
268    /// Returns `self` cast as a `PySequence`.
269    fn into_sequence(self) -> Bound<'py, PySequence>;
270
271    /// Takes the slice `self[low:high]` and returns it as a new tuple.
272    ///
273    /// Indices must be nonnegative, and out-of-range indices are clipped to
274    /// `self.len()`.
275    fn get_slice(&self, low: usize, high: usize) -> Bound<'py, PyTuple>;
276
277    /// Gets the tuple item at the specified index.
278    /// # Example
279    /// ```
280    /// use pyo3::{prelude::*, types::PyTuple};
281    ///
282    /// # fn main() -> PyResult<()> {
283    /// Python::with_gil(|py| -> PyResult<()> {
284    ///     let ob = (1, 2, 3).to_object(py);
285    ///     let tuple = ob.downcast_bound::<PyTuple>(py).unwrap();
286    ///     let obj = tuple.get_item(0);
287    ///     assert_eq!(obj.unwrap().extract::<i32>().unwrap(), 1);
288    ///     Ok(())
289    /// })
290    /// # }
291    /// ```
292    fn get_item(&self, index: usize) -> PyResult<Bound<'py, PyAny>>;
293
294    /// Like [`get_item`][PyTupleMethods::get_item], but returns a borrowed object, which is a slight performance optimization
295    /// by avoiding a reference count change.
296    fn get_borrowed_item<'a>(&'a self, index: usize) -> PyResult<Borrowed<'a, 'py, PyAny>>;
297
298    /// Gets the tuple item at the specified index. Undefined behavior on bad index. Use with caution.
299    ///
300    /// # Safety
301    ///
302    /// Caller must verify that the index is within the bounds of the tuple.
303    #[cfg(not(any(Py_LIMITED_API, PyPy, GraalPy)))]
304    unsafe fn get_item_unchecked(&self, index: usize) -> Bound<'py, PyAny>;
305
306    /// Like [`get_item_unchecked`][PyTupleMethods::get_item_unchecked], but returns a borrowed object,
307    /// which is a slight performance optimization by avoiding a reference count change.
308    ///
309    /// # Safety
310    ///
311    /// Caller must verify that the index is within the bounds of the tuple.
312    #[cfg(not(any(Py_LIMITED_API, PyPy, GraalPy)))]
313    unsafe fn get_borrowed_item_unchecked<'a>(&'a self, index: usize) -> Borrowed<'a, 'py, PyAny>;
314
315    /// Returns `self` as a slice of objects.
316    #[cfg(not(any(Py_LIMITED_API, GraalPy)))]
317    fn as_slice(&self) -> &[Bound<'py, PyAny>];
318
319    /// Determines if self contains `value`.
320    ///
321    /// This is equivalent to the Python expression `value in self`.
322    fn contains<V>(&self, value: V) -> PyResult<bool>
323    where
324        V: ToPyObject;
325
326    /// Returns the first index `i` for which `self[i] == value`.
327    ///
328    /// This is equivalent to the Python expression `self.index(value)`.
329    fn index<V>(&self, value: V) -> PyResult<usize>
330    where
331        V: ToPyObject;
332
333    /// Returns an iterator over the tuple items.
334    fn iter(&self) -> BoundTupleIterator<'py>;
335
336    /// Like [`iter`][PyTupleMethods::iter], but produces an iterator which returns borrowed objects,
337    /// which is a slight performance optimization by avoiding a reference count change.
338    fn iter_borrowed<'a>(&'a self) -> BorrowedTupleIterator<'a, 'py>;
339
340    /// Return a new list containing the contents of this tuple; equivalent to the Python expression `list(tuple)`.
341    ///
342    /// This method is equivalent to `self.as_sequence().to_list()` and faster than `PyList::new(py, self)`.
343    fn to_list(&self) -> Bound<'py, PyList>;
344}
345
346impl<'py> PyTupleMethods<'py> for Bound<'py, PyTuple> {
347    fn len(&self) -> usize {
348        unsafe {
349            #[cfg(not(any(Py_LIMITED_API, PyPy, GraalPy)))]
350            let size = ffi::PyTuple_GET_SIZE(self.as_ptr());
351            #[cfg(any(Py_LIMITED_API, PyPy, GraalPy))]
352            let size = ffi::PyTuple_Size(self.as_ptr());
353            // non-negative Py_ssize_t should always fit into Rust uint
354            size as usize
355        }
356    }
357
358    fn is_empty(&self) -> bool {
359        self.len() == 0
360    }
361
362    fn as_sequence(&self) -> &Bound<'py, PySequence> {
363        unsafe { self.downcast_unchecked() }
364    }
365
366    fn into_sequence(self) -> Bound<'py, PySequence> {
367        unsafe { self.into_any().downcast_into_unchecked() }
368    }
369
370    fn get_slice(&self, low: usize, high: usize) -> Bound<'py, PyTuple> {
371        unsafe {
372            ffi::PyTuple_GetSlice(self.as_ptr(), get_ssize_index(low), get_ssize_index(high))
373                .assume_owned(self.py())
374                .downcast_into_unchecked()
375        }
376    }
377
378    fn get_item(&self, index: usize) -> PyResult<Bound<'py, PyAny>> {
379        self.get_borrowed_item(index).map(Borrowed::to_owned)
380    }
381
382    fn get_borrowed_item<'a>(&'a self, index: usize) -> PyResult<Borrowed<'a, 'py, PyAny>> {
383        self.as_borrowed().get_borrowed_item(index)
384    }
385
386    #[cfg(not(any(Py_LIMITED_API, PyPy, GraalPy)))]
387    unsafe fn get_item_unchecked(&self, index: usize) -> Bound<'py, PyAny> {
388        self.get_borrowed_item_unchecked(index).to_owned()
389    }
390
391    #[cfg(not(any(Py_LIMITED_API, PyPy, GraalPy)))]
392    unsafe fn get_borrowed_item_unchecked<'a>(&'a self, index: usize) -> Borrowed<'a, 'py, PyAny> {
393        self.as_borrowed().get_borrowed_item_unchecked(index)
394    }
395
396    #[cfg(not(any(Py_LIMITED_API, GraalPy)))]
397    fn as_slice(&self) -> &[Bound<'py, PyAny>] {
398        // SAFETY: self is known to be a tuple object, and tuples are immutable
399        let items = unsafe { &(*self.as_ptr().cast::<ffi::PyTupleObject>()).ob_item };
400        // SAFETY: Bound<'py, PyAny> has the same memory layout as *mut ffi::PyObject
401        unsafe { std::slice::from_raw_parts(items.as_ptr().cast(), self.len()) }
402    }
403
404    #[inline]
405    fn contains<V>(&self, value: V) -> PyResult<bool>
406    where
407        V: ToPyObject,
408    {
409        self.as_sequence().contains(value)
410    }
411
412    #[inline]
413    fn index<V>(&self, value: V) -> PyResult<usize>
414    where
415        V: ToPyObject,
416    {
417        self.as_sequence().index(value)
418    }
419
420    fn iter(&self) -> BoundTupleIterator<'py> {
421        BoundTupleIterator::new(self.clone())
422    }
423
424    fn iter_borrowed<'a>(&'a self) -> BorrowedTupleIterator<'a, 'py> {
425        self.as_borrowed().iter_borrowed()
426    }
427
428    fn to_list(&self) -> Bound<'py, PyList> {
429        self.as_sequence()
430            .to_list()
431            .expect("failed to convert tuple to list")
432    }
433}
434
435impl<'a, 'py> Borrowed<'a, 'py, PyTuple> {
436    fn get_borrowed_item(self, index: usize) -> PyResult<Borrowed<'a, 'py, PyAny>> {
437        unsafe {
438            ffi::PyTuple_GetItem(self.as_ptr(), index as Py_ssize_t)
439                .assume_borrowed_or_err(self.py())
440        }
441    }
442
443    #[cfg(not(any(Py_LIMITED_API, PyPy, GraalPy)))]
444    unsafe fn get_borrowed_item_unchecked(self, index: usize) -> Borrowed<'a, 'py, PyAny> {
445        ffi::PyTuple_GET_ITEM(self.as_ptr(), index as Py_ssize_t).assume_borrowed(self.py())
446    }
447
448    pub(crate) fn iter_borrowed(self) -> BorrowedTupleIterator<'a, 'py> {
449        BorrowedTupleIterator::new(self)
450    }
451}
452
453/// Used by `PyTuple::iter()`.
454#[cfg(feature = "gil-refs")]
455pub struct PyTupleIterator<'a>(BorrowedTupleIterator<'a, 'a>);
456
457#[cfg(feature = "gil-refs")]
458impl<'a> Iterator for PyTupleIterator<'a> {
459    type Item = &'a PyAny;
460
461    #[inline]
462    fn next(&mut self) -> Option<Self::Item> {
463        self.0.next().map(Borrowed::into_gil_ref)
464    }
465
466    #[inline]
467    fn size_hint(&self) -> (usize, Option<usize>) {
468        self.0.size_hint()
469    }
470}
471
472#[cfg(feature = "gil-refs")]
473impl<'a> DoubleEndedIterator for PyTupleIterator<'a> {
474    #[inline]
475    fn next_back(&mut self) -> Option<Self::Item> {
476        self.0.next_back().map(Borrowed::into_gil_ref)
477    }
478}
479
480#[cfg(feature = "gil-refs")]
481impl<'a> ExactSizeIterator for PyTupleIterator<'a> {
482    fn len(&self) -> usize {
483        self.0.len()
484    }
485}
486
487#[cfg(feature = "gil-refs")]
488impl FusedIterator for PyTupleIterator<'_> {}
489
490#[cfg(feature = "gil-refs")]
491impl<'a> IntoIterator for &'a PyTuple {
492    type Item = &'a PyAny;
493    type IntoIter = PyTupleIterator<'a>;
494
495    fn into_iter(self) -> Self::IntoIter {
496        PyTupleIterator(BorrowedTupleIterator::new(self.as_borrowed()))
497    }
498}
499
500/// Used by `PyTuple::into_iter()`.
501pub struct BoundTupleIterator<'py> {
502    tuple: Bound<'py, PyTuple>,
503    index: usize,
504    length: usize,
505}
506
507impl<'py> BoundTupleIterator<'py> {
508    fn new(tuple: Bound<'py, PyTuple>) -> Self {
509        let length = tuple.len();
510        BoundTupleIterator {
511            tuple,
512            index: 0,
513            length,
514        }
515    }
516}
517
518impl<'py> Iterator for BoundTupleIterator<'py> {
519    type Item = Bound<'py, PyAny>;
520
521    #[inline]
522    fn next(&mut self) -> Option<Self::Item> {
523        if self.index < self.length {
524            let item = unsafe {
525                BorrowedTupleIterator::get_item(self.tuple.as_borrowed(), self.index).to_owned()
526            };
527            self.index += 1;
528            Some(item)
529        } else {
530            None
531        }
532    }
533
534    #[inline]
535    fn size_hint(&self) -> (usize, Option<usize>) {
536        let len = self.len();
537        (len, Some(len))
538    }
539}
540
541impl<'py> DoubleEndedIterator for BoundTupleIterator<'py> {
542    #[inline]
543    fn next_back(&mut self) -> Option<Self::Item> {
544        if self.index < self.length {
545            let item = unsafe {
546                BorrowedTupleIterator::get_item(self.tuple.as_borrowed(), self.length - 1)
547                    .to_owned()
548            };
549            self.length -= 1;
550            Some(item)
551        } else {
552            None
553        }
554    }
555}
556
557impl<'py> ExactSizeIterator for BoundTupleIterator<'py> {
558    fn len(&self) -> usize {
559        self.length.saturating_sub(self.index)
560    }
561}
562
563impl FusedIterator for BoundTupleIterator<'_> {}
564
565impl<'py> IntoIterator for Bound<'py, PyTuple> {
566    type Item = Bound<'py, PyAny>;
567    type IntoIter = BoundTupleIterator<'py>;
568
569    fn into_iter(self) -> Self::IntoIter {
570        BoundTupleIterator::new(self)
571    }
572}
573
574impl<'py> IntoIterator for &Bound<'py, PyTuple> {
575    type Item = Bound<'py, PyAny>;
576    type IntoIter = BoundTupleIterator<'py>;
577
578    fn into_iter(self) -> Self::IntoIter {
579        self.iter()
580    }
581}
582
583/// Used by `PyTuple::iter_borrowed()`.
584pub struct BorrowedTupleIterator<'a, 'py> {
585    tuple: Borrowed<'a, 'py, PyTuple>,
586    index: usize,
587    length: usize,
588}
589
590impl<'a, 'py> BorrowedTupleIterator<'a, 'py> {
591    fn new(tuple: Borrowed<'a, 'py, PyTuple>) -> Self {
592        let length = tuple.len();
593        BorrowedTupleIterator {
594            tuple,
595            index: 0,
596            length,
597        }
598    }
599
600    unsafe fn get_item(
601        tuple: Borrowed<'a, 'py, PyTuple>,
602        index: usize,
603    ) -> Borrowed<'a, 'py, PyAny> {
604        #[cfg(any(Py_LIMITED_API, PyPy, GraalPy))]
605        let item = tuple.get_borrowed_item(index).expect("tuple.get failed");
606        #[cfg(not(any(Py_LIMITED_API, PyPy, GraalPy)))]
607        let item = tuple.get_borrowed_item_unchecked(index);
608        item
609    }
610}
611
612impl<'a, 'py> Iterator for BorrowedTupleIterator<'a, 'py> {
613    type Item = Borrowed<'a, 'py, PyAny>;
614
615    #[inline]
616    fn next(&mut self) -> Option<Self::Item> {
617        if self.index < self.length {
618            let item = unsafe { Self::get_item(self.tuple, self.index) };
619            self.index += 1;
620            Some(item)
621        } else {
622            None
623        }
624    }
625
626    #[inline]
627    fn size_hint(&self) -> (usize, Option<usize>) {
628        let len = self.len();
629        (len, Some(len))
630    }
631}
632
633impl<'a, 'py> DoubleEndedIterator for BorrowedTupleIterator<'a, 'py> {
634    #[inline]
635    fn next_back(&mut self) -> Option<Self::Item> {
636        if self.index < self.length {
637            let item = unsafe { Self::get_item(self.tuple, self.length - 1) };
638            self.length -= 1;
639            Some(item)
640        } else {
641            None
642        }
643    }
644}
645
646impl<'a, 'py> ExactSizeIterator for BorrowedTupleIterator<'a, 'py> {
647    fn len(&self) -> usize {
648        self.length.saturating_sub(self.index)
649    }
650}
651
652impl FusedIterator for BorrowedTupleIterator<'_, '_> {}
653
654impl IntoPy<Py<PyTuple>> for Bound<'_, PyTuple> {
655    fn into_py(self, _: Python<'_>) -> Py<PyTuple> {
656        self.unbind()
657    }
658}
659
660impl IntoPy<Py<PyTuple>> for &'_ Bound<'_, PyTuple> {
661    fn into_py(self, _: Python<'_>) -> Py<PyTuple> {
662        self.clone().unbind()
663    }
664}
665
666#[cold]
667fn wrong_tuple_length(t: &Bound<'_, PyTuple>, expected_length: usize) -> PyErr {
668    let msg = format!(
669        "expected tuple of length {}, but got tuple of length {}",
670        expected_length,
671        t.len()
672    );
673    exceptions::PyValueError::new_err(msg)
674}
675
676macro_rules! tuple_conversion ({$length:expr,$(($refN:ident, $n:tt, $T:ident)),+} => {
677    impl <$($T: ToPyObject),+> ToPyObject for ($($T,)+) {
678        fn to_object(&self, py: Python<'_>) -> PyObject {
679            array_into_tuple(py, [$(self.$n.to_object(py)),+]).into()
680        }
681    }
682    impl <$($T: IntoPy<PyObject>),+> IntoPy<PyObject> for ($($T,)+) {
683        fn into_py(self, py: Python<'_>) -> PyObject {
684            array_into_tuple(py, [$(self.$n.into_py(py)),+]).into()
685        }
686
687        #[cfg(feature = "experimental-inspect")]
688        fn type_output() -> TypeInfo {
689            TypeInfo::Tuple(Some(vec![$( $T::type_output() ),+]))
690        }
691    }
692
693    impl <$($T: IntoPy<PyObject>),+> IntoPy<Py<PyTuple>> for ($($T,)+) {
694        fn into_py(self, py: Python<'_>) -> Py<PyTuple> {
695            array_into_tuple(py, [$(self.$n.into_py(py)),+])
696        }
697
698        #[cfg(feature = "experimental-inspect")]
699        fn type_output() -> TypeInfo {
700            TypeInfo::Tuple(Some(vec![$( $T::type_output() ),+]))
701        }
702
703        #[inline]
704        fn __py_call_vectorcall1<'py>(
705            self,
706            py: Python<'py>,
707            function: Borrowed<'_, 'py, PyAny>,
708            _: private::Token,
709        ) -> PyResult<Bound<'py, PyAny>> {
710            cfg_if::cfg_if! {
711                if #[cfg(all(Py_3_9, not(any(PyPy, GraalPy, Py_LIMITED_API))))] {
712                    // We need this to drop the arguments correctly.
713                    let args_bound = [$(self.$n.into_py(py).into_bound(py),)*];
714                    if $length == 1 {
715                        unsafe {
716                            ffi::PyObject_CallOneArg(function.as_ptr(), args_bound[0].as_ptr()).assume_owned_or_err(py)
717                        }
718                    } else {
719                        // Prepend one null argument for `PY_VECTORCALL_ARGUMENTS_OFFSET`.
720                        let mut args = [std::ptr::null_mut(), $(args_bound[$n].as_ptr()),*];
721                        unsafe {
722                            ffi::PyObject_Vectorcall(
723                                function.as_ptr(),
724                                args.as_mut_ptr().add(1),
725                                $length + ffi::PY_VECTORCALL_ARGUMENTS_OFFSET,
726                                std::ptr::null_mut(),
727                            )
728                            .assume_owned_or_err(py)
729                        }
730                    }
731                } else {
732                    function.call1(<Self as IntoPy<Py<PyTuple>>>::into_py(self, py).into_bound(py))
733                }
734            }
735        }
736
737        #[inline]
738        fn __py_call_vectorcall<'py>(
739            self,
740            py: Python<'py>,
741            function: Borrowed<'_, 'py, PyAny>,
742            kwargs: Option<Borrowed<'_, '_, PyDict>>,
743            _: private::Token,
744        ) -> PyResult<Bound<'py, PyAny>> {
745            cfg_if::cfg_if! {
746                if #[cfg(all(Py_3_9, not(any(PyPy, GraalPy, Py_LIMITED_API))))] {
747                    // We need this to drop the arguments correctly.
748                    let args_bound = [$(self.$n.into_py(py).into_bound(py),)*];
749                    // Prepend one null argument for `PY_VECTORCALL_ARGUMENTS_OFFSET`.
750                    let mut args = [std::ptr::null_mut(), $(args_bound[$n].as_ptr()),*];
751                    unsafe {
752                        ffi::PyObject_VectorcallDict(
753                            function.as_ptr(),
754                            args.as_mut_ptr().add(1),
755                            $length + ffi::PY_VECTORCALL_ARGUMENTS_OFFSET,
756                            kwargs.map_or_else(std::ptr::null_mut, |kwargs| kwargs.as_ptr()),
757                        )
758                        .assume_owned_or_err(py)
759                    }
760                } else {
761                    function.call(<Self as IntoPy<Py<PyTuple>>>::into_py(self, py).into_bound(py), kwargs.as_deref())
762                }
763            }
764        }
765
766        #[inline]
767        fn __py_call_method_vectorcall1<'py>(
768            self,
769            py: Python<'py>,
770            object: Borrowed<'_, 'py, PyAny>,
771            method_name: Borrowed<'_, 'py, PyString>,
772            _: private::Token,
773        ) -> PyResult<Bound<'py, PyAny>> {
774            cfg_if::cfg_if! {
775                if #[cfg(all(Py_3_9, not(any(PyPy, GraalPy, Py_LIMITED_API))))] {
776                    // We need this to drop the arguments correctly.
777                    let args_bound = [$(self.$n.into_py(py).into_bound(py),)*];
778                    if $length == 1 {
779                        unsafe {
780                            ffi::PyObject_CallMethodOneArg(
781                                    object.as_ptr(),
782                                    method_name.as_ptr(),
783                                    args_bound[0].as_ptr(),
784                            )
785                            .assume_owned_or_err(py)
786                        }
787                    } else {
788                        let mut args = [object.as_ptr(), $(args_bound[$n].as_ptr()),*];
789                        unsafe {
790                            ffi::PyObject_VectorcallMethod(
791                                method_name.as_ptr(),
792                                args.as_mut_ptr(),
793                                // +1 for the receiver.
794                                1 + $length + ffi::PY_VECTORCALL_ARGUMENTS_OFFSET,
795                                std::ptr::null_mut(),
796                            )
797                            .assume_owned_or_err(py)
798                        }
799                    }
800                } else {
801                    object.call_method1(method_name.to_owned(), <Self as IntoPy<Py<PyTuple>>>::into_py(self, py).into_bound(py))
802                }
803            }
804        }
805    }
806
807    impl<'py, $($T: FromPyObject<'py>),+> FromPyObject<'py> for ($($T,)+) {
808        fn extract_bound(obj: &Bound<'py, PyAny>) -> PyResult<Self>
809        {
810            let t = obj.downcast::<PyTuple>()?;
811            if t.len() == $length {
812                #[cfg(any(Py_LIMITED_API, PyPy, GraalPy))]
813                return Ok(($(t.get_borrowed_item($n)?.extract::<$T>()?,)+));
814
815                #[cfg(not(any(Py_LIMITED_API, PyPy, GraalPy)))]
816                unsafe {return Ok(($(t.get_borrowed_item_unchecked($n).extract::<$T>()?,)+));}
817            } else {
818                Err(wrong_tuple_length(t, $length))
819            }
820        }
821
822        #[cfg(feature = "experimental-inspect")]
823        fn type_input() -> TypeInfo {
824            TypeInfo::Tuple(Some(vec![$( $T::type_input() ),+]))
825        }
826    }
827});
828
829fn array_into_tuple<const N: usize>(py: Python<'_>, array: [PyObject; N]) -> Py<PyTuple> {
830    unsafe {
831        let ptr = ffi::PyTuple_New(N.try_into().expect("0 < N <= 12"));
832        let tup = Py::from_owned_ptr(py, ptr);
833        for (index, obj) in array.into_iter().enumerate() {
834            #[cfg(not(any(Py_LIMITED_API, PyPy, GraalPy)))]
835            ffi::PyTuple_SET_ITEM(ptr, index as ffi::Py_ssize_t, obj.into_ptr());
836            #[cfg(any(Py_LIMITED_API, PyPy, GraalPy))]
837            ffi::PyTuple_SetItem(ptr, index as ffi::Py_ssize_t, obj.into_ptr());
838        }
839        tup
840    }
841}
842
843tuple_conversion!(1, (ref0, 0, T0));
844tuple_conversion!(2, (ref0, 0, T0), (ref1, 1, T1));
845tuple_conversion!(3, (ref0, 0, T0), (ref1, 1, T1), (ref2, 2, T2));
846tuple_conversion!(
847    4,
848    (ref0, 0, T0),
849    (ref1, 1, T1),
850    (ref2, 2, T2),
851    (ref3, 3, T3)
852);
853tuple_conversion!(
854    5,
855    (ref0, 0, T0),
856    (ref1, 1, T1),
857    (ref2, 2, T2),
858    (ref3, 3, T3),
859    (ref4, 4, T4)
860);
861tuple_conversion!(
862    6,
863    (ref0, 0, T0),
864    (ref1, 1, T1),
865    (ref2, 2, T2),
866    (ref3, 3, T3),
867    (ref4, 4, T4),
868    (ref5, 5, T5)
869);
870tuple_conversion!(
871    7,
872    (ref0, 0, T0),
873    (ref1, 1, T1),
874    (ref2, 2, T2),
875    (ref3, 3, T3),
876    (ref4, 4, T4),
877    (ref5, 5, T5),
878    (ref6, 6, T6)
879);
880tuple_conversion!(
881    8,
882    (ref0, 0, T0),
883    (ref1, 1, T1),
884    (ref2, 2, T2),
885    (ref3, 3, T3),
886    (ref4, 4, T4),
887    (ref5, 5, T5),
888    (ref6, 6, T6),
889    (ref7, 7, T7)
890);
891tuple_conversion!(
892    9,
893    (ref0, 0, T0),
894    (ref1, 1, T1),
895    (ref2, 2, T2),
896    (ref3, 3, T3),
897    (ref4, 4, T4),
898    (ref5, 5, T5),
899    (ref6, 6, T6),
900    (ref7, 7, T7),
901    (ref8, 8, T8)
902);
903tuple_conversion!(
904    10,
905    (ref0, 0, T0),
906    (ref1, 1, T1),
907    (ref2, 2, T2),
908    (ref3, 3, T3),
909    (ref4, 4, T4),
910    (ref5, 5, T5),
911    (ref6, 6, T6),
912    (ref7, 7, T7),
913    (ref8, 8, T8),
914    (ref9, 9, T9)
915);
916tuple_conversion!(
917    11,
918    (ref0, 0, T0),
919    (ref1, 1, T1),
920    (ref2, 2, T2),
921    (ref3, 3, T3),
922    (ref4, 4, T4),
923    (ref5, 5, T5),
924    (ref6, 6, T6),
925    (ref7, 7, T7),
926    (ref8, 8, T8),
927    (ref9, 9, T9),
928    (ref10, 10, T10)
929);
930
931tuple_conversion!(
932    12,
933    (ref0, 0, T0),
934    (ref1, 1, T1),
935    (ref2, 2, T2),
936    (ref3, 3, T3),
937    (ref4, 4, T4),
938    (ref5, 5, T5),
939    (ref6, 6, T6),
940    (ref7, 7, T7),
941    (ref8, 8, T8),
942    (ref9, 9, T9),
943    (ref10, 10, T10),
944    (ref11, 11, T11)
945);
946
947#[cfg(test)]
948mod tests {
949    use crate::types::{any::PyAnyMethods, tuple::PyTupleMethods, PyList, PyTuple};
950    use crate::{Python, ToPyObject};
951    use std::collections::HashSet;
952
953    #[test]
954    fn test_new() {
955        Python::with_gil(|py| {
956            let ob = PyTuple::new_bound(py, [1, 2, 3]);
957            assert_eq!(3, ob.len());
958            let ob = ob.as_any();
959            assert_eq!((1, 2, 3), ob.extract().unwrap());
960
961            let mut map = HashSet::new();
962            map.insert(1);
963            map.insert(2);
964            PyTuple::new_bound(py, map);
965        });
966    }
967
968    #[test]
969    fn test_len() {
970        Python::with_gil(|py| {
971            let ob = (1, 2, 3).to_object(py);
972            let tuple = ob.downcast_bound::<PyTuple>(py).unwrap();
973            assert_eq!(3, tuple.len());
974            assert!(!tuple.is_empty());
975            let ob = tuple.as_any();
976            assert_eq!((1, 2, 3), ob.extract().unwrap());
977        });
978    }
979
980    #[test]
981    fn test_empty() {
982        Python::with_gil(|py| {
983            let tuple = PyTuple::empty_bound(py);
984            assert!(tuple.is_empty());
985            assert_eq!(0, tuple.len());
986        });
987    }
988
989    #[test]
990    fn test_slice() {
991        Python::with_gil(|py| {
992            let tup = PyTuple::new_bound(py, [2, 3, 5, 7]);
993            let slice = tup.get_slice(1, 3);
994            assert_eq!(2, slice.len());
995            let slice = tup.get_slice(1, 7);
996            assert_eq!(3, slice.len());
997        });
998    }
999
1000    #[test]
1001    fn test_iter() {
1002        Python::with_gil(|py| {
1003            let ob = (1, 2, 3).to_object(py);
1004            let tuple = ob.downcast_bound::<PyTuple>(py).unwrap();
1005            assert_eq!(3, tuple.len());
1006            let mut iter = tuple.iter();
1007
1008            assert_eq!(iter.size_hint(), (3, Some(3)));
1009
1010            assert_eq!(1_i32, iter.next().unwrap().extract::<'_, i32>().unwrap());
1011            assert_eq!(iter.size_hint(), (2, Some(2)));
1012
1013            assert_eq!(2_i32, iter.next().unwrap().extract::<'_, i32>().unwrap());
1014            assert_eq!(iter.size_hint(), (1, Some(1)));
1015
1016            assert_eq!(3_i32, iter.next().unwrap().extract::<'_, i32>().unwrap());
1017            assert_eq!(iter.size_hint(), (0, Some(0)));
1018
1019            assert!(iter.next().is_none());
1020            assert!(iter.next().is_none());
1021        });
1022    }
1023
1024    #[test]
1025    fn test_iter_rev() {
1026        Python::with_gil(|py| {
1027            let ob = (1, 2, 3).to_object(py);
1028            let tuple = ob.downcast_bound::<PyTuple>(py).unwrap();
1029            assert_eq!(3, tuple.len());
1030            let mut iter = tuple.iter().rev();
1031
1032            assert_eq!(iter.size_hint(), (3, Some(3)));
1033
1034            assert_eq!(3_i32, iter.next().unwrap().extract::<'_, i32>().unwrap());
1035            assert_eq!(iter.size_hint(), (2, Some(2)));
1036
1037            assert_eq!(2_i32, iter.next().unwrap().extract::<'_, i32>().unwrap());
1038            assert_eq!(iter.size_hint(), (1, Some(1)));
1039
1040            assert_eq!(1_i32, iter.next().unwrap().extract::<'_, i32>().unwrap());
1041            assert_eq!(iter.size_hint(), (0, Some(0)));
1042
1043            assert!(iter.next().is_none());
1044            assert!(iter.next().is_none());
1045        });
1046    }
1047
1048    #[test]
1049    fn test_bound_iter() {
1050        Python::with_gil(|py| {
1051            let tuple = PyTuple::new_bound(py, [1, 2, 3]);
1052            assert_eq!(3, tuple.len());
1053            let mut iter = tuple.iter();
1054
1055            assert_eq!(iter.size_hint(), (3, Some(3)));
1056
1057            assert_eq!(1, iter.next().unwrap().extract::<i32>().unwrap());
1058            assert_eq!(iter.size_hint(), (2, Some(2)));
1059
1060            assert_eq!(2, iter.next().unwrap().extract::<i32>().unwrap());
1061            assert_eq!(iter.size_hint(), (1, Some(1)));
1062
1063            assert_eq!(3, iter.next().unwrap().extract::<i32>().unwrap());
1064            assert_eq!(iter.size_hint(), (0, Some(0)));
1065
1066            assert!(iter.next().is_none());
1067            assert!(iter.next().is_none());
1068        });
1069    }
1070
1071    #[test]
1072    fn test_bound_iter_rev() {
1073        Python::with_gil(|py| {
1074            let tuple = PyTuple::new_bound(py, [1, 2, 3]);
1075            assert_eq!(3, tuple.len());
1076            let mut iter = tuple.iter().rev();
1077
1078            assert_eq!(iter.size_hint(), (3, Some(3)));
1079
1080            assert_eq!(3, iter.next().unwrap().extract::<i32>().unwrap());
1081            assert_eq!(iter.size_hint(), (2, Some(2)));
1082
1083            assert_eq!(2, iter.next().unwrap().extract::<i32>().unwrap());
1084            assert_eq!(iter.size_hint(), (1, Some(1)));
1085
1086            assert_eq!(1, iter.next().unwrap().extract::<i32>().unwrap());
1087            assert_eq!(iter.size_hint(), (0, Some(0)));
1088
1089            assert!(iter.next().is_none());
1090            assert!(iter.next().is_none());
1091        });
1092    }
1093
1094    #[test]
1095    fn test_into_iter() {
1096        Python::with_gil(|py| {
1097            let ob = (1, 2, 3).to_object(py);
1098            let tuple = ob.downcast_bound::<PyTuple>(py).unwrap();
1099            assert_eq!(3, tuple.len());
1100
1101            for (i, item) in tuple.iter().enumerate() {
1102                assert_eq!(i + 1, item.extract::<'_, usize>().unwrap());
1103            }
1104        });
1105    }
1106
1107    #[test]
1108    fn test_into_iter_bound() {
1109        use crate::Bound;
1110
1111        Python::with_gil(|py| {
1112            let ob = (1, 2, 3).to_object(py);
1113            let tuple: &Bound<'_, PyTuple> = ob.downcast_bound(py).unwrap();
1114            assert_eq!(3, tuple.len());
1115
1116            let mut items = vec![];
1117            for item in tuple {
1118                items.push(item.extract::<usize>().unwrap());
1119            }
1120            assert_eq!(items, vec![1, 2, 3]);
1121        });
1122    }
1123
1124    #[test]
1125    #[cfg(not(any(Py_LIMITED_API, GraalPy)))]
1126    fn test_as_slice() {
1127        Python::with_gil(|py| {
1128            let ob = (1, 2, 3).to_object(py);
1129            let tuple = ob.downcast_bound::<PyTuple>(py).unwrap();
1130
1131            let slice = tuple.as_slice();
1132            assert_eq!(3, slice.len());
1133            assert_eq!(1_i32, slice[0].extract::<'_, i32>().unwrap());
1134            assert_eq!(2_i32, slice[1].extract::<'_, i32>().unwrap());
1135            assert_eq!(3_i32, slice[2].extract::<'_, i32>().unwrap());
1136        });
1137    }
1138
1139    #[test]
1140    fn test_tuple_lengths_up_to_12() {
1141        Python::with_gil(|py| {
1142            let t0 = (0,).to_object(py);
1143            let t1 = (0, 1).to_object(py);
1144            let t2 = (0, 1, 2).to_object(py);
1145            let t3 = (0, 1, 2, 3).to_object(py);
1146            let t4 = (0, 1, 2, 3, 4).to_object(py);
1147            let t5 = (0, 1, 2, 3, 4, 5).to_object(py);
1148            let t6 = (0, 1, 2, 3, 4, 5, 6).to_object(py);
1149            let t7 = (0, 1, 2, 3, 4, 5, 6, 7).to_object(py);
1150            let t8 = (0, 1, 2, 3, 4, 5, 6, 7, 8).to_object(py);
1151            let t9 = (0, 1, 2, 3, 4, 5, 6, 7, 8, 9).to_object(py);
1152            let t10 = (0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10).to_object(py);
1153            let t11 = (0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11).to_object(py);
1154
1155            assert_eq!(t0.extract::<(i32,)>(py).unwrap(), (0,));
1156            assert_eq!(t1.extract::<(i32, i32)>(py).unwrap(), (0, 1,));
1157            assert_eq!(t2.extract::<(i32, i32, i32)>(py).unwrap(), (0, 1, 2,));
1158            assert_eq!(
1159                t3.extract::<(i32, i32, i32, i32,)>(py).unwrap(),
1160                (0, 1, 2, 3,)
1161            );
1162            assert_eq!(
1163                t4.extract::<(i32, i32, i32, i32, i32,)>(py).unwrap(),
1164                (0, 1, 2, 3, 4,)
1165            );
1166            assert_eq!(
1167                t5.extract::<(i32, i32, i32, i32, i32, i32,)>(py).unwrap(),
1168                (0, 1, 2, 3, 4, 5,)
1169            );
1170            assert_eq!(
1171                t6.extract::<(i32, i32, i32, i32, i32, i32, i32,)>(py)
1172                    .unwrap(),
1173                (0, 1, 2, 3, 4, 5, 6,)
1174            );
1175            assert_eq!(
1176                t7.extract::<(i32, i32, i32, i32, i32, i32, i32, i32,)>(py)
1177                    .unwrap(),
1178                (0, 1, 2, 3, 4, 5, 6, 7,)
1179            );
1180            assert_eq!(
1181                t8.extract::<(i32, i32, i32, i32, i32, i32, i32, i32, i32,)>(py)
1182                    .unwrap(),
1183                (0, 1, 2, 3, 4, 5, 6, 7, 8,)
1184            );
1185            assert_eq!(
1186                t9.extract::<(i32, i32, i32, i32, i32, i32, i32, i32, i32, i32,)>(py)
1187                    .unwrap(),
1188                (0, 1, 2, 3, 4, 5, 6, 7, 8, 9,)
1189            );
1190            assert_eq!(
1191                t10.extract::<(i32, i32, i32, i32, i32, i32, i32, i32, i32, i32, i32,)>(py)
1192                    .unwrap(),
1193                (0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10,)
1194            );
1195            assert_eq!(
1196                t11.extract::<(i32, i32, i32, i32, i32, i32, i32, i32, i32, i32, i32, i32,)>(py)
1197                    .unwrap(),
1198                (0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11,)
1199            );
1200        })
1201    }
1202
1203    #[test]
1204    fn test_tuple_get_item_invalid_index() {
1205        Python::with_gil(|py| {
1206            let ob = (1, 2, 3).to_object(py);
1207            let tuple = ob.downcast_bound::<PyTuple>(py).unwrap();
1208            let obj = tuple.get_item(5);
1209            assert!(obj.is_err());
1210            assert_eq!(
1211                obj.unwrap_err().to_string(),
1212                "IndexError: tuple index out of range"
1213            );
1214        });
1215    }
1216
1217    #[test]
1218    fn test_tuple_get_item_sanity() {
1219        Python::with_gil(|py| {
1220            let ob = (1, 2, 3).to_object(py);
1221            let tuple = ob.downcast_bound::<PyTuple>(py).unwrap();
1222            let obj = tuple.get_item(0);
1223            assert_eq!(obj.unwrap().extract::<i32>().unwrap(), 1);
1224        });
1225    }
1226
1227    #[cfg(not(any(Py_LIMITED_API, PyPy, GraalPy)))]
1228    #[test]
1229    fn test_tuple_get_item_unchecked_sanity() {
1230        Python::with_gil(|py| {
1231            let ob = (1, 2, 3).to_object(py);
1232            let tuple = ob.downcast_bound::<PyTuple>(py).unwrap();
1233            let obj = unsafe { tuple.get_item_unchecked(0) };
1234            assert_eq!(obj.extract::<i32>().unwrap(), 1);
1235        });
1236    }
1237
1238    #[test]
1239    #[cfg(feature = "gil-refs")]
1240    #[allow(deprecated)]
1241    fn test_tuple_index_trait() {
1242        Python::with_gil(|py| {
1243            let ob = (1, 2, 3).to_object(py);
1244            let tuple: &PyTuple = ob.downcast(py).unwrap();
1245            assert_eq!(1, tuple[0].extract::<i32>().unwrap());
1246            assert_eq!(2, tuple[1].extract::<i32>().unwrap());
1247            assert_eq!(3, tuple[2].extract::<i32>().unwrap());
1248        });
1249    }
1250
1251    #[test]
1252    #[should_panic]
1253    #[cfg(feature = "gil-refs")]
1254    #[allow(deprecated)]
1255    fn test_tuple_index_trait_panic() {
1256        Python::with_gil(|py| {
1257            let ob = (1, 2, 3).to_object(py);
1258            let tuple: &PyTuple = ob.downcast(py).unwrap();
1259            let _ = &tuple[7];
1260        });
1261    }
1262
1263    #[test]
1264    #[cfg(feature = "gil-refs")]
1265    #[allow(deprecated)]
1266    fn test_tuple_index_trait_ranges() {
1267        Python::with_gil(|py| {
1268            let ob = (1, 2, 3).to_object(py);
1269            let tuple: &PyTuple = ob.downcast(py).unwrap();
1270            assert_eq!(vec![2, 3], tuple[1..3].extract::<Vec<i32>>().unwrap());
1271            assert_eq!(
1272                Vec::<i32>::new(),
1273                tuple[3..3].extract::<Vec<i32>>().unwrap()
1274            );
1275            assert_eq!(vec![2, 3], tuple[1..].extract::<Vec<i32>>().unwrap());
1276            assert_eq!(Vec::<i32>::new(), tuple[3..].extract::<Vec<i32>>().unwrap());
1277            assert_eq!(vec![1, 2, 3], tuple[..].extract::<Vec<i32>>().unwrap());
1278            assert_eq!(vec![2, 3], tuple[1..=2].extract::<Vec<i32>>().unwrap());
1279            assert_eq!(vec![1, 2], tuple[..2].extract::<Vec<i32>>().unwrap());
1280            assert_eq!(vec![1, 2], tuple[..=1].extract::<Vec<i32>>().unwrap());
1281        })
1282    }
1283
1284    #[test]
1285    #[should_panic = "range start index 5 out of range for tuple of length 3"]
1286    #[cfg(feature = "gil-refs")]
1287    #[allow(deprecated)]
1288    fn test_tuple_index_trait_range_panic_start() {
1289        Python::with_gil(|py| {
1290            let ob = (1, 2, 3).to_object(py);
1291            let tuple: &PyTuple = ob.downcast(py).unwrap();
1292            tuple[5..10].extract::<Vec<i32>>().unwrap();
1293        })
1294    }
1295
1296    #[test]
1297    #[should_panic = "range end index 10 out of range for tuple of length 3"]
1298    #[cfg(feature = "gil-refs")]
1299    #[allow(deprecated)]
1300    fn test_tuple_index_trait_range_panic_end() {
1301        Python::with_gil(|py| {
1302            let ob = (1, 2, 3).to_object(py);
1303            let tuple: &PyTuple = ob.downcast(py).unwrap();
1304            tuple[1..10].extract::<Vec<i32>>().unwrap();
1305        })
1306    }
1307
1308    #[test]
1309    #[should_panic = "slice index starts at 2 but ends at 1"]
1310    #[cfg(feature = "gil-refs")]
1311    #[allow(deprecated)]
1312    fn test_tuple_index_trait_range_panic_wrong_order() {
1313        Python::with_gil(|py| {
1314            let ob = (1, 2, 3).to_object(py);
1315            let tuple: &PyTuple = ob.downcast(py).unwrap();
1316            #[allow(clippy::reversed_empty_ranges)]
1317            tuple[2..1].extract::<Vec<i32>>().unwrap();
1318        })
1319    }
1320
1321    #[test]
1322    #[should_panic = "range start index 8 out of range for tuple of length 3"]
1323    #[cfg(feature = "gil-refs")]
1324    #[allow(deprecated)]
1325    fn test_tuple_index_trait_range_from_panic() {
1326        Python::with_gil(|py| {
1327            let ob = (1, 2, 3).to_object(py);
1328            let tuple: &PyTuple = ob.downcast(py).unwrap();
1329            tuple[8..].extract::<Vec<i32>>().unwrap();
1330        })
1331    }
1332
1333    #[test]
1334    fn test_tuple_contains() {
1335        Python::with_gil(|py| {
1336            let ob = (1, 1, 2, 3, 5, 8).to_object(py);
1337            let tuple = ob.downcast_bound::<PyTuple>(py).unwrap();
1338            assert_eq!(6, tuple.len());
1339
1340            let bad_needle = 7i32.to_object(py);
1341            assert!(!tuple.contains(&bad_needle).unwrap());
1342
1343            let good_needle = 8i32.to_object(py);
1344            assert!(tuple.contains(&good_needle).unwrap());
1345
1346            let type_coerced_needle = 8f32.to_object(py);
1347            assert!(tuple.contains(&type_coerced_needle).unwrap());
1348        });
1349    }
1350
1351    #[test]
1352    fn test_tuple_index() {
1353        Python::with_gil(|py| {
1354            let ob = (1, 1, 2, 3, 5, 8).to_object(py);
1355            let tuple = ob.downcast_bound::<PyTuple>(py).unwrap();
1356            assert_eq!(0, tuple.index(1i32).unwrap());
1357            assert_eq!(2, tuple.index(2i32).unwrap());
1358            assert_eq!(3, tuple.index(3i32).unwrap());
1359            assert_eq!(4, tuple.index(5i32).unwrap());
1360            assert_eq!(5, tuple.index(8i32).unwrap());
1361            assert!(tuple.index(42i32).is_err());
1362        });
1363    }
1364
1365    use std::ops::Range;
1366
1367    // An iterator that lies about its `ExactSizeIterator` implementation.
1368    // See https://github.com/PyO3/pyo3/issues/2118
1369    struct FaultyIter(Range<usize>, usize);
1370
1371    impl Iterator for FaultyIter {
1372        type Item = usize;
1373
1374        fn next(&mut self) -> Option<Self::Item> {
1375            self.0.next()
1376        }
1377    }
1378
1379    impl ExactSizeIterator for FaultyIter {
1380        fn len(&self) -> usize {
1381            self.1
1382        }
1383    }
1384
1385    #[test]
1386    #[should_panic(
1387        expected = "Attempted to create PyTuple but `elements` was larger than reported by its `ExactSizeIterator` implementation."
1388    )]
1389    fn too_long_iterator() {
1390        Python::with_gil(|py| {
1391            let iter = FaultyIter(0..usize::MAX, 73);
1392            let _tuple = PyTuple::new_bound(py, iter);
1393        })
1394    }
1395
1396    #[test]
1397    #[should_panic(
1398        expected = "Attempted to create PyTuple but `elements` was smaller than reported by its `ExactSizeIterator` implementation."
1399    )]
1400    fn too_short_iterator() {
1401        Python::with_gil(|py| {
1402            let iter = FaultyIter(0..35, 73);
1403            let _tuple = PyTuple::new_bound(py, iter);
1404        })
1405    }
1406
1407    #[test]
1408    #[should_panic(
1409        expected = "out of range integral type conversion attempted on `elements.len()`"
1410    )]
1411    fn overflowing_size() {
1412        Python::with_gil(|py| {
1413            let iter = FaultyIter(0..0, usize::MAX);
1414
1415            let _tuple = PyTuple::new_bound(py, iter);
1416        })
1417    }
1418
1419    #[cfg(feature = "macros")]
1420    #[test]
1421    fn bad_clone_mem_leaks() {
1422        use crate::{IntoPy, Py, PyAny};
1423        use std::sync::atomic::{AtomicUsize, Ordering::SeqCst};
1424
1425        static NEEDS_DESTRUCTING_COUNT: AtomicUsize = AtomicUsize::new(0);
1426
1427        #[crate::pyclass]
1428        #[pyo3(crate = "crate")]
1429        struct Bad(usize);
1430
1431        impl Clone for Bad {
1432            fn clone(&self) -> Self {
1433                // This panic should not lead to a memory leak
1434                assert_ne!(self.0, 42);
1435                NEEDS_DESTRUCTING_COUNT.fetch_add(1, SeqCst);
1436
1437                Bad(self.0)
1438            }
1439        }
1440
1441        impl Drop for Bad {
1442            fn drop(&mut self) {
1443                NEEDS_DESTRUCTING_COUNT.fetch_sub(1, SeqCst);
1444            }
1445        }
1446
1447        impl ToPyObject for Bad {
1448            fn to_object(&self, py: Python<'_>) -> Py<PyAny> {
1449                self.to_owned().into_py(py)
1450            }
1451        }
1452
1453        struct FaultyIter(Range<usize>, usize);
1454
1455        impl Iterator for FaultyIter {
1456            type Item = Bad;
1457
1458            fn next(&mut self) -> Option<Self::Item> {
1459                self.0.next().map(|i| {
1460                    NEEDS_DESTRUCTING_COUNT.fetch_add(1, SeqCst);
1461                    Bad(i)
1462                })
1463            }
1464        }
1465
1466        impl ExactSizeIterator for FaultyIter {
1467            fn len(&self) -> usize {
1468                self.1
1469            }
1470        }
1471
1472        Python::with_gil(|py| {
1473            std::panic::catch_unwind(|| {
1474                let iter = FaultyIter(0..50, 50);
1475                let _tuple = PyTuple::new_bound(py, iter);
1476            })
1477            .unwrap_err();
1478        });
1479
1480        assert_eq!(
1481            NEEDS_DESTRUCTING_COUNT.load(SeqCst),
1482            0,
1483            "Some destructors did not run"
1484        );
1485    }
1486
1487    #[cfg(feature = "macros")]
1488    #[test]
1489    fn bad_clone_mem_leaks_2() {
1490        use crate::{IntoPy, Py, PyAny};
1491        use std::sync::atomic::{AtomicUsize, Ordering::SeqCst};
1492
1493        static NEEDS_DESTRUCTING_COUNT: AtomicUsize = AtomicUsize::new(0);
1494
1495        #[crate::pyclass]
1496        #[pyo3(crate = "crate")]
1497        struct Bad(usize);
1498
1499        impl Clone for Bad {
1500            fn clone(&self) -> Self {
1501                // This panic should not lead to a memory leak
1502                assert_ne!(self.0, 3);
1503                NEEDS_DESTRUCTING_COUNT.fetch_add(1, SeqCst);
1504
1505                Bad(self.0)
1506            }
1507        }
1508
1509        impl Drop for Bad {
1510            fn drop(&mut self) {
1511                NEEDS_DESTRUCTING_COUNT.fetch_sub(1, SeqCst);
1512            }
1513        }
1514
1515        impl ToPyObject for Bad {
1516            fn to_object(&self, py: Python<'_>) -> Py<PyAny> {
1517                self.to_owned().into_py(py)
1518            }
1519        }
1520
1521        let s = (Bad(1), Bad(2), Bad(3), Bad(4));
1522        NEEDS_DESTRUCTING_COUNT.store(4, SeqCst);
1523        Python::with_gil(|py| {
1524            std::panic::catch_unwind(|| {
1525                let _tuple: Py<PyAny> = s.to_object(py);
1526            })
1527            .unwrap_err();
1528        });
1529        drop(s);
1530
1531        assert_eq!(
1532            NEEDS_DESTRUCTING_COUNT.load(SeqCst),
1533            0,
1534            "Some destructors did not run"
1535        );
1536    }
1537
1538    #[test]
1539    fn test_tuple_to_list() {
1540        Python::with_gil(|py| {
1541            let tuple = PyTuple::new_bound(py, vec![1, 2, 3]);
1542            let list = tuple.to_list();
1543            let list_expected = PyList::new_bound(py, vec![1, 2, 3]);
1544            assert!(list.eq(list_expected).unwrap());
1545        })
1546    }
1547
1548    #[test]
1549    fn test_tuple_as_sequence() {
1550        Python::with_gil(|py| {
1551            let tuple = PyTuple::new_bound(py, vec![1, 2, 3]);
1552            let sequence = tuple.as_sequence();
1553            assert!(tuple.get_item(0).unwrap().eq(1).unwrap());
1554            assert!(sequence.get_item(0).unwrap().eq(1).unwrap());
1555
1556            assert_eq!(tuple.len(), 3);
1557            assert_eq!(sequence.len().unwrap(), 3);
1558        })
1559    }
1560
1561    #[test]
1562    fn test_tuple_into_sequence() {
1563        Python::with_gil(|py| {
1564            let tuple = PyTuple::new_bound(py, vec![1, 2, 3]);
1565            let sequence = tuple.into_sequence();
1566            assert!(sequence.get_item(0).unwrap().eq(1).unwrap());
1567            assert_eq!(sequence.len().unwrap(), 3);
1568        })
1569    }
1570
1571    #[test]
1572    fn test_bound_tuple_get_item() {
1573        Python::with_gil(|py| {
1574            let tuple = PyTuple::new_bound(py, vec![1, 2, 3, 4]);
1575
1576            assert_eq!(tuple.len(), 4);
1577            assert_eq!(tuple.get_item(0).unwrap().extract::<i32>().unwrap(), 1);
1578            assert_eq!(
1579                tuple
1580                    .get_borrowed_item(1)
1581                    .unwrap()
1582                    .extract::<i32>()
1583                    .unwrap(),
1584                2
1585            );
1586            #[cfg(not(any(Py_LIMITED_API, PyPy, GraalPy)))]
1587            {
1588                assert_eq!(
1589                    unsafe { tuple.get_item_unchecked(2) }
1590                        .extract::<i32>()
1591                        .unwrap(),
1592                    3
1593                );
1594                assert_eq!(
1595                    unsafe { tuple.get_borrowed_item_unchecked(3) }
1596                        .extract::<i32>()
1597                        .unwrap(),
1598                    4
1599                );
1600            }
1601        })
1602    }
1603}