Skip to content

Commit 1498918

Browse files
committed
error on passing arguments to #[new] and similar attributes
1 parent f335f42 commit 1498918

File tree

5 files changed

+233
-153
lines changed

5 files changed

+233
-153
lines changed

newsfragments/3483.fixed.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
Emit error on invalid arguments to `#[new]`, `#[classmethod]`, `#[staticmethod]`, and `#[classattr]`.

pyo3-macros-backend/src/method.rs

Lines changed: 144 additions & 129 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@ use quote::ToTokens;
99
use quote::{quote, quote_spanned};
1010
use syn::ext::IdentExt;
1111
use syn::spanned::Spanned;
12-
use syn::Result;
12+
use syn::{Ident, Result};
1313

1414
#[derive(Clone, Debug)]
1515
pub struct FnArg<'a> {
@@ -69,24 +69,6 @@ fn handle_argument_error(pat: &syn::Pat) -> syn::Error {
6969
syn::Error::new(span, msg)
7070
}
7171

72-
#[derive(Clone, PartialEq, Debug, Copy, Eq)]
73-
pub enum MethodTypeAttribute {
74-
/// `#[new]`
75-
New,
76-
/// `#[new]` && `#[classmethod]`
77-
NewClassMethod,
78-
/// `#[classmethod]`
79-
ClassMethod,
80-
/// `#[classattr]`
81-
ClassAttribute,
82-
/// `#[staticmethod]`
83-
StaticMethod,
84-
/// `#[getter]`
85-
Getter,
86-
/// `#[setter]`
87-
Setter,
88-
}
89-
9072
#[derive(Clone, Debug)]
9173
pub enum FnType {
9274
Getter(SelfType),
@@ -278,13 +260,10 @@ impl<'a> FnSpec<'a> {
278260
..
279261
} = options;
280262

281-
let MethodAttributes {
282-
ty: fn_type_attr,
283-
mut python_name,
284-
} = parse_method_attributes(meth_attrs, name.map(|name| name.value.0))?;
263+
let mut python_name = name.map(|name| name.value.0);
285264

286265
let (fn_type, skip_first_arg, fixed_convention) =
287-
Self::parse_fn_type(sig, fn_type_attr, &mut python_name)?;
266+
Self::parse_fn_type(sig, meth_attrs, &mut python_name)?;
288267
ensure_signatures_on_valid_method(&fn_type, signature.as_ref(), text_signature.as_ref())?;
289268

290269
let name = &sig.ident;
@@ -331,9 +310,11 @@ impl<'a> FnSpec<'a> {
331310

332311
fn parse_fn_type(
333312
sig: &syn::Signature,
334-
fn_type_attr: Option<MethodTypeAttribute>,
313+
meth_attrs: &mut Vec<syn::Attribute>,
335314
python_name: &mut Option<syn::Ident>,
336315
) -> Result<(FnType, bool, Option<CallingConvention>)> {
316+
let mut method_attributes = parse_method_attributes(meth_attrs)?;
317+
337318
let name = &sig.ident;
338319
let parse_receiver = |msg: &'static str| {
339320
let first_arg = sig
@@ -351,52 +332,70 @@ impl<'a> FnSpec<'a> {
351332
.map(|stripped| syn::Ident::new(stripped, name.span()))
352333
};
353334

354-
let (fn_type, skip_first_arg, fixed_convention) = match fn_type_attr {
355-
Some(MethodTypeAttribute::StaticMethod) => (FnType::FnStatic, false, None),
356-
Some(MethodTypeAttribute::ClassAttribute) => (FnType::ClassAttribute, false, None),
357-
Some(MethodTypeAttribute::New) | Some(MethodTypeAttribute::NewClassMethod) => {
335+
let (fn_type, skip_first_arg, fixed_convention) = match method_attributes.as_mut_slice() {
336+
[] => (
337+
FnType::Fn(parse_receiver(
338+
"static method needs #[staticmethod] attribute",
339+
)?),
340+
true,
341+
None,
342+
),
343+
[MethodTypeAttribute::StaticMethod(_)] => (FnType::FnStatic, false, None),
344+
[MethodTypeAttribute::ClassAttribute(_)] => (FnType::ClassAttribute, false, None),
345+
[MethodTypeAttribute::New(_)]
346+
| [MethodTypeAttribute::New(_), MethodTypeAttribute::ClassMethod(_)]
347+
| [MethodTypeAttribute::ClassMethod(_), MethodTypeAttribute::New(_)] => {
358348
if let Some(name) = &python_name {
359349
bail_spanned!(name.span() => "`name` not allowed with `#[new]`");
360350
}
361351
*python_name = Some(syn::Ident::new("__new__", Span::call_site()));
362-
if matches!(fn_type_attr, Some(MethodTypeAttribute::New)) {
352+
if matches!(method_attributes.as_slice(), [MethodTypeAttribute::New(_)]) {
363353
(FnType::FnNew, false, Some(CallingConvention::TpNew))
364354
} else {
365355
(FnType::FnNewClass, true, Some(CallingConvention::TpNew))
366356
}
367357
}
368-
Some(MethodTypeAttribute::ClassMethod) => (FnType::FnClass, true, None),
369-
Some(MethodTypeAttribute::Getter) => {
370-
// Strip off "get_" prefix if needed
371-
if python_name.is_none() {
358+
[MethodTypeAttribute::ClassMethod(_)] => (FnType::FnClass, true, None),
359+
[MethodTypeAttribute::Getter(_, name)] => {
360+
if let Some(name) = name.take() {
361+
ensure_spanned!(
362+
python_name.replace(name).is_none(),
363+
python_name.span() => "`name` may only be specified once"
364+
);
365+
} else if python_name.is_none() {
366+
// Strip off "get_" prefix if needed
372367
*python_name = strip_fn_name("get_");
373368
}
374369

375370
(
376-
FnType::Getter(parse_receiver("expected receiver for #[getter]")?),
371+
FnType::Getter(parse_receiver("expected receiver for `#[getter]`")?),
377372
true,
378373
None,
379374
)
380375
}
381-
Some(MethodTypeAttribute::Setter) => {
382-
// Strip off "set_" prefix if needed
383-
if python_name.is_none() {
376+
[MethodTypeAttribute::Setter(_, name)] => {
377+
if let Some(name) = name.take() {
378+
ensure_spanned!(
379+
python_name.replace(name).is_none(),
380+
python_name.span() => "`name` may only be specified once"
381+
);
382+
} else if python_name.is_none() {
383+
// Strip off "get_" prefix if needed
384384
*python_name = strip_fn_name("set_");
385385
}
386386

387387
(
388-
FnType::Setter(parse_receiver("expected receiver for #[setter]")?),
388+
FnType::Setter(parse_receiver("expected receiver for `#[setter]`")?),
389389
true,
390390
None,
391391
)
392392
}
393-
None => (
394-
FnType::Fn(parse_receiver(
395-
"static method needs #[staticmethod] attribute",
396-
)?),
397-
true,
398-
None,
399-
),
393+
[first, second, ..] => {
394+
bail_spanned!(
395+
second.span() =>
396+
format!("`#[{}]` may not be combined with `#[{}]`", first.ident(), second.ident())
397+
)
398+
}
400399
};
401400
Ok((fn_type, skip_first_arg, fixed_convention))
402401
}
@@ -604,107 +603,123 @@ impl<'a> FnSpec<'a> {
604603
}
605604
}
606605

607-
#[derive(Debug)]
608-
struct MethodAttributes {
609-
ty: Option<MethodTypeAttribute>,
610-
python_name: Option<syn::Ident>,
606+
enum MethodTypeAttribute {
607+
New(Span),
608+
ClassMethod(Span),
609+
StaticMethod(Span),
610+
Getter(Span, Option<Ident>),
611+
Setter(Span, Option<Ident>),
612+
ClassAttribute(Span),
611613
}
612614

613-
fn parse_method_attributes(
614-
attrs: &mut Vec<syn::Attribute>,
615-
mut python_name: Option<syn::Ident>,
616-
) -> Result<MethodAttributes> {
617-
let mut new_attrs = Vec::new();
618-
let mut ty: Option<MethodTypeAttribute> = None;
619-
620-
macro_rules! set_compound_ty {
621-
($new_ty:expr, $ident:expr) => {
622-
ty = match (ty, $new_ty) {
623-
(None, new_ty) => Some(new_ty),
624-
(Some(MethodTypeAttribute::ClassMethod), MethodTypeAttribute::New) => Some(MethodTypeAttribute::NewClassMethod),
625-
(Some(MethodTypeAttribute::New), MethodTypeAttribute::ClassMethod) => Some(MethodTypeAttribute::NewClassMethod),
626-
(Some(_), _) => bail_spanned!($ident.span() => "can only combine `new` and `classmethod`"),
627-
};
628-
};
615+
impl MethodTypeAttribute {
616+
fn span(&self) -> Span {
617+
match self {
618+
MethodTypeAttribute::New(span)
619+
| MethodTypeAttribute::ClassMethod(span)
620+
| MethodTypeAttribute::StaticMethod(span)
621+
| MethodTypeAttribute::Getter(span, _)
622+
| MethodTypeAttribute::Setter(span, _)
623+
| MethodTypeAttribute::ClassAttribute(span) => *span,
624+
}
629625
}
630626

631-
macro_rules! set_ty {
632-
($new_ty:expr, $ident:expr) => {
633-
ensure_spanned!(
634-
ty.replace($new_ty).is_none(),
635-
$ident.span() => "cannot combine these method types"
636-
);
637-
};
627+
fn ident(&self) -> &'static str {
628+
match self {
629+
MethodTypeAttribute::New(_) => "new",
630+
MethodTypeAttribute::ClassMethod(_) => "classmethod",
631+
MethodTypeAttribute::StaticMethod(_) => "staticmethod",
632+
MethodTypeAttribute::Getter(_, _) => "getter",
633+
MethodTypeAttribute::Setter(_, _) => "setter",
634+
MethodTypeAttribute::ClassAttribute(_) => "classattr",
635+
}
638636
}
639637

640-
for attr in attrs.drain(..) {
641-
match attr.meta {
642-
syn::Meta::Path(ref name) => {
643-
if name.is_ident("new") || name.is_ident("__new__") {
644-
set_compound_ty!(MethodTypeAttribute::New, name);
645-
} else if name.is_ident("classmethod") {
646-
set_compound_ty!(MethodTypeAttribute::ClassMethod, name);
647-
} else if name.is_ident("staticmethod") {
648-
set_ty!(MethodTypeAttribute::StaticMethod, name);
649-
} else if name.is_ident("classattr") {
650-
set_ty!(MethodTypeAttribute::ClassAttribute, name);
651-
} else if name.is_ident("setter") || name.is_ident("getter") {
652-
if let syn::AttrStyle::Inner(_) = attr.style {
653-
bail_spanned!(
654-
attr.span() => "inner attribute is not supported for setter and getter"
655-
);
656-
}
657-
if name.is_ident("setter") {
658-
set_ty!(MethodTypeAttribute::Setter, name);
659-
} else {
660-
set_ty!(MethodTypeAttribute::Getter, name);
661-
}
662-
} else {
663-
new_attrs.push(attr)
638+
/// Attempts to parse a method type attribute.
639+
///
640+
/// If the attribute does not match one of the attribute names, returns `Ok(None)`.
641+
///
642+
/// Otherwise will either return a parse error or the attribute.
643+
fn parse_if_matching_attribute(attr: &syn::Attribute) -> Result<Option<Self>> {
644+
fn ensure_no_arguments(meta: &syn::Meta, ident: &str) -> syn::Result<()> {
645+
match meta {
646+
syn::Meta::Path(_) => Ok(()),
647+
syn::Meta::List(l) => bail_spanned!(
648+
l.span() => format!(
649+
"`#[{ident}]` does not take any arguments\n= help: did you mean `#[{ident}] #[pyo3({meta})]`?",
650+
ident = ident,
651+
meta = l.tokens,
652+
)
653+
),
654+
syn::Meta::NameValue(nv) => {
655+
bail_spanned!(nv.eq_token.span() => format!("`#[{}]` does not take any arguments", ident))
664656
}
665657
}
666-
syn::Meta::List(ref ml @ syn::MetaList { ref path, .. }) => {
667-
if path.is_ident("new") {
668-
set_ty!(MethodTypeAttribute::New, path);
669-
} else if path.is_ident("setter") || path.is_ident("getter") {
670-
if let syn::AttrStyle::Inner(_) = attr.style {
671-
bail_spanned!(
672-
attr.span() => "inner attribute is not supported for setter and getter"
673-
);
674-
}
675-
676-
if path.is_ident("setter") {
677-
set_ty!(MethodTypeAttribute::Setter, path);
678-
} else {
679-
set_ty!(MethodTypeAttribute::Getter, path);
680-
};
681-
682-
ensure_spanned!(
683-
python_name.is_none(),
684-
python_name.span() => "`name` may only be specified once"
685-
);
658+
}
686659

687-
if let Ok(ident) = ml.parse_args::<syn::Ident>() {
688-
python_name = Some(ident);
689-
} else if let Ok(syn::Lit::Str(s)) = ml.parse_args::<syn::Lit>() {
690-
python_name = Some(s.parse()?);
660+
fn extract_name(meta: &syn::Meta, ident: &str) -> Result<Option<Ident>> {
661+
match meta {
662+
syn::Meta::Path(_) => Ok(None),
663+
syn::Meta::NameValue(nv) => bail_spanned!(
664+
nv.eq_token.span() => format!("expected `#[{}(name)]` to set the name", ident)
665+
),
666+
syn::Meta::List(ml) => {
667+
if let Ok(name) = ml.parse_args::<syn::Ident>() {
668+
Ok(Some(name))
669+
} else if let Ok(name) = ml.parse_args::<syn::LitStr>() {
670+
name.parse().map(Some)
691671
} else {
692-
return Err(syn::Error::new_spanned(
693-
ml,
694-
"expected ident or string literal for property name",
695-
));
672+
bail_spanned!(ml.tokens.span() => "expected ident or string literal for property name");
696673
}
697-
} else {
698-
new_attrs.push(attr)
699674
}
700675
}
701-
syn::Meta::NameValue(_) => new_attrs.push(attr),
676+
}
677+
678+
let meta = &attr.meta;
679+
let path = meta.path();
680+
681+
if path.is_ident("new") {
682+
ensure_no_arguments(meta, "new")?;
683+
Ok(Some(MethodTypeAttribute::New(path.span())))
684+
} else if path.is_ident("__new__") {
685+
// TODO deprecate this form?
686+
ensure_no_arguments(meta, "__new__")?;
687+
Ok(Some(MethodTypeAttribute::New(path.span())))
688+
} else if path.is_ident("classmethod") {
689+
ensure_no_arguments(meta, "classmethod")?;
690+
Ok(Some(MethodTypeAttribute::ClassMethod(path.span())))
691+
} else if path.is_ident("staticmethod") {
692+
ensure_no_arguments(meta, "staticmethod")?;
693+
Ok(Some(MethodTypeAttribute::StaticMethod(path.span())))
694+
} else if path.is_ident("classattr") {
695+
ensure_no_arguments(meta, "classattr")?;
696+
Ok(Some(MethodTypeAttribute::ClassAttribute(path.span())))
697+
} else if path.is_ident("getter") {
698+
let name = extract_name(meta, "getter")?;
699+
Ok(Some(MethodTypeAttribute::Getter(path.span(), name)))
700+
} else if path.is_ident("setter") {
701+
let name = extract_name(meta, "setter")?;
702+
Ok(Some(MethodTypeAttribute::Setter(path.span(), name)))
703+
} else {
704+
Ok(None)
705+
}
706+
}
707+
}
708+
709+
fn parse_method_attributes(attrs: &mut Vec<syn::Attribute>) -> Result<Vec<MethodTypeAttribute>> {
710+
let mut new_attrs = Vec::new();
711+
let mut found_attrs = Vec::new();
712+
713+
for attr in attrs.drain(..) {
714+
match MethodTypeAttribute::parse_if_matching_attribute(&attr)? {
715+
Some(attr) => found_attrs.push(attr),
716+
None => new_attrs.push(attr),
702717
}
703718
}
704719

705720
*attrs = new_attrs;
706721

707-
Ok(MethodAttributes { ty, python_name })
722+
Ok(found_attrs)
708723
}
709724

710725
const IMPL_TRAIT_ERR: &str = "Python functions cannot have `impl Trait` arguments";

tests/ui/invalid_pymethod_names.stderr

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,8 @@
11
error: `name` may only be specified once
2-
--> tests/ui/invalid_pymethod_names.rs:10:19
2+
--> tests/ui/invalid_pymethod_names.rs:11:14
33
|
4-
10 | #[pyo3(name = "num")]
5-
| ^^^^^
4+
11 | #[getter(number)]
5+
| ^^^^^^
66

77
error: `name` may only be specified once
88
--> tests/ui/invalid_pymethod_names.rs:18:12

0 commit comments

Comments
 (0)