Skip to content

Commit d9efe4e

Browse files
committed
Require a #[pymethod] for a method to be recognized
1 parent 8376ec2 commit d9efe4e

File tree

2 files changed

+112
-54
lines changed

2 files changed

+112
-54
lines changed

derive/src/lib.rs

Lines changed: 39 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -249,7 +249,7 @@ fn impl_from_args(input: DeriveInput) -> TokenStream2 {
249249
}
250250

251251
#[proc_macro_attribute]
252-
pub fn py_class(attr: TokenStream, item: TokenStream) -> TokenStream {
252+
pub fn pyclass(attr: TokenStream, item: TokenStream) -> TokenStream {
253253
let attr = parse_macro_input!(attr as AttributeArgs);
254254
let item = parse_macro_input!(item as Item);
255255
impl_py_class(attr, item).into()
@@ -276,50 +276,65 @@ struct Method {
276276
kind: MethodKind,
277277
}
278278

279-
fn item_impl_to_methods<'a>(imp: &'a syn::ItemImpl) -> impl Iterator<Item = Method> + 'a {
280-
imp.items.iter().filter_map(|item| {
279+
/// Parse an impl block into an iterator of methods
280+
fn item_impl_to_methods<'a>(imp: &'a mut syn::ItemImpl) -> impl Iterator<Item = Method> + 'a {
281+
imp.items.iter_mut().filter_map(|item| {
281282
if let ImplItem::Method(meth) = item {
282283
let mut py_name = None;
283284
let mut kind = MethodKind::Method;
284-
let metas_iter = meth
285+
let mut pymethod_to_remove = Vec::new();
286+
let metas = meth
285287
.attrs
286288
.iter()
287-
.filter_map(|attr| {
288-
if attr.path.is_ident("py_class") {
289+
.enumerate()
290+
.filter_map(|(i, attr)| {
291+
if attr.path.is_ident("pymethod") {
289292
let meta = attr.parse_meta().expect("Invalid attribute");
290-
if let Meta::List(list) = meta {
291-
Some(list)
292-
} else {
293-
panic!(
294-
"#[py_class] attribute on a method should be a list, like \
295-
#[py_class(...)]"
296-
)
293+
// remove #[pymethod] because there's no actual proc macro
294+
// implementation for it
295+
pymethod_to_remove.push(i);
296+
match meta {
297+
Meta::List(list) => Some(list),
298+
Meta::Word(_) => None,
299+
Meta::NameValue(_) => panic!(
300+
"#[pymethod = ...] attribute on a method should be a list, like \
301+
#[pymethod(...)]"
302+
),
297303
}
298304
} else {
299305
None
300306
}
301307
})
302308
.flat_map(|attr| attr.nested);
303-
for meta in metas_iter {
309+
for meta in metas {
304310
if let NestedMeta::Meta(meta) = meta {
305311
match meta {
306312
Meta::NameValue(name_value) => {
307313
if name_value.ident == "name" {
308314
if let Lit::Str(s) = &name_value.lit {
309315
py_name = Some(s.value());
310316
} else {
311-
panic!("#[py_class(name = ...)] must be a string");
317+
panic!("#[pymethod(name = ...)] must be a string");
312318
}
313319
}
314320
}
315-
Meta::Word(ident) => match ident.to_string().as_str() {
316-
"property" => kind = MethodKind::Property,
317-
_ => {}
318-
},
321+
Meta::Word(ident) => {
322+
if ident == "property" {
323+
kind = MethodKind::Property
324+
}
325+
}
319326
_ => {}
320327
}
321328
}
322329
}
330+
// if there are no #[pymethods]s, then it's not a method, so exclude it from
331+
// the final result
332+
if pymethod_to_remove.is_empty() {
333+
return None;
334+
}
335+
for i in pymethod_to_remove {
336+
meth.attrs.remove(i);
337+
}
323338
let py_name = py_name.unwrap_or_else(|| meth.sig.ident.to_string());
324339
Some(Method {
325340
fn_name: meth.sig.ident.clone(),
@@ -333,7 +348,7 @@ fn item_impl_to_methods<'a>(imp: &'a syn::ItemImpl) -> impl Iterator<Item = Meth
333348
}
334349

335350
fn impl_py_class(attr: AttributeArgs, item: Item) -> TokenStream2 {
336-
let imp = if let Item::Impl(imp) = item {
351+
let mut imp = if let Item::Impl(imp) = item {
337352
imp
338353
} else {
339354
return quote!(#item);
@@ -347,13 +362,13 @@ fn impl_py_class(attr: AttributeArgs, item: Item) -> TokenStream2 {
347362
if let Lit::Str(s) = name_value.lit {
348363
class_name = Some(s.value());
349364
} else {
350-
panic!("#[py_class(name = ...)] must be a string");
365+
panic!("#[pyclass(name = ...)] must be a string");
351366
}
352367
}
353368
}
354369
}
355370
}
356-
let class_name = class_name.expect("#[py_class] must have a name");
371+
let class_name = class_name.expect("#[pyclass] must have a name");
357372
let mut doc: Option<Vec<String>> = None;
358373
for attr in imp.attrs.iter() {
359374
if attr.path.is_ident("doc") {
@@ -380,8 +395,9 @@ fn impl_py_class(attr: AttributeArgs, item: Item) -> TokenStream2 {
380395
}
381396
None => quote!(None),
382397
};
398+
let methods: Vec<_> = item_impl_to_methods(&mut imp).collect();
383399
let ty = &imp.self_ty;
384-
let methods = item_impl_to_methods(&imp).map(
400+
let methods = methods.iter().map(
385401
|Method {
386402
py_name,
387403
fn_name,

0 commit comments

Comments
 (0)