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_select! {
11    feature = "threading" => {
12        pub trait PyThreadingConstraint: Send + Sync {}
13        impl<T: Send + Sync> PyThreadingConstraint for T {}
14    }
15    _ => {
16        pub trait PyThreadingConstraint {}
17        impl<T> PyThreadingConstraint for T {}
18    }
19}
20
21#[cold]
22pub(crate) fn cold_downcast_type_error(
23    vm: &VirtualMachine,
24    class: &Py<PyType>,
25    obj: &PyObject,
26) -> PyBaseExceptionRef {
27    vm.new_downcast_type_error(class, obj)
28}
29
30/// Native subclasses declared with `#[pyclass(base = Base)]` share the base's
31/// payload type ID. Their base field must start at offset zero, and their
32/// payload must start at the same offset inside `Py<T>` as the base payload.
33/// The derived object must also meet the base object's alignment requirement.
34/// The macro checks these conditions at compile time. `repr(C)` on the payload
35/// alone does not guarantee the second condition:
36///
37/// ```compile_fail,E0080
38/// use rustpython_vm::{builtins::PyDict, pyclass};
39///
40/// #[pyclass(module = false, name = "MisalignedDict", base = PyDict)]
41/// #[derive(Debug)]
42/// #[repr(C, align(64))]
43/// struct MisalignedDict {
44///     base: PyDict,
45/// }
46///
47/// #[pyclass]
48/// impl MisalignedDict {}
49/// ```
50///
51/// An enum cannot provide the required base field layout:
52///
53/// ```compile_fail
54/// use rustpython_vm::{builtins::PyDict, pyclass};
55///
56/// #[pyclass(module = false, name = "EnumDict", base = PyDict)]
57/// #[derive(Debug)]
58/// enum EnumDict {
59///     Dict(PyDict),
60///     Empty,
61/// }
62///
63/// #[pyclass]
64/// impl EnumDict {}
65/// ```
66pub trait PyPayload: MaybeTraverse + PyThreadingConstraint + Sized + 'static {
67    const PAYLOAD_TYPE_ID: core::any::TypeId = core::any::TypeId::of::<Self>();
68
69    /// # Safety
70    /// This function should only be called if `payload_type_id` matches the type of `obj`.
71    #[inline]
72    unsafe fn validate_downcastable_from(_obj: &PyObject) -> bool {
73        true
74    }
75
76    fn try_downcast_from(obj: &PyObject, vm: &VirtualMachine) -> PyResult<()> {
77        if obj.downcastable::<Self>() {
78            return Ok(());
79        }
80
81        let class = Self::class(&vm.ctx);
82        Err(cold_downcast_type_error(vm, class, obj))
83    }
84
85    fn class(ctx: &Context) -> &'static Py<PyType>;
86
87    /// Whether `PyRef::new_ref` skips auto-tracking this type in the GC even
88    /// when it would otherwise qualify (has traverse, dict, or heap type).
89    /// Such objects are created untracked and must be tracked explicitly if
90    /// and when they can become part of a reference cycle. Used by `FrameObject`,
91    /// which is created untracked and tracked lazily only on escape.
92    const NEW_REF_UNTRACKED: bool = false;
93
94    /// Whether this type has a freelist. Types with freelists require
95    /// immediate (non-deferred) GC untracking during dealloc to prevent
96    /// race conditions when the object is reused.
97    const HAS_FREELIST: bool = false;
98
99    /// Maximum number of objects to keep in the freelist.
100    const MAX_FREELIST: usize = 0;
101
102    /// Try to push a dead object onto this type's freelist for reuse.
103    /// Returns true if the object was stored (caller must NOT free the memory).
104    /// Called after tp_clear, so the payload is a cleared husk; implementations
105    /// must not rely on its pre-clear contents.
106    ///
107    /// # Safety
108    /// `obj` must be a valid pointer to a `Py<Self>` with refcount 0
109    /// whose tp_clear has already run, with no outstanding borrows into the
110    /// payload (`PyRef::new_ref` may pop and reuse the husk immediately).
111    #[inline]
112    unsafe fn freelist_push(_obj: *mut PyObject) -> bool {
113        false
114    }
115
116    /// Try to pop a pre-allocated object from this type's freelist.
117    /// The returned pointer still has the old payload; the caller must
118    /// reinitialize `ref_count`, `gc_bits`, and `payload`.
119    ///
120    /// # Safety
121    /// The returned pointer (if Some) must point to a valid `Py<Self>`
122    /// whose payload is still initialized from a previous allocation. The caller
123    /// will drop and overwrite `payload` before reuse.
124    #[inline]
125    unsafe fn freelist_pop(_payload: &Self) -> Option<NonNull<PyObject>> {
126        None
127    }
128
129    #[inline]
130    fn into_pyobject(self, vm: &VirtualMachine) -> PyObjectRef
131    where
132        Self: core::fmt::Debug,
133    {
134        self.into_ref(&vm.ctx).into()
135    }
136
137    #[inline]
138    fn _into_ref(self, cls: PyTypeRef, ctx: &Context) -> PyRef<Self>
139    where
140        Self: core::fmt::Debug,
141    {
142        let dict = if cls.slots.flags.has_feature(PyTypeFlags::HAS_DICT) {
143            Some(ctx.new_dict())
144        } else {
145            None
146        };
147        PyRef::new_ref(self, cls, dict)
148    }
149
150    #[inline]
151    fn into_exact_ref(self, ctx: &Context) -> PyRefExact<Self>
152    where
153        Self: core::fmt::Debug,
154    {
155        unsafe {
156            // Self::into_ref() always returns exact typed PyRef
157            PyRefExact::new_unchecked(self.into_ref(ctx))
158        }
159    }
160
161    #[inline]
162    fn into_ref(self, ctx: &Context) -> PyRef<Self>
163    where
164        Self: core::fmt::Debug,
165    {
166        let cls = Self::class(ctx);
167        self._into_ref(cls.to_owned(), ctx)
168    }
169
170    #[inline]
171    fn into_ref_with_type(self, vm: &VirtualMachine, cls: PyTypeRef) -> PyResult<PyRef<Self>>
172    where
173        Self: core::fmt::Debug,
174    {
175        self.into_ref_with_type_and_dict(vm, cls, true)
176    }
177
178    /// Like `into_ref_with_type`, but leaves the instance `__dict__` unallocated
179    /// until the first attribute write or `__dict__` access. Only valid for types
180    /// whose attribute protocol materializes the dict lazily via `get_or_insert`.
181    #[inline]
182    fn into_ref_with_type_lazy_dict(
183        self,
184        vm: &VirtualMachine,
185        cls: PyTypeRef,
186    ) -> PyResult<PyRef<Self>>
187    where
188        Self: core::fmt::Debug,
189    {
190        self.into_ref_with_type_and_dict(vm, cls, false)
191    }
192
193    #[inline]
194    fn into_ref_with_type_and_dict(
195        self,
196        vm: &VirtualMachine,
197        cls: PyTypeRef,
198        eager_dict: bool,
199    ) -> PyResult<PyRef<Self>>
200    where
201        Self: core::fmt::Debug,
202    {
203        let exact_class = Self::class(&vm.ctx);
204        if cls.fast_issubclass(exact_class) {
205            if exact_class.slots.basicsize != cls.slots.basicsize {
206                #[cold]
207                #[inline(never)]
208                fn _into_ref_size_error(
209                    vm: &VirtualMachine,
210                    cls: &Py<PyType>,
211                    exact_class: &Py<PyType>,
212                ) -> PyBaseExceptionRef {
213                    vm.new_type_error(format!(
214                        "cannot create '{}' instance: size differs from base type '{}'",
215                        cls.name(),
216                        exact_class.name()
217                    ))
218                }
219                return Err(_into_ref_size_error(vm, &cls, exact_class));
220            }
221            let dict = if eager_dict && cls.slots.flags.has_feature(PyTypeFlags::HAS_DICT) {
222                Some(vm.ctx.new_dict())
223            } else {
224                None
225            };
226            Ok(PyRef::new_ref(self, cls, dict))
227        } else {
228            #[cold]
229            #[inline(never)]
230            fn _into_ref_with_type_error(
231                vm: &VirtualMachine,
232                cls: &Py<PyType>,
233                exact_class: &Py<PyType>,
234            ) -> PyBaseExceptionRef {
235                vm.new_type_error(format!(
236                    "'{}' is not a subtype of '{}'",
237                    cls.name(),
238                    exact_class.name()
239                ))
240            }
241            Err(_into_ref_with_type_error(vm, &cls, exact_class))
242        }
243    }
244}
245
246pub trait PyObjectPayload:
247    PyPayload + core::any::Any + core::fmt::Debug + MaybeTraverse + PyThreadingConstraint + 'static
248{
249}
250
251impl<T: PyPayload + core::fmt::Debug + 'static> PyObjectPayload for T {}
252
253pub trait SlotOffset {
254    fn offset() -> usize;
255}