1
1
use super :: rustpython_path_attr;
2
- use proc_macro2:: { Span , TokenStream as TokenStream2 } ;
2
+ use proc_macro2:: TokenStream as TokenStream2 ;
3
3
use quote:: quote;
4
+ use std:: collections:: HashMap ;
4
5
use syn:: { Attribute , AttributeArgs , Ident , ImplItem , Item , Lit , Meta , MethodSig , NestedMeta } ;
5
6
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
+ } ,
9
17
}
10
18
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) ,
18
24
}
19
25
}
20
26
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
33
33
. iter ( )
34
+ . filter_map ( |attr| attr. parse_meta ( ) . ok ( ) )
34
35
. 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
+ } ;
41
52
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
+ _ => { }
48
63
}
49
- } else {
50
- None
51
64
}
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
+ }
63
93
}
64
94
}
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
+ }
69
99
}
100
+ _ => { }
70
101
}
71
- _ => { }
72
102
}
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) ;
73
132
}
74
133
}
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) ;
82
136
}
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
89
138
}
90
139
}
91
140
@@ -98,30 +147,65 @@ pub fn impl_pyimpl(attr: AttributeArgs, item: Item) -> TokenStream2 {
98
147
99
148
let rp_path = rustpython_path_attr ( & attr) ;
100
149
101
- let methods = imp
150
+ let items = imp
102
151
. items
103
152
. iter_mut ( )
104
153
. filter_map ( |item| {
105
154
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 )
107
156
} else {
108
157
None
109
158
}
110
159
} )
111
160
. collect :: < Vec < _ > > ( ) ;
112
161
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) ;
122
176
}
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
+ } ) ;
125
209
126
210
quote ! {
127
211
#imp
@@ -131,6 +215,7 @@ pub fn impl_pyimpl(attr: AttributeArgs, item: Item) -> TokenStream2 {
131
215
class: & #rp_path:: obj:: objtype:: PyClassRef ,
132
216
) {
133
217
#( #methods) *
218
+ #( #properties) *
134
219
}
135
220
}
136
221
}
0 commit comments