use crate::conversion::AsPyPointer;
use crate::exceptions::PyRuntimeError;
use crate::ffi_ptr_ext::FfiPtrExt;
use crate::internal_tricks::{ptr_from_mut, ptr_from_ref};
use crate::pyclass::{boolean_struct::False, PyClass};
use crate::types::any::PyAnyMethods;
#[cfg(feature = "gil-refs")]
use crate::{
conversion::ToPyObject,
impl_::pyclass::PyClassImpl,
pyclass::boolean_struct::True,
pyclass_init::PyClassInitializer,
type_object::{PyLayout, PySizedLayout},
types::PyAny,
PyNativeType, PyResult, PyTypeCheck,
};
use crate::{ffi, Bound, IntoPy, PyErr, PyObject, Python};
use std::fmt;
use std::mem::ManuallyDrop;
use std::ops::{Deref, DerefMut};
pub(crate) mod impl_;
#[cfg(feature = "gil-refs")]
use self::impl_::PyClassObject;
use impl_::{PyClassBorrowChecker, PyClassObjectLayout};
#[cfg(feature = "gil-refs")]
#[deprecated(
since = "0.21.0",
note = "`PyCell` was merged into `Bound`, use that instead; see the migration guide for more info"
)]
#[repr(transparent)]
pub struct PyCell<T: PyClassImpl>(PyClassObject<T>);
#[cfg(feature = "gil-refs")]
#[allow(deprecated)]
unsafe impl<T: PyClass> PyNativeType for PyCell<T> {
type AsRefSource = T;
}
#[cfg(feature = "gil-refs")]
#[allow(deprecated)]
impl<T: PyClass> PyCell<T> {
#[deprecated(
since = "0.21.0",
note = "use `Bound::new(py, value)` or `Py::new(py, value)` instead of `PyCell::new(py, value)`"
)]
pub fn new(py: Python<'_>, value: impl Into<PyClassInitializer<T>>) -> PyResult<&Self> {
Bound::new(py, value).map(Bound::into_gil_ref)
}
pub fn borrow(&self) -> PyRef<'_, T> {
PyRef::borrow(&self.as_borrowed())
}
pub fn borrow_mut(&self) -> PyRefMut<'_, T>
where
T: PyClass<Frozen = False>,
{
PyRefMut::borrow(&self.as_borrowed())
}
pub fn try_borrow(&self) -> Result<PyRef<'_, T>, PyBorrowError> {
PyRef::try_borrow(&self.as_borrowed())
}
pub fn try_borrow_mut(&self) -> Result<PyRefMut<'_, T>, PyBorrowMutError>
where
T: PyClass<Frozen = False>,
{
PyRefMut::try_borrow(&self.as_borrowed())
}
pub unsafe fn try_borrow_unguarded(&self) -> Result<&T, PyBorrowError> {
self.0.ensure_threadsafe();
self.0
.borrow_checker()
.try_borrow_unguarded()
.map(|_: ()| &*self.0.get_ptr())
}
pub fn get(&self) -> &T
where
T: PyClass<Frozen = True> + Sync,
{
unsafe { &*self.get_ptr() }
}
#[inline]
pub fn replace(&self, t: T) -> T
where
T: PyClass<Frozen = False>,
{
std::mem::replace(&mut *self.borrow_mut(), t)
}
pub fn replace_with<F: FnOnce(&mut T) -> T>(&self, f: F) -> T
where
T: PyClass<Frozen = False>,
{
let mut_borrow = &mut *self.borrow_mut();
let replacement = f(mut_borrow);
std::mem::replace(mut_borrow, replacement)
}
#[inline]
pub fn swap(&self, other: &Self)
where
T: PyClass<Frozen = False>,
{
std::mem::swap(&mut *self.borrow_mut(), &mut *other.borrow_mut())
}
pub(crate) fn get_ptr(&self) -> *mut T {
self.0.get_ptr()
}
}
#[cfg(feature = "gil-refs")]
#[allow(deprecated)]
unsafe impl<T: PyClassImpl> PyLayout<T> for PyCell<T> {}
#[cfg(feature = "gil-refs")]
#[allow(deprecated)]
impl<T: PyClass> PySizedLayout<T> for PyCell<T> {}
#[cfg(feature = "gil-refs")]
#[allow(deprecated)]
impl<T> PyTypeCheck for PyCell<T>
where
T: PyClass,
{
const NAME: &'static str = <T as PyTypeCheck>::NAME;
fn type_check(object: &Bound<'_, PyAny>) -> bool {
<T as PyTypeCheck>::type_check(object)
}
}
#[cfg(feature = "gil-refs")]
#[allow(deprecated)]
unsafe impl<T: PyClass> AsPyPointer for PyCell<T> {
fn as_ptr(&self) -> *mut ffi::PyObject {
ptr_from_ref(self) as *mut _
}
}
#[cfg(feature = "gil-refs")]
#[allow(deprecated)]
impl<T: PyClass> ToPyObject for &PyCell<T> {
fn to_object(&self, py: Python<'_>) -> PyObject {
unsafe { PyObject::from_borrowed_ptr(py, self.as_ptr()) }
}
}
#[cfg(feature = "gil-refs")]
#[allow(deprecated)]
impl<T: PyClass> AsRef<PyAny> for PyCell<T> {
fn as_ref(&self) -> &PyAny {
#[allow(deprecated)]
unsafe {
self.py().from_borrowed_ptr(self.as_ptr())
}
}
}
#[cfg(feature = "gil-refs")]
#[allow(deprecated)]
impl<T: PyClass> Deref for PyCell<T> {
type Target = PyAny;
fn deref(&self) -> &PyAny {
#[allow(deprecated)]
unsafe {
self.py().from_borrowed_ptr(self.as_ptr())
}
}
}
#[cfg(feature = "gil-refs")]
#[allow(deprecated)]
impl<T: PyClass + fmt::Debug> fmt::Debug for PyCell<T> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self.try_borrow() {
Ok(borrow) => f.debug_struct("RefCell").field("value", &borrow).finish(),
Err(_) => {
struct BorrowedPlaceholder;
impl fmt::Debug for BorrowedPlaceholder {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.write_str("<borrowed>")
}
}
f.debug_struct("RefCell")
.field("value", &BorrowedPlaceholder)
.finish()
}
}
}
}
#[repr(transparent)]
pub struct PyRef<'p, T: PyClass> {
inner: Bound<'p, T>,
}
impl<'p, T: PyClass> PyRef<'p, T> {
pub fn py(&self) -> Python<'p> {
self.inner.py()
}
}
impl<'p, T, U> AsRef<U> for PyRef<'p, T>
where
T: PyClass<BaseType = U>,
U: PyClass,
{
fn as_ref(&self) -> &T::BaseType {
self.as_super()
}
}
impl<'py, T: PyClass> PyRef<'py, T> {
#[inline]
pub fn as_ptr(&self) -> *mut ffi::PyObject {
self.inner.as_ptr()
}
#[inline]
pub fn into_ptr(self) -> *mut ffi::PyObject {
self.inner.clone().into_ptr()
}
#[track_caller]
pub(crate) fn borrow(obj: &Bound<'py, T>) -> Self {
Self::try_borrow(obj).expect("Already mutably borrowed")
}
pub(crate) fn try_borrow(obj: &Bound<'py, T>) -> Result<Self, PyBorrowError> {
let cell = obj.get_class_object();
cell.ensure_threadsafe();
cell.borrow_checker()
.try_borrow()
.map(|_| Self { inner: obj.clone() })
}
}
impl<'p, T, U> PyRef<'p, T>
where
T: PyClass<BaseType = U>,
U: PyClass,
{
pub fn into_super(self) -> PyRef<'p, U> {
let py = self.py();
PyRef {
inner: unsafe {
ManuallyDrop::new(self)
.as_ptr()
.assume_owned(py)
.downcast_into_unchecked()
},
}
}
pub fn as_super(&self) -> &PyRef<'p, U> {
let ptr = ptr_from_ref::<Bound<'p, T>>(&self.inner)
.cast::<Bound<'p, T::BaseType>>()
.cast::<PyRef<'p, T::BaseType>>();
unsafe { &*ptr }
}
}
impl<'p, T: PyClass> Deref for PyRef<'p, T> {
type Target = T;
#[inline]
fn deref(&self) -> &T {
unsafe { &*self.inner.get_class_object().get_ptr() }
}
}
impl<'p, T: PyClass> Drop for PyRef<'p, T> {
fn drop(&mut self) {
self.inner
.get_class_object()
.borrow_checker()
.release_borrow()
}
}
impl<T: PyClass> IntoPy<PyObject> for PyRef<'_, T> {
fn into_py(self, py: Python<'_>) -> PyObject {
unsafe { PyObject::from_borrowed_ptr(py, self.inner.as_ptr()) }
}
}
impl<T: PyClass> IntoPy<PyObject> for &'_ PyRef<'_, T> {
fn into_py(self, py: Python<'_>) -> PyObject {
unsafe { PyObject::from_borrowed_ptr(py, self.inner.as_ptr()) }
}
}
#[cfg(feature = "gil-refs")]
#[allow(deprecated)]
impl<'a, T: PyClass> std::convert::TryFrom<&'a PyCell<T>> for crate::PyRef<'a, T> {
type Error = PyBorrowError;
fn try_from(cell: &'a crate::PyCell<T>) -> Result<Self, Self::Error> {
cell.try_borrow()
}
}
unsafe impl<'a, T: PyClass> AsPyPointer for PyRef<'a, T> {
fn as_ptr(&self) -> *mut ffi::PyObject {
self.inner.as_ptr()
}
}
impl<T: PyClass + fmt::Debug> fmt::Debug for PyRef<'_, T> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
fmt::Debug::fmt(&**self, f)
}
}
#[repr(transparent)]
pub struct PyRefMut<'p, T: PyClass<Frozen = False>> {
inner: Bound<'p, T>,
}
impl<'p, T: PyClass<Frozen = False>> PyRefMut<'p, T> {
pub fn py(&self) -> Python<'p> {
self.inner.py()
}
}
impl<'p, T, U> AsRef<U> for PyRefMut<'p, T>
where
T: PyClass<BaseType = U, Frozen = False>,
U: PyClass<Frozen = False>,
{
fn as_ref(&self) -> &T::BaseType {
PyRefMut::downgrade(self).as_super()
}
}
impl<'p, T, U> AsMut<U> for PyRefMut<'p, T>
where
T: PyClass<BaseType = U, Frozen = False>,
U: PyClass<Frozen = False>,
{
fn as_mut(&mut self) -> &mut T::BaseType {
self.as_super()
}
}
impl<'py, T: PyClass<Frozen = False>> PyRefMut<'py, T> {
#[inline]
pub fn as_ptr(&self) -> *mut ffi::PyObject {
self.inner.as_ptr()
}
#[inline]
pub fn into_ptr(self) -> *mut ffi::PyObject {
self.inner.clone().into_ptr()
}
#[inline]
#[track_caller]
pub(crate) fn borrow(obj: &Bound<'py, T>) -> Self {
Self::try_borrow(obj).expect("Already borrowed")
}
pub(crate) fn try_borrow(obj: &Bound<'py, T>) -> Result<Self, PyBorrowMutError> {
let cell = obj.get_class_object();
cell.ensure_threadsafe();
cell.borrow_checker()
.try_borrow_mut()
.map(|_| Self { inner: obj.clone() })
}
pub(crate) fn downgrade(slf: &Self) -> &PyRef<'py, T> {
unsafe { &*ptr_from_ref(slf).cast() }
}
}
impl<'p, T, U> PyRefMut<'p, T>
where
T: PyClass<BaseType = U, Frozen = False>,
U: PyClass<Frozen = False>,
{
pub fn into_super(self) -> PyRefMut<'p, U> {
let py = self.py();
PyRefMut {
inner: unsafe {
ManuallyDrop::new(self)
.as_ptr()
.assume_owned(py)
.downcast_into_unchecked()
},
}
}
pub fn as_super(&mut self) -> &mut PyRefMut<'p, U> {
let ptr = ptr_from_mut::<Bound<'p, T>>(&mut self.inner)
.cast::<Bound<'p, T::BaseType>>()
.cast::<PyRefMut<'p, T::BaseType>>();
unsafe { &mut *ptr }
}
}
impl<'p, T: PyClass<Frozen = False>> Deref for PyRefMut<'p, T> {
type Target = T;
#[inline]
fn deref(&self) -> &T {
unsafe { &*self.inner.get_class_object().get_ptr() }
}
}
impl<'p, T: PyClass<Frozen = False>> DerefMut for PyRefMut<'p, T> {
#[inline]
fn deref_mut(&mut self) -> &mut T {
unsafe { &mut *self.inner.get_class_object().get_ptr() }
}
}
impl<'p, T: PyClass<Frozen = False>> Drop for PyRefMut<'p, T> {
fn drop(&mut self) {
self.inner
.get_class_object()
.borrow_checker()
.release_borrow_mut()
}
}
impl<T: PyClass<Frozen = False>> IntoPy<PyObject> for PyRefMut<'_, T> {
fn into_py(self, py: Python<'_>) -> PyObject {
unsafe { PyObject::from_borrowed_ptr(py, self.inner.as_ptr()) }
}
}
impl<T: PyClass<Frozen = False>> IntoPy<PyObject> for &'_ PyRefMut<'_, T> {
fn into_py(self, py: Python<'_>) -> PyObject {
self.inner.clone().into_py(py)
}
}
unsafe impl<'a, T: PyClass<Frozen = False>> AsPyPointer for PyRefMut<'a, T> {
fn as_ptr(&self) -> *mut ffi::PyObject {
self.inner.as_ptr()
}
}
#[cfg(feature = "gil-refs")]
#[allow(deprecated)]
impl<'a, T: PyClass<Frozen = False>> std::convert::TryFrom<&'a PyCell<T>>
for crate::PyRefMut<'a, T>
{
type Error = PyBorrowMutError;
fn try_from(cell: &'a crate::PyCell<T>) -> Result<Self, Self::Error> {
cell.try_borrow_mut()
}
}
impl<T: PyClass<Frozen = False> + fmt::Debug> fmt::Debug for PyRefMut<'_, T> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
fmt::Debug::fmt(self.deref(), f)
}
}
pub struct PyBorrowError {
_private: (),
}
impl fmt::Debug for PyBorrowError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.debug_struct("PyBorrowError").finish()
}
}
impl fmt::Display for PyBorrowError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
fmt::Display::fmt("Already mutably borrowed", f)
}
}
impl From<PyBorrowError> for PyErr {
fn from(other: PyBorrowError) -> Self {
PyRuntimeError::new_err(other.to_string())
}
}
pub struct PyBorrowMutError {
_private: (),
}
impl fmt::Debug for PyBorrowMutError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.debug_struct("PyBorrowMutError").finish()
}
}
impl fmt::Display for PyBorrowMutError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
fmt::Display::fmt("Already borrowed", f)
}
}
impl From<PyBorrowMutError> for PyErr {
fn from(other: PyBorrowMutError) -> Self {
PyRuntimeError::new_err(other.to_string())
}
}
#[cfg(test)]
#[cfg(feature = "macros")]
mod tests {
use super::*;
#[crate::pyclass]
#[pyo3(crate = "crate")]
#[derive(Copy, Clone, PartialEq, Eq, Debug)]
struct SomeClass(i32);
#[cfg(feature = "gil-refs")]
mod deprecated {
use super::*;
#[test]
fn pycell_replace() {
Python::with_gil(|py| {
#[allow(deprecated)]
let cell = PyCell::new(py, SomeClass(0)).unwrap();
assert_eq!(*cell.borrow(), SomeClass(0));
let previous = cell.replace(SomeClass(123));
assert_eq!(previous, SomeClass(0));
assert_eq!(*cell.borrow(), SomeClass(123));
})
}
#[test]
#[should_panic(expected = "Already borrowed: PyBorrowMutError")]
fn pycell_replace_panic() {
Python::with_gil(|py| {
#[allow(deprecated)]
let cell = PyCell::new(py, SomeClass(0)).unwrap();
let _guard = cell.borrow();
cell.replace(SomeClass(123));
})
}
#[test]
fn pycell_replace_with() {
Python::with_gil(|py| {
#[allow(deprecated)]
let cell = PyCell::new(py, SomeClass(0)).unwrap();
assert_eq!(*cell.borrow(), SomeClass(0));
let previous = cell.replace_with(|value| {
*value = SomeClass(2);
SomeClass(123)
});
assert_eq!(previous, SomeClass(2));
assert_eq!(*cell.borrow(), SomeClass(123));
})
}
#[test]
#[should_panic(expected = "Already borrowed: PyBorrowMutError")]
fn pycell_replace_with_panic() {
Python::with_gil(|py| {
#[allow(deprecated)]
let cell = PyCell::new(py, SomeClass(0)).unwrap();
let _guard = cell.borrow();
cell.replace_with(|_| SomeClass(123));
})
}
#[test]
fn pycell_swap() {
Python::with_gil(|py| {
#[allow(deprecated)]
let cell = PyCell::new(py, SomeClass(0)).unwrap();
#[allow(deprecated)]
let cell2 = PyCell::new(py, SomeClass(123)).unwrap();
assert_eq!(*cell.borrow(), SomeClass(0));
assert_eq!(*cell2.borrow(), SomeClass(123));
cell.swap(cell2);
assert_eq!(*cell.borrow(), SomeClass(123));
assert_eq!(*cell2.borrow(), SomeClass(0));
})
}
#[test]
#[should_panic(expected = "Already borrowed: PyBorrowMutError")]
fn pycell_swap_panic() {
Python::with_gil(|py| {
#[allow(deprecated)]
let cell = PyCell::new(py, SomeClass(0)).unwrap();
#[allow(deprecated)]
let cell2 = PyCell::new(py, SomeClass(123)).unwrap();
let _guard = cell.borrow();
cell.swap(cell2);
})
}
#[test]
#[should_panic(expected = "Already borrowed: PyBorrowMutError")]
fn pycell_swap_panic_other_borrowed() {
Python::with_gil(|py| {
#[allow(deprecated)]
let cell = PyCell::new(py, SomeClass(0)).unwrap();
#[allow(deprecated)]
let cell2 = PyCell::new(py, SomeClass(123)).unwrap();
let _guard = cell2.borrow();
cell.swap(cell2);
})
}
}
#[test]
fn test_as_ptr() {
Python::with_gil(|py| {
let cell = Bound::new(py, SomeClass(0)).unwrap();
let ptr = cell.as_ptr();
assert_eq!(cell.borrow().as_ptr(), ptr);
assert_eq!(cell.borrow_mut().as_ptr(), ptr);
})
}
#[test]
fn test_into_ptr() {
Python::with_gil(|py| {
let cell = Bound::new(py, SomeClass(0)).unwrap();
let ptr = cell.as_ptr();
assert_eq!(cell.borrow().into_ptr(), ptr);
unsafe { ffi::Py_DECREF(ptr) };
assert_eq!(cell.borrow_mut().into_ptr(), ptr);
unsafe { ffi::Py_DECREF(ptr) };
})
}
#[crate::pyclass]
#[pyo3(crate = "crate", subclass)]
struct BaseClass {
val1: usize,
}
#[crate::pyclass]
#[pyo3(crate = "crate", extends=BaseClass, subclass)]
struct SubClass {
val2: usize,
}
#[crate::pyclass]
#[pyo3(crate = "crate", extends=SubClass)]
struct SubSubClass {
val3: usize,
}
#[crate::pymethods]
#[pyo3(crate = "crate")]
impl SubSubClass {
#[new]
fn new(py: Python<'_>) -> crate::Py<SubSubClass> {
let init = crate::PyClassInitializer::from(BaseClass { val1: 10 })
.add_subclass(SubClass { val2: 15 })
.add_subclass(SubSubClass { val3: 20 });
crate::Py::new(py, init).expect("allocation error")
}
fn get_values(self_: PyRef<'_, Self>) -> (usize, usize, usize) {
let val1 = self_.as_super().as_super().val1;
let val2 = self_.as_super().val2;
(val1, val2, self_.val3)
}
fn double_values(mut self_: PyRefMut<'_, Self>) {
self_.as_super().as_super().val1 *= 2;
self_.as_super().val2 *= 2;
self_.val3 *= 2;
}
}
#[test]
fn test_pyref_as_super() {
Python::with_gil(|py| {
let obj = SubSubClass::new(py).into_bound(py);
let pyref = obj.borrow();
assert_eq!(pyref.as_super().as_super().val1, 10);
assert_eq!(pyref.as_super().val2, 15);
assert_eq!(pyref.as_ref().val2, 15); assert_eq!(pyref.val3, 20);
assert_eq!(SubSubClass::get_values(pyref), (10, 15, 20));
});
}
#[test]
fn test_pyrefmut_as_super() {
Python::with_gil(|py| {
let obj = SubSubClass::new(py).into_bound(py);
assert_eq!(SubSubClass::get_values(obj.borrow()), (10, 15, 20));
{
let mut pyrefmut = obj.borrow_mut();
assert_eq!(pyrefmut.as_super().as_ref().val1, 10);
pyrefmut.as_super().as_super().val1 -= 5;
pyrefmut.as_super().val2 -= 3;
pyrefmut.as_mut().val2 -= 2; pyrefmut.val3 -= 5;
}
assert_eq!(SubSubClass::get_values(obj.borrow()), (5, 10, 15));
SubSubClass::double_values(obj.borrow_mut());
assert_eq!(SubSubClass::get_values(obj.borrow()), (10, 20, 30));
});
}
#[test]
fn test_pyrefs_in_python() {
Python::with_gil(|py| {
let obj = SubSubClass::new(py);
crate::py_run!(py, obj, "assert obj.get_values() == (10, 15, 20)");
crate::py_run!(py, obj, "assert obj.double_values() is None");
crate::py_run!(py, obj, "assert obj.get_values() == (20, 30, 40)");
});
}
}