From aba3a3552d8af1a6ac7f09cd094f995bc613b538 Mon Sep 17 00:00:00 2001 From: David Hewitt Date: Wed, 22 Nov 2023 21:30:10 +0000 Subject: [PATCH 1/2] remove type_is_pymodule --- pyo3-macros-backend/src/method.rs | 10 +-- pyo3-macros-backend/src/pyfunction.rs | 69 +++++-------------- pyo3-macros-backend/src/pymethod.rs | 2 +- tests/ui/invalid_need_module_arg_position.rs | 4 +- .../invalid_need_module_arg_position.stderr | 17 +++-- 5 files changed, 40 insertions(+), 62 deletions(-) diff --git a/pyo3-macros-backend/src/method.rs b/pyo3-macros-backend/src/method.rs index 034d079b..b9c78ef6 100644 --- a/pyo3-macros-backend/src/method.rs +++ b/pyo3-macros-backend/src/method.rs @@ -81,7 +81,7 @@ pub enum FnType { FnNewClass, FnClass, FnStatic, - FnModule, + FnModule(Span), ClassAttribute, } @@ -93,7 +93,7 @@ impl FnType { | FnType::Fn(_) | FnType::FnClass | FnType::FnNewClass - | FnType::FnModule => true, + | FnType::FnModule(_) => true, FnType::FnNew | FnType::FnStatic | FnType::ClassAttribute => false, } } @@ -117,8 +117,8 @@ impl FnType { ::std::convert::Into::into(_pyo3::types::PyType::from_type_ptr(py, _slf as *mut _pyo3::ffi::PyTypeObject)), } } - FnType::FnModule => { - quote! { + FnType::FnModule(span) => { + quote_spanned! { *span => #[allow(clippy::useless_conversion)] ::std::convert::Into::into(py.from_borrowed_ptr::<_pyo3::types::PyModule>(_slf)), } @@ -633,7 +633,7 @@ impl<'a> FnSpec<'a> { // Getters / Setters / ClassAttribute are not callables on the Python side FnType::Getter(_) | FnType::Setter(_) | FnType::ClassAttribute => return None, FnType::Fn(_) => Some("self"), - FnType::FnModule => Some("module"), + FnType::FnModule(_) => Some("module"), 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 12f25411..5aedc410 100644 --- a/pyo3-macros-backend/src/pyfunction.rs +++ b/pyo3-macros-backend/src/pyfunction.rs @@ -189,30 +189,30 @@ pub fn impl_wrap_pyfunction( let python_name = name.map_or_else(|| func.sig.ident.unraw(), |name| name.value.0); - let mut arguments = func - .sig - .inputs - .iter_mut() - .map(FnArg::parse) - .collect::>>()?; - let tp = if pass_module.is_some() { - const PASS_MODULE_ERR: &str = - "expected &PyModule or Py as first argument with `pass_module`"; - ensure_spanned!( - !arguments.is_empty(), - func.span() => PASS_MODULE_ERR - ); - let arg = arguments.remove(0); - ensure_spanned!( - type_is_pymodule(arg.ty), - arg.ty.span() => PASS_MODULE_ERR - ); - method::FnType::FnModule + 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`" + ), + }; + method::FnType::FnModule(span) } else { method::FnType::FnStatic }; + let arguments = func + .sig + .inputs + .iter_mut() + .skip(if tp.skip_first_rust_argument_in_python_signature() { + 1 + } else { + 0 + }) + .map(FnArg::parse) + .collect::>>()?; + let signature = if let Some(signature) = signature { FunctionSignature::from_arguments_and_attribute(arguments, signature)? } else { @@ -269,34 +269,3 @@ pub fn impl_wrap_pyfunction( }; Ok(wrapped_pyfunction) } - -fn type_is_pymodule(ty: &syn::Type) -> bool { - let is_pymodule = |typath: &syn::TypePath| { - typath - .path - .segments - .last() - .map_or(false, |seg| seg.ident == "PyModule") - }; - match ty { - syn::Type::Reference(tyref) => { - if let syn::Type::Path(typath) = tyref.elem.as_ref() { - return is_pymodule(typath); - } - } - syn::Type::Path(typath) => { - if let Some(syn::PathSegment { - arguments: syn::PathArguments::AngleBracketed(args), - .. - }) = typath.path.segments.last() - { - if args.args.len() != 1 { - return false; - } - return matches!(args.args.first().unwrap(), syn::GenericArgument::Type(syn::Type::Path(typath)) if is_pymodule(typath)); - } - } - _ => {} - } - false -} diff --git a/pyo3-macros-backend/src/pymethod.rs b/pyo3-macros-backend/src/pymethod.rs index a8fd3b41..e058e2f2 100644 --- a/pyo3-macros-backend/src/pymethod.rs +++ b/pyo3-macros-backend/src/pymethod.rs @@ -257,7 +257,7 @@ pub fn gen_py_method( doc: spec.get_doc(meth_attrs), }, )?), - (_, FnType::FnModule) => { + (_, FnType::FnModule(_)) => { unreachable!("methods cannot be FnModule") } }) diff --git a/tests/ui/invalid_need_module_arg_position.rs b/tests/ui/invalid_need_module_arg_position.rs index b3722ae4..2d45f35b 100644 --- a/tests/ui/invalid_need_module_arg_position.rs +++ b/tests/ui/invalid_need_module_arg_position.rs @@ -3,10 +3,10 @@ use pyo3::prelude::*; #[pymodule] fn module(_py: Python<'_>, m: &PyModule) -> PyResult<()> { #[pyfn(m, pass_module)] - fn fail(string: &str, module: &PyModule) -> PyResult<&str> { + fn fail<'py>(string: &str, module: &'py PyModule) -> PyResult<&'py str> { module.name() } Ok(()) } -fn main(){} +fn main() {} diff --git a/tests/ui/invalid_need_module_arg_position.stderr b/tests/ui/invalid_need_module_arg_position.stderr index 65ab4b16..b9231c30 100644 --- a/tests/ui/invalid_need_module_arg_position.stderr +++ b/tests/ui/invalid_need_module_arg_position.stderr @@ -1,5 +1,14 @@ -error: expected &PyModule or Py as first argument with `pass_module` - --> tests/ui/invalid_need_module_arg_position.rs:6:21 +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(string: &str, module: &PyModule) -> PyResult<&str> { - | ^ +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>` From 5ac56b8eb03d94e5b3ec0e0a801caf4da168b29b Mon Sep 17 00:00:00 2001 From: David Hewitt Date: Fri, 24 Nov 2023 03:11:05 +0000 Subject: [PATCH 2/2] 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 | 158 ++++++++---------- tests/ui/invalid_pymethods_duplicates.rs | 32 ++++ tests/ui/invalid_pymethods_duplicates.stderr | 32 ++++ 12 files changed, 223 insertions(+), 177 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 b9c78ef6..f2666459 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) => { @@ -306,7 +308,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) @@ -355,36 +357,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() { @@ -516,17 +522,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> { @@ -535,7 +536,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) } } @@ -634,7 +635,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 5aedc410..6f606516 100644 --- a/pyo3-macros-backend/src/pyfunction.rs +++ b/pyo3-macros-backend/src/pyfunction.rs @@ -193,7 +193,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 e058e2f2..01030cee 100644 --- a/pyo3-macros-backend/src/pymethod.rs +++ b/pyo3-macros-backend/src/pymethod.rs @@ -224,7 +224,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), @@ -237,7 +237,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)?) } @@ -311,7 +311,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 81491cb4..fba7d8b5 100644 --- a/tests/ui/invalid_pyfunctions.rs +++ b/tests/ui/invalid_pyfunctions.rs @@ -15,4 +15,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 ec7e54cc..a3fd845d 100644 --- a/tests/ui/invalid_pyfunctions.stderr +++ b/tests/ui/invalid_pyfunctions.stderr @@ -28,3 +28,24 @@ error: required arguments after an `Option<_>` argument are ambiguous | 16 | 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:19:37 + | +19 | 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:22:43 + | +22 | 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 2f8bb841..00bddfe2 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)] @@ -172,32 +188,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 82abe1d1..1a50c4da 100644 --- a/tests/ui/invalid_pymethods.stderr +++ b/tests/ui/invalid_pymethods.stderr @@ -22,186 +22,174 @@ 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: `pass_module` cannot be used on Python methods - --> tests/ui/invalid_pymethods.rs:166:12 + --> tests/ui/invalid_pymethods.rs:182:12 | -166 | #[pyo3(pass_module)] +182 | #[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:172:29 + --> tests/ui/invalid_pymethods.rs:188:29 | -172 | fn method_self_by_value(self) {} +188 | 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:207:5 + --> tests/ui/invalid_pymethods.rs:197:5 | -207 | macro_invocation!(); +197 | macro_invocation!(); | ^^^^^^^^^^^^^^^^ -error[E0119]: conflicting implementations of trait `pyo3::impl_::pyclass::PyClassNewTextSignature` for type `pyo3::impl_::pyclass::PyClassImplCollector` - --> tests/ui/invalid_pymethods.rs:177:1 - | -177 | #[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:177:1 - | -177 | #[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:192:1 - | -192 | #[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)