diff --git a/doc/whatsnew/2/2.14/full.rst b/doc/whatsnew/2/2.14/full.rst index 8790973589..4df9901e1d 100644 --- a/doc/whatsnew/2/2.14/full.rst +++ b/doc/whatsnew/2/2.14/full.rst @@ -90,6 +90,11 @@ Release date: 2022-06-01 Refs #6462 +* Fixed a false positive regression in 2.13 for ``used-before-assignment`` where it is safe to rely + on a name defined only in an ``except`` block because the ``else`` block returned. + + Closes #6790 + * Removed the ``assign-to-new-keyword`` message as there are no new keywords in the supported Python versions any longer. diff --git a/pylint/checkers/variables.py b/pylint/checkers/variables.py index 59d2fa0307..79c6fbac3b 100644 --- a/pylint/checkers/variables.py +++ b/pylint/checkers/variables.py @@ -680,12 +680,16 @@ def _uncertain_nodes_in_except_blocks( if closest_except_handler.parent_of(node): continue closest_try_except: nodes.TryExcept = closest_except_handler.parent + # If the try or else blocks return, assume the except blocks execute. try_block_returns = any( isinstance(try_statement, nodes.Return) for try_statement in closest_try_except.body ) - # If the try block returns, assume the except blocks execute. - if try_block_returns: + else_block_returns = any( + isinstance(else_statement, nodes.Return) + for else_statement in closest_try_except.orelse + ) + if try_block_returns or else_block_returns: # Exception: if this node is in the final block of the other_node_statement, # it will execute before returning. Assume the except statements are uncertain. if ( @@ -694,6 +698,13 @@ def _uncertain_nodes_in_except_blocks( and closest_try_except.parent.parent_of(node_statement) ): uncertain_nodes.append(other_node) + # Or the node_statement is in the else block of the relevant TryExcept + elif ( + isinstance(node_statement.parent, nodes.TryExcept) + and node_statement in node_statement.parent.orelse + and closest_try_except.parent.parent_of(node_statement) + ): + uncertain_nodes.append(other_node) # Assume the except blocks execute, so long as each handler # defines the name, raises, or returns. elif all( diff --git a/tests/functional/u/used/used_before_assignment_else_return.py b/tests/functional/u/used/used_before_assignment_else_return.py new file mode 100644 index 0000000000..a5dc5c23b5 --- /dev/null +++ b/tests/functional/u/used/used_before_assignment_else_return.py @@ -0,0 +1,61 @@ +"""If the else block returns, it is generally safe to rely on assignments in the except.""" + + +def valid(): + """https://github.com/PyCQA/pylint/issues/6790""" + try: + pass + except ValueError: + error = True + else: + return + + print(error) + + +def invalid(): + """The finally will execute before the else returns.""" + try: + pass + except ValueError: + error = None + else: + return + finally: + print(error) # [used-before-assignment] + + +def invalid_2(): + """The else does not return in every branch.""" + try: + pass + except ValueError: + error = None + else: + if range(0): + return + finally: + print(error) # [used-before-assignment] + + +def invalid_3(): + """Not every except defines the name.""" + try: + pass + except ValueError: + error = None + except KeyError: + pass + finally: + print(error) # [used-before-assignment] + + +def invalid_4(): + """Should not rely on the name in the else even if it returns.""" + try: + pass + except ValueError: + error = True + else: + print(error) # [used-before-assignment] + return diff --git a/tests/functional/u/used/used_before_assignment_else_return.txt b/tests/functional/u/used/used_before_assignment_else_return.txt new file mode 100644 index 0000000000..d7d1835f49 --- /dev/null +++ b/tests/functional/u/used/used_before_assignment_else_return.txt @@ -0,0 +1,4 @@ +used-before-assignment:25:14:25:19:invalid:Using variable 'error' before assignment:CONTROL_FLOW +used-before-assignment:38:14:38:19:invalid_2:Using variable 'error' before assignment:CONTROL_FLOW +used-before-assignment:50:14:50:19:invalid_3:Using variable 'error' before assignment:CONTROL_FLOW +used-before-assignment:60:14:60:19:invalid_4:Using variable 'error' before assignment:CONTROL_FLOW