diff --git a/CHANGELOG.md b/CHANGELOG.md index 6c2c4654..c7a0d5f1 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -23,6 +23,9 @@ and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0. * `PyRawObject::init` is now infallible, e.g. it returns `()` instead of `PyResult<()>`. * Renamed `py_exception!` to `create_exception!` and refactored the error macros. * Renamed `wrap_function!` to `wrap_pyfunction!` + * Renamed `#[prop(get, set)]` to `#[pyo3(get, set)]` + * `#[pyfunction]` now supports the same arguments as `#[pyfn()]` + * Some macros now emit proper spanned errors instead of panics. * Migrated to the 2018 edition * Replace `IntoPyTuple` with `IntoPy>`. Eventually `IntoPy` should replace `ToPyObject` and be itself implemented through `FromPy` * PyTypeObject is now a direct subtrait PyTypeCreate, removing the old cyclical implementation in [#350](https://github.com/PyO3/pyo3/pull/350) diff --git a/README.md b/README.md index 717af978..28785ec1 100644 --- a/README.md +++ b/README.md @@ -15,7 +15,7 @@ A comparison with rust-cpython can be found [in the guide](https://pyo3.rs/maste ## Usage -Pyo3 supports python 2.7 as well as python 3.5 and up. The minimum required rust version is 1.30.0-nightly 2018-08-18. +Pyo3 supports python 2.7 as well as python 3.5 and up. The minimum required rust version is 1.34.0-nightly 2019-02-06. You can either write a native python module in rust or use python from a rust binary. diff --git a/examples/word-count/src/lib.rs b/examples/word-count/src/lib.rs index f6aeb81e..cfec0fc1 100644 --- a/examples/word-count/src/lib.rs +++ b/examples/word-count/src/lib.rs @@ -16,7 +16,7 @@ struct WordCounter { #[pymethods] impl WordCounter { #[new] - fn __new__(obj: &PyRawObject, path: String) -> PyResult<()> { + fn new(obj: &PyRawObject, path: String) -> PyResult<()> { Ok(obj.init(WordCounter { path: PathBuf::from(path), })) diff --git a/guide/src/class.md b/guide/src/class.md index 39995aa4..54def62b 100644 --- a/guide/src/class.md +++ b/guide/src/class.md @@ -279,7 +279,7 @@ For simple cases you can also define getters and setters in your Rust struct fie # use pyo3::prelude::*; #[pyclass] struct MyClass { - #[prop(get, set)] + #[pyo3(get, set)] num: i32 } ``` diff --git a/pyo3-derive-backend/src/args.rs b/pyo3-derive-backend/src/args.rs deleted file mode 100644 index 80b6a385..00000000 --- a/pyo3-derive-backend/src/args.rs +++ /dev/null @@ -1,250 +0,0 @@ -// Copyright (c) 2017-present PyO3 Project and Contributors - -use quote::quote; -use syn; - -#[derive(Debug, Clone, PartialEq)] -pub enum Argument { - VarArgsSeparator, - VarArgs(syn::Ident), - KeywordArgs(syn::Ident), - Arg(syn::Ident, Option), - Kwarg(syn::Ident, String), -} - -pub fn parse_arguments(items: &[syn::NestedMeta]) -> Vec { - let mut arguments = Vec::new(); - let mut has_kw = false; - let mut has_varargs = false; - let mut has_kwargs = false; - - let args_str = quote! { - #(#items),* - } - .to_string(); - - for item in items.iter() { - match item { - syn::NestedMeta::Meta(syn::Meta::Word(ref ident)) => { - // arguments in form #[args(somename)] - if has_kwargs { - println!("syntax error, keyword arguments is defined: {:?}", args_str); - return Vec::new(); - } - if has_kw { - println!( - "syntax error, argument is not allowed after keyword argument: {:?}", - args_str - ); - return Vec::new(); - } - arguments.push(Argument::Arg(ident.clone(), None)) - } - syn::NestedMeta::Meta(syn::Meta::NameValue(ref nv)) => { - match nv.lit { - syn::Lit::Str(ref litstr) => { - if litstr.value() == "*" { - // #[args(args="*")] - if has_kwargs { - println!( - "* - syntax error, keyword arguments is defined: {:?}", - args_str - ); - return Vec::new(); - } - if has_varargs { - println!("*(var args) is defined: {:?}", args_str); - return Vec::new(); - } - has_varargs = true; - arguments.push(Argument::VarArgs(nv.ident.clone())); - } else if litstr.value() == "**" { - // #[args(kwargs="**")] - if has_kwargs { - println!("arguments already define ** (kw args): {:?}", args_str); - return Vec::new(); - } - has_kwargs = true; - arguments.push(Argument::KeywordArgs(nv.ident.clone())); - } else { - if has_varargs { - arguments - .push(Argument::Kwarg(nv.ident.clone(), litstr.value().clone())) - } else { - if has_kwargs { - println!( - "syntax error, keyword arguments is defined: {:?}", - args_str - ); - return Vec::new(); - } - has_kw = true; - arguments.push(Argument::Arg( - nv.ident.clone(), - Some(litstr.value().clone()), - )) - } - } - } - syn::Lit::Int(ref litint) => { - if has_varargs { - arguments.push(Argument::Kwarg( - nv.ident.clone(), - format!("{}", litint.value()), - )); - } else { - if has_kwargs { - println!( - "syntax error, keyword arguments is defined: {:?}", - args_str - ); - return Vec::new(); - } - has_kw = true; - arguments.push(Argument::Arg( - nv.ident.clone(), - Some(format!("{}", litint.value())), - )); - } - } - syn::Lit::Bool(ref litb) => { - if has_varargs { - arguments - .push(Argument::Kwarg(nv.ident.clone(), format!("{}", litb.value))); - } else { - if has_kwargs { - println!( - "syntax error, keyword arguments is defined: {:?}", - args_str - ); - return Vec::new(); - } - has_kw = true; - arguments.push(Argument::Arg( - nv.ident.clone(), - Some(format!("{}", litb.value)), - )); - } - } - _ => { - println!("Only string literal is supported, got: {:?}", nv.lit); - return Vec::new(); - } - } - } - syn::NestedMeta::Literal(ref lit) => { - match lit { - &syn::Lit::Str(ref lits) => { - // #[args("*")] - if lits.value() == "*" { - if has_kwargs { - println!( - "syntax error, keyword arguments is defined: {:?}", - args_str - ); - return Vec::new(); - } - if has_varargs { - println!("arguments already define * (var args): {:?}", args_str); - return Vec::new(); - } - has_varargs = true; - arguments.push(Argument::VarArgsSeparator); - } else { - println!( - "Unknown string literal, got: {:?} args: {:?}", - lits.value(), - args_str - ); - return Vec::new(); - } - } - _ => { - println!( - "Only string literal is supported, got: {:?} args: {:?}", - lit, args_str - ); - return Vec::new(); - } - } - } - _ => { - println!("Unknown argument, got: {:?} args: {:?}", item, args_str); - return Vec::new(); - } - } - } - - arguments -} - -#[cfg(test)] -mod test { - - use crate::args::{parse_arguments, Argument}; - use proc_macro2::TokenStream; - use syn; - - fn items(s: TokenStream) -> Vec { - let dummy: syn::ItemFn = syn::parse_quote! {#s fn dummy() {}}; - match dummy.attrs[0].interpret_meta() { - Some(syn::Meta::List(syn::MetaList { nested, .. })) => { - nested.iter().map(Clone::clone).collect() - } - _ => unreachable!(), - } - } - - #[test] - fn test_errs() { - assert!(parse_arguments(&items(quote! {#[args(test="1", test2)]})).is_empty()); - assert!(parse_arguments(&items(quote! {#[args(test=1, "*", args="*")]})).is_empty()); - assert!( - parse_arguments(&items(quote! {#[args(test=1, kwargs="**", args="*")]})).is_empty() - ); - assert!(parse_arguments(&items(quote! {#[args(test=1, kwargs="**", args)]})).is_empty()); - } - - #[test] - fn test_simple_args() { - let args = parse_arguments(&items(quote! {#[args(test1, test2, test3="None")]})); - assert!( - args == vec![ - Argument::Arg(syn::parse_quote! {test1}, None), - Argument::Arg(syn::parse_quote! {test2}, None), - Argument::Arg(syn::parse_quote! {test3}, Some("None".to_owned())), - ] - ); - } - - #[test] - fn test_varargs() { - let args = parse_arguments(&items( - quote! {#[args(test1, test2="None", "*", test3="None")]}, - )); - assert!( - args == vec![ - Argument::Arg(syn::parse_quote! {test1}, None), - Argument::Arg(syn::parse_quote! {test2}, Some("None".to_owned())), - Argument::VarArgsSeparator, - Argument::Kwarg(syn::parse_quote! {test3}, "None".to_owned()), - ] - ); - } - - #[test] - fn test_all() { - let args = parse_arguments(&items( - quote! {#[args(test1, test2="None", args="*", test3="None", kwargs="**")]}, - )); - assert!( - args == vec![ - Argument::Arg(syn::parse_quote! {test1}, None), - Argument::Arg(syn::parse_quote! {test2}, Some("None".to_owned())), - Argument::VarArgs(syn::parse_quote! {args}), - Argument::Kwarg(syn::parse_quote! {test3}, "None".to_owned()), - Argument::KeywordArgs(syn::parse_quote! {kwargs}), - ] - ); - } -} diff --git a/pyo3-derive-backend/src/func.rs b/pyo3-derive-backend/src/func.rs index e30ac04c..a40c309f 100644 --- a/pyo3-derive-backend/src/func.rs +++ b/pyo3-derive-backend/src/func.rs @@ -2,7 +2,6 @@ use crate::utils::print_err; use proc_macro2::{Span, TokenStream}; use quote::quote; -use syn; // TODO: // Add lifetime support for args with Rptr @@ -55,8 +54,8 @@ pub enum MethodProto { }, } -impl MethodProto { - pub fn eq(&self, name: &str) -> bool { +impl PartialEq for MethodProto { + fn eq(&self, name: &str) -> bool { match *self { MethodProto::Free { name: n, .. } => n == name, MethodProto::Unary { name: n, .. } => n == name, @@ -81,274 +80,221 @@ pub fn impl_method_proto( }; } - if let syn::ReturnType::Type(_, ref ty) = sig.decl.output.clone() { - match *meth { - MethodProto::Free { .. } => unreachable!(), - MethodProto::Unary { pyres, proto, .. } => { - let p: syn::Path = syn::parse_str(proto).unwrap(); - let (ty, succ) = get_res_success(ty); + let ty = &*if let syn::ReturnType::Type(_, ref ty) = sig.decl.output { + ty.clone() + } else { + panic!("fn return type is not supported") + }; - let tmp: syn::ItemFn = syn::parse_quote! { - fn test(&self) -> <#cls as #p<'p>>::Result {} - }; - sig.decl.output = tmp.decl.output; - modify_self_ty(sig); + match *meth { + MethodProto::Free { .. } => unreachable!(), + MethodProto::Unary { pyres, proto, .. } => { + let p: syn::Path = syn::parse_str(proto).unwrap(); + let (ty, succ) = get_res_success(ty); - if pyres { - quote! { - impl<'p> #p<'p> for #cls { - type Success = #succ; - type Result = #ty; - } + let tmp: syn::ItemFn = syn::parse_quote! { + fn test(&self) -> <#cls as #p<'p>>::Result {} + }; + sig.decl.output = tmp.decl.output; + modify_self_ty(sig); + + if pyres { + quote! { + impl<'p> #p<'p> for #cls { + type Success = #succ; + type Result = #ty; } - } else { - quote! { - impl<'p> #p<'p> for #cls { - type Result = #ty; - } + } + } else { + quote! { + impl<'p> #p<'p> for #cls { + type Result = #ty; } } } - MethodProto::Binary { - name, - arg, - pyres, - proto, - } => { - if sig.decl.inputs.len() <= 1 { - println!("Not enough arguments for {}", name); - return TokenStream::new(); - } + } + MethodProto::Binary { + name, + arg, + pyres, + proto, + } => { + if sig.decl.inputs.len() <= 1 { + println!("Not enough arguments for {}", name); + return TokenStream::new(); + } - let p: syn::Path = syn::parse_str(proto).unwrap(); - let arg_name = syn::Ident::new(arg, Span::call_site()); - let arg_ty = get_arg_ty(sig, 1); - let (ty, succ) = get_res_success(ty); + let p: syn::Path = syn::parse_str(proto).unwrap(); + let arg_name = syn::Ident::new(arg, Span::call_site()); + let arg_ty = get_arg_ty(sig, 1); + let (ty, succ) = get_res_success(ty); - let tmp = extract_decl(syn::parse_quote! { - fn test(&self,arg: <#cls as #p<'p>>::#arg_name)-> <#cls as #p<'p>>::Result {} - }); + let tmp = extract_decl(syn::parse_quote! { + fn test(&self,arg: <#cls as #p<'p>>::#arg_name)-> <#cls as #p<'p>>::Result {} + }); - let tmp2 = extract_decl(syn::parse_quote! { - fn test(&self, arg: Option<<#cls as #p<'p>>::#arg_name>) -> <#cls as #p<'p>>::Result {} - }); + let tmp2 = extract_decl(syn::parse_quote! { + fn test(&self, arg: Option<<#cls as #p<'p>>::#arg_name>) -> <#cls as #p<'p>>::Result {} + }); - modify_arg_ty(sig, 1, &tmp, &tmp2); - modify_self_ty(sig); + modify_arg_ty(sig, 1, &tmp, &tmp2); + modify_self_ty(sig); - if pyres { - quote! { - impl<'p> #p<'p> for #cls { - type #arg_name = #arg_ty; - type Success = #succ; - type Result = #ty; - } + if pyres { + quote! { + impl<'p> #p<'p> for #cls { + type #arg_name = #arg_ty; + type Success = #succ; + type Result = #ty; } - } else { - quote! { - impl<'p> #p<'p> for #cls { - type #arg_name = #arg_ty; - type Result = #ty; - } + } + } else { + quote! { + impl<'p> #p<'p> for #cls { + type #arg_name = #arg_ty; + type Result = #ty; } } } - MethodProto::BinaryS { - name, - arg1, - arg2, - pyres, - proto, - } => { - if sig.decl.inputs.len() <= 1 { - print_err(format!("Not enough arguments {}", name), quote!(sig)); - return TokenStream::new(); - } - let p: syn::Path = syn::parse_str(proto).unwrap(); - let arg1_name = syn::Ident::new(arg1, Span::call_site()); - let arg1_ty = get_arg_ty(sig, 0); - let arg2_name = syn::Ident::new(arg2, Span::call_site()); - let arg2_ty = get_arg_ty(sig, 1); - let (ty, succ) = get_res_success(ty); + } + MethodProto::BinaryS { + name, + arg1, + arg2, + pyres, + proto, + } => { + if sig.decl.inputs.len() <= 1 { + print_err(format!("Not enough arguments {}", name), quote!(sig)); + return TokenStream::new(); + } + let p: syn::Path = syn::parse_str(proto).unwrap(); + let arg1_name = syn::Ident::new(arg1, Span::call_site()); + let arg1_ty = get_arg_ty(sig, 0); + let arg2_name = syn::Ident::new(arg2, Span::call_site()); + let arg2_ty = get_arg_ty(sig, 1); + let (ty, succ) = get_res_success(ty); - // rewrite ty - let tmp = extract_decl(syn::parse_quote! {fn test( - arg1: <#cls as #p<'p>>::#arg1_name, - arg2: <#cls as #p<'p>>::#arg2_name) - -> <#cls as #p<'p>>::Result {}}); - let tmp2 = extract_decl(syn::parse_quote! {fn test( - arg1: Option<<#cls as #p<'p>>::#arg1_name>, - arg2: Option<<#cls as #p<'p>>::#arg2_name>) - -> <#cls as #p<'p>>::Result {}}); - modify_arg_ty(sig, 0, &tmp, &tmp2); - modify_arg_ty(sig, 1, &tmp, &tmp2); + // rewrite ty + let tmp = extract_decl(syn::parse_quote! {fn test( + arg1: <#cls as #p<'p>>::#arg1_name, + arg2: <#cls as #p<'p>>::#arg2_name) + -> <#cls as #p<'p>>::Result {}}); + let tmp2 = extract_decl(syn::parse_quote! {fn test( + arg1: Option<<#cls as #p<'p>>::#arg1_name>, + arg2: Option<<#cls as #p<'p>>::#arg2_name>) + -> <#cls as #p<'p>>::Result {}}); + modify_arg_ty(sig, 0, &tmp, &tmp2); + modify_arg_ty(sig, 1, &tmp, &tmp2); - if pyres { - quote! { - impl<'p> #p<'p> for #cls { - type #arg1_name = #arg1_ty; - type #arg2_name = #arg2_ty; - type Success = #succ; - type Result = #ty; - } + if pyres { + quote! { + impl<'p> #p<'p> for #cls { + type #arg1_name = #arg1_ty; + type #arg2_name = #arg2_ty; + type Success = #succ; + type Result = #ty; } - } else { - quote! { - impl<'p> #p<'p> for #cls { - type #arg1_name = #arg1_ty; - type #arg2_name = #arg2_ty; - type Result = #ty; - } + } + } else { + quote! { + impl<'p> #p<'p> for #cls { + type #arg1_name = #arg1_ty; + type #arg2_name = #arg2_ty; + type Result = #ty; } } } - MethodProto::Ternary { - name, - arg1, - arg2, - pyres, - proto, - } => { - if sig.decl.inputs.len() <= 2 { - print_err(format!("Not enough arguments {}", name), quote!(sig)); - return TokenStream::new(); - } - let p: syn::Path = syn::parse_str(proto).unwrap(); - let arg1_name = syn::Ident::new(arg1, Span::call_site()); - let arg1_ty = get_arg_ty(sig, 1); - let arg2_name = syn::Ident::new(arg2, Span::call_site()); - let arg2_ty = get_arg_ty(sig, 2); - let (ty, succ) = get_res_success(ty); + } + MethodProto::Ternary { + name, + arg1, + arg2, + pyres, + proto, + } => { + if sig.decl.inputs.len() <= 2 { + print_err(format!("Not enough arguments {}", name), quote!(sig)); + return TokenStream::new(); + } + let p: syn::Path = syn::parse_str(proto).unwrap(); + let arg1_name = syn::Ident::new(arg1, Span::call_site()); + let arg1_ty = get_arg_ty(sig, 1); + let arg2_name = syn::Ident::new(arg2, Span::call_site()); + let arg2_ty = get_arg_ty(sig, 2); + let (ty, succ) = get_res_success(ty); - // rewrite ty - let tmp = extract_decl(syn::parse_quote! {fn test( - &self, - arg1: <#cls as #p<'p>>::#arg1_name, - arg2: <#cls as #p<'p>>::#arg2_name) - -> <#cls as #p<'p>>::Result {}}); - let tmp2 = extract_decl(syn::parse_quote! {fn test( - &self, - arg1: Option<<#cls as #p<'p>>::#arg1_name>, - arg2: Option<<#cls as #p<'p>>::#arg2_name>) - -> <#cls as #p<'p>>::Result {}}); - modify_arg_ty(sig, 1, &tmp, &tmp2); - modify_arg_ty(sig, 2, &tmp, &tmp2); - modify_self_ty(sig); + // rewrite ty + let tmp = extract_decl(syn::parse_quote! {fn test( + &self, + arg1: <#cls as #p<'p>>::#arg1_name, + arg2: <#cls as #p<'p>>::#arg2_name) + -> <#cls as #p<'p>>::Result {}}); + let tmp2 = extract_decl(syn::parse_quote! {fn test( + &self, + arg1: Option<<#cls as #p<'p>>::#arg1_name>, + arg2: Option<<#cls as #p<'p>>::#arg2_name>) + -> <#cls as #p<'p>>::Result {}}); + modify_arg_ty(sig, 1, &tmp, &tmp2); + modify_arg_ty(sig, 2, &tmp, &tmp2); + modify_self_ty(sig); - if pyres { - quote! { - impl<'p> #p<'p> for #cls { - type #arg1_name = #arg1_ty; - type #arg2_name = #arg2_ty; - type Success = #succ; - type Result = #ty; - } + if pyres { + quote! { + impl<'p> #p<'p> for #cls { + type #arg1_name = #arg1_ty; + type #arg2_name = #arg2_ty; + type Success = #succ; + type Result = #ty; } - } else { - quote! { - impl<'p> #p<'p> for #cls { - type #arg1_name = #arg1_ty; - type #arg2_name = #arg2_ty; - type Result = #ty; - } + } + } else { + quote! { + impl<'p> #p<'p> for #cls { + type #arg1_name = #arg1_ty; + type #arg2_name = #arg2_ty; + type Result = #ty; } } } - MethodProto::TernaryS { - name, - arg1, - arg2, - arg3, - pyres, - proto, - } => { - if sig.decl.inputs.len() <= 2 { - print_err(format!("Not enough arguments {}", name), quote!(sig)); - return TokenStream::new(); - } - let p: syn::Path = syn::parse_str(proto).unwrap(); - let arg1_name = syn::Ident::new(arg1, Span::call_site()); - let arg1_ty = get_arg_ty(sig, 0); - let arg2_name = syn::Ident::new(arg2, Span::call_site()); - let arg2_ty = get_arg_ty(sig, 1); - let arg3_name = syn::Ident::new(arg3, Span::call_site()); - let arg3_ty = get_arg_ty(sig, 2); - let (ty, succ) = get_res_success(ty); - - // rewrite ty - let tmp = extract_decl(syn::parse_quote! {fn test( - arg1: <#cls as #p<'p>>::#arg1_name, - arg2: <#cls as #p<'p>>::#arg2_name, - arg3: <#cls as #p<'p>>::#arg3_name) - -> <#cls as #p<'p>>::Result {}}); - let tmp2 = extract_decl(syn::parse_quote! {fn test( - arg1: Option<<#cls as #p<'p>>::#arg1_name>, - arg2: Option<<#cls as #p<'p>>::#arg2_name>, - arg3: Option<<#cls as #p<'p>>::#arg3_name>) - -> <#cls as #p<'p>>::Result {}}); - modify_arg_ty(sig, 0, &tmp, &tmp2); - modify_arg_ty(sig, 1, &tmp, &tmp2); - modify_arg_ty(sig, 2, &tmp, &tmp2); - - if pyres { - quote! { - impl<'p> #p<'p> for #cls { - type #arg1_name = #arg1_ty; - type #arg2_name = #arg2_ty; - type #arg3_name = #arg3_ty; - type Success = #succ; - type Result = #ty; - } - } - } else { - quote! { - impl<'p> #p<'p> for #cls { - type #arg1_name = #arg1_ty; - type #arg2_name = #arg2_ty; - type #arg3_name = #arg3_ty; - type Result = #ty; - } - } - } + } + MethodProto::TernaryS { + name, + arg1, + arg2, + arg3, + pyres, + proto, + } => { + if sig.decl.inputs.len() <= 2 { + print_err(format!("Not enough arguments {}", name), quote!(sig)); + return TokenStream::new(); } - MethodProto::Quaternary { - name, - arg1, - arg2, - arg3, - proto, - } => { - if sig.decl.inputs.len() <= 3 { - print_err(format!("Not enough arguments {}", name), quote!(sig)); - return TokenStream::new(); - } - let p: syn::Path = syn::parse_str(proto).unwrap(); - let arg1_name = syn::Ident::new(arg1, Span::call_site()); - let arg1_ty = get_arg_ty(sig, 1); - let arg2_name = syn::Ident::new(arg2, Span::call_site()); - let arg2_ty = get_arg_ty(sig, 2); - let arg3_name = syn::Ident::new(arg3, Span::call_site()); - let arg3_ty = get_arg_ty(sig, 3); - let (ty, succ) = get_res_success(ty); + let p: syn::Path = syn::parse_str(proto).unwrap(); + let arg1_name = syn::Ident::new(arg1, Span::call_site()); + let arg1_ty = get_arg_ty(sig, 0); + let arg2_name = syn::Ident::new(arg2, Span::call_site()); + let arg2_ty = get_arg_ty(sig, 1); + let arg3_name = syn::Ident::new(arg3, Span::call_site()); + let arg3_ty = get_arg_ty(sig, 2); + let (ty, succ) = get_res_success(ty); - // rewrite ty - let tmp = extract_decl(syn::parse_quote! {fn test( - &self, - arg1: <#cls as #p<'p>>::#arg1_name, - arg2: <#cls as #p<'p>>::#arg2_name, - arg3: <#cls as #p<'p>>::#arg3_name) - -> <#cls as #p<'p>>::Result {}}); - let tmp2 = extract_decl(syn::parse_quote! {fn test( - &self, - arg1: Option<<#cls as #p<'p>>::#arg1_name>, - arg2: Option<<#cls as #p<'p>>::#arg2_name>, - arg3: Option<<#cls as #p<'p>>::#arg3_name>) - -> <#cls as #p<'p>>::Result {}}); - modify_arg_ty(sig, 1, &tmp, &tmp2); - modify_arg_ty(sig, 2, &tmp, &tmp2); - modify_arg_ty(sig, 3, &tmp, &tmp2); - modify_self_ty(sig); + // rewrite ty + let tmp = extract_decl(syn::parse_quote! {fn test( + arg1: <#cls as #p<'p>>::#arg1_name, + arg2: <#cls as #p<'p>>::#arg2_name, + arg3: <#cls as #p<'p>>::#arg3_name) + -> <#cls as #p<'p>>::Result {}}); + let tmp2 = extract_decl(syn::parse_quote! {fn test( + arg1: Option<<#cls as #p<'p>>::#arg1_name>, + arg2: Option<<#cls as #p<'p>>::#arg2_name>, + arg3: Option<<#cls as #p<'p>>::#arg3_name>) + -> <#cls as #p<'p>>::Result {}}); + modify_arg_ty(sig, 0, &tmp, &tmp2); + modify_arg_ty(sig, 1, &tmp, &tmp2); + modify_arg_ty(sig, 2, &tmp, &tmp2); + if pyres { quote! { impl<'p> #p<'p> for #cls { type #arg1_name = #arg1_ty; @@ -358,10 +304,65 @@ pub fn impl_method_proto( type Result = #ty; } } + } else { + quote! { + impl<'p> #p<'p> for #cls { + type #arg1_name = #arg1_ty; + type #arg2_name = #arg2_ty; + type #arg3_name = #arg3_ty; + type Result = #ty; + } + } + } + } + MethodProto::Quaternary { + name, + arg1, + arg2, + arg3, + proto, + } => { + if sig.decl.inputs.len() <= 3 { + print_err(format!("Not enough arguments {}", name), quote!(sig)); + return TokenStream::new(); + } + let p: syn::Path = syn::parse_str(proto).unwrap(); + let arg1_name = syn::Ident::new(arg1, Span::call_site()); + let arg1_ty = get_arg_ty(sig, 1); + let arg2_name = syn::Ident::new(arg2, Span::call_site()); + let arg2_ty = get_arg_ty(sig, 2); + let arg3_name = syn::Ident::new(arg3, Span::call_site()); + let arg3_ty = get_arg_ty(sig, 3); + let (ty, succ) = get_res_success(ty); + + // rewrite ty + let tmp = extract_decl(syn::parse_quote! {fn test( + &self, + arg1: <#cls as #p<'p>>::#arg1_name, + arg2: <#cls as #p<'p>>::#arg2_name, + arg3: <#cls as #p<'p>>::#arg3_name) + -> <#cls as #p<'p>>::Result {}}); + let tmp2 = extract_decl(syn::parse_quote! {fn test( + &self, + arg1: Option<<#cls as #p<'p>>::#arg1_name>, + arg2: Option<<#cls as #p<'p>>::#arg2_name>, + arg3: Option<<#cls as #p<'p>>::#arg3_name>) + -> <#cls as #p<'p>>::Result {}}); + modify_arg_ty(sig, 1, &tmp, &tmp2); + modify_arg_ty(sig, 2, &tmp, &tmp2); + modify_arg_ty(sig, 3, &tmp, &tmp2); + modify_self_ty(sig); + + quote! { + impl<'p> #p<'p> for #cls { + type #arg1_name = #arg1_ty; + type #arg2_name = #arg2_ty; + type #arg3_name = #arg3_ty; + type Success = #succ; + type Result = #ty; + } } } - } else { - panic!("fn return type is not supported") } } @@ -372,18 +373,15 @@ fn get_arg_ty(sig: &syn::MethodSig, idx: usize) -> syn::Type { match cap.ty { syn::Type::Path(ref ty) => { // use only last path segment for Option<> - let seg = ty.path.segments.last().unwrap().value().clone(); + let seg = *ty.path.segments.last().unwrap().value(); if seg.ident == "Option" { - match seg.arguments { - syn::PathArguments::AngleBracketed(ref data) => { - if let Some(pair) = data.args.last() { - match pair.value() { - syn::GenericArgument::Type(ref ty) => return ty.clone(), - _ => panic!("Option only accepted for concrete types"), - } - }; - } - _ => (), + if let syn::PathArguments::AngleBracketed(ref data) = seg.arguments { + if let Some(pair) = data.args.last() { + match pair.value() { + syn::GenericArgument::Type(ref ty) => return ty.clone(), + _ => panic!("Option only accepted for concrete types"), + } + }; } } cap.ty.clone() @@ -408,7 +406,7 @@ fn get_res_success(ty: &syn::Type) -> (TokenStream, syn::GenericArgument) { let mut succ; match ty { - &syn::Type::Path(ref typath) => { + syn::Type::Path(ref typath) => { if let Some(segment) = typath.path.segments.last() { match segment.value().ident.to_string().as_str() { // check for PyResult @@ -418,23 +416,20 @@ fn get_res_success(ty: &syn::Type) -> (TokenStream, syn::GenericArgument) { succ = data.args[0].clone(); // check for PyResult> - match data.args[0] { - syn::GenericArgument::Type(syn::Type::Path(ref typath)) => { - if let Some(segment) = typath.path.segments.last() { - match segment.value().ident.to_string().as_str() { - // get T from Option - "Option" => match segment.value().arguments { - syn::PathArguments::AngleBracketed(ref data) => { - result = false; - succ = data.args[0].clone(); - } - _ => (), - }, - _ => (), + if let syn::GenericArgument::Type(syn::Type::Path(ref typath)) = + data.args[0] + { + if let Some(segment) = typath.path.segments.last() { + if "Option" == segment.value().ident.to_string().as_str() { + // get T from Option + if let syn::PathArguments::AngleBracketed(ref data) = + segment.value().arguments + { + result = false; + succ = data.args[0].clone(); } } } - _ => (), } } _ => panic!("fn result type is not supported"), @@ -474,7 +469,7 @@ fn modify_arg_ty(sig: &mut syn::MethodSig, idx: usize, decl1: &syn::FnDecl, decl match arg { syn::FnArg::Captured(ref cap) => match cap.ty { syn::Type::Path(ref typath) => { - let seg = typath.path.segments.last().unwrap().value().clone(); + let seg = *typath.path.segments.last().unwrap().value(); if seg.ident == "Option" { sig.decl.inputs[idx] = fix_name(&cap.pat, &decl2.inputs[idx]); } else { diff --git a/pyo3-derive-backend/src/lib.rs b/pyo3-derive-backend/src/lib.rs index 4c32f63c..cedf1dcc 100644 --- a/pyo3-derive-backend/src/lib.rs +++ b/pyo3-derive-backend/src/lib.rs @@ -3,13 +3,20 @@ #![recursion_limit = "1024"] -pub mod args; -pub mod defs; -pub mod func; -pub mod method; -pub mod module; -pub mod py_class; -pub mod py_impl; -pub mod py_method; -pub mod py_proto; -pub mod utils; +mod defs; +mod func; +mod method; +mod module; +mod pyclass; +mod pyfunction; +mod pyimpl; +mod pymethod; +mod pyproto; +mod utils; + +pub use module::{add_fn_to_module, process_functions_in_module, py2_init, py3_init}; +pub use pyclass::{build_py_class, PyClassArgs}; +pub use pyfunction::PyFunctionAttr; +pub use pyimpl::build_py_methods; +pub use pyproto::build_py_proto; +pub use utils::get_doc; diff --git a/pyo3-derive-backend/src/method.rs b/pyo3-derive-backend/src/method.rs index 5d1211d5..cc46f2d4 100644 --- a/pyo3-derive-backend/src/method.rs +++ b/pyo3-derive-backend/src/method.rs @@ -1,10 +1,10 @@ // Copyright (c) 2017-present PyO3 Project and Contributors -use crate::args::{parse_arguments, Argument}; +use crate::pyfunction::Argument; +use crate::pyfunction::PyFunctionAttr; use proc_macro2::TokenStream; use quote::quote; use quote::ToTokens; -use syn; #[derive(Clone, PartialEq, Debug)] pub struct FnArg<'a> { @@ -50,21 +50,21 @@ impl<'a> FnSpec<'a> { name: &'a syn::Ident, sig: &'a syn::MethodSig, meth_attrs: &'a mut Vec, - ) -> FnSpec<'a> { - let (fn_type, fn_attrs) = parse_attributes(meth_attrs); + ) -> syn::Result> { + let (fn_type, fn_attrs) = parse_attributes(meth_attrs)?; let mut has_self = false; let mut arguments = Vec::new(); for input in sig.decl.inputs.iter() { match input { - &syn::FnArg::SelfRef(_) => { + syn::FnArg::SelfRef(_) => { has_self = true; } - &syn::FnArg::SelfValue(_) => { + syn::FnArg::SelfValue(_) => { has_self = true; } - &syn::FnArg::Captured(syn::ArgCaptured { + syn::FnArg::Captured(syn::ArgCaptured { ref pat, ref ty, .. }) => { // skip first argument (cls) @@ -74,17 +74,19 @@ impl<'a> FnSpec<'a> { } let (ident, by_ref, mutability) = match pat { - &syn::Pat::Ident(syn::PatIdent { + syn::Pat::Ident(syn::PatIdent { ref ident, ref by_ref, ref mutability, .. }) => (ident, by_ref, mutability), - _ => panic!("unsupported argument: {:?}", pat), + _ => { + return Err(syn::Error::new_spanned(pat, "unsupported argument")); + } }; let py = match ty { - &syn::Type::Path(syn::TypePath { ref path, .. }) => { + syn::Type::Path(syn::TypePath { ref path, .. }) => { if let Some(segment) = path.segments.last() { segment.value().ident == "Python" } else { @@ -100,32 +102,35 @@ impl<'a> FnSpec<'a> { by_ref, mutability, // mode: mode, - ty: ty, + ty, optional: opt, - py: py, + py, reference: is_ref(name, ty), }); } - &syn::FnArg::Ignored(_) => panic!("ignored argument: {:?}", name), - &syn::FnArg::Inferred(_) => panic!("ingerred argument: {:?}", name), + syn::FnArg::Ignored(_) => { + return Err(syn::Error::new_spanned(name, "ignored argument")); + } + syn::FnArg::Inferred(_) => { + return Err(syn::Error::new_spanned(name, "inferred argument")); + } } } let ty = get_return_info(&sig.decl.output); - FnSpec { + Ok(FnSpec { tp: fn_type, attrs: fn_attrs, args: arguments, output: ty, - } + }) } pub fn is_args(&self, name: &syn::Ident) -> bool { for s in self.attrs.iter() { - match *s { - Argument::VarArgs(ref ident) => return name == ident, - _ => (), + if let Argument::VarArgs(ref ident) = s { + return name == ident; } } false @@ -144,9 +149,8 @@ impl<'a> FnSpec<'a> { pub fn is_kwargs(&self, name: &syn::Ident) -> bool { for s in self.attrs.iter() { - match *s { - Argument::KeywordArgs(ref ident) => return name == ident, - _ => (), + if let Argument::KeywordArgs(ref ident) = s { + return name == ident; } } false @@ -154,9 +158,8 @@ impl<'a> FnSpec<'a> { pub fn accept_kwargs(&self) -> bool { for s in self.attrs.iter() { - match *s { - Argument::KeywordArgs(_) => return true, - _ => (), + if let Argument::KeywordArgs(_) = s { + return true; } } false @@ -167,7 +170,7 @@ impl<'a> FnSpec<'a> { match *s { Argument::Arg(ref ident, ref opt) => { if ident == name { - if let &Some(ref val) = opt { + if let Some(ref val) = opt { let i: syn::Expr = syn::parse_str(&val).unwrap(); return Some(i.into_token_stream()); } @@ -187,36 +190,33 @@ impl<'a> FnSpec<'a> { pub fn is_kw_only(&self, name: &syn::Ident) -> bool { for s in self.attrs.iter() { - match *s { - Argument::Kwarg(ref ident, _) => { - if ident == name { - return true; - } + if let Argument::Kwarg(ref ident, _) = s { + if ident == name { + return true; } - _ => (), } } false } } -pub fn is_ref<'a>(name: &'a syn::Ident, ty: &'a syn::Type) -> bool { +pub fn is_ref(name: &syn::Ident, ty: &syn::Type) -> bool { match ty { - &syn::Type::Reference(_) => return true, - &syn::Type::Path(syn::TypePath { ref path, .. }) => { + syn::Type::Reference(_) => return true, + syn::Type::Path(syn::TypePath { ref path, .. }) => { if let Some(segment) = path.segments.last() { - match segment.value().ident.to_string().as_str() { - "Option" => match segment.value().arguments { + if "Option" == segment.value().ident.to_string().as_str() { + match segment.value().arguments { syn::PathArguments::AngleBracketed(ref params) => { if params.args.len() != 1 { panic!("argument type is not supported by python method: {:?} ({:?}) {:?}", - name, - ty, - path); + name, + ty, + path); } - match ¶ms.args[params.args.len() - 1] { - &syn::GenericArgument::Type(syn::Type::Reference(_)) => return true, - _ => (), + let last = ¶ms.args[params.args.len() - 1]; + if let syn::GenericArgument::Type(syn::Type::Reference(_)) = last { + return true; } } _ => { @@ -225,8 +225,7 @@ pub fn is_ref<'a>(name: &'a syn::Ident, ty: &'a syn::Type) -> bool { name, ty, path ); } - }, - _ => (), + } } } } @@ -240,8 +239,8 @@ pub fn check_arg_ty_and_optional<'a>( ty: &'a syn::Type, ) -> Option<&'a syn::Type> { match ty { - &syn::Type::Path(syn::TypePath { ref path, .. }) => { - //if let &Some(ref qs) = qs { + syn::Type::Path(syn::TypePath { ref path, .. }) => { + //if let Some(ref qs) = qs { // panic!("explicit Self type in a 'qualified path' is not supported: {:?} - {:?}", // name, qs); //} @@ -252,18 +251,18 @@ pub fn check_arg_ty_and_optional<'a>( syn::PathArguments::AngleBracketed(ref params) => { if params.args.len() != 1 { panic!("argument type is not supported by python method: {:?} ({:?}) {:?}", - name, - ty, - path); + name, + ty, + path); } match ¶ms.args[0] { - &syn::GenericArgument::Type(ref ty) => Some(ty), - _ => panic!("argument type is not supported by python method: {:?} ({:?}) {:?}", - name, - ty, - path), - } + syn::GenericArgument::Type(ref ty) => Some(ty), + _ => panic!("argument type is not supported by python method: {:?} ({:?}) {:?}", + name, + ty, + path), + } } _ => { panic!( @@ -287,7 +286,7 @@ pub fn check_arg_ty_and_optional<'a>( } } -fn parse_attributes(attrs: &mut Vec) -> (FnType, Vec) { +fn parse_attributes(attrs: &mut Vec) -> syn::Result<(FnType, Vec)> { let mut new_attrs = Vec::new(); let mut spec = Vec::new(); let mut res: Option = None; @@ -365,8 +364,8 @@ fn parse_attributes(attrs: &mut Vec) -> (FnType, Vec) } } "args" => { - let args = nested.iter().cloned().collect::>(); - spec.extend(parse_arguments(args.as_slice())) + let attrs = PyFunctionAttr::from_meta(nested)?; + spec.extend(attrs.arguments) } _ => new_attrs.push(attr.clone()), }, @@ -377,7 +376,7 @@ fn parse_attributes(attrs: &mut Vec) -> (FnType, Vec) attrs.extend(new_attrs); match res { - Some(tp) => (tp, spec), - None => (FnType::Fn, spec), + Some(tp) => Ok((tp, spec)), + None => Ok((FnType::Fn, spec)), } } diff --git a/pyo3-derive-backend/src/module.rs b/pyo3-derive-backend/src/module.rs index 76b77526..cd64de32 100644 --- a/pyo3-derive-backend/src/module.rs +++ b/pyo3-derive-backend/src/module.rs @@ -1,18 +1,20 @@ // Copyright (c) 2017-present PyO3 Project and Contributors //! Code generation for the function that initializes a python module and adds classes and function. -use crate::args; use crate::method; -use crate::py_method; +use crate::pyfunction; +use crate::pyfunction::PyFunctionAttr; +use crate::pymethod; +use crate::pymethod::get_arg_names; use crate::utils; use proc_macro2::{Span, TokenStream}; use quote::quote; -use syn; +use syn::Ident; /// Generates the function that is called by the python interpreter to initialize the native /// module -pub fn py3_init(fnname: &syn::Ident, name: &syn::Ident, doc: syn::Lit) -> TokenStream { - let cb_name = syn::Ident::new(&format!("PyInit_{}", name), Span::call_site()); +pub fn py3_init(fnname: &Ident, name: &Ident, doc: syn::Lit) -> TokenStream { + let cb_name = Ident::new(&format!("PyInit_{}", name), Span::call_site()); quote! { #[no_mangle] @@ -25,8 +27,8 @@ pub fn py3_init(fnname: &syn::Ident, name: &syn::Ident, doc: syn::Lit) -> TokenS } } -pub fn py2_init(fnname: &syn::Ident, name: &syn::Ident, doc: syn::Lit) -> TokenStream { - let cb_name = syn::Ident::new(&format!("init{}", name), Span::call_site()); +pub fn py2_init(fnname: &Ident, name: &Ident, doc: syn::Lit) -> TokenStream { + let cb_name = Ident::new(&format!("init{}", name), Span::call_site()); quote! { #[no_mangle] @@ -64,10 +66,10 @@ pub fn process_functions_in_module(func: &mut syn::ItemFn) { } /// Transforms a rust fn arg parsed with syn into a method::FnArg -fn wrap_fn_argument<'a>(input: &'a syn::FnArg, name: &'a syn::Ident) -> Option> { +fn wrap_fn_argument<'a>(input: &'a syn::FnArg, name: &'a Ident) -> Option> { match input { - &syn::FnArg::SelfRef(_) | &syn::FnArg::SelfValue(_) => None, - &syn::FnArg::Captured(ref cap) => { + syn::FnArg::SelfRef(_) | &syn::FnArg::SelfValue(_) => None, + syn::FnArg::Captured(ref cap) => { let (mutability, by_ref, ident) = match cap.pat { syn::Pat::Ident(ref patid) => (&patid.mutability, &patid.by_ref, &patid.ident), _ => panic!("unsupported argument: {:?}", cap.pat), @@ -94,23 +96,23 @@ fn wrap_fn_argument<'a>(input: &'a syn::FnArg, name: &'a syn::Ident) -> Option panic!("ignored argument: {:?}", name), - &syn::FnArg::Inferred(_) => panic!("inferred argument: {:?}", name), + syn::FnArg::Ignored(_) => panic!("ignored argument: {:?}", name), + syn::FnArg::Inferred(_) => panic!("inferred argument: {:?}", name), } } /// Extracts the data from the #[pyfn(...)] attribute of a function fn extract_pyfn_attrs( attrs: &mut Vec, -) -> Option<(syn::Ident, syn::Ident, Vec)> { +) -> Option<(Ident, Ident, Vec)> { let mut new_attrs = Vec::new(); let mut fnname = None; let mut modname = None; let mut fn_attrs = Vec::new(); for attr in attrs.iter() { - match attr.interpret_meta() { - Some(syn::Meta::List(ref list)) if list.ident == "pyfn" => { + match attr.parse_meta() { + Ok(syn::Meta::List(ref list)) if list.ident == "pyfn" => { let meta: Vec<_> = list.nested.iter().cloned().collect(); if meta.len() >= 2 { // read module name @@ -129,7 +131,9 @@ fn extract_pyfn_attrs( } // Read additional arguments if list.nested.len() >= 3 { - fn_attrs = args::parse_arguments(&meta[2..meta.len()]); + fn_attrs = PyFunctionAttr::from_meta(&meta[2..meta.len()]) + .unwrap() + .arguments; } } else { panic!("can not parse 'pyfn' params {:?}", attr); @@ -144,10 +148,10 @@ fn extract_pyfn_attrs( } /// Coordinates the naming of a the add-function-to-python-module function -fn function_wrapper_ident(name: &syn::Ident) -> syn::Ident { +fn function_wrapper_ident(name: &Ident) -> Ident { // Make sure this ident matches the one of wrap_pyfunction // The trim_start_matches("r#") is for https://github.com/dtolnay/syn/issues/478 - syn::Ident::new( + Ident::new( &format!( "__pyo3_get_function_{}", name.to_string().trim_start_matches("r#") @@ -160,8 +164,8 @@ fn function_wrapper_ident(name: &syn::Ident) -> syn::Ident { /// function pub fn add_fn_to_module( func: &syn::ItemFn, - python_name: &syn::Ident, - pyfn_attrs: Vec, + python_name: &Ident, + pyfn_attrs: Vec, ) -> TokenStream { let mut arguments = Vec::new(); @@ -214,25 +218,13 @@ pub fn add_fn_to_module( } /// Generate static function wrapper (PyCFunction, PyCFunctionWithKeywords) -fn function_c_wrapper(name: &syn::Ident, spec: &method::FnSpec<'_>) -> TokenStream { - let names: Vec = spec - .args - .iter() - .enumerate() - .map(|item| { - if item.1.py { - syn::Ident::new("_py", Span::call_site()) - } else { - syn::Ident::new(&format!("arg{}", item.0), Span::call_site()) - } - }) - .collect(); +fn function_c_wrapper(name: &Ident, spec: &method::FnSpec<'_>) -> TokenStream { + let names: Vec = get_arg_names(&spec); let cb = quote! { - ::pyo3::ReturnTypeIntoPyResult::return_type_into_py_result(#name(#(#names),*)) + #name(#(#names),*) }; - let body = py_method::impl_arg_params(spec, cb); - let body_to_result = py_method::body_to_result(&body, spec); + let body = pymethod::impl_arg_params(spec, cb); quote! { unsafe extern "C" fn __wrap( @@ -247,7 +239,8 @@ fn function_c_wrapper(name: &syn::Ident, spec: &method::FnSpec<'_>) -> TokenStre let _args = _py.from_borrowed_ptr::<::pyo3::types::PyTuple>(_args); let _kwargs: Option<&::pyo3::types::PyDict> = _py.from_borrowed_ptr_or_opt(_kwargs); - #body_to_result + #body + ::pyo3::callback::cb_convert( ::pyo3::callback::PyObjectCallbackConverter, _py, _result) } diff --git a/pyo3-derive-backend/src/py_proto.rs b/pyo3-derive-backend/src/py_proto.rs deleted file mode 100644 index f80b69cd..00000000 --- a/pyo3-derive-backend/src/py_proto.rs +++ /dev/null @@ -1,98 +0,0 @@ -// Copyright (c) 2017-present PyO3 Project and Contributors - -use crate::defs; -use crate::func::impl_method_proto; -use crate::method::FnSpec; -use crate::py_method; -use proc_macro2::TokenStream; -use quote::quote; -use quote::ToTokens; -use syn; - -pub fn build_py_proto(ast: &mut syn::ItemImpl) -> TokenStream { - if let Some((_, ref mut path, _)) = ast.trait_ { - let proto = if let Some(ref mut segment) = path.segments.last() { - match segment.value().ident.to_string().as_str() { - "PyObjectProtocol" => &defs::OBJECT, - "PyAsyncProtocol" => &defs::ASYNC, - "PyMappingProtocol" => &defs::MAPPING, - "PyIterProtocol" => &defs::ITER, - "PyContextProtocol" => &defs::CONTEXT, - "PySequenceProtocol" => &defs::SEQ, - "PyNumberProtocol" => &defs::NUM, - "PyDescrProtocol" => &defs::DESCR, - "PyBufferProtocol" => &defs::BUFFER, - "PyGCProtocol" => &defs::GC, - _ => panic!("#[pyproto] can not be used with this block"), - } - } else { - panic!("#[pyproto] can only be used with protocol trait implementations") - }; - - let tokens = impl_proto_impl(&ast.self_ty, &mut ast.items, proto); - - // attach lifetime - let mut seg = path.segments.pop().unwrap().into_value(); - seg.arguments = syn::PathArguments::AngleBracketed(syn::parse_quote! {<'p>}); - path.segments.push(seg); - ast.generics.params = syn::parse_quote! {'p}; - - tokens - } else { - panic!("#[pyproto] can only be used with protocol trait implementations") - } -} - -fn impl_proto_impl( - ty: &syn::Type, - impls: &mut Vec, - proto: &defs::Proto, -) -> TokenStream { - let mut tokens = TokenStream::new(); - let mut py_methods = Vec::new(); - - for iimpl in impls.iter_mut() { - match iimpl { - syn::ImplItem::Method(ref mut met) => { - for m in proto.methods { - if m.eq(met.sig.ident.to_string().as_str()) { - impl_method_proto(ty, &mut met.sig, m).to_tokens(&mut tokens); - } - } - for m in proto.py_methods { - let ident = met.sig.ident.clone(); - if m.name == ident.to_string().as_str() { - let name: syn::Ident = syn::parse_str(m.name).unwrap(); - let proto: syn::Path = syn::parse_str(m.proto).unwrap(); - - let fn_spec = FnSpec::parse(&ident, &mut met.sig, &mut met.attrs); - let meth = py_method::impl_proto_wrap(ty, &ident, &fn_spec); - - py_methods.push(quote! { - impl #proto for #ty - { - #[inline] - 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: ""}) - } - } - }); - } - } - } - _ => (), - } - } - - quote! { - #tokens - - #(#py_methods)* - } -} diff --git a/pyo3-derive-backend/src/py_class.rs b/pyo3-derive-backend/src/pyclass.rs similarity index 64% rename from pyo3-derive-backend/src/py_class.rs rename to pyo3-derive-backend/src/pyclass.rs index 7f7b7ee4..043c5faf 100644 --- a/pyo3-derive-backend/src/py_class.rs +++ b/pyo3-derive-backend/src/pyclass.rs @@ -1,43 +1,170 @@ // Copyright (c) 2017-present PyO3 Project and Contributors use crate::method::{FnArg, FnSpec, FnType}; -use crate::py_method::{ - impl_py_getter_def, impl_py_setter_def, impl_wrap_getter, impl_wrap_setter, -}; +use crate::pymethod::{impl_py_getter_def, impl_py_setter_def, impl_wrap_getter, impl_wrap_setter}; use crate::utils; use proc_macro2::{Span, TokenStream}; use quote::quote; -use std::collections::HashMap; -use syn; +use syn::parse::{Parse, ParseStream}; +use syn::punctuated::Punctuated; +use syn::{parse_quote, Expr, Token}; -pub fn build_py_class(class: &mut syn::ItemStruct, attr: &Vec) -> TokenStream { - let (params, flags, base) = parse_attribute(attr); +/// The parsed arguments of the pyclass macro +pub struct PyClassArgs { + pub freelist: Option, + pub name: Option, + pub flags: Vec, + pub base: syn::TypePath, +} + +impl Parse for PyClassArgs { + fn parse(input: ParseStream) -> syn::parse::Result { + let mut slf = PyClassArgs::default(); + + let vars = Punctuated::::parse_terminated(input)?; + for expr in vars { + slf.add_expr(&expr)?; + } + Ok(slf) + } +} + +impl Default for PyClassArgs { + fn default() -> Self { + PyClassArgs { + freelist: None, + name: 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::PyObjectRef}, + } + } +} + +impl PyClassArgs { + /// Adda single expression from the comma separated list in the attribute, which is + /// either a single word or an assignment expression + fn add_expr(&mut self, expr: &Expr) -> syn::parse::Result<()> { + match expr { + syn::Expr::Path(ref exp) if exp.path.segments.len() == 1 => self.add_path(exp), + syn::Expr::Assign(ref assign) => self.add_assign(assign), + _ => Err(syn::Error::new_spanned(expr, "Could not parse arguments")), + } + } + + /// Match a single flag + fn add_assign(&mut self, assign: &syn::ExprAssign) -> syn::Result<()> { + let key = match *assign.left { + syn::Expr::Path(ref exp) if exp.path.segments.len() == 1 => { + exp.path.segments.first().unwrap().value().ident.to_string() + } + _ => { + return Err(syn::Error::new_spanned(assign, "could not parse argument")); + } + }; + + match key.as_str() { + "freelist" => { + // We allow arbitrary expressions here so you can e.g. use `8*64` + self.freelist = Some(*assign.right.clone()); + } + "name" => match *assign.right { + syn::Expr::Path(ref exp) if exp.path.segments.len() == 1 => { + self.name = Some(exp.clone().into()); + } + _ => { + return Err(syn::Error::new_spanned( + *assign.right.clone(), + "Wrong 'name' format", + )); + } + }, + "extends" => match *assign.right { + syn::Expr::Path(ref exp) => { + self.base = syn::TypePath { + path: exp.path.clone(), + qself: None, + }; + } + _ => { + return Err(syn::Error::new_spanned( + *assign.right.clone(), + "Wrong format for extends", + )); + } + }, + _ => { + return Err(syn::Error::new_spanned( + *assign.left.clone(), + "Unsupported parameter", + )); + } + }; + + Ok(()) + } + + /// Match a key/value flag + fn add_path(&mut self, exp: &syn::ExprPath) -> syn::Result<()> { + let flag = exp.path.segments.first().unwrap().value().ident.to_string(); + let path = match flag.as_str() { + "gc" => { + parse_quote! {::pyo3::typeob::PY_TYPE_FLAG_GC} + } + "weakref" => { + parse_quote! {::pyo3::typeob::PY_TYPE_FLAG_WEAKREF} + } + "subclass" => { + parse_quote! {::pyo3::typeob::PY_TYPE_FLAG_BASETYPE} + } + "dict" => { + parse_quote! {::pyo3::typeob::PY_TYPE_FLAG_DICT} + } + _ => { + return Err(syn::Error::new_spanned( + exp.path.clone(), + "Unsupported parameter", + )); + } + }; + + self.flags.push(syn::Expr::Path(path)); + Ok(()) + } +} + +pub fn build_py_class(class: &mut syn::ItemStruct, attr: &PyClassArgs) -> syn::Result { let doc = utils::get_doc(&class.attrs, true); let mut descriptors = Vec::new(); if let syn::Fields::Named(ref mut fields) = class.fields { for field in fields.named.iter_mut() { - let field_descs = parse_descriptors(field); + let field_descs = parse_descriptors(field)?; if !field_descs.is_empty() { descriptors.push((field.clone(), field_descs)); } } } else { - panic!("#[pyclass] can only be used with C-style structs") + return Err(syn::Error::new_spanned( + &class.fields, + "#[pyclass] can only be used with C-style structs", + )); } - impl_class(&class.ident, &base, doc, params, flags, descriptors) + Ok(impl_class(&class.ident, &attr, doc, descriptors)) } -fn parse_descriptors(item: &mut syn::Field) -> Vec { +/// Parses `#[pyo3(get, set)]` +fn parse_descriptors(item: &mut syn::Field) -> syn::Result> { let mut descs = Vec::new(); let mut new_attrs = Vec::new(); for attr in item.attrs.iter() { - if let Some(syn::Meta::List(ref list)) = attr.interpret_meta() { + if let Ok(syn::Meta::List(ref list)) = attr.parse_meta() { match list.ident.to_string().as_str() { - "prop" => { + "pyo3" => { for meta in list.nested.iter() { - if let &syn::NestedMeta::Meta(ref metaitem) = meta { + if let syn::NestedMeta::Meta(ref metaitem) = meta { match metaitem.name().to_string().as_str() { "get" => { descs.push(FnType::Getter(None)); @@ -45,8 +172,11 @@ fn parse_descriptors(item: &mut syn::Field) -> Vec { "set" => { descs.push(FnType::Setter(None)); } - x => { - panic!(r#"Only "get" and "set" supported are, not "{}""#, x); + _ => { + return Err(syn::Error::new_spanned( + metaitem, + "Only get and set are supported", + )); } } } @@ -60,7 +190,7 @@ fn parse_descriptors(item: &mut syn::Field) -> Vec { } item.attrs.clear(); item.attrs.extend(new_attrs); - descs + Ok(descs) } /// The orphan rule disallows using a generic inventory struct, so we create the whole boilerplate @@ -99,19 +229,17 @@ fn impl_inventory(cls: &syn::Ident) -> TokenStream { fn impl_class( cls: &syn::Ident, - base: &syn::TypePath, + attr: &PyClassArgs, doc: syn::Lit, - params: HashMap<&'static str, syn::Expr>, - flags: Vec, descriptors: Vec<(syn::Field, Vec)>, ) -> TokenStream { - let cls_name = match params.get("name") { + let cls_name = match &attr.name { Some(name) => quote! { #name }.to_string(), - None => quote! { #cls }.to_string(), + None => cls.to_string(), }; let extra = { - if let Some(freelist) = params.get("freelist") { + if let Some(freelist) = &attr.freelist { quote! { impl ::pyo3::freelist::PyObjectWithFreeList for #cls { #[inline] @@ -150,11 +278,11 @@ fn impl_class( // insert space for weak ref let mut has_weakref = false; let mut has_dict = false; - for f in flags.iter() { + for f in attr.flags.iter() { if let syn::Expr::Path(ref epath) = f { - if epath.path == syn::parse_quote! {::pyo3::typeob::PY_TYPE_FLAG_WEAKREF} { + if epath.path == parse_quote! {::pyo3::typeob::PY_TYPE_FLAG_WEAKREF} { has_weakref = true; - } else if epath.path == syn::parse_quote! {::pyo3::typeob::PY_TYPE_FLAG_DICT} { + } else if epath.path == parse_quote! {::pyo3::typeob::PY_TYPE_FLAG_DICT} { has_dict = true; } } @@ -172,6 +300,9 @@ fn impl_class( let inventory_impl = impl_inventory(&cls); + let base = &attr.base; + let flags = &attr.flags; + quote! { impl ::pyo3::typeob::PyTypeInfo for #cls { type Type = #cls; @@ -280,7 +411,7 @@ fn impl_descriptors(cls: &syn::Type, descriptors: Vec<(syn::Field, Vec)> py: true, reference: false, }], - output: syn::parse_quote!(PyResult<()>), + output: parse_quote!(PyResult<()>), }; impl_py_setter_def( &name, @@ -307,82 +438,3 @@ fn impl_descriptors(cls: &syn::Type, descriptors: Vec<(syn::Field, Vec)> } } } - -fn parse_attribute( - args: &Vec, -) -> ( - HashMap<&'static str, syn::Expr>, - Vec, - syn::TypePath, -) { - let mut params = HashMap::new(); - // We need the 0 as value for the constant we're later building using quote for when there - // are no other flags - let mut flags = vec![syn::parse_quote! {0}]; - let mut base: syn::TypePath = syn::parse_quote! {::pyo3::types::PyObjectRef}; - - for expr in args.iter() { - match expr { - // Match a single flag - syn::Expr::Path(ref exp) if exp.path.segments.len() == 1 => { - let flag = exp.path.segments.first().unwrap().value().ident.to_string(); - let path = match flag.as_str() { - "gc" => { - syn::parse_quote! {::pyo3::typeob::PY_TYPE_FLAG_GC} - } - "weakref" => { - syn::parse_quote! {::pyo3::typeob::PY_TYPE_FLAG_WEAKREF} - } - "subclass" => { - syn::parse_quote! {::pyo3::typeob::PY_TYPE_FLAG_BASETYPE} - } - "dict" => { - syn::parse_quote! {::pyo3::typeob::PY_TYPE_FLAG_DICT} - } - param => panic!("Unsupported parameter: {}", param), - }; - - flags.push(syn::Expr::Path(path)); - } - - // Match a key/value flag - syn::Expr::Assign(ref ass) => { - let key = match *ass.left { - syn::Expr::Path(ref exp) if exp.path.segments.len() == 1 => { - exp.path.segments.first().unwrap().value().ident.to_string() - } - _ => panic!("could not parse argument: {:?}", ass), - }; - - match key.as_str() { - "freelist" => { - // TODO: check if int literal - params.insert("freelist", *ass.right.clone()); - } - "name" => match *ass.right { - syn::Expr::Path(ref exp) if exp.path.segments.len() == 1 => { - params.insert("name", exp.clone().into()); - } - _ => panic!("Wrong 'name' format: {:?}", *ass.right), - }, - "extends" => match *ass.right { - syn::Expr::Path(ref exp) => { - base = syn::TypePath { - path: exp.path.clone(), - qself: None, - }; - } - _ => panic!("Wrong 'base' format: {:?}", *ass.right), - }, - _ => { - panic!("Unsupported parameter: {:?}", key); - } - } - } - - _ => panic!("could not parse arguments"), - } - } - - (params, flags, base) -} diff --git a/pyo3-derive-backend/src/pyfunction.rs b/pyo3-derive-backend/src/pyfunction.rs new file mode 100644 index 00000000..f599c105 --- /dev/null +++ b/pyo3-derive-backend/src/pyfunction.rs @@ -0,0 +1,261 @@ +// Copyright (c) 2017-present PyO3 Project and Contributors + +use syn::parse::ParseBuffer; +use syn::punctuated::Punctuated; +use syn::{Ident, NestedMeta}; + +#[derive(Debug, Clone, PartialEq)] +pub enum Argument { + VarArgsSeparator, + VarArgs(syn::Ident), + KeywordArgs(syn::Ident), + Arg(syn::Ident, Option), + Kwarg(syn::Ident, String), +} + +/// The attributes of the pyfunction macro +#[derive(Default)] +pub struct PyFunctionAttr { + pub arguments: Vec, + has_kw: bool, + has_varargs: bool, + has_kwargs: bool, +} + +impl syn::parse::Parse for PyFunctionAttr { + fn parse(input: &ParseBuffer) -> syn::Result { + let attr = Punctuated::::parse_terminated(input)?; + Self::from_meta(&attr) + } +} + +impl PyFunctionAttr { + pub fn from_meta<'a>(iter: impl IntoIterator) -> syn::Result { + let mut slf = PyFunctionAttr::default(); + + for item in iter { + slf.add_item(item)? + } + Ok(slf) + } + + pub fn add_item(&mut self, item: &NestedMeta) -> syn::Result<()> { + match item { + NestedMeta::Meta(syn::Meta::Word(ref ident)) => self.add_work(item, ident)?, + NestedMeta::Meta(syn::Meta::NameValue(ref nv)) => { + self.add_name_value(item, nv)?; + } + NestedMeta::Literal(ref lit) => { + self.add_literal(item, lit)?; + } + _ => { + return Err(syn::Error::new_spanned(item, "Unknown argument")); + } + } + + Ok(()) + } + + fn add_literal(&mut self, item: &NestedMeta, lit: &syn::Lit) -> syn::Result<()> { + match lit { + syn::Lit::Str(ref lits) => { + // "*" + if lits.value() == "*" { + if self.has_kwargs { + return Err(syn::Error::new_spanned( + item, + "syntax error, keyword self.arguments is defined", + )); + } + if self.has_varargs { + return Err(syn::Error::new_spanned( + item, + "self.arguments already define * (var args)", + )); + } + self.has_varargs = true; + self.arguments.push(Argument::VarArgsSeparator); + } else { + return Err(syn::Error::new_spanned(lits, "Unknown string literal")); + } + } + _ => { + return Err(syn::Error::new_spanned( + item, + format!("Only string literal is supported, got: {:?}", lit), + )); + } + }; + Ok(()) + } + + fn add_work(&mut self, item: &NestedMeta, ident: &Ident) -> syn::Result<()> { + // self.arguments in form somename + if self.has_kwargs { + return Err(syn::Error::new_spanned( + item, + "syntax error, keyword self.arguments is defined", + )); + } + if self.has_kw { + return Err(syn::Error::new_spanned( + item, + "syntax error, argument is not allowed after keyword argument", + )); + } + self.arguments.push(Argument::Arg(ident.clone(), None)); + Ok(()) + } + + fn add_name_value(&mut self, item: &NestedMeta, nv: &syn::MetaNameValue) -> syn::Result<()> { + match nv.lit { + syn::Lit::Str(ref litstr) => { + if litstr.value() == "*" { + // args="*" + if self.has_kwargs { + return Err(syn::Error::new_spanned( + item, + "* - syntax error, keyword self.arguments is defined", + )); + } + if self.has_varargs { + return Err(syn::Error::new_spanned(item, "*(var args) is defined")); + } + self.has_varargs = true; + self.arguments.push(Argument::VarArgs(nv.ident.clone())); + } else if litstr.value() == "**" { + // kwargs="**" + if self.has_kwargs { + return Err(syn::Error::new_spanned( + item, + "self.arguments already define ** (kw args)", + )); + } + self.has_kwargs = true; + self.arguments.push(Argument::KeywordArgs(nv.ident.clone())); + } else if self.has_varargs { + self.arguments + .push(Argument::Kwarg(nv.ident.clone(), litstr.value().clone())) + } else { + if self.has_kwargs { + return Err(syn::Error::new_spanned( + item, + "syntax error, keyword self.arguments is defined", + )); + } + self.has_kw = true; + self.arguments.push(Argument::Arg( + nv.ident.clone(), + Some(litstr.value().clone()), + )) + } + } + syn::Lit::Int(ref litint) => { + if self.has_varargs { + self.arguments.push(Argument::Kwarg( + nv.ident.clone(), + format!("{}", litint.value()), + )); + } else { + if self.has_kwargs { + return Err(syn::Error::new_spanned( + item, + "syntax error, keyword self.arguments is defined", + )); + } + self.has_kw = true; + self.arguments.push(Argument::Arg( + nv.ident.clone(), + Some(format!("{}", litint.value())), + )); + } + } + syn::Lit::Bool(ref litb) => { + if self.has_varargs { + self.arguments + .push(Argument::Kwarg(nv.ident.clone(), format!("{}", litb.value))); + } else { + if self.has_kwargs { + return Err(syn::Error::new_spanned( + item, + "syntax error, keyword self.arguments is defined", + )); + } + self.has_kw = true; + self.arguments.push(Argument::Arg( + nv.ident.clone(), + Some(format!("{}", litb.value)), + )); + } + } + _ => { + return Err(syn::Error::new_spanned( + nv.lit.clone(), + "Only string literal is supported", + )); + } + }; + Ok(()) + } +} + +#[cfg(test)] +mod test { + use super::{Argument, PyFunctionAttr}; + use proc_macro2::TokenStream; + use quote::quote; + use syn::parse_quote; + + fn items(input: TokenStream) -> syn::Result> { + let py_fn_attr: PyFunctionAttr = syn::parse2(input)?; + Ok(py_fn_attr.arguments) + } + + #[test] + fn test_errs() { + assert!(items(quote! {test="1", test2}).is_err()); + assert!(items(quote! {test, "*", args="*"}).is_err()); + assert!(items(quote! {test, kwargs="**", args="*"}).is_err()); + assert!(items(quote! {test, kwargs="**", args}).is_err()); + } + + #[test] + fn test_simple_args() { + let args = items(quote! {test1, test2, test3="None"}).unwrap(); + assert!( + args == vec![ + Argument::Arg(parse_quote! {test1}, None), + Argument::Arg(parse_quote! {test2}, None), + Argument::Arg(parse_quote! {test3}, Some("None".to_owned())), + ] + ); + } + + #[test] + fn test_varargs() { + let args = items(quote! {test1, test2="None", "*", test3="None"}).unwrap(); + assert!( + args == vec![ + Argument::Arg(parse_quote! {test1}, None), + Argument::Arg(parse_quote! {test2}, Some("None".to_owned())), + Argument::VarArgsSeparator, + Argument::Kwarg(parse_quote! {test3}, "None".to_owned()), + ] + ); + } + + #[test] + fn test_all() { + let args = + items(quote! {test1, test2="None", args="*", test3="None", kwargs="**"}).unwrap(); + assert!( + args == vec![ + Argument::Arg(parse_quote! {test1}, None), + Argument::Arg(parse_quote! {test2}, Some("None".to_owned())), + Argument::VarArgs(parse_quote! {args}), + Argument::Kwarg(parse_quote! {test3}, "None".to_owned()), + Argument::KeywordArgs(parse_quote! {kwargs}), + ] + ); + } +} diff --git a/pyo3-derive-backend/src/py_impl.rs b/pyo3-derive-backend/src/pyimpl.rs similarity index 63% rename from pyo3-derive-backend/src/py_impl.rs rename to pyo3-derive-backend/src/pyimpl.rs index a3556c40..2ca3bf91 100644 --- a/pyo3-derive-backend/src/py_impl.rs +++ b/pyo3-derive-backend/src/pyimpl.rs @@ -1,17 +1,22 @@ // Copyright (c) 2017-present PyO3 Project and Contributors -use crate::py_method; +use crate::pymethod; use proc_macro2::TokenStream; use quote::quote; -use syn; -pub fn build_py_methods(ast: &mut syn::ItemImpl) -> TokenStream { - if ast.trait_.is_some() { - panic!("#[pymethods] can not be used only with trait impl block"); +pub fn build_py_methods(ast: &mut syn::ItemImpl) -> syn::Result { + if let Some((_, ref path, _)) = ast.trait_ { + Err(syn::Error::new_spanned( + path, + "#[pymethods] can not be used only with trait impl block", + )) } else if ast.generics != Default::default() { - panic!("#[pymethods] can not ve used with lifetime parameters or generics"); + Err(syn::Error::new_spanned( + ast.generics.clone(), + "#[pymethods] can not ve used with lifetime parameters or generics", + )) } else { - impl_methods(&ast.self_ty, &mut ast.items) + Ok(impl_methods(&ast.self_ty, &mut ast.items)) } } @@ -21,7 +26,7 @@ pub fn impl_methods(ty: &syn::Type, impls: &mut Vec) -> TokenStre for iimpl in impls.iter_mut() { if let syn::ImplItem::Method(ref mut meth) = iimpl { let name = meth.sig.ident.clone(); - methods.push(py_method::gen_py_method( + methods.push(pymethod::gen_py_method( ty, &name, &mut meth.sig, diff --git a/pyo3-derive-backend/src/py_method.rs b/pyo3-derive-backend/src/pymethod.rs similarity index 70% rename from pyo3-derive-backend/src/py_method.rs rename to pyo3-derive-backend/src/pymethod.rs index 6b064ed2..b79f7a3b 100644 --- a/pyo3-derive-backend/src/py_method.rs +++ b/pyo3-derive-backend/src/pymethod.rs @@ -4,10 +4,8 @@ use crate::method::{FnArg, FnSpec, FnType}; use crate::utils; use proc_macro2::{Span, TokenStream}; use quote::quote; -use quote::ToTokens; -use syn; -pub fn gen_py_method<'a>( +pub fn gen_py_method( cls: &syn::Type, name: &syn::Ident, sig: &mut syn::MethodSig, @@ -16,7 +14,7 @@ pub fn gen_py_method<'a>( check_generic(name, sig); let doc = utils::get_doc(&meth_attrs, true); - let spec = FnSpec::parse(name, sig, meth_attrs); + let spec = FnSpec::parse(name, sig, meth_attrs).unwrap(); match spec.tp { FnType::Fn => impl_py_method_def(name, doc, &spec, &impl_wrap(cls, name, &spec, true)), @@ -42,15 +40,6 @@ fn check_generic(name: &syn::Ident, sig: &syn::MethodSig) { } } -pub fn body_to_result(body: &TokenStream, spec: &FnSpec<'_>) -> TokenStream { - let output = &spec.output; - quote! { - let _result: ::pyo3::PyResult<<#output as ::pyo3::ReturnTypeIntoPyResult>::Inner> = { - #body - }; - } -} - /// Generate function wrapper (PyCFunction, PyCFunctionWithKeywords) pub fn impl_wrap( cls: &syn::Type, @@ -61,11 +50,10 @@ pub fn impl_wrap( let body = impl_call(cls, name, &spec); if spec.args.is_empty() && noargs { - let body_to_result = body_to_result(&body, spec); - quote! { unsafe extern "C" fn __wrap( - _slf: *mut ::pyo3::ffi::PyObject) -> *mut ::pyo3::ffi::PyObject + _slf: *mut ::pyo3::ffi::PyObject + ) -> *mut ::pyo3::ffi::PyObject { const _LOCATION: &'static str = concat!( stringify!(#cls), ".", stringify!(#name), "()"); @@ -73,14 +61,16 @@ pub fn impl_wrap( let _py = ::pyo3::Python::assume_gil_acquired(); let _slf = _py.mut_from_borrowed_ptr::<#cls>(_slf); - #body_to_result + let _result = { + ::pyo3::IntoPyResult::into_py_result(#body) + }; + ::pyo3::callback::cb_convert( ::pyo3::callback::PyObjectCallbackConverter, _py, _result) } } } else { let body = impl_arg_params(&spec, body); - let body_to_result = body_to_result(&body, spec); quote! { unsafe extern "C" fn __wrap( @@ -96,7 +86,8 @@ pub fn impl_wrap( let _args = _py.from_borrowed_ptr::<::pyo3::types::PyTuple>(_args); let _kwargs: Option<&::pyo3::types::PyDict> = _py.from_borrowed_ptr_or_opt(_kwargs); - #body_to_result + #body + ::pyo3::callback::cb_convert( ::pyo3::callback::PyObjectCallbackConverter, _py, _result) } @@ -123,9 +114,8 @@ pub fn impl_proto_wrap(cls: &syn::Type, name: &syn::Ident, spec: &FnSpec<'_>) -> let _args = _py.from_borrowed_ptr::<::pyo3::types::PyTuple>(_args); let _kwargs: Option<&::pyo3::types::PyDict> = _py.from_borrowed_ptr_or_opt(_kwargs); - let _result = { - #body - }; + #body + ::pyo3::callback::cb_convert( ::pyo3::callback::PyObjectCallbackConverter, _py, _result) } @@ -134,24 +124,10 @@ 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 = spec - .args - .iter() - .enumerate() - .map(|item| { - if item.1.py { - syn::Ident::new("_py", Span::call_site()) - } else { - syn::Ident::new(&format!("arg{}", item.0), Span::call_site()) - } - }) - .collect(); - let cb = quote! { - ::pyo3::ReturnTypeIntoPyResult::return_type_into_py_result(#cls::#name(&_obj, #(#names),*)) - }; + let names: Vec = get_arg_names(&spec); + let cb = quote! { #cls::#name(&_obj, #(#names),*) }; let body = impl_arg_params(spec, cb); - let body_to_result = body_to_result(&body, spec); quote! { #[allow(unused_mut)] @@ -170,7 +146,7 @@ pub fn impl_wrap_new(cls: &syn::Type, name: &syn::Ident, spec: &FnSpec<'_>) -> T let _args = _py.from_borrowed_ptr::<::pyo3::types::PyTuple>(_args); let _kwargs: Option<&::pyo3::types::PyDict> = _py.from_borrowed_ptr_or_opt(_kwargs); - #body_to_result + #body match _result { Ok(_) => ::pyo3::IntoPyPointer::into_ptr(_obj), @@ -200,7 +176,6 @@ fn impl_wrap_init(cls: &syn::Type, name: &syn::Ident, spec: &FnSpec<'_>) -> Toke } let body = impl_arg_params(&spec, cb); - let body_to_result = body_to_result(&body, spec); quote! { #[allow(unused_mut)] @@ -216,7 +191,8 @@ fn impl_wrap_init(cls: &syn::Type, name: &syn::Ident, spec: &FnSpec<'_>) -> Toke let _args = _py.from_borrowed_ptr::<::pyo3::types::PyTuple>(_args); let _kwargs: Option<&::pyo3::types::PyDict> = _py.from_borrowed_ptr_or_opt(_kwargs); - #body_to_result + #body + match _result { Ok(_) => 0, Err(e) => { @@ -230,24 +206,10 @@ fn impl_wrap_init(cls: &syn::Type, name: &syn::Ident, spec: &FnSpec<'_>) -> Toke /// Generate class method wrapper (PyCFunction, PyCFunctionWithKeywords) pub fn impl_wrap_class(cls: &syn::Type, name: &syn::Ident, spec: &FnSpec<'_>) -> TokenStream { - let names: Vec = spec - .args - .iter() - .enumerate() - .map(|item| { - if item.1.py { - syn::Ident::new("_py", Span::call_site()) - } else { - syn::Ident::new(&format!("arg{}", item.0), Span::call_site()) - } - }) - .collect(); - let cb = quote! { - ::pyo3::ReturnTypeIntoPyResult::return_type_into_py_result(#cls::#name(&_cls, #(#names),*)) - }; + let names: Vec = get_arg_names(&spec); + let cb = quote! { #cls::#name(&_cls, #(#names),*) }; let body = impl_arg_params(spec, cb); - let body_to_result = body_to_result(&body, spec); quote! { #[allow(unused_mut)] @@ -263,7 +225,8 @@ pub fn impl_wrap_class(cls: &syn::Type, name: &syn::Ident, spec: &FnSpec<'_>) -> let _args = _py.from_borrowed_ptr::<::pyo3::types::PyTuple>(_args); let _kwargs: Option<&::pyo3::types::PyDict> = _py.from_borrowed_ptr_or_opt(_kwargs); - #body_to_result + #body + ::pyo3::callback::cb_convert( ::pyo3::callback::PyObjectCallbackConverter, _py, _result) } @@ -272,24 +235,10 @@ pub fn impl_wrap_class(cls: &syn::Type, name: &syn::Ident, spec: &FnSpec<'_>) -> /// Generate static method wrapper (PyCFunction, PyCFunctionWithKeywords) pub fn impl_wrap_static(cls: &syn::Type, name: &syn::Ident, spec: &FnSpec<'_>) -> TokenStream { - let names: Vec = spec - .args - .iter() - .enumerate() - .map(|item| { - if item.1.py { - syn::Ident::new("_py", Span::call_site()) - } else { - syn::Ident::new(&format!("arg{}", item.0), Span::call_site()) - } - }) - .collect(); - let cb = quote! { - ::pyo3::ReturnTypeIntoPyResult::return_type_into_py_result(#cls::#name(#(#names),*)) - }; + let names: Vec = get_arg_names(&spec); + let cb = quote! { #cls::#name(#(#names),*) }; let body = impl_arg_params(spec, cb); - let body_to_result = body_to_result(&body, spec); quote! { #[allow(unused_mut)] @@ -304,7 +253,8 @@ pub fn impl_wrap_static(cls: &syn::Type, name: &syn::Ident, spec: &FnSpec<'_>) - let _args = _py.from_borrowed_ptr::<::pyo3::types::PyTuple>(_args); let _kwargs: Option<&::pyo3::types::PyDict> = _py.from_borrowed_ptr_or_opt(_kwargs); - #body_to_result + #body + ::pyo3::callback::cb_convert( ::pyo3::callback::PyObjectCallbackConverter, _py, _result) } @@ -342,7 +292,7 @@ pub(crate) fn impl_wrap_setter( name: &syn::Ident, spec: &FnSpec<'_>, ) -> TokenStream { - if spec.args.len() < 1 { + if spec.args.is_empty() { println!( "Not enough arguments for setter {}::{}", quote! {#cls}, @@ -378,197 +328,162 @@ pub(crate) fn impl_wrap_setter( } } -fn impl_call(_cls: &syn::Type, fname: &syn::Ident, spec: &FnSpec<'_>) -> TokenStream { - let names: Vec = spec - .args +/// This function abstracts away some copied code and can propably be simplified itself +pub fn get_arg_names(spec: &FnSpec) -> Vec { + spec.args .iter() .enumerate() - .map(|item| { - if item.1.py { - syn::Ident::new("_py", Span::call_site()) - } else { - syn::Ident::new(&format!("arg{}", item.0), Span::call_site()) - } - }) - .collect(); - quote! { - ::pyo3::ReturnTypeIntoPyResult::return_type_into_py_result(_slf.#fname(#(#names),*)) + .map(|(pos, _)| syn::Ident::new(&format!("arg{}", pos), Span::call_site())) + .collect() +} + +fn impl_call(_cls: &syn::Type, fname: &syn::Ident, spec: &FnSpec<'_>) -> TokenStream { + let names = get_arg_names(spec); + quote! { _slf.#fname(#(#names),*) } +} + +/// Converts a bool to "true" or "false" +fn bool_to_ident(condition: bool) -> syn::Ident { + if condition { + syn::Ident::new("true", Span::call_site()) + } else { + syn::Ident::new("false", Span::call_site()) } } pub fn impl_arg_params(spec: &FnSpec<'_>, body: TokenStream) -> TokenStream { - let args: Vec> = spec - .args - .iter() - .filter(|item| !item.py) - .map(|item| item.clone()) - .collect(); - if args.is_empty() { - return body; + if spec.args.is_empty() { + return quote! { + let _result = { + ::pyo3::IntoPyResult::into_py_result(#body) + }; + }; } let mut params = Vec::new(); for arg in spec.args.iter() { - if arg.py { + if arg.py || spec.is_args(&arg.name) || spec.is_kwargs(&arg.name) { continue; } - if !(spec.is_args(&arg.name) || spec.is_kwargs(&arg.name)) { - let name = arg.name; - let kwonly = if spec.is_kw_only(&arg.name) { - syn::Ident::new("true", Span::call_site()) - } else { - syn::Ident::new("false", Span::call_site()) - }; + let name = arg.name; + let kwonly = bool_to_ident(spec.is_kw_only(&arg.name)); + let opt = bool_to_ident(arg.optional.is_some() || spec.default_value(&arg.name).is_some()); - let opt = if let Some(_) = arg.optional { - syn::Ident::new("true", Span::call_site()) - } else if let Some(_) = spec.default_value(&arg.name) { - syn::Ident::new("true", Span::call_site()) - } else { - syn::Ident::new("false", Span::call_site()) - }; - - params.push(quote! { - ::pyo3::derive_utils::ParamDescription{ - name: stringify!(#name), is_optional: #opt, kw_only: #kwonly} - }); - } + params.push(quote! { + ::pyo3::derive_utils::ParamDescription { + name: stringify!(#name), + is_optional: #opt, + kw_only: #kwonly + } + }); } let placeholders: Vec = params .iter() .map(|_| syn::Ident::new("None", Span::call_site())) .collect(); - // generate extrat args - let len = spec.args.len(); - let mut rargs = spec.args.clone(); - rargs.reverse(); - let mut body = body; - - for (idx, arg) in rargs.iter().enumerate() { - body = impl_arg_param(&arg, &spec, &body, len - idx - 1); + let mut param_conversion = Vec::new(); + let mut option_pos = 0; + for (idx, arg) in spec.args.iter().enumerate() { + param_conversion.push(impl_arg_param(&arg, &spec, idx, &mut option_pos)); } - let accept_args = syn::Ident::new( - if spec.accept_args() { "true" } else { "false" }, - Span::call_site(), - ); - let accept_kwargs = syn::Ident::new( - if spec.accept_kwargs() { - "true" - } else { - "false" - }, - Span::call_site(), - ); + let accept_args = bool_to_ident(spec.accept_args()); + let accept_kwargs = bool_to_ident(spec.accept_kwargs()); // create array of arguments, and then parse quote! { - const _PARAMS: &'static [::pyo3::derive_utils::ParamDescription<'static>] = &[ + use ::pyo3::ObjectProtocol; + const PARAMS: &'static [::pyo3::derive_utils::ParamDescription] = &[ #(#params),* ]; - let mut _output = [#(#placeholders),*]; - match ::pyo3::derive_utils::parse_fn_args(Some(_LOCATION), _PARAMS, &_args, - _kwargs, #accept_args, #accept_kwargs, &mut _output) - { - Ok(_) => { - let mut _iter = _output.iter(); + let mut output = [#(#placeholders),*]; - #body - }, - Err(err) => Err(err) - } + // Workaround to use the question mark operator without rewriting everything + let _result = (|| { + ::pyo3::derive_utils::parse_fn_args( + Some(_LOCATION), + PARAMS, + &_args, + _kwargs, + #accept_args, + #accept_kwargs, + &mut output + )?; + + #(#param_conversion)* + + ::pyo3::IntoPyResult::into_py_result(#body) + })(); } } +/// 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( arg: &FnArg<'_>, spec: &FnSpec<'_>, - body: &TokenStream, idx: usize, + option_pos: &mut usize, ) -> TokenStream { - if arg.py { - return body.clone(); - } - let ty = arg.ty; - let name = arg.name; let arg_name = syn::Ident::new(&format!("arg{}", idx), Span::call_site()); - // First unwrap() asserts the iterated sequence is long enough (which should be guaranteed); - // second unwrap() asserts the parameter was not missing (which fn - // parse_args already checked for). + if arg.py { + return quote! { + let #arg_name = _py; + }; + } + let arg_value = quote!(output[#option_pos]); + *option_pos += 1; + + let ty = arg.ty; + let name = arg.name; if spec.is_args(&name) { quote! { - <#ty as ::pyo3::FromPyObject>::extract(_args.as_ref()) - .and_then(|#arg_name| { - #body - }) + let #arg_name = <#ty as ::pyo3::FromPyObject>::extract(_args.as_ref())?; } } else if spec.is_kwargs(&name) { - quote! {{ + quote! { let #arg_name = _kwargs; - #body - }} - } else { - if let Some(_) = arg.optional { - // default value - let mut default = TokenStream::new(); - if let Some(d) = spec.default_value(name) { - let dt = quote! { Some(#d) }; - dt.to_tokens(&mut default); - } else { - syn::Ident::new("None", Span::call_site()).to_tokens(&mut default); - } - - quote! { - match - match _iter.next().unwrap().as_ref() { - Some(_obj) => { - if _obj.is_none() { - Ok(#default) - } else { - match _obj.extract() { - Ok(_obj) => Ok(Some(_obj)), - Err(e) => Err(e) - } - } - }, - None => Ok(#default) - } - { - Ok(#arg_name) => #body, - Err(e) => Err(e) - } - } - } else if let Some(default) = spec.default_value(name) { - quote! { - match match _iter.next().unwrap().as_ref() { - Some(_obj) => { - if _obj.is_none() { - Ok(#default) - } else { - match _obj.extract() { - Ok(_obj) => Ok(_obj), - Err(e) => Err(e), - } - } - }, - None => Ok(#default) - } { - Ok(#arg_name) => #body, - Err(e) => Err(e) - } - } + } + } else if arg.optional.is_some() { + let default = if let Some(d) = spec.default_value(name) { + quote! { Some(#d) } } else { - quote! { - ::pyo3::ObjectProtocol::extract(_iter.next().unwrap().unwrap()) - .and_then(|#arg_name| { - #body - }) - } + quote! { None } + }; + + quote! { + let #arg_name = match #arg_value.as_ref() { + Some(_obj) => { + if _obj.is_none() { + #default + } else { + Some(_obj.extract()?) + } + }, + None => #default + }; + } + } else if let Some(default) = spec.default_value(name) { + quote! { + let #arg_name = match #arg_value.as_ref() { + Some(_obj) => { + if _obj.is_none() { + #default + } else { + _obj.extract()? + } + }, + None => #default + }; + } + } else { + quote! { + let #arg_name = #arg_value.unwrap().extract()?; } } } @@ -710,7 +625,7 @@ pub(crate) fn impl_py_setter_def( setter: &Option, wrapper: &TokenStream, ) -> TokenStream { - let n = if let &Some(ref name) = setter { + let n = if let Some(ref name) = setter { name.to_string() } else { let n = name.to_string(); @@ -740,7 +655,7 @@ pub(crate) fn impl_py_getter_def( getter: &Option, wrapper: &TokenStream, ) -> TokenStream { - let n = if let &Some(ref name) = getter { + let n = if let Some(ref name) = getter { name.to_string() } else { let n = name.to_string(); diff --git a/pyo3-derive-backend/src/pyproto.rs b/pyo3-derive-backend/src/pyproto.rs new file mode 100644 index 00000000..a551341f --- /dev/null +++ b/pyo3-derive-backend/src/pyproto.rs @@ -0,0 +1,109 @@ +// Copyright (c) 2017-present PyO3 Project and Contributors + +use crate::defs; +use crate::func::impl_method_proto; +use crate::method::FnSpec; +use crate::pymethod; +use proc_macro2::TokenStream; +use quote::quote; +use quote::ToTokens; + +pub fn build_py_proto(ast: &mut syn::ItemImpl) -> syn::Result { + if let Some((_, ref mut path, _)) = ast.trait_ { + let proto = if let Some(ref mut segment) = path.segments.last() { + match segment.value().ident.to_string().as_str() { + "PyObjectProtocol" => &defs::OBJECT, + "PyAsyncProtocol" => &defs::ASYNC, + "PyMappingProtocol" => &defs::MAPPING, + "PyIterProtocol" => &defs::ITER, + "PyContextProtocol" => &defs::CONTEXT, + "PySequenceProtocol" => &defs::SEQ, + "PyNumberProtocol" => &defs::NUM, + "PyDescrProtocol" => &defs::DESCR, + "PyBufferProtocol" => &defs::BUFFER, + "PyGCProtocol" => &defs::GC, + _ => { + return Err(syn::Error::new_spanned( + path, + "#[pyproto] can not be used with this block", + )); + } + } + } else { + return Err(syn::Error::new_spanned( + path, + "#[pyproto] can only be used with protocol trait implementations", + )); + }; + + let tokens = impl_proto_impl(&ast.self_ty, &mut ast.items, proto); + + // attach lifetime + let mut seg = path.segments.pop().unwrap().into_value(); + seg.arguments = syn::PathArguments::AngleBracketed(syn::parse_quote! {<'p>}); + path.segments.push(seg); + ast.generics.params = syn::parse_quote! {'p}; + + Ok(tokens) + } else { + return Err(syn::Error::new_spanned( + ast, + "#[pyproto] can only be used with protocol trait implementations", + )); + } +} + +fn impl_proto_impl( + ty: &syn::Type, + impls: &mut Vec, + proto: &defs::Proto, +) -> TokenStream { + let mut tokens = TokenStream::new(); + let mut py_methods = Vec::new(); + + for iimpl in impls.iter_mut() { + if let syn::ImplItem::Method(ref mut met) = iimpl { + for m in proto.methods { + if m == met.sig.ident.to_string().as_str() { + impl_method_proto(ty, &mut met.sig, m).to_tokens(&mut tokens); + } + } + for m in proto.py_methods { + let ident = met.sig.ident.clone(); + if m.name == ident.to_string().as_str() { + let name: syn::Ident = syn::parse_str(m.name).unwrap(); + let proto: syn::Path = syn::parse_str(m.proto).unwrap(); + + let fn_spec = match FnSpec::parse(&ident, &met.sig, &mut met.attrs) { + Ok(fn_spec) => fn_spec, + Err(err) => return err.to_compile_error(), + }; + let meth = pymethod::impl_proto_wrap(ty, &ident, &fn_spec); + + py_methods.push(quote! { + impl #proto for #ty + { + #[inline] + 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: "" + }) + } + } + }); + } + } + } + } + + quote! { + #tokens + + #(#py_methods)* + } +} diff --git a/pyo3-derive-backend/src/utils.rs b/pyo3-derive-backend/src/utils.rs index b013a0b8..4e329918 100644 --- a/pyo3-derive-backend/src/utils.rs +++ b/pyo3-derive-backend/src/utils.rs @@ -1,5 +1,4 @@ // Copyright (c) 2017-present PyO3 Project and Contributors -use syn; use proc_macro2::TokenStream; @@ -8,7 +7,7 @@ pub fn print_err(msg: String, t: TokenStream) { } // FIXME(althonos): not sure the docstring formatting is on par here. -pub fn get_doc(attrs: &Vec, null_terminated: bool) -> syn::Lit { +pub fn get_doc(attrs: &[syn::Attribute], null_terminated: bool) -> syn::Lit { let mut doc = Vec::new(); // TODO(althonos): set span on produced doc str literal @@ -20,13 +19,13 @@ pub fn get_doc(attrs: &Vec, null_terminated: bool) -> syn::Lit { // span = Some(metanv.span()); if let syn::Lit::Str(ref litstr) = metanv.lit { let d = litstr.value(); - doc.push(if d.starts_with(" ") { + doc.push(if d.starts_with(' ') { d[1..d.len()].to_string() } else { d }); } else { - panic!("could not parse doc"); + panic!("Invalid doc comment"); } } } diff --git a/pyo3cls/src/lib.rs b/pyo3cls/src/lib.rs index a4f7ee54..10536050 100644 --- a/pyo3cls/src/lib.rs +++ b/pyo3cls/src/lib.rs @@ -5,30 +5,26 @@ extern crate proc_macro; use proc_macro::TokenStream; use proc_macro2::Span; -use pyo3_derive_backend::{module, py_class, py_impl, py_proto, utils}; +use pyo3_derive_backend::{ + add_fn_to_module, build_py_class, build_py_methods, build_py_proto, get_doc, + process_functions_in_module, py2_init, py3_init, PyClassArgs, PyFunctionAttr, +}; use quote::quote; -use syn; -use syn::parse::Parser; -use syn::punctuated::Punctuated; -use syn::Token; +use syn::parse_macro_input; #[proc_macro_attribute] pub fn pymodule2(attr: TokenStream, input: TokenStream) -> TokenStream { - // Parse the token stream into a syntax tree - let mut ast: syn::ItemFn = syn::parse(input).expect("#[pymodule] must be used on a function"); + let mut ast = parse_macro_input!(input as syn::ItemFn); - let modname: syn::Ident; - if attr.is_empty() { - modname = ast.ident.clone(); + let modname = if attr.is_empty() { + ast.ident.clone() } else { - modname = syn::parse(attr).expect("could not parse module name"); - } + parse_macro_input!(attr as syn::Ident) + }; - // Process the functions within the module - module::process_functions_in_module(&mut ast); + process_functions_in_module(&mut ast); - // Create the module initialisation function - let expanded = module::py2_init(&ast.ident, &modname, utils::get_doc(&ast.attrs, false)); + let expanded = py2_init(&ast.ident, &modname, get_doc(&ast.attrs, false)); quote!( #ast @@ -37,23 +33,21 @@ pub fn pymodule2(attr: TokenStream, input: TokenStream) -> TokenStream { .into() } +/// Internally, this proc macro create a new c function called `PyInit_{my_module}` +/// that then calls the init function you provided #[proc_macro_attribute] pub fn pymodule3(attr: TokenStream, input: TokenStream) -> TokenStream { - // Parse the token stream into a syntax tree - let mut ast: syn::ItemFn = syn::parse(input).expect("#[pymodule] must be used on a `fn` block"); + let mut ast = parse_macro_input!(input as syn::ItemFn); - let modname: syn::Ident; - if attr.is_empty() { - modname = ast.ident.clone(); + let modname = if attr.is_empty() { + ast.ident.clone() } else { - modname = syn::parse(attr).expect("could not parse module name"); - } + parse_macro_input!(attr as syn::Ident) + }; - // Process the functions within the module - module::process_functions_in_module(&mut ast); + process_functions_in_module(&mut ast); - // Create the module initialisation function - let expanded = module::py3_init(&ast.ident, &modname, utils::get_doc(&ast.attrs, false)); + let expanded = py3_init(&ast.ident, &modname, get_doc(&ast.attrs, false)); quote!( #ast @@ -64,12 +58,8 @@ pub fn pymodule3(attr: TokenStream, input: TokenStream) -> TokenStream { #[proc_macro_attribute] pub fn pyproto(_: TokenStream, input: TokenStream) -> TokenStream { - // Parse the token stream into a syntax tree - let mut ast: syn::ItemImpl = - syn::parse(input).expect("#[pyproto] must be used on an `impl` block"); - - // Build the output - let expanded = py_proto::build_py_proto(&mut ast); + let mut ast = parse_macro_input!(input as syn::ItemImpl); + let expanded = build_py_proto(&mut ast).unwrap_or_else(|e| e.to_compile_error()); quote!( #ast @@ -80,21 +70,9 @@ pub fn pyproto(_: TokenStream, input: TokenStream) -> TokenStream { #[proc_macro_attribute] pub fn pyclass(attr: TokenStream, input: TokenStream) -> TokenStream { - // Parse the token stream into a syntax tree - let mut ast: syn::ItemStruct = - syn::parse(input).expect("#[pyclass] must be used on a `struct`"); - - // Parse the macro arguments into a list of expressions - let parser = Punctuated::::parse_terminated; - let error_message = "The macro attributes should be a list of comma separated expressions"; - let args = parser - .parse(attr) - .expect(error_message) - .into_iter() - .collect(); - - // Build the output - let expanded = py_class::build_py_class(&mut ast, &args); + let mut ast = parse_macro_input!(input as syn::ItemStruct); + let args = parse_macro_input!(attr as PyClassArgs); + let expanded = build_py_class(&mut ast, &args).unwrap_or_else(|e| e.to_compile_error()); quote!( #ast @@ -105,12 +83,8 @@ pub fn pyclass(attr: TokenStream, input: TokenStream) -> TokenStream { #[proc_macro_attribute] pub fn pymethods(_: TokenStream, input: TokenStream) -> TokenStream { - // Parse the token stream into a syntax tree - let mut ast: syn::ItemImpl = - syn::parse(input.clone()).expect("#[pymethods] must be used on an `impl` block"); - - // Build the output - let expanded = py_impl::build_py_methods(&mut ast); + let mut ast = parse_macro_input!(input as syn::ItemImpl); + let expanded = build_py_methods(&mut ast).unwrap_or_else(|e| e.to_compile_error()); quote!( #ast @@ -120,15 +94,16 @@ pub fn pymethods(_: TokenStream, input: TokenStream) -> TokenStream { } #[proc_macro_attribute] -pub fn pyfunction(_: TokenStream, input: TokenStream) -> TokenStream { - let mut ast: syn::ItemFn = syn::parse(input).expect("#[function] must be used on a `fn` block"); +pub fn pyfunction(attr: TokenStream, input: TokenStream) -> TokenStream { + let ast = parse_macro_input!(input as syn::ItemFn); + let args = parse_macro_input!(attr as PyFunctionAttr); // Workaround for https://github.com/dtolnay/syn/issues/478 let python_name = syn::Ident::new( &ast.ident.to_string().trim_start_matches("r#"), Span::call_site(), ); - let expanded = module::add_fn_to_module(&mut ast, &python_name, Vec::new()); + let expanded = add_fn_to_module(&ast, &python_name, args.arguments); quote!( #ast diff --git a/src/buffer.rs b/src/buffer.rs index e8fb14ff..4ab6a8ad 100644 --- a/src/buffer.rs +++ b/src/buffer.rs @@ -17,16 +17,15 @@ // DEALINGS IN THE SOFTWARE. //! `PyBuffer` implementation -use libc; -use std::ffi::CStr; -use std::os::raw; -use std::{cell, mem, slice}; - use crate::err::{self, PyResult}; use crate::exceptions; use crate::ffi; use crate::python::{Python, ToPyPointer}; use crate::types::PyObjectRef; +use libc; +use std::ffi::CStr; +use std::os::raw; +use std::{cell, mem, slice}; /// Allows access to the underlying buffer used by a python object such as `bytes`, `bytearray` or `array.array`. #[repr(transparent)] diff --git a/src/callback.rs b/src/callback.rs index b01f3a93..5626ebbc 100644 --- a/src/callback.rs +++ b/src/callback.rs @@ -2,14 +2,13 @@ //! Utilities for a Python callable object that invokes a Rust function. -use std::os::raw::c_int; -use std::{isize, ptr}; - use crate::conversion::IntoPyObject; use crate::err::PyResult; use crate::ffi::{self, Py_hash_t}; use crate::python::{IntoPyPointer, Python}; use crate::types::exceptions::OverflowError; +use std::os::raw::c_int; +use std::{isize, ptr}; pub trait CallbackConverter { type R; diff --git a/src/class/basic.rs b/src/class/basic.rs index 93d41819..2e3989cb 100644 --- a/src/class/basic.rs +++ b/src/class/basic.rs @@ -8,9 +8,6 @@ //! Parts of the documentation are copied from the respective methods from the //! [typeobj docs](https://docs.python.org/3/c-api/typeobj.html) -use std::os::raw::c_int; -use std::ptr; - use crate::callback::{BoolCallbackConverter, HashConverter, PyObjectCallbackConverter}; use crate::class::methods::PyMethodDef; use crate::conversion::{FromPyObject, IntoPyObject}; @@ -21,6 +18,8 @@ use crate::python::{IntoPyPointer, Python}; use crate::typeob::PyTypeInfo; use crate::types::{exceptions, PyObjectRef}; use crate::CompareOp; +use std::os::raw::c_int; +use std::ptr; /// Basic python class customization #[allow(unused_variables)] diff --git a/src/class/buffer.rs b/src/class/buffer.rs index bf66413f..c78bfbf8 100644 --- a/src/class/buffer.rs +++ b/src/class/buffer.rs @@ -4,12 +4,11 @@ //! //! For more information check [buffer protocol](https://docs.python.org/3/c-api/buffer.html) //! c-api -use std::os::raw::c_int; - use crate::callback::UnitCallbackConverter; use crate::err::PyResult; use crate::ffi; use crate::typeob::PyTypeInfo; +use std::os::raw::c_int; /// Buffer protocol interface /// diff --git a/src/class/descr.rs b/src/class/descr.rs index 2cd67d94..cc1bb29d 100644 --- a/src/class/descr.rs +++ b/src/class/descr.rs @@ -5,8 +5,6 @@ //! [Python information]( //! https://docs.python.org/3/reference/datamodel.html#implementing-descriptors) -use std::os::raw::c_int; - use crate::callback::{PyObjectCallbackConverter, UnitCallbackConverter}; use crate::class::methods::PyMethodDef; use crate::conversion::{FromPyObject, IntoPyObject}; @@ -14,6 +12,7 @@ use crate::err::PyResult; use crate::ffi; use crate::typeob::PyTypeInfo; use crate::types::{PyObjectRef, PyType}; +use std::os::raw::c_int; /// Descriptor interface #[allow(unused_variables)] diff --git a/src/class/gc.rs b/src/class/gc.rs index eb100ad2..a8e3e442 100644 --- a/src/class/gc.rs +++ b/src/class/gc.rs @@ -3,11 +3,10 @@ //! Python GC support //! -use std::os::raw::{c_int, c_void}; - use crate::ffi; use crate::python::{Python, ToPyPointer}; use crate::typeob::PyTypeInfo; +use std::os::raw::{c_int, c_void}; #[repr(transparent)] pub struct PyTraverseError(c_int); diff --git a/src/class/iter.rs b/src/class/iter.rs index c03932ee..5498137c 100644 --- a/src/class/iter.rs +++ b/src/class/iter.rs @@ -2,8 +2,6 @@ //! Python Iterator Interface. //! Trait and support implementation for implementing iterators -use std::ptr; - use crate::callback::{CallbackConverter, PyObjectCallbackConverter}; use crate::conversion::IntoPyObject; use crate::err::PyResult; @@ -11,6 +9,7 @@ use crate::ffi; use crate::instance::PyRefMut; use crate::python::{IntoPyPointer, Python}; use crate::typeob::PyTypeInfo; +use std::ptr; /// Python Iterator Interface. /// diff --git a/src/class/mod.rs b/src/class/mod.rs index 79fb43c6..f49d4d13 100644 --- a/src/class/mod.rs +++ b/src/class/mod.rs @@ -29,7 +29,6 @@ pub use self::sequence::PySequenceProtocol; pub use self::gc::{PyGCProtocol, PyTraverseError, PyVisit}; pub use self::methods::{PyGetterDef, PyMethodDef, PyMethodDefType, PyMethodType, PySetterDef}; - use crate::ffi; /// Operators for the __richcmp__ method diff --git a/src/conversion.rs b/src/conversion.rs index f536df71..a91e0e66 100644 --- a/src/conversion.rs +++ b/src/conversion.rs @@ -339,29 +339,24 @@ where } } +#[doc(hidden)] /// This trait wraps a T: IntoPyObject into PyResult while PyResult remains PyResult. /// -/// This is necessaty because proc macros run before typechecking and can't decide +/// This is necessary because proc macros run before typechecking and can't decide /// whether a return type is a (possibly aliased) PyResult or not. It is also quite handy because /// the codegen is currently built on the assumption that all functions return a PyResult. -pub trait ReturnTypeIntoPyResult { - type Inner; - - fn return_type_into_py_result(self) -> PyResult; +pub trait IntoPyResult { + fn into_py_result(self) -> PyResult; } -impl ReturnTypeIntoPyResult for T { - type Inner = T; - - fn return_type_into_py_result(self) -> PyResult { +impl IntoPyResult for T { + fn into_py_result(self) -> PyResult { Ok(self) } } -impl ReturnTypeIntoPyResult for PyResult { - type Inner = T; - - fn return_type_into_py_result(self) -> PyResult { +impl IntoPyResult for PyResult { + fn into_py_result(self) -> PyResult { self } } diff --git a/src/derive_utils.rs b/src/derive_utils.rs index 8773f06b..627294a3 100644 --- a/src/derive_utils.rs +++ b/src/derive_utils.rs @@ -15,11 +15,11 @@ use crate::GILPool; use crate::Python; use std::ptr; -#[derive(Debug)] /// Description of a python parameter; used for `parse_args()`. -pub struct ParamDescription<'a> { +#[derive(Debug)] +pub struct ParamDescription { /// The name of the parameter. - pub name: &'a str, + pub name: &'static str, /// Whether the parameter is optional. pub is_optional: bool, /// Whether the parameter is optional. diff --git a/src/lib.rs b/src/lib.rs index 949f7b24..63c43492 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -129,8 +129,8 @@ pub use crate::class::*; pub use crate::conversion::{ - FromPyObject, IntoPy, IntoPyObject, PyTryFrom, PyTryInto, ReturnTypeIntoPyResult, - ToBorrowedObject, ToPyObject, + FromPyObject, IntoPy, IntoPyObject, IntoPyResult, PyTryFrom, PyTryInto, ToBorrowedObject, + ToPyObject, }; pub use crate::err::{PyDowncastError, PyErr, PyErrArguments, PyErrValue, PyResult}; pub use crate::instance::{AsPyRef, Py, PyNativeType, PyObjectWithGIL, PyRef, PyRefMut}; diff --git a/src/object.rs b/src/object.rs index 981efeb5..e5c5a1b5 100644 --- a/src/object.rs +++ b/src/object.rs @@ -1,7 +1,5 @@ // Copyright (c) 2017-present PyO3 Project and Contributors -use std::ptr::NonNull; - use crate::conversion::{ FromPyObject, IntoPy, IntoPyObject, PyTryFrom, ToBorrowedObject, ToPyObject, }; @@ -12,6 +10,7 @@ use crate::python::{IntoPyPointer, Python, ToPyPointer}; use crate::pythonrun; use crate::types::{PyDict, PyObjectRef, PyTuple}; use crate::Py; +use std::ptr::NonNull; /// A python object /// diff --git a/src/types/iterator.rs b/src/types/iterator.rs index 70943658..919af21b 100644 --- a/src/types/iterator.rs +++ b/src/types/iterator.rs @@ -83,8 +83,6 @@ impl<'p> Drop for PyIterator<'p> { #[cfg(test)] mod tests { - use indoc::indoc; - use crate::conversion::ToPyObject; use crate::instance::AsPyRef; use crate::objectprotocol::ObjectProtocol; @@ -92,6 +90,7 @@ mod tests { use crate::pythonrun::GILPool; use crate::types::{PyDict, PyList}; use crate::GILGuard; + use indoc::indoc; #[test] fn vec_iter() { diff --git a/src/types/num_common.rs b/src/types/num_common.rs index 6d331667..653c409a 100644 --- a/src/types/num_common.rs +++ b/src/types/num_common.rs @@ -1,9 +1,8 @@ //! common macros for num2.rs and num3.rs -use std::os::raw::c_int; - use crate::err::{PyErr, PyResult}; use crate::python::Python; +use std::os::raw::c_int; pub(super) fn err_if_invalid_value( py: Python, diff --git a/src/types/slice.rs b/src/types/slice.rs index b7cfc576..d02ffef5 100644 --- a/src/types/slice.rs +++ b/src/types/slice.rs @@ -1,13 +1,12 @@ // Copyright (c) 2017-present PyO3 Project and Contributors -use std::os::raw::c_long; - use crate::conversion::ToPyObject; use crate::err::{PyErr, PyResult}; use crate::ffi::{self, Py_ssize_t}; use crate::instance::PyObjectWithGIL; use crate::object::PyObject; use crate::python::{Python, ToPyPointer}; +use std::os::raw::c_long; /// Represents a Python `slice`. /// diff --git a/src/types/string2.rs b/src/types/string2.rs index 2535d467..ac8e9bfc 100644 --- a/src/types/string2.rs +++ b/src/types/string2.rs @@ -203,14 +203,12 @@ impl std::convert::From> for Py { #[cfg(test)] mod test { - use std::borrow::Cow; - + use super::PyString; use crate::conversion::{FromPyObject, PyTryFrom, ToPyObject}; use crate::instance::AsPyRef; use crate::object::PyObject; use crate::python::Python; - - use super::PyString; + use std::borrow::Cow; #[test] fn test_non_bmp() { diff --git a/src/types/typeobject.rs b/src/types/typeobject.rs index 526c295b..272d9373 100644 --- a/src/types/typeobject.rs +++ b/src/types/typeobject.rs @@ -2,15 +2,14 @@ // // based on Daniel Grunwald's https://github.com/dgrunwald/rust-cpython -use std::borrow::Cow; -use std::ffi::CStr; - use crate::err::{PyErr, PyResult}; use crate::ffi; use crate::instance::{Py, PyObjectWithGIL}; use crate::object::PyObject; use crate::python::{Python, ToPyPointer}; use crate::typeob::{PyTypeInfo, PyTypeObject}; +use std::borrow::Cow; +use std::ffi::CStr; /// Represents a reference to a Python `type object`. #[repr(transparent)] diff --git a/tests/test_doc.rs b/tests/test_doc.rs index 2042d5c7..b5f46225 100644 --- a/tests/test_doc.rs +++ b/tests/test_doc.rs @@ -1,5 +1,4 @@ use docmatic; - use std::default::Default; use std::path::{Path, PathBuf}; diff --git a/tests/test_dunder.rs b/tests/test_dunder.rs index 86eaaf04..83ab1057 100644 --- a/tests/test_dunder.rs +++ b/tests/test_dunder.rs @@ -1,7 +1,5 @@ #![feature(specialization)] -use std::{isize, iter}; - use pyo3::class::{ PyContextProtocol, PyIterProtocol, PyMappingProtocol, PyObjectProtocol, PySequenceProtocol, }; @@ -10,6 +8,7 @@ use pyo3::ffi; use pyo3::prelude::*; use pyo3::python::ToPyPointer; use pyo3::types::{PyBytes, PyDict, PyObjectRef, PySlice, PyString, PyType}; +use std::{isize, iter}; #[macro_use] mod common; diff --git a/tests/test_getter_setter.rs b/tests/test_getter_setter.rs index bc2de94e..7eede5be 100644 --- a/tests/test_getter_setter.rs +++ b/tests/test_getter_setter.rs @@ -42,9 +42,9 @@ fn class_with_properties() { #[pyclass] struct GetterSetter { - #[prop(get, set)] + #[pyo3(get, set)] num: i32, - #[prop(get, set)] + #[pyo3(get, set)] text: String, } diff --git a/tests/test_inheritance.rs b/tests/test_inheritance.rs index f13785ea..8780a178 100644 --- a/tests/test_inheritance.rs +++ b/tests/test_inheritance.rs @@ -7,7 +7,7 @@ mod common; #[pyclass] struct BaseClass { - #[prop(get)] + #[pyo3(get)] val1: usize, } @@ -41,7 +41,7 @@ impl BaseClass { #[pyclass(extends=BaseClass)] struct SubClass { - #[prop(get)] + #[pyo3(get)] val2: usize, } diff --git a/tests/test_various.rs b/tests/test_various.rs index 853b00e1..39ea8dc6 100644 --- a/tests/test_various.rs +++ b/tests/test_various.rs @@ -40,7 +40,7 @@ fn mut_ref_arg() { #[pyclass] struct PyUsize { - #[prop(get)] + #[pyo3(get)] pub value: usize, }