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 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 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 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 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 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 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 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 crate::utils::hash_iter(elements.iter(), vm)
567}