Skip to content

Commit cadcd60

Browse files
authored
add warning for unused global / nonlocal names (#825)
1 parent 5f1f434 commit cadcd60

File tree

5 files changed

+63
-5
lines changed

5 files changed

+63
-5
lines changed

pyflakes/checker.py

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -543,6 +543,8 @@ def __init__(self):
543543
super().__init__()
544544
# Simplify: manage the special locals as globals
545545
self.globals = self.alwaysUsed.copy()
546+
# {name: node}
547+
self.indirect_assignments = {}
546548

547549
def unused_assignments(self):
548550
"""
@@ -564,6 +566,9 @@ def unused_annotations(self):
564566
if not binding.used and isinstance(binding, Annotation):
565567
yield name, binding
566568

569+
def unused_indirect_assignments(self):
570+
return self.indirect_assignments.items()
571+
567572

568573
class TypeScope(Scope):
569574
pass
@@ -839,6 +844,8 @@ def checkDeadScopes(self):
839844
self.report(messages.UnusedVariable, binding.source, name)
840845
for name, binding in scope.unused_annotations():
841846
self.report(messages.UnusedAnnotation, binding.source, name)
847+
for name, node in scope.unused_indirect_assignments():
848+
self.report(messages.UnusedIndirectAssignment, node, name)
842849

843850
all_binding = scope.get('__all__')
844851
if all_binding and not isinstance(all_binding, ExportBinding):
@@ -981,6 +988,9 @@ def addBinding(self, node, value):
981988
self.report(messages.RedefinedWhileUnused,
982989
node, value.name, existing.source)
983990

991+
if isinstance(scope, FunctionScope):
992+
scope.indirect_assignments.pop(value.name, None)
993+
984994
elif isinstance(existing, Importation) and value.redefines(existing):
985995
existing.redefined.append(node)
986996

@@ -1177,6 +1187,9 @@ def on_conditional_branch():
11771187
# be executed.
11781188
return
11791189

1190+
if isinstance(self.scope, FunctionScope):
1191+
self.scope.indirect_assignments.pop(name, None)
1192+
11801193
if isinstance(self.scope, FunctionScope) and name in self.scope.globals:
11811194
self.scope.globals.remove(name)
11821195
else:
@@ -1835,6 +1848,8 @@ def GLOBAL(self, node):
18351848
for scope in self.scopeStack[global_scope_index + 1:]:
18361849
scope[node_name] = node_value
18371850

1851+
self.scope.indirect_assignments[node_name] = node
1852+
18381853
NONLOCAL = GLOBAL
18391854

18401855
def GENERATOREXP(self, node):

pyflakes/messages.py

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -168,6 +168,15 @@ def __init__(self, filename, loc, names):
168168
self.message_args = (names,)
169169

170170

171+
class UnusedIndirectAssignment(Message):
172+
"""A `global` or `nonlocal` statement where the name is never reassigned"""
173+
message = '`%s %s` is unused: name is never assigned in scope'
174+
175+
def __init__(self, filename, loc, name):
176+
Message.__init__(self, filename, loc)
177+
self.message_args = (type(loc).__name__.lower(), name)
178+
179+
171180
class ReturnOutsideFunction(Message):
172181
"""
173182
Indicates a return statement outside of a function/method.

pyflakes/test/test_imports.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -654,7 +654,7 @@ def test_usedInGlobal(self):
654654
self.flakes('''
655655
import fu
656656
def f(): global fu
657-
''', m.UnusedImport)
657+
''', m.UnusedImport, m.UnusedIndirectAssignment)
658658

659659
def test_usedAndGlobal(self):
660660
"""
@@ -665,7 +665,7 @@ def test_usedAndGlobal(self):
665665
import foo
666666
def f(): global foo
667667
def g(): foo.is_used()
668-
''')
668+
''', m.UnusedIndirectAssignment)
669669

670670
def test_assignedToGlobal(self):
671671
"""

pyflakes/test/test_other.py

Lines changed: 35 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1085,7 +1085,41 @@ def test_globalDeclaredInDifferentScope(self):
10851085
self.flakes('''
10861086
def f(): global foo
10871087
def g(): foo = 'anything'; foo.is_used()
1088-
''')
1088+
''', m.UnusedIndirectAssignment)
1089+
1090+
def test_unused_global_statement(self):
1091+
self.flakes('''
1092+
g = 0
1093+
def f1():
1094+
global g
1095+
g = 1
1096+
def f2():
1097+
global g # this is unused!
1098+
return g
1099+
''', m.UnusedIndirectAssignment)
1100+
1101+
def test_unused_nonlocal_statement(self):
1102+
self.flakes('''
1103+
def f():
1104+
x = 1
1105+
def set_x():
1106+
nonlocal x
1107+
x = 2
1108+
def get_x():
1109+
nonlocal x
1110+
return x
1111+
set_x()
1112+
return get_x()
1113+
''', m.UnusedIndirectAssignment)
1114+
1115+
def test_unused_global_statement_not_marked_as_used_by_nested_scope(self):
1116+
self.flakes('''
1117+
g = 0
1118+
def f():
1119+
global g
1120+
def f2():
1121+
g = 2
1122+
''', m.UnusedIndirectAssignment, m.UnusedVariable)
10891123

10901124
def test_function_arguments(self):
10911125
"""

pyflakes/test/test_undefined_names.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -327,7 +327,7 @@ def f1():
327327
328328
def f2():
329329
global m
330-
''', m.UndefinedName)
330+
''', m.UndefinedName, m.UnusedIndirectAssignment)
331331

332332
@skip("todo")
333333
def test_unused_global(self):
@@ -462,7 +462,7 @@ def fun2():
462462
a
463463
a = 2
464464
return a
465-
''', m.UndefinedLocal)
465+
''', m.UndefinedLocal, m.UnusedIndirectAssignment)
466466

467467
def test_intermediateClassScopeIgnored(self):
468468
"""

0 commit comments

Comments
 (0)