11use super :: rustpython_path_attr;
2- use proc_macro2:: { Span , TokenStream as TokenStream2 } ;
2+ use proc_macro2:: TokenStream as TokenStream2 ;
33use quote:: quote;
4+ use std:: collections:: HashMap ;
45use 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 }
0 commit comments