diff --git a/crates/ruff_linter/resources/test/fixtures/pyflakes/F811_28.py b/crates/ruff_linter/resources/test/fixtures/pyflakes/F811_28.py new file mode 100644 index 0000000000000..4fc238e98022a --- /dev/null +++ b/crates/ruff_linter/resources/test/fixtures/pyflakes/F811_28.py @@ -0,0 +1,6 @@ +"""Regression test for: https://github.com/astral-sh/ruff/issues/10384""" + +import datetime +from datetime import datetime + +datetime(1, 2, 3) diff --git a/crates/ruff_linter/src/checkers/ast/analyze/deferred_scopes.rs b/crates/ruff_linter/src/checkers/ast/analyze/deferred_scopes.rs index e1d662922384b..c35cf53a77a3a 100644 --- a/crates/ruff_linter/src/checkers/ast/analyze/deferred_scopes.rs +++ b/crates/ruff_linter/src/checkers/ast/analyze/deferred_scopes.rs @@ -259,23 +259,29 @@ pub(crate) fn deferred_scopes(checker: &mut Checker) { diagnostic.set_parent(range.start()); } - if let Some(import) = binding.as_any_import() { - if let Some(source) = binding.source { - diagnostic.try_set_fix(|| { - let statement = checker.semantic().statement(source); - let parent = checker.semantic().parent_statement(source); - let edit = fix::edits::remove_unused_imports( - std::iter::once(import.member_name().as_ref()), - statement, - parent, - checker.locator(), - checker.stylist(), - checker.indexer(), - )?; - Ok(Fix::safe_edit(edit).isolate(Checker::isolation( - checker.semantic().parent_statement_id(source), - ))) - }); + // Remove the import if the binding and the shadowed binding are both imports, + // and both point to the same qualified name. + if let Some(shadowed_import) = shadowed.as_any_import() { + if let Some(import) = binding.as_any_import() { + if shadowed_import.qualified_name() == import.qualified_name() { + if let Some(source) = binding.source { + diagnostic.try_set_fix(|| { + let statement = checker.semantic().statement(source); + let parent = checker.semantic().parent_statement(source); + let edit = fix::edits::remove_unused_imports( + std::iter::once(import.member_name().as_ref()), + statement, + parent, + checker.locator(), + checker.stylist(), + checker.indexer(), + )?; + Ok(Fix::safe_edit(edit).isolate(Checker::isolation( + checker.semantic().parent_statement_id(source), + ))) + }); + } + } } } diff --git a/crates/ruff_linter/src/rules/pyflakes/mod.rs b/crates/ruff_linter/src/rules/pyflakes/mod.rs index 64840631d8bbc..e913278245c49 100644 --- a/crates/ruff_linter/src/rules/pyflakes/mod.rs +++ b/crates/ruff_linter/src/rules/pyflakes/mod.rs @@ -124,6 +124,7 @@ mod tests { #[test_case(Rule::RedefinedWhileUnused, Path::new("F811_25.py"))] #[test_case(Rule::RedefinedWhileUnused, Path::new("F811_26.py"))] #[test_case(Rule::RedefinedWhileUnused, Path::new("F811_27.py"))] + #[test_case(Rule::RedefinedWhileUnused, Path::new("F811_28.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"))] diff --git a/crates/ruff_linter/src/rules/pyflakes/snapshots/ruff_linter__rules__pyflakes__tests__F811_F811_1.py.snap b/crates/ruff_linter/src/rules/pyflakes/snapshots/ruff_linter__rules__pyflakes__tests__F811_F811_1.py.snap index b3d2ebd453667..19db1b0eddce7 100644 --- a/crates/ruff_linter/src/rules/pyflakes/snapshots/ruff_linter__rules__pyflakes__tests__F811_F811_1.py.snap +++ b/crates/ruff_linter/src/rules/pyflakes/snapshots/ruff_linter__rules__pyflakes__tests__F811_F811_1.py.snap @@ -1,15 +1,9 @@ --- source: crates/ruff_linter/src/rules/pyflakes/mod.rs --- -F811_1.py:1:25: F811 [*] Redefinition of unused `FU` from line 1 +F811_1.py:1:25: F811 Redefinition of unused `FU` from line 1 | 1 | import fu as FU, bar as FU | ^^ F811 | = help: Remove definition: `FU` - -ℹ Safe fix -1 |-import fu as FU, bar as FU - 1 |+import fu as FU - - diff --git a/crates/ruff_linter/src/rules/pyflakes/snapshots/ruff_linter__rules__pyflakes__tests__F811_F811_12.py.snap b/crates/ruff_linter/src/rules/pyflakes/snapshots/ruff_linter__rules__pyflakes__tests__F811_F811_12.py.snap index 1411fddf047c7..0d490baf57807 100644 --- a/crates/ruff_linter/src/rules/pyflakes/snapshots/ruff_linter__rules__pyflakes__tests__F811_F811_12.py.snap +++ b/crates/ruff_linter/src/rules/pyflakes/snapshots/ruff_linter__rules__pyflakes__tests__F811_F811_12.py.snap @@ -1,7 +1,7 @@ --- source: crates/ruff_linter/src/rules/pyflakes/mod.rs --- -F811_12.py:6:20: F811 [*] Redefinition of unused `mixer` from line 2 +F811_12.py:6:20: F811 Redefinition of unused `mixer` from line 2 | 4 | pass 5 | else: @@ -10,13 +10,3 @@ F811_12.py:6:20: F811 [*] Redefinition of unused `mixer` from line 2 7 | mixer(123) | = help: Remove definition: `mixer` - -ℹ Safe fix -3 3 | except ImportError: -4 4 | pass -5 5 | else: -6 |- from bb import mixer - 6 |+ pass -7 7 | mixer(123) - - diff --git a/crates/ruff_linter/src/rules/pyflakes/snapshots/ruff_linter__rules__pyflakes__tests__F811_F811_2.py.snap b/crates/ruff_linter/src/rules/pyflakes/snapshots/ruff_linter__rules__pyflakes__tests__F811_F811_2.py.snap index 9e87d29c8d81e..ce01b9cd148c8 100644 --- a/crates/ruff_linter/src/rules/pyflakes/snapshots/ruff_linter__rules__pyflakes__tests__F811_F811_2.py.snap +++ b/crates/ruff_linter/src/rules/pyflakes/snapshots/ruff_linter__rules__pyflakes__tests__F811_F811_2.py.snap @@ -1,15 +1,9 @@ --- source: crates/ruff_linter/src/rules/pyflakes/mod.rs --- -F811_2.py:1:34: F811 [*] Redefinition of unused `FU` from line 1 +F811_2.py:1:34: F811 Redefinition of unused `FU` from line 1 | 1 | from moo import fu as FU, bar as FU | ^^ F811 | = help: Remove definition: `FU` - -ℹ Safe fix -1 |-from moo import fu as FU, bar as FU - 1 |+from moo import fu as FU - - diff --git a/crates/ruff_linter/src/rules/pyflakes/snapshots/ruff_linter__rules__pyflakes__tests__F811_F811_23.py.snap b/crates/ruff_linter/src/rules/pyflakes/snapshots/ruff_linter__rules__pyflakes__tests__F811_F811_23.py.snap index d03de5d32f98b..4d77d04b236e2 100644 --- a/crates/ruff_linter/src/rules/pyflakes/snapshots/ruff_linter__rules__pyflakes__tests__F811_F811_23.py.snap +++ b/crates/ruff_linter/src/rules/pyflakes/snapshots/ruff_linter__rules__pyflakes__tests__F811_F811_23.py.snap @@ -1,18 +1,10 @@ --- source: crates/ruff_linter/src/rules/pyflakes/mod.rs --- -F811_23.py:4:15: F811 [*] Redefinition of unused `foo` from line 3 +F811_23.py:4:15: F811 Redefinition of unused `foo` from line 3 | 3 | import foo as foo 4 | import bar as foo | ^^^ F811 | = help: Remove definition: `foo` - -ℹ Safe fix -1 1 | """Test that shadowing an explicit re-export produces a warning.""" -2 2 | -3 3 | import foo as foo -4 |-import bar as foo - - diff --git a/crates/ruff_linter/src/rules/pyflakes/snapshots/ruff_linter__rules__pyflakes__tests__F811_F811_28.py.snap b/crates/ruff_linter/src/rules/pyflakes/snapshots/ruff_linter__rules__pyflakes__tests__F811_F811_28.py.snap new file mode 100644 index 0000000000000..51c91bc474062 --- /dev/null +++ b/crates/ruff_linter/src/rules/pyflakes/snapshots/ruff_linter__rules__pyflakes__tests__F811_F811_28.py.snap @@ -0,0 +1,12 @@ +--- +source: crates/ruff_linter/src/rules/pyflakes/mod.rs +--- +F811_28.py:4:22: F811 Redefinition of unused `datetime` from line 3 + | +3 | import datetime +4 | from datetime import datetime + | ^^^^^^^^ F811 +5 | +6 | datetime(1, 2, 3) + | + = help: Remove definition: `datetime`