From a90c763bc0cf22caf5c3fb40fd6a6071b542e872 Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Thu, 8 Jun 2017 11:29:40 -0700 Subject: [PATCH] add class and static methods #17 --- pyo3cls/src/method.rs | 43 ++++++++++---- pyo3cls/src/py_method.rs | 117 +++++++++++++++++++++++++++++++++++---- src/class/methods.rs | 19 ++++++- src/ffi/methodobject.rs | 13 +++-- src/objects/mod.rs | 5 +- src/typeob.rs | 7 +++ tests/test_class.rs | 102 ++++++++++++++++++++-------------- 7 files changed, 236 insertions(+), 70 deletions(-) diff --git a/pyo3cls/src/method.rs b/pyo3cls/src/method.rs index ac8ee30f..fb6c7a4f 100644 --- a/pyo3cls/src/method.rs +++ b/pyo3cls/src/method.rs @@ -20,6 +20,8 @@ pub enum FnType { Fn, FnNew, FnCall, + FnClass, + FnStatic, } #[derive(Clone, Debug)] @@ -43,32 +45,47 @@ impl<'a> FnSpec<'a> { meth_attrs: &'a mut Vec) -> FnSpec<'a> { let (fn_type, fn_attrs) = parse_attributes(meth_attrs); - //let mut has_self = false; + let mut has_self = false; let mut py = false; let mut arguments = Vec::new(); - for input in sig.decl.inputs[1..].iter() { + for input in sig.decl.inputs.iter() { match input { &syn::FnArg::SelfRef(_, _) => { - //has_self = true; + has_self = true; }, &syn::FnArg::SelfValue(_) => { - //has_self = true; + has_self = true; } &syn::FnArg::Captured(ref pat, ref ty) => { + // skip first argument (cls) + if (fn_type == FnType::FnClass || fn_type == FnType::FnNew) && !has_self { + has_self = true; + continue + } + let (mode, ident) = match pat { &syn::Pat::Ident(ref mode, ref ident, _) => (mode, ident), _ => panic!("unsupported argument: {:?}", pat), }; - // TODO add check for first py: Python arg - if py { - let opt = check_arg_ty_and_optional(name, ty); - arguments.push(FnArg{name: ident, mode: mode, ty: ty, optional: opt}); - } else { - py = true; + + if !py { + match ty { + &syn::Ty::Path(_, ref path) => + if let Some(segment) = path.segments.last() { + if segment.ident.as_ref() == "Python" { + py = true; + continue; + } + }, + _ => (), + } } + + let opt = check_arg_ty_and_optional(name, ty); + arguments.push(FnArg{name: ident, mode: mode, ty: ty, optional: opt}); } &syn::FnArg::Ignored(_) => panic!("ignored argument: {:?}", name), @@ -204,6 +221,12 @@ fn parse_attributes(attrs: &mut Vec) -> (FnType, Vec) { "call" | "__call__" => { res = Some(FnType::FnCall) }, + "classmethod" => { + res = Some(FnType::FnClass) + }, + "staticmethod" => { + res = Some(FnType::FnStatic) + }, "setter" | "getter" => { if attr.style == syn::AttrStyle::Inner { panic!("Inner style attribute is not diff --git a/pyo3cls/src/py_method.rs b/pyo3cls/src/py_method.rs index 041c863a..cf488e3b 100644 --- a/pyo3cls/src/py_method.rs +++ b/pyo3cls/src/py_method.rs @@ -16,9 +16,13 @@ pub fn gen_py_method<'a>(cls: &Box, name: &syn::Ident, FnType::Fn => impl_py_method_def(name, &spec, &impl_wrap(cls, name, &spec, true)), FnType::FnNew => - impl_py_method_def_new(name, &impl_wrap_new(cls, name, &spec)), + impl_py_method_def_new(name, &impl_wrap_type(cls, name, &spec)), FnType::FnCall => impl_py_method_def_call(name, &impl_wrap(cls, name, &spec, false)), + FnType::FnClass => + impl_py_method_def_class(name, &impl_wrap_class(cls, name, &spec)), + FnType::FnStatic => + impl_py_method_def_static(name, &impl_wrap_static(cls, name, &spec)), FnType::Getter(ref getter) => impl_py_getter_def(name, getter, &impl_wrap_getter(cls, name, &spec)), FnType::Setter(ref setter) => @@ -125,9 +129,13 @@ pub fn impl_proto_wrap(cls: &Box, name: &syn::Ident, spec: &FnSpec) -> } } -/// Generate function wrapper (PyCFunction, PyCFunctionWithKeywords) -pub fn impl_wrap_new(cls: &Box, name: &syn::Ident, spec: &FnSpec) -> Tokens { - let cb = impl_class_new(cls, name, spec); +/// Generate class method wrapper (PyCFunction, PyCFunctionWithKeywords) +pub fn impl_wrap_type(cls: &Box, name: &syn::Ident, spec: &FnSpec) -> Tokens { + let names: Vec<&syn::Ident> = spec.args.iter().map(|item| item.name).collect(); + let cb = quote! {{ + #cls::#name(&cls, py, #(#names),*) + }}; + let body = impl_arg_params(spec, cb); let output = &spec.output; @@ -154,6 +162,69 @@ pub fn impl_wrap_new(cls: &Box, name: &syn::Ident, spec: &FnSpec) -> To } } +/// Generate class method wrapper (PyCFunction, PyCFunctionWithKeywords) +pub fn impl_wrap_class(cls: &Box, name: &syn::Ident, spec: &FnSpec) -> Tokens { + let names: Vec<&syn::Ident> = spec.args.iter().map(|item| item.name).collect(); + let cb = quote! {{ + #cls::#name(&cls, py, #(#names),*) + }}; + let body = impl_arg_params(spec, cb); + let output = &spec.output; + + quote! { + #[allow(unused_mut)] + unsafe extern "C" fn wrap(cls: *mut _pyo3::ffi::PyObject, + args: *mut _pyo3::ffi::PyObject, + kwargs: *mut _pyo3::ffi::PyObject) -> *mut _pyo3::ffi::PyObject + { + const LOCATION: &'static str = concat!(stringify!(#cls),".",stringify!(#name), "()"); + + _pyo3::callback::cb_meth(LOCATION, |py| { + let cls = _pyo3::PyType::from_type_ptr(py, cls as *mut _pyo3::ffi::PyTypeObject); + let args = _pyo3::PyTuple::from_borrowed_ptr(py, args); + let kwargs = _pyo3::argparse::get_kwargs(py, kwargs); + + let result: #output = { + #body + }; + _pyo3::callback::cb_convert( + _pyo3::callback::PyObjectCallbackConverter, py, result) + }) + } + } +} + +/// Generate static method wrapper (PyCFunction, PyCFunctionWithKeywords) +pub fn impl_wrap_static(cls: &Box, name: &syn::Ident, spec: &FnSpec) -> Tokens { + let names: Vec<&syn::Ident> = spec.args.iter().map(|item| item.name).collect(); + let cb = quote! {{ + #cls::#name(py, #(#names),*) + }}; + + let body = impl_arg_params(spec, cb); + let output = &spec.output; + + quote! { + #[allow(unused_mut)] + unsafe extern "C" fn wrap(_slf: *mut _pyo3::ffi::PyObject, + args: *mut _pyo3::ffi::PyObject, + kwargs: *mut _pyo3::ffi::PyObject) -> *mut _pyo3::ffi::PyObject + { + const LOCATION: &'static str = concat!(stringify!(#cls),".",stringify!(#name), "()"); + + _pyo3::callback::cb_meth(LOCATION, |py| { + let args = _pyo3::PyTuple::from_borrowed_ptr(py, args); + let kwargs = _pyo3::argparse::get_kwargs(py, kwargs); + + let result: #output = { + #body + }; + _pyo3::callback::cb_convert( + _pyo3::callback::PyObjectCallbackConverter, py, result) + }) + } + } +} /// Generate functiona wrapper (PyCFunction, PyCFunctionWithKeywords) fn impl_wrap_getter(cls: &Box, name: &syn::Ident, _spec: &FnSpec) -> Tokens { @@ -216,13 +287,6 @@ fn impl_call(_cls: &Box, fname: &syn::Ident, spec: &FnSpec) -> Tokens { }} } -fn impl_class_new(cls: &Box, fname: &syn::Ident, spec: &FnSpec) -> Tokens { - let names: Vec<&syn::Ident> = spec.args.iter().map(|item| item.name).collect(); - quote! {{ - #cls::#fname(&cls, py, #(#names),*) - }} -} - fn impl_arg_params(spec: &FnSpec, body: Tokens) -> Tokens { if spec.args.is_empty() { return body @@ -428,6 +492,37 @@ pub fn impl_py_method_def_new(name: &syn::Ident, wrapper: &Tokens) -> Tokens { } } +pub fn impl_py_method_def_class(name: &syn::Ident, wrapper: &Tokens) -> Tokens { + quote! { + _pyo3::class::PyMethodDefType::Class({ + #wrapper + + _pyo3::class::PyMethodDef { + ml_name: stringify!(#name), + ml_meth: _pyo3::class::PyMethodType::PyCFunctionWithKeywords(wrap), + ml_flags: _pyo3::ffi::METH_VARARGS | _pyo3::ffi::METH_KEYWORDS | + _pyo3::ffi::METH_CLASS, + ml_doc: "", + } + }) + } +} + +pub fn impl_py_method_def_static(name: &syn::Ident, wrapper: &Tokens) -> Tokens { + quote! { + _pyo3::class::PyMethodDefType::Static({ + #wrapper + + _pyo3::class::PyMethodDef { + ml_name: stringify!(#name), + ml_meth: _pyo3::class::PyMethodType::PyCFunctionWithKeywords(wrap), + ml_flags: _pyo3::ffi::METH_VARARGS | _pyo3::ffi::METH_KEYWORDS | _pyo3::ffi::METH_STATIC, + ml_doc: "", + } + }) + } +} + pub fn impl_py_method_def_call(name: &syn::Ident, wrapper: &Tokens) -> Tokens { quote! { _pyo3::class::PyMethodDefType::Call({ diff --git a/src/class/methods.rs b/src/class/methods.rs index 906e726b..a7000e9b 100644 --- a/src/class/methods.rs +++ b/src/class/methods.rs @@ -13,6 +13,8 @@ static NO_PY_METHODS: &'static [PyMethodDefType] = &[]; pub enum PyMethodDefType { New(PyMethodDef), Call(PyMethodDef), + Class(PyMethodDef), + Static(PyMethodDef), Method(PyMethodDef), Getter(PyGetterDef), Setter(PySetterDef), @@ -86,8 +88,21 @@ impl PyMethodDef { pub fn as_method_descr(&self, py: Python, ty: *mut ffi::PyTypeObject) -> PyResult { unsafe { - PyObject::from_owned_ptr_or_err( - py, ffi::PyDescr_NewMethod(ty, Box::into_raw(Box::new(self.as_method_def())))) + if self.ml_flags & ffi::METH_CLASS != 0 { + PyObject::from_owned_ptr_or_err( + py, ffi::PyDescr_NewClassMethod( + ty, Box::into_raw(Box::new(self.as_method_def())))) + } + else if self.ml_flags & ffi::METH_STATIC != 0 { + PyObject::from_owned_ptr_or_err( + py, ffi::PyCFunction_New( + Box::into_raw(Box::new(self.as_method_def())), std::ptr::null_mut())) + } + else { + PyObject::from_owned_ptr_or_err( + py, ffi::PyDescr_NewMethod( + ty, Box::into_raw(Box::new(self.as_method_def())))) + } } } } diff --git a/src/ffi/methodobject.rs b/src/ffi/methodobject.rs index 83f9b3bf..204560fd 100644 --- a/src/ffi/methodobject.rs +++ b/src/ffi/methodobject.rs @@ -16,12 +16,14 @@ pub type PyCFunction = #[cfg(all(Py_3_6, not(Py_LIMITED_API)))] pub type _PyCFunctionFast = - unsafe extern "C" fn (slf: *mut PyObject, args: *mut *mut PyObject, - nargs: ::ffi::pyport::Py_ssize_t, kwnames: *mut PyObject) - -> *mut PyObject; + unsafe extern "C" fn (slf: *mut PyObject, + args: *mut *mut PyObject, + nargs: ::ffi::pyport::Py_ssize_t, + kwnames: *mut PyObject) -> *mut PyObject; pub type PyCFunctionWithKeywords = - unsafe extern "C" fn (slf: *mut PyObject, args: *mut PyObject, + unsafe extern "C" fn (slf: *mut PyObject, + args: *mut PyObject, kwds: *mut PyObject) -> *mut PyObject; pub type PyNoArgsFunction = @@ -31,7 +33,8 @@ pub type PyNoArgsFunction = pub fn PyCFunction_GetFunction(f: *mut PyObject) -> Option; pub fn PyCFunction_GetSelf(f: *mut PyObject) -> *mut PyObject; pub fn PyCFunction_GetFlags(f: *mut PyObject) -> c_int; - pub fn PyCFunction_Call(f: *mut PyObject, args: *mut PyObject, + pub fn PyCFunction_Call(f: *mut PyObject, + args: *mut PyObject, kwds: *mut PyObject) -> *mut PyObject; } diff --git a/src/objects/mod.rs b/src/objects/mod.rs index 33c4d127..a5da7ba8 100644 --- a/src/objects/mod.rs +++ b/src/objects/mod.rs @@ -136,8 +136,11 @@ macro_rules! pyobject_nativetype( impl $crate::python::IntoPyPointer for $name { /// Gets the underlying FFI pointer, returns a owned pointer. #[inline] + #[must_use] fn into_ptr(self) -> *mut $crate::ffi::PyObject { - unsafe{$crate::std::mem::transmute(self)} + let ptr = self.0.as_ptr(); + $crate::std::mem::forget(self); + ptr } } diff --git a/src/typeob.rs b/src/typeob.rs index 44fa133a..a0b63c1b 100644 --- a/src/typeob.rs +++ b/src/typeob.rs @@ -131,6 +131,7 @@ impl PyTypeObject for T where T: PyObjectAlloc + PyTypeInfo { #[inline] fn type_object(py: Python) -> PyType { let mut ty = ::type_object(); + //return unsafe { PyType::from_type_ptr(py, ty) }; if (ty.tp_flags & ffi::Py_TPFLAGS_READY) != 0 { unsafe { PyType::from_type_ptr(py, ty) } @@ -281,6 +282,12 @@ fn py_class_method_defs(py: Python, type_object: *mut ffi::PyTypeObject) &PyMethodDefType::Method(ref def) => { defs.set_item(py, def.ml_name, def.as_method_descr(py, type_object)?)?; } + &PyMethodDefType::Class(ref def) => { + defs.set_item(py, def.ml_name, def.as_method_descr(py, type_object)?)?; + } + &PyMethodDefType::Static(ref def) => { + defs.set_item(py, def.ml_name, def.as_method_descr(py, type_object)?)?; + } _ => (), } } diff --git a/tests/test_class.rs b/tests/test_class.rs index 67b87c8d..d86b4dfa 100644 --- a/tests/test_class.rs +++ b/tests/test_class.rs @@ -241,23 +241,27 @@ fn instance_method_with_args() { py.run("assert obj.method(multiplier=6) == 42", None, Some(&d)).unwrap(); } -/* + #[py::class] -struct ClassMethod {} +struct ClassMethod {token: PyToken} + +#[py::ptr(ClassMethod)] +struct ClassMethodPtr(PyPtr); + #[py::methods] impl ClassMethod { #[new] - fn __new__(cls: &PyType, py: Python) -> PyResult { - ClassMethod::create_instance(py) + fn __new__(cls: &PyType, py: Python) -> PyResult { + py.init(|t| ClassMethod{token: t}) } - //#[classmethod] - //def method(cls) -> PyResult { - // Ok(format!("{}.method()!", cls.name(py))) - //} + #[classmethod] + fn method(cls: &PyType, py: Python) -> PyResult { + Ok(format!("{}.method()!", cls.name(py))) + } } -//#[test] +#[test] fn class_method() { let gil = Python::acquire_gil(); let py = gil.python(); @@ -266,24 +270,32 @@ fn class_method() { d.set_item(py, "C", py.get_type::()).unwrap(); py.run("assert C.method() == 'ClassMethod.method()!'", None, Some(&d)).unwrap(); py.run("assert C().method() == 'ClassMethod.method()!'", None, Some(&d)).unwrap(); -}*/ +} -//py_class!(class ClassMethodWithArgs |py| { -// @classmethod -// def method(cls, input: &str) -> PyResult { -// Ok(format!("{}.method({})", cls.name(py), input)) -// } -//}); -//#[test] -/*fn class_method_with_args() { +#[py::class] +struct ClassMethodWithArgs{token: PyToken} + +#[py::ptr(ClassMethodWithArgs)] +struct ClassMethodWithArgsPtr(PyPtr); + +#[py::methods] +impl ClassMethodWithArgs { + #[classmethod] + fn method(cls: &PyType, py: Python, input: PyString) -> PyResult { + Ok(format!("{}.method({})", cls.name(py), input)) + } +} + +#[test] +fn class_method_with_args() { let gil = Python::acquire_gil(); let py = gil.python(); let d = PyDict::new(py); d.set_item(py, "C", py.get_type::()).unwrap(); py.run("assert C.method('abc') == 'ClassMethodWithArgs.method(abc)'", None, Some(&d)).unwrap(); -}*/ +} #[py::class] struct StaticMethod { @@ -300,14 +312,14 @@ impl StaticMethod { py.init(|t| StaticMethod{token: t}) } - //#[staticmethod] - //fn method(py: Python) -> PyResult<&'static str> { - // Ok("StaticMethod.method()!") - //} + #[staticmethod] + fn method(py: Python) -> PyResult<&'static str> { + Ok("StaticMethod.method()!") + } } -//#[test] -/*fn static_method() { +#[test] +fn static_method() { let gil = Python::acquire_gil(); let py = gil.python(); @@ -316,26 +328,34 @@ impl StaticMethod { d.set_item(py, "C", py.get_type::()).unwrap(); py.run("assert C.method() == 'StaticMethod.method()!'", None, Some(&d)).unwrap(); py.run("assert C().method() == 'StaticMethod.method()!'", None, Some(&d)).unwrap(); -}*/ +} -//py_class!(class StaticMethodWithArgs |py| { -// @staticmethod -// def method(input: i32) -> PyResult { -// Ok(format!("0x{:x}", input)) -// } -//}); +#[py::class] +struct StaticMethodWithArgs{token: PyToken} -//#[test] -//fn static_method_with_args() { -// let gil = Python::acquire_gil(); -// let py = gil.python(); +#[py::ptr(StaticMethodWithArgs)] +struct StaticMethodWithArgsPtr(PyPtr); -// assert_eq!(StaticMethodWithArgs::method(py, 1234).unwrap(), "0x4d2"); -// let d = PyDict::new(py); -// d.set_item(py, "C", py.get_type::()).unwrap(); -// py.run("assert C.method(1337) == '0x539'", None, Some(&d)).unwrap(); -//} +#[py::methods] +impl StaticMethodWithArgs { + #[staticmethod] + fn method(py: Python, input: i32) -> PyResult { + Ok(format!("0x{:x}", input)) + } +} + +#[test] +fn static_method_with_args() { + let gil = Python::acquire_gil(); + let py = gil.python(); + + assert_eq!(StaticMethodWithArgs::method(py, 1234).unwrap(), "0x4d2"); + + let d = PyDict::new(py); + d.set_item(py, "C", py.get_type::()).unwrap(); + py.run("assert C.method(1337) == '0x539'", None, Some(&d)).unwrap(); +} #[py::class] struct GCIntegration {