From 201289d6ca61cf8cc0219d7a00e1f78ab5a4f8f4 Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Fri, 19 May 2017 23:14:59 -0700 Subject: [PATCH] add __new__ and __call__ support --- pyo3cls/src/lib.rs | 1 + pyo3cls/src/method.rs | 340 +++++++++++++++++++++++++++++ pyo3cls/src/py_method.rs | 452 ++++++++++----------------------------- pyo3cls/src/py_proto.rs | 48 ++++- src/class/async.rs | 22 +- src/class/context.rs | 98 ++++++--- src/class/methods.rs | 8 + src/class/typeob.rs | 42 ++-- 8 files changed, 608 insertions(+), 403 deletions(-) create mode 100644 pyo3cls/src/method.rs diff --git a/pyo3cls/src/lib.rs b/pyo3cls/src/lib.rs index 2c0aa949..7b69359f 100644 --- a/pyo3cls/src/lib.rs +++ b/pyo3cls/src/lib.rs @@ -17,6 +17,7 @@ mod py_impl; mod py_proto; mod py_method; mod func; +mod method; mod utils; diff --git a/pyo3cls/src/method.rs b/pyo3cls/src/method.rs new file mode 100644 index 00000000..a4fbb974 --- /dev/null +++ b/pyo3cls/src/method.rs @@ -0,0 +1,340 @@ +// Copyright (c) 2017-present PyO3 Project and Contributors + +use syn; +use quote::{Tokens, ToTokens}; +use utils::for_err_msg; + + +#[derive(Clone, Debug)] +pub struct FnArg<'a> { + pub name: &'a syn::Ident, + pub mode: &'a syn::BindingMode, + pub ty: &'a syn::Ty, + pub optional: Option<&'a syn::Ty>, +} + +#[derive(Clone, PartialEq, Debug)] +pub enum FnType { + Getter(Option), + Setter(Option), + Fn, + FnNew, + FnCall, +} + +#[derive(Clone, Debug)] +pub enum FnAttr { + Args(syn::Ident), + Kwargs(syn::Ident), + Default(syn::Ident, Tokens), +} + +pub struct FnSpec<'a> { + pub tp: FnType, + pub attrs: Vec, + pub args: Vec>, +} + +impl<'a> FnSpec<'a> { + + pub fn parse(name: &'a syn::Ident, + sig: &'a syn::MethodSig, + meth_attrs: &'a mut Vec) -> FnSpec<'a> { + let (fn_type, fn_attrs) = parse_attributes(meth_attrs); + + //let mut has_self = false; + let mut py = false; + let mut arguments = Vec::new(); + + for input in sig.decl.inputs[1..].iter() { + match input { + &syn::FnArg::SelfRef(_, _) => { + //has_self = true; + }, + &syn::FnArg::SelfValue(_) => { + //has_self = true; + } + &syn::FnArg::Captured(ref pat, ref ty) => { + 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; + } + } + &syn::FnArg::Ignored(_) => + panic!("ignored argument: {:?}", name), + } + } + + + FnSpec { + tp: fn_type, + attrs: fn_attrs, + args: arguments + } + } + + pub fn is_args(&self, name: &syn::Ident) -> bool { + for s in self.attrs.iter() { + match *s { + FnAttr::Args(ref ident) => + return name == ident, + _ => (), + } + } + false + } + + pub fn accept_args(&self) -> bool { + for s in self.attrs.iter() { + match *s { + FnAttr::Args(_) => return true, + _ => (), + } + } + false + } + + pub fn is_kwargs(&self, name: &syn::Ident) -> bool { + for s in self.attrs.iter() { + match *s { + FnAttr::Kwargs(ref ident) => + return name == ident, + _ => (), + } + } + false + } + + pub fn accept_kwargs(&self) -> bool { + for s in self.attrs.iter() { + match *s { + FnAttr::Kwargs(_) => return true, + _ => (), + } + } + false + } + + pub fn default_value(&self, name: &syn::Ident) -> Option { + for s in self.attrs.iter() { + match *s { + FnAttr::Default(ref ident, ref val) => { + if ident == name { + return Some(val.clone()) + } + }, + _ => (), + } + } + None + } +} + +fn check_arg_ty_and_optional<'a>(name: &'a syn::Ident, ty: &'a syn::Ty) -> Option<&'a syn::Ty> { + match ty { + &syn::Ty::Path(ref qs, ref path) => { + if let &Some(ref qs) = qs { + panic!("explicit Self type in a 'qualified path' is not supported: {:?} - {:?}", + name, qs); + } + + if let Some(segment) = path.segments.last() { + match segment.ident.as_ref() { + "Option" => { + match segment.parameters { + syn::PathParameters::AngleBracketed(ref params) => { + if params.types.len() != 1 { + panic!("argument type is not supported by python method: {:?} ({:?}) {:?}", + for_err_msg(name), + for_err_msg(ty), + for_err_msg(path)); + } + Some(¶ms.types[0]) + }, + _ => { + panic!("argument type is not supported by python method: {:?} ({:?}) {:?}", + for_err_msg(name), + for_err_msg(ty), + for_err_msg(path)); + } + } + }, + _ => None, + } + } else { + None + } + }, + _ => { + None + //panic!("argument type is not supported by python method: {:?} ({:?})", + //for_err_msg(name), + //for_err_msg(ty)); + }, + } +} + +fn parse_attributes(attrs: &mut Vec) -> (FnType, Vec) { + let mut new_attrs = Vec::new(); + let mut spec = Vec::new(); + let mut res: Option = None; + + for attr in attrs.iter() { + match attr.value { + syn::MetaItem::Word(ref name) => { + match name.as_ref() { + "new" => { + res = Some(FnType::FnNew) + }, + "call" => { + res = Some(FnType::FnCall) + }, + "setter" | "getter" => { + if attr.style == syn::AttrStyle::Inner { + panic!("Inner style attribute is not + supported for setter and getter"); + } + if res != None { + panic!("setter/getter attribute can not be used mutiple times"); + } + if name.as_ref() == "setter" { + res = Some(FnType::Setter(None)) + } else { + res = Some(FnType::Getter(None)) + } + }, + _ => { + new_attrs.push(attr.clone()) + } + } + }, + syn::MetaItem::List(ref name, ref meta) => { + match name.as_ref() { + "new" => { + res = Some(FnType::FnNew) + }, + "call" => { + res = Some(FnType::FnCall) + }, + "setter" | "getter" => { + if attr.style == syn::AttrStyle::Inner { + panic!("Inner style attribute is not + supported for setter and getter"); + } + if res != None { + panic!("setter/getter attribute can not be used mutiple times"); + } + if meta.len() != 1 { + panic!("setter/getter requires one value"); + } + match *meta.first().unwrap() { + syn::NestedMetaItem::MetaItem(syn::MetaItem::Word(ref w)) => { + if name.as_ref() == "setter" { + res = Some(FnType::Setter(Some(w.to_string()))) + } else { + res = Some(FnType::Getter(Some(w.to_string()))) + } + }, + syn::NestedMetaItem::Literal(ref lit) => { + match *lit { + syn::Lit::Str(ref s, syn::StrStyle::Cooked) => { + if name.as_ref() == "setter" { + res = Some(FnType::Setter(Some(s.clone()))) + } else { + res = Some(FnType::Getter(Some(s.clone()))) + } + }, + _ => { + panic!("setter/getter attribute requires str value"); + }, + } + } + _ => { + println!("cannot parse {:?} attribute: {:?}", name, meta); + }, + } + }, + "args" => { + spec.extend(parse_args(meta)) + } + "defaults" => { + // parse: #[defaults(param2=12, param3=12)] + for item in meta.iter() { + if let Some(el) = parse_args_default(item) { + spec.push(el) + } + } + } + _ => { + new_attrs.push(attr.clone()) + } + } + }, + syn::MetaItem::NameValue(_, _) => { + new_attrs.push(attr.clone()) + }, + } + } + attrs.clear(); + attrs.extend(new_attrs); + + match res { + Some(tp) => (tp, spec), + None => (FnType::Fn, spec), + } +} + +/// parse: #[args(args="args", kw="kwargs")] +fn parse_args(items: &Vec) -> Vec { + let mut spec = Vec::new(); + + for item in items.iter() { + match item { + &syn::NestedMetaItem::MetaItem(syn::MetaItem::NameValue(ref ident, ref name)) => { + match *name { + syn::Lit::Str(ref name, _) => match ident.as_ref() { + "args" => + spec.push(FnAttr::Args(syn::Ident::from(name.clone()))), + "kw" => + spec.push(FnAttr::Kwargs(syn::Ident::from(name.clone()))), + _ => (), + }, + _ => (), + } + }, + _ => (), + } + } + + spec +} + +fn parse_args_default(item: &syn::NestedMetaItem) -> Option { + match *item { + syn::NestedMetaItem::MetaItem(syn::MetaItem::NameValue(ref name, ref lit)) => { + let mut t = Tokens::new(); + match lit { + &syn::Lit::Str(ref val, _) => { + syn::Ident::from(val.as_str()).to_tokens(&mut t); + }, + _ => { + lit.to_tokens(&mut t); + } + } + Some(FnAttr::Default(name.clone(), t)) + } + _ => { + println!("expected name value {:?}", item); + None + } + } +} diff --git a/pyo3cls/src/py_method.rs b/pyo3cls/src/py_method.rs index be2425cb..98a2040d 100644 --- a/pyo3cls/src/py_method.rs +++ b/pyo3cls/src/py_method.rs @@ -4,223 +4,30 @@ use syn; use quote::{Tokens, ToTokens}; use utils::for_err_msg; - -#[derive(Debug)] -struct Arg<'a> { - pub name: &'a syn::Ident, - pub mode: &'a syn::BindingMode, - pub ty: &'a syn::Ty, - pub optional: Option<&'a syn::Ty>, -} - -#[derive(PartialEq, Debug)] -enum FnType { - Getter(Option), - Setter(Option), - Fn, -} - -#[derive(Debug)] -enum FnSpec { - Args(syn::Ident), - Kwargs(syn::Ident), - Default(syn::Ident, Tokens), -} +use method::{FnArg, FnSpec, FnType}; pub fn gen_py_method<'a>(cls: &Box, name: &syn::Ident, - sig: &mut syn::MethodSig, - meth_attrs: &mut Vec) -> Tokens + sig: &mut syn::MethodSig, meth_attrs: &mut Vec) -> Tokens { check_generic(name, sig); - let (fn_type, fn_spec) = parse_attributes(meth_attrs); + let spec = FnSpec::parse(name, sig, meth_attrs); - //let mut has_self = false; - let mut py = false; - let mut arguments: Vec = Vec::new(); - - for input in sig.decl.inputs.iter() { - match input { - &syn::FnArg::SelfRef(_, _) => { - //has_self = true; - }, - &syn::FnArg::SelfValue(_) => { - //has_self = true; - } - &syn::FnArg::Captured(ref pat, ref ty) => { - 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(Arg{name: ident, mode: mode, ty: ty, optional: opt}); - } else { - py = true; - } - } - &syn::FnArg::Ignored(_) => - panic!("ignored argument: {:?}", name), - } - } - - match fn_type { + match spec.tp { FnType::Fn => - impl_py_method_def(name, &impl_wrap(cls, name, arguments, fn_spec)), - FnType::Getter(getter) => - impl_py_getter_def(name, getter, &impl_wrap_getter(cls, name, arguments, fn_spec)), - FnType::Setter(setter) => - impl_py_setter_def(name, setter, &impl_wrap_setter(cls, name, arguments, fn_spec)), + impl_py_method_def(name, &impl_wrap(cls, name, &spec)), + FnType::FnNew => + impl_py_method_def_new(name, &impl_wrap_new(cls, name, &spec)), + FnType::FnCall => + impl_py_method_def_call(name, &impl_wrap(cls, name, &spec)), + FnType::Getter(ref getter) => + impl_py_getter_def(name, getter, &impl_wrap_getter(cls, name, &spec)), + FnType::Setter(ref setter) => + impl_py_setter_def(name, setter, &impl_wrap_setter(cls, name, &spec)), } } -fn parse_attributes(attrs: &mut Vec) -> (FnType, Vec) { - let mut new_attrs = Vec::new(); - let mut spec = Vec::new(); - let mut res: Option = None; - - for attr in attrs.iter() { - match attr.value { - syn::MetaItem::Word(ref name) => { - match name.as_ref() { - "setter" | "getter" => { - if attr.style == syn::AttrStyle::Inner { - panic!("Inner style attribute is not - supported for setter and getter"); - } - if res != None { - panic!("setter/getter attribute can not be used mutiple times"); - } - if name.as_ref() == "setter" { - res = Some(FnType::Setter(None)) - } else { - res = Some(FnType::Getter(None)) - } - }, - _ => { - new_attrs.push(attr.clone()) - } - } - }, - syn::MetaItem::List(ref name, ref meta) => { - match name.as_ref() { - "setter" | "getter" => { - if attr.style == syn::AttrStyle::Inner { - panic!("Inner style attribute is not - supported for setter and getter"); - } - if res != None { - panic!("setter/getter attribute can not be used mutiple times"); - } - if meta.len() != 1 { - panic!("setter/getter requires one value"); - } - match *meta.first().unwrap() { - syn::NestedMetaItem::MetaItem(syn::MetaItem::Word(ref w)) => { - if name.as_ref() == "setter" { - res = Some(FnType::Setter(Some(w.to_string()))) - } else { - res = Some(FnType::Getter(Some(w.to_string()))) - } - }, - syn::NestedMetaItem::Literal(ref lit) => { - match *lit { - syn::Lit::Str(ref s, syn::StrStyle::Cooked) => { - if name.as_ref() == "setter" { - res = Some(FnType::Setter(Some(s.clone()))) - } else { - res = Some(FnType::Getter(Some(s.clone()))) - } - }, - _ => { - panic!("setter/getter attribute requires str value"); - }, - } - } - _ => { - println!("cannot parse {:?} attribute: {:?}", name, meta); - }, - } - }, - "args" => { - spec.extend(parse_args(meta)) - } - "defaults" => { - // parse: #[defaults(param2=12, param3=12)] - for item in meta.iter() { - if let Some(el) = parse_args_default(item) { - spec.push(el) - } - } - } - _ => { - new_attrs.push(attr.clone()) - } - } - }, - syn::MetaItem::NameValue(_, _) => { - new_attrs.push(attr.clone()) - }, - } - } - attrs.clear(); - attrs.extend(new_attrs); - - match res { - Some(tp) => (tp, spec), - None => (FnType::Fn, spec), - } -} - -/// parse: #[args(args="args", kw="kwargs")] -fn parse_args(items: &Vec) -> Vec { - let mut spec = Vec::new(); - - for item in items.iter() { - match item { - &syn::NestedMetaItem::MetaItem(syn::MetaItem::NameValue(ref ident, ref name)) => { - match *name { - syn::Lit::Str(ref name, _) => match ident.as_ref() { - "args" => - spec.push(FnSpec::Args(syn::Ident::from(name.clone()))), - "kw" => - spec.push(FnSpec::Kwargs(syn::Ident::from(name.clone()))), - _ => (), - }, - _ => (), - } - }, - _ => (), - } - } - - spec -} - -fn parse_args_default(item: &syn::NestedMetaItem) -> Option { - match *item { - syn::NestedMetaItem::MetaItem(syn::MetaItem::NameValue(ref name, ref lit)) => { - let mut t = Tokens::new(); - match lit { - &syn::Lit::Str(ref val, _) => { - syn::Ident::from(val.as_str()).to_tokens(&mut t); - }, - _ => { - lit.to_tokens(&mut t); - } - } - Some(FnSpec::Default(name.clone(), t)) - } - _ => { - println!("expected name value {:?}", item); - None - } - } -} fn check_generic(name: &syn::Ident, sig: &syn::MethodSig) { if !sig.generics.ty_params.is_empty() { @@ -228,62 +35,47 @@ fn check_generic(name: &syn::Ident, sig: &syn::MethodSig) { } } -fn check_arg_ty_and_optional<'a>(name: &'a syn::Ident, ty: &'a syn::Ty) -> Option<&'a syn::Ty> { - match ty { - &syn::Ty::Path(ref qs, ref path) => { - if let &Some(ref qs) = qs { - panic!("explicit Self type in a 'qualified path' is not supported: {:?} - {:?}", - name, qs); - } - if let Some(segment) = path.segments.last() { - match segment.ident.as_ref() { - "Option" => { - match segment.parameters { - syn::PathParameters::AngleBracketed(ref params) => { - if params.types.len() != 1 { - panic!("argument type is not supported by python method: {:?} ({:?}) {:?}", - for_err_msg(name), - for_err_msg(ty), - for_err_msg(path)); - } - Some(¶ms.types[0]) - }, - _ => { - panic!("argument type is not supported by python method: {:?} ({:?}) {:?}", - for_err_msg(name), - for_err_msg(ty), - for_err_msg(path)); - } - } - }, - _ => None, - } - } else { - None - } - }, - _ => { - None - //panic!("argument type is not supported by python method: {:?} ({:?})", - //for_err_msg(name), - //for_err_msg(ty)); - }, +/// Generate function wrapper (PyCFunction, PyCFunctionWithKeywords) +pub fn impl_wrap(cls: &Box, name: &syn::Ident, spec: &FnSpec) -> Tokens { + let cb = impl_call(cls, name, &spec); + let body = impl_arg_params(&spec, cb); + + quote! { + 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::handle_callback( + LOCATION, _pyo3::callback::PyObjectCallbackConverter, |py| + { + let args: _pyo3::PyTuple = + _pyo3::PyObject::from_borrowed_ptr(py, args).unchecked_cast_into(); + let kwargs: Option<_pyo3::PyDict> = _pyo3::argparse::get_kwargs(py, kwargs); + + let ret = { + #body + }; + _pyo3::PyDrop::release_ref(args, py); + _pyo3::PyDrop::release_ref(kwargs, py); + ret + }) + } } } -/// Generate functiona wrapper (PyCFunction, PyCFunctionWithKeywords) -fn impl_wrap(cls: &Box, - name: &syn::Ident, - args: Vec, spec: Vec) -> Tokens { - let cb = impl_call(cls, name, &args); - let body = impl_arg_params(args, &spec, cb); + +/// 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); + let body = impl_arg_params(spec, cb); quote! { - unsafe extern "C" fn wrap - (slf: *mut _pyo3::ffi::PyObject, - args: *mut _pyo3::ffi::PyObject, - kwargs: *mut _pyo3::ffi::PyObject) -> *mut _pyo3::ffi::PyObject + unsafe extern "C" fn wrap(cls: *mut _pyo3::ffi::PyTypeObject, + args: *mut _pyo3::ffi::PyObject, + kwargs: *mut _pyo3::ffi::PyObject) -> *mut _pyo3::ffi::PyObject { const LOCATION: &'static str = concat!( stringify!(#cls), ".", stringify!(#name), "()"); @@ -307,8 +99,7 @@ fn impl_wrap(cls: &Box, /// Generate functiona wrapper (PyCFunction, PyCFunctionWithKeywords) -fn impl_wrap_getter(cls: &Box, - name: &syn::Ident, _args: Vec, _spec: Vec) -> Tokens { +fn impl_wrap_getter(cls: &Box, name: &syn::Ident, _spec: &FnSpec) -> Tokens { quote! { unsafe extern "C" fn wrap (slf: *mut _pyo3::ffi::PyObject, _: *mut _pyo3::c_void) @@ -330,10 +121,8 @@ fn impl_wrap_getter(cls: &Box, } /// Generate functiona wrapper (PyCFunction, PyCFunctionWithKeywords) -fn impl_wrap_setter(cls: &Box, - name: &syn::Ident, args: Vec, _spec: Vec) -> Tokens { - - let val_ty = args[0].ty; +fn impl_wrap_setter(cls: &Box, name: &syn::Ident, spec: &FnSpec) -> Tokens { + let val_ty = spec.args[0].ty; quote! { unsafe extern "C" fn wrap(slf: *mut _pyo3::ffi::PyObject, @@ -365,8 +154,8 @@ fn impl_wrap_setter(cls: &Box, } -fn impl_call(cls: &Box, fname: &syn::Ident, args: &Vec) -> Tokens { - let names: Vec<&syn::Ident> = args.iter().map(|item| item.name).collect(); +fn impl_call(cls: &Box, fname: &syn::Ident, spec: &FnSpec) -> Tokens { + let names: Vec<&syn::Ident> = spec.args.iter().map(|item| item.name).collect(); quote! { { let slf = _pyo3::PyObject::from_borrowed_ptr(py, slf).unchecked_cast_into::<#cls>(); @@ -377,16 +166,28 @@ fn impl_call(cls: &Box, fname: &syn::Ident, args: &Vec) -> Tokens } } -fn impl_arg_params(mut args: Vec, spec: &Vec, body: Tokens) -> 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! { + { + let cls = _pyo3::PyType::from_type_ptr(py, cls); + let ret = #cls::#fname(&cls, py, #(#names),*); + _pyo3::PyDrop::release_ref(cls, py); + ret + } + } +} + +fn impl_arg_params(spec: &FnSpec, body: Tokens) -> Tokens { let mut params = Vec::new(); - for arg in args.iter() { - if ! (is_args(&arg.name, &spec) || is_kwargs(&arg.name, &spec)) { + for arg in spec.args.iter() { + if ! (spec.is_args(&arg.name) || spec.is_kwargs(&arg.name)) { let name = arg.name.as_ref(); let opt = if let Some(_) = arg.optional { syn::Ident::from("true") } else { - if let Some(_) = get_default_value(&arg.name, spec) { + if let Some(_) = spec.default_value(&arg.name) { syn::Ident::from("true") } else { syn::Ident::from("false") @@ -403,16 +204,17 @@ fn impl_arg_params(mut args: Vec, spec: &Vec, body: Tokens) -> Toke |_| syn::Ident::from("None")).collect(); // generate extrat args - args.reverse(); + let mut rargs = spec.args.clone(); + rargs.reverse(); let mut body = body; - for arg in args.iter() { + for arg in spec.args.iter() { body = impl_arg_param(&arg, &spec, &body); } let accept_args = syn::Ident::from( - if accept_args(spec) { "true" } else { "false" }); + if spec.accept_args() { "true" } else { "false" }); let accept_kwargs = syn::Ident::from( - if accept_kwargs(spec) { "true" } else { "false" }); + if spec.accept_kwargs() { "true" } else { "false" }); // create array of arguments, and then parse quote! { @@ -434,7 +236,7 @@ fn impl_arg_params(mut args: Vec, spec: &Vec, body: Tokens) -> Toke } } -fn impl_arg_param(arg: &Arg, spec: &Vec, body: &Tokens) -> Tokens { +fn impl_arg_param(arg: &FnArg, spec: &FnSpec, body: &Tokens) -> Tokens { let ty = arg.ty; let name = arg.name; @@ -442,7 +244,7 @@ fn impl_arg_param(arg: &Arg, spec: &Vec, body: &Tokens) -> Tokens { // second unwrap() asserts the parameter was not missing (which fn // parse_args already checked for). - if is_args(&name, &spec) { + if spec.is_args(&name) { quote! { match <#ty as _pyo3::FromPyObject>::extract(py, args.as_object()) { @@ -453,7 +255,7 @@ fn impl_arg_param(arg: &Arg, spec: &Vec, body: &Tokens) -> Tokens { } } } - else if is_kwargs(&name, &spec) { + else if spec.is_kwargs(&name) { quote! { let #name = kwargs.as_ref(); #body @@ -463,7 +265,7 @@ fn impl_arg_param(arg: &Arg, spec: &Vec, body: &Tokens) -> Tokens { if let Some(ref opt_ty) = arg.optional { // default value let mut default = Tokens::new(); - if let Some(d) = get_default_value(name, spec) { + if let Some(d) = spec.default_value(name) { let dt = quote!{ Some(#d) }; dt.to_tokens(&mut default); } else { @@ -488,7 +290,7 @@ fn impl_arg_param(arg: &Arg, spec: &Vec, body: &Tokens) -> Tokens { Err(e) => Err(e) } } - } else if let Some(default) = get_default_value(name, spec) { + } else if let Some(default) = spec.default_value(name) { quote! { match match _iter.next().unwrap().as_ref() { Some(obj) => { @@ -523,63 +325,7 @@ fn impl_arg_param(arg: &Arg, spec: &Vec, body: &Tokens) -> Tokens { } } -fn is_args(name: &syn::Ident, spec: &Vec) -> bool { - for s in spec.iter() { - match *s { - FnSpec::Args(ref ident) => - return name == ident, - _ => (), - } - } - false -} - -fn accept_args(spec: &Vec) -> bool { - for s in spec.iter() { - match *s { - FnSpec::Args(_) => return true, - _ => (), - } - } - false -} - -fn is_kwargs(name: &syn::Ident, spec: &Vec) -> bool { - for s in spec.iter() { - match *s { - FnSpec::Kwargs(ref ident) => - return name == ident, - _ => (), - } - } - false -} - -fn accept_kwargs(spec: &Vec) -> bool { - for s in spec.iter() { - match *s { - FnSpec::Kwargs(_) => return true, - _ => (), - } - } - false -} - -fn get_default_value<'a>(name: &syn::Ident, spec: &'a Vec) -> Option<&'a Tokens> { - for s in spec.iter() { - match *s { - FnSpec::Default(ref ident, ref val) => { - if ident == name { - return Some(val) - } - }, - _ => (), - } - } - None -} - -fn impl_py_method_def(name: &syn::Ident, wrapper: &Tokens) -> Tokens { +pub fn impl_py_method_def(name: &syn::Ident, wrapper: &Tokens) -> Tokens { quote! { _pyo3::class::PyMethodDefType::Method({ #wrapper @@ -594,8 +340,38 @@ fn impl_py_method_def(name: &syn::Ident, wrapper: &Tokens) -> Tokens { } } -fn impl_py_setter_def(name: &syn::Ident, setter: Option, wrapper: &Tokens) -> Tokens { - let n = if let Some(ref name) = setter { +pub fn impl_py_method_def_new(name: &syn::Ident, wrapper: &Tokens) -> Tokens { + quote! { + _pyo3::class::PyMethodDefType::New({ + #wrapper + + _pyo3::class::PyMethodDef { + ml_name: stringify!(#name), + ml_meth: _pyo3::class::PyMethodType::PyNewFunc(wrap), + ml_flags: _pyo3::ffi::METH_VARARGS | _pyo3::ffi::METH_KEYWORDS, + ml_doc: "", + } + }) + } +} + +pub fn impl_py_method_def_call(name: &syn::Ident, wrapper: &Tokens) -> Tokens { + quote! { + _pyo3::class::PyMethodDefType::Call({ + #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, + ml_doc: "", + } + }) + } +} + +fn impl_py_setter_def(name: &syn::Ident, setter: &Option, wrapper: &Tokens) -> Tokens { + let n = if let &Some(ref name) = setter { name.to_string() } else { let n = String::from(name.as_ref()); @@ -619,8 +395,8 @@ fn impl_py_setter_def(name: &syn::Ident, setter: Option, wrapper: &Token } } -fn impl_py_getter_def(name: &syn::Ident, getter: Option, wrapper: &Tokens) -> Tokens { - let n = if let Some(ref name) = getter { +fn impl_py_getter_def(name: &syn::Ident, getter: &Option, wrapper: &Tokens) -> Tokens { + let n = if let &Some(ref name) = getter { name.to_string() } else { let n = String::from(name.as_ref()); diff --git a/pyo3cls/src/py_proto.rs b/pyo3cls/src/py_proto.rs index 2e87a557..99787e2f 100644 --- a/pyo3cls/src/py_proto.rs +++ b/pyo3cls/src/py_proto.rs @@ -4,6 +4,7 @@ use syn; use quote::{Tokens, ToTokens}; use py_method; +use method::FnSpec; use func::{MethodProto, impl_method_proto}; @@ -26,10 +27,6 @@ static DEFAULT_METHODS: Methods = Methods { methods: &[], }; -static CONTEXT_METHODS: Methods = Methods { - methods: &["__enter__", "__exit__"], -}; - static DESCR_METHODS: Methods = Methods { methods: &["__delete__", "__set_name__"], }; @@ -75,6 +72,29 @@ static ASYNC: Proto = Proto { ], }; +static CONTEXT: Proto = Proto { + name: "Context", + methods: &[ + MethodProto::Unary{ + name: "__enter__", + proto: "_pyo3::class::context::PyContextEnterProtocol"}, + MethodProto::Quaternary { + name: "__exit__", + arg1: "ExcType", arg2: "ExcValue", arg3: "Traceback", + proto: "_pyo3::class::context::PyContextExitProtocol"}, + ], + py_methods: &[ + PyMethod { + name: "__enter__", + proto: "_pyo3::class::context::PyContextEnterProtocolImpl", + }, + PyMethod { + name: "__exit__", + proto: "_pyo3::class::context::PyContextExitProtocolImpl", + }, + ], +}; + static ITER: Proto = Proto { name: "Iter", py_methods: &[], @@ -128,12 +148,11 @@ pub fn build_py_proto(ast: &mut syn::Item) -> Tokens { impl_proto_impl(ty, impl_items, &MAPPING), "PyIterProtocol" => impl_proto_impl(ty, impl_items, &ITER), + "PyContextProtocol" => + impl_proto_impl(ty, impl_items, &CONTEXT), "PyBufferProtocol" => impl_protocol("_pyo3::class::buffer::PyBufferProtocolImpl", path.clone(), ty, impl_items, &DEFAULT_METHODS), - "PyContextProtocol" => - impl_protocol("_pyo3::class::context::PyContextProtocolImpl", - path.clone(), ty, impl_items, &CONTEXT_METHODS), "PyDescrProtocol" => impl_protocol("_pyo3::class::descr::PyDescrProtocolImpl", path.clone(), ty, impl_items, &DESCR_METHODS), @@ -176,16 +195,23 @@ fn impl_proto_impl(ty: &Box, impls: &mut Vec, proto: &Pr let name = syn::Ident::from(m.name); let proto = syn::Ident::from(m.proto); - let meth = py_method::gen_py_method( - ty, &iimpl.ident, sig, &mut iimpl.attrs); + let fn_spec = FnSpec::parse( + &iimpl.ident, sig, &mut iimpl.attrs); + let meth = py_method::impl_wrap(ty, &iimpl.ident, &fn_spec); py_methods.push( quote! { impl #proto for #ty { #[inline] - fn #name() -> Option<_pyo3::class::methods::PyMethodDefType> { - Some(#meth) + fn #name() -> Option<_pyo3::class::methods::PyMethodDef> { + #meth + + Some(_pyo3::class::PyMethodDef { + ml_name: stringify!(#name), + ml_meth: _pyo3::class::PyMethodType::PyCFunctionWithKeywords(wrap), + ml_flags: _pyo3::ffi::METH_VARARGS | _pyo3::ffi::METH_KEYWORDS, + ml_doc: ""}) } } } diff --git a/src/class/async.rs b/src/class/async.rs index 7e1ab8f7..3db2abea 100644 --- a/src/class/async.rs +++ b/src/class/async.rs @@ -10,7 +10,7 @@ use ffi; use err::PyResult; use python::{Python, PythonObject}; use callback::PyObjectCallbackConverter; -use class::methods::{PyMethodDef, PyMethodDefType}; +use class::methods::PyMethodDef; /// Awaitable interface @@ -99,13 +99,11 @@ impl PyAsyncProtocolImpl for T where T: PyAsyncProtocol { fn methods() -> Vec { let mut methods = Vec::new(); - if let Some(PyMethodDefType::Method(meth)) = - ::__aenter__() { - methods.push(meth) - } - if let Some(PyMethodDefType::Method(meth)) = - ::__aexit__() { - methods.push(meth) + if let Some(def) = ::__aenter__() { + methods.push(def) + } + if let Some(def) = ::__aexit__() { + methods.push(def) } methods @@ -180,27 +178,27 @@ impl PyAsyncAnextProtocolImpl for T } trait PyAsyncAenterProtocolImpl { - fn __aenter__() -> Option; + fn __aenter__() -> Option; } impl PyAsyncAenterProtocolImpl for T where T: PyAsyncProtocol { #[inline] - default fn __aenter__() -> Option { + default fn __aenter__() -> Option { None } } pub trait PyAsyncAexitProtocolImpl { - fn __aexit__() -> Option; + fn __aexit__() -> Option; } impl PyAsyncAexitProtocolImpl for T where T: PyAsyncProtocol { #[inline] - default fn __aexit__() -> Option { + default fn __aexit__() -> Option { None } } diff --git a/src/class/context.rs b/src/class/context.rs index 7c569913..bf8cd03a 100644 --- a/src/class/context.rs +++ b/src/class/context.rs @@ -5,50 +5,88 @@ //! use err::PyResult; -use python::Python; -use objects::PyObject; -use class::{NO_METHODS, NO_PY_METHODS}; +use python::{Python, PythonObject}; +use class::methods::PyMethodDef; -/// Awaitable interface -pub trait PyContextProtocol { +/// Context manager interface +#[allow(unused_variables)] +pub trait PyContextProtocol: PythonObject { - fn __enter__(&self, py: Python) -> PyResult; + fn __enter__(&self, py: Python) + -> Self::Result where Self: PyContextEnterProtocol { unimplemented!() } fn __exit__(&self, py: Python, - exc_type: Option, - exc_value: Option, - traceback: Option) -> PyResult; + exc_type: Option, + exc_value: Option, + traceback: Option) + -> Self::Result where Self: PyContextExitProtocol { unimplemented!() } } - -impl

