astral-sh/ruff

F841 treats `except` bindings differently from all other bindings

Open

#16,537 opened on Mar 6, 2025

View on GitHub
 (2 comments) (0 reactions) (0 assignees)Rust (47,527 stars) (2,088 forks)batch import
bughelp wanted

Description

Summary

The implementation of unused-variable (F841) is split: except bindings are handled separately in bindings.rs, causing false positives. They should be processed consistently with other bindings.

One false positive occurs when the bound variable is not used within the except block but is used outside it. The fix can change program behavior by suppressing a runtime error. The fix could be beneficial, but it should be marked unsafe like other unused bindings’ fixes.

$ cat >f841_1.py <<'# EOF'
def f():
    e = None
    try:
        1 / 0
    except ZeroDivisionError as e:
        pass
    print(e)
f()
# EOF

$ python f841_1.py 2>&1 | tail -n 1
UnboundLocalError: cannot access local variable 'e' where it is not associated with a value

$ ruff --isolated check --select F841 f841_1.py --fix
Found 1 error (1 fixed, 0 remaining).

$ cat f841_1.py
def f():
    e = None
    try:
        1 / 0
    except ZeroDivisionError:
        pass
    print(e)
f()

$ python f841_1.py
None

Another false positive occurs when the binding is not within a function.

$ cat >f841_2.py <<'# EOF'
e = None
try:
    1 / 0
except ZeroDivisionError as e:
    pass
print(e)
# EOF

$ python f841_2.py 2>&1 | tail -n 1
NameError: name 'e' is not defined

$ ruff --isolated check --select F841 f841_2.py --fix
Found 1 error (1 fixed, 0 remaining).

$ cat f841_2.py
e = None
try:
    1 / 0
except ZeroDivisionError:
    pass
print(e)

$ python f841_2.py
None

There are other false positives, but they are all variations on the same theme. They can probably all be fixed by merging the two F841 implementations.

Version

ruff 0.9.9 (091d0af2a 2025-02-28)

Contributor guide