use crate::callback::IntoPyCallbackOutput;
use crate::impl_::pyclass::{PyClassBaseType, PyClassDict, PyClassThreadChecker, PyClassWeakRef};
use crate::{ffi, PyCell, PyClass, PyErr, PyResult, Python};
use crate::{
ffi::PyTypeObject,
pycell::{
impl_::{PyClassBorrowChecker, PyClassMutability},
PyCellContents,
},
type_object::{get_tp_alloc, PyTypeInfo},
};
use std::{
cell::UnsafeCell,
marker::PhantomData,
mem::{ManuallyDrop, MaybeUninit},
};
pub trait PyObjectInit<T>: Sized {
unsafe fn into_new_object(
self,
py: Python<'_>,
subtype: *mut PyTypeObject,
) -> PyResult<*mut ffi::PyObject>;
private_decl! {}
}
pub struct PyNativeTypeInitializer<T: PyTypeInfo>(PhantomData<T>);
impl<T: PyTypeInfo> PyObjectInit<T> for PyNativeTypeInitializer<T> {
unsafe fn into_new_object(
self,
py: Python<'_>,
subtype: *mut PyTypeObject,
) -> PyResult<*mut ffi::PyObject> {
unsafe fn inner(
py: Python<'_>,
type_object: *mut PyTypeObject,
subtype: *mut PyTypeObject,
) -> PyResult<*mut ffi::PyObject> {
#[cfg(addr_of)]
let is_base_object = type_object == std::ptr::addr_of_mut!(ffi::PyBaseObject_Type);
#[cfg(not(addr_of))]
let is_base_object = type_object == &mut ffi::PyBaseObject_Type as _;
if is_base_object {
let alloc = get_tp_alloc(subtype).unwrap_or(ffi::PyType_GenericAlloc);
let obj = alloc(subtype, 0);
return if obj.is_null() {
Err(PyErr::fetch(py))
} else {
Ok(obj)
};
}
#[cfg(Py_LIMITED_API)]
unreachable!("subclassing native types is not possible with the `abi3` feature");
#[cfg(not(Py_LIMITED_API))]
{
match (*type_object).tp_new {
Some(newfunc) => {
let obj = newfunc(subtype, std::ptr::null_mut(), std::ptr::null_mut());
if obj.is_null() {
Err(PyErr::fetch(py))
} else {
Ok(obj)
}
}
None => Err(crate::exceptions::PyTypeError::new_err(
"base type without tp_new",
)),
}
}
}
let type_object = T::type_object_raw(py);
inner(py, type_object, subtype)
}
private_impl! {}
}
pub struct PyClassInitializer<T: PyClass> {
init: T,
super_init: <T::BaseType as PyClassBaseType>::Initializer,
}
impl<T: PyClass> PyClassInitializer<T> {
pub fn new(init: T, super_init: <T::BaseType as PyClassBaseType>::Initializer) -> Self {
Self { init, super_init }
}
pub fn add_subclass<S>(self, subclass_value: S) -> PyClassInitializer<S>
where
S: PyClass<BaseType = T>,
S::BaseType: PyClassBaseType<Initializer = Self>,
{
PyClassInitializer::new(subclass_value, self)
}
#[doc(hidden)]
pub fn create_cell(self, py: Python<'_>) -> PyResult<*mut PyCell<T>>
where
T: PyClass,
{
unsafe { self.create_cell_from_subtype(py, T::type_object_raw(py)) }
}
#[doc(hidden)]
pub unsafe fn create_cell_from_subtype(
self,
py: Python<'_>,
subtype: *mut crate::ffi::PyTypeObject,
) -> PyResult<*mut PyCell<T>>
where
T: PyClass,
{
self.into_new_object(py, subtype).map(|obj| obj as _)
}
}
impl<T: PyClass> PyObjectInit<T> for PyClassInitializer<T> {
unsafe fn into_new_object(
self,
py: Python<'_>,
subtype: *mut PyTypeObject,
) -> PyResult<*mut ffi::PyObject> {
#[repr(C)]
struct PartiallyInitializedPyCell<T: PyClass> {
_ob_base: <T::BaseType as PyClassBaseType>::LayoutAsBase,
contents: MaybeUninit<PyCellContents<T>>,
}
let obj = self.super_init.into_new_object(py, subtype)?;
let cell: *mut PartiallyInitializedPyCell<T> = obj as _;
std::ptr::write(
(*cell).contents.as_mut_ptr(),
PyCellContents {
value: ManuallyDrop::new(UnsafeCell::new(self.init)),
borrow_checker: <T::PyClassMutability as PyClassMutability>::Storage::new(),
thread_checker: T::ThreadChecker::new(),
dict: T::Dict::INIT,
weakref: T::WeakRef::INIT,
},
);
Ok(obj)
}
private_impl! {}
}
impl<T> From<T> for PyClassInitializer<T>
where
T: PyClass,
T::BaseType: PyClassBaseType<Initializer = PyNativeTypeInitializer<T::BaseType>>,
{
#[inline]
fn from(value: T) -> PyClassInitializer<T> {
Self::new(value, PyNativeTypeInitializer(PhantomData))
}
}
impl<S, B> From<(S, B)> for PyClassInitializer<S>
where
S: PyClass<BaseType = B>,
B: PyClass,
B::BaseType: PyClassBaseType<Initializer = PyNativeTypeInitializer<B::BaseType>>,
{
fn from(sub_and_base: (S, B)) -> PyClassInitializer<S> {
let (sub, base) = sub_and_base;
PyClassInitializer::from(base).add_subclass(sub)
}
}
impl<T, U> IntoPyCallbackOutput<PyClassInitializer<T>> for U
where
T: PyClass,
U: Into<PyClassInitializer<T>>,
{
#[inline]
fn convert(self, _py: Python<'_>) -> PyResult<PyClassInitializer<T>> {
Ok(self.into())
}
}