Skip to content

[pyflakes] Fix false positive for names shadowing re-exports (F811)#23356

Merged
ntBre merged 2 commits intoastral-sh:mainfrom
kar-ganap:fix/issue-10874
Feb 27, 2026
Merged

[pyflakes] Fix false positive for names shadowing re-exports (F811)#23356
ntBre merged 2 commits intoastral-sh:mainfrom
kar-ganap:fix/issue-10874

Conversation

@kar-ganap
Copy link
Contributor

Summary

Fixes #10874.

In stub files, explicit re-exports (from x import y as y) at module scope were falsely flagged as redefined (F811) by class-scoped attributes with the same name. A class attribute binding in a nested scope should not invalidate a module-level re-export.

Skip the F811 diagnostic when the shadowed binding is an explicit re-export (is_explicit_export()).

Test plan

  • Reproduction case: ruff check --select F811 stub.pyi no longer emits false positive
  • cargo test -p ruff_linter -- pyflakes — all 462 tests pass

… re-exports

Fixes astral-sh#10874.

In stub files, explicit re-exports (\`from x import y as y\`) at module
scope were falsely flagged as redefined by class-scoped attributes
with the same name. A class attribute binding in a nested scope should
not invalidate a module-level re-export.

Skip the F811 diagnostic when the shadowed binding is an explicit
re-export.
Copy link
Contributor

@ntBre ntBre left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can you add a regression test for this showing that the issue is now resolved?

@astral-sh-bot
Copy link

astral-sh-bot bot commented Feb 16, 2026

ruff-ecosystem results

Linter (stable)

ℹ️ ecosystem check detected linter changes. (+11 -0 violations, +0 -0 fixes in 1 projects; 55 projects unchanged)

python/typeshed (+11 -0 violations, +0 -0 fixes)

ruff check --no-cache --exit-zero --no-fix --output-format concise --no-preview --select E,F,FA,I,PYI,RUF,UP,W

+ stdlib/_socket.pyi:744:45: RUF100 [*] Unused `noqa` directive (unused: `F811`)
+ stdlib/_socket.pyi:841:63: RUF100 [*] Unused `noqa` directive (unused: `F811`)
+ stubs/psutil/psutil/_pswindows.pyi:49:84: RUF100 [*] Unused `noqa` directive (unused: `F811`)
+ stubs/psutil/psutil/_pswindows.pyi:50:84: RUF100 [*] Unused `noqa` directive (unused: `F811`)
+ stubs/psutil/psutil/_pswindows.pyi:51:68: RUF100 [*] Unused `noqa` directive (unused: `F811`)
+ stubs/psutil/psutil/_pswindows.pyi:52:68: RUF100 [*] Unused `noqa` directive (unused: `F811`)
+ stubs/psutil/psutil/_pswindows.pyi:53:72: RUF100 [*] Unused `noqa` directive (unused: `F811`)
+ stubs/psutil/psutil/_pswindows.pyi:54:76: RUF100 [*] Unused `noqa` directive (unused: `F811`)
+ stubs/pysftp/pysftp/__init__.pyi:105:20: RUF100 [*] Unused `noqa` directive (unused: `F811`)
+ stubs/pysftp/pysftp/__init__.pyi:79:89: RUF100 [*] Unused `noqa` directive (unused: `F811`)
+ stubs/pywin32/win32/win32ras.pyi:17:43: RUF100 [*] Unused `noqa` directive (unused: `F811`)

Changes by rule (1 rules affected)

code total + violation - violation + fix - fix
RUF100 11 11 0 0 0

Linter (preview)

ℹ️ ecosystem check detected linter changes. (+11 -0 violations, +0 -0 fixes in 1 projects; 55 projects unchanged)

python/typeshed (+11 -0 violations, +0 -0 fixes)

ruff check --no-cache --exit-zero --no-fix --output-format concise --preview --select E,F,FA,I,PYI,RUF,UP,W

+ stdlib/_socket.pyi:744:45: RUF100 [*] Unused `noqa` directive (unused: `F811`)
+ stdlib/_socket.pyi:841:63: RUF100 [*] Unused `noqa` directive (unused: `F811`)
+ stubs/psutil/psutil/_pswindows.pyi:49:84: RUF100 [*] Unused `noqa` directive (unused: `F811`)
+ stubs/psutil/psutil/_pswindows.pyi:50:84: RUF100 [*] Unused `noqa` directive (unused: `F811`)
+ stubs/psutil/psutil/_pswindows.pyi:51:68: RUF100 [*] Unused `noqa` directive (unused: `F811`)
+ stubs/psutil/psutil/_pswindows.pyi:52:68: RUF100 [*] Unused `noqa` directive (unused: `F811`)
+ stubs/psutil/psutil/_pswindows.pyi:53:72: RUF100 [*] Unused `noqa` directive (unused: `F811`)
+ stubs/psutil/psutil/_pswindows.pyi:54:76: RUF100 [*] Unused `noqa` directive (unused: `F811`)
+ stubs/pysftp/pysftp/__init__.pyi:105:20: RUF100 [*] Unused `noqa` directive (unused: `F811`)
+ stubs/pysftp/pysftp/__init__.pyi:79:89: RUF100 [*] Unused `noqa` directive (unused: `F811`)
+ stubs/pywin32/win32/win32ras.pyi:17:43: RUF100 [*] Unused `noqa` directive (unused: `F811`)

Changes by rule (1 rules affected)

code total + violation - violation + fix - fix
RUF100 11 11 0 0 0

@kar-ganap
Copy link
Contributor Author

Added the regression test (F811_33.pyi) — verifies that module-level re-exports are not flagged as redefined by class-scoped attributes. CI should be green. Ready for another look when you have a moment.

@kar-ganap
Copy link
Contributor Author

Gentle ping — added the regression test (F811_33.pyi) as requested. Ready for another look when you get a chance.

Copy link
Contributor

@ntBre ntBre left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks, this makes sense to me and seems to clean up a bunch of noqa comments in typeshed!

@ntBre ntBre added the bug Something isn't working label Feb 27, 2026
@ntBre ntBre changed the title Fix false-positive F811 for class-scoped names shadowing module-level re-exports [pyflakes] Fix false positive for names shadowing module-level re-exports (F811) Feb 27, 2026
@ntBre
Copy link
Contributor

ntBre commented Feb 27, 2026

Note that I dropped class-scoped from the title because, as the ecosystem results show, this also applies to non-class-variable scopes like function parameters that shadow re-exports. I think this also applies to non-module-level re-exports as written, but that seems okay and also like quite an edge case. Something like this would now be allowed too:

def foo():
    import bar as bar

    class C:
        bar = 1

@ntBre ntBre changed the title [pyflakes] Fix false positive for names shadowing module-level re-exports (F811) [pyflakes] Fix false positive for names shadowing re-exports (F811) Feb 27, 2026
@ntBre ntBre merged commit 8d956e0 into astral-sh:main Feb 27, 2026
43 checks passed
carljm added a commit that referenced this pull request Feb 27, 2026
* main:
  [ty] Take myself out of the reviewer pool for the next few days (#23618)
  [ty] Fix bug where ty would think that a `Callable` with a variadic positional parameter could be a subtype of a `Callable` with a positional-or-keyword parameter (#23610)
  [`ruff`] Add fix for `none-not-at-end-of-union` (`RUF036`)  (#22829)
  Bump cargo dist to 0.31 (#23614)
  [`pyflakes`] Fix false positive for names shadowing re-exports (`F811`) (#23356)
  [`fastapi`] Handle callable class dependencies with `__call__` method (`FAST003`) (#23553)
  [ty] Recurse into tuples and nested tuples when applying special-cased validation of `isinstance()` and `issubclass()` (#23607)
  Update typing conformance suite commit (#23606)
  [ty] Detect invalid uses of `@final` on non-methods (#23604)
  [ty] Move the type hierarchy request handlers to individual modules
  [ty] Wire up the type hierarchy implementation with the LSP
  [ty] Add routine for mapping from system path to vendored path
  [ty] Implement internal routines for providing the LSP "type hierarchy" feature
  [ty] Add some helper methods on `ClassLiteral`
  [ty] Move some module name helper routines to methods on `ModuleName`
  [ty] Bump version of `lsp-types`
  [ty] Refactor to support building constraint sets differently (#23600)
  [ty] Dataclass transform: neither frozen nor non-frozen (#23366)
  [ty] Add snapshot tests for advanced `invalid-assignment` scenarios (#23581)
  [ty] disallow negative narrowing on SubclassOf types (#23598)
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

bug Something isn't working

Projects

None yet

Development

Successfully merging this pull request may close these issues.

False-positive "redefined while unused" error in stub files for constants in class scopes with the same name as module constants

2 participants