Skip to content

Commit b73c069

Browse files
authored
Merge pull request #3504 from davidhewitt/classmethod-receiver
emit helpful error hint for classmethod with receiver
2 parents 76bf521 + ddc04ea commit b73c069

File tree

3 files changed

+77
-61
lines changed

3 files changed

+77
-61
lines changed

pyo3-macros-backend/src/method.rs

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -370,7 +370,18 @@ impl<'a> FnSpec<'a> {
370370
FnType::FnNewClass
371371
}
372372
}
373-
[MethodTypeAttribute::ClassMethod(_)] => FnType::FnClass,
373+
[MethodTypeAttribute::ClassMethod(_)] => {
374+
// Add a helpful hint if the classmethod doesn't look like a classmethod
375+
match sig.inputs.first() {
376+
// Don't actually bother checking the type of the first argument, the compiler
377+
// will error on incorrect type.
378+
Some(syn::FnArg::Typed(_)) => {}
379+
Some(syn::FnArg::Receiver(_)) | None => bail_spanned!(
380+
sig.inputs.span() => "Expected `cls: &PyType` as the first argument to `#[classmethod]`"
381+
),
382+
}
383+
FnType::FnClass
384+
}
374385
[MethodTypeAttribute::Getter(_, name)] => {
375386
if let Some(name) = name.take() {
376387
ensure_spanned!(

tests/ui/invalid_pymethods.rs

Lines changed: 5 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -26,12 +26,11 @@ impl MyClass {
2626
fn staticmethod_with_receiver(&self) {}
2727
}
2828

29-
// FIXME: This currently doesn't fail
30-
// #[pymethods]
31-
// impl MyClass {
32-
// #[classmethod]
33-
// fn classmethod_with_receiver(&self) {}
34-
// }
29+
#[pymethods]
30+
impl MyClass {
31+
#[classmethod]
32+
fn classmethod_with_receiver(&self) {}
33+
}
3534

3635
#[pymethods]
3736
impl MyClass {

tests/ui/invalid_pymethods.stderr

Lines changed: 60 additions & 54 deletions
Original file line numberDiff line numberDiff line change
@@ -22,163 +22,169 @@ error: unexpected receiver
2222
26 | fn staticmethod_with_receiver(&self) {}
2323
| ^
2424

25+
error: Expected `cls: &PyType` as the first argument to `#[classmethod]`
26+
--> tests/ui/invalid_pymethods.rs:32:34
27+
|
28+
32 | fn classmethod_with_receiver(&self) {}
29+
| ^
30+
2531
error: expected receiver for `#[getter]`
26-
--> tests/ui/invalid_pymethods.rs:39:5
32+
--> tests/ui/invalid_pymethods.rs:38:5
2733
|
28-
39 | fn getter_without_receiver() {}
34+
38 | fn getter_without_receiver() {}
2935
| ^^
3036

3137
error: expected receiver for `#[setter]`
32-
--> tests/ui/invalid_pymethods.rs:45:5
38+
--> tests/ui/invalid_pymethods.rs:44:5
3339
|
34-
45 | fn setter_without_receiver() {}
40+
44 | fn setter_without_receiver() {}
3541
| ^^
3642

3743
error: static method needs #[staticmethod] attribute
38-
--> tests/ui/invalid_pymethods.rs:51:5
44+
--> tests/ui/invalid_pymethods.rs:50:5
3945
|
40-
51 | fn text_signature_on_call() {}
46+
50 | fn text_signature_on_call() {}
4147
| ^^
4248

4349
error: `text_signature` not allowed with `getter`
44-
--> tests/ui/invalid_pymethods.rs:57:12
50+
--> tests/ui/invalid_pymethods.rs:56:12
4551
|
46-
57 | #[pyo3(text_signature = "()")]
52+
56 | #[pyo3(text_signature = "()")]
4753
| ^^^^^^^^^^^^^^
4854

4955
error: `text_signature` not allowed with `setter`
50-
--> tests/ui/invalid_pymethods.rs:64:12
56+
--> tests/ui/invalid_pymethods.rs:63:12
5157
|
52-
64 | #[pyo3(text_signature = "()")]
58+
63 | #[pyo3(text_signature = "()")]
5359
| ^^^^^^^^^^^^^^
5460

5561
error: `text_signature` not allowed with `classattr`
56-
--> tests/ui/invalid_pymethods.rs:71:12
62+
--> tests/ui/invalid_pymethods.rs:70:12
5763
|
58-
71 | #[pyo3(text_signature = "()")]
64+
70 | #[pyo3(text_signature = "()")]
5965
| ^^^^^^^^^^^^^^
6066

6167
error: expected a string literal or `None`
62-
--> tests/ui/invalid_pymethods.rs:77:30
68+
--> tests/ui/invalid_pymethods.rs:76:30
6369
|
64-
77 | #[pyo3(text_signature = 1)]
70+
76 | #[pyo3(text_signature = 1)]
6571
| ^
6672

6773
error: `text_signature` may only be specified once
68-
--> tests/ui/invalid_pymethods.rs:84:12
74+
--> tests/ui/invalid_pymethods.rs:83:12
6975
|
70-
84 | #[pyo3(text_signature = None)]
76+
83 | #[pyo3(text_signature = None)]
7177
| ^^^^^^^^^^^^^^
7278

7379
error: `signature` not allowed with `getter`
74-
--> tests/ui/invalid_pymethods.rs:91:12
80+
--> tests/ui/invalid_pymethods.rs:90:12
7581
|
76-
91 | #[pyo3(signature = ())]
82+
90 | #[pyo3(signature = ())]
7783
| ^^^^^^^^^
7884

7985
error: `signature` not allowed with `setter`
80-
--> tests/ui/invalid_pymethods.rs:98:12
86+
--> tests/ui/invalid_pymethods.rs:97:12
8187
|
82-
98 | #[pyo3(signature = ())]
88+
97 | #[pyo3(signature = ())]
8389
| ^^^^^^^^^
8490

8591
error: `signature` not allowed with `classattr`
86-
--> tests/ui/invalid_pymethods.rs:105:12
92+
--> tests/ui/invalid_pymethods.rs:104:12
8793
|
88-
105 | #[pyo3(signature = ())]
94+
104 | #[pyo3(signature = ())]
8995
| ^^^^^^^^^
9096

9197
error: `#[new]` may not be combined with `#[classmethod]` `#[staticmethod]`, `#[classattr]`, `#[getter]`, and `#[setter]`
92-
--> tests/ui/invalid_pymethods.rs:111:7
98+
--> tests/ui/invalid_pymethods.rs:110:7
9399
|
94-
111 | #[new]
100+
110 | #[new]
95101
| ^^^
96102

97103
error: `#[new]` does not take any arguments
98104
= help: did you mean `#[new] #[pyo3(signature = ())]`?
99-
--> tests/ui/invalid_pymethods.rs:122:7
105+
--> tests/ui/invalid_pymethods.rs:121:7
100106
|
101-
122 | #[new(signature = ())]
107+
121 | #[new(signature = ())]
102108
| ^^^
103109

104110
error: `#[new]` does not take any arguments
105111
= note: this was previously accepted and ignored
106-
--> tests/ui/invalid_pymethods.rs:128:11
112+
--> tests/ui/invalid_pymethods.rs:127:11
107113
|
108-
128 | #[new = ()] // in this form there's no suggestion to move arguments to `#[pyo3()]` attribute
114+
127 | #[new = ()] // in this form there's no suggestion to move arguments to `#[pyo3()]` attribute
109115
| ^
110116

111117
error: `#[classmethod]` does not take any arguments
112118
= help: did you mean `#[classmethod] #[pyo3(signature = ())]`?
113-
--> tests/ui/invalid_pymethods.rs:134:7
119+
--> tests/ui/invalid_pymethods.rs:133:7
114120
|
115-
134 | #[classmethod(signature = ())]
121+
133 | #[classmethod(signature = ())]
116122
| ^^^^^^^^^^^
117123

118124
error: `#[staticmethod]` does not take any arguments
119125
= help: did you mean `#[staticmethod] #[pyo3(signature = ())]`?
120-
--> tests/ui/invalid_pymethods.rs:140:7
126+
--> tests/ui/invalid_pymethods.rs:139:7
121127
|
122-
140 | #[staticmethod(signature = ())]
128+
139 | #[staticmethod(signature = ())]
123129
| ^^^^^^^^^^^^
124130

125131
error: `#[classattr]` does not take any arguments
126132
= help: did you mean `#[classattr] #[pyo3(signature = ())]`?
127-
--> tests/ui/invalid_pymethods.rs:146:7
133+
--> tests/ui/invalid_pymethods.rs:145:7
128134
|
129-
146 | #[classattr(signature = ())]
135+
145 | #[classattr(signature = ())]
130136
| ^^^^^^^^^
131137

132138
error: Python functions cannot have generic type parameters
133-
--> tests/ui/invalid_pymethods.rs:152:23
139+
--> tests/ui/invalid_pymethods.rs:151:23
134140
|
135-
152 | fn generic_method<T>(value: T) {}
141+
151 | fn generic_method<T>(value: T) {}
136142
| ^
137143

138144
error: Python functions cannot have `impl Trait` arguments
139-
--> tests/ui/invalid_pymethods.rs:157:48
145+
--> tests/ui/invalid_pymethods.rs:156:48
140146
|
141-
157 | fn impl_trait_method_first_arg(impl_trait: impl AsRef<PyAny>) {}
147+
156 | fn impl_trait_method_first_arg(impl_trait: impl AsRef<PyAny>) {}
142148
| ^^^^
143149

144150
error: Python functions cannot have `impl Trait` arguments
145-
--> tests/ui/invalid_pymethods.rs:162:56
151+
--> tests/ui/invalid_pymethods.rs:161:56
146152
|
147-
162 | fn impl_trait_method_second_arg(&self, impl_trait: impl AsRef<PyAny>) {}
153+
161 | fn impl_trait_method_second_arg(&self, impl_trait: impl AsRef<PyAny>) {}
148154
| ^^^^
149155

150156
error: `async fn` is not yet supported for Python functions.
151157

152158
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
153-
--> tests/ui/invalid_pymethods.rs:167:5
159+
--> tests/ui/invalid_pymethods.rs:166:5
154160
|
155-
167 | async fn async_method(&self) {}
161+
166 | async fn async_method(&self) {}
156162
| ^^^^^
157163

158164
error: `pass_module` cannot be used on Python methods
159-
--> tests/ui/invalid_pymethods.rs:172:12
165+
--> tests/ui/invalid_pymethods.rs:171:12
160166
|
161-
172 | #[pyo3(pass_module)]
167+
171 | #[pyo3(pass_module)]
162168
| ^^^^^^^^^^^
163169

164170
error: Python objects are shared, so 'self' cannot be moved out of the Python interpreter.
165171
Try `&self`, `&mut self, `slf: PyRef<'_, Self>` or `slf: PyRefMut<'_, Self>`.
166-
--> tests/ui/invalid_pymethods.rs:178:29
172+
--> tests/ui/invalid_pymethods.rs:177:29
167173
|
168-
178 | fn method_self_by_value(self) {}
174+
177 | fn method_self_by_value(self) {}
169175
| ^^^^
170176

171177
error: macros cannot be used as items in `#[pymethods]` impl blocks
172178
= note: this was previously accepted and ignored
173-
--> tests/ui/invalid_pymethods.rs:213:5
179+
--> tests/ui/invalid_pymethods.rs:212:5
174180
|
175-
213 | macro_invocation!();
181+
212 | macro_invocation!();
176182
| ^^^^^^^^^^^^^^^^
177183

178184
error[E0119]: conflicting implementations of trait `pyo3::impl_::pyclass::PyClassNewTextSignature<TwoNew>` for type `pyo3::impl_::pyclass::PyClassImplCollector<TwoNew>`
179-
--> tests/ui/invalid_pymethods.rs:183:1
185+
--> tests/ui/invalid_pymethods.rs:182:1
180186
|
181-
183 | #[pymethods]
187+
182 | #[pymethods]
182188
| ^^^^^^^^^^^^
183189
| |
184190
| first implementation here
@@ -187,9 +193,9 @@ error[E0119]: conflicting implementations of trait `pyo3::impl_::pyclass::PyClas
187193
= note: this error originates in the attribute macro `pymethods` (in Nightly builds, run with -Z macro-backtrace for more info)
188194

189195
error[E0592]: duplicate definitions with name `__pymethod___new____`
190-
--> tests/ui/invalid_pymethods.rs:183:1
196+
--> tests/ui/invalid_pymethods.rs:182:1
191197
|
192-
183 | #[pymethods]
198+
182 | #[pymethods]
193199
| ^^^^^^^^^^^^
194200
| |
195201
| duplicate definitions for `__pymethod___new____`
@@ -198,9 +204,9 @@ error[E0592]: duplicate definitions with name `__pymethod___new____`
198204
= note: this error originates in the attribute macro `pymethods` (in Nightly builds, run with -Z macro-backtrace for more info)
199205

200206
error[E0592]: duplicate definitions with name `__pymethod_func__`
201-
--> tests/ui/invalid_pymethods.rs:198:1
207+
--> tests/ui/invalid_pymethods.rs:197:1
202208
|
203-
198 | #[pymethods]
209+
197 | #[pymethods]
204210
| ^^^^^^^^^^^^
205211
| |
206212
| duplicate definitions for `__pymethod_func__`

0 commit comments

Comments
 (0)