Skip to main content

rustpython_vm/types/
slot.rs

1use crate::common::lock::{
2    PyMappedRwLockReadGuard, PyMappedRwLockWriteGuard, PyRwLockReadGuard, PyRwLockWriteGuard,
3};
4use crate::{
5    AsObject, Py, PyObject, PyObjectRef, PyPayload, PyRef, PyResult, VirtualMachine,
6    builtins::{PyInt, PyStr, PyStrInterned, PyType, PyTypeRef},
7    bytecode::ComparisonOperator,
8    common::hash::{PyHash, fix_sentinel, hash_bigint},
9    convert::ToPyObject,
10    function::{
11        Callee, Either, FromArgs, FuncArgs, ItemDoc, PyComparisonValue, PyMethodDef, PySetterValue,
12    },
13    protocol::{
14        BufferFlags, PyBuffer, PyIterReturn, PyMapping, PyMappingMethods, PyMappingSlots, PyNumber,
15        PyNumberMethods, PyNumberSlots, PySequence, PySequenceMethods, PySequenceSlots,
16    },
17    types::slot_defs::{SlotAccessor, find_slot_defs_by_name},
18    vm::Context,
19};
20use core::{any::Any, any::TypeId, borrow::Borrow, cmp::Ordering, ops::Deref};
21use crossbeam_utils::atomic::AtomicCell;
22use num_traits::{Signed, ToPrimitive};
23use rustpython_common::wtf8::Wtf8Buf;
24
25/// Predicate used by [`PyTypeSlots::del_needed`].
26pub type DelNeededFunc = fn(&PyObject) -> bool;
27
28/// Type-erased storage for extension module data attached to heap types.
29pub struct TypeDataSlot {
30    // PyObject_GetTypeData
31    type_id: TypeId,
32    data: Box<dyn Any + Send + Sync>,
33}
34
35impl TypeDataSlot {
36    /// Create a new type data slot with the given data.
37    pub fn new<T: Any + Send + Sync + 'static>(data: T) -> Self {
38        Self {
39            type_id: TypeId::of::<T>(),
40            data: Box::new(data),
41        }
42    }
43
44    /// Get a reference to the data if the type matches.
45    #[must_use]
46    pub fn get<T: Any + 'static>(&self) -> Option<&T> {
47        if self.type_id == TypeId::of::<T>() {
48            self.data.downcast_ref()
49        } else {
50            None
51        }
52    }
53
54    /// Get a mutable reference to the data if the type matches.
55    pub fn get_mut<T: Any + 'static>(&mut self) -> Option<&mut T> {
56        if self.type_id == TypeId::of::<T>() {
57            self.data.downcast_mut()
58        } else {
59            None
60        }
61    }
62}
63
64/// Read guard for type data access, using mapped guard for zero-cost deref.
65pub struct TypeDataRef<'a, T: 'static> {
66    guard: PyMappedRwLockReadGuard<'a, T>,
67}
68
69impl<'a, T: Any + 'static> TypeDataRef<'a, T> {
70    /// Try to create a TypeDataRef from a read guard.
71    /// Returns None if the slot is empty or contains a different type.
72    #[must_use]
73    pub fn try_new(guard: PyRwLockReadGuard<'a, Option<TypeDataSlot>>) -> Option<Self> {
74        PyRwLockReadGuard::try_map(guard, |opt| opt.as_ref().and_then(|slot| slot.get::<T>()))
75            .ok()
76            .map(|guard| Self { guard })
77    }
78}
79
80impl<T: Any + 'static> core::ops::Deref for TypeDataRef<'_, T> {
81    type Target = T;
82
83    fn deref(&self) -> &Self::Target {
84        &self.guard
85    }
86}
87
88/// Write guard for type data access, using mapped guard for zero-cost deref.
89pub struct TypeDataRefMut<'a, T: 'static> {
90    guard: PyMappedRwLockWriteGuard<'a, T>,
91}
92
93impl<'a, T: Any + 'static> TypeDataRefMut<'a, T> {
94    /// Try to create a TypeDataRefMut from a write guard.
95    /// Returns None if the slot is empty or contains a different type.
96    #[must_use]
97    pub fn try_new(guard: PyRwLockWriteGuard<'a, Option<TypeDataSlot>>) -> Option<Self> {
98        PyRwLockWriteGuard::try_map(guard, |opt| {
99            opt.as_mut().and_then(|slot| slot.get_mut::<T>())
100        })
101        .ok()
102        .map(|guard| Self { guard })
103    }
104}
105
106impl<T: Any + 'static> core::ops::Deref for TypeDataRefMut<'_, T> {
107    type Target = T;
108
109    fn deref(&self) -> &Self::Target {
110        &self.guard
111    }
112}
113
114impl<T: Any + 'static> core::ops::DerefMut for TypeDataRefMut<'_, T> {
115    fn deref_mut(&mut self) -> &mut Self::Target {
116        &mut self.guard
117    }
118}
119
120#[macro_export]
121macro_rules! atomic_func {
122    ($x:expr) => {
123        Some($x)
124    };
125}
126
127// The corresponding field in CPython is `tp_` prefixed.
128// e.g. name -> tp_name
129#[derive(Default)]
130#[non_exhaustive]
131pub struct PyTypeSlots {
132    /// # Safety
133    /// For static types, always safe.
134    /// For heap types, `__name__` must alive
135    pub(crate) name: &'static str, // tp_name with <module>.<class> for print, not class name
136
137    /// Full `tp_basicsize`: object header plus payload. `0` before type
138    /// creation means "inherit the base size".
139    pub basicsize: usize,
140    pub itemsize: usize, // tp_itemsize
141
142    // Methods to implement standard operations
143
144    // Method suites for standard classes
145    pub as_number: PyNumberSlots,
146    pub as_sequence: PySequenceSlots,
147    pub as_mapping: PyMappingSlots,
148
149    // More standard operations (here for binary compatibility)
150    pub hash: AtomicCell<Option<HashFunc>>,
151    pub call: AtomicCell<Option<GenericMethod>>,
152    pub vectorcall: AtomicCell<Option<VectorCallFunc>>,
153    pub str: AtomicCell<Option<StringifyFunc>>,
154    pub repr: AtomicCell<Option<StringifyFunc>>,
155    pub getattro: AtomicCell<Option<GetattroFunc>>,
156    pub setattro: AtomicCell<Option<SetattroFunc>>,
157
158    // Functions to access object as input/output buffer
159    pub as_buffer: AtomicCell<Option<AsBufferFunc>>,
160    /// bf_releasebuffer: releasing an export of this type is observable, so the
161    /// type exposes `__release_buffer__`.
162    pub has_release_buffer: AtomicCell<bool>,
163    /// True when a Python-level `__release_buffer__` must be invoked on release.
164    pub python_release_buffer: AtomicCell<bool>,
165
166    // Assigned meaning in release 2.1
167    // rich comparisons
168    pub richcompare: AtomicCell<Option<RichCompareFunc>>,
169
170    // Iterators
171    pub iter: AtomicCell<Option<IterFunc>>,
172    pub iternext: AtomicCell<Option<IterNextFunc>>,
173
174    pub methods: &'static [PyMethodDef],
175
176    // Flags to define presence of optional/expanded features.
177    // One atomic word: runtime code sets and clears bits in place.
178    pub flags: AtomicPyTypeFlags,
179
180    // tp_doc
181    pub doc: ItemDoc,
182
183    // Strong reference on a heap type, borrowed reference on a static type
184    // tp_base
185    // tp_dict
186    pub descr_get: AtomicCell<Option<DescrGetFunc>>,
187    pub descr_set: AtomicCell<Option<DescrSetFunc>>,
188    // tp_dictoffset
189    pub init: AtomicCell<Option<InitFunc>>,
190    // tp_alloc
191    pub alloc: AtomicCell<Option<AllocFunc>>,
192    pub new: AtomicCell<Option<NewFunc>>,
193    // tp_free
194    // tp_is_gc
195    // tp_bases
196    // tp_mro
197    // tp_cache
198    // tp_subclasses
199    // tp_weaklist
200    pub del: AtomicCell<Option<DelFunc>>,
201
202    /// Optional fast, VM-free predicate checked before `del` is invoked via
203    /// `drop_slow_inner`/`try_call_finalizer`. Those call sites otherwise pay
204    /// for attaching to a VM (`with_vm`) unconditionally whenever a type has
205    /// a `del` slot at all, even when the type's own `del` is a documented
206    /// no-op for the object's current state (e.g. an already-exhausted
207    /// generator or coroutine). Returning `false` skips the `del` call
208    /// entirely, avoiding that VM lookup; `None` (the default) preserves the
209    /// prior behavior of always calling `del`.
210    pub del_needed: AtomicCell<Option<DelNeededFunc>>,
211
212    // The count of tp_members.
213    pub member_count: usize,
214}
215
216impl PyTypeSlots {
217    #[must_use]
218    pub fn new(name: &'static str, flags: PyTypeFlags) -> Self {
219        Self {
220            name,
221            flags: AtomicPyTypeFlags::from_plain(flags),
222            ..Default::default()
223        }
224    }
225
226    #[must_use]
227    pub fn heap_default() -> Self {
228        /*
229        Self {
230            init: AtomicCell::new(Some(init_wrapper)),
231            ..Default::default()
232        }
233        */
234        Self::default()
235    }
236}
237
238impl core::fmt::Debug for PyTypeSlots {
239    fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
240        f.write_str("PyTypeSlots")
241    }
242}
243
244bitflagset::bitflagset! {
245    #[derive(Copy, Clone, PartialEq, Eq)]
246    pub struct PyTypeFlags(u64) {
247        const INLINE_VALUES = 2;
248        const MANAGED_WEAKREF = 3;
249        const MANAGED_DICT = 4;
250        const SEQUENCE = 5;
251        const MAPPING = 6;
252        const DISALLOW_INSTANTIATION = 7;
253        const IMMUTABLETYPE = 8;
254        const HEAPTYPE = 9;
255        const BASETYPE = 10;
256        const METHOD_DESCRIPTOR = 17;
257        const IS_ABSTRACT = 20;
258        // Built-in types that match the subject itself in pattern matching
259        // (bool, int, float, str, bytes, bytearray, list, tuple, dict, set, frozenset).
260        // This is not a stable API.
261        const _MATCH_SELF = 22;
262        const HAS_DICT = 40;
263        const HAS_WEAKREF = 41;
264        #[cfg(debug_assertions)]
265        const _CREATED_WITH_FLAGS = 63;
266    }
267}
268
269bitflagset::atomic_bitflagset!(
270    pub struct AtomicPyTypeFlags(core::sync::atomic::AtomicU64) on PyTypeFlags
271);
272
273impl PyTypeFlags {
274    pub const HEAP_TYPE: Self = Self::from_slice(&[Self::HEAPTYPE, Self::BASETYPE]);
275
276    pub const HEAP_TYPE_WITH_DICT: Self =
277        Self::from_bits_retain(Self::HEAP_TYPE.bits() | (1u64 << (Self::HAS_DICT as u32)));
278
279    pub const HEAP_TYPE_DICT_IMMUTABLE: Self = Self::from_bits_retain(
280        Self::HEAP_TYPE_WITH_DICT.bits() | (1u64 << (Self::IMMUTABLETYPE as u32)),
281    );
282
283    pub const COLLECTION: Self = Self::from_slice(&[Self::SEQUENCE, Self::MAPPING]);
284}
285
286impl AtomicPyTypeFlags {
287    #[must_use]
288    pub fn load(&self) -> PyTypeFlags {
289        // Callers read `.bits()` (for example `PyType_GetFlags`), so bits
290        // outside the named set must survive.
291        PyTypeFlags::from_bits_retain(self.as_bits().load(core::sync::atomic::Ordering::Acquire))
292    }
293
294    #[must_use]
295    pub fn has_feature(&self, flag: u8) -> bool {
296        self.contains(&flag)
297    }
298
299    /// Replace `mask` bits with `value & mask`.
300    pub fn replace_masked(&self, mask: PyTypeFlags, value: PyTypeFlags) {
301        let mask_bits = mask.bits();
302        let value_bits = (value & mask).bits();
303        let _ = self.as_bits().try_update(
304            core::sync::atomic::Ordering::AcqRel,
305            core::sync::atomic::Ordering::Acquire,
306            |old| Some((old & !mask_bits) | value_bits),
307        );
308    }
309
310    /// Clear every bit set in `mask`.
311    pub fn remove_masked(&self, mask: PyTypeFlags) {
312        let _ = self
313            .as_bits()
314            .fetch_and(!mask.bits(), core::sync::atomic::Ordering::AcqRel);
315    }
316}
317
318impl core::ops::BitOrAssign<PyTypeFlags> for AtomicPyTypeFlags {
319    fn bitor_assign(&mut self, rhs: PyTypeFlags) {
320        self.as_bits()
321            .fetch_or(rhs.bits(), core::sync::atomic::Ordering::AcqRel);
322    }
323}
324
325// `__flags__` is `PyMemberFlags::ATOMIC`, so the load reads this field as an `AtomicU64`.
326// That relies on `AtomicPyTypeFlags` being `repr(transparent)` over `AtomicU64`.
327const _: () = assert!(
328    core::mem::size_of::<AtomicPyTypeFlags>()
329        == core::mem::size_of::<core::sync::atomic::AtomicU64>()
330        && core::mem::align_of::<AtomicPyTypeFlags>()
331            == core::mem::align_of::<core::sync::atomic::AtomicU64>()
332);
333
334impl crate::builtins::descriptor::MemberLayout for AtomicPyTypeFlags {
335    const KIND: crate::builtins::descriptor::MemberKind = {
336        if core::mem::size_of::<core::ffi::c_ulong>() == 8 {
337            crate::builtins::descriptor::MemberKind::ULong
338        } else {
339            crate::builtins::descriptor::MemberKind::ULongLong
340        }
341    };
342    const ATOMIC: bool = true;
343}
344
345#[cfg(test)]
346mod tests {
347    #[cfg(debug_assertions)]
348    #[test]
349    fn created_with_flags_bit_roundtrips() {
350        use super::{AtomicPyTypeFlags, PyTypeFlags};
351
352        let flags = PyTypeFlags::from_element(PyTypeFlags::_CREATED_WITH_FLAGS);
353        assert!(flags.contains(&PyTypeFlags::_CREATED_WITH_FLAGS));
354        let atomic = AtomicPyTypeFlags::from_plain(flags);
355        assert!(atomic.contains(&PyTypeFlags::_CREATED_WITH_FLAGS));
356    }
357}
358
359pub(crate) type GenericMethod = fn(&PyObject, FuncArgs, &VirtualMachine) -> PyResult;
360/// Vectorcall function pointer (PEP 590).
361/// args: owned positional args followed by kwarg values.
362/// nargs: number of positional args (self prepended by caller if needed).
363/// kwnames: keyword argument names (last kwnames.len() entries in args are kwarg values).
364pub(crate) type VectorCallFunc = fn(
365    &PyObject,              // callable
366    Vec<PyObjectRef>,       // owned args (positional + kwarg values)
367    usize,                  // nargs (positional count)
368    Option<&[PyObjectRef]>, // kwnames (keyword argument names)
369    &VirtualMachine,
370) -> PyResult;
371pub(crate) type HashFunc = fn(&PyObject, &VirtualMachine) -> PyResult<PyHash>;
372// CallFunc = GenericMethod
373pub(crate) type StringifyFunc = fn(&PyObject, &VirtualMachine) -> PyResult<PyRef<PyStr>>;
374pub(crate) type GetattroFunc = fn(&PyObject, &Py<PyStr>, &VirtualMachine) -> PyResult;
375pub(crate) type SetattroFunc =
376    fn(&PyObject, &Py<PyStr>, PySetterValue, &VirtualMachine) -> PyResult<()>;
377/// bf_getbuffer
378pub(crate) type AsBufferFunc = fn(&PyObject, BufferFlags, &VirtualMachine) -> PyResult<PyBuffer>;
379pub(crate) type RichCompareFunc = fn(
380    &PyObject,
381    &PyObject,
382    PyComparisonOp,
383    &VirtualMachine,
384) -> PyResult<Either<PyObjectRef, PyComparisonValue>>;
385pub(crate) type IterFunc = fn(PyObjectRef, &VirtualMachine) -> PyResult;
386pub(crate) type IterNextFunc = fn(&PyObject, &VirtualMachine) -> PyResult<PyIterReturn>;
387pub(crate) type DescrGetFunc =
388    fn(&PyObject, Option<&PyObject>, Option<&PyObject>, &VirtualMachine) -> PyResult;
389pub(crate) type DescrSetFunc =
390    fn(&PyObject, PyObjectRef, PySetterValue, &VirtualMachine) -> PyResult<()>;
391pub(crate) type AllocFunc = fn(PyTypeRef, usize, &VirtualMachine) -> PyResult;
392pub(crate) type NewFunc = fn(PyTypeRef, FuncArgs, &VirtualMachine) -> PyResult;
393pub(crate) type InitFunc = fn(&PyObject, FuncArgs, &VirtualMachine) -> PyResult<()>;
394pub(crate) type DelFunc = fn(&PyObject, &VirtualMachine) -> PyResult<()>;
395
396// Sequence sub-slot function types
397pub(crate) type SeqLenFunc = fn(PySequence<'_>, &VirtualMachine) -> PyResult<usize>;
398pub(crate) type SeqConcatFunc = fn(PySequence<'_>, &PyObject, &VirtualMachine) -> PyResult;
399pub(crate) type SeqRepeatFunc = fn(PySequence<'_>, isize, &VirtualMachine) -> PyResult;
400pub(crate) type SeqItemFunc = fn(PySequence<'_>, isize, &VirtualMachine) -> PyResult;
401pub(crate) type SeqAssItemFunc =
402    fn(PySequence<'_>, isize, Option<PyObjectRef>, &VirtualMachine) -> PyResult<()>;
403pub(crate) type SeqContainsFunc = fn(PySequence<'_>, &PyObject, &VirtualMachine) -> PyResult<bool>;
404
405// Mapping sub-slot function types
406pub(crate) type MapLenFunc = fn(PyMapping<'_>, &VirtualMachine) -> PyResult<usize>;
407pub(crate) type MapSubscriptFunc = fn(PyMapping<'_>, &PyObject, &VirtualMachine) -> PyResult;
408pub(crate) type MapAssSubscriptFunc =
409    fn(PyMapping<'_>, &PyObject, Option<PyObjectRef>, &VirtualMachine) -> PyResult<()>;
410
411// slot_bf_getbuffer
412pub(crate) fn python_as_buffer(
413    obj: &PyObject,
414    flags: BufferFlags,
415    vm: &VirtualMachine,
416) -> PyResult<PyBuffer> {
417    crate::builtins::memory::buffer_from_python_getbuffer(obj, flags, vm)
418}
419
420// slot_sq_length
421pub(crate) fn len_wrapper(obj: &PyObject, vm: &VirtualMachine) -> PyResult<usize> {
422    let ret = vm.call_special_method(obj, identifier!(vm, __len__), ())?;
423    // `__len__` may return any object with `__index__`, not only `int`.
424    let len = ret.try_index(vm)?;
425    let len = len.as_bigint();
426    if len.is_negative() {
427        return Err(vm.new_value_error("__len__() should return >= 0"));
428    }
429    let len = len
430        .to_isize()
431        .ok_or_else(|| vm.new_overflow_error("cannot fit 'int' into an index-sized integer"))?;
432    Ok(len as usize)
433}
434
435pub(crate) fn contains_wrapper(
436    obj: &PyObject,
437    needle: &PyObject,
438    vm: &VirtualMachine,
439) -> PyResult<bool> {
440    let ret = vm.call_special_method(obj, identifier!(vm, __contains__), (needle,))?;
441    ret.try_to_bool(vm)
442}
443
444macro_rules! number_unary_op_wrapper {
445    ($name:ident) => {
446        |a, vm| vm.call_special_method(a.deref(), identifier!(vm, $name), ())
447    };
448}
449macro_rules! number_binary_op_wrapper {
450    ($name:ident) => {
451        |a, b, vm| vm.call_special_method(a, identifier!(vm, $name), (b.to_owned(),))
452    };
453}
454macro_rules! number_binary_right_op_wrapper {
455    ($name:ident) => {
456        |a, b, vm| vm.call_special_method(b, identifier!(vm, $name), (a.to_owned(),))
457    };
458}
459macro_rules! number_ternary_op_wrapper {
460    ($name:ident) => {
461        |a, b, c, vm: &VirtualMachine| {
462            if vm.is_none(c) {
463                vm.call_special_method(a, identifier!(vm, $name), (b.to_owned(),))
464            } else {
465                vm.call_special_method(a, identifier!(vm, $name), (b.to_owned(), c.to_owned()))
466            }
467        }
468    };
469}
470macro_rules! number_ternary_right_op_wrapper {
471    ($name:ident) => {
472        |a, b, c, vm: &VirtualMachine| {
473            if vm.is_none(c) {
474                vm.call_special_method(b, identifier!(vm, $name), (a.to_owned(),))
475            } else {
476                vm.call_special_method(b, identifier!(vm, $name), (a.to_owned(), c.to_owned()))
477            }
478        }
479    };
480}
481fn getitem_wrapper<K: ToPyObject>(obj: &PyObject, needle: K, vm: &VirtualMachine) -> PyResult {
482    vm.call_special_method(obj, identifier!(vm, __getitem__), (needle,))
483}
484
485fn setitem_wrapper<K: ToPyObject>(
486    obj: &PyObject,
487    needle: K,
488    value: Option<PyObjectRef>,
489    vm: &VirtualMachine,
490) -> PyResult<()> {
491    match value {
492        Some(value) => vm.call_special_method(obj, identifier!(vm, __setitem__), (needle, value)),
493        None => vm.call_special_method(obj, identifier!(vm, __delitem__), (needle,)),
494    }
495    .map(drop)
496}
497
498#[inline(never)]
499fn mapping_setitem_wrapper(
500    mapping: PyMapping<'_>,
501    key: &PyObject,
502    value: Option<PyObjectRef>,
503    vm: &VirtualMachine,
504) -> PyResult<()> {
505    setitem_wrapper(mapping.obj, key, value, vm)
506}
507
508#[inline(never)]
509fn mapping_getitem_wrapper(
510    mapping: PyMapping<'_>,
511    key: &PyObject,
512    vm: &VirtualMachine,
513) -> PyResult {
514    getitem_wrapper(mapping.obj, key, vm)
515}
516
517#[inline(never)]
518fn mapping_len_wrapper(mapping: PyMapping<'_>, vm: &VirtualMachine) -> PyResult<usize> {
519    len_wrapper(mapping.obj, vm)
520}
521
522#[inline(never)]
523fn sequence_len_wrapper(seq: PySequence<'_>, vm: &VirtualMachine) -> PyResult<usize> {
524    len_wrapper(seq.obj, vm)
525}
526
527#[inline(never)]
528fn sequence_getitem_wrapper(seq: PySequence<'_>, i: isize, vm: &VirtualMachine) -> PyResult {
529    getitem_wrapper(seq.obj, i, vm)
530}
531
532#[inline(never)]
533fn sequence_setitem_wrapper(
534    seq: PySequence<'_>,
535    i: isize,
536    value: Option<PyObjectRef>,
537    vm: &VirtualMachine,
538) -> PyResult<()> {
539    setitem_wrapper(seq.obj, i, value, vm)
540}
541
542#[inline(never)]
543fn sequence_contains_wrapper(
544    seq: PySequence<'_>,
545    needle: &PyObject,
546    vm: &VirtualMachine,
547) -> PyResult<bool> {
548    contains_wrapper(seq.obj, needle, vm)
549}
550
551fn repr_wrapper(zelf: &PyObject, vm: &VirtualMachine) -> PyResult<PyRef<PyStr>> {
552    let ret = vm.call_special_method(zelf, identifier!(vm, __repr__), ())?;
553    ret.downcast::<PyStr>().map_err(|obj| {
554        vm.new_type_error(format!(
555            "__repr__ returned non-string (type {})",
556            obj.class()
557        ))
558    })
559}
560
561fn str_wrapper(zelf: &PyObject, vm: &VirtualMachine) -> PyResult<PyRef<PyStr>> {
562    let ret = vm.call_special_method(zelf, identifier!(vm, __str__), ())?;
563    ret.downcast::<PyStr>().map_err(|obj| {
564        vm.new_type_error(format!(
565            "__str__ returned non-string (type {})",
566            obj.class()
567        ))
568    })
569}
570
571fn hash_wrapper(zelf: &PyObject, vm: &VirtualMachine) -> PyResult<PyHash> {
572    let hash_obj = vm.call_special_method(zelf, identifier!(vm, __hash__), ())?;
573    let py_int = hash_obj
574        .downcast_ref::<PyInt>()
575        .ok_or_else(|| vm.new_type_error("__hash__ method should return an integer"))?;
576    let big_int = py_int.as_bigint();
577    let hash = big_int
578        .to_i64()
579        .map_or_else(|| hash_bigint(big_int), fix_sentinel);
580    Ok(hash)
581}
582
583/// Marks a type as unhashable. Similar to PyObject_HashNotImplemented in CPython
584pub fn hash_not_implemented(zelf: &PyObject, vm: &VirtualMachine) -> PyResult<PyHash> {
585    Err(vm.new_type_error(format!("unhashable type: '{}'", zelf.class().slot_name())))
586}
587
588fn call_wrapper(zelf: &PyObject, args: FuncArgs, vm: &VirtualMachine) -> PyResult {
589    // `__call__` can name the object being called, and dispatching it pushes no
590    // Python frame, so nothing else counts the nesting.
591    vm.with_recursion("while calling a Python object", || {
592        vm.call_special_method(zelf, identifier!(vm, __call__), args)
593    })
594}
595
596// slot_tp_getattr_hook in CPython
597fn getattro_wrapper(zelf: &PyObject, name: &Py<PyStr>, vm: &VirtualMachine) -> PyResult {
598    let __getattribute__ = identifier!(vm, __getattribute__);
599    let __getattr__ = identifier!(vm, __getattr__);
600    let class = zelf.class();
601    // Keep the original hook if attribute lookup replaces or removes it.
602    let Some(getattr) = class.get_attr(__getattr__) else {
603        return vm.call_special_method(zelf, __getattribute__, (name.to_owned(),));
604    };
605    let generic = class
606        .get_attr(__getattribute__)
607        .is_some_and(|getattribute| {
608            vm.ctx
609                .types
610                .object_type
611                .get_attr(__getattribute__)
612                .is_some_and(|generic| getattribute.is(&generic))
613        });
614    // A generic miss does not need an AttributeError before calling the hook.
615    let result = if generic {
616        zelf.generic_getattr_opt(name, None, vm)
617    } else {
618        vm.call_special_method(zelf, __getattribute__, (name.to_owned(),))
619            .map(Some)
620    };
621    match result {
622        Ok(Some(value)) => return Ok(value),
623        Ok(None) => {}
624        Err(e) if e.fast_isinstance(vm.ctx.exceptions.attribute_error) => {}
625        Err(e) => return Err(e),
626    }
627    // Bind only after lookup, using the instance's current class.
628    if getattr
629        .class()
630        .slots
631        .flags
632        .has_feature(PyTypeFlags::METHOD_DESCRIPTOR)
633    {
634        getattr.call((zelf.to_owned(), name.to_owned()), vm)
635    } else {
636        let bound = vm.call_get_descriptor(&getattr, zelf).transpose()?;
637        bound
638            .as_ref()
639            .unwrap_or(&getattr)
640            .call((name.to_owned(),), vm)
641    }
642}
643
644/// hackcheck: reject object.__setattr__/__delattr__ applied to a type
645/// whose C-level tp_setattro is not the wrapped function.
646pub(crate) fn hackcheck_setattro(
647    obj: &PyObject,
648    func: SetattroFunc,
649    what: &str,
650    vm: &VirtualMachine,
651) -> PyResult<()> {
652    let Some(_typ) = obj.downcast_ref::<PyType>() else {
653        return Ok(());
654    };
655    let obj_cls = obj.class();
656    let obj_setattro = obj_cls.slots.setattro.load();
657
658    let mut defining = obj_cls.to_owned();
659    {
660        let mro = obj_cls.mro.read();
661        for base in mro.iter().rev() {
662            let base_setattro = base.slots.setattro.load();
663            if is_slot_tp_setattro(base_setattro) {
664                continue;
665            } else if setattro_eq(base_setattro, obj_setattro) {
666                defining = base.clone();
667                break;
668            }
669        }
670    }
671
672    let mut base = Some(defining);
673    while let Some(b) = base {
674        let b_setattro = b.slots.setattro.load();
675        if setattro_eq(b_setattro, Some(func)) {
676            return Ok(());
677        } else if !is_slot_tp_setattro(b_setattro) {
678            return Err(vm.new_type_error(format!(
679                "can't apply this {what} to {} object",
680                obj_cls.slot_name()
681            )));
682        }
683        base = b.base.load_owned();
684    }
685    Ok(())
686}
687
688fn is_slot_tp_setattro(f: Option<SetattroFunc>) -> bool {
689    f.is_some_and(|f| fn_addr(f) == fn_addr(setattro_wrapper as SetattroFunc))
690}
691
692fn setattro_eq(a: Option<SetattroFunc>, b: Option<SetattroFunc>) -> bool {
693    match (a, b) {
694        (Some(a), Some(b)) => fn_addr(a) == fn_addr(b),
695        (None, None) => true,
696        _ => false,
697    }
698}
699
700fn setattro_wrapper(
701    zelf: &PyObject,
702    name: &Py<PyStr>,
703    value: PySetterValue,
704    vm: &VirtualMachine,
705) -> PyResult<()> {
706    let name = name.to_owned();
707    match value {
708        PySetterValue::Assign(value) => {
709            vm.call_special_method(zelf, identifier!(vm, __setattr__), (name, value))?;
710        }
711        PySetterValue::Delete => {
712            vm.call_special_method(zelf, identifier!(vm, __delattr__), (name,))?;
713        }
714    };
715    Ok(())
716}
717
718pub(crate) fn richcompare_wrapper(
719    zelf: &PyObject,
720    other: &PyObject,
721    op: PyComparisonOp,
722    vm: &VirtualMachine,
723) -> PyResult<Either<PyObjectRef, PyComparisonValue>> {
724    // slot_tp_richcompare / _PyObject_MaybeCallSpecialOneArg: a missing
725    // method, or a data descriptor whose __get__ raises AttributeError,
726    // is NotImplemented rather than an error.
727    match vm.get_special_method(zelf, op.method_name(&vm.ctx))? {
728        Some(meth) => meth.invoke((other.to_owned(),), vm).map(Either::A),
729        None => Ok(Either::B(PyComparisonValue::NotImplemented)),
730    }
731}
732
733fn iter_wrapper(zelf: PyObjectRef, vm: &VirtualMachine) -> PyResult {
734    // slot_tp_iter: if __iter__ is None, the type is explicitly not iterable
735    let cls = zelf.class();
736    let iter_attr = cls.get_attr(identifier!(vm, __iter__));
737    match iter_attr {
738        Some(attr) if vm.is_none(&attr) => {
739            Err(vm.new_type_error(format!("'{}' object is not iterable", cls.slot_name())))
740        }
741        _ => vm.call_special_method(&zelf, identifier!(vm, __iter__), ()),
742    }
743}
744
745fn bool_wrapper(num: PyNumber<'_>, vm: &VirtualMachine) -> PyResult<bool> {
746    let result = vm.call_special_method(num.obj, identifier!(vm, __bool__), ())?;
747    // __bool__ must return exactly bool, not int subclass
748    if !result.class().is(vm.ctx.types.bool_type) {
749        return Err(vm.new_type_error(format!(
750            "__bool__ should return bool, returned {}",
751            result.class().name()
752        )));
753    }
754    Ok(crate::builtins::bool_::get_value(&result))
755}
756
757/// PyObject_SelfIter in CPython
758#[expect(clippy::unnecessary_wraps, reason = "Needs to comply with a signature")]
759const fn self_iter(zelf: PyObjectRef, _vm: &VirtualMachine) -> PyResult {
760    Ok(zelf)
761}
762
763fn iternext_wrapper(zelf: &PyObject, vm: &VirtualMachine) -> PyResult<PyIterReturn> {
764    PyIterReturn::from_pyresult(
765        vm.call_special_method(zelf, identifier!(vm, __next__), ()),
766        vm,
767    )
768}
769
770fn descr_get_wrapper(
771    zelf: &PyObject,
772    obj: Option<&PyObject>,
773    cls: Option<&PyObject>,
774    vm: &VirtualMachine,
775) -> PyResult {
776    // A descriptor whose `__get__` is the descriptor itself resolves it by
777    // fetching `__get__` again, and none of that pushes a Python frame.
778    vm.with_recursion("while calling a Python object", || {
779        vm.call_special_method(
780            zelf,
781            identifier!(vm, __get__),
782            (obj.map(PyObject::to_owned), cls.map(PyObject::to_owned)),
783        )
784    })
785}
786
787fn descr_set_wrapper(
788    zelf: &PyObject,
789    obj: PyObjectRef,
790    value: PySetterValue,
791    vm: &VirtualMachine,
792) -> PyResult<()> {
793    match value {
794        PySetterValue::Assign(val) => {
795            vm.call_special_method(zelf, identifier!(vm, __set__), (obj, val))
796        }
797        PySetterValue::Delete => vm.call_special_method(zelf, identifier!(vm, __delete__), (obj,)),
798    }
799    .map(drop)
800}
801
802fn init_wrapper(obj: &PyObject, args: FuncArgs, vm: &VirtualMachine) -> PyResult<()> {
803    let res = vm.call_special_method(obj, identifier!(vm, __init__), args)?;
804    if !vm.is_none(&res) {
805        return Err(vm.new_type_error(format!(
806            "__init__() should return None, not '{:.200}'",
807            res.class().name()
808        )));
809    }
810    Ok(())
811}
812
813pub(crate) fn new_wrapper(cls: PyTypeRef, mut args: FuncArgs, vm: &VirtualMachine) -> PyResult {
814    let new = cls.as_object().get_attr(identifier!(vm, __new__), vm)?;
815    args.prepend_arg(cls.into());
816    new.call(args, vm)
817}
818
819fn del_wrapper(zelf: &PyObject, vm: &VirtualMachine) -> PyResult<()> {
820    vm.call_special_method(zelf, identifier!(vm, __del__), ())?;
821    Ok(())
822}
823
824/// Result of looking up a slot function in MRO.
825enum SlotLookupResult<T> {
826    /// Found a native slot function from a wrapper_descriptor.
827    NativeSlot(T),
828    /// Found a Python-level method (not a native slot).
829    /// The caller should use the wrapper function.
830    PythonMethod,
831    /// No method with this name found in MRO at all.
832    /// The caller should inherit the slot from MRO.
833    NotFound,
834}
835
836impl PyType {
837    /// Update slots based on dunder method changes
838    ///
839    /// Iterates SLOT_DEFS to find all slots matching the given name and updates them.
840    /// Also recursively updates subclasses that don't have their own definition.
841    pub(crate) fn update_slot<const ADD: bool>(&self, name: &'static PyStrInterned, ctx: &Context) {
842        debug_assert!(name.as_str().starts_with("__"));
843        debug_assert!(name.as_str().ends_with("__"));
844
845        // Find all slot_defs matching this name and update each
846        // NOTE: Collect into Vec first to avoid issues during iteration
847        let defs: Vec<_> = find_slot_defs_by_name(name.as_str()).collect();
848        for def in defs {
849            self.update_one_slot::<ADD>(def.accessor, name, ctx);
850        }
851
852        // Recursively update subclasses that don't have their own definition
853        self.update_subclasses::<ADD>(name, ctx);
854    }
855
856    /// Recursively update subclasses' slots
857    /// recurse_down_subclasses
858    fn update_subclasses<const ADD: bool>(&self, name: &'static PyStrInterned, ctx: &Context) {
859        let subclasses = self.subclasses.read();
860        for weak_ref in subclasses.iter() {
861            let Some(subclass) = weak_ref.upgrade() else {
862                continue;
863            };
864            let Some(subclass) = subclass.downcast_ref::<Self>() else {
865                continue;
866            };
867
868            // Skip if subclass has its own definition for this attribute
869            if subclass.attributes.contains(name) {
870                continue;
871            }
872
873            // Update subclass's slots
874            for def in find_slot_defs_by_name(name.as_str()) {
875                subclass.update_one_slot::<ADD>(def.accessor, name, ctx);
876            }
877
878            // Recurse into subclass's subclasses
879            subclass.update_subclasses::<ADD>(name, ctx);
880        }
881    }
882
883    /// Update a single slot
884    fn update_one_slot<const ADD: bool>(
885        &self,
886        accessor: SlotAccessor,
887        name: &'static PyStrInterned,
888        ctx: &Context,
889    ) {
890        use crate::builtins::descriptor::SlotFunc;
891
892        // Helper macro for main slots
893        macro_rules! update_main_slot {
894            ($slot:ident, $wrapper:expr, $variant:ident) => {{
895                if ADD {
896                    match self.lookup_slot_in_mro(name, ctx, |sf| {
897                        if let SlotFunc::$variant(f) = sf {
898                            Some(*f)
899                        } else {
900                            None
901                        }
902                    }) {
903                        SlotLookupResult::NativeSlot(func) => {
904                            self.slots.$slot.store(Some(func));
905                        }
906                        SlotLookupResult::PythonMethod => {
907                            self.slots.$slot.store(Some($wrapper));
908                        }
909                        SlotLookupResult::NotFound => {
910                            accessor.inherit_from_mro(self);
911                        }
912                    }
913                } else {
914                    accessor.inherit_from_mro(self);
915                }
916            }};
917        }
918
919        // Helper macro for number/sequence/mapping sub-slots
920        macro_rules! update_sub_slot {
921            ($group:ident, $slot:ident, $wrapper:expr, $variant:ident) => {{
922                // Fall back to the value inherited for this exact field. Left and
923                // right binary ops (e.g. add / right_add) share one accessor but
924                // occupy distinct fields, so the fallback must target this field
925                // rather than the accessor's default field, otherwise resolving
926                // an absent right op would overwrite the left op's dispatcher.
927                let inherit_this_field = || {
928                    let mro = self.mro.read();
929                    let inherited = mro[1..]
930                        .iter()
931                        .find_map(|cls| cls.slots.$group.$slot.load());
932                    self.slots.$group.$slot.store(inherited);
933                };
934                if ADD {
935                    // Check if this type defines any method that maps to this slot.
936                    // Some slots like SqAssItem/MpAssSubscript are shared by multiple
937                    // methods (__setitem__ and __delitem__). If any of those methods
938                    // is defined, we must use the wrapper to ensure Python method calls.
939                    let has_own = {
940                        // Check the current method name
941                        let mut result = self.attributes.contains(name);
942                        // For ass_item/ass_subscript slots, also check the paired method
943                        // (__setitem__ and __delitem__ share the same slot)
944                        if !result
945                            && (stringify!($slot) == "ass_item"
946                                || stringify!($slot) == "ass_subscript")
947                        {
948                            let setitem = ctx.intern_str("__setitem__");
949                            let delitem = ctx.intern_str("__delitem__");
950                            result = self.attributes.contains(setitem)
951                                || self.attributes.contains(delitem);
952                        }
953                        result
954                    };
955                    // Reify the wrapper at a single site so the own and inherited
956                    // branches store the same fn item. binary_op1 compares slot
957                    // fn addresses to decide whether a subclass overrides the op;
958                    // duplicating the wrapper closure across branches yields
959                    // distinct addresses in unmerged debug builds and breaks that
960                    // comparison for an inherited slot.
961                    let store_wrapper = || self.slots.$group.$slot.store(Some($wrapper));
962                    if has_own {
963                        store_wrapper();
964                    } else {
965                        match self.lookup_slot_in_mro(name, ctx, |sf| {
966                            if let SlotFunc::$variant(f) = sf {
967                                Some(*f)
968                            } else {
969                                None
970                            }
971                        }) {
972                            SlotLookupResult::NativeSlot(func) => {
973                                self.slots.$group.$slot.store(Some(func));
974                            }
975                            SlotLookupResult::PythonMethod => {
976                                store_wrapper();
977                            }
978                            SlotLookupResult::NotFound => {
979                                inherit_this_field();
980                            }
981                        }
982                    }
983                } else {
984                    inherit_this_field();
985                }
986            }};
987        }
988
989        match accessor {
990            // === Main slots ===
991            SlotAccessor::TpRepr => update_main_slot!(repr, repr_wrapper, Repr),
992            SlotAccessor::TpStr => update_main_slot!(str, str_wrapper, Str),
993            SlotAccessor::TpHash => {
994                // Special handling for __hash__ = None
995                if ADD {
996                    let method = self.attributes.get(name).or_else(|| {
997                        self.mro
998                            .read()
999                            .iter()
1000                            .find_map(|cls| cls.attributes.get(name))
1001                    });
1002
1003                    if method.as_ref().is_some_and(|m| m.is(&ctx.none)) {
1004                        self.slots.hash.store(Some(hash_not_implemented));
1005                    } else {
1006                        match self.lookup_slot_in_mro(name, ctx, |sf| {
1007                            if let SlotFunc::Hash(f) = sf {
1008                                Some(*f)
1009                            } else {
1010                                None
1011                            }
1012                        }) {
1013                            SlotLookupResult::NativeSlot(func) => {
1014                                self.slots.hash.store(Some(func));
1015                            }
1016                            SlotLookupResult::PythonMethod => {
1017                                self.slots.hash.store(Some(hash_wrapper));
1018                            }
1019                            SlotLookupResult::NotFound => {
1020                                accessor.inherit_from_mro(self);
1021                            }
1022                        }
1023                    }
1024                } else {
1025                    accessor.inherit_from_mro(self);
1026                }
1027            }
1028            SlotAccessor::TpCall => {
1029                update_main_slot!(call, call_wrapper, Call);
1030                // When __call__ is overridden in Python, clear vectorcall
1031                // so the slow path through call_wrapper is used.
1032                if ADD {
1033                    self.slots.vectorcall.store(None);
1034                }
1035            }
1036            SlotAccessor::TpIter => update_main_slot!(iter, iter_wrapper, Iter),
1037            SlotAccessor::TpIternext => update_main_slot!(iternext, iternext_wrapper, IterNext),
1038            SlotAccessor::TpInit => {
1039                update_main_slot!(init, init_wrapper, Init);
1040                if ADD {
1041                    self.slots.vectorcall.store(None);
1042                }
1043            }
1044            SlotAccessor::TpNew => {
1045                // __new__ is a staticmethod, not a PyWrapper descriptor, so
1046                // lookup_slot_in_mro cannot classify it. Resolve __new__
1047                // through the MRO dicts instead: a Python-level definition
1048                // needs the dynamic new_wrapper, while a native type's
1049                // builtin __new__ entry (or no entry at all) means the slot
1050                // is inherited from the solid base, matching update_one_slot's
1051                // tp_new special case over the tp_base-inherited value.
1052                let needs_wrapper = if ADD && self.attributes.contains(name) {
1053                    true
1054                } else {
1055                    // mro[0] is self, so skip it
1056                    self.mro.read()[1..]
1057                        .iter()
1058                        .find(|cls| cls.attributes.contains(name))
1059                        .is_some_and(|cls| {
1060                            cls.slots.new.load().map(|f| fn_addr(f))
1061                                == Some(fn_addr(new_wrapper as NewFunc))
1062                        })
1063                };
1064                if needs_wrapper {
1065                    self.slots.new.store(Some(new_wrapper));
1066                    self.slots.vectorcall.store(None);
1067                } else {
1068                    let inherited = self.base.deref().and_then(|base| base.slots.new.load());
1069                    self.slots.new.store(inherited);
1070                }
1071            }
1072            SlotAccessor::TpDel => update_main_slot!(del, del_wrapper, Del),
1073            SlotAccessor::TpGetattro => {
1074                // __getattribute__ and __getattr__ both map to TpGetattro.
1075                // If __getattr__ is defined anywhere in MRO, we must use the wrapper
1076                // because the native slot won't call __getattr__.
1077                let __getattr__ = identifier!(ctx, __getattr__);
1078                let has_getattr = {
1079                    // mro[0] is self, so skip it
1080                    self.attributes.contains(__getattr__)
1081                        || self
1082                            .mro
1083                            .read()
1084                            .iter()
1085                            .skip(1)
1086                            .any(|cls| cls.attributes.contains(__getattr__))
1087                };
1088
1089                if has_getattr {
1090                    // Must use wrapper to handle __getattr__
1091                    self.slots.getattro.store(Some(getattro_wrapper));
1092                } else if ADD {
1093                    match self.lookup_slot_in_mro(name, ctx, |sf| {
1094                        if let SlotFunc::GetAttro(f) = sf {
1095                            Some(*f)
1096                        } else {
1097                            None
1098                        }
1099                    }) {
1100                        SlotLookupResult::NativeSlot(func) => {
1101                            self.slots.getattro.store(Some(func));
1102                        }
1103                        SlotLookupResult::PythonMethod => {
1104                            self.slots.getattro.store(Some(getattro_wrapper));
1105                        }
1106                        SlotLookupResult::NotFound => {
1107                            accessor.inherit_from_mro(self);
1108                        }
1109                    }
1110                } else {
1111                    accessor.inherit_from_mro(self);
1112                }
1113            }
1114            SlotAccessor::TpSetattro => {
1115                // __setattr__ and __delattr__ share the same slot, so both
1116                // names must be resolved together: a Python-level override of
1117                // either one forces the dispatching wrapper, and the native
1118                // slot is only usable when every resolved name agrees on it.
1119                // This resolution reads the current attribute dicts, so it
1120                // applies the same whether a name was just added or removed.
1121                let extract = |sf: &SlotFunc| match sf {
1122                    SlotFunc::SetAttro(f) | SlotFunc::DelAttro(f) => Some(*f),
1123                    _ => None,
1124                };
1125                let setattr = self.lookup_slot_in_mro(identifier!(ctx, __setattr__), ctx, extract);
1126                let delattr = self.lookup_slot_in_mro(identifier!(ctx, __delattr__), ctx, extract);
1127                use SlotLookupResult::{NativeSlot, NotFound, PythonMethod};
1128                match (setattr, delattr) {
1129                    (PythonMethod, _) | (_, PythonMethod) => {
1130                        self.slots.setattro.store(Some(setattro_wrapper));
1131                    }
1132                    (NativeSlot(set), NativeSlot(del)) => {
1133                        let func = if fn_addr(set) == fn_addr(del) {
1134                            set
1135                        } else {
1136                            setattro_wrapper
1137                        };
1138                        self.slots.setattro.store(Some(func));
1139                    }
1140                    (NativeSlot(func), NotFound) | (NotFound, NativeSlot(func)) => {
1141                        self.slots.setattro.store(Some(func));
1142                    }
1143                    (NotFound, NotFound) => {
1144                        accessor.inherit_from_mro(self);
1145                    }
1146                }
1147            }
1148            SlotAccessor::TpDescrGet => update_main_slot!(descr_get, descr_get_wrapper, DescrGet),
1149            SlotAccessor::TpDescrSet => {
1150                // __set__ and __delete__ share the same slot, so both names
1151                // must be resolved together: a Python-level definition of
1152                // either one forces the dispatching wrapper, and the native
1153                // slot is only usable when every resolved name agrees on it.
1154                // This resolution reads the current attribute dicts, so it
1155                // applies the same whether a name was just added or removed.
1156                let extract = |sf: &SlotFunc| match sf {
1157                    SlotFunc::DescrSet(f) | SlotFunc::DescrDel(f) => Some(*f),
1158                    _ => None,
1159                };
1160                let set = self.lookup_slot_in_mro(identifier!(ctx, __set__), ctx, extract);
1161                let delete = self.lookup_slot_in_mro(identifier!(ctx, __delete__), ctx, extract);
1162                use SlotLookupResult::{NativeSlot, NotFound, PythonMethod};
1163                match (set, delete) {
1164                    (PythonMethod, _) | (_, PythonMethod) => {
1165                        self.slots.descr_set.store(Some(descr_set_wrapper));
1166                    }
1167                    (NativeSlot(set), NativeSlot(delete)) => {
1168                        let func = if fn_addr(set) == fn_addr(delete) {
1169                            set
1170                        } else {
1171                            descr_set_wrapper
1172                        };
1173                        self.slots.descr_set.store(Some(func));
1174                    }
1175                    (NativeSlot(func), NotFound) | (NotFound, NativeSlot(func)) => {
1176                        self.slots.descr_set.store(Some(func));
1177                    }
1178                    (NotFound, NotFound) => {
1179                        accessor.inherit_from_mro(self);
1180                    }
1181                }
1182            }
1183
1184            // === Rich compare (__lt__, __le__, __eq__, __ne__, __gt__, __ge__) ===
1185            SlotAccessor::TpRichcompare => {
1186                if ADD {
1187                    // Check if self or any class in MRO has a Python-defined comparison method
1188                    // All comparison ops share the same slot, so if any is overridden anywhere
1189                    // in the hierarchy with a Python function, we need to use the wrapper
1190                    let cmp_names = [
1191                        identifier!(ctx, __eq__),
1192                        identifier!(ctx, __ne__),
1193                        identifier!(ctx, __lt__),
1194                        identifier!(ctx, __le__),
1195                        identifier!(ctx, __gt__),
1196                        identifier!(ctx, __ge__),
1197                    ];
1198
1199                    let has_python_cmp = {
1200                        // Check self first, then the rest of the MRO
1201                        cmp_names.iter().any(|n| self.attributes.contains(n))
1202                            || self.mro.read()[1..].iter().any(|cls| {
1203                                cmp_names.iter().any(|n| {
1204                                    cls.attributes.get(n).is_some_and(|attr| {
1205                                        // Check if it's a Python function (not a native descriptor)
1206                                        !attr.class().is(ctx.types.wrapper_descriptor_type)
1207                                            && !attr.class().is(ctx.types.method_descriptor_type)
1208                                            && !attr
1209                                                .class()
1210                                                .is(ctx.types.classmethod_descriptor_type)
1211                                    })
1212                                })
1213                            })
1214                    };
1215
1216                    if has_python_cmp {
1217                        // Use wrapper to call the Python method
1218                        self.slots.richcompare.store(Some(richcompare_wrapper));
1219                    } else {
1220                        match self.lookup_slot_in_mro(name, ctx, |sf| {
1221                            if let SlotFunc::RichCompare(f, _) = sf {
1222                                Some(*f)
1223                            } else {
1224                                None
1225                            }
1226                        }) {
1227                            SlotLookupResult::NativeSlot(func) => {
1228                                self.slots.richcompare.store(Some(func));
1229                            }
1230                            SlotLookupResult::PythonMethod => {
1231                                self.slots.richcompare.store(Some(richcompare_wrapper));
1232                            }
1233                            SlotLookupResult::NotFound => {
1234                                accessor.inherit_from_mro(self);
1235                            }
1236                        }
1237                    }
1238                } else {
1239                    accessor.inherit_from_mro(self);
1240                }
1241            }
1242
1243            // === Number binary operations ===
1244            SlotAccessor::NbAdd => {
1245                if name.as_str() == "__radd__" {
1246                    update_sub_slot!(
1247                        as_number,
1248                        right_add,
1249                        number_binary_right_op_wrapper!(__radd__),
1250                        NumBinary
1251                    )
1252                } else {
1253                    update_sub_slot!(
1254                        as_number,
1255                        add,
1256                        number_binary_op_wrapper!(__add__),
1257                        NumBinary
1258                    )
1259                }
1260            }
1261            SlotAccessor::NbInplaceAdd => {
1262                update_sub_slot!(
1263                    as_number,
1264                    inplace_add,
1265                    number_binary_op_wrapper!(__iadd__),
1266                    NumBinary
1267                )
1268            }
1269            SlotAccessor::NbSubtract => {
1270                if name.as_str() == "__rsub__" {
1271                    update_sub_slot!(
1272                        as_number,
1273                        right_subtract,
1274                        number_binary_right_op_wrapper!(__rsub__),
1275                        NumBinary
1276                    )
1277                } else {
1278                    update_sub_slot!(
1279                        as_number,
1280                        subtract,
1281                        number_binary_op_wrapper!(__sub__),
1282                        NumBinary
1283                    )
1284                }
1285            }
1286            SlotAccessor::NbInplaceSubtract => {
1287                update_sub_slot!(
1288                    as_number,
1289                    inplace_subtract,
1290                    number_binary_op_wrapper!(__isub__),
1291                    NumBinary
1292                )
1293            }
1294            SlotAccessor::NbMultiply => {
1295                if name.as_str() == "__rmul__" {
1296                    update_sub_slot!(
1297                        as_number,
1298                        right_multiply,
1299                        number_binary_right_op_wrapper!(__rmul__),
1300                        NumBinary
1301                    )
1302                } else {
1303                    update_sub_slot!(
1304                        as_number,
1305                        multiply,
1306                        number_binary_op_wrapper!(__mul__),
1307                        NumBinary
1308                    )
1309                }
1310            }
1311            SlotAccessor::NbInplaceMultiply => {
1312                update_sub_slot!(
1313                    as_number,
1314                    inplace_multiply,
1315                    number_binary_op_wrapper!(__imul__),
1316                    NumBinary
1317                )
1318            }
1319            SlotAccessor::NbRemainder => {
1320                if name.as_str() == "__rmod__" {
1321                    update_sub_slot!(
1322                        as_number,
1323                        right_remainder,
1324                        number_binary_right_op_wrapper!(__rmod__),
1325                        NumBinary
1326                    )
1327                } else {
1328                    update_sub_slot!(
1329                        as_number,
1330                        remainder,
1331                        number_binary_op_wrapper!(__mod__),
1332                        NumBinary
1333                    )
1334                }
1335            }
1336            SlotAccessor::NbInplaceRemainder => {
1337                update_sub_slot!(
1338                    as_number,
1339                    inplace_remainder,
1340                    number_binary_op_wrapper!(__imod__),
1341                    NumBinary
1342                )
1343            }
1344            SlotAccessor::NbDivmod => {
1345                if name.as_str() == "__rdivmod__" {
1346                    update_sub_slot!(
1347                        as_number,
1348                        right_divmod,
1349                        number_binary_right_op_wrapper!(__rdivmod__),
1350                        NumBinary
1351                    )
1352                } else {
1353                    update_sub_slot!(
1354                        as_number,
1355                        divmod,
1356                        number_binary_op_wrapper!(__divmod__),
1357                        NumBinary
1358                    )
1359                }
1360            }
1361            SlotAccessor::NbPower => {
1362                if name.as_str() == "__rpow__" {
1363                    update_sub_slot!(
1364                        as_number,
1365                        right_power,
1366                        number_ternary_right_op_wrapper!(__rpow__),
1367                        NumTernary
1368                    )
1369                } else {
1370                    update_sub_slot!(
1371                        as_number,
1372                        power,
1373                        number_ternary_op_wrapper!(__pow__),
1374                        NumTernary
1375                    )
1376                }
1377            }
1378            SlotAccessor::NbInplacePower => {
1379                update_sub_slot!(
1380                    as_number,
1381                    inplace_power,
1382                    number_ternary_op_wrapper!(__ipow__),
1383                    NumTernary
1384                )
1385            }
1386            SlotAccessor::NbFloorDivide => {
1387                if name.as_str() == "__rfloordiv__" {
1388                    update_sub_slot!(
1389                        as_number,
1390                        right_floor_divide,
1391                        number_binary_right_op_wrapper!(__rfloordiv__),
1392                        NumBinary
1393                    )
1394                } else {
1395                    update_sub_slot!(
1396                        as_number,
1397                        floor_divide,
1398                        number_binary_op_wrapper!(__floordiv__),
1399                        NumBinary
1400                    )
1401                }
1402            }
1403            SlotAccessor::NbInplaceFloorDivide => {
1404                update_sub_slot!(
1405                    as_number,
1406                    inplace_floor_divide,
1407                    number_binary_op_wrapper!(__ifloordiv__),
1408                    NumBinary
1409                )
1410            }
1411            SlotAccessor::NbTrueDivide => {
1412                if name.as_str() == "__rtruediv__" {
1413                    update_sub_slot!(
1414                        as_number,
1415                        right_true_divide,
1416                        number_binary_right_op_wrapper!(__rtruediv__),
1417                        NumBinary
1418                    )
1419                } else {
1420                    update_sub_slot!(
1421                        as_number,
1422                        true_divide,
1423                        number_binary_op_wrapper!(__truediv__),
1424                        NumBinary
1425                    )
1426                }
1427            }
1428            SlotAccessor::NbInplaceTrueDivide => {
1429                update_sub_slot!(
1430                    as_number,
1431                    inplace_true_divide,
1432                    number_binary_op_wrapper!(__itruediv__),
1433                    NumBinary
1434                )
1435            }
1436            SlotAccessor::NbMatrixMultiply => {
1437                if name.as_str() == "__rmatmul__" {
1438                    update_sub_slot!(
1439                        as_number,
1440                        right_matrix_multiply,
1441                        number_binary_right_op_wrapper!(__rmatmul__),
1442                        NumBinary
1443                    )
1444                } else {
1445                    update_sub_slot!(
1446                        as_number,
1447                        matrix_multiply,
1448                        number_binary_op_wrapper!(__matmul__),
1449                        NumBinary
1450                    )
1451                }
1452            }
1453            SlotAccessor::NbInplaceMatrixMultiply => {
1454                update_sub_slot!(
1455                    as_number,
1456                    inplace_matrix_multiply,
1457                    number_binary_op_wrapper!(__imatmul__),
1458                    NumBinary
1459                )
1460            }
1461
1462            // === Number bitwise operations ===
1463            SlotAccessor::NbLshift => {
1464                if name.as_str() == "__rlshift__" {
1465                    update_sub_slot!(
1466                        as_number,
1467                        right_lshift,
1468                        number_binary_right_op_wrapper!(__rlshift__),
1469                        NumBinary
1470                    )
1471                } else {
1472                    update_sub_slot!(
1473                        as_number,
1474                        lshift,
1475                        number_binary_op_wrapper!(__lshift__),
1476                        NumBinary
1477                    )
1478                }
1479            }
1480            SlotAccessor::NbInplaceLshift => {
1481                update_sub_slot!(
1482                    as_number,
1483                    inplace_lshift,
1484                    number_binary_op_wrapper!(__ilshift__),
1485                    NumBinary
1486                )
1487            }
1488            SlotAccessor::NbRshift => {
1489                if name.as_str() == "__rrshift__" {
1490                    update_sub_slot!(
1491                        as_number,
1492                        right_rshift,
1493                        number_binary_right_op_wrapper!(__rrshift__),
1494                        NumBinary
1495                    )
1496                } else {
1497                    update_sub_slot!(
1498                        as_number,
1499                        rshift,
1500                        number_binary_op_wrapper!(__rshift__),
1501                        NumBinary
1502                    )
1503                }
1504            }
1505            SlotAccessor::NbInplaceRshift => {
1506                update_sub_slot!(
1507                    as_number,
1508                    inplace_rshift,
1509                    number_binary_op_wrapper!(__irshift__),
1510                    NumBinary
1511                )
1512            }
1513            SlotAccessor::NbAnd => {
1514                if name.as_str() == "__rand__" {
1515                    update_sub_slot!(
1516                        as_number,
1517                        right_and,
1518                        number_binary_right_op_wrapper!(__rand__),
1519                        NumBinary
1520                    )
1521                } else {
1522                    update_sub_slot!(
1523                        as_number,
1524                        and,
1525                        number_binary_op_wrapper!(__and__),
1526                        NumBinary
1527                    )
1528                }
1529            }
1530            SlotAccessor::NbInplaceAnd => {
1531                update_sub_slot!(
1532                    as_number,
1533                    inplace_and,
1534                    number_binary_op_wrapper!(__iand__),
1535                    NumBinary
1536                )
1537            }
1538            SlotAccessor::NbXor => {
1539                if name.as_str() == "__rxor__" {
1540                    update_sub_slot!(
1541                        as_number,
1542                        right_xor,
1543                        number_binary_right_op_wrapper!(__rxor__),
1544                        NumBinary
1545                    )
1546                } else {
1547                    update_sub_slot!(
1548                        as_number,
1549                        xor,
1550                        number_binary_op_wrapper!(__xor__),
1551                        NumBinary
1552                    )
1553                }
1554            }
1555            SlotAccessor::NbInplaceXor => {
1556                update_sub_slot!(
1557                    as_number,
1558                    inplace_xor,
1559                    number_binary_op_wrapper!(__ixor__),
1560                    NumBinary
1561                )
1562            }
1563            SlotAccessor::NbOr => {
1564                if name.as_str() == "__ror__" {
1565                    update_sub_slot!(
1566                        as_number,
1567                        right_or,
1568                        number_binary_right_op_wrapper!(__ror__),
1569                        NumBinary
1570                    )
1571                } else {
1572                    update_sub_slot!(as_number, or, number_binary_op_wrapper!(__or__), NumBinary)
1573                }
1574            }
1575            SlotAccessor::NbInplaceOr => {
1576                update_sub_slot!(
1577                    as_number,
1578                    inplace_or,
1579                    number_binary_op_wrapper!(__ior__),
1580                    NumBinary
1581                )
1582            }
1583
1584            // === Number unary operations ===
1585            SlotAccessor::NbNegative => {
1586                update_sub_slot!(
1587                    as_number,
1588                    negative,
1589                    number_unary_op_wrapper!(__neg__),
1590                    NumUnary
1591                )
1592            }
1593            SlotAccessor::NbPositive => {
1594                update_sub_slot!(
1595                    as_number,
1596                    positive,
1597                    number_unary_op_wrapper!(__pos__),
1598                    NumUnary
1599                )
1600            }
1601            SlotAccessor::NbAbsolute => {
1602                update_sub_slot!(
1603                    as_number,
1604                    absolute,
1605                    number_unary_op_wrapper!(__abs__),
1606                    NumUnary
1607                )
1608            }
1609            SlotAccessor::NbInvert => {
1610                update_sub_slot!(
1611                    as_number,
1612                    invert,
1613                    number_unary_op_wrapper!(__invert__),
1614                    NumUnary
1615                )
1616            }
1617            SlotAccessor::NbBool => {
1618                update_sub_slot!(as_number, boolean, bool_wrapper, NumBoolean)
1619            }
1620            SlotAccessor::NbInt => {
1621                update_sub_slot!(as_number, int, number_unary_op_wrapper!(__int__), NumUnary)
1622            }
1623            SlotAccessor::NbFloat => {
1624                update_sub_slot!(
1625                    as_number,
1626                    float,
1627                    number_unary_op_wrapper!(__float__),
1628                    NumUnary
1629                )
1630            }
1631            SlotAccessor::NbIndex => {
1632                update_sub_slot!(
1633                    as_number,
1634                    index,
1635                    number_unary_op_wrapper!(__index__),
1636                    NumUnary
1637                )
1638            }
1639
1640            // === Sequence slots ===
1641            SlotAccessor::SqLength => {
1642                update_sub_slot!(as_sequence, length, sequence_len_wrapper, SeqLength)
1643            }
1644            SlotAccessor::SqConcat => {
1645                // Python __add__ overrides use nb_add, not the inherited sq_concat.
1646                let concat = match self.lookup_slot_in_mro(name, ctx, |sf| {
1647                    if let SlotFunc::SeqConcat(f) = sf {
1648                        Some(*f)
1649                    } else {
1650                        None
1651                    }
1652                }) {
1653                    SlotLookupResult::NativeSlot(func) => Some(func),
1654                    SlotLookupResult::PythonMethod | SlotLookupResult::NotFound => None,
1655                };
1656                self.slots.as_sequence.concat.store(concat);
1657            }
1658            SlotAccessor::SqInplaceConcat if !ADD => {
1659                // Sequence concat uses sq_concat slot - no generic wrapper needed
1660                // (handled by number protocol fallback)
1661                accessor.inherit_from_mro(self);
1662            }
1663            SlotAccessor::SqRepeat | SlotAccessor::SqInplaceRepeat => {
1664                // Python `__mul__`/`__rmul__`/`__imul__` overrides use the number slots and
1665                // leave no sequence repeat, as with `sq_concat`.
1666                let (names, field) = if matches!(accessor, SlotAccessor::SqRepeat) {
1667                    (
1668                        &[identifier!(ctx, __mul__), identifier!(ctx, __rmul__)][..],
1669                        &self.slots.as_sequence.repeat,
1670                    )
1671                } else {
1672                    (
1673                        &[identifier!(ctx, __imul__)][..],
1674                        &self.slots.as_sequence.inplace_repeat,
1675                    )
1676                };
1677                let mut repeat = None;
1678                for &name in names {
1679                    match self.lookup_slot_in_mro(name, ctx, |sf| {
1680                        if let SlotFunc::SeqRepeat(f) = sf {
1681                            Some(*f)
1682                        } else {
1683                            None
1684                        }
1685                    }) {
1686                        SlotLookupResult::NativeSlot(func) => repeat = repeat.or(Some(func)),
1687                        SlotLookupResult::PythonMethod => {
1688                            repeat = None;
1689                            break;
1690                        }
1691                        SlotLookupResult::NotFound => {}
1692                    }
1693                }
1694                field.store(repeat);
1695            }
1696            SlotAccessor::SqItem => {
1697                update_sub_slot!(as_sequence, item, sequence_getitem_wrapper, SeqItem)
1698            }
1699            SlotAccessor::SqAssItem => {
1700                // SqAssItem is shared by __setitem__ (SeqSetItem) and __delitem__ (SeqDelItem)
1701                if ADD {
1702                    let has_own = {
1703                        let setitem = ctx.intern_str("__setitem__");
1704                        let delitem = ctx.intern_str("__delitem__");
1705                        self.attributes.contains(setitem) || self.attributes.contains(delitem)
1706                    };
1707                    if has_own {
1708                        self.slots
1709                            .as_sequence
1710                            .ass_item
1711                            .store(Some(sequence_setitem_wrapper));
1712                    } else {
1713                        match self.lookup_slot_in_mro(name, ctx, |sf| match sf {
1714                            SlotFunc::SeqSetItem(f) | SlotFunc::SeqDelItem(f) => Some(*f),
1715                            _ => None,
1716                        }) {
1717                            SlotLookupResult::NativeSlot(func) => {
1718                                self.slots.as_sequence.ass_item.store(Some(func));
1719                            }
1720                            SlotLookupResult::PythonMethod => {
1721                                self.slots
1722                                    .as_sequence
1723                                    .ass_item
1724                                    .store(Some(sequence_setitem_wrapper));
1725                            }
1726                            SlotLookupResult::NotFound => {
1727                                accessor.inherit_from_mro(self);
1728                            }
1729                        }
1730                    }
1731                } else {
1732                    accessor.inherit_from_mro(self);
1733                }
1734            }
1735            SlotAccessor::SqContains => {
1736                update_sub_slot!(
1737                    as_sequence,
1738                    contains,
1739                    sequence_contains_wrapper,
1740                    SeqContains
1741                )
1742            }
1743
1744            // === Mapping slots ===
1745            SlotAccessor::MpLength => {
1746                update_sub_slot!(as_mapping, length, mapping_len_wrapper, MapLength)
1747            }
1748            SlotAccessor::MpSubscript => {
1749                update_sub_slot!(as_mapping, subscript, mapping_getitem_wrapper, MapSubscript)
1750            }
1751            SlotAccessor::MpAssSubscript => {
1752                // MpAssSubscript is shared by __setitem__ (MapSetSubscript) and __delitem__ (MapDelSubscript)
1753                if ADD {
1754                    let has_own = {
1755                        let setitem = ctx.intern_str("__setitem__");
1756                        let delitem = ctx.intern_str("__delitem__");
1757                        self.attributes.contains(setitem) || self.attributes.contains(delitem)
1758                    };
1759                    if has_own {
1760                        self.slots
1761                            .as_mapping
1762                            .ass_subscript
1763                            .store(Some(mapping_setitem_wrapper));
1764                    } else {
1765                        match self.lookup_slot_in_mro(name, ctx, |sf| match sf {
1766                            SlotFunc::MapSetSubscript(f) | SlotFunc::MapDelSubscript(f) => Some(*f),
1767                            _ => None,
1768                        }) {
1769                            SlotLookupResult::NativeSlot(func) => {
1770                                self.slots.as_mapping.ass_subscript.store(Some(func));
1771                            }
1772                            SlotLookupResult::PythonMethod => {
1773                                self.slots
1774                                    .as_mapping
1775                                    .ass_subscript
1776                                    .store(Some(mapping_setitem_wrapper));
1777                            }
1778                            SlotLookupResult::NotFound => {
1779                                accessor.inherit_from_mro(self);
1780                            }
1781                        }
1782                    }
1783                } else {
1784                    accessor.inherit_from_mro(self);
1785                }
1786            }
1787
1788            // === Buffer protocol ===
1789            SlotAccessor::BfGetBuffer => {
1790                if ADD {
1791                    match self.lookup_slot_in_mro(name, ctx, |sf| {
1792                        if let SlotFunc::GetBuffer(f) = sf {
1793                            Some(*f)
1794                        } else {
1795                            None
1796                        }
1797                    }) {
1798                        SlotLookupResult::NativeSlot(func) => {
1799                            self.slots.as_buffer.store(Some(func));
1800                        }
1801                        SlotLookupResult::PythonMethod => {
1802                            self.slots.as_buffer.store(Some(python_as_buffer));
1803                        }
1804                        SlotLookupResult::NotFound => {
1805                            accessor.inherit_from_mro(self);
1806                        }
1807                    }
1808                } else {
1809                    accessor.inherit_from_mro(self);
1810                }
1811            }
1812            SlotAccessor::BfReleaseBuffer => {
1813                // Which of the two implementations `__release_buffer__` resolves to
1814                // decides whether buffer release has to call back into Python.
1815                if ADD {
1816                    match self.lookup_slot_in_mro(name, ctx, |sf| {
1817                        if matches!(sf, SlotFunc::ReleaseBuffer) {
1818                            Some(())
1819                        } else {
1820                            None
1821                        }
1822                    }) {
1823                        SlotLookupResult::NativeSlot(()) => {
1824                            self.slots.python_release_buffer.store(false);
1825                            self.slots.has_release_buffer.store(true);
1826                        }
1827                        SlotLookupResult::PythonMethod => {
1828                            self.slots.python_release_buffer.store(true);
1829                            self.slots.has_release_buffer.store(true);
1830                        }
1831                        SlotLookupResult::NotFound => {
1832                            accessor.inherit_from_mro(self);
1833                        }
1834                    }
1835                } else {
1836                    accessor.inherit_from_mro(self);
1837                }
1838            }
1839
1840            // Reserved slots - no-op
1841            _ => {}
1842        }
1843    }
1844
1845    /// Look up a method in MRO and extract the slot function if it's a slot wrapper.
1846    fn lookup_slot_in_mro<T: Copy>(
1847        &self,
1848        name: &'static PyStrInterned,
1849        ctx: &Context,
1850        extract: impl Fn(&crate::builtins::descriptor::SlotFunc) -> Option<T>,
1851    ) -> SlotLookupResult<T> {
1852        use crate::builtins::descriptor::PyWrapper;
1853
1854        // Helper to check if a class is a subclass of another by checking MRO
1855        let is_subclass_of = |subclass_mro: &[PyRef<Self>], superclass: &Py<Self>| -> bool {
1856            subclass_mro.iter().any(|c| c.is(superclass))
1857        };
1858
1859        // Helper to extract slot from an attribute if it's a wrapper descriptor
1860        // and the wrapper's type is compatible with the given class.
1861        // bpo-37619: wrapper descriptor from wrong class should not be used directly.
1862        let try_extract = |attr: &PyObject, for_class_mro: &[PyRef<Self>]| -> Option<T> {
1863            if attr.class().is(ctx.types.wrapper_descriptor_type) {
1864                attr.downcast_ref::<PyWrapper>().and_then(|wrapper| {
1865                    // Only extract slot if for_class is a subclass of wrapper.typ
1866                    if is_subclass_of(for_class_mro, wrapper.typ) {
1867                        extract(&wrapper.wrapped)
1868                    } else {
1869                        None
1870                    }
1871                })
1872            } else {
1873                None
1874            }
1875        };
1876
1877        let mro = self.mro.read();
1878
1879        // Look up in self's dict first
1880        let attr_name = self.attributes.get(name);
1881        if let Some(attr) = attr_name {
1882            if let Some(func) = try_extract(&attr, &mro) {
1883                return SlotLookupResult::NativeSlot(func);
1884            }
1885            return SlotLookupResult::PythonMethod;
1886        }
1887
1888        // Look up in MRO (mro[0] is self, so skip it)
1889        for (i, cls) in mro[1..].iter().enumerate() {
1890            let attr_name = cls.attributes.get(name);
1891            if let Some(attr) = attr_name {
1892                // Use the slice starting from this class in MRO
1893                if let Some(func) = try_extract(&attr, &mro[i + 1..]) {
1894                    return SlotLookupResult::NativeSlot(func);
1895                }
1896                return SlotLookupResult::PythonMethod;
1897            }
1898        }
1899        // No method found in MRO
1900        SlotLookupResult::NotFound
1901    }
1902}
1903
1904/// Trait for types that can be constructed via Python's `__new__` method.
1905///
1906/// `slot_new` corresponds to the `__new__` type slot.
1907///
1908/// In most cases, `__new__` simply initializes the payload and assigns a type,
1909/// so you only need to override `py_new`. The default `slot_new` implementation
1910/// will call `py_new` and then wrap the result with `into_ref_with_type`.
1911///
1912/// However, if a subtype requires more than just payload initialization
1913/// (e.g., returning an existing object for optimization, setting attributes
1914/// after creation, or special handling of the class type), you should override
1915/// `slot_new` directly instead of `py_new`.
1916///
1917/// # When to use `py_new` only (most common case):
1918/// - Simple payload initialization that just creates `Self`
1919/// - The type doesn't need special handling for subtypes
1920///
1921/// # When to override `slot_new`:
1922/// - Returning existing objects (e.g., `PyInt`, `PyStr`, `PyBool` for optimization)
1923/// - Setting attributes or dict entries after object creation
1924/// - Special class type handling (e.g., `PyType` and its metaclasses)
1925/// - Post-creation mutations that require `PyRef`
1926#[pyclass]
1927pub trait Constructor: PyPayload + core::fmt::Debug {
1928    type Args: FromArgs;
1929
1930    /// When true, extra keywords are dropped if a subclass replaced `tp_init`.
1931    const DROP_KWARGS_WHEN_INIT_OVERRIDDEN: bool = false;
1932
1933    /// The type slot for `__new__`. Override this only when you need special
1934    /// behavior beyond simple payload creation.
1935    #[inline]
1936    #[pyslot]
1937    fn slot_new(cls: PyTypeRef, args: FuncArgs, vm: &VirtualMachine) -> PyResult {
1938        // The name is the type the slot was written for, not the subclass being
1939        // constructed, so a subclass reports what its base declares.
1940        let args = if Self::DROP_KWARGS_WHEN_INIT_OVERRIDDEN {
1941            drop_kwargs_if_init_overridden(&cls, Self::class(&vm.ctx), args)
1942        } else {
1943            args
1944        };
1945        let args: Self::Args = args.bind_for(vm, Callee::of::<Self>(vm))?;
1946        let payload = Self::py_new(&cls, args, vm)?;
1947        payload.into_ref_with_type(vm, cls).map(Into::into)
1948    }
1949
1950    /// Creates the payload for this type. In most cases, just implement this method
1951    /// and let the default `slot_new` handle wrapping with the correct type.
1952    fn py_new(cls: &Py<PyType>, args: Self::Args, vm: &VirtualMachine) -> PyResult<Self>;
1953}
1954
1955/// Extra keywords are ignored when a subclass has replaced `tp_init`.
1956pub(crate) fn drop_kwargs_if_init_overridden(
1957    cls: &Py<PyType>,
1958    base: &Py<PyType>,
1959    mut args: FuncArgs,
1960) -> FuncArgs {
1961    if args.kwargs.is_empty() {
1962        return args;
1963    }
1964    let uses_base_init = cls.slots.init.load().map(fn_addr) == base.slots.init.load().map(fn_addr);
1965    if !(cls.is(base) || uses_base_init) {
1966        args.kwargs = Default::default();
1967    }
1968    args
1969}
1970
1971pub trait DefaultConstructor: PyPayload + Default + core::fmt::Debug {
1972    fn construct_and_init(args: Self::Args, vm: &VirtualMachine) -> PyResult<PyRef<Self>>
1973    where
1974        Self: Initializer,
1975    {
1976        let this = Self::default().into_ref(&vm.ctx);
1977        Self::init(&this, args, vm)?;
1978        Ok(this)
1979    }
1980}
1981
1982impl<T> Constructor for T
1983where
1984    T: DefaultConstructor,
1985{
1986    type Args = FuncArgs;
1987
1988    fn slot_new(cls: PyTypeRef, _args: FuncArgs, vm: &VirtualMachine) -> PyResult {
1989        Self::default().into_ref_with_type(vm, cls).map(Into::into)
1990    }
1991
1992    fn py_new(cls: &Py<PyType>, _args: Self::Args, vm: &VirtualMachine) -> PyResult<Self> {
1993        Err(vm.new_type_error(format!("cannot create {} instances", cls.slot_name())))
1994    }
1995}
1996
1997#[pyclass]
1998pub trait Initializer: PyPayload {
1999    type Args: FromArgs;
2000
2001    #[inline]
2002    #[pyslot]
2003    fn slot_init(zelf: &PyObject, args: FuncArgs, vm: &VirtualMachine) -> PyResult<()> {
2004        #[cfg(debug_assertions)]
2005        let class_name_for_debug = zelf.class().name().to_string();
2006
2007        let zelf = match zelf.try_to_ref::<Self>(vm) {
2008            Ok(zelf) => zelf,
2009            Err(err) => {
2010                #[cfg(debug_assertions)]
2011                {
2012                    if let Ok(msg) = err.as_object().repr(vm) {
2013                        let double_appearance = msg
2014                            .to_string_lossy()
2015                            .matches(&class_name_for_debug as &str)
2016                            .count()
2017                            == 2;
2018                        assert!(
2019                            !double_appearance,
2020                            "This type `{class_name_for_debug}` doesn't seem to support `init`. Override `slot_init` instead: {msg}"
2021                        )
2022                    }
2023                }
2024                return Err(err);
2025            }
2026        };
2027        let args: Self::Args = args.bind_for(vm, Callee::of::<Self>(vm))?;
2028        Self::init(zelf, args, vm)
2029    }
2030
2031    fn init(zelf: &Py<Self>, args: Self::Args, vm: &VirtualMachine) -> PyResult<()>;
2032}
2033
2034#[pyclass]
2035pub trait Destructor: PyPayload {
2036    #[inline] // for __del__
2037    #[pyslot]
2038    fn slot_del(zelf: &PyObject, vm: &VirtualMachine) -> PyResult<()> {
2039        let zelf = zelf
2040            .downcast_ref()
2041            .ok_or_else(|| vm.new_type_error("unexpected payload for __del__"))?;
2042        Self::del(zelf, vm)
2043    }
2044
2045    fn del(zelf: &Py<Self>, vm: &VirtualMachine) -> PyResult<()>;
2046}
2047
2048#[pyclass]
2049pub trait Callable: PyPayload {
2050    type Args: FromArgs;
2051
2052    #[inline]
2053    #[pyslot]
2054    fn slot_call(zelf: &PyObject, args: FuncArgs, vm: &VirtualMachine) -> PyResult {
2055        let zelf = zelf.downcast_ref().ok_or_else(|| {
2056            let repr = zelf.repr(vm);
2057            let help: Wtf8Buf = if let Ok(repr) = repr.as_ref() {
2058                repr.as_wtf8().to_owned()
2059            } else {
2060                zelf.class().name().to_owned().into()
2061            };
2062            let mut msg = Wtf8Buf::from("unexpected payload for __call__ of ");
2063            msg.push_wtf8(&help);
2064            vm.new_type_error(msg)
2065        })?;
2066        let args = args.bind_for(vm, Callee::of::<Self>(vm))?;
2067        Self::call(zelf, args, vm)
2068    }
2069
2070    fn call(zelf: &Py<Self>, args: Self::Args, vm: &VirtualMachine) -> PyResult;
2071}
2072
2073#[pyclass]
2074pub trait GetDescriptor: PyPayload {
2075    #[pyslot]
2076    fn descr_get(
2077        zelf: &PyObject,
2078        obj: Option<&PyObject>,
2079        cls: Option<&PyObject>,
2080        vm: &VirtualMachine,
2081    ) -> PyResult;
2082
2083    #[inline]
2084    fn _as_pyref<'a>(zelf: &'a PyObject, vm: &VirtualMachine) -> PyResult<&'a Py<Self>> {
2085        zelf.try_to_value(vm)
2086    }
2087
2088    #[inline]
2089    fn _unwrap<'a, 'b>(
2090        zelf: &'a PyObject,
2091        obj: Option<&'b PyObject>,
2092        vm: &'b VirtualMachine,
2093    ) -> PyResult<(&'a Py<Self>, &'b PyObject)> {
2094        let zelf = Self::_as_pyref(zelf, vm)?;
2095        let obj = obj.unwrap_or_else(|| vm.ctx.none.as_object());
2096        Ok((zelf, obj))
2097    }
2098
2099    #[inline]
2100    fn _check<'a, 'b>(
2101        zelf: &'a PyObject,
2102        obj: Option<&'b PyObject>,
2103        vm: &VirtualMachine,
2104    ) -> Option<(&'a Py<Self>, &'b PyObject)> {
2105        // CPython descr_check
2106        let obj = obj?;
2107        // if (!PyObject_TypeCheck(obj, descr->d_type)) {
2108        //     PyErr_Format(PyExc_TypeError,
2109        //                  "descriptor '%V' for '%.100s' objects "
2110        //                  "doesn't apply to a '%.100s' object",
2111        //                  descr_name((PyDescrObject *)descr), "?",
2112        //                  descr->d_type->slot_name,
2113        //                  obj->ob_type->slot_name);
2114        //     *pres = NULL;
2115        //     return 1;
2116        // } else {
2117        Some((Self::_as_pyref(zelf, vm).unwrap(), obj))
2118    }
2119
2120    #[inline]
2121    fn _cls_is(cls: &Option<&PyObject>, other: &impl Borrow<PyObject>) -> bool {
2122        cls.is_some_and(|cls| other.borrow().is(cls))
2123    }
2124}
2125
2126#[pyclass]
2127pub trait Hashable: PyPayload {
2128    #[inline]
2129    #[pyslot]
2130    fn slot_hash(zelf: &PyObject, vm: &VirtualMachine) -> PyResult<PyHash> {
2131        let zelf = zelf
2132            .downcast_ref()
2133            .ok_or_else(|| vm.new_type_error("unexpected payload for __hash__"))?;
2134        Self::hash(zelf, vm)
2135    }
2136
2137    fn hash(zelf: &Py<Self>, vm: &VirtualMachine) -> PyResult<PyHash>;
2138}
2139
2140#[pyclass]
2141pub trait Representable: PyPayload {
2142    #[inline]
2143    #[pyslot]
2144    fn slot_repr(zelf: &PyObject, vm: &VirtualMachine) -> PyResult<PyRef<PyStr>> {
2145        let zelf = zelf
2146            .downcast_ref()
2147            .ok_or_else(|| vm.new_type_error("unexpected payload for __repr__"))?;
2148        Self::repr(zelf, vm)
2149    }
2150
2151    #[inline]
2152    fn repr(zelf: &Py<Self>, vm: &VirtualMachine) -> PyResult<PyRef<PyStr>> {
2153        let repr = Self::repr_wtf8(zelf, vm)?;
2154        Ok(vm.ctx.new_str(repr))
2155    }
2156
2157    fn repr_wtf8(zelf: &Py<Self>, vm: &VirtualMachine) -> PyResult<Wtf8Buf> {
2158        Self::repr_str(zelf, vm).map(|utf8| utf8.into())
2159    }
2160    fn repr_str(_zelf: &Py<Self>, _vm: &VirtualMachine) -> PyResult<String> {
2161        unreachable!("Representable requires overriding either repr_str or repr_wtf8")
2162    }
2163}
2164
2165#[pyclass]
2166pub trait Comparable: PyPayload {
2167    #[inline]
2168    #[pyslot]
2169    fn slot_richcompare(
2170        zelf: &PyObject,
2171        other: &PyObject,
2172        op: PyComparisonOp,
2173        vm: &VirtualMachine,
2174    ) -> PyResult<Either<PyObjectRef, PyComparisonValue>> {
2175        let zelf = zelf.downcast_ref().ok_or_else(|| {
2176            vm.new_type_error(format!(
2177                "unexpected payload for {}",
2178                op.method_name(&vm.ctx).as_str()
2179            ))
2180        })?;
2181        Self::cmp(zelf, other, op, vm).map(Either::B)
2182    }
2183
2184    fn cmp(
2185        zelf: &Py<Self>,
2186        other: &PyObject,
2187        op: PyComparisonOp,
2188        vm: &VirtualMachine,
2189    ) -> PyResult<PyComparisonValue>;
2190}
2191
2192#[derive(Debug, Copy, Clone, Eq, PartialEq)]
2193#[repr(transparent)]
2194pub struct PyComparisonOp(ComparisonOperator);
2195
2196impl From<ComparisonOperator> for PyComparisonOp {
2197    fn from(op: ComparisonOperator) -> Self {
2198        Self(op)
2199    }
2200}
2201
2202#[allow(non_upper_case_globals)]
2203impl PyComparisonOp {
2204    pub const Lt: Self = Self(ComparisonOperator::Less);
2205    pub const Gt: Self = Self(ComparisonOperator::Greater);
2206    pub const Ne: Self = Self(ComparisonOperator::NotEqual);
2207    pub const Eq: Self = Self(ComparisonOperator::Equal);
2208    pub const Le: Self = Self(ComparisonOperator::LessOrEqual);
2209    pub const Ge: Self = Self(ComparisonOperator::GreaterOrEqual);
2210}
2211
2212impl PyComparisonOp {
2213    pub fn eq_only(
2214        self,
2215        f: impl FnOnce() -> PyResult<PyComparisonValue>,
2216    ) -> PyResult<PyComparisonValue> {
2217        match self {
2218            Self::Eq => f(),
2219            Self::Ne => f().map(|x| x.map(|eq| !eq)),
2220            _ => Ok(PyComparisonValue::NotImplemented),
2221        }
2222    }
2223
2224    #[must_use]
2225    pub fn eval_ord(self, ord: Ordering) -> bool {
2226        match self {
2227            Self::Lt => ord == Ordering::Less,
2228            Self::Le => ord != Ordering::Greater,
2229            Self::Eq => ord == Ordering::Equal,
2230            Self::Ne => ord != Ordering::Equal,
2231            Self::Gt => ord == Ordering::Greater,
2232            Self::Ge => ord != Ordering::Less,
2233        }
2234    }
2235
2236    #[must_use]
2237    pub const fn swapped(self) -> Self {
2238        match self {
2239            Self::Lt => Self::Gt,
2240            Self::Le => Self::Ge,
2241            Self::Eq => Self::Eq,
2242            Self::Ne => Self::Ne,
2243            Self::Ge => Self::Le,
2244            Self::Gt => Self::Lt,
2245        }
2246    }
2247
2248    pub fn method_name(self, ctx: &Context) -> &'static PyStrInterned {
2249        match self {
2250            Self::Lt => identifier!(ctx, __lt__),
2251            Self::Le => identifier!(ctx, __le__),
2252            Self::Eq => identifier!(ctx, __eq__),
2253            Self::Ne => identifier!(ctx, __ne__),
2254            Self::Ge => identifier!(ctx, __ge__),
2255            Self::Gt => identifier!(ctx, __gt__),
2256        }
2257    }
2258
2259    #[must_use]
2260    pub const fn operator_token(self) -> &'static str {
2261        match self {
2262            Self::Lt => "<",
2263            Self::Le => "<=",
2264            Self::Eq => "==",
2265            Self::Ne => "!=",
2266            Self::Ge => ">=",
2267            Self::Gt => ">",
2268        }
2269    }
2270
2271    /// Returns an appropriate return value for the comparison when a and b are the same object, if an
2272    /// appropriate return value exists.
2273    #[inline]
2274    pub fn identical_optimization(
2275        self,
2276        a: &impl Borrow<PyObject>,
2277        b: &impl Borrow<PyObject>,
2278    ) -> Option<bool> {
2279        self.map_eq(|| a.borrow().is(b.borrow()))
2280    }
2281
2282    /// The answer to this comparison for two operands that `equal` reports as
2283    /// equal or not, or `None` for an ordering operator, which equality alone
2284    /// cannot settle -- `equal` is not called in that case.
2285    ///
2286    /// This is what lets a type answer `==` and `!=` with an equality test
2287    /// rather than with an ordering: the two agree on the answer, but equality
2288    /// can settle a length mismatch without looking at the contents at all.
2289    ///
2290    /// The two neighbouring helpers answer different questions: [`Self::map_eq`]
2291    /// answers only where its predicate holds, so a caller still handles the
2292    /// other side, and [`Self::eq_only`] declares the comparison
2293    /// `NotImplemented` for an ordering operator. This one leaves the ordering
2294    /// operators to the caller, which is what a type with a real ordering
2295    /// needs.
2296    #[inline]
2297    pub fn eval_eq(self, equal: impl FnOnce() -> bool) -> Option<bool> {
2298        match self {
2299            Self::Eq => Some(equal()),
2300            Self::Ne => Some(!equal()),
2301            _ => None,
2302        }
2303    }
2304
2305    /// Returns `Some(true)` when self is `Eq` and `f()` returns true. Returns `Some(false)` when self
2306    /// is `Ne` and `f()` returns true. Otherwise returns `None`.
2307    #[inline]
2308    pub fn map_eq(self, f: impl FnOnce() -> bool) -> Option<bool> {
2309        let eq = match self {
2310            Self::Eq => true,
2311            Self::Ne => false,
2312            _ => return None,
2313        };
2314        f().then_some(eq)
2315    }
2316}
2317
2318#[pyclass]
2319pub trait GetAttr: PyPayload {
2320    #[pyslot]
2321    fn slot_getattro(obj: &PyObject, name: &Py<PyStr>, vm: &VirtualMachine) -> PyResult {
2322        let zelf = obj
2323            .downcast_ref()
2324            .ok_or_else(|| vm.new_type_error("unexpected payload for __getattribute__"))?;
2325        Self::getattro(zelf, name, vm)
2326    }
2327
2328    fn getattro(zelf: &Py<Self>, name: &Py<PyStr>, vm: &VirtualMachine) -> PyResult;
2329}
2330
2331#[pyclass]
2332pub trait SetAttr: PyPayload {
2333    #[pyslot]
2334    #[inline]
2335    fn slot_setattro(
2336        obj: &PyObject,
2337        name: &Py<PyStr>,
2338        value: PySetterValue,
2339        vm: &VirtualMachine,
2340    ) -> PyResult<()> {
2341        let zelf = obj
2342            .downcast_ref::<Self>()
2343            .ok_or_else(|| vm.new_type_error("unexpected payload for __setattr__"))?;
2344        Self::setattro(zelf, name, value, vm)
2345    }
2346
2347    fn setattro(
2348        zelf: &Py<Self>,
2349        name: &Py<PyStr>,
2350        value: PySetterValue,
2351        vm: &VirtualMachine,
2352    ) -> PyResult<()>;
2353}
2354
2355#[pyclass]
2356pub trait AsBuffer: PyPayload {
2357    /// bf_releasebuffer: set when releasing an export of this type is observable,
2358    /// i.e. the exporter counts exports. Such types expose `__release_buffer__`.
2359    const RELEASE_BUFFER: bool = false;
2360
2361    #[inline]
2362    #[pyslot]
2363    fn slot_as_buffer(
2364        zelf: &PyObject,
2365        flags: BufferFlags,
2366        vm: &VirtualMachine,
2367    ) -> PyResult<PyBuffer> {
2368        let zelf = zelf
2369            .downcast_ref()
2370            .ok_or_else(|| vm.new_type_error("unexpected payload for as_buffer"))?;
2371        let buffer = Self::as_buffer(zelf, vm)?;
2372        if let Err(exc) = flags.check_writable(buffer.desc.readonly, "Object is not writable.", vm)
2373        {
2374            // An acquisition that cannot be served never happened, so the
2375            // exporter's release is undone without running the Python hook.
2376            buffer.abort_acquisition();
2377            return Err(exc);
2378        }
2379        Ok(buffer)
2380    }
2381
2382    fn as_buffer(zelf: &Py<Self>, vm: &VirtualMachine) -> PyResult<PyBuffer>;
2383}
2384
2385#[pyclass]
2386pub trait AsMapping: PyPayload {
2387    fn as_mapping() -> &'static PyMappingMethods;
2388
2389    #[inline]
2390    #[must_use]
2391    fn mapping_downcast(mapping: PyMapping<'_>) -> &Py<Self> {
2392        unsafe { mapping.obj.downcast_unchecked_ref() }
2393    }
2394
2395    fn extend_slots(slots: &mut PyTypeSlots) {
2396        slots.as_mapping.copy_from(Self::as_mapping());
2397    }
2398}
2399
2400#[pyclass]
2401pub trait AsSequence: PyPayload {
2402    fn as_sequence() -> &'static PySequenceMethods;
2403
2404    #[inline]
2405    #[must_use]
2406    fn sequence_downcast(seq: PySequence<'_>) -> &Py<Self> {
2407        unsafe { seq.obj.downcast_unchecked_ref() }
2408    }
2409
2410    fn extend_slots(slots: &mut PyTypeSlots) {
2411        slots.as_sequence.copy_from(Self::as_sequence());
2412    }
2413}
2414
2415#[pyclass]
2416pub trait AsNumber: PyPayload {
2417    #[pyslot]
2418    fn as_number() -> &'static PyNumberMethods;
2419
2420    fn extend_slots(slots: &mut PyTypeSlots) {
2421        slots.as_number.copy_from(Self::as_number());
2422    }
2423
2424    fn clone_exact(_zelf: &Py<Self>, _vm: &VirtualMachine) -> PyRef<Self> {
2425        // not all AsNumber requires this implementation.
2426        unimplemented!()
2427    }
2428
2429    #[inline]
2430    #[must_use]
2431    fn number_downcast(num: PyNumber<'_>) -> &Py<Self> {
2432        unsafe { num.obj.downcast_unchecked_ref() }
2433    }
2434
2435    #[inline]
2436    fn number_downcast_exact(num: PyNumber<'_>, vm: &VirtualMachine) -> PyRef<Self> {
2437        if let Some(zelf) = num.downcast_ref_if_exact::<Self>(vm) {
2438            zelf.to_owned()
2439        } else {
2440            Self::clone_exact(Self::number_downcast(num), vm)
2441        }
2442    }
2443}
2444
2445#[pyclass]
2446pub trait Iterable: PyPayload {
2447    #[pyslot]
2448    fn slot_iter(zelf: PyObjectRef, vm: &VirtualMachine) -> PyResult {
2449        let zelf = zelf
2450            .downcast()
2451            .map_err(|_| vm.new_type_error("unexpected payload for __iter__"))?;
2452        Self::iter(zelf, vm)
2453    }
2454
2455    fn iter(zelf: PyRef<Self>, vm: &VirtualMachine) -> PyResult;
2456
2457    fn extend_slots(_slots: &mut PyTypeSlots) {}
2458}
2459
2460// `Iterator` fits better, but to avoid confusion with rust core::iter::Iterator
2461#[pyclass(with(Iterable))]
2462pub trait IterNext: PyPayload + Iterable {
2463    #[pyslot]
2464    fn slot_iternext(zelf: &PyObject, vm: &VirtualMachine) -> PyResult<PyIterReturn> {
2465        let zelf = zelf
2466            .downcast_ref()
2467            .ok_or_else(|| vm.new_type_error("unexpected payload for __next__"))?;
2468        Self::next(zelf, vm)
2469    }
2470
2471    fn next(zelf: &Py<Self>, vm: &VirtualMachine) -> PyResult<PyIterReturn>;
2472}
2473
2474pub trait SelfIter: PyPayload {}
2475
2476impl<T> Iterable for T
2477where
2478    T: SelfIter,
2479{
2480    #[cold]
2481    fn slot_iter(zelf: PyObjectRef, vm: &VirtualMachine) -> PyResult {
2482        let repr = zelf.repr(vm)?;
2483        unreachable!("slot must be overridden for {}", repr.as_wtf8());
2484    }
2485
2486    #[cold]
2487    fn iter(_zelf: PyRef<Self>, _vm: &VirtualMachine) -> PyResult {
2488        unreachable!("slot_iter is implemented");
2489    }
2490
2491    fn extend_slots(slots: &mut PyTypeSlots) {
2492        let prev = slots.iter.swap(Some(self_iter));
2493        debug_assert!(prev.is_some()); // slot_iter would be set
2494    }
2495}
2496
2497/// Extract the raw address of a function pointer as `usize` without
2498/// triggering miri's "pointer not dereferenceable" UB.
2499///
2500/// The standard `fn_ptr as usize` cast goes through `FnPtr::addr()`
2501/// which attempts to dereference the function pointer's provenance —
2502/// miri considers this UB for function items. `transmute_copy` bypasses
2503/// that path and reads the address as plain integer bytes.
2504///
2505/// The result is suitable for identity comparison only: two function
2506/// pointers with the same address are the same function. The converse
2507/// is not always guaranteed (the compiler may merge identical function
2508/// bodies), but this matches CPython's slot comparison semantics.
2509#[inline(always)]
2510pub(crate) fn fn_addr<T: Copy>(f: T) -> usize {
2511    assert!(
2512        core::mem::size_of::<T>() == core::mem::size_of::<usize>(),
2513        "fn_addr: T must be pointer-sized"
2514    );
2515    unsafe { core::mem::transmute_copy::<T, usize>(&f) }
2516}