summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorDani Alcala <112832187+clavedeluna@users.noreply.github.com>2022-11-03 14:34:16 -0300
committerPierre Sassoulas <pierre.sassoulas@gmail.com>2022-11-17 14:03:15 +0100
commit8b0bb16f614ceca43601e7765f03c7baa80bcf9a (patch)
tree5998b0d4b41b50d016c00f9beba74c8f9af0abd5
parent6d66b183180744b1649ea14b15df4e2537ff6821 (diff)
downloadpylint-git-8b0bb16f614ceca43601e7765f03c7baa80bcf9a.tar.gz
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
-rw-r--r--doc/whatsnew/fragments/7610.bugfix3
-rw-r--r--pylint/checkers/refactoring/refactoring_checker.py12
-rw-r--r--tests/functional/s/stop_iteration_inside_generator.py50
-rw-r--r--tests/functional/s/stop_iteration_inside_generator.txt14
4 files changed, 65 insertions, 14 deletions
diff --git a/doc/whatsnew/fragments/7610.bugfix b/doc/whatsnew/fragments/7610.bugfix
new file mode 100644
index 000000000..3eb49fcbb
--- /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 005f0e03c..665a170c5 100644
--- a/pylint/checkers/refactoring/refactoring_checker.py
+++ b/pylint/checkers/refactoring/refactoring_checker.py
@@ -979,7 +979,7 @@ class RefactoringChecker(checkers.BaseTokenChecker):
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 @@ class RefactoringChecker(checkers.BaseTokenChecker):
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 @@ class RefactoringChecker(checkers.BaseTokenChecker):
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 8620d958c..945a952bd 100644
--- a/tests/functional/s/stop_iteration_inside_generator.py
+++ b/tests/functional/s/stop_iteration_inside_generator.py
@@ -4,17 +4,20 @@ Test that no StopIteration is raised inside a generator
# 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 @@ SomeClassWithNext().some_gen()
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 74ddc6d4e..c351012b5 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