Skip to content

Commit 1881aa2

Browse files
author
exarkun
committed
Merge unused-variable-flakes-2718-2
Author: jml Reviewer: exarkun Fixes: #2718 Add support for detecting local variables which are bound but then never used.
1 parent 52b032c commit 1881aa2

File tree

5 files changed

+276
-25
lines changed

5 files changed

+276
-25
lines changed

pyflakes/checker.py

+113-24
Original file line numberDiff line numberDiff line change
@@ -8,25 +8,37 @@
88
from pyflakes import messages
99

1010

11+
1112
class Binding(object):
1213
"""
14+
Represents the binding of a value to a name.
15+
16+
The checker uses this to keep track of which names have been bound and
17+
which names have not. See L{Assignment} for a special type of binding that
18+
is checked with stricter rules.
19+
1320
@ivar used: pair of (L{Scope}, line-number) indicating the scope and
1421
line number that this binding was last used
1522
"""
23+
1624
def __init__(self, name, source):
1725
self.name = name
1826
self.source = source
1927
self.used = False
2028

29+
2130
def __str__(self):
2231
return self.name
2332

33+
2434
def __repr__(self):
2535
return '<%s object %r from line %r at 0x%x>' % (self.__class__.__name__,
2636
self.name,
2737
self.source.lineno,
2838
id(self))
2939

40+
41+
3042
class UnBinding(Binding):
3143
'''Created by the 'del' operator.'''
3244

@@ -35,22 +47,44 @@ def __init__(self, name, source):
3547
name = name.split('.')[0]
3648
super(Importation, self).__init__(name, source)
3749

50+
51+
52+
class Argument(Binding):
53+
"""
54+
Represents binding a name as an argument.
55+
"""
56+
57+
58+
3859
class Assignment(Binding):
39-
pass
60+
"""
61+
Represents binding a name with an explicit assignment.
62+
63+
The checker will raise warnings for any Assignment that isn't used. Also,
64+
the checker does not consider assignments in tuple/list unpacking to be
65+
Assignments, rather it treats them as simple Bindings.
66+
"""
67+
68+
4069

4170
class FunctionDefinition(Binding):
4271
pass
4372

4473

74+
4575
class Scope(dict):
4676
importStarred = False # set to True when import * is found
4777

78+
4879
def __repr__(self):
4980
return '<%s at 0x%x %s>' % (self.__class__.__name__, id(self), dict.__repr__(self))
5081

82+
5183
def __init__(self):
5284
super(Scope, self).__init__()
5385

86+
87+
5488
class ClassScope(Scope):
5589
pass
5690

@@ -72,41 +106,77 @@ class ModuleScope(Scope):
72106
pass
73107

74108

75-
76109
# Globally defined names which are not attributes of the __builtin__ module.
77110
_MAGIC_GLOBALS = ['__file__', '__builtins__']
78111

79112

80113

81114
class Checker(object):
115+
"""
116+
I check the cleanliness and sanity of Python code.
117+
118+
@ivar _deferredFunctions: Tracking list used by L{deferFunction}. Elements
119+
of the list are two-tuples. The first element is the callable passed
120+
to L{deferFunction}. The second element is a copy of the scope stack
121+
at the time L{deferFunction} was called.
122+
123+
@ivar _deferredAssignments: Similar to C{_deferredFunctions}, but for
124+
callables which are deferred assignment checks.
125+
"""
126+
82127
nodeDepth = 0
83128
traceTree = False
84129

85130
def __init__(self, tree, filename='(none)'):
86-
self.deferred = []
131+
self._deferredFunctions = []
132+
self._deferredAssignments = []
87133
self.dead_scopes = []
88134
self.messages = []
89135
self.filename = filename
90136
self.scopeStack = [ModuleScope()]
91137
self.futuresAllowed = True
92-
93138
self.handleChildren(tree)
94-
for handler, scope in self.deferred:
95-
self.scopeStack = scope
96-
handler()
139+
self._runDeferred(self._deferredFunctions)
140+
# Set _deferredFunctions to None so that deferFunction will fail
141+
# noisily if called after we've run through the deferred functions.
142+
self._deferredFunctions = None
143+
self._runDeferred(self._deferredAssignments)
144+
# Set _deferredAssignments to None so that deferAssignment will fail
145+
# noisly if called after we've run through the deferred assignments.
146+
self._deferredAssignments = None
97147
del self.scopeStack[1:]
98148
self.popScope()
99149
self.check_dead_scopes()
100150

