#![allow(clippy::undocumented_unsafe_blocks)]
use core::{
ffi::{c_int, c_void},
marker::PhantomData,
num::NonZero,
ops::Deref,
ptr::NonNull,
};
use crate::{
ffi,
impl_::{pycell::PyClassMutability, pyclass::PyClassThreadChecker},
instance::PyBorrowedUnbound,
pycell::impl_::{PyClassBorrowChecker, PyClassObjectLayout},
Py, PyClass,
};
#[repr(transparent)]
pub struct PyTraverseError(NonZero<c_int>);
impl PyTraverseError {
pub(crate) fn into_inner(self) -> c_int {
self.0.into()
}
}
#[derive(Clone)]
pub struct PyVisit<'a> {
pub(crate) visit: ffi::visitproc,
pub(crate) arg: *mut c_void,
pub(crate) _guard: PhantomData<&'a ()>,
}
impl PyVisit<'_> {
pub fn call<'a, T, U: 'a>(&self, obj: T) -> Result<(), PyTraverseError>
where
T: Into<Option<&'a Py<U>>>,
{
let ptr = obj.into().map_or_else(core::ptr::null_mut, Py::as_ptr);
if !ptr.is_null() {
match NonZero::new(unsafe { (self.visit)(ptr, self.arg) }) {
None => Ok(()),
Some(r) => Err(PyTraverseError(r)),
}
} else {
Ok(())
}
}
}
pub(crate) struct PyClassTraverseGuard<'a, T: PyClass> {
value: NonNull<T>,
borrow_checker: &'a <T::PyClassMutability as PyClassMutability>::Checker,
phantom: PhantomData<PyBorrowedUnbound<'a, T>>,
}
impl<'a, T: PyClass> PyClassTraverseGuard<'a, T> {
pub(crate) fn try_from_class_object(class_object: PyBorrowedUnbound<'a, T>) -> Option<Self> {
let contents = T::Layout::contents_during_gc(class_object);
if !contents.thread_checker.check() {
return None;
}
let borrow_checker = T::Layout::borrow_checker_during_gc(class_object);
borrow_checker.try_borrow().ok().map(|_| {
let value = unsafe { NonNull::from(&*contents.value.get()) };
Self {
value,
borrow_checker,
phantom: PhantomData,
}
})
}
}
impl<'a, T: PyClass> Deref for PyClassTraverseGuard<'a, T> {
type Target = T;
fn deref(&self) -> &Self::Target {
unsafe { self.value.as_ref() }
}
}
impl<'a, T: PyClass> Drop for PyClassTraverseGuard<'a, T> {
fn drop(&mut self) {
self.borrow_checker.release_borrow();
}
}
#[cfg(test)]
mod tests {
use super::PyVisit;
use static_assertions::assert_not_impl_any;
#[test]
fn py_visit_not_send_sync() {
assert_not_impl_any!(PyVisit<'_>: Send, Sync);
}
}