Skip to content

Commit aeb8493

Browse files
committed
Merge branch 'master' into dict_into_iter
2 parents e04275b + 58a393f commit aeb8493

File tree

9 files changed

+898
-260
lines changed

9 files changed

+898
-260
lines changed

derive/src/pyclass.rs

Lines changed: 164 additions & 79 deletions
Original file line numberDiff line numberDiff line change
@@ -1,91 +1,140 @@
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-
Method,
8-
Property,
7+
enum ClassItem {
8+
Method {
9+
item_ident: Ident,
10+
py_name: String,
11+
},
12+
Property {
13+
item_ident: Ident,
14+
py_name: String,
15+
setter: bool,
16+
},
917
}
1018

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())
19+
fn meta_to_vec(meta: Meta) -> Result<Vec<NestedMeta>, Meta> {
20+
match meta {
21+
Meta::Word(_) => Ok(Vec::new()),
22+
Meta::List(list) => Ok(list.nested.into_iter().collect()),
23+
Meta::NameValue(_) => Err(meta),
1824
}
1925
}
2026

21-
struct Method {
22-
fn_name: Ident,
23-
py_name: String,
24-
kind: MethodKind,
25-
}
26-
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
27+
impl ClassItem {
28+
fn extract_from_syn(attrs: &mut Vec<Attribute>, sig: &MethodSig) -> Option<ClassItem> {
29+
let mut item = None;
30+
let mut attr_idx = None;
31+
// TODO: better error handling throughout this
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).expect(
43+
"#[pyproperty = \"...\"] cannot be a name/value, you probably meant \
44+
#[pyproperty(name = \"...\")]",
45+
);
46+
let mut py_name = None;
47+
for meta in nesteds {
48+
let meta = match meta {
49+
NestedMeta::Meta(meta) => meta,
50+
NestedMeta::Literal(_) => continue,
51+
};
4152
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-
),
53+
Meta::NameValue(name_value) => {
54+
if name_value.ident == "name" {
55+
if let Lit::Str(s) = &name_value.lit {
56+
py_name = Some(s.value());
57+
} else {
58+
panic!("#[pymethod(name = ...)] must be a string");
59+
}
60+
}
61+
}
62+
_ => {}
4863
}
49-
} else {
50-
None
5164
}
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");
65+
item = Some(ClassItem::Method {
66+
item_ident: sig.ident.clone(),
67+
py_name: py_name.unwrap_or_else(|| sig.ident.to_string()),
68+
});
69+
attr_idx = Some(i);
70+
} else if name == "pyproperty" {
71+
if item.is_some() {
72+
panic!("You can only have one #[py*] attribute on an impl item")
73+
}
74+
let nesteds = meta_to_vec(meta).expect(
75+
"#[pyproperty = \"...\"] cannot be a name/value, you probably meant \
76+
#[pyproperty(name = \"...\")]",
77+
);
78+
let mut setter = false;
79+
let mut py_name = None;
80+
for meta in nesteds {
81+
let meta = match meta {
82+
NestedMeta::Meta(meta) => meta,
83+
NestedMeta::Literal(_) => continue,
84+
};
85+
match meta {
86+
Meta::NameValue(name_value) => {
87+
if name_value.ident == "name" {
88+
if let Lit::Str(s) = &name_value.lit {
89+
py_name = Some(s.value());
90+
} else {
91+
panic!("#[pyproperty(name = ...)] must be a string");
92+
}
6393
}
6494
}
65-
}
66-
Meta::Word(ident) => {
67-
if ident == "property" {
68-
kind = MethodKind::Property
95+
Meta::Word(ident) => {
96+
if ident == "setter" {
97+
setter = true;
98+
}
6999
}
100+
_ => {}
70101
}
71-
_ => {}
72102
}
103+
let py_name = py_name.unwrap_or_else(|| {
104+
let item_ident = sig.ident.to_string();
105+
if setter {
106+
if item_ident.starts_with("set_") {
107+
let name = &item_ident["set_".len()..];
108+
if name.is_empty() {
109+
panic!(
110+
"A #[pyproperty(setter)] fn with a set_* name have something \
111+
after \"set_\""
112+
)
113+
} else {
114+
name.to_string()
115+
}
116+
} else {
117+
panic!(
118+
"A #[pyproperty(setter)] fn must either have a `name` parameter or a \
119+
fn name along the lines of \"set_*\""
120+
)
121+
}
122+
} else {
123+
item_ident
124+
}
125+
});
126+
item = Some(ClassItem::Property {
127+
py_name,
128+
item_ident: sig.ident.clone(),
129+
setter,
130+
});
131+
attr_idx = Some(i);
73132
}
74133
}
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);
134+
if let Some(attr_idx) = attr_idx {
135+
attrs.remove(attr_idx);
82136
}
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-
})
137+
item
89138
}
90139
}
91140

