Skip to content

Commit 4a2407d

Browse files
dannyseplerDanny Sepler
and
Danny Sepler
authored
Detect unused annotations in functions (#668)
* Detect unused annotations in functions * Rebase correctly, use snake case, simplify conditions for `unused_annotations` Co-authored-by: Danny Sepler <[email protected]>
1 parent 4dcd92e commit 4a2407d

File tree

3 files changed

+43
-8
lines changed

3 files changed

+43
-8
lines changed

pyflakes/checker.py

+22-4
Original file line numberDiff line numberDiff line change
@@ -588,7 +588,7 @@ def __init__(self):
588588
self.returnValue = None # First non-empty return
589589
self.isGenerator = False # Detect a generator
590590

591-
def unusedAssignments(self):
591+
def unused_assignments(self):
592592
"""
593593
Return a generator for the assignments which have not been used.
594594
"""
@@ -600,6 +600,14 @@ def unusedAssignments(self):
600600
isinstance(binding, Assignment)):
601601
yield name, binding
602602

603+
def unused_annotations(self):
604+
"""
605+
Return a generator for the annotations which have not been used.
606+
"""
607+
for name, binding in self.items():
608+
if not binding.used and isinstance(binding, Annotation):
609+
yield name, binding
610+
603611

604612
class GeneratorScope(Scope):
605613
pass
@@ -1156,6 +1164,7 @@ def handleNodeLoad(self, node):
11561164

11571165
binding = scope.get(name, None)
11581166
if isinstance(binding, Annotation) and not self._in_postponed_annotation:
1167+
scope[name].used = True
11591168
continue
11601169

11611170
if name == 'print' and isinstance(binding, Builtin):
@@ -2084,13 +2093,22 @@ def runFunction():
20842093

20852094
self.handleChildren(node, omit=['decorator_list', 'returns'])
20862095

2087-
def checkUnusedAssignments():
2096+
def check_unused_assignments():
20882097
"""
20892098
Check to see if any assignments have not been used.
20902099
"""
2091-
for name, binding in self.scope.unusedAssignments():
2100+
for name, binding in self.scope.unused_assignments():
20922101
self.report(messages.UnusedVariable, binding.source, name)
2093-
self.deferAssignment(checkUnusedAssignments)
2102+
2103+
def check_unused_annotations():
2104+
"""
2105+
Check to see if any annotations have not been used.
2106+
"""
2107+
for name, binding in self.scope.unused_annotations():
2108+
self.report(messages.UnusedAnnotation, binding.source, name)
2109+
2110+
self.deferAssignment(check_unused_assignments)
2111+
self.deferAssignment(check_unused_annotations)
20942112

20952113
self.popScope()
20962114

pyflakes/messages.py

+12
Original file line numberDiff line numberDiff line change
@@ -156,6 +156,18 @@ def __init__(self, filename, loc, names):
156156
self.message_args = (names,)
157157

158158

159+
class UnusedAnnotation(Message):
160+
"""
161+
Indicates that a variable has been explicitly annotated to but not actually
162+
used.
163+
"""
164+
message = 'local variable %r is annotated but never used'
165+
166+
def __init__(self, filename, loc, names):
167+
Message.__init__(self, filename, loc)
168+
self.message_args = (names,)
169+
170+
159171
class ReturnOutsideFunction(Message):
160172
"""
161173
Indicates a return statement outside of a function/method.

pyflakes/test/test_type_annotations.py

+9-4
Original file line numberDiff line numberDiff line change
@@ -174,7 +174,7 @@ class C:
174174
def f():
175175
name: str
176176
age: int
177-
''')
177+
''', m.UnusedAnnotation, m.UnusedAnnotation)
178178
self.flakes('''
179179
def f():
180180
name: str = 'Bob'
@@ -190,7 +190,7 @@ def f():
190190
from typing import Any
191191
def f():
192192
a: Any
193-
''')
193+
''', m.UnusedAnnotation)
194194
self.flakes('''
195195
foo: not_a_real_type
196196
''', m.UndefinedName)
@@ -356,18 +356,23 @@ def test_unused_annotation(self):
356356
class Cls:
357357
y: int
358358
''')
359-
# TODO: this should print a UnusedVariable message
360359
self.flakes('''
361360
def f():
362361
x: int
363-
''')
362+
''', m.UnusedAnnotation)
364363
# This should only print one UnusedVariable message
365364
self.flakes('''
366365
def f():
367366
x: int
368367
x = 3
369368
''', m.UnusedVariable)
370369

370+
def test_unassigned_annotation_is_undefined(self):
371+
self.flakes('''
372+
name: str
373+
print(name)
374+
''', m.UndefinedName)
375+
371376
def test_annotated_async_def(self):
372377
self.flakes('''
373378
class c: pass

0 commit comments

Comments
 (0)