diff --git a/guide/src/class.md b/guide/src/class.md index 834af6f6..4d3e39cc 100644 --- a/guide/src/class.md +++ b/guide/src/class.md @@ -76,8 +76,8 @@ attribute. Only python `__new__` method can be specified, `__init__` is not avai impl MyClass { #[new] - fn __new__(cls: &PyType, ...) -> PyResult> { - cls.py().init(|token| { + fn __new__(obj: &PyRawObject, ...) -> PyResult<()> { + obj.init(|token| { MyClass { num: 10, debug: False, @@ -92,9 +92,10 @@ Some rules of `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 type object of the class to create. - This may be the type object of a derived class declared in Python. -* The first parameter implicitly has type `&PyType`. +* The first parameter is the raw object, custom `new` method must initialize object + with value of struct using `init` method. Type of the object may be the type object of + a derived class declared in Python. +* The first parameter implicitly has type `&PyRawObject`. * For details on `parameter-list`, see the documentation of `Method arguments` section. * The return type must be `PyResult` for some `T` that implements `IntoPyObject`. Usually, `T` will be `MyType`. @@ -103,22 +104,45 @@ Some rules of `new` method ## Inheritance By default `PyObject` is used as default base class. To override default base class -`base` parameter to `py::class` needs to be used. Value is full path to base class. +`base` parameter for `py::class` needs to be used. Value is full path to base class. +`__new__` method accepts `PyRawObject` object. `obj` instance must be initialized +with value of custom class struct. Subclass must call parent's `__new__` method. ```rust #[py::class] struct BaseClass { + val1: usize +} + +#[py::class] +impl BaseClass { + #[new] + fn __new__(obj: &PyRawObject) -> PyResult<()> { + obj.init(|t| BaseClass{val1: 10}) + } + pub fn method(&self) -> PyResult<() { Ok(()) } } #[py::class(base=BaseClass)] -struct MyEventLoop { - fn method2(&self) -> PyResult<()> { +struct SubClass { + val2: usize +} + +#[py::class] +impl SubClass { + #[new] + fn __new__(obj: &PyRawObject) -> PyResult<()> { + obj.init(|t| SubClass{val2: 10}) + BaseClass::__new__(obj) + } + + fn method2(&self) -> PyResult<()> { self.get_base().method() - } + } } ``` diff --git a/pyo3cls/src/py_class.rs b/pyo3cls/src/py_class.rs index 55671de0..f829a190 100644 --- a/pyo3cls/src/py_class.rs +++ b/pyo3cls/src/py_class.rs @@ -499,7 +499,7 @@ fn parse_attribute(attr: String) -> (HashMap<&'static str, syn::Ident>, }, "base" => { let mut m = String::new(); - for el in elem[2..elem.len()-1].iter() { + for el in elem[2..elem.len()].iter() { let mut t = Tokens::new(); el.to_tokens(&mut t); m += t.as_str().trim(); diff --git a/pyo3cls/src/py_method.rs b/pyo3cls/src/py_method.rs index 01be3345..5c3e9f73 100644 --- a/pyo3cls/src/py_method.rs +++ b/pyo3cls/src/py_method.rs @@ -19,7 +19,7 @@ pub fn gen_py_method<'a>(cls: &Box, name: &syn::Ident, FnType::Fn => impl_py_method_def(name, doc, &spec, &impl_wrap(cls, name, &spec, true)), FnType::FnNew => - impl_py_method_def_new(name, doc, &impl_wrap_type(cls, name, &spec)), + impl_py_method_def_new(name, doc, &impl_wrap_new(cls, name, &spec)), FnType::FnInit => impl_py_method_def_init(name, doc, &impl_wrap_init(cls, name, &spec)), FnType::FnCall => @@ -123,12 +123,12 @@ pub fn impl_proto_wrap(cls: &Box, name: &syn::Ident, spec: &FnSpec) -> } /// Generate class method wrapper (PyCFunction, PyCFunctionWithKeywords) -pub fn impl_wrap_type(cls: &Box, name: &syn::Ident, spec: &FnSpec) -> Tokens { +pub fn impl_wrap_new(cls: &Box, name: &syn::Ident, spec: &FnSpec) -> Tokens { let names: Vec = spec.args.iter().enumerate().map( |item| if item.1.py {syn::Ident::from("_py")} else { syn::Ident::from(format!("arg{}", item.0))}).collect(); let cb = quote! {{ - #cls::#name(&_cls, #(#names),*) + #cls::#name(&_obj, #(#names),*) }}; let body = impl_arg_params(spec, cb); @@ -141,18 +141,33 @@ pub fn impl_wrap_type(cls: &Box, name: &syn::Ident, spec: &FnSpec) -> T _args: *mut _pyo3::ffi::PyObject, _kwargs: *mut _pyo3::ffi::PyObject) -> *mut _pyo3::ffi::PyObject { + use pyo3::typeob::PyTypeInfo; + const _LOCATION: &'static str = concat!(stringify!(#cls),".",stringify!(#name),"()"); let _pool = _pyo3::GILPool::new(); let _py = _pyo3::Python::assume_gil_acquired(); - let _cls = _pyo3::PyType::from_type_ptr(_py, _cls); - let _args = _py.from_borrowed_ptr::<_pyo3::PyTuple>(_args); - let _kwargs = _pyo3::argparse::get_kwargs(_py, _kwargs); + match _pyo3::typeob::PyRawObject::new(_py, #cls::type_object(), _cls) { + Ok(_obj) => { + let _args = _py.from_borrowed_ptr::<_pyo3::PyTuple>(_args); + let _kwargs = _pyo3::argparse::get_kwargs(_py, _kwargs); - let _result: #output = { - #body - }; - _pyo3::callback::cb_convert( - _pyo3::callback::PyObjectCallbackConverter, _py, _result) + let _result: #output = { + #body + }; + + match _result { + Ok(_) => _obj.into_ptr(), + Err(e) => { + //e.restore(_py); + std::ptr::null_mut() + } + } + } + Err(e) => { + //e.restore(_py); + std::ptr::null_mut() + } + } } } } diff --git a/src/instance.rs b/src/instance.rs index 41e07add..d379f54b 100644 --- a/src/instance.rs +++ b/src/instance.rs @@ -18,6 +18,11 @@ use typeob::{PyTypeInfo, PyObjectAlloc}; pub struct PyToken(PhantomData>); impl PyToken { + + pub(crate) fn new() -> PyToken { + PyToken(PhantomData) + } + #[inline(always)] pub fn py(&self) -> Python { unsafe { Python::assume_gil_acquired() } diff --git a/src/lib.rs b/src/lib.rs index 8d850629..619c90ab 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -155,6 +155,7 @@ pub use objects::*; pub use objectprotocol::ObjectProtocol; pub use object::PyObject; pub use noargs::NoArgs; +pub use typeob::{PyTypeInfo, PyRawObject, PyObjectAlloc}; pub use python::{Python, ToPyPointer, IntoPyPointer, IntoPyDictPointer}; pub use pythonrun::{GILGuard, GILPool, prepare_freethreaded_python, prepare_pyo3_library}; pub use instance::{PyToken, PyObjectWithToken, AsPyRef, Py, PyNativeType}; diff --git a/src/prelude.rs b/src/prelude.rs index 9ba5bfc3..90fcea56 100644 --- a/src/prelude.rs +++ b/src/prelude.rs @@ -19,6 +19,7 @@ pub use noargs::NoArgs; pub use python::{Python, ToPyPointer, IntoPyPointer}; pub use err::{PyErr, PyErrValue, PyResult, PyDowncastError, PyErrArguments}; pub use pythonrun::GILGuard; +pub use typeob::PyRawObject; pub use instance::{PyToken, PyObjectWithToken, AsPyRef, Py, PyNativeType}; pub use conversion::{FromPyObject, RefFromPyObject, PyTryFrom, PyTryInto, ToPyObject, ToBorrowedObject, IntoPyObject, IntoPyTuple}; diff --git a/src/typeob.rs b/src/typeob.rs index b041bd7b..733a1fc0 100644 --- a/src/typeob.rs +++ b/src/typeob.rs @@ -9,8 +9,8 @@ use std::collections::HashMap; use {ffi, class, pythonrun}; use err::{PyErr, PyResult}; -use instance::Py; -use python::Python; +use instance::{Py, PyObjectWithToken, PyToken}; +use python::{Python, IntoPyPointer}; use objects::PyType; use class::methods::PyMethodDefType; @@ -55,7 +55,6 @@ pub trait PyTypeInfo { } - /// type object supports python GC pub const PY_TYPE_FLAG_GC: usize = 1<<0; @@ -92,7 +91,93 @@ impl<'a, T: ?Sized> PyTypeInfo for &'a T where T: PyTypeInfo { default fn is_exact_instance(ptr: *mut ffi::PyObject) -> bool { ::is_exact_instance(ptr) } +} +#[allow(dead_code)] +pub struct PyRawObject { + ptr: *mut ffi::PyObject, + tp_ptr: *mut ffi::PyTypeObject, + 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: ptr, + tp_ptr: tp_ptr, + curr_ptr: curr_ptr, + initialized: 0, + }) + } else { + PyErr::fetch(py).into() + } + } + + /// Initialize memory using value. + /// `PyRawObject` is used by class `__new__` method. + /// ``` + /// #[py::class] + /// struct MyClass { + /// token: PyToken + /// } + /// + /// #[py::methods] + /// impl MyClass { + /// #[new] + /// fn __new__(obj: &PyRawObject) -> PyResult<()> { + /// obj.init(|token| MyClass{token| token}) + /// MyClass::BaseType::__new__(obj) + /// } + /// } + /// ``` + pub fn init(&self, f: F) -> PyResult<()> + where F: FnOnce(PyToken) -> T, + T: PyTypeInfo + { + let value = f(PyToken::new()); + + unsafe { + let ptr = (self.ptr as *mut u8).offset(T::OFFSET) as *mut T; + std::ptr::write(ptr, value); + } + Ok(()) + } + + /// Type object + pub fn type_object(&self) -> &PyType { + unsafe {PyType::from_type_ptr(self.py(), self.curr_ptr)} + } + + /// Return reference to object. + pub 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 + return self.ptr + } +} + +impl PyObjectWithToken for PyRawObject { + #[inline(always)] + fn py(&self) -> Python { + unsafe { Python::assume_gil_acquired() } + } } /// A Python object allocator that is usable as a base type for #[class] @@ -212,8 +297,8 @@ pub fn initialize_type<'p, T>(py: Python<'p>, module_name: Option<&str>) -> PyRe "Module name/type name must not contain NUL byte").into_raw(); let type_object: &mut ffi::PyTypeObject = unsafe{&mut *T::type_object()}; - let base_type_object: &mut ffi::PyTypeObject = unsafe{ - &mut *::type_object()}; + let base_type_object: &mut ffi::PyTypeObject = unsafe { + &mut *::type_object() }; type_object.tp_name = name; type_object.tp_doc = T::DESCRIPTION.as_ptr() as *const _; @@ -322,7 +407,7 @@ pub fn initialize_type<'p, T>(py: Python<'p>, module_name: Option<&str>) -> PyRe if ffi::PyType_Ready(type_object) == 0 { Ok(()) } else { - Err(PyErr::fetch(py)) + PyErr::fetch(py).into() } } } diff --git a/tests/test_class.rs b/tests/test_class.rs index 427a4cda..d28cefae 100644 --- a/tests/test_class.rs +++ b/tests/test_class.rs @@ -100,8 +100,8 @@ struct EmptyClassWithNew { #[py::methods] impl EmptyClassWithNew { #[__new__] - fn __new__(cls: &PyType) -> PyResult> { - Ok(Py::new(cls.py(), |t| EmptyClassWithNew{token: t})?.into()) + fn __new__(obj: &PyRawObject) -> PyResult<()> { + obj.init(|t| EmptyClassWithNew{token: t}) } } @@ -122,8 +122,8 @@ struct NewWithOneArg { #[py::methods] impl NewWithOneArg { #[new] - fn __new__(cls: &PyType, arg: i32) -> PyResult<&mut NewWithOneArg> { - cls.py().init_mut(|t| NewWithOneArg{_data: arg, token: t}) + fn __new__(obj: &PyRawObject, arg: i32) -> PyResult<()> { + obj.init(|t| NewWithOneArg{_data: arg, token: t}) } } @@ -148,11 +148,9 @@ struct NewWithTwoArgs { #[py::methods] impl NewWithTwoArgs { #[new] - fn __new__(cls: &PyType, arg1: i32, arg2: i32) -> PyResult> + fn __new__(obj: &PyRawObject, arg1: i32, arg2: i32) -> PyResult<()> { - Py::new( - cls.py(), - |t| NewWithTwoArgs{_data1: arg1, _data2: arg2, token: t}) + obj.init(|t| NewWithTwoArgs{_data1: arg1, _data2: arg2, token: t}) } } @@ -161,7 +159,7 @@ fn new_with_two_args() { let gil = Python::acquire_gil(); let py = gil.python(); let typeobj = py.get_type::(); - let wrp = typeobj.call((10, 20), NoArgs).unwrap(); + let wrp = typeobj.call((10, 20), NoArgs).map_err(|e| e.print(py)).unwrap(); let obj = wrp.cast_as::().unwrap(); assert_eq!(obj._data1, 10); assert_eq!(obj._data2, 20); @@ -339,8 +337,8 @@ struct ClassMethod {token: PyToken} #[py::methods] impl ClassMethod { #[new] - fn __new__(cls: &PyType) -> PyResult> { - cls.py().init(|t| ClassMethod{token: t}) + fn __new__(obj: &PyRawObject) -> PyResult<()> { + obj.init(|t| ClassMethod{token: t}) } #[classmethod] @@ -390,8 +388,8 @@ struct StaticMethod { #[py::methods] impl StaticMethod { #[new] - fn __new__(cls: &PyType) -> PyResult<&StaticMethod> { - Ok(cls.py().init_mut(|t| StaticMethod{token: t})?.into()) + fn __new__(obj: &PyRawObject) -> PyResult<()> { + obj.init(|t| StaticMethod{token: t}) } #[staticmethod] @@ -1185,7 +1183,6 @@ impl ClassWithProperties { } } - #[test] fn class_with_properties() { let gil = Python::acquire_gil(); @@ -1306,3 +1303,42 @@ fn getter_setter_autogen() { py_run!(py, inst, "assert inst.num == 10"); py_run!(py, inst, "inst.num = 20; assert inst.num == 20"); } + +#[py::class] +struct BaseClass { + #[prop(get)] + val1: usize +} + +#[py::methods] +impl BaseClass { + #[new] + fn __new__(obj: &PyRawObject) -> PyResult<()> { + obj.init(|t| BaseClass{val1: 10}) + } +} + +#[py::class(base=BaseClass)] +struct SubClass { + #[prop(get)] + val2: usize +} + +#[py::methods] +impl SubClass { + #[new] + fn __new__(obj: &PyRawObject) -> PyResult<()> { + obj.init(|t| SubClass{val2: 5})?; + BaseClass::__new__(obj) + } +} + +#[test] +fn inheritance_with_new_methods() { + let gil = Python::acquire_gil(); + let py = gil.python(); + let typebase = py.get_type::(); + let typeobj = py.get_type::(); + let inst = typeobj.call(NoArgs, NoArgs).unwrap(); + py_run!(py, inst, "assert inst.val1 == 10; assert inst.val2 == 5"); +}