1
1
use proc_macro2:: TokenStream ;
2
2
use quote:: quote;
3
3
use syn:: { DeriveInput , Ident , Result } ;
4
+ use syn_ext:: ext:: { AttributeExt , GetIdent } ;
5
+ use syn_ext:: types:: Meta ;
4
6
5
- fn field_names ( input : & DeriveInput ) -> Result < Vec < & Ident > > {
6
- let fields = if let syn:: Data :: Struct ( ref struc) = input. data {
7
- & struc. fields
8
- } else {
7
+ // returning a pair of not-skipped and skipped field names
8
+ fn field_names ( input : & mut DeriveInput ) -> Result < ( Vec < Ident > , Vec < Ident > ) > {
9
+ let syn:: Data :: Struct ( struc) = & mut input. data else {
9
10
bail_span ! (
10
11
input,
11
12
"#[pystruct_sequence] can only be on a struct declaration"
12
13
)
13
14
} ;
14
15
15
- let field_names: Vec < _ > = match fields {
16
- syn:: Fields :: Named ( fields) => fields
17
- . named
18
- . iter ( )
19
- . map ( |field| field. ident . as_ref ( ) . unwrap ( ) )
20
- . collect ( ) ,
21
- _ => bail_span ! (
16
+ let syn:: Fields :: Named ( fields) = & mut struc. fields else {
17
+ bail_span ! (
22
18
input,
23
19
"#[pystruct_sequence] can only be on a struct with named fields"
24
- ) ,
20
+ ) ;
25
21
} ;
26
22
27
- Ok ( field_names)
23
+ let mut not_skipped = Vec :: with_capacity ( fields. named . len ( ) ) ;
24
+ let mut skipped = Vec :: with_capacity ( fields. named . len ( ) ) ;
25
+ for field in & mut fields. named {
26
+ let mut skip = false ;
27
+ // Collect all attributes with pystruct and their indices
28
+ let mut attrs_to_remove = Vec :: new ( ) ;
29
+
30
+ for ( i, attr) in field. attrs . iter ( ) . enumerate ( ) {
31
+ if !attr. path ( ) . is_ident ( "pystruct" ) {
32
+ continue ;
33
+ }
34
+
35
+ let Ok ( meta) = attr. parse_meta ( ) else {
36
+ continue ;
37
+ } ;
38
+
39
+ let Meta :: List ( l) = meta else {
40
+ bail_span ! ( input, "Only #[pystruct(...)] form is allowed" ) ;
41
+ } ;
42
+
43
+ let idents: Vec < _ > = l
44
+ . nested
45
+ . iter ( )
46
+ . filter_map ( |n| n. get_ident ( ) )
47
+ . cloned ( )
48
+ . collect ( ) ;
49
+
50
+ // Follow #[serde(skip)] convention.
51
+ // Consider to add skip_serializing and skip_deserializing if required.
52
+ for ident in idents {
53
+ match ident. to_string ( ) . as_str ( ) {
54
+ "skip" => {
55
+ skip = true ;
56
+ }
57
+ _ => {
58
+ bail_span ! ( ident, "Unknown item for #[pystruct(...)]" )
59
+ }
60
+ }
61
+ }
62
+
63
+ attrs_to_remove. push ( i) ;
64
+ }
65
+
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
68
+ for index in attrs_to_remove {
69
+ field. attrs . remove ( index) ;
70
+ }
71
+ let ident = field. ident . clone ( ) . unwrap ( ) ;
72
+ if skip {
73
+ skipped. push ( ident. clone ( ) ) ;
74
+ } else {
75
+ not_skipped. push ( ident. clone ( ) ) ;
76
+ }
77
+ }
78
+
79
+ Ok ( ( not_skipped, skipped) )
28
80
}
29
81
30
- pub ( crate ) fn impl_pystruct_sequence ( input : DeriveInput ) -> Result < TokenStream > {
31
- let field_names = field_names ( & input) ?;
82
+ pub ( crate ) fn impl_pystruct_sequence ( mut input : DeriveInput ) -> Result < TokenStream > {
83
+ let ( not_skipped_fields , _skipped_fields ) = field_names ( & mut input) ?;
32
84
let ty = & input. ident ;
33
85
let ret = quote ! {
34
86
impl :: rustpython_vm:: types:: PyStructSequence for #ty {
35
- const FIELD_NAMES : & ' static [ & ' static str ] = & [ #( stringify!( #field_names ) ) , * ] ;
87
+ const FIELD_NAMES : & ' static [ & ' static str ] = & [ #( stringify!( #not_skipped_fields ) ) , * ] ;
36
88
fn into_tuple( self , vm: & :: rustpython_vm:: VirtualMachine ) -> :: rustpython_vm:: builtins:: PyTuple {
37
89
let items = vec![ #( :: rustpython_vm:: convert:: ToPyObject :: to_pyobject(
38
- self . #field_names ,
90
+ self . #not_skipped_fields ,
39
91
vm,
40
92
) ) , * ] ;
41
93
:: rustpython_vm:: builtins:: PyTuple :: new_unchecked( items. into_boxed_slice( ) )
@@ -50,8 +102,10 @@ pub(crate) fn impl_pystruct_sequence(input: DeriveInput) -> Result<TokenStream>
50
102
Ok ( ret)
51
103
}
52
104
53
- pub ( crate ) fn impl_pystruct_sequence_try_from_object ( input : DeriveInput ) -> Result < TokenStream > {
54
- let field_names = field_names ( & input) ?;
105
+ pub ( crate ) fn impl_pystruct_sequence_try_from_object (
106
+ mut input : DeriveInput ,
107
+ ) -> Result < TokenStream > {
108
+ let ( not_skipped_fields, skipped_fields) = field_names ( & mut input) ?;
55
109
let ty = & input. ident ;
56
110
let ret = quote ! {
57
111
impl :: rustpython_vm:: TryFromObject for #ty {
@@ -60,9 +114,14 @@ pub(crate) fn impl_pystruct_sequence_try_from_object(input: DeriveInput) -> Resu
60
114
let seq = Self :: try_elements_from:: <LEN >( seq, vm) ?;
61
115
// TODO: this is possible to be written without iterator
62
116
let mut iter = seq. into_iter( ) ;
63
- Ok ( Self { #(
64
- #field_names: iter. next( ) . unwrap( ) . clone( ) . try_into_value( vm) ?
65
- ) , * } )
117
+ Ok ( Self {
118
+ #(
119
+ #not_skipped_fields: iter. next( ) . unwrap( ) . clone( ) . try_into_value( vm) ?,
120
+ ) *
121
+ #(
122
+ #skipped_fields: vm. ctx. none( ) ,
123
+ ) *
124
+ } )
66
125
}
67
126
}
68
127
} ;
0 commit comments