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")

`