Skip to content

[optional ext] Emit redefined-loop-name for redefinitions of loop variables in body #5649

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

Merged
merged 38 commits into from
May 2, 2022
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
Show all changes
38 commits
Select commit Hold shift + click to select a range
a80ad9f
Fix #5608: Emit `redefined-outer-name` for redefinitions of loop vari…
jacobtylerwalls Jan 8, 2022
fa33c2e
Extend solution to AugAssign
jacobtylerwalls Mar 28, 2022
859b01e
Merge branch 'main' into redefined-loop-var
jacobtylerwalls Mar 28, 2022
5929ce7
Update tests
jacobtylerwalls Mar 28, 2022
91e118a
Fix false positive involving functions nested under loops
jacobtylerwalls Mar 29, 2022
5b61d0a
Apply suggestions from code review
jacobtylerwalls Mar 29, 2022
c80336d
[pre-commit.ci] auto fixes from pre-commit.com hooks
pre-commit-ci[bot] Mar 29, 2022
506c194
Update test results
jacobtylerwalls Mar 29, 2022
a67f984
Remove is_message_enabled() call
jacobtylerwalls Mar 29, 2022
e6791e2
Create `redefined-loop-name` message
jacobtylerwalls Mar 30, 2022
05dc2df
Update redefined-outer-name description
jacobtylerwalls Apr 4, 2022
45987bb
Merge branch 'main' into redefined-loop-var
jacobtylerwalls Apr 4, 2022
75f98a9
Make `redefined-loop-name` an optional extension
jacobtylerwalls Apr 4, 2022
7147199
Delete relocated code
jacobtylerwalls Apr 4, 2022
c6e9766
Remove moved message description
jacobtylerwalls Apr 4, 2022
46e0f59
Remove disables
jacobtylerwalls Apr 4, 2022
8378199
Bump message ID
jacobtylerwalls Apr 4, 2022
19962f6
Add examples
jacobtylerwalls Apr 5, 2022
c2b5d4e
Merge branch 'main' into redefined-loop-var
jacobtylerwalls Apr 5, 2022
b5c6aa5
Add pylintrc
jacobtylerwalls Apr 5, 2022
09705bd
Add coverage and fix preexisting false positive
jacobtylerwalls Apr 7, 2022
5970936
Add coverage
jacobtylerwalls Apr 7, 2022
cb3cfbf
Merge branch 'main' into redefined-loop-var
jacobtylerwalls Apr 28, 2022
f15d662
Apply suggestions from code review
jacobtylerwalls Apr 30, 2022
3cc7f0a
[pre-commit.ci] auto fixes from pre-commit.com hooks
pre-commit-ci[bot] Apr 30, 2022
3fb8477
Use self.linter.config and remove test file artifact
jacobtylerwalls Apr 30, 2022
86a272e
Merge branch 'redefined-loop-var' of https://github.com/jacobtylerwal…
jacobtylerwalls Apr 30, 2022
e6e4834
Move to checker utils
jacobtylerwalls Apr 30, 2022
0c23bdf
[pre-commit.ci] auto fixes from pre-commit.com hooks
pre-commit-ci[bot] Apr 30, 2022
d2b487c
typo
jacobtylerwalls Apr 30, 2022
0f67bc9
Fix typing
jacobtylerwalls May 1, 2022
2824a1e
Better names
jacobtylerwalls May 1, 2022
d506334
Add caching
jacobtylerwalls May 1, 2022
7c13e3a
keep scope() around
jacobtylerwalls May 1, 2022
efc734f
more specific type
jacobtylerwalls May 1, 2022
e21a932
[pre-commit.ci] auto fixes from pre-commit.com hooks
pre-commit-ci[bot] May 1, 2022
3990ee6
Remove now unused list
jacobtylerwalls May 1, 2022
836ef8a
Fix existing typo
jacobtylerwalls May 1, 2022
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions ChangeLog
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,10 @@ Release date: TBA

Closes #578

* Emit ``redefined-outer-name`` when a loop variable is redefined in the loop
body. Previously, only redefinitions taking place in nested loops were flagged.

Closes #5608

* Fix false negative for ``no-member`` when attempting to assign an instance
attribute to itself without any prior assignment.
Expand Down
5 changes: 5 additions & 0 deletions doc/whatsnew/2.14.rst
Original file line number Diff line number Diff line change
Expand Up @@ -33,3 +33,8 @@ Other Changes
attribute to itself without any prior assignment.

Closes #1555

* Emit ``redefined-outer-name`` when a loop variable is redefined in the loop
body. Previously, only redefinitions taking place in nested loops were flagged.

