summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorDaniƫl van Noord <13665637+DanielNoord@users.noreply.github.com>2021-10-26 09:59:29 +0200
committerPierre Sassoulas <pierre.sassoulas@gmail.com>2021-10-26 11:15:37 +0200
commit9323cf20afff416dabe61b7f303791a1ce7f2bb6 (patch)
tree7f353d7a99be28d8453a7178bad2b7316c4201f3
parent448485a98b66a40fae6db6dbb4f25f4516e5e539 (diff)
downloadpylint-git-9323cf20afff416dabe61b7f303791a1ce7f2bb6.tar.gz
Add control flow check for ``undefined-variable`` in ``if ... else``
Closes #3688
-rw-r--r--ChangeLog6
-rw-r--r--doc/whatsnew/2.12.rst6
-rw-r--r--pylint/checkers/variables.py21
-rw-r--r--tests/functional/u/undefined/undefined_variable_py38.py51
-rw-r--r--tests/functional/u/undefined/undefined_variable_py38.txt2
5 files changed, 85 insertions, 1 deletions
diff --git a/ChangeLog b/ChangeLog
index 9f60f43d1..4e292305f 100644
--- a/ChangeLog
+++ b/ChangeLog
@@ -47,6 +47,12 @@ Release date: TBA
Fixes part of #3688
+* ``undefined-variable`` now correctly triggers for assignment expressions in if ... else statements
+ This includes a basic form of control flow inference for if ... else statements using
+ constant boolean values
+
+ Closes #3688
+
* Fix bug with importing namespace packages with relative imports
Closes #2967 and #5131
diff --git a/doc/whatsnew/2.12.rst b/doc/whatsnew/2.12.rst
index 52b5ba595..6c71ae159 100644
--- a/doc/whatsnew/2.12.rst
+++ b/doc/whatsnew/2.12.rst
@@ -96,6 +96,12 @@ Other Changes
Fixes part of #3688
+* ``undefined-variable`` now correctly triggers for assignment expressions in if ... else statements
+ This includes a basic form of control flow inference for if ... else statements using
+ constant boolean values
+
+ Closes #3688
+
* Fix double emitting of ``not-callable`` on inferrable ``properties``
Closes #4426
diff --git a/pylint/checkers/variables.py b/pylint/checkers/variables.py
index 4851421b2..360b0af55 100644
--- a/pylint/checkers/variables.py
+++ b/pylint/checkers/variables.py
@@ -1211,6 +1211,12 @@ class VariablesChecker(BaseChecker):
)
if is_first_level_ref:
break
+ elif isinstance(defnode, nodes.NamedExpr):
+ if isinstance(defnode.parent, nodes.IfExp):
+ if self._is_never_evaluated(defnode, defnode.parent):
+ self.add_message(
+ "undefined-variable", node=node, args=node.name
+ )
current_consumer.mark_as_consumed(node.name, found_nodes)
# check it's not a loop variable used outside the loop
@@ -1635,6 +1641,21 @@ class VariablesChecker(BaseChecker):
return 2
return 0
+ @staticmethod
+ def _is_never_evaluated(
+ defnode: nodes.NamedExpr, defnode_parent: nodes.IfExp
+ ) -> bool:
+ """Check if a NamedExpr is inside a side of if ... else that never
+ gets evaluated
+ """
+ inferred_test = utils.safe_infer(defnode_parent.test)
+ if isinstance(inferred_test, nodes.Const):
+ if inferred_test.value is True and defnode == defnode_parent.orelse:
+ return True
+ if inferred_test.value is False and defnode == defnode_parent.body:
+ return True
+ return False
+
def _ignore_class_scope(self, node):
"""
Return True if the node is in a local class scope, as an assignment.
diff --git a/tests/functional/u/undefined/undefined_variable_py38.py b/tests/functional/u/undefined/undefined_variable_py38.py
index 950d17fb4..3c00bfd85 100644
--- a/tests/functional/u/undefined/undefined_variable_py38.py
+++ b/tests/functional/u/undefined/undefined_variable_py38.py
@@ -1,5 +1,5 @@
"""Tests for undefined variable with assignment expressions"""
-# pylint: disable=using-constant-test
+# pylint: disable=using-constant-test, expression-not-assigned
# Tests for annotation of variables and potentially undefinition
@@ -48,3 +48,52 @@ def no_parameters_in_function_default() -> None:
print(again_no_default) # [undefined-variable]
+
+# Tests for assignment expressions in if ... else comprehensions
+
+
+[i for i in range(10) if (if_assign_1 := i)]
+
+print(if_assign_1)
+
+IF_TWO = [i for i in range(10) if (if_assign_2 := i)]
+
+print(if_assign_2)
+
+IF_THREE = next(i for i in range(10) if (if_assign_3 := i))
+
+print(if_assign_3)
+
+IF_FOUR = {i: i for i in range(10) if (if_assign_4 := i)}
+
+print(if_assign_4)
+
+IF_FIVE = {i: i if (if_assign_5 := i) else 0 for i in range(10)}
+print(if_assign_5)
+
+{i: i if True else (else_assign_1 := i) for i in range(10)}
+
+print(else_assign_1) # [undefined-variable]
+
+
+# Tests for assignment expressions in the assignment of comprehensions
+
+[(assign_assign_1 := i) for i in range(10)]
+
+print(assign_assign_1)
+
+COMPREHENSION_TWO =[(assign_assign_2 := i) for i in range(10)]
+
+print(assign_assign_2)
+
+COMPREHENSION_THREE = next((assign_assign_3 := i) for i in range(10))
+
+print(assign_assign_3)
+
+COMPREHENSION_FOUR = {i: (assign_assign_4 := i) for i in range(10)}
+
+print(assign_assign_4)
+
+COMPREHENSION_FIVE = {i: (else_assign_2 := i) if False else 0 for i in range(10)}
+
+print(else_assign_2) # [undefined-variable]
diff --git a/tests/functional/u/undefined/undefined_variable_py38.txt b/tests/functional/u/undefined/undefined_variable_py38.txt
index 50a4944ac..d33473d98 100644
--- a/tests/functional/u/undefined/undefined_variable_py38.txt
+++ b/tests/functional/u/undefined/undefined_variable_py38.txt
@@ -1,3 +1,5 @@
undefined-variable:17:15:typing_and_self_referncing_assignment_expression:Undefined variable 'var':HIGH
undefined-variable:42:6::Undefined variable 'no_default':HIGH
undefined-variable:50:6::Undefined variable 'again_no_default':HIGH
+undefined-variable:76:6::Undefined variable 'else_assign_1':HIGH
+undefined-variable:99:6::Undefined variable 'else_assign_2':HIGH