// TODO https://github.com/PyO3/pyo3/issues/5487 #![allow(clippy::undocumented_unsafe_blocks)] //! Contains initialization utilities for `#[pyclass]`. use crate::exceptions::PyTypeError; use crate::ffi_ptr_ext::FfiPtrExt; use crate::impl_::pyclass::PyClassBaseType; use crate::internal::get_slot::TP_NEW; use crate::types::{PyTuple, PyType, PyTypeMethods}; use crate::{ ffi, IntoPyObject, IntoPyObjectExt, PyClass, PyClassInitializer, PyErr, PyResult, Python, }; use crate::{ffi::PyTypeObject, type_object::PyTypeInfo}; use core::marker::PhantomData; /// Initializer for Python types. /// /// This trait is used internally for distinguishing `#[pyclass]` and /// Python native types. pub(crate) trait PyObjectInit: Sized { /// # Safety /// - `subtype` must be a valid pointer to a type object of T or a subclass. unsafe fn into_new_object( self, py: Python<'_>, subtype: *mut PyTypeObject, ) -> PyResult<*mut ffi::PyObject>; } /// Initializer for Python native types, like `PyDict`. pub struct PyNativeTypeInitializer(pub PhantomData); impl PyObjectInit for PyNativeTypeInitializer { unsafe fn into_new_object( self, py: Python<'_>, subtype: *mut PyTypeObject, ) -> PyResult<*mut ffi::PyObject> { unsafe fn inner( py: Python<'_>, type_ptr: *mut PyTypeObject, subtype: *mut PyTypeObject, ) -> PyResult<*mut ffi::PyObject> { let tp_new = unsafe { type_ptr .cast::() .assume_borrowed_unchecked(py) .cast_unchecked::() .get_slot(TP_NEW) .ok_or_else(|| PyTypeError::new_err("base type without tp_new"))? }; // TODO: make it possible to provide real arguments to the base tp_new let obj = unsafe { tp_new(subtype, PyTuple::empty(py).as_ptr(), core::ptr::null_mut()) }; if obj.is_null() { Err(PyErr::fetch(py)) } else { Ok(obj) } } unsafe { inner(py, T::type_object_raw(py), subtype) } } } pub struct TpNewValueTypeResolver(TpNewTupleResolver); pub struct TpNewTupleResolver( ResolveToArbitraryObject, PhantomData<(ClassT, ValueT)>, ); pub struct ResolveToArbitraryObject(()); /// First step of machinery for resolving the type of `#[new]` return values, which is used to /// implement specialization for the various cases. /// /// Call `.resolve()` on the returned tag to get the final tag type. /// /// This resolution step is necessary in order to encode the preference to go via `PyClassInitializer` /// for new instances of `ClassT`. Without this step, the fallback to `IntoPyObject` conflicts for /// `ClassT` because that implementation ignores the `cls` parameter for `PyClassInit` (and would /// therefore be incorrect when instantiating subclasses). pub fn tp_new_resolver(_: &ValueT) -> TpNewValueTypeResolver { TpNewValueTypeResolver(TpNewTupleResolver( ResolveToArbitraryObject(()), PhantomData, )) } impl core::ops::Deref for TpNewValueTypeResolver { type Target = TpNewTupleResolver; fn deref(&self) -> &Self::Target { &self.0 } } impl core::ops::Deref for TpNewTupleResolver { type Target = ResolveToArbitraryObject; fn deref(&self) -> &Self::Target { &self.0 } } /// Second stage of machinery for resolving the type of `#[new]` return values. This /// is the specialized implementation for the case where the return type should be /// used to build a new class object (`T`, `PyClassInitializer`, `(S, B)` tuples). #[expect(private_bounds, reason = "internal trait implementation")] impl TpNewValueTypeResolver where ClassT: PyClass, ValueT: IntoPyClassInitializer, { pub fn resolve(&self, value: ValueT) -> PyClassInitializer { value.into_pyclass_initializer() } } impl TpNewTupleResolver where S: PyClass, B: PyClass + PyClassBaseType>, B::BaseType: PyClassBaseType>, { #[deprecated( since = "0.29.0", note = "Tuple syntax for super class initialization is phased out. Use `PyClassInitializer` instead." )] pub fn resolve(&self, value: (S, B)) -> PyClassInitializer { value.into() } } /// All other conversions fall back to IntoPyObject via the deref implementation impl ResolveToArbitraryObject { pub fn resolve(&self, value: ValueT) -> ValueT { value } } #[diagnostic::on_unimplemented( message = "`{Self}` cannot be used as the return value for `#[new]` methods", note = "all types which implement `IntoPyObject` are suitable return values for `#[new]` methods", note = "`PyClassInitializer` may also be used and is necessary for `#[pyclass(extends = '...')]` types", label = "must be a type which implements `IntoPyObject`, a `PyClassInitializer<{Self}>`, or a `Result` wrapping such types" )] pub(crate) trait PyClassInit<'py, T> { /// # Safety /// - `cls` must be the type object for `T` (or a subclass) unsafe fn init( self, cls: crate::Borrowed<'_, 'py, crate::types::PyType>, ) -> PyResult>; } impl<'py, ClassT, T> PyClassInit<'py, ClassT> for T where T: IntoPyObject<'py>, { unsafe fn init( self, cls: crate::Borrowed<'_, 'py, crate::types::PyType>, ) -> PyResult> { self.into_bound_py_any(cls.py()) } } impl<'py, T> PyClassInit<'py, T> for PyClassInitializer where T: PyClass, { unsafe fn init( self, cls: crate::Borrowed<'_, 'py, crate::types::PyType>, ) -> PyResult> { // SAFETY: caller has guaranteed that `cls` is correct object unsafe { self.create_class_object_of_type(cls.py(), cls.as_type_ptr()) } .map(crate::Bound::into_any) } } /// Analogous to `Into>`, but just an internal equivalent /// to avoid allowing user code to define custom return types from `#[new]`. trait IntoPyClassInitializer: Sized { fn into_pyclass_initializer(self) -> PyClassInitializer; } impl IntoPyClassInitializer for PyClassInitializer { fn into_pyclass_initializer(self) -> PyClassInitializer { self } } impl IntoPyClassInitializer for T where T: PyClass, T::BaseType: PyClassBaseType>, { fn into_pyclass_initializer(self) -> PyClassInitializer { self.into() } }