Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
33 changes: 24 additions & 9 deletions src/internal/pyclass_init.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -22,8 +22,9 @@ pub(crate) trait PyObjectInit<T>: 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<Bound<'_, PyDict>>,
) -> PyResult<*mut ffi::PyObject>;
}

Expand All @@ -33,14 +34,17 @@ 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,
args: Bound<'_, PyTuple>,
kwargs: Option<Bound<'_, PyDict>>,
) -> 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::<ffi::PyObject>()
Expand All @@ -50,16 +54,27 @@ impl<T: PyTypeInfo> PyObjectInit<T> for PyNativeTypeInitializer<T> {
.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(),
)
}
}
}

Expand Down
2 changes: 1 addition & 1 deletion src/prelude.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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};

Expand Down
103 changes: 99 additions & 4 deletions src/pyclass_init.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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;

Expand Down Expand Up @@ -63,6 +66,8 @@ use core::marker::PhantomData;
pub struct PyClassInitializer<T: PyClass> {
init: T,
super_init: <T::BaseType as PyClassBaseType>::Initializer,
args: Option<Py<PyTuple>>,
kwargs: Option<Py<PyDict>>,
}

impl<T: PyClass> PyClassInitializer<T> {
Expand All @@ -72,7 +77,12 @@ impl<T: PyClass> PyClassInitializer<T> {
#[track_caller]
#[inline]
pub fn new(init: T, super_init: <T::BaseType as PyClassBaseType>::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.
Expand Down Expand Up @@ -151,7 +161,12 @@ impl<T: PyClass> PyClassInitializer<T> {
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 { <T as PyClassImpl>::Layout::contents_uninit(obj) };
Expand All @@ -168,9 +183,11 @@ impl<T: PyClass> PyClassInitializer<T> {
impl<T: PyClass> PyObjectInit<T> for PyClassInitializer<T> {
unsafe fn into_new_object(
self,
py: Python<'_>,
subtype: *mut PyTypeObject,
args: Bound<'_, PyTuple>,
_kwargs: Option<Bound<'_, PyDict>>,
) -> PyResult<*mut ffi::PyObject> {
let py = args.py();
unsafe {
self.create_class_object_of_type(py, subtype)
.map(Bound::into_ptr)
Expand Down Expand Up @@ -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<Bound<'py, PyDict>>,
}

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<A>(py: Python<'py>, args: A, kwargs: Option<Bound<'py, PyDict>>) -> PyResult<Self>
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<T>`] 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<PyClassInitializer<Self>> {
/// // `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<T>(self, class: T) -> PyClassInitializer<T>
where
T: PyClass,
T::BaseType: PyClassBaseType<Initializer = PyNativeTypeInitializer<T::BaseType>>,
{
PyClassInitializer {
init: class,
super_init: PyNativeTypeInitializer(PhantomData),
args: Some(self.args.unbind()),
kwargs: self.kwargs.map(Bound::unbind),
}
}
}
Loading