201 lines
6.9 KiB
Rust
201 lines
6.9 KiB
Rust
// 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<T>: 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<T: PyTypeInfo>(pub 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_ptr: *mut PyTypeObject,
|
|
subtype: *mut PyTypeObject,
|
|
) -> PyResult<*mut ffi::PyObject> {
|
|
let tp_new = unsafe {
|
|
type_ptr
|
|
.cast::<ffi::PyObject>()
|
|
.assume_borrowed_unchecked(py)
|
|
.cast_unchecked::<PyType>()
|
|
.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<ClassT, ValueT>(TpNewTupleResolver<ClassT, ValueT>);
|
|
pub struct TpNewTupleResolver<ClassT, ValueT>(
|
|
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<T>`
|
|
/// 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<ClassT, ValueT>(_: &ValueT) -> TpNewValueTypeResolver<ClassT, ValueT> {
|
|
TpNewValueTypeResolver(TpNewTupleResolver(
|
|
ResolveToArbitraryObject(()),
|
|
PhantomData,
|
|
))
|
|
}
|
|
|
|
impl<ClassT, ValueT> core::ops::Deref for TpNewValueTypeResolver<ClassT, ValueT> {
|
|
type Target = TpNewTupleResolver<ClassT, ValueT>;
|
|
fn deref(&self) -> &Self::Target {
|
|
&self.0
|
|
}
|
|
}
|
|
|
|
impl<ClassT, ValueT> core::ops::Deref for TpNewTupleResolver<ClassT, ValueT> {
|
|
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<T>`, `(S, B)` tuples).
|
|
#[expect(private_bounds, reason = "internal trait implementation")]
|
|
impl<ClassT, ValueT> TpNewValueTypeResolver<ClassT, ValueT>
|
|
where
|
|
ClassT: PyClass,
|
|
ValueT: IntoPyClassInitializer<ClassT>,
|
|
{
|
|
pub fn resolve(&self, value: ValueT) -> PyClassInitializer<ClassT> {
|
|
value.into_pyclass_initializer()
|
|
}
|
|
}
|
|
|
|
impl<S, B> TpNewTupleResolver<S, (S, B)>
|
|
where
|
|
S: PyClass<BaseType = B>,
|
|
B: PyClass + PyClassBaseType<Initializer = PyClassInitializer<B>>,
|
|
B::BaseType: PyClassBaseType<Initializer = PyNativeTypeInitializer<B::BaseType>>,
|
|
{
|
|
#[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<S> {
|
|
value.into()
|
|
}
|
|
}
|
|
|
|
/// All other conversions fall back to IntoPyObject via the deref implementation
|
|
impl ResolveToArbitraryObject {
|
|
pub fn resolve<ValueT>(&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<T: PyClass>` 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<T>` 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<crate::Bound<'py, crate::PyAny>>;
|
|
}
|
|
|
|
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<crate::Bound<'py, crate::PyAny>> {
|
|
self.into_bound_py_any(cls.py())
|
|
}
|
|
}
|
|
|
|
impl<'py, T> PyClassInit<'py, T> for PyClassInitializer<T>
|
|
where
|
|
T: PyClass,
|
|
{
|
|
unsafe fn init(
|
|
self,
|
|
cls: crate::Borrowed<'_, 'py, crate::types::PyType>,
|
|
) -> PyResult<crate::Bound<'py, crate::PyAny>> {
|
|
// 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<PyClassInitializer<T>>`, but just an internal equivalent
|
|
/// to avoid allowing user code to define custom return types from `#[new]`.
|
|
trait IntoPyClassInitializer<T: PyClass>: Sized {
|
|
fn into_pyclass_initializer(self) -> PyClassInitializer<T>;
|
|
}
|
|
|
|
impl<T: PyClass> IntoPyClassInitializer<T> for PyClassInitializer<T> {
|
|
fn into_pyclass_initializer(self) -> PyClassInitializer<T> {
|
|
self
|
|
}
|
|
}
|
|
|
|
impl<T> IntoPyClassInitializer<T> for T
|
|
where
|
|
T: PyClass,
|
|
T::BaseType: PyClassBaseType<Initializer = PyNativeTypeInitializer<T::BaseType>>,
|
|
{
|
|
fn into_pyclass_initializer(self) -> PyClassInitializer<T> {
|
|
self.into()
|
|
}
|
|
}
|