mirror of
https://github.com/RustPython/RustPython.git
synced 2026-06-02 19:39:49 +09:00
Merge pull request #2677 from fanninpm/test-type-comments
Add test_type_comments from CPython 3.8
This commit is contained in:
444
Lib/test/test_type_comments.py
Normal file
444
Lib/test/test_type_comments.py
Normal file
@@ -0,0 +1,444 @@
|
||||
import ast
|
||||
import sys
|
||||
import unittest
|
||||
|
||||
|
||||
funcdef = """\
|
||||
def foo():
|
||||
# type: () -> int
|
||||
pass
|
||||
|
||||
def bar(): # type: () -> None
|
||||
pass
|
||||
"""
|
||||
|
||||
asyncdef = """\
|
||||
async def foo():
|
||||
# type: () -> int
|
||||
return await bar()
|
||||
|
||||
async def bar(): # type: () -> int
|
||||
return await bar()
|
||||
"""
|
||||
|
||||
asyncvar = """\
|
||||
async = 12
|
||||
await = 13
|
||||
"""
|
||||
|
||||
asynccomp = """\
|
||||
async def foo(xs):
|
||||
[x async for x in xs]
|
||||
"""
|
||||
|
||||
matmul = """\
|
||||
a = b @ c
|
||||
"""
|
||||
|
||||
fstring = """\
|
||||
a = 42
|
||||
f"{a}"
|
||||
"""
|
||||
|
||||
underscorednumber = """\
|
||||
a = 42_42_42
|
||||
"""
|
||||
|
||||
redundantdef = """\
|
||||
def foo(): # type: () -> int
|
||||
# type: () -> str
|
||||
return ''
|
||||
"""
|
||||
|
||||
nonasciidef = """\
|
||||
def foo():
|
||||
# type: () -> àçčéñt
|
||||
pass
|
||||
"""
|
||||
|
||||
forstmt = """\
|
||||
for a in []: # type: int
|
||||
pass
|
||||
"""
|
||||
|
||||
withstmt = """\
|
||||
with context() as a: # type: int
|
||||
pass
|
||||
"""
|
||||
|
||||
vardecl = """\
|
||||
a = 0 # type: int
|
||||
"""
|
||||
|
||||
ignores = """\
|
||||
def foo():
|
||||
pass # type: ignore
|
||||
|
||||
def bar():
|
||||
x = 1 # type: ignore
|
||||
|
||||
def baz():
|
||||
pass # type: ignore[excuse]
|
||||
pass # type: ignore=excuse
|
||||
pass # type: ignore [excuse]
|
||||
x = 1 # type: ignore whatever
|
||||
"""
|
||||
|
||||
# Test for long-form type-comments in arguments. A test function
|
||||
# named 'fabvk' would have two positional args, a and b, plus a
|
||||
# var-arg *v, plus a kw-arg **k. It is verified in test_longargs()
|
||||
# that it has exactly these arguments, no more, no fewer.
|
||||
longargs = """\
|
||||
def fa(
|
||||
a = 1, # type: A
|
||||
):
|
||||
pass
|
||||
|
||||
def fa(
|
||||
a = 1 # type: A
|
||||
):
|
||||
pass
|
||||
|
||||
def fa(
|
||||
a = 1, # type: A
|
||||
/
|
||||
):
|
||||
pass
|
||||
|
||||
def fab(
|
||||
a, # type: A
|
||||
b, # type: B
|
||||
):
|
||||
pass
|
||||
|
||||
def fab(
|
||||
a, # type: A
|
||||
/,
|
||||
b, # type: B
|
||||
):
|
||||
pass
|
||||
|
||||
def fab(
|
||||
a, # type: A
|
||||
b # type: B
|
||||
):
|
||||
pass
|
||||
|
||||
def fv(
|
||||
*v, # type: V
|
||||
):
|
||||
pass
|
||||
|
||||
def fv(
|
||||
*v # type: V
|
||||
):
|
||||
pass
|
||||
|
||||
def fk(
|
||||
**k, # type: K
|
||||
):
|
||||
pass
|
||||
|
||||
def fk(
|
||||
**k # type: K
|
||||
):
|
||||
pass
|
||||
|
||||
def fvk(
|
||||
*v, # type: V
|
||||
**k, # type: K
|
||||
):
|
||||
pass
|
||||
|
||||
def fvk(
|
||||
*v, # type: V
|
||||
**k # type: K
|
||||
):
|
||||
pass
|
||||
|
||||
def fav(
|
||||
a, # type: A
|
||||
*v, # type: V
|
||||
):
|
||||
pass
|
||||
|
||||
def fav(
|
||||
a, # type: A
|
||||
/,
|
||||
*v, # type: V
|
||||
):
|
||||
pass
|
||||
|
||||
def fav(
|
||||
a, # type: A
|
||||
*v # type: V
|
||||
):
|
||||
pass
|
||||
|
||||
def fak(
|
||||
a, # type: A
|
||||
**k, # type: K
|
||||
):
|
||||
pass
|
||||
|
||||
def fak(
|
||||
a, # type: A
|
||||
/,
|
||||
**k, # type: K
|
||||
):
|
||||
pass
|
||||
|
||||
def fak(
|
||||
a, # type: A
|
||||
**k # type: K
|
||||
):
|
||||
pass
|
||||
|
||||
def favk(
|
||||
a, # type: A
|
||||
*v, # type: V
|
||||
**k, # type: K
|
||||
):
|
||||
pass
|
||||
|
||||
def favk(
|
||||
a, # type: A
|
||||
/,
|
||||
*v, # type: V
|
||||
**k, # type: K
|
||||
):
|
||||
pass
|
||||
|
||||
def favk(
|
||||
a, # type: A
|
||||
*v, # type: V
|
||||
**k # type: K
|
||||
):
|
||||
pass
|
||||
"""
|
||||
|
||||
|
||||
class TypeCommentTests(unittest.TestCase):
|
||||
|
||||
lowest = 4 # Lowest minor version supported
|
||||
highest = sys.version_info[1] # Highest minor version
|
||||
|
||||
def parse(self, source, feature_version=highest):
|
||||
return ast.parse(source, type_comments=True,
|
||||
feature_version=feature_version)
|
||||
|
||||
def parse_all(self, source, minver=lowest, maxver=highest, expected_regex=""):
|
||||
for version in range(self.lowest, self.highest + 1):
|
||||
feature_version = (3, version)
|
||||
if minver <= version <= maxver:
|
||||
try:
|
||||
yield self.parse(source, feature_version)
|
||||
except SyntaxError as err:
|
||||
raise SyntaxError(str(err) + f" feature_version={feature_version}")
|
||||
else:
|
||||
with self.assertRaisesRegex(SyntaxError, expected_regex,
|
||||
msg=f"feature_version={feature_version}"):
|
||||
self.parse(source, feature_version)
|
||||
|
||||
def classic_parse(self, source):
|
||||
return ast.parse(source)
|
||||
|
||||
# TODO: RUSTPYTHON
|
||||
@unittest.expectedFailure
|
||||
def test_funcdef(self):
|
||||
for tree in self.parse_all(funcdef):
|
||||
self.assertEqual(tree.body[0].type_comment, "() -> int")
|
||||
self.assertEqual(tree.body[1].type_comment, "() -> None")
|
||||
tree = self.classic_parse(funcdef)
|
||||
self.assertEqual(tree.body[0].type_comment, None)
|
||||
self.assertEqual(tree.body[1].type_comment, None)
|
||||
|
||||
# TODO: RUSTPYTHON
|
||||
@unittest.expectedFailure
|
||||
def test_asyncdef(self):
|
||||
for tree in self.parse_all(asyncdef, minver=5):
|
||||
self.assertEqual(tree.body[0].type_comment, "() -> int")
|
||||
self.assertEqual(tree.body[1].type_comment, "() -> int")
|
||||
tree = self.classic_parse(asyncdef)
|
||||
self.assertEqual(tree.body[0].type_comment, None)
|
||||
self.assertEqual(tree.body[1].type_comment, None)
|
||||
|
||||
# TODO: RUSTPYTHON
|
||||
@unittest.expectedFailure
|
||||
def test_asyncvar(self):
|
||||
for tree in self.parse_all(asyncvar, maxver=6):
|
||||
pass
|
||||
|
||||
# TODO: RUSTPYTHON
|
||||
@unittest.expectedFailure
|
||||
def test_asynccomp(self):
|
||||
for tree in self.parse_all(asynccomp, minver=6):
|
||||
pass
|
||||
|
||||
# TODO: RUSTPYTHON
|
||||
@unittest.expectedFailure
|
||||
def test_matmul(self):
|
||||
for tree in self.parse_all(matmul, minver=5):
|
||||
pass
|
||||
|
||||
# TODO: RUSTPYTHON
|
||||
@unittest.expectedFailure
|
||||
def test_fstring(self):
|
||||
for tree in self.parse_all(fstring, minver=6):
|
||||
pass
|
||||
|
||||
# TODO: RUSTPYTHON
|
||||
@unittest.expectedFailure
|
||||
def test_underscorednumber(self):
|
||||
for tree in self.parse_all(underscorednumber, minver=6):
|
||||
pass
|
||||
|
||||
# TODO: RUSTPYTHON
|
||||
@unittest.expectedFailure
|
||||
def test_redundantdef(self):
|
||||
for tree in self.parse_all(redundantdef, maxver=0,
|
||||
expected_regex="^Cannot have two type comments on def"):
|
||||
pass
|
||||
|
||||
# TODO: RUSTPYTHON
|
||||
@unittest.expectedFailure
|
||||
def test_nonasciidef(self):
|
||||
for tree in self.parse_all(nonasciidef):
|
||||
self.assertEqual(tree.body[0].type_comment, "() -> àçčéñt")
|
||||
|
||||
# TODO: RUSTPYTHON
|
||||
@unittest.expectedFailure
|
||||
def test_forstmt(self):
|
||||
for tree in self.parse_all(forstmt):
|
||||
self.assertEqual(tree.body[0].type_comment, "int")
|
||||
tree = self.classic_parse(forstmt)
|
||||
self.assertEqual(tree.body[0].type_comment, None)
|
||||
|
||||
# TODO: RUSTPYTHON
|
||||
@unittest.expectedFailure
|
||||
def test_withstmt(self):
|
||||
for tree in self.parse_all(withstmt):
|
||||
self.assertEqual(tree.body[0].type_comment, "int")
|
||||
tree = self.classic_parse(withstmt)
|
||||
self.assertEqual(tree.body[0].type_comment, None)
|
||||
|
||||
# TODO: RUSTPYTHON
|
||||
@unittest.expectedFailure
|
||||
def test_vardecl(self):
|
||||
for tree in self.parse_all(vardecl):
|
||||
self.assertEqual(tree.body[0].type_comment, "int")
|
||||
tree = self.classic_parse(vardecl)
|
||||
self.assertEqual(tree.body[0].type_comment, None)
|
||||
|
||||
# TODO: RUSTPYTHON
|
||||
@unittest.expectedFailure
|
||||
def test_ignores(self):
|
||||
for tree in self.parse_all(ignores):
|
||||
self.assertEqual(
|
||||
[(ti.lineno, ti.tag) for ti in tree.type_ignores],
|
||||
[
|
||||
(2, ''),
|
||||
(5, ''),
|
||||
(8, '[excuse]'),
|
||||
(9, '=excuse'),
|
||||
(10, ' [excuse]'),
|
||||
(11, ' whatever'),
|
||||
])
|
||||
tree = self.classic_parse(ignores)
|
||||
self.assertEqual(tree.type_ignores, [])
|
||||
|
||||
# TODO: RUSTPYTHON
|
||||
@unittest.expectedFailure
|
||||
def test_longargs(self):
|
||||
for tree in self.parse_all(longargs):
|
||||
for t in tree.body:
|
||||
# The expected args are encoded in the function name
|
||||
todo = set(t.name[1:])
|
||||
self.assertEqual(len(t.args.args) + len(t.args.posonlyargs),
|
||||
len(todo) - bool(t.args.vararg) - bool(t.args.kwarg))
|
||||
self.assertTrue(t.name.startswith('f'), t.name)
|
||||
for index, c in enumerate(t.name[1:]):
|
||||
todo.remove(c)
|
||||
if c == 'v':
|
||||
arg = t.args.vararg
|
||||
elif c == 'k':
|
||||
arg = t.args.kwarg
|
||||
else:
|
||||
assert 0 <= ord(c) - ord('a') < len(t.args.posonlyargs + t.args.args)
|
||||
if index < len(t.args.posonlyargs):
|
||||
arg = t.args.posonlyargs[ord(c) - ord('a')]
|
||||
else:
|
||||
arg = t.args.args[ord(c) - ord('a') - len(t.args.posonlyargs)]
|
||||
self.assertEqual(arg.arg, c) # That's the argument name
|
||||
self.assertEqual(arg.type_comment, arg.arg.upper())
|
||||
assert not todo
|
||||
tree = self.classic_parse(longargs)
|
||||
for t in tree.body:
|
||||
for arg in t.args.args + [t.args.vararg, t.args.kwarg]:
|
||||
if arg is not None:
|
||||
self.assertIsNone(arg.type_comment, "%s(%s:%r)" %
|
||||
(t.name, arg.arg, arg.type_comment))
|
||||
|
||||
# TODO: RUSTPYTHON
|
||||
@unittest.expectedFailure
|
||||
def test_inappropriate_type_comments(self):
|
||||
"""Tests for inappropriately-placed type comments.
|
||||
|
||||
These should be silently ignored with type comments off,
|
||||
but raise SyntaxError with type comments on.
|
||||
|
||||
This is not meant to be exhaustive.
|
||||
"""
|
||||
|
||||
def check_both_ways(source):
|
||||
ast.parse(source, type_comments=False)
|
||||
for tree in self.parse_all(source, maxver=0):
|
||||
pass
|
||||
|
||||
check_both_ways("pass # type: int\n")
|
||||
check_both_ways("foo() # type: int\n")
|
||||
check_both_ways("x += 1 # type: int\n")
|
||||
check_both_ways("while True: # type: int\n continue\n")
|
||||
check_both_ways("while True:\n continue # type: int\n")
|
||||
check_both_ways("try: # type: int\n pass\nfinally:\n pass\n")
|
||||
check_both_ways("try:\n pass\nfinally: # type: int\n pass\n")
|
||||
check_both_ways("pass # type: ignorewhatever\n")
|
||||
check_both_ways("pass # type: ignoreé\n")
|
||||
|
||||
# TODO: RUSTPYTHON
|
||||
@unittest.expectedFailure
|
||||
def test_func_type_input(self):
|
||||
|
||||
def parse_func_type_input(source):
|
||||
return ast.parse(source, "<unknown>", "func_type")
|
||||
|
||||
# Some checks below will crash if the returned structure is wrong
|
||||
tree = parse_func_type_input("() -> int")
|
||||
self.assertEqual(tree.argtypes, [])
|
||||
self.assertEqual(tree.returns.id, "int")
|
||||
|
||||
tree = parse_func_type_input("(int) -> List[str]")
|
||||
self.assertEqual(len(tree.argtypes), 1)
|
||||
arg = tree.argtypes[0]
|
||||
self.assertEqual(arg.id, "int")
|
||||
self.assertEqual(tree.returns.value.id, "List")
|
||||
self.assertEqual(tree.returns.slice.value.id, "str")
|
||||
|
||||
tree = parse_func_type_input("(int, *str, **Any) -> float")
|
||||
self.assertEqual(tree.argtypes[0].id, "int")
|
||||
self.assertEqual(tree.argtypes[1].id, "str")
|
||||
self.assertEqual(tree.argtypes[2].id, "Any")
|
||||
self.assertEqual(tree.returns.id, "float")
|
||||
|
||||
with self.assertRaises(SyntaxError):
|
||||
tree = parse_func_type_input("(int, *str, *Any) -> float")
|
||||
|
||||
with self.assertRaises(SyntaxError):
|
||||
tree = parse_func_type_input("(int, **str, Any) -> float")
|
||||
|
||||
with self.assertRaises(SyntaxError):
|
||||
tree = parse_func_type_input("(**int, **str) -> float")
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
unittest.main()
|
||||
Reference in New Issue
Block a user