astral-sh / ruff

An extremely fast Python linter and code formatter, written in Rust.
https://docs.astral.sh/ruff
MIT License
32.14k stars 1.07k forks source link

B013 false positive when using iterable unpacking in except clause #12450

Closed raqbit closed 3 months ago

raqbit commented 3 months ago

When unpacking an iterable into a tuple as part of an except clause, B013 is triggered even though the unpacking can cause the tuple to contain multiple exceptions.

Example:

exceptions = [ConnectionError, PermissionError]

try:
    _foo()
except (*exceptions,):  # B013: A length-one tuple literal is redundant in exception handlers
    pass

Repro on ruff playground: https://play.ruff.rs/6b9daf6a-96fb-48ab-9be6-db236f86ec21 Ruff version: 0.5.4


As a workaround for my specific use case, the tuple constructor accepts an iterable, making the following possible:

try:
    _foo()
except tuple(exceptions):
    pass
charliermarsh commented 3 months ago

Thanks!

raqbit commented 3 months ago

Side note: likely as a side-effect of the incorrect detection, the auto-fix for this is also incorrect, as Ruff will turn the except clause into except <iterable>, whereas Python only supports tuples in except clauses (which is why the unpacking/tuple constructor is required)^1.

try:
    _foo()
except exceptions:
    pass
charliermarsh commented 3 months ago

Ah ok, so this is fine with exceptions = (ConnectionError, PermissionError), but not for non-tuple iterables. Got it.

charliermarsh commented 3 months ago

I think we should just always allow (*exceptions,) though. Even if we can detect a tuple, it's a footgun to change to except exceptions: since that code will silently break if you change the tuple to a list or other collection later.