Skip to main content

rustpython_vm/builtins/
dict.rs

1use super::{
2    IterStatus, PositionIterInternal, PyBaseExceptionRef, PyGenericAlias, PyMappingProxy, PySet,
3    PyStr, PyStrRef, PyTupleRef, PyType, PyTypeRef, set::PySetInner,
4};
5use crate::common::lock::LazyLock;
6use crate::object::{Traverse, TraverseFn};
7use crate::{
8    AsObject, Context, Py, PyExact, PyObject, PyObjectRef, PyPayload, PyRef, PyRefExact, PyResult,
9    TryFromObject, atomic_func,
10    builtins::{
11        PyTuple,
12        iter::{builtins_iter, builtins_reversed},
13        type_::PyAttributes,
14    },
15    class::{PyClassDef, PyClassImpl},
16    common::ascii,
17    dict_inner::{self, DictKey},
18    function::{
19        ArgIterable, FuncArgs, KwArgs, OptionalArg, PyArithmeticValue::*, PyComparisonValue,
20    },
21    iter::PyExactSizeIterator,
22    protocol::{PyIterIter, PyIterReturn, PyMappingMethods, PyNumberMethods, PySequenceMethods},
23    recursion::ReprGuard,
24    types::{
25        AsMapping, AsNumber, AsSequence, Callable, Comparable, Constructor, DefaultConstructor,
26        Initializer, IterNext, Iterable, PyComparisonOp, Representable, SelfIter,
27    },
28    vm::VirtualMachine,
29};
30use alloc::fmt;
31use core::cell::Cell;
32use core::ptr::NonNull;
33use rustpython_common::lock::PyMutex;
34use rustpython_common::wtf8::Wtf8Buf;
35
36pub type DictContentType = dict_inner::Dict;
37
38#[pyclass(module = false, name = "dict", unhashable = true, traverse = "manual")]
39#[derive(Default)]
40pub struct PyDict {
41    entries: DictContentType,
42}
43pub type PyDictRef = PyRef<PyDict>;
44
45// SAFETY: Traverse properly visits all owned PyObjectRefs
46unsafe impl Traverse for PyDict {
47    fn traverse(&self, traverse_fn: &mut TraverseFn<'_>) {
48        self.entries.traverse(traverse_fn);
49    }
50
51    fn clear(&mut self, out: &mut Vec<PyObjectRef>) {
52        // Pop all entries and collect both keys and values
53        for (key, value) in self.entries.drain_entries() {
54            out.push(key);
55            out.push(value);
56        }
57    }
58}
59
60impl fmt::Debug for PyDict {
61    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
62        // TODO: implement more detailed, non-recursive Debug formatter
63        f.write_str("dict")
64    }
65}
66
67thread_local! {
68    static DICT_FREELIST: Cell<crate::object::FreeList<PyDict>> = const { Cell::new(crate::object::FreeList::new()) };
69}
70
71impl PyPayload for PyDict {
72    const MAX_FREELIST: usize = 80;
73    const HAS_FREELIST: bool = true;
74
75    #[inline]
76    fn class(ctx: &Context) -> &'static Py<PyType> {
77        ctx.types.dict_type
78    }
79
80    #[inline]
81    unsafe fn freelist_push(obj: *mut PyObject) -> bool {
82        DICT_FREELIST
83            .try_with(|fl| {
84                let mut list = fl.take();
85                let stored = if list.len() < Self::MAX_FREELIST {
86                    list.push(obj);
87                    true
88                } else {
89                    false
90                };
91                fl.set(list);
92                stored
93            })
94            .unwrap_or(false)
95    }
96
97    #[inline]
98    unsafe fn freelist_pop(_payload: &Self) -> Option<NonNull<PyObject>> {
99        DICT_FREELIST
100            .try_with(|fl| {
101                let mut list = fl.take();
102                let result = list.pop().map(|p| unsafe { NonNull::new_unchecked(p) });
103                fl.set(list);
104                result
105            })
106            .ok()
107            .flatten()
108    }
109}
110
111impl PyDict {
112    #[deprecated(note = "use PyDict::default().into_ref() instead")]
113    pub fn new_ref(ctx: &Context) -> PyRef<Self> {
114        Self::default().into_ref(ctx)
115    }
116
117    /// escape hatch to access the underlying data structure directly. prefer adding a method on
118    /// PyDict instead of using this
119    pub(crate) const fn _as_dict_inner(&self) -> &DictContentType {
120        &self.entries
121    }
122
123    /// Monotonically increasing version for mutation tracking.
124    pub(crate) fn version(&self) -> u64 {
125        self.entries.version()
126    }
127
128    /// Returns all keys as a Vec, atomically under a single read lock.
129    /// Thread-safe: prevents "dictionary changed size during iteration" errors.
130    pub fn keys_vec(&self) -> Vec<PyObjectRef> {
131        self.entries.keys()
132    }
133
134    /// Returns all values as a Vec, atomically under a single read lock.
135    /// Thread-safe: prevents "dictionary changed size during iteration" errors.
136    pub fn values_vec(&self) -> Vec<PyObjectRef> {
137        self.entries.values()
138    }
139
140    /// Returns all items as a Vec, atomically under a single read lock.
141    /// Thread-safe: prevents "dictionary changed size during iteration" errors.
142    pub fn items_vec(&self) -> Vec<(PyObjectRef, PyObjectRef)> {
143        self.entries.items()
144    }
145
146    // Used in update and ior.
147    pub(crate) fn merge_object(&self, other: PyObjectRef, vm: &VirtualMachine) -> PyResult<()> {
148        let casted: Result<PyRefExact<Self>, _> = other.downcast_exact(vm);
149        let other = match casted {
150            Ok(dict_other) => return self.merge_dict(dict_other.into_pyref(), vm),
151            Err(other) => other,
152        };
153        let dict = &self.entries;
154        // Use get_attr to properly invoke __getattribute__ for proxy objects
155        let keys_result = other.get_attr(vm.ctx.intern_str("keys"), vm);
156        let has_keys = match keys_result {
157            Ok(keys_method) => {
158                let keys = keys_method.call((), vm)?.get_iter(vm)?;
159                while let PyIterReturn::Return(key) = keys.next(vm)? {
160                    let val = other.get_item(&*key, vm)?;
161                    dict.insert(vm, &*key, val)?;
162                }
163                true
164            }
165            Err(e) if e.fast_isinstance(vm.ctx.exceptions.attribute_error) => false,
166            Err(e) => return Err(e),
167        };
168        if !has_keys {
169            let iter = other.get_iter(vm)?;
170            loop {
171                fn err(vm: &VirtualMachine) -> PyBaseExceptionRef {
172                    vm.new_value_error("Iterator must have exactly two elements")
173                }
174                let element = match iter.next(vm)? {
175                    PyIterReturn::Return(obj) => obj,
176                    PyIterReturn::StopIteration(_) => break,
177                };
178                let elem_iter = element.get_iter(vm)?;
179                let key = elem_iter.next(vm)?.into_result().map_err(|_| err(vm))?;
180                let value = elem_iter.next(vm)?.into_result().map_err(|_| err(vm))?;
181                if matches!(elem_iter.next(vm)?, PyIterReturn::Return(_)) {
182                    return Err(err(vm));
183                }
184                dict.insert(vm, &*key, value)?;
185            }
186        }
187        Ok(())
188    }
189
190    fn merge_dict(&self, dict_other: PyDictRef, vm: &VirtualMachine) -> PyResult<()> {
191        let dict = &self.entries;
192        let dict_size = &dict_other.size();
193        for (key, value) in &dict_other {
194            dict.insert(vm, &*key, value)?;
195        }
196        if dict_other.entries.has_changed_size(dict_size) {
197            return Err(vm.new_runtime_error("dict mutated during update"));
198        }
199        Ok(())
200    }
201
202    pub fn is_empty(&self) -> bool {
203        self.entries.len() == 0
204    }
205
206    /// Set item variant which can be called with multiple
207    /// key types, such as str to name a notable one.
208    pub(crate) fn inner_setitem<K: DictKey + ?Sized>(
209        &self,
210        key: &K,
211        value: PyObjectRef,
212        vm: &VirtualMachine,
213    ) -> PyResult<()> {
214        self.entries.insert(vm, key, value)
215    }
216
217    pub(crate) fn inner_delitem<K: DictKey + ?Sized>(
218        &self,
219        key: &K,
220        vm: &VirtualMachine,
221    ) -> PyResult<()> {
222        self.entries.delete(vm, key)
223    }
224
225    pub fn get_or_insert(
226        &self,
227        vm: &VirtualMachine,
228        key: PyObjectRef,
229        default: impl FnOnce() -> PyObjectRef,
230    ) -> PyResult {
231        self.entries.setdefault(vm, &*key, default)
232    }
233
234    pub fn from_attributes(attrs: PyAttributes, vm: &VirtualMachine) -> PyResult<Self> {
235        let entries = DictContentType::default();
236
237        for (key, value) in attrs {
238            entries.insert(vm, key, value)?;
239        }
240
241        Ok(Self { entries })
242    }
243
244    pub fn contains_key<K: DictKey + ?Sized>(&self, key: &K, vm: &VirtualMachine) -> bool {
245        self.entries.contains(vm, key).unwrap()
246    }
247
248    pub fn size(&self) -> dict_inner::DictSize {
249        self.entries.size()
250    }
251}
252
253// Python dict methods:
254#[allow(clippy::len_without_is_empty)]
255#[pyclass(
256    with(
257        Py,
258        PyRef,
259        Constructor,
260        Initializer,
261        Comparable,
262        Iterable,
263        AsSequence,
264        AsNumber,
265        AsMapping,
266        Representable
267    ),
268    flags(BASETYPE, MAPPING, _MATCH_SELF)
269)]
270impl PyDict {
271    #[pyclassmethod]
272    fn fromkeys(
273        class: PyTypeRef,
274        iterable: ArgIterable,
275        value: OptionalArg<PyObjectRef>,
276        vm: &VirtualMachine,
277    ) -> PyResult {
278        let value = value.unwrap_or_none(vm);
279        let d = PyType::call(&class, ().into(), vm)?;
280        match d.downcast_exact::<Self>(vm) {
281            Ok(pydict) => {
282                for key in iterable.iter(vm)? {
283                    pydict.__setitem__(key?, value.clone(), vm)?;
284                }
285                Ok(pydict.into_pyref().into())
286            }
287            Err(pyobj) => {
288                for key in iterable.iter(vm)? {
289                    pyobj.set_item(&*key?, value.clone(), vm)?;
290                }
291                Ok(pyobj)
292            }
293        }
294    }
295
296    pub fn __len__(&self) -> usize {
297        self.entries.len()
298    }
299
300    #[pymethod]
301    fn __sizeof__(&self) -> usize {
302        core::mem::size_of::<Self>() + self.entries.sizeof()
303    }
304
305    fn __contains__(&self, key: PyObjectRef, vm: &VirtualMachine) -> PyResult<bool> {
306        self.entries.contains(vm, &*key)
307    }
308
309    fn __delitem__(&self, key: PyObjectRef, vm: &VirtualMachine) -> PyResult<()> {
310        self.inner_delitem(&*key, vm)
311    }
312
313    #[pymethod]
314    pub fn clear(&self) {
315        self.entries.clear()
316    }
317
318    fn __setitem__(
319        &self,
320        key: PyObjectRef,
321        value: PyObjectRef,
322        vm: &VirtualMachine,
323    ) -> PyResult<()> {
324        self.inner_setitem(&*key, value, vm)
325    }
326
327    #[pymethod]
328    fn get(
329        &self,
330        key: PyObjectRef,
331        default: OptionalArg<PyObjectRef>,
332        vm: &VirtualMachine,
333    ) -> PyResult {
334        match self.entries.get(vm, &*key)? {
335            Some(value) => Ok(value),
336            None => Ok(default.unwrap_or_none(vm)),
337        }
338    }
339
340    #[pymethod]
341    fn setdefault(
342        &self,
343        key: PyObjectRef,
344        default: OptionalArg<PyObjectRef>,
345        vm: &VirtualMachine,
346    ) -> PyResult {
347        self.entries
348            .setdefault(vm, &*key, || default.unwrap_or_none(vm))
349    }
350
351    #[pymethod]
352    pub fn copy(&self) -> Self {
353        Self {
354            entries: self.entries.clone(),
355        }
356    }
357
358    #[pymethod]
359    fn update(
360        &self,
361        dict_obj: OptionalArg<PyObjectRef>,
362        kwargs: KwArgs,
363        vm: &VirtualMachine,
364    ) -> PyResult<()> {
365        if let OptionalArg::Present(dict_obj) = dict_obj {
366            self.merge_object(dict_obj, vm)?;
367        }
368        for (key, value) in kwargs {
369            self.entries.insert(vm, &key, value)?;
370        }
371        Ok(())
372    }
373
374    fn __or__(&self, other: PyObjectRef, vm: &VirtualMachine) -> PyResult {
375        let other_dict: Result<PyDictRef, _> = other.downcast();
376        if let Ok(other) = other_dict {
377            let self_cp = self.copy();
378            self_cp.merge_dict(other, vm)?;
379            return Ok(self_cp.into_pyobject(vm));
380        }
381        Ok(vm.ctx.not_implemented())
382    }
383
384    #[pymethod]
385    fn pop(
386        &self,
387        key: PyObjectRef,
388        default: OptionalArg<PyObjectRef>,
389        vm: &VirtualMachine,
390    ) -> PyResult {
391        match self.entries.pop(vm, &*key)? {
392            Some(value) => Ok(value),
393            None => default.ok_or_else(|| vm.new_key_error(key)),
394        }
395    }
396
397    #[pymethod]
398    fn popitem(&self, vm: &VirtualMachine) -> PyResult<(PyObjectRef, PyObjectRef)> {
399        let (key, value) = self.entries.pop_back().ok_or_else(|| {
400            let err_msg = vm
401                .ctx
402                .new_str(ascii!("popitem(): dictionary is empty"))
403                .into();
404            vm.new_key_error(err_msg)
405        })?;
406        Ok((key, value))
407    }
408
409    #[pyclassmethod]
410    fn __class_getitem__(cls: PyTypeRef, args: PyObjectRef, vm: &VirtualMachine) -> PyGenericAlias {
411        PyGenericAlias::from_args(cls, args, vm)
412    }
413}
414
415#[pyclass]
416impl Py<PyDict> {
417    fn inner_cmp(
418        &self,
419        other: &Self,
420        op: PyComparisonOp,
421        item: bool,
422        vm: &VirtualMachine,
423    ) -> PyResult<PyComparisonValue> {
424        if op == PyComparisonOp::Ne {
425            return Self::inner_cmp(self, other, PyComparisonOp::Eq, item, vm)
426                .map(|x| x.map(|eq| !eq));
427        }
428        if !op.eval_ord(self.__len__().cmp(&other.__len__())) {
429            return Ok(Implemented(false));
430        }
431        let (superset, subset) = if self.__len__() < other.__len__() {
432            (other, self)
433        } else {
434            (self, other)
435        };
436        for (k, v1) in subset {
437            match superset.get_item_opt(&*k, vm)? {
438                Some(v2) => {
439                    if v1.is(&v2) {
440                        continue;
441                    }
442                    if item && !vm.bool_eq(&v1, &v2)? {
443                        return Ok(Implemented(false));
444                    }
445                }
446                None => {
447                    return Ok(Implemented(false));
448                }
449            }
450        }
451        Ok(Implemented(true))
452    }
453
454    #[cfg_attr(feature = "flame-it", flame("PyDictRef"))]
455    fn __getitem__(&self, key: PyObjectRef, vm: &VirtualMachine) -> PyResult {
456        self.inner_getitem(&*key, vm)
457    }
458}
459
460#[pyclass]
461impl PyRef<PyDict> {
462    #[pymethod]
463    const fn keys(self) -> PyDictKeys {
464        PyDictKeys::new(self)
465    }
466
467    #[pymethod]
468    const fn values(self) -> PyDictValues {
469        PyDictValues::new(self)
470    }
471
472    #[pymethod]
473    const fn items(self) -> PyDictItems {
474        PyDictItems::new(self)
475    }
476
477    #[pymethod]
478    fn __reversed__(self) -> PyDictReverseKeyIterator {
479        PyDictReverseKeyIterator::new(self)
480    }
481
482    fn __ior__(self, other: PyObjectRef, vm: &VirtualMachine) -> PyResult<Self> {
483        self.merge_object(other, vm)?;
484        Ok(self)
485    }
486
487    fn __ror__(self, other: PyObjectRef, vm: &VirtualMachine) -> PyResult {
488        let other_dict: Result<Self, _> = other.downcast();
489        if let Ok(other) = other_dict {
490            let other_cp = other.copy();
491            other_cp.merge_dict(self, vm)?;
492            return Ok(other_cp.into_pyobject(vm));
493        }
494        Ok(vm.ctx.not_implemented())
495    }
496}
497
498impl DefaultConstructor for PyDict {}
499
500impl Initializer for PyDict {
501    type Args = (OptionalArg<PyObjectRef>, KwArgs);
502
503    fn init(
504        zelf: PyRef<Self>,
505        (dict_obj, kwargs): Self::Args,
506        vm: &VirtualMachine,
507    ) -> PyResult<()> {
508        zelf.update(dict_obj, kwargs, vm)
509    }
510}
511
512impl AsMapping for PyDict {
513    fn as_mapping() -> &'static PyMappingMethods {
514        static AS_MAPPING: PyMappingMethods = PyMappingMethods {
515            length: atomic_func!(|mapping, _vm| Ok(PyDict::mapping_downcast(mapping).__len__())),
516            subscript: atomic_func!(|mapping, needle, vm| {
517                PyDict::mapping_downcast(mapping).inner_getitem(needle, vm)
518            }),
519            ass_subscript: atomic_func!(|mapping, needle, value, vm| {
520                let zelf = PyDict::mapping_downcast(mapping);
521                if let Some(value) = value {
522                    zelf.inner_setitem(needle, value, vm)
523                } else {
524                    zelf.inner_delitem(needle, vm)
525                }
526            }),
527        };
528        &AS_MAPPING
529    }
530}
531
532impl AsSequence for PyDict {
533    fn as_sequence() -> &'static PySequenceMethods {
534        static AS_SEQUENCE: LazyLock<PySequenceMethods> = LazyLock::new(|| PySequenceMethods {
535            contains: atomic_func!(|seq, target, vm| PyDict::sequence_downcast(seq)
536                .entries
537                .contains(vm, target)),
538            ..PySequenceMethods::NOT_IMPLEMENTED
539        });
540        &AS_SEQUENCE
541    }
542}
543
544impl AsNumber for PyDict {
545    fn as_number() -> &'static PyNumberMethods {
546        static AS_NUMBER: PyNumberMethods = PyNumberMethods {
547            or: Some(|a, b, vm| {
548                if let Some(a) = a.downcast_ref::<PyDict>() {
549                    PyDict::__or__(a, b.to_pyobject(vm), vm)
550                } else {
551                    Ok(vm.ctx.not_implemented())
552                }
553            }),
554            inplace_or: Some(|a, b, vm| {
555                if let Some(a) = a.downcast_ref::<PyDict>() {
556                    a.to_owned()
557                        .__ior__(b.to_pyobject(vm), vm)
558                        .map(|d| d.into())
559                } else {
560                    Ok(vm.ctx.not_implemented())
561                }
562            }),
563            ..PyNumberMethods::NOT_IMPLEMENTED
564        };
565        &AS_NUMBER
566    }
567}
568
569impl Comparable for PyDict {
570    fn cmp(
571        zelf: &Py<Self>,
572        other: &PyObject,
573        op: PyComparisonOp,
574        vm: &VirtualMachine,
575    ) -> PyResult<PyComparisonValue> {
576        op.eq_only(|| {
577            let other = class_or_notimplemented!(Self, other);
578            zelf.inner_cmp(other, PyComparisonOp::Eq, true, vm)
579        })
580    }
581}
582
583impl Iterable for PyDict {
584    fn iter(zelf: PyRef<Self>, vm: &VirtualMachine) -> PyResult {
585        Ok(PyDictKeyIterator::new(zelf).into_pyobject(vm))
586    }
587}
588
589impl Representable for PyDict {
590    #[inline]
591    fn repr(zelf: &Py<Self>, vm: &VirtualMachine) -> PyResult<PyStrRef> {
592        let s = if let Some(_guard) = ReprGuard::enter(vm, zelf.as_object()) {
593            let mut result = Wtf8Buf::from("{");
594            let mut first = true;
595            for (key, value) in zelf {
596                if !first {
597                    result.push_str(", ");
598                }
599                first = false;
600                result.push_wtf8(key.repr(vm)?.as_wtf8());
601                result.push_str(": ");
602                result.push_wtf8(value.repr(vm)?.as_wtf8());
603            }
604            result.push_char('}');
605            vm.ctx.new_str(result)
606        } else {
607            vm.ctx.intern_str("{...}").to_owned()
608        };
609        Ok(s)
610    }
611
612    #[cold]
613    fn repr_str(_zelf: &Py<Self>, _vm: &VirtualMachine) -> PyResult<String> {
614        unreachable!("use repr instead")
615    }
616}
617
618impl Py<PyDict> {
619    #[inline]
620    fn exact_dict(&self, vm: &VirtualMachine) -> bool {
621        self.class().is(vm.ctx.types.dict_type)
622    }
623
624    fn missing_opt<K: DictKey + ?Sized>(
625        &self,
626        key: &K,
627        vm: &VirtualMachine,
628    ) -> PyResult<Option<PyObjectRef>> {
629        vm.get_method(self.to_owned().into(), identifier!(vm, __missing__))
630            .map(|methods| methods?.call((key.to_pyobject(vm),), vm))
631            .transpose()
632    }
633
634    #[inline]
635    fn inner_getitem<K: DictKey + ?Sized>(
636        &self,
637        key: &K,
638        vm: &VirtualMachine,
639    ) -> PyResult<PyObjectRef> {
640        if let Some(value) = self.entries.get(vm, key)? {
641            Ok(value)
642        } else if let Some(value) = self.missing_opt(key, vm)? {
643            Ok(value)
644        } else {
645            Err(vm.new_key_error(key.to_pyobject(vm)))
646        }
647    }
648
649    /// Take a python dictionary and convert it to attributes.
650    pub fn to_attributes(&self, vm: &VirtualMachine) -> PyAttributes {
651        let mut attrs = PyAttributes::default();
652        for (key, value) in self {
653            let key: PyRefExact<PyStr> = key.downcast_exact(vm).expect("dict has non-string keys");
654            attrs.insert(vm.ctx.intern_str(key), value);
655        }
656        attrs
657    }
658
659    pub fn get_item_opt<K: DictKey + ?Sized>(
660        &self,
661        key: &K,
662        vm: &VirtualMachine,
663    ) -> PyResult<Option<PyObjectRef>> {
664        if self.exact_dict(vm) {
665            self.entries.get(vm, key)
666            // FIXME: check __missing__?
667        } else {
668            match self.as_object().get_item(key, vm) {
669                Ok(value) => Ok(Some(value)),
670                Err(e) if e.fast_isinstance(vm.ctx.exceptions.key_error) => {
671                    self.missing_opt(key, vm)
672                }
673                Err(e) => Err(e),
674            }
675        }
676    }
677
678    /// Return a cached-entry hint for exact dict fast paths.
679    pub(crate) fn hint_for_key<K: DictKey + ?Sized>(
680        &self,
681        key: &K,
682        vm: &VirtualMachine,
683    ) -> PyResult<Option<u16>> {
684        if self.exact_dict(vm) {
685            self.entries.hint_for_key(vm, key)
686        } else {
687            Ok(None)
688        }
689    }
690
691    /// Fast lookup using a cached entry index hint.
692    pub(crate) fn get_item_opt_hint<K: DictKey + ?Sized>(
693        &self,
694        key: &K,
695        hint: u16,
696        vm: &VirtualMachine,
697    ) -> PyResult<Option<PyObjectRef>> {
698        if self.exact_dict(vm) {
699            self.entries.get_hint(vm, key, usize::from(hint))
700        } else {
701            self.get_item_opt(key, vm)
702        }
703    }
704
705    pub fn get_item<K: DictKey + ?Sized>(&self, key: &K, vm: &VirtualMachine) -> PyResult {
706        if self.exact_dict(vm) {
707            self.inner_getitem(key, vm)
708        } else {
709            self.as_object().get_item(key, vm)
710        }
711    }
712
713    pub fn set_item<K: DictKey + ?Sized>(
714        &self,
715        key: &K,
716        value: PyObjectRef,
717        vm: &VirtualMachine,
718    ) -> PyResult<()> {
719        if self.exact_dict(vm) {
720            self.inner_setitem(key, value, vm)
721        } else {
722            self.as_object().set_item(key, value, vm)
723        }
724    }
725
726    pub fn del_item<K: DictKey + ?Sized>(&self, key: &K, vm: &VirtualMachine) -> PyResult<()> {
727        if self.exact_dict(vm) {
728            self.inner_delitem(key, vm)
729        } else {
730            self.as_object().del_item(key, vm)
731        }
732    }
733
734    pub fn pop_item<K: DictKey + ?Sized>(
735        &self,
736        key: &K,
737        vm: &VirtualMachine,
738    ) -> PyResult<Option<PyObjectRef>> {
739        if self.exact_dict(vm) {
740            self.entries.remove_if_exists(vm, key)
741        } else {
742            let value = self.as_object().get_item(key, vm)?;
743            self.as_object().del_item(key, vm)?;
744            Ok(Some(value))
745        }
746    }
747
748    pub fn get_chain<K: DictKey + ?Sized>(
749        &self,
750        other: &Self,
751        key: &K,
752        vm: &VirtualMachine,
753    ) -> PyResult<Option<PyObjectRef>> {
754        let self_exact = self.exact_dict(vm);
755        let other_exact = other.exact_dict(vm);
756        if self_exact && other_exact {
757            // SAFETY: exact_dict checks passed
758            let self_exact = unsafe { PyExact::ref_unchecked(self) };
759            let other_exact = unsafe { PyExact::ref_unchecked(other) };
760            self_exact.get_chain_exact(other_exact, key, vm)
761        } else if let Some(value) = self.get_item_opt(key, vm)? {
762            Ok(Some(value))
763        } else {
764            other.get_item_opt(key, vm)
765        }
766    }
767}
768
769impl PyExact<PyDict> {
770    /// Look up `key` in `self`, falling back to `other`.
771    /// Both dicts must be exact `dict` types (enforced by `PyExact`).
772    pub(crate) fn get_chain_exact<K: DictKey + ?Sized>(
773        &self,
774        other: &Self,
775        key: &K,
776        vm: &VirtualMachine,
777    ) -> PyResult<Option<PyObjectRef>> {
778        debug_assert!(self.class().is(vm.ctx.types.dict_type));
779        debug_assert!(other.class().is(vm.ctx.types.dict_type));
780        self.entries.get_chain(&other.entries, vm, key)
781    }
782}
783
784// Implement IntoIterator so that we can easily iterate dictionaries from rust code.
785impl IntoIterator for PyDictRef {
786    type Item = (PyObjectRef, PyObjectRef);
787    type IntoIter = DictIntoIter;
788
789    fn into_iter(self) -> Self::IntoIter {
790        DictIntoIter::new(self)
791    }
792}
793
794impl<'a> IntoIterator for &'a PyDictRef {
795    type Item = (PyObjectRef, PyObjectRef);
796    type IntoIter = DictIter<'a>;
797
798    fn into_iter(self) -> Self::IntoIter {
799        DictIter::new(self)
800    }
801}
802
803impl<'a> IntoIterator for &'a Py<PyDict> {
804    type Item = (PyObjectRef, PyObjectRef);
805    type IntoIter = DictIter<'a>;
806
807    fn into_iter(self) -> Self::IntoIter {
808        DictIter::new(self)
809    }
810}
811
812impl<'a> IntoIterator for &'a PyDict {
813    type Item = (PyObjectRef, PyObjectRef);
814    type IntoIter = DictIter<'a>;
815
816    fn into_iter(self) -> Self::IntoIter {
817        DictIter::new(self)
818    }
819}
820
821pub struct DictIntoIter {
822    dict: PyDictRef,
823    position: usize,
824}
825
826impl DictIntoIter {
827    pub const fn new(dict: PyDictRef) -> Self {
828        Self { dict, position: 0 }
829    }
830}
831
832impl Iterator for DictIntoIter {
833    type Item = (PyObjectRef, PyObjectRef);
834
835    fn next(&mut self) -> Option<Self::Item> {
836        let (position, key, value) = self.dict.entries.next_entry(self.position)?;
837        self.position = position;
838        Some((key, value))
839    }
840
841    fn size_hint(&self) -> (usize, Option<usize>) {
842        let l = self.len();
843        (l, Some(l))
844    }
845}
846impl ExactSizeIterator for DictIntoIter {
847    fn len(&self) -> usize {
848        self.dict.entries.len_from_entry_index(self.position)
849    }
850}
851
852pub struct DictIter<'a> {
853    dict: &'a PyDict,
854    position: usize,
855}
856
857impl<'a> DictIter<'a> {
858    pub const fn new(dict: &'a PyDict) -> Self {
859        DictIter { dict, position: 0 }
860    }
861}
862
863impl Iterator for DictIter<'_> {
864    type Item = (PyObjectRef, PyObjectRef);
865
866    fn next(&mut self) -> Option<Self::Item> {
867        let (position, key, value) = self.dict.entries.next_entry(self.position)?;
868        self.position = position;
869        Some((key, value))
870    }
871
872    fn size_hint(&self) -> (usize, Option<usize>) {
873        let l = self.len();
874        (l, Some(l))
875    }
876}
877impl ExactSizeIterator for DictIter<'_> {
878    fn len(&self) -> usize {
879        self.dict.entries.len_from_entry_index(self.position)
880    }
881}
882
883#[pyclass]
884trait DictView: PyPayload + PyClassDef + Iterable + Representable {
885    type ReverseIter: PyPayload + core::fmt::Debug;
886
887    fn dict(&self) -> &Py<PyDict>;
888    fn item(vm: &VirtualMachine, key: PyObjectRef, value: PyObjectRef) -> PyObjectRef;
889
890    fn __len__(&self) -> usize {
891        self.dict().__len__()
892    }
893
894    #[pymethod]
895    fn __reversed__(&self) -> Self::ReverseIter;
896}
897
898macro_rules! dict_view {
899    ( $name: ident, $iter_name: ident, $reverse_iter_name: ident,
900      $class: ident, $iter_class: ident, $reverse_iter_class: ident,
901      $class_name: literal, $iter_class_name: literal, $reverse_iter_class_name: literal,
902      $result_fn: expr) => {
903        #[pyclass(module = false, name = $class_name)]
904        #[derive(Debug)]
905        pub(crate) struct $name {
906            pub dict: PyDictRef,
907        }
908
909        impl $name {
910            pub const fn new(dict: PyDictRef) -> Self {
911                $name { dict }
912            }
913        }
914
915        impl DictView for $name {
916            type ReverseIter = $reverse_iter_name;
917
918            fn dict(&self) -> &Py<PyDict> {
919                &self.dict
920            }
921
922            fn item(vm: &VirtualMachine, key: PyObjectRef, value: PyObjectRef) -> PyObjectRef {
923                #[allow(clippy::redundant_closure_call)]
924                $result_fn(vm, key, value)
925            }
926
927            fn __reversed__(&self) -> Self::ReverseIter {
928                $reverse_iter_name::new(self.dict.clone())
929            }
930        }
931
932        impl Iterable for $name {
933            fn iter(zelf: PyRef<Self>, vm: &VirtualMachine) -> PyResult {
934                Ok($iter_name::new(zelf.dict.clone()).into_pyobject(vm))
935            }
936        }
937
938        impl PyPayload for $name {
939            fn class(ctx: &Context) -> &'static Py<PyType> {
940                ctx.types.$class
941            }
942        }
943
944        impl Representable for $name {
945            #[inline]
946            fn repr(zelf: &Py<Self>, vm: &VirtualMachine) -> PyResult<PyStrRef> {
947                let s = if let Some(_guard) = ReprGuard::enter(vm, zelf.as_object()) {
948                    let mut result = Wtf8Buf::from(format!("{}([", Self::NAME));
949                    let mut first = true;
950                    for (key, value) in zelf.dict().clone() {
951                        if !first {
952                            result.push_str(", ");
953                        }
954                        first = false;
955                        result.push_wtf8(Self::item(vm, key, value).repr(vm)?.as_wtf8());
956                    }
957                    result.push_str("])");
958                    vm.ctx.new_str(result)
959                } else {
960                    vm.ctx.intern_str("{...}").to_owned()
961                };
962                Ok(s)
963            }
964
965            #[cold]
966            fn repr_str(_zelf: &Py<Self>, _vm: &VirtualMachine) -> PyResult<String> {
967                unreachable!("use repr instead")
968            }
969        }
970
971        #[pyclass(module = false, name = $iter_class_name)]
972        #[derive(Debug)]
973        pub(crate) struct $iter_name {
974            pub size: dict_inner::DictSize,
975            pub internal: PyMutex<PositionIterInternal<PyDictRef>>,
976        }
977
978        impl PyPayload for $iter_name {
979            #[inline]
980            fn class(ctx: &Context) -> &'static Py<PyType> {
981                ctx.types.$iter_class
982            }
983        }
984
985        #[pyclass(flags(DISALLOW_INSTANTIATION), with(IterNext, Iterable))]
986        impl $iter_name {
987            fn new(dict: PyDictRef) -> Self {
988                $iter_name {
989                    size: dict.size(),
990                    internal: PyMutex::new(PositionIterInternal::new(dict, 0)),
991                }
992            }
993
994            #[pymethod]
995            fn __length_hint__(&self) -> usize {
996                self.internal.lock().length_hint(|_| self.size.entries_size)
997            }
998
999            #[allow(clippy::redundant_closure_call)]
1000            #[pymethod]
1001            fn __reduce__(&self, vm: &VirtualMachine) -> PyTupleRef {
1002                let iter = builtins_iter(vm);
1003                let internal = self.internal.lock();
1004                let entries = match &internal.status {
1005                    IterStatus::Active(dict) => dict
1006                        .into_iter()
1007                        .map(|(key, value)| ($result_fn)(vm, key, value))
1008                        .collect::<Vec<_>>(),
1009                    IterStatus::Exhausted => vec![],
1010                };
1011                vm.new_tuple((iter, (vm.ctx.new_list(entries),)))
1012            }
1013        }
1014
1015        impl SelfIter for $iter_name {}
1016        impl IterNext for $iter_name {
1017            #[allow(clippy::redundant_closure_call)]
1018            fn next(zelf: &Py<Self>, vm: &VirtualMachine) -> PyResult<PyIterReturn> {
1019                let mut internal = zelf.internal.lock();
1020                let next = if let IterStatus::Active(dict) = &internal.status {
1021                    if dict.entries.has_changed_size(&zelf.size) {
1022                        internal.status = IterStatus::Exhausted;
1023                        return Err(
1024                            vm.new_runtime_error("dictionary changed size during iteration")
1025                        );
1026                    }
1027                    match dict.entries.next_entry(internal.position) {
1028                        Some((position, key, value)) => {
1029                            internal.position = position;
1030                            PyIterReturn::Return(($result_fn)(vm, key, value))
1031                        }
1032                        None => {
1033                            internal.status = IterStatus::Exhausted;
1034                            PyIterReturn::StopIteration(None)
1035                        }
1036                    }
1037                } else {
1038                    PyIterReturn::StopIteration(None)
1039                };
1040                Ok(next)
1041            }
1042        }
1043
1044        #[pyclass(module = false, name = $reverse_iter_class_name)]
1045        #[derive(Debug)]
1046        pub(crate) struct $reverse_iter_name {
1047            pub size: dict_inner::DictSize,
1048            internal: PyMutex<PositionIterInternal<PyDictRef>>,
1049        }
1050
1051        impl PyPayload for $reverse_iter_name {
1052            #[inline]
1053            fn class(ctx: &Context) -> &'static Py<PyType> {
1054                ctx.types.$reverse_iter_class
1055            }
1056        }
1057
1058        #[pyclass(flags(DISALLOW_INSTANTIATION), with(IterNext, Iterable))]
1059        impl $reverse_iter_name {
1060            fn new(dict: PyDictRef) -> Self {
1061                let size = dict.size();
1062                let position = size.entries_size.saturating_sub(1);
1063                $reverse_iter_name {
1064                    size,
1065                    internal: PyMutex::new(PositionIterInternal::new(dict, position)),
1066                }
1067            }
1068
1069            #[allow(clippy::redundant_closure_call)]
1070            #[pymethod]
1071            fn __reduce__(&self, vm: &VirtualMachine) -> PyTupleRef {
1072                let iter = builtins_reversed(vm);
1073                let internal = self.internal.lock();
1074                // TODO: entries must be reversed too
1075                let entries = match &internal.status {
1076                    IterStatus::Active(dict) => dict
1077                        .into_iter()
1078                        .map(|(key, value)| ($result_fn)(vm, key, value))
1079                        .collect::<Vec<_>>(),
1080                    IterStatus::Exhausted => vec![],
1081                };
1082                vm.new_tuple((iter, (vm.ctx.new_list(entries),)))
1083            }
1084
1085            #[pymethod]
1086            fn __length_hint__(&self) -> usize {
1087                self.internal
1088                    .lock()
1089                    .rev_length_hint(|_| self.size.entries_size)
1090            }
1091        }
1092        impl SelfIter for $reverse_iter_name {}
1093        impl IterNext for $reverse_iter_name {
1094            #[allow(clippy::redundant_closure_call)]
1095            fn next(zelf: &Py<Self>, vm: &VirtualMachine) -> PyResult<PyIterReturn> {
1096                let mut internal = zelf.internal.lock();
1097                let next = if let IterStatus::Active(dict) = &internal.status {
1098                    if dict.entries.has_changed_size(&zelf.size) {
1099                        internal.status = IterStatus::Exhausted;
1100                        return Err(
1101                            vm.new_runtime_error("dictionary changed size during iteration")
1102                        );
1103                    }
1104                    match dict.entries.prev_entry(internal.position) {
1105                        Some((position, key, value)) => {
1106                            if internal.position == position {
1107                                internal.status = IterStatus::Exhausted;
1108                            } else {
1109                                internal.position = position;
1110                            }
1111                            PyIterReturn::Return(($result_fn)(vm, key, value))
1112                        }
1113                        None => {
1114                            internal.status = IterStatus::Exhausted;
1115                            PyIterReturn::StopIteration(None)
1116                        }
1117                    }
1118                } else {
1119                    PyIterReturn::StopIteration(None)
1120                };
1121                Ok(next)
1122            }
1123        }
1124    };
1125}
1126
1127dict_view! {
1128    PyDictKeys,
1129    PyDictKeyIterator,
1130    PyDictReverseKeyIterator,
1131    dict_keys_type,
1132    dict_keyiterator_type,
1133    dict_reversekeyiterator_type,
1134    "dict_keys",
1135    "dict_keyiterator",
1136    "dict_reversekeyiterator",
1137    |_vm: &VirtualMachine, key: PyObjectRef, _value: PyObjectRef| key
1138}
1139
1140dict_view! {
1141    PyDictValues,
1142    PyDictValueIterator,
1143    PyDictReverseValueIterator,
1144    dict_values_type,
1145    dict_valueiterator_type,
1146    dict_reversevalueiterator_type,
1147    "dict_values",
1148    "dict_valueiterator",
1149    "dict_reversevalueiterator",
1150    |_vm: &VirtualMachine, _key: PyObjectRef, value: PyObjectRef| value
1151}
1152
1153dict_view! {
1154    PyDictItems,
1155    PyDictItemIterator,
1156    PyDictReverseItemIterator,
1157    dict_items_type,
1158    dict_itemiterator_type,
1159    dict_reverseitemiterator_type,
1160    "dict_items",
1161    "dict_itemiterator",
1162    "dict_reverseitemiterator",
1163    |vm: &VirtualMachine, key: PyObjectRef, value: PyObjectRef|
1164        vm.new_tuple((key, value)).into()
1165}
1166
1167// Set operations defined on set-like views of the dictionary.
1168#[pyclass]
1169trait ViewSetOps: DictView {
1170    fn to_set(zelf: PyRef<Self>, vm: &VirtualMachine) -> PyResult<PySetInner> {
1171        let len = zelf.dict().__len__();
1172        let zelf: PyObjectRef = Self::iter(zelf, vm)?;
1173        let iter = PyIterIter::new(vm, zelf, Some(len));
1174        PySetInner::from_iter(iter, vm)
1175    }
1176
1177    fn __xor__(zelf: PyRef<Self>, other: ArgIterable, vm: &VirtualMachine) -> PyResult<PySet> {
1178        let zelf = Self::to_set(zelf, vm)?;
1179        let inner = zelf.symmetric_difference(other, vm)?;
1180        Ok(PySet { inner })
1181    }
1182
1183    fn __and__(zelf: PyRef<Self>, other: ArgIterable, vm: &VirtualMachine) -> PyResult<PySet> {
1184        let zelf = Self::to_set(zelf, vm)?;
1185        let inner = zelf.intersection(other, vm)?;
1186        Ok(PySet { inner })
1187    }
1188
1189    fn __or__(zelf: PyRef<Self>, other: ArgIterable, vm: &VirtualMachine) -> PyResult<PySet> {
1190        let zelf = Self::to_set(zelf, vm)?;
1191        let inner = zelf.union(other, vm)?;
1192        Ok(PySet { inner })
1193    }
1194
1195    fn __sub__(zelf: PyRef<Self>, other: ArgIterable, vm: &VirtualMachine) -> PyResult<PySet> {
1196        let zelf = Self::to_set(zelf, vm)?;
1197        let inner = zelf.difference(other, vm)?;
1198        Ok(PySet { inner })
1199    }
1200
1201    fn __rsub__(zelf: PyRef<Self>, other: ArgIterable, vm: &VirtualMachine) -> PyResult<PySet> {
1202        let left = PySetInner::from_iter(other.iter(vm)?, vm)?;
1203        let right = ArgIterable::try_from_object(vm, Self::iter(zelf, vm)?)?;
1204        let inner = left.difference(right, vm)?;
1205        Ok(PySet { inner })
1206    }
1207
1208    fn cmp(
1209        zelf: &Py<Self>,
1210        other: &PyObject,
1211        op: PyComparisonOp,
1212        vm: &VirtualMachine,
1213    ) -> PyResult<PyComparisonValue> {
1214        match_class!(match other {
1215            ref dictview @ Self => {
1216                return zelf.dict().inner_cmp(
1217                    dictview.dict(),
1218                    op,
1219                    !zelf.class().is(vm.ctx.types.dict_keys_type),
1220                    vm,
1221                );
1222            }
1223            ref _set @ PySet => {
1224                let inner = Self::to_set(zelf.to_owned(), vm)?;
1225                let zelf_set = PySet { inner }.into_pyobject(vm);
1226                return PySet::cmp(zelf_set.downcast_ref().unwrap(), other, op, vm);
1227            }
1228            ref _dictitems @ PyDictItems => {}
1229            ref _dictkeys @ PyDictKeys => {}
1230            _ => {
1231                return Ok(NotImplemented);
1232            }
1233        });
1234        let lhs: Vec<PyObjectRef> = zelf.as_object().to_owned().try_into_value(vm)?;
1235        let rhs: Vec<PyObjectRef> = other.to_owned().try_into_value(vm)?;
1236        lhs.iter()
1237            .richcompare(rhs.iter(), op, vm)
1238            .map(PyComparisonValue::Implemented)
1239    }
1240
1241    #[pymethod]
1242    fn isdisjoint(zelf: PyRef<Self>, other: ArgIterable, vm: &VirtualMachine) -> PyResult<bool> {
1243        // TODO: to_set is an expensive operation. After merging #3316 rewrite implementation using PySequence_Contains.
1244        let zelf = Self::to_set(zelf, vm)?;
1245        let result = zelf.isdisjoint(other, vm)?;
1246        Ok(result)
1247    }
1248}
1249
1250impl ViewSetOps for PyDictKeys {}
1251#[pyclass(
1252    flags(DISALLOW_INSTANTIATION),
1253    with(
1254        DictView,
1255        Comparable,
1256        Iterable,
1257        ViewSetOps,
1258        AsSequence,
1259        AsNumber,
1260        Representable
1261    )
1262)]
1263impl PyDictKeys {
1264    fn __contains__(zelf: PyObjectRef, key: PyObjectRef, vm: &VirtualMachine) -> PyResult<bool> {
1265        zelf.sequence_unchecked().contains(&key, vm)
1266    }
1267
1268    #[pygetset]
1269    fn mapping(zelf: PyRef<Self>) -> PyMappingProxy {
1270        PyMappingProxy::from(zelf.dict().to_owned())
1271    }
1272}
1273
1274impl Comparable for PyDictKeys {
1275    fn cmp(
1276        zelf: &Py<Self>,
1277        other: &PyObject,
1278        op: PyComparisonOp,
1279        vm: &VirtualMachine,
1280    ) -> PyResult<PyComparisonValue> {
1281        ViewSetOps::cmp(zelf, other, op, vm)
1282    }
1283}
1284
1285impl AsSequence for PyDictKeys {
1286    fn as_sequence() -> &'static PySequenceMethods {
1287        static AS_SEQUENCE: LazyLock<PySequenceMethods> = LazyLock::new(|| PySequenceMethods {
1288            length: atomic_func!(|seq, _vm| Ok(PyDictKeys::sequence_downcast(seq).__len__())),
1289            contains: atomic_func!(|seq, target, vm| {
1290                PyDictKeys::sequence_downcast(seq)
1291                    .dict
1292                    .entries
1293                    .contains(vm, target)
1294            }),
1295            ..PySequenceMethods::NOT_IMPLEMENTED
1296        });
1297        &AS_SEQUENCE
1298    }
1299}
1300
1301impl AsNumber for PyDictKeys {
1302    fn as_number() -> &'static PyNumberMethods {
1303        static AS_NUMBER: PyNumberMethods = PyNumberMethods {
1304            subtract: Some(set_inner_number_subtract),
1305            and: Some(set_inner_number_and),
1306            xor: Some(set_inner_number_xor),
1307            or: Some(set_inner_number_or),
1308            ..PyNumberMethods::NOT_IMPLEMENTED
1309        };
1310        &AS_NUMBER
1311    }
1312}
1313
1314impl ViewSetOps for PyDictItems {}
1315#[pyclass(
1316    flags(DISALLOW_INSTANTIATION),
1317    with(
1318        DictView,
1319        Comparable,
1320        Iterable,
1321        ViewSetOps,
1322        AsSequence,
1323        AsNumber,
1324        Representable
1325    )
1326)]
1327impl PyDictItems {
1328    fn __contains__(zelf: PyObjectRef, needle: PyObjectRef, vm: &VirtualMachine) -> PyResult<bool> {
1329        zelf.sequence_unchecked().contains(&needle, vm)
1330    }
1331    #[pygetset]
1332    fn mapping(zelf: PyRef<Self>) -> PyMappingProxy {
1333        PyMappingProxy::from(zelf.dict().to_owned())
1334    }
1335}
1336
1337impl Comparable for PyDictItems {
1338    fn cmp(
1339        zelf: &Py<Self>,
1340        other: &PyObject,
1341        op: PyComparisonOp,
1342        vm: &VirtualMachine,
1343    ) -> PyResult<PyComparisonValue> {
1344        ViewSetOps::cmp(zelf, other, op, vm)
1345    }
1346}
1347
1348impl AsSequence for PyDictItems {
1349    fn as_sequence() -> &'static PySequenceMethods {
1350        static AS_SEQUENCE: LazyLock<PySequenceMethods> = LazyLock::new(|| PySequenceMethods {
1351            length: atomic_func!(|seq, _vm| Ok(PyDictItems::sequence_downcast(seq).__len__())),
1352            contains: atomic_func!(|seq, target, vm| {
1353                let needle: &Py<PyTuple> = match target.downcast_ref() {
1354                    Some(needle) => needle,
1355                    None => return Ok(false),
1356                };
1357                if needle.len() != 2 {
1358                    return Ok(false);
1359                }
1360
1361                let zelf = PyDictItems::sequence_downcast(seq);
1362                let key = &needle[0];
1363                if !zelf.dict.__contains__(key.to_owned(), vm)? {
1364                    return Ok(false);
1365                }
1366                let value = &needle[1];
1367                let found = zelf.dict().__getitem__(key.to_owned(), vm)?;
1368                vm.identical_or_equal(&found, value)
1369            }),
1370            ..PySequenceMethods::NOT_IMPLEMENTED
1371        });
1372        &AS_SEQUENCE
1373    }
1374}
1375
1376impl AsNumber for PyDictItems {
1377    fn as_number() -> &'static PyNumberMethods {
1378        static AS_NUMBER: PyNumberMethods = PyNumberMethods {
1379            subtract: Some(set_inner_number_subtract),
1380            and: Some(set_inner_number_and),
1381            xor: Some(set_inner_number_xor),
1382            or: Some(set_inner_number_or),
1383            ..PyNumberMethods::NOT_IMPLEMENTED
1384        };
1385        &AS_NUMBER
1386    }
1387}
1388
1389#[pyclass(
1390    flags(DISALLOW_INSTANTIATION),
1391    with(DictView, Iterable, AsSequence, Representable)
1392)]
1393impl PyDictValues {
1394    #[pygetset]
1395    fn mapping(zelf: PyRef<Self>) -> PyMappingProxy {
1396        PyMappingProxy::from(zelf.dict().to_owned())
1397    }
1398}
1399
1400impl AsSequence for PyDictValues {
1401    fn as_sequence() -> &'static PySequenceMethods {
1402        static AS_SEQUENCE: LazyLock<PySequenceMethods> = LazyLock::new(|| PySequenceMethods {
1403            length: atomic_func!(|seq, _vm| Ok(PyDictValues::sequence_downcast(seq).__len__())),
1404            ..PySequenceMethods::NOT_IMPLEMENTED
1405        });
1406        &AS_SEQUENCE
1407    }
1408}
1409
1410fn set_inner_number_op<F>(a: &PyObject, b: &PyObject, f: F, vm: &VirtualMachine) -> PyResult
1411where
1412    F: FnOnce(PySetInner, ArgIterable) -> PyResult<PySetInner>,
1413{
1414    let a = PySetInner::from_iter(
1415        ArgIterable::try_from_object(vm, a.to_owned())?.iter(vm)?,
1416        vm,
1417    )?;
1418    let b = ArgIterable::try_from_object(vm, b.to_owned())?;
1419    Ok(PySet { inner: f(a, b)? }.into_pyobject(vm))
1420}
1421
1422fn set_inner_number_subtract(a: &PyObject, b: &PyObject, vm: &VirtualMachine) -> PyResult {
1423    set_inner_number_op(a, b, |a, b| a.difference(b, vm), vm)
1424}
1425
1426fn set_inner_number_and(a: &PyObject, b: &PyObject, vm: &VirtualMachine) -> PyResult {
1427    set_inner_number_op(a, b, |a, b| a.intersection(b, vm), vm)
1428}
1429
1430fn set_inner_number_xor(a: &PyObject, b: &PyObject, vm: &VirtualMachine) -> PyResult {
1431    set_inner_number_op(a, b, |a, b| a.symmetric_difference(b, vm), vm)
1432}
1433
1434fn set_inner_number_or(a: &PyObject, b: &PyObject, vm: &VirtualMachine) -> PyResult {
1435    set_inner_number_op(a, b, |a, b| a.union(b, vm), vm)
1436}
1437
1438fn vectorcall_dict(
1439    zelf_obj: &PyObject,
1440    args: Vec<PyObjectRef>,
1441    nargs: usize,
1442    kwnames: Option<&[PyObjectRef]>,
1443    vm: &VirtualMachine,
1444) -> PyResult {
1445    let zelf: &Py<PyType> = zelf_obj.downcast_ref().unwrap();
1446    let obj = PyDict::default().into_ref_with_type(vm, zelf.to_owned())?;
1447    let func_args = FuncArgs::from_vectorcall_owned(args, nargs, kwnames);
1448    PyDict::slot_init(obj.clone().into(), func_args, vm)?;
1449    Ok(obj.into())
1450}
1451
1452pub(crate) fn init(context: &'static Context) {
1453    PyDict::extend_class(context, context.types.dict_type);
1454    context
1455        .types
1456        .dict_type
1457        .slots
1458        .vectorcall
1459        .store(Some(vectorcall_dict));
1460    PyDictKeys::extend_class(context, context.types.dict_keys_type);
1461    PyDictKeyIterator::extend_class(context, context.types.dict_keyiterator_type);
1462    PyDictReverseKeyIterator::extend_class(context, context.types.dict_reversekeyiterator_type);
1463    PyDictValues::extend_class(context, context.types.dict_values_type);
1464    PyDictValueIterator::extend_class(context, context.types.dict_valueiterator_type);
1465    PyDictReverseValueIterator::extend_class(context, context.types.dict_reversevalueiterator_type);
1466    PyDictItems::extend_class(context, context.types.dict_items_type);
1467    PyDictItemIterator::extend_class(context, context.types.dict_itemiterator_type);
1468    PyDictReverseItemIterator::extend_class(context, context.types.dict_reverseitemiterator_type);
1469}