Skip to main content

rustpython_vm/object/
ext.rs

1use super::{
2    core::{Py, PyObject, PyObjectRef, PyRef},
3    payload::PyPayload,
4};
5use crate::common::{
6    atomic::{Ordering, PyAtomic, Radium},
7    lock::PyRwLockReadGuard,
8};
9use crate::{
10    VirtualMachine,
11    builtins::{PyBaseExceptionRef, PyStrInterned, PyType},
12    convert::{IntoPyException, ToPyObject, ToPyResult, TryFromObject},
13    vm::Context,
14};
15use alloc::fmt;
16
17use core::{
18    borrow::Borrow,
19    marker::PhantomData,
20    ops::Deref,
21    ptr::{NonNull, null_mut},
22};
23
24/* Python objects and references.
25
26Okay, so each python object itself is an class itself (PyObject). Each
27python object can have several references to it (PyObjectRef). These
28references are Rc (reference counting) rust smart pointers. So when
29all references are destroyed, the object itself also can be cleaned up.
30Basically reference counting, but then done by rust.
31
32*/
33
34/*
35 * Good reference: https://github.com/ProgVal/pythonvm-rust/blob/master/src/objects/mod.rs
36 */
37
38/// Use this type for functions which return a python object or an exception.
39/// Both the python object and the python exception are `PyObjectRef` types
40/// since exceptions are also python objects.
41pub type PyResult<T = PyObjectRef> = Result<T, PyBaseExceptionRef>; // A valid value, or an exception
42
43impl<T: fmt::Display> fmt::Display for PyRef<T>
44where
45    T: PyPayload + fmt::Display,
46{
47    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
48        fmt::Display::fmt(&**self, f)
49    }
50}
51
52impl<T: fmt::Display> fmt::Display for Py<T>
53where
54    T: PyPayload + fmt::Display,
55{
56    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
57        fmt::Display::fmt(&**self, f)
58    }
59}
60
61#[repr(transparent)]
62pub struct PyExact<T> {
63    inner: Py<T>,
64}
65
66impl<T: PyPayload> PyExact<T> {
67    /// # Safety
68    /// Given reference must be exact type of payload T
69    #[inline(always)]
70    pub const unsafe fn ref_unchecked(r: &Py<T>) -> &Self {
71        unsafe { &*(r as *const _ as *const Self) }
72    }
73}
74
75impl<T: PyPayload> Deref for PyExact<T> {
76    type Target = Py<T>;
77
78    #[inline(always)]
79    fn deref(&self) -> &Py<T> {
80        &self.inner
81    }
82}
83
84impl<T: PyPayload> Borrow<PyObject> for PyExact<T> {
85    #[inline(always)]
86    fn borrow(&self) -> &PyObject {
87        self.inner.borrow()
88    }
89}
90
91impl<T: PyPayload> AsRef<PyObject> for PyExact<T> {
92    #[inline(always)]
93    fn as_ref(&self) -> &PyObject {
94        self.inner.as_ref()
95    }
96}
97
98impl<T: PyPayload> Borrow<Py<T>> for PyExact<T> {
99    #[inline(always)]
100    fn borrow(&self) -> &Py<T> {
101        &self.inner
102    }
103}
104
105impl<T: PyPayload> AsRef<Py<T>> for PyExact<T> {
106    #[inline(always)]
107    fn as_ref(&self) -> &Py<T> {
108        &self.inner
109    }
110}
111
112impl<T: PyPayload> alloc::borrow::ToOwned for PyExact<T> {
113    type Owned = PyRefExact<T>;
114
115    fn to_owned(&self) -> Self::Owned {
116        let owned = self.inner.to_owned();
117        unsafe { PyRefExact::new_unchecked(owned) }
118    }
119}
120
121impl<T: PyPayload> PyRef<T> {
122    pub fn into_exact_or(
123        self,
124        ctx: &Context,
125        f: impl FnOnce(Self) -> PyRefExact<T>,
126    ) -> PyRefExact<T> {
127        if self.class().is(T::class(ctx)) {
128            unsafe { PyRefExact::new_unchecked(self) }
129        } else {
130            f(self)
131        }
132    }
133}
134
135/// PyRef but guaranteed not to be a subtype instance
136#[derive(Debug)]
137#[repr(transparent)]
138pub struct PyRefExact<T: PyPayload> {
139    inner: PyRef<T>,
140}
141
142impl<T: PyPayload> PyRefExact<T> {
143    /// # Safety
144    /// obj must have exact type for the payload
145    pub const unsafe fn new_unchecked(obj: PyRef<T>) -> Self {
146        Self { inner: obj }
147    }
148
149    pub fn into_pyref(self) -> PyRef<T> {
150        self.inner
151    }
152}
153
154impl<T: PyPayload> Clone for PyRefExact<T> {
155    fn clone(&self) -> Self {
156        let inner = self.inner.clone();
157        Self { inner }
158    }
159}
160
161impl<T: PyPayload> TryFromObject for PyRefExact<T> {
162    fn try_from_object(vm: &VirtualMachine, obj: PyObjectRef) -> PyResult<Self> {
163        let target_cls = T::class(&vm.ctx);
164        let cls = obj.class();
165        if cls.is(target_cls) {
166            let obj = obj
167                .downcast()
168                .map_err(|obj| vm.new_downcast_runtime_error(target_cls, &obj))?;
169            Ok(Self { inner: obj })
170        } else if cls.fast_issubclass(target_cls) {
171            Err(vm.new_type_error(format!(
172                "Expected an exact instance of '{}', not a subclass '{}'",
173                target_cls.name(),
174                cls.name(),
175            )))
176        } else {
177            Err(vm.new_type_error(format!(
178                "Expected type '{}', not '{}'",
179                target_cls.name(),
180                cls.name(),
181            )))
182        }
183    }
184}
185
186impl<T: PyPayload> Deref for PyRefExact<T> {
187    type Target = PyExact<T>;
188
189    #[inline(always)]
190    fn deref(&self) -> &PyExact<T> {
191        unsafe { PyExact::ref_unchecked(self.inner.deref()) }
192    }
193}
194
195impl<T: PyPayload> Borrow<PyObject> for PyRefExact<T> {
196    #[inline(always)]
197    fn borrow(&self) -> &PyObject {
198        self.inner.borrow()
199    }
200}
201
202impl<T: PyPayload> AsRef<PyObject> for PyRefExact<T> {
203    #[inline(always)]
204    fn as_ref(&self) -> &PyObject {
205        self.inner.as_ref()
206    }
207}
208
209impl<T: PyPayload> Borrow<Py<T>> for PyRefExact<T> {
210    #[inline(always)]
211    fn borrow(&self) -> &Py<T> {
212        self.inner.borrow()
213    }
214}
215
216impl<T: PyPayload> AsRef<Py<T>> for PyRefExact<T> {
217    #[inline(always)]
218    fn as_ref(&self) -> &Py<T> {
219        self.inner.as_ref()
220    }
221}
222
223impl<T: PyPayload> Borrow<PyExact<T>> for PyRefExact<T> {
224    #[inline(always)]
225    fn borrow(&self) -> &PyExact<T> {
226        self
227    }
228}
229
230impl<T: PyPayload> AsRef<PyExact<T>> for PyRefExact<T> {
231    #[inline(always)]
232    fn as_ref(&self) -> &PyExact<T> {
233        self
234    }
235}
236
237impl<T: PyPayload> ToPyObject for PyRefExact<T> {
238    #[inline(always)]
239    fn to_pyobject(self, _vm: &VirtualMachine) -> PyObjectRef {
240        self.inner.into()
241    }
242}
243
244pub struct PyAtomicRef<T> {
245    inner: PyAtomic<*mut u8>,
246    _phantom: PhantomData<T>,
247}
248
249impl<T> Drop for PyAtomicRef<T> {
250    fn drop(&mut self) {
251        // SAFETY: We are dropping the atomic reference, so we can safely
252        // release the pointer.
253        unsafe {
254            let ptr = Radium::swap(&self.inner, null_mut(), Ordering::Relaxed);
255            if let Some(ptr) = NonNull::<PyObject>::new(ptr.cast()) {
256                let _: PyObjectRef = PyObjectRef::from_raw(ptr);
257            }
258        }
259    }
260}
261
262cfg_if::cfg_if! {
263    if #[cfg(feature = "threading")] {
264        unsafe impl<T: Send + PyPayload> Send for PyAtomicRef<T> {}
265        unsafe impl<T: Sync + PyPayload> Sync for PyAtomicRef<T> {}
266        unsafe impl<T: Send + PyPayload> Send for PyAtomicRef<Option<T>> {}
267        unsafe impl<T: Sync + PyPayload> Sync for PyAtomicRef<Option<T>> {}
268        unsafe impl Send for PyAtomicRef<PyObject> {}
269        unsafe impl Sync for PyAtomicRef<PyObject> {}
270        unsafe impl Send for PyAtomicRef<Option<PyObject>> {}
271        unsafe impl Sync for PyAtomicRef<Option<PyObject>> {}
272    }
273}
274
275impl<T: fmt::Debug> fmt::Debug for PyAtomicRef<T> {
276    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
277        write!(f, "PyAtomicRef(")?;
278        unsafe {
279            self.inner
280                .load(Ordering::Relaxed)
281                .cast::<T>()
282                .as_ref()
283                .fmt(f)
284        }?;
285        write!(f, ")")
286    }
287}
288
289impl<T: PyPayload> From<PyRef<T>> for PyAtomicRef<T> {
290    fn from(pyref: PyRef<T>) -> Self {
291        let py = PyRef::leak(pyref);
292        let ptr = py as *const _ as *mut u8;
293        // Expose provenance so we can re-derive via with_exposed_provenance
294        // without Stacked Borrows tag restrictions during bootstrap
295        ptr.expose_provenance();
296        Self {
297            inner: Radium::new(ptr),
298            _phantom: Default::default(),
299        }
300    }
301}
302
303impl<T: PyPayload> Deref for PyAtomicRef<T> {
304    type Target = Py<T>;
305
306    fn deref(&self) -> &Self::Target {
307        unsafe {
308            self.inner
309                .load(Ordering::Relaxed)
310                .cast::<Py<T>>()
311                .as_ref()
312                .unwrap_unchecked()
313        }
314    }
315}
316
317impl<T: PyPayload> PyAtomicRef<T> {
318    /// Load the raw pointer without creating a reference.
319    /// Avoids Stacked Borrows retag, safe for use during bootstrap
320    /// when type objects have self-referential pointers being mutated.
321    #[inline(always)]
322    pub(super) fn load_raw(&self) -> *const Py<T> {
323        self.inner.load(Ordering::Relaxed).cast::<Py<T>>()
324    }
325
326    /// # Safety
327    /// The caller is responsible to keep the returned PyRef alive
328    /// until no more reference can be used via PyAtomicRef::deref()
329    #[must_use]
330    pub unsafe fn swap(&self, pyref: PyRef<T>) -> PyRef<T> {
331        let py = PyRef::leak(pyref) as *const Py<T> as *mut _;
332        let old = Radium::swap(&self.inner, py, Ordering::AcqRel);
333        unsafe { PyRef::from_raw(old.cast()) }
334    }
335
336    pub fn swap_to_temporary_refs(&self, pyref: PyRef<T>, vm: &VirtualMachine) {
337        let old = unsafe { self.swap(pyref) };
338        if let Some(frame) = vm.current_frame() {
339            frame.temporary_refs.lock().push(old.into());
340        }
341    }
342}
343
344impl<T: PyPayload> From<Option<PyRef<T>>> for PyAtomicRef<Option<T>> {
345    fn from(opt_ref: Option<PyRef<T>>) -> Self {
346        let val = opt_ref
347            .map(|x| PyRef::leak(x) as *const Py<T> as *mut _)
348            .unwrap_or(null_mut());
349        Self {
350            inner: Radium::new(val),
351            _phantom: Default::default(),
352        }
353    }
354}
355
356impl<T: PyPayload> PyAtomicRef<Option<T>> {
357    pub fn deref(&self) -> Option<&Py<T>> {
358        self.deref_ordering(Ordering::Relaxed)
359    }
360
361    pub fn deref_ordering(&self, ordering: Ordering) -> Option<&Py<T>> {
362        unsafe { self.inner.load(ordering).cast::<Py<T>>().as_ref() }
363    }
364
365    pub fn to_owned(&self) -> Option<PyRef<T>> {
366        self.to_owned_ordering(Ordering::Relaxed)
367    }
368
369    pub fn to_owned_ordering(&self, ordering: Ordering) -> Option<PyRef<T>> {
370        self.deref_ordering(ordering).map(|x| x.to_owned())
371    }
372
373    /// # Safety
374    /// The caller is responsible to keep the returned PyRef alive
375    /// until no more reference can be used via PyAtomicRef::deref()
376    #[must_use]
377    pub unsafe fn swap(&self, opt_ref: Option<PyRef<T>>) -> Option<PyRef<T>> {
378        let val = opt_ref
379            .map(|x| PyRef::leak(x) as *const Py<T> as *mut _)
380            .unwrap_or(null_mut());
381        let old = Radium::swap(&self.inner, val, Ordering::AcqRel);
382        unsafe { old.cast::<Py<T>>().as_ref().map(|x| PyRef::from_raw(x)) }
383    }
384
385    pub fn swap_to_temporary_refs(&self, opt_ref: Option<PyRef<T>>, vm: &VirtualMachine) {
386        let Some(old) = (unsafe { self.swap(opt_ref) }) else {
387            return;
388        };
389        if let Some(frame) = vm.current_frame() {
390            frame.temporary_refs.lock().push(old.into());
391        }
392    }
393}
394
395impl From<PyObjectRef> for PyAtomicRef<PyObject> {
396    fn from(obj: PyObjectRef) -> Self {
397        let obj = obj.into_raw();
398        Self {
399            inner: Radium::new(obj.cast().as_ptr()),
400            _phantom: Default::default(),
401        }
402    }
403}
404
405impl Deref for PyAtomicRef<PyObject> {
406    type Target = PyObject;
407
408    fn deref(&self) -> &Self::Target {
409        unsafe {
410            self.inner
411                .load(Ordering::Relaxed)
412                .cast::<PyObject>()
413                .as_ref()
414                .unwrap_unchecked()
415        }
416    }
417}
418
419impl PyAtomicRef<PyObject> {
420    /// # Safety
421    /// The caller is responsible to keep the returned PyRef alive
422    /// until no more reference can be used via PyAtomicRef::deref()
423    #[must_use]
424    pub unsafe fn swap(&self, obj: PyObjectRef) -> PyObjectRef {
425        let obj = obj.into_raw();
426        let old = Radium::swap(&self.inner, obj.cast().as_ptr(), Ordering::AcqRel);
427        unsafe { PyObjectRef::from_raw(NonNull::new_unchecked(old.cast())) }
428    }
429
430    pub fn swap_to_temporary_refs(&self, obj: PyObjectRef, vm: &VirtualMachine) {
431        let old = unsafe { self.swap(obj) };
432        if let Some(frame) = vm.current_frame() {
433            frame.temporary_refs.lock().push(old);
434        }
435    }
436}
437
438impl From<Option<PyObjectRef>> for PyAtomicRef<Option<PyObject>> {
439    fn from(obj: Option<PyObjectRef>) -> Self {
440        let val = obj
441            .map(|x| x.into_raw().as_ptr().cast())
442            .unwrap_or(null_mut());
443        Self {
444            inner: Radium::new(val),
445            _phantom: Default::default(),
446        }
447    }
448}
449
450impl PyAtomicRef<Option<PyObject>> {
451    pub fn deref(&self) -> Option<&PyObject> {
452        self.deref_ordering(Ordering::Relaxed)
453    }
454
455    pub fn deref_ordering(&self, ordering: Ordering) -> Option<&PyObject> {
456        unsafe { self.inner.load(ordering).cast::<PyObject>().as_ref() }
457    }
458
459    pub fn to_owned(&self) -> Option<PyObjectRef> {
460        self.to_owned_ordering(Ordering::Relaxed)
461    }
462
463    pub fn to_owned_ordering(&self, ordering: Ordering) -> Option<PyObjectRef> {
464        self.deref_ordering(ordering).map(|x| x.to_owned())
465    }
466
467    /// # Safety
468    /// The caller is responsible to keep the returned PyRef alive
469    /// until no more reference can be used via PyAtomicRef::deref()
470    #[must_use]
471    pub unsafe fn swap(&self, obj: Option<PyObjectRef>) -> Option<PyObjectRef> {
472        let val = obj
473            .map(|x| x.into_raw().as_ptr().cast())
474            .unwrap_or(null_mut());
475        let old = Radium::swap(&self.inner, val, Ordering::AcqRel);
476        unsafe { NonNull::new(old.cast::<PyObject>()).map(|x| PyObjectRef::from_raw(x)) }
477    }
478
479    pub fn swap_to_temporary_refs(&self, obj: Option<PyObjectRef>, vm: &VirtualMachine) {
480        let Some(old) = (unsafe { self.swap(obj) }) else {
481            return;
482        };
483        if let Some(frame) = vm.current_frame() {
484            frame.temporary_refs.lock().push(old);
485        }
486    }
487}
488
489/// Atomic borrowed (non-ref-counted) optional reference to a Python object.
490/// Unlike `PyAtomicRef`, this does NOT own the reference.
491/// The pointed-to object must outlive this reference.
492pub struct PyAtomicBorrow {
493    inner: PyAtomic<*mut u8>,
494}
495
496// Safety: Access patterns ensure the pointed-to object outlives this reference.
497// The owner (generator/coroutine) clears this in its Drop impl before deallocation.
498unsafe impl Send for PyAtomicBorrow {}
499unsafe impl Sync for PyAtomicBorrow {}
500
501impl PyAtomicBorrow {
502    pub fn new() -> Self {
503        Self {
504            inner: Radium::new(null_mut()),
505        }
506    }
507
508    pub fn store(&self, obj: &PyObject) {
509        let ptr = obj as *const PyObject as *mut u8;
510        Radium::store(&self.inner, ptr, Ordering::Relaxed);
511    }
512
513    pub fn load(&self) -> Option<&PyObject> {
514        let ptr = Radium::load(&self.inner, Ordering::Relaxed);
515        if ptr.is_null() {
516            None
517        } else {
518            Some(unsafe { &*(ptr as *const PyObject) })
519        }
520    }
521
522    pub fn clear(&self) {
523        Radium::store(&self.inner, null_mut(), Ordering::Relaxed);
524    }
525
526    pub fn to_owned(&self) -> Option<PyObjectRef> {
527        self.load().map(|obj| obj.to_owned())
528    }
529}
530
531impl Default for PyAtomicBorrow {
532    fn default() -> Self {
533        Self::new()
534    }
535}
536
537impl fmt::Debug for PyAtomicBorrow {
538    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
539        write!(
540            f,
541            "PyAtomicBorrow({:?})",
542            Radium::load(&self.inner, Ordering::Relaxed)
543        )
544    }
545}
546
547pub trait AsObject
548where
549    Self: Borrow<PyObject>,
550{
551    #[inline(always)]
552    fn as_object(&self) -> &PyObject {
553        self.borrow()
554    }
555
556    #[inline(always)]
557    fn get_id(&self) -> usize {
558        self.as_object().unique_id()
559    }
560
561    #[inline(always)]
562    fn is<T>(&self, other: &T) -> bool
563    where
564        T: AsObject,
565    {
566        self.get_id() == other.get_id()
567    }
568
569    #[inline(always)]
570    fn class(&self) -> &Py<PyType> {
571        self.as_object().class()
572    }
573
574    fn get_class_attr(&self, attr_name: &'static PyStrInterned) -> Option<PyObjectRef> {
575        self.class().get_attr(attr_name)
576    }
577
578    /// Determines if `obj` actually an instance of `cls`, this doesn't call __instancecheck__, so only
579    /// use this if `cls` is known to have not overridden the base __instancecheck__ magic method.
580    #[inline]
581    fn fast_isinstance(&self, cls: &Py<PyType>) -> bool {
582        self.class().fast_issubclass(cls)
583    }
584}
585
586impl<T> AsObject for T where T: Borrow<PyObject> {}
587
588impl PyObject {
589    #[inline(always)]
590    fn unique_id(&self) -> usize {
591        self as *const Self as usize
592    }
593}
594
595// impl<T: ?Sized> Borrow<PyObject> for PyRc<T> {
596//     #[inline(always)]
597//     fn borrow(&self) -> &PyObject {
598//         unsafe { &*(&**self as *const T as *const PyObject) }
599//     }
600// }
601
602/// A borrow of a reference to a Python object. This avoids having clone the `PyRef<T>`/
603/// `PyObjectRef`, which isn't that cheap as that increments the atomic reference counter.
604// TODO: check if we still need this
605#[allow(dead_code)]
606pub struct PyLease<'a, T: PyPayload> {
607    inner: PyRwLockReadGuard<'a, PyRef<T>>,
608}
609
610impl<T: PyPayload> PyLease<'_, T> {
611    #[inline(always)]
612    pub fn into_owned(self) -> PyRef<T> {
613        self.inner.clone()
614    }
615}
616
617impl<T: PyPayload> Borrow<PyObject> for PyLease<'_, T> {
618    #[inline(always)]
619    fn borrow(&self) -> &PyObject {
620        self.inner.as_ref()
621    }
622}
623
624impl<T: PyPayload> Deref for PyLease<'_, T> {
625    type Target = PyRef<T>;
626    #[inline(always)]
627    fn deref(&self) -> &Self::Target {
628        &self.inner
629    }
630}
631
632impl<T> fmt::Display for PyLease<'_, T>
633where
634    T: PyPayload + fmt::Display,
635{
636    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
637        fmt::Display::fmt(&**self, f)
638    }
639}
640
641impl<T: PyPayload> ToPyObject for PyRef<T> {
642    #[inline(always)]
643    fn to_pyobject(self, _vm: &VirtualMachine) -> PyObjectRef {
644        self.into()
645    }
646}
647
648impl ToPyObject for PyObjectRef {
649    #[inline(always)]
650    fn to_pyobject(self, _vm: &VirtualMachine) -> PyObjectRef {
651        self
652    }
653}
654
655impl ToPyObject for &PyObject {
656    #[inline(always)]
657    fn to_pyobject(self, _vm: &VirtualMachine) -> PyObjectRef {
658        self.to_owned()
659    }
660}
661
662// Allows a built-in function to return any built-in object payload without
663// explicitly implementing `ToPyObject`.
664impl<T> ToPyObject for T
665where
666    T: PyPayload + core::fmt::Debug + Sized,
667{
668    #[inline(always)]
669    fn to_pyobject(self, vm: &VirtualMachine) -> PyObjectRef {
670        PyPayload::into_pyobject(self, vm)
671    }
672}
673
674impl<T> ToPyResult for T
675where
676    T: ToPyObject,
677{
678    #[inline(always)]
679    fn to_pyresult(self, vm: &VirtualMachine) -> PyResult {
680        Ok(self.to_pyobject(vm))
681    }
682}
683
684impl<T, E> ToPyResult for Result<T, E>
685where
686    T: ToPyObject,
687    E: IntoPyException,
688{
689    #[inline(always)]
690    fn to_pyresult(self, vm: &VirtualMachine) -> PyResult {
691        self.map(|res| T::to_pyobject(res, vm))
692            .map_err(|e| E::into_pyexception(e, vm))
693    }
694}
695
696impl IntoPyException for PyBaseExceptionRef {
697    #[inline(always)]
698    fn into_pyexception(self, _vm: &VirtualMachine) -> PyBaseExceptionRef {
699        self
700    }
701}