@@ -65,8 +65,13 @@ impl SemanticSyntaxChecker {
65
65
}
66
66
Stmt :: Match ( match_stmt) => {
67
67
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
+ }
70
75
}
71
76
Stmt :: FunctionDef ( ast:: StmtFunctionDef { type_params, .. } )
72
77
| Stmt :: ClassDef ( ast:: StmtClassDef { type_params, .. } )
@@ -262,68 +267,6 @@ impl SemanticSyntaxChecker {
262
267
}
263
268
}
264
269
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
-
327
270
fn irrefutable_match_case < Ctx : SemanticSyntaxContext > ( stmt : & ast:: StmtMatch , ctx : & Ctx ) {
328
271
// test_ok irrefutable_case_pattern_at_end
329
272
// match x:
@@ -575,6 +518,9 @@ impl Display for SemanticSyntaxError {
575
518
EscapeDefault ( key)
576
519
)
577
520
}
521
+ SemanticSyntaxErrorKind :: DuplicateMatchClassAttribute ( name) => {
522
+ write ! ( f, "attribute name `{name}` repeated in class pattern" , )
523
+ }
578
524
SemanticSyntaxErrorKind :: LoadBeforeGlobalDeclaration { name, start : _ } => {
579
525
write ! ( f, "name `{name}` is used prior to global declaration" )
580
526
}
@@ -730,6 +676,16 @@ pub enum SemanticSyntaxErrorKind {
730
676
/// [CPython grammar]: https://docs.python.org/3/reference/grammar.html
731
677
DuplicateMatchKey ( String ) ,
732
678
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
+
733
689
/// Represents the use of a `global` variable before its `global` declaration.
734
690
///
735
691
/// ## Examples
@@ -787,12 +743,12 @@ impl Visitor<'_> for ReboundComprehensionVisitor<'_> {
787
743
}
788
744
}
789
745
790
- struct MultipleCaseAssignmentVisitor < ' a , Ctx > {
746
+ struct MatchPatternVisitor < ' a , Ctx > {
791
747
names : FxHashSet < & ' a ast:: name:: Name > ,
792
748
ctx : & ' a Ctx ,
793
749
}
794
750
795
- impl < ' a , Ctx : SemanticSyntaxContext > MultipleCaseAssignmentVisitor < ' a , Ctx > {
751
+ impl < ' a , Ctx : SemanticSyntaxContext > MatchPatternVisitor < ' a , Ctx > {
796
752
fn visit_pattern ( & mut self , pattern : & ' a Pattern ) {
797
753
// test_ok class_keyword_in_case_pattern
798
754
// match 2:
@@ -821,19 +777,87 @@ impl<'a, Ctx: SemanticSyntaxContext> MultipleCaseAssignmentVisitor<'a, Ctx> {
821
777
self . visit_pattern ( pattern) ;
822
778
}
823
779
}
824
- Pattern :: MatchMapping ( ast:: PatternMatchMapping { patterns, rest, .. } ) => {
780
+ Pattern :: MatchMapping ( ast:: PatternMatchMapping {
781
+ keys,
782
+ patterns,
783
+ rest,
784
+ ..
785
+ } ) => {
825
786
for pattern in patterns {
826
787
self . visit_pattern ( pattern) ;
827
788
}
828
789
if let Some ( rest) = rest {
829
790
self . insert ( rest) ;
830
791
}
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
+ }
831
838
}
832
839
Pattern :: MatchClass ( ast:: PatternMatchClass { arguments, .. } ) => {
833
840
for pattern in & arguments. patterns {
834
841
self . visit_pattern ( pattern) ;
835
842
}
843
+ let mut seen = FxHashSet :: default ( ) ;
836
844
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
+ }
837
861
self . visit_pattern ( & keyword. pattern ) ;
838
862
}
839
863
}
0 commit comments