1
1
use crate :: attributes:: {
2
2
self , get_pyo3_options, CrateAttribute , DefaultAttribute , FromPyWithAttribute ,
3
+ RenameAllAttribute , RenamingRule ,
3
4
} ;
4
- use crate :: utils:: Ctx ;
5
+ use crate :: utils:: { self , Ctx } ;
5
6
use proc_macro2:: TokenStream ;
6
7
use quote:: { format_ident, quote, quote_spanned, ToTokens } ;
7
8
use syn:: {
@@ -25,7 +26,7 @@ impl<'a> Enum<'a> {
25
26
///
26
27
/// `data_enum` is the `syn` representation of the input enum, `ident` is the
27
28
/// `Identifier` of the enum.
28
- fn new ( data_enum : & ' a DataEnum , ident : & ' a Ident ) -> Result < Self > {
29
+ fn new ( data_enum : & ' a DataEnum , ident : & ' a Ident , options : ContainerOptions ) -> Result < Self > {
29
30
ensure_spanned ! (
30
31
!data_enum. variants. is_empty( ) ,
31
32
ident. span( ) => "cannot derive FromPyObject for empty enum"
@@ -34,9 +35,21 @@ impl<'a> Enum<'a> {
34
35
. variants
35
36
. iter ( )
36
37
. map ( |variant| {
37
- let attrs = ContainerOptions :: from_attrs ( & variant. attrs ) ?;
38
+ let mut variant_options = ContainerOptions :: from_attrs ( & variant. attrs ) ?;
39
+ if let Some ( rename_all) = & options. rename_all {
40
+ ensure_spanned ! (
41
+ variant_options. rename_all. is_none( ) ,
42
+ variant_options. rename_all. span( ) => "Useless variant `rename_all` - enum is already annotated with `rename_all"
43
+ ) ;
44
+ variant_options. rename_all = Some ( rename_all. clone ( ) ) ;
45
+
46
+ }
38
47
let var_ident = & variant. ident ;
39
- Container :: new ( & variant. fields , parse_quote ! ( #ident:: #var_ident) , attrs)
48
+ Container :: new (
49
+ & variant. fields ,
50
+ parse_quote ! ( #ident:: #var_ident) ,
51
+ variant_options,
52
+ )
40
53
} )
41
54
. collect :: < Result < Vec < _ > > > ( ) ?;
42
55
@@ -129,6 +142,7 @@ struct Container<'a> {
129
142
path : syn:: Path ,
130
143
ty : ContainerType < ' a > ,
131
144
err_name : String ,
145
+ rename_rule : Option < RenamingRule > ,
132
146
}
133
147
134
148
impl < ' a > Container < ' a > {
@@ -138,6 +152,10 @@ impl<'a> Container<'a> {
138
152
fn new ( fields : & ' a Fields , path : syn:: Path , options : ContainerOptions ) -> Result < Self > {
139
153
let style = match fields {
140
154
Fields :: Unnamed ( unnamed) if !unnamed. unnamed . is_empty ( ) => {
155
+ ensure_spanned ! (
156
+ options. rename_all. is_none( ) ,
157
+ options. rename_all. span( ) => "`rename_all` is useless on tuple structs and variants."
158
+ ) ;
141
159
let mut tuple_fields = unnamed
142
160
. unnamed
143
161
. iter ( )
@@ -213,6 +231,10 @@ impl<'a> Container<'a> {
213
231
struct_fields. len( ) == 1 ,
214
232
fields. span( ) => "transparent structs and variants can only have 1 field"
215
233
) ;
234
+ ensure_spanned ! (
235
+ options. rename_all. is_none( ) ,
236
+ options. rename_all. span( ) => "`rename_all` is not permitted on `transparent` structs and variants"
237
+ ) ;
216
238
let field = struct_fields. pop ( ) . unwrap ( ) ;
217
239
ensure_spanned ! (
218
240
field. getter. is_none( ) ,
@@ -236,6 +258,7 @@ impl<'a> Container<'a> {
236
258
path,
237
259
ty : style,
238
260
err_name,
261
+ rename_rule : options. rename_all . map ( |v| v. value . rule ) ,
239
262
} ;
240
263
Ok ( v)
241
264
}
@@ -359,7 +382,11 @@ impl<'a> Container<'a> {
359
382
quote ! ( #pyo3_path:: types:: PyAnyMethods :: getattr( obj, #pyo3_path:: intern!( obj. py( ) , #name) ) )
360
383
}
361
384
FieldGetter :: GetAttr ( None ) => {
362
- quote ! ( #pyo3_path:: types:: PyAnyMethods :: getattr( obj, #pyo3_path:: intern!( obj. py( ) , #field_name) ) )
385
+ let name = self
386
+ . rename_rule
387
+ . map ( |rule| utils:: apply_renaming_rule ( rule, & field_name) ) ;
388
+ let name = name. as_deref ( ) . unwrap_or ( & field_name) ;
389
+ quote ! ( #pyo3_path:: types:: PyAnyMethods :: getattr( obj, #pyo3_path:: intern!( obj. py( ) , #name) ) )
363
390
}
364
391
FieldGetter :: GetItem ( Some ( syn:: Lit :: Str ( key) ) ) => {
365
392
quote ! ( #pyo3_path:: types:: PyAnyMethods :: get_item( obj, #pyo3_path:: intern!( obj. py( ) , #key) ) )
@@ -368,7 +395,11 @@ impl<'a> Container<'a> {
368
395
quote ! ( #pyo3_path:: types:: PyAnyMethods :: get_item( obj, #key) )
369
396
}
370
397
FieldGetter :: GetItem ( None ) => {
371
- quote ! ( #pyo3_path:: types:: PyAnyMethods :: get_item( obj, #pyo3_path:: intern!( obj. py( ) , #field_name) ) )
398
+ let name = self
399
+ . rename_rule
400
+ . map ( |rule| utils:: apply_renaming_rule ( rule, & field_name) ) ;
401
+ let name = name. as_deref ( ) . unwrap_or ( & field_name) ;
402
+ quote ! ( #pyo3_path:: types:: PyAnyMethods :: get_item( obj, #pyo3_path:: intern!( obj. py( ) , #name) ) )
372
403
}
373
404
} ;
374
405
let extractor = if let Some ( FromPyWithAttribute {
@@ -418,6 +449,8 @@ struct ContainerOptions {
418
449
annotation : Option < syn:: LitStr > ,
419
450
/// Change the path for the pyo3 crate
420
451
krate : Option < CrateAttribute > ,
452
+ /// Converts the field idents according to the [RenamingRule] before extraction
453
+ rename_all : Option < RenameAllAttribute > ,
421
454
}
422
455
423
456
/// Attributes for deriving FromPyObject scoped on containers.
@@ -430,6 +463,8 @@ enum ContainerPyO3Attribute {
430
463
ErrorAnnotation ( LitStr ) ,
431
464
/// Change the path for the pyo3 crate
432
465
Crate ( CrateAttribute ) ,
466
+ /// Converts the field idents according to the [RenamingRule] before extraction
467
+ RenameAll ( RenameAllAttribute ) ,
433
468
}
434
469
435
470
impl Parse for ContainerPyO3Attribute {
@@ -447,6 +482,8 @@ impl Parse for ContainerPyO3Attribute {
447
482
input. parse ( ) . map ( ContainerPyO3Attribute :: ErrorAnnotation )
448
483
} else if lookahead. peek ( Token ! [ crate ] ) {
449
484
input. parse ( ) . map ( ContainerPyO3Attribute :: Crate )
485
+ } else if lookahead. peek ( attributes:: kw:: rename_all) {
486
+ input. parse ( ) . map ( ContainerPyO3Attribute :: RenameAll )
450
487
} else {
451
488
Err ( lookahead. error ( ) )
452
489
}
@@ -489,6 +526,13 @@ impl ContainerOptions {
489
526
) ;
490
527
options. krate = Some ( path) ;
491
528
}
529
+ ContainerPyO3Attribute :: RenameAll ( rename_all) => {
530
+ ensure_spanned ! (
531
+ options. rename_all. is_none( ) ,
532
+ rename_all. span( ) => "`rename_all` may only be provided once"
533
+ ) ;
534
+ options. rename_all = Some ( rename_all) ;
535
+ }
492
536
}
493
537
}
494
538
}
@@ -658,7 +702,7 @@ pub fn build_derive_from_pyobject(tokens: &DeriveInput) -> Result<TokenStream> {
658
702
bail_spanned ! ( tokens. span( ) => "`transparent` or `annotation` is not supported \
659
703
at top level for enums") ;
660
704
}
661
- let en = Enum :: new ( en, & tokens. ident ) ?;
705
+ let en = Enum :: new ( en, & tokens. ident , options ) ?;
662
706
en. build ( ctx)
663
707
}
664
708
syn:: Data :: Struct ( st) => {
0 commit comments