summaryrefslogtreecommitdiffstats
path: root/Lib/test/test_grammar.py
diff options
context:
space:
mode:
authorYury Selivanov <yury@magic.io>2016-09-09 03:50:03 (GMT)
committerYury Selivanov <yury@magic.io>2016-09-09 03:50:03 (GMT)
commitf8cb8a16a344ab208fd46876c4b63604987347b8 (patch)
treec44caa48291401d1e1e388004d2762513ac88c93 /Lib/test/test_grammar.py
parent09ad17810c38d1aaae02de69084dd2a8ad9f5cdb (diff)
downloadcpython-f8cb8a16a344ab208fd46876c4b63604987347b8.zip
cpython-f8cb8a16a344ab208fd46876c4b63604987347b8.tar.gz
cpython-f8cb8a16a344ab208fd46876c4b63604987347b8.tar.bz2
Issue #27985: Implement PEP 526 -- Syntax for Variable Annotations.
Patch by Ivan Levkivskyi.
Diffstat (limited to 'Lib/test/test_grammar.py')
-rw-r--r--Lib/test/test_grammar.py178
1 files changed, 178 insertions, 0 deletions
diff --git a/Lib/test/test_grammar.py b/Lib/test/test_grammar.py
index bfe5225..109013f 100644
--- a/Lib/test/test_grammar.py
+++ b/Lib/test/test_grammar.py
@@ -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