1+ use crate :: util:: { ItemMeta , ItemMetaInner } ;
12use proc_macro2:: TokenStream ;
2- use quote:: quote;
3- use syn:: { DeriveInput , Ident , Result } ;
3+ use quote:: { format_ident , quote} ;
4+ use syn:: { DeriveInput , Ident , Item , Result } ;
45use syn_ext:: ext:: { AttributeExt , GetIdent } ;
5- use syn_ext:: types:: Meta ;
6+ use syn_ext:: types:: { Meta , PunctuatedNestedMeta } ;
67
7- // returning a pair of not-skipped and skipped field names
8+ /// Parse field info from struct: returns (not_skipped_fields, skipped_fields)
89fn field_names ( input : & mut DeriveInput ) -> Result < ( Vec < Ident > , Vec < Ident > ) > {
910 let syn:: Data :: Struct ( struc) = & mut input. data else {
10- bail_span ! (
11- input,
12- "#[pystruct_sequence] can only be on a struct declaration"
13- )
11+ bail_span ! ( input, "#[pystructseq] can only be on a struct declaration" )
1412 } ;
1513
1614 let syn:: Fields :: Named ( fields) = & mut struc. fields else {
1715 bail_span ! (
1816 input,
19- "#[pystruct_sequence ] can only be on a struct with named fields"
17+ "#[pystructseq ] can only be on a struct with named fields"
2018 ) ;
2119 } ;
2220
2321 let mut not_skipped = Vec :: with_capacity ( fields. named . len ( ) ) ;
2422 let mut skipped = Vec :: with_capacity ( fields. named . len ( ) ) ;
2523 for field in & mut fields. named {
2624 let mut skip = false ;
27- // Collect all attributes with pystruct and their indices
2825 let mut attrs_to_remove = Vec :: new ( ) ;
2926
3027 for ( i, attr) in field. attrs . iter ( ) . enumerate ( ) {
@@ -47,8 +44,6 @@ fn field_names(input: &mut DeriveInput) -> Result<(Vec<Ident>, Vec<Ident>)> {
4744 . cloned ( )
4845 . collect ( ) ;
4946
50- // Follow #[serde(skip)] convention.
51- // Consider to add skip_serializing and skip_deserializing if required.
5247 for ident in idents {
5348 match ident. to_string ( ) . as_str ( ) {
5449 "skip" => {
@@ -63,27 +58,257 @@ fn field_names(input: &mut DeriveInput) -> Result<(Vec<Ident>, Vec<Ident>)> {
6358 attrs_to_remove. push ( i) ;
6459 }
6560
66- // Remove attributes in reverse order to maintain valid indices
67- attrs_to_remove. sort_unstable_by ( |a, b| b. cmp ( a) ) ; // Sort in descending order
61+ // Remove attributes in reverse order
62+ attrs_to_remove. sort_unstable_by ( |a, b| b. cmp ( a) ) ;
6863 for index in attrs_to_remove {
6964 field. attrs . remove ( index) ;
7065 }
7166 let ident = field. ident . clone ( ) . unwrap ( ) ;
7267 if skip {
73- skipped. push ( ident. clone ( ) ) ;
68+ skipped. push ( ident) ;
7469 } else {
75- not_skipped. push ( ident. clone ( ) ) ;
70+ not_skipped. push ( ident) ;
7671 }
7772 }
7873
7974 Ok ( ( not_skipped, skipped) )
8075}
8176
77+ /// Derive Python type name from Data struct name
78+ /// e.g., StructTimeData -> PyStructTime, StatResultData -> PyStatResult
79+ fn derive_pytype_name ( data_ident : & Ident ) -> Ident {
80+ let name = data_ident. to_string ( ) ;
81+ let base_name = name. strip_suffix ( "Data" ) . unwrap_or ( & name) ;
82+ let py_name = if base_name. starts_with ( "Py" ) {
83+ base_name. to_string ( )
84+ } else {
85+ format ! ( "Py{}" , base_name)
86+ } ;
87+ format_ident ! ( "{}" , py_name)
88+ }
89+
90+ /// Meta parser for #[pystructseq(...)]
91+ struct PyStructSeqMeta {
92+ inner : ItemMetaInner ,
93+ }
94+
95+ impl ItemMeta for PyStructSeqMeta {
96+ const ALLOWED_NAMES : & ' static [ & ' static str ] = & [ "name" , "module" ] ;
97+
98+ fn from_inner ( inner : ItemMetaInner ) -> Self {
99+ Self { inner }
100+ }
101+ fn inner ( & self ) -> & ItemMetaInner {
102+ & self . inner
103+ }
104+ }
105+
106+ impl PyStructSeqMeta {
107+ fn class_name ( & self ) -> Result < String > {
108+ const KEY : & str = "name" ;
109+ let inner = self . inner ( ) ;
110+ if let Some ( ( _, meta) ) = inner. meta_map . get ( KEY ) {
111+ match meta {
112+ Meta :: NameValue ( syn:: MetaNameValue {
113+ value :
114+ syn:: Expr :: Lit ( syn:: ExprLit {
115+ lit : syn:: Lit :: Str ( lit) ,
116+ ..
117+ } ) ,
118+ ..
119+ } ) => return Ok ( lit. value ( ) ) ,
120+ Meta :: Path ( _) => return Ok ( inner. item_name ( ) ) ,
121+ _ => { }
122+ }
123+ bail_span ! (
124+ inner. meta_ident,
125+ "#[pystructseq({KEY}=value)] expects a string value"
126+ )
127+ } else {
128+ // Default: derive from struct name (e.g., StructTimeData -> struct_time)
129+ let name = inner. item_name ( ) ;
130+ let base_name = name. strip_suffix ( "Data" ) . unwrap_or ( & name) ;
131+ Ok ( base_name. to_lowercase ( ) )
132+ }
133+ }
134+
135+ fn module ( & self ) -> Result < Option < String > > {
136+ const KEY : & str = "module" ;
137+ let inner = self . inner ( ) ;
138+ if let Some ( ( _, meta) ) = inner. meta_map . get ( KEY ) {
139+ match meta {
140+ Meta :: NameValue ( syn:: MetaNameValue {
141+ value :
142+ syn:: Expr :: Lit ( syn:: ExprLit {
143+ lit : syn:: Lit :: Str ( lit) ,
144+ ..
145+ } ) ,
146+ ..
147+ } ) => return Ok ( Some ( lit. value ( ) ) ) ,
148+ _ => { }
149+ }
150+ bail_span ! (
151+ inner. meta_ident,
152+ "#[pystructseq({KEY}=value)] expects a string value"
153+ )
154+ } else {
155+ Ok ( None )
156+ }
157+ }
158+ }
159+
160+ /// New attribute macro: #[pystructseq(name = "...", module = "...")]
161+ pub ( crate ) fn impl_pystructseq ( attr : PunctuatedNestedMeta , item : Item ) -> Result < TokenStream > {
162+ let Item :: Struct ( mut struct_item) = item else {
163+ bail_span ! ( item, "#[pystructseq] can only be applied to a struct" ) ;
164+ } ;
165+
166+ let data_ident = struct_item. ident . clone ( ) ;
167+ let fake_ident = Ident :: new ( "pystructseq" , data_ident. span ( ) ) ;
168+ let meta = PyStructSeqMeta :: from_nested ( data_ident. clone ( ) , fake_ident, attr. into_iter ( ) ) ?;
169+
170+ let class_name = meta. class_name ( ) ?;
171+ let module_name = meta. module ( ) ?;
172+ let pytype_ident = derive_pytype_name ( & data_ident) ;
173+
174+ // Parse fields from struct
175+ let mut derive_input: DeriveInput = syn:: parse_quote!( #struct_item) ;
176+ let ( not_skipped_fields, skipped_fields) = field_names ( & mut derive_input) ?;
177+
178+ // Rebuild struct_item from modified derive_input (attributes removed from fields)
179+ if let syn:: Data :: Struct ( s) = derive_input. data {
180+ struct_item. fields = s. fields ;
181+ }
182+
183+ // Generate field index constants
184+ let field_indices: Vec < _ > = not_skipped_fields
185+ . iter ( )
186+ . enumerate ( )
187+ . map ( |( i, field) | {
188+ let const_name = format_ident ! ( "{}_INDEX" , field. to_string( ) . to_uppercase( ) ) ;
189+ quote ! {
190+ pub const #const_name: usize = #i;
191+ }
192+ } )
193+ . collect ( ) ;
194+
195+ // Module name handling
196+ let module_name_tokens = match & module_name {
197+ Some ( m) => quote ! ( Some ( #m) ) ,
198+ None => quote ! ( None ) ,
199+ } ;
200+
201+ let module_class_name = if let Some ( ref m) = module_name {
202+ format ! ( "{}.{}" , m, class_name)
203+ } else {
204+ class_name. clone ( )
205+ } ;
206+
207+ // Generate the output
208+ let output = quote ! {
209+ // Original Data struct (with #[pystruct] attrs removed from fields)
210+ #struct_item
211+
212+ // Field index constants
213+ impl #data_ident {
214+ #( #field_indices) *
215+ }
216+
217+ // PyStructSequenceData trait implementation
218+ impl :: rustpython_vm:: types:: PyStructSequenceData for #data_ident {
219+ type PyType = #pytype_ident;
220+
221+ const REQUIRED_FIELD_NAMES : & ' static [ & ' static str ] = & [ #( stringify!( #not_skipped_fields) , ) * ] ;
222+ const OPTIONAL_FIELD_NAMES : & ' static [ & ' static str ] = & [ #( stringify!( #skipped_fields) , ) * ] ;
223+
224+ fn into_tuple( self , vm: & :: rustpython_vm:: VirtualMachine ) -> :: rustpython_vm:: builtins:: PyTuple {
225+ let items = vec![
226+ #( :: rustpython_vm:: convert:: ToPyObject :: to_pyobject(
227+ self . #not_skipped_fields,
228+ vm,
229+ ) , ) *
230+ ] ;
231+ :: rustpython_vm:: builtins:: PyTuple :: new_unchecked( items. into_boxed_slice( ) )
232+ }
233+ }
234+
235+ // ToPyObject for Data struct
236+ impl :: rustpython_vm:: convert:: ToPyObject for #data_ident {
237+ fn to_pyobject( self , vm: & :: rustpython_vm:: VirtualMachine ) -> :: rustpython_vm:: PyObjectRef {
238+ :: rustpython_vm:: types:: PyStructSequenceData :: into_struct_sequence( self , vm) . into( )
239+ }
240+ }
241+
242+ // TryFromObject for Data struct
243+ impl :: rustpython_vm:: TryFromObject for #data_ident {
244+ fn try_from_object( vm: & :: rustpython_vm:: VirtualMachine , seq: :: rustpython_vm:: PyObjectRef ) -> :: rustpython_vm:: PyResult <Self > {
245+ let seq = <#pytype_ident as :: rustpython_vm:: types:: PyStructSequence >:: try_elements_from( seq, vm) ?;
246+ let mut iter = seq. into_iter( ) ;
247+ Ok ( Self {
248+ #( #not_skipped_fields: iter. next( ) . unwrap( ) . clone( ) . try_into_value( vm) ?, ) *
249+ #( #skipped_fields: match iter. next( ) {
250+ Some ( v) => v. clone( ) . try_into_value( vm) ?,
251+ None => vm. ctx. none( ) ,
252+ } , ) *
253+ } )
254+ }
255+ }
256+
257+ // Empty Python type struct
258+ #[ doc( hidden) ]
259+ pub struct #pytype_ident;
260+
261+ // PyClassDef for Python type
262+ impl :: rustpython_vm:: class:: PyClassDef for #pytype_ident {
263+ const NAME : & ' static str = #class_name;
264+ const MODULE_NAME : Option <& ' static str > = #module_name_tokens;
265+ const TP_NAME : & ' static str = #module_class_name;
266+ const DOC : Option <& ' static str > = None ;
267+ const BASICSIZE : usize = 0 ;
268+ const UNHASHABLE : bool = false ;
269+
270+ type Base = :: rustpython_vm:: builtins:: PyTuple ;
271+ }
272+
273+ // StaticType for Python type
274+ impl :: rustpython_vm:: class:: StaticType for #pytype_ident {
275+ fn static_cell( ) -> & ' static :: rustpython_vm:: common:: static_cell:: StaticCell <:: rustpython_vm:: builtins:: PyTypeRef > {
276+ :: rustpython_vm:: common:: static_cell! {
277+ static CELL : :: rustpython_vm:: builtins:: PyTypeRef ;
278+ }
279+ & CELL
280+ }
281+
282+ fn static_baseclass( ) -> & ' static :: rustpython_vm:: Py <:: rustpython_vm:: builtins:: PyType > {
283+ use :: rustpython_vm:: class:: StaticType ;
284+ :: rustpython_vm:: builtins:: PyTuple :: static_type( )
285+ }
286+ }
287+
288+ // MaybeTraverse (empty - no GC fields in empty struct)
289+ impl :: rustpython_vm:: object:: MaybeTraverse for #pytype_ident {
290+ fn try_traverse( & self , _traverse_fn: & mut :: rustpython_vm:: object:: TraverseFn <' _>) {
291+ // Empty struct has no fields to traverse
292+ }
293+ }
294+
295+ // PyStructSequence trait for Python type
296+ impl :: rustpython_vm:: types:: PyStructSequence for #pytype_ident {
297+ type Data = #data_ident;
298+ }
299+ } ;
300+
301+ Ok ( output)
302+ }
303+
304+ // Legacy derive macros (kept for backward compatibility during migration)
305+ // These use PyStructSequenceLegacy trait
306+
82307pub ( crate ) fn impl_pystruct_sequence ( mut input : DeriveInput ) -> Result < TokenStream > {
83308 let ( not_skipped_fields, skipped_fields) = field_names ( & mut input) ?;
84309 let ty = & input. ident ;
85310 let ret = quote ! {
86- impl :: rustpython_vm:: types:: PyStructSequence for #ty {
311+ impl :: rustpython_vm:: types:: PyStructSequenceLegacy for #ty {
87312 const REQUIRED_FIELD_NAMES : & ' static [ & ' static str ] = & [ #( stringify!( #not_skipped_fields) , ) * ] ;
88313 const OPTIONAL_FIELD_NAMES : & ' static [ & ' static str ] = & [ #( stringify!( #skipped_fields) , ) * ] ;
89314 fn into_tuple( self , vm: & :: rustpython_vm:: VirtualMachine ) -> :: rustpython_vm:: builtins:: PyTuple {
@@ -98,7 +323,7 @@ pub(crate) fn impl_pystruct_sequence(mut input: DeriveInput) -> Result<TokenStre
98323 }
99324 impl :: rustpython_vm:: convert:: ToPyObject for #ty {
100325 fn to_pyobject( self , vm: & :: rustpython_vm:: VirtualMachine ) -> :: rustpython_vm:: PyObjectRef {
101- :: rustpython_vm:: types:: PyStructSequence :: into_struct_sequence( self , vm) . into( )
326+ :: rustpython_vm:: types:: PyStructSequenceLegacy :: into_struct_sequence( self , vm) . into( )
102327 }
103328 }
104329 } ;
@@ -113,7 +338,7 @@ pub(crate) fn impl_pystruct_sequence_try_from_object(
113338 let ret = quote ! {
114339 impl :: rustpython_vm:: TryFromObject for #ty {
115340 fn try_from_object( vm: & :: rustpython_vm:: VirtualMachine , seq: :: rustpython_vm:: PyObjectRef ) -> :: rustpython_vm:: PyResult <Self > {
116- let seq = Self :: try_elements_from( seq, vm) ?;
341+ let seq = < Self as :: rustpython_vm :: types :: PyStructSequenceLegacy > :: try_elements_from( seq, vm) ?;
117342 let mut iter = seq. into_iter( ) ;
118343 Ok ( Self {
119344 #( #not_skipped_fields: iter. next( ) . unwrap( ) . clone( ) . try_into_value( vm) ?, ) *
0 commit comments