diff --git a/ChangeLog b/ChangeLog index 6097526bbd..c606b76106 100644 --- a/ChangeLog +++ b/ChangeLog @@ -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? ============================ diff --git a/astroid/inference.py b/astroid/inference.py index 20c986478e..dd9a565aec 100644 --- a/astroid/inference.py +++ b/astroid/inference.py @@ -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) @@ -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) diff --git a/tests/unittest_inference.py b/tests/unittest_inference.py index 7fb1ed55c9..6b9f4c0609 100644 --- a/tests/unittest_inference.py +++ b/tests/unittest_inference.py @@ -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): + 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( """