summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorJacob Walls <jacobtylerwalls@gmail.com>2021-12-13 03:05:58 -0500
committerGitHub <noreply@github.com>2021-12-13 09:05:58 +0100
commit6c6a7aa36da74a35ac6e2ff543c10457c008f9e7 (patch)
tree11f7227cdf755ccf001410cb066c3955143bd431
parentf8b23eb9cccf4bda452bf56dc1c71b8a046e571b (diff)
downloadpylint-git-6c6a7aa36da74a35ac6e2ff543c10457c008f9e7.tar.gz
Emit `used-before-assignment` in final or except blocks where try statements could have failed (#5384)
* Emit `used-before-assignment` in final or except blocks where try statements could have failed Fix #85, #2615
-rw-r--r--ChangeLog5
-rw-r--r--doc/whatsnew/2.13.rst5
-rw-r--r--pylint/checkers/variables.py54
-rw-r--r--tests/functional/u/use/used_before_assignment_issue2615.py9
-rw-r--r--tests/functional/u/use/used_before_assignment_issue2615.txt1
-rw-r--r--tests/functional/u/use/used_before_assignment_issue85.py9
-rw-r--r--tests/functional/u/use/used_before_assignment_issue85.txt1
7 files changed, 77 insertions, 7 deletions
diff --git a/ChangeLog b/ChangeLog
index 738d8acda..a90ade1cc 100644
--- a/ChangeLog
+++ b/ChangeLog
@@ -16,6 +16,11 @@ Release date: TBA
Closes #4761
+* ``used-before-assignment`` now considers that assignments in a try block
+ may not have occurred when the except or finally blocks are executed.
+
+ Closes #85, #2615
+
* ``used-before-assignment`` now checks names in try blocks.
* Some files in ``pylint.testutils`` were deprecated. In the future imports should be done from the
diff --git a/doc/whatsnew/2.13.rst b/doc/whatsnew/2.13.rst
index 001b6b976..b3372278d 100644
--- a/doc/whatsnew/2.13.rst
+++ b/doc/whatsnew/2.13.rst
@@ -48,6 +48,11 @@ Other Changes
Closes #4761
+* ``used-before-assignment`` now considers that assignments in a try block
+ may not have occurred when the except or finally blocks are executed.
+
+ Closes #85, #2615
+
* ``used-before-assignment`` now checks names in try blocks.
* Require Python ``3.6.2`` to run pylint.
diff --git a/pylint/checkers/variables.py b/pylint/checkers/variables.py
index 13b8589af..0a4a21c18 100644
--- a/pylint/checkers/variables.py
+++ b/pylint/checkers/variables.py
@@ -405,7 +405,10 @@ MSGS = {
"E0601": (
"Using variable %r before assignment",
"used-before-assignment",
- "Used when a local variable is accessed before its assignment.",
+ "Emitted when a local variable is accessed before its assignment took place. "
+ "Assignments in try blocks are assumed not to have occurred when evaluating "
+ "associated except/finally blocks. Assignments in except blocks are assumed "
+ "not to have occurred when evaluating statements outside the block.",
),
"E0602": (
"Undefined variable %r",
@@ -580,12 +583,10 @@ scope_type : {self._atomic.scope_type}
def consumed_uncertain(self) -> DefaultDict[str, List[nodes.NodeNG]]:
"""
Retrieves nodes filtered out by get_next_to_consume() that may not
- have executed, such as statements in except blocks. Checkers that
- want to treat the statements as executed (e.g. for unused-variable)
- may need to add them back.
-
- TODO: A pending PR will extend this to nodes in try blocks when
- evaluating their corresponding except and finally blocks.
+ have executed, such as statements in except blocks, or statements
+ in try blocks (when evaluating their corresponding except and finally
+ blocks). Checkers that want to treat the statements as executed
+ (e.g. for unused-variable) may need to add them back.
"""
return self._atomic.consumed_uncertain
@@ -617,6 +618,7 @@ scope_type : {self._atomic.scope_type}
name = node.name
parent_node = node.parent
found_nodes = self.to_consume.get(name)
+ node_statement = node.statement(future=True)
if (
found_nodes
and isinstance(parent_node, nodes.Assign)
@@ -662,6 +664,44 @@ scope_type : {self._atomic.scope_type}
self.consumed_uncertain[node.name] += difference
found_nodes = filtered_nodes
+ # If this node is in a Finally block of a Try/Finally,
+ # filter out assignments in the try portion, assuming they may fail
+ if (
+ found_nodes
+ and isinstance(node_statement.parent, nodes.TryFinally)
+ and node_statement in node_statement.parent.finalbody
+ ):
+ filtered_nodes = [
+ n
+ for n in found_nodes
+ if not (
+ n.statement(future=True).parent is node_statement.parent
+ and n.statement(future=True) in n.statement(future=True).parent.body
+ )
+ ]
+ filtered_nodes_set = set(filtered_nodes)
+ difference = [n for n in found_nodes if n not in filtered_nodes_set]
+ self.consumed_uncertain[node.name] += difference
+ found_nodes = filtered_nodes
+
+ # If this node is in an ExceptHandler,
+ # filter out assignments in the try portion, assuming they may fail
+ if found_nodes and isinstance(node_statement.parent, nodes.ExceptHandler):
+ filtered_nodes = [
+ n
+ for n in found_nodes
+ if not (
+ isinstance(n.statement(future=True).parent, nodes.TryExcept)
+ and n.statement(future=True) in n.statement(future=True).parent.body
+ and node_statement.parent
+ in n.statement(future=True).parent.handlers
+ )
+ ]
+ filtered_nodes_set = set(filtered_nodes)
+ difference = [n for n in found_nodes if n not in filtered_nodes_set]
+ self.consumed_uncertain[node.name] += difference
+ found_nodes = filtered_nodes
+
return found_nodes
diff --git a/tests/functional/u/use/used_before_assignment_issue2615.py b/tests/functional/u/use/used_before_assignment_issue2615.py
new file mode 100644
index 000000000..912c71387
--- /dev/null
+++ b/tests/functional/u/use/used_before_assignment_issue2615.py
@@ -0,0 +1,9 @@
+"""https://github.com/PyCQA/pylint/issues/2615"""
+def main():
+ """When evaluating except blocks, assume try statements fail."""
+ try:
+ res = 1 / 0
+ res = 42
+ except ZeroDivisionError:
+ print(res) # [used-before-assignment]
+ print(res)
diff --git a/tests/functional/u/use/used_before_assignment_issue2615.txt b/tests/functional/u/use/used_before_assignment_issue2615.txt
new file mode 100644
index 000000000..ce6e4b9d0
--- /dev/null
+++ b/tests/functional/u/use/used_before_assignment_issue2615.txt
@@ -0,0 +1 @@
+used-before-assignment:8:14:8:17:main:Using variable 'res' before assignment:UNDEFINED
diff --git a/tests/functional/u/use/used_before_assignment_issue85.py b/tests/functional/u/use/used_before_assignment_issue85.py
new file mode 100644
index 000000000..58d8e38d8
--- /dev/null
+++ b/tests/functional/u/use/used_before_assignment_issue85.py
@@ -0,0 +1,9 @@
+"""https://github.com/PyCQA/pylint/issues/85"""
+def main():
+ """When evaluating finally blocks, assume try statements fail."""
+ try:
+ res = 1 / 0
+ res = 42
+ finally:
+ print(res) # [used-before-assignment]
+ print(res)
diff --git a/tests/functional/u/use/used_before_assignment_issue85.txt b/tests/functional/u/use/used_before_assignment_issue85.txt
new file mode 100644
index 000000000..ce6e4b9d0
--- /dev/null
+++ b/tests/functional/u/use/used_before_assignment_issue85.txt
@@ -0,0 +1 @@
+used-before-assignment:8:14:8:17:main:Using variable 'res' before assignment:UNDEFINED