@@ -66,6 +66,7 @@ impl FromStr for AttrName {
6666struct ImplContext {
6767 impl_extend_items : ItemNursery ,
6868 getset_items : GetSetNursery ,
69+ member_items : MemberNursery ,
6970 extend_slots_items : ItemNursery ,
7071 class_extensions : Vec < TokenStream > ,
7172 errors : Vec < syn:: Error > ,
@@ -94,6 +95,7 @@ fn extract_items_into_context<'a, Item>(
9495 context. errors . ok_or_push ( r) ;
9596 }
9697 context. errors . ok_or_push ( context. getset_items . validate ( ) ) ;
98+ context. errors . ok_or_push ( context. member_items . validate ( ) ) ;
9799}
98100
99101pub ( crate ) fn impl_pyimpl ( attr : AttributeArgs , item : Item ) -> Result < TokenStream > {
@@ -110,6 +112,7 @@ pub(crate) fn impl_pyimpl(attr: AttributeArgs, item: Item) -> Result<TokenStream
110112 } = extract_impl_attrs ( attr, & Ident :: new ( & quote ! ( ty) . to_string ( ) , ty. span ( ) ) ) ?;
111113
112114 let getset_impl = & context. getset_items ;
115+ let member_impl = & context. member_items ;
113116 let extend_impl = context. impl_extend_items . validate ( ) ?;
114117 let slots_impl = context. extend_slots_items . validate ( ) ?;
115118 let class_extensions = & context. class_extensions ;
@@ -123,6 +126,7 @@ pub(crate) fn impl_pyimpl(attr: AttributeArgs, item: Item) -> Result<TokenStream
123126 class: & ' static :: rustpython_vm:: Py <:: rustpython_vm:: builtins:: PyType >,
124127 ) {
125128 #getset_impl
129+ #member_impl
126130 #extend_impl
127131 #with_impl
128132 #( #class_extensions) *
@@ -146,6 +150,7 @@ pub(crate) fn impl_pyimpl(attr: AttributeArgs, item: Item) -> Result<TokenStream
146150 } = extract_impl_attrs ( attr, & trai. ident ) ?;
147151
148152 let getset_impl = & context. getset_items ;
153+ let member_impl = & context. member_items ;
149154 let extend_impl = & context. impl_extend_items . validate ( ) ?;
150155 let slots_impl = & context. extend_slots_items . validate ( ) ?;
151156 let class_extensions = & context. class_extensions ;
@@ -156,6 +161,7 @@ pub(crate) fn impl_pyimpl(attr: AttributeArgs, item: Item) -> Result<TokenStream
156161 class: & ' static :: rustpython_vm:: Py <:: rustpython_vm:: builtins:: PyType >,
157162 ) {
158163 #getset_impl
164+ #member_impl
159165 #extend_impl
160166 #with_impl
161167 #( #class_extensions) *
@@ -689,23 +695,20 @@ where
689695 let item_attr = args. attrs . remove ( self . index ( ) ) ;
690696 let item_meta = MemberItemMeta :: from_attr ( ident. clone ( ) , & item_attr) ?;
691697
692- let py_name = item_meta. member_name ( ) ?;
693- let tokens = {
694- quote_spanned ! { ident. span( ) =>
695- class. set_str_attr(
696- #py_name,
697- ctx. new_member( #py_name, Self :: #ident, None , class) ,
698- ctx,
699- ) ;
700- }
698+ let ( py_name, member_item_kind) = item_meta. member_name ( ) ?;
699+ let member_kind = match item_meta. member_kind ( ) ? {
700+ Some ( s) => match s. as_str ( ) {
701+ "bool" => MemberKind :: Bool ,
702+ _ => unreachable ! ( ) ,
703+ } ,
704+ _ => MemberKind :: ObjectEx ,
701705 } ;
702706
703- args. context . impl_extend_items . add_item (
707+ args. context . member_items . add_item (
708+ py_name,
709+ member_item_kind,
710+ member_kind,
704711 ident. clone ( ) ,
705- vec ! [ py_name] ,
706- args. cfgs . to_vec ( ) ,
707- tokens,
708- 5 ,
709712 ) ?;
710713 Ok ( ( ) )
711714 }
@@ -805,6 +808,95 @@ impl ToTokens for GetSetNursery {
805808 }
806809}
807810
811+ #[ derive( Default ) ]
812+ #[ allow( clippy:: type_complexity) ]
813+ struct MemberNursery {
814+ map : HashMap < ( String , MemberKind ) , ( Option < Ident > , Option < Ident > ) > ,
815+ validated : bool ,
816+ }
817+
818+ enum MemberItemKind {
819+ Get ,
820+ Set ,
821+ }
822+
823+ #[ derive( Eq , PartialEq , Hash ) ]
824+ enum MemberKind {
825+ Bool ,
826+ ObjectEx ,
827+ }
828+
829+ impl MemberNursery {
830+ fn add_item (
831+ & mut self ,
832+ name : String ,
833+ kind : MemberItemKind ,
834+ member_kind : MemberKind ,
835+ item_ident : Ident ,
836+ ) -> Result < ( ) > {
837+ assert ! ( !self . validated, "new item is not allowed after validation" ) ;
838+ let entry = self . map . entry ( ( name. clone ( ) , member_kind) ) . or_default ( ) ;
839+ let func = match kind {
840+ MemberItemKind :: Get => & mut entry. 0 ,
841+ MemberItemKind :: Set => & mut entry. 1 ,
842+ } ;
843+ if func. is_some ( ) {
844+ return Err ( syn:: Error :: new_spanned (
845+ item_ident,
846+ format ! ( "Multiple member accessors with name '{}'" , name) ,
847+ ) ) ;
848+ }
849+ * func = Some ( item_ident) ;
850+ Ok ( ( ) )
851+ }
852+
853+ fn validate ( & mut self ) -> Result < ( ) > {
854+ let mut errors = Vec :: new ( ) ;
855+ for ( ( name, _) , ( getter, setter) ) in & self . map {
856+ if getter. is_none ( ) {
857+ errors. push ( syn:: Error :: new_spanned (
858+ setter. as_ref ( ) . unwrap ( ) ,
859+ format ! ( "Member '{}' is missing a getter" , name) ,
860+ ) ) ;
861+ } ;
862+ }
863+ errors. into_result ( ) ?;
864+ self . validated = true ;
865+ Ok ( ( ) )
866+ }
867+ }
868+
869+ impl ToTokens for MemberNursery {
870+ fn to_tokens ( & self , tokens : & mut TokenStream ) {
871+ assert ! ( self . validated, "Call `validate()` before token generation" ) ;
872+ let properties = self
873+ . map
874+ . iter ( )
875+ . map ( |( ( name, member_kind) , ( getter, setter) ) | {
876+ let setter = match setter {
877+ Some ( setter) => quote_spanned ! { setter. span( ) => Some ( Self :: #setter) } ,
878+ None => quote ! { None } ,
879+ } ;
880+ let member_kind = match member_kind {
881+ MemberKind :: Bool => {
882+ quote ! ( :: rustpython_vm:: builtins:: descriptor:: MemberKind :: Bool )
883+ }
884+ MemberKind :: ObjectEx => {
885+ quote ! ( :: rustpython_vm:: builtins:: descriptor:: MemberKind :: ObjectEx )
886+ }
887+ } ;
888+ quote_spanned ! { getter. span( ) =>
889+ class. set_str_attr(
890+ #name,
891+ ctx. new_member( #name, #member_kind, Self :: #getter, #setter, class) ,
892+ ctx,
893+ ) ;
894+ }
895+ } ) ;
896+ tokens. extend ( properties) ;
897+ }
898+ }
899+
808900struct MethodItemMeta ( ItemMetaInner ) ;
809901
810902impl ItemMeta for MethodItemMeta {
@@ -983,7 +1075,7 @@ impl SlotItemMeta {
9831075struct MemberItemMeta ( ItemMetaInner ) ;
9841076
9851077impl ItemMeta for MemberItemMeta {
986- const ALLOWED_NAMES : & ' static [ & ' static str ] = & [ "magic" ] ;
1078+ const ALLOWED_NAMES : & ' static [ & ' static str ] = & [ "magic" , "type" , "setter" ] ;
9871079
9881080 fn from_inner ( inner : ItemMetaInner ) -> Self {
9891081 Self ( inner)
@@ -994,11 +1086,52 @@ impl ItemMeta for MemberItemMeta {
9941086}
9951087
9961088impl MemberItemMeta {
997- fn member_name ( & self ) -> Result < String > {
1089+ fn member_name ( & self ) -> Result < ( String , MemberItemKind ) > {
9981090 let inner = self . inner ( ) ;
1091+ let sig_name = inner. item_name ( ) ;
1092+ let extract_prefix_name = |prefix, item_typ| {
1093+ if let Some ( name) = sig_name. strip_prefix ( prefix) {
1094+ if name. is_empty ( ) {
1095+ Err ( syn:: Error :: new_spanned (
1096+ & inner. meta_ident ,
1097+ format ! (
1098+ "A #[{}({typ})] fn with a {prefix}* name must \
1099+ have something after \" {prefix}\" ",
1100+ inner. meta_name( ) ,
1101+ typ = item_typ,
1102+ prefix = prefix
1103+ ) ,
1104+ ) )
1105+ } else {
1106+ Ok ( name. to_owned ( ) )
1107+ }
1108+ } else {
1109+ Err ( syn:: Error :: new_spanned (
1110+ & inner. meta_ident ,
1111+ format ! (
1112+ "A #[{}(setter)] fn must either have a `name` \
1113+ parameter or a fn name along the lines of \" set_*\" ",
1114+ inner. meta_name( )
1115+ ) ,
1116+ ) )
1117+ }
1118+ } ;
9991119 let magic = inner. _bool ( "magic" ) ?;
1000- let name = inner. item_name ( ) ;
1001- Ok ( if magic { format ! ( "__{}__" , name) } else { name } )
1120+ let kind = if inner. _bool ( "setter" ) ? {
1121+ MemberItemKind :: Set
1122+ } else {
1123+ MemberItemKind :: Get
1124+ } ;
1125+ let name = match kind {
1126+ MemberItemKind :: Get => sig_name,
1127+ MemberItemKind :: Set => extract_prefix_name ( "set_" , "setter" ) ?,
1128+ } ;
1129+ Ok ( ( if magic { format ! ( "__{}__" , name) } else { name } , kind) )
1130+ }
1131+
1132+ fn member_kind ( & self ) -> Result < Option < String > > {
1133+ let inner = self . inner ( ) ;
1134+ inner. _optional_str ( "type" )
10021135 }
10031136}
10041137
0 commit comments