Skip to content

Commit

Permalink
Introduce PyClassInitializer
Browse files Browse the repository at this point in the history
  • Loading branch information
kngwyu committed Dec 15, 2019
1 parent a663907 commit b86de93
Show file tree
Hide file tree
Showing 11 changed files with 217 additions and 41 deletions.
24 changes: 18 additions & 6 deletions pyo3-derive-backend/src/pymethod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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)]
Expand All @@ -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),
}
Expand Down Expand Up @@ -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)
};
};
}
Expand Down Expand Up @@ -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(
Expand Down
4 changes: 1 addition & 3 deletions src/derive_utils.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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()`.
Expand Down
9 changes: 5 additions & 4 deletions src/instance.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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};
Expand Down Expand Up @@ -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)
}
Expand Down
6 changes: 3 additions & 3 deletions src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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};

Expand Down Expand Up @@ -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 {
Expand All @@ -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
Expand Down
1 change: 1 addition & 0 deletions src/prelude.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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 changes: 130 additions & 11 deletions src/pyclass.rs
Original file line number Diff line number Diff line change
@@ -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;
Expand Down Expand Up @@ -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,
Expand All @@ -85,49 +86,65 @@ 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_)
}
}

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);
self.dict.clear_dict(py);
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> {
Expand Down Expand Up @@ -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>
Expand Down
11 changes: 8 additions & 3 deletions src/type_object.rs
Original file line number Diff line number Diff line change
Expand Up @@ -12,17 +12,22 @@ 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)
}
unsafe fn internal_mut_cast(obj: &PyAny) -> &mut T {
&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 {
Expand Down Expand Up @@ -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>;
Expand Down
2 changes: 1 addition & 1 deletion tests/test_dunder.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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())
}
}
Expand Down
9 changes: 5 additions & 4 deletions tests/test_gc.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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
}
}

Expand Down
Loading

0 comments on commit b86de93

Please sign in to comment.