Merge pull request #154 from konstin/capybara

Relax return types and add functions
This commit is contained in:
konstin 2018-05-12 20:43:40 +02:00 committed by GitHub
commit 5717463daf
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
14 changed files with 442 additions and 299 deletions

View file

@ -1,3 +1,60 @@
# Python Function
TODO
Pyo3 supports two ways to define a function in python. Both require registering
the function to a [module](./module.md)
One way is defining the function in the module definition.
```rust
#![feature(proc_macro)]
extern crate pyo3;
use pyo3::{py, PyResult, Python, PyModule};
use pyo3::py::modinit as pymodinit;
#[pymodinit(rust2py)]
fn init_mod(py: Python, m: &PyModule) -> PyResult<()> {
// Note that the `#[pyfn()]` annotation automatically converts the arguments from
// Python objects to Rust values; and the Rust return value back into a Python object.
#[pyfn(m, "sum_as_string")]
fn sum_as_string_py(_py: Python, a:i64, b:i64) -> PyResult<String> {
Ok(format!("{}", a + b).to_string())
}
Ok(())
}
# fn main() {}
```
The other is annotating a function with `#[py::function]` and then adding it
to the module using the `add_function_to_module!` macro, which takes the module
as first parameter, the function name as second and an instance of `Python`
as third.
```rust
#![feature(proc_macro, concat_idents)]
#[macro_use]
extern crate pyo3;
use pyo3::{py, PyResult, Python, PyModule};
use pyo3::py::function as pyfunction;
use pyo3::py::modinit as pymodinit;
#[pyfunction]
fn double(x: usize) -> usize {
x * 2
}
#[pymodinit(module_with_functions)]
fn init_mod(py: Python, m: &PyModule) -> PyResult<()> {
m.add_function(wrap_function!(double)).unwrap();
Ok(())
}
# fn main() {}
```

View file

