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 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 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#[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 #[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(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 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(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#[doc(alias = "PyTuple")]
145pub trait PyTupleMethods<'py>: crate::sealed::Sealed {
146 fn len(&self) -> usize;
148
149 fn is_empty(&self) -> bool;
151
152 fn as_sequence(&self) -> &Bound<'py, PySequence>;
154
155 fn into_sequence(self) -> Bound<'py, PySequence>;
157
158 fn get_slice(&self, low: usize, high: usize) -> Bound<'py, PyTuple>;
163
164 fn get_item(&self, index: usize) -> PyResult<Bound<'py, PyAny>>;
179
180 fn get_borrowed_item<'a>(&'a self, index: usize) -> PyResult<Borrowed<'a, 'py, PyAny>>;
183
184 #[cfg(not(any(Py_LIMITED_API, PyPy, GraalPy)))]
190 unsafe fn get_item_unchecked(&self, index: usize) -> Bound<'py, PyAny>;
191
192 #[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 #[cfg(not(any(Py_LIMITED_API, GraalPy)))]
203 fn as_slice(&self) -> &[Bound<'py, PyAny>];
204
205 fn contains<V>(&self, value: V) -> PyResult<bool>
209 where
210 V: IntoPyObject<'py>;
211
212 fn index<V>(&self, value: V) -> PyResult<usize>
216 where
217 V: IntoPyObject<'py>;
218
219 fn iter(&self) -> BoundTupleIterator<'py>;
221
222 fn iter_borrowed<'a>(&'a self) -> BorrowedTupleIterator<'a, 'py>;
225
226 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 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 let items = unsafe { &(*self.as_ptr().cast::<ffi::PyTupleObject>()).ob_item };
286 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
339pub 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
422pub 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,)+ {
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 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 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 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}