Relax return type requirements

Allows returning essentially arbitrary types by wrapping them into a PyResult. This is done with a conversion trait that specializes for PyResult.
This commit is contained in:
konstin 2018-04-06 17:19:32 +02:00
parent ced4eb532c
commit 45bb09b3e8
9 changed files with 105 additions and 65 deletions

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

@ -227,22 +227,21 @@ fn wrap_fn(item: &mut syn::Item) -> Option<Box<syn::Block>> {
};
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)});
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 ty = method::get_return_info(&decl.output);
let spec = method::FnSpec {
tp: method::FnType::Fn,
@ -309,11 +308,11 @@ pub fn impl_wrap(name: &syn::Ident, spec: &method::FnSpec) -> Tokens {
|item| if item.1.py {syn::Ident::from("_py")} else {
syn::Ident::from(format!("arg{}", item.0))}).collect();
let cb = quote! {{
#name(#(#names),*)
#name(#(#names),*).return_type_into_py_result()
}};
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 +328,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

@ -337,7 +337,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

@ -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)
}
@ -128,11 +136,11 @@ pub fn impl_wrap_new(cls: &Box<syn::Ty>, name: &syn::Ident, spec: &FnSpec) -> To
|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),*)
#cls::#name(&_obj, #(#names),*).return_type_into_py_result()
}};
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) => {
@ -212,10 +222,11 @@ pub fn impl_wrap_class(cls: &Box<syn::Ty>, name: &syn::Ident, spec: &FnSpec) ->
|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),*)
#cls::#name(&_cls, #(#names),*).return_type_into_py_result()
}};
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)
}
@ -246,11 +255,11 @@ pub fn impl_wrap_static(cls: &Box<syn::Ty>, name: &syn::Ident, spec: &FnSpec) ->
|item| if item.1.py {syn::Ident::from("_py")} else {
syn::Ident::from(format!("arg{}", item.0))}).collect();
let cb = quote! {{
#cls::#name(#(#names),*)
#cls::#name(#(#names),*).return_type_into_py_result()
}};
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)
}
@ -344,7 +351,7 @@ fn impl_call(_cls: &Box<syn::Ty>, fname: &syn::Ident, spec: &FnSpec) -> Tokens {
}
).collect();
quote! {{
_slf.#fname(#(#names),*)
_slf.#fname(#(#names),*).return_type_into_py_result()
}}
}
@ -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;
@ -444,19 +452,17 @@ fn impl_arg_param(arg: &FnArg, spec: &FnSpec, body: &Tokens, idx: usize) -> Toke
Err(e) => Err(e)
}
}
}
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,8 +506,7 @@ 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) => {

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: ToPyObject;
fn return_type_into_py_result(self) -> PyResult<Self::Inner>;
}
impl<T: ToPyObject> ReturnTypeIntoPyResult for T {
type Inner = T;
default fn return_type_into_py_result(self) -> PyResult<Self::Inner> {
Ok(self)
}
}
impl<T: ToPyObject> 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

@ -165,7 +165,8 @@ 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::*;

View File

@ -396,6 +396,11 @@ impl StaticMethod {
fn method(py: Python) -> PyResult<&'static str> {
Ok("StaticMethod.method()!")
}
#[staticmethod]
fn no_parameters() -> PyResult<&'static str> {
Ok("StaticMethod.no_parameters()!")
}
}
#[test]
@ -408,6 +413,7 @@ fn static_method() {
d.set_item("C", py.get_type::<StaticMethod>()).unwrap();
py.run("assert C.method() == 'StaticMethod.method()!'", None, Some(d)).unwrap();
py.run("assert C().method() == 'StaticMethod.method()!'", None, Some(d)).unwrap();
py.run("assert C.no_parameters() == 'StaticMethod.no_parameters()!'", None, Some(d)).unwrap();
}
#[py::class]