@ -1,7 +1,7 @@
// Copyright (c) 2017-present PyO3 Project and Contributors
use syn;
#[derive(Debug, PartialEq)]
#[derive(Debug, Clone, PartialEq)]
pub enum Argument {
VarArgsSeparator,
VarArgs(String),

View file

@ -1,13 +1,12 @@
// Copyright (c) 2017-present PyO3 Project and Contributors
use syn;
use quote::{Tokens, Ident};
use args::{Argument, parse_arguments};
use quote::{Ident, Tokens};
use syn;
use utils::for_err_msg;
#[derive(Clone, Debug)]
#[derive(Clone, PartialEq, Debug)]
pub struct FnArg<'a> {
pub name: &'a syn::Ident,
pub mode: &'a syn::BindingMode,
@ -29,6 +28,7 @@ pub enum FnType {
FnStatic,
}
#[derive(Clone, PartialEq, Debug)]
pub struct FnSpec<'a> {
pub tp: FnType,
pub attrs: Vec<Argument>,
@ -36,8 +36,14 @@ pub struct FnSpec<'a> {
pub output: syn::Ty,
}
impl<'a> FnSpec<'a> {
pub fn get_return_info(output: &syn::FunctionRetTy) -> syn::Ty {
match output {
&syn::FunctionRetTy::Default => syn::Ty::Tup(vec![]),
&syn::FunctionRetTy::Ty(ref ty) => ty.clone()
}
}
impl<'a> FnSpec<'a> {
/// Parser function signature and function attributes
pub fn parse(name: &'a syn::Ident,
sig: &'a syn::MethodSig,
@ -96,10 +102,7 @@ impl<'a> FnSpec<'a> {
}
}
let ty = match sig.decl.output {
syn::FunctionRetTy::Default => syn::Ty::Infer,
syn::FunctionRetTy::Ty(ref ty) => ty.clone()
};
let ty = get_return_info(&sig.decl.output);
FnSpec {
tp: fn_type,

View file

@ -1,42 +1,14 @@
// Copyright (c) 2017-present PyO3 Project and Contributors
use syn;
use quote::Tokens;
use args;
use method;
use py_method;
use quote::Tokens;
use syn;
use utils;
pub fn build_py3_module_init(ast: &mut syn::Item, attr: String) -> Tokens {
let modname = &attr[1..attr.len()-1].to_string();
match ast.node {
syn::ItemKind::Fn(_, _, _, _, _, ref mut block) => {
let mut stmts = Vec::new();
for stmt in block.stmts.iter_mut() {
match stmt {
&mut syn::Stmt::Item(ref mut item) => {
if let Some(block) = wrap_fn(item) {
for stmt in block.stmts.iter() {
stmts.push(stmt.clone());
}
continue
}
}
_ => (),
}
stmts.push(stmt.clone());
}
block.stmts = stmts;
py3_init(&ast.ident, &modname, utils::get_doc(&ast.attrs, false))
},
_ => panic!("#[modinit] can only be used with fn block"),
}
}
/// Generates the function that is called by the python interpreter to initialize the native
/// module
pub fn py3_init(fnname: &syn::Ident, name: &String, doc: syn::Lit) -> Tokens {
let m_name = syn::Ident::from(name.trim().as_ref());
let cb_name = syn::Ident::from(format!("PyInit_{}", name.trim()).as_ref());
@ -84,34 +56,6 @@ pub fn py3_init(fnname: &syn::Ident, name: &String, doc: syn::Lit) -> Tokens {
}
}
pub fn build_py2_module_init(ast: &mut syn::Item, attr: String) -> Tokens {
let modname = &attr[1..attr.len()-1].to_string();
match ast.node {
syn::ItemKind::Fn(_, _, _, _, _, ref mut block) => {
let mut stmts = Vec::new();
for stmt in block.stmts.iter_mut() {
match stmt {
&mut syn::Stmt::Item(ref mut item) => {
if let Some(block) = wrap_fn(item) {
for stmt in block.stmts.iter() {
stmts.push(stmt.clone());
}
continue
}
}
_ => (),
}
stmts.push(stmt.clone());
}
block.stmts = stmts;
py2_init(&ast.ident, &modname, utils::get_doc(&ast.attrs, false))
},
_ => panic!("#[modinit] can only be used with fn block"),
}
}
pub fn py2_init(fnname: &syn::Ident, name: &String, doc: syn::Lit) -> Tokens {
let m_name = syn::Ident::from(name.trim().as_ref());
let cb_name = syn::Ident::from(format!("init{}", name.trim()).as_ref());
@ -150,170 +94,210 @@ pub fn py2_init(fnname: &syn::Ident, name: &String, doc: syn::Lit) -> Tokens {
}
}
fn wrap_fn(item: &mut syn::Item) -> Option<Box<syn::Block>> {
let name = item.ident.clone();
/// Finds and takes care of the #[pyfn(...)] in #[modinit(...)]
pub fn process_functions_in_module(ast: &mut syn::Item) {
if let syn::ItemKind::Fn(_, _, _, _, _, ref mut block) = ast.node {
let mut stmts: Vec<syn::Stmt> = Vec::new();
for stmt in block.stmts.iter_mut() {
if let &mut syn::Stmt::Item(ref mut item) = stmt {
if let Some((module_name, python_name, pyfn_attrs)) =
extract_pyfn_attrs(&mut item.attrs)
{
let function_to_python = add_fn_to_module(item, &python_name, pyfn_attrs);
let function_wrapper_ident = function_wrapper_ident(&item.ident);
let tokens = quote! {
fn block_wrapper() {
#function_to_python
#module_name.add_function(&#function_wrapper_ident);
}
}.to_string();
let item = syn::parse_item(tokens.as_str()).unwrap();
let block = match item.node {
syn::ItemKind::Fn(_, _, _, _, _, ref block) => block.clone(),
_ => unreachable!(),
};
stmts.extend(block.stmts.into_iter());
}
};
stmts.push(stmt.clone());
}
block.stmts = stmts;
} else {
panic!("#[modinit] can only be used with fn block");
}
}
/// 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<method::FnArg<'a>> {
match input {
&syn::FnArg::SelfRef(_, _) | &syn::FnArg::SelfValue(_) => None,
&syn::FnArg::Captured(ref pat, ref ty) => {
let (mode, ident) = match pat {
&syn::Pat::Ident(ref mode, ref ident, _) => (mode, ident),
_ => panic!("unsupported argument: {:?}", pat),
};
let py = match ty {
&syn::Ty::Path(_, ref path) => if let Some(segment) = path.segments.last() {
segment.ident.as_ref() == "Python"
} else {
false
},
_ => false,
};
let opt = method::check_arg_ty_and_optional(&name, ty);
Some(method::FnArg {
name: ident,
mode,
ty,
optional: opt,
py,
reference: method::is_ref(&name, ty),
})
}
&syn::FnArg::Ignored(_) => panic!("ignored argument: {:?}", name),
}
}
/// Extracts the data from the #[pyfn(...)] attribute of a function
fn extract_pyfn_attrs(
attrs: &mut Vec<syn::Attribute>,
) -> Option<(syn::Ident, syn::Ident, Vec<args::Argument>)> {
let mut new_attrs = Vec::new();
let mut fnname = None;
let mut modname = None;
let mut fn_attrs = Vec::new();
for attr in item.attrs.iter() {
match attr.value {
syn::MetaItem::List(ref name, ref meta) => {
match name.as_ref() {
"pyfn" => {
if meta.len() >= 2 {
match meta[0] {
syn::NestedMetaItem::MetaItem(syn::MetaItem::Word(ref ident)) => {
modname = Some(ident.clone());
}
_ => modname = None
}
match meta[1] {
syn::NestedMetaItem::Literal(syn::Lit::Str(ref s, _)) => {
fnname = Some(syn::Ident::from(s.as_str()));
}
_ => fnname = None
}
} else {
println!("can not parse 'pyfn' params {:?}", attr);
modname = None
for attr in attrs.iter() {
if let syn::MetaItem::List(ref name, ref meta) = attr.value {
if name.as_ref() == "pyfn" {
if meta.len() >= 2 {
match meta[0] {
syn::NestedMetaItem::MetaItem(syn::MetaItem::Word(ref ident)) => {
modname = Some(ident.clone());
}
if meta.len() >= 3 {
fn_attrs = args::parse_arguments(&meta[2..meta.len()]);
}
continue;
_ => panic!("The first parameter of pyfn must be a MetaItem"),
}
_ => (),
match meta[1] {
syn::NestedMetaItem::Literal(syn::Lit::Str(ref s, _)) => {
fnname = Some(syn::Ident::from(s.as_str()));
}
_ => panic!("The second parameter of pyfn must be a Literal"),
}
if meta.len() >= 3 {
fn_attrs = args::parse_arguments(&meta[2..meta.len()]);
}
} else {
panic!("can not parse 'pyfn' params {:?}", attr);
}
continue;
}
_ => (),
};
new_attrs.push(attr.clone())
}
item.attrs.clear();
item.attrs.extend(new_attrs);
if let None = fnname {
return None
}
if let None = modname {
return None
}
match item.node {
syn::ItemKind::Fn(ref decl, _, _, _, _, _) => {
let mut arguments = Vec::new();
for input in decl.inputs.iter() {
match input {
&syn::FnArg::SelfRef(_, _) | &syn::FnArg::SelfValue(_) => (),
&syn::FnArg::Captured(ref pat, ref ty) => {
let (mode, ident) = match pat {
&syn::Pat::Ident(ref mode, ref ident, _) =>
(mode, ident),
_ =>
panic!("unsupported argument: {:?}", pat),
};
let py = match ty {
&syn::Ty::Path(_, ref path) =>
if let Some(segment) = path.segments.last() {
segment.ident.as_ref() == "Python"
} else {
false
},
_ => false
};
let opt = method::check_arg_ty_and_optional(&name, ty);
arguments.push(method::FnArg {name: ident,
mode: mode,
ty: ty,
optional: opt,
py: py,
reference: method::is_ref(&name, ty)});
}
&syn::FnArg::Ignored(_) =>
panic!("ignored argument: {:?}", name),
}
}
let ty = match decl.output {
syn::FunctionRetTy::Default => syn::Ty::Infer,
syn::FunctionRetTy::Ty(ref ty) => ty.clone()
};
let spec = method::FnSpec {
tp: method::FnType::Fn,
attrs: fn_attrs,
args: arguments,
output: ty,
};
let m = modname.unwrap();
let fnname = fnname.unwrap();
let wrapper = impl_wrap(&name, &spec);
let item2 = item.clone();
let doc = utils::get_doc(&item.attrs, true);
let tokens = quote! {
fn test() {
#item2
#[allow(unused_imports)]
{
use std;
use pyo3 as _pyo3;
use pyo3::ObjectProtocol;
#wrapper
let _def = pyo3::class::PyMethodDef {
ml_name: stringify!(#fnname),
ml_meth: pyo3::class::PyMethodType::PyCFunctionWithKeywords(__wrap),
ml_flags: pyo3::ffi::METH_VARARGS | pyo3::ffi::METH_KEYWORDS,
ml_doc: #doc,
};
unsafe {
let func = pyo3::PyObject::from_owned_ptr_or_panic(
py, pyo3::ffi::PyCFunction_New(
Box::into_raw(Box::new(_def.as_method_def())),
std::ptr::null_mut()));
#m.add(stringify!(#fnname), func)?
}
}
}
}.to_string();
let item = syn::parse_item(tokens.as_str()).unwrap();
match item.node {
syn::ItemKind::Fn(_, _, _, _, _, ref block) => {
return Some(block.clone())
},
_ => ()
}
},
_ => (),
}
None
attrs.clear();
attrs.extend(new_attrs);
Some((modname?, fnname?, fn_attrs))
}
/// Coordinates the naming of a the add-function-to-python-module function
fn function_wrapper_ident(name: &syn::Ident) -> syn::Ident {
// Make sure this ident matches the one of wrap_function
syn::Ident::new("__pyo3_get_function_".to_string() + &name.to_string())
}
/// Generate static method wrapper (PyCFunction, PyCFunctionWithKeywords)
pub fn impl_wrap(name: &syn::Ident, spec: &method::FnSpec) -> Tokens {
let names: Vec<syn::Ident> = spec.args.iter().enumerate().map(
|item| if item.1.py {syn::Ident::from("_py")} else {
syn::Ident::from(format!("arg{}", item.0))}).collect();
let cb = quote! {{
#name(#(#names),*)
}};
/// Generates python wrapper over a function that allows adding it to a python module as a python
/// function
pub fn add_fn_to_module(
item: &mut syn::Item,
python_name: &syn::Ident,
pyfn_attrs: Vec<args::Argument>,
) -> Tokens {
let name = item.ident.clone();
let decl = if let syn::ItemKind::Fn(ref decl, _, _, _, _, _) = item.node {
decl.clone()
} else {
panic!("Expected a function")
};
let mut arguments = Vec::new();
for input in decl.inputs.iter() {
if let Some(fn_arg) = wrap_fn_argument(input, &name) {
arguments.push(fn_arg);
}
}
let ty = method::get_return_info(&decl.output);
let spec = method::FnSpec {
tp: method::FnType::Fn,
attrs: pyfn_attrs,
args: arguments,
output: ty,
};
let function_wrapper_ident = function_wrapper_ident(&name);
let wrapper = function_c_wrapper(&name, &spec);
let doc = utils::get_doc(&item.attrs, true);
let tokens = quote! (
fn #function_wrapper_ident(py: ::pyo3::Python) -> ::pyo3::PyObject {
use std;
use pyo3 as _pyo3;
use pyo3::ObjectProtocol;
#wrapper
let _def = pyo3::class::PyMethodDef {
ml_name: stringify!(#python_name),
ml_meth: pyo3::class::PyMethodType::PyCFunctionWithKeywords(__wrap),
ml_flags: pyo3::ffi::METH_VARARGS | pyo3::ffi::METH_KEYWORDS,
ml_doc: #doc,
};
let function = unsafe {
pyo3::PyObject::from_owned_ptr_or_panic(
py,
pyo3::ffi::PyCFunction_New(
Box::into_raw(Box::new(_def.as_method_def())),
std::ptr::null_mut()
)
)
};
function
}
);
tokens
}
/// Generate static function wrapper (PyCFunction, PyCFunctionWithKeywords)
fn function_c_wrapper(name: &syn::Ident, spec: &method::FnSpec) -> Tokens {
let names: Vec<syn::Ident> = spec.args
.iter()
.enumerate()
.map(|item| {
if item.1.py {
syn::Ident::from("_py")
} else {
syn::Ident::from(format!("arg{}", item.0))
}
})
.collect();
let cb = quote! {
::pyo3::ReturnTypeIntoPyResult::return_type_into_py_result(#name(#(#names),*))
};
let body = py_method::impl_arg_params(spec, cb);
let output = &spec.output;
let body_to_result = py_method::body_to_result(&body, spec);
quote! {
#[allow(unused_variables, unused_imports)]
@ -329,9 +313,7 @@ pub fn impl_wrap(name: &syn::Ident, spec: &method::FnSpec) -> Tokens {
let _args = _py.from_borrowed_ptr::<_pyo3::PyTuple>(_args);
let _kwargs = _pyo3::argparse::get_kwargs(_py, _kwargs);
let _result: #output = {
#body
};
#body_to_result
_pyo3::callback::cb_convert(
_pyo3::callback::PyObjectCallbackConverter, _py, _result)
}

View file

@ -268,11 +268,13 @@ fn impl_class(cls: &syn::Ident, base: &syn::Ident,
let gil = _pyo3::Python::acquire_gil();
let py = gil.python();
let error_message = "An error occurred while initializing class ".to_string() +
<#cls as _pyo3::typeob::PyTypeInfo>::NAME.as_ref();
// automatically initialize the class on-demand
_pyo3::typeob::initialize_type::<#cls>(py, None)
.map_err(|e| e.print(py))
.expect(format!("An error occurred while initializing class {}",
<#cls as _pyo3::typeob::PyTypeInfo>::NAME).as_ref());
.expect(&error_message);
}
});
}
@ -337,7 +339,7 @@ fn impl_descriptors(cls: &syn::Ty, descriptors: Vec<(syn::Field, Vec<FnType>)>)
py: true,
reference: false
}],
output: syn::parse::ty("PyResult<()>").expect("error parse PyResult<()>")
output: syn::parse::ty("PyResult<()>").expect("error parse PyResult<()>"),
};
impl_py_setter_def(&name, doc, setter, &impl_wrap_setter(&Box::new(cls.clone()), &setter_name, &spec))
},

View file

@ -19,7 +19,7 @@ pub fn build_py_methods(ast: &mut syn::Item) -> Tokens {
}
}
fn impl_methods(ty: &Box<syn::Ty>, impls: &mut Vec<syn::ImplItem>) -> Tokens {
pub fn impl_methods(ty: &Box<syn::Ty>, impls: &mut Vec<syn::ImplItem>) -> Tokens {
// get method names in impl block
let mut methods = Vec::new();

View file

@ -43,12 +43,23 @@ fn check_generic(name: &syn::Ident, sig: &syn::MethodSig) {
}
pub fn body_to_result(body: &Tokens, spec: &FnSpec) -> Tokens {
let output = &spec.output;
quote! {
use pyo3::ReturnTypeIntoPyResult;
let _result: PyResult<<#output as ReturnTypeIntoPyResult>::Inner> = {
#body
};
}
}
/// Generate function wrapper (PyCFunction, PyCFunctionWithKeywords)
pub fn impl_wrap(cls: &Box<syn::Ty>, name: &syn::Ident, spec: &FnSpec, noargs: bool) -> Tokens {
let cb = impl_call(cls, name, &spec);
let output = &spec.output;
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
@ -59,15 +70,14 @@ pub fn impl_wrap(cls: &Box<syn::Ty>, name: &syn::Ident, spec: &FnSpec, noargs: b
let _py = _pyo3::Python::assume_gil_acquired();
let _slf = _py.mut_from_borrowed_ptr::<#cls>(_slf);
let _result: #output = {
#cb
};
#body_to_result
_pyo3::callback::cb_convert(
_pyo3::callback::PyObjectCallbackConverter, _py, _result)
}
}
} else {
let body = impl_arg_params(&spec, cb);
let body = impl_arg_params(&spec, body);
let body_to_result = body_to_result(&body, spec);
quote! {
unsafe extern "C" fn __wrap(
@ -84,9 +94,7 @@ pub fn impl_wrap(cls: &Box<syn::Ty>, name: &syn::Ident, spec: &FnSpec, noargs: b
let _args = _py.from_borrowed_ptr::<_pyo3::PyTuple>(_args);
let _kwargs = _pyo3::argparse::get_kwargs(_py, _kwargs);
let _result: #output = {
#body
};
#body_to_result
_pyo3::callback::cb_convert(
_pyo3::callback::PyObjectCallbackConverter, _py, _result)
}
@ -127,12 +135,12 @@ pub fn impl_wrap_new(cls: &Box<syn::Ty>, name: &syn::Ident, spec: &FnSpec) -> To
let names: Vec<syn::Ident> = spec.args.iter().enumerate().map(
|item| if item.1.py {syn::Ident::from("_py")} else {
syn::Ident::from(format!("arg{}", item.0))}).collect();
let cb = quote! {{
#cls::#name(&_obj, #(#names),*)
}};
let cb = quote! {
::pyo3::ReturnTypeIntoPyResult::return_type_into_py_result(#cls::#name(&_obj, #(#names),*))
};
let body = impl_arg_params(spec, cb);
let output = &spec.output;
let body_to_result = body_to_result(&body, spec);
quote! {
#[allow(unused_mut)]
@ -152,9 +160,7 @@ pub fn impl_wrap_new(cls: &Box<syn::Ty>, name: &syn::Ident, spec: &FnSpec) -> To
let _args = _py.from_borrowed_ptr::<_pyo3::PyTuple>(_args);
let _kwargs = _pyo3::argparse::get_kwargs(_py, _kwargs);
let _result: #output = {
#body
};
#body_to_result
match _result {
Ok(_) => _obj.into_ptr(),
@ -176,7 +182,13 @@ pub fn impl_wrap_new(cls: &Box<syn::Ty>, name: &syn::Ident, spec: &FnSpec) -> To
/// Generate function wrapper for ffi::initproc
fn impl_wrap_init(cls: &Box<syn::Ty>, name: &syn::Ident, spec: &FnSpec) -> Tokens {
let cb = impl_call(cls, name, &spec);
let output = &spec.output;
if quote! {#output} != quote! {PyResult<()>} || quote! {#output} != quote! {()}{
panic!("Constructor must return PyResult<()> or a ()");
}
let body = impl_arg_params(&spec, cb);
let body_to_result = body_to_result(&body, spec);
quote! {
#[allow(unused_mut)]
@ -192,9 +204,7 @@ fn impl_wrap_init(cls: &Box<syn::Ty>, name: &syn::Ident, spec: &FnSpec) -> Token
let _args = _py.from_borrowed_ptr::<_pyo3::PyTuple>(_args);
let _kwargs = _pyo3::argparse::get_kwargs(_py, _kwargs);
let _result: PyResult<()> = {
#body
};
#body_to_result
match _result {
Ok(_) => 0,
Err(e) => {
@ -211,11 +221,12 @@ pub fn impl_wrap_class(cls: &Box<syn::Ty>, name: &syn::Ident, spec: &FnSpec) ->
let names: Vec<syn::Ident> = spec.args.iter().enumerate().map(
|item| if item.1.py {syn::Ident::from("_py")} else {
syn::Ident::from(format!("arg{}", item.0))}).collect();
let cb = quote! {{
#cls::#name(&_cls, #(#names),*)
}};
let cb = quote! {
::pyo3::ReturnTypeIntoPyResult::return_type_into_py_result(#cls::#name(&_cls, #(#names),*))
};
let body = impl_arg_params(spec, cb);
let output = &spec.output;
let body_to_result = body_to_result(&body, spec);
quote! {
#[allow(unused_mut)]
@ -231,9 +242,7 @@ pub fn impl_wrap_class(cls: &Box<syn::Ty>, name: &syn::Ident, spec: &FnSpec) ->
let _args = _py.from_borrowed_ptr::<_pyo3::PyTuple>(_args);
let _kwargs = _pyo3::argparse::get_kwargs(_py, _kwargs);
let _result: #output = {
#body
};
#body_to_result
_pyo3::callback::cb_convert(
_pyo3::callback::PyObjectCallbackConverter, _py, _result)
}
@ -245,12 +254,12 @@ pub fn impl_wrap_static(cls: &Box<syn::Ty>, name: &syn::Ident, spec: &FnSpec) ->
let names: Vec<syn::Ident> = spec.args.iter().enumerate().map(
|item| if item.1.py {syn::Ident::from("_py")} else {
syn::Ident::from(format!("arg{}", item.0))}).collect();
let cb = quote! {{
#cls::#name(#(#names),*)
}};
let cb = quote! {
::pyo3::ReturnTypeIntoPyResult::return_type_into_py_result(#cls::#name(#(#names),*))
};
let body = impl_arg_params(spec, cb);
let output = &spec.output;
let body_to_result = body_to_result(&body, spec);
quote! {
#[allow(unused_mut)]
@ -265,9 +274,7 @@ pub fn impl_wrap_static(cls: &Box<syn::Ty>, name: &syn::Ident, spec: &FnSpec) ->
let _args = _py.from_borrowed_ptr::<_pyo3::PyTuple>(_args);
let _kwargs = _pyo3::argparse::get_kwargs(_py, _kwargs);
let _result: #output = {
#body
};
#body_to_result
_pyo3::callback::cb_convert(
_pyo3::callback::PyObjectCallbackConverter, _py, _result)
}
@ -343,9 +350,9 @@ fn impl_call(_cls: &Box<syn::Ty>, fname: &syn::Ident, spec: &FnSpec) -> Tokens {
syn::Ident::from(format!("arg{}", item.0))
}
).collect();
quote! {{
_slf.#fname(#(#names),*)
}}
quote! {
::pyo3::ReturnTypeIntoPyResult::return_type_into_py_result(_slf.#fname(#(#names),*))
}
}
pub fn impl_arg_params(spec: &FnSpec, body: Tokens) -> Tokens {
@ -393,6 +400,7 @@ pub fn impl_arg_params(spec: &FnSpec, body: Tokens) -> Tokens {
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);
}
@ -424,7 +432,7 @@ pub fn impl_arg_params(spec: &FnSpec, body: Tokens) -> Tokens {
fn impl_arg_param(arg: &FnArg, spec: &FnSpec, body: &Tokens, idx: usize) -> Tokens {
if arg.py {
return body.clone()
return body.clone();
}
let ty = arg.ty;
let name = arg.name;
@ -436,27 +444,22 @@ fn impl_arg_param(arg: &FnArg, spec: &FnSpec, body: &Tokens, idx: usize) -> Toke
if spec.is_args(&name) {
quote! {
match <#ty as _pyo3::FromPyObject>::extract(_args.as_ref())
{
Ok(#arg_name) => {
#body
}
Err(e) => Err(e)
}
<#ty as _pyo3::FromPyObject>::extract(_args.as_ref())
.and_then(|#arg_name| {
#body
})
}
}
else if spec.is_kwargs(&name) {
} else if spec.is_kwargs(&name) {
quote! {{
let #arg_name = _kwargs;
#body
}}
}
else {
} else {
if let Some(_) = arg.optional {
// default value
let mut default = Tokens::new();
if let Some(d) = spec.default_value(name) {
let dt = quote!{ Some(#d) };
let dt = quote! { Some(#d) };
dt.to_tokens(&mut default);
} else {
syn::Ident::from("None").to_tokens(&mut default);
@ -500,15 +503,12 @@ fn impl_arg_param(arg: &FnArg, spec: &FnSpec, body: &Tokens, idx: usize) -> Toke
Err(e) => Err(e)
}
}
}
else {
} else {
quote! {
match _iter.next().unwrap().as_ref().unwrap().extract() {
Ok(#arg_name) => {
_iter.next().unwrap().as_ref().unwrap().extract()
.and_then(|#arg_name| {
#body
}
Err(e) => Err(e)
}
})
}
}
}

View file

@ -1,19 +1,17 @@
// Copyright (c) 2017-present PyO3 Project and Contributors
#![recursion_limit="1024"]
#![recursion_limit = "1024"]
#![feature(proc_macro)]
extern crate proc_macro;
extern crate syn;
extern crate quote;
extern crate pyo3_derive_backend;
extern crate quote;
extern crate syn;
use std::str::FromStr;
use proc_macro::TokenStream;
use quote::{Tokens, ToTokens};
use pyo3_derive_backend::*;
use quote::{ToTokens, Tokens};
use std::str::FromStr;
#[proc_macro_attribute]
pub fn mod2init(attr: TokenStream, input: TokenStream) -> TokenStream {
@ -24,7 +22,13 @@ pub fn mod2init(attr: TokenStream, input: TokenStream) -> TokenStream {
let mut ast = syn::parse_item(&source).unwrap();
// Build the output
let init = module::build_py2_module_init(&mut ast, utils::attr_with_parentheses(attr));
module::process_functions_in_module(&mut ast);
let attr = utils::attr_with_parentheses(attr);
let modname = &attr[1..attr.len() - 1].to_string();
let init = module::py2_init(&ast.ident, &modname, utils::get_doc(&ast.attrs, false));
// Return the generated impl as a TokenStream
let mut tokens = Tokens::new();
@ -43,7 +47,13 @@ pub fn mod3init(attr: TokenStream, input: TokenStream) -> TokenStream {
let mut ast = syn::parse_item(&source).unwrap();
// Build the output
let init = module::build_py3_module_init(&mut ast, utils::attr_with_parentheses(attr));
module::process_functions_in_module(&mut ast);
let attr = utils::attr_with_parentheses(attr);
let modname = &attr[1..attr.len() - 1].to_string();
let init = module::py3_init(&ast.ident, &modname, utils::get_doc(&ast.attrs, false));
// Return the generated impl as a TokenStream
let mut tokens = Tokens::new();
@ -109,3 +119,23 @@ pub fn methods(_: TokenStream, input: TokenStream) -> TokenStream {
TokenStream::from_str(s.as_str()).unwrap()
}
#[proc_macro_attribute]
pub fn function(_: TokenStream, input: TokenStream) -> TokenStream {
// Construct a string representation of the type definition
let source = input.to_string();
// Parse the string representation into a syntax tree
let mut ast = syn::parse_item(&source).unwrap();
// Build the output
let python_name = ast.ident.clone();
let expanded = module::add_fn_to_module(&mut ast, &python_name, Vec::new());
// Return the generated impl as a TokenStream
let mut tokens = Tokens::new();
ast.to_tokens(&mut tokens);
let s = String::from(tokens.as_str()) + expanded.as_str();
TokenStream::from_str(s.as_str()).unwrap()
}

View file

@ -301,3 +301,31 @@ impl<T> PyTryFrom for T where T: PyTypeInfo {
}
}
}
/// This trait wraps a T: IntoPyObject into PyResult<T> while PyResult<T> remains PyResult<T>.
///
/// This is necessaty 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<Self::Inner>;
}
impl<T: IntoPyObject> ReturnTypeIntoPyResult for T {
type Inner = T;
default fn return_type_into_py_result(self) -> PyResult<Self::Inner> {
Ok(self)
}
}
impl<T: IntoPyObject> ReturnTypeIntoPyResult for PyResult<T> {
type Inner = T;
fn return_type_into_py_result(self) -> PyResult<Self::Inner> {
self
}
}

View file

@ -140,7 +140,7 @@ pub struct PyDowncastError;
/// Helper conversion trait that allows to use custom arguments for exception constructor.
pub trait PyErrArguments {
/// Arguments for exception
fn arguments(&self, Python) -> PyObject;
fn arguments(&self, _: Python) -> PyObject;
}
impl PyErr {

View file

@ -167,13 +167,14 @@ pub use python::{Python, ToPyPointer, IntoPyPointer, IntoPyDictPointer};
pub use pythonrun::{GILGuard, GILPool, prepare_freethreaded_python, prepare_pyo3_library};
pub use instance::{PyToken, PyObjectWithToken, AsPyRef, Py, PyNativeType};
pub use conversion::{FromPyObject, PyTryFrom, PyTryInto,
ToPyObject, ToBorrowedObject, IntoPyObject, IntoPyTuple};
ToPyObject, ToBorrowedObject, IntoPyObject, IntoPyTuple,
ReturnTypeIntoPyResult};
pub mod class;
pub use class::*;
/// Procedural macros
pub mod py {
pub use pyo3cls::{proto, class, methods};
pub use pyo3cls::{proto, class, methods, function};
#[cfg(Py_3)]
pub use pyo3cls::mod3init as modinit;
@ -183,13 +184,24 @@ pub mod py {
}
/// Constructs a `&'static CStr` literal.
macro_rules! cstr(
macro_rules! cstr {
($s: tt) => (
// TODO: verify that $s is a string literal without nuls
unsafe {
::std::ffi::CStr::from_ptr(concat!($s, "\0").as_ptr() as *const _)
}
);
}
/// Returns a function that takes a Python instance and returns a python function.
///
/// Use this together with `#[function]` and [PyModule::add_function].
#[macro_export]
macro_rules! wrap_function (
($function_name:ident) => {
// Make sure this ident matches the one in function_wrapper_ident
&concat_idents!(__pyo3_get_function_, $function_name)
};
);
mod python;

View file

@ -16,7 +16,6 @@ use objectprotocol::ObjectProtocol;
use instance::PyObjectWithToken;
use err::{PyResult, PyErr};
/// Represents a Python `module` object.
pub struct PyModule(PyObject);
@ -140,4 +139,23 @@ impl PyModule {
self.setattr(T::NAME, ty)
}
/// Adds a function to a module, using the functions __name__ as name.
///
/// Use this together with the`#[function]` and [wrap_function!] macro.
///
/// ```rust,ignore
/// m.add_function(wrap_function!(double));
/// ```
///
/// You can also add a function with a custom name using [add](PyModule::add):
///
/// ```rust,ignore
/// m.add("also_double", wrap_function!(double)(py));
/// ```
pub fn add_function(&self, wrapper: &Fn(Python) -> PyObject) -> PyResult<()> {
let function = wrapper(self.py());
let name = function.getattr(self.py(), "__name__").expect("A function must have a __name__");
self.add(name.extract(self.py()).unwrap(), function)
}
}

View file

@ -399,7 +399,7 @@ impl<'p> Python<'p> {
}
#[doc(hidden)]
/// Pass value owneship to `Python` object and get reference back.
/// Pass value ownership to `Python` object and get reference back.
/// Value get cleaned up on the GIL release.
pub fn register_any<T: 'static>(self, ob: T) -> &'p T {
unsafe { pythonrun::register_any(ob) }

View file

@ -1,9 +1,10 @@
#![feature(proc_macro, specialization)]
#![feature(proc_macro, specialization, concat_idents)]
#[macro_use]
extern crate pyo3;
use pyo3::prelude::*;
use pyo3::py::{class, modinit};
use pyo3::{PyDict, PyModule, PyObject, PyResult, Python};
use pyo3::py::{class, function, modinit};
#[class]
@ -13,6 +14,11 @@ fn sum_as_string(a: i64, b: i64) -> String {
format!("{}", a + b).to_string()
}
#[function]
fn double(x: usize) -> usize {
x * 2
}
/// This module is implemented in Rust.
#[modinit(module_with_functions)]
fn init_mod(py: Python, m: &PyModule) -> PyResult<()> {
@ -31,6 +37,9 @@ fn init_mod(py: Python, m: &PyModule) -> PyResult<()> {
m.add("foo", "bar").unwrap();
m.add_function(wrap_function!(double)).unwrap();
m.add("also_double", wrap_function!(double)(py)).unwrap();
Ok(())
}
@ -47,4 +56,6 @@ fn test_module_with_functions() {
py.run("assert module_with_functions.no_parameters() == 42", None, Some(d)).unwrap();
py.run("assert module_with_functions.foo == 'bar'", None, Some(d)).unwrap();
py.run("assert module_with_functions.EmptyClass != None", None, Some(d)).unwrap();
py.run("assert module_with_functions.double(3) == 6", None, Some(d)).unwrap();
py.run("assert module_with_functions.also_double(3) == 6", None, Some(d)).unwrap();
}