11use crate :: error:: Diagnostic ;
2+ use crate :: pystructseq:: PyStructSequenceMeta ;
23use crate :: util:: {
34 ALL_ALLOWED_NAMES , AttrItemMeta , AttributeExt , ClassItemMeta , ContentItem , ContentItemInner ,
45 ErrorVec , ItemMeta , ItemNursery , ModuleItemMeta , SimpleItemMeta , format_doc, iter_use_idents,
@@ -18,6 +19,7 @@ enum AttrName {
1819 Attr ,
1920 Class ,
2021 Exception ,
22+ StructSequence ,
2123}
2224
2325impl std:: fmt:: Display for AttrName {
@@ -27,6 +29,7 @@ impl std::fmt::Display for AttrName {
2729 Self :: Attr => "pyattr" ,
2830 Self :: Class => "pyclass" ,
2931 Self :: Exception => "pyexception" ,
32+ Self :: StructSequence => "pystruct_sequence" ,
3033 } ;
3134 s. fmt ( f)
3235 }
@@ -41,6 +44,7 @@ impl FromStr for AttrName {
4144 "pyattr" => Self :: Attr ,
4245 "pyclass" => Self :: Class ,
4346 "pyexception" => Self :: Exception ,
47+ "pystruct_sequence" => Self :: StructSequence ,
4448 s => {
4549 return Err ( s. to_owned ( ) ) ;
4650 }
@@ -235,6 +239,10 @@ fn module_item_new(
235239 inner : ContentItemInner { index, attr_name } ,
236240 py_attrs,
237241 } ) ,
242+ AttrName :: StructSequence => Box :: new ( StructSequenceItem {
243+ inner : ContentItemInner { index, attr_name } ,
244+ py_attrs,
245+ } ) ,
238246 }
239247}
240248
@@ -301,13 +309,16 @@ where
301309 result. push ( item_new ( i, attr_name, Vec :: new ( ) ) ) ;
302310 } else {
303311 match attr_name {
304- AttrName :: Class | AttrName :: Function | AttrName :: Exception => {
312+ AttrName :: Class
313+ | AttrName :: Function
314+ | AttrName :: Exception
315+ | AttrName :: StructSequence => {
305316 result. push ( item_new ( i, attr_name, py_attrs. clone ( ) ) ) ;
306317 }
307318 _ => {
308319 bail_span ! (
309320 attr,
310- "#[pyclass], #[pyfunction], or #[pyexception ] can follow #[pyattr]" ,
321+ "#[pyclass], #[pyfunction], #[pyexception], or #[pystruct_sequence ] can follow #[pyattr]" ,
311322 )
312323 }
313324 }
@@ -402,6 +413,12 @@ struct AttributeItem {
402413 py_attrs : Vec < usize > ,
403414}
404415
416+ /// #[pystruct_sequence]
417+ struct StructSequenceItem {
418+ inner : ContentItemInner < AttrName > ,
419+ py_attrs : Vec < usize > ,
420+ }
421+
405422impl ContentItem for FunctionItem {
406423 type AttrName = AttrName ;
407424
@@ -426,6 +443,14 @@ impl ContentItem for AttributeItem {
426443 }
427444}
428445
446+ impl ContentItem for StructSequenceItem {
447+ type AttrName = AttrName ;
448+
449+ fn inner ( & self ) -> & ContentItemInner < AttrName > {
450+ & self . inner
451+ }
452+ }
453+
429454struct ModuleItemArgs < ' a > {
430455 item : & ' a mut Item ,
431456 attrs : & ' a mut Vec < Attribute > ,
@@ -602,6 +627,84 @@ impl ModuleItem for ClassItem {
602627 }
603628}
604629
630+ impl ModuleItem for StructSequenceItem {
631+ fn gen_module_item ( & self , args : ModuleItemArgs < ' _ > ) -> Result < ( ) > {
632+ // Get the struct identifier (this IS the Python type, e.g., PyStructTime)
633+ let pytype_ident = match args. item {
634+ Item :: Struct ( s) => s. ident . clone ( ) ,
635+ other => bail_span ! ( other, "#[pystruct_sequence] can only be on a struct" ) ,
636+ } ;
637+
638+ // Parse the #[pystruct_sequence(name = "...", module = "...", no_attr)] attribute
639+ let structseq_attr = & args. attrs [ self . inner . index ] ;
640+ let meta = PyStructSequenceMeta :: from_attr ( pytype_ident. clone ( ) , structseq_attr) ?;
641+
642+ let class_name = meta. class_name ( ) ?. ok_or_else ( || {
643+ syn:: Error :: new_spanned (
644+ structseq_attr,
645+ "#[pystruct_sequence] requires name parameter" ,
646+ )
647+ } ) ?;
648+ let module_name = meta. module ( ) ?. unwrap_or_else ( || args. context . name . clone ( ) ) ;
649+ let no_attr = meta. no_attr ( ) ?;
650+
651+ // Generate the class creation code
652+ let class_new = quote_spanned ! ( pytype_ident. span( ) =>
653+ let new_class = <#pytype_ident as :: rustpython_vm:: class:: PyClassImpl >:: make_class( ctx) ;
654+ new_class. set_attr( rustpython_vm:: identifier!( ctx, __module__) , vm. new_pyobj( #module_name) ) ;
655+ ) ;
656+
657+ // Handle py_attrs for custom names, or use class_name as default
658+ let mut py_names = Vec :: new ( ) ;
659+ for attr_index in self . py_attrs . iter ( ) . rev ( ) {
660+ let attr_attr = args. attrs . remove ( * attr_index) ;
661+ let item_meta = SimpleItemMeta :: from_attr ( pytype_ident. clone ( ) , & attr_attr) ?;
662+ let py_name = item_meta
663+ . optional_name ( )
664+ . unwrap_or_else ( || class_name. clone ( ) ) ;
665+ py_names. push ( py_name) ;
666+ }
667+
668+ // Require explicit #[pyattr] or no_attr, like #[pyclass]
669+ if self . py_attrs . is_empty ( ) && !no_attr {
670+ bail_span ! (
671+ pytype_ident,
672+ "#[pystruct_sequence] requires #[pyattr] to be a module attribute. \
673+ To keep it free type, try #[pystruct_sequence(..., no_attr)]"
674+ )
675+ }
676+
677+ let set_attr = match py_names. len ( ) {
678+ 0 => quote ! {
679+ let _ = new_class; // suppress warning
680+ } ,
681+ 1 => {
682+ let py_name = & py_names[ 0 ] ;
683+ quote ! {
684+ vm. __module_set_attr( & module, vm. ctx. intern_str( #py_name) , new_class) . unwrap( ) ;
685+ }
686+ }
687+ _ => quote ! {
688+ for name in [ #( #py_names, ) * ] {
689+ vm. __module_set_attr( & module, vm. ctx. intern_str( name) , new_class. clone( ) ) . unwrap( ) ;
690+ }
691+ } ,
692+ } ;
693+
694+ args. context . attribute_items . add_item (
695+ pytype_ident. clone ( ) ,
696+ py_names,
697+ args. cfgs . to_vec ( ) ,
698+ quote_spanned ! { pytype_ident. span( ) =>
699+ #class_new
700+ #set_attr
701+ } ,
702+ 0 ,
703+ ) ?;
704+ Ok ( ( ) )
705+ }
706+ }
707+
605708impl ModuleItem for AttributeItem {
606709 fn gen_module_item ( & self , args : ModuleItemArgs < ' _ > ) -> Result < ( ) > {
607710 let cfgs = args. cfgs . to_vec ( ) ;
0 commit comments