diff --git a/crates/ruff/resources/test/fixtures/pyflakes/F821_9.py b/crates/ruff/resources/test/fixtures/pyflakes/F821_9.py new file mode 100644 index 0000000000..e7490cbb51 --- /dev/null +++ b/crates/ruff/resources/test/fixtures/pyflakes/F821_9.py @@ -0,0 +1,26 @@ +"""Test: match statements.""" +from dataclasses import dataclass + + +@dataclass +class Car: + make: str + model: str + + +def f(): + match Car("Toyota", "Corolla"): + case Car("Toyota", model): + print(model) + case Car(make, "Corolla"): + print(make) + + +def f(provided: int) -> int: + match provided: + case True: + return captured # F821 + case [captured, *_]: + return captured + case captured: + return captured diff --git a/crates/ruff/resources/test/fixtures/pyflakes/F841_0.py b/crates/ruff/resources/test/fixtures/pyflakes/F841_0.py index a7989ca6fc..90c44f761c 100644 --- a/crates/ruff/resources/test/fixtures/pyflakes/F841_0.py +++ b/crates/ruff/resources/test/fixtures/pyflakes/F841_0.py @@ -119,3 +119,5 @@ def f(x: int): print("A") case [Bar.A, *_]: print("A") + case y: + pass diff --git a/crates/ruff/src/checkers/ast.rs b/crates/ruff/src/checkers/ast.rs index 482cd2960f..0ad020c53b 100644 --- a/crates/ruff/src/checkers/ast.rs +++ b/crates/ruff/src/checkers/ast.rs @@ -10,7 +10,8 @@ use rustc_hash::{FxHashMap, FxHashSet}; use rustpython_common::cformat::{CFormatError, CFormatErrorType}; use rustpython_parser::ast::{ Arg, Arguments, Comprehension, Constant, Excepthandler, ExcepthandlerKind, Expr, ExprContext, - ExprKind, KeywordData, Located, Location, Operator, Stmt, StmtKind, Suite, + ExprKind, KeywordData, Located, Location, Operator, Pattern, PatternKind, Stmt, StmtKind, + Suite, }; use rustpython_parser::parser; use smallvec::smallvec; @@ -28,7 +29,7 @@ use crate::ast::types::{ RefEquality, Scope, ScopeKind, }; use crate::ast::typing::{match_annotated_subscript, Callable, SubscriptKind}; -use crate::ast::visitor::{walk_excepthandler, Visitor}; +use crate::ast::visitor::{walk_excepthandler, walk_pattern, Visitor}; use crate::ast::{branch_detection, cast, helpers, operations, typing, visitor}; use crate::docstrings::definition::{Definition, DefinitionKind, Docstring, Documentable}; use crate::registry::{Diagnostic, Rule}; @@ -3840,6 +3841,28 @@ where } } + fn visit_pattern(&mut self, pattern: &'b Pattern) { + if let PatternKind::MatchAs { + name: Some(name), .. + } = &pattern.node + { + self.add_binding( + name, + Binding { + kind: BindingKind::Assignment, + runtime_usage: None, + synthetic_usage: None, + typing_usage: None, + range: Range::from_located(pattern), + source: Some(self.current_stmt().clone()), + context: self.execution_context(), + }, + ); + } + + walk_pattern(self, pattern); + } + fn visit_format_spec(&mut self, format_spec: &'b Expr) { match &format_spec.node { ExprKind::JoinedStr { values } => { diff --git a/crates/ruff/src/rules/pyflakes/mod.rs b/crates/ruff/src/rules/pyflakes/mod.rs index e1213742d2..be0b1cf7c1 100644 --- a/crates/ruff/src/rules/pyflakes/mod.rs +++ b/crates/ruff/src/rules/pyflakes/mod.rs @@ -101,6 +101,7 @@ mod tests { #[test_case(Rule::UndefinedName, Path::new("F821_6.py"); "F821_6")] #[test_case(Rule::UndefinedName, Path::new("F821_7.py"); "F821_7")] #[test_case(Rule::UndefinedName, Path::new("F821_8.pyi"); "F821_8")] + #[test_case(Rule::UndefinedName, Path::new("F821_9.py"); "F821_9")] #[test_case(Rule::UndefinedExport, Path::new("F822_0.py"); "F822_0")] #[test_case(Rule::UndefinedExport, Path::new("F822_1.py"); "F822_1")] #[test_case(Rule::UndefinedExport, Path::new("F822_2.py"); "F822_2")] diff --git a/crates/ruff/src/rules/pyflakes/snapshots/ruff__rules__pyflakes__tests__F821_F821_9.py.snap b/crates/ruff/src/rules/pyflakes/snapshots/ruff__rules__pyflakes__tests__F821_F821_9.py.snap new file mode 100644 index 0000000000..e25170c57e --- /dev/null +++ b/crates/ruff/src/rules/pyflakes/snapshots/ruff__rules__pyflakes__tests__F821_F821_9.py.snap @@ -0,0 +1,16 @@ +--- +source: crates/ruff/src/rules/pyflakes/mod.rs +expression: diagnostics +--- +- kind: + UndefinedName: + name: captured + location: + row: 22 + column: 19 + end_location: + row: 22 + column: 27 + fix: ~ + parent: ~ + diff --git a/crates/ruff/src/rules/pyflakes/snapshots/ruff__rules__pyflakes__tests__F841_F841_0.py.snap b/crates/ruff/src/rules/pyflakes/snapshots/ruff__rules__pyflakes__tests__F841_F841_0.py.snap index 99260b262e..d178b025fa 100644 --- a/crates/ruff/src/rules/pyflakes/snapshots/ruff__rules__pyflakes__tests__F841_F841_0.py.snap +++ b/crates/ruff/src/rules/pyflakes/snapshots/ruff__rules__pyflakes__tests__F841_F841_0.py.snap @@ -186,4 +186,15 @@ expression: diagnostics row: 115 column: 10 parent: ~ +- kind: + UnusedVariable: + name: y + location: + row: 122 + column: 13 + end_location: + row: 122 + column: 14 + fix: ~ + parent: ~ diff --git a/crates/ruff/src/rules/pyflakes/snapshots/ruff__rules__pyflakes__tests__f841_dummy_variable_rgx.snap b/crates/ruff/src/rules/pyflakes/snapshots/ruff__rules__pyflakes__tests__f841_dummy_variable_rgx.snap index 34ca884fb0..73e7beebd4 100644 --- a/crates/ruff/src/rules/pyflakes/snapshots/ruff__rules__pyflakes__tests__f841_dummy_variable_rgx.snap +++ b/crates/ruff/src/rules/pyflakes/snapshots/ruff__rules__pyflakes__tests__f841_dummy_variable_rgx.snap @@ -222,4 +222,15 @@ expression: diagnostics row: 115 column: 10 parent: ~ +- kind: + UnusedVariable: + name: y + location: + row: 122 + column: 13 + end_location: + row: 122 + column: 14 + fix: ~ + parent: ~