Skip to main content

rustpython_vm/builtins/
descriptor.rs

1use super::{PyStr, PyStrInterned, PyTuple, PyType};
2use crate::{
3    AsObject, Context, Py, PyObject, PyObjectRef, PyPayload, PyRef, PyResult, VirtualMachine,
4    builtins::{PyTypeRef, builtin_func::PyNativeMethod, type_},
5    class::PyClassImpl,
6    common::hash::PyHash,
7    convert::{ToPyObject, ToPyResult},
8    function::{
9        Callee, FuncArgs, ItemDoc, PyMethodDef, PyMethodFlags, PySetterValue, PySsize, plain_doc,
10    },
11    protocol::{PyNumberBinaryFunc, PyNumberTernaryFunc, PyNumberUnaryFunc},
12    types::{
13        Callable, Comparable, DelFunc, DescrGetFunc, DescrSetFunc, GenericMethod, GetDescriptor,
14        GetattroFunc, HashFunc, Hashable, InitFunc, IterFunc, IterNextFunc, MapAssSubscriptFunc,
15        MapLenFunc, MapSubscriptFunc, PyComparisonOp, Representable, RichCompareFunc,
16        SeqAssItemFunc, SeqConcatFunc, SeqContainsFunc, SeqItemFunc, SeqLenFunc, SeqRepeatFunc,
17        SetattroFunc, StringifyFunc,
18    },
19};
20use core::mem::{align_of, size_of};
21use rustpython_common::lock::PyRwLock;
22
23#[derive(Debug)]
24pub struct PyDescriptor {
25    pub typ: &'static Py<PyType>,
26    pub name: &'static PyStrInterned,
27    pub qualname: PyRwLock<Option<String>>,
28}
29
30#[derive(Debug)]
31pub struct PyDescriptorOwned {
32    pub typ: PyRef<PyType>,
33    pub name: &'static PyStrInterned,
34    pub qualname: PyRwLock<Option<String>>,
35}
36
37#[pyclass(name = "method_descriptor", module = false)]
38pub struct PyMethodDescriptor {
39    #[pymember(name = "__objclass__", path = "typ")]
40    #[pymember(name = "__name__", path = "name")]
41    pub common: PyDescriptor,
42    pub method: &'static PyMethodDef,
43    // vectorcall: vector_call_func,
44    /// Prevent HeapMethodDef from being freed while this descriptor references it
45    pub(crate) _method_def_owner: Option<PyObjectRef>,
46}
47
48impl PyMethodDescriptor {
49    pub fn new(method: &'static PyMethodDef, typ: &'static Py<PyType>, ctx: &Context) -> Self {
50        Self {
51            common: PyDescriptor {
52                typ,
53                name: ctx.intern_str(method.name),
54                qualname: PyRwLock::new(None),
55            },
56            method,
57            _method_def_owner: None,
58        }
59    }
60}
61
62impl PyPayload for PyMethodDescriptor {
63    fn class(ctx: &Context) -> &'static Py<PyType> {
64        ctx.types.method_descriptor_type
65    }
66}
67
68impl core::fmt::Debug for PyMethodDescriptor {
69    fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
70        write!(f, "method descriptor for '{}'", self.common.name)
71    }
72}
73
74impl GetDescriptor for PyMethodDescriptor {
75    fn descr_get(
76        zelf: &PyObject,
77        obj: Option<&PyObject>,
78        cls: Option<&PyObject>,
79        vm: &VirtualMachine,
80    ) -> PyResult {
81        let descr = Self::_as_pyref(zelf, vm).unwrap();
82        let bound = match obj {
83            Some(obj) => {
84                method_descr_typecheck(descr, obj, vm)?;
85                if descr.method.flags.contains(PyMethodFlags::METHOD) {
86                    if cls
87                        .as_ref()
88                        .is_none_or(|c| c.fast_isinstance(vm.ctx.types.type_type))
89                    {
90                        obj.to_owned()
91                    } else {
92                        return Err(vm.new_type_error(format!(
93                            "descriptor '{}' needs a type, not '{}', as arg 2",
94                            descr.common.name.as_str(),
95                            obj.class().name()
96                        )));
97                    }
98                } else if descr.method.flags.contains(PyMethodFlags::CLASS) {
99                    obj.class().to_owned().into()
100                } else {
101                    obj.to_owned()
102                }
103            }
104            None if descr.method.flags.contains(PyMethodFlags::CLASS) => cls.unwrap().to_owned(),
105            None => return Ok(zelf.to_owned()),
106        };
107        Ok(descr.bind(bound, &vm.ctx).into())
108    }
109}
110
111impl Callable for PyMethodDescriptor {
112    type Args = FuncArgs;
113    #[inline]
114    fn call(zelf: &Py<Self>, args: FuncArgs, vm: &VirtualMachine) -> PyResult {
115        if let Some(obj) = args.args.first() {
116            method_descr_typecheck(zelf, obj, vm)?;
117        }
118        (zelf.method.func)(
119            vm,
120            args,
121            Callee::named(zelf.method.name).with_instance_arg(true),
122        )
123    }
124}
125
126impl PyMethodDescriptor {
127    pub fn bind(&self, obj: PyObjectRef, ctx: &Context) -> PyRef<PyNativeMethod> {
128        self.method.build_bound_method(ctx, obj, self.common.typ)
129    }
130}
131
132#[pyclass(
133    with(GetDescriptor, Callable, Representable),
134    flags(METHOD_DESCRIPTOR, DISALLOW_INSTANTIATION)
135)]
136impl Py<PyMethodDescriptor> {
137    #[pygetset]
138    fn __qualname__(&self) -> String {
139        format!("{}.{}", self.common.typ.name(), self.common.name)
140    }
141
142    #[pygetset]
143    fn __doc__(&self) -> Option<&'static str> {
144        type_::rendered_item_doc(self.method.name, self.method.item_doc())
145    }
146
147    #[pygetset]
148    fn __text_signature__(&self) -> Option<&'static str> {
149        let doc = self.method.doc?;
150        type_::get_text_signature_from_internal_doc(self.method.name, doc)
151    }
152
153    #[pymethod]
154    fn __reduce__(
155        &self,
156        vm: &VirtualMachine,
157    ) -> (Option<PyObjectRef>, (Option<PyObjectRef>, &'static str)) {
158        let builtins_getattr = vm.builtins.get_attr("getattr", vm).ok();
159        let classname = vm.builtins.get_attr(&self.common.typ.__name__(vm), vm).ok();
160        (builtins_getattr, (classname, self.method.name))
161    }
162}
163
164impl Representable for PyMethodDescriptor {
165    #[inline]
166    fn repr_str(zelf: &Py<Self>, _vm: &VirtualMachine) -> PyResult<String> {
167        Ok(format!(
168            "<method '{}' of '{}' objects>",
169            zelf.method.name,
170            zelf.common.typ.name()
171        ))
172    }
173}
174
175// METH_CLASS descriptors. Same layout as method_descriptor; a distinct type.
176#[pyclass(name = "classmethod_descriptor", module = false)]
177pub struct PyClassMethodDescriptor {
178    #[pymember(name = "__objclass__", path = "typ")]
179    #[pymember(name = "__name__", path = "name")]
180    pub common: PyDescriptor,
181    pub method: &'static PyMethodDef,
182    pub(crate) _method_def_owner: Option<PyObjectRef>,
183}
184
185impl PyClassMethodDescriptor {
186    pub fn new(method: &'static PyMethodDef, typ: &'static Py<PyType>, ctx: &Context) -> Self {
187        Self {
188            common: PyDescriptor {
189                typ,
190                name: ctx.intern_str(method.name),
191                qualname: PyRwLock::new(None),
192            },
193            method,
194            _method_def_owner: None,
195        }
196    }
197
198    pub fn bind(&self, obj: PyObjectRef, ctx: &Context) -> PyRef<PyNativeMethod> {
199        self.method.build_bound_method(ctx, obj, self.common.typ)
200    }
201}
202
203impl PyPayload for PyClassMethodDescriptor {
204    fn class(ctx: &Context) -> &'static Py<PyType> {
205        ctx.types.classmethod_descriptor_type
206    }
207}
208
209impl core::fmt::Debug for PyClassMethodDescriptor {
210    fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
211        write!(f, "classmethod descriptor for '{}'", self.common.name)
212    }
213}
214
215impl GetDescriptor for PyClassMethodDescriptor {
216    fn descr_get(
217        zelf: &PyObject,
218        obj: Option<&PyObject>,
219        cls: Option<&PyObject>,
220        vm: &VirtualMachine,
221    ) -> PyResult {
222        let descr = Self::_as_pyref(zelf, vm).unwrap();
223        let type_obj = match cls {
224            Some(typ) => typ.to_owned(),
225            None => match obj {
226                Some(o) => o.class().to_owned().into(),
227                None => {
228                    return Err(vm.new_type_error(format!(
229                        "descriptor '{}' for type '{}' needs either an object or a type",
230                        descr.common.name,
231                        descr.common.typ.name()
232                    )));
233                }
234            },
235        };
236        if !type_obj.fast_isinstance(vm.ctx.types.type_type) {
237            return Err(vm.new_type_error(format!(
238                "descriptor '{}' for type '{}' needs a type, not '{}' as arg 2",
239                descr.common.name,
240                descr.common.typ.name(),
241                type_obj.class().name()
242            )));
243        }
244        let typ = type_obj.downcast::<PyType>().map_err(|obj| {
245            vm.new_type_error(format!(
246                "descriptor '{}' for type '{}' needs a type, not '{}' as arg 2",
247                descr.common.name,
248                descr.common.typ.name(),
249                obj.class().name()
250            ))
251        })?;
252        if !typ.fast_issubclass(descr.common.typ) {
253            return Err(vm.new_type_error(format!(
254                "descriptor '{}' requires a subtype of '{}' but received '{}'",
255                descr.common.name,
256                descr.common.typ.name(),
257                typ.name()
258            )));
259        }
260        Ok(descr.bind(typ.into(), &vm.ctx).into())
261    }
262}
263
264impl Callable for PyClassMethodDescriptor {
265    type Args = FuncArgs;
266    fn call(zelf: &Py<Self>, mut args: FuncArgs, vm: &VirtualMachine) -> PyResult {
267        let Some(owner) = args.args.first().cloned() else {
268            return Err(vm.new_type_error(format!(
269                "descriptor '{}' of '{}' object needs an argument",
270                zelf.method.name,
271                zelf.common.typ.name()
272            )));
273        };
274        let bound = Self::descr_get(zelf.as_object(), None, Some(&owner), vm)?;
275        args.args.remove(0);
276        bound.call(args, vm)
277    }
278}
279
280#[pyclass(
281    with(GetDescriptor, Callable, Representable),
282    flags(DISALLOW_INSTANTIATION)
283)]
284impl Py<PyClassMethodDescriptor> {
285    #[pygetset]
286    fn __qualname__(&self) -> String {
287        format!("{}.{}", self.common.typ.name(), self.common.name)
288    }
289
290    #[pygetset]
291    fn __doc__(&self) -> Option<&'static str> {
292        type_::rendered_item_doc(self.method.name, self.method.item_doc())
293    }
294
295    #[pygetset]
296    fn __text_signature__(&self) -> Option<&'static str> {
297        let doc = self.method.doc?;
298        type_::get_text_signature_from_internal_doc(self.method.name, doc)
299    }
300}
301
302impl Representable for PyClassMethodDescriptor {
303    #[inline]
304    fn repr_str(zelf: &Py<Self>, _vm: &VirtualMachine) -> PyResult<String> {
305        Ok(format!(
306            "<method '{}' of '{}' objects>",
307            zelf.method.name,
308            zelf.common.typ.name()
309        ))
310    }
311}
312
313/// Member type. Discriminants match `PyMemberDef.type`.
314#[derive(Copy, Clone, Debug, Eq, PartialEq)]
315#[repr(i32)]
316pub enum MemberKind {
317    /// `short`. Writable cells are `AtomicI16`.
318    Short = 0,
319    Int = 1,
320    /// `long` (`c_long`). Writable cells are the atomic of that width.
321    Long = 2,
322    Double = 4,
323    /// `char *`. The slot is a pointer to a NUL-terminated string. Null loads
324    /// as `None`. Assignment is rejected.
325    String = 5,
326    Object = 6,
327    /// `unsigned char`. Writable cells are `AtomicU8`.
328    UByte = 9,
329    /// `unsigned short`. Writable cells are `AtomicU16`.
330    UShort = 10,
331    Uint = 11,
332    /// `unsigned long` (`c_ulong`). Writable cells are the atomic of that width.
333    ULong = 12,
334    Bool = 14,
335    ObjectEx = 16,
336    /// `long long`. Writable cells are `AtomicI64`.
337    LongLong = 17,
338    /// `unsigned long long`. Writable cells are `AtomicU64`.
339    ULongLong = 18,
340    /// `Py_ssize_t`. Writable cells are `AtomicIsize`.
341    PySsizeT = 19,
342}
343
344impl MemberKind {
345    const fn from_i64() -> Self {
346        if size_of::<core::ffi::c_long>() == 8 {
347            Self::Long
348        } else {
349            Self::LongLong
350        }
351    }
352
353    const fn from_u64() -> Self {
354        if size_of::<core::ffi::c_ulong>() == 8 {
355            Self::ULong
356        } else {
357            Self::ULongLong
358        }
359    }
360
361    #[must_use]
362    pub fn from_i32(value: i32) -> Option<Self> {
363        match value {
364            0 => Some(Self::Short),
365            1 => Some(Self::Int),
366            2 => Some(Self::Long),
367            4 => Some(Self::Double),
368            5 => Some(Self::String),
369            6 => Some(Self::Object),
370            9 => Some(Self::UByte),
371            10 => Some(Self::UShort),
372            11 => Some(Self::Uint),
373            12 => Some(Self::ULong),
374            14 => Some(Self::Bool),
375            16 => Some(Self::ObjectEx),
376            17 => Some(Self::LongLong),
377            18 => Some(Self::ULongLong),
378            19 => Some(Self::PySsizeT),
379            _ => None,
380        }
381    }
382}
383
384bitflags::bitflags! {
385    #[derive(Copy, Clone, Debug, PartialEq, Eq)]
386    #[repr(transparent)]
387    pub struct PyMemberFlags: i32 {
388        const READONLY = 1;
389        const AUDIT_READ = 2;
390        // `_Py_WRITE_RESTRICTED` (4) is deprecated. The bit is reserved and must not be reused.
391        const RELATIVE_OFFSET = 8;
392        /// The field is atomic storage (`Atomic*` or `AtomicPyTypeFlags`).
393        ///
394        /// Not a public member flag. The defined flags are `Py_READONLY` (1),
395        /// `Py_AUDIT_READ` (2), `_Py_WRITE_RESTRICTED` (4, deprecated, do not reuse),
396        /// and `Py_RELATIVE_OFFSET` (8). This bit is set only by `#[pymember]`.
397        /// Extension members leave it clear: a readonly extension member is a plain
398        /// load, and a writable one keeps an atomic access.
399        const ATOMIC = 0x10;
400    }
401}
402
403/// Kind of a `#[pymember]` field. The macro reads [`MemberLayout::KIND`].
404#[doc(hidden)]
405pub trait MemberLayout {
406    const KIND: MemberKind;
407    /// `true` when the field is an atomic cell, not a plain integer or pointer.
408    const ATOMIC: bool = false;
409}
410
411/// Writable `#[pymember]` field. Only an atomic cell may change after publication.
412#[doc(hidden)]
413pub trait MemberCell: MemberLayout {}
414
415/// `KIND` of the field named by `probe`. `probe` is not called.
416#[doc(hidden)]
417#[must_use]
418pub const fn member_kind_of<T: MemberLayout, Owner>(
419    _: for<'a> fn(&'a Owner) -> &'a T,
420) -> MemberKind {
421    <T as MemberLayout>::KIND
422}
423
424/// [`MemberLayout::ATOMIC`] of the field named by `probe`. `probe` is not called.
425#[doc(hidden)]
426#[must_use]
427pub const fn member_atomic_of<T: MemberLayout, Owner>(_: for<'a> fn(&'a Owner) -> &'a T) -> bool {
428    <T as MemberLayout>::ATOMIC
429}
430
431impl MemberLayout for i16 {
432    const KIND: MemberKind = MemberKind::Short;
433}
434impl MemberLayout for core::sync::atomic::AtomicI16 {
435    const KIND: MemberKind = MemberKind::Short;
436    const ATOMIC: bool = true;
437}
438impl MemberCell for core::sync::atomic::AtomicI16 {}
439
440impl MemberLayout for u16 {
441    const KIND: MemberKind = MemberKind::UShort;
442}
443impl MemberLayout for core::sync::atomic::AtomicU16 {
444    const KIND: MemberKind = MemberKind::UShort;
445    const ATOMIC: bool = true;
446}
447impl MemberCell for core::sync::atomic::AtomicU16 {}
448
449impl MemberLayout for u8 {
450    const KIND: MemberKind = MemberKind::UByte;
451}
452impl MemberLayout for core::sync::atomic::AtomicU8 {
453    const KIND: MemberKind = MemberKind::UByte;
454    const ATOMIC: bool = true;
455}
456impl MemberCell for core::sync::atomic::AtomicU8 {}
457
458// `i64` is `long` where `c_long` is 8 bytes, and `long long` otherwise.
459impl MemberLayout for i64 {
460    const KIND: MemberKind = MemberKind::from_i64();
461}
462impl MemberLayout for core::sync::atomic::AtomicI64 {
463    const KIND: MemberKind = MemberKind::from_i64();
464    const ATOMIC: bool = true;
465}
466impl MemberCell for core::sync::atomic::AtomicI64 {}
467
468impl MemberLayout for u64 {
469    const KIND: MemberKind = MemberKind::from_u64();
470}
471impl MemberLayout for core::sync::atomic::AtomicU64 {
472    const KIND: MemberKind = MemberKind::from_u64();
473    const ATOMIC: bool = true;
474}
475impl MemberCell for core::sync::atomic::AtomicU64 {}
476
477/// Readonly `char *`. The word is the pointer, not the characters.
478#[repr(transparent)]
479#[derive(Copy, Clone)]
480pub struct CStrMember(*const core::ffi::c_char);
481
482impl core::fmt::Debug for CStrMember {
483    fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
484        f.debug_struct("CStrMember").field("ptr", &self.0).finish()
485    }
486}
487
488impl CStrMember {
489    #[must_use]
490    pub const fn new(ptr: *const core::ffi::c_char) -> Self {
491        Self(ptr)
492    }
493
494    #[must_use]
495    pub const fn as_ptr(self) -> *const core::ffi::c_char {
496        self.0
497    }
498}
499
500// The pointer is not dereferenced by moving the wrapper.
501unsafe impl Send for CStrMember {}
502unsafe impl Sync for CStrMember {}
503
504const _: () = assert!(
505    size_of::<CStrMember>() == size_of::<*const core::ffi::c_char>()
506        && align_of::<CStrMember>() == align_of::<*const core::ffi::c_char>()
507);
508
509impl MemberLayout for CStrMember {
510    const KIND: MemberKind = MemberKind::String;
511}
512
513impl MemberLayout for bool {
514    const KIND: MemberKind = MemberKind::Bool;
515}
516impl MemberLayout for core::sync::atomic::AtomicBool {
517    const KIND: MemberKind = MemberKind::Bool;
518    const ATOMIC: bool = true;
519}
520impl MemberCell for core::sync::atomic::AtomicBool {}
521
522impl MemberLayout for i32 {
523    const KIND: MemberKind = MemberKind::Int;
524}
525impl MemberLayout for core::sync::atomic::AtomicI32 {
526    const KIND: MemberKind = MemberKind::Int;
527    const ATOMIC: bool = true;
528}
529impl MemberCell for core::sync::atomic::AtomicI32 {}
530
531impl MemberLayout for u32 {
532    const KIND: MemberKind = MemberKind::Uint;
533}
534impl MemberLayout for core::sync::atomic::AtomicU32 {
535    const KIND: MemberKind = MemberKind::Uint;
536    const ATOMIC: bool = true;
537}
538impl MemberCell for core::sync::atomic::AtomicU32 {}
539
540// One object pointer. Readonly members may be a plain pointer. Writable
541// members are an atomic cell: `PyAtomicRef<PyObject>` when the pointer is
542// never null, or `PyAtomicRef<Option<PyObject>>` when it may be.
543impl MemberLayout for PyObjectRef {
544    const KIND: MemberKind = MemberKind::Object;
545}
546impl MemberLayout for Option<PyObjectRef> {
547    const KIND: MemberKind = MemberKind::Object;
548}
549impl MemberLayout for crate::object::PyObjectCell {
550    const KIND: MemberKind = MemberKind::Object;
551    const ATOMIC: bool = true;
552}
553impl<T> MemberLayout for PyRef<T> {
554    const KIND: MemberKind = MemberKind::Object;
555}
556impl<T> MemberLayout for Option<PyRef<T>> {
557    const KIND: MemberKind = MemberKind::Object;
558}
559impl MemberLayout for crate::object::PyAtomicRef<PyObject> {
560    const KIND: MemberKind = MemberKind::Object;
561    const ATOMIC: bool = true;
562}
563// `PyAtomicRef<T>` is the same pointer-sized cell as `PyAtomicRef<PyObject>`
564// (`PhantomData<T>` is zero-sized). Readonly object members may use it. The
565// getter loads the slot as an object pointer. Writable cells stay
566// `PyAtomicRef<PyObject>` or `PyAtomicRef<Option<PyObject>>` (`MemberCell`).
567impl<T: PyPayload> MemberLayout for crate::object::PyAtomicRef<T> {
568    const KIND: MemberKind = MemberKind::Object;
569    const ATOMIC: bool = true;
570}
571impl MemberLayout for crate::object::PyAtomicRef<Option<PyObject>> {
572    const KIND: MemberKind = MemberKind::Object;
573    const ATOMIC: bool = true;
574}
575impl<T: PyPayload> MemberLayout for crate::object::PyAtomicRef<Option<T>> {
576    const KIND: MemberKind = MemberKind::Object;
577    const ATOMIC: bool = true;
578}
579impl MemberLayout for &'static crate::builtins::PyStrInterned {
580    const KIND: MemberKind = MemberKind::Object;
581}
582impl<T: PyPayload> MemberLayout for &'static Py<T> {
583    const KIND: MemberKind = MemberKind::Object;
584}
585impl MemberCell for crate::object::PyAtomicRef<PyObject> {}
586impl MemberCell for crate::object::PyAtomicRef<Option<PyObject>> {}
587
588impl MemberLayout for f64 {
589    const KIND: MemberKind = MemberKind::Double;
590}
591impl MemberLayout for crate::common::atomic::AtomicF64 {
592    const KIND: MemberKind = MemberKind::Double;
593    const ATOMIC: bool = true;
594}
595impl MemberCell for crate::common::atomic::AtomicF64 {}
596
597impl MemberLayout for isize {
598    const KIND: MemberKind = MemberKind::PySsizeT;
599}
600impl MemberLayout for core::sync::atomic::AtomicIsize {
601    const KIND: MemberKind = MemberKind::PySsizeT;
602    const ATOMIC: bool = true;
603}
604impl MemberCell for core::sync::atomic::AtomicIsize {}
605
606// `usize` and `isize` are the same width. A readonly `Py_ssize_t` member may
607// be a `usize` that is not written after publication and whose value fits in
608// `isize` (the getter reads the bits as `isize`).
609const _: () =
610    assert!(size_of::<usize>() == size_of::<isize>() && align_of::<usize>() == align_of::<isize>());
611impl MemberLayout for usize {
612    const KIND: MemberKind = MemberKind::PySsizeT;
613}
614
615// Readonly `co_filename`. The field is `AtomicPtr<PyStrInterned>`: the pointer
616// is never null, and `PyStrInterned` is `repr(transparent)` over `Py<PyStr>`,
617// so the word is an object pointer. `member_get_one` reads an Object member
618// through `PyAtomicRef<Option<PyObject>>` (`get_slot`). That cell is
619// `AtomicPtr<u8>` when `threading` is on and `Cell<*mut u8>` otherwise; both
620// are one pointer, and `AtomicPtr<PyStrInterned>` is one pointer in either
621// build (`Atomic<*mut T>` stores `Align8<*mut T>`, same size and alignment as
622// the pointer). The load copies that word and increfs. Interned strings live
623// for the process, and the slot does not own the reference.
624const _: () = assert!(
625    size_of::<core::sync::atomic::AtomicPtr<PyStrInterned>>()
626        == size_of::<crate::object::PyAtomicRef<Option<PyObject>>>()
627        && align_of::<core::sync::atomic::AtomicPtr<PyStrInterned>>()
628            == align_of::<crate::object::PyAtomicRef<Option<PyObject>>>()
629);
630impl MemberLayout for core::sync::atomic::AtomicPtr<PyStrInterned> {
631    const KIND: MemberKind = MemberKind::Object;
632    const ATOMIC: bool = true;
633}
634
635/// Where `PyMemberDef.offset` points.
636///
637/// `Offset` is a byte offset from the object to the field.
638/// `TupleItem` is a struct-sequence element index: the elements live in the
639/// tuple payload, not as separately addressable fields.
640#[derive(Clone, Copy, Debug)]
641pub enum MemberAccess {
642    Offset,
643    TupleItem,
644}
645
646/// C layout of `PyMemberDef`: name pointer, int type, `Py_ssize_t` offset,
647/// int flags, doc pointer. `size_of` of this is `type`'s `tp_itemsize`.
648#[repr(C)]
649#[allow(dead_code)]
650pub struct PyMemberDefLayout {
651    _name: *const core::ffi::c_char,
652    _type: core::ffi::c_int,
653    _offset: isize,
654    _flags: PyMemberFlags,
655    _doc: *const core::ffi::c_char,
656}
657
658const _: () = assert!(
659    size_of::<PyMemberFlags>() == size_of::<core::ffi::c_int>()
660        && align_of::<PyMemberFlags>() == align_of::<core::ffi::c_int>()
661);
662
663/// Same fields as `PyMemberDef`: name, type, offset, flags, doc.
664pub struct PyMemberDef {
665    pub name: String,
666    pub kind: MemberKind,
667    pub offset: isize,
668    pub flags: PyMemberFlags,
669    pub doc: ItemDoc,
670}
671
672impl PyMemberDef {
673    pub(crate) fn readonly(&self) -> bool {
674        self.flags.contains(PyMemberFlags::READONLY)
675    }
676
677    /// Atomic load when the field is atomic storage. Writable members are
678    /// stored as cells even when an extension did not set [`PyMemberFlags::ATOMIC`].
679    pub(crate) fn atomic_storage(&self) -> bool {
680        self.flags.contains(PyMemberFlags::ATOMIC) || !self.readonly()
681    }
682
683    pub(crate) fn audit_read(&self) -> bool {
684        self.flags.contains(PyMemberFlags::AUDIT_READ)
685    }
686}
687
688impl core::fmt::Debug for PyMemberDef {
689    fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
690        f.debug_struct("PyMemberDef")
691            .field("name", &self.name)
692            .field("kind", &self.kind)
693            .field("offset", &self.offset)
694            .field("flags", &self.flags)
695            .field("doc", &self.doc)
696            .finish()
697    }
698}
699
700/// Const-constructible member spec. Registered as a `PyMemberDef`.
701#[derive(Clone, Copy)]
702pub struct PyMemberSpec {
703    pub name: &'static str,
704    pub kind: MemberKind,
705    pub offset: isize,
706    pub flags: PyMemberFlags,
707    pub doc: ItemDoc,
708}
709
710impl PyMemberSpec {
711    /// Concatenate cfg-gated member groups into one table.
712    #[must_use]
713    pub const fn concat<const N: usize>(parts: &[&[Self]]) -> [Self; N] {
714        const EMPTY: PyMemberSpec = PyMemberSpec {
715            name: "",
716            kind: MemberKind::Object,
717            offset: 0,
718            flags: PyMemberFlags::empty(),
719            doc: ItemDoc::NONE,
720        };
721        let mut out = [EMPTY; N];
722        let mut index = 0;
723        let mut part_index = 0;
724        while part_index < parts.len() {
725            let part = parts[part_index];
726            let mut item_index = 0;
727            while item_index < part.len() {
728                out[index] = part[item_index];
729                index += 1;
730                item_index += 1;
731            }
732            part_index += 1;
733        }
734        out
735    }
736}
737
738// = PyMemberDescrObject
739#[pyclass(name = "member_descriptor", module = false)]
740#[derive(Debug)]
741pub struct PyMemberDescriptor {
742    #[pymember(name = "__objclass__", path = "typ")]
743    #[pymember(name = "__name__", path = "name")]
744    pub common: PyDescriptorOwned,
745    pub member: PyMemberDef,
746    pub access: MemberAccess,
747}
748
749impl PyMemberDescriptor {
750    /// Byte offset of an object-pointer member. `None` for bool, float, and
751    /// struct-sequence indexes, which slot specialization must not treat as cells.
752    pub(crate) fn slot_offset(&self) -> Option<isize> {
753        if matches!(self.access, MemberAccess::TupleItem) {
754            return None;
755        }
756        match self.member.kind {
757            MemberKind::Object | MemberKind::ObjectEx => Some(self.member.offset),
758            MemberKind::Bool
759            | MemberKind::Double
760            | MemberKind::Int
761            | MemberKind::Long
762            | MemberKind::LongLong
763            | MemberKind::Short
764            | MemberKind::String
765            | MemberKind::UByte
766            | MemberKind::Uint
767            | MemberKind::ULong
768            | MemberKind::ULongLong
769            | MemberKind::UShort
770            | MemberKind::PySsizeT => None,
771        }
772    }
773
774    fn get(&self, obj: PyObjectRef, vm: &VirtualMachine) -> PyResult {
775        if self.member.audit_read() {
776            vm.audit("object.__getattr__", || {
777                (obj.clone(), vm.ctx.new_str(self.member.name.as_str()))
778            })?;
779        }
780        match self.access {
781            MemberAccess::Offset => member_get_one(&obj, self.member.offset, &self.member, vm),
782            MemberAccess::TupleItem => {
783                let index = self.member.offset as usize;
784                let tuple = obj.downcast_ref::<PyTuple>().ok_or_else(|| {
785                    vm.new_type_error("unexpected payload for struct sequence member")
786                })?;
787                tuple
788                    .as_slice()
789                    .get(index)
790                    .cloned()
791                    .ok_or_else(|| vm.new_index_error(format!("tuple index {index} out of range")))
792            }
793        }
794    }
795
796    fn set(
797        &self,
798        obj: PyObjectRef,
799        value: PySetterValue<PyObjectRef>,
800        vm: &VirtualMachine,
801    ) -> PyResult<()> {
802        if self.member.readonly() {
803            return Err(vm.new_attribute_error("readonly attribute"));
804        }
805        match self.access {
806            MemberAccess::Offset => {
807                member_set_one(&obj, self.member.offset, &self.member, value, vm)
808            }
809            MemberAccess::TupleItem => Err(vm.new_attribute_error("readonly attribute")),
810        }
811    }
812}
813
814impl PyPayload for PyMemberDescriptor {
815    fn class(ctx: &Context) -> &'static Py<PyType> {
816        ctx.types.member_descriptor_type
817    }
818}
819
820fn calculate_qualname(descr: &PyDescriptorOwned, vm: &VirtualMachine) -> PyResult<Option<String>> {
821    if let Some(qualname) = vm.get_attribute_opt(descr.typ.as_object(), "__qualname__")? {
822        let str = qualname.downcast::<PyStr>().map_err(|_| {
823            vm.new_type_error("<descriptor>.__objclass__.__qualname__ is not a unicode object")
824        })?;
825        Ok(Some(format!("{}.{}", str, descr.name)))
826    } else {
827        Ok(None)
828    }
829}
830
831#[pyclass(with(GetDescriptor, Representable), flags(DISALLOW_INSTANTIATION))]
832impl Py<PyMemberDescriptor> {
833    #[pygetset]
834    fn __doc__(&self) -> Option<&'static str> {
835        plain_doc(self.member.doc)
836    }
837
838    #[pygetset]
839    fn __qualname__(&self, vm: &VirtualMachine) -> PyResult<Option<String>> {
840        let qualname = self.common.qualname.read();
841        Ok(if qualname.is_none() {
842            drop(qualname);
843            let calculated = calculate_qualname(&self.common, vm)?;
844            calculated.clone_into(&mut self.common.qualname.write());
845            calculated
846        } else {
847            qualname.to_owned()
848        })
849    }
850
851    #[pymethod]
852    fn __reduce__(&self, vm: &VirtualMachine) -> PyResult {
853        let builtins_getattr = vm.builtins.get_attr("getattr", vm)?;
854        Ok(vm
855            .ctx
856            .new_tuple(vec![
857                builtins_getattr,
858                vm.ctx
859                    .new_tuple(vec![
860                        self.common.typ.clone().into(),
861                        vm.ctx.new_str(self.common.name.as_str()).into(),
862                    ])
863                    .into(),
864            ])
865            .into())
866    }
867
868    #[pyslot]
869    fn descr_set(
870        zelf: &PyObject,
871        obj: PyObjectRef,
872        value: PySetterValue<PyObjectRef>,
873        vm: &VirtualMachine,
874    ) -> PyResult<()> {
875        let zelf = PyMemberDescriptor::_as_pyref(zelf, vm)?;
876
877        if !obj.class().fast_issubclass(&zelf.common.typ) {
878            return Err(vm.new_type_error(format!(
879                "descriptor '{}' for '{}' objects doesn't apply to a '{}' object",
880                zelf.common.name,
881                zelf.common.typ.name(),
882                obj.class().name()
883            )));
884        }
885
886        zelf.set(obj, value, vm)
887    }
888}
889
890fn member_addr(obj: &PyObject, offset: isize) -> *mut u8 {
891    (obj as *const PyObject as *const u8).wrapping_add(offset as usize) as *mut u8
892}
893
894fn warn_member(vm: &VirtualMachine, message: &str) -> PyResult<()> {
895    crate::warn::warn(
896        vm.ctx.new_str(message).into(),
897        Some(vm.ctx.exceptions.runtime_warning.to_owned()),
898        1,
899        None,
900        vm,
901    )
902}
903
904fn load_i32(obj: &PyObject, offset: isize, atomic: bool) -> i32 {
905    let addr = member_addr(obj, offset);
906    if atomic {
907        // SAFETY: `PyMemberFlags::ATOMIC` or a writable member addresses an aligned `AtomicI32`.
908        unsafe {
909            (*addr.cast::<core::sync::atomic::AtomicI32>())
910                .load(core::sync::atomic::Ordering::Relaxed)
911        }
912    } else {
913        // SAFETY: a plain int member addresses an `i32` that is not written
914        // after publication. The field may be only 4-byte aligned.
915        unsafe { addr.cast::<i32>().read() }
916    }
917}
918
919fn load_i16(obj: &PyObject, offset: isize, atomic: bool) -> i16 {
920    let addr = member_addr(obj, offset);
921    if atomic {
922        // SAFETY: `PyMemberFlags::ATOMIC` or a writable member addresses an aligned `AtomicI16`.
923        unsafe {
924            (*addr.cast::<core::sync::atomic::AtomicI16>())
925                .load(core::sync::atomic::Ordering::Relaxed)
926        }
927    } else {
928        // SAFETY: a plain short member addresses an `i16` that is not written
929        // after publication.
930        unsafe { addr.cast::<i16>().read() }
931    }
932}
933
934fn load_u16(obj: &PyObject, offset: isize, atomic: bool) -> u16 {
935    let addr = member_addr(obj, offset);
936    if atomic {
937        // SAFETY: `PyMemberFlags::ATOMIC` or a writable member addresses an aligned `AtomicU16`.
938        unsafe {
939            (*addr.cast::<core::sync::atomic::AtomicU16>())
940                .load(core::sync::atomic::Ordering::Relaxed)
941        }
942    } else {
943        // SAFETY: a plain unsigned short member addresses a `u16` that is not
944        // written after publication.
945        unsafe { addr.cast::<u16>().read() }
946    }
947}
948
949fn load_u8(obj: &PyObject, offset: isize, atomic: bool) -> u8 {
950    let addr = member_addr(obj, offset);
951    if atomic {
952        // SAFETY: `PyMemberFlags::ATOMIC` or a writable member addresses an `AtomicU8`.
953        unsafe {
954            (*addr.cast::<core::sync::atomic::AtomicU8>())
955                .load(core::sync::atomic::Ordering::Relaxed)
956        }
957    } else {
958        // SAFETY: a readonly unsigned char member addresses a `u8` that is not
959        // written after publication.
960        unsafe { addr.cast::<u8>().read() }
961    }
962}
963
964fn load_i64(obj: &PyObject, offset: isize, atomic: bool) -> i64 {
965    let addr = member_addr(obj, offset);
966    if atomic {
967        // SAFETY: `PyMemberFlags::ATOMIC` or a writable member addresses an aligned `AtomicI64`.
968        unsafe {
969            (*addr.cast::<core::sync::atomic::AtomicI64>())
970                .load(core::sync::atomic::Ordering::Relaxed)
971        }
972    } else {
973        // SAFETY: a plain `long long` member addresses an `i64` that is not
974        // written after publication. The field may be only 4-byte aligned.
975        unsafe { addr.cast::<i64>().read() }
976    }
977}
978
979fn load_u64(obj: &PyObject, offset: isize, atomic: bool) -> u64 {
980    let addr = member_addr(obj, offset);
981    if atomic {
982        // SAFETY: `PyMemberFlags::ATOMIC` or a writable member addresses an aligned `AtomicU64`.
983        unsafe {
984            (*addr.cast::<core::sync::atomic::AtomicU64>())
985                .load(core::sync::atomic::Ordering::Relaxed)
986        }
987    } else {
988        // SAFETY: a plain `unsigned long long` member addresses a `u64` that is
989        // not written after publication. The field may be only 4-byte aligned,
990        // so this is a plain read and not an `AtomicU64` access.
991        unsafe { addr.cast::<u64>().read() }
992    }
993}
994
995#[allow(clippy::unnecessary_cast)] // `c_long` is `i32` or `i64`
996fn load_c_long(obj: &PyObject, offset: isize, atomic: bool) -> core::ffi::c_long {
997    let addr = member_addr(obj, offset);
998    if !atomic {
999        // SAFETY: a plain `long` member addresses a `c_long` that is not
1000        // written after publication.
1001        unsafe { addr.cast::<core::ffi::c_long>().read() }
1002    } else if size_of::<core::ffi::c_long>() == 8 {
1003        // SAFETY: a writable `long` member addresses an aligned `AtomicI64`.
1004        // `c_long` is 8 bytes on this target.
1005        unsafe {
1006            (*addr.cast::<core::sync::atomic::AtomicI64>())
1007                .load(core::sync::atomic::Ordering::Relaxed) as core::ffi::c_long
1008        }
1009    } else {
1010        // SAFETY: a writable `long` member addresses an aligned `AtomicI32`.
1011        // `c_long` is 4 bytes on this target.
1012        unsafe {
1013            (*addr.cast::<core::sync::atomic::AtomicI32>())
1014                .load(core::sync::atomic::Ordering::Relaxed) as core::ffi::c_long
1015        }
1016    }
1017}
1018
1019#[allow(clippy::unnecessary_cast)] // `c_long` is `i32` or `i64`
1020fn store_c_long(obj: &PyObject, offset: isize, value: core::ffi::c_long) {
1021    let addr = member_addr(obj, offset);
1022    if size_of::<core::ffi::c_long>() == 8 {
1023        // SAFETY: a writable `long` member addresses an aligned `AtomicI64`.
1024        // `c_long` is 8 bytes on this target.
1025        unsafe {
1026            (*addr.cast::<core::sync::atomic::AtomicI64>())
1027                .store(value as i64, core::sync::atomic::Ordering::Relaxed);
1028        }
1029    } else {
1030        // SAFETY: a writable `long` member addresses an aligned `AtomicI32`.
1031        // `c_long` is 4 bytes on this target.
1032        unsafe {
1033            (*addr.cast::<core::sync::atomic::AtomicI32>())
1034                .store(value as i32, core::sync::atomic::Ordering::Relaxed);
1035        }
1036    }
1037}
1038
1039#[allow(clippy::unnecessary_cast)] // `c_ulong` is `u32` or `u64`
1040fn load_c_ulong(obj: &PyObject, offset: isize, atomic: bool) -> core::ffi::c_ulong {
1041    let addr = member_addr(obj, offset);
1042    if !atomic {
1043        // SAFETY: a plain `unsigned long` member addresses a `c_ulong` that is
1044        // not written after publication. A plain integer may be only 4-byte
1045        // aligned, so this is not an atomic access.
1046        unsafe { addr.cast::<core::ffi::c_ulong>().read() }
1047    } else if size_of::<core::ffi::c_ulong>() == 8 {
1048        // SAFETY: a writable `unsigned long` member addresses an aligned `AtomicU64`.
1049        // `c_ulong` is 8 bytes on this target.
1050        unsafe {
1051            (*addr.cast::<core::sync::atomic::AtomicU64>())
1052                .load(core::sync::atomic::Ordering::Relaxed) as core::ffi::c_ulong
1053        }
1054    } else {
1055        // SAFETY: a writable `unsigned long` member addresses an aligned `AtomicU32`.
1056        // `c_ulong` is 4 bytes on this target.
1057        unsafe {
1058            (*addr.cast::<core::sync::atomic::AtomicU32>())
1059                .load(core::sync::atomic::Ordering::Relaxed) as core::ffi::c_ulong
1060        }
1061    }
1062}
1063
1064#[allow(clippy::unnecessary_cast)] // `c_ulong` is `u32` or `u64`
1065fn store_c_ulong(obj: &PyObject, offset: isize, value: core::ffi::c_ulong) {
1066    let addr = member_addr(obj, offset);
1067    if size_of::<core::ffi::c_ulong>() == 8 {
1068        // SAFETY: a writable `unsigned long` member addresses an aligned `AtomicU64`.
1069        // `c_ulong` is 8 bytes on this target.
1070        unsafe {
1071            (*addr.cast::<core::sync::atomic::AtomicU64>())
1072                .store(value as u64, core::sync::atomic::Ordering::Relaxed);
1073        }
1074    } else {
1075        // SAFETY: a writable `unsigned long` member addresses an aligned `AtomicU32`.
1076        // `c_ulong` is 4 bytes on this target.
1077        unsafe {
1078            (*addr.cast::<core::sync::atomic::AtomicU32>())
1079                .store(value as u32, core::sync::atomic::Ordering::Relaxed);
1080        }
1081    }
1082}
1083
1084fn load_u32(obj: &PyObject, offset: isize, atomic: bool) -> u32 {
1085    let addr = member_addr(obj, offset);
1086    if atomic {
1087        // SAFETY: `PyMemberFlags::ATOMIC` or a writable member addresses an aligned `AtomicU32`.
1088        unsafe {
1089            (*addr.cast::<core::sync::atomic::AtomicU32>())
1090                .load(core::sync::atomic::Ordering::Relaxed)
1091        }
1092    } else {
1093        // SAFETY: a plain uint member addresses a `u32` that is not written
1094        // after publication.
1095        unsafe { addr.cast::<u32>().read() }
1096    }
1097}
1098
1099fn member_as_c_long(value: &PyObject, vm: &VirtualMachine) -> PyResult<core::ffi::c_long> {
1100    let int_obj = value.try_index(vm)?;
1101    core::ffi::c_long::try_from(int_obj.as_bigint())
1102        .map_err(|_| vm.new_overflow_error("Python int too large to convert to C long"))
1103}
1104
1105fn member_uint_value(
1106    value: &PyObject,
1107    vm: &VirtualMachine,
1108) -> PyResult<(u32, Option<&'static str>)> {
1109    let int_obj = value.try_index(vm)?;
1110    let big = int_obj.as_bigint();
1111    if big.sign() == malachite_bigint::Sign::Minus {
1112        let long_val = core::ffi::c_long::try_from(big)
1113            .map_err(|_| vm.new_overflow_error("Python int too large to convert to C long"))?;
1114        // Keeps the low 32 bits whether `c_long` is 32 or 64 bits wide.
1115        let stored = long_val as u32;
1116        return Ok((stored, Some("Writing negative value into unsigned field")));
1117    }
1118    core::ffi::c_ulong::try_from(big)
1119        .map_err(|_| vm.new_overflow_error("Python int too large to convert to C unsigned long"))?;
1120    let wide = u64::try_from(big).expect("a value that fits c_ulong fits u64");
1121    let stored = wide as u32;
1122    let warning = (wide > u64::from(u32::MAX)).then_some("Truncation of value to unsigned int");
1123    Ok((stored, warning))
1124}
1125
1126// PyMember_GetOne. `offset` is a byte offset from the object to the field.
1127fn member_get_one(
1128    obj: &PyObject,
1129    offset: isize,
1130    member: &PyMemberDef,
1131    vm: &VirtualMachine,
1132) -> PyResult {
1133    let value = match member.kind {
1134        MemberKind::Object => obj.get_slot(offset).unwrap_or_else(|| vm.ctx.none()),
1135        MemberKind::ObjectEx => match obj.get_slot(offset) {
1136            Some(value) => value,
1137            None => {
1138                return Err(vm.new_attribute_error(format!(
1139                    "'{}' object has no attribute '{}'",
1140                    obj.class().fully_qualified_name(vm)?,
1141                    member.name
1142                )));
1143            }
1144        },
1145        MemberKind::Bool => {
1146            let raw = if member.atomic_storage() {
1147                // SAFETY: `PyMemberFlags::ATOMIC` or a writable member addresses an aligned
1148                // `AtomicBool`.
1149                unsafe {
1150                    (*member_addr(obj, offset).cast::<core::sync::atomic::AtomicBool>())
1151                        .load(core::sync::atomic::Ordering::Relaxed)
1152                }
1153            } else {
1154                // SAFETY: a plain bool member is one byte and is not written
1155                // after publication.
1156                unsafe { member_addr(obj, offset).cast::<bool>().read() }
1157            };
1158            vm.ctx.new_bool(raw).into()
1159        }
1160        MemberKind::Int => vm
1161            .ctx
1162            .new_int(load_i32(obj, offset, member.atomic_storage()))
1163            .into(),
1164        MemberKind::Uint => vm
1165            .ctx
1166            .new_int(load_u32(obj, offset, member.atomic_storage()))
1167            .into(),
1168        MemberKind::Double => {
1169            let raw = if member.atomic_storage() {
1170                // SAFETY: `PyMemberFlags::ATOMIC` or a writable member addresses an aligned
1171                // `AtomicF64`.
1172                unsafe {
1173                    (*member_addr(obj, offset).cast::<crate::common::atomic::AtomicF64>())
1174                        .load(core::sync::atomic::Ordering::Relaxed)
1175                }
1176            } else {
1177                // SAFETY: a plain double member addresses an `f64` that is not
1178                // written after publication. A plain `f64` may be only 4-byte
1179                // aligned, so this is a plain read and not an atomic access.
1180                unsafe { member_addr(obj, offset).cast::<f64>().read() }
1181            };
1182            vm.ctx.new_float(raw).into()
1183        }
1184        MemberKind::PySsizeT => {
1185            let raw = if member.atomic_storage() {
1186                // SAFETY: `PyMemberFlags::ATOMIC` or a writable member addresses an aligned
1187                // `AtomicIsize`.
1188                unsafe {
1189                    (*member_addr(obj, offset).cast::<core::sync::atomic::AtomicIsize>())
1190                        .load(core::sync::atomic::Ordering::Relaxed)
1191                }
1192            } else {
1193                // SAFETY: a plain `Py_ssize_t` member addresses an `isize` that
1194                // is not written after publication.
1195                unsafe { member_addr(obj, offset).cast::<isize>().read() }
1196            };
1197            vm.ctx.new_int(raw).into()
1198        }
1199        MemberKind::Short => vm
1200            .ctx
1201            .new_int(load_i16(obj, offset, member.atomic_storage()))
1202            .into(),
1203        MemberKind::UShort => vm
1204            .ctx
1205            .new_int(load_u16(obj, offset, member.atomic_storage()))
1206            .into(),
1207        MemberKind::UByte => vm
1208            .ctx
1209            .new_int(load_u8(obj, offset, member.atomic_storage()))
1210            .into(),
1211        MemberKind::Long => vm
1212            .ctx
1213            .new_int(load_c_long(obj, offset, member.atomic_storage()))
1214            .into(),
1215        MemberKind::LongLong => vm
1216            .ctx
1217            .new_int(load_i64(obj, offset, member.atomic_storage()))
1218            .into(),
1219        MemberKind::ULong => vm
1220            .ctx
1221            .new_int(load_c_ulong(obj, offset, member.atomic_storage()))
1222            .into(),
1223        MemberKind::ULongLong => vm
1224            .ctx
1225            .new_int(load_u64(obj, offset, member.atomic_storage()))
1226            .into(),
1227        MemberKind::String => {
1228            // SAFETY: a string member addresses a `*const c_char` that is not
1229            // written after publication. Null is `None`.
1230            let ptr = unsafe {
1231                member_addr(obj, offset)
1232                    .cast::<*const core::ffi::c_char>()
1233                    .read()
1234            };
1235            if ptr.is_null() {
1236                vm.ctx.none()
1237            } else {
1238                // SAFETY: `ptr` is non-null. The slot points at a `CString` the
1239                // owner keeps alive, so the bytes stay NUL-terminated for this read.
1240                let bytes = unsafe { core::ffi::CStr::from_ptr(ptr) }.to_bytes();
1241                let Ok(text) = core::str::from_utf8(bytes) else {
1242                    return Err(vm.new_unicode_decode_error(
1243                        vm.ctx.new_str("utf-8"),
1244                        vm.ctx.new_bytes(bytes.to_vec()),
1245                        0,
1246                        bytes.len(),
1247                        vm.ctx.new_str("invalid UTF-8"),
1248                    ));
1249                };
1250                vm.ctx.new_str(text).into()
1251            }
1252        }
1253    };
1254    Ok(value)
1255}
1256
1257// PyMember_SetOne.
1258fn member_set_one(
1259    obj: &PyObject,
1260    offset: isize,
1261    member: &PyMemberDef,
1262    value: PySetterValue,
1263    vm: &VirtualMachine,
1264) -> PyResult<()> {
1265    if matches!(value, PySetterValue::Delete)
1266        && !matches!(member.kind, MemberKind::Object | MemberKind::ObjectEx)
1267    {
1268        return Err(vm.new_type_error("can't delete numeric/char attribute"));
1269    }
1270    match member.kind {
1271        MemberKind::Object => match value {
1272            PySetterValue::Assign(v) => obj.set_slot(offset, Some(v)),
1273            PySetterValue::Delete => obj.set_slot(offset, None),
1274        },
1275        MemberKind::ObjectEx => match value {
1276            PySetterValue::Assign(v) => obj.set_slot(offset, Some(v)),
1277            PySetterValue::Delete => {
1278                if obj.get_slot(offset).is_none() {
1279                    return Err(vm.new_attribute_error(member.name.clone()));
1280                }
1281                obj.set_slot(offset, None);
1282            }
1283        },
1284        MemberKind::Bool => {
1285            let PySetterValue::Assign(value) = value else {
1286                return Err(vm.new_type_error("can't delete numeric/char attribute"));
1287            };
1288            if !value.class().is(vm.ctx.types.bool_type) {
1289                return Err(vm.new_type_error("attribute value type must be bool"));
1290            }
1291            let stored = value.is(&vm.ctx.true_value);
1292            // SAFETY: a writable bool member addresses an `AtomicBool`.
1293            unsafe {
1294                (*member_addr(obj, offset).cast::<core::sync::atomic::AtomicBool>())
1295                    .store(stored, core::sync::atomic::Ordering::Relaxed);
1296            }
1297        }
1298        MemberKind::Int => {
1299            let PySetterValue::Assign(value) = value else {
1300                return Err(vm.new_type_error("can't delete numeric/char attribute"));
1301            };
1302            let long_val = member_as_c_long(&value, vm)?;
1303            let stored = long_val as i32;
1304            // SAFETY: a writable int member addresses an aligned `AtomicI32`.
1305            // Readonly members are rejected before this call.
1306            unsafe {
1307                (*member_addr(obj, offset).cast::<core::sync::atomic::AtomicI32>())
1308                    .store(stored, core::sync::atomic::Ordering::Relaxed);
1309            }
1310            let truncated = long_val > i32::MAX as core::ffi::c_long
1311                || long_val < i32::MIN as core::ffi::c_long;
1312            if truncated {
1313                warn_member(vm, "Truncation of value to int")?;
1314            }
1315        }
1316        MemberKind::Uint => {
1317            let PySetterValue::Assign(value) = value else {
1318                return Err(vm.new_type_error("can't delete numeric/char attribute"));
1319            };
1320            let (stored, warning) = member_uint_value(&value, vm)?;
1321            // SAFETY: a writable uint member addresses an aligned `AtomicU32`.
1322            // Readonly members are rejected before this call.
1323            unsafe {
1324                (*member_addr(obj, offset).cast::<core::sync::atomic::AtomicU32>())
1325                    .store(stored, core::sync::atomic::Ordering::Relaxed);
1326            }
1327            if let Some(warning) = warning {
1328                warn_member(vm, warning)?;
1329            }
1330        }
1331        MemberKind::Double => {
1332            let PySetterValue::Assign(value) = value else {
1333                return Err(vm.new_type_error("can't delete numeric/char attribute"));
1334            };
1335            let number = value.try_float(vm)?.to_f64();
1336            // SAFETY: a writable double member addresses an aligned `AtomicF64`.
1337            // Readonly members are rejected before this call.
1338            unsafe {
1339                (*member_addr(obj, offset).cast::<crate::common::atomic::AtomicF64>())
1340                    .store(number, core::sync::atomic::Ordering::Relaxed);
1341            }
1342        }
1343        MemberKind::PySsizeT => {
1344            let PySetterValue::Assign(value) = value else {
1345                return Err(vm.new_type_error("can't delete numeric/char attribute"));
1346            };
1347            // PyLong_AsSsize_t: an int (bool included). No `__index__`.
1348            if !value.fast_isinstance(vm.ctx.types.int_type) {
1349                return Err(vm.new_type_error("an integer is required"));
1350            }
1351            let Some(int_obj) = value.downcast_ref::<crate::builtins::PyInt>() else {
1352                return Err(vm.new_type_error("an integer is required"));
1353            };
1354            let stored = isize::try_from(int_obj.as_bigint()).map_err(|_| {
1355                vm.new_overflow_error("Python int too large to convert to C ssize_t")
1356            })?;
1357            // SAFETY: a writable `Py_ssize_t` member addresses an aligned
1358            // `AtomicIsize`. Readonly members are rejected before this call.
1359            unsafe {
1360                (*member_addr(obj, offset).cast::<core::sync::atomic::AtomicIsize>())
1361                    .store(stored, core::sync::atomic::Ordering::Relaxed);
1362            }
1363        }
1364        MemberKind::Short => {
1365            let PySetterValue::Assign(value) = value else {
1366                return Err(vm.new_type_error("can't delete numeric/char attribute"));
1367            };
1368            let long_val = member_as_c_long(&value, vm)?;
1369            let stored = long_val as i16;
1370            // SAFETY: a writable short member addresses an aligned `AtomicI16`.
1371            unsafe {
1372                (*member_addr(obj, offset).cast::<core::sync::atomic::AtomicI16>())
1373                    .store(stored, core::sync::atomic::Ordering::Relaxed);
1374            }
1375            if long_val > i16::MAX as core::ffi::c_long || long_val < i16::MIN as core::ffi::c_long
1376            {
1377                warn_member(vm, "Truncation of value to short")?;
1378            }
1379        }
1380        MemberKind::UShort => {
1381            let PySetterValue::Assign(value) = value else {
1382                return Err(vm.new_type_error("can't delete numeric/char attribute"));
1383            };
1384            let long_val = member_as_c_long(&value, vm)?;
1385            let stored = long_val as u16;
1386            // SAFETY: a writable unsigned short member addresses an aligned `AtomicU16`.
1387            unsafe {
1388                (*member_addr(obj, offset).cast::<core::sync::atomic::AtomicU16>())
1389                    .store(stored, core::sync::atomic::Ordering::Relaxed);
1390            }
1391            if long_val > u16::MAX as core::ffi::c_long || long_val < 0 {
1392                warn_member(vm, "Truncation of value to unsigned short")?;
1393            }
1394        }
1395        MemberKind::UByte => {
1396            let PySetterValue::Assign(value) = value else {
1397                return Err(vm.new_type_error("can't delete numeric/char attribute"));
1398            };
1399            let long_val = member_as_c_long(&value, vm)?;
1400            let stored = long_val as u8;
1401            // SAFETY: a writable unsigned char member addresses an `AtomicU8`.
1402            unsafe {
1403                (*member_addr(obj, offset).cast::<core::sync::atomic::AtomicU8>())
1404                    .store(stored, core::sync::atomic::Ordering::Relaxed);
1405            }
1406            if long_val > u8::MAX as core::ffi::c_long || long_val < 0 {
1407                warn_member(vm, "Truncation of value to unsigned char")?;
1408            }
1409        }
1410        MemberKind::Long => {
1411            let PySetterValue::Assign(value) = value else {
1412                return Err(vm.new_type_error("can't delete numeric/char attribute"));
1413            };
1414            let stored = member_as_c_long(&value, vm)?;
1415            store_c_long(obj, offset, stored);
1416        }
1417        MemberKind::LongLong => {
1418            let PySetterValue::Assign(value) = value else {
1419                return Err(vm.new_type_error("can't delete numeric/char attribute"));
1420            };
1421            let stored = member_as_c_longlong(&value, vm)?;
1422            // SAFETY: a writable `long long` member addresses an aligned `AtomicI64`.
1423            unsafe {
1424                (*member_addr(obj, offset).cast::<core::sync::atomic::AtomicI64>())
1425                    .store(stored, core::sync::atomic::Ordering::Relaxed);
1426            }
1427        }
1428        MemberKind::ULong => {
1429            let PySetterValue::Assign(value) = value else {
1430                return Err(vm.new_type_error("can't delete numeric/char attribute"));
1431            };
1432            let (stored, warning) = member_ulong_value(&value, vm)?;
1433            store_c_ulong(obj, offset, stored);
1434            if let Some(warning) = warning {
1435                warn_member(vm, warning)?;
1436            }
1437        }
1438        MemberKind::ULongLong => {
1439            let PySetterValue::Assign(value) = value else {
1440                return Err(vm.new_type_error("can't delete numeric/char attribute"));
1441            };
1442            let (stored, warning) = member_ulonglong_value(&value, vm)?;
1443            // SAFETY: a writable `unsigned long long` member addresses an aligned `AtomicU64`.
1444            unsafe {
1445                (*member_addr(obj, offset).cast::<core::sync::atomic::AtomicU64>())
1446                    .store(stored, core::sync::atomic::Ordering::Relaxed);
1447            }
1448            if let Some(warning) = warning {
1449                warn_member(vm, warning)?;
1450            }
1451        }
1452        MemberKind::String => {
1453            return Err(vm.new_type_error("readonly attribute"));
1454        }
1455    }
1456    Ok(())
1457}
1458
1459fn member_as_c_longlong(value: &PyObject, vm: &VirtualMachine) -> PyResult<i64> {
1460    let int_obj = value.try_index(vm)?;
1461    i64::try_from(int_obj.as_bigint())
1462        .map_err(|_| vm.new_overflow_error("Python int too large to convert to C long long"))
1463}
1464
1465fn member_ulong_value(
1466    value: &PyObject,
1467    vm: &VirtualMachine,
1468) -> PyResult<(core::ffi::c_ulong, Option<&'static str>)> {
1469    let int_obj = value.try_index(vm)?;
1470    let big = int_obj.as_bigint();
1471    if big.sign() == malachite_bigint::Sign::Minus {
1472        let long_val = core::ffi::c_long::try_from(big)
1473            .map_err(|_| vm.new_overflow_error("Python int too large to convert to C long"))?;
1474        return Ok((
1475            long_val as core::ffi::c_ulong,
1476            Some("Writing negative value into unsigned field"),
1477        ));
1478    }
1479    let stored = core::ffi::c_ulong::try_from(big)
1480        .map_err(|_| vm.new_overflow_error("Python int too large to convert to C unsigned long"))?;
1481    Ok((stored, None))
1482}
1483
1484fn member_ulonglong_value(
1485    value: &PyObject,
1486    vm: &VirtualMachine,
1487) -> PyResult<(u64, Option<&'static str>)> {
1488    let int_obj = value.try_index(vm)?;
1489    let big = int_obj.as_bigint();
1490    if big.sign() == malachite_bigint::Sign::Minus {
1491        let long_val = core::ffi::c_long::try_from(big)
1492            .map_err(|_| vm.new_overflow_error("Python int too large to convert to C long"))?;
1493        return Ok((
1494            long_val as u64,
1495            Some("Writing negative value into unsigned field"),
1496        ));
1497    }
1498    let stored = u64::try_from(big).map_err(|_| {
1499        vm.new_overflow_error("Python int too large to convert to C unsigned long long")
1500    })?;
1501    Ok((stored, None))
1502}
1503
1504impl Representable for PyMemberDescriptor {
1505    #[inline]
1506    fn repr_str(zelf: &Py<Self>, _vm: &VirtualMachine) -> PyResult<String> {
1507        Ok(format!(
1508            "<member '{}' of '{}' objects>",
1509            zelf.common.name,
1510            zelf.common.typ.slot_name(),
1511        ))
1512    }
1513}
1514
1515impl GetDescriptor for PyMemberDescriptor {
1516    fn descr_get(
1517        zelf: &PyObject,
1518        obj: Option<&PyObject>,
1519        _cls: Option<&PyObject>,
1520        vm: &VirtualMachine,
1521    ) -> PyResult {
1522        let descr = Self::_as_pyref(zelf, vm)?;
1523        match obj {
1524            Some(x) => {
1525                if !x.class().fast_issubclass(&descr.common.typ) {
1526                    return Err(vm.new_type_error(format!(
1527                        "descriptor '{}' for '{}' objects doesn't apply to a '{}' object",
1528                        descr.common.name,
1529                        descr.common.typ.name(),
1530                        x.class().name()
1531                    )));
1532                }
1533                descr.get(x.to_owned(), vm)
1534            }
1535            None => Ok(zelf.to_owned()),
1536        }
1537    }
1538}
1539
1540fn method_descr_typecheck(
1541    descr: &PyMethodDescriptor,
1542    obj: &PyObject,
1543    vm: &VirtualMachine,
1544) -> PyResult<()> {
1545    if descr.method.flags.contains(PyMethodFlags::STATIC)
1546        || descr.method.flags.contains(PyMethodFlags::CLASS)
1547        || obj.fast_isinstance(descr.common.typ)
1548    {
1549        return Ok(());
1550    }
1551    Err(vm.new_type_error(format!(
1552        "descriptor '{}' for '{}' objects doesn't apply to a '{}' object",
1553        descr.common.name.as_str(),
1554        descr.common.typ.name(),
1555        obj.class().name()
1556    )))
1557}
1558
1559/// Vectorcall for method_descriptor: calls native method directly
1560fn vectorcall_method_descriptor(
1561    zelf_obj: &PyObject,
1562    args: Vec<PyObjectRef>,
1563    nargs: usize,
1564    kwnames: Option<&[PyObjectRef]>,
1565    vm: &VirtualMachine,
1566) -> PyResult {
1567    let zelf: &Py<PyMethodDescriptor> = zelf_obj.downcast_ref().unwrap();
1568    if nargs > 0
1569        && let Some(obj) = args.first()
1570    {
1571        method_descr_typecheck(zelf, obj, vm)?;
1572    }
1573    let func_args = FuncArgs::from_vectorcall_owned(args, nargs, kwnames);
1574    (zelf.method.func)(
1575        vm,
1576        func_args,
1577        Callee::named(zelf.method.name).with_instance_arg(true),
1578    )
1579}
1580
1581/// Vectorcall for wrapper_descriptor: calls wrapped slot function
1582fn vectorcall_wrapper(
1583    zelf_obj: &PyObject,
1584    mut args: Vec<PyObjectRef>,
1585    nargs: usize,
1586    kwnames: Option<&[PyObjectRef]>,
1587    vm: &VirtualMachine,
1588) -> PyResult {
1589    let zelf: &Py<PyWrapper> = zelf_obj.downcast_ref().unwrap();
1590    // First positional arg is self
1591    if nargs == 0 {
1592        return Err(vm.new_type_error(format!(
1593            "descriptor '{}' of '{}' object needs an argument",
1594            zelf.name.as_str(),
1595            zelf.typ.name()
1596        )));
1597    }
1598    let obj = args.remove(0);
1599    if !obj.fast_isinstance(zelf.typ) {
1600        return Err(vm.new_type_error(format!(
1601            "descriptor '{}' requires a '{}' object but received a '{}'",
1602            zelf.name.as_str(),
1603            zelf.typ.name(),
1604            obj.class().name()
1605        )));
1606    }
1607    let rest = FuncArgs::from_vectorcall_owned(args, nargs - 1, kwnames);
1608    zelf.wrapped.call(obj, rest, vm)
1609}
1610
1611pub(crate) fn init(ctx: &'static Context) {
1612    PyMemberDescriptor::extend_class(ctx, ctx.types.member_descriptor_type);
1613    PyMethodDescriptor::extend_class(ctx, ctx.types.method_descriptor_type);
1614    ctx.types
1615        .method_descriptor_type
1616        .slots
1617        .vectorcall
1618        .store(Some(vectorcall_method_descriptor));
1619    PyClassMethodDescriptor::extend_class(ctx, ctx.types.classmethod_descriptor_type);
1620    PyWrapper::extend_class(ctx, ctx.types.wrapper_descriptor_type);
1621    ctx.types
1622        .wrapper_descriptor_type
1623        .slots
1624        .vectorcall
1625        .store(Some(vectorcall_wrapper));
1626    PyMethodWrapper::extend_class(ctx, ctx.types.method_wrapper_type);
1627}
1628
1629// PyWrapper - wrapper_descriptor
1630
1631/// Each variant knows how to call the wrapped function with proper types
1632#[derive(Clone, Copy)]
1633pub enum SlotFunc {
1634    // Basic slots
1635    Init(InitFunc),
1636    Hash(HashFunc),
1637    Str(StringifyFunc),
1638    Repr(StringifyFunc),
1639    Iter(IterFunc),
1640    IterNext(IterNextFunc),
1641    Call(GenericMethod),
1642    Del(DelFunc),
1643
1644    // Attribute access slots
1645    GetAttro(GetattroFunc),
1646    SetAttro(SetattroFunc), // __setattr__
1647    DelAttro(SetattroFunc), // __delattr__ (same func type, different PySetterValue)
1648
1649    // Rich comparison slots (with comparison op)
1650    RichCompare(RichCompareFunc, PyComparisonOp),
1651
1652    // Descriptor slots
1653    DescrGet(DescrGetFunc),
1654    DescrSet(DescrSetFunc), // __set__
1655    DescrDel(DescrSetFunc), // __delete__ (same func type, different PySetterValue)
1656
1657    // Sequence sub-slots (sq_*)
1658    SeqLength(SeqLenFunc),
1659    SeqConcat(SeqConcatFunc),
1660    SeqRepeat(SeqRepeatFunc),
1661    SeqItem(SeqItemFunc),
1662    SeqSetItem(SeqAssItemFunc), // __setitem__ (same func type, value = Some)
1663    SeqDelItem(SeqAssItemFunc), // __delitem__ (same func type, value = None)
1664    SeqContains(SeqContainsFunc),
1665
1666    // Mapping sub-slots (mp_*)
1667    MapLength(MapLenFunc),
1668    MapSubscript(MapSubscriptFunc),
1669    MapSetSubscript(MapAssSubscriptFunc), // __setitem__ (same func type, value = Some)
1670    MapDelSubscript(MapAssSubscriptFunc), // __delitem__ (same func type, value = None)
1671
1672    // Number sub-slots (nb_*) - grouped by signature
1673    NumBoolean(PyNumberUnaryFunc<bool>),  // __bool__
1674    NumUnary(PyNumberUnaryFunc),          // __int__, __float__, __index__
1675    NumBinary(PyNumberBinaryFunc),        // __add__, __sub__, __mul__, etc.
1676    NumBinaryRight(PyNumberBinaryFunc),   // __radd__, __rsub__, etc. (swapped args)
1677    NumTernary(PyNumberTernaryFunc),      // __pow__
1678    NumTernaryRight(PyNumberTernaryFunc), // __rpow__ (swapped first two args)
1679
1680    // Buffer protocol
1681    GetBuffer(crate::types::AsBufferFunc), // __buffer__
1682    ReleaseBuffer,                         // __release_buffer__
1683}
1684
1685impl core::fmt::Debug for SlotFunc {
1686    fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
1687        match self {
1688            Self::Init(_) => write!(f, "SlotFunc::Init(...)"),
1689            Self::Hash(_) => write!(f, "SlotFunc::Hash(...)"),
1690            Self::Str(_) => write!(f, "SlotFunc::Str(...)"),
1691            Self::Repr(_) => write!(f, "SlotFunc::Repr(...)"),
1692            Self::Iter(_) => write!(f, "SlotFunc::Iter(...)"),
1693            Self::IterNext(_) => write!(f, "SlotFunc::IterNext(...)"),
1694            Self::Call(_) => write!(f, "SlotFunc::Call(...)"),
1695            Self::Del(_) => write!(f, "SlotFunc::Del(...)"),
1696            Self::GetAttro(_) => write!(f, "SlotFunc::GetAttro(...)"),
1697            Self::SetAttro(_) => write!(f, "SlotFunc::SetAttro(...)"),
1698            Self::DelAttro(_) => write!(f, "SlotFunc::DelAttro(...)"),
1699            Self::RichCompare(_, op) => write!(f, "SlotFunc::RichCompare(..., {op:?})"),
1700            Self::DescrGet(_) => write!(f, "SlotFunc::DescrGet(...)"),
1701            Self::DescrSet(_) => write!(f, "SlotFunc::DescrSet(...)"),
1702            Self::DescrDel(_) => write!(f, "SlotFunc::DescrDel(...)"),
1703            // Sequence sub-slots
1704            Self::SeqLength(_) => write!(f, "SlotFunc::SeqLength(...)"),
1705            Self::SeqConcat(_) => write!(f, "SlotFunc::SeqConcat(...)"),
1706            Self::SeqRepeat(_) => write!(f, "SlotFunc::SeqRepeat(...)"),
1707            Self::SeqItem(_) => write!(f, "SlotFunc::SeqItem(...)"),
1708            Self::SeqSetItem(_) => write!(f, "SlotFunc::SeqSetItem(...)"),
1709            Self::SeqDelItem(_) => write!(f, "SlotFunc::SeqDelItem(...)"),
1710            Self::SeqContains(_) => write!(f, "SlotFunc::SeqContains(...)"),
1711            // Mapping sub-slots
1712            Self::MapLength(_) => write!(f, "SlotFunc::MapLength(...)"),
1713            Self::MapSubscript(_) => write!(f, "SlotFunc::MapSubscript(...)"),
1714            Self::MapSetSubscript(_) => write!(f, "SlotFunc::MapSetSubscript(...)"),
1715            Self::MapDelSubscript(_) => write!(f, "SlotFunc::MapDelSubscript(...)"),
1716            // Number sub-slots
1717            Self::NumBoolean(_) => write!(f, "SlotFunc::NumBoolean(...)"),
1718            Self::NumUnary(_) => write!(f, "SlotFunc::NumUnary(...)"),
1719            Self::NumBinary(_) => write!(f, "SlotFunc::NumBinary(...)"),
1720            Self::NumBinaryRight(_) => write!(f, "SlotFunc::NumBinaryRight(...)"),
1721            Self::NumTernary(_) => write!(f, "SlotFunc::NumTernary(...)"),
1722            Self::NumTernaryRight(_) => write!(f, "SlotFunc::NumTernaryRight(...)"),
1723            Self::GetBuffer(_) => write!(f, "SlotFunc::GetBuffer(...)"),
1724            Self::ReleaseBuffer => write!(f, "SlotFunc::ReleaseBuffer"),
1725        }
1726    }
1727}
1728
1729impl SlotFunc {
1730    /// Call the wrapped slot function with proper type handling
1731    pub fn call(&self, obj: PyObjectRef, args: FuncArgs, vm: &VirtualMachine) -> PyResult {
1732        match self {
1733            Self::Init(func) => {
1734                func(&obj, args, vm)?;
1735                Ok(vm.ctx.none())
1736            }
1737            Self::Hash(func) => {
1738                if !args.args.is_empty() || !args.kwargs.is_empty() {
1739                    return Err(vm.new_type_error("__hash__() takes no arguments (1 given)"));
1740                }
1741                let hash = func(&obj, vm)?;
1742                Ok(vm.ctx.new_int(hash).into())
1743            }
1744            Self::Repr(func) | Self::Str(func) => {
1745                if !args.args.is_empty() || !args.kwargs.is_empty() {
1746                    let name = match self {
1747                        Self::Repr(_) => "__repr__",
1748                        Self::Str(_) => "__str__",
1749                        _ => unreachable!(),
1750                    };
1751                    return Err(vm.new_type_error(format!("{name}() takes no arguments (1 given)")));
1752                }
1753                let s = func(&obj, vm)?;
1754                Ok(s.into())
1755            }
1756            Self::Iter(func) => {
1757                if !args.args.is_empty() || !args.kwargs.is_empty() {
1758                    return Err(vm.new_type_error("__iter__() takes no arguments (1 given)"));
1759                }
1760                func(obj, vm)
1761            }
1762            Self::IterNext(func) => {
1763                if !args.args.is_empty() || !args.kwargs.is_empty() {
1764                    return Err(vm.new_type_error("__next__() takes no arguments (1 given)"));
1765                }
1766                func(&obj, vm).to_pyresult(vm)
1767            }
1768            Self::Call(func) => func(&obj, args, vm),
1769            Self::Del(func) => {
1770                if !args.args.is_empty() || !args.kwargs.is_empty() {
1771                    return Err(vm.new_type_error("__del__() takes no arguments (1 given)"));
1772                }
1773                func(&obj, vm)?;
1774                Ok(vm.ctx.none())
1775            }
1776            Self::GetAttro(func) => {
1777                let (name,): (PyRef<PyStr>,) = args.bind(vm)?;
1778                func(&obj, &name, vm)
1779            }
1780            Self::SetAttro(func) => {
1781                let (name, value): (PyRef<PyStr>, PyObjectRef) = args.bind(vm)?;
1782                crate::types::hackcheck_setattro(&obj, *func, "__setattr__", vm)?;
1783                func(&obj, &name, PySetterValue::Assign(value), vm)?;
1784                Ok(vm.ctx.none())
1785            }
1786            Self::DelAttro(func) => {
1787                let (name,): (PyRef<PyStr>,) = args.bind(vm)?;
1788                crate::types::hackcheck_setattro(&obj, *func, "__delattr__", vm)?;
1789                func(&obj, &name, PySetterValue::Delete, vm)?;
1790                Ok(vm.ctx.none())
1791            }
1792            Self::RichCompare(func, op) => {
1793                let (other,): (PyObjectRef,) = args.bind(vm)?;
1794                func(&obj, &other, *op, vm).map(|r| match r {
1795                    crate::function::Either::A(obj) => obj,
1796                    crate::function::Either::B(cmp_val) => cmp_val.to_pyobject(vm),
1797                })
1798            }
1799            Self::DescrGet(func) => {
1800                let (instance, owner): (PyObjectRef, crate::function::OptionalArg<PyObjectRef>) =
1801                    args.bind(vm)?;
1802                let owner = owner.into_option();
1803                let instance_opt = if vm.is_none(&instance) {
1804                    None
1805                } else {
1806                    Some(instance)
1807                };
1808                func(&obj, instance_opt.as_deref(), owner.as_deref(), vm)
1809            }
1810            Self::DescrSet(func) => {
1811                let (instance, value): (PyObjectRef, PyObjectRef) = args.bind(vm)?;
1812                func(&obj, instance, PySetterValue::Assign(value), vm)?;
1813                Ok(vm.ctx.none())
1814            }
1815            Self::DescrDel(func) => {
1816                let (instance,): (PyObjectRef,) = args.bind(vm)?;
1817                func(&obj, instance, PySetterValue::Delete, vm)?;
1818                Ok(vm.ctx.none())
1819            }
1820            // Sequence sub-slots
1821            Self::SeqLength(func) => {
1822                args.bind::<()>(vm)?;
1823                let len = func(obj.sequence_unchecked(), vm)?;
1824                Ok(vm.ctx.new_int(len).into())
1825            }
1826            Self::SeqConcat(func) => {
1827                let (other,): (PyObjectRef,) = args.bind(vm)?;
1828                func(obj.sequence_unchecked(), &other, vm)
1829            }
1830            Self::SeqRepeat(func) => {
1831                let (n,): (PySsize,) = args.bind(vm)?;
1832                func(obj.sequence_unchecked(), n, vm)
1833            }
1834            Self::SeqItem(func) => {
1835                let (index,): (isize,) = args.bind(vm)?;
1836                func(obj.sequence_unchecked(), index, vm)
1837            }
1838            Self::SeqSetItem(func) => {
1839                let (index, value): (isize, PyObjectRef) = args.bind(vm)?;
1840                func(obj.sequence_unchecked(), index, Some(value), vm)?;
1841                Ok(vm.ctx.none())
1842            }
1843            Self::SeqDelItem(func) => {
1844                let (index,): (isize,) = args.bind(vm)?;
1845                func(obj.sequence_unchecked(), index, None, vm)?;
1846                Ok(vm.ctx.none())
1847            }
1848            Self::SeqContains(func) => {
1849                let (item,): (PyObjectRef,) = args.bind(vm)?;
1850                let result = func(obj.sequence_unchecked(), &item, vm)?;
1851                Ok(vm.ctx.new_bool(result).into())
1852            }
1853            // Mapping sub-slots
1854            Self::MapLength(func) => {
1855                args.bind::<()>(vm)?;
1856                let len = func(obj.mapping_unchecked(), vm)?;
1857                Ok(vm.ctx.new_int(len).into())
1858            }
1859            Self::MapSubscript(func) => {
1860                let (key,): (PyObjectRef,) = args.bind(vm)?;
1861                func(obj.mapping_unchecked(), &key, vm)
1862            }
1863            Self::MapSetSubscript(func) => {
1864                let (key, value): (PyObjectRef, PyObjectRef) = args.bind(vm)?;
1865                func(obj.mapping_unchecked(), &key, Some(value), vm)?;
1866                Ok(vm.ctx.none())
1867            }
1868            Self::MapDelSubscript(func) => {
1869                let (key,): (PyObjectRef,) = args.bind(vm)?;
1870                func(obj.mapping_unchecked(), &key, None, vm)?;
1871                Ok(vm.ctx.none())
1872            }
1873            // Number sub-slots
1874            Self::NumBoolean(func) => {
1875                args.bind::<()>(vm)?;
1876                let result = func(obj.number(), vm)?;
1877                Ok(vm.ctx.new_bool(result).into())
1878            }
1879            Self::NumUnary(func) => {
1880                args.bind::<()>(vm)?;
1881                func(obj.number(), vm)
1882            }
1883            Self::NumBinary(func) => {
1884                let (other,): (PyObjectRef,) = args.bind(vm)?;
1885                func(&obj, &other, vm)
1886            }
1887            Self::NumBinaryRight(func) => {
1888                let (other,): (PyObjectRef,) = args.bind(vm)?;
1889                func(&other, &obj, vm) // Swapped: other op obj
1890            }
1891            Self::NumTernary(func) => {
1892                let (y, z) = pow_args(args, vm)?;
1893                func(&obj, &y, &z, vm)
1894            }
1895            Self::NumTernaryRight(func) => {
1896                let (y, z) = pow_args(args, vm)?;
1897                func(&y, &obj, &z, vm)
1898            }
1899            // Buffer protocol
1900            Self::GetBuffer(func) => {
1901                let (flags_obj,): (PyObjectRef,) = args.bind(vm)?;
1902                let buffer = func(&obj, parse_buffer_flags(&flags_obj, vm)?, vm)?;
1903                crate::builtins::PyMemoryView::from_buffer(buffer, vm)
1904                    .map(|mv| mv.into_pyobject(vm))
1905            }
1906            Self::ReleaseBuffer => {
1907                let (mv_obj,): (PyObjectRef,) = args.bind(vm)?;
1908                let mv = mv_obj
1909                    .downcast::<crate::builtins::PyMemoryView>()
1910                    .map_err(|_| vm.new_type_error("expected a memoryview object"))?;
1911                crate::builtins::memory::release_buffer_from_python(&obj, &mv, vm)?;
1912                Ok(vm.ctx.none())
1913            }
1914        }
1915    }
1916}
1917
1918/// wrap_ternaryfunc / check_pow_args
1919fn pow_args(args: FuncArgs, vm: &VirtualMachine) -> PyResult<(PyObjectRef, PyObjectRef)> {
1920    if let Some(err) = args.check_kwargs_empty(vm) {
1921        return Err(err);
1922    }
1923    let size = args.args.len();
1924    if !(1..=2).contains(&size) {
1925        return Err(vm.new_type_error(format!("expected 1 or 2 arguments, got {size}")));
1926    }
1927    let y = args.args[0].clone();
1928    let z = if size == 2 {
1929        args.args[1].clone()
1930    } else {
1931        vm.ctx.none()
1932    };
1933    Ok((y, z))
1934}
1935
1936/// Parse the `flags` argument of `__buffer__`. wrap_buffer
1937fn parse_buffer_flags(
1938    arg: &PyObject,
1939    vm: &VirtualMachine,
1940) -> PyResult<crate::protocol::BufferFlags> {
1941    use num_traits::ToPrimitive;
1942    let idx = arg.try_index(vm)?;
1943    let flags = idx
1944        .as_bigint()
1945        .to_isize()
1946        .ok_or_else(|| vm.new_overflow_error("cannot fit 'int' into an index-sized integer"))?;
1947    let flags =
1948        i32::try_from(flags).map_err(|_| vm.new_overflow_error("buffer flags out of range"))?;
1949    Ok(crate::protocol::BufferFlags::from_bits_retain(flags as u32))
1950}
1951
1952// wrapper_descriptor: wraps a slot function as a Python method
1953// = PyWrapperDescrObject
1954#[pyclass(name = "wrapper_descriptor", module = false)]
1955#[derive(Debug)]
1956pub(crate) struct PyWrapper {
1957    #[pymember(name = "__objclass__")]
1958    pub typ: &'static Py<PyType>,
1959    #[pymember(name = "__name__")]
1960    pub name: &'static PyStrInterned,
1961    pub wrapped: SlotFunc,
1962    /// Slot text, including the text signature.
1963    pub doc: Option<&'static str>,
1964    /// Plain docstring for this slot when the table has one.
1965    pub plain_off: u32,
1966    pub plain_len: u32,
1967}
1968
1969impl PyPayload for PyWrapper {
1970    fn class(ctx: &Context) -> &'static Py<PyType> {
1971        ctx.types.wrapper_descriptor_type
1972    }
1973}
1974
1975impl GetDescriptor for PyWrapper {
1976    fn descr_get(
1977        zelf: &PyObject,
1978        obj: Option<&PyObject>,
1979        _cls: Option<&PyObject>,
1980        vm: &VirtualMachine,
1981    ) -> PyResult {
1982        match obj {
1983            None => Ok(zelf.to_owned()),
1984            Some(obj) => {
1985                let zelf = zelf.to_owned().downcast::<Self>().unwrap();
1986                Ok(PyMethodWrapper {
1987                    wrapper: zelf,
1988                    obj: obj.to_owned(),
1989                }
1990                .into_pyobject(vm))
1991            }
1992        }
1993    }
1994}
1995
1996impl Callable for PyWrapper {
1997    type Args = FuncArgs;
1998
1999    fn call(zelf: &Py<Self>, args: FuncArgs, vm: &VirtualMachine) -> PyResult {
2000        // list.__init__(l, [1,2,3]) form - first arg is self
2001        let (obj, rest): (PyObjectRef, FuncArgs) = args.bind(vm)?;
2002
2003        if !obj.fast_isinstance(zelf.typ) {
2004            return Err(vm.new_type_error(format!(
2005                "descriptor '{}' requires a '{}' object but received a '{}'",
2006                zelf.name.as_str(),
2007                zelf.typ.name(),
2008                obj.class().name()
2009            )));
2010        }
2011
2012        zelf.wrapped.call(obj, rest, vm)
2013    }
2014}
2015
2016#[pyclass(
2017    with(GetDescriptor, Callable, Representable),
2018    flags(DISALLOW_INSTANTIATION)
2019)]
2020impl Py<PyWrapper> {
2021    #[pygetset]
2022    fn __qualname__(&self) -> String {
2023        format!("{}.{}", self.typ.name(), self.name)
2024    }
2025
2026    #[pygetset]
2027    fn __doc__(&self) -> Option<&'static str> {
2028        if self.plain_len != 0 {
2029            return crate::function::db_doc(self.plain_off, self.plain_len);
2030        }
2031        let doc = self.doc?;
2032        type_::get_doc_from_internal_doc(self.name.as_str(), doc)
2033    }
2034
2035    #[pygetset]
2036    fn __text_signature__(&self) -> Option<String> {
2037        self.doc.and_then(|doc| {
2038            type_::get_text_signature_from_internal_doc(self.name.as_str(), doc)
2039                .map(|signature| signature.to_string())
2040        })
2041    }
2042}
2043
2044impl Representable for PyWrapper {
2045    #[inline]
2046    fn repr_str(zelf: &Py<Self>, _vm: &VirtualMachine) -> PyResult<String> {
2047        Ok(format!(
2048            "<slot wrapper '{}' of '{}' objects>",
2049            zelf.name.as_str(),
2050            zelf.typ.name()
2051        ))
2052    }
2053}
2054
2055// PyMethodWrapper - method-wrapper
2056
2057// method-wrapper: a slot wrapper bound to an instance
2058// Returned when accessing l.__init__ on an instance
2059#[pyclass(name = "method-wrapper", module = false, traverse)]
2060#[derive(Debug)]
2061pub(crate) struct PyMethodWrapper {
2062    pub wrapper: PyRef<PyWrapper>,
2063    #[pymember(name = "__self__")]
2064    #[pytraverse(skip)]
2065    pub obj: PyObjectRef,
2066}
2067
2068impl PyPayload for PyMethodWrapper {
2069    fn class(ctx: &Context) -> &'static Py<PyType> {
2070        ctx.types.method_wrapper_type
2071    }
2072}
2073
2074impl Callable for PyMethodWrapper {
2075    type Args = FuncArgs;
2076
2077    fn call(zelf: &Py<Self>, args: FuncArgs, vm: &VirtualMachine) -> PyResult {
2078        // bpo-37619: Check type compatibility before calling wrapped slot
2079        if !zelf.obj.fast_isinstance(zelf.wrapper.typ) {
2080            return Err(vm.new_type_error(format!(
2081                "descriptor '{}' requires a '{}' object but received a '{}'",
2082                zelf.wrapper.name.as_str(),
2083                zelf.wrapper.typ.name(),
2084                zelf.obj.class().name()
2085            )));
2086        }
2087        zelf.wrapper.wrapped.call(zelf.obj.clone(), args, vm)
2088    }
2089}
2090
2091#[pyclass(
2092    with(Callable, Representable, Hashable, Comparable),
2093    flags(DISALLOW_INSTANTIATION)
2094)]
2095impl Py<PyMethodWrapper> {
2096    #[pygetset]
2097    fn __name__(&self) -> &'static PyStrInterned {
2098        self.wrapper.name
2099    }
2100
2101    #[pygetset]
2102    fn __objclass__(&self) -> PyTypeRef {
2103        self.wrapper.typ.to_owned()
2104    }
2105
2106    #[pygetset]
2107    fn __qualname__(&self) -> String {
2108        format!("{}.{}", self.wrapper.typ.name(), self.wrapper.name)
2109    }
2110
2111    #[pygetset]
2112    fn __doc__(&self) -> Option<&'static str> {
2113        if self.wrapper.plain_len != 0 {
2114            return crate::function::db_doc(self.wrapper.plain_off, self.wrapper.plain_len);
2115        }
2116        let doc = self.wrapper.doc?;
2117        type_::get_doc_from_internal_doc(self.wrapper.name.as_str(), doc)
2118    }
2119
2120    #[pygetset]
2121    fn __text_signature__(&self) -> Option<String> {
2122        self.wrapper.doc.and_then(|doc| {
2123            type_::get_text_signature_from_internal_doc(self.wrapper.name.as_str(), doc)
2124                .map(|signature| signature.to_string())
2125        })
2126    }
2127
2128    #[pymethod]
2129    fn __reduce__(zelf: PyRef<PyMethodWrapper>, vm: &VirtualMachine) -> PyResult {
2130        let builtins_getattr = vm.builtins.get_attr("getattr", vm)?;
2131        Ok(vm
2132            .ctx
2133            .new_tuple(vec![
2134                builtins_getattr,
2135                vm.ctx
2136                    .new_tuple(vec![
2137                        zelf.obj.clone(),
2138                        vm.ctx.new_str(zelf.wrapper.name.as_str()).into(),
2139                    ])
2140                    .into(),
2141            ])
2142            .into())
2143    }
2144}
2145
2146impl Representable for PyMethodWrapper {
2147    #[inline]
2148    fn repr_str(zelf: &Py<Self>, _vm: &VirtualMachine) -> PyResult<String> {
2149        Ok(format!(
2150            "<method-wrapper '{}' of {} object at {:#x}>",
2151            zelf.wrapper.name.as_str(),
2152            zelf.obj.class().name(),
2153            zelf.obj.get_id()
2154        ))
2155    }
2156}
2157
2158impl Hashable for PyMethodWrapper {
2159    fn hash(zelf: &Py<Self>, _vm: &VirtualMachine) -> PyResult<PyHash> {
2160        // wrapperobject_hash: pointer hash of descr xor self
2161        let mut hash =
2162            (zelf.wrapper.as_object().get_id() as PyHash) ^ (zelf.obj.get_id() as PyHash);
2163        if hash == -1 {
2164            hash = -2;
2165        }
2166        Ok(hash)
2167    }
2168}
2169
2170impl Comparable for PyMethodWrapper {
2171    fn cmp(
2172        zelf: &Py<Self>,
2173        other: &PyObject,
2174        op: PyComparisonOp,
2175        _vm: &VirtualMachine,
2176    ) -> PyResult<crate::function::PyComparisonValue> {
2177        op.eq_only(|| {
2178            let other = class_or_notimplemented!(Self, other);
2179            let eq = zelf.wrapper.is(&other.wrapper) && zelf.obj.is(&other.obj);
2180            Ok(eq.into())
2181        })
2182    }
2183}
2184
2185#[cfg(test)]
2186mod tests {
2187    use super::*;
2188    use crate::{protocol::PyNumberMethods, types::AsNumber};
2189    use core::sync::atomic::{AtomicU8, Ordering};
2190
2191    #[pyclass(name = "UByteMembers", module = false)]
2192    #[derive(Debug, PyPayload)]
2193    #[repr(C)]
2194    struct UByteMembers {
2195        prefix: u8,
2196        #[pymember]
2197        readonly: u8,
2198        #[pymember(writable)]
2199        writable: AtomicU8,
2200        suffix: u8,
2201    }
2202
2203    #[pyclass(with(AsNumber))]
2204    impl UByteMembers {}
2205
2206    impl AsNumber for UByteMembers {
2207        fn as_number() -> &'static PyNumberMethods {
2208            static METHODS: PyNumberMethods = PyNumberMethods {
2209                index: Some(|_, vm| Ok(vm.ctx.new_int(42).into())),
2210                ..PyNumberMethods::NOT_IMPLEMENTED
2211            };
2212            &METHODS
2213        }
2214    }
2215
2216    #[pyclass(name = "ReadonlyAtomicMembers", module = false)]
2217    #[derive(Debug, PyPayload)]
2218    struct ReadonlyAtomicMembers {
2219        #[pymember]
2220        byte: AtomicU8,
2221        #[pymember]
2222        object: crate::object::PyObjectCell,
2223    }
2224
2225    #[pyclass]
2226    impl ReadonlyAtomicMembers {}
2227
2228    #[test]
2229    fn readonly_atomic_members_use_atomic_storage() {
2230        crate::Interpreter::without_stdlib(Default::default()).enter(|vm| {
2231            let _class = ReadonlyAtomicMembers::make_static_type();
2232            let obj = ReadonlyAtomicMembers {
2233                byte: AtomicU8::new(128),
2234                object: None.into(),
2235            }
2236            .into_ref(&vm.ctx);
2237
2238            for name in ["byte", "object"] {
2239                let descriptor = obj.class().as_object().get_attr(name, vm).unwrap();
2240                let descriptor = descriptor.downcast_ref::<PyMemberDescriptor>().unwrap();
2241                assert!(descriptor.member.readonly());
2242                assert!(descriptor.member.atomic_storage());
2243                let err = obj
2244                    .as_object()
2245                    .set_attr(name, vm.ctx.none(), vm)
2246                    .unwrap_err();
2247                assert!(err.fast_isinstance(vm.ctx.exceptions.attribute_error));
2248            }
2249
2250            assert!(vm.is_none(&obj.as_object().get_attr("object", vm).unwrap()));
2251            obj.byte.store(255, Ordering::Relaxed);
2252            let value: PyObjectRef = vm.ctx.new_int(42).into();
2253            obj.object.store(Some(value.clone()));
2254            assert!(obj.as_object().get_attr("object", vm).unwrap().is(&value));
2255            let byte = obj.as_object().get_attr("byte", vm).unwrap();
2256            assert_eq!(
2257                u8::try_from(
2258                    byte.downcast_ref::<crate::builtins::PyInt>()
2259                        .unwrap()
2260                        .as_bigint()
2261                )
2262                .unwrap(),
2263                255
2264            );
2265            obj.object.store(None);
2266            assert!(vm.is_none(&obj.as_object().get_attr("object", vm).unwrap()));
2267        });
2268    }
2269
2270    #[test]
2271    fn unsigned_byte_members_preserve_adjacent_fields() {
2272        crate::Interpreter::without_stdlib(Default::default()).enter(|vm| {
2273            let _class = UByteMembers::make_static_type();
2274            let obj = UByteMembers {
2275                prefix: 17,
2276                readonly: 255,
2277                writable: AtomicU8::new(128),
2278                suffix: 29,
2279            }
2280            .into_ref(&vm.ctx);
2281
2282            assert_eq!(MemberKind::from_i32(9), Some(MemberKind::UByte));
2283            for (name, expected) in [("readonly", 255), ("writable", 128)] {
2284                let value = obj.as_object().get_attr(name, vm).unwrap();
2285                let value = value.downcast_ref::<crate::builtins::PyInt>().unwrap();
2286                assert_eq!(i32::try_from(value.as_bigint()).unwrap(), expected);
2287            }
2288            obj.as_object()
2289                .set_attr("writable", vm.ctx.new_int(255), vm)
2290                .unwrap();
2291            assert_eq!(obj.writable.load(Ordering::Relaxed), 255);
2292            assert_eq!((obj.prefix, obj.readonly, obj.suffix), (17, 255, 29));
2293
2294            let err = obj
2295                .as_object()
2296                .set_attr("readonly", vm.ctx.new_int(0), vm)
2297                .unwrap_err();
2298            assert!(err.fast_isinstance(vm.ctx.exceptions.attribute_error));
2299            let err = obj.as_object().del_attr("writable", vm).unwrap_err();
2300            assert!(err.fast_isinstance(vm.ctx.exceptions.type_error));
2301            assert_eq!(obj.writable.load(Ordering::Relaxed), 255);
2302
2303            let err = obj
2304                .as_object()
2305                .set_attr("writable", vm.ctx.new_int(i128::MAX), vm)
2306                .unwrap_err();
2307            assert!(err.fast_isinstance(vm.ctx.exceptions.overflow_error));
2308            assert_eq!(obj.writable.load(Ordering::Relaxed), 255);
2309
2310            obj.as_object()
2311                .set_attr("writable", obj.to_owned(), vm)
2312                .unwrap();
2313            assert_eq!(obj.writable.load(Ordering::Relaxed), 42);
2314
2315            let filters = vm.state.warnings.filters.to_owned();
2316            filters.borrow_vec_mut().insert(
2317                0,
2318                vm.ctx
2319                    .new_tuple(vec![
2320                        vm.ctx.new_str("error").into(),
2321                        vm.ctx.none(),
2322                        vm.ctx.exceptions.runtime_warning.to_owned().into(),
2323                        vm.ctx.none(),
2324                        vm.ctx.new_int(0).into(),
2325                    ])
2326                    .into(),
2327            );
2328            vm.state.warnings.filters_mutated();
2329            for (input, expected) in [(256, 0), (-1, 255)] {
2330                let err = obj
2331                    .as_object()
2332                    .set_attr("writable", vm.ctx.new_int(input), vm)
2333                    .unwrap_err();
2334                assert!(err.fast_isinstance(vm.ctx.exceptions.runtime_warning));
2335                // As in CPython, truncation is stored before the warning is raised.
2336                assert_eq!(obj.writable.load(Ordering::Relaxed), expected);
2337                assert_eq!((obj.prefix, obj.readonly, obj.suffix), (17, 255, 29));
2338            }
2339        });
2340    }
2341}