use crate::exceptions::PyRuntimeError;
use crate::impl_::pyclass::{
PyClassBaseType, PyClassDict, PyClassImpl, PyClassThreadChecker, PyClassWeakRef,
};
use crate::pyclass::{boolean_struct::False, PyClass};
use crate::pyclass_init::PyClassInitializer;
use crate::type_object::{PyLayout, PySizedLayout};
use crate::types::PyAny;
use crate::{
conversion::{AsPyPointer, FromPyPointer, ToPyObject},
ffi::PyBaseObject_Type,
type_object::get_tp_free,
PyTypeInfo,
};
use crate::{ffi, IntoPy, PyErr, PyNativeType, PyObject, PyResult, Python};
use std::cell::UnsafeCell;
use std::fmt;
use std::mem::ManuallyDrop;
use std::ops::{Deref, DerefMut};
pub(crate) mod impl_;
use impl_::{GetBorrowChecker, PyClassBorrowChecker, PyClassMutability};
#[doc(hidden)]
#[repr(C)]
pub struct PyCellBase<T> {
ob_base: T,
}
unsafe impl<T, U> PyLayout<T> for PyCellBase<U> where U: PySizedLayout<T> {}
#[repr(C)]
pub struct PyCell<T: PyClassImpl> {
ob_base: <T::BaseType as PyClassBaseType>::LayoutAsBase,
contents: PyCellContents<T>,
}
#[repr(C)]
pub(crate) struct PyCellContents<T: PyClassImpl> {
pub(crate) value: ManuallyDrop<UnsafeCell<T>>,
pub(crate) borrow_checker: <T::PyClassMutability as PyClassMutability>::Storage,
pub(crate) thread_checker: T::ThreadChecker,
pub(crate) dict: T::Dict,
pub(crate) weakref: T::WeakRef,
}
unsafe impl<T: PyClass> PyNativeType for PyCell<T> {}
impl<T: PyClass> PyCell<T> {
pub fn new(py: Python<'_>, value: impl Into<PyClassInitializer<T>>) -> PyResult<&Self> {
unsafe {
let initializer = value.into();
let self_ = initializer.create_cell(py)?;
FromPyPointer::from_owned_ptr_or_err(py, self_ as _)
}
}
pub fn borrow(&self) -> PyRef<'_, T> {
self.try_borrow().expect("Already mutably borrowed")
}
pub fn borrow_mut(&self) -> PyRefMut<'_, T>
where
T: PyClass<Frozen = False>,
{
self.try_borrow_mut().expect("Already borrowed")
}
pub fn try_borrow(&self) -> Result<PyRef<'_, T>, PyBorrowError> {
self.ensure_threadsafe();
self.borrow_checker()
.try_borrow()
.map(|_| PyRef { inner: self })
}
pub fn try_borrow_mut(&self) -> Result<PyRefMut<'_, T>, PyBorrowMutError>
where
T: PyClass<Frozen = False>,
{
self.ensure_threadsafe();
self.borrow_checker()
.try_borrow_mut()
.map(|_| PyRefMut { inner: self })
}
pub unsafe fn try_borrow_unguarded(&self) -> Result<&T, PyBorrowError> {
self.ensure_threadsafe();
self.borrow_checker()
.try_borrow_unguarded()
.map(|_: ()| &*self.contents.value.get())
}
#[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())
}
fn get_ptr(&self) -> *mut T {
self.contents.value.get()
}
pub(crate) fn dict_offset() -> ffi::Py_ssize_t {
use memoffset::offset_of;
use std::convert::TryInto;
let offset = offset_of!(PyCell<T>, contents) + offset_of!(PyCellContents<T>, dict);
#[allow(clippy::useless_conversion)]
offset.try_into().expect("offset should fit in Py_ssize_t")
}
pub(crate) fn weaklist_offset() -> ffi::Py_ssize_t {
use memoffset::offset_of;
use std::convert::TryInto;
let offset = offset_of!(PyCell<T>, contents) + offset_of!(PyCellContents<T>, weakref);
#[allow(clippy::useless_conversion)]
offset.try_into().expect("offset should fit in Py_ssize_t")
}
}
impl<T: PyClassImpl> PyCell<T> {
fn borrow_checker(&self) -> &<T::PyClassMutability as PyClassMutability>::Checker {
T::PyClassMutability::borrow_checker(self)
}
}
unsafe impl<T: PyClassImpl> PyLayout<T> for PyCell<T> {}
impl<T: PyClass> PySizedLayout<T> for PyCell<T> {}
impl<T: PyClass> AsPyPointer for PyCell<T> {
fn as_ptr(&self) -> *mut ffi::PyObject {
(self as *const _) as *mut _
}
}
impl<T: PyClass> ToPyObject for &PyCell<T> {
fn to_object(&self, py: Python<'_>) -> PyObject {
unsafe { PyObject::from_borrowed_ptr(py, self.as_ptr()) }
}
}
impl<T: PyClass> AsRef<PyAny> for PyCell<T> {
fn as_ref(&self) -> &PyAny {
unsafe { self.py().from_borrowed_ptr(self.as_ptr()) }
}
}
impl<T: PyClass> Deref for PyCell<T> {
type Target = PyAny;
fn deref(&self) -> &PyAny {
unsafe { self.py().from_borrowed_ptr(self.as_ptr()) }
}
}
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()
}
}
}
}
pub struct PyRef<'p, T: PyClass> {
inner: &'p PyCell<T>,
}
impl<'p, T: PyClass> PyRef<'p, T> {
pub fn py(&self) -> Python<'_> {
unsafe { Python::assume_gil_acquired() }
}
}
impl<'p, T, U> AsRef<U> for PyRef<'p, T>
where
T: PyClass<BaseType = U>,
U: PyClass,
{
fn as_ref(&self) -> &T::BaseType {
unsafe { &*self.inner.ob_base.get_ptr() }
}
}
impl<'p, T, U> PyRef<'p, T>
where
T: PyClass<BaseType = U>,
U: PyClass,
{
pub fn into_super(self) -> PyRef<'p, U> {
let PyRef { inner } = self;
std::mem::forget(self);
PyRef {
inner: &inner.ob_base,
}
}
}
impl<'p, T: PyClass> Deref for PyRef<'p, T> {
type Target = T;
#[inline]
fn deref(&self) -> &T {
unsafe { &*self.inner.get_ptr() }
}
}
impl<'p, T: PyClass> Drop for PyRef<'p, T> {
fn drop(&mut self) {
self.inner.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<'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()
}
}
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)
}
}
pub struct PyRefMut<'p, T: PyClass<Frozen = False>> {
inner: &'p PyCell<T>,
}
impl<'p, T: PyClass<Frozen = False>> PyRefMut<'p, T> {
pub fn py(&self) -> Python<'_> {
unsafe { Python::assume_gil_acquired() }
}
}
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 {
unsafe { &*self.inner.ob_base.get_ptr() }
}
}
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 {
unsafe { &mut *self.inner.ob_base.get_ptr() }
}
}
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 PyRefMut { inner } = self;
std::mem::forget(self);
PyRefMut {
inner: &inner.ob_base,
}
}
}
impl<'p, T: PyClass<Frozen = False>> Deref for PyRefMut<'p, T> {
type Target = T;
#[inline]
fn deref(&self) -> &T {
unsafe { &*self.inner.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_ptr() }
}
}
impl<'p, T: PyClass<Frozen = False>> Drop for PyRefMut<'p, T> {
fn drop(&mut self) {
self.inner.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<'a, T: PyClass<Frozen = False>> AsPyPointer for PyRefMut<'a, T> {
fn as_ptr(&self) -> *mut ffi::PyObject {
self.inner.as_ptr()
}
}
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())
}
}
#[doc(hidden)]
pub trait PyCellLayout<T>: PyLayout<T> {
fn ensure_threadsafe(&self);
unsafe fn tp_dealloc(slf: *mut ffi::PyObject, py: Python<'_>);
}
impl<T, U> PyCellLayout<T> for PyCellBase<U>
where
U: PySizedLayout<T>,
T: PyTypeInfo,
{
fn ensure_threadsafe(&self) {}
unsafe fn tp_dealloc(slf: *mut ffi::PyObject, py: Python<'_>) {
if T::type_object_raw(py) == &mut PyBaseObject_Type {
return get_tp_free(ffi::Py_TYPE(slf))(slf as _);
}
#[cfg(not(Py_LIMITED_API))]
{
if let Some(dealloc) = (*T::type_object_raw(py)).tp_dealloc {
dealloc(slf as _);
} else {
get_tp_free(ffi::Py_TYPE(slf))(slf as _);
}
}
#[cfg(Py_LIMITED_API)]
unreachable!("subclassing native types is not possible with the `abi3` feature");
}
}
impl<T: PyClassImpl> PyCellLayout<T> for PyCell<T>
where
<T::BaseType as PyClassBaseType>::LayoutAsBase: PyCellLayout<T::BaseType>,
{
fn ensure_threadsafe(&self) {
self.contents.thread_checker.ensure();
self.ob_base.ensure_threadsafe();
}
unsafe fn tp_dealloc(slf: *mut ffi::PyObject, py: Python<'_>) {
let cell = &mut *(slf as *mut PyCell<T>);
ManuallyDrop::drop(&mut cell.contents.value);
cell.contents.dict.clear_dict(py);
cell.contents.weakref.clear_weakrefs(slf, py);
<T::BaseType as PyClassBaseType>::LayoutAsBase::tp_dealloc(slf, py)
}
}
#[cfg(test)]
#[cfg(feature = "macros")]
mod tests {
use super::*;
#[crate::pyclass]
#[pyo3(crate = "crate")]
#[derive(Copy, Clone, PartialEq, Eq, Debug)]
struct SomeClass(i32);
#[test]
fn pycell_replace() {
Python::with_gil(|py| {
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| {
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| {
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| {
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| {
let cell = PyCell::new(py, SomeClass(0)).unwrap();
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| {
let cell = PyCell::new(py, SomeClass(0)).unwrap();
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| {
let cell = PyCell::new(py, SomeClass(0)).unwrap();
let cell2 = PyCell::new(py, SomeClass(123)).unwrap();
let _guard = cell2.borrow();
cell.swap(cell2);
})
}
}