diff --git a/crates/ruff_linter/resources/test/fixtures/flake8_bugbear/B013.py b/crates/ruff_linter/resources/test/fixtures/flake8_bugbear/B013.py index 8c0bc1ce781ee..bb28f08220a99 100644 --- a/crates/ruff_linter/resources/test/fixtures/flake8_bugbear/B013.py +++ b/crates/ruff_linter/resources/test/fixtures/flake8_bugbear/B013.py @@ -12,3 +12,10 @@ pass except(ValueError,): pass + +list_exceptions = [FileExistsError, FileNotFoundError] + +try: + pass +except (*list_exceptions,): + pass \ No newline at end of file diff --git a/crates/ruff_linter/src/rules/flake8_bugbear/rules/redundant_tuple_in_exception_handler.rs b/crates/ruff_linter/src/rules/flake8_bugbear/rules/redundant_tuple_in_exception_handler.rs index 0f0240d6a4147..4cfa189a96f88 100644 --- a/crates/ruff_linter/src/rules/flake8_bugbear/rules/redundant_tuple_in_exception_handler.rs +++ b/crates/ruff_linter/src/rules/flake8_bugbear/rules/redundant_tuple_in_exception_handler.rs @@ -1,6 +1,5 @@ use ruff_diagnostics::{AlwaysFixableViolation, Diagnostic, Edit, Fix}; use ruff_macros::{derive_message_formats, violation}; -use ruff_python_ast::helpers::map_starred; use ruff_python_ast::{self as ast, ExceptHandler, Expr}; use ruff_text_size::Ranged; @@ -11,6 +10,9 @@ use crate::fix::edits::pad; /// Checks for single-element tuples in exception handlers (e.g., /// `except (ValueError,):`). /// +/// Note: Single-element tuples consisting of a starred expression +/// are allowed. +/// /// ## Why is this bad? /// A tuple with a single element can be more concisely and idiomatically /// expressed as a single value. @@ -69,7 +71,17 @@ pub(crate) fn redundant_tuple_in_exception_handler( let [elt] = elts.as_slice() else { continue; }; - let elt = map_starred(elt); + // It is not safe to replace a single-element + // tuple consisting of a starred expression + // by the unstarred expression because the unstarred + // expression can be any iterable whereas `except` must + // be followed by a literal or a tuple. For example: + // ```python + // except (*[ValueError,FileNotFoundError],) + // ``` + if elt.is_starred_expr() { + continue; + } let mut diagnostic = Diagnostic::new( RedundantTupleInExceptionHandler { name: checker.generator().expr(elt), diff --git a/crates/ruff_linter/src/rules/flake8_bugbear/snapshots/ruff_linter__rules__flake8_bugbear__tests__B013_B013.py.snap b/crates/ruff_linter/src/rules/flake8_bugbear/snapshots/ruff_linter__rules__flake8_bugbear__tests__B013_B013.py.snap index 4581b10bf46ad..658dfd3c7a7c0 100644 --- a/crates/ruff_linter/src/rules/flake8_bugbear/snapshots/ruff_linter__rules__flake8_bugbear__tests__B013_B013.py.snap +++ b/crates/ruff_linter/src/rules/flake8_bugbear/snapshots/ruff_linter__rules__flake8_bugbear__tests__B013_B013.py.snap @@ -22,27 +22,6 @@ B013.py:5:8: B013 [*] A length-one tuple literal is redundant in exception handl 7 7 | except AttributeError: 8 8 | pass -B013.py:11:8: B013 [*] A length-one tuple literal is redundant in exception handlers - | - 9 | except (ImportError, TypeError): -10 | pass -11 | except (*retriable_exceptions,): - | ^^^^^^^^^^^^^^^^^^^^^^^^ B013 -12 | pass -13 | except(ValueError,): - | - = help: Replace with `except retriable_exceptions` - -ℹ Safe fix -8 8 | pass -9 9 | except (ImportError, TypeError): -10 10 | pass -11 |-except (*retriable_exceptions,): - 11 |+except retriable_exceptions: -12 12 | pass -13 13 | except(ValueError,): -14 14 | pass - B013.py:13:7: B013 [*] A length-one tuple literal is redundant in exception handlers | 11 | except (*retriable_exceptions,): @@ -60,5 +39,5 @@ B013.py:13:7: B013 [*] A length-one tuple literal is redundant in exception hand 13 |-except(ValueError,): 13 |+except ValueError: 14 14 | pass - - +15 15 | +16 16 | list_exceptions = [FileExistsError, FileNotFoundError]