1- use crate :: internals:: ast:: { Container , Data , Field , Style } ;
1+ use crate :: internals:: ast:: { Container , Data , Field , Style , Variant } ;
22use crate :: internals:: attr:: { Default , Identifier , TagType } ;
33use crate :: internals:: { ungroup, Ctxt , Derive } ;
4+ use std:: collections:: btree_map:: Entry ;
5+ use std:: collections:: { BTreeMap , BTreeSet } ;
46use syn:: { Member , Type } ;
57
68// 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) {
1618 check_adjacent_tag_conflict ( cx, cont) ;
1719 check_transparent ( cx, cont, derive) ;
1820 check_from_and_try_from ( cx, cont) ;
21+ check_name_conflicts ( cx, cont, derive) ;
1922}
2023
2124// 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) {
475478 ) ;
476479 }
477480}
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