rustpython_vm/builtins/
tuple.rs

1use super::{PositionIterInternal, PyGenericAlias, PyStrRef, PyType, PyTypeRef};
2use crate::common::{hash::PyHash, lock::PyMutex};
3use crate::object::{Traverse, TraverseFn};
4use crate::{
5    atomic_func,
6    class::PyClassImpl,
7    convert::{ToPyObject, TransmuteFromObject},
8    function::{ArgSize, OptionalArg, PyArithmeticValue, PyComparisonValue},
9    iter::PyExactSizeIterator,
10    protocol::{PyIterReturn, PyMappingMethods, PySequenceMethods},
11    recursion::ReprGuard,
12    sequence::{OptionalRangeArgs, SequenceExt},
13    sliceable::{SequenceIndex, SliceableSequenceOp},
14    types::{
15        AsMapping, AsSequence, Comparable, Constructor, Hashable, IterNext, Iterable,
16        PyComparisonOp, Representable, SelfIter, Unconstructible,
17    },
18    utils::collection_repr,
19    vm::VirtualMachine,
20    AsObject, Context, Py, PyObject, PyObjectRef, PyPayload, PyRef, PyResult, TryFromObject,
21};
22use once_cell::sync::Lazy;
23use std::{fmt, marker::PhantomData};
24
25#[pyclass(module = false, name = "tuple", traverse)]
26pub struct PyTuple {
27    elements: Box<[PyObjectRef]>,
28}
29
30impl fmt::Debug for PyTuple {
31    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
32        // TODO: implement more informational, non-recursive Debug formatter
33        f.write_str("tuple")
34    }
35}
36
37impl PyPayload for PyTuple {
38    fn class(ctx: &Context) -> &'static Py<PyType> {
39        ctx.types.tuple_type
40    }
41}
42
43pub trait IntoPyTuple {
44    fn into_pytuple(self, vm: &VirtualMachine) -> PyTupleRef;
45}
46
47impl IntoPyTuple for () {
48    fn into_pytuple(self, vm: &VirtualMachine) -> PyTupleRef {
49        vm.ctx.empty_tuple.clone()
50    }
51}
52
53impl IntoPyTuple for Vec<PyObjectRef> {
54    fn into_pytuple(self, vm: &VirtualMachine) -> PyTupleRef {
55        PyTuple::new_ref(self, &vm.ctx)
56    }
57}
58
59macro_rules! impl_into_pyobj_tuple {
60    ($(($T:ident, $idx:tt)),+) => {
61        impl<$($T: ToPyObject),*> IntoPyTuple for ($($T,)*) {
62            fn into_pytuple(self, vm: &VirtualMachine) -> PyTupleRef {
63                PyTuple::new_ref(vec![$(self.$idx.to_pyobject(vm)),*], &vm.ctx)
64            }
65        }
66
67        impl<$($T: ToPyObject),*> ToPyObject for ($($T,)*) {
68            fn to_pyobject(self, vm: &VirtualMachine) -> PyObjectRef {
69                self.into_pytuple(vm).into()
70            }
71        }
72    };
73}
74
75impl_into_pyobj_tuple!((A, 0));
76impl_into_pyobj_tuple!((A, 0), (B, 1));
77impl_into_pyobj_tuple!((A, 0), (B, 1), (C, 2));
78impl_into_pyobj_tuple!((A, 0), (B, 1), (C, 2), (D, 3));
79impl_into_pyobj_tuple!((A, 0), (B, 1), (C, 2), (D, 3), (E, 4));
80impl_into_pyobj_tuple!((A, 0), (B, 1), (C, 2), (D, 3), (E, 4), (F, 5));
81impl_into_pyobj_tuple!((A, 0), (B, 1), (C, 2), (D, 3), (E, 4), (F, 5), (G, 6));
82
83impl PyTuple {
84    pub(crate) fn fast_getitem(&self, idx: usize) -> PyObjectRef {
85        self.elements[idx].clone()
86    }
87}
88
89pub type PyTupleRef = PyRef<PyTuple>;
90
91impl Constructor for PyTuple {
92    type Args = OptionalArg<PyObjectRef>;
93
94    fn py_new(cls: PyTypeRef, iterable: Self::Args, vm: &VirtualMachine) -> PyResult {
95        let elements = if let OptionalArg::Present(iterable) = iterable {
96            let iterable = if cls.is(vm.ctx.types.tuple_type) {
97                match iterable.downcast_exact::<Self>(vm) {
98                    Ok(tuple) => return Ok(tuple.into_pyref().into()),
99                    Err(iterable) => iterable,
100                }
101            } else {
102                iterable
103            };
104            iterable.try_to_value(vm)?
105        } else {
106            vec![]
107        };
108        // Return empty tuple only for exact tuple types if the iterable is empty.
109        if elements.is_empty() && cls.is(vm.ctx.types.tuple_type) {
110            Ok(vm.ctx.empty_tuple.clone().into())
111        } else {
112            Self {
113                elements: elements.into_boxed_slice(),
114            }
115            .into_ref_with_type(vm, cls)
116            .map(Into::into)
117        }
118    }
119}
120
121impl AsRef<[PyObjectRef]> for PyTuple {
122    fn as_ref(&self) -> &[PyObjectRef] {
123        self.as_slice()
124    }
125}
126
127impl std::ops::Deref for PyTuple {
128    type Target = [PyObjectRef];
129    fn deref(&self) -> &[PyObjectRef] {
130        self.as_slice()
131    }
132}
133
134impl<'a> std::iter::IntoIterator for &'a PyTuple {
135    type Item = &'a PyObjectRef;
136    type IntoIter = std::slice::Iter<'a, PyObjectRef>;
137
138    fn into_iter(self) -> Self::IntoIter {
139        self.iter()
140    }
141}
142
143impl<'a> std::iter::IntoIterator for &'a Py<PyTuple> {
144    type Item = &'a PyObjectRef;
145    type IntoIter = std::slice::Iter<'a, PyObjectRef>;
146
147    fn into_iter(self) -> Self::IntoIter {
148        self.iter()
149    }
150}
151
152impl PyTuple {
153    pub fn new_ref(elements: Vec<PyObjectRef>, ctx: &Context) -> PyRef<Self> {
154        if elements.is_empty() {
155            ctx.empty_tuple.clone()
156        } else {
157            let elements = elements.into_boxed_slice();
158            PyRef::new_ref(Self { elements }, ctx.types.tuple_type.to_owned(), None)
159        }
160    }
161
162    /// Creating a new tuple with given boxed slice.
163    /// NOTE: for usual case, you probably want to use PyTuple::new_ref.
164    /// Calling this function implies trying micro optimization for non-zero-sized tuple.
165    pub fn new_unchecked(elements: Box<[PyObjectRef]>) -> Self {
166        Self { elements }
167    }
168
169    pub fn as_slice(&self) -> &[PyObjectRef] {
170        &self.elements
171    }
172
173    fn repeat(zelf: PyRef<Self>, value: isize, vm: &VirtualMachine) -> PyResult<PyRef<Self>> {
174        Ok(if zelf.elements.is_empty() || value == 0 {
175            vm.ctx.empty_tuple.clone()
176        } else if value == 1 && zelf.class().is(vm.ctx.types.tuple_type) {
177            // Special case: when some `tuple` is multiplied by `1`,
178            // nothing really happens, we need to return an object itself
179            // with the same `id()` to be compatible with CPython.
180            // This only works for `tuple` itself, not its subclasses.
181            zelf
182        } else {
183            let v = zelf.elements.mul(vm, value)?;
184            let elements = v.into_boxed_slice();
185            Self { elements }.into_ref(&vm.ctx)
186        })
187    }
188}
189
190#[pyclass(
191    flags(BASETYPE),
192    with(
193        AsMapping,
194        AsSequence,
195        Hashable,
196        Comparable,
197        Iterable,
198        Constructor,
199        Representable
200    )
201)]
202impl PyTuple {
203    #[pymethod(magic)]
204    fn add(
205        zelf: PyRef<Self>,
206        other: PyObjectRef,
207        vm: &VirtualMachine,
208    ) -> PyArithmeticValue<PyRef<Self>> {
209        let added = other.downcast::<Self>().map(|other| {
210            if other.elements.is_empty() && zelf.class().is(vm.ctx.types.tuple_type) {
211                zelf
212            } else if zelf.elements.is_empty() && other.class().is(vm.ctx.types.tuple_type) {
213                other
214            } else {
215                let elements = zelf
216                    .iter()
217                    .chain(other.as_slice())
218                    .cloned()
219                    .collect::<Box<[_]>>();
220                Self { elements }.into_ref(&vm.ctx)
221            }
222        });
223        PyArithmeticValue::from_option(added.ok())
224    }
225
226    #[pymethod(magic)]
227    fn bool(&self) -> bool {
228        !self.elements.is_empty()
229    }
230
231    #[pymethod]
232    fn count(&self, needle: PyObjectRef, vm: &VirtualMachine) -> PyResult<usize> {
233        let mut count: usize = 0;
234        for element in self {
235            if vm.identical_or_equal(element, &needle)? {
236                count += 1;
237            }
238        }
239        Ok(count)
240    }
241
242    #[pymethod(magic)]
243    #[inline]
244    pub fn len(&self) -> usize {
245        self.elements.len()
246    }
247
248    #[inline]
249    pub fn is_empty(&self) -> bool {
250        self.elements.is_empty()
251    }
252
253    #[pymethod(name = "__rmul__")]
254    #[pymethod(magic)]
255    fn mul(zelf: PyRef<Self>, value: ArgSize, vm: &VirtualMachine) -> PyResult<PyRef<Self>> {
256        Self::repeat(zelf, value.into(), vm)
257    }
258
259    fn _getitem(&self, needle: &PyObject, vm: &VirtualMachine) -> PyResult {
260        match SequenceIndex::try_from_borrowed_object(vm, needle, "tuple")? {
261            SequenceIndex::Int(i) => self.elements.getitem_by_index(vm, i),
262            SequenceIndex::Slice(slice) => self
263                .elements
264                .getitem_by_slice(vm, slice)
265                .map(|x| vm.ctx.new_tuple(x).into()),
266        }
267    }
268
269    #[pymethod(magic)]
270    fn getitem(&self, needle: PyObjectRef, vm: &VirtualMachine) -> PyResult {
271        self._getitem(&needle, vm)
272    }
273
274    #[pymethod]
275    fn index(
276        &self,
277        needle: PyObjectRef,
278        range: OptionalRangeArgs,
279        vm: &VirtualMachine,
280    ) -> PyResult<usize> {
281        let (start, stop) = range.saturate(self.len(), vm)?;
282        for (index, element) in self.elements.iter().enumerate().take(stop).skip(start) {
283            if vm.identical_or_equal(element, &needle)? {
284                return Ok(index);
285            }
286        }
287        Err(vm.new_value_error("tuple.index(x): x not in tuple".to_owned()))
288    }
289
290    fn _contains(&self, needle: &PyObject, vm: &VirtualMachine) -> PyResult<bool> {
291        for element in self.elements.iter() {
292            if vm.identical_or_equal(element, needle)? {
293                return Ok(true);
294            }
295        }
296        Ok(false)
297    }
298
299    #[pymethod(magic)]
300    fn contains(&self, needle: PyObjectRef, vm: &VirtualMachine) -> PyResult<bool> {
301        self._contains(&needle, vm)
302    }
303
304    #[pymethod(magic)]
305    fn getnewargs(zelf: PyRef<Self>, vm: &VirtualMachine) -> (PyTupleRef,) {
306        // the arguments to pass to tuple() is just one tuple - so we'll be doing tuple(tup), which
307        // should just return tup, or tuplesubclass(tup), which'll copy/validate (e.g. for a
308        // structseq)
309        let tup_arg = if zelf.class().is(vm.ctx.types.tuple_type) {
310            zelf
311        } else {
312            PyTuple::new_ref(zelf.elements.clone().into_vec(), &vm.ctx)
313        };
314        (tup_arg,)
315    }
316
317    #[pyclassmethod(magic)]
318    fn class_getitem(cls: PyTypeRef, args: PyObjectRef, vm: &VirtualMachine) -> PyGenericAlias {
319        PyGenericAlias::new(cls, args, vm)
320    }
321}
322
323impl AsMapping for PyTuple {
324    fn as_mapping() -> &'static PyMappingMethods {
325        static AS_MAPPING: Lazy<PyMappingMethods> = Lazy::new(|| PyMappingMethods {
326            length: atomic_func!(|mapping, _vm| Ok(PyTuple::mapping_downcast(mapping).len())),
327            subscript: atomic_func!(
328                |mapping, needle, vm| PyTuple::mapping_downcast(mapping)._getitem(needle, vm)
329            ),
330            ..PyMappingMethods::NOT_IMPLEMENTED
331        });
332        &AS_MAPPING
333    }
334}
335
336impl AsSequence for PyTuple {
337    fn as_sequence() -> &'static PySequenceMethods {
338        static AS_SEQUENCE: Lazy<PySequenceMethods> = Lazy::new(|| PySequenceMethods {
339            length: atomic_func!(|seq, _vm| Ok(PyTuple::sequence_downcast(seq).len())),
340            concat: atomic_func!(|seq, other, vm| {
341                let zelf = PyTuple::sequence_downcast(seq);
342                match PyTuple::add(zelf.to_owned(), other.to_owned(), vm) {
343                    PyArithmeticValue::Implemented(tuple) => Ok(tuple.into()),
344                    PyArithmeticValue::NotImplemented => Err(vm.new_type_error(format!(
345                        "can only concatenate tuple (not '{}') to tuple",
346                        other.class().name()
347                    ))),
348                }
349            }),
350            repeat: atomic_func!(|seq, n, vm| {
351                let zelf = PyTuple::sequence_downcast(seq);
352                PyTuple::repeat(zelf.to_owned(), n, vm).map(|x| x.into())
353            }),
354            item: atomic_func!(|seq, i, vm| {
355                let zelf = PyTuple::sequence_downcast(seq);
356                zelf.elements.getitem_by_index(vm, i)
357            }),
358            contains: atomic_func!(|seq, needle, vm| {
359                let zelf = PyTuple::sequence_downcast(seq);
360                zelf._contains(needle, vm)
361            }),
362            ..PySequenceMethods::NOT_IMPLEMENTED
363        });
364        &AS_SEQUENCE
365    }
366}
367
368impl Hashable for PyTuple {
369    #[inline]
370    fn hash(zelf: &Py<Self>, vm: &VirtualMachine) -> PyResult<PyHash> {
371        tuple_hash(zelf.as_slice(), vm)
372    }
373}
374
375impl Comparable for PyTuple {
376    fn cmp(
377        zelf: &Py<Self>,
378        other: &PyObject,
379        op: PyComparisonOp,
380        vm: &VirtualMachine,
381    ) -> PyResult<PyComparisonValue> {
382        if let Some(res) = op.identical_optimization(zelf, other) {
383            return Ok(res.into());
384        }
385        let other = class_or_notimplemented!(Self, other);
386        zelf.iter()
387            .richcompare(other.iter(), op, vm)
388            .map(PyComparisonValue::Implemented)
389    }
390}
391
392impl Iterable for PyTuple {
393    fn iter(zelf: PyRef<Self>, vm: &VirtualMachine) -> PyResult {
394        Ok(PyTupleIterator {
395            internal: PyMutex::new(PositionIterInternal::new(zelf, 0)),
396        }
397        .into_pyobject(vm))
398    }
399}
400
401impl Representable for PyTuple {
402    #[inline]
403    fn repr(zelf: &Py<Self>, vm: &VirtualMachine) -> PyResult<PyStrRef> {
404        let s = if zelf.len() == 0 {
405            vm.ctx.intern_str("()").to_owned()
406        } else if let Some(_guard) = ReprGuard::enter(vm, zelf.as_object()) {
407            let s = if zelf.len() == 1 {
408                format!("({},)", zelf.elements[0].repr(vm)?)
409            } else {
410                collection_repr(None, "(", ")", zelf.elements.iter(), vm)?
411            };
412            vm.ctx.new_str(s)
413        } else {
414            vm.ctx.intern_str("(...)").to_owned()
415        };
416        Ok(s)
417    }
418
419    #[cold]
420    fn repr_str(_zelf: &Py<Self>, _vm: &VirtualMachine) -> PyResult<String> {
421        unreachable!("use repr instead")
422    }
423}
424
425#[pyclass(module = false, name = "tuple_iterator", traverse)]
426#[derive(Debug)]
427pub(crate) struct PyTupleIterator {
428    internal: PyMutex<PositionIterInternal<PyTupleRef>>,
429}
430
431impl PyPayload for PyTupleIterator {
432    fn class(ctx: &Context) -> &'static Py<PyType> {
433        ctx.types.tuple_iterator_type
434    }
435}
436
437#[pyclass(with(Unconstructible, IterNext, Iterable))]
438impl PyTupleIterator {
439    #[pymethod(magic)]
440    fn length_hint(&self) -> usize {
441        self.internal.lock().length_hint(|obj| obj.len())
442    }
443
444    #[pymethod(magic)]
445    fn setstate(&self, state: PyObjectRef, vm: &VirtualMachine) -> PyResult<()> {
446        self.internal
447            .lock()
448            .set_state(state, |obj, pos| pos.min(obj.len()), vm)
449    }
450
451    #[pymethod(magic)]
452    fn reduce(&self, vm: &VirtualMachine) -> PyTupleRef {
453        self.internal
454            .lock()
455            .builtins_iter_reduce(|x| x.clone().into(), vm)
456    }
457}
458impl Unconstructible for PyTupleIterator {}
459
460impl SelfIter for PyTupleIterator {}
461impl IterNext for PyTupleIterator {
462    fn next(zelf: &Py<Self>, _vm: &VirtualMachine) -> PyResult<PyIterReturn> {
463        zelf.internal.lock().next(|tuple, pos| {
464            Ok(PyIterReturn::from_result(
465                tuple.get(pos).cloned().ok_or(None),
466            ))
467        })
468    }
469}
470
471pub(crate) fn init(context: &Context) {
472    PyTuple::extend_class(context, context.types.tuple_type);
473    PyTupleIterator::extend_class(context, context.types.tuple_iterator_type);
474}
475
476pub struct PyTupleTyped<T: TransmuteFromObject> {
477    // SAFETY INVARIANT: T must be repr(transparent) over PyObjectRef, and the
478    //                   elements must be logically valid when transmuted to T
479    tuple: PyTupleRef,
480    _marker: PhantomData<Vec<T>>,
481}
482
483unsafe impl<T> Traverse for PyTupleTyped<T>
484where
485    T: TransmuteFromObject + Traverse,
486{
487    fn traverse(&self, tracer_fn: &mut TraverseFn) {
488        self.tuple.traverse(tracer_fn);
489    }
490}
491
492impl<T: TransmuteFromObject> TryFromObject for PyTupleTyped<T> {
493    fn try_from_object(vm: &VirtualMachine, obj: PyObjectRef) -> PyResult<Self> {
494        let tuple = PyTupleRef::try_from_object(vm, obj)?;
495        for elem in &*tuple {
496            T::check(vm, elem)?
497        }
498        // SAFETY: the contract of TransmuteFromObject upholds the variant on `tuple`
499        Ok(Self {
500            tuple,
501            _marker: PhantomData,
502        })
503    }
504}
505
506impl<T: TransmuteFromObject> AsRef<[T]> for PyTupleTyped<T> {
507    fn as_ref(&self) -> &[T] {
508        self.as_slice()
509    }
510}
511
512impl<T: TransmuteFromObject> PyTupleTyped<T> {
513    pub fn empty(vm: &VirtualMachine) -> Self {
514        Self {
515            tuple: vm.ctx.empty_tuple.clone(),
516            _marker: PhantomData,
517        }
518    }
519
520    #[inline]
521    pub fn as_slice(&self) -> &[T] {
522        unsafe { &*(self.tuple.as_slice() as *const [PyObjectRef] as *const [T]) }
523    }
524    #[inline]
525    pub fn len(&self) -> usize {
526        self.tuple.len()
527    }
528    #[inline]
529    pub fn is_empty(&self) -> bool {
530        self.tuple.is_empty()
531    }
532}
533
534impl<T: TransmuteFromObject> Clone for PyTupleTyped<T> {
535    fn clone(&self) -> Self {
536        Self {
537            tuple: self.tuple.clone(),
538            _marker: PhantomData,
539        }
540    }
541}
542
543impl<T: TransmuteFromObject + fmt::Debug> fmt::Debug for PyTupleTyped<T> {
544    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
545        self.as_slice().fmt(f)
546    }
547}
548
549impl<T: TransmuteFromObject> From<PyTupleTyped<T>> for PyTupleRef {
550    #[inline]
551    fn from(tup: PyTupleTyped<T>) -> Self {
552        tup.tuple
553    }
554}
555
556impl<T: TransmuteFromObject> ToPyObject for PyTupleTyped<T> {
557    #[inline]
558    fn to_pyobject(self, _vm: &VirtualMachine) -> PyObjectRef {
559        self.tuple.into()
560    }
561}
562
563pub(super) fn tuple_hash(elements: &[PyObjectRef], vm: &VirtualMachine) -> PyResult<PyHash> {
564    // TODO: See #3460 for the correct implementation.
565    // https://github.com/RustPython/RustPython/pull/3460
566    crate::utils::hash_iter(elements.iter(), vm)
567}
Morty Proxy This is a proxified and sanitized view of the page, visit original site.