diff --git a/doc/whatsnew/fragments/10426.false_positive b/doc/whatsnew/fragments/10426.false_positive new file mode 100644 index 0000000000..caa29449ee --- /dev/null +++ b/doc/whatsnew/fragments/10426.false_positive @@ -0,0 +1,3 @@ +Fix a false positive for ``unused-variable`` when multiple except handlers bind the same name under a try block. + +Closes #10426 diff --git a/pylint/checkers/variables.py b/pylint/checkers/variables.py index 4baf6aabf8..c17e8ad096 100644 --- a/pylint/checkers/variables.py +++ b/pylint/checkers/variables.py @@ -2836,9 +2836,7 @@ def _check_is_unused( return # Special case for exception variable - if isinstance(stmt.parent, nodes.ExceptHandler) and any( - n.name == name for n in stmt.parent.nodes_of_class(nodes.Name) - ): + if self._is_exception_binding_used_in_handler(stmt, name): return self.add_message(message_name, args=name, node=stmt) @@ -2914,6 +2912,15 @@ def _check_unused_arguments( self.add_message("unused-argument", args=name, node=stmt, confidence=confidence) + def _is_exception_binding_used_in_handler( + self, stmt: nodes.NodeNG, name: str + ) -> bool: + return ( + isinstance(stmt.parent, nodes.ExceptHandler) + and stmt is stmt.parent.name + and any(n.name == name for n in stmt.parent.nodes_of_class(nodes.Name)) + ) + def _check_late_binding_closure(self, node: nodes.Name) -> None: """Check whether node is a cell var that is assigned within a containing loop. @@ -3245,6 +3252,8 @@ def _check_globals(self, not_consumed: Consumption) -> None: for node in node_lst: if in_type_checking_block(node): continue + if self._is_exception_binding_used_in_handler(node, name): + continue self.add_message("unused-variable", args=(name,), node=node) # pylint: disable = too-many-branches diff --git a/tests/functional/u/unused/unused_global_variable2.py b/tests/functional/u/unused/unused_global_variable2.py index 0722bc63c8..06242bb6e3 100644 --- a/tests/functional/u/unused/unused_global_variable2.py +++ b/tests/functional/u/unused/unused_global_variable2.py @@ -9,3 +9,11 @@ VAR = 'pylint' # [unused-variable] + + +try: + pass +except TypeError as exc: + print(exc) +except ValueError as exc: + print(exc) diff --git a/tests/functional/u/unused/unused_variable.py b/tests/functional/u/unused/unused_variable.py index f15ae75576..b4f5263d56 100644 --- a/tests/functional/u/unused/unused_variable.py +++ b/tests/functional/u/unused/unused_variable.py @@ -187,6 +187,17 @@ def sibling_except_handlers(): except ValueError as e: print(e) + +def test_multiple_except_handlers_under_try(): + try: + pass + except TypeError as exc: + print(exc) + except ValueError as exc: + print(exc) + except ArithmeticError as exc: + print(exc) + def func6(): a = 1 diff --git a/tests/functional/u/unused/unused_variable.txt b/tests/functional/u/unused/unused_variable.txt index 609ce9fef6..8800488bfb 100644 --- a/tests/functional/u/unused/unused_variable.txt +++ b/tests/functional/u/unused/unused_variable.txt @@ -26,4 +26,4 @@ unused-variable:150:4:154:26:func4:Unused variable 'error':UNDEFINED redefined-outer-name:153:8:154:26:func4:Redefining name 'error' from outer scope (line 150):UNDEFINED unused-variable:161:4:162:12:main:Unused variable 'e':UNDEFINED undefined-loop-variable:168:10:168:11:main:Using possibly undefined loop variable 'e':UNDEFINED -unused-variable:217:8:218:16:test_regression_8595:Unused variable 'e':UNDEFINED +unused-variable:228:8:229:16:test_regression_8595:Unused variable 'e':UNDEFINED