[syntax-errors] Duplicate attributes in match class pattern (#17186)

Summary
--

Detects duplicate attributes in a `match` class pattern:

```python
match x:
    case Class(x=1, x=2): ...
```

which are more analogous to the similar check for mapping patterns than
to the
multiple assignments rule.

I also realized that both this and the mapping check would only work on
top-level patterns, despite the possibility that they can be nested
inside other
patterns:

```python
match x:
    case [{"x": 1, "x": 2}]: ...  # false negative in the old version
```

and moved these checks into the recursive pattern visitor instead.

I also tidied up some of the names like the `multiple_case_assignment`
function
and the `MultipleCaseAssignmentVisitor`, which are now doing more than
checking
for multiple assignments.

Test Plan
--

New inline tests for both classes and mappings.
This commit is contained in:
Brent Westbrook 2025-04-03 17:55:37 -04:00 committed by GitHub
parent 6a07dd227d
commit 24b1b1d52c
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
6 changed files with 1257 additions and 69 deletions

View file

@ -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);

View file

@ -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)): ...

View file

@ -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})]: ...

View file

@ -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<Ctx: SemanticSyntaxContext>(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<Ctx: SemanticSyntaxContext>(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<Ctx: SemanticSyntaxContext>(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);
}
}

View file

@ -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
|

View file

@ -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"`
|