Closes #5608
20 changes: 17 additions & 3 deletions pylint/checkers/variables.py
Original file line number Diff line number Diff line change
Expand Up @@ -467,9 +467,10 @@ def _has_locals_call_after_node(stmt, scope):
"`'from X import *'` style import.",
),
"W0621": (
"Redefining name %r from outer scope (line %s)",
"Redefining name %r from outer scope or loop (line %s)",
"redefined-outer-name",
"Used when a variable's name hides a name defined in the outer scope.",
"Used when a variable's name hides a name defined in an outer scope, "
"for loop, or except handler.",
),
"W0622": (
"Redefining built-in %r",
Expand Down Expand Up @@ -982,7 +983,7 @@ class VariablesChecker(BaseChecker):
Checks for
* unused variables / imports
* undefined variables
* redefinition of variable from builtins or from an outer scope
* redefinition of variable from builtins or from an outer scope, for loop, or except handler
* use of variable before assignment
* __all__ consistency
* self/cls assignment
Expand Down Expand Up @@ -1358,6 +1359,19 @@ def visit_global(self, node: nodes.Global) -> None:
self.add_message("global-statement", node=node)

def visit_assignname(self, node: nodes.AssignName) -> None:
if self.linter.is_message_enabled("redefined-outer-name") and isinstance(
node.parent, nodes.Assign
):
for outer_for, outer_variables in self._loop_variables:
if node.name in outer_variables and not in_for_else_branch(
outer_for, node
):
self.add_message(
"redefined-outer-name",
args=(node.name, outer_for.fromlineno),
node=node,
)
break
if isinstance(node.assign_type(), nodes.AugAssign):
self.visit_name(node)

Expand Down
1 change: 1 addition & 0 deletions tests/functional/b/bad_indentation.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ def titii():

def tataa(kdict):
for key in ['1', '2', '3']:
# pylint: disable=redefined-outer-name
key = key.lower()

if key in kdict:
Expand Down
2 changes: 1 addition & 1 deletion tests/functional/c/cellvar_escaping_loop.py
Original file line number Diff line number Diff line change
Expand Up @@ -76,7 +76,7 @@ def good_case10():
lst = []
for i in range(10): # pylint: disable=unused-variable
def func():
i = 100
i = 100 # pylint: disable=redefined-outer-name
def func2(arg=i):
return arg

Expand Down
2 changes: 1 addition & 1 deletion tests/functional/f/function_redefined.txt
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
function-redefined:18:4:18:15:AAAA.method2:method already defined line 15:UNDEFINED
function-redefined:21:0:21:10:AAAA:class already defined line 8:UNDEFINED
function-redefined:35:0:35:9:func2:function already defined line 32:UNDEFINED
redefined-outer-name:37:4:37:16:func2:Redefining name '__revision__' from outer scope (line 7):UNDEFINED
redefined-outer-name:37:4:37:16:func2:Redefining name '__revision__' from outer scope or loop (line 7):UNDEFINED
function-redefined:54:4:54:23:exclusive_func2:function already defined line 48:UNDEFINED
function-redefined:89:0:89:8:ceil:function already defined line 88:UNDEFINED
function-redefined:93:0:93:8:math:function already defined line 92:UNDEFINED
2 changes: 1 addition & 1 deletion tests/functional/n/no/no_dummy_redefined.txt
Original file line number Diff line number Diff line change
@@ -1,2 +1,2 @@
invalid-name:7:0:7:5::"Constant name ""value"" doesn't conform to UPPER_CASE naming style":HIGH
redefined-outer-name:12:4:12:9:clobbering:Redefining name 'value' from outer scope (line 7):UNDEFINED
redefined-outer-name:12:4:12:9:clobbering:Redefining name 'value' from outer scope or loop (line 7):UNDEFINED
2 changes: 1 addition & 1 deletion tests/functional/r/redefine_loop.txt
Original file line number Diff line number Diff line change
@@ -1 +1 @@
redefined-outer-name:4:4:6:20::Redefining name 'item' from outer scope (line 2):UNDEFINED
redefined-outer-name:4:4:6:20::Redefining name 'item' from outer scope or loop (line 2):UNDEFINED
6 changes: 3 additions & 3 deletions tests/functional/r/redefined/redefined_except_handler.txt
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
redefined-outer-name:11:4:12:12::Redefining name 'err' from outer scope (line 8):UNDEFINED
redefined-outer-name:57:8:58:16::Redefining name 'err' from outer scope (line 51):UNDEFINED
redefined-outer-name:11:4:12:12::Redefining name 'err' from outer scope or loop (line 8):UNDEFINED
redefined-outer-name:57:8:58:16::Redefining name 'err' from outer scope or loop (line 51):UNDEFINED
used-before-assignment:69:14:69:29:func:Using variable 'CustomException' before assignment:HIGH
redefined-outer-name:71:4:72:12:func:Redefining name 'CustomException' from outer scope (line 62):UNDEFINED
redefined-outer-name:71:4:72:12:func:Redefining name 'CustomException' from outer scope or loop (line 62):UNDEFINED
15 changes: 15 additions & 0 deletions tests/functional/r/redefined/redefined_loop_name.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
"""Tests for redefinitions of loop variables inside the loop body.

See: https://github.com/PyCQA/pylint/issues/5608
"""
# pylint: disable=invalid-name

lines = ["1\t", "2\t"]
for line in lines:
line = line.strip() # [redefined-outer-name]

lines = [(1, "1\t"), (2, "2\t")]
for i, line in lines:
line = line.strip() # [redefined-outer-name]

line = "no warning"
2 changes: 2 additions & 0 deletions tests/functional/r/redefined/redefined_loop_name.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
redefined-outer-name:9:4:9:8::Redefining name 'line' from outer scope or loop (line 8):UNDEFINED
redefined-outer-name:13:4:13:8::Redefining name 'line' from outer scope or loop (line 12):UNDEFINED
2 changes: 1 addition & 1 deletion tests/functional/r/reimported.txt
Original file line number Diff line number Diff line change
Expand Up @@ -5,5 +5,5 @@ reimported:10:0:10:27::Reimport 'deque' (imported line 9):UNDEFINED
reimported:24:0:24:33::Reimport 'ElementTree' (imported line 23):UNDEFINED
reimported:27:0:27:21::Reimport 'email.encoders' (imported line 26):UNDEFINED
reimported:29:0:29:10::Reimport 'sys' (imported line 21):UNDEFINED
redefined-outer-name:40:4:40:14:reimport:Redefining name 'sys' from outer scope (line 16):UNDEFINED
redefined-outer-name:40:4:40:14:reimport:Redefining name 'sys' from outer scope or loop (line 16):UNDEFINED
reimported:40:4:40:14:reimport:Reimport 'sys' (imported line 21):UNDEFINED
8 changes: 4 additions & 4 deletions tests/functional/r/reused_outer_loop_variable.txt
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
redefined-outer-name:7:4:8:16::Redefining name 'i' from outer scope (line 6):UNDEFINED
redefined-outer-name:12:4:13:25::Redefining name 'i' from outer scope (line 11):UNDEFINED
redefined-outer-name:17:4:18:25::Redefining name 'i' from outer scope (line 16):UNDEFINED
redefined-outer-name:22:4:23:25::Redefining name 'a' from outer scope (line 21):UNDEFINED
redefined-outer-name:7:4:8:16::Redefining name 'i' from outer scope or loop (line 6):UNDEFINED
redefined-outer-name:12:4:13:25::Redefining name 'i' from outer scope or loop (line 11):UNDEFINED
redefined-outer-name:17:4:18:25::Redefining name 'i' from outer scope or loop (line 16):UNDEFINED
redefined-outer-name:22:4:23:25::Redefining name 'a' from outer scope or loop (line 21):UNDEFINED
2 changes: 1 addition & 1 deletion tests/functional/r/reused_outer_loop_variable_py3.txt
Original file line number Diff line number Diff line change
@@ -1 +1 @@
redefined-outer-name:4:4:5:16::Redefining name 'j' from outer scope (line 3):UNDEFINED
redefined-outer-name:4:4:5:16::Redefining name 'j' from outer scope or loop (line 3):UNDEFINED
2 changes: 1 addition & 1 deletion tests/functional/u/undefined/undefined_loop_variable.py
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ def do_else(some_random_list):
VAR2 = B # nor this one

for var1, var2 in TEST_LC:
var1 = var2 + 4
var1 = var2 + 4 # pylint: disable=redefined-outer-name
VAR3 = var1 # [undefined-loop-variable]

for note in __revision__:
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@

for k, v in b_dict.items():
print(k)
k = "another key"
k = "another key" # pylint: disable=redefined-outer-name
print(b_dict[k]) # This is fine, key reassigned


Expand Down Expand Up @@ -68,7 +68,7 @@ class Foo:
for item in d.items():
print(item[0])
print(d[item[0]]) # [unnecessary-dict-index-lookup]
item = (2, "b")
item = (2, "b") # pylint: disable=redefined-outer-name
print(d[item[0]]) # This is fine, no warning thrown as key has been reassigned


Expand Down
8 changes: 4 additions & 4 deletions tests/functional/u/used/used_before_assignment_issue1081.txt
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
used-before-assignment:7:7:7:8:used_before_assignment_1:Using variable 'x' before assignment:HIGH
redefined-outer-name:8:12:8:13:used_before_assignment_1:Redefining name 'x' from outer scope (line 3):UNDEFINED
redefined-outer-name:8:12:8:13:used_before_assignment_1:Redefining name 'x' from outer scope or loop (line 3):UNDEFINED
used-before-assignment:13:7:13:8:used_before_assignment_2:Using variable 'x' before assignment:HIGH
redefined-outer-name:15:4:15:5:used_before_assignment_2:Redefining name 'x' from outer scope (line 3):UNDEFINED
redefined-outer-name:15:4:15:5:used_before_assignment_2:Redefining name 'x' from outer scope or loop (line 3):UNDEFINED
used-before-assignment:19:7:19:8:used_before_assignment_3:Using variable 'x' before assignment:HIGH
redefined-outer-name:21:12:21:13:used_before_assignment_3:Redefining name 'x' from outer scope (line 3):UNDEFINED
redefined-outer-name:30:4:30:5:not_used_before_assignment_2:Redefining name 'x' from outer scope (line 3):UNDEFINED
redefined-outer-name:21:12:21:13:used_before_assignment_3:Redefining name 'x' from outer scope or loop (line 3):UNDEFINED
redefined-outer-name:30:4:30:5:not_used_before_assignment_2:Redefining name 'x' from outer scope or loop (line 3):UNDEFINED