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+ #[ 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