1
- use crate :: internals:: ast:: { Container , Data , Field , Style } ;
1
+ use crate :: internals:: ast:: { Container , Data , Field , Style , Variant } ;
2
2
use crate :: internals:: attr:: { Default , Identifier , TagType } ;
3
3
use crate :: internals:: { ungroup, Ctxt , Derive } ;
4
+ use std:: collections:: btree_map:: Entry ;
5
+ use std:: collections:: { BTreeMap , BTreeSet } ;
4
6
use syn:: { Member , Type } ;
5
7
6
8
// Cross-cutting checks that require looking at more than a single attrs object.
@@ -16,6 +18,7 @@ pub fn check(cx: &Ctxt, cont: &mut Container, derive: Derive) {
16
18
check_adjacent_tag_conflict ( cx, cont) ;
17
19
check_transparent ( cx, cont, derive) ;
18
20
check_from_and_try_from ( cx, cont) ;
21
+ check_name_conflicts ( cx, cont, derive) ;
19
22
}
20
23
21
24
// If some field of a tuple struct is marked #[serde(default)] then all fields
@@ -475,3 +478,134 @@ fn check_from_and_try_from(cx: &Ctxt, cont: &mut Container) {
475
478
) ;
476
479
}
477
480
}
481
+
482
+ // Checks that aliases does not repeated
483
+ fn check_name_conflicts ( cx : & Ctxt , cont : & Container , derive : Derive ) {
484
+ if let Derive :: Deserialize = derive {
485
+ match & cont. data {
486
+ Data :: Enum ( variants) => check_variant_name_conflicts ( cx, & variants) ,
487
+ Data :: Struct ( Style :: Struct , fields) => check_field_name_conflicts ( cx, fields) ,
488
+ _ => { }
489
+ }
490
+ }
491
+ }
492
+
493
+ // All renames already applied
494
+ fn check_variant_name_conflicts ( cx : & Ctxt , variants : & [ Variant ] ) {
495
+ let names: BTreeSet < _ > = variants
496
+ . iter ( )
497
+ . filter_map ( |variant| {
498
+ if variant. attrs . skip_deserializing ( ) {
499
+ None
500
+ } else {
501
+ Some ( variant. attrs . name ( ) . deserialize_name ( ) . to_owned ( ) )
502
+ }
503
+ } )
504
+ . collect ( ) ;
505
+ let mut alias_owners = BTreeMap :: new ( ) ;
506
+
507
+ for variant in variants {
508
+ let name = variant. attrs . name ( ) . deserialize_name ( ) ;
509
+
510
+ for alias in variant. attrs . aliases ( ) . intersection ( & names) {
511
+ // Aliases contains variant names, so filter them out
512
+ if alias == name {
513
+ continue ;
514
+ }
515
+
516
+ // TODO: report other variant location when this become possible
517
+ cx. error_spanned_by (
518
+ variant. original ,
519
+ format ! (
520
+ "alias `{}` conflicts with deserialization name of other variant" ,
521
+ alias
522
+ ) ,
523
+ ) ;
524
+ }
525
+
526
+ for alias in variant. attrs . aliases ( ) {
527
+ // Aliases contains variant names, so filter them out
528
+ if alias == name {
529
+ continue ;
530
+ }
531
+
532
+ match alias_owners. entry ( alias) {
533
+ Entry :: Vacant ( e) => {
534
+ e. insert ( variant) ;
535
+ }
536
+ Entry :: Occupied ( e) => {
537
+ // TODO: report other variant location when this become possible
538
+ cx. error_spanned_by (
539
+ variant. original ,
540
+ format ! (
541
+ "alias `{}` already used by variant {}" ,
542
+ alias,
543
+ e. get( ) . original. ident
544
+ ) ,
545
+ ) ;
546
+ }
547
+ }
548
+ }
549
+
550
+ check_field_name_conflicts ( cx, & variant. fields ) ;
551
+ }
552
+ }
553
+
554
+ // All renames already applied
555
+ fn check_field_name_conflicts ( cx : & Ctxt , fields : & [ Field ] ) {
556
+ let names: BTreeSet < _ > = fields
557
+ . iter ( )
558
+ . filter_map ( |field| {
559
+ if field. attrs . skip_deserializing ( ) {
560
+ None
561
+ } else {
562
+ Some ( field. attrs . name ( ) . deserialize_name ( ) . to_owned ( ) )
563
+ }
564
+ } )
565
+ . collect ( ) ;
566
+ let mut alias_owners = BTreeMap :: new ( ) ;
567
+
568
+ for field in fields {
569
+ let name = field. attrs . name ( ) . deserialize_name ( ) ;
570
+
571
+ for alias in field. attrs . aliases ( ) . intersection ( & names) {
572
+ // Aliases contains field names, so filter them out
573
+ if alias == name {
574
+ continue ;
575
+ }
576
+
577
+ // TODO: report other field location when this become possible
578
+ cx. error_spanned_by (
579
+ field. original ,
580
+ format ! (
581
+ "alias `{}` conflicts with deserialization name of other field" ,
582
+ alias
583
+ ) ,
584
+ ) ;
585
+ }
586
+
587
+ for alias in field. attrs . aliases ( ) {
588
+ // Aliases contains field names, so filter them out
589
+ if alias == name {
590
+ continue ;
591
+ }
592
+
593
+ match alias_owners. entry ( alias) {
594
+ Entry :: Vacant ( e) => {
595
+ e. insert ( field) ;
596
+ }
597
+ Entry :: Occupied ( e) => {
598
+ // TODO: report other field location when this become possible
599
+ cx. error_spanned_by (
600
+ field. original ,
601
+ format ! (
602
+ "alias `{}` already used by field {}" ,
603
+ alias,
604
+ e. get( ) . original. ident. as_ref( ) . unwrap( )
605
+ ) ,
606
+ ) ;
607
+ }
608
+ }
609
+ }
610
+ }
611
+ }
0 commit comments