Skip to content

Commit

Permalink
Detect unused annotations in functions
Browse files Browse the repository at this point in the history
  • Loading branch information
Danny Sepler authored and dannysepler committed Sep 16, 2022
1 parent 4dcd92e commit 60051d2
Show file tree
Hide file tree
Showing 3 changed files with 51 additions and 5 deletions.
21 changes: 21 additions & 0 deletions pyflakes/checker.py
Expand Up @@ -600,6 +600,17 @@ def unusedAssignments(self):
isinstance(binding, Assignment)):
yield name, binding

def unusedAnnotations(self):
"""
Return a generator for the annotations which have not been used.
"""
for name, binding in self.items():
if (not binding.used and
name not in self.globals and
not self.usesLocals and
isinstance(binding, Annotation)):
yield name, binding


class GeneratorScope(Scope):
pass
Expand Down Expand Up @@ -1156,6 +1167,7 @@ def handleNodeLoad(self, node):

binding = scope.get(name, None)
if isinstance(binding, Annotation) and not self._in_postponed_annotation:
scope[name].used = True
continue

if name == 'print' and isinstance(binding, Builtin):
Expand Down Expand Up @@ -2090,7 +2102,16 @@ def checkUnusedAssignments():
"""
for name, binding in self.scope.unusedAssignments():
self.report(messages.UnusedVariable, binding.source, name)

def checkUnusedAnnotations():
"""
Check to see if any annotations have not been used.
"""
for name, binding in self.scope.unusedAnnotations():
self.report(messages.UnusedAnnotation, binding.source, name)

self.deferAssignment(checkUnusedAssignments)
self.deferAssignment(checkUnusedAnnotations)

self.popScope()

Expand Down
19 changes: 19 additions & 0 deletions pyflakes/messages.py
Expand Up @@ -156,6 +156,25 @@ def __init__(self, filename, loc, names):
self.message_args = (names,)


class UnusedAnnotation(Message):
"""
Indicates that a variable has been explicitly annotated to but not actually
used.
"""
message = 'local variable %r is annotated but never used'

def __init__(self, filename, loc, names):
Message.__init__(self, filename, loc)
self.message_args = (names,)


class ReturnWithArgsInsideGenerator(Message):
"""
Indicates a return statement with arguments inside a generator.
"""
message = '\'return\' with argument inside generator'


class ReturnOutsideFunction(Message):
"""
Indicates a return statement outside of a function/method.
Expand Down
16 changes: 11 additions & 5 deletions pyflakes/test/test_type_annotations.py
Expand Up @@ -5,7 +5,7 @@
from sys import version_info

from pyflakes import messages as m
from pyflakes.test.harness import TestCase, skipIf
from pyflakes.test.harness import TestCase, skip, skipIf


class TestTypeAnnotations(TestCase):
Expand Down Expand Up @@ -174,7 +174,7 @@ class C:
def f():
name: str
age: int
''')
''', m.UnusedAnnotation, m.UnusedAnnotation)
self.flakes('''
def f():
name: str = 'Bob'
Expand All @@ -190,7 +190,7 @@ def f():
from typing import Any
def f():
a: Any
''')
''', m.UnusedAnnotation)
self.flakes('''
foo: not_a_real_type
''', m.UndefinedName)
Expand Down Expand Up @@ -356,18 +356,24 @@ def test_unused_annotation(self):
class Cls:
y: int
''')
# TODO: this should print a UnusedVariable message
# This should print an UnusedAnnotation message
self.flakes('''
def f():
x: int
''')
''', m.UnusedAnnotation)
# This should only print one UnusedVariable message
self.flakes('''
def f():
x: int
x = 3
''', m.UnusedVariable)

def test_unassigned_annotation_is_undefined(self):
self.flakes('''
name: str
print(name)
''', m.UndefinedName)

def test_annotated_async_def(self):
self.flakes('''
class c: pass
Expand Down

0 comments on commit 60051d2

Please sign in to comment.