From d06c0b65a6cc51b80e08c73abd064bb51ee1a556 Mon Sep 17 00:00:00 2001 From: Icxolu <10486322+Icxolu@users.noreply.github.com> Date: Sun, 17 May 2026 14:48:48 +0200 Subject: [PATCH] allow passing arguments to native super type constructor --- src/internal/pyclass_init.rs | 33 ++++++++--- src/prelude.rs | 2 +- src/pyclass_init.rs | 103 +++++++++++++++++++++++++++++++++-- 3 files changed, 124 insertions(+), 14 deletions(-) diff --git a/src/internal/pyclass_init.rs b/src/internal/pyclass_init.rs index 037f31e92d7..8b377304a59 100644 --- a/src/internal/pyclass_init.rs +++ b/src/internal/pyclass_init.rs @@ -6,9 +6,9 @@ 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::types::{PyDict, PyTuple, PyType, PyTypeMethods}; use crate::{ - ffi, IntoPyObject, IntoPyObjectExt, PyClass, PyClassInitializer, PyErr, PyResult, Python, + ffi, Bound, IntoPyObject, IntoPyObjectExt, PyClass, PyClassInitializer, PyErr, PyResult, }; use crate::{ffi::PyTypeObject, type_object::PyTypeInfo}; use core::marker::PhantomData; @@ -22,8 +22,9 @@ pub(crate) trait PyObjectInit: Sized { /// - `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, + args: Bound<'_, PyTuple>, + kwargs: Option>, ) -> PyResult<*mut ffi::PyObject>; } @@ -33,14 +34,17 @@ pub struct PyNativeTypeInitializer(pub PhantomData); impl PyObjectInit for PyNativeTypeInitializer { unsafe fn into_new_object( self, - py: Python<'_>, subtype: *mut PyTypeObject, + args: Bound<'_, PyTuple>, + kwargs: Option>, ) -> PyResult<*mut ffi::PyObject> { unsafe fn inner( - py: Python<'_>, type_ptr: *mut PyTypeObject, subtype: *mut PyTypeObject, + args: &Bound<'_, PyTuple>, + kwargs: Option<&Bound<'_, PyDict>>, ) -> PyResult<*mut ffi::PyObject> { + let py = args.py(); let tp_new = unsafe { type_ptr .cast::() @@ -50,16 +54,27 @@ impl PyObjectInit for PyNativeTypeInitializer { .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()) }; + let obj = unsafe { + tp_new( + subtype, + args.as_ptr(), + kwargs.map_or_else(core::ptr::null_mut, Bound::as_ptr), + ) + }; if obj.is_null() { Err(PyErr::fetch(py)) } else { Ok(obj) } } - unsafe { inner(py, T::type_object_raw(py), subtype) } + unsafe { + inner( + T::type_object_raw(args.py()), + subtype, + &args, + kwargs.as_ref(), + ) + } } } diff --git a/src/prelude.rs b/src/prelude.rs index 33a6f23534e..c99e191fe52 100644 --- a/src/prelude.rs +++ b/src/prelude.rs @@ -13,7 +13,7 @@ pub use crate::err::{PyErr, PyResult}; pub use crate::instance::{Borrowed, Bound, Py}; pub use crate::marker::Python; pub use crate::pycell::{PyRef, PyRefMut}; -pub use crate::pyclass_init::PyClassInitializer; +pub use crate::pyclass_init::{PyClassInitializer, PySuperNew}; pub use crate::types::{PyAny, PyModule}; pub use crate::{PyClassGuard, PyClassGuardMut}; diff --git a/src/pyclass_init.rs b/src/pyclass_init.rs index 8e996d9b541..bf5528154fa 100644 --- a/src/pyclass_init.rs +++ b/src/pyclass_init.rs @@ -7,7 +7,10 @@ use crate::impl_::pyclass::{PyClassBaseType, PyClassImpl}; use crate::impl_::pyclass_init::PyNativeTypeInitializer; use crate::internal::pyclass_init::PyObjectInit; use crate::pycell::impl_::PyClassObjectLayout; -use crate::{ffi, Bound, PyClass, PyResult, Python}; +use crate::types::{PyDict, PyTuple}; +use crate::{ + ffi, Bound, BoundObject, IntoPyObject, IntoPyObjectExt, Py, PyClass, PyResult, Python, +}; use crate::{ffi::PyTypeObject, pycell::impl_::PyClassObjectContents}; use core::marker::PhantomData; @@ -63,6 +66,8 @@ use core::marker::PhantomData; pub struct PyClassInitializer { init: T, super_init: ::Initializer, + args: Option>, + kwargs: Option>, } impl PyClassInitializer { @@ -72,7 +77,12 @@ impl PyClassInitializer { #[track_caller] #[inline] pub fn new(init: T, super_init: ::Initializer) -> Self { - Self { init, super_init } + Self { + init, + super_init, + args: None, + kwargs: None, + } } /// Constructs a new initializer from an initializer for the base class. @@ -151,7 +161,12 @@ impl PyClassInitializer { where T: PyClass, { - let obj = unsafe { self.super_init.into_new_object(py, target_type)? }; + let args = self + .args + .map(|args| args.into_bound(py)) + .unwrap_or_else(|| PyTuple::empty(py)); + let kwargs = self.kwargs.map(|kwargs| kwargs.into_bound(py)); + let obj = unsafe { self.super_init.into_new_object(target_type, args, kwargs)? }; // SAFETY: `obj` is constructed using `T::Layout` but has not been initialized yet let contents = unsafe { ::Layout::contents_uninit(obj) }; @@ -168,9 +183,11 @@ impl PyClassInitializer { impl PyObjectInit for PyClassInitializer { unsafe fn into_new_object( self, - py: Python<'_>, subtype: *mut PyTypeObject, + args: Bound<'_, PyTuple>, + _kwargs: Option>, ) -> PyResult<*mut ffi::PyObject> { + let py = args.py(); unsafe { self.create_class_object_of_type(py, subtype) .map(Bound::into_ptr) @@ -202,3 +219,81 @@ where PyClassInitializer::from(base).add_subclass(sub) } } + +/// Wrapper type around the arguments that are passed to the native base type of a `#[pyclass]` +pub struct PySuperNew<'py> { + args: Bound<'py, PyTuple>, + kwargs: Option>, +} + +impl<'py> PySuperNew<'py> { + /// Creates a new [`PySuperNew`] using the provided arguments + /// + /// ``` + /// # use pyo3::prelude::*; + /// + /// # fn main() -> PyResult<()> { + /// # Python::attach(|py| { + /// // create an initializer using two positional and no keyword arguments + /// let super_new = PySuperNew::call(py, ("Hello", "World"), None)?; + /// # Ok(()) + /// # })} + /// ``` + pub fn call(py: Python<'py>, args: A, kwargs: Option>) -> PyResult + where + A: IntoPyObject<'py, Target = PyTuple>, + { + let args = args.into_pyobject_or_pyerr(py)?.into_bound(); + Ok(Self { args, kwargs }) + } + + /// Converts this [`PySuperNew`] into a [`PyClassInitializer`] for a `#[pyclass]`, forwarding + /// its arguments the `T`s native base initializer. + /// + /// ``` + /// # #[cfg(any(not(Py_LIMITED_API), Py_3_12))] + /// # use pyo3::prelude::*; + /// # #[cfg(any(not(Py_LIMITED_API), Py_3_12))] + /// # use pyo3::types::PyDateTime; + /// + /// # #[cfg(any(not(Py_LIMITED_API), Py_3_12))] + /// #[pyclass(extends = PyDateTime, get_all)] + /// struct CustomDate { + /// field: usize + /// } + /// + /// # #[cfg(any(not(Py_LIMITED_API), Py_3_12))] + /// #[pymethods] + /// impl CustomDate { + /// #[new] + /// fn new(py: Python<'_>) -> PyResult> { + /// // `PyDateTime` requires initialization with (year, month, day) positional arguments + /// Ok(PySuperNew::call(py, (2012, 12, 21), None)?.for_class(Self { field: 42 })) + /// } + /// } + /// + /// # #[cfg(any(not(Py_LIMITED_API), Py_3_12))] + /// # fn main() -> PyResult<()> { + /// # use pyo3::PyTypeInfo; + /// # Python::attach(|py| { + /// # let ty = CustomDate::type_object(py); + /// # pyo3::py_run!(py, ty, "assert str(ty()) == '2012-12-21 00:00:00'"); + /// # pyo3::py_run!(py, ty, "assert ty().field == 42"); + /// # Ok(()) + /// # })} + /// # #[cfg(not(any(not(Py_LIMITED_API), Py_3_12)))] + /// # fn main() {} + /// ``` + pub fn for_class(self, class: T) -> PyClassInitializer + where + T: PyClass, + T::BaseType: PyClassBaseType>, + { + PyClassInitializer { + init: class, + super_init: PyNativeTypeInitializer(PhantomData), + args: Some(self.args.unbind()), + kwargs: self.kwargs.map(Bound::unbind), + } + } +}