diff options
-rw-r--r-- | ChangeLog | 4 | ||||
-rw-r--r-- | astroid/scoped_nodes.py | 3 | ||||
-rw-r--r-- | tests/testdata/python3/data/metaclass_recursion/__init__.py | 0 | ||||
-rw-r--r-- | tests/testdata/python3/data/metaclass_recursion/monkeypatch.py | 17 | ||||
-rw-r--r-- | tests/testdata/python3/data/metaclass_recursion/parent.py | 3 | ||||
-rw-r--r-- | tests/unittest_inference.py | 9 |
6 files changed, 35 insertions, 1 deletions
@@ -10,6 +10,10 @@ Release Date: TBA Close PyCQA/pylint#3417 +* Prevent a recursion error to happen when inferring the declared metaclass of a class + + Close #749 + * Raise ``AttributeInferenceError`` when ``getattr()`` receives an empty name Close PyCQA/pylint#2991 diff --git a/astroid/scoped_nodes.py b/astroid/scoped_nodes.py index 8070f17d..60c6499b 100644 --- a/astroid/scoped_nodes.py +++ b/astroid/scoped_nodes.py @@ -2456,7 +2456,8 @@ class ClassDef(mixins.FilterStmtsMixin, LocalsDictNodeNG, node_classes.Statement """Search the given name in the implicit and the explicit metaclass.""" attrs = set() implicit_meta = self.implicit_metaclass() - metaclass = self.metaclass() + context = contextmod.copy_context(context) + metaclass = self.metaclass(context=context) for cls in {implicit_meta, metaclass}: if cls and cls != self and isinstance(cls, ClassDef): cls_attributes = self._get_attribute_from_metaclass(cls, name, context) diff --git a/tests/testdata/python3/data/metaclass_recursion/__init__.py b/tests/testdata/python3/data/metaclass_recursion/__init__.py new file mode 100644 index 00000000..e69de29b --- /dev/null +++ b/tests/testdata/python3/data/metaclass_recursion/__init__.py diff --git a/tests/testdata/python3/data/metaclass_recursion/monkeypatch.py b/tests/testdata/python3/data/metaclass_recursion/monkeypatch.py new file mode 100644 index 00000000..757bb3f8 --- /dev/null +++ b/tests/testdata/python3/data/metaclass_recursion/monkeypatch.py @@ -0,0 +1,17 @@ +# https://github.com/PyCQA/astroid/issues/749 +# Not an actual module but allows us to reproduce the issue +from tests.testdata.python3.data.metaclass_recursion import parent + +class MonkeyPatchClass(parent.OriginalClass): + _original_class = parent.OriginalClass + + @classmethod + def patch(cls): + if parent.OriginalClass != MonkeyPatchClass: + cls._original_class = parent.OriginalClass + parent.OriginalClass = MonkeyPatchClass + + @classmethod + def unpatch(cls): + if parent.OriginalClass == MonkeyPatchClass: + parent.OriginalClass = cls._original_class diff --git a/tests/testdata/python3/data/metaclass_recursion/parent.py b/tests/testdata/python3/data/metaclass_recursion/parent.py new file mode 100644 index 00000000..5cff73e0 --- /dev/null +++ b/tests/testdata/python3/data/metaclass_recursion/parent.py @@ -0,0 +1,3 @@ +# https://github.com/PyCQA/astroid/issues/749 +class OriginalClass: + pass diff --git a/tests/unittest_inference.py b/tests/unittest_inference.py index f9e95e40..dd833e29 100644 --- a/tests/unittest_inference.py +++ b/tests/unittest_inference.py @@ -5755,5 +5755,14 @@ def test_infer_first_argument_of_static_method_in_metaclass(): assert inferred is util.Uninferable +def test_recursion_error_metaclass_monkeypatching(): + module = resources.build_file( + "data/metaclass_recursion/monkeypatch.py", "data.metaclass_recursion" + ) + cls = next(module.igetattr("MonkeyPatchClass")) + assert isinstance(cls, nodes.ClassDef) + assert cls.declared_metaclass() is None + + if __name__ == "__main__": unittest.main() |