use crate::impl_::pyclass::PyClassItems;
use crate::internal_tricks::extract_cstr_or_leak_cstring;
use crate::once_cell::GILOnceCell;
use crate::pyclass::create_type_object;
use crate::pyclass::PyClass;
use crate::types::{PyAny, PyType};
use crate::{conversion::IntoPyPointer, PyMethodDefType};
use crate::{ffi, AsPyPointer, PyNativeType, PyObject, PyResult, Python};
use parking_lot::{const_mutex, Mutex};
use std::thread::{self, ThreadId};
pub unsafe trait PyLayout<T> {}
pub trait PySizedLayout<T>: PyLayout<T> + Sized {}
pub unsafe trait PyTypeInfo: Sized {
const NAME: &'static str;
const MODULE: Option<&'static str>;
type AsRefTarget: PyNativeType;
fn type_object_raw(py: Python<'_>) -> *mut ffi::PyTypeObject;
fn is_type_of(object: &PyAny) -> bool {
unsafe { ffi::PyObject_TypeCheck(object.as_ptr(), Self::type_object_raw(object.py())) != 0 }
}
fn is_exact_type_of(object: &PyAny) -> bool {
unsafe { ffi::Py_TYPE(object.as_ptr()) == Self::type_object_raw(object.py()) }
}
}
pub unsafe trait PyTypeObject {
fn type_object(py: Python<'_>) -> &PyType;
}
unsafe impl<T> PyTypeObject for T
where
T: PyTypeInfo,
{
fn type_object(py: Python<'_>) -> &PyType {
unsafe { py.from_borrowed_ptr(Self::type_object_raw(py) as _) }
}
}
#[doc(hidden)]
pub struct LazyStaticType {
value: GILOnceCell<*mut ffi::PyTypeObject>,
initializing_threads: Mutex<Vec<ThreadId>>,
tp_dict_filled: GILOnceCell<PyResult<()>>,
}
impl LazyStaticType {
pub const fn new() -> Self {
LazyStaticType {
value: GILOnceCell::new(),
initializing_threads: const_mutex(Vec::new()),
tp_dict_filled: GILOnceCell::new(),
}
}
pub fn get_or_init<T: PyClass>(&self, py: Python<'_>) -> *mut ffi::PyTypeObject {
let type_object = *self.value.get_or_init(py, || create_type_object::<T>(py));
self.ensure_init(py, type_object, T::NAME, &T::for_all_items);
type_object
}
fn ensure_init(
&self,
py: Python<'_>,
type_object: *mut ffi::PyTypeObject,
name: &str,
for_all_items: &dyn Fn(&mut dyn FnMut(&PyClassItems)),
) {
if self.tp_dict_filled.get(py).is_some() {
return;
}
{
let thread_id = thread::current().id();
let mut threads = self.initializing_threads.lock();
if threads.contains(&thread_id) {
return;
}
threads.push(thread_id);
}
let mut items = vec![];
for_all_items(&mut |class_items| {
items.extend(class_items.methods.iter().filter_map(|def| {
if let PyMethodDefType::ClassAttribute(attr) = def {
let key = extract_cstr_or_leak_cstring(
attr.name,
"class attribute name cannot contain nul bytes",
)
.unwrap();
let val = (attr.meth.0)(py);
Some((key, val))
} else {
None
}
}));
});
let result = self.tp_dict_filled.get_or_init(py, move || {
let result = initialize_tp_dict(py, type_object as *mut ffi::PyObject, items);
*self.initializing_threads.lock() = Vec::new();
result
});
if let Err(err) = result {
err.clone_ref(py).print(py);
panic!("An error occured while initializing `{}.__dict__`", name);
}
}
}
fn initialize_tp_dict(
py: Python<'_>,
type_object: *mut ffi::PyObject,
items: Vec<(&'static std::ffi::CStr, PyObject)>,
) -> PyResult<()> {
for (key, val) in items {
let ret = unsafe { ffi::PyObject_SetAttrString(type_object, key.as_ptr(), val.into_ptr()) };
crate::err::error_on_minusone(py, ret)?;
}
Ok(())
}
unsafe impl Sync for LazyStaticType {}
#[inline]
pub(crate) unsafe fn get_tp_alloc(tp: *mut ffi::PyTypeObject) -> Option<ffi::allocfunc> {
#[cfg(not(Py_LIMITED_API))]
{
(*tp).tp_alloc
}
#[cfg(Py_LIMITED_API)]
{
let ptr = ffi::PyType_GetSlot(tp, ffi::Py_tp_alloc);
std::mem::transmute(ptr)
}
}
#[inline]
pub(crate) unsafe fn get_tp_free(tp: *mut ffi::PyTypeObject) -> ffi::freefunc {
#[cfg(not(Py_LIMITED_API))]
{
(*tp).tp_free.unwrap()
}
#[cfg(Py_LIMITED_API)]
{
let ptr = ffi::PyType_GetSlot(tp, ffi::Py_tp_free);
debug_assert_ne!(ptr, std::ptr::null_mut());
std::mem::transmute(ptr)
}
}