From 5668e5610e6c645655529d471ae8a7da157bcb2c Mon Sep 17 00:00:00 2001 From: Dani Alcala <112832187+clavedeluna@users.noreply.github.com> Date: Thu, 3 Nov 2022 14:34:16 -0300 Subject: [PATCH] Fix astroid error for custom ``next`` method (#7622) * short-circuit if next method doesnt have args * check for builtins.next qname * add inference confidence level --- doc/whatsnew/fragments/7610.bugfix | 3 ++ .../refactoring/refactoring_checker.py | 12 +++-- .../s/stop_iteration_inside_generator.py | 50 +++++++++++++++++-- .../s/stop_iteration_inside_generator.txt | 14 +++--- 4 files changed, 65 insertions(+), 14 deletions(-) create mode 100644 doc/whatsnew/fragments/7610.bugfix diff --git a/doc/whatsnew/fragments/7610.bugfix b/doc/whatsnew/fragments/7610.bugfix new file mode 100644 index 0000000000..3eb49fcbb4 --- /dev/null +++ b/doc/whatsnew/fragments/7610.bugfix @@ -0,0 +1,3 @@ +Fixes edge case of custom method named ``next`` raised an astroid error. + +Closes #7610 diff --git a/pylint/checkers/refactoring/refactoring_checker.py b/pylint/checkers/refactoring/refactoring_checker.py index 005f0e03c9..665a170c58 100644 --- a/pylint/checkers/refactoring/refactoring_checker.py +++ b/pylint/checkers/refactoring/refactoring_checker.py @@ -979,7 +979,7 @@ def _check_stop_iteration_inside_generator(self, node: nodes.Raise) -> None: if not exc or not isinstance(exc, (bases.Instance, nodes.ClassDef)): return if self._check_exception_inherit_from_stopiteration(exc): - self.add_message("stop-iteration-return", node=node) + self.add_message("stop-iteration-return", node=node, confidence=INFERENCE) @staticmethod def _check_exception_inherit_from_stopiteration( @@ -1135,7 +1135,11 @@ def _looks_like_infinite_iterator(param: nodes.NodeNG) -> bool: return inferred = utils.safe_infer(node.func) - if getattr(inferred, "name", "") == "next": + + if ( + isinstance(inferred, nodes.FunctionDef) + and inferred.qname() == "builtins.next" + ): frame = node.frame(future=True) # The next builtin can only have up to two # positional arguments and no keyword arguments @@ -1147,7 +1151,9 @@ def _looks_like_infinite_iterator(param: nodes.NodeNG) -> bool: and not utils.node_ignores_exception(node, StopIteration) and not _looks_like_infinite_iterator(node.args[0]) ): - self.add_message("stop-iteration-return", node=node) + self.add_message( + "stop-iteration-return", node=node, confidence=INFERENCE + ) def _check_nested_blocks( self, diff --git a/tests/functional/s/stop_iteration_inside_generator.py b/tests/functional/s/stop_iteration_inside_generator.py index 8620d958c6..945a952bd0 100644 --- a/tests/functional/s/stop_iteration_inside_generator.py +++ b/tests/functional/s/stop_iteration_inside_generator.py @@ -4,17 +4,20 @@ # pylint: disable=missing-docstring,invalid-name,import-error, try-except-raise, wrong-import-position,not-callable,raise-missing-from import asyncio + class RebornStopIteration(StopIteration): """ A class inheriting from StopIteration exception """ + # This one is ok def gen_ok(): yield 1 yield 2 yield 3 + # pylint should warn about this one # because of a direct raising of StopIteration inside generator def gen_stopiter(): @@ -23,6 +26,7 @@ def gen_stopiter(): yield 3 raise StopIteration # [stop-iteration-return] + # pylint should warn about this one # because of a direct raising of an exception inheriting from StopIteration inside generator def gen_stopiterchild(): @@ -31,6 +35,7 @@ def gen_stopiterchild(): yield 3 raise RebornStopIteration # [stop-iteration-return] + # pylint should warn here # because of the possibility that next raises a StopIteration exception def gen_next_raises_stopiter(): @@ -38,6 +43,7 @@ def gen_next_raises_stopiter(): while True: yield next(g) # [stop-iteration-return] + # This one is the same as gen_next_raises_stopiter # but is ok because the next function is inside # a try/except block handling StopIteration @@ -49,6 +55,7 @@ def gen_next_inside_try_except(): except StopIteration: return + # This one is the same as gen_next_inside_try_except # but is not ok because the next function is inside # a try/except block that don't handle StopIteration @@ -60,6 +67,7 @@ def gen_next_inside_wrong_try_except(): except ValueError: return + # This one is the same as gen_next_inside_try_except # but is not ok because the next function is inside # a try/except block that handle StopIteration but reraise it @@ -71,11 +79,13 @@ def gen_next_inside_wrong_try_except2(): except StopIteration: raise StopIteration # [stop-iteration-return] + # Those two last are ok def gen_in_for(): for el in gen_ok(): yield el + def gen_yield_from(): yield from gen_ok() @@ -84,7 +94,7 @@ def gen_dont_crash_on_no_exception(): g = gen_ok() while True: try: - yield next(g) # [stop-iteration-return] + yield next(g) # [stop-iteration-return] except ValueError: raise @@ -97,7 +107,7 @@ def gen_dont_crash_on_uninferable(): # https://github.com/PyCQA/pylint/issues/1830 def gen_next_with_sentinel(): - yield next([], 42) # No bad return + yield next([], 42) # No bad return from itertools import count @@ -113,6 +123,7 @@ def generator_using_next(): class SomeClassWithNext: def next(self): return iter([1, 2, 3]) + def some_gen(self): for value in self.next(): yield value @@ -122,8 +133,39 @@ def some_gen(self): def something_invalid(): - raise Exception('cannot iterate this') + raise Exception("cannot iterate this") def invalid_object_passed_to_next(): - yield next(something_invalid()) # [stop-iteration-return] + yield next(something_invalid()) # [stop-iteration-return] + + +# pylint: disable=redefined-builtin,too-many-function-args +def safeiter(it): + """Regression test for issue #7610 when ``next`` builtin is redefined""" + + def next(): + while True: + try: + return next(it) + except StopIteration: + raise + + it = iter(it) + while True: + yield next() + +def other_safeiter(it): + """Regression test for issue #7610 when ``next`` builtin is redefined""" + + def next(*things): + print(*things) + while True: + try: + return next(it) + except StopIteration: + raise + + it = iter(it) + while True: + yield next(1, 2) diff --git a/tests/functional/s/stop_iteration_inside_generator.txt b/tests/functional/s/stop_iteration_inside_generator.txt index 74ddc6d4e1..c351012b56 100644 --- a/tests/functional/s/stop_iteration_inside_generator.txt +++ b/tests/functional/s/stop_iteration_inside_generator.txt @@ -1,7 +1,7 @@ -stop-iteration-return:24:4:24:23:gen_stopiter:Do not raise StopIteration in generator, use return statement instead:UNDEFINED -stop-iteration-return:32:4:32:29:gen_stopiterchild:Do not raise StopIteration in generator, use return statement instead:UNDEFINED -stop-iteration-return:39:14:39:21:gen_next_raises_stopiter:Do not raise StopIteration in generator, use return statement instead:UNDEFINED -stop-iteration-return:59:18:59:25:gen_next_inside_wrong_try_except:Do not raise StopIteration in generator, use return statement instead:UNDEFINED -stop-iteration-return:72:12:72:31:gen_next_inside_wrong_try_except2:Do not raise StopIteration in generator, use return statement instead:UNDEFINED -stop-iteration-return:87:18:87:25:gen_dont_crash_on_no_exception:Do not raise StopIteration in generator, use return statement instead:UNDEFINED -stop-iteration-return:129:10:129:35:invalid_object_passed_to_next:Do not raise StopIteration in generator, use return statement instead:UNDEFINED +stop-iteration-return:27:4:27:23:gen_stopiter:Do not raise StopIteration in generator, use return statement instead:INFERENCE +stop-iteration-return:36:4:36:29:gen_stopiterchild:Do not raise StopIteration in generator, use return statement instead:INFERENCE +stop-iteration-return:44:14:44:21:gen_next_raises_stopiter:Do not raise StopIteration in generator, use return statement instead:INFERENCE +stop-iteration-return:66:18:66:25:gen_next_inside_wrong_try_except:Do not raise StopIteration in generator, use return statement instead:INFERENCE +stop-iteration-return:80:12:80:31:gen_next_inside_wrong_try_except2:Do not raise StopIteration in generator, use return statement instead:INFERENCE +stop-iteration-return:97:18:97:25:gen_dont_crash_on_no_exception:Do not raise StopIteration in generator, use return statement instead:INFERENCE +stop-iteration-return:140:10:140:35:invalid_object_passed_to_next:Do not raise StopIteration in generator, use return statement instead:INFERENCE