Skip to content

Commit 71649ed

Browse files
committed
Introduce PyClassInitializer
1 parent a663907 commit 71649ed

11 files changed

+167
-37
lines changed

pyo3-derive-backend/src/pymethod.rs

Lines changed: 18 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -222,7 +222,11 @@ pub fn impl_proto_wrap(cls: &syn::Type, name: &syn::Ident, spec: &FnSpec<'_>) ->
222222
pub fn impl_wrap_new(cls: &syn::Type, name: &syn::Ident, spec: &FnSpec<'_>) -> TokenStream {
223223
let names: Vec<syn::Ident> = get_arg_names(&spec);
224224
let cb = quote! { #cls::#name(#(#names),*) };
225-
let body = impl_arg_params(spec, cb);
225+
let body = impl_arg_params_(
226+
spec,
227+
cb,
228+
quote! { pyo3::pyclass::IntoInitializer::into_initializer },
229+
);
226230

227231
quote! {
228232
#[allow(unused_mut)]
@@ -239,9 +243,9 @@ pub fn impl_wrap_new(cls: &syn::Type, name: &syn::Ident, spec: &FnSpec<'_>) -> T
239243
let _args = _py.from_borrowed_ptr::<pyo3::types::PyTuple>(_args);
240244
let _kwargs: Option<&pyo3::types::PyDict> = _py.from_borrowed_ptr_or_opt(_kwargs);
241245

242-
# body
246+
#body
243247

244-
match _result.and_then(|slf| pyo3::PyClassShell::new(_py, slf)) {
248+
match _result.and_then(|init| init.create_shell(_py)) {
245249
Ok(slf) => slf as _,
246250
Err(e) => e.restore_and_null(_py),
247251
}
@@ -409,11 +413,11 @@ fn bool_to_ident(condition: bool) -> syn::Ident {
409413
}
410414
}
411415

412-
pub fn impl_arg_params(spec: &FnSpec<'_>, body: TokenStream) -> TokenStream {
416+
fn impl_arg_params_(spec: &FnSpec<'_>, body: TokenStream, into_result: TokenStream) -> TokenStream {
413417
if spec.args.is_empty() {
414418
return quote! {
415419
let _result = {
416-
pyo3::derive_utils::IntoPyResult::into_py_result(#body)
420+
#into_result (#body)
417421
};
418422
};
419423
}
@@ -471,11 +475,19 @@ pub fn impl_arg_params(spec: &FnSpec<'_>, body: TokenStream) -> TokenStream {
471475

472476
#(#param_conversion)*
473477

474-
pyo3::derive_utils::IntoPyResult::into_py_result(#body)
478+
#into_result(#body)
475479
})();
476480
}
477481
}
478482

483+
pub fn impl_arg_params(spec: &FnSpec<'_>, body: TokenStream) -> TokenStream {
484+
impl_arg_params_(
485+
spec,
486+
body,
487+
quote! { pyo3::derive_utils::IntoPyResult::into_py_result },
488+
)
489+
}
490+
479491
/// Re option_pos: The option slice doesn't contain the py: Python argument, so the argument
480492
/// index and the index in option diverge when using py: Python
481493
fn impl_arg_param(

src/derive_utils.rs

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -9,9 +9,7 @@ use crate::exceptions::TypeError;
99
use crate::init_once;
1010
use crate::instance::PyNativeType;
1111
use crate::types::{PyAny, PyDict, PyModule, PyTuple};
12-
use crate::GILPool;
13-
use crate::Python;
14-
use crate::{ffi, IntoPy, PyObject};
12+
use crate::{ffi, GILPool, IntoPy, PyObject, Python};
1513
use std::ptr;
1614

1715
/// Description of a python parameter; used for `parse_args()`.

src/instance.rs

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ use crate::err::{PyErr, PyResult};
33
use crate::gil;
44
use crate::object::PyObject;
55
use crate::objectprotocol::ObjectProtocol;
6-
use crate::pyclass::{PyClass, PyClassShell};
6+
use crate::pyclass::{IntoInitializer, PyClass, PyClassShell};
77
use crate::type_object::{PyConcreteObject, PyTypeInfo};
88
use crate::types::PyAny;
99
use crate::{ffi, IntoPy};
@@ -35,11 +35,12 @@ unsafe impl<T> Sync for Py<T> {}
3535

3636
impl<T> Py<T> {
3737
/// Create new instance of T and move it under python management
38-
pub fn new(py: Python, value: T) -> PyResult<Py<T>>
38+
pub fn new(py: Python, value: impl IntoInitializer<T>) -> PyResult<Py<T>>
3939
where
40-
T: PyClass,
40+
T: PyClass + PyTypeInfo<ConcreteLayout = PyClassShell<T>>,
4141
{
42-
let obj = unsafe { PyClassShell::new(py, value)? };
42+
let initializer = value.into_initializer()?;
43+
let obj = unsafe { initializer.create_shell(py)? };
4344
let ob = unsafe { Py::from_owned_ptr(obj as _) };
4445
Ok(ob)
4546
}

src/lib.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -127,7 +127,7 @@ pub use crate::gil::{init_once, GILGuard, GILPool};
127127
pub use crate::instance::{AsPyRef, ManagedPyRef, Py, PyNativeType};
128128
pub use crate::object::PyObject;
129129
pub use crate::objectprotocol::ObjectProtocol;
130-
pub use crate::pyclass::{PyClass, PyClassShell};
130+
pub use crate::pyclass::{PyClass, PyClassInitializer, PyClassShell};
131131
pub use crate::python::{prepare_freethreaded_python, Python};
132132
pub use crate::type_object::{type_flags, PyConcreteObject, PyTypeInfo};
133133

src/prelude.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ pub use crate::objectprotocol::ObjectProtocol;
1818
pub use crate::python::Python;
1919
pub use crate::{FromPy, FromPyObject, IntoPy, IntoPyPointer, PyTryFrom, PyTryInto, ToPyObject};
2020
// This is only part of the prelude because we need it for the pymodule function
21+
pub use crate::pyclass::PyClassInitializer;
2122
pub use crate::types::PyModule;
2223
pub use pyo3cls::pymodule;
2324
pub use pyo3cls::{pyclass, pyfunction, pymethods, pyproto};

src/pyclass.rs

Lines changed: 124 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
//! An experiment module which has all codes related only to #[pyclass]
22
use crate::class::methods::{PyMethodDefType, PyMethodsProtocol};
33
use crate::conversion::{AsPyPointer, FromPyPointer, ToPyObject};
4+
use crate::exceptions::RuntimeError;
45
use crate::pyclass_slots::{PyClassDict, PyClassWeakRef};
56
use crate::type_object::{type_flags, PyConcreteObject, PyTypeObject};
67
use crate::types::PyAny;
@@ -75,7 +76,7 @@ where
7576
}
7677
}
7778

78-
/// So this is a shell for our *sweet* pyclasses to survive in *harsh* Python world.
79+
/// `PyClassShell` represents the concrete layout of our `#[pyclass]` in the Python heap.
7980
#[repr(C)]
8081
pub struct PyClassShell<T: PyClass> {
8182
ob_base: <T::BaseType as PyTypeInfo>::ConcreteLayout,
@@ -85,49 +86,66 @@ pub struct PyClassShell<T: PyClass> {
8586
}
8687

8788
impl<T: PyClass> PyClassShell<T> {
88-
pub fn new_ref(py: Python, value: T) -> PyResult<&Self> {
89+
pub fn new_ref(py: Python, value: impl IntoInitializer<T>) -> PyResult<&Self>
90+
where
91+
T: PyTypeInfo<ConcreteLayout = Self>,
92+
{
8993
unsafe {
90-
let ptr = Self::new(py, value)?;
91-
FromPyPointer::from_owned_ptr_or_err(py, ptr as _)
94+
let initializer = value.into_initializer()?;
95+
let self_ = Self::new(py)?;
96+
initializer.init_class(&mut *self_)?;
97+
FromPyPointer::from_owned_ptr_or_err(py, self_ as _)
9298
}
9399
}
94100

95-
pub fn new_mut(py: Python, value: T) -> PyResult<&mut Self> {
101+
pub fn new_mut(py: Python, value: impl IntoInitializer<T>) -> PyResult<&mut Self>
102+
where
103+
T: PyTypeInfo<ConcreteLayout = Self>,
104+
{
96105
unsafe {
97-
let ptr = Self::new(py, value)?;
98-
FromPyPointer::from_owned_ptr_or_err(py, ptr as _)
106+
let initializer = value.into_initializer()?;
107+
let self_ = Self::new(py)?;
108+
initializer.init_class(&mut *self_)?;
109+
FromPyPointer::from_owned_ptr_or_err(py, self_ as _)
99110
}
100111
}
101112

102-
pub unsafe fn new(py: Python, value: T) -> PyResult<*mut Self> {
113+
#[doc(hidden)]
114+
pub unsafe fn new(py: Python) -> PyResult<*mut Self> {
103115
T::init_type();
104116
let base = T::alloc(py);
105117
if base.is_null() {
106118
return Err(PyErr::fetch(py));
107119
}
108120
let self_ = base as *mut Self;
109-
(*self_).pyclass = ManuallyDrop::new(value);
110121
(*self_).dict = T::Dict::new();
111122
(*self_).weakref = T::WeakRef::new();
112123
Ok(self_)
113124
}
114125
}
115126

116127
impl<T: PyClass> PyConcreteObject<T> for PyClassShell<T> {
128+
const NEED_INIT: bool = std::mem::size_of::<T>() != 0;
117129
unsafe fn internal_ref_cast(obj: &PyAny) -> &T {
118130
let shell = obj.as_ptr() as *const PyClassShell<T>;
119-
&*(*shell).pyclass
131+
&(*shell).pyclass
120132
}
121133
unsafe fn internal_mut_cast(obj: &PyAny) -> &mut T {
122134
let shell = obj.as_ptr() as *const PyClassShell<T> as *mut PyClassShell<T>;
123-
&mut *(*shell).pyclass
135+
&mut (*shell).pyclass
124136
}
125137
unsafe fn py_drop(&mut self, py: Python) {
126138
ManuallyDrop::drop(&mut self.pyclass);
127139
self.dict.clear_dict(py);
128140
self.weakref.clear_weakrefs(self.as_ptr(), py);
129141
self.ob_base.py_drop(py);
130142
}
143+
unsafe fn py_init(&mut self, value: T) {
144+
self.pyclass = ManuallyDrop::new(value);
145+
}
146+
fn get_super(&mut self) -> Option<&mut <T::BaseType as PyTypeInfo>::ConcreteLayout> {
147+
Some(&mut self.ob_base)
148+
}
131149
}
132150

133151
impl<T: PyClass> AsPyPointer for PyClassShell<T> {
@@ -190,6 +208,101 @@ where
190208
}
191209
}
192210

211+
/// An initializer for `PyClassShell<T>`.
212+
///
213+
/// **NOTE** If
214+
pub struct PyClassInitializer<T: PyTypeInfo> {
215+
init: Option<T>,
216+
super_init: Option<*mut PyClassInitializer<T::BaseType>>,
217+
}
218+
219+
impl<T: PyTypeInfo> PyClassInitializer<T> {
220+
pub fn from_value(value: T) -> Self {
221+
PyClassInitializer {
222+
init: Some(value),
223+
super_init: None,
224+
}
225+
}
226+
227+
pub fn new() -> Self {
228+
PyClassInitializer {
229+
init: None,
230+
super_init: None,
231+
}
232+
}
233+
234+
#[must_use]
235+
#[doc(hiddden)]
236+
pub fn init_class(self, shell: &mut T::ConcreteLayout) -> PyResult<()> {
237+
let PyClassInitializer { init, super_init } = self;
238+
if let Some(value) = init {
239+
unsafe { shell.py_init(value) };
240+
} else if !T::ConcreteLayout::NEED_INIT {
241+
return Err(PyErr::new::<RuntimeError, _>(format!(
242+
"Base class {} is not initialized",
243+
T::NAME
244+
)));
245+
}
246+
if let Some(super_init) = super_init {
247+
let super_init = unsafe { Box::from_raw(super_init) };
248+
if let Some(super_obj) = shell.get_super() {
249+
super_init.init_class(super_obj)?;
250+
}
251+
}
252+
Ok(())
253+
}
254+
255+
pub fn init(&mut self, value: T) {
256+
self.init = Some(value);
257+
}
258+
259+
pub fn get_super(&mut self) -> &mut PyClassInitializer<T::BaseType> {
260+
if let Some(super_init) = self.super_init {
261+
return unsafe { &mut *super_init };
262+
}
263+
let super_init = Box::into_raw(Box::new(PyClassInitializer::new()));
264+
self.super_init = Some(super_init);
265+
return unsafe { &mut *super_init };
266+
}
267+
268+
pub unsafe fn create_shell(self, py: Python) -> PyResult<*mut PyClassShell<T>>
269+
where
270+
T: PyClass + PyTypeInfo<ConcreteLayout = PyClassShell<T>>,
271+
{
272+
let shell = PyClassShell::new(py)?;
273+
self.init_class(&mut *shell)?;
274+
Ok(shell)
275+
}
276+
}
277+
278+
pub trait IntoInitializer<T: PyTypeInfo> {
279+
fn into_initializer(self) -> PyResult<PyClassInitializer<T>>;
280+
}
281+
282+
impl<T: PyTypeInfo> IntoInitializer<T> for T {
283+
fn into_initializer(self) -> PyResult<PyClassInitializer<T>> {
284+
Ok(PyClassInitializer::from_value(self))
285+
}
286+
}
287+
288+
impl<T: PyTypeInfo> IntoInitializer<T> for PyResult<T> {
289+
fn into_initializer(self) -> PyResult<PyClassInitializer<T>> {
290+
self.map(PyClassInitializer::from_value)
291+
}
292+
}
293+
294+
impl<T: PyTypeInfo> IntoInitializer<T> for PyClassInitializer<T> {
295+
fn into_initializer(self) -> PyResult<PyClassInitializer<T>> {
296+
Ok(self)
297+
}
298+
}
299+
300+
impl<T: PyTypeInfo> IntoInitializer<T> for PyResult<PyClassInitializer<T>> {
301+
fn into_initializer(self) -> PyResult<PyClassInitializer<T>> {
302+
self
303+
}
304+
}
305+
193306
/// Register new type in python object system.
194307
#[cfg(not(Py_LIMITED_API))]
195308
pub fn initialize_type<T>(py: Python, module_name: Option<&str>) -> PyResult<*mut ffi::PyTypeObject>

src/type_object.rs

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -12,17 +12,22 @@ use crate::Python;
1212
use std::ptr::NonNull;
1313

1414
/// TODO: write document
15-
pub trait PyConcreteObject<T>: Sized {
15+
pub trait PyConcreteObject<T: PyTypeInfo>: Sized {
16+
const NEED_INIT: bool = false;
1617
unsafe fn internal_ref_cast(obj: &PyAny) -> &T {
1718
&*(obj as *const _ as *const T)
1819
}
1920
unsafe fn internal_mut_cast(obj: &PyAny) -> &mut T {
2021
&mut *(obj as *const _ as *const T as *mut T)
2122
}
2223
unsafe fn py_drop(&mut self, _py: Python) {}
24+
unsafe fn py_init(&mut self, _value: T) {}
25+
fn get_super(&mut self) -> Option<&mut <T::BaseType as PyTypeInfo>::ConcreteLayout> {
26+
None
27+
}
2328
}
2429

25-
impl<T: PyNativeType> PyConcreteObject<T> for ffi::PyObject {}
30+
impl<T: PyNativeType + PyTypeInfo> PyConcreteObject<T> for ffi::PyObject {}
2631

2732
/// Our custom type flags
2833
pub mod type_flags {

tests/test_dunder.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -57,7 +57,7 @@ impl<'p> PyIterProtocol for Iterator {
5757
Ok(slf.into())
5858
}
5959

60-
fn __next__(mut slf: &mut PyClassShell<Self>) -> PyResult<Option<i32>> {
60+
fn __next__(slf: &mut PyClassShell<Self>) -> PyResult<Option<i32>> {
6161
Ok(slf.iter.next())
6262
}
6363
}

tests/test_gc.rs

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ use pyo3::class::PyTraverseError;
33
use pyo3::class::PyVisit;
44
use pyo3::prelude::*;
55
use pyo3::types::{PyAny, PyTuple};
6-
use pyo3::{ffi, py_run, AsPyPointer, PyClassShell};
6+
use pyo3::{ffi, py_run, AsPyPointer, PyClassInitializer, PyClassShell};
77
use std::cell::RefCell;
88
use std::sync::atomic::{AtomicBool, Ordering};
99
use std::sync::Arc;
@@ -235,10 +235,11 @@ struct SubClassWithDrop {
235235

236236
#[pymethods]
237237
impl SubClassWithDrop {
238-
// TODO(kngwyu): Implement baseclass initialization
239238
#[new]
240-
fn new() -> SubClassWithDrop {
241-
SubClassWithDrop { data: None }
239+
fn new() -> PyClassInitializer<Self> {
240+
let mut init = PyClassInitializer::from_value(SubClassWithDrop { data: None });
241+
init.get_super().init(BaseClassWithDrop { data: None });
242+
init
242243
}
243244
}
244245

tests/test_getter_setter.rs

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,6 @@
11
use pyo3::prelude::*;
22
use pyo3::py_run;
33
use pyo3::types::{IntoPyDict, PyList};
4-
use std::isize;
54

65
mod common;
76

tests/test_inheritance.rs

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -48,14 +48,14 @@ struct SubClass {
4848
#[pymethods]
4949
impl SubClass {
5050
#[new]
51-
fn new() -> Self {
52-
SubClass { val2: 5 }
51+
fn new() -> PyClassInitializer<Self> {
52+
let mut init = PyClassInitializer::from_value(SubClass { val2: 5 });
53+
init.get_super().init(BaseClass { val1: 10 });
54+
init
5355
}
5456
}
5557

56-
// TODO(kngwyu): disable untill super().__init__ fixed
5758
#[test]
58-
#[ignore]
5959
fn inheritance_with_new_methods() {
6060
let gil = Python::acquire_gil();
6161
let py = gil.python();

0 commit comments

Comments
 (0)