Skip to content

Commit

Permalink
Fix astroid error for custom next method (#7622)
Browse files Browse the repository at this point in the history
* short-circuit if next method doesnt have args
* check for builtins.next qname
* add inference confidence level
  • Loading branch information
clavedeluna authored and Pierre-Sassoulas committed Nov 17, 2022
1 parent 6d66b18 commit 8b0bb16
Show file tree
Hide file tree
Showing 4 changed files with 65 additions and 14 deletions.
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

0 comments on commit 8b0bb16

Please sign in to comment.