diff --git a/crates/ruff_linter/src/checkers/ast/mod.rs b/crates/ruff_linter/src/checkers/ast/mod.rs index f56966e669..02caf6eea3 100644 --- a/crates/ruff_linter/src/checkers/ast/mod.rs +++ b/crates/ruff_linter/src/checkers/ast/mod.rs @@ -1615,38 +1615,23 @@ impl<'a> Checker<'a> { fn handle_node_store(&mut self, id: &'a str, expr: &Expr) { let parent = self.semantic.current_statement(); + let mut flags = BindingFlags::empty(); + if helpers::is_unpacking_assignment(parent, expr) { + flags.insert(BindingFlags::UNPACKED_ASSIGNMENT); + } + // Match the left-hand side of an annotated assignment, like `x` in `x: int`. if matches!( parent, Stmt::AnnAssign(ast::StmtAnnAssign { value: None, .. }) ) && !self.semantic.in_annotation() { - self.add_binding( - id, - expr.range(), - BindingKind::Annotation, - BindingFlags::empty(), - ); + self.add_binding(id, expr.range(), BindingKind::Annotation, flags); return; } if parent.is_for_stmt() { - self.add_binding( - id, - expr.range(), - BindingKind::LoopVar, - BindingFlags::empty(), - ); - return; - } - - if helpers::is_unpacking_assignment(parent, expr) { - self.add_binding( - id, - expr.range(), - BindingKind::UnpackedAssignment, - BindingFlags::empty(), - ); + self.add_binding(id, expr.range(), BindingKind::LoopVar, flags); return; } @@ -1681,7 +1666,6 @@ impl<'a> Checker<'a> { let (all_names, all_flags) = extract_all_names(parent, |name| self.semantic.is_builtin(name)); - let mut flags = BindingFlags::empty(); if all_flags.intersects(DunderAllFlags::INVALID_OBJECT) { flags |= BindingFlags::INVALID_ALL_OBJECT; } @@ -1705,21 +1689,11 @@ impl<'a> Checker<'a> { .current_expressions() .any(Expr::is_named_expr_expr) { - self.add_binding( - id, - expr.range(), - BindingKind::NamedExprAssignment, - BindingFlags::empty(), - ); + self.add_binding(id, expr.range(), BindingKind::NamedExprAssignment, flags); return; } - self.add_binding( - id, - expr.range(), - BindingKind::Assignment, - BindingFlags::empty(), - ); + self.add_binding(id, expr.range(), BindingKind::Assignment, flags); } fn handle_node_delete(&mut self, expr: &'a Expr) { diff --git a/crates/ruff_linter/src/renamer.rs b/crates/ruff_linter/src/renamer.rs index be17627829..ca8b133dec 100644 --- a/crates/ruff_linter/src/renamer.rs +++ b/crates/ruff_linter/src/renamer.rs @@ -245,7 +245,6 @@ impl Renamer { | BindingKind::Argument | BindingKind::TypeParam | BindingKind::NamedExprAssignment - | BindingKind::UnpackedAssignment | BindingKind::Assignment | BindingKind::BoundException | BindingKind::LoopVar diff --git a/crates/ruff_linter/src/rules/pandas_vet/helpers.rs b/crates/ruff_linter/src/rules/pandas_vet/helpers.rs index 4dc880e039..85ba34ec77 100644 --- a/crates/ruff_linter/src/rules/pandas_vet/helpers.rs +++ b/crates/ruff_linter/src/rules/pandas_vet/helpers.rs @@ -46,7 +46,6 @@ pub(super) fn test_expression(expr: &Expr, semantic: &SemanticModel) -> Resoluti BindingKind::Annotation | BindingKind::Assignment | BindingKind::NamedExprAssignment - | BindingKind::UnpackedAssignment | BindingKind::LoopVar | BindingKind::Global | BindingKind::Nonlocal(_) => Resolution::RelevantLocal, diff --git a/crates/ruff_linter/src/rules/pyflakes/rules/unused_variable.rs b/crates/ruff_linter/src/rules/pyflakes/rules/unused_variable.rs index 2bf9ee3721..156efde80e 100644 --- a/crates/ruff_linter/src/rules/pyflakes/rules/unused_variable.rs +++ b/crates/ruff_linter/src/rules/pyflakes/rules/unused_variable.rs @@ -322,10 +322,9 @@ pub(crate) fn unused_variable(checker: &Checker, scope: &Scope, diagnostics: &mu .bindings() .map(|(name, binding_id)| (name, checker.semantic().binding(binding_id))) .filter_map(|(name, binding)| { - if (binding.kind.is_assignment() - || binding.kind.is_named_expr_assignment() - || (matches!(checker.settings.preview, PreviewMode::Enabled) - && binding.kind.is_unpacked_assignment())) + if (binding.kind.is_assignment() || binding.kind.is_named_expr_assignment()) + && (!binding.is_unpacked_assignment() + || matches!(checker.settings.preview, PreviewMode::Enabled)) && !binding.is_nonlocal() && !binding.is_global() && !binding.is_used() diff --git a/crates/ruff_linter/src/rules/pylint/rules/non_ascii_name.rs b/crates/ruff_linter/src/rules/pylint/rules/non_ascii_name.rs index c3326b9f5f..0312bab0db 100644 --- a/crates/ruff_linter/src/rules/pylint/rules/non_ascii_name.rs +++ b/crates/ruff_linter/src/rules/pylint/rules/non_ascii_name.rs @@ -49,7 +49,6 @@ pub(crate) fn non_ascii_name(binding: &Binding, locator: &Locator) -> Option Kind::Annotation, BindingKind::Argument => Kind::Argument, BindingKind::NamedExprAssignment => Kind::NamedExprAssignment, - BindingKind::UnpackedAssignment => Kind::UnpackedAssignment, BindingKind::Assignment => Kind::Assignment, BindingKind::TypeParam => Kind::TypeParam, BindingKind::LoopVar => Kind::LoopVar, @@ -85,7 +84,6 @@ enum Kind { Annotation, Argument, NamedExprAssignment, - UnpackedAssignment, Assignment, TypeParam, LoopVar, @@ -102,7 +100,6 @@ impl fmt::Display for Kind { Kind::Annotation => f.write_str("Annotation"), Kind::Argument => f.write_str("Argument"), Kind::NamedExprAssignment => f.write_str("Variable"), - Kind::UnpackedAssignment => f.write_str("Variable"), Kind::Assignment => f.write_str("Variable"), Kind::TypeParam => f.write_str("Type parameter"), Kind::LoopVar => f.write_str("Variable"), diff --git a/crates/ruff_python_semantic/src/binding.rs b/crates/ruff_python_semantic/src/binding.rs index dba1616e08..b9cd7b7285 100644 --- a/crates/ruff_python_semantic/src/binding.rs +++ b/crates/ruff_python_semantic/src/binding.rs @@ -87,6 +87,12 @@ impl<'a> Binding<'a> { self.flags.intersects(BindingFlags::INVALID_ALL_OBJECT) } + /// Return `true` if this [`Binding`] represents an unpacked assignment (e.g., `x` in + /// `(x, y) = 1, 2`). + pub const fn is_unpacked_assignment(&self) -> bool { + self.flags.intersects(BindingFlags::UNPACKED_ASSIGNMENT) + } + /// Return `true` if this [`Binding`] represents an unbound variable /// (e.g., `x` in `x = 1; del x`). pub const fn is_unbound(&self) -> bool { @@ -212,7 +218,7 @@ impl<'a> Binding<'a> { bitflags! { /// Flags on a [`Binding`]. #[derive(Debug, Default, Copy, Clone, Eq, PartialEq)] - pub struct BindingFlags: u8 { + pub struct BindingFlags: u16 { /// The binding represents an explicit re-export. /// /// For example, the binding could be `FastAPI` in: @@ -284,6 +290,14 @@ bitflags! { /// _T = "This is a private variable" /// ``` const PRIVATE_DECLARATION = 1 << 7; + + /// The binding represents an unpacked assignment. + /// + /// For example, the binding could be `x` in: + /// ```python + /// (x, y) = 1, 2 + /// ``` + const UNPACKED_ASSIGNMENT = 1 << 8; } } @@ -393,12 +407,6 @@ pub enum BindingKind<'a> { /// ``` NamedExprAssignment, - /// A binding for a unpacking-based assignment, like `x` in: - /// ```python - /// x, y = (1, 2) - /// ``` - UnpackedAssignment, - /// A binding for a "standard" assignment, like `x` in: /// ```python /// x = 1