Invalid variable lookup when walrus operator is used
ostr00000 opened this issue · 1 comments
ostr00000 commented
Steps to reproduce
- Consider following code in
loop_error.py
:"""Test module""" def walrus_in_comprehension_test_2(some_path, module_namespace): """Suspected error""" for mod in some_path.iterdir(): print(mod) for org_mod in some_path.iterdir(): if org_mod.is_dir(): if mod := module_namespace.get_mod_from_alias(org_mod.name): new_name = mod.name else: new_name = org_mod.name print(new_name)
- Run
pylint ./loop_error.py
Current behavior
A warning appears: W0631: Using possibly undefined loop variable 'mod' (undefined-loop-variable)
Expected behavior
No warning, because the variable mod
is always defined.
python -c "from astroid import __pkginfo__; print(__pkginfo__.version)"
output
- 2.14.1
- 2.15.0-dev0 on 56a65da with Python 3.10.6
ostr00000 commented
Investigation
I tried to understand the possible reason for this warning.
Minimum reproducible code
It seems ultra-rare case, because we cannot:
- remove first loop (
for mod in some_path.iterdir():
), - remove first condition (
if org_mod.is_dir():
), - rewrite walrus operator to standard assignment (
mod :=
), - remove
else
with body,
otherwise, there is no error.
Tracked calls
pylint/checkers/variables.py:_loopvar_name:419
- in this function we got only 1 statement inastmts
- should be 2,- I looked at
scope_lookup
functions. Untilastroid/filter_statements.py:_filter_stmts:201
there are always 2 statements - that is good. - So the problem is in
astroid.are_exclusive
. Probably there is an invalid (old, before Python 3.8) assumption thatast.IfExp
branches are searched only inIfExp.body
andIfExp.orelse
and therefore they must be exclusive. But it turns out that if assignment is inIfExp.test
(i.e. walrus operator), branches are not exclusive - actually there are always inclusive.
Simpler minimum reproducible code
It seems that this code is a minimum to reproduce error:
from astroid import nodes, extract_node
node_if, node_body, node_or_else = extract_node("""
if val := True: #@
print(val) #@
else:
print(val) #@
""")
node_if: nodes.If
node_walrus = next(node_if.nodes_of_class(nodes.NamedExpr))
assert not nodes.are_exclusive(node_walrus, node_body)
I will try to fix it and add test cases.