From 8fdecc2a7c9eedad7a0d0e3f64564f0de052cc9d Mon Sep 17 00:00:00 2001 From: Icxolu <10486322+Icxolu@users.noreply.github.com> Date: Thu, 16 Jan 2025 22:51:15 +0100 Subject: [PATCH] allow path in `from_py_with` and deprecate string literal --- guide/src/class/numeric.md | 6 +- guide/src/conversions/traits.md | 6 +- guide/src/function.md | 4 +- newsfragments/4860.changed.md | 1 + pyo3-macros-backend/src/attributes.rs | 32 +++++++++- pyo3-macros-backend/src/frompyobject.rs | 68 ++++++++++++++++++--- pyo3-macros-backend/src/params.rs | 10 +++ pyo3-macros-backend/src/pymethod.rs | 32 ++++++---- tests/test_class_basics.rs | 8 +-- tests/test_compile_error.rs | 1 + tests/test_frompyobject.rs | 12 ++-- tests/test_getter_setter.rs | 2 +- tests/test_methods.rs | 2 +- tests/test_pyfunction.rs | 6 +- tests/ui/deprecated.rs | 37 +++++++++++ tests/ui/deprecated.stderr | 29 +++++++++ tests/ui/forbid_unsafe.rs | 2 +- tests/ui/invalid_argument_attributes.rs | 2 +- tests/ui/invalid_argument_attributes.stderr | 12 ++-- tests/ui/invalid_frompy_derive.rs | 2 +- tests/ui/invalid_frompy_derive.stderr | 12 ++-- 21 files changed, 225 insertions(+), 61 deletions(-) create mode 100644 newsfragments/4860.changed.md create mode 100644 tests/ui/deprecated.rs create mode 100644 tests/ui/deprecated.stderr diff --git a/guide/src/class/numeric.md b/guide/src/class/numeric.md index a441eba4e13..4f73a44adab 100644 --- a/guide/src/class/numeric.md +++ b/guide/src/class/numeric.md @@ -27,7 +27,7 @@ OverflowError: Python int too large to convert to C long ``` Instead of relying on the default [`FromPyObject`] extraction to parse arguments, we can specify our -own extraction function, using the `#[pyo3(from_py_with = "...")]` attribute. Unfortunately PyO3 +own extraction function, using the `#[pyo3(from_py_with = ...)]` attribute. Unfortunately PyO3 doesn't provide a way to wrap Python integers out of the box, but we can do a Python call to mask it and cast it to an `i32`. @@ -62,7 +62,7 @@ struct Number(i32); #[pymethods] impl Number { #[new] - fn new(#[pyo3(from_py_with = "wrap")] value: i32) -> Self { + fn new(#[pyo3(from_py_with = wrap)] value: i32) -> Self { Self(value) } } @@ -225,7 +225,7 @@ struct Number(i32); #[pymethods] impl Number { #[new] - fn new(#[pyo3(from_py_with = "wrap")] value: i32) -> Self { + fn new(#[pyo3(from_py_with = wrap)] value: i32) -> Self { Self(value) } diff --git a/guide/src/conversions/traits.md b/guide/src/conversions/traits.md index 1aa445cce41..730ba5d0246 100644 --- a/guide/src/conversions/traits.md +++ b/guide/src/conversions/traits.md @@ -484,9 +484,9 @@ If the input is neither a string nor an integer, the error message will be: - `pyo3(item)`, `pyo3(item("key"))` - retrieve the field from a mapping, possibly with the custom key specified as an argument. - can be any literal that implements `ToBorrowedObject` -- `pyo3(from_py_with = "...")` +- `pyo3(from_py_with = ...)` - apply a custom function to convert the field from Python the desired Rust type. - - the argument must be the name of the function as a string. + - the argument must be the path to the function. - the function signature must be `fn(&Bound) -> PyResult` where `T` is the Rust type of the argument. - `pyo3(default)`, `pyo3(default = ...)` - if the argument is set, uses the given default value. @@ -503,7 +503,7 @@ use pyo3::prelude::*; #[derive(FromPyObject)] struct RustyStruct { - #[pyo3(item("value"), default, from_py_with = "Bound::<'_, PyAny>::len")] + #[pyo3(item("value"), default, from_py_with = Bound::<'_, PyAny>::len)] len: usize, #[pyo3(item)] other: usize, diff --git a/guide/src/function.md b/guide/src/function.md index fd215a1550e..323bc9c8f87 100644 --- a/guide/src/function.md +++ b/guide/src/function.md @@ -101,7 +101,7 @@ The `#[pyo3]` attribute can be used to modify properties of the generated Python The `#[pyo3]` attribute can be used on individual arguments to modify properties of them in the generated function. It can take any combination of the following options: - - `#[pyo3(from_py_with = "...")]` + - `#[pyo3(from_py_with = ...)]` Set this on an option to specify a custom function to convert the function argument from Python to the desired Rust type, instead of using the default `FromPyObject` extraction. The function signature must be `fn(&Bound<'_, PyAny>) -> PyResult` where `T` is the Rust type of the argument. @@ -115,7 +115,7 @@ The `#[pyo3]` attribute can be used on individual arguments to modify properties } #[pyfunction] - fn object_length(#[pyo3(from_py_with = "get_length")] argument: usize) -> usize { + fn object_length(#[pyo3(from_py_with = get_length)] argument: usize) -> usize { argument } diff --git a/newsfragments/4860.changed.md b/newsfragments/4860.changed.md new file mode 100644 index 00000000000..4f62e45a5c8 --- /dev/null +++ b/newsfragments/4860.changed.md @@ -0,0 +1 @@ +`#[pyo3(from_py_with = ...)]` now take a path rather than a string literal \ No newline at end of file diff --git a/pyo3-macros-backend/src/attributes.rs b/pyo3-macros-backend/src/attributes.rs index bd5da377121..0e37e90d5c1 100644 --- a/pyo3-macros-backend/src/attributes.rs +++ b/pyo3-macros-backend/src/attributes.rs @@ -349,7 +349,37 @@ impl ToTokens for OptionalKeywordAttribute { } } -pub type FromPyWithAttribute = KeywordAttribute>; +#[derive(Debug, Clone)] +pub struct ExprPathWrap { + pub from_lit_str: bool, + pub expr_path: ExprPath, +} + +impl Parse for ExprPathWrap { + fn parse(input: ParseStream<'_>) -> Result { + match input.parse::() { + Ok(expr_path) => Ok(ExprPathWrap { + from_lit_str: false, + expr_path, + }), + Err(e) => match input.parse::>() { + Ok(LitStrValue(expr_path)) => Ok(ExprPathWrap { + from_lit_str: true, + expr_path, + }), + Err(_) => Err(e), + }, + } + } +} + +impl ToTokens for ExprPathWrap { + fn to_tokens(&self, tokens: &mut TokenStream) { + self.expr_path.to_tokens(tokens) + } +} + +pub type FromPyWithAttribute = KeywordAttribute; pub type DefaultAttribute = OptionalKeywordAttribute; diff --git a/pyo3-macros-backend/src/frompyobject.rs b/pyo3-macros-backend/src/frompyobject.rs index b353e2dc16d..893cf71a684 100644 --- a/pyo3-macros-backend/src/frompyobject.rs +++ b/pyo3-macros-backend/src/frompyobject.rs @@ -3,7 +3,7 @@ use crate::attributes::{ }; use crate::utils::Ctx; use proc_macro2::TokenStream; -use quote::{format_ident, quote, ToTokens}; +use quote::{format_ident, quote, quote_spanned, ToTokens}; use syn::{ ext::IdentExt, parenthesized, @@ -284,11 +284,23 @@ impl<'a> Container<'a> { }, Some(FromPyWithAttribute { value: expr_path, .. - }) => quote! { - Ok(#self_ty { - #ident: #pyo3_path::impl_::frompyobject::extract_struct_field_with(#expr_path as fn(_) -> _, obj, #struct_name, #field_name)? - }) - }, + }) => { + let deprecation = expr_path.from_lit_str.then(|| { + quote_spanned! { expr_path.span() => + #[deprecated(since = "0.24.0", note = "`from_py_with` string literals is deprecated. Use the function path instead.")] + #[allow(dead_code)] + const LIT_STR_DEPRECATION: () = (); + let _: () = LIT_STR_DEPRECATION; + } + }).unwrap_or_default(); + + quote! { + #deprecation + Ok(#self_ty { + #ident: #pyo3_path::impl_::frompyobject::extract_struct_field_with(#expr_path as fn(_) -> _, obj, #struct_name, #field_name)? + }) + } + } } } else { match from_py_with { @@ -298,9 +310,20 @@ impl<'a> Container<'a> { Some(FromPyWithAttribute { value: expr_path, .. - }) => quote! { - #pyo3_path::impl_::frompyobject::extract_tuple_struct_field_with(#expr_path as fn(_) -> _, obj, #struct_name, 0).map(#self_ty) - }, + }) => { + let deprecation = expr_path.from_lit_str.then(|| { + quote_spanned! { expr_path.span() => + #[deprecated(since = "0.24.0", note = "`from_py_with` string literals is deprecated. Use the function path instead.")] + #[allow(dead_code)] + const LIT_STR_DEPRECATION: () = (); + let _: () = LIT_STR_DEPRECATION; + } + }).unwrap_or_default(); + quote! { + #deprecation + #pyo3_path::impl_::frompyobject::extract_tuple_struct_field_with(#expr_path as fn(_) -> _, obj, #struct_name, 0).map(#self_ty) + } + } } } } @@ -325,7 +348,21 @@ impl<'a> Container<'a> { } }); + let deprecations = struct_fields + .iter() + .filter_map(|fields| fields.from_py_with.as_ref()).filter(|f|f.value.from_lit_str) + .map(|f| { + quote_spanned! { f.value.span() => { + #[deprecated(since = "0.24.0", note = "`from_py_with` string literals is deprecated. Use the function path instead.")] + #[allow(dead_code)] + const LIT_STR_DEPRECATION: () = (); + let _: () = LIT_STR_DEPRECATION; + }} + }) + .collect::(); + quote!( + #deprecations match #pyo3_path::types::PyAnyMethods::extract(obj) { ::std::result::Result::Ok((#(#field_idents),*)) => ::std::result::Result::Ok(#self_ty(#(#fields),*)), ::std::result::Result::Err(err) => ::std::result::Result::Err(err), @@ -387,7 +424,18 @@ impl<'a> Container<'a> { fields.push(quote!(#ident: #extracted)); } - quote!(::std::result::Result::Ok(#self_ty{#fields})) + let d = struct_fields + .iter() + .filter_map(|field| field.from_py_with.as_ref()) + .filter(|f| f.value.from_lit_str) + .map(|f| quote_spanned! { f.value.span() => { + #[deprecated(since = "0.24.0", note = "`from_py_with` string literals is deprecated. Use the function path instead.")] + #[allow(dead_code)] + const LIT_STR_DEPRECATION: () = (); + let _: () = LIT_STR_DEPRECATION; + }}).collect::(); + + quote!(#d ::std::result::Result::Ok(#self_ty{#fields})) } } diff --git a/pyo3-macros-backend/src/params.rs b/pyo3-macros-backend/src/params.rs index f967149c725..26a7f52da28 100644 --- a/pyo3-macros-backend/src/params.rs +++ b/pyo3-macros-backend/src/params.rs @@ -61,7 +61,17 @@ pub fn impl_arg_params( .filter_map(|(i, arg)| { let from_py_with = &arg.from_py_with()?.value; let from_py_with_holder = format_ident!("from_py_with_{}", i); + let d = from_py_with + .from_lit_str + .then(|| quote_spanned! { from_py_with.span() => + #[deprecated(since = "0.24.0", note = "`from_py_with` string literals is deprecated. Use the function path instead.")] + #[allow(dead_code)] + const LIT_STR_DEPRECATION: () = (); + let _: () = LIT_STR_DEPRECATION; + }) + .unwrap_or_default(); Some(quote_spanned! { from_py_with.span() => + #d let #from_py_with_holder = #from_py_with; }) }) diff --git a/pyo3-macros-backend/src/pymethod.rs b/pyo3-macros-backend/src/pymethod.rs index c21f6d4556e..99f05123653 100644 --- a/pyo3-macros-backend/src/pymethod.rs +++ b/pyo3-macros-backend/src/pymethod.rs @@ -657,18 +657,26 @@ pub fn impl_py_setter_def( PropertyType::Function { spec, .. } => { let (_, args) = split_off_python_arg(&spec.signature.arguments); let value_arg = &args[0]; - let (from_py_with, ident) = - if let Some(from_py_with) = &value_arg.from_py_with().as_ref().map(|f| &f.value) { - let ident = syn::Ident::new("from_py_with", from_py_with.span()); - ( - quote_spanned! { from_py_with.span() => - let #ident = #from_py_with; - }, - ident, - ) - } else { - (quote!(), syn::Ident::new("dummy", Span::call_site())) - }; + let (from_py_with, ident) = if let Some(from_py_with) = + &value_arg.from_py_with().as_ref().map(|f| &f.value) + { + let ident = syn::Ident::new("from_py_with", from_py_with.span()); + let d = from_py_with.from_lit_str.then(|| quote_spanned! { from_py_with.span() => + #[deprecated(since = "0.24.0", note = "`from_py_with` string literals is deprecated. Use the function path instead.")] + #[allow(dead_code)] + const LIT_STR_DEPRECATION: () = (); + let _: () = LIT_STR_DEPRECATION; + }).unwrap_or_default(); + ( + quote_spanned! { from_py_with.span() => + #d + let #ident = #from_py_with; + }, + ident, + ) + } else { + (quote!(), syn::Ident::new("dummy", Span::call_site())) + }; let arg = if let FnArg::Regular(arg) = &value_arg { arg diff --git a/tests/test_class_basics.rs b/tests/test_class_basics.rs index 4a687a89eea..3cca37c3bd9 100644 --- a/tests/test_class_basics.rs +++ b/tests/test_class_basics.rs @@ -357,23 +357,23 @@ struct ClassWithFromPyWithMethods {} #[pymethods] impl ClassWithFromPyWithMethods { - fn instance_method(&self, #[pyo3(from_py_with = "get_length")] argument: usize) -> usize { + fn instance_method(&self, #[pyo3(from_py_with = get_length)] argument: usize) -> usize { argument } #[classmethod] fn classmethod( _cls: &Bound<'_, PyType>, - #[pyo3(from_py_with = "Bound::<'_, PyAny>::len")] argument: usize, + #[pyo3(from_py_with = Bound::<'_, PyAny>::len)] argument: usize, ) -> usize { argument } #[staticmethod] - fn staticmethod(#[pyo3(from_py_with = "get_length")] argument: usize) -> usize { + fn staticmethod(#[pyo3(from_py_with = get_length)] argument: usize) -> usize { argument } - fn __contains__(&self, #[pyo3(from_py_with = "is_even")] obj: bool) -> bool { + fn __contains__(&self, #[pyo3(from_py_with = is_even)] obj: bool) -> bool { obj } } diff --git a/tests/test_compile_error.rs b/tests/test_compile_error.rs index 10b692a604c..b992708bd23 100644 --- a/tests/test_compile_error.rs +++ b/tests/test_compile_error.rs @@ -69,4 +69,5 @@ fn test_compile_errors() { t.compile_fail("tests/ui/invalid_base_class.rs"); t.pass("tests/ui/ambiguous_associated_items.rs"); t.pass("tests/ui/pyclass_probe.rs"); + t.compile_fail("tests/ui/deprecated.rs"); } diff --git a/tests/test_frompyobject.rs b/tests/test_frompyobject.rs index d72a215814c..cf81bd4318a 100644 --- a/tests/test_frompyobject.rs +++ b/tests/test_frompyobject.rs @@ -543,7 +543,7 @@ pub struct Zap { #[pyo3(item)] name: String, - #[pyo3(from_py_with = "Bound::<'_, PyAny>::len", item("my_object"))] + #[pyo3(from_py_with = Bound::<'_, PyAny>::len, item("my_object"))] some_object_length: usize, } @@ -568,7 +568,7 @@ fn test_from_py_with() { #[derive(Debug, FromPyObject)] pub struct ZapTuple( String, - #[pyo3(from_py_with = "Bound::<'_, PyAny>::len")] usize, + #[pyo3(from_py_with = Bound::<'_, PyAny>::len)] usize, ); #[test] @@ -608,10 +608,10 @@ fn test_from_py_with_tuple_struct_error() { #[derive(Debug, FromPyObject, PartialEq, Eq)] pub enum ZapEnum { - Zip(#[pyo3(from_py_with = "Bound::<'_, PyAny>::len")] usize), + Zip(#[pyo3(from_py_with = Bound::<'_, PyAny>::len)] usize), Zap( String, - #[pyo3(from_py_with = "Bound::<'_, PyAny>::len")] usize, + #[pyo3(from_py_with = Bound::<'_, PyAny>::len)] usize, ), } @@ -632,7 +632,7 @@ fn test_from_py_with_enum() { #[derive(Debug, FromPyObject, PartialEq, Eq)] #[pyo3(transparent)] pub struct TransparentFromPyWith { - #[pyo3(from_py_with = "Bound::<'_, PyAny>::len")] + #[pyo3(from_py_with = Bound::<'_, PyAny>::len)] len: usize, } @@ -730,7 +730,7 @@ fn test_with_explicit_default_item() { #[derive(Debug, FromPyObject, PartialEq, Eq)] pub struct WithDefaultItemAndConversionFunction { - #[pyo3(item, default, from_py_with = "Bound::<'_, PyAny>::len")] + #[pyo3(item, default, from_py_with = Bound::<'_, PyAny>::len)] opt: usize, #[pyo3(item)] value: usize, diff --git a/tests/test_getter_setter.rs b/tests/test_getter_setter.rs index cdc8136bede..82a50442ec5 100644 --- a/tests/test_getter_setter.rs +++ b/tests/test_getter_setter.rs @@ -43,7 +43,7 @@ impl ClassWithProperties { } #[setter] - fn set_from_len(&mut self, #[pyo3(from_py_with = "extract_len")] value: i32) { + fn set_from_len(&mut self, #[pyo3(from_py_with = extract_len)] value: i32) { self.num = value; } diff --git a/tests/test_methods.rs b/tests/test_methods.rs index 743fa6e6b4f..9a7f593df2a 100644 --- a/tests/test_methods.rs +++ b/tests/test_methods.rs @@ -1198,7 +1198,7 @@ fn test_issue_2988() { _data: Vec, // The from_py_with here looks a little odd, we just need some way // to encourage the macro to expand the from_py_with default path too - #[pyo3(from_py_with = " as PyAnyMethods>::extract")] _data2: Vec, + #[pyo3(from_py_with = as PyAnyMethods>::extract)] _data2: Vec, ) { } } diff --git a/tests/test_pyfunction.rs b/tests/test_pyfunction.rs index 13ba5405ed3..4e3bdce9e05 100644 --- a/tests/test_pyfunction.rs +++ b/tests/test_pyfunction.rs @@ -143,7 +143,7 @@ fn datetime_to_timestamp(dt: &Bound<'_, PyAny>) -> PyResult { #[cfg(not(Py_LIMITED_API))] #[pyfunction] fn function_with_custom_conversion( - #[pyo3(from_py_with = "datetime_to_timestamp")] timestamp: i64, + #[pyo3(from_py_with = datetime_to_timestamp)] timestamp: i64, ) -> i64 { timestamp } @@ -196,13 +196,13 @@ fn test_from_py_with_defaults() { // issue 2280 combination of from_py_with and Option did not compile #[pyfunction] #[pyo3(signature = (int=None))] - fn from_py_with_option(#[pyo3(from_py_with = "optional_int")] int: Option) -> i32 { + fn from_py_with_option(#[pyo3(from_py_with = optional_int)] int: Option) -> i32 { int.unwrap_or(0) } #[pyfunction(signature = (len=0))] fn from_py_with_default( - #[pyo3(from_py_with = " as PyAnyMethods>::len")] len: usize, + #[pyo3(from_py_with = as PyAnyMethods>::len)] len: usize, ) -> usize { len } diff --git a/tests/ui/deprecated.rs b/tests/ui/deprecated.rs new file mode 100644 index 00000000000..8d5c73780cf --- /dev/null +++ b/tests/ui/deprecated.rs @@ -0,0 +1,37 @@ +#![deny(deprecated)] +use pyo3::prelude::*; + +#[pyfunction] +fn from_py_with_in_function( + #[pyo3(from_py_with = "Bound::<'_, PyAny>::len")] argument: usize, +) -> usize { + argument +} + +#[pyclass] +struct Number(usize); + +#[pymethods] +impl Number { + #[new] + fn from_py_with_in_method( + #[pyo3(from_py_with = "Bound::<'_, PyAny>::len")] value: usize, + ) -> Self { + Self(value) + } +} + +#[derive(FromPyObject)] +struct FromPyWithStruct { + #[pyo3(from_py_with = "Bound::<'_, PyAny>::len")] + len: usize, + other: usize, +} + +#[derive(FromPyObject)] +struct FromPyWithTuple( + #[pyo3(from_py_with = "Bound::<'_, PyAny>::len")] usize, + usize, +); + +fn main() {} diff --git a/tests/ui/deprecated.stderr b/tests/ui/deprecated.stderr new file mode 100644 index 00000000000..c538d0064a7 --- /dev/null +++ b/tests/ui/deprecated.stderr @@ -0,0 +1,29 @@ +error: use of deprecated constant `__pyfunction_from_py_with_in_function::LIT_STR_DEPRECATION`: `from_py_with` string literals is deprecated. Use the function path instead. + --> tests/ui/deprecated.rs:6:27 + | +6 | #[pyo3(from_py_with = "Bound::<'_, PyAny>::len")] argument: usize, + | ^^^^^^^^^^^^^^^^^^^^^^^^^ + | +note: the lint level is defined here + --> tests/ui/deprecated.rs:1:9 + | +1 | #![deny(deprecated)] + | ^^^^^^^^^^ + +error: use of deprecated constant `Number::__pymethod___new____::LIT_STR_DEPRECATION`: `from_py_with` string literals is deprecated. Use the function path instead. + --> tests/ui/deprecated.rs:18:31 + | +18 | #[pyo3(from_py_with = "Bound::<'_, PyAny>::len")] value: usize, + | ^^^^^^^^^^^^^^^^^^^^^^^^^ + +error: use of deprecated constant `>::extract_bound::LIT_STR_DEPRECATION`: `from_py_with` string literals is deprecated. Use the function path instead. + --> tests/ui/deprecated.rs:26:27 + | +26 | #[pyo3(from_py_with = "Bound::<'_, PyAny>::len")] + | ^^^^^^^^^^^^^^^^^^^^^^^^^ + +error: use of deprecated constant `>::extract_bound::LIT_STR_DEPRECATION`: `from_py_with` string literals is deprecated. Use the function path instead. + --> tests/ui/deprecated.rs:33:27 + | +33 | #[pyo3(from_py_with = "Bound::<'_, PyAny>::len")] usize, + | ^^^^^^^^^^^^^^^^^^^^^^^^^ diff --git a/tests/ui/forbid_unsafe.rs b/tests/ui/forbid_unsafe.rs index 4ab7639d658..d9a51c52895 100644 --- a/tests/ui/forbid_unsafe.rs +++ b/tests/ui/forbid_unsafe.rs @@ -37,7 +37,7 @@ mod from_py_with { } #[pyfunction] - fn f(#[pyo3(from_py_with = "bytes_from_py")] _bytes: Vec) {} + fn f(#[pyo3(from_py_with = bytes_from_py)] _bytes: Vec) {} } fn main() {} diff --git a/tests/ui/invalid_argument_attributes.rs b/tests/ui/invalid_argument_attributes.rs index 6797642d77b..c91b0792a68 100644 --- a/tests/ui/invalid_argument_attributes.rs +++ b/tests/ui/invalid_argument_attributes.rs @@ -10,7 +10,7 @@ fn from_py_with_no_value(#[pyo3(from_py_with)] _param: String) {} fn from_py_with_string(#[pyo3("from_py_with")] _param: String) {} #[pyfunction] -fn from_py_with_value_not_a_string(#[pyo3(from_py_with = func)] _param: String) {} +fn from_py_with_value_not_found(#[pyo3(from_py_with = func)] _param: String) {} #[pyfunction] fn from_py_with_repeated(#[pyo3(from_py_with = "func", from_py_with = "func")] _param: String) {} diff --git a/tests/ui/invalid_argument_attributes.stderr b/tests/ui/invalid_argument_attributes.stderr index e6c42f82a87..03e579b0c9b 100644 --- a/tests/ui/invalid_argument_attributes.stderr +++ b/tests/ui/invalid_argument_attributes.stderr @@ -16,14 +16,14 @@ error: expected `cancel_handle` or `from_py_with` 10 | fn from_py_with_string(#[pyo3("from_py_with")] _param: String) {} | ^^^^^^^^^^^^^^ -error: expected string literal - --> tests/ui/invalid_argument_attributes.rs:13:58 - | -13 | fn from_py_with_value_not_a_string(#[pyo3(from_py_with = func)] _param: String) {} - | ^^^^ - error: `from_py_with` may only be specified once per argument --> tests/ui/invalid_argument_attributes.rs:16:56 | 16 | fn from_py_with_repeated(#[pyo3(from_py_with = "func", from_py_with = "func")] _param: String) {} | ^^^^^^^^^^^^ + +error[E0425]: cannot find value `func` in this scope + --> tests/ui/invalid_argument_attributes.rs:13:55 + | +13 | fn from_py_with_value_not_found(#[pyo3(from_py_with = func)] _param: String) {} + | ^^^^ not found in this scope diff --git a/tests/ui/invalid_frompy_derive.rs b/tests/ui/invalid_frompy_derive.rs index d3a778e686b..59116d6ea65 100644 --- a/tests/ui/invalid_frompy_derive.rs +++ b/tests/ui/invalid_frompy_derive.rs @@ -160,7 +160,7 @@ struct InvalidFromPyWith { } #[derive(FromPyObject)] -struct InvalidFromPyWithLiteral { +struct InvalidFromPyWithNotFound { #[pyo3(from_py_with = func)] field: String, } diff --git a/tests/ui/invalid_frompy_derive.stderr b/tests/ui/invalid_frompy_derive.stderr index 5b8c1fc718b..afe848af1c2 100644 --- a/tests/ui/invalid_frompy_derive.stderr +++ b/tests/ui/invalid_frompy_derive.stderr @@ -176,12 +176,6 @@ error: expected `=` 158 | #[pyo3(from_py_with)] | ^ -error: expected string literal - --> tests/ui/invalid_frompy_derive.rs:164:27 - | -164 | #[pyo3(from_py_with = func)] - | ^^^^ - error: `getter` is not permitted on tuple struct elements. --> tests/ui/invalid_frompy_derive.rs:169:27 | @@ -249,3 +243,9 @@ error: `default` is not permitted on tuple struct elements. | 231 | struct NamedTuplesWithDefaultValues(#[pyo3(default)] String); | ^ + +error[E0425]: cannot find value `func` in this scope + --> tests/ui/invalid_frompy_derive.rs:164:27 + | +164 | #[pyo3(from_py_with = func)] + | ^^^^ not found in this scope