[Backport maintenance/4.1.x] Fix cyclic inference by constraints (#2998)
Fix cyclic inference by constraints (#2984)
(cherry picked from commit 1e683cc98cb7d3f6b5d8eb32390a6aea85e9111e)
Co-authored-by: Zen Lee <53538590+zenlyj@users.noreply.github.com>
diff --git a/ChangeLog b/ChangeLog
index a6d6335..4001a54 100644
--- a/ChangeLog
+++ b/ChangeLog
@@ -12,6 +12,8 @@
============================
Release date: TBA
+* Fix infinite recursion caused by cyclic inference in ``Constraint``.
+
* Fix ``RecursionError`` in ``_compute_mro()`` when circular class hierarchies
are created through runtime name rebinding. Circular bases are now resolved
to the original class instead of recursing.
diff --git a/astroid/bases.py b/astroid/bases.py
index b7fd80c..cce31a0 100644
--- a/astroid/bases.py
+++ b/astroid/bases.py
@@ -183,7 +183,10 @@
if not constraint_stmt.parent_of(stmt):
stmt_constraints.update(potential_constraints)
for inf in stmt.infer(context=context):
- if all(constraint.satisfied_by(inf) for constraint in stmt_constraints):
+ if all(
+ constraint.satisfied_by(inf, context)
+ for constraint in stmt_constraints
+ ):
yield inf
inferred = True
else:
diff --git a/astroid/constraint.py b/astroid/constraint.py
index 0dfecc3..1756031 100644
--- a/astroid/constraint.py
+++ b/astroid/constraint.py
@@ -12,6 +12,7 @@
from typing import TYPE_CHECKING
from astroid import helpers, nodes, util
+from astroid.context import InferenceContext
from astroid.exceptions import AstroidTypeError, InferenceError, MroError
from astroid.typing import InferenceResult
@@ -47,7 +48,9 @@
"""
@abstractmethod
- def satisfied_by(self, inferred: InferenceResult) -> bool:
+ def satisfied_by(
+ self, inferred: InferenceResult, context: InferenceContext
+ ) -> bool:
"""Return True if this constraint is satisfied by the given inferred value."""
@@ -76,7 +79,9 @@
return None
- def satisfied_by(self, inferred: InferenceResult) -> bool:
+ def satisfied_by(
+ self, inferred: InferenceResult, context: InferenceContext
+ ) -> bool:
"""Return True if this constraint is satisfied by the given inferred value."""
# Assume true if uninferable
if inferred is util.Uninferable:
@@ -112,7 +117,9 @@
return None
- def satisfied_by(self, inferred: InferenceResult) -> bool:
+ def satisfied_by(
+ self, inferred: InferenceResult, context: InferenceContext
+ ) -> bool:
"""Return True for uninferable results, or depending on negate flag:
- negate=False: satisfied if boolean value is True
@@ -153,7 +160,9 @@
return None
- def satisfied_by(self, inferred: InferenceResult) -> bool:
+ def satisfied_by(
+ self, inferred: InferenceResult, context: InferenceContext
+ ) -> bool:
"""Return True for uninferable results, or depending on negate flag:
- negate=False: satisfied when inferred is an instance of the checked types.
@@ -163,8 +172,8 @@
return True
try:
- types = helpers.class_or_tuple_to_container(self.classinfo)
- matches_checked_types = helpers.object_isinstance(inferred, types)
+ types = helpers.class_or_tuple_to_container(self.classinfo, context)
+ matches_checked_types = helpers.object_isinstance(inferred, types, context)
if matches_checked_types is util.Uninferable:
return True
@@ -204,7 +213,9 @@
return None
- def satisfied_by(self, inferred: InferenceResult) -> bool:
+ def satisfied_by(
+ self, inferred: InferenceResult, context: InferenceContext
+ ) -> bool:
"""Return True for uninferable/ambiguous results, or depending on negate flag:
- negate=False: satisfied when both operands are equal.
@@ -215,7 +226,7 @@
if inferred is util.Uninferable:
return True
- operand_inferred = util.safe_infer(self.operand)
+ operand_inferred = util.safe_infer(self.operand, context)
if operand_inferred is util.Uninferable or operand_inferred is None:
return True