From 7198e531827a8376531c85080f0c593f9dd1635a Mon Sep 17 00:00:00 2001 From: Bhuminjay Soni Date: Sat, 18 Oct 2025 03:05:48 +0530 Subject: [PATCH] [syntax-errors] Alternative `match` patterns bind different names (#20682) ## Summary This PR implements semantic syntax error where alternative patterns bind different names ## Test Plan I have written inline tests as directed in #17412 --------- Signed-off-by: 11happy Co-authored-by: Brent Westbrook --- crates/ruff_linter/src/checkers/ast/mod.rs | 1 + .../err/different_match_pattern_bindings.py | 13 + .../ok/different_match_pattern_bindings.py | 6 + .../ruff_python_parser/src/semantic_errors.rs | 57 +- ...x@different_match_pattern_bindings.py.snap | 1233 +++++++++++++++++ ...id_syntax@irrefutable_case_pattern.py.snap | 9 + ...tements__match__star_pattern_usage.py.snap | 22 + ...x@different_match_pattern_bindings.py.snap | 458 ++++++ .../resources/mdtest/import/star.md | 10 +- 9 files changed, 1805 insertions(+), 4 deletions(-) create mode 100644 crates/ruff_python_parser/resources/inline/err/different_match_pattern_bindings.py create mode 100644 crates/ruff_python_parser/resources/inline/ok/different_match_pattern_bindings.py create mode 100644 crates/ruff_python_parser/tests/snapshots/invalid_syntax@different_match_pattern_bindings.py.snap create mode 100644 crates/ruff_python_parser/tests/snapshots/valid_syntax@different_match_pattern_bindings.py.snap diff --git a/crates/ruff_linter/src/checkers/ast/mod.rs b/crates/ruff_linter/src/checkers/ast/mod.rs index cdae2bb37d..c6d4a5bf3d 100644 --- a/crates/ruff_linter/src/checkers/ast/mod.rs +++ b/crates/ruff_linter/src/checkers/ast/mod.rs @@ -723,6 +723,7 @@ impl SemanticSyntaxContext for Checker<'_> { | SemanticSyntaxErrorKind::IrrefutableCasePattern(_) | SemanticSyntaxErrorKind::SingleStarredAssignment | SemanticSyntaxErrorKind::WriteToDebug(_) + | SemanticSyntaxErrorKind::DifferentMatchPatternBindings | SemanticSyntaxErrorKind::InvalidExpression(..) | SemanticSyntaxErrorKind::DuplicateMatchKey(_) | SemanticSyntaxErrorKind::DuplicateMatchClassAttribute(_) diff --git a/crates/ruff_python_parser/resources/inline/err/different_match_pattern_bindings.py b/crates/ruff_python_parser/resources/inline/err/different_match_pattern_bindings.py new file mode 100644 index 0000000000..f489953590 --- /dev/null +++ b/crates/ruff_python_parser/resources/inline/err/different_match_pattern_bindings.py @@ -0,0 +1,13 @@ +match x: + case [a] | [b]: ... + case [a] | []: ... + case (x, y) | (x,): ... + case [a, _] | [a, b]: ... + case (x, (y | z)): ... + case [a] | [b] | [c]: ... + case [] | [a]: ... + case [a] | [C(x)]: ... + case [[a] | [b]]: ... + case [C(a)] | [C(b)]: ... + case [C(D(a))] | [C(D(b))]: ... + case [(a, b)] | [(c, d)]: ... diff --git a/crates/ruff_python_parser/resources/inline/ok/different_match_pattern_bindings.py b/crates/ruff_python_parser/resources/inline/ok/different_match_pattern_bindings.py new file mode 100644 index 0000000000..b7fff1008e --- /dev/null +++ b/crates/ruff_python_parser/resources/inline/ok/different_match_pattern_bindings.py @@ -0,0 +1,6 @@ +match x: + case [a] | [a]: ... + case (x, y) | (x, y): ... + case (x, (y | y)): ... + case [a, _] | [a, _]: ... + case [a] | [C(a)]: ... diff --git a/crates/ruff_python_parser/src/semantic_errors.rs b/crates/ruff_python_parser/src/semantic_errors.rs index 59c5b30b29..ac57426915 100644 --- a/crates/ruff_python_parser/src/semantic_errors.rs +++ b/crates/ruff_python_parser/src/semantic_errors.rs @@ -1137,6 +1137,9 @@ impl Display for SemanticSyntaxError { } SemanticSyntaxErrorKind::BreakOutsideLoop => f.write_str("`break` outside loop"), SemanticSyntaxErrorKind::ContinueOutsideLoop => f.write_str("`continue` outside loop"), + SemanticSyntaxErrorKind::DifferentMatchPatternBindings => { + write!(f, "alternative patterns bind different names") + } } } } @@ -1516,6 +1519,20 @@ pub enum SemanticSyntaxErrorKind { /// Represents the use of a `continue` statement outside of a loop. ContinueOutsideLoop, + + /// Represents the use of alternative patterns in a `match` statement that bind different names. + /// + /// Python requires all alternatives in an OR pattern (`|`) to bind the same set of names. + /// Using different names results in a `SyntaxError`. + /// + /// ## Example: + /// + /// ```python + /// match 5: + /// case [x] | [y]: # error + /// ... + /// ``` + DifferentMatchPatternBindings, } #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, get_size2::GetSize)] @@ -1758,7 +1775,9 @@ impl<'a, Ctx: SemanticSyntaxContext> MatchPatternVisitor<'a, Ctx> { self.insert(name); } } - Pattern::MatchOr(ast::PatternMatchOr { patterns, .. }) => { + Pattern::MatchOr(ast::PatternMatchOr { + patterns, range, .. + }) => { // each of these patterns should be visited separately because patterns can only be // duplicated within a single arm of the or pattern. For example, the case below is // a valid pattern. @@ -1766,12 +1785,48 @@ impl<'a, Ctx: SemanticSyntaxContext> MatchPatternVisitor<'a, Ctx> { // test_ok multiple_assignment_in_case_pattern // match 2: // case Class(x) | [x] | x: ... + + let mut previous_names: Option> = None; for pattern in patterns { let mut visitor = Self { names: FxHashSet::default(), ctx: self.ctx, }; visitor.visit_pattern(pattern); + let Some(prev) = &previous_names else { + previous_names = Some(visitor.names); + continue; + }; + if prev.symmetric_difference(&visitor.names).next().is_some() { + // test_err different_match_pattern_bindings + // match x: + // case [a] | [b]: ... + // case [a] | []: ... + // case (x, y) | (x,): ... + // case [a, _] | [a, b]: ... + // case (x, (y | z)): ... + // case [a] | [b] | [c]: ... + // case [] | [a]: ... + // case [a] | [C(x)]: ... + // case [[a] | [b]]: ... + // case [C(a)] | [C(b)]: ... + // case [C(D(a))] | [C(D(b))]: ... + // case [(a, b)] | [(c, d)]: ... + + // test_ok different_match_pattern_bindings + // match x: + // case [a] | [a]: ... + // case (x, y) | (x, y): ... + // case (x, (y | y)): ... + // case [a, _] | [a, _]: ... + // case [a] | [C(a)]: ... + SemanticSyntaxChecker::add_error( + self.ctx, + SemanticSyntaxErrorKind::DifferentMatchPatternBindings, + *range, + ); + break; + } } } } diff --git a/crates/ruff_python_parser/tests/snapshots/invalid_syntax@different_match_pattern_bindings.py.snap b/crates/ruff_python_parser/tests/snapshots/invalid_syntax@different_match_pattern_bindings.py.snap new file mode 100644 index 0000000000..a2ec2aa024 --- /dev/null +++ b/crates/ruff_python_parser/tests/snapshots/invalid_syntax@different_match_pattern_bindings.py.snap @@ -0,0 +1,1233 @@ +--- +source: crates/ruff_python_parser/tests/fixtures.rs +input_file: crates/ruff_python_parser/resources/inline/err/different_match_pattern_bindings.py +--- +## AST + +``` +Module( + ModModule { + node_index: NodeIndex(None), + range: 0..347, + body: [ + Match( + StmtMatch { + node_index: NodeIndex(None), + range: 0..346, + subject: Name( + ExprName { + node_index: NodeIndex(None), + range: 6..7, + id: Name("x"), + ctx: Load, + }, + ), + cases: [ + MatchCase { + range: 13..32, + node_index: NodeIndex(None), + pattern: MatchOr( + PatternMatchOr { + range: 18..27, + node_index: NodeIndex(None), + patterns: [ + MatchSequence( + PatternMatchSequence { + range: 18..21, + node_index: NodeIndex(None), + patterns: [ + MatchAs( + PatternMatchAs { + range: 19..20, + node_index: NodeIndex(None), + pattern: None, + name: Some( + Identifier { + id: Name("a"), + range: 19..20, + node_index: NodeIndex(None), + }, + ), + }, + ), + ], + }, + ), + MatchSequence( + PatternMatchSequence { + range: 24..27, + node_index: NodeIndex(None), + patterns: [ + MatchAs( + PatternMatchAs { + range: 25..26, + node_index: NodeIndex(None), + pattern: None, + name: Some( + Identifier { + id: Name("b"), + range: 25..26, + node_index: NodeIndex(None), + }, + ), + }, + ), + ], + }, + ), + ], + }, + ), + guard: None, + body: [ + Expr( + StmtExpr { + node_index: NodeIndex(None), + range: 29..32, + value: EllipsisLiteral( + ExprEllipsisLiteral { + node_index: NodeIndex(None), + range: 29..32, + }, + ), + }, + ), + ], + }, + MatchCase { + range: 37..55, + node_index: NodeIndex(None), + pattern: MatchOr( + PatternMatchOr { + range: 42..50, + node_index: NodeIndex(None), + patterns: [ + MatchSequence( + PatternMatchSequence { + range: 42..45, + node_index: NodeIndex(None), + patterns: [ + MatchAs( + PatternMatchAs { + range: 43..44, + node_index: NodeIndex(None), + pattern: None, + name: Some( + Identifier { + id: Name("a"), + range: 43..44, + node_index: NodeIndex(None), + }, + ), + }, + ), + ], + }, + ), + MatchSequence( + PatternMatchSequence { + range: 48..50, + node_index: NodeIndex(None), + patterns: [], + }, + ), + ], + }, + ), + guard: None, + body: [ + Expr( + StmtExpr { + node_index: NodeIndex(None), + range: 52..55, + value: EllipsisLiteral( + ExprEllipsisLiteral { + node_index: NodeIndex(None), + range: 52..55, + }, + ), + }, + ), + ], + }, + MatchCase { + range: 60..83, + node_index: NodeIndex(None), + pattern: MatchOr( + PatternMatchOr { + range: 65..78, + node_index: NodeIndex(None), + patterns: [ + MatchSequence( + PatternMatchSequence { + range: 65..71, + node_index: NodeIndex(None), + patterns: [ + MatchAs( + PatternMatchAs { + range: 66..67, + node_index: NodeIndex(None), + pattern: None, + name: Some( + Identifier { + id: Name("x"), + range: 66..67, + node_index: NodeIndex(None), + }, + ), + }, + ), + MatchAs( + PatternMatchAs { + range: 69..70, + node_index: NodeIndex(None), + pattern: None, + name: Some( + Identifier { + id: Name("y"), + range: 69..70, + node_index: NodeIndex(None), + }, + ), + }, + ), + ], + }, + ), + MatchSequence( + PatternMatchSequence { + range: 74..78, + node_index: NodeIndex(None), + patterns: [ + MatchAs( + PatternMatchAs { + range: 75..76, + node_index: NodeIndex(None), + pattern: None, + name: Some( + Identifier { + id: Name("x"), + range: 75..76, + node_index: NodeIndex(None), + }, + ), + }, + ), + ], + }, + ), + ], + }, + ), + guard: None, + body: [ + Expr( + StmtExpr { + node_index: NodeIndex(None), + range: 80..83, + value: EllipsisLiteral( + ExprEllipsisLiteral { + node_index: NodeIndex(None), + range: 80..83, + }, + ), + }, + ), + ], + }, + MatchCase { + range: 88..113, + node_index: NodeIndex(None), + pattern: MatchOr( + PatternMatchOr { + range: 93..108, + node_index: NodeIndex(None), + patterns: [ + MatchSequence( + PatternMatchSequence { + range: 93..99, + node_index: NodeIndex(None), + patterns: [ + MatchAs( + PatternMatchAs { + range: 94..95, + node_index: NodeIndex(None), + pattern: None, + name: Some( + Identifier { + id: Name("a"), + range: 94..95, + node_index: NodeIndex(None), + }, + ), + }, + ), + MatchAs( + PatternMatchAs { + range: 97..98, + node_index: NodeIndex(None), + pattern: None, + name: None, + }, + ), + ], + }, + ), + MatchSequence( + PatternMatchSequence { + range: 102..108, + node_index: NodeIndex(None), + patterns: [ + MatchAs( + PatternMatchAs { + range: 103..104, + node_index: NodeIndex(None), + pattern: None, + name: Some( + Identifier { + id: Name("a"), + range: 103..104, + node_index: NodeIndex(None), + }, + ), + }, + ), + MatchAs( + PatternMatchAs { + range: 106..107, + node_index: NodeIndex(None), + pattern: None, + name: Some( + Identifier { + id: Name("b"), + range: 106..107, + node_index: NodeIndex(None), + }, + ), + }, + ), + ], + }, + ), + ], + }, + ), + guard: None, + body: [ + Expr( + StmtExpr { + node_index: NodeIndex(None), + range: 110..113, + value: EllipsisLiteral( + ExprEllipsisLiteral { + node_index: NodeIndex(None), + range: 110..113, + }, + ), + }, + ), + ], + }, + MatchCase { + range: 118..140, + node_index: NodeIndex(None), + pattern: MatchSequence( + PatternMatchSequence { + range: 123..135, + node_index: NodeIndex(None), + patterns: [ + MatchAs( + PatternMatchAs { + range: 124..125, + node_index: NodeIndex(None), + pattern: None, + name: Some( + Identifier { + id: Name("x"), + range: 124..125, + node_index: NodeIndex(None), + }, + ), + }, + ), + MatchOr( + PatternMatchOr { + range: 128..133, + node_index: NodeIndex(None), + patterns: [ + MatchAs( + PatternMatchAs { + range: 128..129, + node_index: NodeIndex(None), + pattern: None, + name: Some( + Identifier { + id: Name("y"), + range: 128..129, + node_index: NodeIndex(None), + }, + ), + }, + ), + MatchAs( + PatternMatchAs { + range: 132..133, + node_index: NodeIndex(None), + pattern: None, + name: Some( + Identifier { + id: Name("z"), + range: 132..133, + node_index: NodeIndex(None), + }, + ), + }, + ), + ], + }, + ), + ], + }, + ), + guard: None, + body: [ + Expr( + StmtExpr { + node_index: NodeIndex(None), + range: 137..140, + value: EllipsisLiteral( + ExprEllipsisLiteral { + node_index: NodeIndex(None), + range: 137..140, + }, + ), + }, + ), + ], + }, + MatchCase { + range: 145..170, + node_index: NodeIndex(None), + pattern: MatchOr( + PatternMatchOr { + range: 150..165, + node_index: NodeIndex(None), + patterns: [ + MatchSequence( + PatternMatchSequence { + range: 150..153, + node_index: NodeIndex(None), + patterns: [ + MatchAs( + PatternMatchAs { + range: 151..152, + node_index: NodeIndex(None), + pattern: None, + name: Some( + Identifier { + id: Name("a"), + range: 151..152, + node_index: NodeIndex(None), + }, + ), + }, + ), + ], + }, + ), + MatchSequence( + PatternMatchSequence { + range: 156..159, + node_index: NodeIndex(None), + patterns: [ + MatchAs( + PatternMatchAs { + range: 157..158, + node_index: NodeIndex(None), + pattern: None, + name: Some( + Identifier { + id: Name("b"), + range: 157..158, + node_index: NodeIndex(None), + }, + ), + }, + ), + ], + }, + ), + MatchSequence( + PatternMatchSequence { + range: 162..165, + node_index: NodeIndex(None), + patterns: [ + MatchAs( + PatternMatchAs { + range: 163..164, + node_index: NodeIndex(None), + pattern: None, + name: Some( + Identifier { + id: Name("c"), + range: 163..164, + node_index: NodeIndex(None), + }, + ), + }, + ), + ], + }, + ), + ], + }, + ), + guard: None, + body: [ + Expr( + StmtExpr { + node_index: NodeIndex(None), + range: 167..170, + value: EllipsisLiteral( + ExprEllipsisLiteral { + node_index: NodeIndex(None), + range: 167..170, + }, + ), + }, + ), + ], + }, + MatchCase { + range: 175..193, + node_index: NodeIndex(None), + pattern: MatchOr( + PatternMatchOr { + range: 180..188, + node_index: NodeIndex(None), + patterns: [ + MatchSequence( + PatternMatchSequence { + range: 180..182, + node_index: NodeIndex(None), + patterns: [], + }, + ), + MatchSequence( + PatternMatchSequence { + range: 185..188, + node_index: NodeIndex(None), + patterns: [ + MatchAs( + PatternMatchAs { + range: 186..187, + node_index: NodeIndex(None), + pattern: None, + name: Some( + Identifier { + id: Name("a"), + range: 186..187, + node_index: NodeIndex(None), + }, + ), + }, + ), + ], + }, + ), + ], + }, + ), + guard: None, + body: [ + Expr( + StmtExpr { + node_index: NodeIndex(None), + range: 190..193, + value: EllipsisLiteral( + ExprEllipsisLiteral { + node_index: NodeIndex(None), + range: 190..193, + }, + ), + }, + ), + ], + }, + MatchCase { + range: 198..220, + node_index: NodeIndex(None), + pattern: MatchOr( + PatternMatchOr { + range: 203..215, + node_index: NodeIndex(None), + patterns: [ + MatchSequence( + PatternMatchSequence { + range: 203..206, + node_index: NodeIndex(None), + patterns: [ + MatchAs( + PatternMatchAs { + range: 204..205, + node_index: NodeIndex(None), + pattern: None, + name: Some( + Identifier { + id: Name("a"), + range: 204..205, + node_index: NodeIndex(None), + }, + ), + }, + ), + ], + }, + ), + MatchSequence( + PatternMatchSequence { + range: 209..215, + node_index: NodeIndex(None), + patterns: [ + MatchClass( + PatternMatchClass { + range: 210..214, + node_index: NodeIndex(None), + cls: Name( + ExprName { + node_index: NodeIndex(None), + range: 210..211, + id: Name("C"), + ctx: Load, + }, + ), + arguments: PatternArguments { + range: 211..214, + node_index: NodeIndex(None), + patterns: [ + MatchAs( + PatternMatchAs { + range: 212..213, + node_index: NodeIndex(None), + pattern: None, + name: Some( + Identifier { + id: Name("x"), + range: 212..213, + node_index: NodeIndex(None), + }, + ), + }, + ), + ], + keywords: [], + }, + }, + ), + ], + }, + ), + ], + }, + ), + guard: None, + body: [ + Expr( + StmtExpr { + node_index: NodeIndex(None), + range: 217..220, + value: EllipsisLiteral( + ExprEllipsisLiteral { + node_index: NodeIndex(None), + range: 217..220, + }, + ), + }, + ), + ], + }, + MatchCase { + range: 225..246, + node_index: NodeIndex(None), + pattern: MatchSequence( + PatternMatchSequence { + range: 230..241, + node_index: NodeIndex(None), + patterns: [ + MatchOr( + PatternMatchOr { + range: 231..240, + node_index: NodeIndex(None), + patterns: [ + MatchSequence( + PatternMatchSequence { + range: 231..234, + node_index: NodeIndex(None), + patterns: [ + MatchAs( + PatternMatchAs { + range: 232..233, + node_index: NodeIndex(None), + pattern: None, + name: Some( + Identifier { + id: Name("a"), + range: 232..233, + node_index: NodeIndex(None), + }, + ), + }, + ), + ], + }, + ), + MatchSequence( + PatternMatchSequence { + range: 237..240, + node_index: NodeIndex(None), + patterns: [ + MatchAs( + PatternMatchAs { + range: 238..239, + node_index: NodeIndex(None), + pattern: None, + name: Some( + Identifier { + id: Name("b"), + range: 238..239, + node_index: NodeIndex(None), + }, + ), + }, + ), + ], + }, + ), + ], + }, + ), + ], + }, + ), + guard: None, + body: [ + Expr( + StmtExpr { + node_index: NodeIndex(None), + range: 243..246, + value: EllipsisLiteral( + ExprEllipsisLiteral { + node_index: NodeIndex(None), + range: 243..246, + }, + ), + }, + ), + ], + }, + MatchCase { + range: 251..276, + node_index: NodeIndex(None), + pattern: MatchOr( + PatternMatchOr { + range: 256..271, + node_index: NodeIndex(None), + patterns: [ + MatchSequence( + PatternMatchSequence { + range: 256..262, + node_index: NodeIndex(None), + patterns: [ + MatchClass( + PatternMatchClass { + range: 257..261, + node_index: NodeIndex(None), + cls: Name( + ExprName { + node_index: NodeIndex(None), + range: 257..258, + id: Name("C"), + ctx: Load, + }, + ), + arguments: PatternArguments { + range: 258..261, + node_index: NodeIndex(None), + patterns: [ + MatchAs( + PatternMatchAs { + range: 259..260, + node_index: NodeIndex(None), + pattern: None, + name: Some( + Identifier { + id: Name("a"), + range: 259..260, + node_index: NodeIndex(None), + }, + ), + }, + ), + ], + keywords: [], + }, + }, + ), + ], + }, + ), + MatchSequence( + PatternMatchSequence { + range: 265..271, + node_index: NodeIndex(None), + patterns: [ + MatchClass( + PatternMatchClass { + range: 266..270, + node_index: NodeIndex(None), + cls: Name( + ExprName { + node_index: NodeIndex(None), + range: 266..267, + id: Name("C"), + ctx: Load, + }, + ), + arguments: PatternArguments { + range: 267..270, + node_index: NodeIndex(None), + patterns: [ + MatchAs( + PatternMatchAs { + range: 268..269, + node_index: NodeIndex(None), + pattern: None, + name: Some( + Identifier { + id: Name("b"), + range: 268..269, + node_index: NodeIndex(None), + }, + ), + }, + ), + ], + keywords: [], + }, + }, + ), + ], + }, + ), + ], + }, + ), + guard: None, + body: [ + Expr( + StmtExpr { + node_index: NodeIndex(None), + range: 273..276, + value: EllipsisLiteral( + ExprEllipsisLiteral { + node_index: NodeIndex(None), + range: 273..276, + }, + ), + }, + ), + ], + }, + MatchCase { + range: 281..312, + node_index: NodeIndex(None), + pattern: MatchOr( + PatternMatchOr { + range: 286..307, + node_index: NodeIndex(None), + patterns: [ + MatchSequence( + PatternMatchSequence { + range: 286..295, + node_index: NodeIndex(None), + patterns: [ + MatchClass( + PatternMatchClass { + range: 287..294, + node_index: NodeIndex(None), + cls: Name( + ExprName { + node_index: NodeIndex(None), + range: 287..288, + id: Name("C"), + ctx: Load, + }, + ), + arguments: PatternArguments { + range: 288..294, + node_index: NodeIndex(None), + patterns: [ + MatchClass( + PatternMatchClass { + range: 289..293, + node_index: NodeIndex(None), + cls: Name( + ExprName { + node_index: NodeIndex(None), + range: 289..290, + id: Name("D"), + ctx: Load, + }, + ), + arguments: PatternArguments { + range: 290..293, + node_index: NodeIndex(None), + patterns: [ + MatchAs( + PatternMatchAs { + range: 291..292, + node_index: NodeIndex(None), + pattern: None, + name: Some( + Identifier { + id: Name("a"), + range: 291..292, + node_index: NodeIndex(None), + }, + ), + }, + ), + ], + keywords: [], + }, + }, + ), + ], + keywords: [], + }, + }, + ), + ], + }, + ), + MatchSequence( + PatternMatchSequence { + range: 298..307, + node_index: NodeIndex(None), + patterns: [ + MatchClass( + PatternMatchClass { + range: 299..306, + node_index: NodeIndex(None), + cls: Name( + ExprName { + node_index: NodeIndex(None), + range: 299..300, + id: Name("C"), + ctx: Load, + }, + ), + arguments: PatternArguments { + range: 300..306, + node_index: NodeIndex(None), + patterns: [ + MatchClass( + PatternMatchClass { + range: 301..305, + node_index: NodeIndex(None), + cls: Name( + ExprName { + node_index: NodeIndex(None), + range: 301..302, + id: Name("D"), + ctx: Load, + }, + ), + arguments: PatternArguments { + range: 302..305, + node_index: NodeIndex(None), + patterns: [ + MatchAs( + PatternMatchAs { + range: 303..304, + node_index: NodeIndex(None), + pattern: None, + name: Some( + Identifier { + id: Name("b"), + range: 303..304, + node_index: NodeIndex(None), + }, + ), + }, + ), + ], + keywords: [], + }, + }, + ), + ], + keywords: [], + }, + }, + ), + ], + }, + ), + ], + }, + ), + guard: None, + body: [ + Expr( + StmtExpr { + node_index: NodeIndex(None), + range: 309..312, + value: EllipsisLiteral( + ExprEllipsisLiteral { + node_index: NodeIndex(None), + range: 309..312, + }, + ), + }, + ), + ], + }, + MatchCase { + range: 317..346, + node_index: NodeIndex(None), + pattern: MatchOr( + PatternMatchOr { + range: 322..341, + node_index: NodeIndex(None), + patterns: [ + MatchSequence( + PatternMatchSequence { + range: 322..330, + node_index: NodeIndex(None), + patterns: [ + MatchSequence( + PatternMatchSequence { + range: 323..329, + node_index: NodeIndex(None), + patterns: [ + MatchAs( + PatternMatchAs { + range: 324..325, + node_index: NodeIndex(None), + pattern: None, + name: Some( + Identifier { + id: Name("a"), + range: 324..325, + node_index: NodeIndex(None), + }, + ), + }, + ), + MatchAs( + PatternMatchAs { + range: 327..328, + node_index: NodeIndex(None), + pattern: None, + name: Some( + Identifier { + id: Name("b"), + range: 327..328, + node_index: NodeIndex(None), + }, + ), + }, + ), + ], + }, + ), + ], + }, + ), + MatchSequence( + PatternMatchSequence { + range: 333..341, + node_index: NodeIndex(None), + patterns: [ + MatchSequence( + PatternMatchSequence { + range: 334..340, + node_index: NodeIndex(None), + patterns: [ + MatchAs( + PatternMatchAs { + range: 335..336, + node_index: NodeIndex(None), + pattern: None, + name: Some( + Identifier { + id: Name("c"), + range: 335..336, + node_index: NodeIndex(None), + }, + ), + }, + ), + MatchAs( + PatternMatchAs { + range: 338..339, + node_index: NodeIndex(None), + pattern: None, + name: Some( + Identifier { + id: Name("d"), + range: 338..339, + node_index: NodeIndex(None), + }, + ), + }, + ), + ], + }, + ), + ], + }, + ), + ], + }, + ), + guard: None, + body: [ + Expr( + StmtExpr { + node_index: NodeIndex(None), + range: 343..346, + value: EllipsisLiteral( + ExprEllipsisLiteral { + node_index: NodeIndex(None), + range: 343..346, + }, + ), + }, + ), + ], + }, + ], + }, + ), + ], + }, +) +``` +## Semantic Syntax Errors + + | +1 | match x: +2 | case [a] | [b]: ... + | ^^^^^^^^^ Syntax Error: alternative patterns bind different names +3 | case [a] | []: ... +4 | case (x, y) | (x,): ... + | + + + | +1 | match x: +2 | case [a] | [b]: ... +3 | case [a] | []: ... + | ^^^^^^^^ Syntax Error: alternative patterns bind different names +4 | case (x, y) | (x,): ... +5 | case [a, _] | [a, b]: ... + | + + + | +2 | case [a] | [b]: ... +3 | case [a] | []: ... +4 | case (x, y) | (x,): ... + | ^^^^^^^^^^^^^ Syntax Error: alternative patterns bind different names +5 | case [a, _] | [a, b]: ... +6 | case (x, (y | z)): ... + | + + + | +3 | case [a] | []: ... +4 | case (x, y) | (x,): ... +5 | case [a, _] | [a, b]: ... + | ^^^^^^^^^^^^^^^ Syntax Error: alternative patterns bind different names +6 | case (x, (y | z)): ... +7 | case [a] | [b] | [c]: ... + | + + + | +4 | case (x, y) | (x,): ... +5 | case [a, _] | [a, b]: ... +6 | case (x, (y | z)): ... + | ^^^^^ Syntax Error: alternative patterns bind different names +7 | case [a] | [b] | [c]: ... +8 | case [] | [a]: ... + | + + + | +5 | case [a, _] | [a, b]: ... +6 | case (x, (y | z)): ... +7 | case [a] | [b] | [c]: ... + | ^^^^^^^^^^^^^^^ Syntax Error: alternative patterns bind different names +8 | case [] | [a]: ... +9 | case [a] | [C(x)]: ... + | + + + | + 6 | case (x, (y | z)): ... + 7 | case [a] | [b] | [c]: ... + 8 | case [] | [a]: ... + | ^^^^^^^^ Syntax Error: alternative patterns bind different names + 9 | case [a] | [C(x)]: ... +10 | case [[a] | [b]]: ... + | + + + | + 7 | case [a] | [b] | [c]: ... + 8 | case [] | [a]: ... + 9 | case [a] | [C(x)]: ... + | ^^^^^^^^^^^^ Syntax Error: alternative patterns bind different names +10 | case [[a] | [b]]: ... +11 | case [C(a)] | [C(b)]: ... + | + + + | + 8 | case [] | [a]: ... + 9 | case [a] | [C(x)]: ... +10 | case [[a] | [b]]: ... + | ^^^^^^^^^ Syntax Error: alternative patterns bind different names +11 | case [C(a)] | [C(b)]: ... +12 | case [C(D(a))] | [C(D(b))]: ... + | + + + | + 9 | case [a] | [C(x)]: ... +10 | case [[a] | [b]]: ... +11 | case [C(a)] | [C(b)]: ... + | ^^^^^^^^^^^^^^^ Syntax Error: alternative patterns bind different names +12 | case [C(D(a))] | [C(D(b))]: ... +13 | case [(a, b)] | [(c, d)]: ... + | + + + | +10 | case [[a] | [b]]: ... +11 | case [C(a)] | [C(b)]: ... +12 | case [C(D(a))] | [C(D(b))]: ... + | ^^^^^^^^^^^^^^^^^^^^^ Syntax Error: alternative patterns bind different names +13 | case [(a, b)] | [(c, d)]: ... + | + + + | +11 | case [C(a)] | [C(b)]: ... +12 | case [C(D(a))] | [C(D(b))]: ... +13 | case [(a, b)] | [(c, d)]: ... + | ^^^^^^^^^^^^^^^^^^^ Syntax Error: alternative patterns bind different names + | diff --git a/crates/ruff_python_parser/tests/snapshots/invalid_syntax@irrefutable_case_pattern.py.snap b/crates/ruff_python_parser/tests/snapshots/invalid_syntax@irrefutable_case_pattern.py.snap index 678ef97091..00d25b2f52 100644 --- a/crates/ruff_python_parser/tests/snapshots/invalid_syntax@irrefutable_case_pattern.py.snap +++ b/crates/ruff_python_parser/tests/snapshots/invalid_syntax@irrefutable_case_pattern.py.snap @@ -427,3 +427,12 @@ Module( | ^^^ Syntax Error: name capture `var` makes remaining patterns unreachable 12 | case 2: ... | + + + | + 9 | case 2: ... +10 | match x: +11 | case enum.variant | var: ... # or pattern with irrefutable part + | ^^^^^^^^^^^^^^^^^^ Syntax Error: alternative patterns bind different names +12 | case 2: ... + | diff --git a/crates/ruff_python_parser/tests/snapshots/invalid_syntax@statements__match__star_pattern_usage.py.snap b/crates/ruff_python_parser/tests/snapshots/invalid_syntax@statements__match__star_pattern_usage.py.snap index 121bc2d2a5..8db3ef5b5b 100644 --- a/crates/ruff_python_parser/tests/snapshots/invalid_syntax@statements__match__star_pattern_usage.py.snap +++ b/crates/ruff_python_parser/tests/snapshots/invalid_syntax@statements__match__star_pattern_usage.py.snap @@ -613,3 +613,25 @@ Module( | ^^ Syntax Error: Star pattern cannot be used here 24 | pass | + + +## Semantic Syntax Errors + + | + 7 | case *foo: + 8 | pass + 9 | case *foo | 1: + | ^^^^^^^^ Syntax Error: alternative patterns bind different names +10 | pass +11 | case 1 | *foo: + | + + + | + 9 | case *foo | 1: +10 | pass +11 | case 1 | *foo: + | ^^^^^^^^ Syntax Error: alternative patterns bind different names +12 | pass +13 | case Foo(*_): + | diff --git a/crates/ruff_python_parser/tests/snapshots/valid_syntax@different_match_pattern_bindings.py.snap b/crates/ruff_python_parser/tests/snapshots/valid_syntax@different_match_pattern_bindings.py.snap new file mode 100644 index 0000000000..8f7ec2412e --- /dev/null +++ b/crates/ruff_python_parser/tests/snapshots/valid_syntax@different_match_pattern_bindings.py.snap @@ -0,0 +1,458 @@ +--- +source: crates/ruff_python_parser/tests/fixtures.rs +input_file: crates/ruff_python_parser/resources/inline/ok/different_match_pattern_bindings.py +--- +## AST + +``` +Module( + ModModule { + node_index: NodeIndex(None), + range: 0..147, + body: [ + Match( + StmtMatch { + node_index: NodeIndex(None), + range: 0..146, + subject: Name( + ExprName { + node_index: NodeIndex(None), + range: 6..7, + id: Name("x"), + ctx: Load, + }, + ), + cases: [ + MatchCase { + range: 13..32, + node_index: NodeIndex(None), + pattern: MatchOr( + PatternMatchOr { + range: 18..27, + node_index: NodeIndex(None), + patterns: [ + MatchSequence( + PatternMatchSequence { + range: 18..21, + node_index: NodeIndex(None), + patterns: [ + MatchAs( + PatternMatchAs { + range: 19..20, + node_index: NodeIndex(None), + pattern: None, + name: Some( + Identifier { + id: Name("a"), + range: 19..20, + node_index: NodeIndex(None), + }, + ), + }, + ), + ], + }, + ), + MatchSequence( + PatternMatchSequence { + range: 24..27, + node_index: NodeIndex(None), + patterns: [ + MatchAs( + PatternMatchAs { + range: 25..26, + node_index: NodeIndex(None), + pattern: None, + name: Some( + Identifier { + id: Name("a"), + range: 25..26, + node_index: NodeIndex(None), + }, + ), + }, + ), + ], + }, + ), + ], + }, + ), + guard: None, + body: [ + Expr( + StmtExpr { + node_index: NodeIndex(None), + range: 29..32, + value: EllipsisLiteral( + ExprEllipsisLiteral { + node_index: NodeIndex(None), + range: 29..32, + }, + ), + }, + ), + ], + }, + MatchCase { + range: 37..62, + node_index: NodeIndex(None), + pattern: MatchOr( + PatternMatchOr { + range: 42..57, + node_index: NodeIndex(None), + patterns: [ + MatchSequence( + PatternMatchSequence { + range: 42..48, + node_index: NodeIndex(None), + patterns: [ + MatchAs( + PatternMatchAs { + range: 43..44, + node_index: NodeIndex(None), + pattern: None, + name: Some( + Identifier { + id: Name("x"), + range: 43..44, + node_index: NodeIndex(None), + }, + ), + }, + ), + MatchAs( + PatternMatchAs { + range: 46..47, + node_index: NodeIndex(None), + pattern: None, + name: Some( + Identifier { + id: Name("y"), + range: 46..47, + node_index: NodeIndex(None), + }, + ), + }, + ), + ], + }, + ), + MatchSequence( + PatternMatchSequence { + range: 51..57, + node_index: NodeIndex(None), + patterns: [ + MatchAs( + PatternMatchAs { + range: 52..53, + node_index: NodeIndex(None), + pattern: None, + name: Some( + Identifier { + id: Name("x"), + range: 52..53, + node_index: NodeIndex(None), + }, + ), + }, + ), + MatchAs( + PatternMatchAs { + range: 55..56, + node_index: NodeIndex(None), + pattern: None, + name: Some( + Identifier { + id: Name("y"), + range: 55..56, + node_index: NodeIndex(None), + }, + ), + }, + ), + ], + }, + ), + ], + }, + ), + guard: None, + body: [ + Expr( + StmtExpr { + node_index: NodeIndex(None), + range: 59..62, + value: EllipsisLiteral( + ExprEllipsisLiteral { + node_index: NodeIndex(None), + range: 59..62, + }, + ), + }, + ), + ], + }, + MatchCase { + range: 67..89, + node_index: NodeIndex(None), + pattern: MatchSequence( + PatternMatchSequence { + range: 72..84, + node_index: NodeIndex(None), + patterns: [ + MatchAs( + PatternMatchAs { + range: 73..74, + node_index: NodeIndex(None), + pattern: None, + name: Some( + Identifier { + id: Name("x"), + range: 73..74, + node_index: NodeIndex(None), + }, + ), + }, + ), + MatchOr( + PatternMatchOr { + range: 77..82, + node_index: NodeIndex(None), + patterns: [ + MatchAs( + PatternMatchAs { + range: 77..78, + node_index: NodeIndex(None), + pattern: None, + name: Some( + Identifier { + id: Name("y"), + range: 77..78, + node_index: NodeIndex(None), + }, + ), + }, + ), + MatchAs( + PatternMatchAs { + range: 81..82, + node_index: NodeIndex(None), + pattern: None, + name: Some( + Identifier { + id: Name("y"), + range: 81..82, + node_index: NodeIndex(None), + }, + ), + }, + ), + ], + }, + ), + ], + }, + ), + guard: None, + body: [ + Expr( + StmtExpr { + node_index: NodeIndex(None), + range: 86..89, + value: EllipsisLiteral( + ExprEllipsisLiteral { + node_index: NodeIndex(None), + range: 86..89, + }, + ), + }, + ), + ], + }, + MatchCase { + range: 94..119, + node_index: NodeIndex(None), + pattern: MatchOr( + PatternMatchOr { + range: 99..114, + node_index: NodeIndex(None), + patterns: [ + MatchSequence( + PatternMatchSequence { + range: 99..105, + node_index: NodeIndex(None), + patterns: [ + MatchAs( + PatternMatchAs { + range: 100..101, + node_index: NodeIndex(None), + pattern: None, + name: Some( + Identifier { + id: Name("a"), + range: 100..101, + node_index: NodeIndex(None), + }, + ), + }, + ), + MatchAs( + PatternMatchAs { + range: 103..104, + node_index: NodeIndex(None), + pattern: None, + name: None, + }, + ), + ], + }, + ), + MatchSequence( + PatternMatchSequence { + range: 108..114, + node_index: NodeIndex(None), + patterns: [ + MatchAs( + PatternMatchAs { + range: 109..110, + node_index: NodeIndex(None), + pattern: None, + name: Some( + Identifier { + id: Name("a"), + range: 109..110, + node_index: NodeIndex(None), + }, + ), + }, + ), + MatchAs( + PatternMatchAs { + range: 112..113, + node_index: NodeIndex(None), + pattern: None, + name: None, + }, + ), + ], + }, + ), + ], + }, + ), + guard: None, + body: [ + Expr( + StmtExpr { + node_index: NodeIndex(None), + range: 116..119, + value: EllipsisLiteral( + ExprEllipsisLiteral { + node_index: NodeIndex(None), + range: 116..119, + }, + ), + }, + ), + ], + }, + MatchCase { + range: 124..146, + node_index: NodeIndex(None), + pattern: MatchOr( + PatternMatchOr { + range: 129..141, + node_index: NodeIndex(None), + patterns: [ + MatchSequence( + PatternMatchSequence { + range: 129..132, + node_index: NodeIndex(None), + patterns: [ + MatchAs( + PatternMatchAs { + range: 130..131, + node_index: NodeIndex(None), + pattern: None, + name: Some( + Identifier { + id: Name("a"), + range: 130..131, + node_index: NodeIndex(None), + }, + ), + }, + ), + ], + }, + ), + MatchSequence( + PatternMatchSequence { + range: 135..141, + node_index: NodeIndex(None), + patterns: [ + MatchClass( + PatternMatchClass { + range: 136..140, + node_index: NodeIndex(None), + cls: Name( + ExprName { + node_index: NodeIndex(None), + range: 136..137, + id: Name("C"), + ctx: Load, + }, + ), + arguments: PatternArguments { + range: 137..140, + node_index: NodeIndex(None), + patterns: [ + MatchAs( + PatternMatchAs { + range: 138..139, + node_index: NodeIndex(None), + pattern: None, + name: Some( + Identifier { + id: Name("a"), + range: 138..139, + node_index: NodeIndex(None), + }, + ), + }, + ), + ], + keywords: [], + }, + }, + ), + ], + }, + ), + ], + }, + ), + guard: None, + body: [ + Expr( + StmtExpr { + node_index: NodeIndex(None), + range: 143..146, + value: EllipsisLiteral( + ExprEllipsisLiteral { + node_index: NodeIndex(None), + range: 143..146, + }, + ), + }, + ), + ], + }, + ], + }, + ), + ], + }, +) +``` diff --git a/crates/ty_python_semantic/resources/mdtest/import/star.md b/crates/ty_python_semantic/resources/mdtest/import/star.md index f944ba2172..adef7f91d3 100644 --- a/crates/ty_python_semantic/resources/mdtest/import/star.md +++ b/crates/ty_python_semantic/resources/mdtest/import/star.md @@ -194,7 +194,7 @@ match get_object(): ... case I(foo=R): ... - case P | Q: + case P | Q: # error: [invalid-syntax] "alternative patterns bind different names" ... match 56: @@ -292,7 +292,9 @@ match 42: ... case [D]: ... - case E | F: # error: [invalid-syntax] "name capture `E` makes remaining patterns unreachable" + # error: [invalid-syntax] "name capture `E` makes remaining patterns unreachable" + # error: [invalid-syntax] "alternative patterns bind different names" + case E | F: ... case object(foo=G): ... @@ -360,7 +362,9 @@ match 42: ... case [D]: ... - case E | F: # error: [invalid-syntax] "name capture `E` makes remaining patterns unreachable" + # error: [invalid-syntax] "name capture `E` makes remaining patterns unreachable" + # error: [invalid-syntax] "alternative patterns bind different names" + case E | F: ... case object(foo=G): ...