pylint icon indicating copy to clipboard operation
pylint copied to clipboard

undefined-loop-variable in walrus in comprehension test

Open abe-winter opened this issue 3 years ago • 2 comments

Bug description

I think I'm getting a spurious undefined-loop-variable warning when:

  • walrus expression in 'if' block of list comprehension
  • loop variable in list comprehension (x, here) is the same as a previous loop
# pylint: disable=C0114,C0116,C0103

def example(arr):
    for x in arr:
        pass
    print([y for x in arr if (y := x)]) # this warns undefined-loop-variable
    print([y for x in arr if x]) # this is fine

def example2(arr):
    print([y for x in arr if (y := x)]) # this is fine

Configuration

No response

Command used

pylint example.py

Pylint output

************* Module example
example.py:6:35: W0631: Using possibly undefined loop variable 'x' (undefined-loop-variable)

------------------------------------------------------------------
Your code has been rated at 8.57/10 (previous run: 8.00/10, +0.57)

Expected behavior

Shouldn't warn.

Pylint version

pylint 2.14.5
astroid 2.11.7
Python 3.9.7 (default, Jun 22 2022, 20:11:26) 
[GCC 11.2.0]

OS / Environment

ubuntu 21.10

Additional dependencies

No response

abe-winter avatar Jul 23 '22 02:07 abe-winter

Edited: thought I had a smaller reproducer, but it missed the point...

jacobtylerwalls avatar Aug 04 '22 11:08 jacobtylerwalls

Draft diff, needs tests:

diff --git a/pylint/checkers/variables.py b/pylint/checkers/variables.py
index 0cdd37c5e..dc3fbc821 100644
--- a/pylint/checkers/variables.py
+++ b/pylint/checkers/variables.py
@@ -2247,6 +2247,14 @@ class VariablesChecker(BaseChecker):
         ):
             return
 
+        maybe_walrus = utils.get_node_first_ancestor_of_type(node, nodes.NamedExpr)
+        if maybe_walrus:
+            maybe_comprehension = utils.get_node_first_ancestor_of_type(maybe_walrus, nodes.Comprehension)
+            if maybe_comprehension:
+                comprehension_scope = utils.get_node_first_ancestor_of_type(maybe_comprehension, nodes.ComprehensionScope)
+                if comprehension_scope.parent.scope() is scope and node.name in comprehension_scope.locals:
+                    return
+
         # For functions we can do more by inferring the length of the itered object
         try:
             inferred = next(assign.iter.infer())

jacobtylerwalls avatar Aug 04 '22 12:08 jacobtylerwalls