Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions doc/whatsnew/fragments/9139.bugfix
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
Log RecursionError out as warning during inference.

Closes #9139
9 changes: 9 additions & 0 deletions pylint/checkers/base/basic_checker.py
Original file line number Diff line number Diff line change
Expand Up @@ -374,6 +374,9 @@ def _check_using_constant_test(
call_inferred = list(inferred.infer_call_result(node))
except astroid.InferenceError:
call_inferred = None
except RecursionError:
utils.warn_on_recursion_error()
call_inferred = None
if call_inferred:
self.add_message(
"missing-parentheses-for-call-in-test",
Expand Down Expand Up @@ -608,6 +611,9 @@ def is_iterable(internal_node: nodes.NodeNG) -> bool:
value = next(default.infer())
except astroid.InferenceError:
continue
except RecursionError:
utils.warn_on_recursion_error()
continue

if (
isinstance(value, astroid.Instance)
Expand Down Expand Up @@ -839,6 +845,9 @@ def _check_reversed(self, node: nodes.Call) -> None:
func = next(node.args[0].func.infer())
except astroid.InferenceError:
return
except RecursionError:
utils.warn_on_recursion_error()
return
if getattr(
func, "name", None
) == "iter" and utils.is_builtin_object(func):
Expand Down
2 changes: 1 addition & 1 deletion pylint/checkers/base/comparison_checker.py
Original file line number Diff line number Diff line change
Expand Up @@ -148,7 +148,7 @@ def _is_float_nan(node: nodes.NodeNG) -> bool:
if isinstance(node, nodes.Call) and len(node.args) == 1:
if (
node.args[0].value.lower() == "nan"
and node.inferred()[0].pytype() == "builtins.float"
and utils.safe_infer(node).pytype() == "builtins.float"
):
return True
return False
Expand Down
38 changes: 33 additions & 5 deletions pylint/checkers/classes/class_checker.py
Original file line number Diff line number Diff line change
Expand Up @@ -252,9 +252,12 @@ def _has_different_parameters_default_value(
if is_same_fn is None:
# If the default value comparison is unhandled, assume the value is different
return True
if not is_same_fn(original_default, overridden_default):
# Two args with same type but different values
return True
try:
if not is_same_fn(original_default, overridden_default):
# Two args with same type but different values
return True
except RecursionError:
utils.warn_on_recursion_error()
return False


Expand Down Expand Up @@ -409,6 +412,9 @@ def _has_data_descriptor(cls: nodes.ClassDef, attr: str) -> bool:
except astroid.InferenceError:
# Can't infer, avoid emitting a false positive in this case.
return True
except RecursionError:
utils.warn_on_recursion_error()
return True
return False


Expand All @@ -435,6 +441,9 @@ def _called_in_methods(
bound = next(call.func.infer())
except (astroid.InferenceError, StopIteration):
continue
except RecursionError:
utils.warn_on_recursion_error()
continue
if not isinstance(bound, astroid.BoundMethod):
continue
func_obj = bound._proxied
Expand Down Expand Up @@ -466,6 +475,9 @@ def _is_attribute_property(name: str, klass: nodes.ClassDef) -> bool:
inferred = next(attr.infer())
except astroid.InferenceError:
continue
except RecursionError:
utils.warn_on_recursion_error()
continue
if isinstance(inferred, nodes.FunctionDef) and decorated_with_property(
inferred
):
Expand All @@ -483,7 +495,11 @@ def _is_attribute_property(name: str, klass: nodes.ClassDef) -> bool:
def _has_same_layout_slots(
slots: list[nodes.Const | None], assigned_value: nodes.Name
) -> bool:
inferred = next(assigned_value.infer())
try:
inferred = next(assigned_value.infer())
except RecursionError:
utils.warn_on_recursion_error()
return False
if isinstance(inferred, nodes.ClassDef):
other_slots = inferred.slots()
if all(
Expand Down Expand Up @@ -1216,7 +1232,7 @@ def _check_attribute_defined_outside_init(self, cnode: nodes.ClassDef) -> None:
"attribute-defined-outside-init", args=attr, node=node
)

# pylint: disable = too-many-branches
# pylint: disable = too-many-branches, too-many-return-statements
def visit_functiondef(self, node: nodes.FunctionDef) -> None:
"""Check method arguments, overriding."""
# ignore actual functions
Expand Down Expand Up @@ -1278,6 +1294,9 @@ def visit_functiondef(self, node: nodes.FunctionDef) -> None:
inferred = next(inferred.infer_call_result(inferred))
except astroid.InferenceError:
return
except RecursionError:
utils.warn_on_recursion_error()
return
try:
if (
isinstance(inferred, (astroid.Instance, nodes.ClassDef))
Expand Down Expand Up @@ -1513,6 +1532,9 @@ def _check_slots(self, node: nodes.ClassDef) -> None:
self._check_slots_elt(elt, node)
except astroid.InferenceError:
continue
except RecursionError:
utils.warn_on_recursion_error()
continue
self._check_redefined_slots(node, slots, values)

def _check_redefined_slots(
Expand Down Expand Up @@ -2196,6 +2218,9 @@ def _check_init(self, node: nodes.FunctionDef, klass_node: nodes.ClassDef) -> No
)
except astroid.InferenceError:
continue
except RecursionError:
utils.warn_on_recursion_error()
continue
for klass, method in not_called_yet.items():
# Check if the init of the class that defines this init has already
# been called.
Expand Down Expand Up @@ -2350,4 +2375,7 @@ def _ancestors_to_call(
to_call[base_node] = init_node
except astroid.InferenceError:
continue
except RecursionError:
utils.warn_on_recursion_error()
continue
return to_call
7 changes: 7 additions & 0 deletions pylint/checkers/classes/special_methods_checker.py
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@
is_function_body_ellipsis,
only_required_for_messages,
safe_infer,
warn_on_recursion_error,
)
from pylint.lint.pylinter import PyLinter

Expand All @@ -44,13 +45,19 @@ def _safe_infer_call_result(
return None # inference failed
except StopIteration:
return None # no values inferred
except RecursionError:
warn_on_recursion_error()
return None
try:
next(inferit)
return None # there is ambiguity on the inferred node
except astroid.InferenceError:
return None # there is some kind of ambiguity
except StopIteration:
return value
except RecursionError:
warn_on_recursion_error()
return value


class SpecialMethodsChecker(BaseChecker):
Expand Down
4 changes: 4 additions & 0 deletions pylint/checkers/newstyle.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@
has_known_bases,
node_frame_class,
only_required_for_messages,
warn_on_recursion_error,
)
from pylint.typing import MessageDefinitionTuple

Expand Down Expand Up @@ -107,6 +108,9 @@ def visit_functiondef(self, node: nodes.FunctionDef) -> None:
supcls = call.args and next(call.args[0].infer(), None)
except astroid.InferenceError:
continue
except RecursionError:
warn_on_recursion_error()
continue

# If the supcls is in the ancestors of klass super can be used to skip
# a step in the mro() and get a method from a higher parent
Expand Down
3 changes: 3 additions & 0 deletions pylint/checkers/refactoring/implicit_booleaness_checker.py
Original file line number Diff line number Diff line change
Expand Up @@ -144,6 +144,9 @@ def visit_call(self, node: nodes.Call) -> None:
except astroid.InferenceError:
# Probably undefined-variable, abort check
return
except RecursionError:
utils.warn_on_recursion_error()
return
mother_classes = self.base_names_of_instance(instance)
affected_by_pep8 = any(
t in mother_classes for t in ("str", "tuple", "list", "set")
Expand Down
4 changes: 2 additions & 2 deletions pylint/checkers/refactoring/refactoring_checker.py
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@
from pylint import checkers
from pylint.checkers import utils
from pylint.checkers.base.basic_error_checker import _loop_exits_early
from pylint.checkers.utils import node_frame_class
from pylint.checkers.utils import node_frame_class, safe_infer
from pylint.interfaces import HIGH, INFERENCE, Confidence

if TYPE_CHECKING:
Expand Down Expand Up @@ -1997,7 +1997,7 @@ def _is_node_return_ended(self, node: nodes.NodeNG) -> bool:
return True
if isinstance(node, nodes.Call):
try:
funcdef_node = node.func.inferred()[0]
funcdef_node = safe_infer(node.func)
if self._is_function_def_never_returning(funcdef_node):
return True
except astroid.InferenceError:
Expand Down
7 changes: 7 additions & 0 deletions pylint/checkers/stdlib.py
Original file line number Diff line number Diff line change
Expand Up @@ -592,6 +592,8 @@ def _check_shallow_copy_environ(self, node: nodes.Call) -> None:
confidence = INFERENCE
try:
inferred_args = arg.inferred()
except RecursionError:
utils.warn_on_recursion_error()
except astroid.InferenceError:
return
for inferred in inferred_args:
Expand Down Expand Up @@ -713,6 +715,8 @@ def _check_lru_cache_decorators(self, node: nodes.FunctionDef) -> None:
break
except astroid.InferenceError:
pass
except RecursionError:
utils.warn_on_recursion_error()
for lru_cache_node in lru_cache_nodes:
self.add_message(
"method-cache-max-size-none",
Expand Down Expand Up @@ -767,6 +771,9 @@ def _check_datetime(self, node: nodes.NodeNG) -> None:
inferred = next(node.infer())
except astroid.InferenceError:
return
except RecursionError:
utils.warn_on_recursion_error()
return
if isinstance(inferred, astroid.Instance) and inferred.qname() in {
"_pydatetime.time",
"datetime.time",
Expand Down
6 changes: 6 additions & 0 deletions pylint/checkers/strings.py
Original file line number Diff line number Diff line change
Expand Up @@ -470,6 +470,9 @@ def _check_new_format(self, node: nodes.Call, func: bases.BoundMethod) -> None:
strnode = next(func.bound.infer())
except astroid.InferenceError:
return
except RecursionError:
utils.warn_on_recursion_error()
return
if not (isinstance(strnode, nodes.Const) and isinstance(strnode.value, str)):
return
try:
Expand Down Expand Up @@ -634,6 +637,9 @@ def _check_new_format_specifiers(
except astroid.InferenceError:
# can't check further if we can't infer it
break
except RecursionError:
utils.warn_on_recursion_error()
break


class StringConstantChecker(BaseTokenChecker, BaseRawFileChecker):
Expand Down
40 changes: 27 additions & 13 deletions pylint/checkers/typecheck.py
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,7 @@
supports_getitem,
supports_membership_test,
supports_setitem,
warn_on_recursion_error,
)
from pylint.constants import PY310_PLUS
from pylint.interfaces import HIGH, INFERENCE
Expand Down Expand Up @@ -793,6 +794,9 @@ def _infer_from_metaclass_constructor(
inferred = next(func.infer_call_result(func, context), None)
except astroid.InferenceError:
return None
except RecursionError:
utils.warn_on_recursion_error()
return None
return inferred or None


Expand Down Expand Up @@ -1087,6 +1091,9 @@ def visit_attribute(

try:
inferred = list(node.expr.infer())
except RecursionError:
warn_on_recursion_error()
return
except astroid.InferenceError:
return

Expand Down Expand Up @@ -1359,6 +1366,9 @@ def _check_uninferable_call(self, node: nodes.Call) -> None:
call_results = list(attr.infer_call_result(node))
except astroid.InferenceError:
continue
except RecursionError:
utils.warn_on_recursion_error()
continue

if all(
isinstance(return_node, util.UninferableBase)
Expand Down Expand Up @@ -1651,6 +1661,7 @@ def visit_call(self, node: nodes.Call) -> None:
confidence=INFERENCE,
)

# pylint: disable=too-many-try-statements
@staticmethod
def _keyword_argument_is_in_all_decorator_returns(
func: nodes.FunctionDef, keyword: str
Expand All @@ -1673,21 +1684,24 @@ def _keyword_argument_is_in_all_decorator_returns(
if not isinstance(inferred, nodes.FunctionDef):
return False

for return_value in inferred.infer_call_result(caller=None):
# infer_call_result() returns nodes.Const.None for None return values
# so this also catches non-returning decorators
if not isinstance(return_value, nodes.FunctionDef):
return False

# If the return value uses a kwarg the keyword will be consumed
if return_value.args.kwarg:
continue
try:
for return_value in inferred.infer_call_result(caller=None):
# infer_call_result() returns nodes.Const.None for None return values
# so this also catches non-returning decorators
if not isinstance(return_value, nodes.FunctionDef):
return False

# If the return value uses a kwarg the keyword will be consumed
if return_value.args.kwarg:
continue

# Check if the keyword is another type of argument
if return_value.args.is_argument(keyword):
continue
# Check if the keyword is another type of argument
if return_value.args.is_argument(keyword):
continue

return False
return False
except RecursionError:
utils.warn_on_recursion_error()

return True

Expand Down
Loading