Repository: PyCQA/pyflakes
Pull request #694 (2022-05-17 00:14:37)

add tests for python3.11-specific syntax
fortunately we don't really need to make a new release for this thanks to the work I did in #490

the only behaviour this "fixes" is this pattern which I highly doubt anyone would use in reality:

```python
try:
    undefined_name
except* NameError:
    pass
```


Patch:
diff --git a/pyflakes/checker.py b/pyflakes/checker.py
index 7f33d6a..c89bba9 100644
--- a/pyflakes/checker.py
+++ b/pyflakes/checker.py
@@ -540,6 +540,12 @@ class Assignment(Binding):
     """
 
 
+class NamedExprAssignment(Assignment):
+    """
+    Represents binding a name with an assignment expression.
+    """
+
+
 class Annotation(Binding):
     """
     Represents binding a name to a type without an associated value.
@@ -1159,7 +1165,14 @@ class Checker(object):
         # don't treat annotations as assignments if there is an existing value
         # in scope
         if value.name not in self.scope or not isinstance(value, Annotation):
-            self.scope[value.name] = value
+            cur_scope_pos = -1
+            # As per PEP 572, use scope in which outermost generator is defined
+            while (
+                isinstance(value, NamedExprAssignment) and
+                isinstance(self.scopeStack[cur_scope_pos], GeneratorScope)
+            ):
+                cur_scope_pos -= 1
+            self.scopeStack[cur_scope_pos][value.name] = value
 
     def _unknown_handler(self, node):
         # this environment variable configures whether to error on unknown
@@ -1302,6 +1315,8 @@ class Checker(object):
             binding = ExportBinding(name, node._pyflakes_parent, self.scope)
         elif PY2 and isinstance(getattr(node, 'ctx', None), ast.Param):
             binding = Argument(name, self.getScopeNode(node))
+        elif PY38_PLUS and isinstance(parent_stmt, ast.NamedExpr):
+            binding = NamedExprAssignment(name, node)
         else:
             binding = Assignment(name, node)
         self.addBinding(node, binding)
@@ -2331,7 +2346,7 @@ class Checker(object):
         # Process the other nodes: "except:", "else:", "finally:"
         self.handleChildren(node, omit='body')
 
-    TRYEXCEPT = TRY
+    TRYEXCEPT = TRYSTAR = TRY
 
     def EXCEPTHANDLER(self, node):
         if PY2 or node.name is None:


Test patch:
diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml
index 0c5996f..54f1494 100644
--- a/.github/workflows/test.yml
+++ b/.github/workflows/test.yml
@@ -1,6 +1,10 @@
 name: Test
 
-on: [push, pull_request]
+on:
+  pull_request:
+  push:
+    branches: [master, main, 'test-me-*']
+    tags: ['*']
 
 jobs:
   build:
@@ -8,7 +12,7 @@ jobs:
     strategy:
       fail-fast: false
       matrix:
-        python-version: ["3.6", "3.7", "3.8", "3.9", "3.10", "pypy-3.7"]
+        python-version: ["3.6", "3.7", "3.8", "3.9", "3.10", "3.11.0-beta - 3.11.999", "pypy-3.7"]
         os: [ubuntu-latest]
         # Include minimum py3 + maximum py3 + pypy3 on Windows
         include:
diff --git a/pyflakes/test/test_other.py b/pyflakes/test/test_other.py
index 68813bd..97685ef 100644
--- a/pyflakes/test/test_other.py
+++ b/pyflakes/test/test_other.py
@@ -1668,6 +1668,15 @@ class TestUnusedAssignment(TestCase):
         except Exception as e: pass
         ''', m.UnusedVariable)
 
+    @skipIf(version_info < (3, 11), 'new in Python 3.11')
+    def test_exception_unused_in_except_star(self):
+        self.flakes('''
+            try:
+                pass
+            except* OSError as e:
+                pass
+        ''', m.UnusedVariable)
+
     def test_exceptionUnusedInExceptInFunction(self):
         self.flakes('''
         def download_review():
@@ -1772,6 +1781,23 @@ class TestUnusedAssignment(TestCase):
         print(x)
         ''')
 
+    @skipIf(version_info < (3, 8), 'new in Python 3.8')
+    def test_assign_expr_generator_scope(self):
+        """Test assignment expressions in generator expressions."""
+        self.flakes('''
+        if (any((y := x[0]) for x in [[True]])):
+            print(y)
+        ''')
+
+    @skipIf(version_info < (3, 8), 'new in Python 3.8')
+    def test_assign_expr_nested(self):
+        """Test assignment expressions in nested expressions."""
+        self.flakes('''
+        if ([(y:=x) for x in range(4) if [(z:=q) for q in range(4)]]):
+            print(y)
+            print(z)
+        ''')
+
 
 class TestStringFormatting(TestCase):
 
diff --git a/pyflakes/test/test_type_annotations.py b/pyflakes/test/test_type_annotations.py
index 3b6d5e7..1caecb4 100644
--- a/pyflakes/test/test_type_annotations.py
+++ b/pyflakes/test/test_type_annotations.py
@@ -801,3 +801,18 @@ class TestTypeAnnotations(TestCase):
             class Y(NamedTuple):
                 y: NamedTuple("v", [("vv", int)])
         """)
+
+    @skipIf(version_info < (3, 11), 'new in Python 3.11')
+    def test_variadic_generics(self):
+        self.flakes("""
+            from typing import Generic
+            from typing import TypeVarTuple
+
+            Ts = TypeVarTuple('Ts')
+
+            class Shape(Generic[*Ts]): pass
+
+            def f(*args: *Ts) -> None: ...
+
+            def g(x: Shape[*Ts]) -> Shape[*Ts]: ...
+        """)