summaryrefslogtreecommitdiff
path: root/pyflakes/checker.py
diff options
context:
space:
mode:
Diffstat (limited to 'pyflakes/checker.py')
-rw-r--r--pyflakes/checker.py62
1 files changed, 49 insertions, 13 deletions
diff --git a/pyflakes/checker.py b/pyflakes/checker.py
index 1fbad48..16c783f 100644
--- a/pyflakes/checker.py
+++ b/pyflakes/checker.py
@@ -79,6 +79,10 @@ else:
LOOP_TYPES = (ast.While, ast.For)
FUNCTION_TYPES = (ast.FunctionDef,)
+if PY36_PLUS:
+ ANNASSIGN_TYPES = (ast.AnnAssign,)
+else:
+ ANNASSIGN_TYPES = ()
if PY38_PLUS:
def _is_singleton(node): # type: (ast.AST) -> bool
@@ -528,6 +532,16 @@ class Assignment(Binding):
"""
+class Annotation(Binding):
+ """
+ Represents binding a name to a type without an associated value.
+
+ As long as this name is not assigned a value in another binding, it is considered
+ undefined for most purposes. One notable exception is using the name as a type
+ annotation.
+ """
+
+
class FunctionDefinition(Definition):
pass
@@ -732,6 +746,12 @@ def is_typing_overload(value, scope_stack):
)
+class AnnotationState:
+ NONE = 0
+ STRING = 1
+ BARE = 2
+
+
def in_annotation(func):
@functools.wraps(func)
def in_annotation_func(self, *args, **kwargs):
@@ -740,6 +760,14 @@ def in_annotation(func):
return in_annotation_func
+def in_string_annotation(func):
+ @functools.wraps(func)
+ def in_annotation_func(self, *args, **kwargs):
+ with self._enter_annotation(AnnotationState.STRING):
+ return func(self, *args, **kwargs)
+ return in_annotation_func
+
+
def make_tokens(code):
# PY3: tokenize.tokenize requires readline of bytes
if not isinstance(code, bytes):
@@ -826,7 +854,7 @@ class Checker(object):
nodeDepth = 0
offset = None
traceTree = False
- _in_annotation = False
+ _in_annotation = AnnotationState.NONE
_in_typing_literal = False
_in_deferred = False
@@ -1146,8 +1174,11 @@ class Checker(object):
# iteration
continue
- if (name == 'print' and
- isinstance(scope.get(name, None), Builtin)):
+ binding = scope.get(name, None)
+ if isinstance(binding, Annotation) and not self._in_postponed_annotation:
+ continue
+
+ if name == 'print' and isinstance(binding, Builtin):
parent = self.getParent(node)
if (isinstance(parent, ast.BinOp) and
isinstance(parent.op, ast.RShift)):
@@ -1222,7 +1253,9 @@ class Checker(object):
break
parent_stmt = self.getParent(node)
- if isinstance(parent_stmt, (FOR_TYPES, ast.comprehension)) or (
+ if isinstance(parent_stmt, ANNASSIGN_TYPES) and parent_stmt.value is None:
+ binding = Annotation(name, node)
+ elif isinstance(parent_stmt, (FOR_TYPES, ast.comprehension)) or (
parent_stmt != node._pyflakes_parent and
not self.isLiteralTupleUnpacking(parent_stmt)):
binding = Binding(name, node)
@@ -1265,13 +1298,20 @@ class Checker(object):
self.report(messages.UndefinedName, node, name)
@contextlib.contextmanager
- def _enter_annotation(self):
- orig, self._in_annotation = self._in_annotation, True
+ def _enter_annotation(self, ann_type=AnnotationState.BARE):
+ orig, self._in_annotation = self._in_annotation, ann_type
try:
yield
finally:
self._in_annotation = orig
+ @property
+ def _in_postponed_annotation(self):
+ return (
+ self._in_annotation == AnnotationState.STRING or
+ self.annotationsFutureEnabled
+ )
+
def _handle_type_comments(self, node):
for (lineno, col_offset), comment in self._type_comments.get(node, ()):
comment = comment.split(':', 1)[1].strip()
@@ -1399,7 +1439,7 @@ class Checker(object):
self.popScope()
self.scopeStack = saved_stack
- @in_annotation
+ @in_string_annotation
def handleStringAnnotation(self, s, node, ref_lineno, ref_col_offset, err):
try:
tree = ast.parse(s)
@@ -1611,7 +1651,7 @@ class Checker(object):
len(node.args) >= 1 and
isinstance(node.args[0], ast.Str)
):
- with self._enter_annotation():
+ with self._enter_annotation(AnnotationState.STRING):
self.handleNode(node.args[0], node)
self.handleChildren(node)
@@ -2224,11 +2264,7 @@ class Checker(object):
self.scope[node.name] = prev_definition
def ANNASSIGN(self, node):
- if node.value:
- # Only bind the *targets* if the assignment has a value.
- # Otherwise it's not really ast.Store and shouldn't silence
- # UndefinedLocal warnings.
- self.handleNode(node.target, node)
+ self.handleNode(node.target, node)
self.handleAnnotation(node.annotation, node)
if node.value:
# If the assignment has value, handle the *value* now.