Skip to content

Commit 6a07dd2

Browse files
authored
[syntax-errors] Fix multiple assignment for class keyword argument (#17184)
Summary -- Fixes #17181. The cases being tested with multiple *keys* being equal are actually a slightly different error, more like the error for `MatchMapping` than like the other multiple assignment errors: ```pycon >>> match x: ... case Class(x=x, x=x): ... ... File "<python-input-249>", line 2 case Class(x=x, x=x): ... ^ SyntaxError: attribute name repeated in class pattern: x >>> match x: ... case {"x": 1, "x": 2}: ... ... File "<python-input-251>", line 2 case {"x": 1, "x": 2}: ... ^^^^^^^^^^^^^^^^ SyntaxError: mapping pattern checks duplicate key ('x') >>> match x: ... case [x, x]: ... ... File "<python-input-252>", line 2 case [x, x]: ... ^ SyntaxError: multiple assignments to name 'x' in pattern ``` This PR just stops the false positive reported in the issue, but I will quickly follow it up with a new rule (or possibly combined with the mapping rule) catching the repeated attributes separately. Test Plan -- New inline `test_ok` and updating the `test_err` cases to have duplicate values instead of keys.
1 parent 8aa5686 commit 6a07dd2

5 files changed

+132
-48
lines changed

crates/ruff_python_parser/resources/inline/err/multiple_assignment_in_case_pattern.py

+2-2
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,6 @@
55
case {1: x, 2: x}: ... # MatchMapping duplicate pattern
66
case {1: x, **x}: ... # MatchMapping duplicate in **rest
77
case Class(x, x): ... # MatchClass positional
8-
case Class(x=1, x=2): ... # MatchClass keyword
9-
case [x] | {1: x} | Class(x=1, x=2): ... # MatchOr
8+
case Class(y=x, z=x): ... # MatchClass keyword
9+
case [x] | {1: x} | Class(y=x, z=x): ... # MatchOr
1010
case x as x: ... # MatchAs
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
match 2:
2+
case Class(x=x): ...

crates/ruff_python_parser/src/semantic_errors.rs

+6-3
Original file line numberDiff line numberDiff line change
@@ -794,6 +794,10 @@ struct MultipleCaseAssignmentVisitor<'a, Ctx> {
794794

795795
impl<'a, Ctx: SemanticSyntaxContext> MultipleCaseAssignmentVisitor<'a, Ctx> {
796796
fn visit_pattern(&mut self, pattern: &'a Pattern) {
797+
// test_ok class_keyword_in_case_pattern
798+
// match 2:
799+
// case Class(x=x): ...
800+
797801
// test_err multiple_assignment_in_case_pattern
798802
// match 2:
799803
// case [y, z, y]: ... # MatchSequence
@@ -802,8 +806,8 @@ impl<'a, Ctx: SemanticSyntaxContext> MultipleCaseAssignmentVisitor<'a, Ctx> {
802806
// case {1: x, 2: x}: ... # MatchMapping duplicate pattern
803807
// case {1: x, **x}: ... # MatchMapping duplicate in **rest
804808
// case Class(x, x): ... # MatchClass positional
805-
// case Class(x=1, x=2): ... # MatchClass keyword
806-
// case [x] | {1: x} | Class(x=1, x=2): ... # MatchOr
809+
// case Class(y=x, z=x): ... # MatchClass keyword
810+
// case [x] | {1: x} | Class(y=x, z=x): ... # MatchOr
807811
// case x as x: ... # MatchAs
808812
match pattern {
809813
Pattern::MatchValue(_) | Pattern::MatchSingleton(_) => {}
@@ -830,7 +834,6 @@ impl<'a, Ctx: SemanticSyntaxContext> MultipleCaseAssignmentVisitor<'a, Ctx> {
830834
self.visit_pattern(pattern);
831835
}
832836
for keyword in &arguments.keywords {
833-
self.insert(&keyword.attr);
834837
self.visit_pattern(&keyword.pattern);
835838
}
836839
}

crates/ruff_python_parser/tests/snapshots/invalid_syntax@multiple_assignment_in_case_pattern.py.snap

+39-43
Original file line numberDiff line numberDiff line change
@@ -391,18 +391,17 @@ Module(
391391
PatternKeyword {
392392
range: 331..334,
393393
attr: Identifier {
394-
id: Name("x"),
394+
id: Name("y"),
395395
range: 331..332,
396396
},
397-
pattern: MatchValue(
398-
PatternMatchValue {
397+
pattern: MatchAs(
398+
PatternMatchAs {
399399
range: 333..334,
400-
value: NumberLiteral(
401-
ExprNumberLiteral {
400+
pattern: None,
401+
name: Some(
402+
Identifier {
403+
id: Name("x"),
402404
range: 333..334,
403-
value: Int(
404-
1,
405-
),
406405
},
407406
),
408407
},
@@ -411,18 +410,17 @@ Module(
411410
PatternKeyword {
412411
range: 336..339,
413412
attr: Identifier {
414-
id: Name("x"),
413+
id: Name("z"),
415414
range: 336..337,
416415
},
417-
pattern: MatchValue(
418-
PatternMatchValue {
416+
pattern: MatchAs(
417+
PatternMatchAs {
419418
range: 338..339,
420-
value: NumberLiteral(
421-
ExprNumberLiteral {
419+
pattern: None,
420+
name: Some(
421+
Identifier {
422+
id: Name("x"),
422423
range: 338..339,
423-
value: Int(
424-
2,
425-
),
426424
},
427425
),
428426
},
@@ -518,18 +516,17 @@ Module(
518516
PatternKeyword {
519517
range: 398..401,
520518
attr: Identifier {
521-
id: Name("x"),
519+
id: Name("y"),
522520
range: 398..399,
523521
},
524-
pattern: MatchValue(
525-
PatternMatchValue {
522+
pattern: MatchAs(
523+
PatternMatchAs {
526524
range: 400..401,
527-
value: NumberLiteral(
528-
ExprNumberLiteral {
525+
pattern: None,
526+
name: Some(
527+
Identifier {
528+
id: Name("x"),
529529
range: 400..401,
530-
value: Int(
531-
1,
532-
),
533530
},
534531
),
535532
},
@@ -538,18 +535,17 @@ Module(
538535
PatternKeyword {
539536
range: 403..406,
540537
attr: Identifier {
541-
id: Name("x"),
538+
id: Name("z"),
542539
range: 403..404,
543540
},
544-
pattern: MatchValue(
545-
PatternMatchValue {
541+
pattern: MatchAs(
542+
PatternMatchAs {
546543
range: 405..406,
547-
value: NumberLiteral(
548-
ExprNumberLiteral {
544+
pattern: None,
545+
name: Some(
546+
Identifier {
547+
id: Name("x"),
549548
range: 405..406,
550-
value: Int(
551-
2,
552-
),
553549
},
554550
),
555551
},
@@ -681,7 +677,7 @@ Module(
681677
6 | case {1: x, **x}: ... # MatchMapping duplicate in **rest
682678
| ^ Syntax Error: multiple assignments to name `x` in pattern
683679
7 | case Class(x, x): ... # MatchClass positional
684-
8 | case Class(x=1, x=2): ... # MatchClass keyword
680+
8 | case Class(y=x, z=x): ... # MatchClass keyword
685681
|
686682

687683

@@ -690,33 +686,33 @@ Module(
690686
6 | case {1: x, **x}: ... # MatchMapping duplicate in **rest
691687
7 | case Class(x, x): ... # MatchClass positional
692688
| ^ Syntax Error: multiple assignments to name `x` in pattern
693-
8 | case Class(x=1, x=2): ... # MatchClass keyword
694-
9 | case [x] | {1: x} | Class(x=1, x=2): ... # MatchOr
689+
8 | case Class(y=x, z=x): ... # MatchClass keyword
690+
9 | case [x] | {1: x} | Class(y=x, z=x): ... # MatchOr
695691
|
696692

697693

698694
|
699695
6 | case {1: x, **x}: ... # MatchMapping duplicate in **rest
700696
7 | case Class(x, x): ... # MatchClass positional
701-
8 | case Class(x=1, x=2): ... # MatchClass keyword
702-
| ^ Syntax Error: multiple assignments to name `x` in pattern
703-
9 | case [x] | {1: x} | Class(x=1, x=2): ... # MatchOr
697+
8 | case Class(y=x, z=x): ... # MatchClass keyword
698+
| ^ Syntax Error: multiple assignments to name `x` in pattern
699+
9 | case [x] | {1: x} | Class(y=x, z=x): ... # MatchOr
704700
10 | case x as x: ... # MatchAs
705701
|
706702

707703

708704
|
709705
7 | case Class(x, x): ... # MatchClass positional
710-
8 | case Class(x=1, x=2): ... # MatchClass keyword
711-
9 | case [x] | {1: x} | Class(x=1, x=2): ... # MatchOr
712-
| ^ Syntax Error: multiple assignments to name `x` in pattern
706+
8 | case Class(y=x, z=x): ... # MatchClass keyword
707+
9 | case [x] | {1: x} | Class(y=x, z=x): ... # MatchOr
708+
| ^ Syntax Error: multiple assignments to name `x` in pattern
713709
10 | case x as x: ... # MatchAs
714710
|
715711

716712

717713
|
718-
8 | case Class(x=1, x=2): ... # MatchClass keyword
719-
9 | case [x] | {1: x} | Class(x=1, x=2): ... # MatchOr
714+
8 | case Class(y=x, z=x): ... # MatchClass keyword
715+
9 | case [x] | {1: x} | Class(y=x, z=x): ... # MatchOr
720716
10 | case x as x: ... # MatchAs
721717
| ^ Syntax Error: multiple assignments to name `x` in pattern
722718
|
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,83 @@
1+
---
2+
source: crates/ruff_python_parser/tests/fixtures.rs
3+
input_file: crates/ruff_python_parser/resources/inline/ok/class_keyword_in_case_pattern.py
4+
---
5+
## AST
6+
7+
```
8+
Module(
9+
ModModule {
10+
range: 0..34,
11+
body: [
12+
Match(
13+
StmtMatch {
14+
range: 0..33,
15+
subject: NumberLiteral(
16+
ExprNumberLiteral {
17+
range: 6..7,
18+
value: Int(
19+
2,
20+
),
21+
},
22+
),
23+
cases: [
24+
MatchCase {
25+
range: 13..33,
26+
pattern: MatchClass(
27+
PatternMatchClass {
28+
range: 18..28,
29+
cls: Name(
30+
ExprName {
31+
range: 18..23,
32+
id: Name("Class"),
33+
ctx: Load,
34+
},
35+
),
36+
arguments: PatternArguments {
37+
range: 23..28,
38+
patterns: [],
39+
keywords: [
40+
PatternKeyword {
41+
range: 24..27,
42+
attr: Identifier {
43+
id: Name("x"),
44+
range: 24..25,
45+
},
46+
pattern: MatchAs(
47+
PatternMatchAs {
48+
range: 26..27,
49+
pattern: None,
50+
name: Some(
51+
Identifier {
52+
id: Name("x"),
53+
range: 26..27,
54+
},
55+
),
56+
},
57+
),
58+
},
59+
],
60+
},
61+
},
62+
),
63+
guard: None,
64+
body: [
65+
Expr(
66+
StmtExpr {
67+
range: 30..33,
68+
value: EllipsisLiteral(
69+
ExprEllipsisLiteral {
70+
range: 30..33,
71+
},
72+
),
73+
},
74+
),
75+
],
76+
},
77+
],
78+
},
79+
),
80+
],
81+
},
82+
)
83+
```

0 commit comments

Comments
 (0)