Introduce PyClassInitializer
This commit is contained in:
parent
a6639076b9
commit
b86de9376d
|
@ -222,7 +222,11 @@ pub fn impl_proto_wrap(cls: &syn::Type, name: &syn::Ident, spec: &FnSpec<'_>) ->
|
|||
pub fn impl_wrap_new(cls: &syn::Type, name: &syn::Ident, spec: &FnSpec<'_>) -> TokenStream {
|
||||
let names: Vec<syn::Ident> = get_arg_names(&spec);
|
||||
let cb = quote! { #cls::#name(#(#names),*) };
|
||||
let body = impl_arg_params(spec, cb);
|
||||
let body = impl_arg_params_(
|
||||
spec,
|
||||
cb,
|
||||
quote! { pyo3::pyclass::IntoInitializer::into_initializer },
|
||||
);
|
||||
|
||||
quote! {
|
||||
#[allow(unused_mut)]
|
||||
|
@ -239,9 +243,9 @@ pub fn impl_wrap_new(cls: &syn::Type, name: &syn::Ident, spec: &FnSpec<'_>) -> T
|
|||
let _args = _py.from_borrowed_ptr::<pyo3::types::PyTuple>(_args);
|
||||
let _kwargs: Option<&pyo3::types::PyDict> = _py.from_borrowed_ptr_or_opt(_kwargs);
|
||||
|
||||
# body
|
||||
#body
|
||||
|
||||
match _result.and_then(|slf| pyo3::PyClassShell::new(_py, slf)) {
|
||||
match _result.and_then(|init| init.create_shell(_py)) {
|
||||
Ok(slf) => slf as _,
|
||||
Err(e) => e.restore_and_null(_py),
|
||||
}
|
||||
|
@ -409,11 +413,11 @@ fn bool_to_ident(condition: bool) -> syn::Ident {
|
|||
}
|
||||
}
|
||||
|
||||
pub fn impl_arg_params(spec: &FnSpec<'_>, body: TokenStream) -> TokenStream {
|
||||
fn impl_arg_params_(spec: &FnSpec<'_>, body: TokenStream, into_result: TokenStream) -> TokenStream {
|
||||
if spec.args.is_empty() {
|
||||
return quote! {
|
||||
let _result = {
|
||||
pyo3::derive_utils::IntoPyResult::into_py_result(#body)
|
||||
#into_result (#body)
|
||||
};
|
||||
};
|
||||
}
|
||||
|
@ -471,11 +475,19 @@ pub fn impl_arg_params(spec: &FnSpec<'_>, body: TokenStream) -> TokenStream {
|
|||
|
||||
#(#param_conversion)*
|
||||
|
||||
pyo3::derive_utils::IntoPyResult::into_py_result(#body)
|
||||
#into_result(#body)
|
||||
})();
|
||||
}
|
||||
}
|
||||
|
||||
pub fn impl_arg_params(spec: &FnSpec<'_>, body: TokenStream) -> TokenStream {
|
||||
impl_arg_params_(
|
||||
spec,
|
||||
body,
|
||||
quote! { pyo3::derive_utils::IntoPyResult::into_py_result },
|
||||
)
|
||||
}
|
||||
|
||||
/// Re option_pos: The option slice doesn't contain the py: Python argument, so the argument
|
||||
/// index and the index in option diverge when using py: Python
|
||||
fn impl_arg_param(
|
||||
|
|
|
@ -9,9 +9,7 @@ use crate::exceptions::TypeError;
|
|||
use crate::init_once;
|
||||
use crate::instance::PyNativeType;
|
||||
use crate::types::{PyAny, PyDict, PyModule, PyTuple};
|
||||
use crate::GILPool;
|
||||
use crate::Python;
|
||||
use crate::{ffi, IntoPy, PyObject};
|
||||
use crate::{ffi, GILPool, IntoPy, PyObject, Python};
|
||||
use std::ptr;
|
||||
|
||||
/// Description of a python parameter; used for `parse_args()`.
|
||||
|
|
|
@ -3,7 +3,7 @@ use crate::err::{PyErr, PyResult};
|
|||
use crate::gil;
|
||||
use crate::object::PyObject;
|
||||
use crate::objectprotocol::ObjectProtocol;
|
||||
use crate::pyclass::{PyClass, PyClassShell};
|
||||
use crate::pyclass::{IntoInitializer, PyClass, PyClassShell};
|
||||
use crate::type_object::{PyConcreteObject, PyTypeInfo};
|
||||
use crate::types::PyAny;
|
||||
use crate::{ffi, IntoPy};
|
||||
|
@ -35,11 +35,12 @@ unsafe impl<T> Sync for Py<T> {}
|
|||
|
||||
impl<T> Py<T> {
|
||||
/// Create new instance of T and move it under python management
|
||||
pub fn new(py: Python, value: T) -> PyResult<Py<T>>
|
||||
pub fn new(py: Python, value: impl IntoInitializer<T>) -> PyResult<Py<T>>
|
||||
where
|
||||
T: PyClass,
|
||||
T: PyClass + PyTypeInfo<ConcreteLayout = PyClassShell<T>>,
|
||||
{
|
||||
let obj = unsafe { PyClassShell::new(py, value)? };
|
||||
let initializer = value.into_initializer()?;
|
||||
let obj = unsafe { initializer.create_shell(py)? };
|
||||
let ob = unsafe { Py::from_owned_ptr(obj as _) };
|
||||
Ok(ob)
|
||||
}
|
||||
|
|
|
@ -127,7 +127,7 @@ pub use crate::gil::{init_once, GILGuard, GILPool};
|
|||
pub use crate::instance::{AsPyRef, ManagedPyRef, Py, PyNativeType};
|
||||
pub use crate::object::PyObject;
|
||||
pub use crate::objectprotocol::ObjectProtocol;
|
||||
pub use crate::pyclass::{PyClass, PyClassShell};
|
||||
pub use crate::pyclass::{PyClass, PyClassInitializer, PyClassShell};
|
||||
pub use crate::python::{prepare_freethreaded_python, Python};
|
||||
pub use crate::type_object::{type_flags, PyConcreteObject, PyTypeInfo};
|
||||
|
||||
|
@ -217,7 +217,7 @@ macro_rules! wrap_pymodule {
|
|||
///
|
||||
/// # Example
|
||||
/// ```
|
||||
/// use pyo3::{prelude::*, py_run};
|
||||
/// use pyo3::{prelude::*, py_run, PyClassShell};
|
||||
/// #[pyclass]
|
||||
/// #[derive(Debug)]
|
||||
/// struct Time {
|
||||
|
@ -240,7 +240,7 @@ macro_rules! wrap_pymodule {
|
|||
/// }
|
||||
/// let gil = Python::acquire_gil();
|
||||
/// let py = gil.python();
|
||||
/// let time = PyRef::new(py, Time {hour: 8, minute: 43, second: 16}).unwrap();
|
||||
/// let time = PyClassShell::new_ref(py, Time {hour: 8, minute: 43, second: 16}).unwrap();
|
||||
/// let time_as_tuple = (8, 43, 16);
|
||||
/// py_run!(py, time time_as_tuple, r#"
|
||||
/// assert time.hour == 8
|
||||
|
|
|
@ -18,6 +18,7 @@ pub use crate::objectprotocol::ObjectProtocol;
|
|||
pub use crate::python::Python;
|
||||
pub use crate::{FromPy, FromPyObject, IntoPy, IntoPyPointer, PyTryFrom, PyTryInto, ToPyObject};
|
||||
// This is only part of the prelude because we need it for the pymodule function
|
||||
pub use crate::pyclass::PyClassInitializer;
|
||||
pub use crate::types::PyModule;
|
||||
pub use pyo3cls::pymodule;
|
||||
pub use pyo3cls::{pyclass, pyfunction, pymethods, pyproto};
|
||||
|
|
141
src/pyclass.rs
141
src/pyclass.rs
|
@ -1,6 +1,7 @@
|
|||
//! An experiment module which has all codes related only to #[pyclass]
|
||||
use crate::class::methods::{PyMethodDefType, PyMethodsProtocol};
|
||||
use crate::conversion::{AsPyPointer, FromPyPointer, ToPyObject};
|
||||
use crate::exceptions::RuntimeError;
|
||||
use crate::pyclass_slots::{PyClassDict, PyClassWeakRef};
|
||||
use crate::type_object::{type_flags, PyConcreteObject, PyTypeObject};
|
||||
use crate::types::PyAny;
|
||||
|
@ -75,7 +76,7 @@ where
|
|||
}
|
||||
}
|
||||
|
||||
/// So this is a shell for our *sweet* pyclasses to survive in *harsh* Python world.
|
||||
/// `PyClassShell` represents the concrete layout of our `#[pyclass]` in the Python heap.
|
||||
#[repr(C)]
|
||||
pub struct PyClassShell<T: PyClass> {
|
||||
ob_base: <T::BaseType as PyTypeInfo>::ConcreteLayout,
|
||||
|
@ -85,28 +86,37 @@ pub struct PyClassShell<T: PyClass> {
|
|||
}
|
||||
|
||||
impl<T: PyClass> PyClassShell<T> {
|
||||
pub fn new_ref(py: Python, value: T) -> PyResult<&Self> {
|
||||
pub fn new_ref(py: Python, value: impl IntoInitializer<T>) -> PyResult<&Self>
|
||||
where
|
||||
T: PyTypeInfo<ConcreteLayout = Self>,
|
||||
{
|
||||
unsafe {
|
||||
let ptr = Self::new(py, value)?;
|
||||
FromPyPointer::from_owned_ptr_or_err(py, ptr as _)
|
||||
let initializer = value.into_initializer()?;
|
||||
let self_ = initializer.create_shell(py)?;
|
||||
FromPyPointer::from_owned_ptr_or_err(py, self_ as _)
|
||||
}
|
||||
}
|
||||
|
||||
pub fn new_mut(py: Python, value: T) -> PyResult<&mut Self> {
|
||||
pub fn new_mut(py: Python, value: impl IntoInitializer<T>) -> PyResult<&mut Self>
|
||||
where
|
||||
T: PyTypeInfo<ConcreteLayout = Self>,
|
||||
{
|
||||
unsafe {
|
||||
let ptr = Self::new(py, value)?;
|
||||
FromPyPointer::from_owned_ptr_or_err(py, ptr as _)
|
||||
let initializer = value.into_initializer()?;
|
||||
let self_ = initializer.create_shell(py)?;
|
||||
FromPyPointer::from_owned_ptr_or_err(py, self_ as _)
|
||||
}
|
||||
}
|
||||
|
||||
pub unsafe fn new(py: Python, value: T) -> PyResult<*mut Self> {
|
||||
#[doc(hidden)]
|
||||
unsafe fn new(py: Python) -> PyResult<*mut Self> {
|
||||
<T::BaseType as PyTypeObject>::init_type();
|
||||
T::init_type();
|
||||
let base = T::alloc(py);
|
||||
if base.is_null() {
|
||||
return Err(PyErr::fetch(py));
|
||||
}
|
||||
let self_ = base as *mut Self;
|
||||
(*self_).pyclass = ManuallyDrop::new(value);
|
||||
(*self_).dict = T::Dict::new();
|
||||
(*self_).weakref = T::WeakRef::new();
|
||||
Ok(self_)
|
||||
|
@ -114,13 +124,14 @@ impl<T: PyClass> PyClassShell<T> {
|
|||
}
|
||||
|
||||
impl<T: PyClass> PyConcreteObject<T> for PyClassShell<T> {
|
||||
const NEED_INIT: bool = std::mem::size_of::<T>() != 0;
|
||||
unsafe fn internal_ref_cast(obj: &PyAny) -> &T {
|
||||
let shell = obj.as_ptr() as *const PyClassShell<T>;
|
||||
&*(*shell).pyclass
|
||||
&(*shell).pyclass
|
||||
}
|
||||
unsafe fn internal_mut_cast(obj: &PyAny) -> &mut T {
|
||||
let shell = obj.as_ptr() as *const PyClassShell<T> as *mut PyClassShell<T>;
|
||||
&mut *(*shell).pyclass
|
||||
&mut (*shell).pyclass
|
||||
}
|
||||
unsafe fn py_drop(&mut self, py: Python) {
|
||||
ManuallyDrop::drop(&mut self.pyclass);
|
||||
|
@ -128,6 +139,12 @@ impl<T: PyClass> PyConcreteObject<T> for PyClassShell<T> {
|
|||
self.weakref.clear_weakrefs(self.as_ptr(), py);
|
||||
self.ob_base.py_drop(py);
|
||||
}
|
||||
unsafe fn py_init(&mut self, value: T) {
|
||||
self.pyclass = ManuallyDrop::new(value);
|
||||
}
|
||||
fn get_super(&mut self) -> Option<&mut <T::BaseType as PyTypeInfo>::ConcreteLayout> {
|
||||
Some(&mut self.ob_base)
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: PyClass> AsPyPointer for PyClassShell<T> {
|
||||
|
@ -190,6 +207,108 @@ where
|
|||
}
|
||||
}
|
||||
|
||||
/// An initializer for `PyClassShell<T>`.
|
||||
///
|
||||
/// **NOTE** If
|
||||
pub struct PyClassInitializer<T: PyTypeInfo> {
|
||||
init: Option<T>,
|
||||
super_init: Option<*mut PyClassInitializer<T::BaseType>>,
|
||||
}
|
||||
|
||||
impl<T: PyTypeInfo> PyClassInitializer<T> {
|
||||
pub fn from_value(value: T) -> Self {
|
||||
PyClassInitializer {
|
||||
init: Some(value),
|
||||
super_init: None,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn new() -> Self {
|
||||
PyClassInitializer {
|
||||
init: None,
|
||||
super_init: None,
|
||||
}
|
||||
}
|
||||
|
||||
#[must_use]
|
||||
#[doc(hiddden)]
|
||||
pub fn init_class(self, shell: &mut T::ConcreteLayout) -> PyResult<()> {
|
||||
macro_rules! raise_err {
|
||||
($name: path) => {
|
||||
return Err(PyErr::new::<RuntimeError, _>(format!(
|
||||
"Base class '{}' is not initialized",
|
||||
$name
|
||||
)));
|
||||
};
|
||||
}
|
||||
let PyClassInitializer { init, super_init } = self;
|
||||
if let Some(value) = init {
|
||||
unsafe { shell.py_init(value) };
|
||||
} else if !T::ConcreteLayout::NEED_INIT {
|
||||
raise_err!(T::NAME);
|
||||
}
|
||||
if let Some(super_init) = super_init {
|
||||
let super_init = unsafe { Box::from_raw(super_init) };
|
||||
if let Some(super_obj) = shell.get_super() {
|
||||
super_init.init_class(super_obj)?;
|
||||
}
|
||||
} else if <T::BaseType as PyTypeInfo>::ConcreteLayout::NEED_INIT {
|
||||
raise_err!(T::BaseType::NAME)
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn init(&mut self, value: T) {
|
||||
self.init = Some(value);
|
||||
}
|
||||
|
||||
pub fn get_super(&mut self) -> &mut PyClassInitializer<T::BaseType> {
|
||||
if let Some(super_init) = self.super_init {
|
||||
return unsafe { &mut *super_init };
|
||||
}
|
||||
let super_init = Box::into_raw(Box::new(PyClassInitializer::new()));
|
||||
self.super_init = Some(super_init);
|
||||
return unsafe { &mut *super_init };
|
||||
}
|
||||
|
||||
pub unsafe fn create_shell(self, py: Python) -> PyResult<*mut PyClassShell<T>>
|
||||
where
|
||||
T: PyClass + PyTypeInfo<ConcreteLayout = PyClassShell<T>>,
|
||||
{
|
||||
let shell = PyClassShell::new(py)?;
|
||||
self.init_class(&mut *shell)?;
|
||||
Ok(shell)
|
||||
}
|
||||
}
|
||||
|
||||
pub trait IntoInitializer<T: PyTypeInfo> {
|
||||
fn into_initializer(self) -> PyResult<PyClassInitializer<T>>;
|
||||
}
|
||||
|
||||
impl<T: PyTypeInfo> IntoInitializer<T> for T {
|
||||
fn into_initializer(self) -> PyResult<PyClassInitializer<T>> {
|
||||
Ok(PyClassInitializer::from_value(self))
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: PyTypeInfo> IntoInitializer<T> for PyResult<T> {
|
||||
fn into_initializer(self) -> PyResult<PyClassInitializer<T>> {
|
||||
self.map(PyClassInitializer::from_value)
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: PyTypeInfo> IntoInitializer<T> for PyClassInitializer<T> {
|
||||
fn into_initializer(self) -> PyResult<PyClassInitializer<T>> {
|
||||
Ok(self)
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: PyTypeInfo> IntoInitializer<T> for PyResult<PyClassInitializer<T>> {
|
||||
fn into_initializer(self) -> PyResult<PyClassInitializer<T>> {
|
||||
self
|
||||
}
|
||||
}
|
||||
|
||||
/// Register new type in python object system.
|
||||
#[cfg(not(Py_LIMITED_API))]
|
||||
pub fn initialize_type<T>(py: Python, module_name: Option<&str>) -> PyResult<*mut ffi::PyTypeObject>
|
||||
|
|
|
@ -12,7 +12,8 @@ use crate::Python;
|
|||
use std::ptr::NonNull;
|
||||
|
||||
/// TODO: write document
|
||||
pub trait PyConcreteObject<T>: Sized {
|
||||
pub trait PyConcreteObject<T: PyTypeInfo>: Sized {
|
||||
const NEED_INIT: bool = false;
|
||||
unsafe fn internal_ref_cast(obj: &PyAny) -> &T {
|
||||
&*(obj as *const _ as *const T)
|
||||
}
|
||||
|
@ -20,9 +21,13 @@ pub trait PyConcreteObject<T>: Sized {
|
|||
&mut *(obj as *const _ as *const T as *mut T)
|
||||
}
|
||||
unsafe fn py_drop(&mut self, _py: Python) {}
|
||||
unsafe fn py_init(&mut self, _value: T) {}
|
||||
fn get_super(&mut self) -> Option<&mut <T::BaseType as PyTypeInfo>::ConcreteLayout> {
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: PyNativeType> PyConcreteObject<T> for ffi::PyObject {}
|
||||
impl<T: PyNativeType + PyTypeInfo> PyConcreteObject<T> for ffi::PyObject {}
|
||||
|
||||
/// Our custom type flags
|
||||
pub mod type_flags {
|
||||
|
@ -57,7 +62,7 @@ pub trait PyTypeInfo: Sized {
|
|||
const FLAGS: usize = 0;
|
||||
|
||||
/// Base class
|
||||
type BaseType: PyTypeInfo;
|
||||
type BaseType: PyTypeInfo + PyTypeObject;
|
||||
|
||||
/// Layout
|
||||
type ConcreteLayout: PyConcreteObject<Self>;
|
||||
|
|
|
@ -57,7 +57,7 @@ impl<'p> PyIterProtocol for Iterator {
|
|||
Ok(slf.into())
|
||||
}
|
||||
|
||||
fn __next__(mut slf: &mut PyClassShell<Self>) -> PyResult<Option<i32>> {
|
||||
fn __next__(slf: &mut PyClassShell<Self>) -> PyResult<Option<i32>> {
|
||||
Ok(slf.iter.next())
|
||||
}
|
||||
}
|
||||
|
|
|
@ -3,7 +3,7 @@ use pyo3::class::PyTraverseError;
|
|||
use pyo3::class::PyVisit;
|
||||
use pyo3::prelude::*;
|
||||
use pyo3::types::{PyAny, PyTuple};
|
||||
use pyo3::{ffi, py_run, AsPyPointer, PyClassShell};
|
||||
use pyo3::{ffi, py_run, AsPyPointer, PyClassInitializer, PyClassShell};
|
||||
use std::cell::RefCell;
|
||||
use std::sync::atomic::{AtomicBool, Ordering};
|
||||
use std::sync::Arc;
|
||||
|
@ -235,10 +235,11 @@ struct SubClassWithDrop {
|
|||
|
||||
#[pymethods]
|
||||
impl SubClassWithDrop {
|
||||
// TODO(kngwyu): Implement baseclass initialization
|
||||
#[new]
|
||||
fn new() -> SubClassWithDrop {
|
||||
SubClassWithDrop { data: None }
|
||||
fn new() -> PyClassInitializer<Self> {
|
||||
let mut init = PyClassInitializer::from_value(SubClassWithDrop { data: None });
|
||||
init.get_super().init(BaseClassWithDrop { data: None });
|
||||
init
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -1,7 +1,6 @@
|
|||
use pyo3::prelude::*;
|
||||
use pyo3::py_run;
|
||||
use pyo3::types::{IntoPyDict, PyList};
|
||||
use std::isize;
|
||||
|
||||
mod common;
|
||||
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
use pyo3::prelude::*;
|
||||
use pyo3::py_run;
|
||||
|
||||
#[cfg(feature = "unsound-subclass")]
|
||||
use pyo3::types::IntoPyDict;
|
||||
|
||||
|
@ -48,19 +49,58 @@ struct SubClass {
|
|||
#[pymethods]
|
||||
impl SubClass {
|
||||
#[new]
|
||||
fn new() -> Self {
|
||||
SubClass { val2: 5 }
|
||||
fn new() -> PyClassInitializer<Self> {
|
||||
let mut init = PyClassInitializer::from_value(SubClass { val2: 5 });
|
||||
init.get_super().init(BaseClass { val1: 10 });
|
||||
init
|
||||
}
|
||||
}
|
||||
|
||||
// TODO(kngwyu): disable untill super().__init__ fixed
|
||||
#[test]
|
||||
#[ignore]
|
||||
fn inheritance_with_new_methods() {
|
||||
let gil = Python::acquire_gil();
|
||||
let py = gil.python();
|
||||
let _typebase = py.get_type::<BaseClass>();
|
||||
let _baseobj = py.get_type::<BaseClass>();
|
||||
let typeobj = py.get_type::<SubClass>();
|
||||
let inst = typeobj.call((), None).unwrap();
|
||||
py_run!(py, inst, "assert inst.val1 == 10; assert inst.val2 == 5");
|
||||
}
|
||||
|
||||
#[pyclass(extends=BaseClass)]
|
||||
struct InvalidSubClass {
|
||||
#[pyo3(get)]
|
||||
val2: usize,
|
||||
}
|
||||
|
||||
#[pymethods]
|
||||
impl InvalidSubClass {
|
||||
#[new]
|
||||
fn new() -> PyClassInitializer<Self> {
|
||||
PyClassInitializer::from_value(InvalidSubClass { val2: 5 })
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn uninit_baseclass_raise_exception() {
|
||||
let gil = Python::acquire_gil();
|
||||
let py = gil.python();
|
||||
let _baseclass = py.get_type::<BaseClass>();
|
||||
let subclass = py.get_type::<InvalidSubClass>();
|
||||
py_expect_exception!(py, subclass, "subclass()", RuntimeError);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn uninit_baseclass_returns_err() {
|
||||
let gil = Python::acquire_gil();
|
||||
let py = gil.python();
|
||||
let subclass = pyo3::pyclass::PyClassShell::new_ref(py, InvalidSubClass { val2: 5 });
|
||||
if let Err(err) = subclass {
|
||||
py_run!(
|
||||
py,
|
||||
err,
|
||||
r#"str(err) == "Base class 'BaseClass' is not initialized""#
|
||||
)
|
||||
} else {
|
||||
panic!("Uninitialized class detection failed!!!")
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue