diff options
author | Danny Sepler <dannysepler@gmail.com> | 2022-09-16 00:00:27 -0400 |
---|---|---|
committer | GitHub <noreply@github.com> | 2022-09-16 00:00:27 -0400 |
commit | 4a2407d7aa0834c37c9fe97e2db37fa8d01caa5b (patch) | |
tree | 0a2d90a4c44b6006cefc45f332328b4ece27c9ce | |
parent | 4dcd92e45efeb0615ba1c96d45241a037d30abe0 (diff) | |
download | pyflakes-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.py | 26 | ||||
-rw-r--r-- | pyflakes/messages.py | 12 | ||||
-rw-r--r-- | pyflakes/test/test_type_annotations.py | 13 |
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 |