Merge pull request #3484 from davidhewitt/new-no-arguments

error on passing arguments to `#[new]` and similar attributes
This commit is contained in:
David Hewitt 2023-10-08 21:21:51 +00:00 committed by GitHub
commit 234c7b3d66
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
6 changed files with 328 additions and 194 deletions

View File

@ -0,0 +1 @@
Emit error on invalid arguments to `#[new]`, `#[classmethod]`, `#[staticmethod]`, and `#[classattr]`.

View File

@ -1,3 +1,5 @@
use std::fmt::Display;
use crate::attributes::{TextSignatureAttribute, TextSignatureAttributeValue};
use crate::params::impl_arg_params;
use crate::pyfunction::{FunctionSignature, PyFunctionArgPyO3Attributes};
@ -9,7 +11,7 @@ use quote::ToTokens;
use quote::{quote, quote_spanned};
use syn::ext::IdentExt;
use syn::spanned::Spanned;
use syn::Result;
use syn::{Ident, Result};
#[derive(Clone, Debug)]
pub struct FnArg<'a> {
@ -69,24 +71,6 @@ fn handle_argument_error(pat: &syn::Pat) -> syn::Error {
syn::Error::new(span, msg)
}
#[derive(Clone, PartialEq, Debug, Copy, Eq)]
pub enum MethodTypeAttribute {
/// `#[new]`
New,
/// `#[new]` && `#[classmethod]`
NewClassMethod,
/// `#[classmethod]`
ClassMethod,
/// `#[classattr]`
ClassAttribute,
/// `#[staticmethod]`
StaticMethod,
/// `#[getter]`
Getter,
/// `#[setter]`
Setter,
}
#[derive(Clone, Debug)]
pub enum FnType {
Getter(SelfType),
@ -101,6 +85,18 @@ pub enum FnType {
}
impl FnType {
pub fn skip_first_rust_argument_in_python_signature(&self) -> bool {
match self {
FnType::Getter(_)
| FnType::Setter(_)
| FnType::Fn(_)
| FnType::FnClass
| FnType::FnNewClass
| FnType::FnModule => true,
FnType::FnNew | FnType::FnStatic | FnType::ClassAttribute => false,
}
}
pub fn self_arg(&self, cls: Option<&syn::Type>, error_mode: ExtractErrorMode) -> TokenStream {
match self {
FnType::Getter(st) | FnType::Setter(st) | FnType::Fn(st) => {
@ -278,31 +274,25 @@ impl<'a> FnSpec<'a> {
..
} = options;
let MethodAttributes {
ty: fn_type_attr,
mut python_name,
} = parse_method_attributes(meth_attrs, name.map(|name| name.value.0))?;
let mut python_name = name.map(|name| name.value.0);
let (fn_type, skip_first_arg, fixed_convention) =
Self::parse_fn_type(sig, fn_type_attr, &mut python_name)?;
let fn_type = Self::parse_fn_type(sig, meth_attrs, &mut python_name)?;
ensure_signatures_on_valid_method(&fn_type, signature.as_ref(), text_signature.as_ref())?;
let name = &sig.ident;
let ty = get_return_info(&sig.output);
let python_name = python_name.as_ref().unwrap_or(name).unraw();
let arguments: Vec<_> = if skip_first_arg {
sig.inputs
.iter_mut()
.skip(1)
.map(FnArg::parse)
.collect::<Result<_>>()?
} else {
sig.inputs
.iter_mut()
.map(FnArg::parse)
.collect::<Result<_>>()?
};
let arguments: Vec<_> = sig
.inputs
.iter_mut()
.skip(if fn_type.skip_first_rust_argument_in_python_signature() {
1
} else {
0
})
.map(FnArg::parse)
.collect::<Result<_>>()?;
let signature = if let Some(signature) = signature {
FunctionSignature::from_arguments_and_attribute(arguments, signature)?
@ -310,8 +300,11 @@ impl<'a> FnSpec<'a> {
FunctionSignature::from_arguments(arguments)?
};
let convention =
fixed_convention.unwrap_or_else(|| CallingConvention::from_signature(&signature));
let convention = if matches!(fn_type, FnType::FnNew | FnType::FnNewClass) {
CallingConvention::TpNew
} else {
CallingConvention::from_signature(&signature)
};
Ok(FnSpec {
tp: fn_type,
@ -331,9 +324,11 @@ impl<'a> FnSpec<'a> {
fn parse_fn_type(
sig: &syn::Signature,
fn_type_attr: Option<MethodTypeAttribute>,
meth_attrs: &mut Vec<syn::Attribute>,
python_name: &mut Option<syn::Ident>,
) -> Result<(FnType, bool, Option<CallingConvention>)> {
) -> Result<FnType> {
let mut method_attributes = parse_method_attributes(meth_attrs)?;
let name = &sig.ident;
let parse_receiver = |msg: &'static str| {
let first_arg = sig
@ -351,54 +346,77 @@ impl<'a> FnSpec<'a> {
.map(|stripped| syn::Ident::new(stripped, name.span()))
};
let (fn_type, skip_first_arg, fixed_convention) = match fn_type_attr {
Some(MethodTypeAttribute::StaticMethod) => (FnType::FnStatic, false, None),
Some(MethodTypeAttribute::ClassAttribute) => (FnType::ClassAttribute, false, None),
Some(MethodTypeAttribute::New) | Some(MethodTypeAttribute::NewClassMethod) => {
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!(fn_type_attr, Some(MethodTypeAttribute::New)) {
(FnType::FnNew, false, Some(CallingConvention::TpNew))
if matches!(method_attributes.as_slice(), [MethodTypeAttribute::New(_)]) {
FnType::FnNew
} else {
(FnType::FnNewClass, true, Some(CallingConvention::TpNew))
FnType::FnNewClass
}
}
Some(MethodTypeAttribute::ClassMethod) => (FnType::FnClass, true, None),
Some(MethodTypeAttribute::Getter) => {
// Strip off "get_" prefix if needed
if python_name.is_none() {
[MethodTypeAttribute::ClassMethod(_)] => FnType::FnClass,
[MethodTypeAttribute::Getter(_, name)] => {
if let Some(name) = name.take() {
ensure_spanned!(
python_name.replace(name).is_none(),
python_name.span() => "`name` may only be specified once"
);
} else if python_name.is_none() {
// Strip off "get_" prefix if needed
*python_name = strip_fn_name("get_");
}
(
FnType::Getter(parse_receiver("expected receiver for #[getter]")?),
true,
None,
)
FnType::Getter(parse_receiver("expected receiver for `#[getter]`")?)
}
Some(MethodTypeAttribute::Setter) => {
// Strip off "set_" prefix if needed
if python_name.is_none() {
[MethodTypeAttribute::Setter(_, name)] => {
if let Some(name) = name.take() {
ensure_spanned!(
python_name.replace(name).is_none(),
python_name.span() => "`name` may only be specified once"
);
} else if python_name.is_none() {
// Strip off "set_" prefix if needed
*python_name = strip_fn_name("set_");
}
(
FnType::Setter(parse_receiver("expected receiver for #[setter]")?),
true,
None,
)
FnType::Setter(parse_receiver("expected receiver for `#[setter]`")?)
}
[first, rest @ .., last] => {
// Join as many of the spans together as possible
let span = rest
.iter()
.fold(first.span(), |s, next| s.join(next.span()).unwrap_or(s));
let span = span.join(last.span()).unwrap_or(span);
// List all the attributes in the error message
let mut msg = format!("`{}` may not be combined with", first);
let mut is_first = true;
for attr in &*rest {
msg.push_str(&format!(" `{}`", attr));
if is_first {
is_first = false;
} else {
msg.push(',');
}
}
if !rest.is_empty() {
msg.push_str(" and");
}
msg.push_str(&format!(" `{}`", last));
bail_spanned!(span => msg)
}
None => (
FnType::Fn(parse_receiver(
"static method needs #[staticmethod] attribute",
)?),
true,
None,
),
};
Ok((fn_type, skip_first_arg, fixed_convention))
Ok(fn_type)
}
/// Return a C wrapper function for this signature.
@ -604,107 +622,128 @@ impl<'a> FnSpec<'a> {
}
}
#[derive(Debug)]
struct MethodAttributes {
ty: Option<MethodTypeAttribute>,
python_name: Option<syn::Ident>,
enum MethodTypeAttribute {
New(Span),
ClassMethod(Span),
StaticMethod(Span),
Getter(Span, Option<Ident>),
Setter(Span, Option<Ident>),
ClassAttribute(Span),
}
fn parse_method_attributes(
attrs: &mut Vec<syn::Attribute>,
mut python_name: Option<syn::Ident>,
) -> Result<MethodAttributes> {
impl MethodTypeAttribute {
fn span(&self) -> Span {
match self {
MethodTypeAttribute::New(span)
| MethodTypeAttribute::ClassMethod(span)
| MethodTypeAttribute::StaticMethod(span)
| MethodTypeAttribute::Getter(span, _)
| MethodTypeAttribute::Setter(span, _)
| MethodTypeAttribute::ClassAttribute(span) => *span,
}
}
/// Attempts to parse a method type attribute.
///
/// If the attribute does not match one of the attribute names, returns `Ok(None)`.
///
/// Otherwise will either return a parse error or the attribute.
fn parse_if_matching_attribute(attr: &syn::Attribute) -> Result<Option<Self>> {
fn ensure_no_arguments(meta: &syn::Meta, ident: &str) -> syn::Result<()> {
match meta {
syn::Meta::Path(_) => Ok(()),
syn::Meta::List(l) => bail_spanned!(
l.span() => format!(
"`#[{ident}]` does not take any arguments\n= help: did you mean `#[{ident}] #[pyo3({meta})]`?",
ident = ident,
meta = l.tokens,
)
),
syn::Meta::NameValue(nv) => {
bail_spanned!(nv.eq_token.span() => format!(
"`#[{}]` does not take any arguments\n= note: this was previously accepted and ignored",
ident
))
}
}
}
fn extract_name(meta: &syn::Meta, ident: &str) -> Result<Option<Ident>> {
match meta {
syn::Meta::Path(_) => Ok(None),
syn::Meta::NameValue(nv) => bail_spanned!(
nv.eq_token.span() => format!("expected `#[{}(name)]` to set the name", ident)
),
syn::Meta::List(l) => {
if let Ok(name) = l.parse_args::<syn::Ident>() {
Ok(Some(name))
} else if let Ok(name) = l.parse_args::<syn::LitStr>() {
name.parse().map(Some)
} else {
bail_spanned!(l.tokens.span() => "expected ident or string literal for property name");
}
}
}
}
let meta = &attr.meta;
let path = meta.path();
if path.is_ident("new") {
ensure_no_arguments(meta, "new")?;
Ok(Some(MethodTypeAttribute::New(path.span())))
} else if path.is_ident("__new__") {
// TODO deprecate this form?
ensure_no_arguments(meta, "__new__")?;
Ok(Some(MethodTypeAttribute::New(path.span())))
} else if path.is_ident("classmethod") {
ensure_no_arguments(meta, "classmethod")?;
Ok(Some(MethodTypeAttribute::ClassMethod(path.span())))
} else if path.is_ident("staticmethod") {
ensure_no_arguments(meta, "staticmethod")?;
Ok(Some(MethodTypeAttribute::StaticMethod(path.span())))
} else if path.is_ident("classattr") {
ensure_no_arguments(meta, "classattr")?;
Ok(Some(MethodTypeAttribute::ClassAttribute(path.span())))
} else if path.is_ident("getter") {
let name = extract_name(meta, "getter")?;
Ok(Some(MethodTypeAttribute::Getter(path.span(), name)))
} else if path.is_ident("setter") {
let name = extract_name(meta, "setter")?;
Ok(Some(MethodTypeAttribute::Setter(path.span(), name)))
} else {
Ok(None)
}
}
}
impl Display for MethodTypeAttribute {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
MethodTypeAttribute::New(_) => "#[new]".fmt(f),
MethodTypeAttribute::ClassMethod(_) => "#[classmethod]".fmt(f),
MethodTypeAttribute::StaticMethod(_) => "#[staticmethod]".fmt(f),
MethodTypeAttribute::Getter(_, _) => "#[getter]".fmt(f),
MethodTypeAttribute::Setter(_, _) => "#[setter]".fmt(f),
MethodTypeAttribute::ClassAttribute(_) => "#[classattr]".fmt(f),
}
}
}
fn parse_method_attributes(attrs: &mut Vec<syn::Attribute>) -> Result<Vec<MethodTypeAttribute>> {
let mut new_attrs = Vec::new();
let mut ty: Option<MethodTypeAttribute> = None;
macro_rules! set_compound_ty {
($new_ty:expr, $ident:expr) => {
ty = match (ty, $new_ty) {
(None, new_ty) => Some(new_ty),
(Some(MethodTypeAttribute::ClassMethod), MethodTypeAttribute::New) => Some(MethodTypeAttribute::NewClassMethod),
(Some(MethodTypeAttribute::New), MethodTypeAttribute::ClassMethod) => Some(MethodTypeAttribute::NewClassMethod),
(Some(_), _) => bail_spanned!($ident.span() => "can only combine `new` and `classmethod`"),
};
};
}
macro_rules! set_ty {
($new_ty:expr, $ident:expr) => {
ensure_spanned!(
ty.replace($new_ty).is_none(),
$ident.span() => "cannot combine these method types"
);
};
}
let mut found_attrs = Vec::new();
for attr in attrs.drain(..) {
match attr.meta {
syn::Meta::Path(ref name) => {
if name.is_ident("new") || name.is_ident("__new__") {
set_compound_ty!(MethodTypeAttribute::New, name);
} else if name.is_ident("classmethod") {
set_compound_ty!(MethodTypeAttribute::ClassMethod, name);
} else if name.is_ident("staticmethod") {
set_ty!(MethodTypeAttribute::StaticMethod, name);
} else if name.is_ident("classattr") {
set_ty!(MethodTypeAttribute::ClassAttribute, name);
} else if name.is_ident("setter") || name.is_ident("getter") {
if let syn::AttrStyle::Inner(_) = attr.style {
bail_spanned!(
attr.span() => "inner attribute is not supported for setter and getter"
);
}
if name.is_ident("setter") {
set_ty!(MethodTypeAttribute::Setter, name);
} else {
set_ty!(MethodTypeAttribute::Getter, name);
}
} else {
new_attrs.push(attr)
}
}
syn::Meta::List(ref ml @ syn::MetaList { ref path, .. }) => {
if path.is_ident("new") {
set_ty!(MethodTypeAttribute::New, path);
} else if path.is_ident("setter") || path.is_ident("getter") {
if let syn::AttrStyle::Inner(_) = attr.style {
bail_spanned!(
attr.span() => "inner attribute is not supported for setter and getter"
);
}
if path.is_ident("setter") {
set_ty!(MethodTypeAttribute::Setter, path);
} else {
set_ty!(MethodTypeAttribute::Getter, path);
};
ensure_spanned!(
python_name.is_none(),
python_name.span() => "`name` may only be specified once"
);
if let Ok(ident) = ml.parse_args::<syn::Ident>() {
python_name = Some(ident);
} else if let Ok(syn::Lit::Str(s)) = ml.parse_args::<syn::Lit>() {
python_name = Some(s.parse()?);
} else {
return Err(syn::Error::new_spanned(
ml,
"expected ident or string literal for property name",
));
}
} else {
new_attrs.push(attr)
}
}
syn::Meta::NameValue(_) => new_attrs.push(attr),
match MethodTypeAttribute::parse_if_matching_attribute(&attr)? {
Some(attr) => found_attrs.push(attr),
None => new_attrs.push(attr),
}
}
*attrs = new_attrs;
Ok(MethodAttributes { ty, python_name })
Ok(found_attrs)
}
const IMPL_TRAIT_ERR: &str = "Python functions cannot have `impl Trait` arguments";

View File

@ -26,4 +26,17 @@ impl TestClass {
fn new(&self) -> Self { Self { num: 0 } }
}
#[pymethods]
impl TestClass {
#[getter(1)]
fn get_one(&self) -> Self { Self { num: 0 } }
}
#[pymethods]
impl TestClass {
#[getter = 1]
fn get_two(&self) -> Self { Self { num: 0 } }
}
fn main() {}

View File

@ -1,8 +1,8 @@
error: `name` may only be specified once
--> tests/ui/invalid_pymethod_names.rs:10:19
--> tests/ui/invalid_pymethod_names.rs:11:14
|
10 | #[pyo3(name = "num")]
| ^^^^^
11 | #[getter(number)]
| ^^^^^^
error: `name` may only be specified once
--> tests/ui/invalid_pymethod_names.rs:18:12
@ -15,3 +15,15 @@ error: `name` not allowed with `#[new]`
|
24 | #[pyo3(name = "makenew")]
| ^^^^^^^^^
error: expected ident or string literal for property name
--> tests/ui/invalid_pymethod_names.rs:31:14
|
31 | #[getter(1)]
| ^
error: expected `#[getter(name)]` to set the name
--> tests/ui/invalid_pymethod_names.rs:37:14
|
37 | #[getter = 1]
| ^

View File

@ -108,11 +108,45 @@ impl MyClass {
#[pymethods]
impl MyClass {
#[classattr]
#[new]
#[classmethod]
#[staticmethod]
#[classattr]
#[getter(x)]
#[setter(x)]
fn multiple_method_types() {}
}
#[pymethods]
impl MyClass {
#[new(signature = ())]
fn new_takes_no_arguments(&self) {}
}
#[pymethods]
impl MyClass {
#[new = ()] // in this form there's no suggestion to move arguments to `#[pyo3()]` attribute
fn new_takes_no_arguments_nv(&self) {}
}
#[pymethods]
impl MyClass {
#[classmethod(signature = ())]
fn classmethod_takes_no_arguments(&self) {}
}
#[pymethods]
impl MyClass {
#[staticmethod(signature = ())]
fn staticmethod_takes_no_arguments(&self) {}
}
#[pymethods]
impl MyClass {
#[classattr(signature = ())]
fn classattr_takes_no_arguments(&self) {}
}
#[pymethods]
impl MyClass {
fn generic_method<T>(value: T) {}

View File

@ -22,13 +22,13 @@ error: unexpected receiver
26 | fn staticmethod_with_receiver(&self) {}
| ^
error: expected receiver for #[getter]
error: expected receiver for `#[getter]`
--> tests/ui/invalid_pymethods.rs:39:5
|
39 | fn getter_without_receiver() {}
| ^^
error: expected receiver for #[setter]
error: expected receiver for `#[setter]`
--> tests/ui/invalid_pymethods.rs:45:5
|
45 | fn setter_without_receiver() {}
@ -88,62 +88,97 @@ error: `signature` not allowed with `classattr`
105 | #[pyo3(signature = ())]
| ^^^^^^^^^
error: cannot combine these method types
--> tests/ui/invalid_pymethods.rs:112:7
error: `#[new]` may not be combined with `#[classmethod]` `#[staticmethod]`, `#[classattr]`, `#[getter]`, and `#[setter]`
--> tests/ui/invalid_pymethods.rs:111:7
|
112 | #[staticmethod]
111 | #[new]
| ^^^
error: `#[new]` does not take any arguments
= help: did you mean `#[new] #[pyo3(signature = ())]`?
--> tests/ui/invalid_pymethods.rs:122:7
|
122 | #[new(signature = ())]
| ^^^
error: `#[new]` does not take any arguments
= note: this was previously accepted and ignored
--> tests/ui/invalid_pymethods.rs:128:11
|
128 | #[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:134:7
|
134 | #[classmethod(signature = ())]
| ^^^^^^^^^^^
error: `#[staticmethod]` does not take any arguments
= help: did you mean `#[staticmethod] #[pyo3(signature = ())]`?
--> tests/ui/invalid_pymethods.rs:140:7
|
140 | #[staticmethod(signature = ())]
| ^^^^^^^^^^^^
error: Python functions cannot have generic type parameters
--> tests/ui/invalid_pymethods.rs:118:23
error: `#[classattr]` does not take any arguments
= help: did you mean `#[classattr] #[pyo3(signature = ())]`?
--> tests/ui/invalid_pymethods.rs:146:7
|
118 | fn generic_method<T>(value: T) {}
146 | #[classattr(signature = ())]
| ^^^^^^^^^
error: Python functions cannot have generic type parameters
--> tests/ui/invalid_pymethods.rs:152:23
|
152 | fn generic_method<T>(value: T) {}
| ^
error: Python functions cannot have `impl Trait` arguments
--> tests/ui/invalid_pymethods.rs:123:48
--> tests/ui/invalid_pymethods.rs:157:48
|
123 | fn impl_trait_method_first_arg(impl_trait: impl AsRef<PyAny>) {}
157 | fn impl_trait_method_first_arg(impl_trait: impl AsRef<PyAny>) {}
| ^^^^
error: Python functions cannot have `impl Trait` arguments
--> tests/ui/invalid_pymethods.rs:128:56
--> tests/ui/invalid_pymethods.rs:162:56
|
128 | fn impl_trait_method_second_arg(&self, impl_trait: impl AsRef<PyAny>) {}
162 | fn impl_trait_method_second_arg(&self, impl_trait: impl AsRef<PyAny>) {}
| ^^^^
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:133:5
--> tests/ui/invalid_pymethods.rs:167:5
|
133 | async fn async_method(&self) {}
167 | async fn async_method(&self) {}
| ^^^^^
error: `pass_module` cannot be used on Python methods
--> tests/ui/invalid_pymethods.rs:138:12
--> tests/ui/invalid_pymethods.rs:172:12
|
138 | #[pyo3(pass_module)]
172 | #[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:144:29
--> tests/ui/invalid_pymethods.rs:178:29
|
144 | fn method_self_by_value(self) {}
178 | 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:179:5
--> tests/ui/invalid_pymethods.rs:213:5
|
179 | macro_invocation!();
213 | macro_invocation!();
| ^^^^^^^^^^^^^^^^
error[E0119]: conflicting implementations of trait `pyo3::impl_::pyclass::PyClassNewTextSignature<TwoNew>` for type `pyo3::impl_::pyclass::PyClassImplCollector<TwoNew>`
--> tests/ui/invalid_pymethods.rs:149:1
--> tests/ui/invalid_pymethods.rs:183:1
|
149 | #[pymethods]
183 | #[pymethods]
| ^^^^^^^^^^^^
| |
| first implementation here
@ -152,9 +187,9 @@ error[E0119]: conflicting implementations of trait `pyo3::impl_::pyclass::PyClas
= 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:149:1
--> tests/ui/invalid_pymethods.rs:183:1
|
149 | #[pymethods]
183 | #[pymethods]
| ^^^^^^^^^^^^
| |
| duplicate definitions for `__pymethod___new____`
@ -163,9 +198,9 @@ error[E0592]: duplicate definitions with name `__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:164:1
--> tests/ui/invalid_pymethods.rs:198:1
|
164 | #[pymethods]
198 | #[pymethods]
| ^^^^^^^^^^^^
| |
| duplicate definitions for `__pymethod_func__`