diff --git a/ChangeLog b/ChangeLog index 4b16576793..7b253d05cf 100644 --- a/ChangeLog +++ b/ChangeLog @@ -19,6 +19,9 @@ Release Date: TBA * Use ``inference_tip`` for ``typing.TypedDict`` brain. +* Fix inference of attributes defined in a base class that is an inner class + + Closes #904 What's New in astroid 2.5.2? ============================ diff --git a/astroid/inference.py b/astroid/inference.py index 926dabd9be..72281bb00a 100644 --- a/astroid/inference.py +++ b/astroid/inference.py @@ -315,6 +315,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) @@ -325,7 +326,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 07765cd540..b35553bd3d 100644 --- a/tests/unittest_inference.py +++ b/tests/unittest_inference.py @@ -3890,6 +3890,72 @@ 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()) + self.assertIsInstance(inferred, nodes.Const) + self.assertEqual(inferred.value, 123) + + def test_infer_subclass_attr_inner_class_works_from_classdef(self): + node = extract_node( + """ + class Outer: + class Inner: + data = 123 + + class Test(Outer.Inner): + pass + Test + """ + ) + cls_def = next(node.infer()) + self.assertIsInstance(cls_def, nodes.ClassDef) + inferred = next(cls_def.igetattr('data')) + self.assertIsInstance(inferred, nodes.Const) + self.assertEqual(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()) + self.assertIsInstance(inferred, nodes.Const) + self.assertEqual(inferred.value, 123) + + def test_infer_subclass_attr_inner_class(self): + node = extract_node( + """ + class Outer: + class Inner: + data = 123 + + class Test(Outer.Inner): + pass + Test.data + """ + ) + inferred = next(node.infer()) + self.assertIsInstance(inferred, nodes.Const) + self.assertEqual(inferred.value, 123) + def test_delayed_attributes_without_slots(self): ast_node = extract_node( """