Skip to content

Commit cbdab22

Browse files
committed
Change #[pymethod(property)] to #[pyproperty]
1 parent d84ad43 commit cbdab22

File tree

1 file changed

+161
-77
lines changed

1 file changed

+161
-77
lines changed

derive/src/pyclass.rs

Lines changed: 161 additions & 77 deletions
Original file line numberDiff line numberDiff line change
@@ -1,91 +1,138 @@
11
use super::rustpython_path_attr;
2-
use proc_macro2::{Span, TokenStream as TokenStream2};
2+
use proc_macro2::TokenStream as TokenStream2;
33
use quote::quote;
4+
use std::collections::HashMap;
45
use syn::{Attribute, AttributeArgs, Ident, ImplItem, Item, Lit, Meta, MethodSig, NestedMeta};
56

6-
enum MethodKind {
7+
#[derive(PartialEq, Clone, Copy)]
8+
enum ClassItemKind {
79
Method,
8-
Property,
10+
PropertyGetter,
11+
PropertySetter,
912
}
1013

11-
impl MethodKind {
12-
fn to_ctx_constructor_fn(&self) -> Ident {
13-
let f = match self {
14-
MethodKind::Method => "new_rustfunc",
15-
MethodKind::Property => "new_property",
16-
};
17-
Ident::new(f, Span::call_site())
18-
}
14+
struct ClassItem {
15+
item_name: Ident,
16+
py_name: String,
17+
kind: ClassItemKind,
1918
}
2019

21-
struct Method {
22-
fn_name: Ident,
23-
py_name: String,
24-
kind: MethodKind,
20+
fn meta_to_vec(meta: Meta) -> Result<Vec<NestedMeta>, Meta> {
21+
match meta {
22+
Meta::Word(_) => Ok(Vec::new()),
23+
Meta::List(list) => Ok(list.nested.into_iter().collect()),
24+
Meta::NameValue(_) => Err(meta),
25+
}
2526
}
2627

27-
impl Method {
28-
fn from_syn(attrs: &mut Vec<Attribute>, sig: &MethodSig) -> Option<Method> {
29-
let mut py_name = None;
30-
let mut kind = MethodKind::Method;
31-
let mut pymethod_to_remove = Vec::new();
32-
let metas = attrs
28+
impl ClassItem {
29+
fn extract_from_syn(attrs: &mut Vec<Attribute>, sig: &MethodSig) -> Option<ClassItem> {
30+
let mut item = None;
31+
let mut attr_idx = None;
32+
for (i, meta) in attrs
3333
.iter()
34+
.filter_map(|attr| attr.parse_meta().ok())
3435
.enumerate()
35-
.filter_map(|(i, attr)| {
36-
if attr.path.is_ident("pymethod") {
37-
let meta = attr.parse_meta().expect("Invalid attribute");
38-
// remove #[pymethod] because there's no actual proc macro
39-
// implementation for it
40-
pymethod_to_remove.push(i);
36+
{
37+
let name = meta.name();
38+
if name == "pymethod" {
39+
if item.is_some() {
40+
panic!("You can only have one #[py*] attribute on an impl item")
41+
}
42+
let nesteds = meta_to_vec(meta)
43+
.expect("#[pymethod = ...] must be a list, e.g. #[pymethod(...)]");
44+
let mut py_name = None;
45+
for meta in nesteds {
46+
let meta = match meta {
47+
NestedMeta::Meta(meta) => meta,
48+
NestedMeta::Literal(_) => panic!("Expected a meta, found a literal"),
49+
};
4150
match meta {
42-
Meta::List(list) => Some(list),
43-
Meta::Word(_) => None,
44-
Meta::NameValue(_) => panic!(
45-
"#[pymethod = ...] attribute on a method should be a list, like \
46-
#[pymethod(...)]"
47-
),
51+
Meta::NameValue(name_value) => {
52+
if name_value.ident == "name" {
53+
if let Lit::Str(s) = &name_value.lit {
54+
py_name = Some(s.value());
55+
} else {
56+
panic!("#[pymethod(name = ...)] must be a string");
57+
}
58+
}
59+
}
60+
_ => {}
4861
}
49-
} else {
50-
None
5162
}
52-
})
53-
.flat_map(|attr| attr.nested);
54-
for meta in metas {
55-
if let NestedMeta::Meta(meta) = meta {
56-
match meta {
57-
Meta::NameValue(name_value) => {
58-
if name_value.ident == "name" {
59-
if let Lit::Str(s) = &name_value.lit {
60-
py_name = Some(s.value());
61-
} else {
62-
panic!("#[pymethod(name = ...)] must be a string");
63+
item = Some(ClassItem {
64+
item_name: sig.ident.clone(),
65+
py_name: py_name.unwrap_or_else(|| sig.ident.to_string()),
66+
kind: ClassItemKind::Method,
67+
});
68+
attr_idx = Some(i);
69+
} else if name == "pyproperty" {
70+
if item.is_some() {
71+
panic!("You can only have one #[py*] attribute on an impl item")
72+
}
73+
let nesteds = meta_to_vec(meta)
74+
.expect("#[pyproperty = ...] must be a list, e.g. #[pyproperty(...)]");
75+
let mut setter = false;
76+
let mut py_name = None;
77+
for meta in nesteds {
78+
let meta = match meta {
79+
NestedMeta::Meta(meta) => meta,
80+
NestedMeta::Literal(_) => panic!("Expected a meta, found a literal"),
81+
};
82+
match meta {
83+
Meta::NameValue(name_value) => {
84+
if name_value.ident == "name" {
85+
if let Lit::Str(s) = &name_value.lit {
86+
py_name = Some(s.value());
87+
} else {
88+
panic!("#[pyproperty(name = ...)] must be a string");
89+
}
6390
}
6491
}
65-
}
66-
Meta::Word(ident) => {
67-
if ident == "property" {
68-
kind = MethodKind::Property
92+
Meta::Word(ident) => {
93+
if ident == "setter" {
94+
setter = true;
95+
}
6996
}
97+
_ => {}
7098
}
71-
_ => {}
7299
}
100+
let kind = if setter {
101+
ClassItemKind::PropertySetter
102+
} else {
103+
ClassItemKind::PropertyGetter
104+
};
105+
let py_name = py_name.unwrap_or_else(|| {
106+
let item_name = sig.ident.to_string();
107+
if item_name.starts_with("set_") {
108+
let name = &item_name["set_".len()..];
109+
if name.is_empty() {
110+
panic!(
111+
"A #[pyproperty(setter)] fn with a set_* name have something \
112+
after \"set_\""
113+
)
114+
} else {
115+
name.to_string()
116+
}
117+
} else {
118+
panic!(
119+
"A #[pyproperty(setter)] fn must either have a `name` parameter or a \
120+
fn name along the lines of \"set_*\""
121+
)
122+
}
123+
});
124+
item = Some(ClassItem {
125+
py_name,
126+
item_name: sig.ident.clone(),
127+
kind,
128+
});
129+
attr_idx = Some(i);
73130
}
74131
}
75-
// if there are no #[pymethods]s, then it's not a method, so exclude it from
76-
// the final result
77-
if pymethod_to_remove.is_empty() {
78-
return None;
79-
}
80-
for i in pymethod_to_remove {
81-
attrs.remove(i);
132+
if let Some(attr_idx) = attr_idx {
133+
attrs.remove(attr_idx);
82134
}
83-
let py_name = py_name.unwrap_or_else(|| sig.ident.to_string());
84-
Some(Method {
85-
fn_name: sig.ident.clone(),
86-
py_name,
87-
kind,
88-
})
135+
item
89136
}
90137
}
91138

@@ -98,30 +145,66 @@ pub fn impl_pyimpl(attr: AttributeArgs, item: Item) -> TokenStream2 {
98145

99146
let rp_path = rustpython_path_attr(&attr);
100147

101-
let methods = imp
148+
let items = imp
102149
.items
103150
.iter_mut()
104151
.filter_map(|item| {
105152
if let ImplItem::Method(meth) = item {
106-
Method::from_syn(&mut meth.attrs, &meth.sig)
153+
ClassItem::extract_from_syn(&mut meth.attrs, &meth.sig)
107154
} else {
108155
None
109156
}
110157
})
111158
.collect::<Vec<_>>();
112159
let ty = &imp.self_ty;
113-
let methods = methods.iter().map(
114-
|Method {
115-
py_name,
116-
fn_name,
117-
kind,
118-
}| {
119-
let constructor_fn = kind.to_ctx_constructor_fn();
120-
quote! {
121-
class.set_str_attr(#py_name, ctx.#constructor_fn(Self::#fn_name));
160+
let mut properties: HashMap<&str, (Option<&Ident>, Option<&Ident>)> = HashMap::new();
161+
for item in items.iter() {
162+
match item.kind {
163+
ClassItemKind::PropertyGetter => {
164+
let (ref mut getter, _) = properties.entry(&item.py_name).or_default();
165+
if getter.is_some() {
166+
panic!("Multiple property getters with name {:?}", &item.py_name)
167+
}
168+
*getter = Some(&item.item_name);
169+
}
170+
ClassItemKind::PropertySetter => {
171+
let (_, ref mut setter) = properties.entry(&item.py_name).or_default();
172+
if setter.is_some() {
173+
panic!("Multiple property getters with name {:?}", &item.py_name)
174+
}
175+
*setter = Some(&item.item_name);
122176
}
123-
},
124-
);
177+
ClassItemKind::Method => {}
178+
}
179+
}
180+
let methods = items.iter().filter_map(|item| {
181+
if let ClassItemKind::Method = item.kind {
182+
let ClassItem {
183+
py_name, item_name, ..
184+
} = item;
185+
Some(quote! {
186+
class.set_str_attr(#py_name, ctx.new_rustfunc(Self::#item_name));
187+
})
188+
} else {
189+
None
190+
}
191+
});
192+
let properties = properties.iter().map(|(name, prop)| {
193+
let getter = match prop.0 {
194+
Some(getter) => getter,
195+
None => panic!("Property {:?} is missing a getter", name),
196+
};
197+
let add_setter = prop.1.map(|setter| quote!(.add_setter(Self::#setter)));
198+
quote! {
199+
class.set_str_attr(
200+
#name,
201+
#rp_path::obj::objproperty::PropertyBuilder
202+
.add_getter(Self::#getter)
203+
#add_setter
204+
.create(),
205+
);
206+
}
207+
});
125208

126209
quote! {
127210
#imp
@@ -131,6 +214,7 @@ pub fn impl_pyimpl(attr: AttributeArgs, item: Item) -> TokenStream2 {
131214
class: &#rp_path::obj::objtype::PyClassRef,
132215
) {
133216
#(#methods)*
217+
#(#properties)*
134218
}
135219
}
136220
}

0 commit comments

Comments
 (0)