bpo-35975: Support parsing earlier minor versions of Python 3 (GH-12086) · python/cpython@495da29 (original) (raw)
1
1
`import ast
`
``
2
`+
import sys
`
2
3
`import unittest
`
3
4
``
4
5
``
`@@ -20,6 +21,29 @@ async def bar(): # type: () -> int
`
20
21
` return await bar()
`
21
22
`"""
`
22
23
``
``
24
`+
asyncvar = """\
`
``
25
`+
async = 12
`
``
26
`+
await = 13
`
``
27
`+
"""
`
``
28
+
``
29
`+
asynccomp = """\
`
``
30
`+
async def foo(xs):
`
``
31
`+
[x async for x in xs]
`
``
32
`+
"""
`
``
33
+
``
34
`+
matmul = """\
`
``
35
`+
a = b @ c
`
``
36
`+
"""
`
``
37
+
``
38
`+
fstring = """\
`
``
39
`+
a = 42
`
``
40
`+
f"{a}"
`
``
41
`+
"""
`
``
42
+
``
43
`+
underscorednumber = """\
`
``
44
`+
a = 42_42_42
`
``
45
`+
"""
`
``
46
+
23
47
`redundantdef = """\
`
24
48
`def foo(): # type: () -> int
`
25
49
` # type: () -> str
`
`@@ -155,80 +179,117 @@ def favk(
`
155
179
``
156
180
`class TypeCommentTests(unittest.TestCase):
`
157
181
``
158
``
`-
def parse(self, source):
`
159
``
`-
return ast.parse(source, type_comments=True)
`
``
182
`+
lowest = 4 # Lowest minor version supported
`
``
183
`+
highest = sys.version_info[1] # Highest minor version
`
``
184
+
``
185
`+
def parse(self, source, feature_version=highest):
`
``
186
`+
return ast.parse(source, type_comments=True,
`
``
187
`+
feature_version=feature_version)
`
``
188
+
``
189
`+
def parse_all(self, source, minver=lowest, maxver=highest, expected_regex=""):
`
``
190
`+
for feature_version in range(self.lowest, self.highest + 1):
`
``
191
`+
if minver <= feature_version <= maxver:
`
``
192
`+
try:
`
``
193
`+
yield self.parse(source, feature_version)
`
``
194
`+
except SyntaxError as err:
`
``
195
`+
raise SyntaxError(str(err) + f" feature_version={feature_version}")
`
``
196
`+
else:
`
``
197
`+
with self.assertRaisesRegex(SyntaxError, expected_regex,
`
``
198
`+
msg=f"feature_version={feature_version}"):
`
``
199
`+
self.parse(source, feature_version)
`
160
200
``
161
201
`def classic_parse(self, source):
`
162
202
`return ast.parse(source)
`
163
203
``
164
204
`def test_funcdef(self):
`
165
``
`-
tree = self.parse(funcdef)
`
166
``
`-
self.assertEqual(tree.body[0].type_comment, "() -> int")
`
167
``
`-
self.assertEqual(tree.body[1].type_comment, "() -> None")
`
``
205
`+
for tree in self.parse_all(funcdef):
`
``
206
`+
self.assertEqual(tree.body[0].type_comment, "() -> int")
`
``
207
`+
self.assertEqual(tree.body[1].type_comment, "() -> None")
`
168
208
`tree = self.classic_parse(funcdef)
`
169
209
`self.assertEqual(tree.body[0].type_comment, None)
`
170
210
`self.assertEqual(tree.body[1].type_comment, None)
`
171
211
``
172
212
`def test_asyncdef(self):
`
173
``
`-
tree = self.parse(asyncdef)
`
174
``
`-
self.assertEqual(tree.body[0].type_comment, "() -> int")
`
175
``
`-
self.assertEqual(tree.body[1].type_comment, "() -> int")
`
``
213
`+
for tree in self.parse_all(asyncdef, minver=5):
`
``
214
`+
self.assertEqual(tree.body[0].type_comment, "() -> int")
`
``
215
`+
self.assertEqual(tree.body[1].type_comment, "() -> int")
`
176
216
`tree = self.classic_parse(asyncdef)
`
177
217
`self.assertEqual(tree.body[0].type_comment, None)
`
178
218
`self.assertEqual(tree.body[1].type_comment, None)
`
179
219
``
``
220
`+
def test_asyncvar(self):
`
``
221
`+
for tree in self.parse_all(asyncvar, maxver=6):
`
``
222
`+
pass
`
``
223
+
``
224
`+
def test_asynccomp(self):
`
``
225
`+
for tree in self.parse_all(asynccomp, minver=6):
`
``
226
`+
pass
`
``
227
+
``
228
`+
def test_matmul(self):
`
``
229
`+
for tree in self.parse_all(matmul, minver=5):
`
``
230
`+
pass
`
``
231
+
``
232
`+
def test_fstring(self):
`
``
233
`+
for tree in self.parse_all(fstring, minver=6):
`
``
234
`+
pass
`
``
235
+
``
236
`+
def test_underscorednumber(self):
`
``
237
`+
for tree in self.parse_all(underscorednumber, minver=6):
`
``
238
`+
pass
`
``
239
+
180
240
`def test_redundantdef(self):
`
181
``
`-
with self.assertRaisesRegex(SyntaxError, "^Cannot have two type comments on def"):
`
182
``
`-
tree = self.parse(redundantdef)
`
``
241
`+
for tree in self.parse_all(redundantdef, maxver=0,
`
``
242
`+
expected_regex="^Cannot have two type comments on def"):
`
``
243
`+
pass
`
183
244
``
184
245
`def test_nonasciidef(self):
`
185
``
`-
tree = self.parse(nonasciidef)
`
186
``
`-
self.assertEqual(tree.body[0].type_comment, "() -> àçčéñt")
`
``
246
`+
for tree in self.parse_all(nonasciidef):
`
``
247
`+
self.assertEqual(tree.body[0].type_comment, "() -> àçčéñt")
`
187
248
``
188
249
`def test_forstmt(self):
`
189
``
`-
tree = self.parse(forstmt)
`
190
``
`-
self.assertEqual(tree.body[0].type_comment, "int")
`
``
250
`+
for tree in self.parse_all(forstmt):
`
``
251
`+
self.assertEqual(tree.body[0].type_comment, "int")
`
191
252
`tree = self.classic_parse(forstmt)
`
192
253
`self.assertEqual(tree.body[0].type_comment, None)
`
193
254
``
194
255
`def test_withstmt(self):
`
195
``
`-
tree = self.parse(withstmt)
`
196
``
`-
self.assertEqual(tree.body[0].type_comment, "int")
`
``
256
`+
for tree in self.parse_all(withstmt):
`
``
257
`+
self.assertEqual(tree.body[0].type_comment, "int")
`
197
258
`tree = self.classic_parse(withstmt)
`
198
259
`self.assertEqual(tree.body[0].type_comment, None)
`
199
260
``
200
261
`def test_vardecl(self):
`
201
``
`-
tree = self.parse(vardecl)
`
202
``
`-
self.assertEqual(tree.body[0].type_comment, "int")
`
``
262
`+
for tree in self.parse_all(vardecl):
`
``
263
`+
self.assertEqual(tree.body[0].type_comment, "int")
`
203
264
`tree = self.classic_parse(vardecl)
`
204
265
`self.assertEqual(tree.body[0].type_comment, None)
`
205
266
``
206
267
`def test_ignores(self):
`
207
``
`-
tree = self.parse(ignores)
`
208
``
`-
self.assertEqual([ti.lineno for ti in tree.type_ignores], [2, 5])
`
``
268
`+
for tree in self.parse_all(ignores):
`
``
269
`+
self.assertEqual([ti.lineno for ti in tree.type_ignores], [2, 5])
`
209
270
`tree = self.classic_parse(ignores)
`
210
271
`self.assertEqual(tree.type_ignores, [])
`
211
272
``
212
273
`def test_longargs(self):
`
213
``
`-
tree = self.parse(longargs)
`
214
``
`-
for t in tree.body:
`
215
``
`-
The expected args are encoded in the function name
`
216
``
`-
todo = set(t.name[1:])
`
217
``
`-
self.assertEqual(len(t.args.args),
`
218
``
`-
len(todo) - bool(t.args.vararg) - bool(t.args.kwarg))
`
219
``
`-
self.assertTrue(t.name.startswith('f'), t.name)
`
220
``
`-
for c in t.name[1:]:
`
221
``
`-
todo.remove(c)
`
222
``
`-
if c == 'v':
`
223
``
`-
arg = t.args.vararg
`
224
``
`-
elif c == 'k':
`
225
``
`-
arg = t.args.kwarg
`
226
``
`-
else:
`
227
``
`-
assert 0 <= ord(c) - ord('a') < len(t.args.args)
`
228
``
`-
arg = t.args.args[ord(c) - ord('a')]
`
229
``
`-
self.assertEqual(arg.arg, c) # That's the argument name
`
230
``
`-
self.assertEqual(arg.type_comment, arg.arg.upper())
`
231
``
`-
assert not todo
`
``
274
`+
for tree in self.parse_all(longargs):
`
``
275
`+
for t in tree.body:
`
``
276
`+
The expected args are encoded in the function name
`
``
277
`+
todo = set(t.name[1:])
`
``
278
`+
self.assertEqual(len(t.args.args),
`
``
279
`+
len(todo) - bool(t.args.vararg) - bool(t.args.kwarg))
`
``
280
`+
self.assertTrue(t.name.startswith('f'), t.name)
`
``
281
`+
for c in t.name[1:]:
`
``
282
`+
todo.remove(c)
`
``
283
`+
if c == 'v':
`
``
284
`+
arg = t.args.vararg
`
``
285
`+
elif c == 'k':
`
``
286
`+
arg = t.args.kwarg
`
``
287
`+
else:
`
``
288
`+
assert 0 <= ord(c) - ord('a') < len(t.args.args)
`
``
289
`+
arg = t.args.args[ord(c) - ord('a')]
`
``
290
`+
self.assertEqual(arg.arg, c) # That's the argument name
`
``
291
`+
self.assertEqual(arg.type_comment, arg.arg.upper())
`
``
292
`+
assert not todo
`
232
293
`tree = self.classic_parse(longargs)
`
233
294
`for t in tree.body:
`
234
295
`for arg in t.args.args + [t.args.vararg, t.args.kwarg]:
`
`@@ -247,8 +308,8 @@ def test_inappropriate_type_comments(self):
`
247
308
``
248
309
`def check_both_ways(source):
`
249
310
`ast.parse(source, type_comments=False)
`
250
``
`-
with self.assertRaises(SyntaxError):
`
251
``
`-
ast.parse(source, type_comments=True)
`
``
311
`+
for tree in self.parse_all(source, maxver=0):
`
``
312
`+
pass
`
252
313
``
253
314
`check_both_ways("pass # type: int\n")
`
254
315
`check_both_ways("foo() # type: int\n")
`