From e0513d74f5d8327d48d945f7fdb0f6cff176bfc6 Mon Sep 17 00:00:00 2001 From: David Hewitt Date: Fri, 24 Nov 2023 03:11:05 +0000 Subject: [PATCH] improve error for invalid `#[classmethod]` receivers --- pyo3-macros-backend/src/method.rs | 71 ++++---- pyo3-macros-backend/src/pyfunction.rs | 2 +- pyo3-macros-backend/src/pymethod.rs | 6 +- tests/test_compile_error.rs | 2 +- tests/ui/invalid_need_module_arg_position.rs | 12 -- .../invalid_need_module_arg_position.stderr | 14 -- tests/ui/invalid_pyfunctions.rs | 8 + tests/ui/invalid_pyfunctions.stderr | 21 +++ tests/ui/invalid_pymethods.rs | 42 ++--- tests/ui/invalid_pymethods.stderr | 162 ++++++++---------- tests/ui/invalid_pymethods_duplicates.rs | 32 ++++ tests/ui/invalid_pymethods_duplicates.stderr | 32 ++++ 12 files changed, 225 insertions(+), 179 deletions(-) delete mode 100644 tests/ui/invalid_need_module_arg_position.rs delete mode 100644 tests/ui/invalid_need_module_arg_position.stderr create mode 100644 tests/ui/invalid_pymethods_duplicates.rs create mode 100644 tests/ui/invalid_pymethods_duplicates.stderr diff --git a/pyo3-macros-backend/src/method.rs b/pyo3-macros-backend/src/method.rs index 9ac87ef1..c0aa898e 100644 --- a/pyo3-macros-backend/src/method.rs +++ b/pyo3-macros-backend/src/method.rs @@ -78,8 +78,8 @@ pub enum FnType { Setter(SelfType), Fn(SelfType), FnNew, - FnNewClass, - FnClass, + FnNewClass(Span), + FnClass(Span), FnStatic, FnModule(Span), ClassAttribute, @@ -91,8 +91,8 @@ impl FnType { FnType::Getter(_) | FnType::Setter(_) | FnType::Fn(_) - | FnType::FnClass - | FnType::FnNewClass + | FnType::FnClass(_) + | FnType::FnNewClass(_) | FnType::FnModule(_) => true, FnType::FnNew | FnType::FnStatic | FnType::ClassAttribute => false, } @@ -111,10 +111,12 @@ impl FnType { FnType::FnNew | FnType::FnStatic | FnType::ClassAttribute => { quote!() } - FnType::FnClass | FnType::FnNewClass => { - quote! { + FnType::FnClass(span) | FnType::FnNewClass(span) => { + let py = syn::Ident::new("py", Span::call_site()); + let slf: Ident = syn::Ident::new("_slf", Span::call_site()); + quote_spanned! { *span => #[allow(clippy::useless_conversion)] - ::std::convert::Into::into(_pyo3::types::PyType::from_type_ptr(py, _slf as *mut _pyo3::ffi::PyTypeObject)), + ::std::convert::Into::into(_pyo3::types::PyType::from_type_ptr(#py, #slf.cast())), } } FnType::FnModule(span) => { @@ -305,7 +307,7 @@ impl<'a> FnSpec<'a> { FunctionSignature::from_arguments(arguments)? }; - let convention = if matches!(fn_type, FnType::FnNew | FnType::FnNewClass) { + let convention = if matches!(fn_type, FnType::FnNew | FnType::FnNewClass(_)) { CallingConvention::TpNew } else { CallingConvention::from_signature(&signature) @@ -353,36 +355,40 @@ impl<'a> FnSpec<'a> { .map(|stripped| syn::Ident::new(stripped, name.span())) }; + let mut set_name_to_new = || { + if let Some(name) = &python_name { + bail_spanned!(name.span() => "`name` not allowed with `#[new]`"); + } + *python_name = Some(syn::Ident::new("__new__", Span::call_site())); + Ok(()) + }; + let fn_type = match method_attributes.as_mut_slice() { [] => FnType::Fn(parse_receiver( "static method needs #[staticmethod] attribute", )?), [MethodTypeAttribute::StaticMethod(_)] => FnType::FnStatic, [MethodTypeAttribute::ClassAttribute(_)] => FnType::ClassAttribute, - [MethodTypeAttribute::New(_)] - | [MethodTypeAttribute::New(_), MethodTypeAttribute::ClassMethod(_)] - | [MethodTypeAttribute::ClassMethod(_), MethodTypeAttribute::New(_)] => { - if let Some(name) = &python_name { - bail_spanned!(name.span() => "`name` not allowed with `#[new]`"); - } - *python_name = Some(syn::Ident::new("__new__", Span::call_site())); - if matches!(method_attributes.as_slice(), [MethodTypeAttribute::New(_)]) { - FnType::FnNew - } else { - FnType::FnNewClass - } + [MethodTypeAttribute::New(_)] => { + set_name_to_new()?; + FnType::FnNew + } + [MethodTypeAttribute::New(_), MethodTypeAttribute::ClassMethod(span)] + | [MethodTypeAttribute::ClassMethod(span), MethodTypeAttribute::New(_)] => { + set_name_to_new()?; + FnType::FnNewClass(*span) } [MethodTypeAttribute::ClassMethod(_)] => { // Add a helpful hint if the classmethod doesn't look like a classmethod - match sig.inputs.first() { + let span = match sig.inputs.first() { // Don't actually bother checking the type of the first argument, the compiler // will error on incorrect type. - Some(syn::FnArg::Typed(_)) => {} + Some(syn::FnArg::Typed(first_arg)) => first_arg.ty.span(), Some(syn::FnArg::Receiver(_)) | None => bail_spanned!( - sig.inputs.span() => "Expected `cls: &PyType` as the first argument to `#[classmethod]`" + sig.paren_token.span.join() => "Expected `&PyType` or `Py` as the first argument to `#[classmethod]`" ), - } - FnType::FnClass + }; + FnType::FnClass(span) } [MethodTypeAttribute::Getter(_, name)] => { if let Some(name) = name.take() { @@ -510,17 +516,12 @@ impl<'a> FnSpec<'a> { } CallingConvention::TpNew => { let (arg_convert, args) = impl_arg_params(self, cls, false)?; - let call = match &self.tp { - FnType::FnNew => quote! { #rust_name(#(#args),*) }, - FnType::FnNewClass => { - quote! { #rust_name(_pyo3::types::PyType::from_type_ptr(py, subtype), #(#args),*) } - } - x => panic!("Only `FnNew` or `FnNewClass` may use the `TpNew` calling convention. Got: {:?}", x), - }; + let self_arg = self.tp.self_arg(cls, ExtractErrorMode::Raise); + let call = quote! { #rust_name(#self_arg #(#args),*) }; quote! { unsafe fn #ident( py: _pyo3::Python<'_>, - subtype: *mut _pyo3::ffi::PyTypeObject, + _slf: *mut _pyo3::ffi::PyTypeObject, _args: *mut _pyo3::ffi::PyObject, _kwargs: *mut _pyo3::ffi::PyObject ) -> _pyo3::PyResult<*mut _pyo3::ffi::PyObject> { @@ -529,7 +530,7 @@ impl<'a> FnSpec<'a> { #arg_convert let result = #call; let initializer: _pyo3::PyClassInitializer::<#cls> = result.convert(py)?; - let cell = initializer.create_cell_from_subtype(py, subtype)?; + let cell = initializer.create_cell_from_subtype(py, _slf)?; ::std::result::Result::Ok(cell as *mut _pyo3::ffi::PyObject) } } @@ -628,7 +629,7 @@ impl<'a> FnSpec<'a> { FnType::Getter(_) | FnType::Setter(_) | FnType::ClassAttribute => return None, FnType::Fn(_) => Some("self"), FnType::FnModule(_) => Some("module"), - FnType::FnClass | FnType::FnNewClass => Some("cls"), + FnType::FnClass(_) | FnType::FnNewClass(_) => Some("cls"), FnType::FnStatic | FnType::FnNew => None, }; diff --git a/pyo3-macros-backend/src/pyfunction.rs b/pyo3-macros-backend/src/pyfunction.rs index d721708f..dc62d77f 100644 --- a/pyo3-macros-backend/src/pyfunction.rs +++ b/pyo3-macros-backend/src/pyfunction.rs @@ -195,7 +195,7 @@ pub fn impl_wrap_pyfunction( let span = match func.sig.inputs.first() { Some(syn::FnArg::Typed(first_arg)) => first_arg.ty.span(), Some(syn::FnArg::Receiver(_)) | None => bail_spanned!( - func.span() => "expected `&PyModule` or `Py` as first argument with `pass_module`" + func.sig.paren_token.span.join() => "expected `&PyModule` or `Py` as first argument with `pass_module`" ), }; method::FnType::FnModule(span) diff --git a/pyo3-macros-backend/src/pymethod.rs b/pyo3-macros-backend/src/pymethod.rs index 7856621c..9b857dd8 100644 --- a/pyo3-macros-backend/src/pymethod.rs +++ b/pyo3-macros-backend/src/pymethod.rs @@ -225,7 +225,7 @@ pub fn gen_py_method( &spec.get_doc(meth_attrs), None, )?), - (_, FnType::FnClass) => GeneratedPyMethod::Method(impl_py_method_def( + (_, FnType::FnClass(_)) => GeneratedPyMethod::Method(impl_py_method_def( cls, spec, &spec.get_doc(meth_attrs), @@ -238,7 +238,7 @@ pub fn gen_py_method( Some(quote!(_pyo3::ffi::METH_STATIC)), )?), // special prototypes - (_, FnType::FnNew) | (_, FnType::FnNewClass) => { + (_, FnType::FnNew) | (_, FnType::FnNewClass(_)) => { GeneratedPyMethod::Proto(impl_py_method_def_new(cls, spec)?) } @@ -312,7 +312,7 @@ pub fn impl_py_method_def( let add_flags = flags.map(|flags| quote!(.flags(#flags))); let methoddef_type = match spec.tp { FnType::FnStatic => quote!(Static), - FnType::FnClass => quote!(Class), + FnType::FnClass(_) => quote!(Class), _ => quote!(Method), }; let methoddef = spec.get_methoddef(quote! { #cls::#wrapper_ident }, doc); diff --git a/tests/test_compile_error.rs b/tests/test_compile_error.rs index 3919886f..7c98886a 100644 --- a/tests/test_compile_error.rs +++ b/tests/test_compile_error.rs @@ -5,7 +5,6 @@ fn test_compile_errors() { let t = trybuild::TestCases::new(); - t.compile_fail("tests/ui/invalid_need_module_arg_position.rs"); t.compile_fail("tests/ui/invalid_property_args.rs"); t.compile_fail("tests/ui/invalid_proto_pymethods.rs"); t.compile_fail("tests/ui/invalid_pyclass_args.rs"); @@ -14,6 +13,7 @@ fn test_compile_errors() { t.compile_fail("tests/ui/invalid_pyfunction_signatures.rs"); #[cfg(any(not(Py_LIMITED_API), Py_3_11))] t.compile_fail("tests/ui/invalid_pymethods_buffer.rs"); + t.compile_fail("tests/ui/invalid_pymethods_duplicates.rs"); t.compile_fail("tests/ui/invalid_pymethod_names.rs"); t.compile_fail("tests/ui/invalid_pymodule_args.rs"); t.compile_fail("tests/ui/reject_generics.rs"); diff --git a/tests/ui/invalid_need_module_arg_position.rs b/tests/ui/invalid_need_module_arg_position.rs deleted file mode 100644 index 2d45f35b..00000000 --- a/tests/ui/invalid_need_module_arg_position.rs +++ /dev/null @@ -1,12 +0,0 @@ -use pyo3::prelude::*; - -#[pymodule] -fn module(_py: Python<'_>, m: &PyModule) -> PyResult<()> { - #[pyfn(m, pass_module)] - fn fail<'py>(string: &str, module: &'py PyModule) -> PyResult<&'py str> { - module.name() - } - Ok(()) -} - -fn main() {} diff --git a/tests/ui/invalid_need_module_arg_position.stderr b/tests/ui/invalid_need_module_arg_position.stderr deleted file mode 100644 index b9231c30..00000000 --- a/tests/ui/invalid_need_module_arg_position.stderr +++ /dev/null @@ -1,14 +0,0 @@ -error[E0277]: the trait bound `&str: From<&pyo3::prelude::PyModule>` is not satisfied - --> tests/ui/invalid_need_module_arg_position.rs:6:26 - | -6 | fn fail<'py>(string: &str, module: &'py PyModule) -> PyResult<&'py str> { - | ^ the trait `From<&pyo3::prelude::PyModule>` is not implemented for `&str` - | - = help: the following other types implement trait `From`: - > - >> - >> - > - > - > - = note: required for `&pyo3::prelude::PyModule` to implement `Into<&str>` diff --git a/tests/ui/invalid_pyfunctions.rs b/tests/ui/invalid_pyfunctions.rs index 2a30a0d1..f67371ff 100644 --- a/tests/ui/invalid_pyfunctions.rs +++ b/tests/ui/invalid_pyfunctions.rs @@ -18,4 +18,12 @@ fn destructured_argument((a, b): (i32, i32)) {} #[pyfunction] fn function_with_required_after_option(_opt: Option, _x: i32) {} +#[pyfunction(pass_module)] +fn pass_module_but_no_arguments<'py>() {} + +#[pyfunction(pass_module)] +fn first_argument_not_module<'py>(string: &str, module: &'py PyModule) -> PyResult<&'py str> { + module.name() +} + fn main() {} diff --git a/tests/ui/invalid_pyfunctions.stderr b/tests/ui/invalid_pyfunctions.stderr index 9f140926..407077d5 100644 --- a/tests/ui/invalid_pyfunctions.stderr +++ b/tests/ui/invalid_pyfunctions.stderr @@ -36,3 +36,24 @@ error: required arguments after an `Option<_>` argument are ambiguous | 19 | fn function_with_required_after_option(_opt: Option, _x: i32) {} | ^^^ + +error: expected `&PyModule` or `Py` as first argument with `pass_module` + --> tests/ui/invalid_pyfunctions.rs:22:37 + | +22 | fn pass_module_but_no_arguments<'py>() {} + | ^^ + +error[E0277]: the trait bound `&str: From<&pyo3::prelude::PyModule>` is not satisfied + --> tests/ui/invalid_pyfunctions.rs:25:43 + | +25 | fn first_argument_not_module<'py>(string: &str, module: &'py PyModule) -> PyResult<&'py str> { + | ^ the trait `From<&pyo3::prelude::PyModule>` is not implemented for `&str` + | + = help: the following other types implement trait `From`: + > + >> + >> + > + > + > + = note: required for `&pyo3::prelude::PyModule` to implement `Into<&str>` diff --git a/tests/ui/invalid_pymethods.rs b/tests/ui/invalid_pymethods.rs index 8622d02b..d5779e85 100644 --- a/tests/ui/invalid_pymethods.rs +++ b/tests/ui/invalid_pymethods.rs @@ -32,6 +32,22 @@ impl MyClass { fn classmethod_with_receiver(&self) {} } +#[pymethods] +impl MyClass { + #[classmethod] + fn classmethod_missing_argument() -> Self { + Self {} + } +} + +#[pymethods] +impl MyClass { + #[classmethod] + fn classmethod_wrong_first_argument(_x: i32) -> Self { + Self {} + } +} + #[pymethods] impl MyClass { #[getter(x)] @@ -177,32 +193,6 @@ impl MyClass { fn method_self_by_value(self) {} } -struct TwoNew {} - -#[pymethods] -impl TwoNew { - #[new] - fn new_1() -> Self { - Self {} - } - - #[new] - fn new_2() -> Self { - Self {} - } -} - -struct DuplicateMethod {} - -#[pymethods] -impl DuplicateMethod { - #[pyo3(name = "func")] - fn func_a(&self) {} - - #[pyo3(name = "func")] - fn func_b(&self) {} -} - macro_rules! macro_invocation { () => {}; } diff --git a/tests/ui/invalid_pymethods.stderr b/tests/ui/invalid_pymethods.stderr index 24fb5242..81b16c7a 100644 --- a/tests/ui/invalid_pymethods.stderr +++ b/tests/ui/invalid_pymethods.stderr @@ -22,194 +22,182 @@ error: unexpected receiver 26 | fn staticmethod_with_receiver(&self) {} | ^ -error: Expected `cls: &PyType` as the first argument to `#[classmethod]` - --> tests/ui/invalid_pymethods.rs:32:34 +error: Expected `&PyType` or `Py` as the first argument to `#[classmethod]` + --> tests/ui/invalid_pymethods.rs:32:33 | 32 | fn classmethod_with_receiver(&self) {} - | ^ + | ^^^^^^^ + +error: Expected `&PyType` or `Py` as the first argument to `#[classmethod]` + --> tests/ui/invalid_pymethods.rs:38:36 + | +38 | fn classmethod_missing_argument() -> Self { + | ^^ error: expected receiver for `#[getter]` - --> tests/ui/invalid_pymethods.rs:38:5 + --> tests/ui/invalid_pymethods.rs:54:5 | -38 | fn getter_without_receiver() {} +54 | fn getter_without_receiver() {} | ^^ error: expected receiver for `#[setter]` - --> tests/ui/invalid_pymethods.rs:44:5 + --> tests/ui/invalid_pymethods.rs:60:5 | -44 | fn setter_without_receiver() {} +60 | fn setter_without_receiver() {} | ^^ error: static method needs #[staticmethod] attribute - --> tests/ui/invalid_pymethods.rs:50:5 + --> tests/ui/invalid_pymethods.rs:66:5 | -50 | fn text_signature_on_call() {} +66 | fn text_signature_on_call() {} | ^^ error: `text_signature` not allowed with `getter` - --> tests/ui/invalid_pymethods.rs:56:12 + --> tests/ui/invalid_pymethods.rs:72:12 | -56 | #[pyo3(text_signature = "()")] +72 | #[pyo3(text_signature = "()")] | ^^^^^^^^^^^^^^ error: `text_signature` not allowed with `setter` - --> tests/ui/invalid_pymethods.rs:63:12 + --> tests/ui/invalid_pymethods.rs:79:12 | -63 | #[pyo3(text_signature = "()")] +79 | #[pyo3(text_signature = "()")] | ^^^^^^^^^^^^^^ error: `text_signature` not allowed with `classattr` - --> tests/ui/invalid_pymethods.rs:70:12 + --> tests/ui/invalid_pymethods.rs:86:12 | -70 | #[pyo3(text_signature = "()")] +86 | #[pyo3(text_signature = "()")] | ^^^^^^^^^^^^^^ error: expected a string literal or `None` - --> tests/ui/invalid_pymethods.rs:76:30 + --> tests/ui/invalid_pymethods.rs:92:30 | -76 | #[pyo3(text_signature = 1)] +92 | #[pyo3(text_signature = 1)] | ^ error: `text_signature` may only be specified once - --> tests/ui/invalid_pymethods.rs:83:12 + --> tests/ui/invalid_pymethods.rs:99:12 | -83 | #[pyo3(text_signature = None)] +99 | #[pyo3(text_signature = None)] | ^^^^^^^^^^^^^^ error: `signature` not allowed with `getter` - --> tests/ui/invalid_pymethods.rs:90:12 - | -90 | #[pyo3(signature = ())] - | ^^^^^^^^^ + --> tests/ui/invalid_pymethods.rs:106:12 + | +106 | #[pyo3(signature = ())] + | ^^^^^^^^^ error: `signature` not allowed with `setter` - --> tests/ui/invalid_pymethods.rs:97:12 - | -97 | #[pyo3(signature = ())] - | ^^^^^^^^^ + --> tests/ui/invalid_pymethods.rs:113:12 + | +113 | #[pyo3(signature = ())] + | ^^^^^^^^^ error: `signature` not allowed with `classattr` - --> tests/ui/invalid_pymethods.rs:104:12 + --> tests/ui/invalid_pymethods.rs:120:12 | -104 | #[pyo3(signature = ())] +120 | #[pyo3(signature = ())] | ^^^^^^^^^ error: `#[new]` may not be combined with `#[classmethod]` `#[staticmethod]`, `#[classattr]`, `#[getter]`, and `#[setter]` - --> tests/ui/invalid_pymethods.rs:110:7 + --> tests/ui/invalid_pymethods.rs:126:7 | -110 | #[new] +126 | #[new] | ^^^ error: `#[new]` does not take any arguments = help: did you mean `#[new] #[pyo3(signature = ())]`? - --> tests/ui/invalid_pymethods.rs:121:7 + --> tests/ui/invalid_pymethods.rs:137:7 | -121 | #[new(signature = ())] +137 | #[new(signature = ())] | ^^^ error: `#[new]` does not take any arguments = note: this was previously accepted and ignored - --> tests/ui/invalid_pymethods.rs:127:11 + --> tests/ui/invalid_pymethods.rs:143:11 | -127 | #[new = ()] // in this form there's no suggestion to move arguments to `#[pyo3()]` attribute +143 | #[new = ()] // in this form there's no suggestion to move arguments to `#[pyo3()]` attribute | ^ error: `#[classmethod]` does not take any arguments = help: did you mean `#[classmethod] #[pyo3(signature = ())]`? - --> tests/ui/invalid_pymethods.rs:133:7 + --> tests/ui/invalid_pymethods.rs:149:7 | -133 | #[classmethod(signature = ())] +149 | #[classmethod(signature = ())] | ^^^^^^^^^^^ error: `#[staticmethod]` does not take any arguments = help: did you mean `#[staticmethod] #[pyo3(signature = ())]`? - --> tests/ui/invalid_pymethods.rs:139:7 + --> tests/ui/invalid_pymethods.rs:155:7 | -139 | #[staticmethod(signature = ())] +155 | #[staticmethod(signature = ())] | ^^^^^^^^^^^^ error: `#[classattr]` does not take any arguments = help: did you mean `#[classattr] #[pyo3(signature = ())]`? - --> tests/ui/invalid_pymethods.rs:145:7 + --> tests/ui/invalid_pymethods.rs:161:7 | -145 | #[classattr(signature = ())] +161 | #[classattr(signature = ())] | ^^^^^^^^^ error: Python functions cannot have generic type parameters - --> tests/ui/invalid_pymethods.rs:151:23 + --> tests/ui/invalid_pymethods.rs:167:23 | -151 | fn generic_method(value: T) {} +167 | fn generic_method(value: T) {} | ^ error: Python functions cannot have `impl Trait` arguments - --> tests/ui/invalid_pymethods.rs:156:48 + --> tests/ui/invalid_pymethods.rs:172:48 | -156 | fn impl_trait_method_first_arg(impl_trait: impl AsRef) {} +172 | fn impl_trait_method_first_arg(impl_trait: impl AsRef) {} | ^^^^ error: Python functions cannot have `impl Trait` arguments - --> tests/ui/invalid_pymethods.rs:161:56 + --> tests/ui/invalid_pymethods.rs:177:56 | -161 | fn impl_trait_method_second_arg(&self, impl_trait: impl AsRef) {} +177 | fn impl_trait_method_second_arg(&self, impl_trait: impl AsRef) {} | ^^^^ error: `async fn` is not yet supported for Python functions. Additional crates such as `pyo3-asyncio` can be used to integrate async Rust and Python. For more information, see https://github.com/PyO3/pyo3/issues/1632 - --> tests/ui/invalid_pymethods.rs:166:5 + --> tests/ui/invalid_pymethods.rs:182:5 | -166 | async fn async_method(&self) {} +182 | async fn async_method(&self) {} | ^^^^^ error: `pass_module` cannot be used on Python methods - --> tests/ui/invalid_pymethods.rs:171:12 + --> tests/ui/invalid_pymethods.rs:187:12 | -171 | #[pyo3(pass_module)] +187 | #[pyo3(pass_module)] | ^^^^^^^^^^^ error: Python objects are shared, so 'self' cannot be moved out of the Python interpreter. Try `&self`, `&mut self, `slf: PyRef<'_, Self>` or `slf: PyRefMut<'_, Self>`. - --> tests/ui/invalid_pymethods.rs:177:29 + --> tests/ui/invalid_pymethods.rs:193:29 | -177 | fn method_self_by_value(self) {} +193 | fn method_self_by_value(self) {} | ^^^^ error: macros cannot be used as items in `#[pymethods]` impl blocks = note: this was previously accepted and ignored - --> tests/ui/invalid_pymethods.rs:212:5 + --> tests/ui/invalid_pymethods.rs:202:5 | -212 | macro_invocation!(); +202 | macro_invocation!(); | ^^^^^^^^^^^^^^^^ -error[E0119]: conflicting implementations of trait `pyo3::impl_::pyclass::PyClassNewTextSignature` for type `pyo3::impl_::pyclass::PyClassImplCollector` - --> tests/ui/invalid_pymethods.rs:182:1 - | -182 | #[pymethods] - | ^^^^^^^^^^^^ - | | - | first implementation here - | conflicting implementation for `pyo3::impl_::pyclass::PyClassImplCollector` - | - = note: this error originates in the attribute macro `pymethods` (in Nightly builds, run with -Z macro-backtrace for more info) - -error[E0592]: duplicate definitions with name `__pymethod___new____` - --> tests/ui/invalid_pymethods.rs:182:1 - | -182 | #[pymethods] - | ^^^^^^^^^^^^ - | | - | duplicate definitions for `__pymethod___new____` - | other definition for `__pymethod___new____` - | - = note: this error originates in the attribute macro `pymethods` (in Nightly builds, run with -Z macro-backtrace for more info) - -error[E0592]: duplicate definitions with name `__pymethod_func__` - --> tests/ui/invalid_pymethods.rs:197:1 - | -197 | #[pymethods] - | ^^^^^^^^^^^^ - | | - | duplicate definitions for `__pymethod_func__` - | other definition for `__pymethod_func__` - | - = note: this error originates in the attribute macro `pymethods` (in Nightly builds, run with -Z macro-backtrace for more info) +error[E0277]: the trait bound `i32: From<&PyType>` is not satisfied + --> tests/ui/invalid_pymethods.rs:46:45 + | +46 | fn classmethod_wrong_first_argument(_x: i32) -> Self { + | ^^^ the trait `From<&PyType>` is not implemented for `i32` + | + = help: the following other types implement trait `From`: + > + > + > + > + > + > + = note: required for `&PyType` to implement `Into` diff --git a/tests/ui/invalid_pymethods_duplicates.rs b/tests/ui/invalid_pymethods_duplicates.rs new file mode 100644 index 00000000..d05d7095 --- /dev/null +++ b/tests/ui/invalid_pymethods_duplicates.rs @@ -0,0 +1,32 @@ +//! These tests are located in a separate file because they cause conflicting implementation +//! errors, which means other errors such as typechecking errors are not reported. + +use pyo3::prelude::*; + +struct TwoNew {} + +#[pymethods] +impl TwoNew { + #[new] + fn new_1() -> Self { + Self {} + } + + #[new] + fn new_2() -> Self { + Self {} + } +} + +struct DuplicateMethod {} + +#[pymethods] +impl DuplicateMethod { + #[pyo3(name = "func")] + fn func_a(&self) {} + + #[pyo3(name = "func")] + fn func_b(&self) {} +} + +fn main() {} diff --git a/tests/ui/invalid_pymethods_duplicates.stderr b/tests/ui/invalid_pymethods_duplicates.stderr new file mode 100644 index 00000000..38bb6f86 --- /dev/null +++ b/tests/ui/invalid_pymethods_duplicates.stderr @@ -0,0 +1,32 @@ +error[E0119]: conflicting implementations of trait `pyo3::impl_::pyclass::PyClassNewTextSignature` for type `pyo3::impl_::pyclass::PyClassImplCollector` + --> tests/ui/invalid_pymethods_duplicates.rs:8:1 + | +8 | #[pymethods] + | ^^^^^^^^^^^^ + | | + | first implementation here + | conflicting implementation for `pyo3::impl_::pyclass::PyClassImplCollector` + | + = note: this error originates in the attribute macro `pymethods` (in Nightly builds, run with -Z macro-backtrace for more info) + +error[E0592]: duplicate definitions with name `__pymethod___new____` + --> tests/ui/invalid_pymethods_duplicates.rs:8:1 + | +8 | #[pymethods] + | ^^^^^^^^^^^^ + | | + | duplicate definitions for `__pymethod___new____` + | other definition for `__pymethod___new____` + | + = note: this error originates in the attribute macro `pymethods` (in Nightly builds, run with -Z macro-backtrace for more info) + +error[E0592]: duplicate definitions with name `__pymethod_func__` + --> tests/ui/invalid_pymethods_duplicates.rs:23:1 + | +23 | #[pymethods] + | ^^^^^^^^^^^^ + | | + | duplicate definitions for `__pymethod_func__` + | other definition for `__pymethod_func__` + | + = note: this error originates in the attribute macro `pymethods` (in Nightly builds, run with -Z macro-backtrace for more info)