@@ -2,7 +2,7 @@ use proc_macro2::TokenStream;
22use quote:: { ToTokens , quote} ;
33use syn:: ext:: IdentExt ;
44use syn:: meta:: ParseNestedMeta ;
5- use syn:: { Attribute , Data , DeriveInput , Expr , Field , Ident , Result , Token , parse_quote} ;
5+ use syn:: { Attribute , Data , DeriveInput , Expr , Field , Ident , Lit , Result , Token , parse_quote} ;
66
77/// The kind of the python parameter, this corresponds to the value of Parameter.kind
88/// (https://docs.python.org/3/library/inspect.html#inspect.Parameter.kind)
@@ -37,6 +37,7 @@ struct ArgAttribute {
3737 name : Option < String > ,
3838 kind : ParameterKind ,
3939 default : Option < DefaultValue > ,
40+ py_default : Option < String > ,
4041 error_msg : Option < String > ,
4142}
4243
@@ -64,6 +65,7 @@ impl ArgAttribute {
6465 name : None ,
6566 kind,
6667 default : None ,
68+ py_default : None ,
6769 error_msg : None ,
6870 } ) ;
6971 return Ok ( ( ) ) ;
@@ -91,6 +93,13 @@ impl ArgAttribute {
9193 if self . default . is_none ( ) {
9294 self . default = Some ( None ) ;
9395 }
96+ // Text-only signature default. See the `FromArgs` derive docs.
97+ } else if meta. path . is_ident ( "py_default" ) {
98+ if self . py_default . is_some ( ) {
99+ return Err ( meta. error ( "py_default already set" ) ) ;
100+ }
101+ let val = meta. value ( ) ?. parse :: < syn:: LitStr > ( ) ?;
102+ self . py_default = Some ( val. value ( ) ) ;
94103 } else if meta. path . is_ident ( "name" ) {
95104 if self . name . is_some ( ) {
96105 return Err ( meta. error ( "already have a name" ) ) ;
@@ -212,6 +221,96 @@ fn generate_field((i, field): (usize, &Field)) -> Result<TokenStream> {
212221 Ok ( file_output)
213222}
214223
224+ fn is_phantom ( field : & Field ) -> bool {
225+ field
226+ . ident
227+ . as_ref ( )
228+ . is_some_and ( |ident| ident. unraw ( ) . to_string ( ) . starts_with ( "_phantom" ) )
229+ }
230+
231+ fn python_str_repr ( s : & str ) -> String {
232+ let mut out = String :: from ( "'" ) ;
233+ for c in s. chars ( ) {
234+ match c {
235+ '\\' => out. push_str ( "\\ \\ " ) ,
236+ '\'' => out. push_str ( "\\ '" ) ,
237+ '\n' => out. push_str ( "\\ n" ) ,
238+ '\r' => out. push_str ( "\\ r" ) ,
239+ '\t' => out. push_str ( "\\ t" ) ,
240+ _ => out. push ( c) ,
241+ }
242+ }
243+ out. push ( '\'' ) ;
244+ out
245+ }
246+
247+ fn python_default_repr ( default : Option < & DefaultValue > , py_default : Option < & str > ) -> Option < String > {
248+ if let Some ( py_default) = py_default {
249+ return Some ( py_default. to_owned ( ) ) ;
250+ }
251+ match default {
252+ None => None ,
253+ Some ( None ) => Some ( "<unrepresentable>" . to_owned ( ) ) ,
254+ Some ( Some ( expr) ) => match expr {
255+ Expr :: Lit ( syn:: ExprLit { lit, .. } ) => match lit {
256+ Lit :: Bool ( b) => Some ( if b. value {
257+ "True" . to_owned ( )
258+ } else {
259+ "False" . to_owned ( )
260+ } ) ,
261+ Lit :: Int ( i) => Some ( i. base10_digits ( ) . to_owned ( ) ) ,
262+ Lit :: Float ( f) => Some ( f. base10_digits ( ) . to_owned ( ) ) ,
263+ Lit :: Str ( s) => Some ( python_str_repr ( & s. value ( ) ) ) ,
264+ _ => Some ( "<unrepresentable>" . to_owned ( ) ) ,
265+ } ,
266+ Expr :: Path ( path) if path. qself . is_none ( ) && path. path . is_ident ( "None" ) => {
267+ Some ( "None" . to_owned ( ) )
268+ }
269+ _ => Some ( "<unrepresentable>" . to_owned ( ) ) ,
270+ } ,
271+ }
272+ }
273+
274+ fn param_token ( field : & Field , attr : & ArgAttribute ) -> Result < TokenStream > {
275+ if let ParameterKind :: Flatten = attr. kind {
276+ let ty = & field. ty ;
277+ return Ok ( quote ! {
278+ :: rustpython_vm:: function:: Param :: flatten(
279+ <#ty as :: rustpython_vm:: function:: FromArgs >:: PARAMS
280+ )
281+ } ) ;
282+ }
283+
284+ let name = field. ident . as_ref ( ) . map ( |ident| ident. unraw ( ) . to_string ( ) ) ;
285+ let pyname = attr
286+ . name
287+ . clone ( )
288+ . or ( name)
289+ . ok_or_else ( || err_span ! ( field, "field in tuple struct must have name attribute" ) ) ?;
290+ let default = python_default_repr ( attr. default . as_ref ( ) , attr. py_default . as_deref ( ) ) ;
291+ let default_tok = match & default {
292+ Some ( default) => quote ! ( Some ( #default ) ) ,
293+ None => quote ! ( None ) ,
294+ } ;
295+ let kind = match attr. kind {
296+ ParameterKind :: PositionalOnly => {
297+ quote ! ( :: rustpython_vm:: function:: ParamKind :: PositionalOnly )
298+ }
299+ ParameterKind :: PositionalOrKeyword => {
300+ quote ! ( :: rustpython_vm:: function:: ParamKind :: PositionalOrKeyword )
301+ }
302+ ParameterKind :: KeywordOnly => quote ! ( :: rustpython_vm:: function:: ParamKind :: KeywordOnly ) ,
303+ ParameterKind :: Flatten => unreachable ! ( ) ,
304+ } ;
305+ Ok ( quote ! {
306+ :: rustpython_vm:: function:: Param {
307+ name: #pyname,
308+ kind: #kind,
309+ default : #default_tok,
310+ }
311+ } )
312+ }
313+
215314fn compute_arity_bounds ( field_attrs : & [ ArgAttribute ] ) -> ( usize , usize ) {
216315 let positional_fields = field_attrs. iter ( ) . filter ( |attr| {
217316 matches ! (
@@ -230,27 +329,40 @@ fn compute_arity_bounds(field_attrs: &[ArgAttribute]) -> (usize, usize) {
230329}
231330
232331pub ( crate ) fn impl_from_args ( input : DeriveInput ) -> Result < TokenStream > {
233- let ( fields, field_attrs) = match input. data {
234- Data :: Struct ( syn:: DataStruct { fields, .. } ) => (
235- fields
236- . iter ( )
237- . enumerate ( )
238- . map ( generate_field)
239- . collect :: < Result < TokenStream > > ( ) ?,
240- fields
241- . iter ( )
242- . filter_map ( |field| field. try_into ( ) . ok ( ) )
243- . collect :: < Vec < ArgAttribute > > ( ) ,
244- ) ,
245- _ => bail_span ! ( input, "FromArgs input must be a struct" ) ,
332+ let Data :: Struct ( syn:: DataStruct {
333+ fields : struct_fields,
334+ ..
335+ } ) = input. data
336+ else {
337+ bail_span ! ( input, "FromArgs input must be a struct" )
246338 } ;
247339
340+ let fields = struct_fields
341+ . iter ( )
342+ . enumerate ( )
343+ . map ( generate_field)
344+ . collect :: < Result < TokenStream > > ( ) ?;
345+ let field_attrs = struct_fields
346+ . iter ( )
347+ . map ( ArgAttribute :: try_from)
348+ . collect :: < Result < Vec < _ > > > ( ) ?;
248349 let ( min_arity, max_arity) = compute_arity_bounds ( & field_attrs) ;
249350
351+ let mut params = Vec :: new ( ) ;
352+ for field in & struct_fields {
353+ if is_phantom ( field) {
354+ continue ;
355+ }
356+ params. push ( param_token ( field, & ArgAttribute :: try_from ( field) ?) ?) ;
357+ }
358+
250359 let name = input. ident ;
251360 let ( impl_generics, ty_generics, where_clause) = input. generics . split_for_impl ( ) ;
252361 let output = quote ! {
253362 impl #impl_generics :: rustpython_vm:: function:: FromArgs for #name #ty_generics #where_clause {
363+ const PARAMS : Option <& ' static [ :: rustpython_vm:: function:: Param ] > =
364+ Some ( & [ #( #params) , * ] ) ;
365+
254366 fn arity( ) -> :: std:: ops:: RangeInclusive <usize > {
255367 #min_arity..=#max_arity
256368 }
0 commit comments