diff --git a/ChangeLog b/ChangeLog index b01821574..8f3ac7d97 100644 --- a/ChangeLog +++ b/ChangeLog @@ -12,6 +12,12 @@ What's New in astroid 2.15.1? ============================= Release date: TBA +* Restore behavior of setting a Call as a base for classes created using ``six.with_metaclass()``, + and harden support for using enums as metaclasses in this case. + + Reverts #1622 + Refs PyCQA/pylint#5935 + Refs PyCQA/pylint#7506 What's New in astroid 2.15.0? diff --git a/astroid/brain/brain_six.py b/astroid/brain/brain_six.py index a35cfdd69..0eb945d8c 100644 --- a/astroid/brain/brain_six.py +++ b/astroid/brain/brain_six.py @@ -219,7 +219,6 @@ def transform_six_with_metaclass(node): """ call = node.bases[0] node._metaclass = call.args[0] - node.bases = call.args[1:] return node diff --git a/astroid/nodes/scoped_nodes/scoped_nodes.py b/astroid/nodes/scoped_nodes/scoped_nodes.py index bec817d75..530d9e6d3 100644 --- a/astroid/nodes/scoped_nodes/scoped_nodes.py +++ b/astroid/nodes/scoped_nodes/scoped_nodes.py @@ -1703,7 +1703,15 @@ def infer_call_result(self, caller=None, context: InferenceContext | None = None metaclass = next(caller.args[0].infer(context), None) if isinstance(metaclass, ClassDef): try: - class_bases = [next(arg.infer(context)) for arg in caller.args[1:]] + class_bases = [ + # Find the first non-None inferred base value + next( + b + for b in arg.infer(context=context.clone()) + if not (isinstance(b, Const) and b.value is None) + ) + for arg in caller.args[1:] + ] except StopIteration as e: raise InferenceError(node=caller.args[1:], context=context) from e new_class = ClassDef(name="temporary_class") diff --git a/tests/brain/test_six.py b/tests/brain/test_six.py index 1ff184d3b..c9dac5624 100644 --- a/tests/brain/test_six.py +++ b/tests/brain/test_six.py @@ -110,8 +110,7 @@ class B(six.with_metaclass(A, C)): inferred = next(ast_node.infer()) self.assertIsInstance(inferred, nodes.ClassDef) self.assertEqual(inferred.name, "B") - self.assertIsInstance(inferred.bases[0], nodes.Name) - self.assertEqual(inferred.bases[0].name, "C") + self.assertIsInstance(inferred.bases[0], nodes.Call) ancestors = tuple(inferred.ancestors()) self.assertIsInstance(ancestors[0], nodes.ClassDef) self.assertEqual(ancestors[0].name, "C") @@ -131,7 +130,7 @@ class Foo(six.with_metaclass(FooMeta, Enum)): #@ bar = 1 """ klass = astroid.extract_node(code) - assert list(klass.ancestors())[-1].name == "Enum" + assert next(klass.ancestors()).name == "Enum" def test_six_with_metaclass_with_additional_transform(self) -> None: def transform_class(cls: Any) -> ClassDef: diff --git a/tests/test_scoped_nodes.py b/tests/test_scoped_nodes.py index a69983c65..2722c56fa 100644 --- a/tests/test_scoped_nodes.py +++ b/tests/test_scoped_nodes.py @@ -1403,6 +1403,20 @@ class WithMeta(six.with_metaclass(type, object)): #@ self.assertEqual(["object"], [base.name for base in klass.ancestors()]) self.assertEqual("type", klass.metaclass().name) + @unittest.skipUnless(HAS_SIX, "These tests require the six library") + def test_metaclass_generator_hack_enum_base(self): + """Regression test for https://github.com/PyCQA/pylint/issues/5935""" + klass = builder.extract_node( + """ + import six + from enum import Enum, EnumMeta + + class PetEnumPy2Metaclass(six.with_metaclass(EnumMeta, Enum)): #@ + DOG = "dog" + """ + ) + self.assertEqual(list(klass.local_attr_ancestors("DOG")), []) + def test_add_metaclass(self) -> None: klass = builder.extract_node( """