summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--ChangeLog4
-rw-r--r--astroid/scoped_nodes.py3
-rw-r--r--tests/testdata/python3/data/metaclass_recursion/__init__.py0
-rw-r--r--tests/testdata/python3/data/metaclass_recursion/monkeypatch.py17
-rw-r--r--tests/testdata/python3/data/metaclass_recursion/parent.py3
-rw-r--r--tests/unittest_inference.py9
6 files changed, 35 insertions, 1 deletions
diff --git a/ChangeLog b/ChangeLog
index d9708b07..884e83b2 100644
--- a/ChangeLog
+++ b/ChangeLog
@@ -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()