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 // Copyright (c) 2017-present PyO3 Project and Contributors
use syn; use syn;
#[derive(Debug, PartialEq)] #[derive(Debug, Clone, PartialEq)]
pub enum Argument { pub enum Argument {
VarArgsSeparator, VarArgsSeparator,
VarArgs(String), VarArgs(String),

View File

@ -1,13 +1,12 @@
// Copyright (c) 2017-present PyO3 Project and Contributors // Copyright (c) 2017-present PyO3 Project and Contributors
use syn;
use quote::{Tokens, Ident};
use args::{Argument, parse_arguments}; use args::{Argument, parse_arguments};
use quote::{Ident, Tokens};
use syn;
use utils::for_err_msg; use utils::for_err_msg;
#[derive(Clone, Debug)] #[derive(Clone, PartialEq, Debug)]
pub struct FnArg<'a> { pub struct FnArg<'a> {
pub name: &'a syn::Ident, pub name: &'a syn::Ident,
pub mode: &'a syn::BindingMode, pub mode: &'a syn::BindingMode,
@ -29,6 +28,7 @@ pub enum FnType {
FnStatic, FnStatic,
} }
#[derive(Clone, PartialEq, Debug)]
pub struct FnSpec<'a> { pub struct FnSpec<'a> {
pub tp: FnType, pub tp: FnType,
pub attrs: Vec<Argument>, pub attrs: Vec<Argument>,
@ -36,8 +36,14 @@ pub struct FnSpec<'a> {
pub output: syn::Ty, 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 /// Parser function signature and function attributes
pub fn parse(name: &'a syn::Ident, pub fn parse(name: &'a syn::Ident,
sig: &'a syn::MethodSig, sig: &'a syn::MethodSig,
@ -96,10 +102,7 @@ impl<'a> FnSpec<'a> {
} }
} }
let ty = match sig.decl.output { let ty = get_return_info(&sig.decl.output);
syn::FunctionRetTy::Default => syn::Ty::Infer,
syn::FunctionRetTy::Ty(ref ty) => ty.clone()
};
FnSpec { FnSpec {
tp: fn_type, 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); let opt = method::check_arg_ty_and_optional(&name, ty);
arguments.push(method::FnArg {name: ident, arguments.push(method::FnArg {
name: ident,
mode: mode, mode: mode,
ty: ty, ty: ty,
optional: opt, optional: opt,
py: py, py: py,
reference: method::is_ref(&name, ty)}); reference: method::is_ref(&name, ty),
});
} }
&syn::FnArg::Ignored(_) => &syn::FnArg::Ignored(_) =>
panic!("ignored argument: {:?}", name), panic!("ignored argument: {:?}", name),
} }
} }
let ty = match decl.output { let ty = method::get_return_info(&decl.output);
syn::FunctionRetTy::Default => syn::Ty::Infer,
syn::FunctionRetTy::Ty(ref ty) => ty.clone()
};
let spec = method::FnSpec { let spec = method::FnSpec {
tp: method::FnType::Fn, 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 { |item| if item.1.py {syn::Ident::from("_py")} else {
syn::Ident::from(format!("arg{}", item.0))}).collect(); syn::Ident::from(format!("arg{}", item.0))}).collect();
let cb = quote! {{ let cb = quote! {{
#name(#(#names),*) #name(#(#names),*).return_type_into_py_result()
}}; }};
let body = py_method::impl_arg_params(spec, cb); 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! { quote! {
#[allow(unused_variables, unused_imports)] #[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 _args = _py.from_borrowed_ptr::<_pyo3::PyTuple>(_args);
let _kwargs = _pyo3::argparse::get_kwargs(_py, _kwargs); let _kwargs = _pyo3::argparse::get_kwargs(_py, _kwargs);
let _result: #output = { #body_to_result
#body
};
_pyo3::callback::cb_convert( _pyo3::callback::cb_convert(
_pyo3::callback::PyObjectCallbackConverter, _py, _result) _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, py: true,
reference: false 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)) 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) /// Generate function wrapper (PyCFunction, PyCFunctionWithKeywords)
pub fn impl_wrap(cls: &Box<syn::Ty>, name: &syn::Ident, spec: &FnSpec, noargs: bool) -> Tokens { pub fn impl_wrap(cls: &Box<syn::Ty>, name: &syn::Ident, spec: &FnSpec, noargs: bool) -> Tokens {
let cb = impl_call(cls, name, &spec); let body = impl_call(cls, name, &spec);
let output = &spec.output;
if spec.args.is_empty() && noargs { if spec.args.is_empty() && noargs {
let body_to_result = body_to_result(&body, spec);
quote! { quote! {
unsafe extern "C" fn __wrap( unsafe extern "C" fn __wrap(
_slf: *mut _pyo3::ffi::PyObject) -> *mut _pyo3::ffi::PyObject _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 _py = _pyo3::Python::assume_gil_acquired();
let _slf = _py.mut_from_borrowed_ptr::<#cls>(_slf); let _slf = _py.mut_from_borrowed_ptr::<#cls>(_slf);
let _result: #output = { #body_to_result
#cb
};
_pyo3::callback::cb_convert( _pyo3::callback::cb_convert(
_pyo3::callback::PyObjectCallbackConverter, _py, _result) _pyo3::callback::PyObjectCallbackConverter, _py, _result)
} }
} }
} else { } 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! { quote! {
unsafe extern "C" fn __wrap( 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 _args = _py.from_borrowed_ptr::<_pyo3::PyTuple>(_args);
let _kwargs = _pyo3::argparse::get_kwargs(_py, _kwargs); let _kwargs = _pyo3::argparse::get_kwargs(_py, _kwargs);
let _result: #output = { #body_to_result
#body
};
_pyo3::callback::cb_convert( _pyo3::callback::cb_convert(
_pyo3::callback::PyObjectCallbackConverter, _py, _result) _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 { |item| if item.1.py {syn::Ident::from("_py")} else {
syn::Ident::from(format!("arg{}", item.0))}).collect(); syn::Ident::from(format!("arg{}", item.0))}).collect();
let cb = quote! {{ let cb = quote! {{
#cls::#name(&_obj, #(#names),*) #cls::#name(&_obj, #(#names),*).return_type_into_py_result()
}}; }};
let body = impl_arg_params(spec, cb); let body = impl_arg_params(spec, cb);
let output = &spec.output; let body_to_result = body_to_result(&body, spec);
quote! { quote! {
#[allow(unused_mut)] #[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 _args = _py.from_borrowed_ptr::<_pyo3::PyTuple>(_args);
let _kwargs = _pyo3::argparse::get_kwargs(_py, _kwargs); let _kwargs = _pyo3::argparse::get_kwargs(_py, _kwargs);
let _result: #output = { #body_to_result
#body
};
match _result { match _result {
Ok(_) => _obj.into_ptr(), 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 /// Generate function wrapper for ffi::initproc
fn impl_wrap_init(cls: &Box<syn::Ty>, name: &syn::Ident, spec: &FnSpec) -> Tokens { fn impl_wrap_init(cls: &Box<syn::Ty>, name: &syn::Ident, spec: &FnSpec) -> Tokens {
let cb = impl_call(cls, name, &spec); 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 = impl_arg_params(&spec, cb);
let body_to_result = body_to_result(&body, spec);
quote! { quote! {
#[allow(unused_mut)] #[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 _args = _py.from_borrowed_ptr::<_pyo3::PyTuple>(_args);
let _kwargs = _pyo3::argparse::get_kwargs(_py, _kwargs); let _kwargs = _pyo3::argparse::get_kwargs(_py, _kwargs);
let _result: PyResult<()> = { #body_to_result
#body
};
match _result { match _result {
Ok(_) => 0, Ok(_) => 0,
Err(e) => { 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 { |item| if item.1.py {syn::Ident::from("_py")} else {
syn::Ident::from(format!("arg{}", item.0))}).collect(); syn::Ident::from(format!("arg{}", item.0))}).collect();
let cb = quote! {{ let cb = quote! {{
#cls::#name(&_cls, #(#names),*) #cls::#name(&_cls, #(#names),*).return_type_into_py_result()
}}; }};
let body = impl_arg_params(spec, cb); let body = impl_arg_params(spec, cb);
let output = &spec.output; let body_to_result = body_to_result(&body, spec);
quote! { quote! {
#[allow(unused_mut)] #[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 _args = _py.from_borrowed_ptr::<_pyo3::PyTuple>(_args);
let _kwargs = _pyo3::argparse::get_kwargs(_py, _kwargs); let _kwargs = _pyo3::argparse::get_kwargs(_py, _kwargs);
let _result: #output = { #body_to_result
#body
};
_pyo3::callback::cb_convert( _pyo3::callback::cb_convert(
_pyo3::callback::PyObjectCallbackConverter, _py, _result) _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 { |item| if item.1.py {syn::Ident::from("_py")} else {
syn::Ident::from(format!("arg{}", item.0))}).collect(); syn::Ident::from(format!("arg{}", item.0))}).collect();
let cb = quote! {{ let cb = quote! {{
#cls::#name(#(#names),*) #cls::#name(#(#names),*).return_type_into_py_result()
}}; }};
let body = impl_arg_params(spec, cb); let body = impl_arg_params(spec, cb);
let output = &spec.output; let body_to_result = body_to_result(&body, spec);
quote! { quote! {
#[allow(unused_mut)] #[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 _args = _py.from_borrowed_ptr::<_pyo3::PyTuple>(_args);
let _kwargs = _pyo3::argparse::get_kwargs(_py, _kwargs); let _kwargs = _pyo3::argparse::get_kwargs(_py, _kwargs);
let _result: #output = { #body_to_result
#body
};
_pyo3::callback::cb_convert( _pyo3::callback::cb_convert(
_pyo3::callback::PyObjectCallbackConverter, _py, _result) _pyo3::callback::PyObjectCallbackConverter, _py, _result)
} }
@ -344,7 +351,7 @@ fn impl_call(_cls: &Box<syn::Ty>, fname: &syn::Ident, spec: &FnSpec) -> Tokens {
} }
).collect(); ).collect();
quote! {{ 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(); let mut rargs = spec.args.clone();
rargs.reverse(); rargs.reverse();
let mut body = body; let mut body = body;
for (idx, arg) in rargs.iter().enumerate() { for (idx, arg) in rargs.iter().enumerate() {
body = impl_arg_param(&arg, &spec, &body, len-idx-1); 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 { fn impl_arg_param(arg: &FnArg, spec: &FnSpec, body: &Tokens, idx: usize) -> Tokens {
if arg.py { if arg.py {
return body.clone() return body.clone();
} }
let ty = arg.ty; let ty = arg.ty;
let name = arg.name; 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) Err(e) => Err(e)
} }
} }
} } else if spec.is_kwargs(&name) {
else if spec.is_kwargs(&name) {
quote! {{ quote! {{
let #arg_name = _kwargs; let #arg_name = _kwargs;
#body #body
}} }}
} } else {
else {
if let Some(_) = arg.optional { if let Some(_) = arg.optional {
// default value // default value
let mut default = Tokens::new(); let mut default = Tokens::new();
if let Some(d) = spec.default_value(name) { if let Some(d) = spec.default_value(name) {
let dt = quote!{ Some(#d) }; let dt = quote! { Some(#d) };
dt.to_tokens(&mut default); dt.to_tokens(&mut default);
} else { } else {
syn::Ident::from("None").to_tokens(&mut default); 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) Err(e) => Err(e)
} }
} }
} } else {
else {
quote! { quote! {
match _iter.next().unwrap().as_ref().unwrap().extract() { match _iter.next().unwrap().as_ref().unwrap().extract() {
Ok(#arg_name) => { 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. /// Helper conversion trait that allows to use custom arguments for exception constructor.
pub trait PyErrArguments { pub trait PyErrArguments {
/// Arguments for exception /// Arguments for exception
fn arguments(&self, Python) -> PyObject; fn arguments(&self, _: Python) -> PyObject;
} }
impl PyErr { 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 pythonrun::{GILGuard, GILPool, prepare_freethreaded_python, prepare_pyo3_library};
pub use instance::{PyToken, PyObjectWithToken, AsPyRef, Py, PyNativeType}; pub use instance::{PyToken, PyObjectWithToken, AsPyRef, Py, PyNativeType};
pub use conversion::{FromPyObject, PyTryFrom, PyTryInto, pub use conversion::{FromPyObject, PyTryFrom, PyTryInto,
ToPyObject, ToBorrowedObject, IntoPyObject, IntoPyTuple}; ToPyObject, ToBorrowedObject, IntoPyObject, IntoPyTuple,
ReturnTypeIntoPyResult};
pub mod class; pub mod class;
pub use class::*; pub use class::*;

View File

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