summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorJimmy Jia <tesrin@gmail.com>2017-11-25 14:47:21 -0500
committerIan Stapleton Cordasco <graffatcolmingov@gmail.com>2017-11-25 13:47:21 -0600
commit8aece72839adb4add6c31ccfcb021cef2d731a81 (patch)
tree3504711f42a96564d4a578ec335903ff2c05fe87
parent8d0f995dafda7105a6966057fd11383f1ea6fb71 (diff)
downloadpyflakes-8aece72839adb4add6c31ccfcb021cef2d731a81.tar.gz
Handle string literal annotations (#313)
-rw-r--r--pyflakes/checker.py43
-rw-r--r--pyflakes/messages.py8
-rw-r--r--pyflakes/test/test_other.py74
3 files changed, 121 insertions, 4 deletions
diff --git a/pyflakes/checker.py b/pyflakes/checker.py
index d8f093a..a070822 100644
--- a/pyflakes/checker.py
+++ b/pyflakes/checker.py
@@ -922,6 +922,39 @@ class Checker(object):
self.popScope()
self.scopeStack = saved_stack
+ def handleAnnotation(self, annotation, node):
+ if isinstance(annotation, ast.Str):
+ # Defer handling forward annotation.
+ def handleForwardAnnotation():
+ try:
+ tree = ast.parse(annotation.s)
+ except SyntaxError:
+ self.report(
+ messages.ForwardAnnotationSyntaxError,
+ node,
+ annotation.s,
+ )
+ return
+
+ body = tree.body
+ if len(body) != 1 or not isinstance(body[0], ast.Expr):
+ self.report(
+ messages.ForwardAnnotationSyntaxError,
+ node,
+ annotation.s,
+ )
+ return
+
+ parsed_annotation = tree.body[0].value
+ for descendant in ast.walk(parsed_annotation):
+ ast.copy_location(descendant, annotation)
+
+ self.handleNode(parsed_annotation, node)
+
+ self.deferFunction(handleForwardAnnotation)
+ else:
+ self.handleNode(annotation, node)
+
def ignore(self, node):
pass
@@ -1160,9 +1193,11 @@ class Checker(object):
if arg in args[:idx]:
self.report(messages.DuplicateArgument, node, arg)
- for child in annotations + defaults:
- if child:
- self.handleNode(child, node)
+ for annotation in annotations:
+ self.handleAnnotation(annotation, node)
+
+ for default in defaults:
+ self.handleNode(default, node)
def runFunction():
@@ -1375,7 +1410,7 @@ class Checker(object):
# Otherwise it's not really ast.Store and shouldn't silence
# UndefinedLocal warnings.
self.handleNode(node.target, node)
- self.handleNode(node.annotation, node)
+ self.handleAnnotation(node.annotation, node)
if node.value:
# If the assignment has value, handle the *value* now.
self.handleNode(node.value, node)
diff --git a/pyflakes/messages.py b/pyflakes/messages.py
index 9e9406c..670f95f 100644
--- a/pyflakes/messages.py
+++ b/pyflakes/messages.py
@@ -231,3 +231,11 @@ class AssertTuple(Message):
Assertion test is a tuple, which are always True.
"""
message = 'assertion is always true, perhaps remove parentheses?'
+
+
+class ForwardAnnotationSyntaxError(Message):
+ message = 'syntax error in forward annotation %r'
+
+ def __init__(self, filename, loc, annotation):
+ Message.__init__(self, filename, loc)
+ self.message_args = (annotation,)
diff --git a/pyflakes/test/test_other.py b/pyflakes/test/test_other.py
index ba052f1..14f213c 100644
--- a/pyflakes/test/test_other.py
+++ b/pyflakes/test/test_other.py
@@ -1890,3 +1890,77 @@ class TestAsyncStatements(TestCase):
class C:
foo: not_a_real_type = None
''', m.UndefinedName)
+ self.flakes('''
+ from foo import Bar
+ bar: Bar
+ ''')
+ self.flakes('''
+ from foo import Bar
+ bar: 'Bar'
+ ''')
+ self.flakes('''
+ import foo
+ bar: foo.Bar
+ ''')
+ self.flakes('''
+ import foo
+ bar: 'foo.Bar'
+ ''')
+ self.flakes('''
+ from foo import Bar
+ def f(bar: Bar): pass
+ ''')
+ self.flakes('''
+ from foo import Bar
+ def f(bar: 'Bar'): pass
+ ''')
+ self.flakes('''
+ from foo import Bar
+ def f(bar) -> Bar: return bar
+ ''')
+ self.flakes('''
+ from foo import Bar
+ def f(bar) -> 'Bar': return bar
+ ''')
+ self.flakes('''
+ bar: 'Bar'
+ ''', m.UndefinedName)
+ self.flakes('''
+ bar: 'foo.Bar'
+ ''', m.UndefinedName)
+ self.flakes('''
+ from foo import Bar
+ bar: str
+ ''', m.UnusedImport)
+ self.flakes('''
+ from foo import Bar
+ def f(bar: str): pass
+ ''', m.UnusedImport)
+ self.flakes('''
+ def f(a: A) -> A: pass
+ class A: pass
+ ''', m.UndefinedName, m.UndefinedName)
+ self.flakes('''
+ def f(a: 'A') -> 'A': return a
+ class A: pass
+ ''')
+ self.flakes('''
+ a: A
+ class A: pass
+ ''', m.UndefinedName)
+ self.flakes('''
+ a: 'A'
+ class A: pass
+ ''')
+ self.flakes('''
+ a: 'A B'
+ ''', m.ForwardAnnotationSyntaxError)
+ self.flakes('''
+ a: 'A; B'
+ ''', m.ForwardAnnotationSyntaxError)
+ self.flakes('''
+ a: '1 + 2'
+ ''')
+ self.flakes('''
+ a: 'a: "A"'
+ ''', m.ForwardAnnotationSyntaxError)