From 2b5e28d5a5d72d23a532570fe7180560a634b31d Mon Sep 17 00:00:00 2001 From: detachhead Date: Tue, 6 Sep 2022 17:41:17 +1000 Subject: [PATCH 01/11] fix `undefined-loop-variable` with `NoReturn` and `Never` --- pylint/checkers/variables.py | 3 +++ .../u/undefined/undefined_loop_variable.py | 25 +++++++++++++++++++ 2 files changed, 28 insertions(+) diff --git a/pylint/checkers/variables.py b/pylint/checkers/variables.py index a426ad2470..dfd0d13d43 100644 --- a/pylint/checkers/variables.py +++ b/pylint/checkers/variables.py @@ -2257,6 +2257,9 @@ def _loopvar_name(self, node: astroid.Name) -> None: if any( isinstance( else_stmt, (nodes.Return, nodes.Raise, nodes.Break, nodes.Continue) + ) or ( + isinstance(else_stmt, nodes.FunctionDef) and + else_stmt.type_comment_returns.qualname() in ("typing.NoReturn", "typing.Never") ) for else_stmt in assign.orelse ): diff --git a/tests/functional/u/undefined/undefined_loop_variable.py b/tests/functional/u/undefined/undefined_loop_variable.py index 3e096472d9..1ad2889e42 100644 --- a/tests/functional/u/undefined/undefined_loop_variable.py +++ b/tests/functional/u/undefined/undefined_loop_variable.py @@ -1,4 +1,6 @@ # pylint: disable=missing-docstring,redefined-builtin, consider-using-f-string, unnecessary-direct-lambda-call +from typing import NoReturn, Never + def do_stuff(some_random_list): for var in some_random_list: @@ -125,6 +127,29 @@ def for_else_continue(iterable): print(thing) +def for_else_no_return(iterable): + def foo() -> NoReturn: + ... + + while True: + for thing in iterable: + break + else: + foo() + print(thing) + + +def for_else_never(iterable): + def foo() -> Never: + ... + + while True: + for thing in iterable: + break + else: + foo() + print(thing) + lst = [] lst2 = [1, 2, 3] From 49b17b42050ca722eec631edd8a6467487eb04be Mon Sep 17 00:00:00 2001 From: detachhead Date: Wed, 7 Sep 2022 09:28:57 +1000 Subject: [PATCH 02/11] idk --- pylint/checkers/variables.py | 4 ++- .../u/undefined/undefined_loop_variable.py | 29 ++++++++++--------- 2 files changed, 18 insertions(+), 15 deletions(-) diff --git a/pylint/checkers/variables.py b/pylint/checkers/variables.py index dfd0d13d43..fbd68e7572 100644 --- a/pylint/checkers/variables.py +++ b/pylint/checkers/variables.py @@ -2258,7 +2258,9 @@ def _loopvar_name(self, node: astroid.Name) -> None: isinstance( else_stmt, (nodes.Return, nodes.Raise, nodes.Break, nodes.Continue) ) or ( - isinstance(else_stmt, nodes.FunctionDef) and + isinstance(else_stmt, nodes.Expr) and + # doesnt work + any(isinstance(expr, nodes.FunctionDef) for expr in else_stmt.get_children()) and else_stmt.type_comment_returns.qualname() in ("typing.NoReturn", "typing.Never") ) for else_stmt in assign.orelse diff --git a/tests/functional/u/undefined/undefined_loop_variable.py b/tests/functional/u/undefined/undefined_loop_variable.py index 1ad2889e42..a272a8b20b 100644 --- a/tests/functional/u/undefined/undefined_loop_variable.py +++ b/tests/functional/u/undefined/undefined_loop_variable.py @@ -1,5 +1,5 @@ # pylint: disable=missing-docstring,redefined-builtin, consider-using-f-string, unnecessary-direct-lambda-call -from typing import NoReturn, Never +from typing import NoReturn def do_stuff(some_random_list): @@ -128,27 +128,28 @@ def for_else_continue(iterable): def for_else_no_return(iterable): - def foo() -> NoReturn: + def fail() -> NoReturn: ... while True: for thing in iterable: break else: - foo() + fail() print(thing) - -def for_else_never(iterable): - def foo() -> Never: - ... - - while True: - for thing in iterable: - break - else: - foo() - print(thing) +# now do i make this only run on python 3.11 +# def for_else_never(iterable): +# from typing import Never +# def foo() -> Never: +# ... +# +# while True: +# for thing in iterable: +# break +# else: +# foo() +# print(thing) lst = [] lst2 = [1, 2, 3] From 70bedeb432b97f0c5a9a658838fa5d230b997f4f Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Tue, 6 Sep 2022 23:39:00 +0000 Subject: [PATCH 03/11] [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci --- pylint/checkers/variables.py | 14 ++++++++++---- 1 file changed, 10 insertions(+), 4 deletions(-) diff --git a/pylint/checkers/variables.py b/pylint/checkers/variables.py index fbd68e7572..ad29680b2b 100644 --- a/pylint/checkers/variables.py +++ b/pylint/checkers/variables.py @@ -2257,11 +2257,17 @@ def _loopvar_name(self, node: astroid.Name) -> None: if any( isinstance( else_stmt, (nodes.Return, nodes.Raise, nodes.Break, nodes.Continue) - ) or ( - isinstance(else_stmt, nodes.Expr) and + ) + or ( + isinstance(else_stmt, nodes.Expr) + and # doesnt work - any(isinstance(expr, nodes.FunctionDef) for expr in else_stmt.get_children()) and - else_stmt.type_comment_returns.qualname() in ("typing.NoReturn", "typing.Never") + any( + isinstance(expr, nodes.FunctionDef) + for expr in else_stmt.get_children() + ) + and else_stmt.type_comment_returns.qualname() + in ("typing.NoReturn", "typing.Never") ) for else_stmt in assign.orelse ): From 83cb0a32bfabb00bf5d2236345dc264b0c8a1b02 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Dani=C3=ABl=20van=20Noord?= <13665637+DanielNoord@users.noreply.github.com> Date: Fri, 16 Sep 2022 14:21:41 +0200 Subject: [PATCH 04/11] Fix --- pylint/checkers/variables.py | 37 ++++++++------- .../u/undefined/undefined_loop_variable.py | 45 +++++++------------ .../u/undefined/undefined_loop_variable.txt | 8 ++-- .../undefined_loop_variable_py311.py | 17 +++++++ .../undefined_loop_variable_py311.rc | 2 + 5 files changed, 59 insertions(+), 50 deletions(-) create mode 100644 tests/functional/u/undefined/undefined_loop_variable_py311.py create mode 100644 tests/functional/u/undefined/undefined_loop_variable_py311.rc diff --git a/pylint/checkers/variables.py b/pylint/checkers/variables.py index ad29680b2b..db0ad05a36 100644 --- a/pylint/checkers/variables.py +++ b/pylint/checkers/variables.py @@ -2254,24 +2254,27 @@ def _loopvar_name(self, node: astroid.Name) -> None: if not isinstance(assign, nodes.For): self.add_message("undefined-loop-variable", args=node.name, node=node) return - if any( - isinstance( + for else_stmt in assign.orelse: + if isinstance( else_stmt, (nodes.Return, nodes.Raise, nodes.Break, nodes.Continue) - ) - or ( - isinstance(else_stmt, nodes.Expr) - and - # doesnt work - any( - isinstance(expr, nodes.FunctionDef) - for expr in else_stmt.get_children() - ) - and else_stmt.type_comment_returns.qualname() - in ("typing.NoReturn", "typing.Never") - ) - for else_stmt in assign.orelse - ): - return + ): + return + if isinstance(else_stmt, nodes.Expr) and isinstance( + else_stmt.value, nodes.Call + ): + inferred_func = utils.safe_infer(else_stmt.value.func) + if ( + isinstance(inferred_func, nodes.FunctionDef) + and inferred_func.returns + ): + inferred_return = utils.safe_infer(inferred_func.returns) + if isinstance( + inferred_return, nodes.FunctionDef + ) and inferred_return.qname() in { + "typing.NoReturn", + "typing.Never", + }: + return maybe_walrus = utils.get_node_first_ancestor_of_type(node, nodes.NamedExpr) if maybe_walrus: diff --git a/tests/functional/u/undefined/undefined_loop_variable.py b/tests/functional/u/undefined/undefined_loop_variable.py index a272a8b20b..b00bd5b22e 100644 --- a/tests/functional/u/undefined/undefined_loop_variable.py +++ b/tests/functional/u/undefined/undefined_loop_variable.py @@ -1,11 +1,12 @@ # pylint: disable=missing-docstring,redefined-builtin, consider-using-f-string, unnecessary-direct-lambda-call + from typing import NoReturn def do_stuff(some_random_list): for var in some_random_list: pass - return var # [undefined-loop-variable] + return var # [undefined-loop-variable] def do_else(some_random_list): @@ -16,15 +17,16 @@ def do_else(some_random_list): var = 84 return var -__revision__ = 'yo' + +__revision__ = "yo" TEST_LC = [C for C in __revision__ if C.isalpha()] -B = [B for B in __revision__ if B.isalpha()] -VAR2 = B # nor this one +B = [B for B in __revision__ if B.isalpha()] +VAR2 = B # nor this one for var1, var2 in TEST_LC: var1 = var2 + 4 -VAR3 = var1 # [undefined-loop-variable] +VAR3 = var1 # [undefined-loop-variable] for note in __revision__: note.something() @@ -72,9 +74,10 @@ def do_stuff_with_a_range(): def do_stuff_with_redefined_range(): def range(key): yield from [1, key] + for var in range(3): pass - return var # [undefined-loop-variable] + return var # [undefined-loop-variable] def test(content): @@ -83,13 +86,13 @@ def handle_line(layne): if "X" in layne: layne = layne.replace("X", "Y") elif "Y" in layne: # line 5 - layne = '{}'.format(layne) + layne = "{}".format(layne) elif "Z" in layne: # line 7 - layne = f'{layne}' + layne = f"{layne}" else: - layne = '%s' % layne # line 10 + layne = "%s" % layne # line 10 - for layne in content.split('\n'): + for layne in content.split("\n"): handle_line(layne) @@ -138,18 +141,6 @@ def fail() -> NoReturn: fail() print(thing) -# now do i make this only run on python 3.11 -# def for_else_never(iterable): -# from typing import Never -# def foo() -> Never: -# ... -# -# while True: -# for thing in iterable: -# break -# else: -# foo() -# print(thing) lst = [] lst2 = [1, 2, 3] @@ -157,19 +148,15 @@ def fail() -> NoReturn: for item in lst: pass -bigger = [ - [ - x for x in lst2 if x > item - ] - for item in lst -] +bigger = [[x for x in lst2 if x > item] for item in lst] def lambda_in_first_of_two_loops(): """https://github.com/PyCQA/pylint/issues/6419""" my_list = [] for thing in my_list: - print_it = lambda: print(thing) # pylint: disable=cell-var-from-loop, unnecessary-lambda-assignment + # pylint: disable-next=cell-var-from-loop, unnecessary-lambda-assignment + print_it = lambda: print(thing) print_it() for thing in my_list: diff --git a/tests/functional/u/undefined/undefined_loop_variable.txt b/tests/functional/u/undefined/undefined_loop_variable.txt index dce1300ce3..ffab79ed68 100644 --- a/tests/functional/u/undefined/undefined_loop_variable.txt +++ b/tests/functional/u/undefined/undefined_loop_variable.txt @@ -1,4 +1,4 @@ -undefined-loop-variable:6:11:6:14:do_stuff:Using possibly undefined loop variable 'var':UNDEFINED -undefined-loop-variable:25:7:25:11::Using possibly undefined loop variable 'var1':UNDEFINED -undefined-loop-variable:75:11:75:14:do_stuff_with_redefined_range:Using possibly undefined loop variable 'var':UNDEFINED -undefined-loop-variable:181:11:181:20:find_even_number:Using possibly undefined loop variable 'something':UNDEFINED +undefined-loop-variable:9:11:9:14:do_stuff:Using possibly undefined loop variable 'var':UNDEFINED +undefined-loop-variable:29:7:29:11::Using possibly undefined loop variable 'var1':UNDEFINED +undefined-loop-variable:80:11:80:14:do_stuff_with_redefined_range:Using possibly undefined loop variable 'var':UNDEFINED +undefined-loop-variable:194:11:194:20:find_even_number:Using possibly undefined loop variable 'something':UNDEFINED diff --git a/tests/functional/u/undefined/undefined_loop_variable_py311.py b/tests/functional/u/undefined/undefined_loop_variable_py311.py new file mode 100644 index 0000000000..93b43a5468 --- /dev/null +++ b/tests/functional/u/undefined/undefined_loop_variable_py311.py @@ -0,0 +1,17 @@ +"""Tests for undefined-loop-variable using Python 3.11 syntax.""" + +from typing import Never + + +def for_else_never(iterable): + """Test for-else with Never type.""" + + def idontreturn() -> Never: + """This function never returns.""" + + while True: + for thing in iterable: + break + else: + idontreturn() + print(thing) diff --git a/tests/functional/u/undefined/undefined_loop_variable_py311.rc b/tests/functional/u/undefined/undefined_loop_variable_py311.rc new file mode 100644 index 0000000000..56e6770585 --- /dev/null +++ b/tests/functional/u/undefined/undefined_loop_variable_py311.rc @@ -0,0 +1,2 @@ +[testoptions] +min_pyver=3.11 From c6d7b05e1cc0c7a7757fe1cf073eb6cf7de5521a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Dani=C3=ABl=20van=20Noord?= <13665637+DanielNoord@users.noreply.github.com> Date: Fri, 16 Sep 2022 14:23:59 +0200 Subject: [PATCH 05/11] Use constants --- pylint/checkers/variables.py | 12 +++++++----- pylint/constants.py | 13 +++++++++++++ pylint/extensions/typing.py | 7 +------ 3 files changed, 21 insertions(+), 11 deletions(-) diff --git a/pylint/checkers/variables.py b/pylint/checkers/variables.py index db0ad05a36..1a609323ac 100644 --- a/pylint/checkers/variables.py +++ b/pylint/checkers/variables.py @@ -28,7 +28,12 @@ in_type_checking_block, is_postponed_evaluation_enabled, ) -from pylint.constants import PY39_PLUS, TYPING_TYPE_CHECKS_GUARDS +from pylint.constants import ( + PY39_PLUS, + TYPING_NEVER, + TYPING_NORETURN, + TYPING_TYPE_CHECKS_GUARDS, +) from pylint.interfaces import CONTROL_FLOW, HIGH, INFERENCE, INFERENCE_FAILURE from pylint.typing import MessageDefinitionTuple @@ -2270,10 +2275,7 @@ def _loopvar_name(self, node: astroid.Name) -> None: inferred_return = utils.safe_infer(inferred_func.returns) if isinstance( inferred_return, nodes.FunctionDef - ) and inferred_return.qname() in { - "typing.NoReturn", - "typing.Never", - }: + ) and inferred_return.qname() in {*TYPING_NORETURN, *TYPING_NEVER}: return maybe_walrus = utils.get_node_first_ancestor_of_type(node, nodes.NamedExpr) diff --git a/pylint/constants.py b/pylint/constants.py index a609f9cd6f..6ad5b82d35 100644 --- a/pylint/constants.py +++ b/pylint/constants.py @@ -155,3 +155,16 @@ def _get_pylint_home() -> str: PYLINT_HOME = _get_pylint_home() + +TYPING_NORETURN = frozenset( + ( + "typing.NoReturn", + "typing_extensions.NoReturn", + ) +) +TYPING_NEVER = frozenset( + ( + "typing.Never", + "typing_extensions.Never", + ) +) diff --git a/pylint/extensions/typing.py b/pylint/extensions/typing.py index 615f40a48e..5f89cd5613 100644 --- a/pylint/extensions/typing.py +++ b/pylint/extensions/typing.py @@ -17,6 +17,7 @@ only_required_for_messages, safe_infer, ) +from pylint.constants import TYPING_NORETURN from pylint.interfaces import INFERENCE if TYPE_CHECKING: @@ -75,12 +76,6 @@ class TypingAlias(NamedTuple): ALIAS_NAMES = frozenset(key.split(".")[1] for key in DEPRECATED_TYPING_ALIASES) UNION_NAMES = ("Optional", "Union") -TYPING_NORETURN = frozenset( - ( - "typing.NoReturn", - "typing_extensions.NoReturn", - ) -) class DeprecatedTypingAliasMsg(NamedTuple): From d40e7b9eb27c9710600bd796b9618d5cb6696c41 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Dani=C3=ABl=20van=20Noord?= <13665637+DanielNoord@users.noreply.github.com> Date: Fri, 16 Sep 2022 14:25:14 +0200 Subject: [PATCH 06/11] Add TODO --- pylint/checkers/variables.py | 1 + 1 file changed, 1 insertion(+) diff --git a/pylint/checkers/variables.py b/pylint/checkers/variables.py index 1a609323ac..9456251e5a 100644 --- a/pylint/checkers/variables.py +++ b/pylint/checkers/variables.py @@ -2264,6 +2264,7 @@ def _loopvar_name(self, node: astroid.Name) -> None: else_stmt, (nodes.Return, nodes.Raise, nodes.Break, nodes.Continue) ): return + # TODO: 2.16: Consider using RefactoringChecker._is_function_def_never_returning if isinstance(else_stmt, nodes.Expr) and isinstance( else_stmt.value, nodes.Call ): From 55f0e08b61e5d0d649f5144bab87a9be9f986abb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Dani=C3=ABl=20van=20Noord?= <13665637+DanielNoord@users.noreply.github.com> Date: Fri, 16 Sep 2022 14:34:38 +0200 Subject: [PATCH 07/11] Add news fragment --- doc/whatsnew/fragments/7311.false_positive | 4 ++++ 1 file changed, 4 insertions(+) create mode 100644 doc/whatsnew/fragments/7311.false_positive diff --git a/doc/whatsnew/fragments/7311.false_positive b/doc/whatsnew/fragments/7311.false_positive new file mode 100644 index 0000000000..72b31749c3 --- /dev/null +++ b/doc/whatsnew/fragments/7311.false_positive @@ -0,0 +1,4 @@ +Fix false positive for ``undefined-loop-variable`` in ``for-else`` loops that use a function that is +typed as being ``NoReturn`` or ``Never``. + +Closes #7311 From b0ed222e3b9654f5798fb84ba533bf26a1c749a5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Dani=C3=ABl=20van=20Noord?= <13665637+DanielNoord@users.noreply.github.com> Date: Sat, 17 Sep 2022 16:13:20 +0200 Subject: [PATCH 08/11] Revert formatting --- .../u/undefined/undefined_loop_variable.py | 32 ++++++++++--------- .../u/undefined/undefined_loop_variable.txt | 6 ++-- 2 files changed, 20 insertions(+), 18 deletions(-) diff --git a/tests/functional/u/undefined/undefined_loop_variable.py b/tests/functional/u/undefined/undefined_loop_variable.py index b00bd5b22e..6901454611 100644 --- a/tests/functional/u/undefined/undefined_loop_variable.py +++ b/tests/functional/u/undefined/undefined_loop_variable.py @@ -6,7 +6,7 @@ def do_stuff(some_random_list): for var in some_random_list: pass - return var # [undefined-loop-variable] + return var # [undefined-loop-variable] def do_else(some_random_list): @@ -17,16 +17,15 @@ def do_else(some_random_list): var = 84 return var - -__revision__ = "yo" +__revision__ = 'yo' TEST_LC = [C for C in __revision__ if C.isalpha()] -B = [B for B in __revision__ if B.isalpha()] -VAR2 = B # nor this one +B = [B for B in __revision__ if B.isalpha()] +VAR2 = B # nor this one for var1, var2 in TEST_LC: var1 = var2 + 4 -VAR3 = var1 # [undefined-loop-variable] +VAR3 = var1 # [undefined-loop-variable] for note in __revision__: note.something() @@ -74,10 +73,9 @@ def do_stuff_with_a_range(): def do_stuff_with_redefined_range(): def range(key): yield from [1, key] - for var in range(3): pass - return var # [undefined-loop-variable] + return var # [undefined-loop-variable] def test(content): @@ -86,13 +84,13 @@ def handle_line(layne): if "X" in layne: layne = layne.replace("X", "Y") elif "Y" in layne: # line 5 - layne = "{}".format(layne) + layne = '{}'.format(layne) elif "Z" in layne: # line 7 - layne = f"{layne}" + layne = f'{layne}' else: - layne = "%s" % layne # line 10 + layne = '%s' % layne # line 10 - for layne in content.split("\n"): + for layne in content.split('\n'): handle_line(layne) @@ -148,15 +146,19 @@ def fail() -> NoReturn: for item in lst: pass -bigger = [[x for x in lst2 if x > item] for item in lst] +bigger = [ + [ + x for x in lst2 if x > item + ] + for item in lst +] def lambda_in_first_of_two_loops(): """https://github.com/PyCQA/pylint/issues/6419""" my_list = [] for thing in my_list: - # pylint: disable-next=cell-var-from-loop, unnecessary-lambda-assignment - print_it = lambda: print(thing) + print_it = lambda: print(thing) # pylint: disable=cell-var-from-loop, unnecessary-lambda-assignment print_it() for thing in my_list: diff --git a/tests/functional/u/undefined/undefined_loop_variable.txt b/tests/functional/u/undefined/undefined_loop_variable.txt index ffab79ed68..78dc602ede 100644 --- a/tests/functional/u/undefined/undefined_loop_variable.txt +++ b/tests/functional/u/undefined/undefined_loop_variable.txt @@ -1,4 +1,4 @@ undefined-loop-variable:9:11:9:14:do_stuff:Using possibly undefined loop variable 'var':UNDEFINED -undefined-loop-variable:29:7:29:11::Using possibly undefined loop variable 'var1':UNDEFINED -undefined-loop-variable:80:11:80:14:do_stuff_with_redefined_range:Using possibly undefined loop variable 'var':UNDEFINED -undefined-loop-variable:194:11:194:20:find_even_number:Using possibly undefined loop variable 'something':UNDEFINED +undefined-loop-variable:28:7:28:11::Using possibly undefined loop variable 'var1':UNDEFINED +undefined-loop-variable:78:11:78:14:do_stuff_with_redefined_range:Using possibly undefined loop variable 'var':UNDEFINED +undefined-loop-variable:196:11:196:20:find_even_number:Using possibly undefined loop variable 'something':UNDEFINED From 92bf4ff70cbef7ca1b96cc1c0b3870b4b19bad62 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Dani=C3=ABl=20van=20Noord?= <13665637+DanielNoord@users.noreply.github.com> Date: Sat, 17 Sep 2022 16:13:42 +0200 Subject: [PATCH 09/11] Update doc/whatsnew/fragments/7311.false_positive Co-authored-by: Jacob Walls --- doc/whatsnew/fragments/7311.false_positive | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/doc/whatsnew/fragments/7311.false_positive b/doc/whatsnew/fragments/7311.false_positive index 72b31749c3..84d57502b4 100644 --- a/doc/whatsnew/fragments/7311.false_positive +++ b/doc/whatsnew/fragments/7311.false_positive @@ -1,4 +1,4 @@ -Fix false positive for ``undefined-loop-variable`` in ``for-else`` loops that use a function that is -typed as being ``NoReturn`` or ``Never``. +Fix false positive for ``undefined-loop-variable`` in ``for-else`` loops that use a function +having a return type annotation of ``NoReturn`` or ``Never``. Closes #7311 From fdb6cad7f0a5d2d6c4edc4b193bd569a3cf46244 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Dani=C3=ABl=20van=20Noord?= <13665637+DanielNoord@users.noreply.github.com> Date: Sat, 17 Sep 2022 16:31:04 +0200 Subject: [PATCH 10/11] Fix on lower versions --- tests/functional/u/undefined/undefined_loop_variable.py | 7 ++++++- tests/functional/u/undefined/undefined_loop_variable.txt | 8 ++++---- 2 files changed, 10 insertions(+), 5 deletions(-) diff --git a/tests/functional/u/undefined/undefined_loop_variable.py b/tests/functional/u/undefined/undefined_loop_variable.py index 6901454611..9ab08d5953 100644 --- a/tests/functional/u/undefined/undefined_loop_variable.py +++ b/tests/functional/u/undefined/undefined_loop_variable.py @@ -1,6 +1,11 @@ # pylint: disable=missing-docstring,redefined-builtin, consider-using-f-string, unnecessary-direct-lambda-call -from typing import NoReturn +import sys + +if sys.version_info >= (3, 8): + from typing import NoReturn +else: + from typing_extensions import NoReturn def do_stuff(some_random_list): diff --git a/tests/functional/u/undefined/undefined_loop_variable.txt b/tests/functional/u/undefined/undefined_loop_variable.txt index 78dc602ede..e10c9e0021 100644 --- a/tests/functional/u/undefined/undefined_loop_variable.txt +++ b/tests/functional/u/undefined/undefined_loop_variable.txt @@ -1,4 +1,4 @@ -undefined-loop-variable:9:11:9:14:do_stuff:Using possibly undefined loop variable 'var':UNDEFINED -undefined-loop-variable:28:7:28:11::Using possibly undefined loop variable 'var1':UNDEFINED -undefined-loop-variable:78:11:78:14:do_stuff_with_redefined_range:Using possibly undefined loop variable 'var':UNDEFINED -undefined-loop-variable:196:11:196:20:find_even_number:Using possibly undefined loop variable 'something':UNDEFINED +undefined-loop-variable:14:11:14:14:do_stuff:Using possibly undefined loop variable 'var':UNDEFINED +undefined-loop-variable:33:7:33:11::Using possibly undefined loop variable 'var1':UNDEFINED +undefined-loop-variable:83:11:83:14:do_stuff_with_redefined_range:Using possibly undefined loop variable 'var':UNDEFINED +undefined-loop-variable:201:11:201:20:find_even_number:Using possibly undefined loop variable 'something':UNDEFINED From 370ec100ba79633befb71b650bad2812496af535 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Dani=C3=ABl=20van=20Noord?= <13665637+DanielNoord@users.noreply.github.com> Date: Mon, 19 Sep 2022 10:23:05 +0200 Subject: [PATCH 11/11] Fix --- pylint/checkers/variables.py | 14 ++++++++++++-- 1 file changed, 12 insertions(+), 2 deletions(-) diff --git a/pylint/checkers/variables.py b/pylint/checkers/variables.py index 9456251e5a..ef2d8c1889 100644 --- a/pylint/checkers/variables.py +++ b/pylint/checkers/variables.py @@ -19,7 +19,7 @@ from typing import TYPE_CHECKING, Any, NamedTuple import astroid -from astroid import extract_node, nodes +from astroid import bases, extract_node, nodes from astroid.nodes import _base_nodes from astroid.typing import InferenceResult @@ -2276,7 +2276,17 @@ def _loopvar_name(self, node: astroid.Name) -> None: inferred_return = utils.safe_infer(inferred_func.returns) if isinstance( inferred_return, nodes.FunctionDef - ) and inferred_return.qname() in {*TYPING_NORETURN, *TYPING_NEVER}: + ) and inferred_return.qname() in { + *TYPING_NORETURN, + *TYPING_NEVER, + "typing._SpecialForm", + }: + return + # typing_extensions.NoReturn returns a _SpecialForm + if ( + isinstance(inferred_return, bases.Instance) + and inferred_return.qname() == "typing._SpecialForm" + ): return maybe_walrus = utils.get_node_first_ancestor_of_type(node, nodes.NamedExpr)