Skip to content

Commit 69a5a02

Browse files
authored
In Python 3.8, use the stdlib ast instead of typed_ast (#6539)
To test with Python 3.8, this requires python/cpython#12295. The typeshed changes were in python/typeshed#2859 (and synced in #6540).
1 parent 58debfa commit 69a5a02

File tree

8 files changed

+183
-144
lines changed

8 files changed

+183
-144
lines changed

mypy/fastparse.py

Lines changed: 171 additions & 55 deletions
Original file line numberDiff line numberDiff line change
@@ -39,24 +39,64 @@
3939
from mypy.options import Options
4040

4141
try:
42-
from typed_ast import ast3
43-
from typed_ast.ast3 import (
44-
AST,
45-
Call,
46-
FunctionType,
47-
Name,
48-
Attribute,
49-
Ellipsis as ast3_Ellipsis,
50-
Starred,
51-
NameConstant,
52-
Expression as ast3_Expression,
53-
Str,
54-
Bytes,
55-
Index,
56-
Num,
57-
UnaryOp,
58-
USub,
59-
)
42+
# Check if we can use the stdlib ast module instead of typed_ast.
43+
if sys.version_info >= (3, 8):
44+
import ast as ast3
45+
assert 'kind' in ast3.Constant._fields, \
46+
"This 3.8.0 alpha (%s) is too old; 3.8.0a3 required" % sys.version.split()[0]
47+
from ast import (
48+
AST,
49+
Call,
50+
FunctionType,
51+
Name,
52+
Attribute,
53+
Ellipsis as ast3_Ellipsis,
54+
Starred,
55+
NameConstant,
56+
Expression as ast3_Expression,
57+
Str,
58+
Bytes,
59+
Index,
60+
Num,
61+
UnaryOp,
62+
USub,
63+
)
64+
65+
def ast3_parse(source: Union[str, bytes], filename: str, mode: str,
66+
feature_version: int = sys.version_info[1]) -> AST:
67+
return ast3.parse(source, filename, mode,
68+
type_comments=True, # This works the magic
69+
feature_version=feature_version)
70+
71+
NamedExpr = ast3.NamedExpr
72+
Constant = ast3.Constant
73+
else:
74+
from typed_ast import ast3
75+
from typed_ast.ast3 import (
76+
AST,
77+
Call,
78+
FunctionType,
79+
Name,
80+
Attribute,
81+
Ellipsis as ast3_Ellipsis,
82+
Starred,
83+
NameConstant,
84+
Expression as ast3_Expression,
85+
Str,
86+
Bytes,
87+
Index,
88+
Num,
89+
UnaryOp,
90+
USub,
91+
)
92+
93+
def ast3_parse(source: Union[str, bytes], filename: str, mode: str,
94+
feature_version: int = sys.version_info[1]) -> AST:
95+
return ast3.parse(source, filename, mode, feature_version=feature_version)
96+
97+
# These don't exist before 3.8
98+
NamedExpr = Any
99+
Constant = Any
60100
except ImportError:
61101
if sys.version_info.minor > 2:
62102
try:
@@ -122,7 +162,7 @@ def parse(source: Union[str, bytes],
122162
else:
123163
assert options.python_version[0] >= 3
124164
feature_version = options.python_version[1]
125-
ast = ast3.parse(source, fnam, 'exec', feature_version=feature_version)
165+
ast = ast3_parse(source, fnam, 'exec', feature_version=feature_version)
126166

127167
tree = ASTConverter(options=options,
128168
is_stub=is_stub_file,
@@ -146,7 +186,7 @@ def parse_type_comment(type_comment: str,
146186
assume_str_is_unicode: bool = True,
147187
) -> Optional[Type]:
148188
try:
149-
typ = ast3.parse(type_comment, '<type_comment>', 'eval')
189+
typ = ast3_parse(type_comment, '<type_comment>', 'eval')
150190
except SyntaxError as e:
151191
if errors is not None:
152192
errors.report(line, e.offset, TYPE_COMMENT_SYNTAX_ERROR, blocker=True)
@@ -366,16 +406,12 @@ def visit_Module(self, mod: ast3.Module) -> MypyFile:
366406
# arguments = (arg* args, arg? vararg, arg* kwonlyargs, expr* kw_defaults,
367407
# arg? kwarg, expr* defaults)
368408
def visit_FunctionDef(self, n: ast3.FunctionDef) -> Union[FuncDef, Decorator]:
369-
f = self.do_func_def(n)
370-
f.set_line(n.lineno, n.col_offset) # Overrides set_line -- can't use self.set_line
371-
return f
409+
return self.do_func_def(n)
372410

373411
# AsyncFunctionDef(identifier name, arguments args,
374412
# stmt* body, expr* decorator_list, expr? returns, string? type_comment)
375413
def visit_AsyncFunctionDef(self, n: ast3.AsyncFunctionDef) -> Union[FuncDef, Decorator]:
376-
f = self.do_func_def(n, is_coroutine=True)
377-
f.set_line(n.lineno, n.col_offset) # Overrides set_line -- can't use self.set_line
378-
return f
414+
return self.do_func_def(n, is_coroutine=True)
379415

380416
def do_func_def(self, n: Union[ast3.FunctionDef, ast3.AsyncFunctionDef],
381417
is_coroutine: bool = False) -> Union[FuncDef, Decorator]:
@@ -397,7 +433,7 @@ def do_func_def(self, n: Union[ast3.FunctionDef, ast3.AsyncFunctionDef],
397433
return_type = None
398434
elif n.type_comment is not None:
399435
try:
400-
func_type_ast = ast3.parse(n.type_comment, '<func_type>', 'func_type')
436+
func_type_ast = ast3_parse(n.type_comment, '<func_type>', 'func_type')
401437
assert isinstance(func_type_ast, FunctionType)
402438
# for ellipsis arg
403439
if (len(func_type_ast.argtypes) == 1 and
@@ -470,15 +506,25 @@ def do_func_def(self, n: Union[ast3.FunctionDef, ast3.AsyncFunctionDef],
470506
func_type.line = lineno
471507

472508
if n.decorator_list:
509+
if sys.version_info < (3, 8):
510+
# Before 3.8, [typed_]ast the line number points to the first decorator.
511+
# In 3.8, it points to the 'def' line, where we want it.
512+
lineno += len(n.decorator_list)
513+
473514
var = Var(func_def.name())
474515
var.is_ready = False
475-
var.set_line(n.decorator_list[0].lineno)
516+
var.set_line(lineno)
476517

477518
func_def.is_decorated = True
478-
func_def.set_line(lineno + len(n.decorator_list))
479-
func_def.body.set_line(func_def.get_line())
480-
return Decorator(func_def, self.translate_expr_list(n.decorator_list), var)
519+
func_def.set_line(lineno, n.col_offset)
520+
func_def.body.set_line(lineno) # TODO: Why?
521+
522+
deco = Decorator(func_def, self.translate_expr_list(n.decorator_list), var)
523+
deco.set_line(n.decorator_list[0].lineno)
524+
return deco
481525
else:
526+
# FuncDef overrides set_line -- can't use self.set_line
527+
func_def.set_line(lineno, n.col_offset)
482528
return func_def
483529

484530
def set_type_optional(self, type: Optional[Type], initializer: Optional[Expression]) -> None:
@@ -568,7 +614,15 @@ def visit_ClassDef(self, n: ast3.ClassDef) -> ClassDef:
568614
metaclass=dict(keywords).get('metaclass'),
569615
keywords=keywords)
570616
cdef.decorators = self.translate_expr_list(n.decorator_list)
571-
self.set_line(cdef, n)
617+
if n.decorator_list and sys.version_info >= (3, 8):
618+
# Before 3.8, n.lineno points to the first decorator; in
619+
# 3.8, it points to the 'class' statement. We always make
620+
# it point to the first decorator. (The node structure
621+
# here is different than for decorated functions.)
622+
cdef.line = n.decorator_list[0].lineno
623+
cdef.column = n.col_offset
624+
else:
625+
self.set_line(cdef, n)
572626
self.class_nesting -= 1
573627
return cdef
574628

@@ -617,6 +671,10 @@ def visit_AugAssign(self, n: ast3.AugAssign) -> OperatorAssignmentStmt:
617671
self.visit(n.value))
618672
return self.set_line(s, n)
619673

674+
def visit_NamedExpr(self, n: NamedExpr) -> None:
675+
self.fail("assignment expressions are not yet supported", n.lineno, n.col_offset)
676+
return None
677+
620678
# For(expr target, expr iter, stmt* body, stmt* orelse, string? type_comment)
621679
def visit_For(self, n: ast3.For) -> ForStmt:
622680
if n.type_comment is not None:
@@ -926,6 +984,30 @@ def visit_Call(self, n: Call) -> CallExpr:
926984
cast('List[Optional[str]]', [None] * len(args)) + keyword_names)
927985
return self.set_line(e, n)
928986

987+
# Constant(object value) -- a constant, in Python 3.8.
988+
def visit_Constant(self, n: Constant) -> Any:
989+
val = n.value
990+
e = None # type: Any
991+
if val is None:
992+
e = NameExpr('None')
993+
elif isinstance(val, str):
994+
e = StrExpr(n.s)
995+
elif isinstance(val, bytes):
996+
e = BytesExpr(bytes_to_human_readable_repr(n.s))
997+
elif isinstance(val, bool): # Must check before int!
998+
e = NameExpr(str(val))
999+
elif isinstance(val, int):
1000+
e = IntExpr(val)
1001+
elif isinstance(val, float):
1002+
e = FloatExpr(val)
1003+
elif isinstance(val, complex):
1004+
e = ComplexExpr(val)
1005+
elif val is Ellipsis:
1006+
e = EllipsisExpr()
1007+
else:
1008+
raise RuntimeError('Constant not implemented for ' + str(type(val)))
1009+
return self.set_line(e, n)
1010+
9291011
# Num(object n) -- a number as a PyObject.
9301012
def visit_Num(self, n: ast3.Num) -> Union[IntExpr, FloatExpr, ComplexExpr]:
9311013
# The n field has the type complex, but complex isn't *really*
@@ -994,7 +1076,8 @@ def visit_Bytes(self, n: ast3.Bytes) -> Union[BytesExpr, StrExpr]:
9941076

9951077
# NameConstant(singleton value)
9961078
def visit_NameConstant(self, n: NameConstant) -> NameExpr:
997-
return NameExpr(str(n.value))
1079+
e = NameExpr(str(n.value))
1080+
return self.set_line(e, n)
9981081

9991082
# Ellipsis
10001083
def visit_Ellipsis(self, n: ast3_Ellipsis) -> EllipsisExpr:
@@ -1094,7 +1177,7 @@ def invalid_type(self, node: AST, note: Optional[str] = None) -> RawExpressionTy
10941177
def visit(self, node: ast3.expr) -> Type: ...
10951178

10961179
@overload # noqa
1097-
def visit(self, node: Optional[AST]) -> Optional[Type]: ...
1180+
def visit(self, node: Optional[AST]) -> Optional[Type]: ... # noqa
10981181

10991182
def visit(self, node: Optional[AST]) -> Optional[Type]: # noqa
11001183
"""Modified visit -- keep track of the stack of nodes"""
@@ -1205,6 +1288,34 @@ def visit_NameConstant(self, n: NameConstant) -> Type:
12051288
else:
12061289
return UnboundType(str(n.value), line=self.line)
12071290

1291+
# Only for 3.8 and newer
1292+
def visit_Constant(self, n: Constant) -> Type:
1293+
val = n.value
1294+
if val is None:
1295+
# None is a type.
1296+
return UnboundType('None', line=self.line)
1297+
if isinstance(val, str):
1298+
# Parse forward reference.
1299+
if (n.kind and 'u' in n.kind) or self.assume_str_is_unicode:
1300+
return parse_type_string(n.s, 'builtins.unicode', self.line, n.col_offset,
1301+
assume_str_is_unicode=self.assume_str_is_unicode)
1302+
else:
1303+
return parse_type_string(n.s, 'builtins.str', self.line, n.col_offset,
1304+
assume_str_is_unicode=self.assume_str_is_unicode)
1305+
if val is Ellipsis:
1306+
# '...' is valid in some types.
1307+
return EllipsisType(line=self.line)
1308+
if isinstance(val, bool):
1309+
# Special case for True/False.
1310+
return RawExpressionType(val, 'builtins.bool', line=self.line)
1311+
if isinstance(val, (int, float, complex)):
1312+
return self.numeric_type(val, n)
1313+
if isinstance(val, bytes):
1314+
contents = bytes_to_human_readable_repr(val)
1315+
return RawExpressionType(contents, 'builtins.bytes', self.line, column=n.col_offset)
1316+
# Everything else is invalid.
1317+
return self.invalid_type(n)
1318+
12081319
# UnaryOp(op, operand)
12091320
def visit_UnaryOp(self, n: UnaryOp) -> Type:
12101321
# We support specifically Literal[-4] and nothing else.
@@ -1216,13 +1327,11 @@ def visit_UnaryOp(self, n: UnaryOp) -> Type:
12161327
return typ
12171328
return self.invalid_type(n)
12181329

1219-
# Num(number n)
1220-
def visit_Num(self, n: Num) -> Type:
1221-
# The n field has the type complex, but complex isn't *really*
1330+
def numeric_type(self, value: object, n: AST) -> Type:
1331+
# The node's field has the type complex, but complex isn't *really*
12221332
# a parent of int and float, and this causes isinstance below
12231333
# to think that the complex branch is always picked. Avoid
12241334
# this by throwing away the type.
1225-
value = n.n # type: object
12261335
if isinstance(value, int):
12271336
numeric_value = value # type: Optional[int]
12281337
type_name = 'builtins.int'
@@ -1239,24 +1348,31 @@ def visit_Num(self, n: Num) -> Type:
12391348
column=getattr(n, 'col_offset', -1),
12401349
)
12411350

1242-
# Str(string s)
1243-
def visit_Str(self, n: Str) -> Type:
1244-
# Note: we transform these fallback types into the correct types in
1245-
# 'typeanal.py' -- specifically in the named_type_with_normalized_str method.
1246-
# If we're analyzing Python 3, that function will translate 'builtins.unicode'
1247-
# into 'builtins.str'. In contrast, if we're analyzing Python 2 code, we'll
1248-
# translate 'builtins.bytes' in the method below into 'builtins.str'.
1249-
if 'u' in n.kind or self.assume_str_is_unicode:
1250-
return parse_type_string(n.s, 'builtins.unicode', self.line, n.col_offset,
1251-
assume_str_is_unicode=self.assume_str_is_unicode)
1252-
else:
1253-
return parse_type_string(n.s, 'builtins.str', self.line, n.col_offset,
1254-
assume_str_is_unicode=self.assume_str_is_unicode)
1351+
if sys.version_info < (3, 8):
1352+
# Using typed_ast
1353+
1354+
# Num(number n)
1355+
def visit_Num(self, n: Num) -> Type:
1356+
return self.numeric_type(n.n, n)
1357+
1358+
# Str(string s)
1359+
def visit_Str(self, n: Str) -> Type:
1360+
# Note: we transform these fallback types into the correct types in
1361+
# 'typeanal.py' -- specifically in the named_type_with_normalized_str method.
1362+
# If we're analyzing Python 3, that function will translate 'builtins.unicode'
1363+
# into 'builtins.str'. In contrast, if we're analyzing Python 2 code, we'll
1364+
# translate 'builtins.bytes' in the method below into 'builtins.str'.
1365+
if 'u' in n.kind or self.assume_str_is_unicode:
1366+
return parse_type_string(n.s, 'builtins.unicode', self.line, n.col_offset,
1367+
assume_str_is_unicode=self.assume_str_is_unicode)
1368+
else:
1369+
return parse_type_string(n.s, 'builtins.str', self.line, n.col_offset,
1370+
assume_str_is_unicode=self.assume_str_is_unicode)
12551371

1256-
# Bytes(bytes s)
1257-
def visit_Bytes(self, n: Bytes) -> Type:
1258-
contents = bytes_to_human_readable_repr(n.s)
1259-
return RawExpressionType(contents, 'builtins.bytes', self.line, column=n.col_offset)
1372+
# Bytes(bytes s)
1373+
def visit_Bytes(self, n: Bytes) -> Type:
1374+
contents = bytes_to_human_readable_repr(n.s)
1375+
return RawExpressionType(contents, 'builtins.bytes', self.line, column=n.col_offset)
12601376

12611377
# Subscript(expr value, slice slice, expr_context ctx)
12621378
def visit_Subscript(self, n: ast3.Subscript) -> Type:

mypy/fastparse2.py

Lines changed: 5 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -57,11 +57,8 @@
5757
Attribute,
5858
Tuple as ast27_Tuple,
5959
)
60-
from typed_ast import ast3
61-
from typed_ast.ast3 import (
62-
FunctionType,
63-
Ellipsis as ast3_Ellipsis,
64-
)
60+
# Import ast3 from fastparse, which has special case for Python 3.8
61+
from mypy.fastparse import ast3, ast3_parse
6562
except ImportError:
6663
if sys.version_info.minor > 2:
6764
try:
@@ -339,11 +336,11 @@ def visit_FunctionDef(self, n: ast27.FunctionDef) -> Statement:
339336
return_type = None
340337
elif type_comment is not None and len(type_comment) > 0:
341338
try:
342-
func_type_ast = ast3.parse(type_comment, '<func_type>', 'func_type')
343-
assert isinstance(func_type_ast, FunctionType)
339+
func_type_ast = ast3_parse(type_comment, '<func_type>', 'func_type')
340+
assert isinstance(func_type_ast, ast3.FunctionType)
344341
# for ellipsis arg
345342
if (len(func_type_ast.argtypes) == 1 and
346-
isinstance(func_type_ast.argtypes[0], ast3_Ellipsis)):
343+
isinstance(func_type_ast.argtypes[0], ast3.Ellipsis)):
347344
arg_types = [a.type_annotation
348345
if a.type_annotation is not None
349346
else AnyType(TypeOfAny.unannotated)

mypy/test/helpers.py

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -43,10 +43,15 @@ def assert_string_arrays_equal(expected: List[str], actual: List[str],
4343
msg: str) -> None:
4444
"""Assert that two string arrays are equal.
4545
46+
We consider "can't" and "cannot" equivalent, by replacing the
47+
former with the latter before comparing.
48+
4649
Display any differences in a human-readable form.
4750
"""
4851

4952
actual = clean_up(actual)
53+
actual = [line.replace("can't", "cannot") for line in actual]
54+
expected = [line.replace("can't", "cannot") for line in expected]
5055

5156
if actual != expected:
5257
num_skip_start = num_skipped_prefix_lines(expected, actual)

test-data/unit/check-underscores.test

Lines changed: 0 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -3,11 +3,6 @@
33
x = 1000_000 # E: Underscores in numeric literals are only supported in Python 3.6 and greater
44
[out]
55

6-
[case testUnderscoresSyntaxError]
7-
# flags: --python-version 3.6
8-
x = 1000_000_ # E: invalid token
9-
[out]
10-
116
[case testUnderscoresBasics]
127
# flags: --python-version 3.6
138
x: int

0 commit comments

Comments
 (0)