diff --git a/pyo3-macros-backend/src/method.rs b/pyo3-macros-backend/src/method.rs index 3bd010c4547..036106efb4b 100644 --- a/pyo3-macros-backend/src/method.rs +++ b/pyo3-macros-backend/src/method.rs @@ -435,9 +435,12 @@ impl SelfType { // // The trailing `?` exists because if the extraction fails here it represents // a genuine type error, should not fall back to e.g. `ExtractErrorMode::NotImplemented`. + // + // The cast is inlined (without a redundant nested `unsafe` block, unlike + // the `Checked` path below) to keep the generated code small. quote! { unsafe { #pyo3_path::impl_::extract_argument::#method::<#cls>( - #arg, + #pyo3_path::impl_::extract_argument::#cast_fn(#py, #slf), &mut #holder, ) }? } @@ -462,9 +465,9 @@ impl SelfType { } SelfType::TryFromBoundRef { span, non_null } => { let bound_ref = if *non_null { - quote! { unsafe { #pyo3_path::Bound::ref_from_non_null(#py, &#slf) } } + quote! { #pyo3_path::Bound::ref_from_non_null(#py, &#slf) } } else { - quote! { unsafe { #pyo3_path::Bound::ref_from_ptr(#py, &#slf) } } + quote! { #pyo3_path::Bound::ref_from_ptr(#py, &#slf) } }; let pyo3_path = pyo3_path.to_tokens_spanned(*span); let receiver = match self_conversion.0 { @@ -474,39 +477,27 @@ impl SelfType { // an instance of the correct type (or a compatible subtype) before // invoking the slot. // - // The wrapping `Ok(...?)` here is because an error here should not - // be treated by e.g. `ExtractErrorMode::NotImplemented` as falling - // back to the default, but instead a genuine type error. - quote! { - unsafe { - #pyo3_path::PyResult::Ok( - #pyo3_path::impl_::extract_argument::cast_bound_ref_trusted::<#cls>(#bound_ref)? - ) - } - } + // Note: cast failures inside the fused helper can only occur on + // PyPy (CPython's slot dispatch contract guarantees the receiver + // type); on PyPy under `ExtractErrorMode::NotImplemented` they now + // fall back to the default like `TryFrom` failures always have, + // instead of raising directly. + // The `unsafe` token is deliberately spanned at the macro call site + // (plain `quote!`) so that `#![forbid(unsafe_code)]` in user code + // doesn't reject the generated unsafe block. + let call = quote_spanned! { *span => + #pyo3_path::impl_::extract_argument::extract_receiver_trusted::<#cls, _>(#bound_ref) + }; + quote! { unsafe { #call } } } SelfConversionPolicyInner::Checked => { - quote_spanned! { *span => - #bound_ref.cast::<#cls>() - .map_err(::std::convert::Into::<#pyo3_path::PyErr>::into) - } + let call = quote_spanned! { *span => + #pyo3_path::impl_::extract_argument::extract_receiver::<#cls, _>(#bound_ref) + }; + quote! { unsafe { #call } } } }; - error_mode.handle_error( - quote_spanned! { *span => - #receiver - .and_then( - #[allow( - clippy::unnecessary_fallible_conversions, - clippy::useless_conversion, - reason = "anything implementing `TryFrom<&Bound>` is permitted" - )] - |bound| ::std::convert::TryFrom::try_from(bound).map_err(::std::convert::Into::into) - ) - - }, - ctx - ) + error_mode.handle_error(receiver, ctx) } } } @@ -904,7 +895,11 @@ impl<'a> FnSpec<'a> { (None, None) }; let args = self_arg.into_iter().chain(args); - let ok_wrap = quotes::ok_wrap(ret_ident.to_token_stream(), ctx); + let return_conversion = quotes::wrap_into_pyobject( + ret_ident.to_token_stream(), + quote!(assume_attached.py()), + ctx, + ); quote! { { let coroutine = { @@ -930,8 +925,7 @@ impl<'a> FnSpec<'a> { function(#(#args),*) }; let #ret_ident = future.await; - let #ret_ident = #ok_wrap; - #pyo3_path::impl_::wrap::converter(&#ret_ident).map_into_pyobject(assume_attached.py(), #ret_ident) + #return_conversion }, ) }; @@ -940,10 +934,7 @@ impl<'a> FnSpec<'a> { } } else { let args = self_arg.into_iter().chain(args); - let return_conversion = quotes::map_result_into_ptr( - quotes::ok_wrap(ret_ident.to_token_stream(), ctx), - ctx, - ); + let return_conversion = quotes::wrap_into_ptr(ret_ident.to_token_stream(), ctx); quote! { { #init_holders @@ -990,8 +981,7 @@ impl<'a> FnSpec<'a> { ) -> #pyo3_path::PyResult<*mut #pyo3_path::ffi::PyObject> { let function = #rust_name; // Shadow the function name to avoid #3017 #warnings - let result = #call; - result + #call } } } @@ -1005,8 +995,7 @@ impl<'a> FnSpec<'a> { let function = #rust_name; // Shadow the function name to avoid #3017 #arg_convert #warnings - let result = #call; - result + #call } ); } @@ -1025,8 +1014,7 @@ impl<'a> FnSpec<'a> { let function = #rust_name; // Shadow the function name to avoid #3017 #arg_convert #warnings - let result = #call; - result + #call } } } @@ -1049,20 +1037,24 @@ impl<'a> FnSpec<'a> { FnType::FnStatic => quote! { .flags(#pyo3_path::ffi::METH_STATIC) }, _ => quote! {}, }; - let trampoline = match convention { - CallingConvention::Noargs => Ident::new("noargs", Span::call_site()), + // Constructor names on `PyMethodDef` and (shorter) trampoline aliases in + // `impl_::trampoline` - the short aliases keep the generated code small. + let (constructor, trampoline) = match convention { + CallingConvention::Noargs => ("noargs", "noargs"), CallingConvention::Fastcall => { - Ident::new("maybe_fastcall_cfunction_with_keywords", Span::call_site()) + ("maybe_fastcall_cfunction_with_keywords", "fastcall_kw") } - CallingConvention::Varargs => Ident::new("cfunction_with_keywords", Span::call_site()), + CallingConvention::Varargs => ("cfunction_with_keywords", "cfunc_kw"), }; + let constructor = Ident::new(constructor, Span::call_site()); + let trampoline = Ident::new(trampoline, Span::call_site()); let doc = if let Some(doc) = doc { doc.to_cstr_stream(ctx)? } else { c"".to_token_stream() }; Ok(quote! { - #pyo3_path::impl_::pymethods::PyMethodDef::#trampoline( + #pyo3_path::impl_::pymethods::PyMethodDef::#constructor( #python_name, #pyo3_path::impl_::trampoline::get_trampoline_function!(#trampoline, #wrapper), #doc, diff --git a/pyo3-macros-backend/src/params.rs b/pyo3-macros-backend/src/params.rs index 305cf06aaf5..d09a1e81fba 100644 --- a/pyo3-macros-backend/src/params.rs +++ b/pyo3-macros-backend/src/params.rs @@ -29,9 +29,18 @@ impl Holders { pub fn init_holders(&self, ctx: &Ctx) -> TokenStream { let Ctx { pyo3_path, .. } = ctx; let holders = &self.holders; + if holders.is_empty() { + return TokenStream::new(); + } + // The holders are declared with a single tuple pattern to keep the generated code + // small (this also avoids triggering `clippy::let_unit_value` for the holders which + // are just `()`). + let holders_init = holders + .iter() + .map(|_| quote!(#pyo3_path::impl_::extract_argument::FunctionArgumentHolder::INIT)) + .collect::>(); quote! { - #[allow(clippy::let_unit_value, reason = "many holders are just `()`")] - #(let mut #holders = #pyo3_path::impl_::extract_argument::FunctionArgumentHolder::INIT;)* + let (#(mut #holders,)*) = (#(#holders_init,)*); } } } @@ -102,10 +111,7 @@ pub fn impl_arg_params( .map(|(name, default_value)| { let required = default_value.is_none(); quote! { - #pyo3_path::impl_::extract_argument::KeywordOnlyParameterDescription { - name: #name, - required: #required, - } + #pyo3_path::impl_::extract_argument::KeywordOnlyParameterDescription::new(#name, #required) } }); @@ -165,14 +171,15 @@ pub fn impl_arg_params( // create array of arguments, and then parse ( quote! { - const DESCRIPTION: #pyo3_path::impl_::extract_argument::FunctionDescription = #pyo3_path::impl_::extract_argument::FunctionDescription { - cls_name: #cls_name, - func_name: stringify!(#python_name), - positional_parameter_names: &[#(#positional_parameter_names),*], - positional_only_parameters: #positional_only_parameters, - required_positional_parameters: #required_positional_parameters, - keyword_only_parameters: &[#(#keyword_only_parameters),*], - }; + const DESCRIPTION: #pyo3_path::impl_::extract_argument::FunctionDescription = + #pyo3_path::impl_::extract_argument::FunctionDescription::new( + #cls_name, + stringify!(#python_name), + &[#(#positional_parameter_names),*], + #positional_only_parameters, + #required_positional_parameters, + &[#(#keyword_only_parameters),*], + ); let mut #args_array = [::std::option::Option::None; #num_params]; let (_args, _kwargs) = #extract_expression; #from_py_with @@ -247,13 +254,10 @@ pub(crate) fn impl_regular_arg_param( let pyo3_path = pyo3_path.to_tokens_spanned(arg.ty.span()); // Use this macro inside this function, to ensure that all code generated here is associated - // with the function argument - let use_probe = quote! { - #[allow(unused_imports, reason = "`Probe` trait used on negative case only")] - use #pyo3_path::impl_::pyclass::Probe as _; - }; + // with the function argument. The `Probe` trait used by some of the extraction machinery is + // imported once per wrapper function (see `Holders::init_holders`). macro_rules! quote_arg_span { - ($($tokens:tt)*) => { quote_spanned!(arg.ty.span() => { #use_probe $($tokens)* }) } + ($($tokens:tt)*) => { quote_spanned!(arg.ty.span() => $($tokens)*) } } let name_str = arg.name.to_string(); @@ -282,10 +286,9 @@ pub(crate) fn impl_regular_arg_param( )? } } else { - let unwrap = quote! {unsafe { #pyo3_path::impl_::extract_argument::unwrap_required_argument_bound(#arg_value.as_deref()) }}; quote_arg_span! { - #pyo3_path::impl_::extract_argument::from_py_with( - #unwrap, + #pyo3_path::impl_::extract_argument::from_py_with_required( + #arg_value.as_deref(), #name_str, #extractor, )? @@ -306,10 +309,9 @@ pub(crate) fn impl_regular_arg_param( } } else { let holder = holders.push_holder(arg.ty.span()); - let unwrap = quote! { unsafe { #pyo3_path::impl_::extract_argument::unwrap_required_argument(#arg_value) } }; quote_arg_span! { - #pyo3_path::impl_::extract_argument::extract_argument( - #unwrap, + #pyo3_path::impl_::extract_argument::extract_required_argument( + #arg_value, &mut #holder, #name_str )? diff --git a/pyo3-macros-backend/src/pyclass.rs b/pyo3-macros-backend/src/pyclass.rs index 679be7efdc3..4a96ed26c8d 100644 --- a/pyo3-macros-backend/src/pyclass.rs +++ b/pyo3-macros-backend/src/pyclass.rs @@ -1136,14 +1136,11 @@ fn impl_simple_enum( quote! { impl<'py> #pyo3_path::conversion::IntoPyObject<'py> for #cls { type Target = Self; - type Output = #pyo3_path::Bound<'py, >::Target>; + type Output = #pyo3_path::Bound<'py, Self>; type Error = #pyo3_path::PyErr; #output_type - fn into_pyobject(self, py: #pyo3_path::Python<'py>) -> ::std::result::Result< - >::Output, - >::Error, - > { + fn into_pyobject(self, py: #pyo3_path::Python<'py>) -> #pyo3_path::PyResult<#pyo3_path::Bound<'py, Self>> { // TODO(icxolu): switch this to lookup the variants on the type object, once that is immutable static SINGLETON: [#pyo3_path::sync::PyOnceLock<#pyo3_path::Py<#cls>>; #num] = [const { #pyo3_path::sync::PyOnceLock::<#pyo3_path::Py<#cls>>::new() }; #num]; @@ -1268,14 +1265,11 @@ fn impl_complex_enum( quote! { impl<'py> #pyo3_path::conversion::IntoPyObject<'py> for #cls { type Target = Self; - type Output = #pyo3_path::Bound<'py, >::Target>; + type Output = #pyo3_path::Bound<'py, Self>; type Error = #pyo3_path::PyErr; #output_type - fn into_pyobject(self, py: #pyo3_path::Python<'py>) -> ::std::result::Result< - ::Output, - ::Error, - > { + fn into_pyobject(self, py: #pyo3_path::Python<'py>) -> #pyo3_path::PyResult<#pyo3_path::Bound<'py, Self>> { match self { #(#match_arms)* } @@ -2309,15 +2303,7 @@ fn impl_pytypeinfo(cls: &Ident, attr: &PyClassArgs, ctx: &Ctx) -> TokenStream { #[inline] fn type_object_raw(py: #pyo3_path::Python<'_>) -> *mut #pyo3_path::ffi::PyTypeObject { - use #pyo3_path::prelude::PyTypeMethods; - <#cls as #pyo3_path::impl_::pyclass::PyClassImpl>::lazy_type_object() - .get_or_try_init(py) - .unwrap_or_else(|e| #pyo3_path::impl_::pyclass::type_object_init_failed( - py, - e, - ::NAME - )) - .as_type_ptr() + #pyo3_path::impl_::pyclass::pyclass_type_object_raw::(py) } } } @@ -2773,14 +2759,11 @@ impl<'a> PyClassImplsBuilder<'a> { quote! { impl<'py> #pyo3_path::conversion::IntoPyObject<'py> for #cls { type Target = Self; - type Output = #pyo3_path::Bound<'py, >::Target>; + type Output = #pyo3_path::Bound<'py, Self>; type Error = #pyo3_path::PyErr; #output_type - fn into_pyobject(self, py: #pyo3_path::Python<'py>) -> ::std::result::Result< - ::Output, - ::Error, - > { + fn into_pyobject(self, py: #pyo3_path::Python<'py>) -> #pyo3_path::PyResult<#pyo3_path::Bound<'py, Self>> { #pyo3_path::Bound::new(py, self) } } @@ -2792,17 +2775,26 @@ impl<'a> PyClassImplsBuilder<'a> { fn impl_pyclassimpl(&self, ctx: &Ctx) -> Result { let Ctx { pyo3_path, .. } = ctx; let cls = self.cls_ident; - let doc = if let Some(doc) = &self.doc { - doc.to_cstr_stream(ctx)? - } else { - c"".to_token_stream() + // `RAW_DOC` defaults to `c""` in the trait definition; only emit it when there is a + // docstring. + let raw_doc = match &self.doc { + Some(doc) => { + let doc = doc.to_cstr_stream(ctx)?; + Some(quote! { const RAW_DOC: &'static ::std::ffi::CStr = #doc; }) + } + None => None, }; - let module = if let Some(ModuleAttribute { value, .. }) = &self.attr.options.module { - quote! { ::core::option::Option::Some(#value) } - } else { - quote! { ::core::option::Option::None } - }; + // All of these consts have defaults in the `PyClassImpl` trait definition; only emit + // them when they differ from the default, to keep the generated code small. + let module = self + .attr + .options + .module + .as_ref() + .map(|ModuleAttribute { value, .. }| { + quote! { const MODULE: ::std::option::Option<&str> = ::core::option::Option::Some(#value); } + }); let is_basetype = self.attr.options.subclass.is_some(); let base = match &self.attr.options.extends { @@ -2819,6 +2811,13 @@ impl<'a> PyClassImplsBuilder<'a> { cls.span() => "a `#[pyclass]` cannot be both a `mapping` and a `sequence`" ); + let is_basetype = is_basetype.then(|| quote! { const IS_BASETYPE: bool = true; }); + let is_subclass_const = is_subclass.then(|| quote! { const IS_SUBCLASS: bool = true; }); + let is_mapping = is_mapping.then(|| quote! { const IS_MAPPING: bool = true; }); + let is_sequence = is_sequence.then(|| quote! { const IS_SEQUENCE: bool = true; }); + let is_immutable_type = + is_immutable_type.then(|| quote! { const IS_IMMUTABLE_TYPE: bool = true; }); + let dict_offset = if self.attr.options.dict.is_some() { quote! { fn dict_offset() -> ::std::option::Option<#pyo3_path::impl_::pyclass::PyObjectOffset> { @@ -2847,7 +2846,10 @@ impl<'a> PyClassImplsBuilder<'a> { let (pymethods_items, inventory, inventory_class) = match self.methods_type { PyClassMethodsType::Specialization => ( - quote! {{ use #pyo3_path::impl_::pyclass::PyMethods as _; collector.py_methods() }}, + quote! {{ + use #pyo3_path::impl_::pyclass::PyMethods as _; + #pyo3_path::impl_::pyclass::PyClassImplCollector::::new().py_methods() + }}, None, None, ), @@ -2880,12 +2882,57 @@ impl<'a> PyClassImplsBuilder<'a> { self.default_slots .iter() .map(|meth| &meth.associated_method), - ); + ) + .collect::>(); + // Skip emitting the associated-methods impl block entirely when it would be empty. + let default_methods_impl = (!default_methods.is_empty()).then(|| { + quote! { + #[doc(hidden)] + #[allow(non_snake_case)] + impl #cls { + #(#default_methods)* + } + } + }); - let default_method_defs = self.default_methods.iter().map(|meth| &meth.method_def); - let default_slot_defs = self.default_slots.iter().map(|slot| &slot.slot_def); + let default_method_defs = self + .default_methods + .iter() + .map(|meth| &meth.method_def) + .collect::>(); + let default_slot_defs = self + .default_slots + .iter() + .map(|slot| &slot.slot_def) + .collect::>(); let freelist_slots = self.freelist_slots(ctx); + // When there are no intrinsic items, point at a shared empty `PyClassItems` to keep the + // generated code small. + let items_iter = if default_method_defs.is_empty() + && default_slot_defs.is_empty() + && freelist_slots.is_empty() + { + quote! { + fn items_iter() -> #pyo3_path::impl_::pyclass::PyClassItemsIter { + #pyo3_path::impl_::pyclass::PyClassItemsIter::new( + &#pyo3_path::impl_::pyclass::NO_PY_CLASS_ITEMS, + #pymethods_items, + ) + } + } + } else { + quote! { + fn items_iter() -> #pyo3_path::impl_::pyclass::PyClassItemsIter { + static INTRINSIC_ITEMS: #pyo3_path::impl_::pyclass::PyClassItems = #pyo3_path::impl_::pyclass::PyClassItems { + methods: &[#(#default_method_defs),*], + slots: &[#(#default_slot_defs),* #(#freelist_slots),*], + }; + #pyo3_path::impl_::pyclass::PyClassItemsIter::new(&INTRINSIC_ITEMS, #pymethods_items) + } + } + }; + let class_mutability = if self.attr.options.frozen.is_some() { quote! { ImmutableChild @@ -2975,25 +3022,31 @@ impl<'a> PyClassImplsBuilder<'a> { TokenStream::new() }; + let assertions = (!assertions.is_empty()).then(|| { + quote! { + #[allow(dead_code)] + const _: () = { + #assertions + }; + } + }); + Ok(quote! { #deprecation #extract_pyclass_with_clone - #[allow(dead_code)] - const _: () ={ - #assertions - }; + #assertions #pyclass_base_type_impl impl #pyo3_path::impl_::pyclass::PyClassImpl for #cls { - const MODULE: ::std::option::Option<&str> = #module; - const IS_BASETYPE: bool = #is_basetype; - const IS_SUBCLASS: bool = #is_subclass; - const IS_MAPPING: bool = #is_mapping; - const IS_SEQUENCE: bool = #is_sequence; - const IS_IMMUTABLE_TYPE: bool = #is_immutable_type; + #module + #is_basetype + #is_subclass_const + #is_mapping + #is_sequence + #is_immutable_type type Layout = ::Layout; type BaseType = #base; @@ -3004,16 +3057,9 @@ impl<'a> PyClassImplsBuilder<'a> { type WeakRef = #weakref; type BaseNativeType = #base_nativetype; - fn items_iter() -> #pyo3_path::impl_::pyclass::PyClassItemsIter { - let collector = #pyo3_path::impl_::pyclass::PyClassImplCollector::::new(); - static INTRINSIC_ITEMS: #pyo3_path::impl_::pyclass::PyClassItems = #pyo3_path::impl_::pyclass::PyClassItems { - methods: &[#(#default_method_defs),*], - slots: &[#(#default_slot_defs),* #(#freelist_slots),*], - }; - #pyo3_path::impl_::pyclass::PyClassItemsIter::new(&INTRINSIC_ITEMS, #pymethods_items) - } + #items_iter - const RAW_DOC: &'static ::std::ffi::CStr = #doc; + #raw_doc const DOC: &'static ::std::ffi::CStr = { use #pyo3_path::impl_ as impl_; @@ -3038,11 +3084,7 @@ impl<'a> PyClassImplsBuilder<'a> { } } - #[doc(hidden)] - #[allow(non_snake_case)] - impl #cls { - #(#default_methods)* - } + #default_methods_impl #inventory_class }) diff --git a/pyo3-macros-backend/src/pyimpl.rs b/pyo3-macros-backend/src/pyimpl.rs index b29c8344e3d..453b6560fa7 100644 --- a/pyo3-macros-backend/src/pyimpl.rs +++ b/pyo3-macros-backend/src/pyimpl.rs @@ -223,16 +223,23 @@ pub fn impl_methods( PyClassMethodsType::Inventory => submit_methods_inventory(ty, methods, proto_impls, ctx), }; + // Skip emitting the associated-methods impl block entirely when it would be empty. + let associated_methods_impl = (!associated_methods.is_empty()).then(|| { + quote! { + #[doc(hidden)] + #[allow(non_snake_case)] + impl #ty { + #(#associated_methods)* + } + } + }); + Ok(quote! { #(#extra_fragments)* #items - #[doc(hidden)] - #[allow(non_snake_case)] - impl #ty { - #(#associated_methods)* - } + #associated_methods_impl }) } diff --git a/pyo3-macros-backend/src/pymethod.rs b/pyo3-macros-backend/src/pymethod.rs index 60c6439a76d..ad1099885fc 100644 --- a/pyo3-macros-backend/src/pymethod.rs +++ b/pyo3-macros-backend/src/pymethod.rs @@ -562,13 +562,13 @@ pub(crate) fn impl_py_class_attribute( let wrapper_ident = format_ident!("__pymethod_{}__", name); let python_name = spec.null_terminated_python_name(); - let body = quotes::ok_wrap(fncall, ctx); + let conversion = quotes::wrap_into_pyobject(quote!(result), quote!(py), ctx); let associated_method = quote! { fn #wrapper_ident(py: #pyo3_path::Python<'_>) -> #pyo3_path::PyResult<#pyo3_path::Py<#pyo3_path::PyAny>> { let function = #cls::#name; // Shadow the method name to avoid #3017 - let result = #body; - #pyo3_path::impl_::wrap::converter(&result).map_into_pyobject(py, result) + let result = #fncall; + #conversion } }; @@ -1546,7 +1546,7 @@ fn generate_method_body( let value = syn::Ident::new("value", Span::call_site()); let resolver = quote_spanned! { *output_span => - #pyo3_path::impl_::pymethods::tp_new_resolver::<#cls, _>(&#value).resolve(#value); + #pyo3_path::impl_::pymethods::tp_new_resolver::<#cls, _>(&#value).resolve(#value) }; let body = quote! { diff --git a/pyo3-macros-backend/src/quotes.rs b/pyo3-macros-backend/src/quotes.rs index 7786837c205..2d9c3063cd5 100644 --- a/pyo3-macros-backend/src/quotes.rs +++ b/pyo3-macros-backend/src/quotes.rs @@ -9,28 +9,30 @@ pub(crate) fn some_wrap(obj: TokenStream, ctx: &Ctx) -> TokenStream { } } -pub(crate) fn ok_wrap(obj: TokenStream, ctx: &Ctx) -> TokenStream { +/// Fused return-value conversion: wraps a (possibly `Result`) return value and converts it +/// into a `PyResult<*mut ffi::PyObject>` in a single call. `obj` must be an identifier +/// bound to the function's return value (it is evaluated twice). +pub(crate) fn wrap_into_ptr(obj: TokenStream, ctx: &Ctx) -> TokenStream { let Ctx { pyo3_path, output_span, } = ctx; let pyo3_path = pyo3_path.to_tokens_spanned(*output_span); - quote_spanned! { *output_span => { - let obj = #obj; - #[allow(clippy::useless_conversion, reason = "needed for Into conversion, may be redundant")] - #pyo3_path::impl_::wrap::converter(&obj).wrap(obj).map_err(::core::convert::Into::<#pyo3_path::PyErr>::into) - }} + let py = syn::Ident::new("py", proc_macro2::Span::call_site()); + quote_spanned! { *output_span => + #pyo3_path::impl_::wrap::converter(&#obj).wrap_into_ptr(#py, #obj) + } } -pub(crate) fn map_result_into_ptr(result: TokenStream, ctx: &Ctx) -> TokenStream { +/// As `wrap_into_ptr`, but produces a `PyResult>`. The Python token expression +/// is supplied by the caller. +pub(crate) fn wrap_into_pyobject(obj: TokenStream, py: TokenStream, ctx: &Ctx) -> TokenStream { let Ctx { pyo3_path, output_span, } = ctx; let pyo3_path = pyo3_path.to_tokens_spanned(*output_span); - let py = syn::Ident::new("py", proc_macro2::Span::call_site()); - quote_spanned! { *output_span => { - let result = #result; - #pyo3_path::impl_::wrap::converter(&result).map_into_ptr(#py, result) - }} + quote_spanned! { *output_span => + #pyo3_path::impl_::wrap::converter(&#obj).wrap_into_pyobject(#py, #obj) + } } diff --git a/src/impl_/extract_argument.rs b/src/impl_/extract_argument.rs index 361a427bb56..d6e06f35ea5 100644 --- a/src/impl_/extract_argument.rs +++ b/src/impl_/extract_argument.rs @@ -292,6 +292,39 @@ pub unsafe fn cast_bound_ref_trusted<'a, 'py, T: PyTypeCheck>( } } +/// Fused extraction for receivers which implement `TryFrom<&Bound>` (e.g. `Py`, +/// `Bound`, `PyRef`), used to keep the generated code small. Trusted variant: performs an +/// unchecked cast for extension-type slot receivers where CPython guarantees the receiver type. +/// +/// # Safety +/// The caller must ensure that `bound_ref` is an instance of `T`. This invariant is upheld by +/// CPython when dispatching through type slots. +#[inline] +pub unsafe fn extract_receiver_trusted<'a, 'py, T, R>( + bound_ref: &'a Bound<'py, PyAny>, +) -> PyResult +where + T: PyTypeCheck + 'a, + R: TryFrom<&'a Bound<'py, T>>, + R::Error: Into, +{ + // SAFETY: caller guarantees `bound_ref` is an instance of `T` + let bound = unsafe { cast_bound_ref_trusted::(bound_ref) }?; + R::try_from(bound).map_err(Into::into) +} + +/// As [`extract_receiver_trusted`], but performs a checked cast. +#[inline] +pub fn extract_receiver<'a, 'py, T, R>(bound_ref: &'a Bound<'py, PyAny>) -> PyResult +where + T: PyTypeCheck + 'a, + R: TryFrom<&'a Bound<'py, T>>, + R::Error: Into, +{ + let bound = bound_ref.cast::().map_err(PyErr::from)?; + R::try_from(bound).map_err(Into::into) +} + /// The standard implementation of how PyO3 extracts a `#[pyfunction]` or `#[pymethod]` function argument. pub fn extract_argument<'a, 'holder, 'py, T, const IMPLEMENTS_FROMPYOBJECT: bool>( obj: Borrowed<'a, 'py, PyAny>, @@ -307,6 +340,28 @@ where } } +/// Fused unwrap + [`extract_argument`], used for required arguments to keep the generated +/// code small. +/// +/// The macro-generated caller guarantees `obj` is `Some` (the `FunctionDescription` +/// `extract_arguments_` methods check that all required arguments are provided); the `None` +/// arm is unreachable but kept as a branch so that this function is safe to call (avoiding +/// an `unsafe` block in the generated code). +#[inline] +pub fn extract_required_argument<'a, 'holder, 'py, T, const IMPLEMENTS_FROMPYOBJECT: bool>( + obj: Option>, + holder: &'holder mut T::Holder, + arg_name: &str, +) -> PyResult +where + T: PyFunctionArgument<'a, 'holder, 'py, IMPLEMENTS_FROMPYOBJECT>, +{ + match obj { + Some(obj) => extract_argument(obj, holder, arg_name), + None => unreachable!("required argument was not extracted"), + } +} + /// Alternative to [`extract_argument`] used when the argument has a default value provided by an annotation. pub fn extract_argument_with_default<'a, 'holder, 'py, T, const IMPLEMENTS_FROMPYOBJECT: bool>( obj: Option>, @@ -335,6 +390,23 @@ pub fn from_py_with<'a, 'py, T>( } } +/// Fused unwrap + [`from_py_with`], used for required arguments with a +/// `#[pyo3(from_py_with)]` annotation to keep the generated code small. +/// +/// As [`extract_required_argument`], the `None` arm is unreachable but kept as a branch so +/// that this function is safe to call. +#[inline] +pub fn from_py_with_required<'a, 'py, T>( + obj: Option<&'a Bound<'py, PyAny>>, + arg_name: &str, + extractor: fn(&'a Bound<'py, PyAny>) -> PyResult, +) -> PyResult { + match obj { + Some(obj) => from_py_with(obj, arg_name, extractor), + None => unreachable!("required argument was not extracted"), + } +} + /// Alternative to [`extract_argument`] used when the argument has a `#[pyo3(from_py_with)]` annotation and also a default value. pub fn from_py_with_with_default<'a, 'py, T>( obj: Option<&'a Bound<'py, PyAny>>, @@ -362,40 +434,6 @@ pub fn argument_extraction_error(py: Python<'_>, arg_name: &str, error: PyErr) - error } -/// Unwraps the Option<&PyAny> produced by the FunctionDescription `extract_arguments_` methods. -/// They check if required methods are all provided. -/// -/// # Safety -/// `argument` must not be `None` -#[inline] -pub unsafe fn unwrap_required_argument<'a, 'py>( - argument: Option>, -) -> Borrowed<'a, 'py, PyAny> { - match argument { - Some(value) => value, - #[cfg(debug_assertions)] - None => unreachable!("required method argument was not extracted"), - // SAFETY: invariant of calling this function. Enforced by the macros. - #[cfg(not(debug_assertions))] - None => unsafe { core::hint::unreachable_unchecked() }, - } -} - -/// Variant of above used with `from_py_with` extractors on required arguments. -#[inline] -pub unsafe fn unwrap_required_argument_bound<'a, 'py>( - argument: Option<&'a Bound<'py, PyAny>>, -) -> &'a Bound<'py, PyAny> { - match argument { - Some(value) => value, - #[cfg(debug_assertions)] - None => unreachable!("required method argument was not extracted"), - // SAFETY: invariant of calling this function. Enforced by the macros. - #[cfg(not(debug_assertions))] - None => unsafe { core::hint::unreachable_unchecked() }, - } -} - /// Cast a raw `*mut ffi::PyObject` to a `PyArg`. This is used to access safer PyO3 /// APIs with the assumption that the borrowed argument is valid for the lifetime `'py`. /// @@ -449,6 +487,13 @@ pub struct KeywordOnlyParameterDescription { pub required: bool, } +impl KeywordOnlyParameterDescription { + /// Compact constructor used by the macros to keep the generated code small. + pub const fn new(name: &'static str, required: bool) -> Self { + Self { name, required } + } +} + /// Function argument specification for a `#[pyfunction]` or `#[pymethod]`. pub struct FunctionDescription { pub cls_name: Option<&'static str>, @@ -460,6 +505,25 @@ pub struct FunctionDescription { } impl FunctionDescription { + /// Compact constructor used by the macros to keep the generated code small. + pub const fn new( + cls_name: Option<&'static str>, + func_name: &'static str, + positional_parameter_names: &'static [&'static str], + positional_only_parameters: usize, + required_positional_parameters: usize, + keyword_only_parameters: &'static [KeywordOnlyParameterDescription], + ) -> Self { + Self { + cls_name, + func_name, + positional_parameter_names, + positional_only_parameters, + required_positional_parameters, + keyword_only_parameters, + } + } + fn full_name(&self) -> String { if let Some(cls_name) = self.cls_name { format!("{}.{}()", cls_name, self.func_name) diff --git a/src/impl_/pyclass.rs b/src/impl_/pyclass.rs index c1d911c6388..0053a7c874c 100644 --- a/src/impl_/pyclass.rs +++ b/src/impl_/pyclass.rs @@ -33,7 +33,7 @@ mod lazy_type_object; mod probes; pub use assertions::*; -pub use lazy_type_object::{type_object_init_failed, LazyTypeObject}; +pub use lazy_type_object::{pyclass_type_object_raw, type_object_init_failed, LazyTypeObject}; pub use probes::*; /// Gets the offset of the dictionary from the start of the object in bytes. @@ -179,6 +179,13 @@ pub struct PyClassItems { // Allow PyClassItems in statics unsafe impl Sync for PyClassItems {} +/// Shared empty items, used by the macros for classes without any intrinsic items to keep the +/// generated code small. +pub static NO_PY_CLASS_ITEMS: PyClassItems = PyClassItems { + methods: &[], + slots: &[], +}; + /// Implements the underlying functionality of `#[pyclass]`, assembled by various proc macros. /// /// Users are discouraged from implementing this trait manually; it is a PyO3 implementation detail @@ -188,7 +195,7 @@ pub trait PyClassImpl: Sized + 'static { /// /// (Currently defaults to `builtins` if unset, this will likely be improved in the future, it /// may also be removed when passing module objects in class init.) - const MODULE: Option<&'static str>; + const MODULE: Option<&'static str> = None; /// #[pyclass(subclass)] const IS_BASETYPE: bool = false; @@ -239,12 +246,12 @@ pub trait PyClassImpl: Sized + 'static { /// Docstring for the class provided on the struct or enum. /// /// This is exposed for `PyClassDocGenerator` to use as a docstring piece. - const RAW_DOC: &'static CStr; + const RAW_DOC: &'static CStr = c""; /// Fully rendered class doc, including the `text_signature` if a constructor is defined. /// /// This is constructed at compile-time with const specialization via the proc macros with help - /// from the PyClassDocGenerator` type. + /// from the `PyClassDocGenerator` type. const DOC: &'static CStr; fn items_iter() -> PyClassItemsIter; diff --git a/src/impl_/pyclass/lazy_type_object.rs b/src/impl_/pyclass/lazy_type_object.rs index 2142b1d4568..06f28e27fa7 100644 --- a/src/impl_/pyclass/lazy_type_object.rs +++ b/src/impl_/pyclass/lazy_type_object.rs @@ -261,6 +261,17 @@ pub fn type_object_init_failed(py: Python<'_>, err: PyErr, type_name: &str) -> ! panic!("failed to create type object for `{type_name}`") } +/// The full macro-expanded implementation of `type_object_raw` for `#[pyclass]` types, kept +/// out-of-line here to reduce the amount of macro-generated code. +#[inline] +pub fn pyclass_type_object_raw(py: Python<'_>) -> *mut ffi::PyTypeObject { + use crate::types::PyTypeMethods; + T::lazy_type_object() + .get_or_try_init(py) + .unwrap_or_else(|e| type_object_init_failed(py, e, ::NAME)) + .as_type_ptr() +} + #[cold] fn wrap_in_runtime_error(py: Python<'_>, err: PyErr, message: String) -> PyErr { let runtime_err = PyRuntimeError::new_err(message); diff --git a/src/impl_/trampoline.rs b/src/impl_/trampoline.rs index 8ff1fa3f653..5987b4624bd 100644 --- a/src/impl_/trampoline.rs +++ b/src/impl_/trampoline.rs @@ -154,6 +154,10 @@ pub use self::fastcall_cfunction_with_keywords as maybe_fastcall_cfunction_with_ #[cfg(not(any(Py_3_10, not(Py_LIMITED_API))))] pub use self::cfunction_with_keywords as maybe_fastcall_cfunction_with_keywords; +/// Short aliases for the trampolines above, used by the macros to keep the generated code small. +pub use self::cfunction_with_keywords as cfunc_kw; +pub use self::maybe_fastcall_cfunction_with_keywords as fastcall_kw; + // Trampolines used by slot methods trampolines!( pub fn getattrofunc(slf: *mut ffi::PyObject, attr: *mut ffi::PyObject) -> *mut ffi::PyObject; diff --git a/src/impl_/wrap.rs b/src/impl_/wrap.rs index 7e463697bc1..30bc10673b8 100644 --- a/src/impl_/wrap.rs +++ b/src/impl_/wrap.rs @@ -3,7 +3,7 @@ use core::{convert::Infallible, marker::PhantomData, ops::Deref}; use crate::{ - ffi, types::PyNone, Bound, IntoPyObject, IntoPyObjectExt, Py, PyAny, PyResult, Python, + ffi, types::PyNone, Bound, IntoPyObject, IntoPyObjectExt, Py, PyAny, PyErr, PyResult, Python, }; /// Used to wrap values in `Option` for default arguments. @@ -92,15 +92,36 @@ impl Deref for UnknownReturnResultType { } } -impl EmptyTupleConverter> { +impl EmptyTupleConverter<()> { #[inline] - pub fn map_into_ptr(&self, py: Python<'_>, obj: PyResult<()>) -> PyResult<*mut ffi::PyObject> { + pub fn wrap_into_ptr(&self, py: Python<'_>, _obj: ()) -> PyResult<*mut ffi::PyObject> { + Ok(PyNone::get(py).to_owned().into_ptr()) + } + + #[inline] + pub fn wrap_into_pyobject(&self, py: Python<'_>, _obj: ()) -> PyResult> { + Ok(PyNone::get(py).to_owned().into_any().unbind()) + } +} + +impl EmptyTupleConverter> +where + PyErr: From, +{ + #[inline] + pub fn wrap_into_ptr( + &self, + py: Python<'_>, + obj: Result<(), E>, + ) -> PyResult<*mut ffi::PyObject> { obj.map(|_| PyNone::get(py).to_owned().into_ptr()) + .map_err(PyErr::from) } #[inline] - pub fn map_into_pyobject(&self, py: Python<'_>, obj: PyResult<()>) -> PyResult> { + pub fn wrap_into_pyobject(&self, py: Python<'_>, obj: Result<(), E>) -> PyResult> { obj.map(|_| PyNone::get(py).to_owned().into_any().unbind()) + .map_err(PyErr::from) } } @@ -109,6 +130,16 @@ impl<'py, T: IntoPyObject<'py>> IntoPyObjectConverter { pub fn wrap(&self, obj: T) -> Result { Ok(obj) } + + #[inline] + pub fn wrap_into_ptr(&self, py: Python<'py>, obj: T) -> PyResult<*mut ffi::PyObject> { + obj.into_bound_py_any(py).map(Bound::into_ptr) + } + + #[inline] + pub fn wrap_into_pyobject(&self, py: Python<'py>, obj: T) -> PyResult> { + obj.into_py_any(py) + } } impl<'py, T: IntoPyObject<'py>, E> IntoPyObjectConverter> { @@ -118,20 +149,21 @@ impl<'py, T: IntoPyObject<'py>, E> IntoPyObjectConverter> { } #[inline] - pub fn map_into_pyobject(&self, py: Python<'py>, obj: PyResult) -> PyResult> + pub fn wrap_into_ptr(&self, py: Python<'py>, obj: Result) -> PyResult<*mut ffi::PyObject> where - T: IntoPyObject<'py>, + PyErr: From, { - obj.and_then(|obj| obj.into_py_any(py)) + obj.map_err(PyErr::from) + .and_then(|obj| obj.into_bound_py_any(py)) + .map(Bound::into_ptr) } #[inline] - pub fn map_into_ptr(&self, py: Python<'py>, obj: PyResult) -> PyResult<*mut ffi::PyObject> + pub fn wrap_into_pyobject(&self, py: Python<'py>, obj: Result) -> PyResult> where - T: IntoPyObject<'py>, + PyErr: From, { - obj.and_then(|obj| obj.into_bound_py_any(py)) - .map(Bound::into_ptr) + obj.map_err(PyErr::from).and_then(|obj| obj.into_py_any(py)) } } @@ -143,6 +175,26 @@ impl UnknownReturnResultType> { { unreachable!("should be handled by IntoPyObjectConverter") } + + #[inline] + pub fn wrap_into_ptr<'py>( + &self, + _: Python<'py>, + _: Result, + ) -> PyResult<*mut ffi::PyObject> + where + T: IntoPyObject<'py>, + { + unreachable!("should be handled by IntoPyObjectConverter") + } + + #[inline] + pub fn wrap_into_pyobject<'py>(&self, _: Python<'py>, _: Result) -> PyResult> + where + T: IntoPyObject<'py>, + { + unreachable!("should be handled by IntoPyObjectConverter") + } } impl UnknownReturnType { @@ -155,7 +207,7 @@ impl UnknownReturnType { } #[inline] - pub fn map_into_pyobject<'py>(&self, _: Python<'py>, _: PyResult) -> PyResult> + pub fn wrap_into_ptr<'py>(&self, _: Python<'py>, _: T) -> PyResult<*mut ffi::PyObject> where T: IntoPyObject<'py>, { @@ -163,7 +215,7 @@ impl UnknownReturnType { } #[inline] - pub fn map_into_ptr<'py>(&self, _: Python<'py>, _: PyResult) -> PyResult<*mut ffi::PyObject> + pub fn wrap_into_pyobject<'py>(&self, _: Python<'py>, _: T) -> PyResult> where T: IntoPyObject<'py>, { diff --git a/src/pyclass/guard.rs b/src/pyclass/guard.rs index c8c6c910d05..7ae186fcae3 100644 --- a/src/pyclass/guard.rs +++ b/src/pyclass/guard.rs @@ -475,25 +475,12 @@ impl Drop for PyClassGuardMap<'_, U> { /// _slf: *mut ::pyo3::ffi::PyObject, /// ) -> ::pyo3::PyResult<*mut ::pyo3::ffi::PyObject> { /// let function = Number::increment; -/// # #[allow(clippy::let_unit_value)] -/// let mut holder_0 = ::pyo3::impl_::extract_argument::FunctionArgumentHolder::INIT; -/// let result = { -/// let ret = function(::pyo3::impl_::extract_argument::extract_pyclass_ref_mut::( -/// unsafe { ::pyo3::impl_::extract_argument::cast_function_argument(py, _slf) }, -/// &mut holder_0, -/// )?); -/// { -/// let result = { -/// let obj = ret; -/// # #[allow(clippy::useless_conversion)] -/// ::pyo3::impl_::wrap::converter(&obj) -/// .wrap(obj) -/// .map_err(::core::convert::Into::<::pyo3::PyErr>::into) -/// }; -/// ::pyo3::impl_::wrap::converter(&result).map_into_ptr(py, result) -/// } -/// }; -/// result +/// let (mut holder_0,) = (::pyo3::impl_::extract_argument::FunctionArgumentHolder::INIT,); +/// let ret = function(::pyo3::impl_::extract_argument::extract_pyclass_ref_mut::( +/// unsafe { ::pyo3::impl_::extract_argument::cast_function_argument(py, _slf) }, +/// &mut holder_0, +/// )?); +/// ::pyo3::impl_::wrap::converter(&ret).wrap_into_ptr(py, ret) /// } /// /// unsafe { diff --git a/tests/ui/invalid_cancel_handle.stderr b/tests/ui/invalid_cancel_handle.stderr index 6b29752299a..fe41f54afa1 100644 --- a/tests/ui/invalid_cancel_handle.stderr +++ b/tests/ui/invalid_cancel_handle.stderr @@ -88,14 +88,14 @@ error[E0277]: `CancelHandle` cannot be used as a Python function argument `(T0, T1, T2, T3, T4, T5)` implements `FromPyObject<'a, 'py>` and $N others = note: required for `CancelHandle` to implement `pyo3::impl_::extract_argument::PyFunctionArgument<'_, '_, '_, true>` -note: required by a bound in `pyo3::impl_::extract_argument::extract_argument` +note: required by a bound in `pyo3::impl_::extract_argument::extract_required_argument` --> src/impl_/extract_argument.rs | - | pub fn extract_argument<'a, 'holder, 'py, T, const IMPLEMENTS_FROMPYOBJECT: bool>( - | ---------------- required by a bound in this function + | pub fn extract_required_argument<'a, 'holder, 'py, T, const IMPLEMENTS_FROMPYOBJECT: bool>( + | ------------------------- required by a bound in this function ... | T: PyFunctionArgument<'a, 'holder, 'py, IMPLEMENTS_FROMPYOBJECT>, - | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ required by this bound in `extract_argument` + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ required by this bound in `extract_required_argument` error: aborting due to 7 previous errors diff --git a/tests/ui/invalid_pyclass_args.default.stderr b/tests/ui/invalid_pyclass_args.default.stderr index c9f0b639323..0be5cebad6d 100644 --- a/tests/ui/invalid_pyclass_args.default.stderr +++ b/tests/ui/invalid_pyclass_args.default.stderr @@ -444,14 +444,14 @@ error[E0277]: `Box` cannot be used as a Pyt `(T0, T1, T2, T3, T4, T5)` implements `pyo3::FromPyObject<'a, 'py>` and $N others = note: required for `Box` to implement `pyo3::impl_::extract_argument::PyFunctionArgument<'_, '_, '_, true>` -note: required by a bound in `pyo3::impl_::extract_argument::extract_argument` +note: required by a bound in `pyo3::impl_::extract_argument::extract_required_argument` --> src/impl_/extract_argument.rs | - | pub fn extract_argument<'a, 'holder, 'py, T, const IMPLEMENTS_FROMPYOBJECT: bool>( - | ---------------- required by a bound in this function + | pub fn extract_required_argument<'a, 'holder, 'py, T, const IMPLEMENTS_FROMPYOBJECT: bool>( + | ------------------------- required by a bound in this function ... | T: PyFunctionArgument<'a, 'holder, 'py, IMPLEMENTS_FROMPYOBJECT>, - | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ required by this bound in `extract_argument` + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ required by this bound in `extract_required_argument` error[E0034]: multiple applicable items in scope --> tests/ui/invalid_pyclass_args.rs:258:1 diff --git a/tests/ui/invalid_pyclass_args.inspect.stderr b/tests/ui/invalid_pyclass_args.inspect.stderr index 7e8ed6dd490..226318e2ae7 100644 --- a/tests/ui/invalid_pyclass_args.inspect.stderr +++ b/tests/ui/invalid_pyclass_args.inspect.stderr @@ -474,14 +474,14 @@ error[E0277]: `Box` cannot be used as a Pyt `(T0, T1, T2, T3, T4, T5)` implements `pyo3::FromPyObject<'a, 'py>` and $N others = note: required for `Box` to implement `pyo3::impl_::extract_argument::PyFunctionArgument<'_, '_, '_, true>` -note: required by a bound in `pyo3::impl_::extract_argument::extract_argument` +note: required by a bound in `pyo3::impl_::extract_argument::extract_required_argument` --> src/impl_/extract_argument.rs | - | pub fn extract_argument<'a, 'holder, 'py, T, const IMPLEMENTS_FROMPYOBJECT: bool>( - | ---------------- required by a bound in this function + | pub fn extract_required_argument<'a, 'holder, 'py, T, const IMPLEMENTS_FROMPYOBJECT: bool>( + | ------------------------- required by a bound in this function ... | T: PyFunctionArgument<'a, 'holder, 'py, IMPLEMENTS_FROMPYOBJECT>, - | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ required by this bound in `extract_argument` + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ required by this bound in `extract_required_argument` error[E0034]: multiple applicable items in scope --> tests/ui/invalid_pyclass_args.rs:258:1 diff --git a/tests/ui/invalid_pyclass_generic.stderr b/tests/ui/invalid_pyclass_generic.stderr index 1a99c4444d4..02edf7878da 100644 --- a/tests/ui/invalid_pyclass_generic.stderr +++ b/tests/ui/invalid_pyclass_generic.stderr @@ -70,10 +70,10 @@ error[E0034]: multiple applicable items in scope --> tests/ui/invalid_pyclass_generic.rs:4:1 | 4 | #[pyclass(generic)] - | ^^^^^^^^^^^^^^^^^^^ multiple `wrap` found + | ^^^^^^^^^^^^^^^^^^^ multiple `wrap_into_ptr` found | - = note: candidate #1 is defined in an impl for the type `pyo3::impl_::wrap::IntoPyObjectConverter>` - = note: candidate #2 is defined in an impl for the type `pyo3::impl_::wrap::IntoPyObjectConverter` + = note: candidate #1 is defined in an impl for the type `pyo3::impl_::wrap::EmptyTupleConverter<()>` + = note: candidate #2 is defined in an impl for the type `pyo3::impl_::wrap::EmptyTupleConverter>` = note: this error originates in the attribute macro `pyclass` (in Nightly builds, run with -Z macro-backtrace for more info) error[E0308]: mismatched types @@ -133,10 +133,10 @@ error[E0034]: multiple applicable items in scope --> tests/ui/invalid_pyclass_generic.rs:25:10 | 25 | ) -> PyResult> { - | ^^^^^^^^ multiple `wrap` found + | ^^^^^^^^ multiple `wrap_into_ptr` found | - = note: candidate #1 is defined in an impl for the type `pyo3::impl_::wrap::IntoPyObjectConverter>` - = note: candidate #2 is defined in an impl for the type `pyo3::impl_::wrap::IntoPyObjectConverter` + = note: candidate #1 is defined in an impl for the type `pyo3::impl_::wrap::EmptyTupleConverter<()>` + = note: candidate #2 is defined in an impl for the type `pyo3::impl_::wrap::EmptyTupleConverter>` error: aborting due to 9 previous errors diff --git a/tests/ui/invalid_pyfunction_argument.default.stderr b/tests/ui/invalid_pyfunction_argument.default.stderr index 6c92b898bfc..e0bc3143da9 100644 --- a/tests/ui/invalid_pyfunction_argument.default.stderr +++ b/tests/ui/invalid_pyfunction_argument.default.stderr @@ -17,14 +17,14 @@ error[E0277]: `Atomic<*mut ()>` cannot be used as a Python function argument `(T0, T1, T2, T3, T4, T5)` implements `FromPyObject<'a, 'py>` and $N others = note: required for `Atomic<*mut ()>` to implement `pyo3::impl_::extract_argument::PyFunctionArgument<'_, '_, '_, true>` -note: required by a bound in `pyo3::impl_::extract_argument::extract_argument` +note: required by a bound in `pyo3::impl_::extract_argument::extract_required_argument` --> src/impl_/extract_argument.rs | - | pub fn extract_argument<'a, 'holder, 'py, T, const IMPLEMENTS_FROMPYOBJECT: bool>( - | ---------------- required by a bound in this function + | pub fn extract_required_argument<'a, 'holder, 'py, T, const IMPLEMENTS_FROMPYOBJECT: bool>( + | ------------------------- required by a bound in this function ... | T: PyFunctionArgument<'a, 'holder, 'py, IMPLEMENTS_FROMPYOBJECT>, - | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ required by this bound in `extract_argument` + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ required by this bound in `extract_required_argument` error[E0277]: `Foo` cannot be used as a Python function argument --> tests/ui/invalid_pyfunction_argument.rs:20:40 @@ -50,14 +50,14 @@ help: the trait `FromPyObject<'_, '_>` is not implemented for `Foo` `(T0, T1, T2, T3, T4, T5)` implements `FromPyObject<'a, 'py>` and $N others = note: required for `Foo` to implement `pyo3::impl_::extract_argument::PyFunctionArgument<'_, '_, '_, true>` -note: required by a bound in `pyo3::impl_::extract_argument::extract_argument` +note: required by a bound in `pyo3::impl_::extract_argument::extract_required_argument` --> src/impl_/extract_argument.rs | - | pub fn extract_argument<'a, 'holder, 'py, T, const IMPLEMENTS_FROMPYOBJECT: bool>( - | ---------------- required by a bound in this function + | pub fn extract_required_argument<'a, 'holder, 'py, T, const IMPLEMENTS_FROMPYOBJECT: bool>( + | ------------------------- required by a bound in this function ... | T: PyFunctionArgument<'a, 'holder, 'py, IMPLEMENTS_FROMPYOBJECT>, - | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ required by this bound in `extract_argument` + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ required by this bound in `extract_required_argument` error: aborting due to 2 previous errors diff --git a/tests/ui/invalid_pyfunction_argument.inspect.stderr b/tests/ui/invalid_pyfunction_argument.inspect.stderr index d9d6db27076..97cde38b25c 100644 --- a/tests/ui/invalid_pyfunction_argument.inspect.stderr +++ b/tests/ui/invalid_pyfunction_argument.inspect.stderr @@ -80,14 +80,14 @@ error[E0277]: `Atomic<*mut ()>` cannot be used as a Python function argument `(T0, T1, T2, T3, T4, T5)` implements `FromPyObject<'a, 'py>` and $N others = note: required for `Atomic<*mut ()>` to implement `pyo3::impl_::extract_argument::PyFunctionArgument<'_, '_, '_, true>` -note: required by a bound in `pyo3::impl_::extract_argument::extract_argument` +note: required by a bound in `pyo3::impl_::extract_argument::extract_required_argument` --> src/impl_/extract_argument.rs | - | pub fn extract_argument<'a, 'holder, 'py, T, const IMPLEMENTS_FROMPYOBJECT: bool>( - | ---------------- required by a bound in this function + | pub fn extract_required_argument<'a, 'holder, 'py, T, const IMPLEMENTS_FROMPYOBJECT: bool>( + | ------------------------- required by a bound in this function ... | T: PyFunctionArgument<'a, 'holder, 'py, IMPLEMENTS_FROMPYOBJECT>, - | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ required by this bound in `extract_argument` + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ required by this bound in `extract_required_argument` error[E0277]: `Foo` cannot be used as a Python function argument --> tests/ui/invalid_pyfunction_argument.rs:20:40 @@ -113,14 +113,14 @@ help: the trait `FromPyObject<'_, '_>` is not implemented for `Foo` `(T0, T1, T2, T3, T4, T5)` implements `FromPyObject<'a, 'py>` and $N others = note: required for `Foo` to implement `pyo3::impl_::extract_argument::PyFunctionArgument<'_, '_, '_, true>` -note: required by a bound in `pyo3::impl_::extract_argument::extract_argument` +note: required by a bound in `pyo3::impl_::extract_argument::extract_required_argument` --> src/impl_/extract_argument.rs | - | pub fn extract_argument<'a, 'holder, 'py, T, const IMPLEMENTS_FROMPYOBJECT: bool>( - | ---------------- required by a bound in this function + | pub fn extract_required_argument<'a, 'holder, 'py, T, const IMPLEMENTS_FROMPYOBJECT: bool>( + | ------------------------- required by a bound in this function ... | T: PyFunctionArgument<'a, 'holder, 'py, IMPLEMENTS_FROMPYOBJECT>, - | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ required by this bound in `extract_argument` + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ required by this bound in `extract_required_argument` error: aborting due to 4 previous errors diff --git a/tests/ui/invalid_pymethod_receiver.rs b/tests/ui/invalid_pymethod_receiver.rs index d0836616f54..4b56c39e2b9 100644 --- a/tests/ui/invalid_pymethod_receiver.rs +++ b/tests/ui/invalid_pymethod_receiver.rs @@ -3,10 +3,14 @@ use pyo3::prelude::*; #[pyclass] struct MyClass {} +// Deliberately a local type with no `From` implementations, so that the error output +// doesn't include a candidate list which varies with the enabled features. +struct NotASelfType; + #[pymethods] impl MyClass { - fn method_with_invalid_self_type(_slf: i32, _py: Python<'_>, _index: u32) {} - //~^ ERROR: the trait bound `i32: TryFrom<&pyo3::Bound<'_, MyClass>>` is not satisfied + fn method_with_invalid_self_type(_slf: NotASelfType, _py: Python<'_>, _index: u32) {} + //~^ ERROR: the trait bound `NotASelfType: TryFrom<&pyo3::Bound<'_, MyClass>>` is not satisfied } fn main() {} diff --git a/tests/ui/invalid_pymethod_receiver.stderr b/tests/ui/invalid_pymethod_receiver.stderr index 53c21ee7c20..2acecf05445 100644 --- a/tests/ui/invalid_pymethod_receiver.stderr +++ b/tests/ui/invalid_pymethod_receiver.stderr @@ -1,17 +1,24 @@ -error[E0277]: the trait bound `i32: TryFrom<&pyo3::Bound<'_, MyClass>>` is not satisfied - --> tests/ui/invalid_pymethod_receiver.rs:8:44 +error[E0277]: the trait bound `NotASelfType: TryFrom<&pyo3::Bound<'_, MyClass>>` is not satisfied + --> tests/ui/invalid_pymethod_receiver.rs:12:44 + | + 12 | fn method_with_invalid_self_type(_slf: NotASelfType, _py: Python<'_>, _index: u32) {} + | ^^^^^^^^^^^^ unsatisfied trait bound + | +help: the trait `From<&pyo3::Bound<'_, MyClass>>` is not implemented for `NotASelfType` + --> tests/ui/invalid_pymethod_receiver.rs:8:1 + | + 8 | struct NotASelfType; + | ^^^^^^^^^^^^^^^^^^^ + = note: required for `&pyo3::Bound<'_, MyClass>` to implement `Into` + = note: required for `NotASelfType` to implement `TryFrom<&pyo3::Bound<'_, MyClass>>` +note: required by a bound in `pyo3::impl_::extract_argument::extract_receiver_trusted` + --> src/impl_/extract_argument.rs | -8 | fn method_with_invalid_self_type(_slf: i32, _py: Python<'_>, _index: u32) {} - | ^^^ the trait `From<&pyo3::Bound<'_, MyClass>>` is not implemented for `i32` - | - = help: `i32` implements trait `From`: - From - From - From - From - From - = note: required for `&pyo3::Bound<'_, MyClass>` to implement `Into` - = note: required for `i32` to implement `TryFrom<&pyo3::Bound<'_, MyClass>>` + | pub unsafe fn extract_receiver_trusted<'a, 'py, T, R>( + | ------------------------ required by a bound in this function +... + | R: TryFrom<&'a Bound<'py, T>>, + | ^^^^^^^^^^^^^^^^^^^^^^^^^^ required by this bound in `extract_receiver_trusted` error: aborting due to 1 previous error diff --git a/tests/ui/invalid_result_conversion.stderr b/tests/ui/invalid_result_conversion.stderr index 0717e86ef61..8b6fc4bea53 100644 --- a/tests/ui/invalid_result_conversion.stderr +++ b/tests/ui/invalid_result_conversion.stderr @@ -1,20 +1,27 @@ error[E0277]: the trait bound `PyErr: From` is not satisfied - --> tests/ui/invalid_result_conversion.rs:22:25 - | -22 | fn should_not_work() -> Result<(), MyError> { - | ^^^^^^ the trait `From` is not implemented for `PyErr` - | - = help: `PyErr` implements trait `From`: - From - From> - From> - From - From - From - From> - From - and $N others - = note: required for `MyError` to implement `Into` + --> tests/ui/invalid_result_conversion.rs:22:25 + | + 22 | fn should_not_work() -> Result<(), MyError> { + | ^^^^^^ the trait `From` is not implemented for `PyErr` + | + = help: `PyErr` implements trait `From`: + From + From> + From> + From + From + From + From> + From + and $N others +note: required by a bound in `pyo3::impl_::wrap::IntoPyObjectConverter::>::wrap_into_ptr` + --> src/impl_/wrap.rs + | + | pub fn wrap_into_ptr(&self, py: Python<'py>, obj: Result) -> PyResult<*mut ffi::PyObject> + | ------------- required by a bound in this associated function + | where + | PyErr: From, + | ^^^^^^^ required by this bound in `IntoPyObjectConverter::>::wrap_into_ptr` error: aborting due to 1 previous error diff --git a/tests/ui/missing_intopy.default.stderr b/tests/ui/missing_intopy.default.stderr index 541c1a64b8c..60cafee5782 100644 --- a/tests/ui/missing_intopy.default.stderr +++ b/tests/ui/missing_intopy.default.stderr @@ -22,25 +22,15 @@ help: the trait `IntoPyObject<'_>` is not implemented for `Blah` &'a (T0, T1, T2, T3) &'a (T0, T1, T2, T3, T4) and $N others -note: required by a bound in `pyo3::impl_::wrap::UnknownReturnType::::wrap` +note: required by a bound in `pyo3::impl_::wrap::UnknownReturnType::::wrap_into_ptr` --> src/impl_/wrap.rs | - | pub fn wrap<'py>(&self, _: T) -> T - | ---- required by a bound in this associated function + | pub fn wrap_into_ptr<'py>(&self, _: Python<'py>, _: T) -> PyResult<*mut ffi::PyObject> + | ------------- required by a bound in this associated function | where | T: IntoPyObject<'py>, - | ^^^^^^^^^^^^^^^^^ required by this bound in `UnknownReturnType::::wrap` + | ^^^^^^^^^^^^^^^^^ required by this bound in `UnknownReturnType::::wrap_into_ptr` -error[E0599]: no method named `map_err` found for struct `Blah` in the current scope - --> tests/ui/missing_intopy.rs:8:14 - | -5 | struct Blah; - | ----------- method `map_err` not found for this struct -... -8 | fn blah() -> Blah { - | ^^^^ method not found in `Blah` - -error: aborting due to 2 previous errors +error: aborting due to 1 previous error -Some errors have detailed explanations: E0277, E0599. -For more information about an error, try `rustc --explain E0277`. +For more information about this error, try `rustc --explain E0277`. diff --git a/tests/ui/missing_intopy.inspect.stderr b/tests/ui/missing_intopy.inspect.stderr index aa3ea009914..50c527125d7 100644 --- a/tests/ui/missing_intopy.inspect.stderr +++ b/tests/ui/missing_intopy.inspect.stderr @@ -53,25 +53,15 @@ help: the trait `IntoPyObject<'_>` is not implemented for `Blah` &'a (T0, T1, T2, T3) &'a (T0, T1, T2, T3, T4) and $N others -note: required by a bound in `pyo3::impl_::wrap::UnknownReturnType::::wrap` +note: required by a bound in `pyo3::impl_::wrap::UnknownReturnType::::wrap_into_ptr` --> src/impl_/wrap.rs | - | pub fn wrap<'py>(&self, _: T) -> T - | ---- required by a bound in this associated function + | pub fn wrap_into_ptr<'py>(&self, _: Python<'py>, _: T) -> PyResult<*mut ffi::PyObject> + | ------------- required by a bound in this associated function | where | T: IntoPyObject<'py>, - | ^^^^^^^^^^^^^^^^^ required by this bound in `UnknownReturnType::::wrap` + | ^^^^^^^^^^^^^^^^^ required by this bound in `UnknownReturnType::::wrap_into_ptr` -error[E0599]: no method named `map_err` found for struct `Blah` in the current scope - --> tests/ui/missing_intopy.rs:8:14 - | -5 | struct Blah; - | ----------- method `map_err` not found for this struct -... -8 | fn blah() -> Blah { - | ^^^^ method not found in `Blah` - -error: aborting due to 3 previous errors +error: aborting due to 2 previous errors -Some errors have detailed explanations: E0277, E0599. -For more information about an error, try `rustc --explain E0277`. +For more information about this error, try `rustc --explain E0277`. diff --git a/tests/ui/missing_intopy.rs b/tests/ui/missing_intopy.rs index c60a86b42f6..2a15bfe6c30 100644 --- a/tests/ui/missing_intopy.rs +++ b/tests/ui/missing_intopy.rs @@ -7,7 +7,6 @@ struct Blah; #[pyo3::pyfunction] fn blah() -> Blah { //~^ ERROR: `Blah` cannot be converted to a Python object - //~| ERROR: no method named `map_err` found for struct `Blah` in the current scope //~[inspect]| ERROR: the trait bound `Blah: pyo3::impl_::introspection::return_type::Sealed` is not satisfied Blah }