@@ -18,6 +18,7 @@ enum AttrName {
1818 Attr ,
1919 Class ,
2020 Exception ,
21+ StructSequence ,
2122}
2223
2324impl std:: fmt:: Display for AttrName {
@@ -27,6 +28,7 @@ impl std::fmt::Display for AttrName {
2728 Self :: Attr => "pyattr" ,
2829 Self :: Class => "pyclass" ,
2930 Self :: Exception => "pyexception" ,
31+ Self :: StructSequence => "pystruct_sequence" ,
3032 } ;
3133 s. fmt ( f)
3234 }
@@ -41,6 +43,7 @@ impl FromStr for AttrName {
4143 "pyattr" => Self :: Attr ,
4244 "pyclass" => Self :: Class ,
4345 "pyexception" => Self :: Exception ,
46+ "pystruct_sequence" => Self :: StructSequence ,
4447 s => {
4548 return Err ( s. to_owned ( ) ) ;
4649 }
@@ -235,6 +238,10 @@ fn module_item_new(
235238 inner : ContentItemInner { index, attr_name } ,
236239 py_attrs,
237240 } ) ,
241+ AttrName :: StructSequence => Box :: new ( StructSequenceItem {
242+ inner : ContentItemInner { index, attr_name } ,
243+ py_attrs,
244+ } ) ,
238245 }
239246}
240247
@@ -402,6 +409,12 @@ struct AttributeItem {
402409 py_attrs : Vec < usize > ,
403410}
404411
412+ /// #[pystruct_sequence]
413+ struct StructSequenceItem {
414+ inner : ContentItemInner < AttrName > ,
415+ py_attrs : Vec < usize > ,
416+ }
417+
405418impl ContentItem for FunctionItem {
406419 type AttrName = AttrName ;
407420
@@ -426,6 +439,14 @@ impl ContentItem for AttributeItem {
426439 }
427440}
428441
442+ impl ContentItem for StructSequenceItem {
443+ type AttrName = AttrName ;
444+
445+ fn inner ( & self ) -> & ContentItemInner < AttrName > {
446+ & self . inner
447+ }
448+ }
449+
429450struct ModuleItemArgs < ' a > {
430451 item : & ' a mut Item ,
431452 attrs : & ' a mut Vec < Attribute > ,
@@ -602,6 +623,112 @@ impl ModuleItem for ClassItem {
602623 }
603624}
604625
626+ impl ModuleItem for StructSequenceItem {
627+ fn gen_module_item ( & self , args : ModuleItemArgs < ' _ > ) -> Result < ( ) > {
628+ // Get the struct identifier (this IS the Python type, e.g., PyStructTime)
629+ let pytype_ident = match args. item {
630+ Item :: Struct ( s) => s. ident . clone ( ) ,
631+ other => bail_span ! ( other, "#[pystruct_sequence] can only be on a struct" ) ,
632+ } ;
633+
634+ // Parse the #[pystruct_sequence(name = "...", module = "...", no_attr)] attribute
635+ let structseq_attr = & args. attrs [ self . inner . index ] ;
636+ let ( class_name, module_name, no_attr) = {
637+ use syn_ext:: ext:: AttributeExt as _;
638+ let meta = structseq_attr. parse_meta ( ) . ok ( ) ;
639+ let mut name = None ;
640+ let mut module = None ;
641+ let mut no_attr = false ;
642+ if let Some ( syn_ext:: types:: Meta :: List ( list) ) = meta {
643+ for nested in list. nested . iter ( ) {
644+ match nested {
645+ syn_ext:: types:: NestedMeta :: Meta ( syn_ext:: types:: Meta :: NameValue ( nv) ) => {
646+ if let syn:: Expr :: Lit ( syn:: ExprLit {
647+ lit : syn:: Lit :: Str ( lit) ,
648+ ..
649+ } ) = & nv. value
650+ {
651+ if nv. path . is_ident ( "name" ) {
652+ name = Some ( lit. value ( ) ) ;
653+ } else if nv. path . is_ident ( "module" ) {
654+ module = Some ( lit. value ( ) ) ;
655+ }
656+ }
657+ }
658+ syn_ext:: types:: NestedMeta :: Meta ( syn_ext:: types:: Meta :: Path ( path) ) => {
659+ if path. is_ident ( "no_attr" ) {
660+ no_attr = true ;
661+ }
662+ }
663+ _ => { }
664+ }
665+ }
666+ }
667+ // name parameter is required
668+ let class_name = name. ok_or_else ( || {
669+ syn:: Error :: new_spanned (
670+ structseq_attr,
671+ "#[pystruct_sequence] requires name parameter" ,
672+ )
673+ } ) ?;
674+ // Use explicit module if provided, otherwise use context module name
675+ let module_name = module. unwrap_or_else ( || args. context . name . clone ( ) ) ;
676+ ( class_name, module_name, no_attr)
677+ } ;
678+
679+ // Generate the class creation code
680+ let class_new = quote_spanned ! ( pytype_ident. span( ) =>
681+ let new_class = <#pytype_ident as :: rustpython_vm:: class:: PyClassImpl >:: make_class( ctx) ;
682+ new_class. set_attr( rustpython_vm:: identifier!( ctx, __module__) , vm. new_pyobj( #module_name) ) ;
683+ ) ;
684+
685+ // Handle py_attrs for custom names, or use class_name as default
686+ let mut py_names = Vec :: new ( ) ;
687+ for attr_index in self . py_attrs . iter ( ) . rev ( ) {
688+ let attr_attr = args. attrs . remove ( * attr_index) ;
689+ let item_meta = SimpleItemMeta :: from_attr ( pytype_ident. clone ( ) , & attr_attr) ?;
690+ let py_name = item_meta
691+ . optional_name ( )
692+ . unwrap_or_else ( || class_name. clone ( ) ) ;
693+ py_names. push ( py_name) ;
694+ }
695+
696+ // If no #[pyattr] was used and no_attr is not set, auto-register with the class name
697+ if py_names. is_empty ( ) && !no_attr {
698+ py_names. push ( class_name. clone ( ) ) ;
699+ }
700+
701+ let set_attr = match py_names. len ( ) {
702+ 0 => quote ! {
703+ let _ = new_class; // suppress warning
704+ } ,
705+ 1 => {
706+ let py_name = & py_names[ 0 ] ;
707+ quote ! {
708+ vm. __module_set_attr( & module, vm. ctx. intern_str( #py_name) , new_class) . unwrap( ) ;
709+ }
710+ }
711+ _ => quote ! {
712+ for name in [ #( #py_names, ) * ] {
713+ vm. __module_set_attr( & module, vm. ctx. intern_str( name) , new_class. clone( ) ) . unwrap( ) ;
714+ }
715+ } ,
716+ } ;
717+
718+ args. context . attribute_items . add_item (
719+ pytype_ident. clone ( ) ,
720+ py_names,
721+ args. cfgs . to_vec ( ) ,
722+ quote_spanned ! { pytype_ident. span( ) =>
723+ #class_new
724+ #set_attr
725+ } ,
726+ 0 ,
727+ ) ?;
728+ Ok ( ( ) )
729+ }
730+ }
731+
605732impl ModuleItem for AttributeItem {
606733 fn gen_module_item ( & self , args : ModuleItemArgs < ' _ > ) -> Result < ( ) > {
607734 let cfgs = args. cfgs . to_vec ( ) ;
0 commit comments