[pyflakes] Detect assignments that shadow definitions (F811) (#11961)

## Summary
This PR updates `F811` rule to include assignment as possible shadowed
binding. This will fix issue: #11828 .

## Test Plan

Add a test file, F811_30.py, which includes a redefinition after an
assignment and a verified snapshot file.
This commit is contained in:
ukyen 2024-06-23 18:29:32 +01:00 committed by GitHub
parent c3f61a012e
commit 068b75cc8e
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
4 changed files with 85 additions and 2 deletions

View file

@ -0,0 +1,37 @@
"""Regression test for: https://github.com/astral-sh/ruff/issues/11828"""
class A:
"""A."""
def foo(self) -> None:
"""Foo."""
bar = foo
def bar(self) -> None:
"""Bar."""
class B:
"""B."""
def baz(self) -> None:
"""Baz."""
baz = 1
class C:
"""C."""
def foo(self) -> None:
"""Foo."""
bar = (foo := 1)
class D:
"""D."""
foo = 1
foo = 2
bar = (foo := 3)
bar = (foo := 4)

View file

@ -125,6 +125,7 @@ mod tests {
#[test_case(Rule::RedefinedWhileUnused, Path::new("F811_27.py"))]
#[test_case(Rule::RedefinedWhileUnused, Path::new("F811_28.py"))]
#[test_case(Rule::RedefinedWhileUnused, Path::new("F811_29.pyi"))]
#[test_case(Rule::RedefinedWhileUnused, Path::new("F811_30.py"))]
#[test_case(Rule::UndefinedName, Path::new("F821_0.py"))]
#[test_case(Rule::UndefinedName, Path::new("F821_1.py"))]
#[test_case(Rule::UndefinedName, Path::new("F821_2.py"))]

View file

@ -0,0 +1,30 @@
---
source: crates/ruff_linter/src/rules/pyflakes/mod.rs
---
F811_30.py:12:9: F811 Redefinition of unused `bar` from line 10
|
10 | bar = foo
11 |
12 | def bar(self) -> None:
| ^^^ F811
13 | """Bar."""
|
= help: Remove definition: `bar`
F811_30.py:21:5: F811 Redefinition of unused `baz` from line 18
|
19 | """Baz."""
20 |
21 | baz = 1
| ^^^ F811
|
= help: Remove definition: `baz`
F811_30.py:29:12: F811 Redefinition of unused `foo` from line 26
|
27 | """Foo."""
28 |
29 | bar = (foo := 1)
| ^^^ F811
|
= help: Remove definition: `foo`

View file

@ -177,16 +177,31 @@ impl<'a> Binding<'a> {
| BindingKind::Builtin => {
return false;
}
// Assignment-assignment bindings are not considered redefinitions, as in:
// ```python
// x = 1
// x = 2
// ```
BindingKind::Assignment | BindingKind::NamedExprAssignment => {
if matches!(
existing.kind,
BindingKind::Assignment | BindingKind::NamedExprAssignment
) {
return false;
}
}
_ => {}
}
// Otherwise, the shadowed binding must be a class definition, function definition, or
// import to be considered a redefinition.
// Otherwise, the shadowed binding must be a class definition, function definition,
// import, or assignment to be considered a redefinition.
matches!(
existing.kind,
BindingKind::ClassDefinition(_)
| BindingKind::FunctionDefinition(_)
| BindingKind::Import(_)
| BindingKind::FromImport(_)
| BindingKind::Assignment
| BindingKind::NamedExprAssignment
)
}