summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorDanny Sepler <dannysepler@gmail.com>2022-09-16 00:00:27 -0400
committerGitHub <noreply@github.com>2022-09-16 00:00:27 -0400
commit4a2407d7aa0834c37c9fe97e2db37fa8d01caa5b (patch)
tree0a2d90a4c44b6006cefc45f332328b4ece27c9ce
parent4dcd92e45efeb0615ba1c96d45241a037d30abe0 (diff)
downloadpyflakes-4a2407d7aa0834c37c9fe97e2db37fa8d01caa5b.tar.gz
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 <dsepler@flatiron.com>
-rw-r--r--pyflakes/checker.py26
-rw-r--r--pyflakes/messages.py12
-rw-r--r--pyflakes/test/test_type_annotations.py13
3 files changed, 43 insertions, 8 deletions
diff --git a/pyflakes/checker.py b/pyflakes/checker.py
index 56fc3ca..89c9d0a 100644
--- a/pyflakes/checker.py
+++ b/pyflakes/checker.py
@@ -588,7 +588,7 @@ class FunctionScope(Scope):
self.returnValue = None # First non-empty return
self.isGenerator = False # Detect a generator
- def unusedAssignments(self):
+ def unused_assignments(self):
"""
Return a generator for the assignments which have not been used.
"""
@@ -600,6 +600,14 @@ class FunctionScope(Scope):
isinstance(binding, Assignment)):
yield name, binding
+ def unused_annotations(self):
+ """
+ Return a generator for the annotations which have not been used.
+ """
+ for name, binding in self.items():
+ if not binding.used and isinstance(binding, Annotation):
+ yield name, binding
+
class GeneratorScope(Scope):
pass
@@ -1156,6 +1164,7 @@ class Checker:
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):
@@ -2084,13 +2093,22 @@ class Checker:
self.handleChildren(node, omit=['decorator_list', 'returns'])
- def checkUnusedAssignments():
+ def check_unused_assignments():
"""
Check to see if any assignments have not been used.
"""
- for name, binding in self.scope.unusedAssignments():
+ for name, binding in self.scope.unused_assignments():
self.report(messages.UnusedVariable, binding.source, name)
- self.deferAssignment(checkUnusedAssignments)
+
+ def check_unused_annotations():
+ """
+ Check to see if any annotations have not been used.
+ """
+ for name, binding in self.scope.unused_annotations():
+ self.report(messages.UnusedAnnotation, binding.source, name)
+
+ self.deferAssignment(check_unused_assignments)
+ self.deferAssignment(check_unused_annotations)
self.popScope()
diff --git a/pyflakes/messages.py b/pyflakes/messages.py
index 37c4432..c2246cf 100644
--- a/pyflakes/messages.py
+++ b/pyflakes/messages.py
@@ -156,6 +156,18 @@ class UnusedVariable(Message):
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 ReturnOutsideFunction(Message):
"""
Indicates a return statement outside of a function/method.
diff --git a/pyflakes/test/test_type_annotations.py b/pyflakes/test/test_type_annotations.py
index d881205..2ad9f45 100644
--- a/pyflakes/test/test_type_annotations.py
+++ b/pyflakes/test/test_type_annotations.py
@@ -174,7 +174,7 @@ class TestTypeAnnotations(TestCase):
def f():
name: str
age: int
- ''')
+ ''', m.UnusedAnnotation, m.UnusedAnnotation)
self.flakes('''
def f():
name: str = 'Bob'
@@ -190,7 +190,7 @@ class TestTypeAnnotations(TestCase):
from typing import Any
def f():
a: Any
- ''')
+ ''', m.UnusedAnnotation)
self.flakes('''
foo: not_a_real_type
''', m.UndefinedName)
@@ -356,11 +356,10 @@ class TestTypeAnnotations(TestCase):
class Cls:
y: int
''')
- # TODO: this should print a UnusedVariable message
self.flakes('''
def f():
x: int
- ''')
+ ''', m.UnusedAnnotation)
# This should only print one UnusedVariable message
self.flakes('''
def f():
@@ -368,6 +367,12 @@ class TestTypeAnnotations(TestCase):
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