Repository: python/mypy

Issue:
Update fastparse for 3.9 AST changes
In 3.9.0a5, some changes landed to the AST for subscripting (https://github.com/python/cpython/pull/9605). We need to support them.

This first got reported in #8614 as "syntax error in type comment", which is pretty bad, also! We should clean up that error message when we fix this.



Discussion:
If it helps, I've got CI failing 3.9.0a5 here with the same error: https://travis-ci.org/github/cooperlees/aioexabgp/jobs/673829413
- If not, please ignore.
- Happy to help test anything if needed

Thanks, subscribing.
Do you need help with this? I reviewed the CPython change and I can probably figure out how to do this easily.

Resolving patch:
diff --git a/mypy/fastparse.py b/mypy/fastparse.py
--- a/mypy/fastparse.py
+++ b/mypy/fastparse.py
@@ -49,6 +49,8 @@
         import ast as ast3
         assert 'kind' in ast3.Constant._fields, \
                "This 3.8.0 alpha (%s) is too old; 3.8.0a3 required" % sys.version.split()[0]
+        # TODO: Num, Str, Bytes, NameConstant, Ellipsis are deprecated in 3.8.
+        # TODO: Index, ExtSlice are deprecated in 3.9.
         from ast import (
             AST,
             Call,
@@ -1503,19 +1505,30 @@ def visit_Bytes(self, n: Bytes) -> Type:
         contents = bytes_to_human_readable_repr(n.s)
         return RawExpressionType(contents, 'builtins.bytes', self.line, column=n.col_offset)
 
-    # Subscript(expr value, slice slice, expr_context ctx)
+    # Subscript(expr value, slice slice, expr_context ctx)  # Python 3.8 and before
+    # Subscript(expr value, expr slice, expr_context ctx)  # Python 3.9 and later
     def visit_Subscript(self, n: ast3.Subscript) -> Type:
-        if not isinstance(n.slice, Index):
-            self.fail(TYPE_COMMENT_SYNTAX_ERROR, self.line, getattr(n, 'col_offset', -1))
-            return AnyType(TypeOfAny.from_error)
+        if sys.version_info >= (3, 9):  # Really 3.9a5 or later
+            sliceval = n.slice  # type: Any
+            if (isinstance(sliceval, ast3.Slice) or
+                (isinstance(sliceval, ast3.Tuple) and
+                 any(isinstance(x, ast3.Slice) for x in sliceval.elts))):
+                self.fail(TYPE_COMMENT_SYNTAX_ERROR, self.line, getattr(n, 'col_offset', -1))
+                return AnyType(TypeOfAny.from_error)
+        else:
+            # Python 3.8 or earlier use a different AST structure for subscripts
+            if not isinstance(n.slice, Index):
+                self.fail(TYPE_COMMENT_SYNTAX_ERROR, self.line, getattr(n, 'col_offset', -1))
+                return AnyType(TypeOfAny.from_error)
+            sliceval = n.slice.value
 
         empty_tuple_index = False
-        if isinstance(n.slice.value, ast3.Tuple):
-            params = self.translate_expr_list(n.slice.value.elts)
-            if len(n.slice.value.elts) == 0:
+        if isinstance(sliceval, ast3.Tuple):
+            params = self.translate_expr_list(sliceval.elts)
+            if len(sliceval.elts) == 0:
                 empty_tuple_index = True
         else:
-            params = [self.visit(n.slice.value)]
+            params = [self.visit(sliceval)]
 
         value = self.visit(n.value)
         if isinstance(value, UnboundType) and not value.args:


Test patch:
diff --git a/mypy/test/helpers.py b/mypy/test/helpers.py
--- a/mypy/test/helpers.py
+++ b/mypy/test/helpers.py
@@ -233,6 +233,8 @@ def clean_up(a: List[str]) -> List[str]:
     remove trailing carriage returns.
     """
     res = []
+    pwd = os.getcwd()
+    driver = pwd + '/driver.py'
     for s in a:
         prefix = os.sep
         ss = s
@@ -241,6 +243,8 @@ def clean_up(a: List[str]) -> List[str]:
                 ss = ss.replace(p, '')
         # Ignore spaces at end of line.
         ss = re.sub(' +$', '', ss)
+        # Remove pwd from driver.py's path
+        ss = ss.replace(driver, 'driver.py')
         res.append(re.sub('\\r$', '', ss))
     return res
 
diff --git a/mypyc/test-data/run.test b/mypyc/test-data/run.test
--- a/mypyc/test-data/run.test
+++ b/mypyc/test-data/run.test
@@ -4352,7 +4352,10 @@ import sys
 
 # We lie about the version we are running in tests if it is 3.5, so
 # that hits a crash case.
-if sys.version_info[:2] == (3, 8):
+if sys.version_info[:2] == (3, 9):
+    def version() -> int:
+        return 9
+elif sys.version_info[:2] == (3, 8):
     def version() -> int:
         return 8
 elif sys.version_info[:2] == (3, 7):