mirror of
https://github.com/python/cpython.git
synced 2025-08-04 08:59:19 +00:00
Issue #27985: Implement PEP 526 -- Syntax for Variable Annotations.
Patch by Ivan Levkivskyi.
This commit is contained in:
parent
09ad17810c
commit
f8cb8a16a3
45 changed files with 3242 additions and 1308 deletions
|
@ -8,6 +8,14 @@ import sys
|
|||
# testing import *
|
||||
from sys import *
|
||||
|
||||
# different import patterns to check that __annotations__ does not interfere
|
||||
# with import machinery
|
||||
import test.ann_module as ann_module
|
||||
import typing
|
||||
from collections import ChainMap
|
||||
from test import ann_module2
|
||||
import test
|
||||
|
||||
|
||||
class TokenTests(unittest.TestCase):
|
||||
|
||||
|
@ -139,6 +147,19 @@ the \'lazy\' dog.\n\
|
|||
compile(s, "<test>", "exec")
|
||||
self.assertIn("unexpected EOF", str(cm.exception))
|
||||
|
||||
var_annot_global: int # a global annotated is necessary for test_var_annot
|
||||
|
||||
# custom namespace for testing __annotations__
|
||||
|
||||
class CNS:
|
||||
def __init__(self):
|
||||
self._dct = {}
|
||||
def __setitem__(self, item, value):
|
||||
self._dct[item.lower()] = value
|
||||
def __getitem__(self, item):
|
||||
return self._dct[item]
|
||||
|
||||
|
||||
class GrammarTests(unittest.TestCase):
|
||||
|
||||
# single_input: NEWLINE | simple_stmt | compound_stmt NEWLINE
|
||||
|
@ -154,6 +175,163 @@ class GrammarTests(unittest.TestCase):
|
|||
# testlist ENDMARKER
|
||||
x = eval('1, 0 or 1')
|
||||
|
||||
def test_var_annot_basics(self):
|
||||
# all these should be allowed
|
||||
var1: int = 5
|
||||
var2: [int, str]
|
||||
my_lst = [42]
|
||||
def one():
|
||||
return 1
|
||||
int.new_attr: int
|
||||
[list][0]: type
|
||||
my_lst[one()-1]: int = 5
|
||||
self.assertEqual(my_lst, [5])
|
||||
|
||||
def test_var_annot_syntax_errors(self):
|
||||
# parser pass
|
||||
check_syntax_error(self, "def f: int")
|
||||
check_syntax_error(self, "x: int: str")
|
||||
check_syntax_error(self, "def f():\n"
|
||||
" nonlocal x: int\n")
|
||||
# AST pass
|
||||
check_syntax_error(self, "[x, 0]: int\n")
|
||||
check_syntax_error(self, "f(): int\n")
|
||||
check_syntax_error(self, "(x,): int")
|
||||
check_syntax_error(self, "def f():\n"
|
||||
" (x, y): int = (1, 2)\n")
|
||||
# symtable pass
|
||||
check_syntax_error(self, "def f():\n"
|
||||
" x: int\n"
|
||||
" global x\n")
|
||||
check_syntax_error(self, "def f():\n"
|
||||
" global x\n"
|
||||
" x: int\n")
|
||||
|
||||
def test_var_annot_basic_semantics(self):
|
||||
# execution order
|
||||
with self.assertRaises(ZeroDivisionError):
|
||||
no_name[does_not_exist]: no_name_again = 1/0
|
||||
with self.assertRaises(NameError):
|
||||
no_name[does_not_exist]: 1/0 = 0
|
||||
global var_annot_global
|
||||
|
||||
# function semantics
|
||||
def f():
|
||||
st: str = "Hello"
|
||||
a.b: int = (1, 2)
|
||||
return st
|
||||
self.assertEqual(f.__annotations__, {})
|
||||
def f_OK():
|
||||
x: 1/0
|
||||
f_OK()
|
||||
def fbad():
|
||||
x: int
|
||||
print(x)
|
||||
with self.assertRaises(UnboundLocalError):
|
||||
fbad()
|
||||
def f2bad():
|
||||
(no_such_global): int
|
||||
print(no_such_global)
|
||||
try:
|
||||
f2bad()
|
||||
except Exception as e:
|
||||
self.assertIs(type(e), NameError)
|
||||
|
||||
# class semantics
|
||||
class C:
|
||||
x: int
|
||||
s: str = "attr"
|
||||
z = 2
|
||||
def __init__(self, x):
|
||||
self.x: int = x
|
||||
self.assertEqual(C.__annotations__, {'x': int, 's': str})
|
||||
with self.assertRaises(NameError):
|
||||
class CBad:
|
||||
no_such_name_defined.attr: int = 0
|
||||
with self.assertRaises(NameError):
|
||||
class Cbad2(C):
|
||||
x: int
|
||||
x.y: list = []
|
||||
|
||||
def test_var_annot_metaclass_semantics(self):
|
||||
class CMeta(type):
|
||||
@classmethod
|
||||
def __prepare__(metacls, name, bases, **kwds):
|
||||
return {'__annotations__': CNS()}
|
||||
class CC(metaclass=CMeta):
|
||||
XX: 'ANNOT'
|
||||
self.assertEqual(CC.__annotations__['xx'], 'ANNOT')
|
||||
|
||||
def test_var_annot_module_semantics(self):
|
||||
with self.assertRaises(AttributeError):
|
||||
print(test.__annotations__)
|
||||
self.assertEqual(ann_module.__annotations__,
|
||||
{1: 2, 'x': int, 'y': str, 'f': typing.Tuple[int, int]})
|
||||
self.assertEqual(ann_module.M.__annotations__,
|
||||
{'123': 123, 'o': type})
|
||||
self.assertEqual(ann_module2.__annotations__, {})
|
||||
self.assertEqual(typing.get_type_hints(ann_module2.CV,
|
||||
ann_module2.__dict__),
|
||||
ChainMap({'var': typing.ClassVar[ann_module2.CV]}, {}))
|
||||
|
||||
def test_var_annot_in_module(self):
|
||||
# check that functions fail the same way when executed
|
||||
# outside of module where they were defined
|
||||
from test.ann_module3 import f_bad_ann, g_bad_ann, D_bad_ann
|
||||
with self.assertRaises(NameError):
|
||||
f_bad_ann()
|
||||
with self.assertRaises(NameError):
|
||||
g_bad_ann()
|
||||
with self.assertRaises(NameError):
|
||||
D_bad_ann(5)
|
||||
|
||||
def test_var_annot_simple_exec(self):
|
||||
gns = {}; lns= {}
|
||||
exec("'docstring'\n"
|
||||
"__annotations__[1] = 2\n"
|
||||
"x: int = 5\n", gns, lns)
|
||||
self.assertEqual(lns["__annotations__"], {1: 2, 'x': int})
|
||||
with self.assertRaises(KeyError):
|
||||
gns['__annotations__']
|
||||
|
||||
def test_var_annot_custom_maps(self):
|
||||
# tests with custom locals() and __annotations__
|
||||
ns = {'__annotations__': CNS()}
|
||||
exec('X: int; Z: str = "Z"; (w): complex = 1j', ns)
|
||||
self.assertEqual(ns['__annotations__']['x'], int)
|
||||
self.assertEqual(ns['__annotations__']['z'], str)
|
||||
with self.assertRaises(KeyError):
|
||||
ns['__annotations__']['w']
|
||||
nonloc_ns = {}
|
||||
class CNS2:
|
||||
def __init__(self):
|
||||
self._dct = {}
|
||||
def __setitem__(self, item, value):
|
||||
nonlocal nonloc_ns
|
||||
self._dct[item] = value
|
||||
nonloc_ns[item] = value
|
||||
def __getitem__(self, item):
|
||||
return self._dct[item]
|
||||
exec('x: int = 1', {}, CNS2())
|
||||
self.assertEqual(nonloc_ns['__annotations__']['x'], int)
|
||||
|
||||
def test_var_annot_refleak(self):
|
||||
# complex case: custom locals plus custom __annotations__
|
||||
# this was causing refleak
|
||||
cns = CNS()
|
||||
nonloc_ns = {'__annotations__': cns}
|
||||
class CNS2:
|
||||
def __init__(self):
|
||||
self._dct = {'__annotations__': cns}
|
||||
def __setitem__(self, item, value):
|
||||
nonlocal nonloc_ns
|
||||
self._dct[item] = value
|
||||
nonloc_ns[item] = value
|
||||
def __getitem__(self, item):
|
||||
return self._dct[item]
|
||||
exec('X: str', {}, CNS2())
|
||||
self.assertEqual(nonloc_ns['__annotations__']['x'], str)
|
||||
|
||||
def test_funcdef(self):
|
||||
### [decorators] 'def' NAME parameters ['->' test] ':' suite
|
||||
### decorator: '@' dotted_name [ '(' [arglist] ')' ] NEWLINE
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue