Skip to content

Commit

Permalink
Log RecursionError out as warning during inference
Browse files Browse the repository at this point in the history
  • Loading branch information
jacobtylerwalls committed May 12, 2024
1 parent fd6790b commit 2e338eb
Show file tree
Hide file tree
Showing 15 changed files with 173 additions and 32 deletions.
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

Check warning on line 379 in pylint/checkers/base/basic_checker.py

View check run for this annotation

Codecov / codecov/patch

pylint/checkers/base/basic_checker.py#L377-L379

Added lines #L377 - L379 were not covered by tests
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

Check warning on line 616 in pylint/checkers/base/basic_checker.py

View check run for this annotation

Codecov / codecov/patch

pylint/checkers/base/basic_checker.py#L614-L616

Added lines #L614 - L616 were not covered by tests

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

Check warning on line 850 in pylint/checkers/base/basic_checker.py

View check run for this annotation

Codecov / codecov/patch

pylint/checkers/base/basic_checker.py#L848-L850

Added lines #L848 - L850 were not covered by tests
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
36 changes: 32 additions & 4 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()

Check warning on line 260 in pylint/checkers/classes/class_checker.py

View check run for this annotation

Codecov / codecov/patch

pylint/checkers/classes/class_checker.py#L259-L260

Added lines #L259 - L260 were not covered by tests
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

Check warning on line 417 in pylint/checkers/classes/class_checker.py

View check run for this annotation

Codecov / codecov/patch

pylint/checkers/classes/class_checker.py#L415-L417

Added lines #L415 - L417 were not covered by tests
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

Check warning on line 446 in pylint/checkers/classes/class_checker.py

View check run for this annotation

Codecov / codecov/patch

pylint/checkers/classes/class_checker.py#L444-L446

Added lines #L444 - L446 were not covered by tests
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

Check warning on line 480 in pylint/checkers/classes/class_checker.py

View check run for this annotation

Codecov / codecov/patch

pylint/checkers/classes/class_checker.py#L478-L480

Added lines #L478 - L480 were not covered by tests
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

Check warning on line 502 in pylint/checkers/classes/class_checker.py

View check run for this annotation

Codecov / codecov/patch

pylint/checkers/classes/class_checker.py#L500-L502

Added lines #L500 - L502 were not covered by tests
if isinstance(inferred, nodes.ClassDef):
other_slots = inferred.slots()
if all(
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

Check warning on line 1299 in pylint/checkers/classes/class_checker.py

View check run for this annotation

Codecov / codecov/patch

pylint/checkers/classes/class_checker.py#L1297-L1299

Added lines #L1297 - L1299 were not covered by tests
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

Check warning on line 1537 in pylint/checkers/classes/class_checker.py

View check run for this annotation

Codecov / codecov/patch

pylint/checkers/classes/class_checker.py#L1535-L1537

Added lines #L1535 - L1537 were not covered by tests
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

Check warning on line 2223 in pylint/checkers/classes/class_checker.py

View check run for this annotation

Codecov / codecov/patch

pylint/checkers/classes/class_checker.py#L2221-L2223

Added lines #L2221 - L2223 were not covered by tests
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

Check warning on line 2380 in pylint/checkers/classes/class_checker.py

View check run for this annotation

Codecov / codecov/patch

pylint/checkers/classes/class_checker.py#L2378-L2380

Added lines #L2378 - L2380 were not covered by tests
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

Check warning on line 50 in pylint/checkers/classes/special_methods_checker.py

View check run for this annotation

Codecov / codecov/patch

pylint/checkers/classes/special_methods_checker.py#L48-L50

Added lines #L48 - L50 were not covered by tests
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

Check warning on line 60 in pylint/checkers/classes/special_methods_checker.py

View check run for this annotation

Codecov / codecov/patch

pylint/checkers/classes/special_methods_checker.py#L58-L60

Added lines #L58 - L60 were not covered by tests


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

Check warning on line 113 in pylint/checkers/newstyle.py

View check run for this annotation

Codecov / codecov/patch

pylint/checkers/newstyle.py#L111-L113

Added lines #L111 - L113 were not covered by tests

# 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

Check warning on line 149 in pylint/checkers/refactoring/implicit_booleaness_checker.py

View check run for this annotation

Codecov / codecov/patch

pylint/checkers/refactoring/implicit_booleaness_checker.py#L147-L149

Added lines #L147 - L149 were not covered by tests
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()

Check warning on line 596 in pylint/checkers/stdlib.py

View check run for this annotation

Codecov / codecov/patch

pylint/checkers/stdlib.py#L596

Added line #L596 was not covered by tests
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()

Check warning on line 719 in pylint/checkers/stdlib.py

View check run for this annotation

Codecov / codecov/patch

pylint/checkers/stdlib.py#L718-L719

Added lines #L718 - L719 were not covered by tests
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

Check warning on line 776 in pylint/checkers/stdlib.py

View check run for this annotation

Codecov / codecov/patch

pylint/checkers/stdlib.py#L774-L776

Added lines #L774 - L776 were not covered by tests
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

Check warning on line 475 in pylint/checkers/strings.py

View check run for this annotation

Codecov / codecov/patch

pylint/checkers/strings.py#L473-L475

Added lines #L473 - L475 were not covered by tests
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

Check warning on line 642 in pylint/checkers/strings.py

View check run for this annotation

Codecov / codecov/patch

pylint/checkers/strings.py#L640-L642

Added lines #L640 - L642 were not covered by tests


class StringConstantChecker(BaseTokenChecker, BaseRawFileChecker):
Expand Down
39 changes: 26 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

Check warning on line 799 in pylint/checkers/typecheck.py

View check run for this annotation

Codecov / codecov/patch

pylint/checkers/typecheck.py#L797-L799

Added lines #L797 - L799 were not covered by tests
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

Check warning on line 1096 in pylint/checkers/typecheck.py

View check run for this annotation

Codecov / codecov/patch

pylint/checkers/typecheck.py#L1095-L1096

Added lines #L1095 - L1096 were not covered by tests
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

Check warning on line 1371 in pylint/checkers/typecheck.py

View check run for this annotation

Codecov / codecov/patch

pylint/checkers/typecheck.py#L1369-L1371

Added lines #L1369 - L1371 were not covered by tests

if all(
isinstance(return_node, util.UninferableBase)
Expand Down Expand Up @@ -1673,21 +1683,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()

Check warning on line 1703 in pylint/checkers/typecheck.py

View workflow job for this annotation

GitHub Actions / pylint

W0717

try clause contains 8 statements, expected at most 7

Check warning on line 1703 in pylint/checkers/typecheck.py

View check run for this annotation

Codecov / codecov/patch

pylint/checkers/typecheck.py#L1702-L1703

Added lines #L1702 - L1703 were not covered by tests

return True

Expand Down
Loading

0 comments on commit 2e338eb

Please sign in to comment.