PyContextProtocol for P { - - default fn __enter__(&self, py: Python) -> PyResult { - Ok(py.None()) - } - - default fn __exit__(&self, py: Python, - _exc_type: Option, - _exc_value: Option, - _traceback: Option) -> PyResult { - Ok(py.None()) - } +pub trait PyContextEnterProtocol: PyContextProtocol { + type Success: ::ToPyObject; + type Result: Into>; } +pub trait PyContextExitProtocol: PyContextProtocol { + type ExcType: for<'a> ::FromPyObject<'a>; + type ExcValue: for<'a> ::FromPyObject<'a>; + type Traceback: for<'a> ::FromPyObject<'a>; + type Success: ::ToPyObject; + type Result: Into>; +} #[doc(hidden)] pub trait PyContextProtocolImpl { - fn methods() -> &'static [&'static str]; - - fn py_methods() -> &'static [::methods::PyMethodDefType]; + fn methods() -> Vec; } impl PyContextProtocolImpl for T { - default fn methods() -> &'static [&'static str] { - NO_METHODS - } - default fn py_methods() -> &'static [::methods::PyMethodDefType] { - NO_PY_METHODS + #[inline] + default fn methods() -> Vec { + Vec::new() + } +} + +impl PyContextProtocolImpl for T where T: PyContextProtocol { + #[inline] + fn methods() -> Vec { + let mut methods = Vec::new(); + + if let Some(def) = ::__enter__() { + methods.push(def) + } + if let Some(def) = ::__exit__() { + methods.push(def) + } + + methods + } +} + +#[doc(hidden)] +trait PyContextEnterProtocolImpl { + fn __enter__() -> Option; +} + +impl PyContextEnterProtocolImpl for T + where T: PyContextProtocol +{ + #[inline] + default fn __enter__() -> Option { + None + } +} + +pub trait PyContextExitProtocolImpl { + fn __exit__() -> Option; +} + +impl PyContextExitProtocolImpl for T + where T: PyContextProtocol +{ + #[inline] + default fn __exit__() -> Option { + None } } diff --git a/src/class/methods.rs b/src/class/methods.rs index cc508421..f0ab03bf 100644 --- a/src/class/methods.rs +++ b/src/class/methods.rs @@ -6,6 +6,8 @@ use ffi; use class::NO_PY_METHODS; pub enum PyMethodDefType { + New(PyMethodDef), + Call(PyMethodDef), Method(PyMethodDef), Getter(PyGetterDef), Setter(PySetterDef), @@ -16,6 +18,7 @@ pub enum PyMethodType { PyCFunction(ffi::PyCFunction), PyCFunctionWithKeywords(ffi::PyCFunctionWithKeywords), PyNoArgsFunction(ffi::PyNoArgsFunction), + PyNewFunc(ffi::newfunc), } #[derive(Copy, Clone)] @@ -63,6 +66,11 @@ impl PyMethodDef { ::std::mem::transmute::< ffi::PyNoArgsFunction, ffi::PyCFunction>(meth) }, + PyMethodType::PyNewFunc(meth) => + unsafe { + ::std::mem::transmute::< + ffi::newfunc, ffi::PyCFunction>(meth) + }, }; ffi::PyMethodDef { diff --git a/src/class/typeob.rs b/src/class/typeob.rs index e0e9a25c..21008b2d 100644 --- a/src/class/typeob.rs +++ b/src/class/typeob.rs @@ -53,7 +53,7 @@ pub fn initialize_type(py: Python, module_name: Option<&str>, type_name: &str // type name let name = match module_name { Some(module_name) => CString::new(format!("{}.{}", module_name, type_name)), - None => CString::new(stringify!(type_name)) + None => CString::new(type_name) }; let name = name.expect( "Module name/type name must not contain NUL byte").into_raw(); @@ -127,7 +127,7 @@ pub fn initialize_type(py: Python, module_name: Option<&str>, type_name: &str } // normal methods - let mut methods = py_class_method_defs::(); + let (new, call, mut methods) = py_class_method_defs::(); if !methods.is_empty() { methods.push(ffi::PyMethodDef_INIT); type_object.tp_methods = methods.as_ptr() as *mut _; @@ -137,6 +137,10 @@ pub fn initialize_type(py: Python, module_name: Option<&str>, type_name: &str mem::forget(methods); } + // __new__ method + type_object.tp_new = new; + // __call__ method + type_object.tp_call = call; // properties let mut props = py_class_properties::(); @@ -171,15 +175,12 @@ unsafe extern "C" fn tp_dealloc_callback(obj: *mut ffi::PyObject) where T: Ba r } -fn py_class_method_defs() -> Vec { +fn py_class_method_defs() -> (Option, + Option, + Vec) { let mut defs = Vec::new(); - - for def in ::py_methods() { - match def { - &PyMethodDefType::Method(ref def) => defs.push(def.as_method_def()), - _ => (), - } - } + let mut call = None; + let mut new = None; for def in ::py_methods() { match def { @@ -189,15 +190,32 @@ fn py_class_method_defs() -> Vec { } for def in ::py_methods() { match def { - &PyMethodDefType::Method(ref def) => defs.push(def.as_method_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) => { + 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()) + } - defs + (new, call, defs) }