Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Improve logic of variable lookup (LookupMixin._filter_stmts) #1111

Merged
merged 3 commits into from
Aug 2, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 10 additions & 0 deletions ChangeLog
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,16 @@ Release date: TBA

* Added support to infer return type of ``typing.cast()``

* Fix variable lookup's handling of exclusive statements

Closes PyCQA/pylint#3711

* Fix variable lookup's handling of function parameters

Closes PyCQA/astroid#180

* Fix variable lookup's handling of except clause variables


What's New in astroid 2.6.5?
============================
Expand Down
35 changes: 32 additions & 3 deletions astroid/node_classes.py
Original file line number Diff line number Diff line change
Expand Up @@ -1211,16 +1211,45 @@ def _filter_stmts(self, stmts, frame, offset):
if not (optional_assign or are_exclusive(_stmts[pindex], node)):
del _stmt_parents[pindex]
del _stmts[pindex]

# If self and node are exclusive, then we can ignore node
if are_exclusive(self, node):
continue

# An AssignName node overrides previous assignments if:
# 1. node's statement always assigns
# 2. node and self are in the same block (i.e., has the same parent as self)
if isinstance(node, AssignName):
if not optional_assign and stmt.parent is mystmt.parent:
if isinstance(stmt, ExceptHandler):
# If node's statement is an ExceptHandler, then it is the variable
# bound to the caught exception. If self is not contained within
# the exception handler block, node should override previous assignments;
# otherwise, node should be ignored, as an exception variable
# is local to the handler block.
if stmt.parent_of(self):
_stmts = []
_stmt_parents = []
else:
continue
elif not optional_assign and stmt.parent is mystmt.parent:
_stmts = []
_stmt_parents = []
elif isinstance(node, DelName):
# Remove all previously stored assignments
_stmts = []
_stmt_parents = []
continue
if not are_exclusive(self, node):
_stmts.append(node)
# Add the new assignment
_stmts.append(node)
if isinstance(node, Arguments) or isinstance(node.parent, Arguments):
# Special case for _stmt_parents when node is a function parameter;
# in this case, stmt is the enclosing FunctionDef, which is what we
# want to add to _stmt_parents, not stmt.parent. This case occurs when
# node is an Arguments node (representing varargs or kwargs parameter),
# and when node.parent is an Arguments node (other parameters).
# See issue #180.
_stmt_parents.append(stmt)
else:
_stmt_parents.append(stmt.parent)
return _stmts

Expand Down
18 changes: 18 additions & 0 deletions tests/unittest_inference.py
Original file line number Diff line number Diff line change
Expand Up @@ -4805,6 +4805,24 @@ def test(*args): return args
inferred = next(node.infer())
self.assertEqual(inferred, util.Uninferable)

def test_args_overwritten(self):
# https://github.com/PyCQA/astroid/issues/180
node = extract_node(
"""
next = 42
def wrapper(next=next):
next = 24
def test():
return next
return test
wrapper()() #@
"""
)
inferred = node.inferred()
self.assertEqual(len(inferred), 1)
self.assertIsInstance(inferred[0], nodes.Const, inferred[0])
self.assertEqual(inferred[0].value, 24)


class SliceTest(unittest.TestCase):
def test_slice(self):
Expand Down
Loading