Refactor wrapper generation, and enable fastcall for static/class methods.

This commit is contained in:
Georg Brandl 2021-06-05 17:04:56 +02:00
parent 4c6d46c86b
commit a8d2649032
6 changed files with 533 additions and 638 deletions

View File

@ -15,6 +15,7 @@ mod from_pyobject;
mod konst; mod konst;
mod method; mod method;
mod module; mod module;
mod params;
mod proto_method; mod proto_method;
mod pyclass; mod pyclass;
mod pyfunction; mod pyfunction;

View File

@ -1,15 +1,17 @@
// Copyright (c) 2017-present PyO3 Project and Contributors // Copyright (c) 2017-present PyO3 Project and Contributors
use crate::attributes::TextSignatureAttribute; use crate::attributes::TextSignatureAttribute;
use crate::params::impl_arg_params;
use crate::pyfunction::PyFunctionOptions; use crate::pyfunction::PyFunctionOptions;
use crate::pyfunction::{PyFunctionArgPyO3Attributes, PyFunctionSignature}; use crate::pyfunction::{PyFunctionArgPyO3Attributes, PyFunctionSignature};
use crate::utils; use crate::utils;
use crate::{deprecations::Deprecations, pyfunction::Argument}; use crate::{deprecations::Deprecations, pyfunction::Argument};
use proc_macro2::TokenStream; use proc_macro2::{Span, TokenStream};
use quote::ToTokens; use quote::ToTokens;
use quote::{quote, quote_spanned}; use quote::{quote, quote_spanned};
use syn::ext::IdentExt; use syn::ext::IdentExt;
use syn::spanned::Spanned; use syn::spanned::Spanned;
use syn::Result;
#[derive(Clone, PartialEq, Debug)] #[derive(Clone, PartialEq, Debug)]
pub struct FnArg<'a> { pub struct FnArg<'a> {
@ -24,7 +26,7 @@ pub struct FnArg<'a> {
impl<'a> FnArg<'a> { impl<'a> FnArg<'a> {
/// Transforms a rust fn arg parsed with syn into a method::FnArg /// Transforms a rust fn arg parsed with syn into a method::FnArg
pub fn parse(arg: &'a mut syn::FnArg) -> syn::Result<Self> { pub fn parse(arg: &'a mut syn::FnArg) -> Result<Self> {
match arg { match arg {
syn::FnArg::Receiver(recv) => { syn::FnArg::Receiver(recv) => {
bail_spanned!(recv.span() => "unexpected receiver") bail_spanned!(recv.span() => "unexpected receiver")
@ -86,13 +88,44 @@ pub enum FnType {
FnNew, FnNew,
FnClass, FnClass,
FnStatic, FnStatic,
FnModule,
ClassAttribute, ClassAttribute,
} }
impl FnType {
pub fn self_conversion(&self, cls: Option<&syn::Type>) -> TokenStream {
match self {
FnType::Getter(st) | FnType::Setter(st) | FnType::Fn(st) | FnType::FnCall(st) => {
st.receiver(cls.expect("no class given for Fn with a \"self\" receiver"))
}
FnType::FnNew | FnType::FnStatic | FnType::ClassAttribute => {
quote!()
}
FnType::FnClass => {
quote! {
let _slf = pyo3::types::PyType::from_type_ptr(_py, _slf as *mut pyo3::ffi::PyTypeObject);
}
}
FnType::FnModule => {
quote! {
let _slf = _py.from_borrowed_ptr::<pyo3::types::PyModule>(_slf);
}
}
}
}
pub fn self_arg(&self) -> TokenStream {
match self {
FnType::FnNew | FnType::FnStatic | FnType::ClassAttribute => quote!(),
_ => quote!(_slf,),
}
}
}
#[derive(Clone, Debug)] #[derive(Clone, Debug)]
pub enum SelfType { pub enum SelfType {
Receiver { mutable: bool }, Receiver { mutable: bool },
TryFromPyCell(proc_macro2::Span), TryFromPyCell(Span),
} }
impl SelfType { impl SelfType {
@ -123,6 +156,31 @@ impl SelfType {
} }
} }
/// Determines which CPython calling convention a given FnSpec uses.
#[derive(Clone, Debug)]
pub enum CallingConvention {
Noargs, // METH_NOARGS
Varargs, // METH_VARARGS | METH_KEYWORDS
Fastcall, // METH_FASTCALL | METH_KEYWORDS (Py3.7+ and !abi3)
TpNew, // special convention for tp_new
}
impl CallingConvention {
/// Determine default calling convention from an argument signature.
///
/// Different other slots (tp_call, tp_new) can have other requirements
/// and are set manually (see `parse_fn_type` below).
pub fn from_args(args: &[FnArg<'_>]) -> Self {
if args.is_empty() {
Self::Noargs
} else if cfg!(all(Py_3_7, not(Py_LIMITED_API))) {
Self::Fastcall
} else {
Self::Varargs
}
}
}
pub struct FnSpec<'a> { pub struct FnSpec<'a> {
pub tp: FnType, pub tp: FnType,
// Rust function name // Rust function name
@ -135,6 +193,7 @@ pub struct FnSpec<'a> {
pub output: syn::Type, pub output: syn::Type,
pub doc: syn::LitStr, pub doc: syn::LitStr,
pub deprecations: Deprecations, pub deprecations: Deprecations,
pub convention: CallingConvention,
} }
pub fn get_return_info(output: &syn::ReturnType) -> syn::Type { pub fn get_return_info(output: &syn::ReturnType) -> syn::Type {
@ -144,7 +203,7 @@ pub fn get_return_info(output: &syn::ReturnType) -> syn::Type {
} }
} }
pub fn parse_method_receiver(arg: &syn::FnArg) -> syn::Result<SelfType> { pub fn parse_method_receiver(arg: &syn::FnArg) -> Result<SelfType> {
match arg { match arg {
syn::FnArg::Receiver(recv) => Ok(SelfType::Receiver { syn::FnArg::Receiver(recv) => Ok(SelfType::Receiver {
mutable: recv.mutability.is_some(), mutable: recv.mutability.is_some(),
@ -174,19 +233,12 @@ impl<'a> FnSpec<'a> {
(accept_args, accept_kwargs) (accept_args, accept_kwargs)
} }
/// Return true if the function can use METH_FASTCALL.
///
/// This is true on Py3.7+, except with the stable ABI (abi3).
pub fn can_use_fastcall(&self) -> bool {
cfg!(all(Py_3_7, not(Py_LIMITED_API)))
}
/// Parser function signature and function attributes /// Parser function signature and function attributes
pub fn parse( pub fn parse(
sig: &'a mut syn::Signature, sig: &'a mut syn::Signature,
meth_attrs: &mut Vec<syn::Attribute>, meth_attrs: &mut Vec<syn::Attribute>,
options: PyFunctionOptions, options: PyFunctionOptions,
) -> syn::Result<FnSpec<'a>> { ) -> Result<FnSpec<'a>> {
let MethodAttributes { let MethodAttributes {
ty: fn_type_attr, ty: fn_type_attr,
args: fn_attrs, args: fn_attrs,
@ -198,18 +250,19 @@ impl<'a> FnSpec<'a> {
if let Some(name) = &python_name { if let Some(name) = &python_name {
bail_spanned!(name.span() => "`name` not allowed with `#[new]`"); bail_spanned!(name.span() => "`name` not allowed with `#[new]`");
} }
python_name = Some(syn::Ident::new("__new__", proc_macro2::Span::call_site())) python_name = Some(syn::Ident::new("__new__", Span::call_site()))
} }
Some(MethodTypeAttribute::Call) => { Some(MethodTypeAttribute::Call) => {
if let Some(name) = &python_name { if let Some(name) = &python_name {
bail_spanned!(name.span() => "`name` not allowed with `#[call]`"); bail_spanned!(name.span() => "`name` not allowed with `#[call]`");
} }
python_name = Some(syn::Ident::new("__call__", proc_macro2::Span::call_site())) python_name = Some(syn::Ident::new("__call__", Span::call_site()))
} }
_ => {} _ => {}
} }
let (fn_type, skip_first_arg) = Self::parse_fn_type(sig, fn_type_attr, &mut python_name)?; let (fn_type, skip_first_arg, fixed_convention) =
Self::parse_fn_type(sig, fn_type_attr, &mut python_name)?;
Self::ensure_text_signature_on_valid_method(&fn_type, options.text_signature.as_ref())?; Self::ensure_text_signature_on_valid_method(&fn_type, options.text_signature.as_ref())?;
let name = &sig.ident; let name = &sig.ident;
@ -224,22 +277,26 @@ impl<'a> FnSpec<'a> {
.map(|attr| (&python_name, attr)), .map(|attr| (&python_name, attr)),
)?; )?;
let arguments = if skip_first_arg { let arguments: Vec<_> = if skip_first_arg {
sig.inputs sig.inputs
.iter_mut() .iter_mut()
.skip(1) .skip(1)
.map(FnArg::parse) .map(FnArg::parse)
.collect::<syn::Result<_>>()? .collect::<Result<_>>()?
} else { } else {
sig.inputs sig.inputs
.iter_mut() .iter_mut()
.map(FnArg::parse) .map(FnArg::parse)
.collect::<syn::Result<_>>()? .collect::<Result<_>>()?
}; };
let convention =
fixed_convention.unwrap_or_else(|| CallingConvention::from_args(&arguments));
Ok(FnSpec { Ok(FnSpec {
tp: fn_type, tp: fn_type,
name, name,
convention,
python_name, python_name,
attrs: fn_attrs, attrs: fn_attrs,
args: arguments, args: arguments,
@ -280,7 +337,7 @@ impl<'a> FnSpec<'a> {
sig: &syn::Signature, sig: &syn::Signature,
fn_type_attr: Option<MethodTypeAttribute>, fn_type_attr: Option<MethodTypeAttribute>,
python_name: &mut Option<syn::Ident>, python_name: &mut Option<syn::Ident>,
) -> syn::Result<(FnType, bool)> { ) -> Result<(FnType, bool, Option<CallingConvention>)> {
let name = &sig.ident; let name = &sig.ident;
let parse_receiver = |msg: &'static str| { let parse_receiver = |msg: &'static str| {
let first_arg = sig let first_arg = sig
@ -301,20 +358,23 @@ impl<'a> FnSpec<'a> {
} }
}; };
let (fn_type, skip_first_arg) = match fn_type_attr { let (fn_type, skip_first_arg, fixed_convention) = match fn_type_attr {
Some(MethodTypeAttribute::StaticMethod) => (FnType::FnStatic, false), Some(MethodTypeAttribute::StaticMethod) => (FnType::FnStatic, false, None),
Some(MethodTypeAttribute::ClassAttribute) => { Some(MethodTypeAttribute::ClassAttribute) => {
ensure_spanned!( ensure_spanned!(
sig.inputs.is_empty(), sig.inputs.is_empty(),
sig.inputs.span() => "class attribute methods cannot take arguments" sig.inputs.span() => "class attribute methods cannot take arguments"
); );
(FnType::ClassAttribute, false) (FnType::ClassAttribute, false, None)
} }
Some(MethodTypeAttribute::New) => (FnType::FnNew, false), Some(MethodTypeAttribute::New) => {
Some(MethodTypeAttribute::ClassMethod) => (FnType::FnClass, true), (FnType::FnNew, false, Some(CallingConvention::TpNew))
}
Some(MethodTypeAttribute::ClassMethod) => (FnType::FnClass, true, None),
Some(MethodTypeAttribute::Call) => ( Some(MethodTypeAttribute::Call) => (
FnType::FnCall(parse_receiver("expected receiver for #[call]")?), FnType::FnCall(parse_receiver("expected receiver for #[call]")?),
true, true,
Some(CallingConvention::Varargs),
), ),
Some(MethodTypeAttribute::Getter) => { Some(MethodTypeAttribute::Getter) => {
// Strip off "get_" prefix if needed // Strip off "get_" prefix if needed
@ -325,6 +385,7 @@ impl<'a> FnSpec<'a> {
( (
FnType::Getter(parse_receiver("expected receiver for #[getter]")?), FnType::Getter(parse_receiver("expected receiver for #[getter]")?),
true, true,
None,
) )
} }
Some(MethodTypeAttribute::Setter) => { Some(MethodTypeAttribute::Setter) => {
@ -336,6 +397,7 @@ impl<'a> FnSpec<'a> {
( (
FnType::Setter(parse_receiver("expected receiver for #[setter]")?), FnType::Setter(parse_receiver("expected receiver for #[setter]")?),
true, true,
None,
) )
} }
None => ( None => (
@ -343,9 +405,10 @@ impl<'a> FnSpec<'a> {
"static method needs #[staticmethod] attribute", "static method needs #[staticmethod] attribute",
)?), )?),
true, true,
None,
), ),
}; };
Ok((fn_type, skip_first_arg)) Ok((fn_type, skip_first_arg, fixed_convention))
} }
pub fn is_args(&self, name: &syn::Ident) -> bool { pub fn is_args(&self, name: &syn::Ident) -> bool {
@ -393,6 +456,115 @@ impl<'a> FnSpec<'a> {
} }
false false
} }
/// Return a C wrapper function for this signature.
pub fn get_wrapper_function(
&self,
ident: &proc_macro2::Ident,
cls: Option<&syn::Type>,
) -> Result<TokenStream> {
let deprecations = &self.deprecations;
let self_conversion = self.tp.self_conversion(cls);
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 func_name = &self.name;
let rust_name = if let Some(cls) = cls {
quote!(#cls::#func_name)
} else {
quote!(#func_name)
};
let rust_call =
quote! { pyo3::callback::convert(#py, #rust_name(#self_arg #(#arg_names),*)) };
Ok(match self.convention {
CallingConvention::Noargs => {
quote! {
unsafe extern "C" fn #ident (
_slf: *mut pyo3::ffi::PyObject,
_args: *mut pyo3::ffi::PyObject,
) -> *mut pyo3::ffi::PyObject
{
#deprecations
pyo3::callback::handle_panic(|#py| {
#self_conversion
#rust_call
})
}
}
}
CallingConvention::Fastcall => {
let arg_convert_and_rust_call = impl_arg_params(self, cls, rust_call, &py, true)?;
quote! {
unsafe extern "C" fn #ident (
_slf: *mut pyo3::ffi::PyObject,
_args: *const *mut pyo3::ffi::PyObject,
_nargs: pyo3::ffi::Py_ssize_t,
_kwnames: *mut pyo3::ffi::PyObject) -> *mut pyo3::ffi::PyObject
{
#deprecations
pyo3::callback::handle_panic(|#py| {
#self_conversion
let _kwnames: Option<&pyo3::types::PyTuple> = #py.from_borrowed_ptr_or_opt(_kwnames);
// Safety: &PyAny has the same memory layout as `*mut ffi::PyObject`
let _args = _args as *const &pyo3::PyAny;
let _kwargs = if let Some(kwnames) = _kwnames {
std::slice::from_raw_parts(_args.offset(_nargs), kwnames.len())
} else {
&[]
};
let _args = std::slice::from_raw_parts(_args, _nargs as usize);
#arg_convert_and_rust_call
})
}
}
}
CallingConvention::Varargs => {
let arg_convert_and_rust_call = impl_arg_params(self, cls, rust_call, &py, false)?;
quote! {
unsafe extern "C" fn #ident (
_slf: *mut pyo3::ffi::PyObject,
_args: *mut pyo3::ffi::PyObject,
_kwargs: *mut pyo3::ffi::PyObject) -> *mut pyo3::ffi::PyObject
{
#deprecations
pyo3::callback::handle_panic(|#py| {
#self_conversion
let _args = #py.from_borrowed_ptr::<pyo3::types::PyTuple>(_args);
let _kwargs: Option<&pyo3::types::PyDict> = #py.from_borrowed_ptr_or_opt(_kwargs);
#arg_convert_and_rust_call
})
}
}
}
CallingConvention::TpNew => {
let rust_call = quote! { #rust_name(#(#arg_names),*) };
let arg_convert_and_rust_call = impl_arg_params(self, cls, rust_call, &py, false)?;
quote! {
unsafe extern "C" fn __wrap(
subtype: *mut pyo3::ffi::PyTypeObject,
_args: *mut pyo3::ffi::PyObject,
_kwargs: *mut pyo3::ffi::PyObject) -> *mut pyo3::ffi::PyObject
{
#deprecations
use pyo3::callback::IntoPyCallbackOutput;
pyo3::callback::handle_panic(|#py| {
let _args = #py.from_borrowed_ptr::<pyo3::types::PyTuple>(_args);
let _kwargs: Option<&pyo3::types::PyDict> = #py.from_borrowed_ptr_or_opt(_kwargs);
let result = #arg_convert_and_rust_call;
let initializer: pyo3::PyClassInitializer::<#cls> = result.convert(#py)?;
let cell = initializer.create_cell_from_subtype(#py, subtype)?;
Ok(cell as *mut pyo3::ffi::PyObject)
})
}
}
}
})
}
} }
#[derive(Clone, PartialEq, Debug)] #[derive(Clone, PartialEq, Debug)]
@ -405,7 +577,7 @@ struct MethodAttributes {
fn parse_method_attributes( fn parse_method_attributes(
attrs: &mut Vec<syn::Attribute>, attrs: &mut Vec<syn::Attribute>,
mut python_name: Option<syn::Ident>, mut python_name: Option<syn::Ident>,
) -> syn::Result<MethodAttributes> { ) -> Result<MethodAttributes> {
let mut new_attrs = Vec::new(); let mut new_attrs = Vec::new();
let mut args = Vec::new(); let mut args = Vec::new();
let mut ty: Option<MethodTypeAttribute> = None; let mut ty: Option<MethodTypeAttribute> = None;

View File

@ -0,0 +1,257 @@
// Copyright (c) 2017-present PyO3 Project and Contributors
use crate::{
attributes::FromPyWithAttribute,
method::{FnArg, FnSpec},
};
use proc_macro2::{Span, TokenStream};
use quote::{quote, quote_spanned};
use syn::ext::IdentExt;
use syn::spanned::Spanned;
use syn::Result;
pub fn impl_arg_params(
spec: &FnSpec<'_>,
self_: Option<&syn::Type>,
body: TokenStream,
py: &syn::Ident,
fastcall: bool,
) -> Result<TokenStream> {
if spec.args.is_empty() {
return Ok(body);
}
let mut positional_parameter_names = Vec::new();
let mut required_positional_parameters = 0usize;
let mut keyword_only_parameters = Vec::new();
for arg in spec.args.iter() {
if arg.py || spec.is_args(&arg.name) || spec.is_kwargs(&arg.name) {
continue;
}
let name = arg.name.unraw().to_string();
let kwonly = spec.is_kw_only(&arg.name);
let required = !(arg.optional.is_some() || spec.default_value(&arg.name).is_some());
if kwonly {
keyword_only_parameters.push(quote! {
pyo3::derive_utils::KeywordOnlyParameterDescription {
name: #name,
required: #required,
}
});
} else {
if required {
required_positional_parameters += 1;
}
positional_parameter_names.push(name);
}
}
let num_params = positional_parameter_names.len() + keyword_only_parameters.len();
let args_array = syn::Ident::new("output", Span::call_site());
let mut param_conversion = Vec::new();
let mut option_pos = 0;
for (idx, arg) in spec.args.iter().enumerate() {
param_conversion.push(impl_arg_param(
&arg,
&spec,
idx,
self_,
&mut option_pos,
py,
&args_array,
)?);
}
let (accept_args, accept_kwargs) = spec.accept_args_kwargs();
let cls_name = if let Some(cls) = self_ {
quote! { Some(<#cls as pyo3::type_object::PyTypeInfo>::NAME) }
} else {
quote! { None }
};
let python_name = &spec.python_name;
let (args_to_extract, kwargs_to_extract) = if fastcall {
// _args is a &[&PyAny], _kwnames is a Option<&PyTuple> containing the
// keyword names of the keyword args in _kwargs
(
// need copied() for &&PyAny -> &PyAny
quote! { _args.iter().copied() },
quote! { _kwnames.map(|kwnames| {
kwnames.as_slice().iter().copied().zip(_kwargs.iter().copied())
}) },
)
} else {
// _args is a &PyTuple, _kwargs is an Option<&PyDict>
(
quote! { _args.iter() },
quote! { _kwargs.map(|dict| dict.iter()) },
)
};
// create array of arguments, and then parse
Ok(quote! {{
const DESCRIPTION: pyo3::derive_utils::FunctionDescription = pyo3::derive_utils::FunctionDescription {
cls_name: #cls_name,
func_name: stringify!(#python_name),
positional_parameter_names: &[#(#positional_parameter_names),*],
// TODO: https://github.com/PyO3/pyo3/issues/1439 - support specifying these
positional_only_parameters: 0,
required_positional_parameters: #required_positional_parameters,
keyword_only_parameters: &[#(#keyword_only_parameters),*],
accept_varargs: #accept_args,
accept_varkeywords: #accept_kwargs,
};
let mut #args_array = [None; #num_params];
let (_args, _kwargs) = DESCRIPTION.extract_arguments(
#py,
#args_to_extract,
#kwargs_to_extract,
&mut #args_array
)?;
#(#param_conversion)*
#body
}})
}
/// Re option_pos: The option slice doesn't contain the py: Python argument, so the argument
/// index and the index in option diverge when using py: Python
fn impl_arg_param(
arg: &FnArg<'_>,
spec: &FnSpec<'_>,
idx: usize,
self_: Option<&syn::Type>,
option_pos: &mut usize,
py: &syn::Ident,
args_array: &syn::Ident,
) -> Result<TokenStream> {
// Use this macro inside this function, to ensure that all code generated here is associated
// with the function argument
macro_rules! quote_arg_span {
($($tokens:tt)*) => { quote_spanned!(arg.ty.span() => $($tokens)*) }
}
let arg_name = syn::Ident::new(&format!("arg{}", idx), Span::call_site());
if arg.py {
return Ok(quote_arg_span! { let #arg_name = #py; });
}
let ty = arg.ty;
let name = arg.name;
let transform_error = quote_arg_span! {
|e| pyo3::derive_utils::argument_extraction_error(#py, stringify!(#name), e)
};
if spec.is_args(&name) {
ensure_spanned!(
arg.optional.is_none(),
arg.name.span() => "args cannot be optional"
);
return Ok(quote_arg_span! {
let #arg_name = _args.unwrap().extract().map_err(#transform_error)?;
});
} else if spec.is_kwargs(&name) {
ensure_spanned!(
arg.optional.is_some(),
arg.name.span() => "kwargs must be Option<_>"
);
return Ok(quote_arg_span! {
let #arg_name = _kwargs.map(|kwargs| kwargs.extract())
.transpose()
.map_err(#transform_error)?;
});
}
let arg_value = quote_arg_span!(#args_array[#option_pos]);
*option_pos += 1;
let extract = if let Some(FromPyWithAttribute(expr_path)) = &arg.attrs.from_py_with {
quote_arg_span! { #expr_path(_obj).map_err(#transform_error) }
} else {
quote_arg_span! { _obj.extract().map_err(#transform_error) }
};
let arg_value_or_default = match (spec.default_value(name), arg.optional.is_some()) {
(Some(default), true) if default.to_string() != "None" => {
quote_arg_span! { #arg_value.map_or_else(|| Ok(Some(#default)), |_obj| #extract)? }
}
(Some(default), _) => {
quote_arg_span! { #arg_value.map_or_else(|| Ok(#default), |_obj| #extract)? }
}
(None, true) => quote_arg_span! { #arg_value.map_or(Ok(None), |_obj| #extract)? },
(None, false) => {
quote_arg_span! {
{
let _obj = #arg_value.expect("Failed to extract required method argument");
#extract?
}
}
}
};
return if let syn::Type::Reference(tref) = arg.optional.as_ref().unwrap_or(&ty) {
let (tref, mut_) = preprocess_tref(tref, self_);
let (target_ty, borrow_tmp) = if arg.optional.is_some() {
// Get Option<&T> from Option<PyRef<T>>
(
quote_arg_span! { 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;
let #arg_name = #borrow_tmp;
})
} else {
Ok(quote_arg_span! {
let #arg_name = #arg_value_or_default;
})
};
/// Replace `Self`, remove lifetime and get mutability from the type
fn preprocess_tref(
tref: &syn::TypeReference,
self_: Option<&syn::Type>,
) -> (syn::TypeReference, Option<syn::token::Mut>) {
let mut tref = tref.to_owned();
if let Some(syn::Type::Path(tpath)) = self_ {
replace_self(&mut tref, &tpath.path);
}
tref.lifetime = None;
let mut_ = tref.mutability;
(tref, mut_)
}
/// Replace `Self` with the exact type name since it is used out of the impl block
fn replace_self(tref: &mut syn::TypeReference, self_path: &syn::Path) {
match &mut *tref.elem {
syn::Type::Reference(tref_inner) => replace_self(tref_inner, self_path),
syn::Type::Path(tpath) => {
if let Some(ident) = tpath.path.get_ident() {
if ident == "Self" {
tpath.path = self_path.to_owned();
}
}
}
_ => {}
}
}
}

View File

@ -7,8 +7,8 @@ use crate::{
TextSignatureAttribute, TextSignatureAttribute,
}, },
deprecations::Deprecations, deprecations::Deprecations,
method::{self, FnArg, FnSpec}, method::{self, CallingConvention, FnArg},
pymethod::{check_generic, get_arg_names, impl_arg_params}, pymethod::check_generic,
utils::{self, ensure_not_async_fn}, utils::{self, ensure_not_async_fn},
}; };
use proc_macro2::{Span, TokenStream}; use proc_macro2::{Span, TokenStream};
@ -411,8 +411,13 @@ pub fn impl_wrap_pyfunction(
let function_wrapper_ident = function_wrapper_ident(&func.sig.ident); let function_wrapper_ident = function_wrapper_ident(&func.sig.ident);
let spec = method::FnSpec { let spec = method::FnSpec {
tp: method::FnType::FnStatic, tp: if options.pass_module.is_some() {
name: &function_wrapper_ident, method::FnType::FnModule
} else {
method::FnType::FnStatic
},
name: &func.sig.ident,
convention: CallingConvention::from_args(&arguments),
python_name, python_name,
attrs: signature.arguments, attrs: signature.arguments,
args: arguments, args: arguments,
@ -426,19 +431,17 @@ pub fn impl_wrap_pyfunction(
let name = &func.sig.ident; let name = &func.sig.ident;
let wrapper_ident = format_ident!("__pyo3_raw_{}", name); let wrapper_ident = format_ident!("__pyo3_raw_{}", name);
let wrapper = function_c_wrapper(name, &wrapper_ident, &spec, options.pass_module.is_some())?; let wrapper = spec.get_wrapper_function(&wrapper_ident, None)?;
let (methoddef_meth, cfunc_variant) = if spec.args.is_empty() { let (methoddef_meth, cfunc_variant) = match spec.convention {
(quote!(noargs), quote!(PyCFunction)) CallingConvention::Noargs => (quote!(noargs), quote!(PyCFunction)),
} else if spec.can_use_fastcall() { CallingConvention::Fastcall => (
(
quote!(fastcall_cfunction_with_keywords), quote!(fastcall_cfunction_with_keywords),
quote!(PyCFunctionFastWithKeywords), quote!(PyCFunctionFastWithKeywords),
) ),
} else { _ => (
(
quote!(cfunction_with_keywords), quote!(cfunction_with_keywords),
quote!(PyCFunctionWithKeywords), quote!(PyCFunctionWithKeywords),
) ),
}; };
let wrapped_pyfunction = quote! { let wrapped_pyfunction = quote! {
@ -459,94 +462,6 @@ pub fn impl_wrap_pyfunction(
Ok((function_wrapper_ident, wrapped_pyfunction)) Ok((function_wrapper_ident, wrapped_pyfunction))
} }
/// Generate static function wrapper (PyCFunction, PyCFunctionWithKeywords)
fn function_c_wrapper(
name: &Ident,
wrapper_ident: &Ident,
spec: &FnSpec<'_>,
pass_module: bool,
) -> Result<TokenStream> {
let names: Vec<Ident> = get_arg_names(&spec);
let (cb, slf_module) = if pass_module {
(
quote! {
pyo3::callback::convert(_py, #name(_slf, #(#names),*))
},
Some(quote! {
let _slf = _py.from_borrowed_ptr::<pyo3::types::PyModule>(_slf);
}),
)
} else {
(
quote! {
pyo3::callback::convert(_py, #name(#(#names),*))
},
None,
)
};
let py = syn::Ident::new("_py", Span::call_site());
let deprecations = &spec.deprecations;
if spec.args.is_empty() {
Ok(quote! {
unsafe extern "C" fn #wrapper_ident(
_slf: *mut pyo3::ffi::PyObject,
_unused: *mut pyo3::ffi::PyObject) -> *mut pyo3::ffi::PyObject
{
#deprecations
pyo3::callback::handle_panic(|#py| {
#slf_module
#cb
})
}
})
} else if spec.can_use_fastcall() {
let body = impl_arg_params(spec, None, cb, &py, true)?;
Ok(quote! {
unsafe extern "C" fn #wrapper_ident(
_slf: *mut pyo3::ffi::PyObject,
_args: *const *mut pyo3::ffi::PyObject,
_nargs: pyo3::ffi::Py_ssize_t,
_kwnames: *mut pyo3::ffi::PyObject) -> *mut pyo3::ffi::PyObject
{
pyo3::callback::handle_panic(|#py| {
#slf_module
// _nargs is the number of positional arguments in the _args array,
// the number of KW args is given by the length of _kwnames
let _kwnames: Option<&pyo3::types::PyTuple> = #py.from_borrowed_ptr_or_opt(_kwnames);
// Safety: &PyAny has the same memory layout as `*mut ffi::PyObject`
let _args = _args as *const &pyo3::PyAny;
let _kwargs = if let Some(kwnames) = _kwnames {
std::slice::from_raw_parts(_args.offset(_nargs), kwnames.len())
} else {
&[]
};
let _args = std::slice::from_raw_parts(_args, _nargs as usize);
#body
})
}
})
} else {
let body = impl_arg_params(spec, None, cb, &py, false)?;
Ok(quote! {
unsafe extern "C" fn #wrapper_ident(
_slf: *mut pyo3::ffi::PyObject,
_args: *mut pyo3::ffi::PyObject,
_kwargs: *mut pyo3::ffi::PyObject) -> *mut pyo3::ffi::PyObject
{
#deprecations
pyo3::callback::handle_panic(|#py| {
#slf_module
let _args = #py.from_borrowed_ptr::<pyo3::types::PyTuple>(_args);
let _kwargs: Option<&pyo3::types::PyDict> = #py.from_borrowed_ptr_or_opt(_kwargs);
#body
})
}
})
}
}
fn type_is_pymodule(ty: &syn::Type) -> bool { fn type_is_pymodule(ty: &syn::Type) -> bool {
if let syn::Type::Reference(tyref) = ty { if let syn::Type::Reference(tyref) = ty {
if let syn::Type::Path(typath) = tyref.elem.as_ref() { if let syn::Type::Path(typath) = tyref.elem.as_ref() {

View File

@ -1,16 +1,18 @@
// Copyright (c) 2017-present PyO3 Project and Contributors
use std::borrow::Cow; use std::borrow::Cow;
use crate::attributes::NameAttribute; use crate::attributes::NameAttribute;
use crate::konst::ConstSpec;
use crate::method::CallingConvention;
use crate::utils::ensure_not_async_fn; use crate::utils::ensure_not_async_fn;
// Copyright (c) 2017-present PyO3 Project and Contributors
use crate::{attributes::FromPyWithAttribute, konst::ConstSpec};
use crate::{deprecations::Deprecations, utils}; use crate::{deprecations::Deprecations, utils};
use crate::{ use crate::{
method::{FnArg, FnSpec, FnType, SelfType}, method::{FnArg, FnSpec, FnType, SelfType},
pyfunction::PyFunctionOptions, pyfunction::PyFunctionOptions,
}; };
use proc_macro2::{Span, TokenStream}; use proc_macro2::{Span, TokenStream};
use quote::{quote, quote_spanned}; use quote::quote;
use syn::{ext::IdentExt, spanned::Spanned, Result}; use syn::{ext::IdentExt, spanned::Spanned, Result};
pub enum GeneratedPyMethod { pub enum GeneratedPyMethod {
@ -31,15 +33,21 @@ pub fn gen_py_method(
let spec = FnSpec::parse(sig, &mut *meth_attrs, options)?; let spec = FnSpec::parse(sig, &mut *meth_attrs, options)?;
Ok(match &spec.tp { Ok(match &spec.tp {
FnType::Fn(self_ty) => { // ordinary functions (with some specialties)
GeneratedPyMethod::Method(impl_py_method_def(cls, &spec, self_ty, None)?) FnType::Fn(_) => GeneratedPyMethod::Method(impl_py_method_def(cls, &spec, None)?),
} FnType::FnClass => GeneratedPyMethod::Method(impl_py_method_def(
cls,
&spec,
Some(quote!(pyo3::ffi::METH_CLASS)),
)?),
FnType::FnStatic => GeneratedPyMethod::Method(impl_py_method_def(
cls,
&spec,
Some(quote!(pyo3::ffi::METH_STATIC)),
)?),
// special prototypes
FnType::FnNew => GeneratedPyMethod::New(impl_py_method_def_new(cls, &spec)?), FnType::FnNew => GeneratedPyMethod::New(impl_py_method_def_new(cls, &spec)?),
FnType::FnCall(self_ty) => { FnType::FnCall(_) => GeneratedPyMethod::Call(impl_py_method_def_call(cls, &spec)?),
GeneratedPyMethod::Call(impl_py_method_def_call(cls, &spec, self_ty)?)
}
FnType::FnClass => GeneratedPyMethod::Method(impl_py_method_def_class(cls, &spec)?),
FnType::FnStatic => GeneratedPyMethod::Method(impl_py_method_def_static(cls, &spec)?),
FnType::ClassAttribute => { FnType::ClassAttribute => {
GeneratedPyMethod::Method(impl_py_method_class_attribute(cls, &spec)) GeneratedPyMethod::Method(impl_py_method_class_attribute(cls, &spec))
} }
@ -57,10 +65,13 @@ pub fn gen_py_method(
spec: &spec, spec: &spec,
}, },
)?), )?),
FnType::FnModule => {
unreachable!("methods cannot be FnModule")
}
}) })
} }
pub(crate) fn check_generic(sig: &syn::Signature) -> syn::Result<()> { pub fn check_generic(sig: &syn::Signature) -> syn::Result<()> {
let err_msg = |typ| format!("Python functions cannot have generic {} parameters", typ); let err_msg = |typ| format!("Python functions cannot have generic {} parameters", typ);
for param in &sig.generics.params { for param in &sig.generics.params {
match param { match param {
@ -92,183 +103,10 @@ pub fn gen_py_const(cls: &syn::Type, spec: &ConstSpec) -> TokenStream {
impl_py_const_class_attribute(&spec, &wrapper) impl_py_const_class_attribute(&spec, &wrapper)
} }
/// Generate function wrapper for PyCFunctionWithKeywords
pub fn impl_wrap_cfunction_with_keywords(
cls: &syn::Type,
spec: &FnSpec<'_>,
self_ty: &SelfType,
) -> Result<TokenStream> {
let body = impl_call(cls, &spec);
let slf = self_ty.receiver(cls);
let py = syn::Ident::new("_py", Span::call_site());
let body = impl_arg_params(&spec, Some(cls), body, &py, false)?;
let deprecations = &spec.deprecations;
Ok(quote! {{
unsafe extern "C" fn __wrap(
_slf: *mut pyo3::ffi::PyObject,
_args: *mut pyo3::ffi::PyObject,
_kwargs: *mut pyo3::ffi::PyObject) -> *mut pyo3::ffi::PyObject
{
#deprecations
pyo3::callback::handle_panic(|#py| {
#slf
let _args = #py.from_borrowed_ptr::<pyo3::types::PyTuple>(_args);
let _kwargs: Option<&pyo3::types::PyDict> = #py.from_borrowed_ptr_or_opt(_kwargs);
#body
})
}
__wrap
}})
}
/// Generate function wrapper for PyCFunctionFastWithKeywords
pub fn impl_wrap_fastcall_cfunction_with_keywords(
cls: &syn::Type,
spec: &FnSpec<'_>,
self_ty: &SelfType,
) -> Result<TokenStream> {
let body = impl_call(cls, &spec);
let slf = self_ty.receiver(cls);
let py = syn::Ident::new("_py", Span::call_site());
let body = impl_arg_params(&spec, Some(cls), body, &py, true)?;
Ok(quote! {{
unsafe extern "C" fn __wrap(
_slf: *mut pyo3::ffi::PyObject,
_args: *const *mut pyo3::ffi::PyObject,
_nargs: pyo3::ffi::Py_ssize_t,
_kwnames: *mut pyo3::ffi::PyObject) -> *mut pyo3::ffi::PyObject
{
pyo3::callback::handle_panic(|#py| {
#slf
let _kwnames: Option<&pyo3::types::PyTuple> = #py.from_borrowed_ptr_or_opt(_kwnames);
// Safety: &PyAny has the same memory layout as `*mut ffi::PyObject`
let _args = _args as *const &pyo3::PyAny;
let _kwargs = if let Some(kwnames) = _kwnames {
std::slice::from_raw_parts(_args.offset(_nargs), kwnames.len())
} else {
&[]
};
let _args = std::slice::from_raw_parts(_args, _nargs as usize);
#body
})
}
__wrap
}})
}
/// Generate function wrapper PyCFunction
pub fn impl_wrap_noargs(cls: &syn::Type, spec: &FnSpec<'_>, self_ty: &SelfType) -> TokenStream {
let body = impl_call(cls, &spec);
let slf = self_ty.receiver(cls);
let deprecations = &spec.deprecations;
assert!(spec.args.is_empty());
quote! {{
unsafe extern "C" fn __wrap(
_slf: *mut pyo3::ffi::PyObject,
_args: *mut pyo3::ffi::PyObject,
) -> *mut pyo3::ffi::PyObject
{
#deprecations
pyo3::callback::handle_panic(|_py| {
#slf
#body
})
}
__wrap
}}
}
/// Generate class method wrapper (PyCFunction, PyCFunctionWithKeywords)
pub fn impl_wrap_new(cls: &syn::Type, spec: &FnSpec<'_>) -> Result<TokenStream> {
let name = &spec.name;
let names: Vec<syn::Ident> = get_arg_names(&spec);
let cb = quote! { #cls::#name(#(#names),*) };
let py = syn::Ident::new("_py", Span::call_site());
let body = impl_arg_params(spec, Some(cls), cb, &py, false)?;
let deprecations = &spec.deprecations;
Ok(quote! {{
#[allow(unused_mut)]
unsafe extern "C" fn __wrap(
subtype: *mut pyo3::ffi::PyTypeObject,
_args: *mut pyo3::ffi::PyObject,
_kwargs: *mut pyo3::ffi::PyObject) -> *mut pyo3::ffi::PyObject
{
#deprecations
use pyo3::callback::IntoPyCallbackOutput;
pyo3::callback::handle_panic(|#py| {
let _args = #py.from_borrowed_ptr::<pyo3::types::PyTuple>(_args);
let _kwargs: Option<&pyo3::types::PyDict> = #py.from_borrowed_ptr_or_opt(_kwargs);
let initializer: pyo3::PyClassInitializer::<#cls> = #body.convert(#py)?;
let cell = initializer.create_cell_from_subtype(#py, subtype)?;
Ok(cell as *mut pyo3::ffi::PyObject)
})
}
__wrap
}})
}
/// Generate class method wrapper (PyCFunction, PyCFunctionWithKeywords)
pub fn impl_wrap_class(cls: &syn::Type, spec: &FnSpec<'_>) -> Result<TokenStream> {
let name = &spec.name;
let names: Vec<syn::Ident> = get_arg_names(&spec);
let cb = quote! { pyo3::callback::convert(_py, #cls::#name(&_cls, #(#names),*)) };
let py = syn::Ident::new("_py", Span::call_site());
let body = impl_arg_params(spec, Some(cls), cb, &py, false)?;
let deprecations = &spec.deprecations;
Ok(quote! {{
#[allow(unused_mut)]
unsafe extern "C" fn __wrap(
_cls: *mut pyo3::ffi::PyObject,
_args: *mut pyo3::ffi::PyObject,
_kwargs: *mut pyo3::ffi::PyObject) -> *mut pyo3::ffi::PyObject
{
#deprecations
pyo3::callback::handle_panic(|#py| {
let _cls = pyo3::types::PyType::from_type_ptr(#py, _cls as *mut pyo3::ffi::PyTypeObject);
let _args = #py.from_borrowed_ptr::<pyo3::types::PyTuple>(_args);
let _kwargs: Option<&pyo3::types::PyDict> = #py.from_borrowed_ptr_or_opt(_kwargs);
#body
})
}
__wrap
}})
}
/// Generate static method wrapper (PyCFunction, PyCFunctionWithKeywords)
pub fn impl_wrap_static(cls: &syn::Type, spec: &FnSpec<'_>) -> Result<TokenStream> {
let name = &spec.name;
let names: Vec<syn::Ident> = get_arg_names(&spec);
let cb = quote! { pyo3::callback::convert(_py, #cls::#name(#(#names),*)) };
let py = syn::Ident::new("_py", Span::call_site());
let body = impl_arg_params(spec, Some(cls), cb, &py, false)?;
let deprecations = &spec.deprecations;
Ok(quote! {{
#[allow(unused_mut)]
unsafe extern "C" fn __wrap(
_slf: *mut pyo3::ffi::PyObject,
_args: *mut pyo3::ffi::PyObject,
_kwargs: *mut pyo3::ffi::PyObject) -> *mut pyo3::ffi::PyObject
{
#deprecations
pyo3::callback::handle_panic(|#py| {
let _args = #py.from_borrowed_ptr::<pyo3::types::PyTuple>(_args);
let _kwargs: Option<&pyo3::types::PyDict> = #py.from_borrowed_ptr_or_opt(_kwargs);
#body
})
}
__wrap
}})
}
/// Generate a wrapper for initialization of a class attribute from a method /// Generate a wrapper for initialization of a class attribute from a method
/// annotated with `#[classattr]`. /// annotated with `#[classattr]`.
/// To be called in `pyo3::pyclass::initialize_type_object`. /// To be called in `pyo3::pyclass::initialize_type_object`.
pub fn impl_wrap_class_attribute(cls: &syn::Type, spec: &FnSpec<'_>) -> TokenStream { fn impl_wrap_class_attribute(cls: &syn::Type, spec: &FnSpec<'_>) -> TokenStream {
let name = &spec.name; let name = &spec.name;
let cb = quote! { #cls::#name() }; let cb = quote! { #cls::#name() };
let deprecations = &spec.deprecations; let deprecations = &spec.deprecations;
@ -388,7 +226,6 @@ pub(crate) fn impl_wrap_setter(
PropertyType::Function { self_type, .. } => self_type.receiver(cls), PropertyType::Function { self_type, .. } => self_type.receiver(cls),
}; };
Ok(quote! {{ Ok(quote! {{
#[allow(unused_mut)]
unsafe extern "C" fn __wrap( unsafe extern "C" fn __wrap(
_slf: *mut pyo3::ffi::PyObject, _slf: *mut pyo3::ffi::PyObject,
_value: *mut pyo3::ffi::PyObject, _: *mut std::os::raw::c_void) -> std::os::raw::c_int _value: *mut pyo3::ffi::PyObject, _: *mut std::os::raw::c_void) -> std::os::raw::c_int
@ -405,301 +242,40 @@ pub(crate) fn impl_wrap_setter(
}}) }})
} }
/// This function abstracts away some copied code and can propably be simplified itself
pub fn get_arg_names(spec: &FnSpec) -> Vec<syn::Ident> {
(0..spec.args.len())
.map(|pos| syn::Ident::new(&format!("arg{}", pos), Span::call_site()))
.collect()
}
fn impl_call(cls: &syn::Type, spec: &FnSpec<'_>) -> TokenStream {
let fname = &spec.name;
let names = get_arg_names(spec);
quote! { pyo3::callback::convert(_py, #cls::#fname(_slf, #(#names),*)) }
}
pub fn impl_arg_params(
spec: &FnSpec<'_>,
self_: Option<&syn::Type>,
body: TokenStream,
py: &syn::Ident,
fastcall: bool,
) -> Result<TokenStream> {
if spec.args.is_empty() {
return Ok(body);
}
let mut positional_parameter_names = Vec::new();
let mut required_positional_parameters = 0usize;
let mut keyword_only_parameters = Vec::new();
for arg in spec.args.iter() {
if arg.py || spec.is_args(&arg.name) || spec.is_kwargs(&arg.name) {
continue;
}
let name = arg.name.unraw().to_string();
let kwonly = spec.is_kw_only(&arg.name);
let required = !(arg.optional.is_some() || spec.default_value(&arg.name).is_some());
if kwonly {
keyword_only_parameters.push(quote! {
pyo3::derive_utils::KeywordOnlyParameterDescription {
name: #name,
required: #required,
}
});
} else {
if required {
required_positional_parameters += 1;
}
positional_parameter_names.push(name);
}
}
let num_params = positional_parameter_names.len() + keyword_only_parameters.len();
let args_array = syn::Ident::new("output", Span::call_site());
let mut param_conversion = Vec::new();
let mut option_pos = 0;
for (idx, arg) in spec.args.iter().enumerate() {
param_conversion.push(impl_arg_param(
&arg,
&spec,
idx,
self_,
&mut option_pos,
py,
&args_array,
)?);
}
let (accept_args, accept_kwargs) = spec.accept_args_kwargs();
let cls_name = if let Some(cls) = self_ {
quote! { Some(<#cls as pyo3::type_object::PyTypeInfo>::NAME) }
} else {
quote! { None }
};
let python_name = &spec.python_name;
let (args_to_extract, kwargs_to_extract) = if fastcall {
// _args is a &[&PyAny], _kwnames is a Option<&PyTuple> containing the
// keyword names of the keyword args in _kwargs
(
// need copied() for &&PyAny -> &PyAny
quote! { _args.iter().copied() },
quote! { _kwnames.map(|kwnames| {
kwnames.as_slice().iter().copied().zip(_kwargs.iter().copied())
}) },
)
} else {
// _args is a &PyTuple, _kwargs is an Option<&PyDict>
(
quote! { _args.iter() },
quote! { _kwargs.map(|dict| dict.iter()) },
)
};
// create array of arguments, and then parse
Ok(quote! {
{
const DESCRIPTION: pyo3::derive_utils::FunctionDescription = pyo3::derive_utils::FunctionDescription {
cls_name: #cls_name,
func_name: stringify!(#python_name),
positional_parameter_names: &[#(#positional_parameter_names),*],
// TODO: https://github.com/PyO3/pyo3/issues/1439 - support specifying these
positional_only_parameters: 0,
required_positional_parameters: #required_positional_parameters,
keyword_only_parameters: &[#(#keyword_only_parameters),*],
accept_varargs: #accept_args,
accept_varkeywords: #accept_kwargs,
};
let mut #args_array = [None; #num_params];
let (_args, _kwargs) = DESCRIPTION.extract_arguments(
#py,
#args_to_extract,
#kwargs_to_extract,
&mut #args_array
)?;
#(#param_conversion)*
#body
}
})
}
/// Re option_pos: The option slice doesn't contain the py: Python argument, so the argument
/// index and the index in option diverge when using py: Python
fn impl_arg_param(
arg: &FnArg<'_>,
spec: &FnSpec<'_>,
idx: usize,
self_: Option<&syn::Type>,
option_pos: &mut usize,
py: &syn::Ident,
args_array: &syn::Ident,
) -> Result<TokenStream> {
// Use this macro inside this function, to ensure that all code generated here is associated
// with the function argument
macro_rules! quote_arg_span {
($($tokens:tt)*) => { quote_spanned!(arg.ty.span() => $($tokens)*) }
}
let arg_name = syn::Ident::new(&format!("arg{}", idx), Span::call_site());
if arg.py {
return Ok(quote_arg_span! { let #arg_name = #py; });
}
let ty = arg.ty;
let name = arg.name;
let transform_error = quote_arg_span! {
|e| pyo3::derive_utils::argument_extraction_error(#py, stringify!(#name), e)
};
if spec.is_args(&name) {
ensure_spanned!(
arg.optional.is_none(),
arg.name.span() => "args cannot be optional"
);
return Ok(quote_arg_span! {
let #arg_name = _args.unwrap().extract().map_err(#transform_error)?;
});
} else if spec.is_kwargs(&name) {
ensure_spanned!(
arg.optional.is_some(),
arg.name.span() => "kwargs must be Option<_>"
);
return Ok(quote_arg_span! {
let #arg_name = _kwargs.map(|kwargs| kwargs.extract())
.transpose()
.map_err(#transform_error)?;
});
}
let arg_value = quote_arg_span!(#args_array[#option_pos]);
*option_pos += 1;
let extract = if let Some(FromPyWithAttribute(expr_path)) = &arg.attrs.from_py_with {
quote_arg_span! { #expr_path(_obj).map_err(#transform_error) }
} else {
quote_arg_span! { _obj.extract().map_err(#transform_error) }
};
let arg_value_or_default = match (spec.default_value(name), arg.optional.is_some()) {
(Some(default), true) if default.to_string() != "None" => {
quote_arg_span! { #arg_value.map_or_else(|| Ok(Some(#default)), |_obj| #extract)? }
}
(Some(default), _) => {
quote_arg_span! { #arg_value.map_or_else(|| Ok(#default), |_obj| #extract)? }
}
(None, true) => quote_arg_span! { #arg_value.map_or(Ok(None), |_obj| #extract)? },
(None, false) => {
quote_arg_span! {
{
let _obj = #arg_value.expect("Failed to extract required method argument");
#extract?
}
}
}
};
return if let syn::Type::Reference(tref) = arg.optional.as_ref().unwrap_or(&ty) {
let (tref, mut_) = preprocess_tref(tref, self_);
let (target_ty, borrow_tmp) = if arg.optional.is_some() {
// Get Option<&T> from Option<PyRef<T>>
(
quote_arg_span! { 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;
let #arg_name = #borrow_tmp;
})
} else {
Ok(quote_arg_span! {
let #arg_name = #arg_value_or_default;
})
};
/// Replace `Self`, remove lifetime and get mutability from the type
fn preprocess_tref(
tref: &syn::TypeReference,
self_: Option<&syn::Type>,
) -> (syn::TypeReference, Option<syn::token::Mut>) {
let mut tref = tref.to_owned();
if let Some(syn::Type::Path(tpath)) = self_ {
replace_self(&mut tref, &tpath.path);
}
tref.lifetime = None;
let mut_ = tref.mutability;
(tref, mut_)
}
/// Replace `Self` with the exact type name since it is used out of the impl block
fn replace_self(tref: &mut syn::TypeReference, self_path: &syn::Path) {
match &mut *tref.elem {
syn::Type::Reference(tref_inner) => replace_self(tref_inner, self_path),
syn::Type::Path(tpath) => {
if let Some(ident) = tpath.path.get_ident() {
if ident == "Self" {
tpath.path = self_path.to_owned();
}
}
}
_ => {}
}
}
}
pub fn impl_py_method_def( pub fn impl_py_method_def(
cls: &syn::Type, cls: &syn::Type,
spec: &FnSpec, spec: &FnSpec,
self_ty: &SelfType,
flags: Option<TokenStream>, flags: Option<TokenStream>,
) -> Result<TokenStream> { ) -> Result<TokenStream> {
let wrapper_ident = syn::Ident::new("__wrap", Span::call_site());
let wrapper_def = spec.get_wrapper_function(&wrapper_ident, Some(cls))?;
let add_flags = flags.map(|flags| quote!(.flags(#flags))); let add_flags = flags.map(|flags| quote!(.flags(#flags)));
let python_name = spec.null_terminated_python_name();
let doc = &spec.doc; let doc = &spec.doc;
let (methoddef_meth, cfunc_variant) = if spec.args.is_empty() { let python_name = spec.null_terminated_python_name();
(quote!(noargs), quote!(PyCFunction)) let methoddef_type = match spec.tp {
} else if spec.can_use_fastcall() { FnType::FnStatic => quote!(Static),
( FnType::FnClass => quote!(Class),
_ => quote!(Method),
};
let (methoddef_meth, cfunc_variant) = match spec.convention {
CallingConvention::Noargs => (quote!(noargs), quote!(PyCFunction)),
CallingConvention::Fastcall => (
quote!(fastcall_cfunction_with_keywords), quote!(fastcall_cfunction_with_keywords),
quote!(PyCFunctionFastWithKeywords), quote!(PyCFunctionFastWithKeywords),
) ),
} else { _ => (
(
quote!(cfunction_with_keywords), quote!(cfunction_with_keywords),
quote!(PyCFunctionWithKeywords), quote!(PyCFunctionWithKeywords),
) ),
};
let wrapper = if spec.args.is_empty() {
impl_wrap_noargs(cls, spec, self_ty)
} else if spec.can_use_fastcall() {
impl_wrap_fastcall_cfunction_with_keywords(cls, &spec, self_ty)?
} else {
impl_wrap_cfunction_with_keywords(cls, &spec, self_ty)?
}; };
Ok(quote! { Ok(quote! {
pyo3::class::PyMethodDefType::Method({ pyo3::class::PyMethodDefType:: #methoddef_type ({
pyo3::class::PyMethodDef:: #methoddef_meth ( pyo3::class::PyMethodDef:: #methoddef_meth (
#python_name, #python_name,
pyo3::class::methods:: #cfunc_variant (#wrapper), pyo3::class::methods:: #cfunc_variant ({
#wrapper_def
#wrapper_ident
}),
#doc #doc
) )
#add_flags #add_flags
@ -707,48 +283,22 @@ pub fn impl_py_method_def(
}) })
} }
pub fn impl_py_method_def_new(cls: &syn::Type, spec: &FnSpec) -> Result<TokenStream> { fn impl_py_method_def_new(cls: &syn::Type, spec: &FnSpec) -> Result<TokenStream> {
let wrapper = impl_wrap_new(cls, &spec)?; let wrapper_ident = syn::Ident::new("__wrap", Span::call_site());
let wrapper = spec.get_wrapper_function(&wrapper_ident, Some(cls))?;
Ok(quote! { Ok(quote! {
impl pyo3::class::impl_::PyClassNewImpl<#cls> for pyo3::class::impl_::PyClassImplCollector<#cls> { impl pyo3::class::impl_::PyClassNewImpl<#cls> for pyo3::class::impl_::PyClassImplCollector<#cls> {
fn new_impl(self) -> Option<pyo3::ffi::newfunc> { fn new_impl(self) -> Option<pyo3::ffi::newfunc> {
Some(#wrapper) Some({
#wrapper
#wrapper_ident
})
} }
} }
}) })
} }
pub fn impl_py_method_def_class(cls: &syn::Type, spec: &FnSpec) -> Result<TokenStream> { fn impl_py_method_class_attribute(cls: &syn::Type, spec: &FnSpec) -> TokenStream {
let wrapper = impl_wrap_class(cls, &spec)?;
let python_name = spec.null_terminated_python_name();
let doc = &spec.doc;
Ok(quote! {
pyo3::class::PyMethodDefType::Class({
pyo3::class::PyMethodDef::cfunction_with_keywords(
#python_name,
pyo3::class::methods::PyCFunctionWithKeywords(#wrapper),
#doc
).flags(pyo3::ffi::METH_CLASS)
})
})
}
pub fn impl_py_method_def_static(cls: &syn::Type, spec: &FnSpec) -> Result<TokenStream> {
let wrapper = impl_wrap_static(cls, &spec)?;
let python_name = spec.null_terminated_python_name();
let doc = &spec.doc;
Ok(quote! {
pyo3::class::PyMethodDefType::Static({
pyo3::class::PyMethodDef::cfunction_with_keywords(
#python_name,
pyo3::class::methods::PyCFunctionWithKeywords(#wrapper),
#doc
).flags(pyo3::ffi::METH_STATIC)
})
})
}
pub fn impl_py_method_class_attribute(cls: &syn::Type, spec: &FnSpec) -> TokenStream {
let wrapper = impl_wrap_class_attribute(cls, &spec); let wrapper = impl_wrap_class_attribute(cls, &spec);
let python_name = spec.null_terminated_python_name(); let python_name = spec.null_terminated_python_name();
quote! { quote! {
@ -761,7 +311,7 @@ pub fn impl_py_method_class_attribute(cls: &syn::Type, spec: &FnSpec) -> TokenSt
} }
} }
pub fn impl_py_const_class_attribute(spec: &ConstSpec, wrapper: &TokenStream) -> TokenStream { fn impl_py_const_class_attribute(spec: &ConstSpec, wrapper: &TokenStream) -> TokenStream {
let python_name = &spec.null_terminated_python_name(); let python_name = &spec.null_terminated_python_name();
quote! { quote! {
{ {
@ -775,16 +325,16 @@ pub fn impl_py_const_class_attribute(spec: &ConstSpec, wrapper: &TokenStream) ->
} }
} }
pub fn impl_py_method_def_call( fn impl_py_method_def_call(cls: &syn::Type, spec: &FnSpec) -> Result<TokenStream> {
cls: &syn::Type, let wrapper_ident = syn::Ident::new("__wrap", Span::call_site());
spec: &FnSpec, let wrapper = spec.get_wrapper_function(&wrapper_ident, Some(cls))?;
self_ty: &SelfType,
) -> Result<TokenStream> {
let wrapper = impl_wrap_cfunction_with_keywords(cls, &spec, self_ty)?;
Ok(quote! { Ok(quote! {
impl pyo3::class::impl_::PyClassCallImpl<#cls> for pyo3::class::impl_::PyClassImplCollector<#cls> { impl pyo3::class::impl_::PyClassCallImpl<#cls> for pyo3::class::impl_::PyClassImplCollector<#cls> {
fn call_impl(self) -> Option<pyo3::ffi::PyCFunctionWithKeywords> { fn call_impl(self) -> Option<pyo3::ffi::PyCFunctionWithKeywords> {
Some(#wrapper) Some({
#wrapper
#wrapper_ident
})
} }
} }
}) })

View File

@ -73,8 +73,8 @@ fn impl_proto_impl(
None None
}; };
let method = if let FnType::Fn(self_ty) = &fn_spec.tp { let method = if let FnType::Fn(_) = &fn_spec.tp {
pymethod::impl_py_method_def(ty, &fn_spec, &self_ty, flags)? pymethod::impl_py_method_def(ty, &fn_spec, flags)?
} else { } else {
bail_spanned!( bail_spanned!(
met.sig.span() => "expected method with receiver for #[pyproto] method" met.sig.span() => "expected method with receiver for #[pyproto] method"