add class and static methods #17

This commit is contained in:
Nikolay Kim 2017-06-08 11:29:40 -07:00
parent b3079f759c
commit a90c763bc0
7 changed files with 236 additions and 70 deletions

View File

@ -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<syn::Attribute>) -> 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<syn::Attribute>) -> (FnType, Vec<FnAttr>) {
"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

View File

@ -16,9 +16,13 @@ pub fn gen_py_method<'a>(cls: &Box<syn::Ty>, 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<syn::Ty>, name: &syn::Ident, spec: &FnSpec) ->
}
}
/// Generate function wrapper (PyCFunction, PyCFunctionWithKeywords)
pub fn impl_wrap_new(cls: &Box<syn::Ty>, 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<syn::Ty>, 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<syn::Ty>, name: &syn::Ident, spec: &FnSpec) -> To
}
}
/// Generate class method wrapper (PyCFunction, PyCFunctionWithKeywords)
pub fn impl_wrap_class(cls: &Box<syn::Ty>, 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<syn::Ty>, 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<syn::Ty>, name: &syn::Ident, _spec: &FnSpec) -> Tokens {
@ -216,13 +287,6 @@ fn impl_call(_cls: &Box<syn::Ty>, fname: &syn::Ident, spec: &FnSpec) -> Tokens {
}}
}
fn impl_class_new(cls: &Box<syn::Ty>, 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({

View File

@ -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<PyObject> {
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()))))
}
}
}
}

View File

@ -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<PyCFunction>;
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;
}

View File

@ -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
}
}

View File

@ -131,6 +131,7 @@ impl<T> PyTypeObject for T where T: PyObjectAlloc + PyTypeInfo {
#[inline]
fn type_object(py: Python) -> PyType {
let mut ty = <T as PyTypeInfo>::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<T>(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)?)?;
}
_ => (),
}
}

View File

@ -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> {
ClassMethod::create_instance(py)
fn __new__(cls: &PyType, py: Python) -> PyResult<ClassMethodPtr> {
py.init(|t| ClassMethod{token: t})
}
//#[classmethod]
//def method(cls) -> PyResult<String> {
// Ok(format!("{}.method()!", cls.name(py)))
//}
#[classmethod]
fn method(cls: &PyType, py: Python) -> PyResult<String> {
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::<ClassMethod>()).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<String> {
// 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<String> {
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::<ClassMethodWithArgs>()).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::<StaticMethod>()).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<String> {
// 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::<StaticMethodWithArgs>()).unwrap();
// py.run("assert C.method(1337) == '0x539'", None, Some(&d)).unwrap();
//}
#[py::methods]
impl StaticMethodWithArgs {
#[staticmethod]
fn method(py: Python, input: i32) -> PyResult<String> {
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::<StaticMethodWithArgs>()).unwrap();
py.run("assert C.method(1337) == '0x539'", None, Some(&d)).unwrap();
}
#[py::class]
struct GCIntegration {