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

Fix inference cycle in getattr when a base is an astroid.Attribute #934

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
4 changes: 4 additions & 0 deletions ChangeLog
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,10 @@ Release Date: TBA

Closes #922

* Fix inference of attributes defined in a base class that is an inner class

Closes #904


What's New in astroid 2.5.6?
============================
Expand Down
3 changes: 2 additions & 1 deletion astroid/inference.py
Original file line number Diff line number Diff line change
Expand Up @@ -309,6 +309,7 @@ def infer_attribute(self, context=None):
elif not context:
context = contextmod.InferenceContext()

old_boundnode = context.boundnode
try:
context.boundnode = owner
yield from owner.igetattr(self.attrname, context)
Expand All @@ -319,7 +320,7 @@ def infer_attribute(self, context=None):
):
pass
finally:
context.boundnode = None
context.boundnode = old_boundnode
return dict(node=self, context=context)


Expand Down
59 changes: 59 additions & 0 deletions tests/unittest_inference.py
Original file line number Diff line number Diff line change
Expand Up @@ -3894,6 +3894,65 @@ class Clazz(metaclass=_Meta):
).inferred()[0]
assert isinstance(cls, nodes.ClassDef) and cls.name == "Clazz"

def test_infer_subclass_attr_outer_class(self):
node = extract_node(
"""
class Outer:
data = 123

class Test(Outer):
pass
Test.data
"""
)
inferred = next(node.infer())
assert isinstance(inferred, nodes.Const)
assert inferred.value == 123

def test_infer_subclass_attr_inner_class_works_indirectly(self):
node = extract_node(
"""
class Outer:
class Inner:
data = 123
Inner = Outer.Inner

class Test(Inner):
pass
Test.data
"""
)
inferred = next(node.infer())
assert isinstance(inferred, nodes.Const)
assert inferred.value == 123

def test_infer_subclass_attr_inner_class(self):
nelfin marked this conversation as resolved.
Show resolved Hide resolved
clsdef_node, attr_node = extract_node(
"""
class Outer:
class Inner:
data = 123

class Test(Outer.Inner):
pass
Test #@
Test.data #@
"""
)
clsdef = next(clsdef_node.infer())
assert isinstance(clsdef, nodes.ClassDef)
inferred = next(clsdef.igetattr("data"))
assert isinstance(inferred, nodes.Const)
assert inferred.value == 123
# Inferring the value of .data via igetattr() worked before the
# old_boundnode fixes in infer_subscript, so it should have been
# possible to infer the subscript directly. It is the difference
# between these two cases that led to the discovery of the cause of the
# bug in https://github.com/PyCQA/astroid/issues/904
inferred = next(attr_node.infer())
assert isinstance(inferred, nodes.Const)
assert inferred.value == 123

def test_delayed_attributes_without_slots(self):
ast_node = extract_node(
"""
Expand Down