[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