Skip to main content

rustpython_vm/object/
payload.rs

1use crate::object::{MaybeTraverse, Py, PyObjectRef, PyRef, PyResult};
2use crate::{
3    PyObject, PyRefExact,
4    builtins::{PyBaseExceptionRef, PyType, PyTypeRef},
5    types::PyTypeFlags,
6    vm::{Context, VirtualMachine},
7};
8use core::ptr::NonNull;
9
10cfg_if::cfg_if! {
11    if #[cfg(feature = "threading")] {
12        pub trait PyThreadingConstraint: Send + Sync {}
13        impl<T: Send + Sync> PyThreadingConstraint for T {}
14    } else {
15        pub trait PyThreadingConstraint {}
16        impl<T> PyThreadingConstraint for T {}
17    }
18}
19
20#[cold]
21pub(crate) fn cold_downcast_type_error(
22    vm: &VirtualMachine,
23    class: &Py<PyType>,
24    obj: &PyObject,
25) -> PyBaseExceptionRef {
26    vm.new_downcast_type_error(class, obj)
27}
28
29pub trait PyPayload: MaybeTraverse + PyThreadingConstraint + Sized + 'static {
30    const PAYLOAD_TYPE_ID: core::any::TypeId = core::any::TypeId::of::<Self>();
31
32    /// # Safety
33    /// This function should only be called if `payload_type_id` matches the type of `obj`.
34    #[inline]
35    unsafe fn validate_downcastable_from(_obj: &PyObject) -> bool {
36        true
37    }
38
39    fn try_downcast_from(obj: &PyObject, vm: &VirtualMachine) -> PyResult<()> {
40        if obj.downcastable::<Self>() {
41            return Ok(());
42        }
43
44        let class = Self::class(&vm.ctx);
45        Err(cold_downcast_type_error(vm, class, obj))
46    }
47
48    fn class(ctx: &Context) -> &'static Py<PyType>;
49
50    /// Whether this type has a freelist. Types with freelists require
51    /// immediate (non-deferred) GC untracking during dealloc to prevent
52    /// race conditions when the object is reused.
53    const HAS_FREELIST: bool = false;
54
55    /// Maximum number of objects to keep in the freelist.
56    const MAX_FREELIST: usize = 0;
57
58    /// Try to push a dead object onto this type's freelist for reuse.
59    /// Returns true if the object was stored (caller must NOT free the memory).
60    /// Called before tp_clear, so the payload is still intact.
61    ///
62    /// # Safety
63    /// `obj` must be a valid pointer to a `PyInner<Self>` with refcount 0.
64    /// The payload is still initialized and can be read for bucket selection.
65    #[inline]
66    unsafe fn freelist_push(_obj: *mut PyObject) -> bool {
67        false
68    }
69
70    /// Try to pop a pre-allocated object from this type's freelist.
71    /// The returned pointer still has the old payload; the caller must
72    /// reinitialize `ref_count`, `gc_bits`, and `payload`.
73    ///
74    /// # Safety
75    /// The returned pointer (if Some) must point to a valid `PyInner<Self>`
76    /// whose payload is still initialized from a previous allocation. The caller
77    /// will drop and overwrite `payload` before reuse.
78    #[inline]
79    unsafe fn freelist_pop(_payload: &Self) -> Option<NonNull<PyObject>> {
80        None
81    }
82
83    #[inline]
84    fn into_pyobject(self, vm: &VirtualMachine) -> PyObjectRef
85    where
86        Self: core::fmt::Debug,
87    {
88        self.into_ref(&vm.ctx).into()
89    }
90
91    #[inline]
92    fn _into_ref(self, cls: PyTypeRef, ctx: &Context) -> PyRef<Self>
93    where
94        Self: core::fmt::Debug,
95    {
96        let dict = if cls.slots.flags.has_feature(PyTypeFlags::HAS_DICT) {
97            Some(ctx.new_dict())
98        } else {
99            None
100        };
101        PyRef::new_ref(self, cls, dict)
102    }
103
104    #[inline]
105    fn into_exact_ref(self, ctx: &Context) -> PyRefExact<Self>
106    where
107        Self: core::fmt::Debug,
108    {
109        unsafe {
110            // Self::into_ref() always returns exact typed PyRef
111            PyRefExact::new_unchecked(self.into_ref(ctx))
112        }
113    }
114
115    #[inline]
116    fn into_ref(self, ctx: &Context) -> PyRef<Self>
117    where
118        Self: core::fmt::Debug,
119    {
120        let cls = Self::class(ctx);
121        self._into_ref(cls.to_owned(), ctx)
122    }
123
124    #[inline]
125    fn into_ref_with_type(self, vm: &VirtualMachine, cls: PyTypeRef) -> PyResult<PyRef<Self>>
126    where
127        Self: core::fmt::Debug,
128    {
129        let exact_class = Self::class(&vm.ctx);
130        if cls.fast_issubclass(exact_class) {
131            if exact_class.slots.basicsize != cls.slots.basicsize {
132                #[cold]
133                #[inline(never)]
134                fn _into_ref_size_error(
135                    vm: &VirtualMachine,
136                    cls: &Py<PyType>,
137                    exact_class: &Py<PyType>,
138                ) -> PyBaseExceptionRef {
139                    vm.new_type_error(format!(
140                        "cannot create '{}' instance: size differs from base type '{}'",
141                        cls.name(),
142                        exact_class.name()
143                    ))
144                }
145                return Err(_into_ref_size_error(vm, &cls, exact_class));
146            }
147            Ok(self._into_ref(cls, &vm.ctx))
148        } else {
149            #[cold]
150            #[inline(never)]
151            fn _into_ref_with_type_error(
152                vm: &VirtualMachine,
153                cls: &Py<PyType>,
154                exact_class: &Py<PyType>,
155            ) -> PyBaseExceptionRef {
156                vm.new_type_error(format!(
157                    "'{}' is not a subtype of '{}'",
158                    &cls.name(),
159                    exact_class.name()
160                ))
161            }
162            Err(_into_ref_with_type_error(vm, &cls, exact_class))
163        }
164    }
165}
166
167pub trait PyObjectPayload:
168    PyPayload + core::any::Any + core::fmt::Debug + MaybeTraverse + PyThreadingConstraint + 'static
169{
170}
171
172impl<T: PyPayload + core::fmt::Debug + 'static> PyObjectPayload for T {}
173
174pub trait SlotOffset {
175    fn offset() -> usize;
176}