@@ -65,8 +65,13 @@ impl SemanticSyntaxChecker {
6565 }
6666 Stmt :: Match ( match_stmt) => {
6767 Self :: irrefutable_match_case ( match_stmt, ctx) ;
68- Self :: multiple_case_assignment ( match_stmt, ctx) ;
69- Self :: duplicate_match_mapping_keys ( match_stmt, ctx) ;
68+ for case in & match_stmt. cases {
69+ let mut visitor = MatchPatternVisitor {
70+ names : FxHashSet :: default ( ) ,
71+ ctx,
72+ } ;
73+ visitor. visit_pattern ( & case. pattern ) ;
74+ }
7075 }
7176 Stmt :: FunctionDef ( ast:: StmtFunctionDef { type_params, .. } )
7277 | Stmt :: ClassDef ( ast:: StmtClassDef { type_params, .. } )
@@ -262,68 +267,6 @@ impl SemanticSyntaxChecker {
262267 }
263268 }
264269
265- fn multiple_case_assignment < Ctx : SemanticSyntaxContext > ( stmt : & ast:: StmtMatch , ctx : & Ctx ) {
266- for case in & stmt. cases {
267- let mut visitor = MultipleCaseAssignmentVisitor {
268- names : FxHashSet :: default ( ) ,
269- ctx,
270- } ;
271- visitor. visit_pattern ( & case. pattern ) ;
272- }
273- }
274-
275- fn duplicate_match_mapping_keys < Ctx : SemanticSyntaxContext > ( stmt : & ast:: StmtMatch , ctx : & Ctx ) {
276- for mapping in stmt
277- . cases
278- . iter ( )
279- . filter_map ( |case| case. pattern . as_match_mapping ( ) )
280- {
281- let mut seen = FxHashSet :: default ( ) ;
282- for key in mapping
283- . keys
284- . iter ( )
285- // complex numbers (`1 + 2j`) are allowed as keys but are not literals
286- // because they are represented as a `BinOp::Add` between a real number and
287- // an imaginary number
288- . filter ( |key| key. is_literal_expr ( ) || key. is_bin_op_expr ( ) )
289- {
290- if !seen. insert ( ComparableExpr :: from ( key) ) {
291- let key_range = key. range ( ) ;
292- let duplicate_key = ctx. source ( ) [ key_range] . to_string ( ) ;
293- // test_ok duplicate_match_key_attr
294- // match x:
295- // case {x.a: 1, x.a: 2}: ...
296-
297- // test_err duplicate_match_key
298- // match x:
299- // case {"x": 1, "x": 2}: ...
300- // case {b"x": 1, b"x": 2}: ...
301- // case {0: 1, 0: 2}: ...
302- // case {1.0: 1, 1.0: 2}: ...
303- // case {1.0 + 2j: 1, 1.0 + 2j: 2}: ...
304- // case {True: 1, True: 2}: ...
305- // case {None: 1, None: 2}: ...
306- // case {
307- // """x
308- // y
309- // z
310- // """: 1,
311- // """x
312- // y
313- // z
314- // """: 2}: ...
315- // case {"x": 1, "x": 2, "x": 3}: ...
316- // case {0: 1, "x": 1, 0: 2, "x": 2}: ...
317- Self :: add_error (
318- ctx,
319- SemanticSyntaxErrorKind :: DuplicateMatchKey ( duplicate_key) ,
320- key_range,
321- ) ;
322- }
323- }
324- }
325- }
326-
327270 fn irrefutable_match_case < Ctx : SemanticSyntaxContext > ( stmt : & ast:: StmtMatch , ctx : & Ctx ) {
328271 // test_ok irrefutable_case_pattern_at_end
329272 // match x:
@@ -575,6 +518,9 @@ impl Display for SemanticSyntaxError {
575518 EscapeDefault ( key)
576519 )
577520 }
521+ SemanticSyntaxErrorKind :: DuplicateMatchClassAttribute ( name) => {
522+ write ! ( f, "attribute name `{name}` repeated in class pattern" , )
523+ }
578524 SemanticSyntaxErrorKind :: LoadBeforeGlobalDeclaration { name, start : _ } => {
579525 write ! ( f, "name `{name}` is used prior to global declaration" )
580526 }
@@ -730,6 +676,16 @@ pub enum SemanticSyntaxErrorKind {
730676 /// [CPython grammar]: https://docs.python.org/3/reference/grammar.html
731677 DuplicateMatchKey ( String ) ,
732678
679+ /// Represents a duplicate attribute name in a `match` class pattern.
680+ ///
681+ /// ## Examples
682+ ///
683+ /// ```python
684+ /// match x:
685+ /// case Class(x=1, x=2): ...
686+ /// ```
687+ DuplicateMatchClassAttribute ( ast:: name:: Name ) ,
688+
733689 /// Represents the use of a `global` variable before its `global` declaration.
734690 ///
735691 /// ## Examples
@@ -787,12 +743,12 @@ impl Visitor<'_> for ReboundComprehensionVisitor<'_> {
787743 }
788744}
789745
790- struct MultipleCaseAssignmentVisitor < ' a , Ctx > {
746+ struct MatchPatternVisitor < ' a , Ctx > {
791747 names : FxHashSet < & ' a ast:: name:: Name > ,
792748 ctx : & ' a Ctx ,
793749}
794750
795- impl < ' a , Ctx : SemanticSyntaxContext > MultipleCaseAssignmentVisitor < ' a , Ctx > {
751+ impl < ' a , Ctx : SemanticSyntaxContext > MatchPatternVisitor < ' a , Ctx > {
796752 fn visit_pattern ( & mut self , pattern : & ' a Pattern ) {
797753 // test_ok class_keyword_in_case_pattern
798754 // match 2:
@@ -821,19 +777,87 @@ impl<'a, Ctx: SemanticSyntaxContext> MultipleCaseAssignmentVisitor<'a, Ctx> {
821777 self . visit_pattern ( pattern) ;
822778 }
823779 }
824- Pattern :: MatchMapping ( ast:: PatternMatchMapping { patterns, rest, .. } ) => {
780+ Pattern :: MatchMapping ( ast:: PatternMatchMapping {
781+ keys,
782+ patterns,
783+ rest,
784+ ..
785+ } ) => {
825786 for pattern in patterns {
826787 self . visit_pattern ( pattern) ;
827788 }
828789 if let Some ( rest) = rest {
829790 self . insert ( rest) ;
830791 }
792+
793+ let mut seen = FxHashSet :: default ( ) ;
794+ for key in keys
795+ . iter ( )
796+ // complex numbers (`1 + 2j`) are allowed as keys but are not literals
797+ // because they are represented as a `BinOp::Add` between a real number and
798+ // an imaginary number
799+ . filter ( |key| key. is_literal_expr ( ) || key. is_bin_op_expr ( ) )
800+ {
801+ if !seen. insert ( ComparableExpr :: from ( key) ) {
802+ let key_range = key. range ( ) ;
803+ let duplicate_key = self . ctx . source ( ) [ key_range] . to_string ( ) ;
804+ // test_ok duplicate_match_key_attr
805+ // match x:
806+ // case {x.a: 1, x.a: 2}: ...
807+
808+ // test_err duplicate_match_key
809+ // match x:
810+ // case {"x": 1, "x": 2}: ...
811+ // case {b"x": 1, b"x": 2}: ...
812+ // case {0: 1, 0: 2}: ...
813+ // case {1.0: 1, 1.0: 2}: ...
814+ // case {1.0 + 2j: 1, 1.0 + 2j: 2}: ...
815+ // case {True: 1, True: 2}: ...
816+ // case {None: 1, None: 2}: ...
817+ // case {
818+ // """x
819+ // y
820+ // z
821+ // """: 1,
822+ // """x
823+ // y
824+ // z
825+ // """: 2}: ...
826+ // case {"x": 1, "x": 2, "x": 3}: ...
827+ // case {0: 1, "x": 1, 0: 2, "x": 2}: ...
828+ // case [{"x": 1, "x": 2}]: ...
829+ // case Foo(x=1, y={"x": 1, "x": 2}): ...
830+ // case [Foo(x=1), Foo(x=1, y={"x": 1, "x": 2})]: ...
831+ SemanticSyntaxChecker :: add_error (
832+ self . ctx ,
833+ SemanticSyntaxErrorKind :: DuplicateMatchKey ( duplicate_key) ,
834+ key_range,
835+ ) ;
836+ }
837+ }
831838 }
832839 Pattern :: MatchClass ( ast:: PatternMatchClass { arguments, .. } ) => {
833840 for pattern in & arguments. patterns {
834841 self . visit_pattern ( pattern) ;
835842 }
843+ let mut seen = FxHashSet :: default ( ) ;
836844 for keyword in & arguments. keywords {
845+ if !seen. insert ( & keyword. attr . id ) {
846+ // test_err duplicate_match_class_attr
847+ // match x:
848+ // case Class(x=1, x=2): ...
849+ // case [Class(x=1, x=2)]: ...
850+ // case {"x": x, "y": Foo(x=1, x=2)}: ...
851+ // case [{}, {"x": x, "y": Foo(x=1, x=2)}]: ...
852+ // case Class(x=1, d={"x": 1, "x": 2}, other=Class(x=1, x=2)): ...
853+ SemanticSyntaxChecker :: add_error (
854+ self . ctx ,
855+ SemanticSyntaxErrorKind :: DuplicateMatchClassAttribute (
856+ keyword. attr . id . clone ( ) ,
857+ ) ,
858+ keyword. attr . range ,
859+ ) ;
860+ }
837861 self . visit_pattern ( & keyword. pattern ) ;
838862 }
839863 }
0 commit comments