pyo3/impl_/
pyclass_init.rs

1//! Contains initialization utilities for `#[pyclass]`.
2use crate::ffi_ptr_ext::FfiPtrExt;
3use crate::internal::get_slot::TP_ALLOC;
4use crate::types::PyType;
5use crate::{ffi, Borrowed, PyErr, PyResult, Python};
6use crate::{ffi::PyTypeObject, sealed::Sealed, type_object::PyTypeInfo};
7use std::marker::PhantomData;
8use std::ptr;
9
10/// Initializer for Python types.
11///
12/// This trait is intended to use internally for distinguishing `#[pyclass]` and
13/// Python native types.
14pub trait PyObjectInit<T>: Sized + Sealed {
15    /// # Safety
16    /// - `subtype` must be a valid pointer to a type object of T or a subclass.
17    unsafe fn into_new_object(
18        self,
19        py: Python<'_>,
20        subtype: *mut PyTypeObject,
21    ) -> PyResult<*mut ffi::PyObject>;
22
23    #[doc(hidden)]
24    fn can_be_subclassed(&self) -> bool;
25}
26
27/// Initializer for Python native types, like `PyDict`.
28pub struct PyNativeTypeInitializer<T: PyTypeInfo>(pub PhantomData<T>);
29
30impl<T: PyTypeInfo> PyObjectInit<T> for PyNativeTypeInitializer<T> {
31    unsafe fn into_new_object(
32        self,
33        py: Python<'_>,
34        subtype: *mut PyTypeObject,
35    ) -> PyResult<*mut ffi::PyObject> {
36        unsafe fn inner(
37            py: Python<'_>,
38            type_object: *mut PyTypeObject,
39            subtype: *mut PyTypeObject,
40        ) -> PyResult<*mut ffi::PyObject> {
41            // HACK (due to FIXME below): PyBaseObject_Type's tp_new isn't happy with NULL arguments
42            let is_base_object = ptr::eq(type_object, ptr::addr_of!(ffi::PyBaseObject_Type));
43            let subtype_borrowed: Borrowed<'_, '_, PyType> = unsafe {
44                subtype
45                    .cast::<ffi::PyObject>()
46                    .assume_borrowed_unchecked(py)
47                    .downcast_unchecked()
48            };
49
50            if is_base_object {
51                let alloc = subtype_borrowed
52                    .get_slot(TP_ALLOC)
53                    .unwrap_or(ffi::PyType_GenericAlloc);
54
55                let obj = unsafe { alloc(subtype, 0) };
56                return if obj.is_null() {
57                    Err(PyErr::fetch(py))
58                } else {
59                    Ok(obj)
60                };
61            }
62
63            #[cfg(Py_LIMITED_API)]
64            unreachable!("subclassing native types is not possible with the `abi3` feature");
65
66            #[cfg(not(Py_LIMITED_API))]
67            {
68                match unsafe { (*type_object).tp_new } {
69                    // FIXME: Call __new__ with actual arguments
70                    Some(newfunc) => {
71                        let obj =
72                            unsafe { newfunc(subtype, std::ptr::null_mut(), std::ptr::null_mut()) };
73                        if obj.is_null() {
74                            Err(PyErr::fetch(py))
75                        } else {
76                            Ok(obj)
77                        }
78                    }
79                    None => Err(crate::exceptions::PyTypeError::new_err(
80                        "base type without tp_new",
81                    )),
82                }
83            }
84        }
85        let type_object = T::type_object_raw(py);
86        unsafe { inner(py, type_object, subtype) }
87    }
88
89    #[inline]
90    fn can_be_subclassed(&self) -> bool {
91        true
92    }
93}