From 6a07dd227d1cdcae4666767fa288ef3e09bcd7b5 Mon Sep 17 00:00:00 2001 From: Brent Westbrook <36778786+ntBre@users.noreply.github.com> Date: Thu, 3 Apr 2025 17:32:39 -0400 Subject: [PATCH] [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 "", line 2 case Class(x=x, x=x): ... ^ SyntaxError: attribute name repeated in class pattern: x >>> match x: ... case {"x": 1, "x": 2}: ... ... File "", line 2 case {"x": 1, "x": 2}: ... ^^^^^^^^^^^^^^^^ SyntaxError: mapping pattern checks duplicate key ('x') >>> match x: ... case [x, x]: ... ... File "", 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. --- .../multiple_assignment_in_case_pattern.py | 4 +- .../ok/class_keyword_in_case_pattern.py | 2 + .../ruff_python_parser/src/semantic_errors.rs | 9 +- ...ultiple_assignment_in_case_pattern.py.snap | 82 +++++++++--------- ...ntax@class_keyword_in_case_pattern.py.snap | 83 +++++++++++++++++++ 5 files changed, 132 insertions(+), 48 deletions(-) create mode 100644 crates/ruff_python_parser/resources/inline/ok/class_keyword_in_case_pattern.py create mode 100644 crates/ruff_python_parser/tests/snapshots/valid_syntax@class_keyword_in_case_pattern.py.snap diff --git a/crates/ruff_python_parser/resources/inline/err/multiple_assignment_in_case_pattern.py b/crates/ruff_python_parser/resources/inline/err/multiple_assignment_in_case_pattern.py index 45323edd66..b5e00fba2e 100644 --- a/crates/ruff_python_parser/resources/inline/err/multiple_assignment_in_case_pattern.py +++ b/crates/ruff_python_parser/resources/inline/err/multiple_assignment_in_case_pattern.py @@ -5,6 +5,6 @@ match 2: case {1: x, 2: x}: ... # MatchMapping duplicate pattern case {1: x, **x}: ... # MatchMapping duplicate in **rest case Class(x, x): ... # MatchClass positional - case Class(x=1, x=2): ... # MatchClass keyword - case [x] | {1: x} | Class(x=1, x=2): ... # MatchOr + case Class(y=x, z=x): ... # MatchClass keyword + case [x] | {1: x} | Class(y=x, z=x): ... # MatchOr case x as x: ... # MatchAs diff --git a/crates/ruff_python_parser/resources/inline/ok/class_keyword_in_case_pattern.py b/crates/ruff_python_parser/resources/inline/ok/class_keyword_in_case_pattern.py new file mode 100644 index 0000000000..2852450d58 --- /dev/null +++ b/crates/ruff_python_parser/resources/inline/ok/class_keyword_in_case_pattern.py @@ -0,0 +1,2 @@ +match 2: + case Class(x=x): ... diff --git a/crates/ruff_python_parser/src/semantic_errors.rs b/crates/ruff_python_parser/src/semantic_errors.rs index fba092d52c..2687eca82e 100644 --- a/crates/ruff_python_parser/src/semantic_errors.rs +++ b/crates/ruff_python_parser/src/semantic_errors.rs @@ -794,6 +794,10 @@ struct MultipleCaseAssignmentVisitor<'a, Ctx> { impl<'a, Ctx: SemanticSyntaxContext> MultipleCaseAssignmentVisitor<'a, Ctx> { fn visit_pattern(&mut self, pattern: &'a Pattern) { + // test_ok class_keyword_in_case_pattern + // match 2: + // case Class(x=x): ... + // test_err multiple_assignment_in_case_pattern // match 2: // case [y, z, y]: ... # MatchSequence @@ -802,8 +806,8 @@ impl<'a, Ctx: SemanticSyntaxContext> MultipleCaseAssignmentVisitor<'a, Ctx> { // case {1: x, 2: x}: ... # MatchMapping duplicate pattern // case {1: x, **x}: ... # MatchMapping duplicate in **rest // case Class(x, x): ... # MatchClass positional - // case Class(x=1, x=2): ... # MatchClass keyword - // case [x] | {1: x} | Class(x=1, x=2): ... # MatchOr + // case Class(y=x, z=x): ... # MatchClass keyword + // case [x] | {1: x} | Class(y=x, z=x): ... # MatchOr // case x as x: ... # MatchAs match pattern { Pattern::MatchValue(_) | Pattern::MatchSingleton(_) => {} @@ -830,7 +834,6 @@ impl<'a, Ctx: SemanticSyntaxContext> MultipleCaseAssignmentVisitor<'a, Ctx> { self.visit_pattern(pattern); } for keyword in &arguments.keywords { - self.insert(&keyword.attr); self.visit_pattern(&keyword.pattern); } } diff --git a/crates/ruff_python_parser/tests/snapshots/invalid_syntax@multiple_assignment_in_case_pattern.py.snap b/crates/ruff_python_parser/tests/snapshots/invalid_syntax@multiple_assignment_in_case_pattern.py.snap index 3484caa037..c47d0febba 100644 --- a/crates/ruff_python_parser/tests/snapshots/invalid_syntax@multiple_assignment_in_case_pattern.py.snap +++ b/crates/ruff_python_parser/tests/snapshots/invalid_syntax@multiple_assignment_in_case_pattern.py.snap @@ -391,18 +391,17 @@ Module( PatternKeyword { range: 331..334, attr: Identifier { - id: Name("x"), + id: Name("y"), range: 331..332, }, - pattern: MatchValue( - PatternMatchValue { + pattern: MatchAs( + PatternMatchAs { range: 333..334, - value: NumberLiteral( - ExprNumberLiteral { + pattern: None, + name: Some( + Identifier { + id: Name("x"), range: 333..334, - value: Int( - 1, - ), }, ), }, @@ -411,18 +410,17 @@ Module( PatternKeyword { range: 336..339, attr: Identifier { - id: Name("x"), + id: Name("z"), range: 336..337, }, - pattern: MatchValue( - PatternMatchValue { + pattern: MatchAs( + PatternMatchAs { range: 338..339, - value: NumberLiteral( - ExprNumberLiteral { + pattern: None, + name: Some( + Identifier { + id: Name("x"), range: 338..339, - value: Int( - 2, - ), }, ), }, @@ -518,18 +516,17 @@ Module( PatternKeyword { range: 398..401, attr: Identifier { - id: Name("x"), + id: Name("y"), range: 398..399, }, - pattern: MatchValue( - PatternMatchValue { + pattern: MatchAs( + PatternMatchAs { range: 400..401, - value: NumberLiteral( - ExprNumberLiteral { + pattern: None, + name: Some( + Identifier { + id: Name("x"), range: 400..401, - value: Int( - 1, - ), }, ), }, @@ -538,18 +535,17 @@ Module( PatternKeyword { range: 403..406, attr: Identifier { - id: Name("x"), + id: Name("z"), range: 403..404, }, - pattern: MatchValue( - PatternMatchValue { + pattern: MatchAs( + PatternMatchAs { range: 405..406, - value: NumberLiteral( - ExprNumberLiteral { + pattern: None, + name: Some( + Identifier { + id: Name("x"), range: 405..406, - value: Int( - 2, - ), }, ), }, @@ -681,7 +677,7 @@ Module( 6 | case {1: x, **x}: ... # MatchMapping duplicate in **rest | ^ Syntax Error: multiple assignments to name `x` in pattern 7 | case Class(x, x): ... # MatchClass positional -8 | case Class(x=1, x=2): ... # MatchClass keyword +8 | case Class(y=x, z=x): ... # MatchClass keyword | @@ -690,33 +686,33 @@ Module( 6 | case {1: x, **x}: ... # MatchMapping duplicate in **rest 7 | case Class(x, x): ... # MatchClass positional | ^ Syntax Error: multiple assignments to name `x` in pattern -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 | | 6 | case {1: x, **x}: ... # MatchMapping duplicate in **rest 7 | case Class(x, x): ... # MatchClass positional - 8 | case Class(x=1, x=2): ... # MatchClass keyword - | ^ Syntax Error: multiple assignments to name `x` in pattern - 9 | case [x] | {1: x} | Class(x=1, x=2): ... # MatchOr + 8 | case Class(y=x, z=x): ... # MatchClass keyword + | ^ Syntax Error: multiple assignments to name `x` in pattern + 9 | case [x] | {1: x} | Class(y=x, z=x): ... # MatchOr 10 | case x as x: ... # MatchAs | | 7 | 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 - | ^ Syntax Error: multiple assignments to name `x` in pattern + 8 | case Class(y=x, z=x): ... # MatchClass keyword + 9 | case [x] | {1: x} | Class(y=x, z=x): ... # MatchOr + | ^ Syntax Error: multiple assignments to name `x` in pattern 10 | case x as x: ... # MatchAs | | - 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 10 | case x as x: ... # MatchAs | ^ Syntax Error: multiple assignments to name `x` in pattern | diff --git a/crates/ruff_python_parser/tests/snapshots/valid_syntax@class_keyword_in_case_pattern.py.snap b/crates/ruff_python_parser/tests/snapshots/valid_syntax@class_keyword_in_case_pattern.py.snap new file mode 100644 index 0000000000..99fc05f67b --- /dev/null +++ b/crates/ruff_python_parser/tests/snapshots/valid_syntax@class_keyword_in_case_pattern.py.snap @@ -0,0 +1,83 @@ +--- +source: crates/ruff_python_parser/tests/fixtures.rs +input_file: crates/ruff_python_parser/resources/inline/ok/class_keyword_in_case_pattern.py +--- +## AST + +``` +Module( + ModModule { + range: 0..34, + body: [ + Match( + StmtMatch { + range: 0..33, + subject: NumberLiteral( + ExprNumberLiteral { + range: 6..7, + value: Int( + 2, + ), + }, + ), + cases: [ + MatchCase { + range: 13..33, + pattern: MatchClass( + PatternMatchClass { + range: 18..28, + cls: Name( + ExprName { + range: 18..23, + id: Name("Class"), + ctx: Load, + }, + ), + arguments: PatternArguments { + range: 23..28, + patterns: [], + keywords: [ + PatternKeyword { + range: 24..27, + attr: Identifier { + id: Name("x"), + range: 24..25, + }, + pattern: MatchAs( + PatternMatchAs { + range: 26..27, + pattern: None, + name: Some( + Identifier { + id: Name("x"), + range: 26..27, + }, + ), + }, + ), + }, + ], + }, + }, + ), + guard: None, + body: [ + Expr( + StmtExpr { + range: 30..33, + value: EllipsisLiteral( + ExprEllipsisLiteral { + range: 30..33, + }, + ), + }, + ), + ], + }, + ], + }, + ), + ], + }, +) +```