From 4b5fa7e977076f2211bba57b520fb51355897e45 Mon Sep 17 00:00:00 2001 From: kngwyu Date: Sat, 7 Dec 2019 17:56:49 +0900 Subject: [PATCH 01/22] Introduce PyClass trait and PyClassShell --- src/class/iter.rs | 13 +- src/class/macros.rs | 6 +- src/conversion.rs | 12 +- src/ffi/mod.rs | 2 +- src/freelist.rs | 35 +-- src/instance.rs | 314 +----------------------- src/lib.rs | 11 +- src/object.rs | 16 +- src/prelude.rs | 4 +- src/pyclass.rs | 366 ++++++++++++++++++++++++++++ src/python.rs | 15 +- src/type_object.rs | 474 ++---------------------------------- src/types/any.rs | 21 +- src/types/mod.rs | 26 +- src/types/module.rs | 10 +- tests/test_pyclass_shell.rs | 26 ++ 16 files changed, 482 insertions(+), 869 deletions(-) mode change 100644 => 100755 src/ffi/mod.rs create mode 100644 src/pyclass.rs create mode 100644 tests/test_pyclass_shell.rs diff --git a/src/class/iter.rs b/src/class/iter.rs index a528289dd67..92fe028bb04 100644 --- a/src/class/iter.rs +++ b/src/class/iter.rs @@ -4,11 +4,8 @@ use crate::callback::{CallbackConverter, PyObjectCallbackConverter}; use crate::err::PyResult; -use crate::instance::PyRefMut; -use crate::type_object::PyTypeInfo; -use crate::IntoPyPointer; -use crate::Python; -use crate::{ffi, IntoPy, PyObject}; +use crate::{ffi, IntoPy, PyClass, PyClassShell, PyObject}; +use crate::{IntoPyPointer, Python}; use std::ptr; /// Python Iterator Interface. @@ -16,15 +13,15 @@ use std::ptr; /// more information /// `https://docs.python.org/3/c-api/typeobj.html#c.PyTypeObject.tp_iter` #[allow(unused_variables)] -pub trait PyIterProtocol<'p>: PyTypeInfo + Sized { - fn __iter__(slf: PyRefMut<'p, Self>) -> Self::Result +pub trait PyIterProtocol<'p>: PyClass { + fn __iter__(slf: &mut PyClassShell) -> Self::Result where Self: PyIterIterProtocol<'p>, { unimplemented!() } - fn __next__(slf: PyRefMut<'p, Self>) -> Self::Result + fn __next__(slf: &mut PyClassShell) -> Self::Result where Self: PyIterNextProtocol<'p>, { diff --git a/src/class/macros.rs b/src/class/macros.rs index b849aad5604..f188d177273 100644 --- a/src/class/macros.rs +++ b/src/class/macros.rs @@ -35,11 +35,11 @@ macro_rules! py_unary_pyref_func { where T: for<'p> $trait<'p>, { - use $crate::instance::PyRefMut; + use $crate::{FromPyPointer, PyClassShell}; let py = $crate::Python::assume_gil_acquired(); let _pool = $crate::GILPool::new(py); - let slf = py.mut_from_borrowed_ptr::(slf); - let res = $class::$f(PyRefMut::from_mut(slf)).into(); + let slf: &mut PyClassShell = FromPyPointer::from_borrowed_ptr_or_panic(py, slf); + let res = $class::$f(slf).into(); $crate::callback::cb_convert($conv, py, res) } Some(wrap::<$class>) diff --git a/src/conversion.rs b/src/conversion.rs index 38f436de496..8b71294d506 100644 --- a/src/conversion.rs +++ b/src/conversion.rs @@ -393,22 +393,14 @@ where #[inline] unsafe fn try_from_unchecked>(value: V) -> &'v T { let value = value.into(); - let ptr = if T::OFFSET == 0 { - value as *const _ as *const u8 as *const T - } else { - (value.as_ptr() as *const u8).offset(T::OFFSET) as *const T - }; + let ptr = value as *const _ as *const u8 as *const T; &*ptr } #[inline] unsafe fn try_from_mut_unchecked>(value: V) -> &'v mut T { let value = value.into(); - let ptr = if T::OFFSET == 0 { - value as *const _ as *mut u8 as *mut T - } else { - (value.as_ptr() as *mut u8).offset(T::OFFSET) as *mut T - }; + let ptr = value as *const _ as *mut u8 as *mut T; &mut *ptr } } diff --git a/src/ffi/mod.rs b/src/ffi/mod.rs old mode 100644 new mode 100755 index 7fa6d1b5f0d..19d3bb86f8f --- a/src/ffi/mod.rs +++ b/src/ffi/mod.rs @@ -1,6 +1,6 @@ #![allow(non_camel_case_types, non_snake_case, non_upper_case_globals)] #![cfg_attr(Py_LIMITED_API, allow(unused_imports))] -#![cfg_attr(feature="cargo-clippy", allow(clippy::inline_always))] +#![cfg_attr(feature = "cargo-clippy", allow(clippy::inline_always))] pub use self::bltinmodule::*; pub use self::boolobject::*; diff --git a/src/freelist.rs b/src/freelist.rs index ec0c17781e0..6fb0c28733c 100644 --- a/src/freelist.rs +++ b/src/freelist.rs @@ -3,7 +3,8 @@ //! Free allocation list use crate::ffi; -use crate::type_object::{pytype_drop, PyObjectAlloc, PyTypeInfo}; +use crate::pyclass::{tp_free_fallback, PyClassAlloc}; +use crate::type_object::{PyConcreteObject, PyTypeInfo}; use crate::Python; use std::mem; use std::os::raw::c_void; @@ -11,7 +12,7 @@ use std::os::raw::c_void; /// Implementing this trait for custom class adds free allocation list to class. /// The performance improvement applies to types that are often created and deleted in a row, /// so that they can benefit from a freelist. -pub trait PyObjectWithFreeList: PyTypeInfo { +pub trait PyObjectWithFreeList { fn get_free_list() -> &'static mut FreeList<*mut ffi::PyObject>; } @@ -67,22 +68,23 @@ impl FreeList { } } -impl PyObjectAlloc for T +impl PyClassAlloc for T where - T: PyObjectWithFreeList, + T: PyTypeInfo + PyObjectWithFreeList, { - unsafe fn alloc(_py: Python) -> *mut ffi::PyObject { + unsafe fn alloc(_py: Python) -> *mut Self::ConcreteLayout { if let Some(obj) = ::get_free_list().pop() { ffi::PyObject_Init(obj, ::type_object()); - obj + obj as _ } else { - ffi::PyType_GenericAlloc(::type_object(), 0) + ffi::PyType_GenericAlloc(::type_object(), 0) as _ } } - unsafe fn dealloc(py: Python, obj: *mut ffi::PyObject) { - pytype_drop::(py, obj); + unsafe fn dealloc(py: Python, self_: *mut Self::ConcreteLayout) { + (*self_).py_drop(py); + let obj = self_ as _; if ffi::PyObject_CallFinalizerFromDealloc(obj) < 0 { return; } @@ -90,20 +92,7 @@ where if let Some(obj) = ::get_free_list().insert(obj) { match Self::type_object().tp_free { Some(free) => free(obj as *mut c_void), - None => { - let ty = ffi::Py_TYPE(obj); - if ffi::PyType_IS_GC(ty) != 0 { - ffi::PyObject_GC_Del(obj as *mut c_void); - } else { - ffi::PyObject_Free(obj as *mut c_void); - } - - // For heap types, PyType_GenericAlloc calls INCREF on the type objects, - // so we need to call DECREF here: - if ffi::PyType_HasFeature(ty, ffi::Py_TPFLAGS_HEAPTYPE) != 0 { - ffi::Py_DECREF(ty as *mut ffi::PyObject); - } - } + None => tp_free_fallback(obj), } } } diff --git a/src/instance.rs b/src/instance.rs index 100710279bb..5b7b436f837 100644 --- a/src/instance.rs +++ b/src/instance.rs @@ -2,17 +2,15 @@ use crate::err::{PyErr, PyResult}; use crate::gil; use crate::instance; -use crate::internal_tricks::Unsendable; use crate::object::PyObject; use crate::objectprotocol::ObjectProtocol; -use crate::type_object::PyTypeCreate; -use crate::type_object::{PyTypeInfo, PyTypeObject}; +use crate::pyclass::{PyClass, PyClassShell}; +use crate::type_object::PyTypeInfo; use crate::types::PyAny; use crate::{ffi, IntoPy}; -use crate::{AsPyPointer, FromPyObject, FromPyPointer, IntoPyPointer, Python, ToPyObject}; +use crate::{AsPyPointer, FromPyObject, IntoPyPointer, Python, ToPyObject}; use std::marker::PhantomData; use std::mem; -use std::ops::{Deref, DerefMut}; use std::ptr::NonNull; /// Types that are built into the python interpreter. @@ -25,243 +23,6 @@ pub unsafe trait PyNativeType: Sized { } } -/// A special reference of type `T`. `PyRef` refers a instance of T, which exists in the Python -/// heap as a part of a Python object. -/// -/// We can't implement `AsPyPointer` or `ToPyObject` for `pyclass`es, because they're not Python -/// objects until copied to the Python heap. So, instead of treating `&pyclass`es as Python objects, -/// we need to use special reference types `PyRef` and `PyRefMut`. -/// -/// # Example -/// -/// ``` -/// use pyo3::prelude::*; -/// use pyo3::types::IntoPyDict; -/// #[pyclass] -/// struct Point { -/// x: i32, -/// y: i32, -/// } -/// #[pymethods] -/// impl Point { -/// fn length(&self) -> i32 { -/// self.x * self.y -/// } -/// } -/// let gil = Python::acquire_gil(); -/// let py = gil.python(); -/// let obj = PyRef::new(gil.python(), Point { x: 3, y: 4 }).unwrap(); -/// let d = [("p", obj)].into_py_dict(py); -/// py.run("assert p.length() == 12", None, Some(d)).unwrap(); -/// ``` -#[derive(Debug)] -pub struct PyRef<'a, T: PyTypeInfo>(&'a T, Unsendable); - -#[allow(clippy::cast_ptr_alignment)] -fn ref_to_ptr(t: &T) -> *mut ffi::PyObject -where - T: PyTypeInfo, -{ - unsafe { (t as *const _ as *mut u8).offset(-T::OFFSET) as *mut _ } -} - -impl<'a, T: PyTypeInfo> PyRef<'a, T> { - pub(crate) fn from_ref(r: &'a T) -> Self { - PyRef(r, Unsendable::default()) - } -} - -impl<'p, T> PyRef<'p, T> -where - T: PyTypeInfo + PyTypeObject + PyTypeCreate, -{ - pub fn new(py: Python<'p>, value: T) -> PyResult> { - let obj = T::create(py)?; - obj.init(value); - unsafe { Self::from_owned_ptr_or_err(py, obj.into_ptr()) } - } -} - -impl<'a, T: PyTypeInfo> AsPyPointer for PyRef<'a, T> { - fn as_ptr(&self) -> *mut ffi::PyObject { - ref_to_ptr(self.0) - } -} - -impl<'a, T: PyTypeInfo> ToPyObject for PyRef<'a, T> { - fn to_object(&self, py: Python) -> PyObject { - unsafe { PyObject::from_borrowed_ptr(py, self.as_ptr()) } - } -} - -impl<'a, T: PyTypeInfo> IntoPy for PyRef<'a, T> { - fn into_py(self, py: Python) -> PyObject { - self.to_object(py) - } -} - -impl<'a, T: PyTypeInfo> Deref for PyRef<'a, T> { - type Target = T; - fn deref(&self) -> &T { - self.0 - } -} - -unsafe impl<'p, T> FromPyPointer<'p> for PyRef<'p, T> -where - T: PyTypeInfo, -{ - unsafe fn from_owned_ptr_or_opt(py: Python<'p>, ptr: *mut ffi::PyObject) -> Option { - FromPyPointer::from_owned_ptr_or_opt(py, ptr).map(Self::from_ref) - } - unsafe fn from_borrowed_ptr_or_opt(py: Python<'p>, ptr: *mut ffi::PyObject) -> Option { - FromPyPointer::from_borrowed_ptr_or_opt(py, ptr).map(Self::from_ref) - } -} - -/// Mutable version of [`PyRef`](struct.PyRef.html). -/// # Example -/// ``` -/// use pyo3::prelude::*; -/// use pyo3::types::IntoPyDict; -/// #[pyclass] -/// struct Point { -/// x: i32, -/// y: i32, -/// } -/// #[pymethods] -/// impl Point { -/// fn length(&self) -> i32 { -/// self.x * self.y -/// } -/// } -/// let gil = Python::acquire_gil(); -/// let py = gil.python(); -/// let mut obj = PyRefMut::new(gil.python(), Point { x: 3, y: 4 }).unwrap(); -/// let d = vec![("p", obj.to_object(py))].into_py_dict(py); -/// obj.x = 5; obj.y = 20; -/// py.run("assert p.length() == 100", None, Some(d)).unwrap(); -/// ``` -#[derive(Debug)] -pub struct PyRefMut<'a, T: PyTypeInfo>(&'a mut T, Unsendable); - -impl<'a, T: PyTypeInfo> PyRefMut<'a, T> { - pub(crate) fn from_mut(t: &'a mut T) -> Self { - PyRefMut(t, Unsendable::default()) - } -} - -impl<'p, T> PyRefMut<'p, T> -where - T: PyTypeInfo + PyTypeObject + PyTypeCreate, -{ - pub fn new(py: Python<'p>, value: T) -> PyResult> { - let obj = T::create(py)?; - obj.init(value); - unsafe { Self::from_owned_ptr_or_err(py, obj.into_ptr()) } - } -} - -impl<'a, T: PyTypeInfo> AsPyPointer for PyRefMut<'a, T> { - fn as_ptr(&self) -> *mut ffi::PyObject { - ref_to_ptr(self.0) - } -} - -impl<'a, T: PyTypeInfo> ToPyObject for PyRefMut<'a, T> { - fn to_object(&self, py: Python) -> PyObject { - unsafe { PyObject::from_borrowed_ptr(py, self.as_ptr()) } - } -} - -impl<'a, T: PyTypeInfo> IntoPy for PyRefMut<'a, T> { - fn into_py(self, py: Python) -> PyObject { - self.to_object(py) - } -} - -impl<'a, T: PyTypeInfo> Deref for PyRefMut<'a, T> { - type Target = T; - fn deref(&self) -> &T { - self.0 - } -} - -impl<'a, T: PyTypeInfo> DerefMut for PyRefMut<'a, T> { - fn deref_mut(&mut self) -> &mut T { - self.0 - } -} - -unsafe impl<'p, T> FromPyPointer<'p> for PyRefMut<'p, T> -where - T: PyTypeInfo, -{ - unsafe fn from_owned_ptr_or_opt(py: Python<'p>, ptr: *mut ffi::PyObject) -> Option { - FromPyPointer::from_owned_ptr_or_opt(py, ptr).map(Self::from_mut) - } - unsafe fn from_borrowed_ptr_or_opt(py: Python<'p>, ptr: *mut ffi::PyObject) -> Option { - FromPyPointer::from_borrowed_ptr_or_opt(py, ptr).map(Self::from_mut) - } -} - -/// Trait implements object reference extraction from python managed pointer. -pub trait AsPyRef: Sized { - /// Return reference to object. - fn as_ref(&self, py: Python) -> PyRef; - - /// Return mutable reference to object. - fn as_mut(&mut self, py: Python) -> PyRefMut; - - /// Acquire python gil and call closure with object reference. - fn with(&self, f: F) -> R - where - F: FnOnce(Python, PyRef) -> R, - { - let gil = Python::acquire_gil(); - let py = gil.python(); - - f(py, self.as_ref(py)) - } - - /// Acquire python gil and call closure with mutable object reference. - fn with_mut(&mut self, f: F) -> R - where - F: FnOnce(Python, PyRefMut) -> R, - { - let gil = Python::acquire_gil(); - let py = gil.python(); - - f(py, self.as_mut(py)) - } - - fn into_py(self, f: F) -> R - where - Self: IntoPyPointer, - F: FnOnce(Python, PyRef) -> R, - { - let gil = Python::acquire_gil(); - let py = gil.python(); - - let result = f(py, self.as_ref(py)); - py.xdecref(self); - result - } - - fn into_mut_py(mut self, f: F) -> R - where - Self: IntoPyPointer, - F: FnOnce(Python, PyRefMut) -> R, - { - let gil = Python::acquire_gil(); - let py = gil.python(); - - let result = f(py, self.as_mut(py)); - py.xdecref(self); - result - } -} - /// Safe wrapper around unsafe `*mut ffi::PyObject` pointer with specified type information. /// /// `Py` is thread-safe, because any python related operations require a Python<'p> token. @@ -277,12 +38,10 @@ impl Py { /// Create new instance of T and move it under python management pub fn new(py: Python, value: T) -> PyResult> where - T: PyTypeCreate, + T: PyClass, { - let ob = T::create(py)?; - ob.init(value); - - let ob = unsafe { Py::from_owned_ptr(ob.into_ptr()) }; + let obj = unsafe { PyClassShell::::new(py, value) }; + let ob = unsafe { Py::from_owned_ptr(obj as _) }; Ok(ob) } @@ -357,48 +116,15 @@ impl Py { } } -/// Specialization workaround -trait AsPyRefDispatch: AsPyPointer { - fn as_ref_dispatch(&self, _py: Python) -> &T; - fn as_mut_dispatch(&mut self, _py: Python) -> &mut T; -} - -impl AsPyRefDispatch for Py { - default fn as_ref_dispatch(&self, _py: Python) -> &T { - unsafe { - let ptr = (self.as_ptr() as *mut u8).offset(T::OFFSET) as *mut T; - ptr.as_ref().expect("Py has a null pointer") - } - } - default fn as_mut_dispatch(&mut self, _py: Python) -> &mut T { - unsafe { - let ptr = (self.as_ptr() as *mut u8).offset(T::OFFSET) as *mut T; - ptr.as_mut().expect("Py has a null pointer") - } - } +pub trait AsPyRef: Sized { + /// Return reference to object. + fn as_ref(&self, py: Python) -> &T; } -impl AsPyRefDispatch for Py { - fn as_ref_dispatch(&self, _py: Python) -> &T { +impl AsPyRef for Py { + fn as_ref(&self, _py: Python) -> &T { unsafe { &*(self as *const instance::Py as *const T) } } - fn as_mut_dispatch(&mut self, _py: Python) -> &mut T { - unsafe { &mut *(self as *mut _ as *mut T) } - } -} - -impl AsPyRef for Py -where - T: PyTypeInfo, -{ - #[inline] - fn as_ref(&self, py: Python) -> PyRef { - PyRef::from_ref(self.as_ref_dispatch(py)) - } - #[inline] - fn as_mut(&mut self, py: Python) -> PyRefMut { - PyRefMut::from_mut(self.as_mut_dispatch(py)) - } } impl ToPyObject for Py { @@ -459,24 +185,6 @@ impl std::convert::From> for PyObject { } } -impl<'a, T> std::convert::From> for Py -where - T: PyTypeInfo, -{ - fn from(ob: PyRef<'a, T>) -> Self { - unsafe { Py::from_borrowed_ptr(ob.as_ptr()) } - } -} - -impl<'a, T> std::convert::From> for Py -where - T: PyTypeInfo, -{ - fn from(ob: PyRefMut<'a, T>) -> Self { - unsafe { Py::from_borrowed_ptr(ob.as_ptr()) } - } -} - impl<'a, T> std::convert::From<&'a T> for PyObject where T: AsPyPointer, diff --git a/src/lib.rs b/src/lib.rs index d25831630d7..293b645699a 100755 --- a/src/lib.rs +++ b/src/lib.rs @@ -124,11 +124,12 @@ pub use crate::conversion::{ }; pub use crate::err::{PyDowncastError, PyErr, PyErrArguments, PyErrValue, PyResult}; pub use crate::gil::{init_once, GILGuard, GILPool}; -pub use crate::instance::{AsPyRef, ManagedPyRef, Py, PyNativeType, PyRef, PyRefMut}; +pub use crate::instance::{ManagedPyRef, Py, PyNativeType}; pub use crate::object::PyObject; pub use crate::objectprotocol::ObjectProtocol; +pub use crate::pyclass::{PyClass, PyClassAlloc, PyClassShell}; pub use crate::python::{prepare_freethreaded_python, Python}; -pub use crate::type_object::{PyObjectAlloc, PyRawObject, PyTypeInfo}; +pub use crate::type_object::{PyConcreteObject, PyTypeInfo}; // Re-exported for wrap_function #[doc(hidden)] @@ -146,9 +147,6 @@ pub use libc; #[doc(hidden)] pub use unindent; -/// Raw ffi declarations for the c interface of python -pub mod ffi; - pub mod buffer; #[doc(hidden)] pub mod callback; @@ -158,6 +156,8 @@ mod conversion; pub mod derive_utils; mod err; pub mod exceptions; +/// Raw ffi declarations for the c interface of python +pub mod ffi; pub mod freelist; mod gil; mod instance; @@ -166,6 +166,7 @@ pub mod marshal; mod object; mod objectprotocol; pub mod prelude; +pub mod pyclass; mod python; pub mod type_object; pub mod types; diff --git a/src/object.rs b/src/object.rs index 128ef8377bc..731ec08802f 100644 --- a/src/object.rs +++ b/src/object.rs @@ -3,12 +3,11 @@ use crate::err::{PyDowncastError, PyErr, PyResult}; use crate::ffi; use crate::gil; -use crate::instance::{AsPyRef, PyNativeType, PyRef, PyRefMut}; +use crate::instance::{AsPyRef, PyNativeType}; use crate::types::{PyAny, PyDict, PyTuple}; -use crate::AsPyPointer; -use crate::Py; -use crate::Python; +use crate::{AsPyPointer, Py, Python}; use crate::{FromPyObject, IntoPy, IntoPyPointer, PyTryFrom, ToBorrowedObject, ToPyObject}; +use std::convert::AsRef; use std::ptr::NonNull; /// A python object @@ -253,13 +252,8 @@ impl PyObject { } impl AsPyRef for PyObject { - #[inline] - fn as_ref(&self, _py: Python) -> PyRef { - unsafe { PyRef::from_ref(&*(self as *const _ as *const PyAny)) } - } - #[inline] - fn as_mut(&mut self, _py: Python) -> PyRefMut { - unsafe { PyRefMut::from_mut(&mut *(self as *mut _ as *mut PyAny)) } + fn as_ref(&self, _py: Python) -> &PyAny { + unsafe { &*(self as *const _ as *const PyAny) } } } diff --git a/src/prelude.rs b/src/prelude.rs index 5e927446069..821bc93976e 100644 --- a/src/prelude.rs +++ b/src/prelude.rs @@ -12,14 +12,12 @@ pub use crate::err::{PyErr, PyResult}; pub use crate::gil::GILGuard; -pub use crate::instance::{AsPyRef, Py, PyRef, PyRefMut}; +pub use crate::instance::Py; pub use crate::object::PyObject; 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::types::PyModule; -// This is required for the constructor -pub use crate::PyRawObject; pub use pyo3cls::pymodule; pub use pyo3cls::{pyclass, pyfunction, pymethods, pyproto}; diff --git a/src/pyclass.rs b/src/pyclass.rs new file mode 100644 index 00000000000..56d8c70877b --- /dev/null +++ b/src/pyclass.rs @@ -0,0 +1,366 @@ +//! An experiment module which has all codes related only to #[pyclass] +use crate::class::methods::{PyMethodDefType, PyMethodsProtocol}; +use crate::conversion::FromPyPointer; +use crate::type_object::{PyConcreteObject, PyTypeObject}; +use crate::{class, ffi, gil, PyErr, PyResult, PyTypeInfo, Python}; +use std::ffi::CString; +use std::mem::ManuallyDrop; +use std::os::raw::c_void; +use std::ptr::{self, NonNull}; + +/// A trait that enables custome alloc/dealloc implementations for pyclasses. +pub trait PyClassAlloc: PyTypeInfo + Sized { + unsafe fn alloc(_py: Python) -> *mut Self::ConcreteLayout { + let tp_ptr = Self::type_object(); + let alloc = (*tp_ptr).tp_alloc.unwrap_or(ffi::PyType_GenericAlloc); + alloc(tp_ptr, 0) as _ + } + + unsafe fn dealloc(py: Python, self_: *mut Self::ConcreteLayout) { + (*self_).py_drop(py); + let obj = self_ as _; + if ffi::PyObject_CallFinalizerFromDealloc(obj) < 0 { + return; + } + + match Self::type_object().tp_free { + Some(free) => free(obj as *mut c_void), + None => tp_free_fallback(obj), + } + } +} + +#[doc(hidden)] +pub unsafe fn tp_free_fallback(obj: *mut ffi::PyObject) { + let ty = ffi::Py_TYPE(obj); + if ffi::PyType_IS_GC(ty) != 0 { + ffi::PyObject_GC_Del(obj as *mut c_void); + } else { + ffi::PyObject_Free(obj as *mut c_void); + } + + // For heap types, PyType_GenericAlloc calls INCREF on the type objects, + // so we need to call DECREF here: + if ffi::PyType_HasFeature(ty, ffi::Py_TPFLAGS_HEAPTYPE) != 0 { + ffi::Py_DECREF(ty as *mut ffi::PyObject); + } +} + +pub trait PyClass: PyTypeInfo + Sized + PyClassAlloc + PyMethodsProtocol {} + +unsafe impl PyTypeObject for T +where + T: PyClass, +{ + fn init_type() -> NonNull { + let type_object = unsafe { ::type_object() }; + + if (type_object.tp_flags & ffi::Py_TPFLAGS_READY) == 0 { + // automatically initialize the class on-demand + let gil = Python::acquire_gil(); + let py = gil.python(); + + initialize_type::(py, ::MODULE).unwrap_or_else(|e| { + e.print(py); + panic!("An error occurred while initializing class {}", Self::NAME) + }); + } + + unsafe { NonNull::new_unchecked(type_object) } + } +} + +/// So this is a shell for our *sweet* pyclasses to survive in *harsh* Python world. +#[repr(C)] +pub struct PyClassShell { + ob_base: ::ConcreteLayout, + pyclass: ManuallyDrop, +} + +impl PyClassShell { + pub unsafe fn new(py: Python, value: T) -> *mut Self { + T::init_type(); + let base = T::alloc(py); + let self_ = base as *mut Self; + (*self_).pyclass = ManuallyDrop::new(value); + self_ + } +} + +impl PyConcreteObject for PyClassShell { + unsafe fn py_drop(&mut self, py: Python) { + ManuallyDrop::drop(&mut self.pyclass); + self.ob_base.py_drop(py); + } +} + +impl std::ops::Deref for PyClassShell { + type Target = T; + fn deref(&self) -> &T { + self.pyclass.deref() + } +} + +impl std::ops::DerefMut for PyClassShell { + fn deref_mut(&mut self) -> &mut T { + self.pyclass.deref_mut() + } +} + +unsafe impl<'p, T> FromPyPointer<'p> for &'p PyClassShell +where + T: PyClass, +{ + unsafe fn from_owned_ptr_or_opt(py: Python<'p>, ptr: *mut ffi::PyObject) -> Option { + NonNull::new(ptr).map(|p| &**(gil::register_owned(py, p) as *const _ as *const Self)) + } + unsafe fn from_borrowed_ptr_or_opt(py: Python<'p>, ptr: *mut ffi::PyObject) -> Option { + NonNull::new(ptr).map(|p| &**(gil::register_borrowed(py, p) as *const _ as *const Self)) + } +} + +unsafe impl<'p, T> FromPyPointer<'p> for &'p mut PyClassShell +where + T: PyClass, +{ + unsafe fn from_owned_ptr_or_opt(py: Python<'p>, ptr: *mut ffi::PyObject) -> Option { + NonNull::new(ptr).map(|p| &mut **(gil::register_owned(py, p) as *const _ as *mut Self)) + } + unsafe fn from_borrowed_ptr_or_opt(py: Python<'p>, ptr: *mut ffi::PyObject) -> Option { + NonNull::new(ptr).map(|p| &mut **(gil::register_borrowed(py, p) as *const _ as *mut Self)) + } +} + +/// type object supports python GC +const PY_TYPE_FLAG_GC: usize = 1; + +/// Type object supports python weak references +const PY_TYPE_FLAG_WEAKREF: usize = 1 << 1; + +/// Type object can be used as the base type of another type +const PY_TYPE_FLAG_BASETYPE: usize = 1 << 2; + +/// The instances of this type have a dictionary containing instance variables +const PY_TYPE_FLAG_DICT: usize = 1 << 3; + +/// Register new type in python object system. +#[cfg(not(Py_LIMITED_API))] +pub fn initialize_type(py: Python, module_name: Option<&str>) -> PyResult<*mut ffi::PyTypeObject> +where + T: PyClass, +{ + let type_object: &mut ffi::PyTypeObject = unsafe { T::type_object() }; + let base_type_object: &mut ffi::PyTypeObject = + unsafe { ::type_object() }; + + // PyPy will segfault if passed only a nul terminator as `tp_doc`. + // ptr::null() is OK though. + if T::DESCRIPTION == "\0" { + type_object.tp_doc = ptr::null(); + } else { + type_object.tp_doc = T::DESCRIPTION.as_ptr() as *const _; + }; + + type_object.tp_base = base_type_object; + + let name = match module_name { + Some(module_name) => format!("{}.{}", module_name, T::NAME), + None => T::NAME.to_string(), + }; + let name = CString::new(name).expect("Module name/type name must not contain NUL byte"); + type_object.tp_name = name.into_raw(); + + // dealloc + unsafe extern "C" fn tp_dealloc_callback(obj: *mut ffi::PyObject) + where + T: PyClassAlloc, + { + let py = Python::assume_gil_acquired(); + let _pool = gil::GILPool::new_no_pointers(py); + ::dealloc(py, (obj as *mut T::ConcreteLayout) as _) + } + type_object.tp_dealloc = Some(tp_dealloc_callback::); + + // type size + type_object.tp_basicsize = std::mem::size_of::() as isize; + + // weakref support (check py3cls::py_class::impl_class) + if T::FLAGS & PY_TYPE_FLAG_WEAKREF != 0 { + // STUB + type_object.tp_weaklistoffset = 0isize; + } + + // __dict__ support + let has_dict = T::FLAGS & PY_TYPE_FLAG_DICT != 0; + if has_dict { + // STUB + type_object.tp_dictoffset = 0isize; + } + + // GC support + ::update_type_object(type_object); + + // descriptor protocol + ::tp_as_descr(type_object); + + // iterator methods + ::tp_as_iter(type_object); + + // basic methods + ::tp_as_object(type_object); + + fn to_ptr(value: Option) -> *mut T { + value + .map(|v| Box::into_raw(Box::new(v))) + .unwrap_or_else(ptr::null_mut) + } + + // number methods + type_object.tp_as_number = to_ptr(::tp_as_number()); + // mapping methods + type_object.tp_as_mapping = + to_ptr(::tp_as_mapping()); + // sequence methods + type_object.tp_as_sequence = + to_ptr(::tp_as_sequence()); + // async methods + type_object.tp_as_async = to_ptr(::tp_as_async()); + // buffer protocol + type_object.tp_as_buffer = to_ptr(::tp_as_buffer()); + + // normal methods + let (new, call, mut methods) = py_class_method_defs::(); + if !methods.is_empty() { + methods.push(ffi::PyMethodDef_INIT); + type_object.tp_methods = Box::into_raw(methods.into_boxed_slice()) as *mut _; + } + + // __new__ method + type_object.tp_new = new; + // __call__ method + type_object.tp_call = call; + + // properties + let mut props = py_class_properties::(); + + if has_dict { + props.push(ffi::PyGetSetDef_DICT); + } + if !props.is_empty() { + props.push(ffi::PyGetSetDef_INIT); + type_object.tp_getset = Box::into_raw(props.into_boxed_slice()) as *mut _; + } + + // set type flags + py_class_flags::(type_object); + + // register type object + unsafe { + if ffi::PyType_Ready(type_object) == 0 { + Ok(type_object as *mut ffi::PyTypeObject) + } else { + PyErr::fetch(py).into() + } + } +} + +fn py_class_flags(type_object: &mut ffi::PyTypeObject) { + if type_object.tp_traverse != None + || type_object.tp_clear != None + || T::FLAGS & PY_TYPE_FLAG_GC != 0 + { + type_object.tp_flags = ffi::Py_TPFLAGS_DEFAULT | ffi::Py_TPFLAGS_HAVE_GC; + } else { + type_object.tp_flags = ffi::Py_TPFLAGS_DEFAULT; + } + if T::FLAGS & PY_TYPE_FLAG_BASETYPE != 0 { + type_object.tp_flags |= ffi::Py_TPFLAGS_BASETYPE; + } +} + +fn py_class_method_defs() -> ( + Option, + Option, + Vec, +) { + let mut defs = Vec::new(); + let mut call = None; + let mut new = None; + + for def in T::py_methods() { + match *def { + PyMethodDefType::New(ref def) => { + if let class::methods::PyMethodType::PyNewFunc(meth) = def.ml_meth { + new = Some(meth) + } + } + PyMethodDefType::Call(ref def) => { + if let class::methods::PyMethodType::PyCFunctionWithKeywords(meth) = def.ml_meth { + call = Some(meth) + } else { + panic!("Method type is not supoorted by tp_call slot") + } + } + PyMethodDefType::Method(ref def) + | PyMethodDefType::Class(ref def) + | PyMethodDefType::Static(ref def) => { + defs.push(def.as_method_def()); + } + _ => (), + } + } + + for def in ::methods() { + defs.push(def.as_method_def()); + } + for def in ::methods() { + defs.push(def.as_method_def()); + } + for def in ::methods() { + defs.push(def.as_method_def()); + } + for def in ::methods() { + defs.push(def.as_method_def()); + } + for def in ::methods() { + defs.push(def.as_method_def()); + } + + py_class_async_methods::(&mut defs); + + (new, call, defs) +} + +fn py_class_async_methods(defs: &mut Vec) { + for def in ::methods() { + defs.push(def.as_method_def()); + } +} + +fn py_class_properties() -> Vec { + let mut defs = std::collections::HashMap::new(); + + for def in T::py_methods() { + match *def { + PyMethodDefType::Getter(ref getter) => { + let name = getter.name.to_string(); + if !defs.contains_key(&name) { + let _ = defs.insert(name.clone(), ffi::PyGetSetDef_INIT); + } + let def = defs.get_mut(&name).expect("Failed to call get_mut"); + getter.copy_to(def); + } + PyMethodDefType::Setter(ref setter) => { + let name = setter.name.to_string(); + if !defs.contains_key(&name) { + let _ = defs.insert(name.clone(), ffi::PyGetSetDef_INIT); + } + let def = defs.get_mut(&name).expect("Failed to call get_mut"); + setter.copy_to(def); + } + _ => (), + } + } + + defs.values().cloned().collect() +} diff --git a/src/python.rs b/src/python.rs index f9164f2519b..873b4ef37d2 100644 --- a/src/python.rs +++ b/src/python.rs @@ -272,23 +272,14 @@ impl<'p> Python<'p> { } impl<'p> Python<'p> { + // TODO(kngwyu): Now offset dies, so what should this functions do pub(crate) unsafe fn unchecked_downcast(self, ob: &PyAny) -> &'p T { - if T::OFFSET == 0 { - &*(ob as *const _ as *const T) - } else { - let ptr = (ob.as_ptr() as *mut u8).offset(T::OFFSET) as *mut T; - &*ptr - } + &*(ob as *const _ as *const T) } #[allow(clippy::cast_ref_to_mut)] // FIXME pub(crate) unsafe fn unchecked_mut_downcast(self, ob: &PyAny) -> &'p mut T { - if T::OFFSET == 0 { - &mut *(ob as *const _ as *mut T) - } else { - let ptr = (ob.as_ptr() as *mut u8).offset(T::OFFSET) as *mut T; - &mut *ptr - } + &mut *(ob as *const _ as *mut T) } /// Register object in release pool, and try to downcast to specific type. diff --git a/src/type_object.rs b/src/type_object.rs index 1c1a493e94f..b5af7b18fb5 100644 --- a/src/type_object.rs +++ b/src/type_object.rs @@ -2,20 +2,25 @@ //! Python type object information -use crate::class::methods::PyMethodDefType; -use crate::err::{PyErr, PyResult}; -use crate::instance::{Py, PyNativeType}; +use crate::ffi; +use crate::instance::Py; use crate::types::PyAny; use crate::types::PyType; use crate::AsPyPointer; -use crate::IntoPyPointer; use crate::Python; -use crate::{class, ffi, gil}; -use class::methods::PyMethodsProtocol; -use std::collections::HashMap; -use std::ffi::CString; -use std::os::raw::c_void; -use std::ptr::{self, NonNull}; +use std::ptr::NonNull; + +pub trait PyConcreteObject: Sized { + unsafe fn py_drop(&mut self, _py: Python) {} +} + +impl AsPyPointer for T { + fn as_ptr(&self) -> *mut ffi::PyObject { + (self as *const _) as _ + } +} + +impl PyConcreteObject for ffi::PyObject {} /// Python type information. pub trait PyTypeInfo { @@ -31,9 +36,6 @@ pub trait PyTypeInfo { /// Class doc string const DESCRIPTION: &'static str = "\0"; - /// Size of the rust PyObject structure (PyObject + rust structure) - const SIZE: usize; - /// `Type` instance offset inside PyObject structure const OFFSET: isize; @@ -43,6 +45,9 @@ pub trait PyTypeInfo { /// Base class type BaseType: PyTypeInfo; + /// Layout + type ConcreteLayout: PyConcreteObject; + /// PyTypeObject instance for this type, which might still need to /// be initialized unsafe fn type_object() -> &'static mut ffi::PyTypeObject; @@ -58,180 +63,6 @@ pub trait PyTypeInfo { } } -/// type object supports python GC -pub const PY_TYPE_FLAG_GC: usize = 1; - -/// Type object supports python weak references -pub const PY_TYPE_FLAG_WEAKREF: usize = 1 << 1; - -/// Type object can be used as the base type of another type -pub const PY_TYPE_FLAG_BASETYPE: usize = 1 << 2; - -/// The instances of this type have a dictionary containing instance variables -pub const PY_TYPE_FLAG_DICT: usize = 1 << 3; - -/// Special object that is used for python object creation. -/// `pyo3` library automatically creates this object for class `__new__` method. -/// Behavior is undefined if constructor of custom class does not initialze -/// instance of `PyRawObject` with rust value with `init` method. -/// Calling of `__new__` method of base class is developer's responsibility. -/// -/// Example of custom class implementation with `__new__` method: -/// ``` -/// use pyo3::prelude::*; -/// -/// #[pyclass] -/// struct MyClass { } -/// -/// #[pymethods] -/// impl MyClass { -/// #[new] -/// fn new(obj: &PyRawObject) { -/// obj.init(MyClass { }) -/// } -/// } -/// ``` -#[allow(dead_code)] -pub struct PyRawObject { - ptr: *mut ffi::PyObject, - /// Type object of class which __new__ method get called - tp_ptr: *mut ffi::PyTypeObject, - /// Type object of top most class in inheritance chain, - /// it might be python class. - curr_ptr: *mut ffi::PyTypeObject, - // initialized: usize, -} - -impl PyRawObject { - #[must_use] - pub unsafe fn new( - py: Python, - tp_ptr: *mut ffi::PyTypeObject, - curr_ptr: *mut ffi::PyTypeObject, - ) -> PyResult { - let alloc = (*curr_ptr).tp_alloc.unwrap_or(ffi::PyType_GenericAlloc); - let ptr = alloc(curr_ptr, 0); - - if !ptr.is_null() { - Ok(PyRawObject { - ptr, - tp_ptr, - curr_ptr, - // initialized: 0, - }) - } else { - PyErr::fetch(py).into() - } - } - - #[must_use] - pub unsafe fn new_with_ptr( - py: Python, - ptr: *mut ffi::PyObject, - tp_ptr: *mut ffi::PyTypeObject, - curr_ptr: *mut ffi::PyTypeObject, - ) -> PyResult { - if !ptr.is_null() { - Ok(PyRawObject { - ptr, - tp_ptr, - curr_ptr, - // initialized: 0, - }) - } else { - PyErr::fetch(py).into() - } - } - - pub fn init(&self, value: T) { - unsafe { - // The `as *mut u8` part is required because the offset is in bytes - let ptr = (self.ptr as *mut u8).offset(T::OFFSET) as *mut T; - std::ptr::write(ptr, value); - } - } - - /// Type object - pub fn type_object(&self) -> &PyType { - unsafe { PyType::from_type_ptr(self.py(), self.curr_ptr) } - } -} - -impl AsRef for PyRawObject { - #[inline] - fn as_ref(&self) -> &T { - // TODO: check is object initialized - unsafe { - let ptr = (self.ptr as *mut u8).offset(T::OFFSET) as *mut T; - ptr.as_ref().unwrap() - } - } -} - -impl IntoPyPointer for PyRawObject { - fn into_ptr(self) -> *mut ffi::PyObject { - // TODO: panic if not all types initialized - self.ptr - } -} - -unsafe impl PyNativeType for PyRawObject {} - -pub(crate) unsafe fn pytype_drop(py: Python, obj: *mut ffi::PyObject) { - if T::OFFSET != 0 { - let ptr = (obj as *mut u8).offset(T::OFFSET) as *mut T; - std::ptr::drop_in_place(ptr); - pytype_drop::(py, obj); - } -} - -/// A Python object allocator that is usable as a base type for `#[pyclass]` -/// -/// All native types and all `#[pyclass]` types use the default functions, while -/// [PyObjectWithFreeList](crate::freelist::PyObjectWithFreeList) gets a special version. -pub trait PyObjectAlloc: PyTypeInfo + Sized { - unsafe fn alloc(_py: Python) -> *mut ffi::PyObject { - let tp_ptr = Self::type_object(); - let alloc = (*tp_ptr).tp_alloc.unwrap_or(ffi::PyType_GenericAlloc); - alloc(tp_ptr, 0) - } - - /// Calls the rust destructor for the object and frees the memory - /// (usually by calling ptr->ob_type->tp_free). - /// This function is used as tp_dealloc implementation. - unsafe fn dealloc(py: Python, obj: *mut ffi::PyObject) { - Self::drop(py, obj); - - if ffi::PyObject_CallFinalizerFromDealloc(obj) < 0 { - return; - } - - match Self::type_object().tp_free { - Some(free) => free(obj as *mut c_void), - None => { - let ty = ffi::Py_TYPE(obj); - if ffi::PyType_IS_GC(ty) != 0 { - ffi::PyObject_GC_Del(obj as *mut c_void); - } else { - ffi::PyObject_Free(obj as *mut c_void); - } - - // For heap types, PyType_GenericAlloc calls INCREF on the type objects, - // so we need to call DECREF here: - if ffi::PyType_HasFeature(ty, ffi::Py_TPFLAGS_HEAPTYPE) != 0 { - ffi::Py_DECREF(ty as *mut ffi::PyObject); - } - } - } - } - - #[allow(unconditional_recursion)] - /// Calls the rust destructor for the object. - unsafe fn drop(py: Python, obj: *mut ffi::PyObject) { - pytype_drop::(py, obj); - } -} - /// Python object types that have a corresponding type object. /// /// This trait is marked unsafe because not fulfilling the contract for [PyTypeObject::init_type] @@ -246,272 +77,3 @@ pub unsafe trait PyTypeObject { unsafe { Py::from_borrowed_ptr(Self::init_type().as_ptr() as *mut ffi::PyObject) } } } - -unsafe impl PyTypeObject for T -where - T: PyTypeInfo + PyMethodsProtocol + PyObjectAlloc, -{ - fn init_type() -> NonNull { - let type_object = unsafe { ::type_object() }; - - if (type_object.tp_flags & ffi::Py_TPFLAGS_READY) == 0 { - // automatically initialize the class on-demand - let gil = Python::acquire_gil(); - let py = gil.python(); - - initialize_type::(py, ::MODULE).unwrap_or_else(|e| { - e.print(py); - panic!("An error occurred while initializing class {}", Self::NAME) - }); - } - - unsafe { NonNull::new_unchecked(type_object) } - } -} - -/// Python object types that can be instanciated with [Self::create()] -/// -/// We can't just make this a part of [PyTypeObject] because exceptions have -/// no PyTypeInfo -pub trait PyTypeCreate: PyObjectAlloc + PyTypeObject + Sized { - /// Create PyRawObject which can be initialized with rust value - #[must_use] - fn create(py: Python) -> PyResult { - Self::init_type(); - - unsafe { - let ptr = Self::alloc(py); - PyRawObject::new_with_ptr( - py, - ptr, - ::type_object(), - ::type_object(), - ) - } - } -} - -impl PyTypeCreate for T where T: PyObjectAlloc + PyTypeObject + Sized {} - -/// Register new type in python object system. -#[cfg(not(Py_LIMITED_API))] -pub fn initialize_type(py: Python, module_name: Option<&str>) -> PyResult<*mut ffi::PyTypeObject> -where - T: PyObjectAlloc + PyTypeInfo + PyMethodsProtocol, -{ - let type_object: &mut ffi::PyTypeObject = unsafe { T::type_object() }; - let base_type_object: &mut ffi::PyTypeObject = - unsafe { ::type_object() }; - - // PyPy will segfault if passed only a nul terminator as `tp_doc`. - // ptr::null() is OK though. - if T::DESCRIPTION == "\0" { - type_object.tp_doc = ptr::null(); - } else { - type_object.tp_doc = T::DESCRIPTION.as_ptr() as *const _; - }; - - type_object.tp_base = base_type_object; - - let name = match module_name { - Some(module_name) => format!("{}.{}", module_name, T::NAME), - None => T::NAME.to_string(), - }; - let name = CString::new(name).expect("Module name/type name must not contain NUL byte"); - type_object.tp_name = name.into_raw(); - - // dealloc - type_object.tp_dealloc = Some(tp_dealloc_callback::); - - // type size - type_object.tp_basicsize = ::SIZE as ffi::Py_ssize_t; - - let mut offset = T::SIZE; - // weakref support (check py3cls::py_class::impl_class) - if T::FLAGS & PY_TYPE_FLAG_WEAKREF != 0 { - offset -= std::mem::size_of::<*const ffi::PyObject>(); - type_object.tp_weaklistoffset = offset as isize; - } - - // __dict__ support - let has_dict = T::FLAGS & PY_TYPE_FLAG_DICT != 0; - if has_dict { - offset -= std::mem::size_of::<*const ffi::PyObject>(); - type_object.tp_dictoffset = offset as isize; - } - - // GC support - ::update_type_object(type_object); - - // descriptor protocol - ::tp_as_descr(type_object); - - // iterator methods - ::tp_as_iter(type_object); - - // basic methods - ::tp_as_object(type_object); - - fn to_ptr(value: Option) -> *mut T { - value - .map(|v| Box::into_raw(Box::new(v))) - .unwrap_or_else(ptr::null_mut) - } - - // number methods - type_object.tp_as_number = to_ptr(::tp_as_number()); - // mapping methods - type_object.tp_as_mapping = - to_ptr(::tp_as_mapping()); - // sequence methods - type_object.tp_as_sequence = - to_ptr(::tp_as_sequence()); - // async methods - type_object.tp_as_async = to_ptr(::tp_as_async()); - // buffer protocol - type_object.tp_as_buffer = to_ptr(::tp_as_buffer()); - - // normal methods - let (new, call, mut methods) = py_class_method_defs::(); - if !methods.is_empty() { - methods.push(ffi::PyMethodDef_INIT); - type_object.tp_methods = Box::into_raw(methods.into_boxed_slice()) as *mut _; - } - - // __new__ method - type_object.tp_new = new; - // __call__ method - type_object.tp_call = call; - - // properties - let mut props = py_class_properties::(); - - if has_dict { - props.push(ffi::PyGetSetDef_DICT); - } - if !props.is_empty() { - props.push(ffi::PyGetSetDef_INIT); - type_object.tp_getset = Box::into_raw(props.into_boxed_slice()) as *mut _; - } - - // set type flags - py_class_flags::(type_object); - - // register type object - unsafe { - if ffi::PyType_Ready(type_object) == 0 { - Ok(type_object as *mut ffi::PyTypeObject) - } else { - PyErr::fetch(py).into() - } - } -} - -unsafe extern "C" fn tp_dealloc_callback(obj: *mut ffi::PyObject) -where - T: PyObjectAlloc, -{ - let py = Python::assume_gil_acquired(); - let _pool = gil::GILPool::new_no_pointers(py); - ::dealloc(py, obj) -} -fn py_class_flags(type_object: &mut ffi::PyTypeObject) { - if type_object.tp_traverse != None - || type_object.tp_clear != None - || T::FLAGS & PY_TYPE_FLAG_GC != 0 - { - type_object.tp_flags = ffi::Py_TPFLAGS_DEFAULT | ffi::Py_TPFLAGS_HAVE_GC; - } else { - type_object.tp_flags = ffi::Py_TPFLAGS_DEFAULT; - } - if T::FLAGS & PY_TYPE_FLAG_BASETYPE != 0 { - type_object.tp_flags |= ffi::Py_TPFLAGS_BASETYPE; - } -} - -fn py_class_method_defs() -> ( - Option, - Option, - Vec, -) { - let mut defs = Vec::new(); - let mut call = None; - let mut new = None; - - for def in T::py_methods() { - match *def { - PyMethodDefType::New(ref def) => { - if let class::methods::PyMethodType::PyNewFunc(meth) = def.ml_meth { - new = Some(meth) - } - } - PyMethodDefType::Call(ref def) => { - if let class::methods::PyMethodType::PyCFunctionWithKeywords(meth) = def.ml_meth { - call = Some(meth) - } else { - panic!("Method type is not supoorted by tp_call slot") - } - } - PyMethodDefType::Method(ref def) - | PyMethodDefType::Class(ref def) - | PyMethodDefType::Static(ref def) => { - defs.push(def.as_method_def()); - } - _ => (), - } - } - - for def in ::methods() { - defs.push(def.as_method_def()); - } - for def in ::methods() { - defs.push(def.as_method_def()); - } - for def in ::methods() { - defs.push(def.as_method_def()); - } - for def in ::methods() { - defs.push(def.as_method_def()); - } - for def in ::methods() { - defs.push(def.as_method_def()); - } - - py_class_async_methods::(&mut defs); - - (new, call, defs) -} - -fn py_class_async_methods(defs: &mut Vec) { - for def in ::methods() { - defs.push(def.as_method_def()); - } -} - -fn py_class_properties() -> Vec { - let mut defs = HashMap::new(); - - for def in T::py_methods() { - match *def { - PyMethodDefType::Getter(ref getter) => { - let name = getter.name.to_string(); - if !defs.contains_key(&name) { - let _ = defs.insert(name.clone(), ffi::PyGetSetDef_INIT); - } - let def = defs.get_mut(&name).expect("Failed to call get_mut"); - getter.copy_to(def); - } - PyMethodDefType::Setter(ref setter) => { - let name = setter.name.to_string(); - if !defs.contains_key(&name) { - let _ = defs.insert(name.clone(), ffi::PyGetSetDef_INIT); - } - let def = defs.get_mut(&name).expect("Failed to call get_mut"); - setter.copy_to(def); - } - _ => (), - } - } - - defs.values().cloned().collect() -} diff --git a/src/types/any.rs b/src/types/any.rs index dbbc5cf0647..aa962f6689a 100644 --- a/src/types/any.rs +++ b/src/types/any.rs @@ -1,7 +1,8 @@ use crate::conversion::AsPyPointer; +use crate::conversion::PyTryFrom; use crate::err::PyDowncastError; use crate::internal_tricks::Unsendable; -use crate::{ffi, PyObject, PyRef, PyRefMut, PyTryFrom, PyTypeInfo}; +use crate::{ffi, PyObject}; /// Represents a python's [Any](https://docs.python.org/3/library/typing.html#typing.Any) type. /// We can convert all python objects as `PyAny`. @@ -41,21 +42,3 @@ impl PyAny { T::try_from_mut(self) } } - -impl<'a, T> From> for &'a PyAny -where - T: PyTypeInfo, -{ - fn from(pref: PyRef<'a, T>) -> &'a PyAny { - unsafe { &*(pref.as_ptr() as *const PyAny) } - } -} - -impl<'a, T> From> for &'a PyAny -where - T: PyTypeInfo, -{ - fn from(pref: PyRefMut<'a, T>) -> &'a PyAny { - unsafe { &*(pref.as_ptr() as *const PyAny) } - } -} diff --git a/src/types/mod.rs b/src/types/mod.rs index a73bb1888d7..6f13c2b5f54 100644 --- a/src/types/mod.rs +++ b/src/types/mod.rs @@ -82,7 +82,7 @@ macro_rules! pyobject_native_type_named ( macro_rules! pyobject_native_type ( ($name: ty, $typeobject: expr, $module: expr, $checkfunction: path $(,$type_param: ident)*) => { pyobject_native_type_named!($name $(,$type_param)*); - pyobject_native_type_convert!($name, $typeobject, $module, $checkfunction $(,$type_param)*); + pyobject_native_type_convert!($name, ffi::PyObject, $typeobject, $module, $checkfunction $(,$type_param)*); impl<'a, $($type_param,)*> ::std::convert::From<&'a $name> for &'a $crate::types::PyAny { fn from(ob: &'a $name) -> Self { @@ -91,20 +91,23 @@ macro_rules! pyobject_native_type ( } }; ($name: ty, $typeobject: expr, $checkfunction: path $(,$type_param: ident)*) => { - pyobject_native_type!{$name, $typeobject, Some("builtins"), $checkfunction $(,$type_param)*} + pyobject_native_type! { + $name, $typeobject, Some("builtins"), $checkfunction $(,$type_param)* + } }; ); #[macro_export] macro_rules! pyobject_native_type_convert( - ($name: ty, $typeobject: expr, $module: expr, $checkfunction: path $(,$type_param: ident)*) => { + ($name: ty, $layout: ty, $typeobject: expr, + $module: expr, $checkfunction: path $(,$type_param: ident)*) => { impl<$($type_param,)*> $crate::type_object::PyTypeInfo for $name { type Type = (); type BaseType = $crate::types::PyAny; + type ConcreteLayout = $layout; const NAME: &'static str = stringify!($name); const MODULE: Option<&'static str> = $module; - const SIZE: usize = ::std::mem::size_of::<$crate::ffi::PyObject>(); const OFFSET: isize = 0; #[inline] @@ -120,12 +123,12 @@ macro_rules! pyobject_native_type_convert( } } - impl<$($type_param,)*> $crate::type_object::PyObjectAlloc for $name {} - unsafe impl<$($type_param,)*> $crate::type_object::PyTypeObject for $name { fn init_type() -> std::ptr::NonNull<$crate::ffi::PyTypeObject> { unsafe { - std::ptr::NonNull::new_unchecked(::type_object() as *mut _) + std::ptr::NonNull::new_unchecked( + ::type_object() as *mut _ + ) } } } @@ -160,8 +163,15 @@ macro_rules! pyobject_native_type_convert( } }; ($name: ty, $typeobject: expr, $checkfunction: path $(,$type_param: ident)*) => { - pyobject_native_type_convert!{$name, $typeobject, Some("builtins"), $checkfunction $(,$type_param)*} + pyobject_native_type_convert! { + $name, ffi::PyObject, $typeobject, Some("builtins"), $checkfunction $(,$type_param)* + } }; + // ($name: ty, $layout: ty, $typeobject: expr, $checkfunction: path $(,$type_param: ident)*) => { + // pyobject_native_type_convert! { + // $name, $layout, $typeobject, Some("builtins"), $checkfunction $(,$type_param)* + // } + // }; ); mod any; diff --git a/src/types/module.rs b/src/types/module.rs index fa5babe3747..40aec2b02bb 100644 --- a/src/types/module.rs +++ b/src/types/module.rs @@ -9,15 +9,11 @@ use crate::instance::PyNativeType; use crate::internal_tricks::Unsendable; use crate::object::PyObject; use crate::objectprotocol::ObjectProtocol; -use crate::type_object::PyTypeCreate; +use crate::pyclass::PyClass; use crate::type_object::PyTypeObject; use crate::types::PyTuple; use crate::types::{PyAny, PyDict, PyList}; -use crate::AsPyPointer; -use crate::IntoPy; -use crate::Py; -use crate::Python; -use crate::ToPyObject; +use crate::{AsPyPointer, IntoPy, Py, Python, ToPyObject}; use std::ffi::{CStr, CString}; use std::os::raw::c_char; use std::str; @@ -173,7 +169,7 @@ impl PyModule { /// and adds the type to this module. pub fn add_class(&self) -> PyResult<()> where - T: PyTypeCreate, + T: PyClass, { self.add(T::NAME, ::type_object()) } diff --git a/tests/test_pyclass_shell.rs b/tests/test_pyclass_shell.rs new file mode 100644 index 00000000000..df9c1ce3619 --- /dev/null +++ b/tests/test_pyclass_shell.rs @@ -0,0 +1,26 @@ +use pyo3::prelude::*; +use pyo3::py_run; +use pyo3::pyclass::PyClassShell; +use pyo3::types::PyAny; +use pyo3::FromPyPointer; + +#[pyclass] +struct Class { + member: i32, +} + +#[pymethods] +impl Class { + fn hello(&self) -> i32 { + self.member + } +} + +#[test] +fn test_shell() { + let class = Class { member: 128 }; + let gil = Python::acquire_gil(); + let py = gil.python(); + // let obj: &PyAny = unsafe { FromPyPointer::from_owned_ptr(py, PyClassShell::new(py, class)) }; + // py_run!(py, obj, "assert obj.hello() == 128"); +} From bdb66afe0a8ca5d212449a9f0a87bbd00d3fccfe Mon Sep 17 00:00:00 2001 From: kngwyu Date: Sun, 8 Dec 2019 17:18:25 +0900 Subject: [PATCH 02/22] Make PyClassShell have dict&weakref --- pyo3-derive-backend/src/pyclass.rs | 46 +++++++--------- pyo3-derive-backend/src/pymethod.rs | 27 +++------- src/class/iter.rs | 2 +- src/class/macros.rs | 2 +- src/err.rs | 7 +++ src/freelist.rs | 8 +-- src/instance.rs | 7 ++- src/internal_tricks.rs | 19 +++++++ src/lib.rs | 8 +-- src/object.rs | 1 - src/pyclass.rs | 84 ++++++++++++++++++----------- src/pyclass_slots.rs | 72 +++++++++++++++++++++++++ src/type_object.rs | 19 +++++-- src/types/any.rs | 1 - src/types/mod.rs | 1 - 15 files changed, 212 insertions(+), 92 deletions(-) create mode 100644 src/pyclass_slots.rs diff --git a/pyo3-derive-backend/src/pyclass.rs b/pyo3-derive-backend/src/pyclass.rs index b4a8d6fe346..76336aef018 100644 --- a/pyo3-derive-backend/src/pyclass.rs +++ b/pyo3-derive-backend/src/pyclass.rs @@ -126,10 +126,10 @@ impl PyClassArgs { let flag = exp.path.segments.first().unwrap().ident.to_string(); let path = match flag.as_str() { "gc" => { - parse_quote! {pyo3::type_object::PY_TYPE_FLAG_GC} + parse_quote! {pyo3::type_flags::GC} } "weakref" => { - parse_quote! {pyo3::type_object::PY_TYPE_FLAG_WEAKREF} + parse_quote! {pyo3::type_flags::WEAKREF} } "subclass" => { if cfg!(not(feature = "unsound-subclass")) { @@ -138,10 +138,10 @@ impl PyClassArgs { "You need to activate the `unsound-subclass` feature if you want to use subclassing", )); } - parse_quote! {pyo3::type_object::PY_TYPE_FLAG_BASETYPE} + parse_quote! {pyo3::type_flags::BASETYPE} } "dict" => { - parse_quote! {pyo3::type_object::PY_TYPE_FLAG_DICT} + parse_quote! {pyo3::type_flags::DICT} } _ => { return Err(syn::Error::new_spanned( @@ -267,7 +267,7 @@ fn impl_class( let extra = { if let Some(freelist) = &attr.freelist { quote! { - impl pyo3::freelist::PyObjectWithFreeList for #cls { + impl pyo3::freelist::PyClassWithFreeList for #cls { #[inline] fn get_free_list() -> &'static mut pyo3::freelist::FreeList<*mut pyo3::ffi::PyObject> { static mut FREELIST: *mut pyo3::freelist::FreeList<*mut pyo3::ffi::PyObject> = 0 as *mut _; @@ -285,7 +285,7 @@ fn impl_class( } } else { quote! { - impl pyo3::type_object::PyObjectAlloc for #cls {} + impl pyo3::pyclass::PyClassAlloc for #cls {} } } }; @@ -308,24 +308,25 @@ fn impl_class( let mut has_gc = false; for f in attr.flags.iter() { if let syn::Expr::Path(ref epath) = f { - if epath.path == parse_quote! {pyo3::type_object::PY_TYPE_FLAG_WEAKREF} { + if epath.path == parse_quote! { pyo3::type_flags::WEAKREF } { has_weakref = true; - } else if epath.path == parse_quote! {pyo3::type_object::PY_TYPE_FLAG_DICT} { + } else if epath.path == parse_quote! { pyo3::type_flags::DICT } { has_dict = true; - } else if epath.path == parse_quote! {pyo3::type_object::PY_TYPE_FLAG_GC} { + } else if epath.path == parse_quote! { pyo3::type_flags::GC } { has_gc = true; } } } + // TODO: implement dict and weakref let weakref = if has_weakref { - quote! {std::mem::size_of::<*const pyo3::ffi::PyObject>()} + quote! { type WeakRef = pyo3::pyclass_slots::PyClassWeakRefSlot; } } else { - quote! {0} + quote! { type WeakRef = pyo3::pyclass_slots::PyClassDummySlot; } }; let dict = if has_dict { - quote! {std::mem::size_of::<*const pyo3::ffi::PyObject>()} + quote! { type Dict = pyo3::pyclass_slots::PyClassDictSlot; } } else { - quote! {0} + quote! { type Dict = pyo3::pyclass_slots::PyClassDummySlot; } }; let module = if let Some(m) = &attr.module { quote! { Some(#m) } @@ -358,25 +359,13 @@ fn impl_class( impl pyo3::type_object::PyTypeInfo for #cls { type Type = #cls; type BaseType = #base; + type ConcreteLayout = pyo3::pyclass::PyClassShell; const NAME: &'static str = #cls_name; const MODULE: Option<&'static str> = #module; const DESCRIPTION: &'static str = #doc; const FLAGS: usize = #(#flags)|*; - const SIZE: usize = { - Self::OFFSET as usize + - ::std::mem::size_of::<#cls>() + #weakref + #dict - }; - const OFFSET: isize = { - // round base_size up to next multiple of align - ( - (<#base as pyo3::type_object::PyTypeInfo>::SIZE + - ::std::mem::align_of::<#cls>() - 1) / - ::std::mem::align_of::<#cls>() * ::std::mem::align_of::<#cls>() - ) as isize - }; - #[inline] unsafe fn type_object() -> &'static mut pyo3::ffi::PyTypeObject { static mut TYPE_OBJECT: pyo3::ffi::PyTypeObject = pyo3::ffi::PyTypeObject_INIT; @@ -384,6 +373,11 @@ fn impl_class( } } + impl pyo3::PyClass for #cls { + #dict + #weakref + } + impl pyo3::IntoPy for #cls { fn into_py(self, py: pyo3::Python) -> pyo3::PyObject { pyo3::IntoPy::into_py(pyo3::Py::new(py, self).unwrap(), py) diff --git a/pyo3-derive-backend/src/pymethod.rs b/pyo3-derive-backend/src/pymethod.rs index ba32667c606..4d01f99c9c0 100644 --- a/pyo3-derive-backend/src/pymethod.rs +++ b/pyo3-derive-backend/src/pymethod.rs @@ -237,25 +237,14 @@ pub fn impl_wrap_new(cls: &syn::Type, name: &syn::Ident, spec: &FnSpec<'_>) -> T const _LOCATION: &'static str = concat!(stringify!(#cls),".",stringify!(#name),"()"); let _py = pyo3::Python::assume_gil_acquired(); let _pool = pyo3::GILPool::new(_py); - match pyo3::type_object::PyRawObject::new(_py, #cls::type_object(), _cls) { - Ok(_obj) => { - let _args = _py.from_borrowed_ptr::(_args); - let _kwargs: Option<&pyo3::types::PyDict> = _py.from_borrowed_ptr_or_opt(_kwargs); - - #body - - match _result { - Ok(_) => pyo3::IntoPyPointer::into_ptr(_obj), - Err(e) => { - e.restore(_py); - ::std::ptr::null_mut() - } - } - } - Err(e) => { - e.restore(_py); - ::std::ptr::null_mut() - } + let _args = _py.from_borrowed_ptr::(_args); + let _kwargs: Option<&pyo3::types::PyDict> = _py.from_borrowed_ptr_or_opt(_kwargs); + + #body + + match <<#cls as pyo3::PyTypeInfo>::ConcreteLayout as pyo3::pyclass::PyClassNew>::new(_py, _result) { + Ok(_slf) => _slf as _, + Err(e) => e.restore_and_null(), } } } diff --git a/src/class/iter.rs b/src/class/iter.rs index 92fe028bb04..802bd159e40 100644 --- a/src/class/iter.rs +++ b/src/class/iter.rs @@ -4,7 +4,7 @@ use crate::callback::{CallbackConverter, PyObjectCallbackConverter}; use crate::err::PyResult; -use crate::{ffi, IntoPy, PyClass, PyClassShell, PyObject}; +use crate::{ffi, pyclass::PyClassShell, IntoPy, PyClass, PyObject}; use crate::{IntoPyPointer, Python}; use std::ptr; diff --git a/src/class/macros.rs b/src/class/macros.rs index f188d177273..fc86b62f9e5 100644 --- a/src/class/macros.rs +++ b/src/class/macros.rs @@ -35,7 +35,7 @@ macro_rules! py_unary_pyref_func { where T: for<'p> $trait<'p>, { - use $crate::{FromPyPointer, PyClassShell}; + use $crate::{pyclass::PyClassShell, FromPyPointer}; let py = $crate::Python::assume_gil_acquired(); let _pool = $crate::GILPool::new(py); let slf: &mut PyClassShell = FromPyPointer::from_borrowed_ptr_or_panic(py, slf); diff --git a/src/err.rs b/src/err.rs index 3f653e36d41..3868ebd809c 100644 --- a/src/err.rs +++ b/src/err.rs @@ -326,6 +326,13 @@ impl PyErr { unsafe { ffi::PyErr_Restore(ptype.into_ptr(), pvalue, ptraceback.into_ptr()) } } + #[doc(hidden)] + /// Utility method for proc-macro code + pub fn restore_and_null(self, py: Python) -> *mut T { + self.restore(py); + std::ptr::null_mut() + } + /// Issue a warning message. /// May return a PyErr if warnings-as-errors is enabled. pub fn warn(py: Python, category: &PyAny, message: &str, stacklevel: i32) -> PyResult<()> { diff --git a/src/freelist.rs b/src/freelist.rs index 6fb0c28733c..113088029f2 100644 --- a/src/freelist.rs +++ b/src/freelist.rs @@ -12,7 +12,7 @@ use std::os::raw::c_void; /// Implementing this trait for custom class adds free allocation list to class. /// The performance improvement applies to types that are often created and deleted in a row, /// so that they can benefit from a freelist. -pub trait PyObjectWithFreeList { +pub trait PyClassWithFreeList { fn get_free_list() -> &'static mut FreeList<*mut ffi::PyObject>; } @@ -70,10 +70,10 @@ impl FreeList { impl PyClassAlloc for T where - T: PyTypeInfo + PyObjectWithFreeList, + T: PyTypeInfo + PyClassWithFreeList, { unsafe fn alloc(_py: Python) -> *mut Self::ConcreteLayout { - if let Some(obj) = ::get_free_list().pop() { + if let Some(obj) = ::get_free_list().pop() { ffi::PyObject_Init(obj, ::type_object()); obj as _ } else { @@ -89,7 +89,7 @@ where return; } - if let Some(obj) = ::get_free_list().insert(obj) { + if let Some(obj) = ::get_free_list().insert(obj) { match Self::type_object().tp_free { Some(free) => free(obj as *mut c_void), None => tp_free_fallback(obj), diff --git a/src/instance.rs b/src/instance.rs index 5b7b436f837..4fd52a4ceaa 100644 --- a/src/instance.rs +++ b/src/instance.rs @@ -36,11 +36,16 @@ unsafe impl Sync for Py {} impl Py { /// Create new instance of T and move it under python management + /// + /// **NOTE** + /// This method's `where` bound is actually the same as `PyClass`. + /// However, since Rust still doesn't have higher order generics, we cannot represent + /// this bound by `PyClass`. pub fn new(py: Python, value: T) -> PyResult> where T: PyClass, { - let obj = unsafe { PyClassShell::::new(py, value) }; + let obj = unsafe { PyClassShell::new(py, value)? }; let ob = unsafe { Py::from_owned_ptr(obj as _) }; Ok(ob) } diff --git a/src/internal_tricks.rs b/src/internal_tricks.rs index 1cf8a56a68f..1d6a5eb9f27 100644 --- a/src/internal_tricks.rs +++ b/src/internal_tricks.rs @@ -4,3 +4,22 @@ use std::rc::Rc; /// A marker type that makes the type !Send. /// Temporal hack until https://github.com/rust-lang/rust/issues/13231 is resolved. pub(crate) type Unsendable = PhantomData>; + +pub struct PrivateMarker; + +macro_rules! private_decl { + () => { + /// This trait is private to implement; this method exists to make it + /// impossible to implement outside the crate. + fn __private__(&self) -> crate::internal_tricks::PrivateMarker; + } +} + +macro_rules! private_impl { + () => { + #[doc(hidden)] + fn __private__(&self) -> crate::internal_tricks::PrivateMarker { + crate::internal_tricks::PrivateMarker + } + } +} diff --git a/src/lib.rs b/src/lib.rs index 293b645699a..88d62b69df4 100755 --- a/src/lib.rs +++ b/src/lib.rs @@ -124,12 +124,12 @@ pub use crate::conversion::{ }; pub use crate::err::{PyDowncastError, PyErr, PyErrArguments, PyErrValue, PyResult}; pub use crate::gil::{init_once, GILGuard, GILPool}; -pub use crate::instance::{ManagedPyRef, Py, PyNativeType}; +pub use crate::instance::{AsPyRef, ManagedPyRef, Py, PyNativeType}; pub use crate::object::PyObject; pub use crate::objectprotocol::ObjectProtocol; -pub use crate::pyclass::{PyClass, PyClassAlloc, PyClassShell}; +pub use crate::pyclass::{PyClass, PyClassShell}; pub use crate::python::{prepare_freethreaded_python, Python}; -pub use crate::type_object::{PyConcreteObject, PyTypeInfo}; +pub use crate::type_object::{type_flags, PyConcreteObject, PyTypeInfo}; // Re-exported for wrap_function #[doc(hidden)] @@ -161,12 +161,14 @@ pub mod ffi; pub mod freelist; mod gil; mod instance; +#[macro_use] mod internal_tricks; pub mod marshal; mod object; mod objectprotocol; pub mod prelude; pub mod pyclass; +pub mod pyclass_slots; mod python; pub mod type_object; pub mod types; diff --git a/src/object.rs b/src/object.rs index 731ec08802f..d52440dd4df 100644 --- a/src/object.rs +++ b/src/object.rs @@ -7,7 +7,6 @@ use crate::instance::{AsPyRef, PyNativeType}; use crate::types::{PyAny, PyDict, PyTuple}; use crate::{AsPyPointer, Py, Python}; use crate::{FromPyObject, IntoPy, IntoPyPointer, PyTryFrom, ToBorrowedObject, ToPyObject}; -use std::convert::AsRef; use std::ptr::NonNull; /// A python object diff --git a/src/pyclass.rs b/src/pyclass.rs index 56d8c70877b..2b1fd1b6ece 100644 --- a/src/pyclass.rs +++ b/src/pyclass.rs @@ -1,8 +1,9 @@ //! An experiment module which has all codes related only to #[pyclass] use crate::class::methods::{PyMethodDefType, PyMethodsProtocol}; -use crate::conversion::FromPyPointer; -use crate::type_object::{PyConcreteObject, PyTypeObject}; -use crate::{class, ffi, gil, PyErr, PyResult, PyTypeInfo, Python}; +use crate::conversion::{AsPyPointer, FromPyPointer, ToPyObject}; +use crate::pyclass_slots::{PyClassDict, PyClassWeakRef}; +use crate::type_object::{type_flags, PyConcreteObject, PyTypeObject}; +use crate::{class, ffi, gil, PyErr, PyObject, PyResult, PyTypeInfo, Python}; use std::ffi::CString; use std::mem::ManuallyDrop; use std::os::raw::c_void; @@ -46,7 +47,10 @@ pub unsafe fn tp_free_fallback(obj: *mut ffi::PyObject) { } } -pub trait PyClass: PyTypeInfo + Sized + PyClassAlloc + PyMethodsProtocol {} +pub trait PyClass: PyTypeInfo + Sized + PyClassAlloc + PyMethodsProtocol { + type Dict: PyClassDict; + type WeakRef: PyClassWeakRef; +} unsafe impl PyTypeObject for T where @@ -75,21 +79,44 @@ where pub struct PyClassShell { ob_base: ::ConcreteLayout, pyclass: ManuallyDrop, + dict: T::Dict, + weakref: T::WeakRef, } impl PyClassShell { - pub unsafe fn new(py: Python, value: T) -> *mut Self { + pub fn new_ref(py: Python, value: T) -> PyResult<&Self> { + unsafe { + let ptr = Self::new(py, value)?; + FromPyPointer::from_owned_ptr_or_err(py, ptr as _) + } + } + + pub fn new_mut(py: Python, value: T) -> PyResult<&mut Self> { + unsafe { + let ptr = Self::new(py, value)?; + FromPyPointer::from_owned_ptr_or_err(py, ptr as _) + } + } + + pub unsafe fn new(py: Python, value: T) -> PyResult<*mut Self> { 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_ + (*self_).dict = T::Dict::new(); + (*self_).weakref = T::WeakRef::new(); + Ok(self_) } } impl PyConcreteObject for PyClassShell { 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); } } @@ -107,6 +134,12 @@ impl std::ops::DerefMut for PyClassShell { } } +impl ToPyObject for PyClassShell { + fn to_object(&self, py: Python<'_>) -> PyObject { + unsafe { PyObject::from_borrowed_ptr(py, self.as_ptr()) } + } +} + unsafe impl<'p, T> FromPyPointer<'p> for &'p PyClassShell where T: PyClass, @@ -131,18 +164,6 @@ where } } -/// type object supports python GC -const PY_TYPE_FLAG_GC: usize = 1; - -/// Type object supports python weak references -const PY_TYPE_FLAG_WEAKREF: usize = 1 << 1; - -/// Type object can be used as the base type of another type -const PY_TYPE_FLAG_BASETYPE: usize = 1 << 2; - -/// The instances of this type have a dictionary containing instance variables -const PY_TYPE_FLAG_DICT: usize = 1 << 3; - /// Register new type in python object system. #[cfg(not(Py_LIMITED_API))] pub fn initialize_type(py: Python, module_name: Option<&str>) -> PyResult<*mut ffi::PyTypeObject> @@ -182,19 +203,20 @@ where type_object.tp_dealloc = Some(tp_dealloc_callback::); // type size - type_object.tp_basicsize = std::mem::size_of::() as isize; + type_object.tp_basicsize = std::mem::size_of::() as ffi::Py_ssize_t; - // weakref support (check py3cls::py_class::impl_class) - if T::FLAGS & PY_TYPE_FLAG_WEAKREF != 0 { - // STUB - type_object.tp_weaklistoffset = 0isize; - } + let mut offset = type_object.tp_basicsize; // __dict__ support - let has_dict = T::FLAGS & PY_TYPE_FLAG_DICT != 0; - if has_dict { - // STUB - type_object.tp_dictoffset = 0isize; + if let Some(dict_offset) = T::Dict::OFFSET { + offset += dict_offset as ffi::Py_ssize_t; + type_object.tp_dictoffset = offset; + } + + // weakref support + if let Some(weakref_offset) = T::WeakRef::OFFSET { + offset += weakref_offset as ffi::Py_ssize_t; + type_object.tp_weaklistoffset = offset; } // GC support @@ -243,7 +265,7 @@ where // properties let mut props = py_class_properties::(); - if has_dict { + if T::Dict::OFFSET.is_some() { props.push(ffi::PyGetSetDef_DICT); } if !props.is_empty() { @@ -267,13 +289,13 @@ where fn py_class_flags(type_object: &mut ffi::PyTypeObject) { if type_object.tp_traverse != None || type_object.tp_clear != None - || T::FLAGS & PY_TYPE_FLAG_GC != 0 + || T::FLAGS & type_flags::GC != 0 { type_object.tp_flags = ffi::Py_TPFLAGS_DEFAULT | ffi::Py_TPFLAGS_HAVE_GC; } else { type_object.tp_flags = ffi::Py_TPFLAGS_DEFAULT; } - if T::FLAGS & PY_TYPE_FLAG_BASETYPE != 0 { + if T::FLAGS & type_flags::BASETYPE != 0 { type_object.tp_flags |= ffi::Py_TPFLAGS_BASETYPE; } } diff --git a/src/pyclass_slots.rs b/src/pyclass_slots.rs new file mode 100644 index 00000000000..698816bb036 --- /dev/null +++ b/src/pyclass_slots.rs @@ -0,0 +1,72 @@ +//! This module contains additional fields pf pyclass +// TODO(kngwyu): Add vectorcall support +use crate::{ffi, Python}; + +const POINTER_SIZE: isize = std::mem::size_of::<*mut ffi::PyObject>() as _; + +/// Represents `__dict__`. +pub trait PyClassDict { + const OFFSET: Option = None; + fn new() -> Self; + fn clear_dict(&mut self, _py: Python) {} + private_decl! {} +} + +/// Represents `__weakref__`. +pub trait PyClassWeakRef { + const OFFSET: Option = None; + fn new() -> Self; + fn clear_weakrefs(&mut self, _obj: *mut ffi::PyObject, _py: Python) {} + private_decl! {} +} + +/// Dummy slot means the function doesn't has such a feature. +pub struct PyClassDummySlot; + +impl PyClassDict for PyClassDummySlot { + private_impl! {} + fn new() -> Self { + PyClassDummySlot + } +} + +impl PyClassWeakRef for PyClassDummySlot { + private_impl! {} + fn new() -> Self { + PyClassDummySlot + } +} + +/// actural dict field +#[repr(transparent)] +pub struct PyClassDictSlot(*mut ffi::PyObject); + +impl PyClassDict for PyClassDictSlot { + private_impl! {} + const OFFSET: Option = Some(-POINTER_SIZE); + fn new() -> Self { + Self(std::ptr::null_mut()) + } + fn clear_dict(&mut self, _py: Python) { + if self.0 != std::ptr::null_mut() { + unsafe { ffi::PyDict_Clear(self.0) } + } + } +} + +/// actural weakref field +#[repr(transparent)] +pub struct PyClassWeakRefSlot(*mut ffi::PyObject); + +impl PyClassWeakRef for PyClassWeakRefSlot { + private_impl! {} + const OFFSET: Option = Some(-POINTER_SIZE); + fn new() -> Self { + Self(std::ptr::null_mut()) + } + fn clear_weakrefs(&mut self, obj: *mut ffi::PyObject, _py: Python) { + if self.0 != std::ptr::null_mut() { + unsafe { ffi::PyObject_ClearWeakRefs(obj) } + } + } +} diff --git a/src/type_object.rs b/src/type_object.rs index b5af7b18fb5..e7ff7dc5b4c 100644 --- a/src/type_object.rs +++ b/src/type_object.rs @@ -10,6 +10,7 @@ use crate::AsPyPointer; use crate::Python; use std::ptr::NonNull; +/// TODO: write document pub trait PyConcreteObject: Sized { unsafe fn py_drop(&mut self, _py: Python) {} } @@ -22,6 +23,21 @@ impl AsPyPointer for T { impl PyConcreteObject for ffi::PyObject {} +/// Our custom type flags +pub mod type_flags { + /// type object supports python GC + pub const GC: usize = 1; + + /// Type object supports python weak references + pub const WEAKREF: usize = 1 << 1; + + /// Type object can be used as the base type of another type + pub const BASETYPE: usize = 1 << 2; + + /// The instances of this type have a dictionary containing instance variables + pub const DICT: usize = 1 << 3; +} + /// Python type information. pub trait PyTypeInfo { /// Type of objects to store in PyObject struct @@ -36,9 +52,6 @@ pub trait PyTypeInfo { /// Class doc string const DESCRIPTION: &'static str = "\0"; - /// `Type` instance offset inside PyObject structure - const OFFSET: isize; - /// Type flags (ie PY_TYPE_FLAG_GC, PY_TYPE_FLAG_WEAKREF) const FLAGS: usize = 0; diff --git a/src/types/any.rs b/src/types/any.rs index aa962f6689a..b299c6c95cf 100644 --- a/src/types/any.rs +++ b/src/types/any.rs @@ -1,4 +1,3 @@ -use crate::conversion::AsPyPointer; use crate::conversion::PyTryFrom; use crate::err::PyDowncastError; use crate::internal_tricks::Unsendable; diff --git a/src/types/mod.rs b/src/types/mod.rs index 6f13c2b5f54..48eb96cb98d 100644 --- a/src/types/mod.rs +++ b/src/types/mod.rs @@ -108,7 +108,6 @@ macro_rules! pyobject_native_type_convert( const NAME: &'static str = stringify!($name); const MODULE: Option<&'static str> = $module; - const OFFSET: isize = 0; #[inline] unsafe fn type_object() -> &'static mut $crate::ffi::PyTypeObject { From 4d7dfafe2bc21c8c63fe6fcc7da231d3a1d43504 Mon Sep 17 00:00:00 2001 From: kngwyu Date: Sun, 8 Dec 2019 22:47:19 +0900 Subject: [PATCH 03/22] Allow slf: &PyClassShell --- pyo3-derive-backend/src/method.rs | 28 ++++++++++++++++++---------- pyo3-derive-backend/src/pymethod.rs | 17 ++++++++--------- 2 files changed, 26 insertions(+), 19 deletions(-) diff --git a/pyo3-derive-backend/src/method.rs b/pyo3-derive-backend/src/method.rs index f95fe820f25..9efe22d2be2 100644 --- a/pyo3-derive-backend/src/method.rs +++ b/pyo3-derive-backend/src/method.rs @@ -27,7 +27,7 @@ pub enum FnType { FnCall, FnClass, FnStatic, - PySelf(syn::TypePath), + PySelfNew(syn::TypeReference), } #[derive(Clone, PartialEq, Debug)] @@ -103,13 +103,16 @@ impl<'a> FnSpec<'a> { if fn_type == FnType::Fn && !has_self { if arguments.is_empty() { - panic!("Static method needs #[staticmethod] attribute"); + return Err(syn::Error::new_spanned( + name, + "Static method needs #[staticmethod] attribute", + )); } let tp = match arguments.remove(0).ty { - syn::Type::Path(p) => replace_self(p), - _ => panic!("Invalid type as self"), + syn::Type::Reference(r) => replace_self(r)?, + x => return Err(syn::Error::new_spanned(x, "Invalid type as custom self")), }; - fn_type = FnType::PySelf(tp); + fn_type = FnType::PySelfNew(tp); } Ok(FnSpec { @@ -386,15 +389,19 @@ fn parse_attributes(attrs: &mut Vec) -> syn::Result<(FnType, Vec } } -// Replace A with A<_> -fn replace_self(path: &syn::TypePath) -> syn::TypePath { +// Replace &A with &A<_> +fn replace_self(refn: &syn::TypeReference) -> syn::Result { fn infer(span: proc_macro2::Span) -> syn::GenericArgument { syn::GenericArgument::Type(syn::Type::Infer(syn::TypeInfer { underscore_token: syn::token::Underscore { spans: [span] }, })) } - let mut res = path.to_owned(); - for seg in &mut res.path.segments { + let mut res = refn.to_owned(); + let tp = match &mut *res.elem { + syn::Type::Path(p) => p, + _ => return Err(syn::Error::new_spanned(refn, "unsupported argument")), + }; + for seg in &mut tp.path.segments { if let syn::PathArguments::AngleBracketed(ref mut g) = seg.arguments { let mut args = syn::punctuated::Punctuated::new(); for arg in &g.args { @@ -415,5 +422,6 @@ fn replace_self(path: &syn::TypePath) -> syn::TypePath { g.args = args; } } - res + res.lifetime = None; + Ok(res) } diff --git a/pyo3-derive-backend/src/pymethod.rs b/pyo3-derive-backend/src/pymethod.rs index 4d01f99c9c0..e47edc049bb 100644 --- a/pyo3-derive-backend/src/pymethod.rs +++ b/pyo3-derive-backend/src/pymethod.rs @@ -34,7 +34,7 @@ pub fn gen_py_method( }; let text_signature = match &spec.tp { - FnType::Fn | FnType::PySelf(_) | FnType::FnClass | FnType::FnStatic => { + FnType::Fn | FnType::PySelfNew(_) | FnType::FnClass | FnType::FnStatic => { utils::parse_text_signature_attrs(&mut *meth_attrs, name)? } FnType::FnNew => parse_erroneous_text_signature( @@ -59,7 +59,7 @@ pub fn gen_py_method( Ok(match spec.tp { FnType::Fn => impl_py_method_def(name, doc, &spec, &impl_wrap(cls, name, &spec, true)), - FnType::PySelf(ref self_ty) => impl_py_method_def( + FnType::PySelfNew(ref self_ty) => impl_py_method_def( name, doc, &spec, @@ -127,7 +127,7 @@ pub fn impl_wrap_pyslf( cls: &syn::Type, name: &syn::Ident, spec: &FnSpec<'_>, - self_ty: &syn::TypePath, + self_ty: &syn::TypeReference, noargs: bool, ) -> TokenStream { let names = get_arg_names(spec); @@ -221,8 +221,7 @@ pub fn impl_proto_wrap(cls: &syn::Type, name: &syn::Ident, spec: &FnSpec<'_>) -> /// Generate class method wrapper (PyCFunction, PyCFunctionWithKeywords) pub fn impl_wrap_new(cls: &syn::Type, name: &syn::Ident, spec: &FnSpec<'_>) -> TokenStream { let names: Vec = get_arg_names(&spec); - let cb = quote! { #cls::#name(&_obj, #(#names),*) }; - + let cb = quote! { #cls::#name(#(#names),*) }; let body = impl_arg_params(spec, cb); quote! { @@ -240,11 +239,11 @@ pub fn impl_wrap_new(cls: &syn::Type, name: &syn::Ident, spec: &FnSpec<'_>) -> T let _args = _py.from_borrowed_ptr::(_args); let _kwargs: Option<&pyo3::types::PyDict> = _py.from_borrowed_ptr_or_opt(_kwargs); - #body + # body - match <<#cls as pyo3::PyTypeInfo>::ConcreteLayout as pyo3::pyclass::PyClassNew>::new(_py, _result) { - Ok(_slf) => _slf as _, - Err(e) => e.restore_and_null(), + match _result.and_then(|slf| pyo3::PyClassShell::new(_py, slf)) { + Ok(slf) => slf as _, + Err(e) => e.restore_and_null(_py), } } } From a6639076b9bf5946a1ca954f613e1ac15d6c0b89 Mon Sep 17 00:00:00 2001 From: kngwyu Date: Sat, 14 Dec 2019 23:16:39 +0900 Subject: [PATCH 04/22] Introduce PyInternalCaster --- pyo3-derive-backend/src/method.rs | 2 +- src/class/macros.rs | 4 ++-- src/conversion.rs | 18 +++++++-------- src/instance.rs | 37 +++++++++++++++++++----------- src/pyclass.rs | 38 ++++++++++++++++++++++++++----- src/python.rs | 14 ++---------- src/type_object.rs | 21 +++++++++-------- tests/test_class_basics.rs | 2 +- tests/test_class_new.rs | 16 ++++++------- tests/test_datetime.rs | 0 tests/test_dunder.rs | 24 +++++++++---------- tests/test_gc.rs | 24 ++++++++----------- tests/test_inheritance.rs | 12 +++++----- tests/test_mapping.rs | 9 ++++---- tests/test_methods.rs | 16 ++++++------- tests/test_pyclass_shell.rs | 26 --------------------- tests/test_pyself.rs | 27 +++++++++++++--------- tests/test_sequence.rs | 9 ++++---- tests/test_text_signature.rs | 12 +++++----- tests/test_various.rs | 21 ++++++++--------- 20 files changed, 165 insertions(+), 167 deletions(-) mode change 100644 => 100755 tests/test_datetime.rs mode change 100644 => 100755 tests/test_mapping.rs delete mode 100644 tests/test_pyclass_shell.rs diff --git a/pyo3-derive-backend/src/method.rs b/pyo3-derive-backend/src/method.rs index 9efe22d2be2..3d4a75f483d 100644 --- a/pyo3-derive-backend/src/method.rs +++ b/pyo3-derive-backend/src/method.rs @@ -65,7 +65,7 @@ impl<'a> FnSpec<'a> { ref pat, ref ty, .. }) => { // skip first argument (cls) - if (fn_type == FnType::FnClass || fn_type == FnType::FnNew) && !has_self { + if fn_type == FnType::FnClass && !has_self { has_self = true; continue; } diff --git a/src/class/macros.rs b/src/class/macros.rs index fc86b62f9e5..7fbf2499be9 100644 --- a/src/class/macros.rs +++ b/src/class/macros.rs @@ -35,10 +35,10 @@ macro_rules! py_unary_pyref_func { where T: for<'p> $trait<'p>, { - use $crate::{pyclass::PyClassShell, FromPyPointer}; + use $crate::pyclass::PyClassShell; let py = $crate::Python::assume_gil_acquired(); let _pool = $crate::GILPool::new(py); - let slf: &mut PyClassShell = FromPyPointer::from_borrowed_ptr_or_panic(py, slf); + let slf: &mut PyClassShell = &mut *(slf as *mut PyClassShell); let res = $class::$f(slf).into(); $crate::callback::cb_convert($conv, py, res) } diff --git a/src/conversion.rs b/src/conversion.rs index 8b71294d506..66a2bb5fe09 100644 --- a/src/conversion.rs +++ b/src/conversion.rs @@ -3,7 +3,7 @@ //! Conversions between various states of rust and python types and their wrappers. use crate::err::{self, PyDowncastError, PyResult}; use crate::object::PyObject; -use crate::type_object::PyTypeInfo; +use crate::type_object::{PyConcreteObject, PyTypeInfo}; use crate::types::PyAny; use crate::types::PyTuple; use crate::{ffi, gil, Py, Python}; @@ -393,15 +393,13 @@ where #[inline] unsafe fn try_from_unchecked>(value: V) -> &'v T { let value = value.into(); - let ptr = value as *const _ as *const u8 as *const T; - &*ptr + T::ConcreteLayout::internal_ref_cast(value) } #[inline] unsafe fn try_from_mut_unchecked>(value: V) -> &'v mut T { let value = value.into(); - let ptr = value as *const _ as *mut u8 as *mut T; - &mut *ptr + T::ConcreteLayout::internal_mut_cast(value) } } @@ -453,10 +451,11 @@ where T: PyTypeInfo, { unsafe fn from_owned_ptr_or_opt(py: Python<'p>, ptr: *mut ffi::PyObject) -> Option { - NonNull::new(ptr).map(|p| py.unchecked_downcast(gil::register_owned(py, p))) + NonNull::new(ptr).map(|p| T::ConcreteLayout::internal_ref_cast(gil::register_owned(py, p))) } unsafe fn from_borrowed_ptr_or_opt(py: Python<'p>, ptr: *mut ffi::PyObject) -> Option { - NonNull::new(ptr).map(|p| py.unchecked_downcast(gil::register_borrowed(py, p))) + NonNull::new(ptr) + .map(|p| T::ConcreteLayout::internal_ref_cast(gil::register_borrowed(py, p))) } } @@ -465,10 +464,11 @@ where T: PyTypeInfo, { unsafe fn from_owned_ptr_or_opt(py: Python<'p>, ptr: *mut ffi::PyObject) -> Option { - NonNull::new(ptr).map(|p| py.unchecked_mut_downcast(gil::register_owned(py, p))) + NonNull::new(ptr).map(|p| T::ConcreteLayout::internal_mut_cast(gil::register_owned(py, p))) } unsafe fn from_borrowed_ptr_or_opt(py: Python<'p>, ptr: *mut ffi::PyObject) -> Option { - NonNull::new(ptr).map(|p| py.unchecked_mut_downcast(gil::register_borrowed(py, p))) + NonNull::new(ptr) + .map(|p| T::ConcreteLayout::internal_mut_cast(gil::register_borrowed(py, p))) } } diff --git a/src/instance.rs b/src/instance.rs index 4fd52a4ceaa..9c728ecb92f 100644 --- a/src/instance.rs +++ b/src/instance.rs @@ -1,11 +1,10 @@ // Copyright (c) 2017-present PyO3 Project and Contributors use crate::err::{PyErr, PyResult}; use crate::gil; -use crate::instance; use crate::object::PyObject; use crate::objectprotocol::ObjectProtocol; use crate::pyclass::{PyClass, PyClassShell}; -use crate::type_object::PyTypeInfo; +use crate::type_object::{PyConcreteObject, PyTypeInfo}; use crate::types::PyAny; use crate::{ffi, IntoPy}; use crate::{AsPyPointer, FromPyObject, IntoPyPointer, Python, ToPyObject}; @@ -36,11 +35,6 @@ unsafe impl Sync for Py {} impl Py { /// Create new instance of T and move it under python management - /// - /// **NOTE** - /// This method's `where` bound is actually the same as `PyClass`. - /// However, since Rust still doesn't have higher order generics, we cannot represent - /// this bound by `PyClass`. pub fn new(py: Python, value: T) -> PyResult> where T: PyClass, @@ -128,7 +122,8 @@ pub trait AsPyRef: Sized { impl AsPyRef for Py { fn as_ref(&self, _py: Python) -> &T { - unsafe { &*(self as *const instance::Py as *const T) } + let any = self as *const Py as *const PyAny; + unsafe { T::ConcreteLayout::internal_ref_cast(&*any) } } } @@ -143,8 +138,8 @@ impl IntoPy for Py { /// Converts `Py` instance -> PyObject. /// Consumes `self` without calling `Py_DECREF()` #[inline] - fn into_py(self, py: Python) -> PyObject { - unsafe { PyObject::from_owned_ptr(py, self.into_ptr()) } + fn into_py(self, _py: Python) -> PyObject { + unsafe { PyObject::from_not_null(self.into_non_null()) } } } @@ -161,9 +156,25 @@ impl IntoPyPointer for Py { #[inline] #[must_use] fn into_ptr(self) -> *mut ffi::PyObject { - let ptr = self.0.as_ptr(); - std::mem::forget(self); - ptr + self.into_non_null().as_ptr() + } +} + +impl<'a, T> std::convert::From<&PyClassShell> for Py +where + T: PyClass, +{ + fn from(shell: &PyClassShell) -> Self { + unsafe { Py::from_borrowed_ptr(shell.as_ptr()) } + } +} + +impl<'a, T> std::convert::From<&mut PyClassShell> for Py +where + T: PyClass, +{ + fn from(shell: &mut PyClassShell) -> Self { + unsafe { Py::from_borrowed_ptr(shell.as_ptr()) } } } diff --git a/src/pyclass.rs b/src/pyclass.rs index 2b1fd1b6ece..eba23e2c239 100644 --- a/src/pyclass.rs +++ b/src/pyclass.rs @@ -3,6 +3,7 @@ use crate::class::methods::{PyMethodDefType, PyMethodsProtocol}; use crate::conversion::{AsPyPointer, FromPyPointer, ToPyObject}; use crate::pyclass_slots::{PyClassDict, PyClassWeakRef}; use crate::type_object::{type_flags, PyConcreteObject, PyTypeObject}; +use crate::types::PyAny; use crate::{class, ffi, gil, PyErr, PyObject, PyResult, PyTypeInfo, Python}; use std::ffi::CString; use std::mem::ManuallyDrop; @@ -112,7 +113,15 @@ impl PyClassShell { } } -impl PyConcreteObject for PyClassShell { +impl PyConcreteObject for PyClassShell { + unsafe fn internal_ref_cast(obj: &PyAny) -> &T { + let shell = obj.as_ptr() as *const PyClassShell; + &*(*shell).pyclass + } + unsafe fn internal_mut_cast(obj: &PyAny) -> &mut T { + let shell = obj.as_ptr() as *const PyClassShell as *mut PyClassShell; + &mut *(*shell).pyclass + } unsafe fn py_drop(&mut self, py: Python) { ManuallyDrop::drop(&mut self.pyclass); self.dict.clear_dict(py); @@ -121,6 +130,12 @@ impl PyConcreteObject for PyClassShell { } } +impl AsPyPointer for PyClassShell { + fn as_ptr(&self) -> *mut ffi::PyObject { + (self as *const _) as *mut _ + } +} + impl std::ops::Deref for PyClassShell { type Target = T; fn deref(&self) -> &T { @@ -134,7 +149,13 @@ impl std::ops::DerefMut for PyClassShell { } } -impl ToPyObject for PyClassShell { +impl ToPyObject for &PyClassShell { + fn to_object(&self, py: Python<'_>) -> PyObject { + unsafe { PyObject::from_borrowed_ptr(py, self.as_ptr()) } + } +} + +impl ToPyObject for &mut PyClassShell { fn to_object(&self, py: Python<'_>) -> PyObject { unsafe { PyObject::from_borrowed_ptr(py, self.as_ptr()) } } @@ -145,10 +166,11 @@ where T: PyClass, { unsafe fn from_owned_ptr_or_opt(py: Python<'p>, ptr: *mut ffi::PyObject) -> Option { - NonNull::new(ptr).map(|p| &**(gil::register_owned(py, p) as *const _ as *const Self)) + NonNull::new(ptr).map(|p| &*(gil::register_owned(py, p).as_ptr() as *const PyClassShell)) } unsafe fn from_borrowed_ptr_or_opt(py: Python<'p>, ptr: *mut ffi::PyObject) -> Option { - NonNull::new(ptr).map(|p| &**(gil::register_borrowed(py, p) as *const _ as *const Self)) + NonNull::new(ptr) + .map(|p| &*(gil::register_borrowed(py, p).as_ptr() as *const PyClassShell)) } } @@ -157,10 +179,14 @@ where T: PyClass, { unsafe fn from_owned_ptr_or_opt(py: Python<'p>, ptr: *mut ffi::PyObject) -> Option { - NonNull::new(ptr).map(|p| &mut **(gil::register_owned(py, p) as *const _ as *mut Self)) + NonNull::new(ptr).map(|p| { + &mut *(gil::register_owned(py, p).as_ptr() as *const PyClassShell as *mut _) + }) } unsafe fn from_borrowed_ptr_or_opt(py: Python<'p>, ptr: *mut ffi::PyObject) -> Option { - NonNull::new(ptr).map(|p| &mut **(gil::register_borrowed(py, p) as *const _ as *mut Self)) + NonNull::new(ptr).map(|p| { + &mut *(gil::register_borrowed(py, p).as_ptr() as *const PyClassShell as *mut _) + }) } } diff --git a/src/python.rs b/src/python.rs index 873b4ef37d2..545bf213e4e 100644 --- a/src/python.rs +++ b/src/python.rs @@ -7,7 +7,7 @@ use crate::ffi; use crate::gil::{self, GILGuard}; use crate::instance::AsPyRef; use crate::object::PyObject; -use crate::type_object::{PyTypeInfo, PyTypeObject}; +use crate::type_object::{PyConcreteObject, PyTypeInfo, PyTypeObject}; use crate::types::{PyAny, PyDict, PyModule, PyType}; use crate::AsPyPointer; use crate::{FromPyPointer, IntoPyPointer, PyTryFrom}; @@ -272,16 +272,6 @@ impl<'p> Python<'p> { } impl<'p> Python<'p> { - // TODO(kngwyu): Now offset dies, so what should this functions do - pub(crate) unsafe fn unchecked_downcast(self, ob: &PyAny) -> &'p T { - &*(ob as *const _ as *const T) - } - - #[allow(clippy::cast_ref_to_mut)] // FIXME - pub(crate) unsafe fn unchecked_mut_downcast(self, ob: &PyAny) -> &'p mut T { - &mut *(ob as *const _ as *mut T) - } - /// Register object in release pool, and try to downcast to specific type. pub fn checked_cast_as(self, obj: PyObject) -> Result<&'p T, PyDowncastError> where @@ -297,7 +287,7 @@ impl<'p> Python<'p> { T: PyTypeInfo, { let p = gil::register_owned(self, obj.into_nonnull()); - self.unchecked_downcast(p) + T::ConcreteLayout::internal_ref_cast(p) } /// Register `ffi::PyObject` pointer in release pool diff --git a/src/type_object.rs b/src/type_object.rs index e7ff7dc5b4c..4b93971606e 100644 --- a/src/type_object.rs +++ b/src/type_object.rs @@ -4,6 +4,7 @@ use crate::ffi; use crate::instance::Py; +use crate::instance::PyNativeType; use crate::types::PyAny; use crate::types::PyType; use crate::AsPyPointer; @@ -11,17 +12,17 @@ use crate::Python; use std::ptr::NonNull; /// TODO: write document -pub trait PyConcreteObject: Sized { - unsafe fn py_drop(&mut self, _py: Python) {} -} - -impl AsPyPointer for T { - fn as_ptr(&self) -> *mut ffi::PyObject { - (self as *const _) as _ +pub trait PyConcreteObject: Sized { + 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) {} } -impl PyConcreteObject for ffi::PyObject {} +impl PyConcreteObject for ffi::PyObject {} /// Our custom type flags pub mod type_flags { @@ -39,7 +40,7 @@ pub mod type_flags { } /// Python type information. -pub trait PyTypeInfo { +pub trait PyTypeInfo: Sized { /// Type of objects to store in PyObject struct type Type; @@ -59,7 +60,7 @@ pub trait PyTypeInfo { type BaseType: PyTypeInfo; /// Layout - type ConcreteLayout: PyConcreteObject; + type ConcreteLayout: PyConcreteObject; /// PyTypeObject instance for this type, which might still need to /// be initialized diff --git a/tests/test_class_basics.rs b/tests/test_class_basics.rs index 00e2674e64a..62ea640cba4 100644 --- a/tests/test_class_basics.rs +++ b/tests/test_class_basics.rs @@ -1,6 +1,6 @@ use pyo3::prelude::*; use pyo3::py_run; -use pyo3::type_object::initialize_type; +use pyo3::pyclass::initialize_type; mod common; diff --git a/tests/test_class_new.rs b/tests/test_class_new.rs index bc1a1a1ed72..ce3107e9f23 100644 --- a/tests/test_class_new.rs +++ b/tests/test_class_new.rs @@ -1,5 +1,4 @@ use pyo3::prelude::*; -use pyo3::PyRawObject; #[pyclass] struct EmptyClassWithNew {} @@ -7,8 +6,8 @@ struct EmptyClassWithNew {} #[pymethods] impl EmptyClassWithNew { #[new] - fn new(obj: &PyRawObject) { - obj.init(EmptyClassWithNew {}); + fn new() -> EmptyClassWithNew { + EmptyClassWithNew {} } } @@ -25,6 +24,7 @@ fn empty_class_with_new() { } #[pyclass] +#[derive(Debug)] struct NewWithOneArg { _data: i32, } @@ -32,8 +32,8 @@ struct NewWithOneArg { #[pymethods] impl NewWithOneArg { #[new] - fn new(obj: &PyRawObject, arg: i32) { - obj.init(NewWithOneArg { _data: arg }) + fn new(arg: i32) -> NewWithOneArg { + NewWithOneArg { _data: arg } } } @@ -56,11 +56,11 @@ struct NewWithTwoArgs { #[pymethods] impl NewWithTwoArgs { #[new] - fn new(obj: &PyRawObject, arg1: i32, arg2: i32) { - obj.init(NewWithTwoArgs { + fn new(arg1: i32, arg2: i32) -> Self { + NewWithTwoArgs { _data1: arg1, _data2: arg2, - }) + } } } diff --git a/tests/test_datetime.rs b/tests/test_datetime.rs old mode 100644 new mode 100755 diff --git a/tests/test_dunder.rs b/tests/test_dunder.rs index 4dd10062dc8..6bb177cf647 100755 --- a/tests/test_dunder.rs +++ b/tests/test_dunder.rs @@ -4,11 +4,9 @@ use pyo3::class::{ PyContextProtocol, PyIterProtocol, PyMappingProtocol, PyObjectProtocol, PySequenceProtocol, }; use pyo3::exceptions::{IndexError, ValueError}; -use pyo3::ffi; use pyo3::prelude::*; -use pyo3::py_run; use pyo3::types::{IntoPyDict, PyAny, PyBytes, PySlice, PyType}; -use pyo3::AsPyPointer; +use pyo3::{ffi, py_run, AsPyPointer, PyClassShell}; use std::convert::TryFrom; use std::{isize, iter}; @@ -55,11 +53,11 @@ struct Iterator { #[pyproto] impl<'p> PyIterProtocol for Iterator { - fn __iter__(slf: PyRefMut) -> PyResult> { + fn __iter__(slf: &mut PyClassShell) -> PyResult> { Ok(slf.into()) } - fn __next__(mut slf: PyRefMut) -> PyResult> { + fn __next__(mut slf: &mut PyClassShell) -> PyResult> { Ok(slf.iter.next()) } } @@ -252,7 +250,7 @@ fn setitem() { let gil = Python::acquire_gil(); let py = gil.python(); - let c = PyRef::new(py, SetItem { key: 0, val: 0 }).unwrap(); + let c = PyClassShell::new_ref(py, SetItem { key: 0, val: 0 }).unwrap(); py_run!(py, c, "c[1] = 2"); assert_eq!(c.key, 1); assert_eq!(c.val, 2); @@ -277,7 +275,7 @@ fn delitem() { let gil = Python::acquire_gil(); let py = gil.python(); - let c = PyRef::new(py, DelItem { key: 0 }).unwrap(); + let c = PyClassShell::new_ref(py, DelItem { key: 0 }).unwrap(); py_run!(py, c, "del c[1]"); assert_eq!(c.key, 1); py_expect_exception!(py, c, "c[1] = 2", NotImplementedError); @@ -306,7 +304,7 @@ fn setdelitem() { let gil = Python::acquire_gil(); let py = gil.python(); - let c = PyRef::new(py, SetDelItem { val: None }).unwrap(); + let c = PyClassShell::new_ref(py, SetDelItem { val: None }).unwrap(); py_run!(py, c, "c[1] = 2"); assert_eq!(c.val, Some(2)); py_run!(py, c, "del c[1]"); @@ -385,7 +383,7 @@ fn context_manager() { let gil = Python::acquire_gil(); let py = gil.python(); - let mut c = PyRefMut::new(py, ContextManager { exit_called: false }).unwrap(); + let c = PyClassShell::new_mut(py, ContextManager { exit_called: false }).unwrap(); py_run!(py, c, "with c as x: assert x == 42"); assert!(c.exit_called); @@ -457,7 +455,7 @@ struct DunderDictSupport {} fn dunder_dict_support() { let gil = Python::acquire_gil(); let py = gil.python(); - let inst = PyRef::new(py, DunderDictSupport {}).unwrap(); + let inst = PyClassShell::new_ref(py, DunderDictSupport {}).unwrap(); py_run!( py, inst, @@ -472,7 +470,7 @@ fn dunder_dict_support() { fn access_dunder_dict() { let gil = Python::acquire_gil(); let py = gil.python(); - let inst = PyRef::new(py, DunderDictSupport {}).unwrap(); + let inst = PyClassShell::new_ref(py, DunderDictSupport {}).unwrap(); py_run!( py, inst, @@ -490,7 +488,7 @@ struct WeakRefDunderDictSupport {} fn weakref_dunder_dict_support() { let gil = Python::acquire_gil(); let py = gil.python(); - let inst = PyRef::new(py, WeakRefDunderDictSupport {}).unwrap(); + let inst = PyClassShell::new_ref(py, WeakRefDunderDictSupport {}).unwrap(); py_run!( py, inst, @@ -515,7 +513,7 @@ impl PyObjectProtocol for ClassWithGetAttr { fn getattr_doesnt_override_member() { let gil = Python::acquire_gil(); let py = gil.python(); - let inst = PyRef::new(py, ClassWithGetAttr { data: 4 }).unwrap(); + let inst = PyClassShell::new_ref(py, ClassWithGetAttr { data: 4 }).unwrap(); py_assert!(py, inst, "inst.data == 4"); py_assert!(py, inst, "inst.a == 8"); } diff --git a/tests/test_gc.rs b/tests/test_gc.rs index 26465438a25..81fa8963aa8 100644 --- a/tests/test_gc.rs +++ b/tests/test_gc.rs @@ -1,13 +1,9 @@ use pyo3::class::PyGCProtocol; use pyo3::class::PyTraverseError; use pyo3::class::PyVisit; -use pyo3::ffi; use pyo3::prelude::*; -use pyo3::py_run; -use pyo3::types::PyAny; -use pyo3::types::PyTuple; -use pyo3::AsPyPointer; -use pyo3::PyRawObject; +use pyo3::types::{PyAny, PyTuple}; +use pyo3::{ffi, py_run, AsPyPointer, PyClassShell}; use std::cell::RefCell; use std::sync::atomic::{AtomicBool, Ordering}; use std::sync::Arc; @@ -157,7 +153,7 @@ fn gc_integration() { { let gil = Python::acquire_gil(); let py = gil.python(); - let inst = PyRef::new( + let inst = PyClassShell::new_mut( py, GCIntegration { self_ref: RefCell::new(py.None()), @@ -192,7 +188,7 @@ impl PyGCProtocol for GCIntegration2 { fn gc_integration2() { let gil = Python::acquire_gil(); let py = gil.python(); - let inst = PyRef::new(py, GCIntegration2 {}).unwrap(); + let inst = PyClassShell::new_ref(py, GCIntegration2 {}).unwrap(); py_run!(py, inst, "import gc; assert inst in gc.get_objects()"); } @@ -203,7 +199,7 @@ struct WeakRefSupport {} fn weakref_support() { let gil = Python::acquire_gil(); let py = gil.python(); - let inst = PyRef::new(py, WeakRefSupport {}).unwrap(); + let inst = PyClassShell::new_ref(py, WeakRefSupport {}).unwrap(); py_run!( py, inst, @@ -219,8 +215,8 @@ struct BaseClassWithDrop { #[pymethods] impl BaseClassWithDrop { #[new] - fn new(obj: &PyRawObject) { - obj.init(BaseClassWithDrop { data: None }) + fn new() -> BaseClassWithDrop { + BaseClassWithDrop { data: None } } } @@ -239,10 +235,10 @@ struct SubClassWithDrop { #[pymethods] impl SubClassWithDrop { + // TODO(kngwyu): Implement baseclass initialization #[new] - fn new(obj: &PyRawObject) { - obj.init(SubClassWithDrop { data: None }); - BaseClassWithDrop::new(obj); + fn new() -> SubClassWithDrop { + SubClassWithDrop { data: None } } } diff --git a/tests/test_inheritance.rs b/tests/test_inheritance.rs index 69f70c644cd..462ff138094 100644 --- a/tests/test_inheritance.rs +++ b/tests/test_inheritance.rs @@ -2,7 +2,6 @@ use pyo3::prelude::*; use pyo3::py_run; #[cfg(feature = "unsound-subclass")] use pyo3::types::IntoPyDict; -use std::isize; mod common; @@ -35,8 +34,8 @@ fn subclass() { #[pymethods] impl BaseClass { #[new] - fn new(obj: &PyRawObject) { - obj.init(BaseClass { val1: 10 }) + fn new() -> Self { + BaseClass { val1: 10 } } } @@ -49,13 +48,14 @@ struct SubClass { #[pymethods] impl SubClass { #[new] - fn new(obj: &PyRawObject) { - obj.init(SubClass { val2: 5 }); - BaseClass::new(obj); + fn new() -> Self { + SubClass { val2: 5 } } } +// TODO(kngwyu): disable untill super().__init__ fixed #[test] +#[ignore] fn inheritance_with_new_methods() { let gil = Python::acquire_gil(); let py = gil.python(); diff --git a/tests/test_mapping.rs b/tests/test_mapping.rs old mode 100644 new mode 100755 index 6160da0fc66..17a0203bf57 --- a/tests/test_mapping.rs +++ b/tests/test_mapping.rs @@ -15,20 +15,19 @@ struct Mapping { #[pymethods] impl Mapping { #[new] - fn new(obj: &PyRawObject, elements: Option<&PyList>) -> PyResult<()> { + fn new(elements: Option<&PyList>) -> PyResult { if let Some(pylist) = elements { let mut elems = HashMap::with_capacity(pylist.len()); for (i, pyelem) in pylist.into_iter().enumerate() { let elem = String::extract(pyelem)?; elems.insert(elem, i); } - obj.init(Self { index: elems }); + Ok(Self { index: elems }) } else { - obj.init(Self { + Ok(Self { index: HashMap::new(), - }); + }) } - Ok(()) } } diff --git a/tests/test_methods.rs b/tests/test_methods.rs index b2295f008dd..153bc2b5fca 100644 --- a/tests/test_methods.rs +++ b/tests/test_methods.rs @@ -1,7 +1,7 @@ use pyo3::prelude::*; use pyo3::py_run; use pyo3::types::{IntoPyDict, PyDict, PyList, PySet, PyString, PyTuple, PyType}; -use pyo3::PyRawObject; +use pyo3::PyClassShell; mod common; @@ -23,7 +23,7 @@ fn instance_method() { let gil = Python::acquire_gil(); let py = gil.python(); - let obj = PyRefMut::new(py, InstanceMethod { member: 42 }).unwrap(); + let obj = PyClassShell::new_mut(py, InstanceMethod { member: 42 }).unwrap(); assert_eq!(obj.method().unwrap(), 42); let d = [("obj", obj)].into_py_dict(py); py.run("assert obj.method() == 42", None, Some(d)).unwrap(); @@ -49,7 +49,7 @@ fn instance_method_with_args() { let gil = Python::acquire_gil(); let py = gil.python(); - let obj = PyRefMut::new(py, InstanceMethodWithArgs { member: 7 }).unwrap(); + let obj = PyClassShell::new_mut(py, InstanceMethodWithArgs { member: 7 }).unwrap(); assert_eq!(obj.method(6).unwrap(), 42); let d = [("obj", obj)].into_py_dict(py); py.run("assert obj.method(3) == 21", None, Some(d)).unwrap(); @@ -63,8 +63,8 @@ struct ClassMethod {} #[pymethods] impl ClassMethod { #[new] - fn new(obj: &PyRawObject) { - obj.init(ClassMethod {}) + fn new() -> Self { + ClassMethod {} } #[classmethod] @@ -137,8 +137,8 @@ struct StaticMethod {} #[pymethods] impl StaticMethod { #[new] - fn new(obj: &PyRawObject) { - obj.init(StaticMethod {}) + fn new() -> Self { + StaticMethod {} } #[staticmethod] @@ -395,7 +395,7 @@ impl MethodWithLifeTime { fn method_with_lifetime() { let gil = Python::acquire_gil(); let py = gil.python(); - let obj = PyRef::new(py, MethodWithLifeTime {}).unwrap(); + let obj = PyClassShell::new_ref(py, MethodWithLifeTime {}).unwrap(); py_run!( py, obj, diff --git a/tests/test_pyclass_shell.rs b/tests/test_pyclass_shell.rs deleted file mode 100644 index df9c1ce3619..00000000000 --- a/tests/test_pyclass_shell.rs +++ /dev/null @@ -1,26 +0,0 @@ -use pyo3::prelude::*; -use pyo3::py_run; -use pyo3::pyclass::PyClassShell; -use pyo3::types::PyAny; -use pyo3::FromPyPointer; - -#[pyclass] -struct Class { - member: i32, -} - -#[pymethods] -impl Class { - fn hello(&self) -> i32 { - self.member - } -} - -#[test] -fn test_shell() { - let class = Class { member: 128 }; - let gil = Python::acquire_gil(); - let py = gil.python(); - // let obj: &PyAny = unsafe { FromPyPointer::from_owned_ptr(py, PyClassShell::new(py, class)) }; - // py_run!(py, obj, "assert obj.hello() == 128"); -} diff --git a/tests/test_pyself.rs b/tests/test_pyself.rs index 5f2ce854178..00eebfb128f 100644 --- a/tests/test_pyself.rs +++ b/tests/test_pyself.rs @@ -2,7 +2,7 @@ use pyo3; use pyo3::prelude::*; use pyo3::types::{PyBytes, PyString}; -use pyo3::PyIterProtocol; +use pyo3::{AsPyRef, PyClassShell, PyIterProtocol}; use std::collections::HashMap; mod common; @@ -10,20 +10,23 @@ mod common; /// Assumes it's a file reader or so. /// Inspired by https://github.com/jothan/cordoba, thanks. #[pyclass] -#[derive(Clone)] +#[derive(Clone, Debug)] struct Reader { inner: HashMap, } #[pymethods] impl Reader { - fn clone_ref(slf: PyRef) -> PyRef { + fn clone_ref(slf: &PyClassShell) -> &PyClassShell { slf } - fn clone_ref_with_py<'py>(slf: PyRef<'py, Self>, _py: Python<'py>) -> PyRef<'py, Self> { + fn clone_ref_with_py<'py>( + slf: &'py PyClassShell, + _py: Python<'py>, + ) -> &'py PyClassShell { slf } - fn get_iter(slf: PyRef, keys: Py) -> PyResult { + fn get_iter(slf: &PyClassShell, keys: Py) -> PyResult { Ok(Iter { reader: slf.into(), keys, @@ -31,7 +34,7 @@ impl Reader { }) } fn get_iter_and_reset( - mut slf: PyRefMut, + slf: &mut PyClassShell, keys: Py, py: Python, ) -> PyResult { @@ -54,13 +57,15 @@ struct Iter { #[pyproto] impl PyIterProtocol for Iter { - fn __iter__(slf: PyRefMut) -> PyResult { + fn __iter__(slf: &mut PyClassShell) -> PyResult { let py = unsafe { Python::assume_gil_acquired() }; Ok(slf.to_object(py)) } - fn __next__(mut slf: PyRefMut) -> PyResult> { + + fn __next__(slf: &mut PyClassShell) -> PyResult> { let py = unsafe { Python::assume_gil_acquired() }; - match slf.keys.as_ref(py).as_bytes().get(slf.idx) { + let bytes = slf.keys.as_ref(py).as_bytes(); + match bytes.get(slf.idx) { Some(&b) => { let res = slf .reader @@ -84,7 +89,7 @@ fn reader() -> Reader { } #[test] -fn test_nested_iter() { +fn test_nested_iter1() { let gil = Python::acquire_gil(); let py = gil.python(); let reader: PyObject = reader().into_py(py); @@ -108,7 +113,7 @@ fn test_clone_ref() { fn test_nested_iter_reset() { let gil = Python::acquire_gil(); let py = gil.python(); - let reader = PyRef::new(py, reader()).unwrap(); + let reader = PyClassShell::new_ref(py, reader()).unwrap(); py_assert!( py, reader, diff --git a/tests/test_sequence.rs b/tests/test_sequence.rs index 6241f33ecf3..9b97aa6b69b 100644 --- a/tests/test_sequence.rs +++ b/tests/test_sequence.rs @@ -14,20 +14,19 @@ struct ByteSequence { #[pymethods] impl ByteSequence { #[new] - fn new(obj: &PyRawObject, elements: Option<&PyList>) -> PyResult<()> { + fn new(elements: Option<&PyList>) -> PyResult { if let Some(pylist) = elements { let mut elems = Vec::with_capacity(pylist.len()); for pyelem in pylist.into_iter() { let elem = u8::extract(pyelem)?; elems.push(elem); } - obj.init(Self { elements: elems }); + Ok(Self { elements: elems }) } else { - obj.init(Self { + Ok(Self { elements: Vec::new(), - }); + }) } - Ok(()) } } diff --git a/tests/test_text_signature.rs b/tests/test_text_signature.rs index 72df1fdf111..6406cbf590b 100644 --- a/tests/test_text_signature.rs +++ b/tests/test_text_signature.rs @@ -1,5 +1,5 @@ use pyo3::prelude::*; -use pyo3::{types::PyType, wrap_pyfunction, wrap_pymodule}; +use pyo3::{types::PyType, wrap_pyfunction, wrap_pymodule, PyClassShell}; mod common; @@ -44,9 +44,9 @@ fn class_with_docs_and_signature() { impl MyClass { #[new] #[args(a, b = "None", "*", c = 42)] - fn __new__(obj: &PyRawObject, a: i32, b: Option, c: i32) { + fn __new__(a: i32, b: Option, c: i32) -> Self { let _ = (a, b, c); - obj.init(Self {}); + Self {} } } @@ -76,9 +76,9 @@ fn class_with_signature() { impl MyClass { #[new] #[args(a, b = "None", "*", c = 42)] - fn __new__(obj: &PyRawObject, a: i32, b: Option, c: i32) { + fn __new__(a: i32, b: Option, c: i32) -> Self { let _ = (a, b, c); - obj.init(Self {}); + Self {} } } @@ -144,7 +144,7 @@ fn test_methods() { let _ = a; } #[text_signature = "($self, b)"] - fn pyself_method(_this: PyRef, b: i32) { + fn pyself_method(_this: &PyClassShell, b: i32) { let _ = b; } #[classmethod] diff --git a/tests/test_various.rs b/tests/test_various.rs index 655101db2ad..c433cc5dd07 100644 --- a/tests/test_various.rs +++ b/tests/test_various.rs @@ -1,9 +1,8 @@ use pyo3::prelude::*; -use pyo3::type_object::initialize_type; +use pyo3::pyclass::initialize_type; use pyo3::types::IntoPyDict; use pyo3::types::{PyDict, PyTuple}; -use pyo3::{py_run, wrap_pyfunction}; -use std::isize; +use pyo3::{py_run, wrap_pyfunction, AsPyRef, PyClassShell}; mod common; @@ -83,8 +82,8 @@ fn intopytuple_pyclass() { let py = gil.python(); let tup = ( - PyRef::new(py, SimplePyClass {}).unwrap(), - PyRef::new(py, SimplePyClass {}).unwrap(), + PyClassShell::new_ref(py, SimplePyClass {}).unwrap(), + PyClassShell::new_ref(py, SimplePyClass {}).unwrap(), ); py_assert!(py, tup, "type(tup[0]).__name__ == 'SimplePyClass'"); py_assert!(py, tup, "type(tup[0]).__name__ == type(tup[1]).__name__"); @@ -108,8 +107,8 @@ fn pytuple_pyclass_iter() { let tup = PyTuple::new( py, [ - PyRef::new(py, SimplePyClass {}).unwrap(), - PyRef::new(py, SimplePyClass {}).unwrap(), + PyClassShell::new_ref(py, SimplePyClass {}).unwrap(), + PyClassShell::new_ref(py, SimplePyClass {}).unwrap(), ] .iter(), ); @@ -124,12 +123,12 @@ struct PickleSupport {} #[pymethods] impl PickleSupport { #[new] - fn new(obj: &PyRawObject) { - obj.init({ PickleSupport {} }); + fn new() -> PickleSupport { + PickleSupport {} } pub fn __reduce__<'py>( - slf: PyRef, + slf: &'py PyClassShell, py: Python<'py>, ) -> PyResult<(PyObject, &'py PyTuple, PyObject)> { let cls = slf.to_object(py).getattr(py, "__class__")?; @@ -155,7 +154,7 @@ fn test_pickle() { module.add_class::().unwrap(); add_module(py, module).unwrap(); initialize_type::(py, Some("test_module")).unwrap(); - let inst = PyRef::new(py, PickleSupport {}).unwrap(); + let inst = PyClassShell::new_ref(py, PickleSupport {}).unwrap(); py_run!( py, inst, From b86de9376d36b32596b5d96b649e7ba224898179 Mon Sep 17 00:00:00 2001 From: kngwyu Date: Sun, 15 Dec 2019 11:41:32 +0900 Subject: [PATCH 05/22] Introduce PyClassInitializer --- pyo3-derive-backend/src/pymethod.rs | 24 +++-- src/derive_utils.rs | 4 +- src/instance.rs | 9 +- src/lib.rs | 6 +- src/prelude.rs | 1 + src/pyclass.rs | 141 +++++++++++++++++++++++++--- src/type_object.rs | 11 ++- tests/test_dunder.rs | 2 +- tests/test_gc.rs | 9 +- tests/test_getter_setter.rs | 1 - tests/test_inheritance.rs | 50 +++++++++- 11 files changed, 217 insertions(+), 41 deletions(-) diff --git a/pyo3-derive-backend/src/pymethod.rs b/pyo3-derive-backend/src/pymethod.rs index e47edc049bb..355a1e2e3fa 100644 --- a/pyo3-derive-backend/src/pymethod.rs +++ b/pyo3-derive-backend/src/pymethod.rs @@ -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 = 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::(_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( diff --git a/src/derive_utils.rs b/src/derive_utils.rs index c1a9ac32f3d..f64cde0d8f6 100644 --- a/src/derive_utils.rs +++ b/src/derive_utils.rs @@ -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()`. diff --git a/src/instance.rs b/src/instance.rs index 9c728ecb92f..b91f278683e 100644 --- a/src/instance.rs +++ b/src/instance.rs @@ -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 Sync for Py {} impl Py { /// Create new instance of T and move it under python management - pub fn new(py: Python, value: T) -> PyResult> + pub fn new(py: Python, value: impl IntoInitializer) -> PyResult> where - T: PyClass, + T: PyClass + PyTypeInfo>, { - 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) } diff --git a/src/lib.rs b/src/lib.rs index 88d62b69df4..383f753fe4e 100755 --- a/src/lib.rs +++ b/src/lib.rs @@ -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 diff --git a/src/prelude.rs b/src/prelude.rs index 821bc93976e..4b4cd038d06 100644 --- a/src/prelude.rs +++ b/src/prelude.rs @@ -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}; diff --git a/src/pyclass.rs b/src/pyclass.rs index eba23e2c239..074d13f7b5f 100644 --- a/src/pyclass.rs +++ b/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 { ob_base: ::ConcreteLayout, @@ -85,28 +86,37 @@ pub struct PyClassShell { } impl PyClassShell { - pub fn new_ref(py: Python, value: T) -> PyResult<&Self> { + pub fn new_ref(py: Python, value: impl IntoInitializer) -> PyResult<&Self> + where + T: PyTypeInfo, + { 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) -> PyResult<&mut Self> + where + T: PyTypeInfo, + { 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> { + ::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 PyClassShell { } impl PyConcreteObject for PyClassShell { + const NEED_INIT: bool = std::mem::size_of::() != 0; unsafe fn internal_ref_cast(obj: &PyAny) -> &T { let shell = obj.as_ptr() as *const PyClassShell; - &*(*shell).pyclass + &(*shell).pyclass } unsafe fn internal_mut_cast(obj: &PyAny) -> &mut T { let shell = obj.as_ptr() as *const PyClassShell as *mut PyClassShell; - &mut *(*shell).pyclass + &mut (*shell).pyclass } unsafe fn py_drop(&mut self, py: Python) { ManuallyDrop::drop(&mut self.pyclass); @@ -128,6 +139,12 @@ impl PyConcreteObject for PyClassShell { 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 ::ConcreteLayout> { + Some(&mut self.ob_base) + } } impl AsPyPointer for PyClassShell { @@ -190,6 +207,108 @@ where } } +/// An initializer for `PyClassShell`. +/// +/// **NOTE** If +pub struct PyClassInitializer { + init: Option, + super_init: Option<*mut PyClassInitializer>, +} + +impl PyClassInitializer { + 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::(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 ::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 { + 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> + where + T: PyClass + PyTypeInfo>, + { + let shell = PyClassShell::new(py)?; + self.init_class(&mut *shell)?; + Ok(shell) + } +} + +pub trait IntoInitializer { + fn into_initializer(self) -> PyResult>; +} + +impl IntoInitializer for T { + fn into_initializer(self) -> PyResult> { + Ok(PyClassInitializer::from_value(self)) + } +} + +impl IntoInitializer for PyResult { + fn into_initializer(self) -> PyResult> { + self.map(PyClassInitializer::from_value) + } +} + +impl IntoInitializer for PyClassInitializer { + fn into_initializer(self) -> PyResult> { + Ok(self) + } +} + +impl IntoInitializer for PyResult> { + fn into_initializer(self) -> PyResult> { + self + } +} + /// Register new type in python object system. #[cfg(not(Py_LIMITED_API))] pub fn initialize_type(py: Python, module_name: Option<&str>) -> PyResult<*mut ffi::PyTypeObject> diff --git a/src/type_object.rs b/src/type_object.rs index 4b93971606e..39768602019 100644 --- a/src/type_object.rs +++ b/src/type_object.rs @@ -12,7 +12,8 @@ use crate::Python; use std::ptr::NonNull; /// TODO: write document -pub trait PyConcreteObject: Sized { +pub trait PyConcreteObject: 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: 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 ::ConcreteLayout> { + None + } } -impl PyConcreteObject for ffi::PyObject {} +impl PyConcreteObject 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; diff --git a/tests/test_dunder.rs b/tests/test_dunder.rs index 6bb177cf647..6fecc4c77cf 100755 --- a/tests/test_dunder.rs +++ b/tests/test_dunder.rs @@ -57,7 +57,7 @@ impl<'p> PyIterProtocol for Iterator { Ok(slf.into()) } - fn __next__(mut slf: &mut PyClassShell) -> PyResult> { + fn __next__(slf: &mut PyClassShell) -> PyResult> { Ok(slf.iter.next()) } } diff --git a/tests/test_gc.rs b/tests/test_gc.rs index 81fa8963aa8..cac90a79f40 100644 --- a/tests/test_gc.rs +++ b/tests/test_gc.rs @@ -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 { + let mut init = PyClassInitializer::from_value(SubClassWithDrop { data: None }); + init.get_super().init(BaseClassWithDrop { data: None }); + init } } diff --git a/tests/test_getter_setter.rs b/tests/test_getter_setter.rs index cacf5652c7d..7610e81fa5b 100644 --- a/tests/test_getter_setter.rs +++ b/tests/test_getter_setter.rs @@ -1,7 +1,6 @@ use pyo3::prelude::*; use pyo3::py_run; use pyo3::types::{IntoPyDict, PyList}; -use std::isize; mod common; diff --git a/tests/test_inheritance.rs b/tests/test_inheritance.rs index 462ff138094..14aed197edf 100644 --- a/tests/test_inheritance.rs +++ b/tests/test_inheritance.rs @@ -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 { + 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::(); + let _baseobj = py.get_type::(); let typeobj = py.get_type::(); 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 { + 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::(); + let subclass = py.get_type::(); + 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!!!") + } +} From 6b84401126c3c9137eb5928c4537c5ddbc9cef5a Mon Sep 17 00:00:00 2001 From: kngwyu Date: Sat, 21 Dec 2019 23:21:41 +0900 Subject: [PATCH 06/22] Make it enable to safely inherit native types --- pyo3-derive-backend/src/pyclass.rs | 16 ++++++--- src/conversion.rs | 2 +- src/ffi/code.rs | 7 ---- src/ffi/complexobject.rs | 4 +-- src/ffi/datetime.rs | 0 src/ffi/dictobject.rs | 16 +++++++++ src/ffi/floatobject.rs | 1 - src/ffi/listobject.rs | 8 +---- src/ffi/longobject.rs | 6 ++-- src/ffi/setobject.rs | 24 ++++++++++++- src/ffi/sliceobject.rs | 8 +++++ src/ffi/tupleobject.rs | 1 - src/freelist.rs | 4 +-- src/instance.rs | 6 ++-- src/lib.rs | 2 +- src/pyclass.rs | 54 +++++++++++++++++++++--------- src/python.rs | 2 +- src/type_object.rs | 29 +++++++++------- src/types/any.rs | 10 +++++- src/types/boolobject.rs | 2 +- src/types/bytearray.rs | 2 +- src/types/bytes.rs | 7 +--- src/types/complex.rs | 7 +++- src/types/datetime.rs | 6 +++- src/types/dict.rs | 7 +++- src/types/floatob.rs | 7 +++- src/types/list.rs | 2 +- src/types/mod.rs | 44 +++++++++++++++--------- src/types/module.rs | 2 +- src/types/num.rs | 7 +--- src/types/set.rs | 9 +++-- src/types/slice.rs | 7 +++- src/types/string.rs | 2 +- src/types/tuple.rs | 2 +- src/types/typeobject.rs | 2 +- tests/test_inheritance.rs | 53 +++++++++++++++++++++++++++++ 36 files changed, 264 insertions(+), 104 deletions(-) mode change 100644 => 100755 src/ffi/datetime.rs diff --git a/pyo3-derive-backend/src/pyclass.rs b/pyo3-derive-backend/src/pyclass.rs index 487bbaa3b6a..48c8a8125ec 100644 --- a/pyo3-derive-backend/src/pyclass.rs +++ b/pyo3-derive-backend/src/pyclass.rs @@ -16,6 +16,7 @@ pub struct PyClassArgs { pub name: Option, pub flags: Vec, pub base: syn::TypePath, + pub has_extends: bool, pub module: Option, } @@ -39,8 +40,9 @@ impl Default for PyClassArgs { module: None, // We need the 0 as value for the constant we're later building using quote for when there // are no other flags - flags: vec![parse_quote! {0}], - base: parse_quote! {pyo3::types::PyAny}, + flags: vec![parse_quote! { 0 }], + base: parse_quote! { pyo3::types::PyAny }, + has_extends: false, } } } @@ -89,6 +91,7 @@ impl PyClassArgs { path: exp.path.clone(), qself: None, }; + self.has_extends = true; } _ => { return Err(syn::Error::new_spanned( @@ -318,7 +321,7 @@ fn impl_class( } } } - // TODO: implement dict and weakref + let weakref = if has_weakref { quote! { type WeakRef = pyo3::pyclass_slots::PyClassWeakRefSlot; } } else { @@ -355,6 +358,11 @@ fn impl_class( let base = &attr.base; let flags = &attr.flags; + let extended = if attr.has_extends { + quote! { pyo3::type_flags::EXTENDED } + } else { + quote! { 0 } + }; Ok(quote! { impl pyo3::type_object::PyTypeInfo for #cls { @@ -365,7 +373,7 @@ fn impl_class( const NAME: &'static str = #cls_name; const MODULE: Option<&'static str> = #module; const DESCRIPTION: &'static str = #doc; - const FLAGS: usize = #(#flags)|*; + const FLAGS: usize = #(#flags)|* | #extended; #[inline] unsafe fn type_object() -> &'static mut pyo3::ffi::PyTypeObject { diff --git a/src/conversion.rs b/src/conversion.rs index 66a2bb5fe09..a6f65c53386 100644 --- a/src/conversion.rs +++ b/src/conversion.rs @@ -3,7 +3,7 @@ //! Conversions between various states of rust and python types and their wrappers. use crate::err::{self, PyDowncastError, PyResult}; use crate::object::PyObject; -use crate::type_object::{PyConcreteObject, PyTypeInfo}; +use crate::type_object::{PyObjectLayout, PyTypeInfo}; use crate::types::PyAny; use crate::types::PyTuple; use crate::{ffi, gil, Py, Python}; diff --git a/src/ffi/code.rs b/src/ffi/code.rs index 310cb307c4b..05609269007 100644 --- a/src/ffi/code.rs +++ b/src/ffi/code.rs @@ -31,13 +31,6 @@ pub struct PyCodeObject { pub co_extra: *mut c_void, } -impl Default for PyCodeObject { - #[inline] - fn default() -> Self { - unsafe { ::std::mem::zeroed() } - } -} - /* Masks for co_flags */ pub const CO_OPTIMIZED: c_int = 0x0001; pub const CO_NEWLOCALS: c_int = 0x0002; diff --git a/src/ffi/complexobject.rs b/src/ffi/complexobject.rs index 024c93f956a..884d9eee89b 100644 --- a/src/ffi/complexobject.rs +++ b/src/ffi/complexobject.rs @@ -27,7 +27,6 @@ extern "C" { pub fn PyComplex_ImagAsDouble(op: *mut PyObject) -> c_double; } -#[cfg(not(Py_LIMITED_API))] #[repr(C)] #[derive(Copy, Clone)] pub struct Py_complex { @@ -35,11 +34,10 @@ pub struct Py_complex { pub imag: c_double, } -#[cfg(not(Py_LIMITED_API))] #[repr(C)] #[derive(Copy, Clone)] pub struct PyComplexObject { - _ob_base: PyObject, + pub ob_base: PyObject, pub cval: Py_complex, } diff --git a/src/ffi/datetime.rs b/src/ffi/datetime.rs old mode 100644 new mode 100755 diff --git a/src/ffi/dictobject.rs b/src/ffi/dictobject.rs index 6982f8f54eb..1c8273af815 100644 --- a/src/ffi/dictobject.rs +++ b/src/ffi/dictobject.rs @@ -13,6 +13,22 @@ extern "C" { pub static mut PyDictValues_Type: PyTypeObject; } +#[repr(C)] +pub struct PyDictKeysObject { + _unused: [u8; 0], +} + +#[repr(C)] +#[derive(Debug)] +pub struct PyDictObject { + pub ob_base: PyObject, + pub ma_used: Py_ssize_t, + #[cfg(Py_3_6)] + pub ma_version_tag: u64, + pub ma_keys: *mut PyDictKeysObject, + pub ma_values: *mut *mut PyObject, +} + #[inline] pub unsafe fn PyDict_Check(op: *mut PyObject) -> c_int { PyType_FastSubclass(Py_TYPE(op), Py_TPFLAGS_DICT_SUBCLASS) diff --git a/src/ffi/floatobject.rs b/src/ffi/floatobject.rs index f5d8edd30f1..7ea4f223795 100644 --- a/src/ffi/floatobject.rs +++ b/src/ffi/floatobject.rs @@ -1,7 +1,6 @@ use crate::ffi::object::*; use std::os::raw::{c_double, c_int}; -#[cfg(not(Py_LIMITED_API))] #[repr(C)] pub struct PyFloatObject { pub ob_base: PyObject, diff --git a/src/ffi/listobject.rs b/src/ffi/listobject.rs index c504ae4bbc1..9b8fa7e69d9 100644 --- a/src/ffi/listobject.rs +++ b/src/ffi/listobject.rs @@ -5,13 +5,7 @@ use std::os::raw::c_int; #[repr(C)] #[derive(Copy, Clone)] pub struct PyListObject { - #[cfg(py_sys_config = "Py_TRACE_REFS")] - pub _ob_next: *mut PyObject, - #[cfg(py_sys_config = "Py_TRACE_REFS")] - pub _ob_prev: *mut PyObject, - pub ob_refcnt: Py_ssize_t, - pub ob_type: *mut PyTypeObject, - pub ob_size: Py_ssize_t, + pub ob_base: PyVarObject, pub ob_item: *mut *mut PyObject, pub allocated: Py_ssize_t, } diff --git a/src/ffi/longobject.rs b/src/ffi/longobject.rs index 45bcebfd8ef..ba260b6b527 100644 --- a/src/ffi/longobject.rs +++ b/src/ffi/longobject.rs @@ -6,8 +6,10 @@ use std::os::raw::{ }; /// This is an opaque type in the python c api -#[repr(transparent)] -pub struct PyLongObject(*mut c_void); +#[repr(C)] +pub struct PyLongObject { + _unused: [u8; 0], +} #[cfg_attr(windows, link(name = "pythonXY"))] extern "C" { diff --git a/src/ffi/setobject.rs b/src/ffi/setobject.rs index d1e4dbd703e..dc241e8775b 100644 --- a/src/ffi/setobject.rs +++ b/src/ffi/setobject.rs @@ -1,5 +1,5 @@ use crate::ffi::object::*; -use crate::ffi::pyport::Py_ssize_t; +use crate::ffi::pyport::{Py_hash_t, Py_ssize_t}; use std::os::raw::c_int; #[cfg_attr(windows, link(name = "pythonXY"))] @@ -11,6 +11,28 @@ extern "C" { pub static mut PySetIter_Type: PyTypeObject; } +pub const PySet_MINSIZE: usize = 8; + +#[repr(C)] +#[derive(Debug)] +pub struct setentry { + pub key: *mut PyObject, + pub hash: Py_hash_t, +} + +#[repr(C)] +pub struct PySetObject { + pub ob_base: PyObject, + pub fill: Py_ssize_t, + pub used: Py_ssize_t, + pub mask: Py_ssize_t, + pub table: *mut setentry, + pub hash: Py_hash_t, + pub finger: Py_ssize_t, + pub smalltable: [setentry; PySet_MINSIZE], + pub weakreflist: *mut PyObject, +} + #[inline] #[cfg_attr(PyPy, link_name = "PyPyFrozenSet_CheckExact")] pub unsafe fn PyFrozenSet_CheckExact(ob: *mut PyObject) -> c_int { diff --git a/src/ffi/sliceobject.rs b/src/ffi/sliceobject.rs index 9068a887203..259caa388dd 100644 --- a/src/ffi/sliceobject.rs +++ b/src/ffi/sliceobject.rs @@ -26,6 +26,14 @@ pub unsafe fn PySlice_Check(op: *mut PyObject) -> c_int { (Py_TYPE(op) == &mut PySlice_Type) as c_int } +#[repr(C)] +pub struct PySliceObject { + pub ob_base: PyObject, + pub start: *mut PyObject, + pub stop: *mut PyObject, + pub step: *mut PyObject, +} + #[cfg_attr(windows, link(name = "pythonXY"))] extern "C" { #[cfg_attr(PyPy, link_name = "PyPySlice_New")] diff --git a/src/ffi/tupleobject.rs b/src/ffi/tupleobject.rs index 191016a6e4f..3d46347cfd1 100644 --- a/src/ffi/tupleobject.rs +++ b/src/ffi/tupleobject.rs @@ -3,7 +3,6 @@ use crate::ffi::pyport::Py_ssize_t; use std::os::raw::c_int; #[repr(C)] -#[cfg(not(Py_LIMITED_API))] pub struct PyTupleObject { pub ob_base: PyVarObject, pub ob_item: [*mut PyObject; 1], diff --git a/src/freelist.rs b/src/freelist.rs index 113088029f2..cb7113eb3a1 100644 --- a/src/freelist.rs +++ b/src/freelist.rs @@ -4,7 +4,7 @@ use crate::ffi; use crate::pyclass::{tp_free_fallback, PyClassAlloc}; -use crate::type_object::{PyConcreteObject, PyTypeInfo}; +use crate::type_object::{PyObjectLayout, PyTypeInfo}; use crate::Python; use std::mem; use std::os::raw::c_void; @@ -77,7 +77,7 @@ where ffi::PyObject_Init(obj, ::type_object()); obj as _ } else { - ffi::PyType_GenericAlloc(::type_object(), 0) as _ + crate::pyclass::default_alloc::() as _ } } diff --git a/src/instance.rs b/src/instance.rs index b91f278683e..697e4484982 100644 --- a/src/instance.rs +++ b/src/instance.rs @@ -4,7 +4,7 @@ use crate::gil; use crate::object::PyObject; use crate::objectprotocol::ObjectProtocol; use crate::pyclass::{IntoInitializer, PyClass, PyClassShell}; -use crate::type_object::{PyConcreteObject, PyTypeInfo}; +use crate::type_object::{PyObjectLayout, PyTypeInfo}; use crate::types::PyAny; use crate::{ffi, IntoPy}; use crate::{AsPyPointer, FromPyObject, IntoPyPointer, Python, ToPyObject}; @@ -37,7 +37,9 @@ impl Py { /// Create new instance of T and move it under python management pub fn new(py: Python, value: impl IntoInitializer) -> PyResult> where - T: PyClass + PyTypeInfo>, + T: PyClass, + ::ConcreteLayout: + crate::type_object::PyObjectSizedLayout, { let initializer = value.into_initializer()?; let obj = unsafe { initializer.create_shell(py)? }; diff --git a/src/lib.rs b/src/lib.rs index 4330e0eb6ea..ce549f96194 100755 --- a/src/lib.rs +++ b/src/lib.rs @@ -129,7 +129,7 @@ pub use crate::object::PyObject; pub use crate::objectprotocol::ObjectProtocol; pub use crate::pyclass::{PyClass, PyClassInitializer, PyClassShell}; pub use crate::python::{prepare_freethreaded_python, Python}; -pub use crate::type_object::{type_flags, PyConcreteObject, PyTypeInfo}; +pub use crate::type_object::{type_flags, PyTypeInfo}; // Re-exported for wrap_function #[doc(hidden)] diff --git a/src/pyclass.rs b/src/pyclass.rs index 074d13f7b5f..92203846690 100644 --- a/src/pyclass.rs +++ b/src/pyclass.rs @@ -3,7 +3,7 @@ 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::type_object::{type_flags, PyObjectLayout, PyObjectSizedLayout, PyTypeObject}; use crate::types::PyAny; use crate::{class, ffi, gil, PyErr, PyObject, PyResult, PyTypeInfo, Python}; use std::ffi::CString; @@ -11,12 +11,25 @@ use std::mem::ManuallyDrop; use std::os::raw::c_void; use std::ptr::{self, NonNull}; +#[inline] +pub(crate) unsafe fn default_alloc() -> *mut ffi::PyObject { + let tp_ptr = T::type_object(); + if T::FLAGS & type_flags::EXTENDED != 0 + && ::ConcreteLayout::IS_NATIVE_TYPE + { + let base_tp_ptr = ::type_object(); + if let Some(base_new) = (*base_tp_ptr).tp_new { + return base_new(tp_ptr, ptr::null_mut(), ptr::null_mut()); + } + } + let alloc = (*tp_ptr).tp_alloc.unwrap_or(ffi::PyType_GenericAlloc); + alloc(tp_ptr, 0) +} + /// A trait that enables custome alloc/dealloc implementations for pyclasses. pub trait PyClassAlloc: PyTypeInfo + Sized { unsafe fn alloc(_py: Python) -> *mut Self::ConcreteLayout { - let tp_ptr = Self::type_object(); - let alloc = (*tp_ptr).tp_alloc.unwrap_or(ffi::PyType_GenericAlloc); - alloc(tp_ptr, 0) as _ + default_alloc::() as _ } unsafe fn dealloc(py: Python, self_: *mut Self::ConcreteLayout) { @@ -49,7 +62,9 @@ pub unsafe fn tp_free_fallback(obj: *mut ffi::PyObject) { } } -pub trait PyClass: PyTypeInfo + Sized + PyClassAlloc + PyMethodsProtocol { +pub trait PyClass: + PyTypeInfo> + Sized + PyClassAlloc + PyMethodsProtocol +{ type Dict: PyClassDict; type WeakRef: PyClassWeakRef; } @@ -88,7 +103,8 @@ pub struct PyClassShell { impl PyClassShell { pub fn new_ref(py: Python, value: impl IntoInitializer) -> PyResult<&Self> where - T: PyTypeInfo, + ::ConcreteLayout: + crate::type_object::PyObjectSizedLayout, { unsafe { let initializer = value.into_initializer()?; @@ -99,7 +115,8 @@ impl PyClassShell { pub fn new_mut(py: Python, value: impl IntoInitializer) -> PyResult<&mut Self> where - T: PyTypeInfo, + ::ConcreteLayout: + crate::type_object::PyObjectSizedLayout, { unsafe { let initializer = value.into_initializer()?; @@ -109,7 +126,11 @@ impl PyClassShell { } #[doc(hidden)] - unsafe fn new(py: Python) -> PyResult<*mut Self> { + unsafe fn new(py: Python) -> PyResult<*mut Self> + where + ::ConcreteLayout: + crate::type_object::PyObjectSizedLayout, + { ::init_type(); T::init_type(); let base = T::alloc(py); @@ -123,8 +144,12 @@ impl PyClassShell { } } -impl PyConcreteObject for PyClassShell { +impl PyObjectLayout for PyClassShell { const NEED_INIT: bool = std::mem::size_of::() != 0; + const IS_NATIVE_TYPE: bool = false; + fn get_super(&mut self) -> Option<&mut ::ConcreteLayout> { + Some(&mut self.ob_base) + } unsafe fn internal_ref_cast(obj: &PyAny) -> &T { let shell = obj.as_ptr() as *const PyClassShell; &(*shell).pyclass @@ -142,11 +167,10 @@ impl PyConcreteObject for PyClassShell { unsafe fn py_init(&mut self, value: T) { self.pyclass = ManuallyDrop::new(value); } - fn get_super(&mut self) -> Option<&mut ::ConcreteLayout> { - Some(&mut self.ob_base) - } } +impl PyObjectSizedLayout for PyClassShell {} + impl AsPyPointer for PyClassShell { fn as_ptr(&self) -> *mut ffi::PyObject { (self as *const _) as *mut _ @@ -208,8 +232,6 @@ where } /// An initializer for `PyClassShell`. -/// -/// **NOTE** If pub struct PyClassInitializer { init: Option, super_init: Option<*mut PyClassInitializer>, @@ -273,7 +295,9 @@ impl PyClassInitializer { pub unsafe fn create_shell(self, py: Python) -> PyResult<*mut PyClassShell> where - T: PyClass + PyTypeInfo>, + T: PyClass, + ::ConcreteLayout: + crate::type_object::PyObjectSizedLayout, { let shell = PyClassShell::new(py)?; self.init_class(&mut *shell)?; diff --git a/src/python.rs b/src/python.rs index 00b9a36e40e..e5b31ae8f31 100644 --- a/src/python.rs +++ b/src/python.rs @@ -7,7 +7,7 @@ use crate::ffi; use crate::gil::{self, GILGuard}; use crate::instance::AsPyRef; use crate::object::PyObject; -use crate::type_object::{PyConcreteObject, PyTypeInfo, PyTypeObject}; +use crate::type_object::{PyObjectLayout, PyTypeInfo, PyTypeObject}; use crate::types::{PyAny, PyDict, PyModule, PyType}; use crate::AsPyPointer; use crate::{FromPyPointer, IntoPyPointer, PyTryFrom}; diff --git a/src/type_object.rs b/src/type_object.rs index 39768602019..f4350a8661f 100644 --- a/src/type_object.rs +++ b/src/type_object.rs @@ -2,32 +2,32 @@ //! Python type object information -use crate::ffi; use crate::instance::Py; -use crate::instance::PyNativeType; -use crate::types::PyAny; -use crate::types::PyType; -use crate::AsPyPointer; -use crate::Python; +use crate::types::{PyAny, PyType}; +use crate::{ffi, AsPyPointer, Python}; use std::ptr::NonNull; /// TODO: write document -pub trait PyConcreteObject: Sized { +pub trait PyObjectLayout { const NEED_INIT: bool = false; + const IS_NATIVE_TYPE: bool = true; + + fn get_super(&mut self) -> Option<&mut ::ConcreteLayout> { + None + } + 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 ::ConcreteLayout> { - None - } + unsafe fn py_drop(&mut self, _py: Python) {} } -impl PyConcreteObject for ffi::PyObject {} +pub trait PyObjectSizedLayout: PyObjectLayout + Sized {} /// Our custom type flags pub mod type_flags { @@ -42,6 +42,9 @@ pub mod type_flags { /// The instances of this type have a dictionary containing instance variables pub const DICT: usize = 1 << 3; + + /// The class declared by #[pyclass(extends=~)] + pub const EXTENDED: usize = 1 << 4; } /// Python type information. @@ -65,7 +68,7 @@ pub trait PyTypeInfo: Sized { type BaseType: PyTypeInfo + PyTypeObject; /// Layout - type ConcreteLayout: PyConcreteObject; + type ConcreteLayout: PyObjectLayout; /// PyTypeObject instance for this type, which might still need to /// be initialized diff --git a/src/types/any.rs b/src/types/any.rs index b299c6c95cf..f17adc3f04d 100644 --- a/src/types/any.rs +++ b/src/types/any.rs @@ -23,8 +23,16 @@ use crate::{ffi, PyObject}; /// ``` #[repr(transparent)] pub struct PyAny(PyObject, Unsendable); +impl crate::type_object::PyObjectLayout for ffi::PyObject {} +impl crate::type_object::PyObjectSizedLayout for ffi::PyObject {} pyobject_native_type_named!(PyAny); -pyobject_native_type_convert!(PyAny, ffi::PyBaseObject_Type, ffi::PyObject_Check); +pyobject_native_type_convert!( + PyAny, + ffi::PyObject, + ffi::PyBaseObject_Type, + Some("builtins"), + ffi::PyObject_Check +); impl PyAny { pub fn downcast_ref(&self) -> Result<&T, PyDowncastError> diff --git a/src/types/boolobject.rs b/src/types/boolobject.rs index 7204090e61c..3f88a2a2e19 100644 --- a/src/types/boolobject.rs +++ b/src/types/boolobject.rs @@ -13,7 +13,7 @@ use crate::{PyTryFrom, ToPyObject}; #[repr(transparent)] pub struct PyBool(PyObject, Unsendable); -pyobject_native_type!(PyBool, ffi::PyBool_Type, ffi::PyBool_Check); +pyobject_native_type!(PyBool, ffi::PyObject, ffi::PyBool_Type, ffi::PyBool_Check); impl PyBool { /// Depending on `val`, returns `py.True()` or `py.False()`. diff --git a/src/types/bytearray.rs b/src/types/bytearray.rs index bdc7455102a..8f08a2ecbb1 100644 --- a/src/types/bytearray.rs +++ b/src/types/bytearray.rs @@ -13,7 +13,7 @@ use std::slice; #[repr(transparent)] pub struct PyByteArray(PyObject, Unsendable); -pyobject_native_type!(PyByteArray, ffi::PyByteArray_Type, ffi::PyByteArray_Check); +pyobject_native_var_type!(PyByteArray, ffi::PyByteArray_Type, ffi::PyByteArray_Check); impl PyByteArray { /// Creates a new Python bytearray object. diff --git a/src/types/bytes.rs b/src/types/bytes.rs index 2734d6912ee..8b26b82ed26 100644 --- a/src/types/bytes.rs +++ b/src/types/bytes.rs @@ -18,12 +18,7 @@ use std::str; #[repr(transparent)] pub struct PyBytes(PyObject, Unsendable); -pyobject_native_type!( - PyBytes, - ffi::PyBytes_Type, - Some("builtins"), - ffi::PyBytes_Check -); +pyobject_native_var_type!(PyBytes, ffi::PyBytes_Type, ffi::PyBytes_Check); impl PyBytes { /// Creates a new Python byte string object. diff --git a/src/types/complex.rs b/src/types/complex.rs index 4eef6792a13..40e213ca2a2 100644 --- a/src/types/complex.rs +++ b/src/types/complex.rs @@ -13,7 +13,12 @@ use std::os::raw::c_double; #[repr(transparent)] pub struct PyComplex(PyObject, Unsendable); -pyobject_native_type!(PyComplex, ffi::PyComplex_Type, ffi::PyComplex_Check); +pyobject_native_type!( + PyComplex, + ffi::PyComplexObject, + ffi::PyComplex_Type, + ffi::PyComplex_Check +); impl PyComplex { /// Creates a new Python `PyComplex` object, from its real and imaginary values. diff --git a/src/types/datetime.rs b/src/types/datetime.rs index 15f898950d7..9ec79669c81 100644 --- a/src/types/datetime.rs +++ b/src/types/datetime.rs @@ -69,6 +69,7 @@ pub trait PyTimeAccess { pub struct PyDate(PyObject, Unsendable); pyobject_native_type!( PyDate, + crate::ffi::PyDateTime_Date, *PyDateTimeAPI.DateType, Some("datetime"), PyDate_Check @@ -124,6 +125,7 @@ impl PyDateAccess for PyDate { pub struct PyDateTime(PyObject, Unsendable); pyobject_native_type!( PyDateTime, + crate::ffi::PyDateTime_DateTime, *PyDateTimeAPI.DateTimeType, Some("datetime"), PyDateTime_Check @@ -233,6 +235,7 @@ impl PyTimeAccess for PyDateTime { pub struct PyTime(PyObject, Unsendable); pyobject_native_type!( PyTime, + crate::ffi::PyDateTime_Time, *PyDateTimeAPI.TimeType, Some("datetime"), PyTime_Check @@ -315,7 +318,7 @@ impl PyTimeAccess for PyTime { /// /// This is an abstract base class and should not be constructed directly. pub struct PyTzInfo(PyObject, Unsendable); -pyobject_native_type!( +pyobject_native_var_type!( PyTzInfo, *PyDateTimeAPI.TZInfoType, Some("datetime"), @@ -326,6 +329,7 @@ pyobject_native_type!( pub struct PyDelta(PyObject, Unsendable); pyobject_native_type!( PyDelta, + crate::ffi::PyDateTime_Delta, *PyDateTimeAPI.DeltaType, Some("datetime"), PyDelta_Check diff --git a/src/types/dict.rs b/src/types/dict.rs index 8740b7e5e4f..3e4883245ba 100644 --- a/src/types/dict.rs +++ b/src/types/dict.rs @@ -17,7 +17,12 @@ use std::{cmp, collections, hash}; #[repr(transparent)] pub struct PyDict(PyObject, Unsendable); -pyobject_native_type!(PyDict, ffi::PyDict_Type, ffi::PyDict_Check); +pyobject_native_type!( + PyDict, + ffi::PyDictObject, + ffi::PyDict_Type, + ffi::PyDict_Check +); impl PyDict { /// Creates a new empty dictionary. diff --git a/src/types/floatob.rs b/src/types/floatob.rs index a27bfb5b73c..4f7d464b794 100644 --- a/src/types/floatob.rs +++ b/src/types/floatob.rs @@ -25,7 +25,12 @@ use std::os::raw::c_double; #[repr(transparent)] pub struct PyFloat(PyObject, Unsendable); -pyobject_native_type!(PyFloat, ffi::PyFloat_Type, ffi::PyFloat_Check); +pyobject_native_type!( + PyFloat, + ffi::PyFloatObject, + ffi::PyFloat_Type, + ffi::PyFloat_Check +); impl PyFloat { /// Creates a new Python `float` object. diff --git a/src/types/list.rs b/src/types/list.rs index 9392482d2cd..00bdc7f9fef 100644 --- a/src/types/list.rs +++ b/src/types/list.rs @@ -17,7 +17,7 @@ use crate::{ToBorrowedObject, ToPyObject}; #[repr(transparent)] pub struct PyList(PyObject, Unsendable); -pyobject_native_type!(PyList, ffi::PyList_Type, ffi::PyList_Check); +pyobject_native_var_type!(PyList, ffi::PyList_Type, ffi::PyList_Check); impl PyList { /// Construct a new list with the given elements. diff --git a/src/types/mod.rs b/src/types/mod.rs index 48eb96cb98d..8a581cf117c 100644 --- a/src/types/mod.rs +++ b/src/types/mod.rs @@ -79,10 +79,12 @@ macro_rules! pyobject_native_type_named ( ); #[macro_export] -macro_rules! pyobject_native_type ( - ($name: ty, $typeobject: expr, $module: expr, $checkfunction: path $(,$type_param: ident)*) => { +macro_rules! pyobject_native_type { + ($name: ty, $layout: path, $typeobject: expr, $module: expr, $checkfunction: path $(,$type_param: ident)*) => { + impl $crate::type_object::PyObjectLayout<$name> for $layout {} + impl $crate::type_object::PyObjectSizedLayout<$name> for $layout {} pyobject_native_type_named!($name $(,$type_param)*); - pyobject_native_type_convert!($name, ffi::PyObject, $typeobject, $module, $checkfunction $(,$type_param)*); + pyobject_native_type_convert!($name, $layout, $typeobject, $module, $checkfunction $(,$type_param)*); impl<'a, $($type_param,)*> ::std::convert::From<&'a $name> for &'a $crate::types::PyAny { fn from(ob: &'a $name) -> Self { @@ -90,16 +92,36 @@ macro_rules! pyobject_native_type ( } } }; - ($name: ty, $typeobject: expr, $checkfunction: path $(,$type_param: ident)*) => { + ($name: ty, $layout: path, $typeobject: expr, $checkfunction: path $(,$type_param: ident)*) => { pyobject_native_type! { + $name, $layout, $typeobject, Some("builtins"), $checkfunction $(,$type_param)* + } + }; +} + +#[macro_export] +macro_rules! pyobject_native_var_type { + ($name: ty, $typeobject: expr, $module: expr, $checkfunction: path $(,$type_param: ident)*) => { + impl $crate::type_object::PyObjectLayout<$name> for $crate::ffi::PyObject {} + pyobject_native_type_named!($name $(,$type_param)*); + pyobject_native_type_convert!($name, $crate::ffi::PyObject, + $typeobject, $module, $checkfunction $(,$type_param)*); + impl<'a, $($type_param,)*> ::std::convert::From<&'a $name> for &'a $crate::types::PyAny { + fn from(ob: &'a $name) -> Self { + unsafe{&*(ob as *const $name as *const $crate::types::PyAny)} + } + } + }; + ($name: ty, $typeobject: expr, $checkfunction: path $(,$type_param: ident)*) => { + pyobject_native_var_type! { $name, $typeobject, Some("builtins"), $checkfunction $(,$type_param)* } }; -); +} #[macro_export] macro_rules! pyobject_native_type_convert( - ($name: ty, $layout: ty, $typeobject: expr, + ($name: ty, $layout: path, $typeobject: expr, $module: expr, $checkfunction: path $(,$type_param: ident)*) => { impl<$($type_param,)*> $crate::type_object::PyTypeInfo for $name { type Type = (); @@ -161,16 +183,6 @@ macro_rules! pyobject_native_type_convert( } } }; - ($name: ty, $typeobject: expr, $checkfunction: path $(,$type_param: ident)*) => { - pyobject_native_type_convert! { - $name, ffi::PyObject, $typeobject, Some("builtins"), $checkfunction $(,$type_param)* - } - }; - // ($name: ty, $layout: ty, $typeobject: expr, $checkfunction: path $(,$type_param: ident)*) => { - // pyobject_native_type_convert! { - // $name, $layout, $typeobject, Some("builtins"), $checkfunction $(,$type_param)* - // } - // }; ); mod any; diff --git a/src/types/module.rs b/src/types/module.rs index 40aec2b02bb..d152c5291ab 100644 --- a/src/types/module.rs +++ b/src/types/module.rs @@ -22,7 +22,7 @@ use std::str; #[repr(transparent)] pub struct PyModule(PyObject, Unsendable); -pyobject_native_type!(PyModule, ffi::PyModule_Type, ffi::PyModule_Check); +pyobject_native_var_type!(PyModule, ffi::PyModule_Type, ffi::PyModule_Check); impl PyModule { /// Create a new module object with the `__name__` attribute set to name. diff --git a/src/types/num.rs b/src/types/num.rs index ba191f79bbd..b964af11c9b 100644 --- a/src/types/num.rs +++ b/src/types/num.rs @@ -120,12 +120,7 @@ macro_rules! int_convert_128 { #[repr(transparent)] pub struct PyLong(PyObject, Unsendable); -pyobject_native_type!( - PyLong, - ffi::PyLong_Type, - Some("builtins"), - ffi::PyLong_Check -); +pyobject_native_var_type!(PyLong, ffi::PyLong_Type, ffi::PyLong_Check); macro_rules! int_fits_c_long { ($rust_type:ty) => { diff --git a/src/types/set.rs b/src/types/set.rs index c62ef734e32..db88eb168d8 100644 --- a/src/types/set.rs +++ b/src/types/set.rs @@ -20,8 +20,13 @@ pub struct PySet(PyObject, Unsendable); #[repr(transparent)] pub struct PyFrozenSet(PyObject, Unsendable); -pyobject_native_type!(PySet, ffi::PySet_Type, Some("builtins"), ffi::PySet_Check); -pyobject_native_type!(PyFrozenSet, ffi::PyFrozenSet_Type, ffi::PyFrozenSet_Check); +pyobject_native_type!(PySet, ffi::PySetObject, ffi::PySet_Type, ffi::PySet_Check); +pyobject_native_type!( + PyFrozenSet, + ffi::PySetObject, + ffi::PyFrozenSet_Type, + ffi::PyFrozenSet_Check +); impl PySet { /// Creates a new set. diff --git a/src/types/slice.rs b/src/types/slice.rs index d618a2a903b..fbea20edda3 100644 --- a/src/types/slice.rs +++ b/src/types/slice.rs @@ -15,7 +15,12 @@ use std::os::raw::c_long; #[repr(transparent)] pub struct PySlice(PyObject, Unsendable); -pyobject_native_type!(PySlice, ffi::PySlice_Type, ffi::PySlice_Check); +pyobject_native_type!( + PySlice, + ffi::PySliceObject, + ffi::PySlice_Type, + ffi::PySlice_Check +); /// Represents a Python `slice` indices pub struct PySliceIndices { diff --git a/src/types/string.rs b/src/types/string.rs index ad216a7e810..b355e27ebb9 100644 --- a/src/types/string.rs +++ b/src/types/string.rs @@ -24,7 +24,7 @@ use std::str; #[repr(transparent)] pub struct PyString(PyObject, Unsendable); -pyobject_native_type!(PyString, ffi::PyUnicode_Type, ffi::PyUnicode_Check); +pyobject_native_var_type!(PyString, ffi::PyUnicode_Type, ffi::PyUnicode_Check); impl PyString { /// Creates a new Python string object. diff --git a/src/types/tuple.rs b/src/types/tuple.rs index e2c7eb450f4..2626c71f732 100644 --- a/src/types/tuple.rs +++ b/src/types/tuple.rs @@ -18,7 +18,7 @@ use std::slice; #[repr(transparent)] pub struct PyTuple(PyObject, Unsendable); -pyobject_native_type!(PyTuple, ffi::PyTuple_Type, ffi::PyTuple_Check); +pyobject_native_var_type!(PyTuple, ffi::PyTuple_Type, ffi::PyTuple_Check); impl PyTuple { /// Construct a new tuple with the given elements. diff --git a/src/types/typeobject.rs b/src/types/typeobject.rs index f079595cde9..ae18a3b1d48 100644 --- a/src/types/typeobject.rs +++ b/src/types/typeobject.rs @@ -17,7 +17,7 @@ use std::ffi::CStr; #[repr(transparent)] pub struct PyType(PyObject, Unsendable); -pyobject_native_type!(PyType, ffi::PyType_Type, ffi::PyType_Check); +pyobject_native_var_type!(PyType, ffi::PyType_Type, ffi::PyType_Check); impl PyType { #[inline] diff --git a/tests/test_inheritance.rs b/tests/test_inheritance.rs index 14aed197edf..c3a3eb8135f 100644 --- a/tests/test_inheritance.rs +++ b/tests/test_inheritance.rs @@ -4,6 +4,7 @@ use pyo3::py_run; #[cfg(feature = "unsound-subclass")] use pyo3::types::IntoPyDict; +use pyo3::types::{PyDict, PySet}; mod common; #[pyclass] @@ -104,3 +105,55 @@ fn uninit_baseclass_returns_err() { panic!("Uninitialized class detection failed!!!") } } + +#[pyclass(extends=PySet)] +struct SetWithName { + #[pyo3(get(name))] + _name: &'static str, +} + +#[pymethods] +impl SetWithName { + #[new] + fn new() -> Self { + SetWithName { _name: "Hello :)" } + } +} + +#[test] +fn inherit_set() { + let gil = Python::acquire_gil(); + let py = gil.python(); + let set_sub = pyo3::pyclass::PyClassShell::new_ref(py, SetWithName::new()).unwrap(); + py_run!( + py, + set_sub, + r#"set_sub.add(10); assert list(set_sub) == [10]; assert set_sub._name == "Hello :)""# + ); +} + +#[pyclass(extends=PyDict)] +struct DictWithName { + #[pyo3(get(name))] + _name: &'static str, +} + +#[pymethods] +impl DictWithName { + #[new] + fn new() -> Self { + DictWithName { _name: "Hello :)" } + } +} + +#[test] +fn inherit_dict() { + let gil = Python::acquire_gil(); + let py = gil.python(); + let dict_sub = pyo3::pyclass::PyClassShell::new_ref(py, DictWithName::new()).unwrap(); + py_run!( + py, + dict_sub, + r#"dict_sub[0] = 1; assert dict_sub[0] == 1; assert dict_sub._name == "Hello :)""# + ); +} From efa16a6dc79028f8ee281191d5b2b96f84b6eb1b Mon Sep 17 00:00:00 2001 From: kngwyu Date: Sun, 22 Dec 2019 19:41:25 +0900 Subject: [PATCH 07/22] Fix documents accompanied by PyClassShell --- guide/src/class.md | 184 ++++++++++++++++++++++------------ guide/src/function.md | 4 +- guide/src/python_from_rust.md | 5 +- guide/src/rust_cpython.md | 11 +- src/prelude.rs | 2 +- src/pyclass.rs | 105 +++++++++++++++++-- src/type_object.rs | 2 +- 7 files changed, 226 insertions(+), 87 deletions(-) diff --git a/guide/src/class.md b/guide/src/class.md index 1b4e7ba9773..a08300dc3bc 100644 --- a/guide/src/class.md +++ b/guide/src/class.md @@ -15,30 +15,95 @@ struct MyClass { } ``` -The above example generates implementations for `PyTypeInfo` and `PyTypeObject` for `MyClass`. +The above example generates implementations for `PyTypeInfo`, `PyTypeObject` +and `PyClass` for `MyClass`. -## Get Python objects from `pyclass` +Specifically, the following implementation is generated. + +```rust +use pyo3::prelude::*; +use pyo3::{PyClassShell, PyTypeInfo}; + +struct MyClass { + num: i32, + debug: bool, +} + +impl pyo3::pyclass::PyClassAlloc for MyClass {} + +impl PyTypeInfo for MyClass { + type Type = MyClass; + type BaseType = pyo3::types::PyAny; + type ConcreteLayout = PyClassShell; + + const NAME: &'static str = "MyClass"; + const MODULE: Option<&'static str> = None; + const DESCRIPTION: &'static str = "This is a demo class"; + const FLAGS: usize = 0; + + #[inline] + unsafe fn type_object() -> &'static mut pyo3::ffi::PyTypeObject { + static mut TYPE_OBJECT: pyo3::ffi::PyTypeObject = pyo3::ffi::PyTypeObject_INIT; + &mut TYPE_OBJECT + } +} -You can use `pyclass`es like normal rust structs. +impl pyo3::pyclass::PyClass for MyClass { + type Dict = pyo3::pyclass_slots::PyClassDummySlot; + type WeakRef = pyo3::pyclass_slots::PyClassDummySlot; +} -However, if instantiated normally, you can't treat `pyclass`es as Python objects. +impl pyo3::IntoPy for MyClass { + fn into_py(self, py: pyo3::Python) -> pyo3::PyObject { + pyo3::IntoPy::into_py(pyo3::Py::new(py, self).unwrap(), py) + } +} -To get a Python object which includes `pyclass`, we have to use some special methods. +pub struct MyClassGeneratedPyo3Inventory { + methods: &'static [pyo3::class::PyMethodDefType], +} -### `PyRef` +impl pyo3::class::methods::PyMethodsInventory for MyClassGeneratedPyo3Inventory { + fn new(methods: &'static [pyo3::class::PyMethodDefType]) -> Self { + Self { methods } + } -`PyRef` is a special reference, which ensures that the referred struct is a part of -a Python object, and you are also holding the GIL. + fn get_methods(&self) -> &'static [pyo3::class::PyMethodDefType] { + self.methods + } +} + +impl pyo3::class::methods::PyMethodsInventoryDispatch for MyClass { + type InventoryType = MyClassGeneratedPyo3Inventory; +} + +pyo3::inventory::collect!(MyClassGeneratedPyo3Inventory); + +# let gil = Python::acquire_gil(); +# let py = gil.python(); +# let cls = py.get_type::(); +# pyo3::py_run!(py, cls, "assert cls.__name__ == 'MyClass'") +``` -You can get an instance of `PyRef` by `PyRef::new`, which does 3 things: -1. Allocates a Python object in the Python heap -2. Copies the Rust struct into the Python object -3. Returns a reference to it -You can use `PyRef` just like `&T`, because it implements `Deref`. +## Get Python objects from `pyclass` +You sometimes need to convert your pyclass into Python object in Rust code(e.g., for testing it). + +You can use `PyClassShell` or `Py` for this purpose. + +### `PyClassShell` + +`PyClassShell` is a special type made for `PyClass`. + +It represents the actual layout of pyclass in Python world and used to extract it from +Python object pointer. + +To make pyclass in Python heap, you can use `PyClassShell::new_ref` or `PyClassShell::new_mut`. + ```rust # use pyo3::prelude::*; # use pyo3::types::PyDict; +# use pyo3::PyClassShell; #[pyclass] struct MyClass { num: i32, @@ -46,26 +111,15 @@ struct MyClass { } let gil = Python::acquire_gil(); let py = gil.python(); -let obj = PyRef::new(py, MyClass { num: 3, debug: true }).unwrap(); +let obj = PyClassShell::new_ref(py, MyClass { num: 3, debug: true }).unwrap(); +// You can use deref assert_eq!(obj.num, 3); let dict = PyDict::new(py); -// You can treat a `PyRef` as a Python object +// You can treat a `&PyClassShell` as a normal Python object dict.set_item("obj", obj).unwrap(); -``` -### `PyRefMut` - -`PyRefMut` is a mutable version of `PyRef`. -```rust -# use pyo3::prelude::*; -#[pyclass] -struct MyClass { - num: i32, - debug: bool, -} -let gil = Python::acquire_gil(); -let py = gil.python(); -let mut obj = PyRefMut::new(py, MyClass { num: 3, debug: true }).unwrap(); +// return &mut PyClassShell +let obj = PyClassShell::new_mut(py, MyClass { num: 3, debug: true }).unwrap(); obj.num = 5; ``` @@ -115,7 +169,6 @@ attribute. Only Python's `__new__` method can be specified, `__init__` is not av ```rust # use pyo3::prelude::*; -# use pyo3::PyRawObject; #[pyclass] struct MyClass { num: i32, @@ -123,14 +176,9 @@ struct MyClass { #[pymethods] impl MyClass { - #[new] - fn new(obj: &PyRawObject, num: i32) { - obj.init({ - MyClass { - num, - } - }); + fn new(num: i32) -> Self { + MyClass { num } } } ``` @@ -139,24 +187,29 @@ Rules for the `new` method: * If no method marked with `#[new]` is declared, object instances can only be created from Rust, but not from Python. -* The first parameter is the raw object and the custom `new` method must initialize the object - with an instance of the struct using the `init` method. The type of the object may be the type object of - a derived class declared in Python. -* The first parameter must have type `&PyRawObject`. +* All parameters are from Python. +* It can return one of these types: + - `T` + - `PyResult` + - `PyClassInitializer` + - `PyResult>` +* If you pyclass declared with `#[pyclass(extends=BaseType)]` and `BaseType` +is also `#[pyclass]`, you have to return `PyClassInitializer` or +`PyResult>` with the baseclass initialized. See the +below Inheritance section for detail. * For details on the parameter list, see the `Method arguments` section below. -* The return value must be `T` or `PyResult` where `T` is ignored, so it can - be just `()` as in the example above. - ## Inheritance By default, `PyObject` is used as the base class. To override this default, use the `extends` parameter for `pyclass` with the full path to the base class. -The `new` method of subclasses must call their parent's `new` method. +The `new` method of subclasses must call `initializer.get_super().init`, +where `initializer` is `PyClassInitializer`. -```rust,ignore +```rust # use pyo3::prelude::*; -# use pyo3::PyRawObject; +use pyo3::PyClassShell; + #[pyclass] struct BaseClass { val1: usize, @@ -165,12 +218,12 @@ struct BaseClass { #[pymethods] impl BaseClass { #[new] - fn new(obj: &PyRawObject) { - obj.init(BaseClass { val1: 10 }); + fn new() -> Self { + BaseClass { val1: 10 } } - pub fn method(&self) -> PyResult<()> { - Ok(()) + pub fn method(&self) -> PyResult { + Ok(5) } } @@ -182,15 +235,22 @@ struct SubClass { #[pymethods] impl SubClass { #[new] - fn new(obj: &PyRawObject) { - obj.init(SubClass { val2: 10 }); - BaseClass::new(obj); + fn new() -> PyClassInitializer { + let mut init = PyClassInitializer::from_value(SubClass { val2: 10 }); + init.get_super().init(BaseClass::new()); + init } - fn method2(&self) -> PyResult<()> { - self.get_base().method() + fn method2(self_: &PyClassShell) -> PyResult { + self_.get_super().method().map(|x| x * 2) } } + + +# let gil = Python::acquire_gil(); +# let py = gil.python(); +# let sub = pyo3::PyClassShell::new_ref(py, SubClass::new()).unwrap(); +# pyo3::py_run!(py, sub, "assert sub.method2() == 10") ``` The `ObjectProtocol` trait provides a `get_base()` method, which returns a reference @@ -589,18 +649,16 @@ struct GCTracked {} // Fails because it does not implement PyGCProtocol Iterators can be defined using the [`PyIterProtocol`](https://docs.rs/pyo3/latest/pyo3/class/iter/trait.PyIterProtocol.html) trait. It includes two methods `__iter__` and `__next__`: - * `fn __iter__(slf: PyRefMut) -> PyResult>` - * `fn __next__(slf: PyRefMut) -> PyResult>>` + * `fn __iter__(slf: &mut PyClassShell) -> PyResult>` + * `fn __next__(slf: &mut PyClassShell) -> PyResult>>` Returning `Ok(None)` from `__next__` indicates that that there are no further items. Example: ```rust -extern crate pyo3; - use pyo3::prelude::*; -use pyo3::PyIterProtocol; +use pyo3::{PyIterProtocol, PyClassShell}; #[pyclass] struct MyIterator { @@ -609,10 +667,10 @@ struct MyIterator { #[pyproto] impl PyIterProtocol for MyIterator { - fn __iter__(slf: PyRefMut) -> PyResult> { + fn __iter__(slf: &mut PyClassShell) -> PyResult> { Ok(slf.into()) } - fn __next__(mut slf: PyRefMut) -> PyResult> { + fn __next__(slf: &mut PyClassShell) -> PyResult> { Ok(slf.iter.next()) } } diff --git a/guide/src/function.md b/guide/src/function.md index 26d16a0ef73..74925131733 100644 --- a/guide/src/function.md +++ b/guide/src/function.md @@ -108,8 +108,8 @@ impl MyClass { // the signature for the constructor is attached // to the struct definition instead. #[new] - fn new(obj: &PyRawObject, c: i32, d: &str) { - obj.init(Self {}); + fn new(c: i32, d: &str) -> Self { + Self {} } // the self argument should be written $self #[text_signature = "($self, e, f)"] diff --git a/guide/src/python_from_rust.md b/guide/src/python_from_rust.md index 5eaee1769d9..c20a3281853 100644 --- a/guide/src/python_from_rust.md +++ b/guide/src/python_from_rust.md @@ -35,7 +35,8 @@ Since `py_run!` can cause panic, we recommend you to use this macro only for tes your Python extensions quickly. ```rust -use pyo3::{PyObjectProtocol, prelude::*, py_run}; +use pyo3::prelude::*; +use pyo3::{PyClassShell, PyObjectProtocol, py_run}; # fn main() { #[pyclass] struct UserData { @@ -60,7 +61,7 @@ let userdata = UserData { id: 34, name: "Yu".to_string(), }; -let userdata = PyRef::new(py, userdata).unwrap(); +let userdata = PyClassShell::new_ref(py, userdata).unwrap(); let userdata_as_tuple = (34, "Yu"); py_run!(py, userdata userdata_as_tuple, r#" assert repr(userdata) == "User Yu(id: 34)" diff --git a/guide/src/rust_cpython.md b/guide/src/rust_cpython.md index dbaa841f6e0..ebb3fdaf604 100644 --- a/guide/src/rust_cpython.md +++ b/guide/src/rust_cpython.md @@ -26,7 +26,6 @@ py_class!(class MyClass |py| { ```rust use pyo3::prelude::*; -use pyo3::PyRawObject; #[pyclass] struct MyClass { @@ -36,12 +35,8 @@ struct MyClass { #[pymethods] impl MyClass { #[new] - fn new(obj: &PyRawObject, num: u32) { - obj.init({ - MyClass { - num, - } - }); + fn new(num: u32) -> Self { + MyClass { num } } fn half(&self) -> PyResult { @@ -74,7 +69,7 @@ impl PyList { fn new(py: Python) -> &PyList {...} - fn get_item(&self, index: isize) -> &PyObject {...} + fn get_item(&self, index: isize) -> &PyAny {...} } ``` diff --git a/src/prelude.rs b/src/prelude.rs index 4b4cd038d06..5290839c064 100644 --- a/src/prelude.rs +++ b/src/prelude.rs @@ -12,7 +12,7 @@ pub use crate::err::{PyErr, PyResult}; pub use crate::gil::GILGuard; -pub use crate::instance::Py; +pub use crate::instance::{AsPyRef, Py}; pub use crate::object::PyObject; pub use crate::objectprotocol::ObjectProtocol; pub use crate::python::Python; diff --git a/src/pyclass.rs b/src/pyclass.rs index 92203846690..6ec5ac493b9 100644 --- a/src/pyclass.rs +++ b/src/pyclass.rs @@ -62,6 +62,11 @@ pub unsafe fn tp_free_fallback(obj: *mut ffi::PyObject) { } } +/// If `PyClass` is implemented for `T`, then we can use `T` in the Python world, +/// via `PyClassShell`. +/// +/// `#[pyclass]` attribute automatically implement this trait for your Rust struct, +/// so you don't have to use this trait directly. pub trait PyClass: PyTypeInfo> + Sized + PyClassAlloc + PyMethodsProtocol { @@ -91,7 +96,28 @@ where } } -/// `PyClassShell` represents the concrete layout of our `#[pyclass]` in the Python heap. +/// `PyClassShell` represents the concrete layout of `PyClass` in the Python heap. +/// +/// You can use it for testing your `#[pyclass]` correctly works. +/// +/// ``` +/// # use pyo3::prelude::*; +/// # use pyo3::{py_run, PyClassShell}; +/// #[pyclass] +/// struct Book { +/// #[pyo3(get)] +/// name: &'static str, +/// author: &'static str, +/// } +/// let gil = Python::acquire_gil(); +/// let py = gil.python(); +/// let book = Book { +/// name: "The Man in the High Castle", +/// author: "Philip Kindred Dick", +/// }; +/// let book_shell = PyClassShell::new_ref(py, book).unwrap(); +/// py_run!(py, book_shell, "assert book_shell.name[-6:] == 'Castle'"); +/// ``` #[repr(C)] pub struct PyClassShell { ob_base: ::ConcreteLayout, @@ -101,6 +127,7 @@ pub struct PyClassShell { } impl PyClassShell { + /// Make new `PyClassShell` on the Python heap and returns the reference of it. pub fn new_ref(py: Python, value: impl IntoInitializer) -> PyResult<&Self> where ::ConcreteLayout: @@ -113,6 +140,7 @@ impl PyClassShell { } } + /// Make new `PyClassShell` on the Python heap and returns the mutable reference of it. pub fn new_mut(py: Python, value: impl IntoInitializer) -> PyResult<&mut Self> where ::ConcreteLayout: @@ -125,7 +153,16 @@ impl PyClassShell { } } - #[doc(hidden)] + /// Get the reference of base object. + pub fn get_super(&self) -> &::ConcreteLayout { + &self.ob_base + } + + /// Get the mutable reference of base object. + pub fn get_super_mut(&mut self) -> &mut ::ConcreteLayout { + &mut self.ob_base + } + unsafe fn new(py: Python) -> PyResult<*mut Self> where ::ConcreteLayout: @@ -147,7 +184,7 @@ impl PyClassShell { impl PyObjectLayout for PyClassShell { const NEED_INIT: bool = std::mem::size_of::() != 0; const IS_NATIVE_TYPE: bool = false; - fn get_super(&mut self) -> Option<&mut ::ConcreteLayout> { + fn get_super_or(&mut self) -> Option<&mut ::ConcreteLayout> { Some(&mut self.ob_base) } unsafe fn internal_ref_cast(obj: &PyAny) -> &T { @@ -231,13 +268,53 @@ where } } -/// An initializer for `PyClassShell`. +/// A speciall initializer for `PyClassShell`, which enables `super().__init__` +/// in Rust code. +/// +/// You have to use it only when your `#[pyclass]` extends another `#[pyclass]`. +/// +/// ``` +/// # use pyo3::prelude::*; +/// # use pyo3::py_run; +/// #[pyclass] +/// struct BaseClass { +/// #[pyo3(get)] +/// basename: &'static str, +/// } +/// #[pyclass(extends=BaseClass)] +/// struct SubClass { +/// #[pyo3(get)] +/// subname: &'static str, +/// } +/// #[pymethods] +/// impl SubClass { +/// #[new] +/// fn new() -> PyClassInitializer { +/// let mut init = PyClassInitializer::from_value(SubClass{ subname: "sub" }); +/// init.get_super().init(BaseClass { basename: "base" }); +/// init +/// } +/// } +/// let gil = Python::acquire_gil(); +/// let py = gil.python(); +/// let _basetype = py.get_type::(); +/// let typeobj = py.get_type::(); +/// let inst = typeobj.call((), None).unwrap(); +/// py_run!(py, inst, "assert inst.basename == 'base'; assert inst.subname == 'sub'"); +/// ``` pub struct PyClassInitializer { init: Option, super_init: Option<*mut PyClassInitializer>, } impl PyClassInitializer { + /// Construct `PyClassInitializer` for specified value `value`. + /// + /// Same as + /// ```ignore + /// let mut init = PyClassInitializer::(); + /// init.init(value); + /// ``` pub fn from_value(value: T) -> Self { PyClassInitializer { init: Some(value), @@ -245,6 +322,7 @@ impl PyClassInitializer { } } + /// Make new `PyClassInitializer` with empty values. pub fn new() -> Self { PyClassInitializer { init: None, @@ -271,7 +349,7 @@ impl PyClassInitializer { } if let Some(super_init) = super_init { let super_init = unsafe { Box::from_raw(super_init) }; - if let Some(super_obj) = shell.get_super() { + if let Some(super_obj) = shell.get_super_or() { super_init.init_class(super_obj)?; } } else if ::ConcreteLayout::NEED_INIT { @@ -280,10 +358,13 @@ impl PyClassInitializer { Ok(()) } + /// Pass the value that you use in Python to the initializer. pub fn init(&mut self, value: T) { self.init = Some(value); } + /// Get the initializer for the base object. + /// Resembles `super().__init__()` in Python. pub fn get_super(&mut self) -> &mut PyClassInitializer { if let Some(super_init) = self.super_init { return unsafe { &mut *super_init }; @@ -293,6 +374,7 @@ impl PyClassInitializer { return unsafe { &mut *super_init }; } + #[doc(hidden)] pub unsafe fn create_shell(self, py: Python) -> PyResult<*mut PyClassShell> where T: PyClass, @@ -305,29 +387,32 @@ impl PyClassInitializer { } } -pub trait IntoInitializer { +/// Represets that we can convert the type to `PyClassInitializer`. +/// +/// It is mainly used in our proc-macro code. +pub trait IntoInitializer { fn into_initializer(self) -> PyResult>; } -impl IntoInitializer for T { +impl IntoInitializer for T { fn into_initializer(self) -> PyResult> { Ok(PyClassInitializer::from_value(self)) } } -impl IntoInitializer for PyResult { +impl IntoInitializer for PyResult { fn into_initializer(self) -> PyResult> { self.map(PyClassInitializer::from_value) } } -impl IntoInitializer for PyClassInitializer { +impl IntoInitializer for PyClassInitializer { fn into_initializer(self) -> PyResult> { Ok(self) } } -impl IntoInitializer for PyResult> { +impl IntoInitializer for PyResult> { fn into_initializer(self) -> PyResult> { self } diff --git a/src/type_object.rs b/src/type_object.rs index f4350a8661f..36f4d2efbfa 100644 --- a/src/type_object.rs +++ b/src/type_object.rs @@ -12,7 +12,7 @@ pub trait PyObjectLayout { const NEED_INIT: bool = false; const IS_NATIVE_TYPE: bool = true; - fn get_super(&mut self) -> Option<&mut ::ConcreteLayout> { + fn get_super_or(&mut self) -> Option<&mut ::ConcreteLayout> { None } From e2dc843de537895c454dd13920a00eeab4bcc31d Mon Sep 17 00:00:00 2001 From: kngwyu Date: Sun, 22 Dec 2019 23:54:41 +0900 Subject: [PATCH 08/22] Fix a corner case for PyClassInitializer --- src/pyclass.rs | 2 +- tests/test_inheritance.rs | 26 +++++++++++++++++++++----- 2 files changed, 22 insertions(+), 6 deletions(-) diff --git a/src/pyclass.rs b/src/pyclass.rs index 6ec5ac493b9..cbacc912521 100644 --- a/src/pyclass.rs +++ b/src/pyclass.rs @@ -344,7 +344,7 @@ impl PyClassInitializer { let PyClassInitializer { init, super_init } = self; if let Some(value) = init { unsafe { shell.py_init(value) }; - } else if !T::ConcreteLayout::NEED_INIT { + } else if T::ConcreteLayout::NEED_INIT { raise_err!(T::NAME); } if let Some(super_init) = super_init { diff --git a/tests/test_inheritance.rs b/tests/test_inheritance.rs index c3a3eb8135f..19f6439e170 100644 --- a/tests/test_inheritance.rs +++ b/tests/test_inheritance.rs @@ -68,16 +68,30 @@ fn inheritance_with_new_methods() { } #[pyclass(extends=BaseClass)] -struct InvalidSubClass { +struct InvalidSubClass1 { #[pyo3(get)] val2: usize, } #[pymethods] -impl InvalidSubClass { +impl InvalidSubClass1 { #[new] fn new() -> PyClassInitializer { - PyClassInitializer::from_value(InvalidSubClass { val2: 5 }) + PyClassInitializer::from_value(InvalidSubClass1 { val2: 5 }) + } +} + +#[pyclass(extends=BaseClass)] +struct InvalidSubClass2 { + #[pyo3(get)] + val2: usize, +} + +#[pymethods] +impl InvalidSubClass2 { + #[new] + fn new() -> PyClassInitializer { + PyClassInitializer::new() } } @@ -86,7 +100,9 @@ fn uninit_baseclass_raise_exception() { let gil = Python::acquire_gil(); let py = gil.python(); let _baseclass = py.get_type::(); - let subclass = py.get_type::(); + let subclass = py.get_type::(); + py_expect_exception!(py, subclass, "subclass()", RuntimeError); + let subclass = py.get_type::(); py_expect_exception!(py, subclass, "subclass()", RuntimeError); } @@ -94,7 +110,7 @@ fn uninit_baseclass_raise_exception() { 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 }); + let subclass = pyo3::pyclass::PyClassShell::new_ref(py, InvalidSubClass1 { val2: 5 }); if let Err(err) = subclass { py_run!( py, From acb1120c55c6738d015d204ffd79d367a0c2425e Mon Sep 17 00:00:00 2001 From: kngwyu Date: Sun, 22 Dec 2019 23:59:28 +0900 Subject: [PATCH 09/22] Fix examples with the new #[new] API --- examples/rustapi_module/src/buf_and_str.rs | 4 ++-- examples/rustapi_module/src/datetime.rs | 4 ++-- examples/rustapi_module/src/dict_iter.rs | 4 ++-- examples/rustapi_module/src/othermod.rs | 6 +++--- examples/rustapi_module/src/subclassing.rs | 4 ++-- examples/word-count/src/lib.rs | 6 +++--- src/types/datetime.rs | 3 ++- 7 files changed, 16 insertions(+), 15 deletions(-) diff --git a/examples/rustapi_module/src/buf_and_str.rs b/examples/rustapi_module/src/buf_and_str.rs index c21c6db3d39..24da9025421 100644 --- a/examples/rustapi_module/src/buf_and_str.rs +++ b/examples/rustapi_module/src/buf_and_str.rs @@ -9,8 +9,8 @@ struct BytesExtractor {} #[pymethods] impl BytesExtractor { #[new] - pub fn __new__(obj: &PyRawObject) { - obj.init({ BytesExtractor {} }); + pub fn __new__() -> Self { + BytesExtractor {} } pub fn from_bytes(&mut self, bytes: &PyBytes) -> PyResult { diff --git a/examples/rustapi_module/src/datetime.rs b/examples/rustapi_module/src/datetime.rs index 677eab1fa00..cc20b6c67cf 100644 --- a/examples/rustapi_module/src/datetime.rs +++ b/examples/rustapi_module/src/datetime.rs @@ -195,8 +195,8 @@ pub struct TzClass {} #[pymethods] impl TzClass { #[new] - fn new(obj: &PyRawObject) { - obj.init(TzClass {}) + fn new() -> Self { + TzClass {} } fn utcoffset<'p>(&self, py: Python<'p>, _dt: &PyDateTime) -> PyResult<&'p PyDelta> { diff --git a/examples/rustapi_module/src/dict_iter.rs b/examples/rustapi_module/src/dict_iter.rs index c102cecb94b..1911ac63b5c 100644 --- a/examples/rustapi_module/src/dict_iter.rs +++ b/examples/rustapi_module/src/dict_iter.rs @@ -16,8 +16,8 @@ pub struct DictSize { #[pymethods] impl DictSize { #[new] - fn new(obj: &PyRawObject, expected: u32) { - obj.init(DictSize { expected }) + fn new(expected: u32) -> Self { + DictSize { expected } } fn iter_dict(&mut self, _py: Python<'_>, dict: &PyDict) -> PyResult { diff --git a/examples/rustapi_module/src/othermod.rs b/examples/rustapi_module/src/othermod.rs index dd43694dbbd..20745b29fb6 100644 --- a/examples/rustapi_module/src/othermod.rs +++ b/examples/rustapi_module/src/othermod.rs @@ -13,10 +13,10 @@ pub struct ModClass { #[pymethods] impl ModClass { #[new] - fn new(obj: &PyRawObject) { - obj.init(ModClass { + fn new() -> Self { + ModClass { _somefield: String::from("contents"), - }) + } } fn noop(&self, x: usize) -> usize { diff --git a/examples/rustapi_module/src/subclassing.rs b/examples/rustapi_module/src/subclassing.rs index 4f7abf34617..61ead72fe1e 100644 --- a/examples/rustapi_module/src/subclassing.rs +++ b/examples/rustapi_module/src/subclassing.rs @@ -8,8 +8,8 @@ pub struct Subclassable {} #[pymethods] impl Subclassable { #[new] - fn new(obj: &PyRawObject) { - obj.init(Subclassable {}); + fn new() -> Self { + Subclassable {} } } diff --git a/examples/word-count/src/lib.rs b/examples/word-count/src/lib.rs index 29f97808c0f..cf9b21e69fc 100644 --- a/examples/word-count/src/lib.rs +++ b/examples/word-count/src/lib.rs @@ -16,10 +16,10 @@ struct WordCounter { #[pymethods] impl WordCounter { #[new] - fn new(obj: &PyRawObject, path: String) { - obj.init(WordCounter { + fn new(path: String) -> Self { + WordCounter { path: PathBuf::from(path), - }); + } } /// Searches for the word, parallelized by rayon diff --git a/src/types/datetime.rs b/src/types/datetime.rs index 9ec79669c81..293eabff09b 100644 --- a/src/types/datetime.rs +++ b/src/types/datetime.rs @@ -318,8 +318,9 @@ impl PyTimeAccess for PyTime { /// /// This is an abstract base class and should not be constructed directly. pub struct PyTzInfo(PyObject, Unsendable); -pyobject_native_var_type!( +pyobject_native_type!( PyTzInfo, + crate::ffi::PyObject, *PyDateTimeAPI.TZInfoType, Some("datetime"), PyTZInfo_Check From d5cff058efa2ff6b5f17ce5703d01fc3af9ebb99 Mon Sep 17 00:00:00 2001 From: kngwyu Date: Mon, 23 Dec 2019 02:09:36 +0900 Subject: [PATCH 10/22] Fix documents and a clippy warning --- guide/src/class.md | 6 +++--- src/pyclass.rs | 2 +- src/pyclass_slots.rs | 4 ++-- 3 files changed, 6 insertions(+), 6 deletions(-) diff --git a/guide/src/class.md b/guide/src/class.md index a08300dc3bc..c1acd236a31 100644 --- a/guide/src/class.md +++ b/guide/src/class.md @@ -87,7 +87,7 @@ pyo3::inventory::collect!(MyClassGeneratedPyo3Inventory); ## Get Python objects from `pyclass` -You sometimes need to convert your pyclass into Python object in Rust code(e.g., for testing it). +You sometimes need to convert your `pyclass` into a Python object in Rust code (e.g., for testing it). You can use `PyClassShell` or `Py` for this purpose. @@ -95,8 +95,8 @@ You can use `PyClassShell` or `Py` for this purpose. `PyClassShell` is a special type made for `PyClass`. -It represents the actual layout of pyclass in Python world and used to extract it from -Python object pointer. +It represents the actual layout of `pyclass` on the Python heap and is used to extract it from +the Python object pointer. To make pyclass in Python heap, you can use `PyClassShell::new_ref` or `PyClassShell::new_mut`. diff --git a/src/pyclass.rs b/src/pyclass.rs index cbacc912521..f772b2c0ce6 100644 --- a/src/pyclass.rs +++ b/src/pyclass.rs @@ -371,7 +371,7 @@ impl PyClassInitializer { } let super_init = Box::into_raw(Box::new(PyClassInitializer::new())); self.super_init = Some(super_init); - return unsafe { &mut *super_init }; + unsafe { &mut *super_init } } #[doc(hidden)] diff --git a/src/pyclass_slots.rs b/src/pyclass_slots.rs index 698816bb036..60b57d46155 100644 --- a/src/pyclass_slots.rs +++ b/src/pyclass_slots.rs @@ -37,7 +37,7 @@ impl PyClassWeakRef for PyClassDummySlot { } } -/// actural dict field +/// actual dict field #[repr(transparent)] pub struct PyClassDictSlot(*mut ffi::PyObject); @@ -54,7 +54,7 @@ impl PyClassDict for PyClassDictSlot { } } -/// actural weakref field +/// actual weakref field #[repr(transparent)] pub struct PyClassWeakRefSlot(*mut ffi::PyObject); From ea517569333b0b8e3b78c4d1f1b173b371f31dd6 Mon Sep 17 00:00:00 2001 From: kngwyu Date: Mon, 23 Dec 2019 16:52:16 +0900 Subject: [PATCH 11/22] Resolve some clippy complains --- src/object.rs | 2 +- src/pyclass.rs | 24 ++++++++++++------------ src/pyclass_slots.rs | 17 ++++++++--------- src/type_object.rs | 13 ++++++++++++- src/types/string.rs | 2 +- src/types/tuple.rs | 2 +- tests/test_inheritance.rs | 2 +- 7 files changed, 36 insertions(+), 26 deletions(-) diff --git a/src/object.rs b/src/object.rs index d52440dd4df..c465a8c84a3 100644 --- a/src/object.rs +++ b/src/object.rs @@ -162,7 +162,7 @@ impl PyObject { where D: FromPyObject<'p>, { - FromPyObject::extract(self.as_ref(py).into()) + FromPyObject::extract(self.as_ref(py)) } /// Retrieves an attribute value. diff --git a/src/pyclass.rs b/src/pyclass.rs index f772b2c0ce6..8ec9897f467 100644 --- a/src/pyclass.rs +++ b/src/pyclass.rs @@ -307,12 +307,21 @@ pub struct PyClassInitializer { super_init: Option<*mut PyClassInitializer>, } +impl Default for PyClassInitializer { + fn default() -> Self { + Self { + init: None, + super_init: None, + } + } +} + impl PyClassInitializer { /// Construct `PyClassInitializer` for specified value `value`. /// /// Same as /// ```ignore - /// let mut init = PyClassInitializer::(); + /// let mut init = PyClassInitializer::default::(); /// init.init(value); /// ``` pub fn from_value(value: T) -> Self { @@ -322,17 +331,8 @@ impl PyClassInitializer { } } - /// Make new `PyClassInitializer` with empty values. - pub fn new() -> Self { - PyClassInitializer { - init: None, - super_init: None, - } - } - #[must_use] - #[doc(hiddden)] - pub fn init_class(self, shell: &mut T::ConcreteLayout) -> PyResult<()> { + fn init_class(self, shell: &mut T::ConcreteLayout) -> PyResult<()> { macro_rules! raise_err { ($name: path) => { return Err(PyErr::new::(format!( @@ -369,7 +369,7 @@ impl PyClassInitializer { if let Some(super_init) = self.super_init { return unsafe { &mut *super_init }; } - let super_init = Box::into_raw(Box::new(PyClassInitializer::new())); + let super_init = Box::into_raw(Box::new(PyClassInitializer::default())); self.super_init = Some(super_init); unsafe { &mut *super_init } } diff --git a/src/pyclass_slots.rs b/src/pyclass_slots.rs index 60b57d46155..b79a546c783 100644 --- a/src/pyclass_slots.rs +++ b/src/pyclass_slots.rs @@ -1,5 +1,4 @@ //! This module contains additional fields pf pyclass -// TODO(kngwyu): Add vectorcall support use crate::{ffi, Python}; const POINTER_SIZE: isize = std::mem::size_of::<*mut ffi::PyObject>() as _; @@ -8,7 +7,7 @@ const POINTER_SIZE: isize = std::mem::size_of::<*mut ffi::PyObject>() as _; pub trait PyClassDict { const OFFSET: Option = None; fn new() -> Self; - fn clear_dict(&mut self, _py: Python) {} + unsafe fn clear_dict(&mut self, _py: Python) {} private_decl! {} } @@ -16,7 +15,7 @@ pub trait PyClassDict { pub trait PyClassWeakRef { const OFFSET: Option = None; fn new() -> Self; - fn clear_weakrefs(&mut self, _obj: *mut ffi::PyObject, _py: Python) {} + unsafe fn clear_weakrefs(&mut self, _obj: *mut ffi::PyObject, _py: Python) {} private_decl! {} } @@ -47,9 +46,9 @@ impl PyClassDict for PyClassDictSlot { fn new() -> Self { Self(std::ptr::null_mut()) } - fn clear_dict(&mut self, _py: Python) { - if self.0 != std::ptr::null_mut() { - unsafe { ffi::PyDict_Clear(self.0) } + unsafe fn clear_dict(&mut self, _py: Python) { + if !self.0.is_null() { + ffi::PyDict_Clear(self.0) } } } @@ -64,9 +63,9 @@ impl PyClassWeakRef for PyClassWeakRefSlot { fn new() -> Self { Self(std::ptr::null_mut()) } - fn clear_weakrefs(&mut self, obj: *mut ffi::PyObject, _py: Python) { - if self.0 != std::ptr::null_mut() { - unsafe { ffi::PyObject_ClearWeakRefs(obj) } + unsafe fn clear_weakrefs(&mut self, obj: *mut ffi::PyObject, _py: Python) { + if !self.0.is_null() { + ffi::PyObject_ClearWeakRefs(obj) } } } diff --git a/src/type_object.rs b/src/type_object.rs index 36f4d2efbfa..1db2070cb5d 100644 --- a/src/type_object.rs +++ b/src/type_object.rs @@ -7,7 +7,11 @@ use crate::types::{PyAny, PyType}; use crate::{ffi, AsPyPointer, Python}; use std::ptr::NonNull; -/// TODO: write document +/// `T: PyObjectLayout` represents that `T` is a concrete representaion of `U` in Python heap. +/// E.g., `PyClassShell` is a concrete representaion of all `pyclass`es, and `ffi::PyObject` +/// is of `PyAny`. +/// +/// This trait is intended to be used internally. pub trait PyObjectLayout { const NEED_INIT: bool = false; const IS_NATIVE_TYPE: bool = true; @@ -19,6 +23,8 @@ pub trait PyObjectLayout { unsafe fn internal_ref_cast(obj: &PyAny) -> &T { &*(obj as *const _ as *const T) } + + #[allow(clippy::mut_from_ref)] unsafe fn internal_mut_cast(obj: &PyAny) -> &mut T { &mut *(obj as *const _ as *const T as *mut T) } @@ -27,6 +33,11 @@ pub trait PyObjectLayout { unsafe fn py_drop(&mut self, _py: Python) {} } +/// `T: PyObjectSizedLayout` represents `T` is not a instance of +/// [`PyVarObject`](https://docs.python.org/3.8/c-api/structures.html?highlight=pyvarobject#c.PyVarObject). +/// , in addition that `T` is a concrete representaion of `U`. +/// +/// `pyclass`es need this trait for their base class. pub trait PyObjectSizedLayout: PyObjectLayout + Sized {} /// Our custom type flags diff --git a/src/types/string.rs b/src/types/string.rs index b355e27ebb9..51d490f15a5 100644 --- a/src/types/string.rs +++ b/src/types/string.rs @@ -207,7 +207,7 @@ mod test { let s = "Hello Python"; let py_string = s.to_object(py); - let s2: &str = FromPyObject::extract(py_string.as_ref(py).into()).unwrap(); + let s2: &str = FromPyObject::extract(py_string.as_ref(py)).unwrap(); assert_eq!(s, s2); } diff --git a/src/types/tuple.rs b/src/types/tuple.rs index 2626c71f732..f7a6264006c 100644 --- a/src/types/tuple.rs +++ b/src/types/tuple.rs @@ -119,7 +119,7 @@ impl<'a> Iterator for PyTupleIterator<'a> { if self.index < self.slice.len() { let item = self.slice[self.index].as_ref(self.py); self.index += 1; - Some(item.into()) + Some(item) } else { None } diff --git a/tests/test_inheritance.rs b/tests/test_inheritance.rs index 19f6439e170..0052bd515fc 100644 --- a/tests/test_inheritance.rs +++ b/tests/test_inheritance.rs @@ -91,7 +91,7 @@ struct InvalidSubClass2 { impl InvalidSubClass2 { #[new] fn new() -> PyClassInitializer { - PyClassInitializer::new() + PyClassInitializer::default() } } From 2e3ece80220c452d40e22802b9a0f6766cc6a710 Mon Sep 17 00:00:00 2001 From: kngwyu Date: Tue, 24 Dec 2019 00:29:27 +0900 Subject: [PATCH 12/22] Try to enhance class section in the guide --- guide/src/class.md | 13 ++++++------- 1 file changed, 6 insertions(+), 7 deletions(-) diff --git a/guide/src/class.md b/guide/src/class.md index c1acd236a31..353cb5b07a3 100644 --- a/guide/src/class.md +++ b/guide/src/class.md @@ -89,16 +89,15 @@ pyo3::inventory::collect!(MyClassGeneratedPyo3Inventory); ## Get Python objects from `pyclass` You sometimes need to convert your `pyclass` into a Python object in Rust code (e.g., for testing it). -You can use `PyClassShell` or `Py` for this purpose. +For getting *GIL-bounded*(i.e., with `'py` lifetime) references of `pyclass`, +you can use `PyClassShell`. +Or you can use `Py` directly, for *not-GIL-bounded* references. ### `PyClassShell` +`PyClassShell` represents the actual layout of `pyclass` on the Python heap. -`PyClassShell` is a special type made for `PyClass`. - -It represents the actual layout of `pyclass` on the Python heap and is used to extract it from -the Python object pointer. - -To make pyclass in Python heap, you can use `PyClassShell::new_ref` or `PyClassShell::new_mut`. +If you want to instantiate `pyclass` in Python and get the the reference, +you can use `PyClassShell::new_ref` or `PyClassShell::new_mut`. ```rust # use pyo3::prelude::*; From 58590393c50dcc1a26f567377ba456b271b1957f Mon Sep 17 00:00:00 2001 From: kngwyu Date: Tue, 24 Dec 2019 12:27:22 +0900 Subject: [PATCH 13/22] Fix accidently changed file permission --- src/ffi/datetime.rs | 0 src/ffi/mod.rs | 0 src/lib.rs | 0 tests/test_arithmetics.rs | 0 tests/test_compile_error.rs | 0 tests/test_datetime.rs | 0 tests/test_dunder.rs | 0 tests/test_mapping.rs | 0 8 files changed, 0 insertions(+), 0 deletions(-) mode change 100755 => 100644 src/ffi/datetime.rs mode change 100755 => 100644 src/ffi/mod.rs mode change 100755 => 100644 src/lib.rs mode change 100755 => 100644 tests/test_arithmetics.rs mode change 100755 => 100644 tests/test_compile_error.rs mode change 100755 => 100644 tests/test_datetime.rs mode change 100755 => 100644 tests/test_dunder.rs mode change 100755 => 100644 tests/test_mapping.rs diff --git a/src/ffi/datetime.rs b/src/ffi/datetime.rs old mode 100755 new mode 100644 diff --git a/src/ffi/mod.rs b/src/ffi/mod.rs old mode 100755 new mode 100644 diff --git a/src/lib.rs b/src/lib.rs old mode 100755 new mode 100644 diff --git a/tests/test_arithmetics.rs b/tests/test_arithmetics.rs old mode 100755 new mode 100644 diff --git a/tests/test_compile_error.rs b/tests/test_compile_error.rs old mode 100755 new mode 100644 diff --git a/tests/test_datetime.rs b/tests/test_datetime.rs old mode 100755 new mode 100644 diff --git a/tests/test_dunder.rs b/tests/test_dunder.rs old mode 100755 new mode 100644 diff --git a/tests/test_mapping.rs b/tests/test_mapping.rs old mode 100755 new mode 100644 From 766a520a10ca7e8d295b468bdc47f4209e679db2 Mon Sep 17 00:00:00 2001 From: kngwyu Date: Sat, 28 Dec 2019 16:40:45 +0900 Subject: [PATCH 14/22] Documentation enhancement --- guide/src/class.md | 42 +++++++++++++++++++++++------------------- src/pyclass.rs | 17 +++++++++-------- 2 files changed, 32 insertions(+), 27 deletions(-) diff --git a/guide/src/class.md b/guide/src/class.md index 353cb5b07a3..90c254442ba 100644 --- a/guide/src/class.md +++ b/guide/src/class.md @@ -18,12 +18,13 @@ struct MyClass { The above example generates implementations for `PyTypeInfo`, `PyTypeObject` and `PyClass` for `MyClass`. -Specifically, the following implementation is generated. +Specifically, the following implementation is generated: ```rust use pyo3::prelude::*; use pyo3::{PyClassShell, PyTypeInfo}; +/// Class for demonstration struct MyClass { num: i32, debug: bool, @@ -38,7 +39,7 @@ impl PyTypeInfo for MyClass { const NAME: &'static str = "MyClass"; const MODULE: Option<&'static str> = None; - const DESCRIPTION: &'static str = "This is a demo class"; + const DESCRIPTION: &'static str = "Class for demonstration"; const FLAGS: usize = 0; #[inline] @@ -89,14 +90,14 @@ pyo3::inventory::collect!(MyClassGeneratedPyo3Inventory); ## Get Python objects from `pyclass` You sometimes need to convert your `pyclass` into a Python object in Rust code (e.g., for testing it). -For getting *GIL-bounded*(i.e., with `'py` lifetime) references of `pyclass`, +For getting *GIL-bounded* (i.e., with `'py` lifetime) references of `pyclass`, you can use `PyClassShell`. Or you can use `Py` directly, for *not-GIL-bounded* references. ### `PyClassShell` `PyClassShell` represents the actual layout of `pyclass` on the Python heap. -If you want to instantiate `pyclass` in Python and get the the reference, +If you want to instantiate `pyclass` in Python and get the reference, you can use `PyClassShell::new_ref` or `PyClassShell::new_mut`. ```rust @@ -182,21 +183,24 @@ impl MyClass { } ``` -Rules for the `new` method: - -* If no method marked with `#[new]` is declared, object instances can only be created - from Rust, but not from Python. -* All parameters are from Python. -* It can return one of these types: - - `T` - - `PyResult` - - `PyClassInitializer` - - `PyResult>` -* If you pyclass declared with `#[pyclass(extends=BaseType)]` and `BaseType` -is also `#[pyclass]`, you have to return `PyClassInitializer` or -`PyResult>` with the baseclass initialized. See the -below Inheritance section for detail. -* For details on the parameter list, see the `Method arguments` section below. +If no method marked with `#[new]` is declared, object instances can only be +created from Rust, but not from Python. + +For arguments, see the `Method arguments` section below. + +### Return type + +If your pyclass is declared with baseclass(i.e., you use `#[pyclass(extends=...)])`), +you must return a `PyClassInitializer` with the base class initialized. + +For constructors that may fail, you should wrap the return type in a PyResult as well. +Consult the table below to determine which type your constructor should return: + + +| | **Cannot fail** | **May fail** | +|--------------------|-------------------------|-----------------------------------| +| **No inheritance** | `T` | `PyResult` | +| **Inheritance** | `PyClassInitializer` | `PyResult>` | ## Inheritance diff --git a/src/pyclass.rs b/src/pyclass.rs index 8ec9897f467..258090d62f5 100644 --- a/src/pyclass.rs +++ b/src/pyclass.rs @@ -26,7 +26,7 @@ pub(crate) unsafe fn default_alloc() -> *mut ffi::PyObject { alloc(tp_ptr, 0) } -/// A trait that enables custome alloc/dealloc implementations for pyclasses. +/// A trait that enables custom alloc/dealloc implementations for pyclasses. pub trait PyClassAlloc: PyTypeInfo + Sized { unsafe fn alloc(_py: Python) -> *mut Self::ConcreteLayout { default_alloc::() as _ @@ -65,7 +65,7 @@ pub unsafe fn tp_free_fallback(obj: *mut ffi::PyObject) { /// If `PyClass` is implemented for `T`, then we can use `T` in the Python world, /// via `PyClassShell`. /// -/// `#[pyclass]` attribute automatically implement this trait for your Rust struct, +/// The `#[pyclass]` attribute automatically implements this trait for your Rust struct, /// so you don't have to use this trait directly. pub trait PyClass: PyTypeInfo> + Sized + PyClassAlloc + PyMethodsProtocol @@ -96,9 +96,10 @@ where } } -/// `PyClassShell` represents the concrete layout of `PyClass` in the Python heap. +/// `PyClassShell` represents the concrete layout of `T: PyClass` when it is converted +/// to a Python class. /// -/// You can use it for testing your `#[pyclass]` correctly works. +/// You can use it to test your `#[pyclass]` correctly works. /// /// ``` /// # use pyo3::prelude::*; @@ -268,7 +269,7 @@ where } } -/// A speciall initializer for `PyClassShell`, which enables `super().__init__` +/// A special initializer for `PyClassShell`, which enables `super().__init__` /// in Rust code. /// /// You have to use it only when your `#[pyclass]` extends another `#[pyclass]`. @@ -387,9 +388,9 @@ impl PyClassInitializer { } } -/// Represets that we can convert the type to `PyClassInitializer`. +/// Represents that we can convert the type to `PyClassInitializer`. /// -/// It is mainly used in our proc-macro code. +/// This is mainly used in our proc-macro code. pub trait IntoInitializer { fn into_initializer(self) -> PyResult>; } @@ -418,7 +419,7 @@ impl IntoInitializer for PyResult> { } } -/// Register new type in python object system. +/// Register new type in the python object system. #[cfg(not(Py_LIMITED_API))] pub fn initialize_type(py: Python, module_name: Option<&str>) -> PyResult<*mut ffi::PyTypeObject> where From 18e565fab5598a5ef847165169621abe234f28e7 Mon Sep 17 00:00:00 2001 From: kngwyu Date: Sun, 5 Jan 2020 16:01:05 +0900 Subject: [PATCH 15/22] New PyClassInitializer --- guide/src/class.md | 88 ++++++++++---- pyo3-derive-backend/src/pyclass.rs | 20 +++- pyo3-derive-backend/src/pymethod.rs | 2 +- src/instance.rs | 3 +- src/lib.rs | 4 +- src/prelude.rs | 2 +- src/pyclass.rs | 157 +------------------------ src/pyclass_init.rs | 172 ++++++++++++++++++++++++++++ src/type_object.rs | 5 +- src/types/mod.rs | 1 + tests/test_gc.rs | 11 +- tests/test_inheritance.rs | 62 +--------- 12 files changed, 276 insertions(+), 251 deletions(-) create mode 100644 src/pyclass_init.rs diff --git a/guide/src/class.md b/guide/src/class.md index 90c254442ba..c9aecaee3e6 100644 --- a/guide/src/class.md +++ b/guide/src/class.md @@ -22,7 +22,6 @@ Specifically, the following implementation is generated: ```rust use pyo3::prelude::*; -use pyo3::{PyClassShell, PyTypeInfo}; /// Class for demonstration struct MyClass { @@ -32,10 +31,11 @@ struct MyClass { impl pyo3::pyclass::PyClassAlloc for MyClass {} -impl PyTypeInfo for MyClass { +impl pyo3::PyTypeInfo for MyClass { type Type = MyClass; type BaseType = pyo3::types::PyAny; - type ConcreteLayout = PyClassShell; + type ConcreteLayout = pyo3::PyClassShell; + type Initializer = pyo3::PyClassInitializer; const NAME: &'static str = "MyClass"; const MODULE: Option<&'static str> = None; @@ -189,25 +189,27 @@ created from Rust, but not from Python. For arguments, see the `Method arguments` section below. ### Return type - -If your pyclass is declared with baseclass(i.e., you use `#[pyclass(extends=...)])`), -you must return a `PyClassInitializer` with the base class initialized. +Generally, `#[new]` method have to return `T: IntoInitializer`, which can be +converted from `Self` or `Result` for non-nested types. For constructors that may fail, you should wrap the return type in a PyResult as well. Consult the table below to determine which type your constructor should return: - -| | **Cannot fail** | **May fail** | -|--------------------|-------------------------|-----------------------------------| -| **No inheritance** | `T` | `PyResult` | -| **Inheritance** | `PyClassInitializer` | `PyResult>` | +| | **Cannot fail** | **May fail** | +|-----------------------------|-------------------------|-----------------------------------| +|**No inheritance** | `T` | `PyResult` | +|**Inheritance(T Inherits U)**| `(T, U)` | `PyResult<(T, U)>` | +|**Inheritance(General Case)**| `PyClassInitializer` | `PyResult>` | ## Inheritance -By default, `PyObject` is used as the base class. To override this default, +By default, `PyAny` is used as the base class. To override this default, use the `extends` parameter for `pyclass` with the full path to the base class. -The `new` method of subclasses must call `initializer.get_super().init`, -where `initializer` is `PyClassInitializer`. + +For convinience, `(T, U)` implements `IntoInitializer` where `U` is the +baseclass of `T`. +But for more deeply nested inheritance, you have to return `PyClassInitializer` +explicitly. ```rust # use pyo3::prelude::*; @@ -226,7 +228,7 @@ impl BaseClass { } pub fn method(&self) -> PyResult { - Ok(5) + Ok(self.val1) } } @@ -238,22 +240,64 @@ struct SubClass { #[pymethods] impl SubClass { #[new] - fn new() -> PyClassInitializer { - let mut init = PyClassInitializer::from_value(SubClass { val2: 10 }); - init.get_super().init(BaseClass::new()); - init + fn new() -> (Self, BaseClass) { + (SubClass{ val2: 15}, BaseClass::new()) } fn method2(self_: &PyClassShell) -> PyResult { - self_.get_super().method().map(|x| x * 2) + self_.get_super().method().map(|x| x * self_.val2) + } +} + +#[pyclass(extends=SubClass)] +struct SubSubClass { + val3: usize, +} + +#[pymethods] +impl SubSubClass { + #[new] + fn new() -> impl IntoInitializer { + Ok(SubClass::new() + .into_initializer()? + .add_subclass(SubSubClass{val3: 20})) + } + + fn method3(self_: &PyClassShell) -> PyResult { + let super_ = self_.get_super(); + SubClass::method2(super_).map(|x| x * self_.val3) } } # let gil = Python::acquire_gil(); # let py = gil.python(); -# let sub = pyo3::PyClassShell::new_ref(py, SubClass::new()).unwrap(); -# pyo3::py_run!(py, sub, "assert sub.method2() == 10") +# let subsub = pyo3::PyClassShell::new_ref(py, SubSubClass::new()).unwrap(); +# pyo3::py_run!(py, subsub, "assert subsub.method3() == 3000") +``` + +If `SubClass` does not provide baseclass initialization, compile fails. +```compile_fail +# use pyo3::prelude::*; +use pyo3::PyClassShell; + +#[pyclass] +struct BaseClass { + val1: usize, +} + +#[pyclass(extends=BaseClass)] +struct SubClass { + val2: usize, +} + +#[pymethods] +impl SubClass { + #[new] + fn new() -> Self { + SubClass{ val2: 15} + } +} ``` The `ObjectProtocol` trait provides a `get_base()` method, which returns a reference diff --git a/pyo3-derive-backend/src/pyclass.rs b/pyo3-derive-backend/src/pyclass.rs index 48c8a8125ec..b7d490b3ab7 100644 --- a/pyo3-derive-backend/src/pyclass.rs +++ b/pyo3-derive-backend/src/pyclass.rs @@ -364,11 +364,25 @@ fn impl_class( quote! { 0 } }; + // If #cls is not extended type, we allow Self->PyObject conversion + let into_pyobject = if !attr.has_extends { + quote! { + impl pyo3::IntoPy for #cls { + fn into_py(self, py: pyo3::Python) -> pyo3::PyObject { + pyo3::IntoPy::into_py(pyo3::Py::new(py, self).unwrap(), py) + } + } + } + } else { + quote! {} + }; + Ok(quote! { impl pyo3::type_object::PyTypeInfo for #cls { type Type = #cls; type BaseType = #base; type ConcreteLayout = pyo3::pyclass::PyClassShell; + type Initializer = pyo3::pyclass_init::PyClassInitializer; const NAME: &'static str = #cls_name; const MODULE: Option<&'static str> = #module; @@ -387,11 +401,7 @@ fn impl_class( #weakref } - impl pyo3::IntoPy for #cls { - fn into_py(self, py: pyo3::Python) -> pyo3::PyObject { - pyo3::IntoPy::into_py(pyo3::Py::new(py, self).unwrap(), py) - } - } + #into_pyobject #inventory_impl diff --git a/pyo3-derive-backend/src/pymethod.rs b/pyo3-derive-backend/src/pymethod.rs index f836d804062..ed38d10f929 100644 --- a/pyo3-derive-backend/src/pymethod.rs +++ b/pyo3-derive-backend/src/pymethod.rs @@ -154,7 +154,7 @@ pub fn impl_wrap_new(cls: &syn::Type, spec: &FnSpec<'_>) -> TokenStream { let body = impl_arg_params_( spec, cb, - quote! { pyo3::pyclass::IntoInitializer::into_initializer }, + quote! { pyo3::pyclass_init::IntoInitializer::into_initializer }, ); quote! { diff --git a/src/instance.rs b/src/instance.rs index 697e4484982..f04680299b0 100644 --- a/src/instance.rs +++ b/src/instance.rs @@ -3,7 +3,8 @@ use crate::err::{PyErr, PyResult}; use crate::gil; use crate::object::PyObject; use crate::objectprotocol::ObjectProtocol; -use crate::pyclass::{IntoInitializer, PyClass, PyClassShell}; +use crate::pyclass::{PyClass, PyClassShell}; +use crate::pyclass_init::IntoInitializer; use crate::type_object::{PyObjectLayout, PyTypeInfo}; use crate::types::PyAny; use crate::{ffi, IntoPy}; diff --git a/src/lib.rs b/src/lib.rs index 9ac1cf22257..c8aa52545e4 100755 --- a/src/lib.rs +++ b/src/lib.rs @@ -127,7 +127,8 @@ 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, PyClassInitializer, PyClassShell}; +pub use crate::pyclass::{PyClass, PyClassShell}; +pub use crate::pyclass_init::{IntoInitializer, PyClassInitializer}; pub use crate::python::{prepare_freethreaded_python, Python}; pub use crate::type_object::{type_flags, PyTypeInfo}; @@ -170,6 +171,7 @@ mod object; mod objectprotocol; pub mod prelude; pub mod pyclass; +pub mod pyclass_init; pub mod pyclass_slots; mod python; pub mod type_object; diff --git a/src/prelude.rs b/src/prelude.rs index 5290839c064..b28386f0f63 100644 --- a/src/prelude.rs +++ b/src/prelude.rs @@ -18,7 +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::pyclass_init::{IntoInitializer, PyClassInitializer}; pub use crate::types::PyModule; pub use pyo3cls::pymodule; pub use pyo3cls::{pyclass, pyfunction, pymethods, pyproto}; diff --git a/src/pyclass.rs b/src/pyclass.rs index 258090d62f5..0487bee0f15 100644 --- a/src/pyclass.rs +++ b/src/pyclass.rs @@ -1,7 +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_init::IntoInitializer; use crate::pyclass_slots::{PyClassDict, PyClassWeakRef}; use crate::type_object::{type_flags, PyObjectLayout, PyObjectSizedLayout, PyTypeObject}; use crate::types::PyAny; @@ -79,6 +79,7 @@ where T: PyClass, { fn init_type() -> NonNull { + ::init_type(); let type_object = unsafe { ::type_object() }; if (type_object.tp_flags & ffi::Py_TPFLAGS_READY) == 0 { @@ -164,12 +165,11 @@ impl PyClassShell { &mut self.ob_base } - unsafe fn new(py: Python) -> PyResult<*mut Self> + pub(crate) unsafe fn new(py: Python) -> PyResult<*mut Self> where ::ConcreteLayout: crate::type_object::PyObjectSizedLayout, { - ::init_type(); T::init_type(); let base = T::alloc(py); if base.is_null() { @@ -183,7 +183,6 @@ impl PyClassShell { } impl PyObjectLayout for PyClassShell { - const NEED_INIT: bool = std::mem::size_of::() != 0; const IS_NATIVE_TYPE: bool = false; fn get_super_or(&mut self) -> Option<&mut ::ConcreteLayout> { Some(&mut self.ob_base) @@ -269,156 +268,6 @@ where } } -/// A special initializer for `PyClassShell`, which enables `super().__init__` -/// in Rust code. -/// -/// You have to use it only when your `#[pyclass]` extends another `#[pyclass]`. -/// -/// ``` -/// # use pyo3::prelude::*; -/// # use pyo3::py_run; -/// #[pyclass] -/// struct BaseClass { -/// #[pyo3(get)] -/// basename: &'static str, -/// } -/// #[pyclass(extends=BaseClass)] -/// struct SubClass { -/// #[pyo3(get)] -/// subname: &'static str, -/// } -/// #[pymethods] -/// impl SubClass { -/// #[new] -/// fn new() -> PyClassInitializer { -/// let mut init = PyClassInitializer::from_value(SubClass{ subname: "sub" }); -/// init.get_super().init(BaseClass { basename: "base" }); -/// init -/// } -/// } -/// let gil = Python::acquire_gil(); -/// let py = gil.python(); -/// let _basetype = py.get_type::(); -/// let typeobj = py.get_type::(); -/// let inst = typeobj.call((), None).unwrap(); -/// py_run!(py, inst, "assert inst.basename == 'base'; assert inst.subname == 'sub'"); -/// ``` -pub struct PyClassInitializer { - init: Option, - super_init: Option<*mut PyClassInitializer>, -} - -impl Default for PyClassInitializer { - fn default() -> Self { - Self { - init: None, - super_init: None, - } - } -} - -impl PyClassInitializer { - /// Construct `PyClassInitializer` for specified value `value`. - /// - /// Same as - /// ```ignore - /// let mut init = PyClassInitializer::default::(); - /// init.init(value); - /// ``` - pub fn from_value(value: T) -> Self { - PyClassInitializer { - init: Some(value), - super_init: None, - } - } - - #[must_use] - fn init_class(self, shell: &mut T::ConcreteLayout) -> PyResult<()> { - macro_rules! raise_err { - ($name: path) => { - return Err(PyErr::new::(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_or() { - super_init.init_class(super_obj)?; - } - } else if ::ConcreteLayout::NEED_INIT { - raise_err!(T::BaseType::NAME) - } - Ok(()) - } - - /// Pass the value that you use in Python to the initializer. - pub fn init(&mut self, value: T) { - self.init = Some(value); - } - - /// Get the initializer for the base object. - /// Resembles `super().__init__()` in Python. - pub fn get_super(&mut self) -> &mut PyClassInitializer { - if let Some(super_init) = self.super_init { - return unsafe { &mut *super_init }; - } - let super_init = Box::into_raw(Box::new(PyClassInitializer::default())); - self.super_init = Some(super_init); - unsafe { &mut *super_init } - } - - #[doc(hidden)] - pub unsafe fn create_shell(self, py: Python) -> PyResult<*mut PyClassShell> - where - T: PyClass, - ::ConcreteLayout: - crate::type_object::PyObjectSizedLayout, - { - let shell = PyClassShell::new(py)?; - self.init_class(&mut *shell)?; - Ok(shell) - } -} - -/// Represents that we can convert the type to `PyClassInitializer`. -/// -/// This is mainly used in our proc-macro code. -pub trait IntoInitializer { - fn into_initializer(self) -> PyResult>; -} - -impl IntoInitializer for T { - fn into_initializer(self) -> PyResult> { - Ok(PyClassInitializer::from_value(self)) - } -} - -impl IntoInitializer for PyResult { - fn into_initializer(self) -> PyResult> { - self.map(PyClassInitializer::from_value) - } -} - -impl IntoInitializer for PyClassInitializer { - fn into_initializer(self) -> PyResult> { - Ok(self) - } -} - -impl IntoInitializer for PyResult> { - fn into_initializer(self) -> PyResult> { - self - } -} - /// Register new type in the python object system. #[cfg(not(Py_LIMITED_API))] pub fn initialize_type(py: Python, module_name: Option<&str>) -> PyResult<*mut ffi::PyTypeObject> diff --git a/src/pyclass_init.rs b/src/pyclass_init.rs new file mode 100644 index 00000000000..c89b9b91290 --- /dev/null +++ b/src/pyclass_init.rs @@ -0,0 +1,172 @@ +use crate::pyclass::{PyClass, PyClassShell}; +use crate::type_object::{PyObjectLayout, PyObjectSizedLayout, PyTypeInfo}; +use crate::{PyResult, Python}; +use std::marker::PhantomData; + +pub trait PyObjectInit: Sized { + fn init_class(self, shell: &mut T::ConcreteLayout); +} + +pub struct PyNativeTypeInitializer(PhantomData); + +impl PyObjectInit for PyNativeTypeInitializer { + fn init_class(self, _shell: &mut T::ConcreteLayout) {} +} + +/// An initializer for `PyClassShell`. +/// +/// You can use this type to initalize complicatedly nested `#[pyclass]`. +/// +/// ``` +/// # use pyo3::prelude::*; +/// # use pyo3::py_run; +/// #[pyclass] +/// struct BaseClass { +/// #[pyo3(get)] +/// basename: &'static str, +/// } +/// #[pyclass(extends=BaseClass)] +/// struct SubClass { +/// #[pyo3(get)] +/// subname: &'static str, +/// } +/// #[pyclass(extends=SubClass)] +/// struct SubSubClass { +/// #[pyo3(get)] +/// subsubname: &'static str, +/// } +/// +/// #[pymethods] +/// impl SubSubClass { +/// #[new] +/// fn new() -> PyClassInitializer { +/// let base_init = PyClassInitializer::from_value(BaseClass{basename: "base"}); +/// base_init.add_subclass(SubClass{subname: "sub"}) +/// .add_subclass(SubSubClass{subsubname: "subsub"}) +/// } +/// } +/// let gil = Python::acquire_gil(); +/// let py = gil.python(); +/// let typeobj = py.get_type::(); +/// let inst = typeobj.call((), None).unwrap(); +/// py_run!(py, inst, r#" +/// assert inst.basename == 'base' +/// assert inst.subname == 'sub' +/// assert inst.subsubname == 'subsub'"#); +/// ``` +pub struct PyClassInitializer { + init: T, + super_init: Box<::Initializer>, +} + +impl PyClassInitializer { + pub fn new(init: T, super_init: ::Initializer) -> Self { + Self { + init, + super_init: Box::new(super_init), + } + } + + pub fn add_subclass(self, subclass_value: S) -> PyClassInitializer + where + S: PyClass + PyTypeInfo, + S::BaseType: PyTypeInfo, + { + PyClassInitializer::new(subclass_value, self) + } + + #[doc(hidden)] + pub unsafe fn create_shell(self, py: Python) -> PyResult<*mut PyClassShell> + where + T: PyClass, + ::ConcreteLayout: PyObjectSizedLayout, + { + let shell = PyClassShell::new(py)?; + self.init_class(&mut *shell); + Ok(shell) + } +} + +impl PyClassInitializer +where + T: PyClass, + T::BaseType: PyTypeInfo>, +{ + pub fn from_value(value: T) -> Self { + Self::new(value, PyNativeTypeInitializer(PhantomData)) + } +} + +impl PyObjectInit for PyClassInitializer { + fn init_class(self, obj: &mut T::ConcreteLayout) { + let Self { init, super_init } = self; + unsafe { + obj.py_init(init); + } + if let Some(super_obj) = obj.get_super_or() { + super_init.init_class(super_obj); + } + } +} + +/// Represets that we can convert the type to `PyClassInitializer`. +/// +/// It is mainly used in our proc-macro code. +pub trait IntoInitializer { + fn into_initializer(self) -> PyResult>; +} + +impl IntoInitializer for T +where + T: PyClass, + T::BaseType: PyTypeInfo>, +{ + fn into_initializer(self) -> PyResult> { + Ok(PyClassInitializer::from_value(self)) + } +} + +impl IntoInitializer for PyResult +where + T: PyClass, + T::BaseType: PyTypeInfo>, +{ + fn into_initializer(self) -> PyResult> { + self.map(PyClassInitializer::from_value) + } +} + +impl IntoInitializer for (S, B) +where + S: PyClass + PyTypeInfo, + B: PyClass + PyTypeInfo>, + B::BaseType: PyTypeInfo>, +{ + fn into_initializer(self) -> PyResult> { + let (sub, base) = self; + Ok(PyClassInitializer::from_value(base).add_subclass(sub)) + } +} + +impl IntoInitializer for PyResult<(S, B)> +where + S: PyClass + PyTypeInfo, + B: PyClass + PyTypeInfo>, + B::BaseType: PyTypeInfo>, +{ + fn into_initializer(self) -> PyResult> { + self.map(|(sub, base)| PyClassInitializer::from_value(base).add_subclass(sub)) + } +} + +impl IntoInitializer for PyClassInitializer { + fn into_initializer(self) -> PyResult> { + Ok(self) + } +} + +impl IntoInitializer for PyResult> { + fn into_initializer(self) -> PyResult> { + self + } +} diff --git a/src/type_object.rs b/src/type_object.rs index 1db2070cb5d..c35a190fd2e 100644 --- a/src/type_object.rs +++ b/src/type_object.rs @@ -3,6 +3,7 @@ //! Python type object information use crate::instance::Py; +use crate::pyclass_init::PyObjectInit; use crate::types::{PyAny, PyType}; use crate::{ffi, AsPyPointer, Python}; use std::ptr::NonNull; @@ -13,7 +14,6 @@ use std::ptr::NonNull; /// /// This trait is intended to be used internally. pub trait PyObjectLayout { - const NEED_INIT: bool = false; const IS_NATIVE_TYPE: bool = true; fn get_super_or(&mut self) -> Option<&mut ::ConcreteLayout> { @@ -81,6 +81,9 @@ pub trait PyTypeInfo: Sized { /// Layout type ConcreteLayout: PyObjectLayout; + /// Initializer for layout + type Initializer: PyObjectInit; + /// PyTypeObject instance for this type, which might still need to /// be initialized unsafe fn type_object() -> &'static mut ffi::PyTypeObject; diff --git a/src/types/mod.rs b/src/types/mod.rs index 8a581cf117c..53a7f1e1ee8 100644 --- a/src/types/mod.rs +++ b/src/types/mod.rs @@ -127,6 +127,7 @@ macro_rules! pyobject_native_type_convert( type Type = (); type BaseType = $crate::types::PyAny; type ConcreteLayout = $layout; + type Initializer = $crate::pyclass_init::PyNativeTypeInitializer; const NAME: &'static str = stringify!($name); const MODULE: Option<&'static str> = $module; diff --git a/tests/test_gc.rs b/tests/test_gc.rs index cac90a79f40..8f2d0923513 100644 --- a/tests/test_gc.rs +++ b/tests/test_gc.rs @@ -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, PyClassInitializer, PyClassShell}; +use pyo3::{ffi, py_run, AsPyPointer, PyClassShell}; use std::cell::RefCell; use std::sync::atomic::{AtomicBool, Ordering}; use std::sync::Arc; @@ -236,10 +236,11 @@ struct SubClassWithDrop { #[pymethods] impl SubClassWithDrop { #[new] - fn new() -> PyClassInitializer { - let mut init = PyClassInitializer::from_value(SubClassWithDrop { data: None }); - init.get_super().init(BaseClassWithDrop { data: None }); - init + fn new() -> (Self, BaseClassWithDrop) { + ( + SubClassWithDrop { data: None }, + BaseClassWithDrop { data: None }, + ) } } diff --git a/tests/test_inheritance.rs b/tests/test_inheritance.rs index 0052bd515fc..3eb55f1ef4a 100644 --- a/tests/test_inheritance.rs +++ b/tests/test_inheritance.rs @@ -50,10 +50,8 @@ struct SubClass { #[pymethods] impl SubClass { #[new] - fn new() -> PyClassInitializer { - let mut init = PyClassInitializer::from_value(SubClass { val2: 5 }); - init.get_super().init(BaseClass { val1: 10 }); - init + fn new() -> (Self, BaseClass) { + (SubClass { val2: 5 }, BaseClass { val1: 10 }) } } @@ -61,67 +59,11 @@ impl SubClass { fn inheritance_with_new_methods() { let gil = Python::acquire_gil(); let py = gil.python(); - let _baseobj = py.get_type::(); let typeobj = py.get_type::(); let inst = typeobj.call((), None).unwrap(); py_run!(py, inst, "assert inst.val1 == 10; assert inst.val2 == 5"); } -#[pyclass(extends=BaseClass)] -struct InvalidSubClass1 { - #[pyo3(get)] - val2: usize, -} - -#[pymethods] -impl InvalidSubClass1 { - #[new] - fn new() -> PyClassInitializer { - PyClassInitializer::from_value(InvalidSubClass1 { val2: 5 }) - } -} - -#[pyclass(extends=BaseClass)] -struct InvalidSubClass2 { - #[pyo3(get)] - val2: usize, -} - -#[pymethods] -impl InvalidSubClass2 { - #[new] - fn new() -> PyClassInitializer { - PyClassInitializer::default() - } -} - -#[test] -fn uninit_baseclass_raise_exception() { - let gil = Python::acquire_gil(); - let py = gil.python(); - let _baseclass = py.get_type::(); - let subclass = py.get_type::(); - py_expect_exception!(py, subclass, "subclass()", RuntimeError); - let subclass = py.get_type::(); - 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, InvalidSubClass1 { 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!!!") - } -} - #[pyclass(extends=PySet)] struct SetWithName { #[pyo3(get(name))] From 60edeb889ee9e23f53e79fcea495e497faba7aab Mon Sep 17 00:00:00 2001 From: David Hewitt <1939362+davidhewitt@users.noreply.github.com> Date: Mon, 6 Jan 2020 13:14:41 +0000 Subject: [PATCH 16/22] Simplify IntoInitializer --- guide/src/class.md | 8 ++-- pyo3-derive-backend/src/pymethod.rs | 4 +- src/derive_utils.rs | 21 ++++++++++ src/instance.rs | 2 +- src/pyclass.rs | 4 +- src/pyclass_init.rs | 60 ++++++++++------------------- 6 files changed, 50 insertions(+), 49 deletions(-) diff --git a/guide/src/class.md b/guide/src/class.md index c9aecaee3e6..a205b92fbf1 100644 --- a/guide/src/class.md +++ b/guide/src/class.md @@ -257,10 +257,10 @@ struct SubSubClass { #[pymethods] impl SubSubClass { #[new] - fn new() -> impl IntoInitializer { - Ok(SubClass::new() - .into_initializer()? - .add_subclass(SubSubClass{val3: 20})) + fn new() -> PyClassInitializer { + SubClass::new() + .into_initializer() + .add_subclass(SubSubClass{val3: 20}) } fn method3(self_: &PyClassShell) -> PyResult { diff --git a/pyo3-derive-backend/src/pymethod.rs b/pyo3-derive-backend/src/pymethod.rs index ed38d10f929..4a48043ae89 100644 --- a/pyo3-derive-backend/src/pymethod.rs +++ b/pyo3-derive-backend/src/pymethod.rs @@ -154,7 +154,7 @@ pub fn impl_wrap_new(cls: &syn::Type, spec: &FnSpec<'_>) -> TokenStream { let body = impl_arg_params_( spec, cb, - quote! { pyo3::pyclass_init::IntoInitializer::into_initializer }, + quote! { pyo3::derive_utils::IntoPyNewResult::into_pynew_result }, ); quote! { @@ -174,7 +174,7 @@ pub fn impl_wrap_new(cls: &syn::Type, spec: &FnSpec<'_>) -> TokenStream { #body - match _result.and_then(|init| init.create_shell(_py)) { + match _result.and_then(|init| init.into_initializer().create_shell(_py)) { Ok(slf) => slf as _, Err(e) => e.restore_and_null(_py), } diff --git a/src/derive_utils.rs b/src/derive_utils.rs index f64cde0d8f6..4ee5c4cfbee 100644 --- a/src/derive_utils.rs +++ b/src/derive_utils.rs @@ -10,6 +10,7 @@ use crate::init_once; use crate::instance::PyNativeType; use crate::types::{PyAny, PyDict, PyModule, PyTuple}; use crate::{ffi, GILPool, IntoPy, PyObject, Python}; +use crate::pyclass_init::IntoInitializer; use std::ptr; /// Description of a python parameter; used for `parse_args()`. @@ -179,3 +180,23 @@ impl> IntoPyResult for PyResult { self } } + +use crate::pyclass::PyClass; + +/// Variant of IntoPyResult for the specific case of #[new]. In the case of returning (Sub, Base) +/// from #[new], IntoPyResult can't apply because (Sub, Base) doesn't implement IntoPy. +pub trait IntoPyNewResult> { + fn into_pynew_result(self) -> PyResult; +} + +impl> IntoPyNewResult for I { + fn into_pynew_result(self) -> PyResult { + Ok(self) + } +} + +impl> IntoPyNewResult for PyResult { + fn into_pynew_result(self) -> PyResult { + self + } +} diff --git a/src/instance.rs b/src/instance.rs index f04680299b0..2a1a827844d 100644 --- a/src/instance.rs +++ b/src/instance.rs @@ -42,7 +42,7 @@ impl Py { ::ConcreteLayout: crate::type_object::PyObjectSizedLayout, { - let initializer = value.into_initializer()?; + let initializer = value.into_initializer(); let obj = unsafe { initializer.create_shell(py)? }; let ob = unsafe { Py::from_owned_ptr(obj as _) }; Ok(ob) diff --git a/src/pyclass.rs b/src/pyclass.rs index 0487bee0f15..26ad384fe6c 100644 --- a/src/pyclass.rs +++ b/src/pyclass.rs @@ -136,7 +136,7 @@ impl PyClassShell { crate::type_object::PyObjectSizedLayout, { unsafe { - let initializer = value.into_initializer()?; + let initializer = value.into_initializer(); let self_ = initializer.create_shell(py)?; FromPyPointer::from_owned_ptr_or_err(py, self_ as _) } @@ -149,7 +149,7 @@ impl PyClassShell { crate::type_object::PyObjectSizedLayout, { unsafe { - let initializer = value.into_initializer()?; + let initializer = value.into_initializer(); let self_ = initializer.create_shell(py)?; FromPyPointer::from_owned_ptr_or_err(py, self_ as _) } diff --git a/src/pyclass_init.rs b/src/pyclass_init.rs index c89b9b91290..35bbb6824c0 100644 --- a/src/pyclass_init.rs +++ b/src/pyclass_init.rs @@ -109,64 +109,44 @@ impl PyObjectInit for PyClassInitializer { } } -/// Represets that we can convert the type to `PyClassInitializer`. -/// -/// It is mainly used in our proc-macro code. -pub trait IntoInitializer { - fn into_initializer(self) -> PyResult>; -} - -impl IntoInitializer for T +impl From for PyClassInitializer where T: PyClass, T::BaseType: PyTypeInfo>, { - fn into_initializer(self) -> PyResult> { - Ok(PyClassInitializer::from_value(self)) + fn from(value: T) -> PyClassInitializer { + PyClassInitializer::from_value(value) } } -impl IntoInitializer for PyResult -where - T: PyClass, - T::BaseType: PyTypeInfo>, -{ - fn into_initializer(self) -> PyResult> { - self.map(PyClassInitializer::from_value) - } +/// An extension of Into which extends the range of possible types from `#[pyclass]`'s `#[new]`. +/// +/// In particular it permits for the return type of `#[new]` to be a (SubType, BaseType) pair +/// which will also be initialized. +/// +/// It is mainly used in our proc-macro code. +pub trait IntoInitializer { + fn into_initializer(self) -> PyClassInitializer; } -impl IntoInitializer for (S, B) +impl IntoInitializer for U where - S: PyClass + PyTypeInfo, - B: PyClass + PyTypeInfo>, - B::BaseType: PyTypeInfo>, + T: PyClass, + U: Into> { - fn into_initializer(self) -> PyResult> { - let (sub, base) = self; - Ok(PyClassInitializer::from_value(base).add_subclass(sub)) + fn into_initializer(self) -> PyClassInitializer { + self.into() } } -impl IntoInitializer for PyResult<(S, B)> +impl IntoInitializer for (S, B) where S: PyClass + PyTypeInfo, B: PyClass + PyTypeInfo>, B::BaseType: PyTypeInfo>, { - fn into_initializer(self) -> PyResult> { - self.map(|(sub, base)| PyClassInitializer::from_value(base).add_subclass(sub)) - } -} - -impl IntoInitializer for PyClassInitializer { - fn into_initializer(self) -> PyResult> { - Ok(self) - } -} - -impl IntoInitializer for PyResult> { - fn into_initializer(self) -> PyResult> { - self + fn into_initializer(self) -> PyClassInitializer { + let (sub, base_init) = self; + base_init.into_initializer().add_subclass(sub) } } From b602b4bf6c0255650a3e2971654b6d7618ef56a2 Mon Sep 17 00:00:00 2001 From: kngwyu Date: Tue, 7 Jan 2020 12:49:36 +0900 Subject: [PATCH 17/22] Enhance documentation and tests around #[new] --- guide/src/class.md | 18 +++++++++----- src/derive_utils.rs | 5 ++-- src/pyclass_init.rs | 16 +++---------- tests/test_inheritance.rs | 49 +++++++++++++++++++++++++++++++++++++++ 4 files changed, 66 insertions(+), 22 deletions(-) diff --git a/guide/src/class.md b/guide/src/class.md index a205b92fbf1..a235308be43 100644 --- a/guide/src/class.md +++ b/guide/src/class.md @@ -189,8 +189,12 @@ created from Rust, but not from Python. For arguments, see the `Method arguments` section below. ### Return type -Generally, `#[new]` method have to return `T: IntoInitializer`, which can be -converted from `Self` or `Result` for non-nested types. +Generally, `#[new]` method have to return `T: IntoInitializer` or +`PyResult where T: IntoInitializer`. + +If your `T: PyClass` inherits just `PyAny`, you can return just `Self` or `PyResult`. +But if it inherits other `U: PyClass`, you have to return tuple `(T, U)` or +`PyClassInitializer`. For constructors that may fail, you should wrap the return type in a PyResult as well. Consult the table below to determine which type your constructor should return: @@ -202,7 +206,6 @@ Consult the table below to determine which type your constructor should return: |**Inheritance(General Case)**| `PyClassInitializer` | `PyResult>` | ## Inheritance - By default, `PyAny` is used as the base class. To override this default, use the `extends` parameter for `pyclass` with the full path to the base class. @@ -276,6 +279,12 @@ impl SubSubClass { # pyo3::py_run!(py, subsub, "assert subsub.method3() == 3000") ``` +To aceess super class, you can use either of these two ways. +- Use `self_: &PyClassShell` instead of `self`, and call `get_super()` +- `ObjectProtocol::get_base` +We recommend `PyClasShell` here, since it makes the context much clearer. + + If `SubClass` does not provide baseclass initialization, compile fails. ```compile_fail # use pyo3::prelude::*; @@ -300,9 +309,6 @@ impl SubClass { } ``` -The `ObjectProtocol` trait provides a `get_base()` method, which returns a reference -to the instance of the base struct. - ## Object properties diff --git a/src/derive_utils.rs b/src/derive_utils.rs index 4ee5c4cfbee..f907ad70eee 100644 --- a/src/derive_utils.rs +++ b/src/derive_utils.rs @@ -8,9 +8,10 @@ use crate::err::PyResult; use crate::exceptions::TypeError; use crate::init_once; use crate::instance::PyNativeType; +use crate::pyclass::PyClass; +use crate::pyclass_init::IntoInitializer; use crate::types::{PyAny, PyDict, PyModule, PyTuple}; use crate::{ffi, GILPool, IntoPy, PyObject, Python}; -use crate::pyclass_init::IntoInitializer; use std::ptr; /// Description of a python parameter; used for `parse_args()`. @@ -181,8 +182,6 @@ impl> IntoPyResult for PyResult { } } -use crate::pyclass::PyClass; - /// Variant of IntoPyResult for the specific case of #[new]. In the case of returning (Sub, Base) /// from #[new], IntoPyResult can't apply because (Sub, Base) doesn't implement IntoPy. pub trait IntoPyNewResult> { diff --git a/src/pyclass_init.rs b/src/pyclass_init.rs index 35bbb6824c0..85ea6a4f3b4 100644 --- a/src/pyclass_init.rs +++ b/src/pyclass_init.rs @@ -40,7 +40,7 @@ impl PyObjectInit for PyNativeTypeInitializer { /// impl SubSubClass { /// #[new] /// fn new() -> PyClassInitializer { -/// let base_init = PyClassInitializer::from_value(BaseClass{basename: "base"}); +/// let base_init = PyClassInitializer::from(BaseClass{basename: "base"}); /// base_init.add_subclass(SubClass{subname: "sub"}) /// .add_subclass(SubSubClass{subsubname: "subsub"}) /// } @@ -87,16 +87,6 @@ impl PyClassInitializer { } } -impl PyClassInitializer -where - T: PyClass, - T::BaseType: PyTypeInfo>, -{ - pub fn from_value(value: T) -> Self { - Self::new(value, PyNativeTypeInitializer(PhantomData)) - } -} - impl PyObjectInit for PyClassInitializer { fn init_class(self, obj: &mut T::ConcreteLayout) { let Self { init, super_init } = self; @@ -115,7 +105,7 @@ where T::BaseType: PyTypeInfo>, { fn from(value: T) -> PyClassInitializer { - PyClassInitializer::from_value(value) + Self::new(value, PyNativeTypeInitializer(PhantomData)) } } @@ -132,7 +122,7 @@ pub trait IntoInitializer { impl IntoInitializer for U where T: PyClass, - U: Into> + U: Into>, { fn into_initializer(self) -> PyClassInitializer { self.into() diff --git a/tests/test_inheritance.rs b/tests/test_inheritance.rs index 3eb55f1ef4a..b0b93ee040c 100644 --- a/tests/test_inheritance.rs +++ b/tests/test_inheritance.rs @@ -64,6 +64,55 @@ fn inheritance_with_new_methods() { py_run!(py, inst, "assert inst.val1 == 10; assert inst.val2 == 5"); } +#[pyclass] +struct BaseClassWithResult { + _val: usize, +} + +#[pymethods] +impl BaseClassWithResult { + #[new] + fn new(value: isize) -> PyResult { + if 0 <= value { + Ok(Self { _val: value as _ }) + } else { + Err(PyErr::new::(())) + } + } +} + +#[pyclass(extends=BaseClassWithResult)] +struct SubClass2 {} + +#[pymethods] +impl SubClass2 { + #[new] + fn new(value: isize) -> PyResult<(Self, BaseClassWithResult)> { + let base = BaseClassWithResult::new(value)?; + Ok((Self {}, base)) + } +} + +#[test] +fn handle_result_in_new() { + let gil = Python::acquire_gil(); + let py = gil.python(); + let subclass = py.get_type::(); + py_run!( + py, + subclass, + r#" +try: + subclass(-10) + assert Fals +except RuntimeError as e: + pass +except Exception as e: + raise e +"# + ); +} + #[pyclass(extends=PySet)] struct SetWithName { #[pyo3(get(name))] From f26e07cfd6bb025481540cf6309faaae68eb32e2 Mon Sep 17 00:00:00 2001 From: kngwyu Date: Tue, 7 Jan 2020 13:07:14 +0900 Subject: [PATCH 18/22] Replace IntoInitializer with Into> --- guide/src/class.md | 13 ++++--------- pyo3-derive-backend/src/pymethod.rs | 2 +- src/derive_utils.rs | 8 ++++---- src/instance.rs | 6 +++--- src/lib.rs | 2 +- src/prelude.rs | 2 +- src/pyclass.rs | 10 +++++----- src/pyclass_init.rs | 28 ++++------------------------ 8 files changed, 23 insertions(+), 48 deletions(-) mode change 100755 => 100644 src/lib.rs diff --git a/guide/src/class.md b/guide/src/class.md index a235308be43..3f5c3374734 100644 --- a/guide/src/class.md +++ b/guide/src/class.md @@ -189,12 +189,8 @@ created from Rust, but not from Python. For arguments, see the `Method arguments` section below. ### Return type -Generally, `#[new]` method have to return `T: IntoInitializer` or -`PyResult where T: IntoInitializer`. - -If your `T: PyClass` inherits just `PyAny`, you can return just `Self` or `PyResult`. -But if it inherits other `U: PyClass`, you have to return tuple `(T, U)` or -`PyClassInitializer`. +Generally, `#[new]` method have to return `T: Into>` or +`PyResult where T: Into>`. For constructors that may fail, you should wrap the return type in a PyResult as well. Consult the table below to determine which type your constructor should return: @@ -209,7 +205,7 @@ Consult the table below to determine which type your constructor should return: By default, `PyAny` is used as the base class. To override this default, use the `extends` parameter for `pyclass` with the full path to the base class. -For convinience, `(T, U)` implements `IntoInitializer` where `U` is the +For convinience, `(T, U)` implements `Into>` where `U` is the baseclass of `T`. But for more deeply nested inheritance, you have to return `PyClassInitializer` explicitly. @@ -261,8 +257,7 @@ struct SubSubClass { impl SubSubClass { #[new] fn new() -> PyClassInitializer { - SubClass::new() - .into_initializer() + PyClassInitializer::from(SubClass::new()) .add_subclass(SubSubClass{val3: 20}) } diff --git a/pyo3-derive-backend/src/pymethod.rs b/pyo3-derive-backend/src/pymethod.rs index 4a48043ae89..e7af4967de7 100644 --- a/pyo3-derive-backend/src/pymethod.rs +++ b/pyo3-derive-backend/src/pymethod.rs @@ -174,7 +174,7 @@ pub fn impl_wrap_new(cls: &syn::Type, spec: &FnSpec<'_>) -> TokenStream { #body - match _result.and_then(|init| init.into_initializer().create_shell(_py)) { + match _result.and_then(|init| pyo3::PyClassInitializer::from(init).create_shell(_py)) { Ok(slf) => slf as _, Err(e) => e.restore_and_null(_py), } diff --git a/src/derive_utils.rs b/src/derive_utils.rs index f907ad70eee..a5e284b5f9a 100644 --- a/src/derive_utils.rs +++ b/src/derive_utils.rs @@ -9,7 +9,7 @@ use crate::exceptions::TypeError; use crate::init_once; use crate::instance::PyNativeType; use crate::pyclass::PyClass; -use crate::pyclass_init::IntoInitializer; +use crate::pyclass_init::PyClassInitializer; use crate::types::{PyAny, PyDict, PyModule, PyTuple}; use crate::{ffi, GILPool, IntoPy, PyObject, Python}; use std::ptr; @@ -184,17 +184,17 @@ impl> IntoPyResult for PyResult { /// Variant of IntoPyResult for the specific case of #[new]. In the case of returning (Sub, Base) /// from #[new], IntoPyResult can't apply because (Sub, Base) doesn't implement IntoPy. -pub trait IntoPyNewResult> { +pub trait IntoPyNewResult>> { fn into_pynew_result(self) -> PyResult; } -impl> IntoPyNewResult for I { +impl>> IntoPyNewResult for I { fn into_pynew_result(self) -> PyResult { Ok(self) } } -impl> IntoPyNewResult for PyResult { +impl>> IntoPyNewResult for PyResult { fn into_pynew_result(self) -> PyResult { self } diff --git a/src/instance.rs b/src/instance.rs index 2a1a827844d..5a6475e152c 100644 --- a/src/instance.rs +++ b/src/instance.rs @@ -4,7 +4,7 @@ use crate::gil; use crate::object::PyObject; use crate::objectprotocol::ObjectProtocol; use crate::pyclass::{PyClass, PyClassShell}; -use crate::pyclass_init::IntoInitializer; +use crate::pyclass_init::PyClassInitializer; use crate::type_object::{PyObjectLayout, PyTypeInfo}; use crate::types::PyAny; use crate::{ffi, IntoPy}; @@ -36,13 +36,13 @@ unsafe impl Sync for Py {} impl Py { /// Create new instance of T and move it under python management - pub fn new(py: Python, value: impl IntoInitializer) -> PyResult> + pub fn new(py: Python, value: impl Into>) -> PyResult> where T: PyClass, ::ConcreteLayout: crate::type_object::PyObjectSizedLayout, { - let initializer = value.into_initializer(); + let initializer = value.into(); let obj = unsafe { initializer.create_shell(py)? }; let ob = unsafe { Py::from_owned_ptr(obj as _) }; Ok(ob) diff --git a/src/lib.rs b/src/lib.rs old mode 100755 new mode 100644 index c8aa52545e4..375e76cd40f --- a/src/lib.rs +++ b/src/lib.rs @@ -128,7 +128,7 @@ 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_init::{IntoInitializer, PyClassInitializer}; +pub use crate::pyclass_init::PyClassInitializer; pub use crate::python::{prepare_freethreaded_python, Python}; pub use crate::type_object::{type_flags, PyTypeInfo}; diff --git a/src/prelude.rs b/src/prelude.rs index b28386f0f63..1612c974c1e 100644 --- a/src/prelude.rs +++ b/src/prelude.rs @@ -18,7 +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_init::{IntoInitializer, PyClassInitializer}; +pub use crate::pyclass_init::PyClassInitializer; pub use crate::types::PyModule; pub use pyo3cls::pymodule; pub use pyo3cls::{pyclass, pyfunction, pymethods, pyproto}; diff --git a/src/pyclass.rs b/src/pyclass.rs index 26ad384fe6c..93124001633 100644 --- a/src/pyclass.rs +++ b/src/pyclass.rs @@ -1,7 +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::pyclass_init::IntoInitializer; +use crate::pyclass_init::PyClassInitializer; use crate::pyclass_slots::{PyClassDict, PyClassWeakRef}; use crate::type_object::{type_flags, PyObjectLayout, PyObjectSizedLayout, PyTypeObject}; use crate::types::PyAny; @@ -130,26 +130,26 @@ pub struct PyClassShell { impl PyClassShell { /// Make new `PyClassShell` on the Python heap and returns the reference of it. - pub fn new_ref(py: Python, value: impl IntoInitializer) -> PyResult<&Self> + pub fn new_ref(py: Python, value: impl Into>) -> PyResult<&Self> where ::ConcreteLayout: crate::type_object::PyObjectSizedLayout, { unsafe { - let initializer = value.into_initializer(); + let initializer = value.into(); let self_ = initializer.create_shell(py)?; FromPyPointer::from_owned_ptr_or_err(py, self_ as _) } } /// Make new `PyClassShell` on the Python heap and returns the mutable reference of it. - pub fn new_mut(py: Python, value: impl IntoInitializer) -> PyResult<&mut Self> + pub fn new_mut(py: Python, value: impl Into>) -> PyResult<&mut Self> where ::ConcreteLayout: crate::type_object::PyObjectSizedLayout, { unsafe { - let initializer = value.into_initializer(); + let initializer = value.into(); let self_ = initializer.create_shell(py)?; FromPyPointer::from_owned_ptr_or_err(py, self_ as _) } diff --git a/src/pyclass_init.rs b/src/pyclass_init.rs index 85ea6a4f3b4..d208f431be9 100644 --- a/src/pyclass_init.rs +++ b/src/pyclass_init.rs @@ -109,34 +109,14 @@ where } } -/// An extension of Into which extends the range of possible types from `#[pyclass]`'s `#[new]`. -/// -/// In particular it permits for the return type of `#[new]` to be a (SubType, BaseType) pair -/// which will also be initialized. -/// -/// It is mainly used in our proc-macro code. -pub trait IntoInitializer { - fn into_initializer(self) -> PyClassInitializer; -} - -impl IntoInitializer for U -where - T: PyClass, - U: Into>, -{ - fn into_initializer(self) -> PyClassInitializer { - self.into() - } -} - -impl IntoInitializer for (S, B) +impl From<(S, B)> for PyClassInitializer where S: PyClass + PyTypeInfo, B: PyClass + PyTypeInfo>, B::BaseType: PyTypeInfo>, { - fn into_initializer(self) -> PyClassInitializer { - let (sub, base_init) = self; - base_init.into_initializer().add_subclass(sub) + fn from(sub_and_base: (S, B)) -> PyClassInitializer { + let (sub, base) = sub_and_base; + PyClassInitializer::from(base).add_subclass(sub) } } From 67a98d6c4aedec7a25b2cfb78f0bda29412a2a74 Mon Sep 17 00:00:00 2001 From: kngwyu Date: Tue, 7 Jan 2020 17:37:29 +0900 Subject: [PATCH 19/22] Remove unnecessary Box --- src/pyclass_init.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/pyclass_init.rs b/src/pyclass_init.rs index d208f431be9..ef2d25fbbe3 100644 --- a/src/pyclass_init.rs +++ b/src/pyclass_init.rs @@ -56,14 +56,14 @@ impl PyObjectInit for PyNativeTypeInitializer { /// ``` pub struct PyClassInitializer { init: T, - super_init: Box<::Initializer>, + super_init: ::Initializer, } impl PyClassInitializer { pub fn new(init: T, super_init: ::Initializer) -> Self { Self { init, - super_init: Box::new(super_init), + super_init: super_init, } } From ab0a731e5e96d034d735c551ecb607c391877598 Mon Sep 17 00:00:00 2001 From: kngwyu Date: Tue, 7 Jan 2020 17:38:20 +0900 Subject: [PATCH 20/22] Fix use order in prelude --- src/prelude.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/prelude.rs b/src/prelude.rs index 1612c974c1e..4e98df7cfe5 100644 --- a/src/prelude.rs +++ b/src/prelude.rs @@ -15,10 +15,10 @@ pub use crate::gil::GILGuard; pub use crate::instance::{AsPyRef, Py}; pub use crate::object::PyObject; pub use crate::objectprotocol::ObjectProtocol; +pub use crate::pyclass_init::PyClassInitializer; 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_init::PyClassInitializer; pub use crate::types::PyModule; pub use pyo3cls::pymodule; pub use pyo3cls::{pyclass, pyfunction, pymethods, pyproto}; From c57177a16902a9b95e21ad5d155e81ce0ba35d17 Mon Sep 17 00:00:00 2001 From: kngwyu Date: Wed, 8 Jan 2020 22:44:50 +0900 Subject: [PATCH 21/22] Refine tests and documents around pyclass.rs --- src/lib.rs | 0 src/pyclass.rs | 22 ++++++++++----- src/pyclass_init.rs | 56 +++++++++++++++++++++++++++++++++------ src/pyclass_slots.rs | 17 +++++++----- src/type_object.rs | 3 ++- tests/test_dunder.rs | 0 tests/test_inheritance.rs | 10 +++---- 7 files changed, 80 insertions(+), 28 deletions(-) mode change 100644 => 100755 src/lib.rs mode change 100644 => 100755 tests/test_dunder.rs diff --git a/src/lib.rs b/src/lib.rs old mode 100644 new mode 100755 diff --git a/src/pyclass.rs b/src/pyclass.rs index 93124001633..715cf0dde0e 100644 --- a/src/pyclass.rs +++ b/src/pyclass.rs @@ -1,4 +1,4 @@ -//! An experiment module which has all codes related only to #[pyclass] +//! Traits and structs for `#[pyclass]`. use crate::class::methods::{PyMethodDefType, PyMethodsProtocol}; use crate::conversion::{AsPyPointer, FromPyPointer, ToPyObject}; use crate::pyclass_init::PyClassInitializer; @@ -26,12 +26,20 @@ pub(crate) unsafe fn default_alloc() -> *mut ffi::PyObject { alloc(tp_ptr, 0) } -/// A trait that enables custom alloc/dealloc implementations for pyclasses. +/// This trait enables custom alloc/dealloc implementations for `T: PyClass`. pub trait PyClassAlloc: PyTypeInfo + Sized { + /// Allocate the actual field for `#[pyclass]`. + /// + /// # Safety + /// This function must return a valid pointer to the Python heap. unsafe fn alloc(_py: Python) -> *mut Self::ConcreteLayout { default_alloc::() as _ } + /// Deallocate `#[pyclass]` on the Python heap. + /// + /// # Safety + /// `self_` must be a valid pointer to the Python heap. unsafe fn dealloc(py: Python, self_: *mut Self::ConcreteLayout) { (*self_).py_drop(py); let obj = self_ as _; @@ -188,11 +196,11 @@ impl PyObjectLayout for PyClassShell { Some(&mut self.ob_base) } unsafe fn internal_ref_cast(obj: &PyAny) -> &T { - let shell = obj.as_ptr() as *const PyClassShell; + let shell = obj.as_ptr() as *const Self; &(*shell).pyclass } unsafe fn internal_mut_cast(obj: &PyAny) -> &mut T { - let shell = obj.as_ptr() as *const PyClassShell as *mut PyClassShell; + let shell = obj.as_ptr() as *const _ as *mut Self; &mut (*shell).pyclass } unsafe fn py_drop(&mut self, py: Python) { @@ -258,17 +266,17 @@ where { unsafe fn from_owned_ptr_or_opt(py: Python<'p>, ptr: *mut ffi::PyObject) -> Option { NonNull::new(ptr).map(|p| { - &mut *(gil::register_owned(py, p).as_ptr() as *const PyClassShell as *mut _) + &mut *(gil::register_owned(py, p).as_ptr() as *const _ as *mut PyClassShell) }) } unsafe fn from_borrowed_ptr_or_opt(py: Python<'p>, ptr: *mut ffi::PyObject) -> Option { NonNull::new(ptr).map(|p| { - &mut *(gil::register_borrowed(py, p).as_ptr() as *const PyClassShell as *mut _) + &mut *(gil::register_borrowed(py, p).as_ptr() as *const _ as *mut PyClassShell) }) } } -/// Register new type in the python object system. +/// Register `T: PyClass` to Python interpreter. #[cfg(not(Py_LIMITED_API))] pub fn initialize_type(py: Python, module_name: Option<&str>) -> PyResult<*mut ffi::PyTypeObject> where diff --git a/src/pyclass_init.rs b/src/pyclass_init.rs index ef2d25fbbe3..bc2904da177 100644 --- a/src/pyclass_init.rs +++ b/src/pyclass_init.rs @@ -1,22 +1,32 @@ +//! Initialization utilities for `#[pyclass]`. use crate::pyclass::{PyClass, PyClassShell}; use crate::type_object::{PyObjectLayout, PyObjectSizedLayout, PyTypeInfo}; use crate::{PyResult, Python}; use std::marker::PhantomData; +/// Initializer for Python types. +/// +/// This trait is intended to use internally for distinguishing `#[pyclass]` and +/// Python native types. pub trait PyObjectInit: Sized { fn init_class(self, shell: &mut T::ConcreteLayout); + private_decl! {} } +/// Initializer for Python native type, like `PyDict`. pub struct PyNativeTypeInitializer(PhantomData); impl PyObjectInit for PyNativeTypeInitializer { fn init_class(self, _shell: &mut T::ConcreteLayout) {} + private_impl! {} } -/// An initializer for `PyClassShell`. +/// Initializer for our `#[pyclass]` system. /// /// You can use this type to initalize complicatedly nested `#[pyclass]`. /// +/// # Example +/// /// ``` /// # use pyo3::prelude::*; /// # use pyo3::py_run; @@ -40,9 +50,9 @@ impl PyObjectInit for PyNativeTypeInitializer { /// impl SubSubClass { /// #[new] /// fn new() -> PyClassInitializer { -/// let base_init = PyClassInitializer::from(BaseClass{basename: "base"}); -/// base_init.add_subclass(SubClass{subname: "sub"}) -/// .add_subclass(SubSubClass{subsubname: "subsub"}) +/// PyClassInitializer::from(BaseClass { basename: "base" }) +/// .add_subclass(SubClass { subname: "sub" }) +/// .add_subclass(SubSubClass { subsubname: "subsub" }) /// } /// } /// let gil = Python::acquire_gil(); @@ -60,13 +70,42 @@ pub struct PyClassInitializer { } impl PyClassInitializer { + /// Constract new initialzer from value `T` and base class' initializer. + /// + /// We recommend to mainly use `add_subclass`, instead of directly call `new`. pub fn new(init: T, super_init: ::Initializer) -> Self { - Self { - init, - super_init: super_init, - } + Self { init, super_init } } + /// Constructs a new initializer from base class' initializer. + /// + /// # Example + /// ``` + /// # use pyo3::prelude::*; + /// #[pyclass] + /// struct BaseClass { + /// value: u32, + /// } + /// + /// impl BaseClass { + /// fn new(value: i32) -> PyResult { + /// Ok(Self { + /// value: std::convert::TryFrom::try_from(value)?, + /// }) + /// } + /// } + /// #[pyclass(extends=BaseClass)] + /// struct SubClass {} + /// + /// #[pymethods] + /// impl SubClass { + /// #[new] + /// fn new(value: i32) -> PyResult> { + /// let base_init = PyClassInitializer::from(BaseClass::new(value)?); + /// Ok(base_init.add_subclass(SubClass {})) + /// } + /// } + /// ``` pub fn add_subclass(self, subclass_value: S) -> PyClassInitializer where S: PyClass + PyTypeInfo, @@ -97,6 +136,7 @@ impl PyObjectInit for PyClassInitializer { super_init.init_class(super_obj); } } + private_impl! {} } impl From for PyClassInitializer diff --git a/src/pyclass_slots.rs b/src/pyclass_slots.rs index b79a546c783..539e630e3eb 100644 --- a/src/pyclass_slots.rs +++ b/src/pyclass_slots.rs @@ -1,9 +1,10 @@ -//! This module contains additional fields pf pyclass +//! This module contains additional fields for `#[pyclass]`.. +//! Mainly used by our proc-macro codes. use crate::{ffi, Python}; const POINTER_SIZE: isize = std::mem::size_of::<*mut ffi::PyObject>() as _; -/// Represents `__dict__`. +/// Represents `__dict__` field for `#[pyclass]`. pub trait PyClassDict { const OFFSET: Option = None; fn new() -> Self; @@ -11,7 +12,7 @@ pub trait PyClassDict { private_decl! {} } -/// Represents `__weakref__`. +/// Represents `__weakref__` field for `#[pyclass]`. pub trait PyClassWeakRef { const OFFSET: Option = None; fn new() -> Self; @@ -19,7 +20,7 @@ pub trait PyClassWeakRef { private_decl! {} } -/// Dummy slot means the function doesn't has such a feature. +/// Zero-sized dummy field. pub struct PyClassDummySlot; impl PyClassDict for PyClassDummySlot { @@ -36,7 +37,9 @@ impl PyClassWeakRef for PyClassDummySlot { } } -/// actual dict field +/// Actual dict field, which holds the pointer to `__dict__`. +/// +/// `#[pyclass(dict)]` automatically adds this. #[repr(transparent)] pub struct PyClassDictSlot(*mut ffi::PyObject); @@ -53,7 +56,9 @@ impl PyClassDict for PyClassDictSlot { } } -/// actual weakref field +/// Actual weakref field, which holds the pointer to `__weakref__`. +/// +/// `#[pyclass(weakref)]` automatically adds this. #[repr(transparent)] pub struct PyClassWeakRefSlot(*mut ffi::PyObject); diff --git a/src/type_object.rs b/src/type_object.rs index c35a190fd2e..1aedc12d887 100644 --- a/src/type_object.rs +++ b/src/type_object.rs @@ -1,5 +1,4 @@ // Copyright (c) 2017-present PyO3 Project and Contributors - //! Python type object information use crate::instance::Py; @@ -41,6 +40,7 @@ pub trait PyObjectLayout { pub trait PyObjectSizedLayout: PyObjectLayout + Sized {} /// Our custom type flags +#[doc(hidden)] pub mod type_flags { /// type object supports python GC pub const GC: usize = 1; @@ -59,6 +59,7 @@ pub mod type_flags { } /// Python type information. +/// All Python native types(e.g., `PyDict`) and `#[pyclass]` structs implement this trait. pub trait PyTypeInfo: Sized { /// Type of objects to store in PyObject struct type Type; diff --git a/tests/test_dunder.rs b/tests/test_dunder.rs old mode 100644 new mode 100755 diff --git a/tests/test_inheritance.rs b/tests/test_inheritance.rs index b0b93ee040c..4bbd3701213 100644 --- a/tests/test_inheritance.rs +++ b/tests/test_inheritance.rs @@ -73,11 +73,9 @@ struct BaseClassWithResult { impl BaseClassWithResult { #[new] fn new(value: isize) -> PyResult { - if 0 <= value { - Ok(Self { _val: value as _ }) - } else { - Err(PyErr::new::(())) - } + Ok(Self { + _val: std::convert::TryFrom::try_from(value)?, + }) } } @@ -105,7 +103,7 @@ fn handle_result_in_new() { try: subclass(-10) assert Fals -except RuntimeError as e: +except ValueError as e: pass except Exception as e: raise e From 439efbb71ea64ca7b1da6755e930ba1ce70f868c Mon Sep 17 00:00:00 2001 From: kngwyu Date: Sat, 11 Jan 2020 15:06:54 +0900 Subject: [PATCH 22/22] Update CHANGELOG --- CHANGELOG.md | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 4ef6eff35de..5665a29b405 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -11,15 +11,21 @@ and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0. * The blanket implementations for `FromPyObject` for `&T` and `&mut T` are no longer specializable. Implement `PyTryFrom` for your type to control the behavior of `FromPyObject::extract()` for your types. * The implementation for `IntoPy for T` where `U: FromPy` is no longer specializable. Control the behavior of this via the implementation of `FromPy`. +* `#[new]` does not take `PyRawObject` and can reutrn `Self` [#683](https://github.com/PyO3/pyo3/pull/683) ### Added * Implemented `IntoIterator` for `PySet` and `PyFrozenSet`. [#716](https://github.com/PyO3/pyo3/pull/716) +* `PyClass`, `PyClassShell`, `PyObjectLayout`, `PyClassInitializer` [#683](https://github.com/PyO3/pyo3/pull/683) ### Fixed * Clear error indicator when the exception is handled on the Rust side. [#719](https://github.com/PyO3/pyo3/pull/719) +### Removed + +* `PyRef`, `PyRefMut`, `PyRawObject` [#683](https://github.com/PyO3/pyo3/pull/683) + ## [0.8.5] * Support for `#[name = "foo"]` attribute for `#[pyfunction]` and in `#[pymethods]`. [#692](https://github.com/PyO3/pyo3/pull/692)