@@ -98,30 +147,65 @@ pub fn impl_pyimpl(attr: AttributeArgs, item: Item) -> TokenStream2 {
98147

99148
let rp_path = rustpython_path_attr(&attr);
100149

101-
let methods = imp
150+
let items = imp
102151
.items
103152
.iter_mut()
104153
.filter_map(|item| {
105154
if let ImplItem::Method(meth) = item {
106-
Method::from_syn(&mut meth.attrs, &meth.sig)
155+
ClassItem::extract_from_syn(&mut meth.attrs, &meth.sig)
107156
} else {
108157
None
109158
}
110159
})
111160
.collect::<Vec<_>>();
112161
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));
162+
let mut properties: HashMap<&str, (Option<&Ident>, Option<&Ident>)> = HashMap::new();
163+
for item in items.iter() {
164+
match item {
165+
ClassItem::Property {
166+
item_ident,
167+
py_name,
168+
setter,
169+
} => {
170+
let entry = properties.entry(py_name).or_default();
171+
let func = if *setter { &mut entry.1 } else { &mut entry.0 };
172+
if func.is_some() {
173+
panic!("Multiple property accessors with name {:?}", py_name)
174+
}
175+
*func = Some(item_ident);
122176
}
123-
},
124-
);
177+
_ => {}
178+
}
179+
}
180+
let methods = items.iter().filter_map(|item| {
181+
if let ClassItem::Method {
182+
item_ident,
183+
py_name,
184+
} = item
185+
{
186+
Some(quote! {
187+
class.set_str_attr(#py_name, ctx.new_rustfunc(Self::#item_ident));
188+
})
189+
} else {
190+
None
191+
}
192+
});
193+
let properties = properties.iter().map(|(name, prop)| {
194+
let getter = match prop.0 {
195+
Some(getter) => getter,
196+
None => panic!("Property {:?} is missing a getter", name),
197+
};
198+
let add_setter = prop.1.map(|setter| quote!(.add_setter(Self::#setter)));
199+
quote! {
200+
class.set_str_attr(
201+
#name,
202+
#rp_path::obj::objproperty::PropertyBuilder::new(ctx)
203+
.add_getter(Self::#getter)
204+
#add_setter
205+
.create(),
206+
);
207+
}
208+
});
125209

126210
quote! {
127211
#imp
@@ -131,6 +215,7 @@ pub fn impl_pyimpl(attr: AttributeArgs, item: Item) -> TokenStream2 {
131215
class: &#rp_path::obj::objtype::PyClassRef,
132216
) {
133217
#(#methods)*
218+
#(#properties)*
134219
}
135220
}
136221
}

parser/src/lexer.rs

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -540,8 +540,12 @@ where
540540
let end_pos = self.get_pos();
541541

542542
let tok = if is_bytes {
543-
Tok::Bytes {
544-
value: string_content.as_bytes().to_vec(),
543+
if string_content.is_ascii() {
544+
Tok::Bytes {
545+
value: string_content.as_bytes().to_vec(),
546+
}
547+
} else {
548+
return Err(LexicalError::StringError);
545549
}
546550
} else {
547551
Tok::String {

0 commit comments

Comments
 (0)