pyo3/types/
tuple.rs

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