diff --git a/crates/ruff_linter/resources/test/fixtures/pyflakes/F811_33.pyi b/crates/ruff_linter/resources/test/fixtures/pyflakes/F811_33.pyi new file mode 100644 index 0000000000000..3454d4c1c5e2b --- /dev/null +++ b/crates/ruff_linter/resources/test/fixtures/pyflakes/F811_33.pyi @@ -0,0 +1,7 @@ +# Regression test for https://github.com/astral-sh/ruff/issues/10874 +# Explicit re-exports at module scope should not be flagged as redefined +# by class-scoped attributes with the same name. +from x import y as y + +class Foo: + y = 42 # OK — class attribute, different scope from module-level re-export diff --git a/crates/ruff_linter/src/rules/pyflakes/mod.rs b/crates/ruff_linter/src/rules/pyflakes/mod.rs index a9d5dd6d02f5d..b0fce789a1df8 100644 --- a/crates/ruff_linter/src/rules/pyflakes/mod.rs +++ b/crates/ruff_linter/src/rules/pyflakes/mod.rs @@ -132,6 +132,7 @@ mod tests { #[test_case(Rule::RedefinedWhileUnused, Path::new("F811_30.py"))] #[test_case(Rule::RedefinedWhileUnused, Path::new("F811_31.py"))] #[test_case(Rule::RedefinedWhileUnused, Path::new("F811_32.py"))] + #[test_case(Rule::RedefinedWhileUnused, Path::new("F811_33.pyi"))] #[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/rules/redefined_while_unused.rs b/crates/ruff_linter/src/rules/pyflakes/rules/redefined_while_unused.rs index 09dd2d460e4f2..c93d0fc8c6029 100644 --- a/crates/ruff_linter/src/rules/pyflakes/rules/redefined_while_unused.rs +++ b/crates/ruff_linter/src/rules/pyflakes/rules/redefined_while_unused.rs @@ -126,6 +126,13 @@ pub(crate) fn redefined_while_unused(checker: &Checker, scope_id: ScopeId, scope ) { continue; } + + // Don't flag explicit re-exports (e.g., `from x import y as y`). + // A binding in a nested scope (like a class attribute) doesn't + // invalidate a module-level re-export. + if shadowed.is_explicit_export() { + continue; + } } // If the bindings are in different forks, abort. diff --git a/crates/ruff_linter/src/rules/pyflakes/snapshots/ruff_linter__rules__pyflakes__tests__F811_F811_33.pyi.snap b/crates/ruff_linter/src/rules/pyflakes/snapshots/ruff_linter__rules__pyflakes__tests__F811_F811_33.pyi.snap new file mode 100644 index 0000000000000..d0b409f39ee0b --- /dev/null +++ b/crates/ruff_linter/src/rules/pyflakes/snapshots/ruff_linter__rules__pyflakes__tests__F811_F811_33.pyi.snap @@ -0,0 +1,4 @@ +--- +source: crates/ruff_linter/src/rules/pyflakes/mod.rs +--- +