Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Fix astroid error for custom next method #7622

Merged
merged 7 commits into from Nov 3, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
3 changes: 3 additions & 0 deletions doc/whatsnew/fragments/7610.bugfix
@@ -0,0 +1,3 @@
Fixes edge case of custom method named ``next`` raised an astroid error.

Closes #7610
12 changes: 9 additions & 3 deletions pylint/checkers/refactoring/refactoring_checker.py
Expand Up @@ -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(
Expand Down Expand Up @@ -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
Expand All @@ -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,
Expand Down
50 changes: 46 additions & 4 deletions tests/functional/s/stop_iteration_inside_generator.py
Expand Up @@ -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():
Expand All @@ -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():
Expand All @@ -31,13 +35,15 @@ 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():
g = gen_ok()
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
Expand All @@ -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
Expand All @@ -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
Expand All @@ -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()

Expand All @@ -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

Expand All @@ -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
Expand All @@ -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
Expand All @@ -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)
14 changes: 7 additions & 7 deletions 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