101-
def defer(self, callable):
102-
'''Schedule something to be called after just before completion.
151+
152+
def deferFunction(self, callable):
153+
'''
154+
Schedule a function handler to be called just before completion.
103155
104156
This is used for handling function bodies, which must be deferred
105157
because code later in the file might modify the global scope. When
106158
`callable` is called, the scope at the time this is called will be
107159
restored, however it will contain any new bindings added to it.
108160
'''
109-
self.deferred.append( (callable, self.scopeStack[:]) )
161+
self._deferredFunctions.append((callable, self.scopeStack[:]))
162+
163+
164+
def deferAssignment(self, callable):
165+
"""
166+
Schedule an assignment handler to be called just after deferred
167+
function handlers.
168+
"""
169+
self._deferredAssignments.append((callable, self.scopeStack[:]))
170+
171+
172+
def _runDeferred(self, deferred):
173+
"""
174+
Run the callables in C{deferred} using their associated scope stack.
175+
"""
176+
for handler, scope in deferred:
177+
self.scopeStack = scope
178+
handler()
179+
110180

111181
def scope(self):
112182
return self.scopeStack[-1]
@@ -132,9 +202,10 @@ def report(self, messageClass, *args, **kwargs):
132202

133203
def handleChildren(self, tree):
134204
for node in tree.getChildNodes():
135-
self.handleNode(node)
205+
self.handleNode(node, tree)
136206

137-
def handleNode(self, node):
207+
def handleNode(self, node, parent):
208+
node.parent = parent
138209
if self.traceTree:
139210
print ' ' * self.nodeDepth + node.__class__.__name__
140211
self.nodeDepth += 1
@@ -208,7 +279,7 @@ def WITH(self, node):
208279
# Of course these are assignments, not references, so we have to
209280
# handle them as a special case here.
210281

211-
self.handleNode(node.expr)
282+
self.handleNode(node.expr, node)
212283

213284
if isinstance(node.vars, ast.AssTuple):
214285
varNodes = node.vars.nodes
@@ -232,8 +303,8 @@ def GLOBAL(self, node):
232303

233304
def LISTCOMP(self, node):
234305
for qual in node.quals:
235-
self.handleNode(qual)
236-
self.handleNode(node.expr)
306+
self.handleNode(qual, node)
307+
self.handleNode(node.expr, node)
237308

238309
GENEXPRINNER = LISTCOMP
239310

@@ -305,7 +376,7 @@ def FUNCTION(self, node):
305376

306377
def LAMBDA(self, node):
307378
for default in node.defaults:
308-
self.handleNode(default)
379+
self.handleNode(default, node)
309380

310381
def runFunction():
311382
args = []
@@ -322,11 +393,21 @@ def addArgs(arglist):
322393
self.pushFunctionScope()
323394
addArgs(node.argnames)
324395
for name in args:
325-
self.addBinding(node.lineno, Assignment(name, node), reportRedef=False)
326-
self.handleNode(node.code)
396+
self.addBinding(node.lineno, Argument(name, node), reportRedef=False)
397+
self.handleNode(node.code, node)
398+
def checkUnusedAssignments():
399+
"""
400+
Check to see if any assignments have not been used.
401+
"""
402+
for name, binding in self.scope.iteritems():
403+
if (not binding.used and not name in self.scope.globals
404+
and isinstance(binding, Assignment)):
405+
self.report(messages.UnusedVariable,
406+
binding.source.lineno, name)
407+
self.deferAssignment(checkUnusedAssignments)
327408
self.popScope()
328409

329-
self.defer(runFunction)
410+
self.deferFunction(runFunction)
330411

331412

332413
def CLASS(self, node):
@@ -337,9 +418,9 @@ def CLASS(self, node):
337418
"""
338419
if getattr(node, "decorators", None) is not None:
339420
self.handleChildren(node.decorators)
340-
self.addBinding(node.lineno, Assignment(node.name, node))
421+
self.addBinding(node.lineno, Binding(node.name, node))
341422
for baseNode in node.bases:
342-
self.handleNode(baseNode)
423+
self.handleNode(baseNode, node)
343424
self.pushClassScope()
344425
self.handleChildren(node.code)
345426
self.popScope()
@@ -372,12 +453,20 @@ def ASSNAME(self, node):
372453
scope[node.name].source.lineno)
373454
break
374455

375-
self.addBinding(node.lineno, Assignment(node.name, node))
456+
if isinstance(node.parent,
457+
(ast.For, ast.ListCompFor, ast.GenExprFor,
458+
ast.AssTuple, ast.AssList)):
459+
binding = Binding(node.name, node)
460+
else:
461+
binding = Assignment(node.name, node)
462+
if node.name in self.scope:
463+
binding.used = self.scope[node.name].used
464+
self.addBinding(node.lineno, binding)
376465

377466
def ASSIGN(self, node):
378-
self.handleNode(node.expr)
467+
self.handleNode(node.expr, node)
379468
for subnode in node.nodes[::-1]:
380-
self.handleNode(subnode)
469+
self.handleNode(subnode, node)
381470

382471
def IMPORT(self, node):
383472
for name, alias in node.names:

pyflakes/messages.py

+12
Original file line numberDiff line numberDiff line change
@@ -71,3 +71,15 @@ class LateFutureImport(Message):
7171
def __init__(self, filename, lineno, names):
7272
Message.__init__(self, filename, lineno)
7373
self.message_args = (names,)
74+
75+
76+
class UnusedVariable(Message):
77+
"""
78+
Indicates that a variable has been explicity assigned to but not actually
79+
used.
80+
"""
81+
82+
message = 'local variable %r is assigned to but never used'
83+
def __init__(self, filename, lineno, names):
84+
Message.__init__(self, filename, lineno)
85+
self.message_args = (names,)

pyflakes/test/test_imports.py

+1
Original file line numberDiff line numberDiff line change
@@ -401,6 +401,7 @@ def test_nonGlobalDoesNotRedefine(self):
401401
import fu
402402
def a():
403403
fu = 3
404+
return fu
404405
fu
405406
''')
406407

0 commit comments

Comments
 (0)