mirror of
https://github.com/astral-sh/ruff.git
synced 2025-08-04 10:48:32 +00:00
[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:
parent
6a07dd227d
commit
24b1b1d52c
6 changed files with 1257 additions and 69 deletions
|
@ -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);
|
||||
|
|
|
@ -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)): ...
|
|
@ -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})]: ...
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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"`
|
||||
|
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue