Merge pull request #2503 from davidhewitt/extract_argument_holder

pyfunction: use extract_argument with holder to avoid extractext
This commit is contained in:
David Hewitt 2022-07-17 07:14:53 +01:00 committed by GitHub
commit fa19f322d2
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
12 changed files with 310 additions and 245 deletions

View File

@ -73,6 +73,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
- Fix incorrect enum names being returned by `repr` for enums renamed by `#[pyclass(name)]` [#2457](https://github.com/PyO3/pyo3/pull/2457) - Fix incorrect enum names being returned by `repr` for enums renamed by `#[pyclass(name)]` [#2457](https://github.com/PyO3/pyo3/pull/2457)
- Fix incorrect Python version cfg definition on `PyObject_CallNoArgs`[#2476](https://github.com/PyO3/pyo3/pull/2476) - Fix incorrect Python version cfg definition on `PyObject_CallNoArgs`[#2476](https://github.com/PyO3/pyo3/pull/2476)
- Fix use-after-free in `PyCapsule` type. [#2481](https://github.com/PyO3/pyo3/pull/2481) - Fix use-after-free in `PyCapsule` type. [#2481](https://github.com/PyO3/pyo3/pull/2481)
- Fix several clippy warnings generated by `#[pyfunction]` arguments. [#2503](https://github.com/PyO3/pyo3/pull/2503)
## [0.16.5] - 2022-05-15 ## [0.16.5] - 2022-05-15

View File

@ -967,12 +967,24 @@ impl ::pyo3::PyClass for MyClass {
type Frozen = pyo3::pyclass::boolean_struct::False; type Frozen = pyo3::pyclass::boolean_struct::False;
} }
impl<'a> ::pyo3::derive_utils::ExtractExt<'a> for &'a mut MyClass { impl<'a, 'py> ::pyo3::impl_::extract_argument::PyFunctionArgument<'a, 'py> for &'a MyClass
type Target = ::pyo3::PyRefMut<'a, MyClass>; {
type Holder = ::std::option::Option<::pyo3::PyRef<'py, MyClass>>;
#[inline]
fn extract(obj: &'py ::pyo3::PyAny, holder: &'a mut Self::Holder) -> ::pyo3::PyResult<Self> {
::pyo3::impl_::extract_argument::extract_pyclass_ref(obj, holder)
}
} }
impl<'a> ::pyo3::derive_utils::ExtractExt<'a> for &'a MyClass { impl<'a, 'py> ::pyo3::impl_::extract_argument::PyFunctionArgument<'a, 'py> for &'a mut MyClass
type Target = ::pyo3::PyRef<'a, MyClass>; {
type Holder = ::std::option::Option<::pyo3::PyRefMut<'py, MyClass>>;
#[inline]
fn extract(obj: &'py ::pyo3::PyAny, holder: &'a mut Self::Holder) -> ::pyo3::PyResult<Self> {
::pyo3::impl_::extract_argument::extract_pyclass_ref_mut(obj, holder)
}
} }
impl pyo3::IntoPy<PyObject> for MyClass { impl pyo3::IntoPy<PyObject> for MyClass {

View File

@ -148,6 +148,11 @@ pub fn print_feature_cfgs() {
if rustc_minor_version >= 51 { if rustc_minor_version >= 51 {
println!("cargo:rustc-cfg=addr_of"); println!("cargo:rustc-cfg=addr_of");
} }
// Enable use of Option::insert on Rust 1.53 and greater
if rustc_minor_version >= 53 {
println!("cargo:rustc-cfg=option_insert");
}
} }
/// Private exports used in PyO3's build.rs /// Private exports used in PyO3's build.rs

View File

@ -460,9 +460,6 @@ impl<'a> FnSpec<'a> {
let deprecations = &self.deprecations; let deprecations = &self.deprecations;
let self_conversion = self.tp.self_conversion(cls, ExtractErrorMode::Raise); let self_conversion = self.tp.self_conversion(cls, ExtractErrorMode::Raise);
let self_arg = self.tp.self_arg(); let self_arg = self.tp.self_arg();
let arg_names = (0..self.args.len())
.map(|pos| syn::Ident::new(&format!("arg{}", pos), Span::call_site()))
.collect::<Vec<_>>();
let py = syn::Ident::new("_py", Span::call_site()); let py = syn::Ident::new("_py", Span::call_site());
let func_name = &self.name; let func_name = &self.name;
let rust_name = if let Some(cls) = cls { let rust_name = if let Some(cls) = cls {
@ -472,19 +469,22 @@ impl<'a> FnSpec<'a> {
}; };
// The method call is necessary to generate a decent error message. // The method call is necessary to generate a decent error message.
let rust_call = quote! { let rust_call = |args: Vec<TokenStream>| {
let mut ret = #rust_name(#self_arg #(#arg_names),*); quote! {
let mut ret = #rust_name(#self_arg #(#args),*);
if false { if false {
use _pyo3::impl_::ghost::IntoPyResult; use _pyo3::impl_::ghost::IntoPyResult;
ret.assert_into_py_result(); ret.assert_into_py_result();
}
_pyo3::callback::convert(#py, ret)
} }
_pyo3::callback::convert(#py, ret)
}; };
Ok(match self.convention { Ok(match self.convention {
CallingConvention::Noargs => { CallingConvention::Noargs => {
let call = rust_call(vec![]);
quote! { quote! {
unsafe extern "C" fn #ident ( unsafe extern "C" fn #ident (
_slf: *mut _pyo3::ffi::PyObject, _slf: *mut _pyo3::ffi::PyObject,
@ -496,13 +496,14 @@ impl<'a> FnSpec<'a> {
let #py = gil.python(); let #py = gil.python();
_pyo3::callback::panic_result_into_callback_output(#py, ::std::panic::catch_unwind(move || -> _pyo3::PyResult<_> { _pyo3::callback::panic_result_into_callback_output(#py, ::std::panic::catch_unwind(move || -> _pyo3::PyResult<_> {
#self_conversion #self_conversion
#rust_call #call
})) }))
} }
} }
} }
CallingConvention::Fastcall => { CallingConvention::Fastcall => {
let arg_convert = impl_arg_params(self, cls, &py, true)?; let (arg_convert, args) = impl_arg_params(self, cls, &py, true)?;
let call = rust_call(args);
quote! { quote! {
unsafe extern "C" fn #ident ( unsafe extern "C" fn #ident (
_slf: *mut _pyo3::ffi::PyObject, _slf: *mut _pyo3::ffi::PyObject,
@ -516,13 +517,14 @@ impl<'a> FnSpec<'a> {
_pyo3::callback::panic_result_into_callback_output(#py, ::std::panic::catch_unwind(move || -> _pyo3::PyResult<_> { _pyo3::callback::panic_result_into_callback_output(#py, ::std::panic::catch_unwind(move || -> _pyo3::PyResult<_> {
#self_conversion #self_conversion
#arg_convert #arg_convert
#rust_call #call
})) }))
} }
} }
} }
CallingConvention::Varargs => { CallingConvention::Varargs => {
let arg_convert = impl_arg_params(self, cls, &py, false)?; let (arg_convert, args) = impl_arg_params(self, cls, &py, false)?;
let call = rust_call(args);
quote! { quote! {
unsafe extern "C" fn #ident ( unsafe extern "C" fn #ident (
_slf: *mut _pyo3::ffi::PyObject, _slf: *mut _pyo3::ffi::PyObject,
@ -535,14 +537,14 @@ impl<'a> FnSpec<'a> {
_pyo3::callback::panic_result_into_callback_output(#py, ::std::panic::catch_unwind(move || -> _pyo3::PyResult<_> { _pyo3::callback::panic_result_into_callback_output(#py, ::std::panic::catch_unwind(move || -> _pyo3::PyResult<_> {
#self_conversion #self_conversion
#arg_convert #arg_convert
#rust_call #call
})) }))
} }
} }
} }
CallingConvention::TpNew => { CallingConvention::TpNew => {
let rust_call = quote! { #rust_name(#(#arg_names),*) }; let (arg_convert, args) = impl_arg_params(self, cls, &py, false)?;
let arg_convert = impl_arg_params(self, cls, &py, false)?; let call = quote! { #rust_name(#(#args),*) };
quote! { quote! {
unsafe extern "C" fn #ident ( unsafe extern "C" fn #ident (
subtype: *mut _pyo3::ffi::PyTypeObject, subtype: *mut _pyo3::ffi::PyTypeObject,
@ -555,7 +557,7 @@ impl<'a> FnSpec<'a> {
let #py = gil.python(); let #py = gil.python();
_pyo3::callback::panic_result_into_callback_output(#py, ::std::panic::catch_unwind(move || -> _pyo3::PyResult<_> { _pyo3::callback::panic_result_into_callback_output(#py, ::std::panic::catch_unwind(move || -> _pyo3::PyResult<_> {
#arg_convert #arg_convert
let result = #rust_call; let result = #call;
let initializer: _pyo3::PyClassInitializer::<#cls> = result.convert(#py)?; let initializer: _pyo3::PyClassInitializer::<#cls> = result.convert(#py)?;
initializer.into_new_object(#py, subtype) initializer.into_new_object(#py, subtype)
})) }))

View File

@ -4,7 +4,6 @@ use crate::{
attributes::FromPyWithAttribute, attributes::FromPyWithAttribute,
method::{FnArg, FnSpec}, method::{FnArg, FnSpec},
pyfunction::Argument, pyfunction::Argument,
utils::{remove_lifetime, unwrap_ty_group},
}; };
use proc_macro2::{Span, TokenStream}; use proc_macro2::{Span, TokenStream};
use quote::{quote, quote_spanned}; use quote::{quote, quote_spanned};
@ -55,9 +54,9 @@ pub fn impl_arg_params(
self_: Option<&syn::Type>, self_: Option<&syn::Type>,
py: &syn::Ident, py: &syn::Ident,
fastcall: bool, fastcall: bool,
) -> Result<TokenStream> { ) -> Result<(TokenStream, Vec<TokenStream>)> {
if spec.args.is_empty() { if spec.args.is_empty() {
return Ok(TokenStream::new()); return Ok((TokenStream::new(), vec![]));
} }
let args_array = syn::Ident::new("output", Span::call_site()); let args_array = syn::Ident::new("output", Span::call_site());
@ -65,15 +64,18 @@ pub fn impl_arg_params(
if !fastcall && is_forwarded_args(&spec.args, &spec.attrs) { if !fastcall && is_forwarded_args(&spec.args, &spec.attrs) {
// In the varargs convention, we can just pass though if the signature // In the varargs convention, we can just pass though if the signature
// is (*args, **kwds). // is (*args, **kwds).
let mut arg_convert = vec![]; let arg_convert = spec
for (i, arg) in spec.args.iter().enumerate() { .args
arg_convert.push(impl_arg_param(arg, spec, i, &mut 0, py, &args_array)?); .iter()
} .map(|arg| impl_arg_param(arg, spec, &mut 0, py, &args_array))
return Ok(quote! { .collect::<Result<_>>()?;
let _args = #py.from_borrowed_ptr::<_pyo3::types::PyTuple>(_args); return Ok((
let _kwargs: ::std::option::Option<&_pyo3::types::PyDict> = #py.from_borrowed_ptr_or_opt(_kwargs); quote! {
#(#arg_convert)* let _args = #py.from_borrowed_ptr::<_pyo3::types::PyTuple>(_args);
}); let _kwargs: ::std::option::Option<&_pyo3::types::PyDict> = #py.from_borrowed_ptr_or_opt(_kwargs);
},
arg_convert,
));
}; };
let mut positional_parameter_names = Vec::new(); let mut positional_parameter_names = Vec::new();
@ -111,18 +113,12 @@ pub fn impl_arg_params(
let num_params = positional_parameter_names.len() + keyword_only_parameters.len(); let num_params = positional_parameter_names.len() + keyword_only_parameters.len();
let mut param_conversion = Vec::new();
let mut option_pos = 0; let mut option_pos = 0;
for (idx, arg) in spec.args.iter().enumerate() { let param_conversion = spec
param_conversion.push(impl_arg_param( .args
arg, .iter()
spec, .map(|arg| impl_arg_param(arg, spec, &mut option_pos, py, &args_array))
idx, .collect::<Result<_>>()?;
&mut option_pos,
py,
&args_array,
)?);
}
let (accept_args, accept_kwargs) = accept_args_kwargs(&spec.attrs); let (accept_args, accept_kwargs) = accept_args_kwargs(&spec.attrs);
let args_handler = if accept_args { let args_handler = if accept_args {
@ -165,21 +161,22 @@ pub fn impl_arg_params(
}; };
// create array of arguments, and then parse // create array of arguments, and then parse
Ok(quote! { Ok((
const DESCRIPTION: _pyo3::impl_::extract_argument::FunctionDescription = _pyo3::impl_::extract_argument::FunctionDescription { quote! {
cls_name: #cls_name, const DESCRIPTION: _pyo3::impl_::extract_argument::FunctionDescription = _pyo3::impl_::extract_argument::FunctionDescription {
func_name: stringify!(#python_name), cls_name: #cls_name,
positional_parameter_names: &[#(#positional_parameter_names),*], func_name: stringify!(#python_name),
positional_only_parameters: #positional_only_parameters, positional_parameter_names: &[#(#positional_parameter_names),*],
required_positional_parameters: #required_positional_parameters, positional_only_parameters: #positional_only_parameters,
keyword_only_parameters: &[#(#keyword_only_parameters),*], required_positional_parameters: #required_positional_parameters,
}; keyword_only_parameters: &[#(#keyword_only_parameters),*],
};
let mut #args_array = [::std::option::Option::None; #num_params]; let mut #args_array = [::std::option::Option::None; #num_params];
let (_args, _kwargs) = #extract_expression; let (_args, _kwargs) = #extract_expression;
},
#(#param_conversion)* param_conversion,
}) ))
} }
/// Re option_pos: The option slice doesn't contain the py: Python argument, so the argument /// Re option_pos: The option slice doesn't contain the py: Python argument, so the argument
@ -187,7 +184,6 @@ pub fn impl_arg_params(
fn impl_arg_param( fn impl_arg_param(
arg: &FnArg<'_>, arg: &FnArg<'_>,
spec: &FnSpec<'_>, spec: &FnSpec<'_>,
idx: usize,
option_pos: &mut usize, option_pos: &mut usize,
py: &syn::Ident, py: &syn::Ident,
args_array: &syn::Ident, args_array: &syn::Ident,
@ -198,13 +194,10 @@ fn impl_arg_param(
($($tokens:tt)*) => { quote_spanned!(arg.ty.span() => $($tokens)*) } ($($tokens:tt)*) => { quote_spanned!(arg.ty.span() => $($tokens)*) }
} }
let arg_name = syn::Ident::new(&format!("arg{}", idx), Span::call_site());
if arg.py { if arg.py {
return Ok(quote_arg_span! { let #arg_name = #py; }); return Ok(quote_arg_span! { #py });
} }
let ty = arg.ty;
let name = arg.name; let name = arg.name;
let name_str = name.to_string(); let name_str = name.to_string();
@ -214,7 +207,11 @@ fn impl_arg_param(
arg.name.span() => "args cannot be optional" arg.name.span() => "args cannot be optional"
); );
return Ok(quote_arg_span! { return Ok(quote_arg_span! {
let #arg_name = _pyo3::impl_::extract_argument::extract_argument(_args, #name_str)?; _pyo3::impl_::extract_argument::extract_argument(
_args,
&mut { _pyo3::impl_::extract_argument::FunctionArgumentHolder::INIT },
#name_str
)?
}); });
} else if is_kwargs(&spec.attrs, name) { } else if is_kwargs(&spec.attrs, name) {
ensure_spanned!( ensure_spanned!(
@ -222,31 +219,35 @@ fn impl_arg_param(
arg.name.span() => "kwargs must be Option<_>" arg.name.span() => "kwargs must be Option<_>"
); );
return Ok(quote_arg_span! { return Ok(quote_arg_span! {
let #arg_name = _pyo3::impl_::extract_argument::extract_optional_argument(_kwargs.map(|kwargs| kwargs.as_ref()), #name_str)?; _pyo3::impl_::extract_argument::extract_optional_argument(
_kwargs.map(|kwargs| kwargs.as_ref()),
&mut { _pyo3::impl_::extract_argument::FunctionArgumentHolder::INIT },
#name_str
)?
}); });
} }
let arg_value = quote_arg_span!(#args_array[#option_pos]); let arg_value = quote_arg_span!(#args_array[#option_pos]);
*option_pos += 1; *option_pos += 1;
let arg_value_or_default = if let Some(FromPyWithAttribute { let tokens = if let Some(FromPyWithAttribute {
value: expr_path, .. value: expr_path, ..
}) = &arg.attrs.from_py_with }) = &arg.attrs.from_py_with
{ {
match (spec.default_value(name), arg.optional.is_some()) { match (spec.default_value(name), arg.optional.is_some()) {
(Some(default), true) if default.to_string() != "None" => { (Some(default), true) if default.to_string() != "None" => {
quote_arg_span! { quote_arg_span! {
_pyo3::impl_::extract_argument::from_py_with_with_default(#arg_value, #name_str, #expr_path, || Some(#default)) _pyo3::impl_::extract_argument::from_py_with_with_default(#arg_value, #name_str, #expr_path, || Some(#default))?
} }
} }
(Some(default), _) => { (Some(default), _) => {
quote_arg_span! { quote_arg_span! {
_pyo3::impl_::extract_argument::from_py_with_with_default(#arg_value, #name_str, #expr_path, || #default) _pyo3::impl_::extract_argument::from_py_with_with_default(#arg_value, #name_str, #expr_path, || #default)?
} }
} }
(None, true) => { (None, true) => {
quote_arg_span! { quote_arg_span! {
_pyo3::impl_::extract_argument::from_py_with_with_default(#arg_value, #name_str, #expr_path, || Some(None)) _pyo3::impl_::extract_argument::from_py_with_with_default(#arg_value, #name_str, #expr_path, || Some(None))?
} }
} }
(None, false) => { (None, false) => {
@ -255,7 +256,7 @@ fn impl_arg_param(
_pyo3::impl_::extract_argument::unwrap_required_argument(#arg_value), _pyo3::impl_::extract_argument::unwrap_required_argument(#arg_value),
#name_str, #name_str,
#expr_path, #expr_path,
) )?
} }
} }
} }
@ -263,59 +264,43 @@ fn impl_arg_param(
match (spec.default_value(name), arg.optional.is_some()) { match (spec.default_value(name), arg.optional.is_some()) {
(Some(default), true) if default.to_string() != "None" => { (Some(default), true) if default.to_string() != "None" => {
quote_arg_span! { quote_arg_span! {
_pyo3::impl_::extract_argument::extract_argument_with_default(#arg_value, #name_str, || Some(#default)) _pyo3::impl_::extract_argument::extract_argument_with_default(
#arg_value,
&mut { _pyo3::impl_::extract_argument::FunctionArgumentHolder::INIT },
#name_str,
|| Some(#default)
)?
} }
} }
(Some(default), _) => { (Some(default), _) => {
quote_arg_span! { quote_arg_span! {
_pyo3::impl_::extract_argument::extract_argument_with_default(#arg_value, #name_str, || #default) _pyo3::impl_::extract_argument::extract_argument_with_default(
#arg_value,
&mut { _pyo3::impl_::extract_argument::FunctionArgumentHolder::INIT },
#name_str,
|| #default
)?
} }
} }
(None, true) => { (None, true) => {
quote_arg_span! { quote_arg_span! {
_pyo3::impl_::extract_argument::extract_optional_argument(#arg_value, #name_str) _pyo3::impl_::extract_argument::extract_optional_argument(
#arg_value,
&mut { _pyo3::impl_::extract_argument::FunctionArgumentHolder::INIT },
#name_str
)?
} }
} }
(None, false) => { (None, false) => {
quote_arg_span! { quote_arg_span! {
_pyo3::impl_::extract_argument::extract_argument( _pyo3::impl_::extract_argument::extract_argument(
_pyo3::impl_::extract_argument::unwrap_required_argument(#arg_value), _pyo3::impl_::extract_argument::unwrap_required_argument(#arg_value),
&mut { _pyo3::impl_::extract_argument::FunctionArgumentHolder::INIT },
#name_str #name_str
) )?
} }
} }
} }
}; };
Ok(tokens)
return if let syn::Type::Reference(tref) = unwrap_ty_group(arg.optional.unwrap_or(ty)) {
let tref = remove_lifetime(tref);
let mut_ = tref.mutability;
let (target_ty, borrow_tmp) = if arg.optional.is_some() {
// Get Option<&T> from Option<PyRef<T>>
(
quote_arg_span! { ::std::option::Option<<#tref as _pyo3::derive_utils::ExtractExt<'_>>::Target> },
if mut_.is_some() {
quote_arg_span! { _tmp.as_deref_mut() }
} else {
quote_arg_span! { _tmp.as_deref() }
},
)
} else {
// Get &T from PyRef<T>
(
quote_arg_span! { <#tref as _pyo3::derive_utils::ExtractExt<'_>>::Target },
quote_arg_span! { &#mut_ *_tmp },
)
};
Ok(quote_arg_span! {
let #mut_ _tmp: #target_ty = #arg_value_or_default?;
#[allow(clippy::needless_option_as_deref)]
let #arg_name = #borrow_tmp;
})
} else {
Ok(quote_arg_span! {
let #arg_name = #arg_value_or_default?;
})
};
} }

View File

@ -777,21 +777,36 @@ impl<'a> PyClassImplsBuilder<'a> {
let cls = self.cls; let cls = self.cls;
if self.attr.options.frozen.is_some() { if self.attr.options.frozen.is_some() {
quote! { quote! {
impl<'a> _pyo3::derive_utils::ExtractExt<'a> for &'a #cls impl<'a, 'py> _pyo3::impl_::extract_argument::PyFunctionArgument<'a, 'py> for &'a #cls
{ {
type Target = _pyo3::PyRef<'a, #cls>; type Holder = ::std::option::Option<_pyo3::PyRef<'py, #cls>>;
#[inline]
fn extract(obj: &'py _pyo3::PyAny, holder: &'a mut Self::Holder) -> _pyo3::PyResult<Self> {
_pyo3::impl_::extract_argument::extract_pyclass_ref(obj, holder)
}
} }
} }
} else { } else {
quote! { quote! {
impl<'a> _pyo3::derive_utils::ExtractExt<'a> for &'a #cls impl<'a, 'py> _pyo3::impl_::extract_argument::PyFunctionArgument<'a, 'py> for &'a #cls
{ {
type Target = _pyo3::PyRef<'a, #cls>; type Holder = ::std::option::Option<_pyo3::PyRef<'py, #cls>>;
#[inline]
fn extract(obj: &'py _pyo3::PyAny, holder: &'a mut Self::Holder) -> _pyo3::PyResult<Self> {
_pyo3::impl_::extract_argument::extract_pyclass_ref(obj, holder)
}
} }
impl<'a> _pyo3::derive_utils::ExtractExt<'a> for &'a mut #cls impl<'a, 'py> _pyo3::impl_::extract_argument::PyFunctionArgument<'a, 'py> for &'a mut #cls
{ {
type Target = _pyo3::PyRefMut<'a, #cls>; type Holder = ::std::option::Option<_pyo3::PyRefMut<'py, #cls>>;
#[inline]
fn extract(obj: &'py _pyo3::PyAny, holder: &'a mut Self::Holder) -> _pyo3::PyResult<Self> {
_pyo3::impl_::extract_argument::extract_pyclass_ref_mut(obj, holder)
}
} }
} }
} }

View File

@ -4,7 +4,7 @@ use std::borrow::Cow;
use crate::attributes::NameAttribute; use crate::attributes::NameAttribute;
use crate::method::{CallingConvention, ExtractErrorMode}; use crate::method::{CallingConvention, ExtractErrorMode};
use crate::utils::{ensure_not_async_fn, remove_lifetime, unwrap_ty_group, PythonDoc}; use crate::utils::{ensure_not_async_fn, PythonDoc};
use crate::{deprecations::Deprecations, utils}; use crate::{deprecations::Deprecations, utils};
use crate::{ use crate::{
method::{FnArg, FnSpec, FnType, SelfType}, method::{FnArg, FnSpec, FnType, SelfType},
@ -12,7 +12,6 @@ use crate::{
}; };
use proc_macro2::{Span, TokenStream}; use proc_macro2::{Span, TokenStream};
use quote::{format_ident, quote, ToTokens}; use quote::{format_ident, quote, ToTokens};
use syn::Ident;
use syn::{ext::IdentExt, spanned::Spanned, Result}; use syn::{ext::IdentExt, spanned::Spanned, Result};
/// Generated code for a single pymethod item. /// Generated code for a single pymethod item.
@ -839,81 +838,66 @@ impl Ty {
arg: &FnArg<'_>, arg: &FnArg<'_>,
extract_error_mode: ExtractErrorMode, extract_error_mode: ExtractErrorMode,
) -> TokenStream { ) -> TokenStream {
let name_str = arg.name.unraw().to_string();
match self { match self {
Ty::Object => { Ty::Object => extract_object(
let extract = handle_error( extract_error_mode,
extract_error_mode, py,
py, &name_str,
quote! {
#py.from_borrowed_ptr::<_pyo3::PyAny>(#ident).extract()
},
);
extract_object(arg.ty, ident, extract)
}
Ty::MaybeNullObject => {
let extract = handle_error(
extract_error_mode,
py,
quote! {
#py.from_borrowed_ptr::<_pyo3::PyAny>(
if #ident.is_null() {
_pyo3::ffi::Py_None()
} else {
#ident
}
).extract()
},
);
extract_object(arg.ty, ident, extract)
}
Ty::NonNullObject => {
let extract = handle_error(
extract_error_mode,
py,
quote! {
#py.from_borrowed_ptr::<_pyo3::PyAny>(#ident.as_ptr()).extract()
},
);
extract_object(arg.ty, ident, extract)
}
Ty::IPowModulo => {
let extract = handle_error(
extract_error_mode,
py,
quote! {
#ident.extract(#py)
},
);
extract_object(arg.ty, ident, extract)
}
Ty::CompareOp => {
let extract = handle_error(
extract_error_mode,
py,
quote! {
_pyo3::class::basic::CompareOp::from_raw(#ident)
.ok_or_else(|| _pyo3::exceptions::PyValueError::new_err("invalid comparison operator"))
},
);
quote! { quote! {
let #ident = #extract; #py.from_borrowed_ptr::<_pyo3::PyAny>(#ident)
} },
} ),
Ty::MaybeNullObject => extract_object(
extract_error_mode,
py,
&name_str,
quote! {
#py.from_borrowed_ptr::<_pyo3::PyAny>(
if #ident.is_null() {
_pyo3::ffi::Py_None()
} else {
#ident
}
)
},
),
Ty::NonNullObject => extract_object(
extract_error_mode,
py,
&name_str,
quote! {
#py.from_borrowed_ptr::<_pyo3::PyAny>(#ident.as_ptr())
},
),
Ty::IPowModulo => extract_object(
extract_error_mode,
py,
&name_str,
quote! {
#ident.to_borrowed_any(#py)
},
),
Ty::CompareOp => handle_error(
extract_error_mode,
py,
quote! {
_pyo3::class::basic::CompareOp::from_raw(#ident)
.ok_or_else(|| _pyo3::exceptions::PyValueError::new_err("invalid comparison operator"))
},
),
Ty::PySsizeT => { Ty::PySsizeT => {
let ty = arg.ty; let ty = arg.ty;
let extract = handle_error( handle_error(
extract_error_mode, extract_error_mode,
py, py,
quote! { quote! {
::std::convert::TryInto::<#ty>::try_into(#ident).map_err(|e| _pyo3::exceptions::PyValueError::new_err(e.to_string())) ::std::convert::TryInto::<#ty>::try_into(#ident).map_err(|e| _pyo3::exceptions::PyValueError::new_err(e.to_string()))
}, },
); )
quote! {
let #ident = #extract;
}
} }
// Just pass other types through unmodified // Just pass other types through unmodified
Ty::PyBuffer | Ty::Int | Ty::PyHashT | Ty::Void => quote! {}, Ty::PyBuffer | Ty::Int | Ty::PyHashT | Ty::Void => quote! { #ident },
} }
} }
} }
@ -934,19 +918,23 @@ fn handle_error(
} }
} }
fn extract_object(target: &syn::Type, ident: &syn::Ident, extract: TokenStream) -> TokenStream { fn extract_object(
if let syn::Type::Reference(tref) = unwrap_ty_group(target) { extract_error_mode: ExtractErrorMode,
let tref = remove_lifetime(tref); py: &syn::Ident,
let mut_ = tref.mutability; name: &str,
source: TokenStream,
) -> TokenStream {
handle_error(
extract_error_mode,
py,
quote! { quote! {
let #mut_ #ident: <#tref as _pyo3::derive_utils::ExtractExt<'_>>::Target = #extract; _pyo3::impl_::extract_argument::extract_argument(
let #ident = &#mut_ *#ident; #source,
} &mut { _pyo3::impl_::extract_argument::FunctionArgumentHolder::INIT },
} else { #name
quote! { )
let #ident = #extract; },
} )
}
} }
enum ReturnMode { enum ReturnMode {
@ -1096,12 +1084,8 @@ fn generate_method_body(
) -> Result<TokenStream> { ) -> Result<TokenStream> {
let self_conversion = spec.tp.self_conversion(Some(cls), extract_error_mode); let self_conversion = spec.tp.self_conversion(Some(cls), extract_error_mode);
let rust_name = spec.name; let rust_name = spec.name;
let (arg_idents, arg_count, conversions) = let args = extract_proto_arguments(py, spec, arguments, extract_error_mode)?;
extract_proto_arguments(py, &spec.args, arguments, extract_error_mode)?; let call = quote! { _pyo3::callback::convert(#py, #cls::#rust_name(_slf, #(#args),*)) };
if arg_count != arguments.len() {
bail_spanned!(spec.name.span() => format!("Expected {} arguments, got {}", arguments.len(), arg_count));
}
let call = quote! { _pyo3::callback::convert(#py, #cls::#rust_name(_slf, #(#arg_idents),*)) };
let body = if let Some(return_mode) = return_mode { let body = if let Some(return_mode) = return_mode {
return_mode.return_call_output(py, call) return_mode.return_call_output(py, call)
} else { } else {
@ -1109,7 +1093,6 @@ fn generate_method_body(
}; };
Ok(quote! { Ok(quote! {
#self_conversion #self_conversion
#conversions
#body #body
}) })
} }
@ -1243,30 +1226,30 @@ const __RPOW__: SlotFragmentDef = SlotFragmentDef::new("__rpow__", &[Ty::Object,
fn extract_proto_arguments( fn extract_proto_arguments(
py: &syn::Ident, py: &syn::Ident,
method_args: &[FnArg<'_>], spec: &FnSpec<'_>,
proto_args: &[Ty], proto_args: &[Ty],
extract_error_mode: ExtractErrorMode, extract_error_mode: ExtractErrorMode,
) -> Result<(Vec<Ident>, usize, TokenStream)> { ) -> Result<Vec<TokenStream>> {
let mut arg_idents = Vec::with_capacity(method_args.len()); let mut args = Vec::with_capacity(spec.args.len());
let mut non_python_args = 0; let mut non_python_args = 0;
let mut args_conversions = Vec::with_capacity(proto_args.len()); for arg in &spec.args {
for arg in method_args {
if arg.py { if arg.py {
arg_idents.push(py.clone()); args.push(quote! { #py });
} else { } else {
let ident = syn::Ident::new(&format!("arg{}", non_python_args), Span::call_site()); let ident = syn::Ident::new(&format!("arg{}", non_python_args), Span::call_site());
let conversions = proto_args.get(non_python_args) let conversions = proto_args.get(non_python_args)
.ok_or_else(|| err_spanned!(arg.ty.span() => format!("Expected at most {} non-python arguments", proto_args.len())))? .ok_or_else(|| err_spanned!(arg.ty.span() => format!("Expected at most {} non-python arguments", proto_args.len())))?
.extract(py, &ident, arg, extract_error_mode); .extract(py, &ident, arg, extract_error_mode);
non_python_args += 1; non_python_args += 1;
args_conversions.push(conversions); args.push(conversions);
arg_idents.push(ident);
} }
} }
let conversions = quote!(#(#args_conversions)*);
Ok((arg_idents, non_python_args, conversions)) if non_python_args != proto_args.len() {
bail_spanned!(spec.name.span() => format!("Expected {} arguments, got {}", proto_args.len(), non_python_args));
}
Ok(args)
} }
struct StaticIdent(&'static str); struct StaticIdent(&'static str);

View File

@ -164,13 +164,6 @@ pub fn unwrap_ty_group(mut ty: &syn::Type) -> &syn::Type {
ty ty
} }
/// Remove lifetime from reference
pub(crate) fn remove_lifetime(tref: &syn::TypeReference) -> syn::TypeReference {
let mut tref = tref.to_owned();
tref.lifetime = None;
tref
}
/// Extract the path to the pyo3 crate, or use the default (`::pyo3`). /// Extract the path to the pyo3 crate, or use the default (`::pyo3`).
pub(crate) fn get_pyo3_crate(attr: &Option<CrateAttribute>) -> syn::Path { pub(crate) fn get_pyo3_crate(attr: &Option<CrateAttribute>) -> syn::Path {
attr.as_ref() attr.as_ref()

View File

@ -733,7 +733,7 @@ where
.try_borrow_mut()? .try_borrow_mut()?
.__ipow__( .__ipow__(
extract_or_return_not_implemented!(other), extract_or_return_not_implemented!(other),
match modulo.extract(py) { match modulo.to_borrowed_any(py).extract() {
Ok(value) => value, Ok(value) => value,
Err(_) => { Err(_) => {
let res = crate::ffi::Py_NotImplemented(); let res = crate::ffi::Py_NotImplemented();

View File

@ -6,19 +6,6 @@
use crate::{types::PyModule, PyCell, PyClass, PyErr, Python}; use crate::{types::PyModule, PyCell, PyClass, PyErr, Python};
/// Utility trait to enable &PyClass as a pymethod/function argument
#[doc(hidden)]
pub trait ExtractExt<'a> {
type Target: crate::FromPyObject<'a>;
}
impl<'a, T> ExtractExt<'a> for T
where
T: crate::FromPyObject<'a>,
{
type Target = T;
}
/// A trait for types that can be borrowed from a cell. /// A trait for types that can be borrowed from a cell.
/// ///
/// This serves to unify the use of `PyRef` and `PyRefMut` in automatically /// This serves to unify the use of `PyRef` and `PyRefMut` in automatically

View File

@ -1,17 +1,91 @@
use crate::{ use crate::{
exceptions::PyTypeError, exceptions::PyTypeError,
ffi, ffi,
pyclass::boolean_struct::False,
types::{PyDict, PyString, PyTuple}, types::{PyDict, PyString, PyTuple},
FromPyObject, PyAny, PyErr, PyResult, PyTypeInfo, Python, FromPyObject, PyAny, PyClass, PyErr, PyRef, PyRefMut, PyResult, PyTypeInfo, Python,
}; };
/// A trait which is used to help PyO3 macros extract function arguments.
///
/// `#[pyclass]` structs need to extract as `PyRef<T>` and `PyRefMut<T>`
/// wrappers rather than extracting `&T` and `&mut T` directly. The `Holder` type is used
/// to hold these temporary wrappers - the way the macro is constructed, these wrappers
/// will be dropped as soon as the pyfunction call ends.
///
/// There exists a trivial blanket implementation for `T: FromPyObject` with `Holder = ()`.
pub trait PyFunctionArgument<'a, 'py>: Sized + 'a {
type Holder: FunctionArgumentHolder;
fn extract(obj: &'py PyAny, holder: &'a mut Self::Holder) -> PyResult<Self>;
}
impl<'a, 'py, T> PyFunctionArgument<'a, 'py> for T
where
T: FromPyObject<'py> + 'a,
{
type Holder = ();
#[inline]
fn extract(obj: &'py PyAny, _: &'a mut ()) -> PyResult<Self> {
obj.extract()
}
}
/// Trait for types which can be a function argument holder - they should
/// to be able to const-initialize to an empty value.
pub trait FunctionArgumentHolder: Sized {
const INIT: Self;
}
impl FunctionArgumentHolder for () {
const INIT: Self = ();
}
impl<T> FunctionArgumentHolder for Option<T> {
const INIT: Self = None;
}
#[inline]
pub fn extract_pyclass_ref<'a, 'py: 'a, T: PyClass>(
obj: &'py PyAny,
holder: &'a mut Option<PyRef<'py, T>>,
) -> PyResult<&'a T> {
#[cfg(not(option_insert))]
{
*holder = Some(obj.extract()?);
return Ok(holder.as_deref().unwrap());
}
#[cfg(option_insert)]
Ok(&*holder.insert(obj.extract()?))
}
#[inline]
pub fn extract_pyclass_ref_mut<'a, 'py: 'a, T: PyClass<Frozen = False>>(
obj: &'py PyAny,
holder: &'a mut Option<PyRefMut<'py, T>>,
) -> PyResult<&'a mut T> {
#[cfg(not(option_insert))]
{
*holder = Some(obj.extract()?);
return Ok(holder.as_deref_mut().unwrap());
}
#[cfg(option_insert)]
Ok(&mut *holder.insert(obj.extract()?))
}
/// The standard implementation of how PyO3 extracts a `#[pyfunction]` or `#[pymethod]` function argument. /// The standard implementation of how PyO3 extracts a `#[pyfunction]` or `#[pymethod]` function argument.
#[doc(hidden)] #[doc(hidden)]
pub fn extract_argument<'py, T>(obj: &'py PyAny, arg_name: &str) -> PyResult<T> pub fn extract_argument<'a, 'py, T>(
obj: &'py PyAny,
holder: &'a mut T::Holder,
arg_name: &str,
) -> PyResult<T>
where where
T: FromPyObject<'py>, T: PyFunctionArgument<'a, 'py>,
{ {
match obj.extract() { match PyFunctionArgument::extract(obj, holder) {
Ok(value) => Ok(value), Ok(value) => Ok(value),
Err(e) => Err(argument_extraction_error(obj.py(), arg_name, e)), Err(e) => Err(argument_extraction_error(obj.py(), arg_name, e)),
} }
@ -20,31 +94,39 @@ where
/// Alternative to [`extract_argument`] used for `Option<T>` arguments (because they are implicitly treated /// Alternative to [`extract_argument`] used for `Option<T>` arguments (because they are implicitly treated
/// as optional if at the end of the positional parameters). /// as optional if at the end of the positional parameters).
#[doc(hidden)] #[doc(hidden)]
pub fn extract_optional_argument<'py, T>( pub fn extract_optional_argument<'a, 'py, T>(
obj: Option<&'py PyAny>, obj: Option<&'py PyAny>,
holder: &'a mut T::Holder,
arg_name: &str, arg_name: &str,
) -> PyResult<Option<T>> ) -> PyResult<Option<T>>
where where
T: FromPyObject<'py>, T: PyFunctionArgument<'a, 'py>,
{ {
match obj { match obj {
Some(obj) => extract_argument(obj, arg_name), Some(obj) => {
if obj.is_none() {
Ok(None)
} else {
extract_argument(obj, holder, arg_name).map(Some)
}
}
None => Ok(None), None => Ok(None),
} }
} }
/// Alternative to [`extract_argument`] used when the argument has a default value provided by an annotation. /// Alternative to [`extract_argument`] used when the argument has a default value provided by an annotation.
#[doc(hidden)] #[doc(hidden)]
pub fn extract_argument_with_default<'py, T>( pub fn extract_argument_with_default<'a, 'py, T>(
obj: Option<&'py PyAny>, obj: Option<&'py PyAny>,
holder: &'a mut T::Holder,
arg_name: &str, arg_name: &str,
default: fn() -> T, default: fn() -> T,
) -> PyResult<T> ) -> PyResult<T>
where where
T: FromPyObject<'py>, T: PyFunctionArgument<'a, 'py>,
{ {
match obj { match obj {
Some(obj) => extract_argument(obj, arg_name), Some(obj) => extract_argument(obj, holder, arg_name),
None => Ok(default()), None => Ok(default()),
} }
} }

View File

@ -1,5 +1,5 @@
use crate::internal_tricks::{extract_cstr_or_leak_cstring, NulByteInString}; use crate::internal_tricks::{extract_cstr_or_leak_cstring, NulByteInString};
use crate::{ffi, AsPyPointer, FromPyObject, PyAny, PyObject, PyResult, Python}; use crate::{ffi, AsPyPointer, PyAny, PyObject, PyResult, Python};
use std::ffi::CStr; use std::ffi::CStr;
use std::fmt; use std::fmt;
use std::os::raw::{c_int, c_void}; use std::os::raw::{c_int, c_void};
@ -25,14 +25,14 @@ pub type ipowfunc = unsafe extern "C" fn(
impl IPowModulo { impl IPowModulo {
#[cfg(Py_3_8)] #[cfg(Py_3_8)]
#[inline] #[inline]
pub fn extract<'a, T: FromPyObject<'a>>(self, py: Python<'a>) -> PyResult<T> { pub fn to_borrowed_any(self, py: Python<'_>) -> &PyAny {
unsafe { py.from_borrowed_ptr::<PyAny>(self.0) }.extract() unsafe { py.from_borrowed_ptr::<PyAny>(self.0) }
} }
#[cfg(not(Py_3_8))] #[cfg(not(Py_3_8))]
#[inline] #[inline]
pub fn extract<'a, T: FromPyObject<'a>>(self, py: Python<'a>) -> PyResult<T> { pub fn to_borrowed_any(self, py: Python<'_>) -> &PyAny {
unsafe { py.from_borrowed_ptr::<PyAny>(ffi::Py_None()) }.extract() unsafe { py.from_borrowed_ptr::<PyAny>(ffi::Py_None()) }
} }
} }