Skip to main content

rustpython_vm/builtins/
dict.rs

1use super::{
2    IterStatus, PositionIterInternal, PyBaseExceptionRef, PyFrozenSet, PyGenericAlias,
3    PyMappingProxy, PySet, PyStr, PyStrRef, PyTupleRef, PyType, PyTypeRef, locked_step, set,
4    set::PySetInner,
5};
6use crate::common::lock::LazyLock;
7use crate::object::{Traverse, TraverseFn};
8use crate::stdlib::PyOrderedDictItems;
9use crate::{
10    AsObject, Context, Py, PyExact, PyObject, PyObjectRef, PyPayload, PyRef, PyRefExact, PyResult,
11    TryFromObject, atomic_func,
12    builtins::{PyList, PyTuple, iter::builtins_iter, type_::PyAttributes},
13    class::{PyClassDef, PyClassImpl},
14    common::{ascii, hash::PyHash},
15    dict_inner::{self, DictKey},
16    function::{ArgIterable, FuncArgs, KwArgs, OptionalArg, PyArithmeticValue, PyComparisonValue},
17    protocol::{PyIter, PyIterReturn, PyMappingMethods, PyNumberMethods, PySequenceMethods},
18    recursion::ReprGuard,
19    types::{
20        AsMapping, AsNumber, AsSequence, Callable, Comparable, Constructor, DefaultConstructor,
21        Initializer, IterNext, Iterable, PyComparisonOp, Representable, SelfIter,
22    },
23    vm::VirtualMachine,
24};
25use alloc::fmt;
26use core::cell::Cell;
27use core::ptr::NonNull;
28use rustpython_common::atomic::{Ordering, PyAtomic, Radium};
29use rustpython_common::lock::PyMutex;
30use rustpython_common::wtf8::Wtf8Buf;
31
32pub(crate) type DictContentType = dict_inner::Dict;
33
34#[pyclass(module = false, name = "dict", unhashable = true, traverse = "manual")]
35#[derive(Default)]
36// OrderedDict contains eight-byte-aligned fields on 32-bit targets too.
37#[repr(align(8))]
38pub struct PyDict {
39    entries: DictContentType,
40}
41pub type PyDictRef = PyRef<PyDict>;
42
43// SAFETY: Traverse properly visits all owned PyObjectRefs
44unsafe impl Traverse for PyDict {
45    fn traverse(&self, traverse_fn: &mut TraverseFn<'_>) {
46        self.entries.traverse(traverse_fn);
47    }
48
49    fn clear(&mut self, out: &mut Vec<PyObjectRef>) {
50        // Pop all entries and collect both keys and values
51        for (key, value) in self.entries.drain_entries() {
52            out.push(key);
53            out.push(value);
54        }
55    }
56}
57
58impl fmt::Debug for PyDict {
59    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
60        // TODO: implement more detailed, non-recursive Debug formatter
61        f.write_str("dict")
62    }
63}
64
65thread_local! {
66    static DICT_FREELIST: Cell<crate::object::FreeList<PyDict>> = const { Cell::new(crate::object::FreeList::new()) };
67}
68
69impl PyPayload for PyDict {
70    const MAX_FREELIST: usize = 80;
71    const HAS_FREELIST: bool = true;
72
73    #[inline]
74    fn class(ctx: &Context) -> &'static Py<PyType> {
75        ctx.types.dict_type
76    }
77
78    #[inline]
79    unsafe fn freelist_push(obj: *mut PyObject) -> bool {
80        DICT_FREELIST
81            .try_with(|fl| {
82                let mut list = fl.take();
83                let stored = if list.len() < Self::MAX_FREELIST {
84                    list.push(obj);
85                    true
86                } else {
87                    false
88                };
89                fl.set(list);
90                stored
91            })
92            .unwrap_or(false)
93    }
94
95    #[inline]
96    unsafe fn freelist_pop(_payload: &Self) -> Option<NonNull<PyObject>> {
97        DICT_FREELIST
98            .try_with(|fl| {
99                let mut list = fl.take();
100                let result = list.pop().map(|p| unsafe { NonNull::new_unchecked(p) });
101                fl.set(list);
102                result
103            })
104            .ok()
105            .flatten()
106    }
107}
108
109impl PyDict {
110    #[deprecated(note = "use PyDict::default().into_ref() instead")]
111    pub fn new_ref(ctx: &Context) -> PyRef<Self> {
112        Self::default().into_ref(ctx)
113    }
114
115    /// escape hatch to access the underlying data structure directly. prefer adding a method on
116    /// PyDict instead of using this
117    pub(crate) const fn _as_dict_inner(&self) -> &DictContentType {
118        &self.entries
119    }
120
121    /// Returns all keys as a Vec, atomically under a single read lock.
122    /// Thread-safe: prevents "dictionary changed size during iteration" errors.
123    pub fn keys_vec(&self) -> Vec<PyObjectRef> {
124        self.entries.keys()
125    }
126
127    /// Returns all values as a Vec, atomically under a single read lock.
128    /// Thread-safe: prevents "dictionary changed size during iteration" errors.
129    pub fn values_vec(&self) -> Vec<PyObjectRef> {
130        self.entries.values()
131    }
132
133    /// Returns all items as a Vec, atomically under a single read lock.
134    /// Thread-safe: prevents "dictionary changed size during iteration" errors.
135    pub fn items_vec(&self) -> Vec<(PyObjectRef, PyObjectRef)> {
136        self.entries.items()
137    }
138
139    fn merge_object_with_override(
140        &self,
141        other: PyObjectRef,
142        override_existing: bool,
143        vm: &VirtualMachine,
144    ) -> PyResult<()> {
145        let casted: Result<PyRefExact<Self>, _> = other.downcast_exact(vm);
146        let other = match casted {
147            Ok(dict_other) => {
148                return self.merge_dict(&dict_other, override_existing, vm);
149            }
150            Err(other) => other,
151        };
152        let dict = &self.entries;
153        // Use get_attr to properly invoke __getattribute__ for proxy objects
154        let keys_result = other.get_attr(vm.ctx.intern_str("keys"), vm);
155        let has_keys = match keys_result {
156            Ok(keys_method) => {
157                let keys = PyIter::try_from_object(vm, keys_method.call((), vm)?)?;
158                while let PyIterReturn::Return(key) = keys.next(vm)? {
159                    if !override_existing && dict.contains(vm, &*key)? {
160                        continue;
161                    }
162                    let val = other.get_item(&*key, vm)?;
163                    dict.insert(vm, &*key, val)?;
164                }
165                true
166            }
167            Err(e) if e.fast_isinstance(vm.ctx.exceptions.attribute_error) => false,
168            Err(e) => return Err(e),
169        };
170        if !has_keys {
171            return self.merge_from_seq2(&other, override_existing, vm);
172        }
173        Ok(())
174    }
175
176    // Used in update and ior.
177    pub fn merge_object(&self, other: PyObjectRef, vm: &VirtualMachine) -> PyResult<()> {
178        self.merge_object_with_override(other, true, vm)
179    }
180
181    pub fn merge_object_if_missing(&self, other: PyObjectRef, vm: &VirtualMachine) -> PyResult<()> {
182        self.merge_object_with_override(other, false, vm)
183    }
184
185    fn add_update_sequence_note(
186        exc: PyBaseExceptionRef,
187        index: usize,
188        vm: &VirtualMachine,
189    ) -> PyBaseExceptionRef {
190        if !exc.fast_isinstance(vm.ctx.exceptions.type_error) {
191            return exc;
192        }
193
194        let note =
195            format!("Cannot convert dictionary update sequence element #{index} to a sequence");
196        match vm.call_method(exc.as_object(), "add_note", (vm.ctx.new_str(note),)) {
197            Ok(_) => exc,
198            Err(note_err) => {
199                note_err.set_context(Some(exc));
200                note_err
201            }
202        }
203    }
204
205    fn update_sequence_pair_from_slice(
206        elements: &[PyObjectRef],
207        index: usize,
208        vm: &VirtualMachine,
209    ) -> PyResult<(PyObjectRef, PyObjectRef)> {
210        let [key, value] = elements else {
211            return Err(vm.new_value_error(format!(
212                "dictionary update sequence element #{index} has length {}; 2 is required",
213                elements.len()
214            )));
215        };
216        Ok((key.clone(), value.clone()))
217    }
218
219    pub(crate) fn update_sequence_pair(
220        element: PyObjectRef,
221        index: usize,
222        vm: &VirtualMachine,
223    ) -> PyResult<(PyObjectRef, PyObjectRef)> {
224        let element = match element.downcast_exact::<PyList>(vm) {
225            Ok(list) => {
226                let elements = list.borrow_vec();
227                return Self::update_sequence_pair_from_slice(&elements, index, vm);
228            }
229            Err(element) => element,
230        };
231        let element = match element.downcast_exact::<PyTuple>(vm) {
232            Ok(tuple) => {
233                return Self::update_sequence_pair_from_slice(tuple.as_slice(), index, vm);
234            }
235            Err(element) => element,
236        };
237
238        let elements = (|| {
239            let elem_iter = PyIter::try_from_object(vm, element).map_err(|exc| {
240                if exc.fast_isinstance(vm.ctx.exceptions.type_error) {
241                    vm.new_type_error("object is not iterable")
242                } else {
243                    exc
244                }
245            })?;
246            elem_iter
247                .into_iter::<PyObjectRef>(vm)
248                .collect::<PyResult<Vec<_>>>()
249        })()
250        .map_err(|exc| Self::add_update_sequence_note(exc, index, vm))?;
251
252        Self::update_sequence_pair_from_slice(&elements, index, vm)
253    }
254
255    pub fn merge_from_seq2(
256        &self,
257        seq2: &PyObject,
258        override_existing: bool,
259        vm: &VirtualMachine,
260    ) -> PyResult<()> {
261        let iter = seq2.get_iter(vm)?;
262        let dict = &self.entries;
263
264        for (index, element) in iter.iter::<PyObjectRef>(vm)?.enumerate() {
265            let (key, value) = Self::update_sequence_pair(element?, index, vm)?;
266
267            if !override_existing && dict.contains(vm, &*key)? {
268                continue;
269            }
270            dict.insert(vm, &*key, value)?;
271        }
272        Ok(())
273    }
274
275    pub(crate) fn merge_dict(
276        &self,
277        dict_other: &Py<Self>,
278        override_existing: bool,
279        vm: &VirtualMachine,
280    ) -> PyResult<()> {
281        let dict = &self.entries;
282        let dict_size = &dict_other.size();
283        let mut position = 0;
284        while let Some((next, key, value, hash)) = dict_other.entries.next_entry_with_hash(position)
285        {
286            position = next;
287            if !override_existing && dict.contains_known_hash(vm, &*key, hash)? {
288                continue;
289            }
290            dict.insert_known_hash(vm, &*key, hash, value)?;
291        }
292        if dict_other.entries.has_changed_size(dict_size) {
293            return Err(vm.new_runtime_error("dict mutated during update"));
294        }
295        Ok(())
296    }
297
298    pub fn is_empty(&self) -> bool {
299        self.entries.len() == 0
300    }
301
302    /// Set item variant which can be called with multiple
303    /// key types, such as str to name a notable one.
304    pub fn inner_setitem<K: DictKey + ?Sized>(
305        &self,
306        key: &K,
307        value: PyObjectRef,
308        vm: &VirtualMachine,
309    ) -> PyResult<()> {
310        self.entries.insert(vm, key, value)
311    }
312
313    pub(crate) fn inner_delitem<K: DictKey + ?Sized>(
314        &self,
315        key: &K,
316        vm: &VirtualMachine,
317    ) -> PyResult<()> {
318        self.entries.delete(vm, key)
319    }
320
321    pub fn get_or_insert(
322        &self,
323        vm: &VirtualMachine,
324        key: &PyObject,
325        default: impl FnOnce() -> PyObjectRef,
326    ) -> PyResult {
327        self.entries.setdefault(vm, key, default)
328    }
329
330    pub fn from_attributes(attrs: PyAttributes, vm: &VirtualMachine) -> PyResult<Self> {
331        let entries = DictContentType::default();
332
333        for (key, value) in attrs {
334            entries.insert(vm, key, value)?;
335        }
336
337        Ok(Self { entries })
338    }
339
340    pub fn contains_key<K: DictKey + ?Sized>(&self, key: &K, vm: &VirtualMachine) -> bool {
341        self.entries.contains(vm, key).unwrap()
342    }
343
344    pub fn size(&self) -> dict_inner::DictSize {
345        self.entries.size()
346    }
347
348    pub fn next_entry(&self, position: usize) -> Option<(usize, PyObjectRef, PyObjectRef)> {
349        self.entries.next_entry(position)
350    }
351
352    pub fn inner_getitem_opt<K: DictKey + ?Sized>(
353        &self,
354        key: &K,
355        vm: &VirtualMachine,
356    ) -> PyResult<Option<PyObjectRef>> {
357        self.entries.get(vm, key)
358    }
359
360    /// Keys of `obj` with their stored hashes, or `None` if it must be iterated
361    /// generically. Only exact dicts and sets qualify, as in CPython's
362    /// `_PyDict_FromKeys`: a subclass may override `__iter__`.
363    fn fromkeys_known_hashes(
364        obj: &PyObject,
365        vm: &VirtualMachine,
366    ) -> Option<Vec<(PyObjectRef, PyHash)>> {
367        if let Some(dict) = obj.downcast_ref_if_exact::<Self>(vm) {
368            Some(dict.entries.keys_with_hashes())
369        } else {
370            set::exact_set_keys_with_hashes(obj, vm)
371        }
372    }
373}
374
375#[derive(FromArgs)]
376struct DictGetArgs {
377    #[pyarg(positional)]
378    key: PyObjectRef,
379    #[pyarg(positional, optional)]
380    default: Option<PyObjectRef>,
381}
382
383#[derive(FromArgs)]
384struct FromKeysArgs {
385    #[pyarg(positional)]
386    iterable: ArgIterable,
387    #[pyarg(positional, optional)]
388    value: Option<PyObjectRef>,
389}
390
391// Python dict methods:
392impl PyDict {
393    pub fn __len__(&self) -> usize {
394        self.entries.len()
395    }
396
397    fn __contains__(&self, key: &PyObject, vm: &VirtualMachine) -> PyResult<bool> {
398        self.entries.contains(vm, key)
399    }
400
401    fn __delitem__(&self, key: &PyObject, vm: &VirtualMachine) -> PyResult<()> {
402        self.inner_delitem(key, vm)
403    }
404
405    fn __setitem__(&self, key: &PyObject, value: PyObjectRef, vm: &VirtualMachine) -> PyResult<()> {
406        self.inner_setitem(key, value, vm)
407    }
408
409    pub(crate) fn setdefault(
410        &self,
411        key: PyObjectRef,
412        default: PyObjectRef,
413        vm: &VirtualMachine,
414    ) -> PyResult {
415        self.entries.setdefault(vm, &*key, || default)
416    }
417
418    fn __or__(&self, other: PyObjectRef, vm: &VirtualMachine) -> PyResult {
419        // Accept dict subclasses that inherit this implementation. Subclasses with
420        // their own reflected slot, such as OrderedDict, take precedence in dispatch.
421        if let Ok(other) = other.downcast::<Self>() {
422            let self_cp = self.copy();
423            self_cp.merge_dict(&other, true, vm)?;
424            return Ok(self_cp.into_pyobject(vm));
425        }
426        Ok(vm.ctx.not_implemented())
427    }
428
429    pub(crate) fn __sizeof__(&self) -> usize {
430        core::mem::size_of::<Self>() + self.entries.sizeof()
431    }
432    pub fn clear(&self) {
433        self.entries.clear()
434    }
435    #[must_use]
436    pub fn copy(&self) -> Self {
437        Self {
438            entries: self.entries.clone(),
439        }
440    }
441    pub(crate) fn update(
442        &self,
443        dict_obj: OptionalArg<PyObjectRef>,
444        kwargs: KwArgs,
445        vm: &VirtualMachine,
446    ) -> PyResult<()> {
447        if let OptionalArg::Present(dict_obj) = dict_obj {
448            self.merge_object(dict_obj, vm)?;
449        }
450        for (key, value) in kwargs {
451            self.entries.insert(vm, &key, value)?;
452        }
453        Ok(())
454    }
455}
456
457#[pyclass(
458    with(
459        Py,
460        PyRef,
461        Constructor,
462        Initializer,
463        Comparable,
464        Iterable,
465        AsSequence,
466        AsNumber,
467        AsMapping,
468        Representable
469    ),
470    flags(BASETYPE, MAPPING, _MATCH_SELF)
471)]
472impl PyDict {}
473
474#[pyclass]
475impl Py<PyDict> {
476    pub(crate) fn inner_cmp(
477        &self,
478        other: &Self,
479        op: PyComparisonOp,
480        item: bool,
481        vm: &VirtualMachine,
482    ) -> PyResult<PyComparisonValue> {
483        if op == PyComparisonOp::Ne {
484            return Self::inner_cmp(self, other, PyComparisonOp::Eq, item, vm)
485                .map(|x| x.map(|eq| !eq));
486        }
487        if !op.eval_ord(self.__len__().cmp(&other.__len__())) {
488            return Ok(PyArithmeticValue::Implemented(false));
489        }
490        let (superset, subset) = if self.__len__() < other.__len__() {
491            (other, self)
492        } else {
493            (self, other)
494        };
495        for (k, v1) in subset {
496            match superset.get_item_opt(&*k, vm)? {
497                Some(v2) => {
498                    if v1.is(&v2) {
499                        continue;
500                    }
501                    if item && !vm.bool_eq(&v1, &v2)? {
502                        return Ok(PyArithmeticValue::Implemented(false));
503                    }
504                }
505                None => {
506                    return Ok(PyArithmeticValue::Implemented(false));
507                }
508            }
509        }
510        Ok(PyArithmeticValue::Implemented(true))
511    }
512
513    #[cfg_attr(feature = "flame-it", flame("PyDictRef"))]
514    #[pymethod(coexist)]
515    fn __getitem__(&self, key: PyObjectRef, vm: &VirtualMachine) -> PyResult {
516        self.inner_getitem(&*key, vm)
517    }
518
519    fn __ror__(&self, other: PyObjectRef, vm: &VirtualMachine) -> PyResult {
520        let other_dict = other.downcast::<PyDict>();
521        if let Ok(other) = other_dict {
522            let other_cp = other.copy();
523            other_cp.merge_dict(self, true, vm)?;
524            return Ok(other_cp.into_pyobject(vm));
525        }
526        Ok(vm.ctx.not_implemented())
527    }
528
529    #[pyclassmethod]
530    fn fromkeys(class: PyTypeRef, args: FromKeysArgs, vm: &VirtualMachine) -> PyResult {
531        let FromKeysArgs { iterable, value } = args;
532        let value = value.unwrap_or_else(|| vm.ctx.none());
533        let d = PyType::call(&class, ().into(), vm)?;
534        match d.downcast_exact::<PyDict>(vm) {
535            Ok(pydict) => {
536                if let Some(keys) = PyDict::fromkeys_known_hashes(iterable.as_object(), vm) {
537                    if class.is(vm.ctx.types.dict_type) {
538                        pydict.entries.reserve_for_empty(keys.len());
539                    }
540                    for (key, hash) in keys {
541                        pydict
542                            .entries
543                            .insert_known_hash(vm, &*key, hash, value.clone())?;
544                    }
545                } else {
546                    for key in iterable.iter(vm)? {
547                        let key = key?;
548                        pydict.__setitem__(&key, value.clone(), vm)?;
549                    }
550                }
551                Ok(pydict.into_pyref().into())
552            }
553            Err(pyobj) => {
554                for key in iterable.iter(vm)? {
555                    pyobj.set_item(&*key?, value.clone(), vm)?;
556                }
557                Ok(pyobj)
558            }
559        }
560    }
561
562    #[pymethod]
563    pub(crate) fn __sizeof__(&self) -> usize {
564        self.payload.__sizeof__()
565    }
566
567    #[pymethod]
568    pub fn clear(&self) {
569        self.payload.clear()
570    }
571
572    #[pymethod]
573    fn get(&self, args: DictGetArgs, vm: &VirtualMachine) -> PyResult {
574        Ok(self
575            .entries
576            .get(vm, &*args.key)?
577            .unwrap_or_else(|| args.default.unwrap_or_else(|| vm.ctx.none())))
578    }
579
580    #[pymethod(name = "setdefault")]
581    fn setdefault_py(&self, args: DictGetArgs, vm: &VirtualMachine) -> PyResult {
582        let default = args.default.unwrap_or_else(|| vm.ctx.none());
583        self.setdefault(args.key, default, vm)
584    }
585
586    #[pymethod]
587    #[must_use]
588    pub fn copy(&self) -> PyDict {
589        self.payload.copy()
590    }
591
592    #[pymethod]
593    pub(crate) fn update(
594        &self,
595        dict_obj: OptionalArg<PyObjectRef>,
596        kwargs: KwArgs,
597        vm: &VirtualMachine,
598    ) -> PyResult<()> {
599        self.payload.update(dict_obj, kwargs, vm)
600    }
601
602    #[pymethod]
603    fn pop(
604        &self,
605        key: PyObjectRef,
606        default: OptionalArg<PyObjectRef>,
607        vm: &VirtualMachine,
608    ) -> PyResult {
609        match self.entries.pop(vm, &*key)? {
610            Some(value) => Ok(value),
611            None => default.ok_or_else(|| vm.new_key_error(key)),
612        }
613    }
614
615    #[pymethod]
616    fn popitem(&self, vm: &VirtualMachine) -> PyResult<(PyObjectRef, PyObjectRef)> {
617        let (key, value) = self.entries.pop_back().ok_or_else(|| {
618            let err_msg = vm
619                .ctx
620                .new_str(ascii!("popitem(): dictionary is empty"))
621                .into();
622            vm.new_key_error(err_msg)
623        })?;
624        Ok((key, value))
625    }
626
627    #[pyclassmethod]
628    fn __class_getitem__(
629        cls: PyTypeRef,
630        object: PyObjectRef,
631        vm: &VirtualMachine,
632    ) -> PyResult<PyGenericAlias> {
633        PyGenericAlias::from_args(cls, object, vm)
634    }
635}
636
637#[pyclass]
638impl PyRef<PyDict> {
639    #[pymethod]
640    const fn keys(self) -> PyDictKeys {
641        PyDictKeys::new(self)
642    }
643
644    #[pymethod]
645    const fn values(self) -> PyDictValues {
646        PyDictValues::new(self)
647    }
648
649    #[pymethod]
650    const fn items(self) -> PyDictItems {
651        PyDictItems::new(self)
652    }
653
654    #[pymethod]
655    fn __reversed__(self) -> PyDictReverseKeyIterator {
656        PyDictReverseKeyIterator::new(self)
657    }
658
659    fn __ior__(self, other: PyObjectRef, vm: &VirtualMachine) -> PyResult<Self> {
660        self.merge_object(other, vm)?;
661        Ok(self)
662    }
663}
664
665impl DefaultConstructor for PyDict {}
666
667impl Initializer for PyDict {
668    type Args = (OptionalArg<PyObjectRef>, KwArgs);
669
670    fn init(zelf: &Py<Self>, (dict_obj, kwargs): Self::Args, vm: &VirtualMachine) -> PyResult<()> {
671        zelf.update(dict_obj, kwargs, vm)
672    }
673}
674
675impl AsMapping for PyDict {
676    fn as_mapping() -> &'static PyMappingMethods {
677        static AS_MAPPING: PyMappingMethods = PyMappingMethods {
678            length: atomic_func!(|mapping, _vm| Ok(PyDict::mapping_downcast(mapping).__len__())),
679            subscript: atomic_func!(|mapping, needle, vm| {
680                PyDict::mapping_downcast(mapping).inner_getitem(needle, vm)
681            }),
682            ass_subscript: atomic_func!(|mapping, needle, value, vm| {
683                let zelf = PyDict::mapping_downcast(mapping);
684                if let Some(value) = value {
685                    zelf.inner_setitem(needle, value, vm)?;
686                    if zelf.as_object().is(vm.builtins.dict().as_object()) {
687                        crate::stdlib::_testinternalcapi::note_builtin_dict();
688                    }
689                    Ok(())
690                } else {
691                    zelf.inner_delitem(needle, vm)
692                }
693            }),
694        };
695        &AS_MAPPING
696    }
697}
698
699impl AsSequence for PyDict {
700    fn as_sequence() -> &'static PySequenceMethods {
701        static AS_SEQUENCE: LazyLock<PySequenceMethods> = LazyLock::new(|| PySequenceMethods {
702            contains: atomic_func!(|seq, target, vm| PyDict::sequence_downcast(seq)
703                .entries
704                .contains(vm, target)),
705            ..PySequenceMethods::NOT_IMPLEMENTED
706        });
707        &AS_SEQUENCE
708    }
709}
710
711impl AsNumber for PyDict {
712    fn as_number() -> &'static PyNumberMethods {
713        static AS_NUMBER: PyNumberMethods = PyNumberMethods {
714            or: Some(|a, b, vm| {
715                if let Some(a) = a.downcast_ref::<PyDict>() {
716                    PyDict::__or__(a, b.to_pyobject(vm), vm)
717                } else {
718                    Ok(vm.ctx.not_implemented())
719                }
720            }),
721            inplace_or: Some(|a, b, vm| {
722                if let Some(a) = a.downcast_ref::<PyDict>() {
723                    a.to_owned()
724                        .__ior__(b.to_pyobject(vm), vm)
725                        .map(|d| d.into())
726                } else {
727                    Ok(vm.ctx.not_implemented())
728                }
729            }),
730            ..PyNumberMethods::NOT_IMPLEMENTED
731        };
732        &AS_NUMBER
733    }
734}
735
736impl Comparable for PyDict {
737    fn cmp(
738        zelf: &Py<Self>,
739        other: &PyObject,
740        op: PyComparisonOp,
741        vm: &VirtualMachine,
742    ) -> PyResult<PyComparisonValue> {
743        op.eq_only(|| {
744            let other = class_or_notimplemented!(Self, other);
745            zelf.inner_cmp(other, PyComparisonOp::Eq, true, vm)
746        })
747    }
748}
749
750impl Iterable for PyDict {
751    fn iter(zelf: PyRef<Self>, vm: &VirtualMachine) -> PyResult {
752        Ok(PyDictKeyIterator::new(zelf).into_pyobject(vm))
753    }
754}
755
756impl Representable for PyDict {
757    #[inline]
758    fn repr(zelf: &Py<Self>, vm: &VirtualMachine) -> PyResult<PyStrRef> {
759        let s = if let Some(_guard) = ReprGuard::enter(vm, zelf.as_object()) {
760            let mut result = Wtf8Buf::from("{");
761            let mut first = true;
762            for (key, value) in zelf {
763                if !first {
764                    result.push_str(", ");
765                }
766                first = false;
767                result.push_wtf8(key.repr(vm)?.as_wtf8());
768                result.push_str(": ");
769                result.push_wtf8(value.repr(vm)?.as_wtf8());
770            }
771            result.push_char('}');
772            vm.ctx.new_str(result)
773        } else {
774            vm.ctx.intern_str("{...}").to_owned()
775        };
776        Ok(s)
777    }
778
779    #[cold]
780    fn repr_str(_zelf: &Py<Self>, _vm: &VirtualMachine) -> PyResult<String> {
781        unreachable!("use repr instead")
782    }
783}
784
785impl Py<PyDict> {
786    #[inline]
787    fn exact_dict(&self, vm: &VirtualMachine) -> bool {
788        self.class().is(vm.ctx.types.dict_type)
789    }
790
791    fn missing_opt<K: DictKey + ?Sized>(
792        &self,
793        key: &K,
794        vm: &VirtualMachine,
795    ) -> PyResult<Option<PyObjectRef>> {
796        vm.get_method(self.to_owned().into(), identifier!(vm, __missing__))
797            .map(|methods| methods?.call((key.to_pyobject(vm),), vm))
798            .transpose()
799    }
800
801    #[inline]
802    fn inner_getitem<K: DictKey + ?Sized>(
803        &self,
804        key: &K,
805        vm: &VirtualMachine,
806    ) -> PyResult<PyObjectRef> {
807        if let Some(value) = self.entries.get(vm, key)? {
808            Ok(value)
809        } else if let Some(value) = self.missing_opt(key, vm)? {
810            Ok(value)
811        } else {
812            Err(vm.new_key_error(key.to_pyobject(vm)))
813        }
814    }
815
816    /// Take a python dictionary and convert it to attributes.
817    ///
818    /// `PyAttributes` is keyed by interned strings, so a key that is not a
819    /// string has nowhere to go. Those keys are left out, and `on_non_string`
820    /// is called once, the first time one turns up, so the caller can decide
821    /// whether that deserves an error or a warning.
822    pub fn to_attributes(
823        &self,
824        vm: &VirtualMachine,
825        on_non_string: impl FnOnce(&VirtualMachine) -> PyResult<()>,
826    ) -> PyResult<PyAttributes> {
827        let mut attrs = PyAttributes::default();
828        let mut on_non_string = Some(on_non_string);
829        for (key, value) in self {
830            let key = match key.downcast_exact::<PyStr>(vm) {
831                Ok(key) => vm.ctx.intern_str(key),
832                // `PyStr`, not the exact type: a `str` subclass names an
833                // attribute just as well, the same way it does as a keyword
834                // argument. Interning drops the subclass, which nothing but
835                // the key object itself can observe.
836                Err(key) => match key.downcast_ref::<PyStr>() {
837                    Some(key) => vm.ctx.intern_str(key.as_wtf8()),
838                    None => {
839                        if let Some(on_non_string) = on_non_string.take() {
840                            on_non_string(vm)?;
841                        }
842                        continue;
843                    }
844                },
845            };
846            attrs.insert(key, value);
847        }
848        Ok(attrs)
849    }
850
851    pub(crate) fn get_item_known_hash(
852        &self,
853        key: &PyObject,
854        hash: crate::common::hash::PyHash,
855        vm: &VirtualMachine,
856    ) -> PyResult<Option<PyObjectRef>> {
857        self.entries.get_known_hash(vm, key, hash)
858    }
859
860    pub(crate) fn set_item_known_hash(
861        &self,
862        key: &PyObject,
863        hash: crate::common::hash::PyHash,
864        value: PyObjectRef,
865        vm: &VirtualMachine,
866    ) -> PyResult<()> {
867        self.entries.insert_known_hash(vm, key, hash, value)
868    }
869
870    pub(crate) fn del_item_known_hash(
871        &self,
872        key: &PyObject,
873        hash: crate::common::hash::PyHash,
874        vm: &VirtualMachine,
875    ) -> PyResult<()> {
876        if self.entries.delete_if_exists_known_hash(vm, key, hash)? {
877            Ok(())
878        } else {
879            Err(vm.new_key_error(key.to_owned()))
880        }
881    }
882
883    pub(crate) fn contains_known_hash(
884        &self,
885        key: &PyObject,
886        hash: crate::common::hash::PyHash,
887        vm: &VirtualMachine,
888    ) -> PyResult<bool> {
889        self.entries.contains_known_hash(vm, key, hash)
890    }
891
892    pub fn get_item_opt<K: DictKey + ?Sized>(
893        &self,
894        key: &K,
895        vm: &VirtualMachine,
896    ) -> PyResult<Option<PyObjectRef>> {
897        if self.exact_dict(vm) {
898            self.entries.get(vm, key)
899            // Match CPython's exact-dict fast path: __missing__ only participates
900            // for dict subclasses through the generic mapping lookup path below.
901        } else {
902            match self.as_object().get_item(key, vm) {
903                Ok(value) => Ok(Some(value)),
904                Err(e) if e.fast_isinstance(vm.ctx.exceptions.key_error) => {
905                    self.missing_opt(key, vm)
906                }
907                Err(e) => Err(e),
908            }
909        }
910    }
911
912    /// Return a cached-entry hint for exact dict fast paths.
913    pub(crate) fn hint_for_key<K: DictKey + ?Sized>(
914        &self,
915        key: &K,
916        vm: &VirtualMachine,
917    ) -> PyResult<Option<u16>> {
918        if self.exact_dict(vm) {
919            self.entries.hint_for_key(vm, key)
920        } else {
921            Ok(None)
922        }
923    }
924
925    /// Read a cached exact-dict entry after validating its key-layout stamp.
926    #[inline]
927    pub(crate) fn get_item_by_index_and_keys_version(
928        &self,
929        version: u16,
930        index: u16,
931    ) -> Option<PyObjectRef> {
932        self.entries
933            .get_index_if_keys_version(u32::from(version), usize::from(index))
934    }
935
936    /// Lookup trying a cached entry index hint first.
937    ///
938    /// When the hint misses but the key is present, also returns a refreshed
939    /// hint (`None` when the hint hit or no hint is representable).
940    pub(crate) fn get_item_opt_refresh_hint<K: DictKey + ?Sized>(
941        &self,
942        key: &K,
943        hint: u16,
944        vm: &VirtualMachine,
945    ) -> PyResult<Option<(PyObjectRef, Option<u16>)>> {
946        if self.exact_dict(vm) {
947            if let Some(value) = self.entries.get_hint(vm, key, usize::from(hint))? {
948                return Ok(Some((value, None)));
949            }
950            self.entries.get_with_hint(vm, key)
951        } else {
952            Ok(self.get_item_opt(key, vm)?.map(|value| (value, None)))
953        }
954    }
955
956    /// Store using a cached entry index hint for the value-replace fast path.
957    ///
958    /// On a hint miss, returns a refreshed hint for the key (`None` when the
959    /// hint hit or no hint is representable).
960    pub(crate) fn set_item_with_hint<K: DictKey + ?Sized>(
961        &self,
962        key: &K,
963        hint: u16,
964        value: PyObjectRef,
965        vm: &VirtualMachine,
966    ) -> PyResult<Option<u16>> {
967        if self.exact_dict(vm) {
968            self.entries
969                .insert_with_hint(vm, key, usize::from(hint), value)
970        } else {
971            self.as_object().set_item(key, value, vm)?;
972            Ok(None)
973        }
974    }
975
976    /// Current keys-version stamp of the underlying storage (0 if unset).
977    pub(crate) fn keys_version(&self) -> u32 {
978        self.entries.keys_version()
979    }
980
981    pub(crate) fn module_attr_cache(
982        &self,
983        name: &super::PyStrInterned,
984        vm: &VirtualMachine,
985    ) -> Option<(u32, u16)> {
986        self.exact_dict(vm)
987            .then(|| self.entries.module_attr_cache(name, vm))
988            .flatten()
989    }
990
991    #[inline]
992    pub(crate) fn get_cached_module_attr(
993        &self,
994        name: &super::PyStrInterned,
995        version: usize,
996        index: usize,
997        vm: &VirtualMachine,
998    ) -> Option<PyObjectRef> {
999        self.exact_dict(vm)
1000            .then(|| {
1001                self.entries
1002                    .get_cached_module_attr(name, version, index, vm)
1003            })
1004            .flatten()
1005    }
1006
1007    /// Current keys-version stamp, assigning one if none is set.
1008    ///
1009    /// Returns 0 for dict subclasses: their lookup can be overridden, so a
1010    /// key-set attestation on the raw storage must never be cached for them.
1011    pub(crate) fn assign_keys_version(&self, vm: &VirtualMachine) -> u32 {
1012        if self.exact_dict(vm) {
1013            self.entries.assign_keys_version()
1014        } else {
1015            0
1016        }
1017    }
1018
1019    pub fn get_item<K: DictKey + ?Sized>(&self, key: &K, vm: &VirtualMachine) -> PyResult {
1020        if self.exact_dict(vm) {
1021            self.inner_getitem(key, vm)
1022        } else {
1023            self.as_object().get_item(key, vm)
1024        }
1025    }
1026
1027    pub fn set_item<K: DictKey + ?Sized>(
1028        &self,
1029        key: &K,
1030        value: PyObjectRef,
1031        vm: &VirtualMachine,
1032    ) -> PyResult<()> {
1033        if self.exact_dict(vm) {
1034            self.inner_setitem(key, value, vm)?;
1035            if self.as_object().is(vm.builtins.dict().as_object()) {
1036                crate::stdlib::_testinternalcapi::note_builtin_dict();
1037            }
1038            Ok(())
1039        } else {
1040            self.as_object().set_item(key, value, vm)
1041        }
1042    }
1043
1044    pub fn del_item<K: DictKey + ?Sized>(&self, key: &K, vm: &VirtualMachine) -> PyResult<()> {
1045        if self.exact_dict(vm) {
1046            self.inner_delitem(key, vm)
1047        } else {
1048            self.as_object().del_item(key, vm)
1049        }
1050    }
1051
1052    pub fn pop_item<K: DictKey + ?Sized>(
1053        &self,
1054        key: &K,
1055        vm: &VirtualMachine,
1056    ) -> PyResult<Option<PyObjectRef>> {
1057        if self.exact_dict(vm) {
1058            self.entries.remove_if_exists(vm, key)
1059        } else {
1060            let value = self.as_object().get_item(key, vm)?;
1061            self.as_object().del_item(key, vm)?;
1062            Ok(Some(value))
1063        }
1064    }
1065
1066    pub fn get_chain<K: DictKey + ?Sized>(
1067        &self,
1068        other: &Self,
1069        key: &K,
1070        vm: &VirtualMachine,
1071    ) -> PyResult<Option<PyObjectRef>> {
1072        let self_exact = self.exact_dict(vm);
1073        let other_exact = other.exact_dict(vm);
1074        if self_exact && other_exact {
1075            // SAFETY: exact_dict checks passed
1076            let self_exact = unsafe { PyExact::ref_unchecked(self) };
1077            let other_exact = unsafe { PyExact::ref_unchecked(other) };
1078            self_exact.get_chain_exact(other_exact, key, vm)
1079        } else if let Some(value) = self.get_item_opt(key, vm)? {
1080            Ok(Some(value))
1081        } else {
1082            other.get_item_opt(key, vm)
1083        }
1084    }
1085}
1086
1087impl PyExact<PyDict> {
1088    /// Look up `key` in `self`, falling back to `other`.
1089    /// Both dicts must be exact `dict` types (enforced by `PyExact`).
1090    pub(crate) fn get_chain_exact<K: DictKey + ?Sized>(
1091        &self,
1092        other: &Self,
1093        key: &K,
1094        vm: &VirtualMachine,
1095    ) -> PyResult<Option<PyObjectRef>> {
1096        debug_assert!(self.class().is(vm.ctx.types.dict_type));
1097        debug_assert!(other.class().is(vm.ctx.types.dict_type));
1098        self.entries.get_chain(&other.entries, vm, key)
1099    }
1100}
1101
1102// Implement IntoIterator so that we can easily iterate dictionaries from rust code.
1103impl IntoIterator for PyDictRef {
1104    type Item = (PyObjectRef, PyObjectRef);
1105    type IntoIter = DictIntoIter;
1106
1107    fn into_iter(self) -> Self::IntoIter {
1108        DictIntoIter::new(self)
1109    }
1110}
1111
1112impl<'a> IntoIterator for &'a PyDictRef {
1113    type Item = (PyObjectRef, PyObjectRef);
1114    type IntoIter = DictIter<'a>;
1115
1116    fn into_iter(self) -> Self::IntoIter {
1117        DictIter::new(self)
1118    }
1119}
1120
1121impl<'a> IntoIterator for &'a Py<PyDict> {
1122    type Item = (PyObjectRef, PyObjectRef);
1123    type IntoIter = DictIter<'a>;
1124
1125    fn into_iter(self) -> Self::IntoIter {
1126        DictIter::new(self)
1127    }
1128}
1129
1130pub struct DictIntoIter {
1131    dict: PyDictRef,
1132    position: usize,
1133}
1134
1135impl DictIntoIter {
1136    pub const fn new(dict: PyDictRef) -> Self {
1137        Self { dict, position: 0 }
1138    }
1139}
1140
1141impl Iterator for DictIntoIter {
1142    type Item = (PyObjectRef, PyObjectRef);
1143
1144    fn next(&mut self) -> Option<Self::Item> {
1145        let (position, key, value) = self.dict.entries.next_entry(self.position)?;
1146        self.position = position;
1147        Some((key, value))
1148    }
1149
1150    fn size_hint(&self) -> (usize, Option<usize>) {
1151        let l = self.len();
1152        (l, Some(l))
1153    }
1154}
1155
1156impl ExactSizeIterator for DictIntoIter {
1157    fn len(&self) -> usize {
1158        self.dict.entries.len_from_entry_index(self.position)
1159    }
1160}
1161
1162pub struct DictIter<'a> {
1163    dict: &'a Py<PyDict>,
1164    position: usize,
1165}
1166
1167impl<'a> DictIter<'a> {
1168    pub const fn new(dict: &'a Py<PyDict>) -> Self {
1169        DictIter { dict, position: 0 }
1170    }
1171}
1172
1173impl Iterator for DictIter<'_> {
1174    type Item = (PyObjectRef, PyObjectRef);
1175
1176    fn next(&mut self) -> Option<Self::Item> {
1177        let (position, key, value) = self.dict.entries.next_entry(self.position)?;
1178        self.position = position;
1179        Some((key, value))
1180    }
1181
1182    fn size_hint(&self) -> (usize, Option<usize>) {
1183        let l = self.len();
1184        (l, Some(l))
1185    }
1186}
1187
1188impl ExactSizeIterator for DictIter<'_> {
1189    fn len(&self) -> usize {
1190        self.dict.entries.len_from_entry_index(self.position)
1191    }
1192}
1193
1194#[pyclass]
1195trait DictView: PyPayload + PyClassDef + Iterable + Representable {
1196    type ReverseIter: PyPayload + core::fmt::Debug;
1197
1198    fn dict(&self) -> &Py<PyDict>;
1199    fn item(vm: &VirtualMachine, key: PyObjectRef, value: PyObjectRef) -> PyObjectRef;
1200
1201    fn __len__(&self) -> usize {
1202        self.dict().__len__()
1203    }
1204
1205    #[pymethod]
1206    fn __reversed__(zelf: &Py<Self>) -> Self::ReverseIter;
1207}
1208
1209macro_rules! dict_view {
1210    (
1211        $name: ident,
1212        $iter_name: ident,
1213        $reverse_iter_name: ident,
1214        $class: ident,
1215        $iter_class: ident,
1216        $reverse_iter_class: ident,
1217        $class_name: literal,
1218        $iter_class_name: literal,
1219        $reverse_iter_class_name: literal,
1220        $unhashable: literal,
1221        $project_fn: expr,
1222        $result_fn: expr
1223    ) => {
1224        #[pyclass(module = false, name = $class_name, unhashable = $unhashable)]
1225        #[derive(Debug)]
1226        pub(crate) struct $name {
1227            pub(crate) dict: PyDictRef,
1228        }
1229
1230        impl $name {
1231            pub(crate) const fn new(dict: PyDictRef) -> Self {
1232                $name { dict }
1233            }
1234        }
1235
1236        impl DictView for $name {
1237            type ReverseIter = $reverse_iter_name;
1238
1239            fn dict(&self) -> &Py<PyDict> {
1240                &self.dict
1241            }
1242
1243            fn item(vm: &VirtualMachine, key: PyObjectRef, value: PyObjectRef) -> PyObjectRef {
1244                $result_fn(vm, $project_fn(&key, &value))
1245            }
1246
1247            fn __reversed__(zelf: &Py<Self>) -> Self::ReverseIter {
1248                $reverse_iter_name::new(zelf.dict.clone())
1249            }
1250        }
1251
1252        impl Iterable for $name {
1253            fn iter(zelf: PyRef<Self>, vm: &VirtualMachine) -> PyResult {
1254                Ok($iter_name::new(zelf.dict.clone()).into_pyobject(vm))
1255            }
1256        }
1257
1258        impl PyPayload for $name {
1259            fn class(ctx: &Context) -> &'static Py<PyType> {
1260                ctx.types.$class
1261            }
1262        }
1263
1264        impl Representable for $name {
1265            #[inline]
1266            fn repr(zelf: &Py<Self>, vm: &VirtualMachine) -> PyResult<PyStrRef> {
1267                let s = if let Some(_guard) = ReprGuard::enter(vm, zelf.as_object()) {
1268                    let mut result = Wtf8Buf::from(format!("{}([", Self::NAME));
1269                    let mut first = true;
1270                    for (key, value) in zelf.dict().clone() {
1271                        if !first {
1272                            result.push_str(", ");
1273                        }
1274                        first = false;
1275                        result.push_wtf8(Self::item(vm, key, value).repr(vm)?.as_wtf8());
1276                    }
1277                    result.push_str("])");
1278                    vm.ctx.new_str(result)
1279                } else {
1280                    vm.ctx.intern_str("{...}").to_owned()
1281                };
1282                Ok(s)
1283            }
1284
1285            #[cold]
1286            fn repr_str(_zelf: &Py<Self>, _vm: &VirtualMachine) -> PyResult<String> {
1287                unreachable!("use repr instead")
1288            }
1289        }
1290
1291        #[pyclass(module = false, name = $iter_class_name)]
1292        #[derive(Debug)]
1293        pub(crate) struct $iter_name {
1294            pub(crate) size: dict_inner::DictSize,
1295            /// Whether the dict was found to have changed, which
1296            /// `dictiter_iternextkey()` records by writing a size no dict can
1297            /// have. Sticky: what it makes the iterator answer, it answers
1298            /// from then on.
1299            changed: PyAtomic<bool>,
1300            pub(crate) internal: PyMutex<PositionIterInternal<PyDictRef>>,
1301        }
1302
1303        impl PyPayload for $iter_name {
1304            #[inline]
1305            fn class(ctx: &Context) -> &'static Py<PyType> {
1306                ctx.types.$iter_class
1307            }
1308        }
1309
1310        impl $iter_name {
1311            fn new(dict: PyDictRef) -> Self {
1312                $iter_name {
1313                    size: dict.size(),
1314                    changed: Radium::new(false),
1315                    internal: PyMutex::new(PositionIterInternal::new(dict, 0)),
1316                }
1317            }
1318        }
1319
1320        #[pyclass(flags(DISALLOW_INSTANTIATION), with(IterNext, Iterable))]
1321        impl Py<$iter_name> {
1322            #[pymethod]
1323            fn __length_hint__(&self) -> usize {
1324                // `dictiter_len()` answers for a dict it can no longer walk
1325                // with nothing, comparing the size it captured against the
1326                // dict's own every time it is asked.
1327                if self.changed.load(Ordering::Relaxed) {
1328                    return 0;
1329                }
1330                self.internal.lock().length_hint(|dict| {
1331                    if dict.size() == self.size {
1332                        self.size.entries_size
1333                    } else {
1334                        0
1335                    }
1336                })
1337            }
1338
1339            #[pymethod]
1340            fn __reduce__(&self, vm: &VirtualMachine) -> PyResult<PyTupleRef> {
1341                let iter = builtins_iter(vm)?;
1342                let internal = self.internal.lock();
1343                let entries = match &internal.status {
1344                    IterStatus::Active(dict) => {
1345                        let mut position = internal.position;
1346                        let mut entries = Vec::new();
1347                        while let Some((next_position, key, value)) =
1348                            dict.entries.next_entry(position)
1349                        {
1350                            entries.push(($result_fn)(vm, ($project_fn)(&key, &value)));
1351                            position = next_position;
1352                        }
1353                        entries
1354                    }
1355                    IterStatus::Exhausted => vec![],
1356                };
1357                Ok(vm.new_tuple((iter, (vm.ctx.new_list(entries),))))
1358            }
1359        }
1360
1361        impl SelfIter for $iter_name {}
1362
1363        impl IterNext for $iter_name {
1364            fn next(zelf: &Py<Self>, vm: &VirtualMachine) -> PyResult<PyIterReturn> {
1365                locked_step(&zelf.internal, |internal| {
1366                    let IterStatus::Active(dict) = &internal.status else {
1367                        return (Ok(PyIterReturn::StopIteration(None)), None);
1368                    };
1369                    let mutated =
1370                        || vm.new_runtime_error("dictionary changed size during iteration");
1371                    if zelf.changed.load(Ordering::Relaxed) {
1372                        // The dict is not looked at again once it has been
1373                        // found to change: an iterator that has raised keeps
1374                        // raising.
1375                        return (Err(mutated()), None);
1376                    }
1377                    let entry =
1378                        dict.entries
1379                            .next_entry_checked(internal.position, &zelf.size, $project_fn);
1380                    match entry {
1381                        Err(dict_inner::DictChanged) => {
1382                            zelf.changed.store(true, Ordering::Relaxed);
1383                            (Err(mutated()), None)
1384                        }
1385                        Ok(Some((position, item))) => {
1386                            internal.position = position;
1387                            (Ok(PyIterReturn::Return(($result_fn)(vm, item))), None)
1388                        }
1389                        Ok(None) => (Ok(PyIterReturn::StopIteration(None)), internal.exhaust()),
1390                    }
1391                })
1392            }
1393        }
1394
1395        #[pyclass(module = false, name = $reverse_iter_class_name)]
1396        #[derive(Debug)]
1397        pub(crate) struct $reverse_iter_name {
1398            pub(crate) size: dict_inner::DictSize,
1399            /// As in `$iter_name`.
1400            changed: PyAtomic<bool>,
1401            internal: PyMutex<PositionIterInternal<PyDictRef>>,
1402        }
1403
1404        impl PyPayload for $reverse_iter_name {
1405            #[inline]
1406            fn class(ctx: &Context) -> &'static Py<PyType> {
1407                ctx.types.$reverse_iter_class
1408            }
1409        }
1410
1411        impl $reverse_iter_name {
1412            fn new(dict: PyDictRef) -> Self {
1413                let size = dict.size();
1414                let position = size.entries_size.saturating_sub(1);
1415                $reverse_iter_name {
1416                    size,
1417                    changed: Radium::new(false),
1418                    internal: PyMutex::new(PositionIterInternal::new(dict, position)),
1419                }
1420            }
1421        }
1422
1423        #[pyclass(flags(DISALLOW_INSTANTIATION), with(IterNext, Iterable))]
1424        impl Py<$reverse_iter_name> {
1425            #[pymethod]
1426            fn __reduce__(&self, vm: &VirtualMachine) -> PyResult<PyTupleRef> {
1427                let iter = builtins_iter(vm)?;
1428                let internal = self.internal.lock();
1429                let entries = match &internal.status {
1430                    IterStatus::Active(dict) => {
1431                        let mut position = internal.position;
1432                        let mut entries = Vec::new();
1433                        while let Some((found_index, key, value)) =
1434                            dict.entries.prev_entry(position)
1435                        {
1436                            entries.push(($result_fn)(vm, ($project_fn)(&key, &value)));
1437                            if found_index == 0 {
1438                                break;
1439                            }
1440                            position = found_index - 1;
1441                        }
1442                        entries
1443                    }
1444                    IterStatus::Exhausted => vec![],
1445                };
1446                Ok(vm.new_tuple((iter, (vm.ctx.new_list(entries),))))
1447            }
1448
1449            #[pymethod]
1450            fn __length_hint__(&self) -> usize {
1451                // As in `$iter_name`.
1452                if self.changed.load(Ordering::Relaxed) {
1453                    return 0;
1454                }
1455                let internal = self.internal.lock();
1456                match &internal.status {
1457                    IterStatus::Active(dict) if dict.size() == self.size => {
1458                        internal.rev_length_hint(|_| self.size.entries_size)
1459                    }
1460                    _ => 0,
1461                }
1462            }
1463        }
1464
1465        impl SelfIter for $reverse_iter_name {}
1466
1467        impl IterNext for $reverse_iter_name {
1468            fn next(zelf: &Py<Self>, vm: &VirtualMachine) -> PyResult<PyIterReturn> {
1469                locked_step(&zelf.internal, |internal| {
1470                    let IterStatus::Active(dict) = &internal.status else {
1471                        return (Ok(PyIterReturn::StopIteration(None)), None);
1472                    };
1473                    let mutated =
1474                        || vm.new_runtime_error("dictionary changed size during iteration");
1475                    if zelf.changed.load(Ordering::Relaxed) {
1476                        // The dict is not looked at again once it has been
1477                        // found to change: an iterator that has raised keeps
1478                        // raising.
1479                        return (Err(mutated()), None);
1480                    }
1481                    let entry =
1482                        dict.entries
1483                            .prev_entry_checked(internal.position, &zelf.size, $project_fn);
1484                    match entry {
1485                        Err(dict_inner::DictChanged) => {
1486                            zelf.changed.store(true, Ordering::Relaxed);
1487                            (Err(mutated()), None)
1488                        }
1489                        Ok(Some((found_index, item))) => {
1490                            let released = if found_index == 0 {
1491                                internal.exhaust()
1492                            } else {
1493                                internal.position = found_index - 1;
1494                                None
1495                            };
1496                            (Ok(PyIterReturn::Return(($result_fn)(vm, item))), released)
1497                        }
1498                        Ok(None) => (Ok(PyIterReturn::StopIteration(None)), internal.exhaust()),
1499                    }
1500                })
1501            }
1502        }
1503    };
1504}
1505
1506dict_view! {
1507    PyDictKeys,
1508    PyDictKeyIterator,
1509    PyDictReverseKeyIterator,
1510    dict_keys_type,
1511    dict_keyiterator_type,
1512    dict_reversekeyiterator_type,
1513    "dict_keys",
1514    "dict_keyiterator",
1515    "dict_reversekeyiterator",
1516    true,
1517    |key: &PyObject, _value| key.to_owned(),
1518    |_vm: &VirtualMachine, key: PyObjectRef| key
1519}
1520
1521dict_view! {
1522    PyDictValues,
1523    PyDictValueIterator,
1524    PyDictReverseValueIterator,
1525    dict_values_type,
1526    dict_valueiterator_type,
1527    dict_reversevalueiterator_type,
1528    "dict_values",
1529    "dict_valueiterator",
1530    "dict_reversevalueiterator",
1531    false,
1532    |_key: &PyObject, value: &PyObjectRef| value.clone(),
1533    |_vm: &VirtualMachine, value: PyObjectRef| value
1534}
1535
1536dict_view! {
1537    PyDictItems,
1538    PyDictItemIterator,
1539    PyDictReverseItemIterator,
1540    dict_items_type,
1541    dict_itemiterator_type,
1542    dict_reverseitemiterator_type,
1543    "dict_items",
1544    "dict_itemiterator",
1545    "dict_reverseitemiterator",
1546    true,
1547    |key: &PyObject, value: &PyObjectRef| (key.to_owned(), value.clone()),
1548    // Builds a tuple, so it runs after the dict's read guard is released.
1549    |vm: &VirtualMachine, (key, value): (PyObjectRef, PyObjectRef)|
1550        vm.new_tuple((key, value)).into()
1551}
1552
1553fn is_set_or_dict_view(obj: &PyObject) -> bool {
1554    obj.downcast_ref::<PySet>().is_some()
1555        || obj.downcast_ref::<PyFrozenSet>().is_some()
1556        || obj.downcast_ref::<PyDictKeys>().is_some()
1557        || obj.downcast_ref::<PyDictItems>().is_some()
1558}
1559
1560// Set operations defined on set-like views of the dictionary.
1561#[pyclass]
1562trait ViewSetOps: DictView {
1563    fn cmp(
1564        zelf: &Py<Self>,
1565        other: &PyObject,
1566        op: PyComparisonOp,
1567        vm: &VirtualMachine,
1568    ) -> PyResult<PyComparisonValue> {
1569        if let Some(dictview) = other.downcast_ref::<Self>() {
1570            return zelf.dict().inner_cmp(
1571                dictview.dict(),
1572                op,
1573                !zelf.class().is(vm.ctx.types.dict_keys_type),
1574                vm,
1575            );
1576        }
1577        if !is_set_or_dict_view(other) {
1578            return Ok(PyComparisonValue::NotImplemented);
1579        }
1580        if op == PyComparisonOp::Ne {
1581            return ViewSetOps::cmp(zelf, other, PyComparisonOp::Eq, vm)
1582                .map(|result| result.map(|equal| !equal));
1583        }
1584        if !op.eval_ord(zelf.__len__().cmp(&other.length(vm)?)) {
1585            return Ok(PyComparisonValue::Implemented(false));
1586        }
1587        let (subset, superset) = if matches!(op, PyComparisonOp::Gt | PyComparisonOp::Ge) {
1588            (other, zelf.as_object())
1589        } else {
1590            (zelf.as_object(), other)
1591        };
1592        let subset = ArgIterable::<PyObjectRef>::try_from_object(vm, subset.to_owned())?;
1593        for item in subset.iter(vm)? {
1594            let item = item?;
1595            if !superset.sequence_unchecked().contains(&item, vm)? {
1596                return Ok(PyComparisonValue::Implemented(false));
1597            }
1598        }
1599        Ok(PyComparisonValue::Implemented(true))
1600    }
1601
1602    #[pymethod]
1603    fn isdisjoint(zelf: PyRef<Self>, object: ArgIterable, vm: &VirtualMachine) -> PyResult<bool> {
1604        if zelf.is(object.as_object()) {
1605            return Ok(zelf.__len__() == 0);
1606        }
1607        let other_is_larger = if is_set_or_dict_view(object.as_object()) {
1608            let len = zelf.__len__();
1609            object.as_object().length(vm)? > len
1610        } else {
1611            false
1612        };
1613        let (container, iterable) = if other_is_larger {
1614            (object.as_object(), zelf.as_object())
1615        } else {
1616            (zelf.as_object(), object.as_object())
1617        };
1618        let iterable = ArgIterable::<PyObjectRef>::try_from_object(vm, iterable.to_owned())?;
1619        for item in iterable.iter(vm)? {
1620            let item = item?;
1621            if container.sequence_unchecked().contains(&item, vm)? {
1622                return Ok(false);
1623            }
1624        }
1625        Ok(true)
1626    }
1627}
1628
1629impl ViewSetOps for PyDictKeys {}
1630
1631#[pyclass(
1632    flags(DISALLOW_INSTANTIATION),
1633    with(
1634        DictView,
1635        Comparable,
1636        Iterable,
1637        ViewSetOps,
1638        AsSequence,
1639        AsNumber,
1640        Representable
1641    )
1642)]
1643impl PyDictKeys {
1644    fn __contains__(zelf: &PyObject, key: &PyObject, vm: &VirtualMachine) -> PyResult<bool> {
1645        zelf.sequence_unchecked().contains(key, vm)
1646    }
1647
1648    #[pygetset]
1649    fn mapping(zelf: PyRef<Self>) -> PyMappingProxy {
1650        PyMappingProxy::from(zelf.dict().to_owned())
1651    }
1652}
1653
1654impl Comparable for PyDictKeys {
1655    fn cmp(
1656        zelf: &Py<Self>,
1657        other: &PyObject,
1658        op: PyComparisonOp,
1659        vm: &VirtualMachine,
1660    ) -> PyResult<PyComparisonValue> {
1661        ViewSetOps::cmp(zelf, other, op, vm)
1662    }
1663}
1664
1665impl AsSequence for PyDictKeys {
1666    fn as_sequence() -> &'static PySequenceMethods {
1667        static AS_SEQUENCE: LazyLock<PySequenceMethods> = LazyLock::new(|| PySequenceMethods {
1668            length: atomic_func!(|seq, _vm| Ok(PyDictKeys::sequence_downcast(seq).__len__())),
1669            contains: atomic_func!(|seq, target, vm| {
1670                PyDictKeys::sequence_downcast(seq)
1671                    .dict
1672                    .entries
1673                    .contains(vm, target)
1674            }),
1675            ..PySequenceMethods::NOT_IMPLEMENTED
1676        });
1677        &AS_SEQUENCE
1678    }
1679}
1680
1681impl AsNumber for PyDictKeys {
1682    fn as_number() -> &'static PyNumberMethods {
1683        static AS_NUMBER: PyNumberMethods = PyNumberMethods {
1684            subtract: Some(set_inner_number_subtract),
1685            and: Some(set_inner_number_and),
1686            xor: Some(set_inner_number_xor),
1687            or: Some(set_inner_number_or),
1688            ..PyNumberMethods::NOT_IMPLEMENTED
1689        };
1690        &AS_NUMBER
1691    }
1692}
1693
1694impl ViewSetOps for PyDictItems {}
1695#[pyclass(
1696    flags(DISALLOW_INSTANTIATION),
1697    with(
1698        DictView,
1699        Comparable,
1700        Iterable,
1701        ViewSetOps,
1702        AsSequence,
1703        AsNumber,
1704        Representable
1705    )
1706)]
1707impl PyDictItems {
1708    fn __contains__(zelf: &PyObject, needle: &PyObject, vm: &VirtualMachine) -> PyResult<bool> {
1709        zelf.sequence_unchecked().contains(needle, vm)
1710    }
1711    #[pygetset]
1712    fn mapping(zelf: PyRef<Self>) -> PyMappingProxy {
1713        PyMappingProxy::from(zelf.dict().to_owned())
1714    }
1715}
1716
1717impl Comparable for PyDictItems {
1718    fn cmp(
1719        zelf: &Py<Self>,
1720        other: &PyObject,
1721        op: PyComparisonOp,
1722        vm: &VirtualMachine,
1723    ) -> PyResult<PyComparisonValue> {
1724        ViewSetOps::cmp(zelf, other, op, vm)
1725    }
1726}
1727
1728impl AsSequence for PyDictItems {
1729    fn as_sequence() -> &'static PySequenceMethods {
1730        static AS_SEQUENCE: LazyLock<PySequenceMethods> = LazyLock::new(|| PySequenceMethods {
1731            length: atomic_func!(|seq, _vm| Ok(PyDictItems::sequence_downcast(seq).__len__())),
1732            contains: atomic_func!(|seq, target, vm| {
1733                let needle: &Py<PyTuple> = match target.downcast_ref() {
1734                    Some(needle) => needle,
1735                    None => return Ok(false),
1736                };
1737                if needle.as_slice().len() != 2 {
1738                    return Ok(false);
1739                }
1740
1741                let zelf = PyDictItems::sequence_downcast(seq);
1742                let key = &needle.as_slice()[0];
1743                let Some(found) = zelf.dict().inner_getitem_opt(&**key, vm)? else {
1744                    return Ok(false);
1745                };
1746                let value = &needle.as_slice()[1];
1747                vm.identical_or_equal(&found, value)
1748            }),
1749            ..PySequenceMethods::NOT_IMPLEMENTED
1750        });
1751        &AS_SEQUENCE
1752    }
1753}
1754
1755impl AsNumber for PyDictItems {
1756    fn as_number() -> &'static PyNumberMethods {
1757        static AS_NUMBER: PyNumberMethods = PyNumberMethods {
1758            subtract: Some(set_inner_number_subtract),
1759            and: Some(set_inner_number_and),
1760            xor: Some(dict_item_view_number_xor),
1761            or: Some(set_inner_number_or),
1762            ..PyNumberMethods::NOT_IMPLEMENTED
1763        };
1764        &AS_NUMBER
1765    }
1766}
1767
1768#[pyclass(
1769    flags(DISALLOW_INSTANTIATION),
1770    with(DictView, Iterable, AsSequence, Representable)
1771)]
1772impl PyDictValues {
1773    #[pygetset]
1774    fn mapping(zelf: PyRef<Self>) -> PyMappingProxy {
1775        PyMappingProxy::from(zelf.dict().to_owned())
1776    }
1777}
1778
1779impl AsSequence for PyDictValues {
1780    fn as_sequence() -> &'static PySequenceMethods {
1781        static AS_SEQUENCE: LazyLock<PySequenceMethods> = LazyLock::new(|| PySequenceMethods {
1782            length: atomic_func!(|seq, _vm| Ok(PyDictValues::sequence_downcast(seq).__len__())),
1783            ..PySequenceMethods::NOT_IMPLEMENTED
1784        });
1785        &AS_SEQUENCE
1786    }
1787}
1788
1789fn set_inner_number_op<F>(a: &PyObject, b: &PyObject, f: F, vm: &VirtualMachine) -> PyResult
1790where
1791    F: FnOnce(PySetInner, ArgIterable) -> PyResult<PySetInner>,
1792{
1793    let a = PySetInner::from_iter(
1794        ArgIterable::try_from_object(vm, a.to_owned())?.iter(vm)?,
1795        vm,
1796    )?;
1797    let b = ArgIterable::try_from_object(vm, b.to_owned())?;
1798    Ok(PySet { inner: f(a, b)? }.into_pyobject(vm))
1799}
1800
1801pub(crate) fn set_inner_number_subtract(
1802    a: &PyObject,
1803    b: &PyObject,
1804    vm: &VirtualMachine,
1805) -> PyResult {
1806    set_inner_number_op(a, b, |a, b| a.difference(b, vm), vm)
1807}
1808
1809pub(crate) fn set_inner_number_and(a: &PyObject, b: &PyObject, vm: &VirtualMachine) -> PyResult {
1810    let a_is_view =
1811        a.downcast_ref::<PyDictKeys>().is_some() || a.downcast_ref::<PyDictItems>().is_some();
1812    let (view, other) = if a_is_view { (a, b) } else { (b, a) };
1813    set_view_number_and(view, other, vm)
1814}
1815
1816pub(crate) fn set_view_number_and(
1817    view: &PyObject,
1818    other: &PyObject,
1819    vm: &VirtualMachine,
1820) -> PyResult {
1821    let other = ArgIterable::<PyObjectRef>::try_from_object(vm, other.to_owned())?;
1822    let result = PySet::default();
1823    for item in other.iter(vm)? {
1824        let item = item?;
1825        if view.sequence_unchecked().contains(&item, vm)? {
1826            result.add(item, vm)?;
1827        }
1828    }
1829    Ok(result.into_pyobject(vm))
1830}
1831
1832pub(crate) fn set_inner_number_xor(a: &PyObject, b: &PyObject, vm: &VirtualMachine) -> PyResult {
1833    set_inner_number_op(a, b, |a, b| a.symmetric_difference(b, vm), vm)
1834}
1835
1836pub(crate) fn set_item_view_number_xor(
1837    a: &PyObject,
1838    b: &PyObject,
1839    both_are_item_views: bool,
1840    vm: &VirtualMachine,
1841) -> PyResult {
1842    if !both_are_item_views {
1843        return set_inner_number_xor(a, b, vm);
1844    }
1845    let result = PySet::default();
1846    let a_iterable = ArgIterable::<PyObjectRef>::try_from_object(vm, a.to_owned())?;
1847    for item in a_iterable.iter(vm)? {
1848        let item = item?;
1849        if !b.sequence_unchecked().contains(&item, vm)? {
1850            result.add(item, vm)?;
1851        }
1852    }
1853    let b_iterable = ArgIterable::<PyObjectRef>::try_from_object(vm, b.to_owned())?;
1854    for item in b_iterable.iter(vm)? {
1855        let item = item?;
1856        if !a.sequence_unchecked().contains(&item, vm)? {
1857            result.add(item, vm)?;
1858        }
1859    }
1860    Ok(result.into_pyobject(vm))
1861}
1862
1863fn dict_item_view_number_xor(a: &PyObject, b: &PyObject, vm: &VirtualMachine) -> PyResult {
1864    let is_native_item_view = |obj: &PyObject| {
1865        obj.downcast_ref::<PyDictItems>().is_some()
1866            || obj.downcast_ref::<PyOrderedDictItems>().is_some()
1867    };
1868    set_item_view_number_xor(a, b, is_native_item_view(a) && is_native_item_view(b), vm)
1869}
1870
1871pub(crate) fn set_inner_number_or(a: &PyObject, b: &PyObject, vm: &VirtualMachine) -> PyResult {
1872    set_inner_number_op(a, b, |a, b| a.union(b, vm), vm)
1873}
1874
1875fn vectorcall_dict(
1876    zelf_obj: &PyObject,
1877    args: Vec<PyObjectRef>,
1878    nargs: usize,
1879    kwnames: Option<&[PyObjectRef]>,
1880    vm: &VirtualMachine,
1881) -> PyResult {
1882    let zelf: &Py<PyType> = zelf_obj.downcast_ref().unwrap();
1883    let obj = PyDict::default().into_ref_with_type(vm, zelf.to_owned())?;
1884    let func_args = FuncArgs::from_vectorcall_owned(args, nargs, kwnames);
1885    PyDict::slot_init(obj.as_object(), func_args, vm)?;
1886    Ok(obj.into())
1887}
1888
1889pub(crate) fn init(context: &'static Context) {
1890    PyDict::extend_class(context, context.types.dict_type);
1891    context
1892        .types
1893        .dict_type
1894        .slots
1895        .vectorcall
1896        .store(Some(vectorcall_dict));
1897    PyDictKeys::extend_class(context, context.types.dict_keys_type);
1898    PyDictKeyIterator::extend_class(context, context.types.dict_keyiterator_type);
1899    PyDictReverseKeyIterator::extend_class(context, context.types.dict_reversekeyiterator_type);
1900    PyDictValues::extend_class(context, context.types.dict_values_type);
1901    PyDictValueIterator::extend_class(context, context.types.dict_valueiterator_type);
1902    PyDictReverseValueIterator::extend_class(context, context.types.dict_reversevalueiterator_type);
1903    PyDictItems::extend_class(context, context.types.dict_items_type);
1904    PyDictItemIterator::extend_class(context, context.types.dict_itemiterator_type);
1905    PyDictReverseItemIterator::extend_class(context, context.types.dict_reverseitemiterator_type);
1906}