diff --git a/crates/ruff_linter/src/checkers/ast/mod.rs b/crates/ruff_linter/src/checkers/ast/mod.rs index cd20506aa0..d599ec0f52 100644 --- a/crates/ruff_linter/src/checkers/ast/mod.rs +++ b/crates/ruff_linter/src/checkers/ast/mod.rs @@ -574,6 +574,7 @@ impl SemanticSyntaxContext for Checker<'_> { | SemanticSyntaxErrorKind::SingleStarredAssignment | SemanticSyntaxErrorKind::WriteToDebug(_) | SemanticSyntaxErrorKind::DuplicateMatchKey(_) + | SemanticSyntaxErrorKind::DuplicateMatchClassAttribute(_) | SemanticSyntaxErrorKind::InvalidStarExpression => { if self.settings.preview.is_enabled() { self.semantic_errors.borrow_mut().push(error); diff --git a/crates/ruff_python_parser/resources/inline/err/duplicate_match_class_attr.py b/crates/ruff_python_parser/resources/inline/err/duplicate_match_class_attr.py new file mode 100644 index 0000000000..3721777993 --- /dev/null +++ b/crates/ruff_python_parser/resources/inline/err/duplicate_match_class_attr.py @@ -0,0 +1,6 @@ +match x: + case Class(x=1, x=2): ... + case [Class(x=1, x=2)]: ... + case {"x": x, "y": Foo(x=1, x=2)}: ... + case [{}, {"x": x, "y": Foo(x=1, x=2)}]: ... + case Class(x=1, d={"x": 1, "x": 2}, other=Class(x=1, x=2)): ... diff --git a/crates/ruff_python_parser/resources/inline/err/duplicate_match_key.py b/crates/ruff_python_parser/resources/inline/err/duplicate_match_key.py index 9f42626a11..f6e4b8a7f0 100644 --- a/crates/ruff_python_parser/resources/inline/err/duplicate_match_key.py +++ b/crates/ruff_python_parser/resources/inline/err/duplicate_match_key.py @@ -17,3 +17,6 @@ match x: """: 2}: ... case {"x": 1, "x": 2, "x": 3}: ... case {0: 1, "x": 1, 0: 2, "x": 2}: ... + case [{"x": 1, "x": 2}]: ... + case Foo(x=1, y={"x": 1, "x": 2}): ... + case [Foo(x=1), Foo(x=1, y={"x": 1, "x": 2})]: ... diff --git a/crates/ruff_python_parser/src/semantic_errors.rs b/crates/ruff_python_parser/src/semantic_errors.rs index 2687eca82e..1e12ce2760 100644 --- a/crates/ruff_python_parser/src/semantic_errors.rs +++ b/crates/ruff_python_parser/src/semantic_errors.rs @@ -65,8 +65,13 @@ impl SemanticSyntaxChecker { } Stmt::Match(match_stmt) => { Self::irrefutable_match_case(match_stmt, ctx); - Self::multiple_case_assignment(match_stmt, ctx); - Self::duplicate_match_mapping_keys(match_stmt, ctx); + for case in &match_stmt.cases { + let mut visitor = MatchPatternVisitor { + names: FxHashSet::default(), + ctx, + }; + visitor.visit_pattern(&case.pattern); + } } Stmt::FunctionDef(ast::StmtFunctionDef { type_params, .. }) | Stmt::ClassDef(ast::StmtClassDef { type_params, .. }) @@ -262,68 +267,6 @@ impl SemanticSyntaxChecker { } } - fn multiple_case_assignment(stmt: &ast::StmtMatch, ctx: &Ctx) { - for case in &stmt.cases { - let mut visitor = MultipleCaseAssignmentVisitor { - names: FxHashSet::default(), - ctx, - }; - visitor.visit_pattern(&case.pattern); - } - } - - fn duplicate_match_mapping_keys(stmt: &ast::StmtMatch, ctx: &Ctx) { - for mapping in stmt - .cases - .iter() - .filter_map(|case| case.pattern.as_match_mapping()) - { - let mut seen = FxHashSet::default(); - for key in mapping - .keys - .iter() - // complex numbers (`1 + 2j`) are allowed as keys but are not literals - // because they are represented as a `BinOp::Add` between a real number and - // an imaginary number - .filter(|key| key.is_literal_expr() || key.is_bin_op_expr()) - { - if !seen.insert(ComparableExpr::from(key)) { - let key_range = key.range(); - let duplicate_key = ctx.source()[key_range].to_string(); - // test_ok duplicate_match_key_attr - // match x: - // case {x.a: 1, x.a: 2}: ... - - // test_err duplicate_match_key - // match x: - // case {"x": 1, "x": 2}: ... - // case {b"x": 1, b"x": 2}: ... - // case {0: 1, 0: 2}: ... - // case {1.0: 1, 1.0: 2}: ... - // case {1.0 + 2j: 1, 1.0 + 2j: 2}: ... - // case {True: 1, True: 2}: ... - // case {None: 1, None: 2}: ... - // case { - // """x - // y - // z - // """: 1, - // """x - // y - // z - // """: 2}: ... - // case {"x": 1, "x": 2, "x": 3}: ... - // case {0: 1, "x": 1, 0: 2, "x": 2}: ... - Self::add_error( - ctx, - SemanticSyntaxErrorKind::DuplicateMatchKey(duplicate_key), - key_range, - ); - } - } - } - } - fn irrefutable_match_case(stmt: &ast::StmtMatch, ctx: &Ctx) { // test_ok irrefutable_case_pattern_at_end // match x: @@ -575,6 +518,9 @@ impl Display for SemanticSyntaxError { EscapeDefault(key) ) } + SemanticSyntaxErrorKind::DuplicateMatchClassAttribute(name) => { + write!(f, "attribute name `{name}` repeated in class pattern",) + } SemanticSyntaxErrorKind::LoadBeforeGlobalDeclaration { name, start: _ } => { write!(f, "name `{name}` is used prior to global declaration") } @@ -730,6 +676,16 @@ pub enum SemanticSyntaxErrorKind { /// [CPython grammar]: https://docs.python.org/3/reference/grammar.html DuplicateMatchKey(String), + /// Represents a duplicate attribute name in a `match` class pattern. + /// + /// ## Examples + /// + /// ```python + /// match x: + /// case Class(x=1, x=2): ... + /// ``` + DuplicateMatchClassAttribute(ast::name::Name), + /// Represents the use of a `global` variable before its `global` declaration. /// /// ## Examples @@ -787,12 +743,12 @@ impl Visitor<'_> for ReboundComprehensionVisitor<'_> { } } -struct MultipleCaseAssignmentVisitor<'a, Ctx> { +struct MatchPatternVisitor<'a, Ctx> { names: FxHashSet<&'a ast::name::Name>, ctx: &'a Ctx, } -impl<'a, Ctx: SemanticSyntaxContext> MultipleCaseAssignmentVisitor<'a, Ctx> { +impl<'a, Ctx: SemanticSyntaxContext> MatchPatternVisitor<'a, Ctx> { fn visit_pattern(&mut self, pattern: &'a Pattern) { // test_ok class_keyword_in_case_pattern // match 2: @@ -821,19 +777,87 @@ impl<'a, Ctx: SemanticSyntaxContext> MultipleCaseAssignmentVisitor<'a, Ctx> { self.visit_pattern(pattern); } } - Pattern::MatchMapping(ast::PatternMatchMapping { patterns, rest, .. }) => { + Pattern::MatchMapping(ast::PatternMatchMapping { + keys, + patterns, + rest, + .. + }) => { for pattern in patterns { self.visit_pattern(pattern); } if let Some(rest) = rest { self.insert(rest); } + + let mut seen = FxHashSet::default(); + for key in keys + .iter() + // complex numbers (`1 + 2j`) are allowed as keys but are not literals + // because they are represented as a `BinOp::Add` between a real number and + // an imaginary number + .filter(|key| key.is_literal_expr() || key.is_bin_op_expr()) + { + if !seen.insert(ComparableExpr::from(key)) { + let key_range = key.range(); + let duplicate_key = self.ctx.source()[key_range].to_string(); + // test_ok duplicate_match_key_attr + // match x: + // case {x.a: 1, x.a: 2}: ... + + // test_err duplicate_match_key + // match x: + // case {"x": 1, "x": 2}: ... + // case {b"x": 1, b"x": 2}: ... + // case {0: 1, 0: 2}: ... + // case {1.0: 1, 1.0: 2}: ... + // case {1.0 + 2j: 1, 1.0 + 2j: 2}: ... + // case {True: 1, True: 2}: ... + // case {None: 1, None: 2}: ... + // case { + // """x + // y + // z + // """: 1, + // """x + // y + // z + // """: 2}: ... + // case {"x": 1, "x": 2, "x": 3}: ... + // case {0: 1, "x": 1, 0: 2, "x": 2}: ... + // case [{"x": 1, "x": 2}]: ... + // case Foo(x=1, y={"x": 1, "x": 2}): ... + // case [Foo(x=1), Foo(x=1, y={"x": 1, "x": 2})]: ... + SemanticSyntaxChecker::add_error( + self.ctx, + SemanticSyntaxErrorKind::DuplicateMatchKey(duplicate_key), + key_range, + ); + } + } } Pattern::MatchClass(ast::PatternMatchClass { arguments, .. }) => { for pattern in &arguments.patterns { self.visit_pattern(pattern); } + let mut seen = FxHashSet::default(); for keyword in &arguments.keywords { + if !seen.insert(&keyword.attr.id) { + // test_err duplicate_match_class_attr + // match x: + // case Class(x=1, x=2): ... + // case [Class(x=1, x=2)]: ... + // case {"x": x, "y": Foo(x=1, x=2)}: ... + // case [{}, {"x": x, "y": Foo(x=1, x=2)}]: ... + // case Class(x=1, d={"x": 1, "x": 2}, other=Class(x=1, x=2)): ... + SemanticSyntaxChecker::add_error( + self.ctx, + SemanticSyntaxErrorKind::DuplicateMatchClassAttribute( + keyword.attr.id.clone(), + ), + keyword.attr.range, + ); + } self.visit_pattern(&keyword.pattern); } } diff --git a/crates/ruff_python_parser/tests/snapshots/invalid_syntax@duplicate_match_class_attr.py.snap b/crates/ruff_python_parser/tests/snapshots/invalid_syntax@duplicate_match_class_attr.py.snap new file mode 100644 index 0000000000..f4d1b42821 --- /dev/null +++ b/crates/ruff_python_parser/tests/snapshots/invalid_syntax@duplicate_match_class_attr.py.snap @@ -0,0 +1,715 @@ +--- +source: crates/ruff_python_parser/tests/fixtures.rs +input_file: crates/ruff_python_parser/resources/inline/err/duplicate_match_class_attr.py +--- +## AST + +``` +Module( + ModModule { + range: 0..231, + body: [ + Match( + StmtMatch { + range: 0..230, + subject: Name( + ExprName { + range: 6..7, + id: Name("x"), + ctx: Load, + }, + ), + cases: [ + MatchCase { + range: 13..38, + pattern: MatchClass( + PatternMatchClass { + range: 18..33, + cls: Name( + ExprName { + range: 18..23, + id: Name("Class"), + ctx: Load, + }, + ), + arguments: PatternArguments { + range: 23..33, + patterns: [], + keywords: [ + PatternKeyword { + range: 24..27, + attr: Identifier { + id: Name("x"), + range: 24..25, + }, + pattern: MatchValue( + PatternMatchValue { + range: 26..27, + value: NumberLiteral( + ExprNumberLiteral { + range: 26..27, + value: Int( + 1, + ), + }, + ), + }, + ), + }, + PatternKeyword { + range: 29..32, + attr: Identifier { + id: Name("x"), + range: 29..30, + }, + pattern: MatchValue( + PatternMatchValue { + range: 31..32, + value: NumberLiteral( + ExprNumberLiteral { + range: 31..32, + value: Int( + 2, + ), + }, + ), + }, + ), + }, + ], + }, + }, + ), + guard: None, + body: [ + Expr( + StmtExpr { + range: 35..38, + value: EllipsisLiteral( + ExprEllipsisLiteral { + range: 35..38, + }, + ), + }, + ), + ], + }, + MatchCase { + range: 43..70, + pattern: MatchSequence( + PatternMatchSequence { + range: 48..65, + patterns: [ + MatchClass( + PatternMatchClass { + range: 49..64, + cls: Name( + ExprName { + range: 49..54, + id: Name("Class"), + ctx: Load, + }, + ), + arguments: PatternArguments { + range: 54..64, + patterns: [], + keywords: [ + PatternKeyword { + range: 55..58, + attr: Identifier { + id: Name("x"), + range: 55..56, + }, + pattern: MatchValue( + PatternMatchValue { + range: 57..58, + value: NumberLiteral( + ExprNumberLiteral { + range: 57..58, + value: Int( + 1, + ), + }, + ), + }, + ), + }, + PatternKeyword { + range: 60..63, + attr: Identifier { + id: Name("x"), + range: 60..61, + }, + pattern: MatchValue( + PatternMatchValue { + range: 62..63, + value: NumberLiteral( + ExprNumberLiteral { + range: 62..63, + value: Int( + 2, + ), + }, + ), + }, + ), + }, + ], + }, + }, + ), + ], + }, + ), + guard: None, + body: [ + Expr( + StmtExpr { + range: 67..70, + value: EllipsisLiteral( + ExprEllipsisLiteral { + range: 67..70, + }, + ), + }, + ), + ], + }, + MatchCase { + range: 75..113, + pattern: MatchMapping( + PatternMatchMapping { + range: 80..108, + keys: [ + StringLiteral( + ExprStringLiteral { + range: 81..84, + value: StringLiteralValue { + inner: Single( + StringLiteral { + range: 81..84, + value: "x", + flags: StringLiteralFlags { + quote_style: Double, + prefix: Empty, + triple_quoted: false, + }, + }, + ), + }, + }, + ), + StringLiteral( + ExprStringLiteral { + range: 89..92, + value: StringLiteralValue { + inner: Single( + StringLiteral { + range: 89..92, + value: "y", + flags: StringLiteralFlags { + quote_style: Double, + prefix: Empty, + triple_quoted: false, + }, + }, + ), + }, + }, + ), + ], + patterns: [ + MatchAs( + PatternMatchAs { + range: 86..87, + pattern: None, + name: Some( + Identifier { + id: Name("x"), + range: 86..87, + }, + ), + }, + ), + MatchClass( + PatternMatchClass { + range: 94..107, + cls: Name( + ExprName { + range: 94..97, + id: Name("Foo"), + ctx: Load, + }, + ), + arguments: PatternArguments { + range: 97..107, + patterns: [], + keywords: [ + PatternKeyword { + range: 98..101, + attr: Identifier { + id: Name("x"), + range: 98..99, + }, + pattern: MatchValue( + PatternMatchValue { + range: 100..101, + value: NumberLiteral( + ExprNumberLiteral { + range: 100..101, + value: Int( + 1, + ), + }, + ), + }, + ), + }, + PatternKeyword { + range: 103..106, + attr: Identifier { + id: Name("x"), + range: 103..104, + }, + pattern: MatchValue( + PatternMatchValue { + range: 105..106, + value: NumberLiteral( + ExprNumberLiteral { + range: 105..106, + value: Int( + 2, + ), + }, + ), + }, + ), + }, + ], + }, + }, + ), + ], + rest: None, + }, + ), + guard: None, + body: [ + Expr( + StmtExpr { + range: 110..113, + value: EllipsisLiteral( + ExprEllipsisLiteral { + range: 110..113, + }, + ), + }, + ), + ], + }, + MatchCase { + range: 118..162, + pattern: MatchSequence( + PatternMatchSequence { + range: 123..157, + patterns: [ + MatchMapping( + PatternMatchMapping { + range: 124..126, + keys: [], + patterns: [], + rest: None, + }, + ), + MatchMapping( + PatternMatchMapping { + range: 128..156, + keys: [ + StringLiteral( + ExprStringLiteral { + range: 129..132, + value: StringLiteralValue { + inner: Single( + StringLiteral { + range: 129..132, + value: "x", + flags: StringLiteralFlags { + quote_style: Double, + prefix: Empty, + triple_quoted: false, + }, + }, + ), + }, + }, + ), + StringLiteral( + ExprStringLiteral { + range: 137..140, + value: StringLiteralValue { + inner: Single( + StringLiteral { + range: 137..140, + value: "y", + flags: StringLiteralFlags { + quote_style: Double, + prefix: Empty, + triple_quoted: false, + }, + }, + ), + }, + }, + ), + ], + patterns: [ + MatchAs( + PatternMatchAs { + range: 134..135, + pattern: None, + name: Some( + Identifier { + id: Name("x"), + range: 134..135, + }, + ), + }, + ), + MatchClass( + PatternMatchClass { + range: 142..155, + cls: Name( + ExprName { + range: 142..145, + id: Name("Foo"), + ctx: Load, + }, + ), + arguments: PatternArguments { + range: 145..155, + patterns: [], + keywords: [ + PatternKeyword { + range: 146..149, + attr: Identifier { + id: Name("x"), + range: 146..147, + }, + pattern: MatchValue( + PatternMatchValue { + range: 148..149, + value: NumberLiteral( + ExprNumberLiteral { + range: 148..149, + value: Int( + 1, + ), + }, + ), + }, + ), + }, + PatternKeyword { + range: 151..154, + attr: Identifier { + id: Name("x"), + range: 151..152, + }, + pattern: MatchValue( + PatternMatchValue { + range: 153..154, + value: NumberLiteral( + ExprNumberLiteral { + range: 153..154, + value: Int( + 2, + ), + }, + ), + }, + ), + }, + ], + }, + }, + ), + ], + rest: None, + }, + ), + ], + }, + ), + guard: None, + body: [ + Expr( + StmtExpr { + range: 159..162, + value: EllipsisLiteral( + ExprEllipsisLiteral { + range: 159..162, + }, + ), + }, + ), + ], + }, + MatchCase { + range: 167..230, + pattern: MatchClass( + PatternMatchClass { + range: 172..225, + cls: Name( + ExprName { + range: 172..177, + id: Name("Class"), + ctx: Load, + }, + ), + arguments: PatternArguments { + range: 177..225, + patterns: [], + keywords: [ + PatternKeyword { + range: 178..181, + attr: Identifier { + id: Name("x"), + range: 178..179, + }, + pattern: MatchValue( + PatternMatchValue { + range: 180..181, + value: NumberLiteral( + ExprNumberLiteral { + range: 180..181, + value: Int( + 1, + ), + }, + ), + }, + ), + }, + PatternKeyword { + range: 183..201, + attr: Identifier { + id: Name("d"), + range: 183..184, + }, + pattern: MatchMapping( + PatternMatchMapping { + range: 185..201, + keys: [ + StringLiteral( + ExprStringLiteral { + range: 186..189, + value: StringLiteralValue { + inner: Single( + StringLiteral { + range: 186..189, + value: "x", + flags: StringLiteralFlags { + quote_style: Double, + prefix: Empty, + triple_quoted: false, + }, + }, + ), + }, + }, + ), + StringLiteral( + ExprStringLiteral { + range: 194..197, + value: StringLiteralValue { + inner: Single( + StringLiteral { + range: 194..197, + value: "x", + flags: StringLiteralFlags { + quote_style: Double, + prefix: Empty, + triple_quoted: false, + }, + }, + ), + }, + }, + ), + ], + patterns: [ + MatchValue( + PatternMatchValue { + range: 191..192, + value: NumberLiteral( + ExprNumberLiteral { + range: 191..192, + value: Int( + 1, + ), + }, + ), + }, + ), + MatchValue( + PatternMatchValue { + range: 199..200, + value: NumberLiteral( + ExprNumberLiteral { + range: 199..200, + value: Int( + 2, + ), + }, + ), + }, + ), + ], + rest: None, + }, + ), + }, + PatternKeyword { + range: 203..224, + attr: Identifier { + id: Name("other"), + range: 203..208, + }, + pattern: MatchClass( + PatternMatchClass { + range: 209..224, + cls: Name( + ExprName { + range: 209..214, + id: Name("Class"), + ctx: Load, + }, + ), + arguments: PatternArguments { + range: 214..224, + patterns: [], + keywords: [ + PatternKeyword { + range: 215..218, + attr: Identifier { + id: Name("x"), + range: 215..216, + }, + pattern: MatchValue( + PatternMatchValue { + range: 217..218, + value: NumberLiteral( + ExprNumberLiteral { + range: 217..218, + value: Int( + 1, + ), + }, + ), + }, + ), + }, + PatternKeyword { + range: 220..223, + attr: Identifier { + id: Name("x"), + range: 220..221, + }, + pattern: MatchValue( + PatternMatchValue { + range: 222..223, + value: NumberLiteral( + ExprNumberLiteral { + range: 222..223, + value: Int( + 2, + ), + }, + ), + }, + ), + }, + ], + }, + }, + ), + }, + ], + }, + }, + ), + guard: None, + body: [ + Expr( + StmtExpr { + range: 227..230, + value: EllipsisLiteral( + ExprEllipsisLiteral { + range: 227..230, + }, + ), + }, + ), + ], + }, + ], + }, + ), + ], + }, +) +``` +## Semantic Syntax Errors + + | +1 | match x: +2 | case Class(x=1, x=2): ... + | ^ Syntax Error: attribute name `x` repeated in class pattern +3 | case [Class(x=1, x=2)]: ... +4 | case {"x": x, "y": Foo(x=1, x=2)}: ... + | + + + | +1 | match x: +2 | case Class(x=1, x=2): ... +3 | case [Class(x=1, x=2)]: ... + | ^ Syntax Error: attribute name `x` repeated in class pattern +4 | case {"x": x, "y": Foo(x=1, x=2)}: ... +5 | case [{}, {"x": x, "y": Foo(x=1, x=2)}]: ... + | + + + | +2 | case Class(x=1, x=2): ... +3 | case [Class(x=1, x=2)]: ... +4 | case {"x": x, "y": Foo(x=1, x=2)}: ... + | ^ Syntax Error: attribute name `x` repeated in class pattern +5 | case [{}, {"x": x, "y": Foo(x=1, x=2)}]: ... +6 | case Class(x=1, d={"x": 1, "x": 2}, other=Class(x=1, x=2)): ... + | + + + | +3 | case [Class(x=1, x=2)]: ... +4 | case {"x": x, "y": Foo(x=1, x=2)}: ... +5 | case [{}, {"x": x, "y": Foo(x=1, x=2)}]: ... + | ^ Syntax Error: attribute name `x` repeated in class pattern +6 | case Class(x=1, d={"x": 1, "x": 2}, other=Class(x=1, x=2)): ... + | + + + | +4 | case {"x": x, "y": Foo(x=1, x=2)}: ... +5 | case [{}, {"x": x, "y": Foo(x=1, x=2)}]: ... +6 | case Class(x=1, d={"x": 1, "x": 2}, other=Class(x=1, x=2)): ... + | ^^^ Syntax Error: mapping pattern checks duplicate key `"x"` + | + + + | +4 | case {"x": x, "y": Foo(x=1, x=2)}: ... +5 | case [{}, {"x": x, "y": Foo(x=1, x=2)}]: ... +6 | case Class(x=1, d={"x": 1, "x": 2}, other=Class(x=1, x=2)): ... + | ^ Syntax Error: attribute name `x` repeated in class pattern + | diff --git a/crates/ruff_python_parser/tests/snapshots/invalid_syntax@duplicate_match_key.py.snap b/crates/ruff_python_parser/tests/snapshots/invalid_syntax@duplicate_match_key.py.snap index 9f41893b60..4d4dbc2eca 100644 --- a/crates/ruff_python_parser/tests/snapshots/invalid_syntax@duplicate_match_key.py.snap +++ b/crates/ruff_python_parser/tests/snapshots/invalid_syntax@duplicate_match_key.py.snap @@ -7,11 +7,11 @@ input_file: crates/ruff_python_parser/resources/inline/err/duplicate_match_key.p ``` Module( ModModule { - range: 0..402, + range: 0..533, body: [ Match( StmtMatch { - range: 0..401, + range: 0..532, subject: Name( ExprName { range: 6..7, @@ -897,6 +897,412 @@ Module( ), ], }, + MatchCase { + range: 406..434, + pattern: MatchSequence( + PatternMatchSequence { + range: 411..429, + patterns: [ + MatchMapping( + PatternMatchMapping { + range: 412..428, + keys: [ + StringLiteral( + ExprStringLiteral { + range: 413..416, + value: StringLiteralValue { + inner: Single( + StringLiteral { + range: 413..416, + value: "x", + flags: StringLiteralFlags { + quote_style: Double, + prefix: Empty, + triple_quoted: false, + }, + }, + ), + }, + }, + ), + StringLiteral( + ExprStringLiteral { + range: 421..424, + value: StringLiteralValue { + inner: Single( + StringLiteral { + range: 421..424, + value: "x", + flags: StringLiteralFlags { + quote_style: Double, + prefix: Empty, + triple_quoted: false, + }, + }, + ), + }, + }, + ), + ], + patterns: [ + MatchValue( + PatternMatchValue { + range: 418..419, + value: NumberLiteral( + ExprNumberLiteral { + range: 418..419, + value: Int( + 1, + ), + }, + ), + }, + ), + MatchValue( + PatternMatchValue { + range: 426..427, + value: NumberLiteral( + ExprNumberLiteral { + range: 426..427, + value: Int( + 2, + ), + }, + ), + }, + ), + ], + rest: None, + }, + ), + ], + }, + ), + guard: None, + body: [ + Expr( + StmtExpr { + range: 431..434, + value: EllipsisLiteral( + ExprEllipsisLiteral { + range: 431..434, + }, + ), + }, + ), + ], + }, + MatchCase { + range: 439..477, + pattern: MatchClass( + PatternMatchClass { + range: 444..472, + cls: Name( + ExprName { + range: 444..447, + id: Name("Foo"), + ctx: Load, + }, + ), + arguments: PatternArguments { + range: 447..472, + patterns: [], + keywords: [ + PatternKeyword { + range: 448..451, + attr: Identifier { + id: Name("x"), + range: 448..449, + }, + pattern: MatchValue( + PatternMatchValue { + range: 450..451, + value: NumberLiteral( + ExprNumberLiteral { + range: 450..451, + value: Int( + 1, + ), + }, + ), + }, + ), + }, + PatternKeyword { + range: 453..471, + attr: Identifier { + id: Name("y"), + range: 453..454, + }, + pattern: MatchMapping( + PatternMatchMapping { + range: 455..471, + keys: [ + StringLiteral( + ExprStringLiteral { + range: 456..459, + value: StringLiteralValue { + inner: Single( + StringLiteral { + range: 456..459, + value: "x", + flags: StringLiteralFlags { + quote_style: Double, + prefix: Empty, + triple_quoted: false, + }, + }, + ), + }, + }, + ), + StringLiteral( + ExprStringLiteral { + range: 464..467, + value: StringLiteralValue { + inner: Single( + StringLiteral { + range: 464..467, + value: "x", + flags: StringLiteralFlags { + quote_style: Double, + prefix: Empty, + triple_quoted: false, + }, + }, + ), + }, + }, + ), + ], + patterns: [ + MatchValue( + PatternMatchValue { + range: 461..462, + value: NumberLiteral( + ExprNumberLiteral { + range: 461..462, + value: Int( + 1, + ), + }, + ), + }, + ), + MatchValue( + PatternMatchValue { + range: 469..470, + value: NumberLiteral( + ExprNumberLiteral { + range: 469..470, + value: Int( + 2, + ), + }, + ), + }, + ), + ], + rest: None, + }, + ), + }, + ], + }, + }, + ), + guard: None, + body: [ + Expr( + StmtExpr { + range: 474..477, + value: EllipsisLiteral( + ExprEllipsisLiteral { + range: 474..477, + }, + ), + }, + ), + ], + }, + MatchCase { + range: 482..532, + pattern: MatchSequence( + PatternMatchSequence { + range: 487..527, + patterns: [ + MatchClass( + PatternMatchClass { + range: 488..496, + cls: Name( + ExprName { + range: 488..491, + id: Name("Foo"), + ctx: Load, + }, + ), + arguments: PatternArguments { + range: 491..496, + patterns: [], + keywords: [ + PatternKeyword { + range: 492..495, + attr: Identifier { + id: Name("x"), + range: 492..493, + }, + pattern: MatchValue( + PatternMatchValue { + range: 494..495, + value: NumberLiteral( + ExprNumberLiteral { + range: 494..495, + value: Int( + 1, + ), + }, + ), + }, + ), + }, + ], + }, + }, + ), + MatchClass( + PatternMatchClass { + range: 498..526, + cls: Name( + ExprName { + range: 498..501, + id: Name("Foo"), + ctx: Load, + }, + ), + arguments: PatternArguments { + range: 501..526, + patterns: [], + keywords: [ + PatternKeyword { + range: 502..505, + attr: Identifier { + id: Name("x"), + range: 502..503, + }, + pattern: MatchValue( + PatternMatchValue { + range: 504..505, + value: NumberLiteral( + ExprNumberLiteral { + range: 504..505, + value: Int( + 1, + ), + }, + ), + }, + ), + }, + PatternKeyword { + range: 507..525, + attr: Identifier { + id: Name("y"), + range: 507..508, + }, + pattern: MatchMapping( + PatternMatchMapping { + range: 509..525, + keys: [ + StringLiteral( + ExprStringLiteral { + range: 510..513, + value: StringLiteralValue { + inner: Single( + StringLiteral { + range: 510..513, + value: "x", + flags: StringLiteralFlags { + quote_style: Double, + prefix: Empty, + triple_quoted: false, + }, + }, + ), + }, + }, + ), + StringLiteral( + ExprStringLiteral { + range: 518..521, + value: StringLiteralValue { + inner: Single( + StringLiteral { + range: 518..521, + value: "x", + flags: StringLiteralFlags { + quote_style: Double, + prefix: Empty, + triple_quoted: false, + }, + }, + ), + }, + }, + ), + ], + patterns: [ + MatchValue( + PatternMatchValue { + range: 515..516, + value: NumberLiteral( + ExprNumberLiteral { + range: 515..516, + value: Int( + 1, + ), + }, + ), + }, + ), + MatchValue( + PatternMatchValue { + range: 523..524, + value: NumberLiteral( + ExprNumberLiteral { + range: 523..524, + value: Int( + 2, + ), + }, + ), + }, + ), + ], + rest: None, + }, + ), + }, + ], + }, + }, + ), + ], + }, + ), + guard: None, + body: [ + Expr( + StmtExpr { + range: 529..532, + value: EllipsisLiteral( + ExprEllipsisLiteral { + range: 529..532, + }, + ), + }, + ), + ], + }, ], }, ), @@ -994,6 +1400,7 @@ Module( 18 | case {"x": 1, "x": 2, "x": 3}: ... | ^^^ Syntax Error: mapping pattern checks duplicate key `"x"` 19 | case {0: 1, "x": 1, 0: 2, "x": 2}: ... +20 | case [{"x": 1, "x": 2}]: ... | @@ -1003,6 +1410,7 @@ Module( 18 | case {"x": 1, "x": 2, "x": 3}: ... | ^^^ Syntax Error: mapping pattern checks duplicate key `"x"` 19 | case {0: 1, "x": 1, 0: 2, "x": 2}: ... +20 | case [{"x": 1, "x": 2}]: ... | @@ -1011,6 +1419,8 @@ Module( 18 | case {"x": 1, "x": 2, "x": 3}: ... 19 | case {0: 1, "x": 1, 0: 2, "x": 2}: ... | ^ Syntax Error: mapping pattern checks duplicate key `0` +20 | case [{"x": 1, "x": 2}]: ... +21 | case Foo(x=1, y={"x": 1, "x": 2}): ... | @@ -1019,4 +1429,33 @@ Module( 18 | case {"x": 1, "x": 2, "x": 3}: ... 19 | case {0: 1, "x": 1, 0: 2, "x": 2}: ... | ^^^ Syntax Error: mapping pattern checks duplicate key `"x"` +20 | case [{"x": 1, "x": 2}]: ... +21 | case Foo(x=1, y={"x": 1, "x": 2}): ... + | + + + | +18 | case {"x": 1, "x": 2, "x": 3}: ... +19 | case {0: 1, "x": 1, 0: 2, "x": 2}: ... +20 | case [{"x": 1, "x": 2}]: ... + | ^^^ Syntax Error: mapping pattern checks duplicate key `"x"` +21 | case Foo(x=1, y={"x": 1, "x": 2}): ... +22 | case [Foo(x=1), Foo(x=1, y={"x": 1, "x": 2})]: ... + | + + + | +19 | case {0: 1, "x": 1, 0: 2, "x": 2}: ... +20 | case [{"x": 1, "x": 2}]: ... +21 | case Foo(x=1, y={"x": 1, "x": 2}): ... + | ^^^ Syntax Error: mapping pattern checks duplicate key `"x"` +22 | case [Foo(x=1), Foo(x=1, y={"x": 1, "x": 2})]: ... + | + + + | +20 | case [{"x": 1, "x": 2}]: ... +21 | case Foo(x=1, y={"x": 1, "x": 2}): ... +22 | case [Foo(x=1), Foo(x=1, y={"x": 1, "x": 2})]: ... + | ^^^ Syntax Error: mapping pattern checks duplicate key `"x"` |