@@ -4,8 +4,8 @@ use proc_macro::TokenStream;
4
4
use proc_macro2:: { Span , TokenStream as TokenStream2 } ;
5
5
use quote:: quote;
6
6
use syn:: {
7
- parse_macro_input, AttributeArgs , Data , DeriveInput , Fields , Ident , ImplItem , Item , Lit , Meta ,
8
- NestedMeta ,
7
+ parse_macro_input, Attribute , AttributeArgs , Data , DeriveInput , Expr , Field , Fields , Ident ,
8
+ ImplItem , Item , Lit , Meta , NestedMeta ,
9
9
} ;
10
10
11
11
fn rustpython_path ( inside_vm : bool ) -> syn:: Path {
@@ -42,28 +42,193 @@ fn rustpython_path_attr(attr: &AttributeArgs) -> syn::Path {
42
42
} ) )
43
43
}
44
44
45
- #[ proc_macro_derive( FromArgs , attributes( __inside_vm) ) ]
45
+ #[ proc_macro_derive( FromArgs , attributes( __inside_vm, pyarg ) ) ]
46
46
pub fn derive_from_args ( input : TokenStream ) -> TokenStream {
47
47
let ast: DeriveInput = syn:: parse ( input) . unwrap ( ) ;
48
48
49
49
let gen = impl_from_args ( ast) ;
50
50
gen. to_string ( ) . parse ( ) . unwrap ( )
51
51
}
52
52
53
+ /// The kind of the python parameter, this corresponds to the value of Parameter.kind
54
+ /// (https://docs.python.org/3/library/inspect.html#inspect.Parameter.kind)
55
+ enum ParameterKind {
56
+ PositionalOnly ,
57
+ PositionalOrKeyword ,
58
+ KeywordOnly ,
59
+ }
60
+
61
+ impl ParameterKind {
62
+ fn from_ident ( ident : & Ident ) -> ParameterKind {
63
+ if ident == "positional_only" {
64
+ ParameterKind :: PositionalOnly
65
+ } else if ident == "positional_or_keyword" {
66
+ ParameterKind :: PositionalOrKeyword
67
+ } else if ident == "keyword_only" {
68
+ ParameterKind :: KeywordOnly
69
+ } else {
70
+ panic ! ( "Unrecognised attribute" )
71
+ }
72
+ }
73
+ }
74
+
75
+ struct ArgAttribute {
76
+ kind : ParameterKind ,
77
+ default : Option < Expr > ,
78
+ optional : bool ,
79
+ }
80
+
81
+ impl ArgAttribute {
82
+ fn from_attribute ( attr : & Attribute ) -> Option < ArgAttribute > {
83
+ if !attr. path . is_ident ( "pyarg" ) {
84
+ return None ;
85
+ }
86
+
87
+ match attr. parse_meta ( ) . unwrap ( ) {
88
+ Meta :: List ( list) => {
89
+ let mut iter = list. nested . iter ( ) ;
90
+ let first_arg = iter. next ( ) . expect ( "at least one argument in pyarg list" ) ;
91
+ let kind = match first_arg {
92
+ NestedMeta :: Meta ( Meta :: Word ( ident) ) => ParameterKind :: from_ident ( ident) ,
93
+ _ => panic ! ( "Bad syntax for first pyarg attribute argument" ) ,
94
+ } ;
95
+
96
+ let mut attribute = ArgAttribute {
97
+ kind,
98
+ default : None ,
99
+ optional : false ,
100
+ } ;
101
+
102
+ while let Some ( arg) = iter. next ( ) {
103
+ attribute. parse_argument ( arg) ;
104
+ }
105
+
106
+ assert ! (
107
+ attribute. default . is_none( ) || !attribute. optional,
108
+ "Can't set both a default value and optional"
109
+ ) ;
110
+
111
+ Some ( attribute)
112
+ }
113
+ _ => panic ! ( "Bad syntax for pyarg attribute" ) ,
114
+ }
115
+ }
116
+
117
+ fn parse_argument ( & mut self , arg : & NestedMeta ) {
118
+ match arg {
119
+ NestedMeta :: Meta ( Meta :: Word ( ident) ) => {
120
+ if ident == "default" {
121
+ assert ! ( self . default . is_none( ) , "Default already set" ) ;
122
+ let expr = syn:: parse_str :: < Expr > ( "Default::default()" ) . unwrap ( ) ;
123
+ self . default = Some ( expr) ;
124
+ } else if ident == "optional" {
125
+ self . optional = true ;
126
+ } else {
127
+ panic ! ( "Unrecognised pyarg attribute '{}'" , ident) ;
128
+ }
129
+ }
130
+ NestedMeta :: Meta ( Meta :: NameValue ( name_value) ) => {
131
+ if name_value. ident == "default" {
132
+ assert ! ( self . default . is_none( ) , "Default already set" ) ;
133
+
134
+ match name_value. lit {
135
+ Lit :: Str ( ref val) => {
136
+ let expr = val
137
+ . parse :: < Expr > ( )
138
+ . expect ( "a valid expression for default argument" ) ;
139
+ self . default = Some ( expr) ;
140
+ }
141
+ _ => panic ! ( "Expected string value for default argument" ) ,
142
+ }
143
+ } else if name_value. ident == "optional" {
144
+ match name_value. lit {
145
+ Lit :: Bool ( ref val) => {
146
+ self . optional = val. value ;
147
+ }
148
+ _ => panic ! ( "Expected boolean value for optional argument" ) ,
149
+ }
150
+ } else {
151
+ panic ! ( "Unrecognised pyarg attribute '{}'" , name_value. ident) ;
152
+ }
153
+ }
154
+ _ => panic ! ( "Bad syntax for first pyarg attribute argument" ) ,
155
+ } ;
156
+ }
157
+ }
158
+
159
+ fn generate_field ( field : & Field ) -> TokenStream2 {
160
+ let mut pyarg_attrs = field
161
+ . attrs
162
+ . iter ( )
163
+ . filter_map ( ArgAttribute :: from_attribute)
164
+ . collect :: < Vec < _ > > ( ) ;
165
+ let attr = if pyarg_attrs. is_empty ( ) {
166
+ ArgAttribute {
167
+ kind : ParameterKind :: PositionalOrKeyword ,
168
+ default : None ,
169
+ optional : false ,
170
+ }
171
+ } else if pyarg_attrs. len ( ) == 1 {
172
+ pyarg_attrs. remove ( 0 )
173
+ } else {
174
+ panic ! (
175
+ "Multiple pyarg attributes on field '{}'" ,
176
+ field. ident. as_ref( ) . unwrap( )
177
+ ) ;
178
+ } ;
179
+
180
+ let name = & field. ident ;
181
+ let middle = quote ! {
182
+ . map( |x| crate :: pyobject:: TryFromObject :: try_from_object( vm, x) ) . transpose( ) ?
183
+ } ;
184
+ let ending = if let Some ( default) = attr. default {
185
+ quote ! {
186
+ . unwrap_or_else( || #default )
187
+ }
188
+ } else if attr. optional {
189
+ quote ! {
190
+ . map( crate :: function:: OptionalArg :: Present )
191
+ . unwrap_or( crate :: function:: OptionalArg :: Missing )
192
+ }
193
+ } else {
194
+ let err = match attr. kind {
195
+ ParameterKind :: PositionalOnly | ParameterKind :: PositionalOrKeyword => quote ! {
196
+ crate :: function:: ArgumentError :: TooFewArgs
197
+ } ,
198
+ ParameterKind :: KeywordOnly => quote ! {
199
+ crate :: function:: ArgumentError :: RequiredKeywordArgument ( tringify!( #name) )
200
+ } ,
201
+ } ;
202
+ quote ! {
203
+ . ok_or_else( || #err) ?
204
+ }
205
+ } ;
206
+
207
+ match attr. kind {
208
+ ParameterKind :: PositionalOnly => {
209
+ quote ! {
210
+ #name: args. take_positional( ) #middle#ending,
211
+ }
212
+ }
213
+ ParameterKind :: PositionalOrKeyword => {
214
+ quote ! {
215
+ #name: args. take_positional_keyword( stringify!( #name) ) #middle#ending,
216
+ }
217
+ }
218
+ ParameterKind :: KeywordOnly => {
219
+ quote ! {
220
+ #name: args. take_keyword( stringify!( #name) ) #middle#ending,
221
+ }
222
+ }
223
+ }
224
+ }
225
+
53
226
fn impl_from_args ( input : DeriveInput ) -> TokenStream2 {
54
227
let rp_path = rustpython_path_derive ( & input) ;
55
228
let fields = match input. data {
56
229
Data :: Struct ( ref data) => {
57
230
match data. fields {
58
- Fields :: Named ( ref fields) => fields. named . iter ( ) . map ( |field| {
59
- let name = & field. ident ;
60
- quote ! {
61
- #name: crate :: pyobject:: TryFromObject :: try_from_object(
62
- vm,
63
- args. take_keyword( stringify!( #name) ) . unwrap_or_else( || vm. ctx. none( ) )
64
- ) ?,
65
- }
66
- } ) ,
231
+ Fields :: Named ( ref fields) => fields. named . iter ( ) . map ( generate_field) ,
67
232
Fields :: Unnamed ( _) | Fields :: Unit => unimplemented ! ( ) , // TODO: better error message
68
233
}
69
234
}
0 commit comments