Improve the span and message for return types of pymethod/functions (#4220)

* Improve the span and message for return types of pymethod/functions

* Don't pass the span

* fixup trybuild output

---------

Co-authored-by: David Hewitt <mail@davidhewitt.dev>
This commit is contained in:
Bruno Kolenbrander 2024-06-20 11:23:40 +02:00 committed by GitHub
parent e6b2216b04
commit b25b3b3a7b
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
16 changed files with 162 additions and 101 deletions

View File

@ -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);

View File

@ -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<FromPyWithAttribute>,
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<TokenStream, syn::Token![,]> = Punctuated::new();
@ -670,8 +670,8 @@ pub fn build_derive_from_pyobject(tokens: &DeriveInput) -> Result<TokenStream> {
.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) => {

View File

@ -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))
}

View File

@ -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<TokenStream> {
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! {

View File

@ -90,8 +90,8 @@ pub fn pymodule_module_impl(mut module: syn::ItemMod) -> Result<TokenStream> {
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<TokenStream> {
pub fn pymodule_function_impl(mut function: syn::ItemFn) -> Result<TokenStream> {
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<TokenStream>
}
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<syn::Stmt> = Vec::new();
#[cfg(feature = "gil-refs")]

View File

@ -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<TokenStream>) {
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

View File

@ -227,7 +227,7 @@ pub fn build_py_class(
) -> syn::Result<TokenStream> {
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<TokenStream> {
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<TokenStream> {
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<TokenStream> {
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<MethodAndMethodDef>, Vec<MethodAndSlotDef>)> {
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<Ident>,
fields_types: &mut Vec<syn::Type>,
) -> Result<(Vec<MethodAndMethodDef>, Vec<syn::ImplItemFn>)> {
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<Self>) -> #pyo3_path::PyResult<usize> {
@ -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<MethodAndMethodDef>, Vec<MethodAndSlotDef>)> {
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<MethodAndSlotDef> {
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<MethodAndSlotDef> {
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<TokenStream, syn::Error> {
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<syn::ImplItemFn>, Option<MethodAndSlotDef>)> {
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<syn::ImplItemFn>, Option<MethodAndSlotDef>)> {
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<TokenStream> {
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<TokenStream> {
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 {

View File

@ -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()

View File

@ -90,7 +90,6 @@ pub fn impl_methods(
methods_type: PyClassMethodsType,
options: PyImplOptions,
) -> syn::Result<TokenStream> {
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<TokenStream>,
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<String>,
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<TokenStream>,
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;

View File

@ -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<TokenStream>,
ctx: &Ctx,
) -> Result<MethodAndMethodDef> {
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<MethodAndSlotDef> {
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<MethodAndSlotDef> {
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<MethodAndSlotDef> {
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<MethodAndMethodDef> {
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<MethodAndMethodDef> {
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<MethodAndMethodDef> {
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<TokenStream> {
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<MethodAndSlotDef> {
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<TokenStream> {
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<TokenStream> {
let Ctx { pyo3_path } = ctx;
let Ctx { pyo3_path, .. } = ctx;
let SlotFragmentDef {
fragment,
arguments,

View File

@ -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) }
}

View File

@ -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<String>,
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<CrateAttribute>) -> Self {
pub(crate) fn new(attr: &Option<CrateAttribute>, 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,
}
}
}

View File

@ -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<T>: Sized {
/// Performs the conversion.
fn into_py(self, py: Python<'_>) -> T;

View File

@ -20,6 +20,15 @@ impl<T> SomeWrap<T> for Option<T> {
}
/// 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<T> {
type Error;
fn wrap(self) -> Result<T, Self::Error>;

View File

@ -1,8 +1,8 @@
error[E0277]: the trait bound `PyErr: From<MyError>` is not satisfied
--> tests/ui/invalid_result_conversion.rs:21:1
--> tests/ui/invalid_result_conversion.rs:22:25
|
21 | #[pyfunction]
| ^^^^^^^^^^^^^ the trait `From<MyError>` is not implemented for `PyErr`, which is required by `MyError: Into<PyErr>`
22 | fn should_not_work() -> Result<(), MyError> {
| ^^^^^^ the trait `From<MyError>` is not implemented for `PyErr`, which is required by `MyError: Into<PyErr>`
|
= help: the following other types implement trait `From<T>`:
<PyErr as From<AddrParseError>>
@ -15,4 +15,3 @@ error[E0277]: the trait bound `PyErr: From<MyError>` is not satisfied
<PyErr as From<IntoInnerError<W>>>
and $N others
= note: required for `MyError` to implement `Into<PyErr>`
= note: this error originates in the attribute macro `pyfunction` (in Nightly builds, run with -Z macro-backtrace for more info)

View File

@ -1,9 +1,11 @@
error[E0277]: the trait bound `Blah: OkWrap<Blah>` 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<Py<PyAny>>` is not implemented for `Blah`, which is required by `Blah: OkWrap<_>`
4 | fn blah() -> Blah{
| ^^^^ the trait `IntoPy<Py<PyAny>>` 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<T>` is implemented for `Result<T, E>`
= note: required for `Blah` to implement `OkWrap<Blah>`
= note: this error originates in the attribute macro `pyo3::pyfunction` (in Nightly builds, run with -Z macro-backtrace for more info)