diff --git a/pyo3-macros-backend/src/deprecations.rs b/pyo3-macros-backend/src/deprecations.rs index 4db40cc8..68375900 100644 --- a/pyo3-macros-backend/src/deprecations.rs +++ b/pyo3-macros-backend/src/deprecations.rs @@ -32,7 +32,7 @@ impl<'ctx> Deprecations<'ctx> { impl<'ctx> ToTokens for Deprecations<'ctx> { fn to_tokens(&self, tokens: &mut TokenStream) { - let Self(deprecations, Ctx { pyo3_path }) = self; + let Self(deprecations, Ctx { pyo3_path, .. }) = self; for (deprecation, span) in deprecations { let pyo3_path = pyo3_path.to_tokens_spanned(*span); diff --git a/pyo3-macros-backend/src/frompyobject.rs b/pyo3-macros-backend/src/frompyobject.rs index 7f26e5b1..d7767174 100644 --- a/pyo3-macros-backend/src/frompyobject.rs +++ b/pyo3-macros-backend/src/frompyobject.rs @@ -45,7 +45,7 @@ impl<'a> Enum<'a> { /// Build derivation body for enums. fn build(&self, ctx: &Ctx) -> (TokenStream, TokenStream) { - let Ctx { pyo3_path } = ctx; + let Ctx { pyo3_path, .. } = ctx; let mut var_extracts = Vec::new(); let mut variant_names = Vec::new(); let mut error_names = Vec::new(); @@ -263,7 +263,7 @@ impl<'a> Container<'a> { from_py_with: &Option, ctx: &Ctx, ) -> (TokenStream, TokenStream) { - let Ctx { pyo3_path } = ctx; + let Ctx { pyo3_path, .. } = ctx; let self_ty = &self.path; let struct_name = self.name(); if let Some(ident) = field_ident { @@ -329,7 +329,7 @@ impl<'a> Container<'a> { struct_fields: &[TupleStructField], ctx: &Ctx, ) -> (TokenStream, TokenStream) { - let Ctx { pyo3_path } = ctx; + let Ctx { pyo3_path, .. } = ctx; let self_ty = &self.path; let struct_name = &self.name(); let field_idents: Vec<_> = (0..struct_fields.len()) @@ -382,7 +382,7 @@ impl<'a> Container<'a> { struct_fields: &[NamedStructField<'_>], ctx: &Ctx, ) -> (TokenStream, TokenStream) { - let Ctx { pyo3_path } = ctx; + let Ctx { pyo3_path, .. } = ctx; let self_ty = &self.path; let struct_name = &self.name(); let mut fields: Punctuated = Punctuated::new(); @@ -670,8 +670,8 @@ pub fn build_derive_from_pyobject(tokens: &DeriveInput) -> Result { .push(parse_quote!(#gen_ident: FromPyObject<#lt_param>)) } let options = ContainerOptions::from_attrs(&tokens.attrs)?; - let ctx = &Ctx::new(&options.krate); - let Ctx { pyo3_path } = &ctx; + let ctx = &Ctx::new(&options.krate, None); + let Ctx { pyo3_path, .. } = &ctx; let (derives, from_py_with_deprecations) = match &tokens.data { syn::Data::Enum(en) => { diff --git a/pyo3-macros-backend/src/konst.rs b/pyo3-macros-backend/src/konst.rs index e7d8d554..2a7667dd 100644 --- a/pyo3-macros-backend/src/konst.rs +++ b/pyo3-macros-backend/src/konst.rs @@ -30,7 +30,7 @@ impl ConstSpec<'_> { /// Null-terminated Python name pub fn null_terminated_python_name(&self, ctx: &Ctx) -> TokenStream { - let Ctx { pyo3_path } = ctx; + let Ctx { pyo3_path, .. } = ctx; let name = self.python_name().to_string(); quote!(#pyo3_path::ffi::c_str!(#name)) } diff --git a/pyo3-macros-backend/src/method.rs b/pyo3-macros-backend/src/method.rs index 74542637..294cd4db 100644 --- a/pyo3-macros-backend/src/method.rs +++ b/pyo3-macros-backend/src/method.rs @@ -224,7 +224,7 @@ impl FnType { holders: &mut Holders, ctx: &Ctx, ) -> TokenStream { - let Ctx { pyo3_path } = ctx; + let Ctx { pyo3_path, .. } = ctx; match self { FnType::Getter(st) | FnType::Setter(st) | FnType::Fn(st) => { let mut receiver = st.receiver( @@ -281,7 +281,7 @@ pub enum ExtractErrorMode { impl ExtractErrorMode { pub fn handle_error(self, extract: TokenStream, ctx: &Ctx) -> TokenStream { - let Ctx { pyo3_path } = ctx; + let Ctx { pyo3_path, .. } = ctx; match self { ExtractErrorMode::Raise => quote! { #extract? }, ExtractErrorMode::NotImplemented => quote! { @@ -306,7 +306,7 @@ impl SelfType { // main macro callsite. let py = syn::Ident::new("py", Span::call_site()); let slf = syn::Ident::new("_slf", Span::call_site()); - let Ctx { pyo3_path } = ctx; + let Ctx { pyo3_path, .. } = ctx; match self { SelfType::Receiver { span, mutable } => { let method = if *mutable { @@ -473,7 +473,7 @@ impl<'a> FnSpec<'a> { } pub fn null_terminated_python_name(&self, ctx: &Ctx) -> TokenStream { - let Ctx { pyo3_path } = ctx; + let Ctx { pyo3_path, .. } = ctx; let span = self.python_name.span(); let pyo3_path = pyo3_path.to_tokens_spanned(span); let name = self.python_name.to_string(); @@ -600,7 +600,10 @@ impl<'a> FnSpec<'a> { cls: Option<&syn::Type>, ctx: &Ctx, ) -> Result { - let Ctx { pyo3_path } = ctx; + let Ctx { + pyo3_path, + output_span, + } = ctx; let mut cancel_handle_iter = self .signature .arguments @@ -703,7 +706,18 @@ impl<'a> FnSpec<'a> { } } }; - quotes::map_result_into_ptr(quotes::ok_wrap(call, ctx), ctx) + + // We must assign the output_span to the return value of the call, + // but *not* of the call itself otherwise the spans get really weird + let ret_expr = quote! { let ret = #call; }; + let ret_var = quote_spanned! {*output_span=> ret }; + let return_conversion = quotes::map_result_into_ptr(quotes::ok_wrap(ret_var, ctx), ctx); + quote! { + { + #ret_expr + #return_conversion + } + } }; let func_name = &self.name; @@ -731,7 +745,6 @@ impl<'a> FnSpec<'a> { let call = rust_call(args, &mut holders); let check_gil_refs = holders.check_gil_refs(); let init_holders = holders.init_holders(ctx); - quote! { unsafe fn #ident<'py>( py: #pyo3_path::Python<'py>, @@ -804,7 +817,7 @@ impl<'a> FnSpec<'a> { let self_arg = self .tp .self_arg(cls, ExtractErrorMode::Raise, &mut holders, ctx); - let call = quote! { #rust_name(#self_arg #(#args),*) }; + let call = quote_spanned! {*output_span=> #rust_name(#self_arg #(#args),*) }; let init_holders = holders.init_holders(ctx); let check_gil_refs = holders.check_gil_refs(); quote! { @@ -833,7 +846,7 @@ impl<'a> FnSpec<'a> { /// Return a `PyMethodDef` constructor for this function, matching the selected /// calling convention. pub fn get_methoddef(&self, wrapper: impl ToTokens, doc: &PythonDoc, ctx: &Ctx) -> TokenStream { - let Ctx { pyo3_path } = ctx; + let Ctx { pyo3_path, .. } = ctx; let python_name = self.null_terminated_python_name(ctx); match self.convention { CallingConvention::Noargs => quote! { diff --git a/pyo3-macros-backend/src/module.rs b/pyo3-macros-backend/src/module.rs index 0383046e..3443507b 100644 --- a/pyo3-macros-backend/src/module.rs +++ b/pyo3-macros-backend/src/module.rs @@ -90,8 +90,8 @@ pub fn pymodule_module_impl(mut module: syn::ItemMod) -> Result { bail_spanned!(module.span() => "`#[pymodule]` can only be used on inline modules") }; let options = PyModuleOptions::from_attrs(attrs)?; - let ctx = &Ctx::new(&options.krate); - let Ctx { pyo3_path } = ctx; + let ctx = &Ctx::new(&options.krate, None); + let Ctx { pyo3_path, .. } = ctx; let doc = get_doc(attrs, None, ctx); let name = options.name.unwrap_or_else(|| ident.unraw()); let full_name = if let Some(module) = &options.module { @@ -326,9 +326,9 @@ pub fn pymodule_module_impl(mut module: syn::ItemMod) -> Result { pub fn pymodule_function_impl(mut function: syn::ItemFn) -> Result { let options = PyModuleOptions::from_attrs(&mut function.attrs)?; process_functions_in_module(&options, &mut function)?; - let ctx = &Ctx::new(&options.krate); + let ctx = &Ctx::new(&options.krate, None); let stmts = std::mem::take(&mut function.block.stmts); - let Ctx { pyo3_path } = ctx; + let Ctx { pyo3_path, .. } = ctx; let ident = &function.sig.ident; let name = options.name.unwrap_or_else(|| ident.unraw()); let vis = &function.vis; @@ -400,7 +400,7 @@ pub fn pymodule_function_impl(mut function: syn::ItemFn) -> Result } fn module_initialization(name: &syn::Ident, ctx: &Ctx) -> TokenStream { - let Ctx { pyo3_path } = ctx; + let Ctx { pyo3_path, .. } = ctx; let pyinit_symbol = format!("PyInit_{}", name); let name = name.to_string(); @@ -424,8 +424,8 @@ fn module_initialization(name: &syn::Ident, ctx: &Ctx) -> TokenStream { /// Finds and takes care of the #[pyfn(...)] in `#[pymodule]` fn process_functions_in_module(options: &PyModuleOptions, func: &mut syn::ItemFn) -> Result<()> { - let ctx = &Ctx::new(&options.krate); - let Ctx { pyo3_path } = ctx; + let ctx = &Ctx::new(&options.krate, None); + let Ctx { pyo3_path, .. } = ctx; let mut stmts: Vec = Vec::new(); #[cfg(feature = "gil-refs")] diff --git a/pyo3-macros-backend/src/params.rs b/pyo3-macros-backend/src/params.rs index cab9d2a7..f7d71b92 100644 --- a/pyo3-macros-backend/src/params.rs +++ b/pyo3-macros-backend/src/params.rs @@ -48,7 +48,7 @@ impl Holders { } pub fn init_holders(&self, ctx: &Ctx) -> TokenStream { - let Ctx { pyo3_path } = ctx; + let Ctx { pyo3_path, .. } = ctx; let holders = &self.holders; let gil_refs_checkers = self.gil_refs_checkers.iter().map(|checker| match checker { GilRefChecker::FunctionArg(ident) => ident, @@ -94,7 +94,7 @@ pub(crate) fn check_arg_for_gil_refs( gil_refs_checker: syn::Ident, ctx: &Ctx, ) -> TokenStream { - let Ctx { pyo3_path } = ctx; + let Ctx { pyo3_path, .. } = ctx; quote! { #pyo3_path::impl_::deprecations::inspect_type(#tokens, &#gil_refs_checker) } @@ -108,7 +108,7 @@ pub fn impl_arg_params( ctx: &Ctx, ) -> (TokenStream, Vec) { let args_array = syn::Ident::new("output", Span::call_site()); - let Ctx { pyo3_path } = ctx; + let Ctx { pyo3_path, .. } = ctx; let from_py_with = spec .signature @@ -242,7 +242,7 @@ fn impl_arg_param( holders: &mut Holders, ctx: &Ctx, ) -> TokenStream { - let Ctx { pyo3_path } = ctx; + let Ctx { pyo3_path, .. } = ctx; let args_array = syn::Ident::new("output", Span::call_site()); match arg { @@ -290,7 +290,7 @@ pub(crate) fn impl_regular_arg_param( holders: &mut Holders, ctx: &Ctx, ) -> TokenStream { - let Ctx { pyo3_path } = ctx; + let Ctx { pyo3_path, .. } = ctx; 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 diff --git a/pyo3-macros-backend/src/pyclass.rs b/pyo3-macros-backend/src/pyclass.rs index 3e40977e..85732ae5 100644 --- a/pyo3-macros-backend/src/pyclass.rs +++ b/pyo3-macros-backend/src/pyclass.rs @@ -227,7 +227,7 @@ pub fn build_py_class( ) -> syn::Result { args.options.take_pyo3_options(&mut class.attrs)?; - let ctx = &Ctx::new(&args.options.krate); + let ctx = &Ctx::new(&args.options.krate, None); let doc = utils::get_doc(&class.attrs, None, ctx); if let Some(lt) = class.generics.lifetimes().next() { @@ -383,7 +383,7 @@ fn impl_class( methods_type: PyClassMethodsType, ctx: &Ctx, ) -> syn::Result { - let Ctx { pyo3_path } = ctx; + let Ctx { pyo3_path, .. } = ctx; let pytypeinfo_impl = impl_pytypeinfo(cls, args, None, ctx); let (default_richcmp, default_richcmp_slot) = @@ -457,7 +457,7 @@ pub fn build_py_enum( ) -> syn::Result { args.options.take_pyo3_options(&mut enum_.attrs)?; - let ctx = &Ctx::new(&args.options.krate); + let ctx = &Ctx::new(&args.options.krate, None); if let Some(extends) = &args.options.extends { bail_spanned!(extends.span() => "enums can't extend from other classes"); } else if let Some(subclass) = &args.options.subclass { @@ -872,7 +872,7 @@ fn impl_complex_enum( methods_type: PyClassMethodsType, ctx: &Ctx, ) -> Result { - let Ctx { pyo3_path } = ctx; + let Ctx { pyo3_path, .. } = ctx; let cls = complex_enum.ident; let ty: syn::Type = syn::parse_quote!(#cls); @@ -886,7 +886,7 @@ fn impl_complex_enum( rigged_args }; - let ctx = &Ctx::new(&args.options.krate); + let ctx = &Ctx::new(&args.options.krate, None); let cls = complex_enum.ident; let variants = complex_enum.variants; let pytypeinfo = impl_pytypeinfo(cls, &args, None, ctx); @@ -1071,7 +1071,7 @@ fn impl_complex_enum_struct_variant_cls( variant: &PyClassEnumStructVariant<'_>, ctx: &Ctx, ) -> Result<(TokenStream, Vec, Vec)> { - let Ctx { pyo3_path } = ctx; + let Ctx { pyo3_path, .. } = ctx; let variant_ident = &variant.ident; let variant_cls = gen_complex_enum_variant_class_ident(enum_name, variant.ident); let variant_cls_type = parse_quote!(#variant_cls); @@ -1135,7 +1135,7 @@ fn impl_complex_enum_tuple_variant_field_getters( field_names: &mut Vec, fields_types: &mut Vec, ) -> Result<(Vec, Vec)> { - let Ctx { pyo3_path } = ctx; + let Ctx { pyo3_path, .. } = ctx; let mut field_getters = vec![]; let mut field_getter_impls = vec![]; @@ -1182,7 +1182,7 @@ fn impl_complex_enum_tuple_variant_len( variant_cls_type: &syn::Type, num_fields: usize, ) -> Result<(MethodAndSlotDef, syn::ImplItemFn)> { - let Ctx { pyo3_path } = ctx; + let Ctx { pyo3_path, .. } = ctx; let mut len_method_impl: syn::ImplItemFn = parse_quote! { fn __len__(slf: #pyo3_path::PyRef) -> #pyo3_path::PyResult { @@ -1202,7 +1202,7 @@ fn impl_complex_enum_tuple_variant_getitem( variant_cls_type: &syn::Type, num_fields: usize, ) -> Result<(MethodAndSlotDef, syn::ImplItemFn)> { - let Ctx { pyo3_path } = ctx; + let Ctx { pyo3_path, .. } = ctx; let match_arms: Vec<_> = (0..num_fields) .map(|i| { @@ -1243,7 +1243,7 @@ fn impl_complex_enum_tuple_variant_cls( variant: &PyClassEnumTupleVariant<'_>, ctx: &Ctx, ) -> Result<(TokenStream, Vec, Vec)> { - let Ctx { pyo3_path } = ctx; + let Ctx { pyo3_path, .. } = ctx; let variant_ident = &variant.ident; let variant_cls = gen_complex_enum_variant_class_ident(enum_name, variant.ident); let variant_cls_type = parse_quote!(#variant_cls); @@ -1400,7 +1400,7 @@ pub fn gen_complex_enum_variant_attr( spec: &ConstSpec<'_>, ctx: &Ctx, ) -> MethodAndMethodDef { - let Ctx { pyo3_path } = ctx; + let Ctx { pyo3_path, .. } = ctx; let member = &spec.rust_ident; let wrapper_ident = format_ident!("__pymethod_variant_cls_{}__", member); let deprecations = &spec.attributes.deprecations; @@ -1449,7 +1449,7 @@ fn complex_enum_struct_variant_new<'a>( variant: PyClassEnumStructVariant<'a>, ctx: &Ctx, ) -> Result { - let Ctx { pyo3_path } = ctx; + let Ctx { pyo3_path, .. } = ctx; let variant_cls = format_ident!("{}_{}", cls, variant.ident); let variant_cls_type: syn::Type = parse_quote!(#variant_cls); @@ -1506,7 +1506,7 @@ fn complex_enum_tuple_variant_new<'a>( variant: PyClassEnumTupleVariant<'a>, ctx: &Ctx, ) -> Result { - let Ctx { pyo3_path } = ctx; + let Ctx { pyo3_path, .. } = ctx; let variant_cls: Ident = format_ident!("{}_{}", cls, variant.ident); let variant_cls_type: syn::Type = parse_quote!(#variant_cls); @@ -1645,7 +1645,7 @@ fn impl_pytypeinfo( deprecations: Option<&Deprecations<'_>>, ctx: &Ctx, ) -> TokenStream { - let Ctx { pyo3_path } = ctx; + let Ctx { pyo3_path, .. } = ctx; let cls_name = get_class_python_name(cls, attr).to_string(); let module = if let Some(ModuleAttribute { value, .. }) = &attr.options.module { @@ -1689,7 +1689,7 @@ fn pyclass_richcmp_arms( options: &PyClassPyO3Options, ctx: &Ctx, ) -> std::result::Result { - let Ctx { pyo3_path } = ctx; + let Ctx { pyo3_path, .. } = ctx; let eq_arms = options .eq @@ -1743,7 +1743,7 @@ fn pyclass_richcmp_simple_enum( repr_type: &syn::Ident, ctx: &Ctx, ) -> Result<(Option, Option)> { - let Ctx { pyo3_path } = ctx; + let Ctx { pyo3_path, .. } = ctx; if let Some(eq_int) = options.eq_int { ensure_spanned!(options.eq.is_some(), eq_int.span() => "The `eq_int` option requires the `eq` option."); @@ -1827,7 +1827,7 @@ fn pyclass_richcmp( cls: &syn::Type, ctx: &Ctx, ) -> Result<(Option, Option)> { - let Ctx { pyo3_path } = ctx; + let Ctx { pyo3_path, .. } = ctx; if let Some(eq_int) = options.eq_int { bail_spanned!(eq_int.span() => "`eq_int` can only be used on simple enums.") } @@ -1940,7 +1940,7 @@ impl<'a> PyClassImplsBuilder<'a> { } fn impl_pyclass(&self, ctx: &Ctx) -> TokenStream { - let Ctx { pyo3_path } = ctx; + let Ctx { pyo3_path, .. } = ctx; let cls = self.cls; let frozen = if self.attr.options.frozen.is_some() { @@ -1956,7 +1956,7 @@ impl<'a> PyClassImplsBuilder<'a> { } } fn impl_extractext(&self, ctx: &Ctx) -> TokenStream { - let Ctx { pyo3_path } = ctx; + let Ctx { pyo3_path, .. } = ctx; let cls = self.cls; if self.attr.options.frozen.is_some() { quote! { @@ -1996,7 +1996,7 @@ impl<'a> PyClassImplsBuilder<'a> { } fn impl_into_py(&self, ctx: &Ctx) -> TokenStream { - let Ctx { pyo3_path } = ctx; + let Ctx { pyo3_path, .. } = ctx; let cls = self.cls; let attr = self.attr; // If #cls is not extended type, we allow Self->PyObject conversion @@ -2013,7 +2013,7 @@ impl<'a> PyClassImplsBuilder<'a> { } } fn impl_pyclassimpl(&self, ctx: &Ctx) -> Result { - let Ctx { pyo3_path } = ctx; + let Ctx { pyo3_path, .. } = ctx; let cls = self.cls; let doc = self.doc.as_ref().map_or( quote! {#pyo3_path::ffi::c_str!("")}, @@ -2184,7 +2184,7 @@ impl<'a> PyClassImplsBuilder<'a> { } fn impl_add_to_module(&self, ctx: &Ctx) -> TokenStream { - let Ctx { pyo3_path } = ctx; + let Ctx { pyo3_path, .. } = ctx; let cls = self.cls; quote! { impl #cls { @@ -2196,7 +2196,7 @@ impl<'a> PyClassImplsBuilder<'a> { fn impl_freelist(&self, ctx: &Ctx) -> TokenStream { let cls = self.cls; - let Ctx { pyo3_path } = ctx; + let Ctx { pyo3_path, .. } = ctx; self.attr.options.freelist.as_ref().map_or(quote!{}, |freelist| { let freelist = &freelist.value; @@ -2219,7 +2219,7 @@ impl<'a> PyClassImplsBuilder<'a> { } fn freelist_slots(&self, ctx: &Ctx) -> Vec { - let Ctx { pyo3_path } = ctx; + let Ctx { pyo3_path, .. } = ctx; let cls = self.cls; if self.attr.options.freelist.is_some() { @@ -2244,7 +2244,7 @@ impl<'a> PyClassImplsBuilder<'a> { } fn define_inventory_class(inventory_class_name: &syn::Ident, ctx: &Ctx) -> TokenStream { - let Ctx { pyo3_path } = ctx; + let Ctx { pyo3_path, .. } = ctx; quote! { #[doc(hidden)] pub struct #inventory_class_name { diff --git a/pyo3-macros-backend/src/pyfunction.rs b/pyo3-macros-backend/src/pyfunction.rs index 147193d1..25f0d5b3 100644 --- a/pyo3-macros-backend/src/pyfunction.rs +++ b/pyo3-macros-backend/src/pyfunction.rs @@ -205,8 +205,8 @@ pub fn impl_wrap_pyfunction( krate, } = options; - let ctx = &Ctx::new(&krate); - let Ctx { pyo3_path } = &ctx; + let ctx = &Ctx::new(&krate, Some(&func.sig)); + let Ctx { pyo3_path, .. } = &ctx; let python_name = name .as_ref() diff --git a/pyo3-macros-backend/src/pyimpl.rs b/pyo3-macros-backend/src/pyimpl.rs index a1242d49..dbb1fba8 100644 --- a/pyo3-macros-backend/src/pyimpl.rs +++ b/pyo3-macros-backend/src/pyimpl.rs @@ -90,7 +90,6 @@ pub fn impl_methods( methods_type: PyClassMethodsType, options: PyImplOptions, ) -> syn::Result { - let ctx = &Ctx::new(&options.krate); let mut trait_impls = Vec::new(); let mut proto_impls = Vec::new(); let mut methods = Vec::new(); @@ -101,6 +100,7 @@ pub fn impl_methods( for iimpl in impls { match iimpl { syn::ImplItem::Fn(meth) => { + let ctx = &Ctx::new(&options.krate, Some(&meth.sig)); let mut fun_options = PyFunctionOptions::from_attrs(&mut meth.attrs)?; fun_options.krate = fun_options.krate.or_else(|| options.krate.clone()); match pymethod::gen_py_method(ty, &mut meth.sig, &mut meth.attrs, fun_options, ctx)? @@ -129,6 +129,7 @@ pub fn impl_methods( } } syn::ImplItem::Const(konst) => { + let ctx = &Ctx::new(&options.krate, None); let attributes = ConstAttributes::from_attrs(&mut konst.attrs, ctx)?; if attributes.is_class_attr { let spec = ConstSpec { @@ -159,11 +160,10 @@ pub fn impl_methods( _ => {} } } + let ctx = &Ctx::new(&options.krate, None); add_shared_proto_slots(ty, &mut proto_impls, implemented_proto_fragments, ctx); - let ctx = &Ctx::new(&options.krate); - let items = match methods_type { PyClassMethodsType::Specialization => impl_py_methods(ty, methods, proto_impls, ctx), PyClassMethodsType::Inventory => submit_methods_inventory(ty, methods, proto_impls, ctx), @@ -187,7 +187,7 @@ pub fn gen_py_const(cls: &syn::Type, spec: &ConstSpec<'_>, ctx: &Ctx) -> MethodA let wrapper_ident = format_ident!("__pymethod_{}__", member); let deprecations = &spec.attributes.deprecations; let python_name = spec.null_terminated_python_name(ctx); - let Ctx { pyo3_path } = ctx; + let Ctx { pyo3_path, .. } = ctx; let associated_method = quote! { fn #wrapper_ident(py: #pyo3_path::Python<'_>) -> #pyo3_path::PyResult<#pyo3_path::PyObject> { @@ -217,7 +217,7 @@ fn impl_py_methods( proto_impls: Vec, ctx: &Ctx, ) -> TokenStream { - let Ctx { pyo3_path } = ctx; + let Ctx { pyo3_path, .. } = ctx; quote! { #[allow(unknown_lints, non_local_definitions)] impl #pyo3_path::impl_::pyclass::PyMethods<#ty> @@ -240,7 +240,7 @@ fn add_shared_proto_slots( mut implemented_proto_fragments: HashSet, ctx: &Ctx, ) { - let Ctx { pyo3_path } = ctx; + let Ctx { pyo3_path, .. } = ctx; macro_rules! try_add_shared_slot { ($slot:ident, $($fragments:literal),*) => {{ let mut implemented = false; @@ -298,7 +298,7 @@ fn submit_methods_inventory( proto_impls: Vec, ctx: &Ctx, ) -> TokenStream { - let Ctx { pyo3_path } = ctx; + let Ctx { pyo3_path, .. } = ctx; quote! { #pyo3_path::inventory::submit! { type Inventory = <#ty as #pyo3_path::impl_::pyclass::PyClassImpl>::Inventory; diff --git a/pyo3-macros-backend/src/pymethod.rs b/pyo3-macros-backend/src/pymethod.rs index 735b55a1..05947524 100644 --- a/pyo3-macros-backend/src/pymethod.rs +++ b/pyo3-macros-backend/src/pymethod.rs @@ -196,7 +196,7 @@ pub fn gen_py_method( ensure_function_options_valid(&options)?; let method = PyMethod::parse(sig, meth_attrs, options, ctx)?; let spec = &method.spec; - let Ctx { pyo3_path } = ctx; + let Ctx { pyo3_path, .. } = ctx; Ok(match (method.kind, &spec.tp) { // Class attributes go before protos so that class attributes can be used to set proto @@ -318,7 +318,7 @@ pub fn impl_py_method_def( flags: Option, ctx: &Ctx, ) -> Result { - let Ctx { pyo3_path } = ctx; + let Ctx { pyo3_path, .. } = ctx; let wrapper_ident = format_ident!("__pymethod_{}__", spec.python_name); let associated_method = spec.get_wrapper_function(&wrapper_ident, Some(cls), ctx)?; let add_flags = flags.map(|flags| quote!(.flags(#flags))); @@ -343,7 +343,7 @@ pub fn impl_py_method_def_new( spec: &FnSpec<'_>, ctx: &Ctx, ) -> Result { - let Ctx { pyo3_path } = ctx; + let Ctx { pyo3_path, .. } = ctx; let wrapper_ident = syn::Ident::new("__pymethod___new____", Span::call_site()); let associated_method = spec.get_wrapper_function(&wrapper_ident, Some(cls), ctx)?; // Use just the text_signature_call_signature() because the class' Python name @@ -393,7 +393,7 @@ pub fn impl_py_method_def_new( } fn impl_call_slot(cls: &syn::Type, mut spec: FnSpec<'_>, ctx: &Ctx) -> Result { - let Ctx { pyo3_path } = ctx; + let Ctx { pyo3_path, .. } = ctx; // HACK: __call__ proto slot must always use varargs calling convention, so change the spec. // Probably indicates there's a refactoring opportunity somewhere. @@ -433,7 +433,7 @@ fn impl_traverse_slot( spec: &FnSpec<'_>, ctx: &Ctx, ) -> syn::Result { - let Ctx { pyo3_path } = ctx; + let Ctx { pyo3_path, .. } = ctx; if let (Some(py_arg), _) = split_off_python_arg(&spec.signature.arguments) { return Err(syn::Error::new_spanned(py_arg.ty, "__traverse__ may not take `Python`. \ Usually, an implementation of `__traverse__(&self, visit: PyVisit<'_>) -> Result<(), PyTraverseError>` \ @@ -484,7 +484,7 @@ fn impl_py_class_attribute( spec: &FnSpec<'_>, ctx: &Ctx, ) -> syn::Result { - let Ctx { pyo3_path } = ctx; + let Ctx { pyo3_path, .. } = ctx; let (py_arg, args) = split_off_python_arg(&spec.signature.arguments); ensure_spanned!( args.is_empty(), @@ -559,7 +559,7 @@ pub fn impl_py_setter_def( property_type: PropertyType<'_>, ctx: &Ctx, ) -> Result { - let Ctx { pyo3_path } = ctx; + let Ctx { pyo3_path, .. } = ctx; let python_name = property_type.null_terminated_python_name(ctx)?; let doc = property_type.doc(ctx); let mut holders = Holders::new(); @@ -745,7 +745,7 @@ pub fn impl_py_getter_def( property_type: PropertyType<'_>, ctx: &Ctx, ) -> Result { - let Ctx { pyo3_path } = ctx; + let Ctx { pyo3_path, .. } = ctx; let python_name = property_type.null_terminated_python_name(ctx)?; let doc = property_type.doc(ctx); @@ -871,7 +871,7 @@ pub enum PropertyType<'a> { impl PropertyType<'_> { fn null_terminated_python_name(&self, ctx: &Ctx) -> Result { - let Ctx { pyo3_path } = ctx; + let Ctx { pyo3_path, .. } = ctx; match self { PropertyType::Descriptor { field, @@ -913,7 +913,7 @@ pub const __REPR__: SlotDef = SlotDef::new("Py_tp_repr", "reprfunc"); pub const __HASH__: SlotDef = SlotDef::new("Py_tp_hash", "hashfunc") .ret_ty(Ty::PyHashT) .return_conversion(TokenGenerator( - |Ctx { pyo3_path }: &Ctx| quote! { #pyo3_path::callback::HashCallbackOutput }, + |Ctx { pyo3_path, .. }: &Ctx| quote! { #pyo3_path::callback::HashCallbackOutput }, )); pub const __RICHCMP__: SlotDef = SlotDef::new("Py_tp_richcompare", "richcmpfunc") .extract_error_mode(ExtractErrorMode::NotImplemented) @@ -1036,7 +1036,11 @@ enum Ty { impl Ty { fn ffi_type(self, ctx: &Ctx) -> TokenStream { - let Ctx { pyo3_path } = ctx; + let Ctx { + pyo3_path, + output_span, + } = ctx; + let pyo3_path = pyo3_path.to_tokens_spanned(*output_span); match self { Ty::Object | Ty::MaybeNullObject => quote! { *mut #pyo3_path::ffi::PyObject }, Ty::NonNullObject => quote! { ::std::ptr::NonNull<#pyo3_path::ffi::PyObject> }, @@ -1057,7 +1061,7 @@ impl Ty { holders: &mut Holders, ctx: &Ctx, ) -> TokenStream { - let Ctx { pyo3_path } = ctx; + let Ctx { pyo3_path, .. } = ctx; match self { Ty::Object => extract_object( extract_error_mode, @@ -1122,7 +1126,7 @@ fn extract_object( source_ptr: TokenStream, ctx: &Ctx, ) -> TokenStream { - let Ctx { pyo3_path } = ctx; + let Ctx { pyo3_path, .. } = ctx; let gil_refs_checker = holders.push_gil_refs_checker(arg.ty().span()); let name = arg.name().unraw().to_string(); @@ -1162,7 +1166,7 @@ enum ReturnMode { impl ReturnMode { fn return_call_output(&self, call: TokenStream, ctx: &Ctx, holders: &Holders) -> TokenStream { - let Ctx { pyo3_path } = ctx; + let Ctx { pyo3_path, .. } = ctx; let check_gil_refs = holders.check_gil_refs(); match self { ReturnMode::Conversion(conversion) => { @@ -1265,7 +1269,7 @@ impl SlotDef { method_name: &str, ctx: &Ctx, ) -> Result { - let Ctx { pyo3_path } = ctx; + let Ctx { pyo3_path, .. } = ctx; let SlotDef { slot, func_ty, @@ -1345,7 +1349,7 @@ fn generate_method_body( return_mode: Option<&ReturnMode>, ctx: &Ctx, ) -> Result { - let Ctx { pyo3_path } = ctx; + let Ctx { pyo3_path, .. } = ctx; let self_arg = spec .tp .self_arg(Some(cls), extract_error_mode, holders, ctx); @@ -1397,7 +1401,7 @@ impl SlotFragmentDef { spec: &FnSpec<'_>, ctx: &Ctx, ) -> Result { - let Ctx { pyo3_path } = ctx; + let Ctx { pyo3_path, .. } = ctx; let SlotFragmentDef { fragment, arguments, diff --git a/pyo3-macros-backend/src/quotes.rs b/pyo3-macros-backend/src/quotes.rs index ceef23fb..6f6f64da 100644 --- a/pyo3-macros-backend/src/quotes.rs +++ b/pyo3-macros-backend/src/quotes.rs @@ -1,23 +1,31 @@ use crate::utils::Ctx; use proc_macro2::TokenStream; -use quote::quote; +use quote::{quote, quote_spanned}; pub(crate) fn some_wrap(obj: TokenStream, ctx: &Ctx) -> TokenStream { - let Ctx { pyo3_path } = ctx; + let Ctx { pyo3_path, .. } = ctx; quote! { #pyo3_path::impl_::wrap::SomeWrap::wrap(#obj) } } pub(crate) fn ok_wrap(obj: TokenStream, ctx: &Ctx) -> TokenStream { - let Ctx { pyo3_path } = ctx; - quote! { + let Ctx { + pyo3_path, + output_span, + } = ctx; + let pyo3_path = pyo3_path.to_tokens_spanned(*output_span); + quote_spanned! {*output_span=> #pyo3_path::impl_::wrap::OkWrap::wrap(#obj) .map_err(::core::convert::Into::<#pyo3_path::PyErr>::into) } } pub(crate) fn map_result_into_ptr(result: TokenStream, ctx: &Ctx) -> TokenStream { - let Ctx { pyo3_path } = ctx; - quote! { #pyo3_path::impl_::wrap::map_result_into_ptr(py, #result) } + let Ctx { + pyo3_path, + output_span, + } = ctx; + let pyo3_path = pyo3_path.to_tokens_spanned(*output_span); + quote_spanned! {*output_span=> #pyo3_path::impl_::wrap::map_result_into_ptr(py, #result) } } diff --git a/pyo3-macros-backend/src/utils.rs b/pyo3-macros-backend/src/utils.rs index 1586379a..22b16010 100644 --- a/pyo3-macros-backend/src/utils.rs +++ b/pyo3-macros-backend/src/utils.rs @@ -1,9 +1,9 @@ +use crate::attributes::{CrateAttribute, RenamingRule}; use proc_macro2::{Span, TokenStream}; use quote::{quote, ToTokens}; +use syn::spanned::Spanned; use syn::{punctuated::Punctuated, Token}; -use crate::attributes::{CrateAttribute, RenamingRule}; - /// Macro inspired by `anyhow::anyhow!` to create a compiler error with the given span. macro_rules! err_spanned { ($span:expr => $msg:expr) => { @@ -86,7 +86,7 @@ pub fn get_doc( mut text_signature: Option, ctx: &Ctx, ) -> PythonDoc { - let Ctx { pyo3_path } = ctx; + let Ctx { pyo3_path, .. } = ctx; // insert special divider between `__text_signature__` and doc // (assume text_signature is itself well-formed) if let Some(text_signature) = &mut text_signature { @@ -162,17 +162,35 @@ pub fn unwrap_ty_group(mut ty: &syn::Type) -> &syn::Type { } pub struct Ctx { + /// Where we can find the pyo3 crate pub pyo3_path: PyO3CratePath, + + /// If we are in a pymethod or pyfunction, + /// this will be the span of the return type + pub output_span: Span, } impl Ctx { - pub(crate) fn new(attr: &Option) -> Self { + pub(crate) fn new(attr: &Option, signature: Option<&syn::Signature>) -> Self { let pyo3_path = match attr { Some(attr) => PyO3CratePath::Given(attr.value.0.clone()), None => PyO3CratePath::Default, }; - Self { pyo3_path } + let output_span = if let Some(syn::Signature { + output: syn::ReturnType::Type(_, output_type), + .. + }) = &signature + { + output_type.span() + } else { + Span::call_site() + }; + + Self { + pyo3_path, + output_span, + } } } diff --git a/src/conversion.rs b/src/conversion.rs index 44dbc3c7..6a089e18 100644 --- a/src/conversion.rs +++ b/src/conversion.rs @@ -152,7 +152,15 @@ pub trait ToPyObject { /// # } /// ``` /// Python code will see this as any of the `int`, `string` or `None` objects. -#[doc(alias = "IntoPyCallbackOutput")] +#[cfg_attr( + diagnostic_namespace, + diagnostic::on_unimplemented( + message = "`{Self}` cannot be converted to a Python object", + note = "`IntoPy` is automatically implemented by the `#[pyclass]` macro", + note = "if you do not wish to have a corresponding Python type, implement it manually", + note = "if you do not own `{Self}` you can perform a manual conversion to one of the types in `pyo3::types::*`" + ) +)] pub trait IntoPy: Sized { /// Performs the conversion. fn into_py(self, py: Python<'_>) -> T; diff --git a/src/impl_/wrap.rs b/src/impl_/wrap.rs index 2110d841..b6a6a4a8 100644 --- a/src/impl_/wrap.rs +++ b/src/impl_/wrap.rs @@ -20,6 +20,15 @@ impl SomeWrap for Option { } /// Used to wrap the result of `#[pyfunction]` and `#[pymethods]`. +#[cfg_attr( + diagnostic_namespace, + diagnostic::on_unimplemented( + message = "`{Self}` cannot be converted to a Python object", + note = "`IntoPy` is automatically implemented by the `#[pyclass]` macro", + note = "if you do not wish to have a corresponding Python type, implement `IntoPy` manually", + note = "if you do not own `{Self}` you can perform a manual conversion to one of the types in `pyo3::types::*`" + ) +)] pub trait OkWrap { type Error; fn wrap(self) -> Result; diff --git a/tests/ui/invalid_result_conversion.stderr b/tests/ui/invalid_result_conversion.stderr index a18cd6c7..b34c6396 100644 --- a/tests/ui/invalid_result_conversion.stderr +++ b/tests/ui/invalid_result_conversion.stderr @@ -1,8 +1,8 @@ error[E0277]: the trait bound `PyErr: From` is not satisfied - --> tests/ui/invalid_result_conversion.rs:21:1 + --> tests/ui/invalid_result_conversion.rs:22:25 | -21 | #[pyfunction] - | ^^^^^^^^^^^^^ the trait `From` is not implemented for `PyErr`, which is required by `MyError: Into` +22 | fn should_not_work() -> Result<(), MyError> { + | ^^^^^^ the trait `From` is not implemented for `PyErr`, which is required by `MyError: Into` | = help: the following other types implement trait `From`: > @@ -15,4 +15,3 @@ error[E0277]: the trait bound `PyErr: From` is not satisfied >> and $N others = note: required for `MyError` to implement `Into` - = note: this error originates in the attribute macro `pyfunction` (in Nightly builds, run with -Z macro-backtrace for more info) diff --git a/tests/ui/missing_intopy.stderr b/tests/ui/missing_intopy.stderr index c0a60143..e781b38f 100644 --- a/tests/ui/missing_intopy.stderr +++ b/tests/ui/missing_intopy.stderr @@ -1,9 +1,11 @@ -error[E0277]: the trait bound `Blah: OkWrap` is not satisfied - --> tests/ui/missing_intopy.rs:3:1 +error[E0277]: `Blah` cannot be converted to a Python object + --> tests/ui/missing_intopy.rs:4:14 | -3 | #[pyo3::pyfunction] - | ^^^^^^^^^^^^^^^^^^^ the trait `IntoPy>` is not implemented for `Blah`, which is required by `Blah: OkWrap<_>` +4 | fn blah() -> Blah{ + | ^^^^ the trait `IntoPy>` is not implemented for `Blah`, which is required by `Blah: OkWrap<_>` | + = note: `IntoPy` is automatically implemented by the `#[pyclass]` macro + = note: if you do not wish to have a corresponding Python type, implement `IntoPy` manually + = note: if you do not own `Blah` you can perform a manual conversion to one of the types in `pyo3::types::*` = help: the trait `OkWrap` is implemented for `Result` = note: required for `Blah` to implement `OkWrap` - = note: this error originates in the attribute macro `pyo3::pyfunction` (in Nightly builds, run with -Z macro-backtrace for more info)