Skip to content

Detect __all__ exports via augmented and annotated assignments - #421

Open
gaoflow wants to merge 1 commit into
jendrikseipp:mainfrom
gaoflow:fix-all-augmented-annotated-assign
Open

Detect __all__ exports via augmented and annotated assignments#421
gaoflow wants to merge 1 commit into
jendrikseipp:mainfrom
gaoflow:fix-all-augmented-annotated-assign

Conversation

@gaoflow

@gaoflow gaoflow commented Jun 29, 2026

Copy link
Copy Markdown

The bug

vulture marks names listed in __all__ as used (issue #172 / PR #282), but only when __all__ is built with a plain assignment. If a module populates __all__ with an augmented or annotated assignment, the exported names are reported as dead code:

def foo(): pass
def bar(): pass

__all__ = []
__all__ += ["foo", "bar"]      # or:  __all__: list = ["foo", "bar"]
$ vulture example.py
example.py:1: unused function 'foo' (60% confidence)
example.py:2: unused function 'bar' (60% confidence)

Both __all__ += [...] and __all__: list[str] = [...] are idiomatic, semantically-identical ways to declare exports, so flagging them means vulture reports a package's entire public API as unused — the one thing it must never do. Plain __all__ = ["foo", "bar"] is handled correctly, so the behaviour is also internally inconsistent.

Root cause

Vulture.visit_Assign was the only place __all__ was inspected, and _assigns_special_variable__all__ asserts an ast.Assign node. __all__ += [...] is an ast.AugAssign and __all__: list = [...] is an ast.AnnAssign; with no visit_AugAssign / visit_AnnAssign handlers, those nodes were never examined and the listed names were never added to used_names.

Fix

Factor the list/tuple-of-strings handling into a small _handle__all__ helper and call it from visit_Assign, visit_AugAssign, and visit_AnnAssign:

  • visit_AugAssign covers __all__ += [...],
  • visit_AnnAssign covers __all__: list = [...] (a bare annotation has value=None, which the helper ignores),
  • assignments to an attribute __all__ (e.g. other.__all__ = [...]) are still ignored, since the target check requires a bare ast.Name.

visit_AnnAssign is also added to vulture/whitelists/ast_whitelist.py because vulture scans itself and NodeVisitor methods are dispatched implicitly (visit_AugAssign was already whitelisted).

Tests

Added six cases to tests/test_imports.py: += with a list and with a tuple, an annotated assignment, a =-then-+= mix, and two negative controls — += with a string (still unused, matching __all__ = "Foo") and a bare __all__: list annotation (must not crash). The four positive tests fail on main; all pass with the fix, and the full suite stays green (297 → 303).

vulture only inspected `__all__ = [...]` (ast.Assign), so names exported
via `__all__ += [...]` (AugAssign) or `__all__: list = [...]` (AnnAssign)
were reported as unused, which flags a package's whole public API as dead
code. Generalize the __all__ handling into a helper and add visit_AugAssign
and visit_AnnAssign (the latter also added to the AST whitelist, since
vulture scans itself).
@codecov

codecov Bot commented Jun 29, 2026

Copy link
Copy Markdown

Codecov Report

✅ All modified and coverable lines are covered by tests.
✅ Project coverage is 93.54%. Comparing base (81fb2ac) to head (edf4421).
⚠️ Report is 1 commits behind head on main.

Additional details and impacted files
@@            Coverage Diff             @@
##             main     #421      +/-   ##
==========================================
+ Coverage   93.46%   93.54%   +0.07%     
==========================================
  Files           9        9              
  Lines         597      604       +7     
==========================================
+ Hits          558      565       +7     
  Misses         39       39              

☔ View full report in Codecov by Harness.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant