Repository: PyCQA/pyflakes (python)

Issue:
"undefined name in __all__" has non-deterministic ordering
Found this while putting together a tool to look for regressions in the latest release and noticed that this produces things out-of-order!

```console
$ cat t.py 
__all__ = ('a', 'b', 'c', 'd')
$ python3 -m pyflakes t.py
t.py:1:1 undefined name 'a' in __all__
t.py:1:1 undefined name 'd' in __all__
t.py:1:1 undefined name 'b' in __all__
t.py:1:1 undefined name 'c' in __all__
$ python3 -m pyflakes t.py
t.py:1:1 undefined name 'c' in __all__
t.py:1:1 undefined name 'd' in __all__
t.py:1:1 undefined name 'b' in __all__
t.py:1:1 undefined name 'a' in __all__
```

This is due to an intermediate set in the code for that check: https://github.com/PyCQA/pyflakes/blob/0e4194b238ffd006441f1a33373062d9c7272d0e/pyflakes/checker.py#L993-L997

Pull request:
deterministic ordering for errors in __all__
Resolves #603

I couldn't figure out a way to test this that wasn't flakey and without writing a bunch of test infra to look at the actual messages (since they're all the same type)

Here's a demo of the fix however:

```console
$ python3 -m pyflakes t.py
t.py:1:1 undefined name 'a' in __all__
t.py:1:1 undefined name 'b' in __all__
t.py:1:1 undefined name 'c' in __all__
t.py:1:1 undefined name 'd' in __all__
$ python3 -m pyflakes t.py
t.py:1:1 undefined name 'a' in __all__
t.py:1:1 undefined name 'b' in __all__
t.py:1:1 undefined name 'c' in __all__
t.py:1:1 undefined name 'd' in __all__
$ python3 -m pyflakes t.py
t.py:1:1 undefined name 'a' in __all__
t.py:1:1 undefined name 'b' in __all__
t.py:1:1 undefined name 'c' in __all__
t.py:1:1 undefined name 'd' in __all__
$ python3 -m pyflakes t.py
t.py:1:1 undefined name 'a' in __all__
t.py:1:1 undefined name 'b' in __all__
t.py:1:1 undefined name 'c' in __all__
t.py:1:1 undefined name 'd' in __all__
$ python3 -m pyflakes t.py
t.py:1:1 undefined name 'a' in __all__
t.py:1:1 undefined name 'b' in __all__
t.py:1:1 undefined name 'c' in __all__
t.py:1:1 undefined name 'd' in __all__
```

Patch:
diff --git a/pyflakes/checker.py b/pyflakes/checker.py
index 0d65506..38f8f73 100644
--- a/pyflakes/checker.py
+++ b/pyflakes/checker.py
@@ -992,7 +992,10 @@ class Checker(object):
 
             if all_binding:
                 all_names = set(all_binding.names)
-                undefined = all_names.difference(scope)
+                undefined = [
+                    name for name in all_binding.names
+                    if name not in scope
+                ]
             else:
                 all_names = undefined = []
 
@@ -1680,6 +1683,19 @@ class Checker(object):
             with self._enter_annotation(AnnotationState.STRING):
                 self.handleNode(node.args[0], node)
 
+        elif _is_typing(node.func, 'TypeVar', self.scopeStack):
+            # TypeVar("T", "int", "str")
+            for arg in node.args[1:]:
+                if isinstance(arg, ast.Str):
+                    with self._enter_annotation():
+                        self.handleNode(arg, node)
+
+            # TypeVar("T", bound="str")
+            for keyword in node.keywords:
+                if keyword.arg == 'bound' and isinstance(keyword.value, ast.Str):
+                    with self._enter_annotation():
+                        self.handleNode(keyword.value, node)
+
         self.handleChildren(node)
 
     def _handle_percent_format(self, node):


Test patch:
diff --git a/pyflakes/test/test_type_annotations.py b/pyflakes/test/test_type_annotations.py
index d5a3e08..409238b 100644
--- a/pyflakes/test/test_type_annotations.py
+++ b/pyflakes/test/test_type_annotations.py
@@ -524,6 +524,21 @@ class TestTypeAnnotations(TestCase):
         maybe_int = tsac('Maybe[int]', 42)
         """)
 
+    def test_quoted_TypeVar_constraints(self):
+        self.flakes("""
+        from typing import TypeVar, Optional
+
+        T = TypeVar('T', 'str', 'Optional[int]', bytes)
+        """)
+
+    def test_quoted_TypeVar_bound(self):
+        self.flakes("""
+        from typing import TypeVar, Optional, List
+
+        T = TypeVar('T', bound='Optional[int]')
+        S = TypeVar('S', int, bound='List[int]')
+        """)
+
     @skipIf(version_info < (3,), 'new in Python 3')
     def test_literal_type_typing(self):
         self.flakes("""