Repository: PyCQA/pyflakes

Issue:
undefined name '__annotations__' when use __annotations__ in module level
Environment:
- Python==3.7.1
- pyflakes==2.0.0

When using pyflake to check following code:

```Python3
# t.py
a: int = 2
print(__annotations__)
```

It will reported that

```
t.py:3: undefined name '__annotations__'
```
About `__annotations__`, please see [PEP 526 -- Syntax for Variable Annotations](https://www.python.org/dev/peps/pep-0526/)
> In addition, at the module or class level, if the item being annotated is a simple name, then it and the annotation will be stored in the `__annotations__` attribute of that module or class (mangled if private) as an ordered mapping from names to evaluated annotations. 

And `__annotations__` is not a [`builtin_vars`](https://github.com/PyCQA/pyflakes/blob/master/pyflakes/checker.py#L21), so it will not found.

I think this attribute should be added in [`_MAGIC_GLOBALS = ['__file__', '__builtins__', 'WindowsError']`](https://github.com/PyCQA/pyflakes/blob/master/pyflakes/checker.py#L488)

If you accept this solution, I will pull request to fix it :)

Discussion:

Patch:
diff --git a/pyflakes/api.py b/pyflakes/api.py
index 805a886..3ddb386 100644
--- a/pyflakes/api.py
+++ b/pyflakes/api.py
@@ -5,6 +5,7 @@ from __future__ import with_statement
 
 import sys
 import os
+import platform
 import re
 import _ast
 
@@ -91,9 +92,6 @@ def checkPath(filename, reporter=None):
     try:
         with open(filename, 'rb') as f:
             codestr = f.read()
-    except UnicodeError:
-        reporter.unexpectedError(filename, 'problem decoding source')
-        return 1
     except IOError:
         msg = sys.exc_info()[1]
         reporter.unexpectedError(filename, msg.args[1])
@@ -186,6 +184,14 @@ def _exitOnSignal(sigName, message):
         pass
 
 
+def _get_version():
+    """
+    Retrieve and format package version along with python version & OS used
+    """
+    return ('%s Python %s on %s' %
+            (__version__, platform.python_version(), platform.system()))
+
+
 def main(prog=None, args=None):
     """Entry point for the script "pyflakes"."""
     import optparse
@@ -194,7 +200,7 @@ def main(prog=None, args=None):
     _exitOnSignal('SIGINT', '... stopped')
     _exitOnSignal('SIGPIPE', 1)
 
-    parser = optparse.OptionParser(prog=prog, version=__version__)
+    parser = optparse.OptionParser(prog=prog, version=_get_version())
     (__, args) = parser.parse_args(args=args)
     reporter = modReporter._makeDefaultReporter()
     if args:
diff --git a/pyflakes/checker.py b/pyflakes/checker.py
index e05bdf4..406b806 100644
--- a/pyflakes/checker.py
+++ b/pyflakes/checker.py
@@ -11,7 +11,8 @@ import os
 import sys
 
 PY2 = sys.version_info < (3, 0)
-PY34 = sys.version_info < (3, 5)    # Python 2.7 to 3.4
+PY35_PLUS = sys.version_info >= (3, 5)    # Python 3.5 and above
+PY36_PLUS = sys.version_info >= (3, 6)    # Python 3.6 and above
 try:
     sys.pypy_version_info
     PYPY = True
@@ -55,12 +56,12 @@ else:
         if isinstance(n, ast.Try):
             return [n.body + n.orelse] + [[hdl] for hdl in n.handlers]
 
-if PY34:
-    FOR_TYPES = (ast.For,)
-    LOOP_TYPES = (ast.While, ast.For)
-else:
+if PY35_PLUS:
     FOR_TYPES = (ast.For, ast.AsyncFor)
     LOOP_TYPES = (ast.While, ast.For, ast.AsyncFor)
+else:
+    FOR_TYPES = (ast.For,)
+    LOOP_TYPES = (ast.While, ast.For)
 
 
 class _FieldsOrder(dict):
@@ -486,6 +487,9 @@ class DoctestScope(ModuleScope):
 # Globally defined names which are not attributes of the builtins module, or
 # are only present on some platforms.
 _MAGIC_GLOBALS = ['__file__', '__builtins__', 'WindowsError']
+# module scope annotation will store in `__annotations__`, see also PEP 526.
+if PY36_PLUS:
+    _MAGIC_GLOBALS.append('__annotations__')
 
 
 def getNodeName(node):


Test patch:
diff --git a/pyflakes/test/test_undefined_names.py b/pyflakes/test/test_undefined_names.py
index 88eba93..222ffce 100644
--- a/pyflakes/test/test_undefined_names.py
+++ b/pyflakes/test/test_undefined_names.py
@@ -227,6 +227,14 @@ class Test(TestCase):
         """
         self.flakes('WindowsError')
 
+    @skipIf(version_info < (3, 6), 'new feature in 3.6')
+    def test_moduleAnnotations(self):
+        """
+        Use of the C{__annotations__} in module scope should not emit
+        an undefined name warning when version is greater than or equal to 3.6.
+        """
+        self.flakes('__annotations__')
+
     def test_magicGlobalsFile(self):
         """
         Use of the C{__file__} magic global should not emit an undefined name