Repository: PyCQA/pyflakes (python)

Issue:
F821 Error with Forward Reference and `from __future__ import annotations`
With this example file:

```python
# test.py
from __future__ import annotations
from dataclasses import dataclass

@dataclass
class MyData:
    member: MyMember

@dataclass
class MyMember:
    value: int
```

I get an F821 error:
```shell
$ flake8 --select=F test.py
test.py:8:13: F821 undefined name 'MyMember'
```

I suppose the error is correct, but forward references in this form are explicitly supported with the `annotations` future and I think this should be changed in some manner.

Pull request:
Add support for PEP 563 (#356)
If a module imports `annotations` from `__future__`, postpone the evaluation
of annotations like it is done for annotation strings.

Closes #356 

Patch:
diff --git a/.travis.yml b/.travis.yml
index 29f1d44..2113aec 100644
--- a/.travis.yml
+++ b/.travis.yml
@@ -1,18 +1,21 @@
 language: python
 cache: pip
-python:
-  - 2.7
-  - 3.4
-  - 3.5
-  - 3.6
-  - nightly
-  - pypy
-  - pypy-5.3
-  - pypy3
+matrix:
+  include:
+    - python: 2.7
+    - python: 3.4
+    - python: 3.5
+    - python: 3.6
+    - python: pypy
+    - python: pypy-5.3
+    - python: pypy3
+    - python: 3.7
+      dist: xenial
+    - python: nightly
+      dist: xenial
 install:
   - pip install --upgrade .
   - pip list
 script:
   - python setup.py test -q
   - if [ "$TRAVIS_PYTHON_VERSION" != "nightly" ]; then pip install flake8==2.1.0 pep8==1.5.6 && flake8 --version && flake8 pyflakes setup.py; fi
-sudo: false
diff --git a/pyflakes/checker.py b/pyflakes/checker.py
index 5c12820..e05bdf4 100644
--- a/pyflakes/checker.py
+++ b/pyflakes/checker.py
@@ -476,6 +476,7 @@ class GeneratorScope(Scope):
 class ModuleScope(Scope):
     """Scope for a module."""
     _futures_allowed = True
+    _annotations_future_enabled = False
 
 
 class DoctestScope(ModuleScope):
@@ -628,6 +629,19 @@ class Checker(object):
         if isinstance(self.scope, ModuleScope):
             self.scope._futures_allowed = False
 
+    @property
+    def annotationsFutureEnabled(self):
+        scope = self.scopeStack[0]
+        if not isinstance(scope, ModuleScope):
+            return False
+        return scope._annotations_future_enabled
+
+    @annotationsFutureEnabled.setter
+    def annotationsFutureEnabled(self, value):
+        assert value is True
+        assert isinstance(self.scope, ModuleScope)
+        self.scope._annotations_future_enabled = True
+
     @property
     def scope(self):
         return self.scopeStack[-1]
@@ -1068,6 +1082,8 @@ class Checker(object):
                 self.handleNode(parsed_annotation, node)
 
             self.deferFunction(handleForwardAnnotation)
+        elif self.annotationsFutureEnabled:
+            self.deferFunction(lambda: self.handleNode(annotation, node))
         else:
             self.handleNode(annotation, node)
 
@@ -1448,6 +1464,8 @@ class Checker(object):
                 if alias.name not in __future__.all_feature_names:
                     self.report(messages.FutureFeatureNotDefined,
                                 node, alias.name)
+                if alias.name == 'annotations':
+                    self.annotationsFutureEnabled = True
             elif alias.name == '*':
                 # Only Python 2, local import * is a SyntaxWarning
                 if not PY2 and not isinstance(self.scope, ModuleScope):
diff --git a/tox.ini b/tox.ini
index fabad9d..f3db2bb 100644
--- a/tox.ini
+++ b/tox.ini
@@ -1,7 +1,7 @@
 [tox]
 skip_missing_interpreters = True
 envlist =
-    py27,py34,py35,py36,pypy,pypy3
+    py27,py34,py35,py36,py37,pypy,pypy3
 
 [testenv]
 deps =


Test patch:
diff --git a/pyflakes/test/test_other.py b/pyflakes/test/test_other.py
index b8301ea..d0fff0a 100644
--- a/pyflakes/test/test_other.py
+++ b/pyflakes/test/test_other.py
@@ -2055,6 +2055,24 @@ class TestAsyncStatements(TestCase):
         a: 'a: "A"'
         ''', m.ForwardAnnotationSyntaxError)
 
+    @skipIf(version_info < (3, 7), 'new in Python 3.7')
+    def test_postponed_annotations(self):
+        self.flakes('''
+        from __future__ import annotations
+        def f(a: A) -> A: pass
+        class A:
+            b: B
+        class B: pass
+        ''')
+
+        self.flakes('''
+        from __future__ import annotations
+        def f(a: A) -> A: pass
+        class A:
+            b: Undefined
+        class B: pass
+        ''', m.UndefinedName)
+
     def test_raise_notimplemented(self):
         self.flakes('''
         raise NotImplementedError("This is fine")