Skip to content

Commit

Permalink
Restore setting a Call as a base for classes using `six.with_metaclas…
Browse files Browse the repository at this point in the history
…s` (#2049)

Harden support for using enums as metaclasses.

Fixes the crash in pylint-dev/pylint#5935 by adopting the check
for not-none bases as in ClassDef._inferred_bases without recausing
the false positive reported in pylint-dev/pylint#7506, which requires
correct bases.

(cherry picked from commit b5ebf99)
  • Loading branch information
jacobtylerwalls authored and github-actions[bot] committed Mar 26, 2023
1 parent edf88c6 commit 7aa5c2d
Show file tree
Hide file tree
Showing 5 changed files with 31 additions and 5 deletions.
6 changes: 6 additions & 0 deletions ChangeLog
Original file line number Diff line number Diff line change
Expand Up @@ -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?
Expand Down
1 change: 0 additions & 1 deletion astroid/brain/brain_six.py
Original file line number Diff line number Diff line change
Expand Up @@ -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


Expand Down
10 changes: 9 additions & 1 deletion astroid/nodes/scoped_nodes/scoped_nodes.py
Original file line number Diff line number Diff line change
Expand Up @@ -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")
Expand Down
5 changes: 2 additions & 3 deletions tests/brain/test_six.py
Original file line number Diff line number Diff line change
Expand Up @@ -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")
Expand All @@ -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:
Expand Down
14 changes: 14 additions & 0 deletions tests/test_scoped_nodes.py
Original file line number Diff line number Diff line change
Expand Up @@ -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(
"""
Expand Down

0 comments on commit 7aa5c2d

Please sign in to comment.