diff options
Diffstat (limited to 'tests/run/test_grammar.py')
-rw-r--r-- | tests/run/test_grammar.py | 739 |
1 files changed, 604 insertions, 135 deletions
diff --git a/tests/run/test_grammar.py b/tests/run/test_grammar.py index 9f30c3129..dfa11e087 100644 --- a/tests/run/test_grammar.py +++ b/tests/run/test_grammar.py @@ -1,9 +1,26 @@ -### COPIED FROM CPython 3.5 - ADDED PART FOLLOWS ### +### COPIED FROM CPython 3.9 - ADDED PART FOLLOWS ### # cython: language_level=3 +import cython import contextlib from tempfile import NamedTemporaryFile -from Cython.Compiler.Main import compile as cython_compile +from Cython.Compiler.Main import compile as cython_compile, CompileError +from Cython.Build.Inline import cython_inline + + +@contextlib.contextmanager +def hidden_stderr(): + try: + from StringIO import StringIO + except ImportError: + from io import StringIO + + old_stderr = sys.stderr + try: + sys.stderr = StringIO() + yield + finally: + sys.stderr = old_stderr def _compile(code): @@ -11,36 +28,43 @@ def _compile(code): f.write(code.encode('utf8')) f.flush() - try: - from StringIO import StringIO - except ImportError: - from io import StringIO - - old_stderr = sys.stderr - try: - sys.stderr = StringIO() + with hidden_stderr(): result = cython_compile(f.name, language_level=3) - finally: - sys.stderr = old_stderr return result -def check_syntax_error(test, code): +def check_syntax_error(test, code, msg=None): result = _compile(code) assert not result.c_file -def compile(code, name, what): - assert what == 'exec' - result = _compile(code) - if not result.c_file: - raise SyntaxError('unexpected EOF') # see usage of compile() below +def check_syntax_warning(test, code, *args): + # ignore for now + return _compile(code) -def exec(code): - result = _compile(code) - if not result.c_file: - raise SyntaxError('unexpected EOF') # see usage of compile() below +if cython.compiled: + def compile(code, name, what): + assert what == 'exec' + result = _compile(code) + if not result.c_file: + raise SyntaxError('unexpected EOF') # see usage of compile() below + + def exec(code): + result = _compile(code) + if not result.c_file: + raise SyntaxError('unexpected EOF') # see usage of compile() below + + def eval(code): + try: + with hidden_stderr(): + return cython_inline(code) + except CompileError as exc: + raise SyntaxError(str(exc)) + + +def use_old_parser(): + return False import unittest @@ -72,16 +96,109 @@ skip = unittest.skip # Python test set -- part 1, grammar. # This just tests whether the parser accepts them all. -#from test.support import check_syntax_error +#from test.support import check_syntax_error, check_syntax_warning, use_old_parser import inspect import unittest import sys +import warnings # 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 + +# These are shared with test_tokenize and other test modules. +# +# Note: since several test cases filter out floats by looking for "e" and ".", +# don't add hexadecimal literals that contain "e" or "E". +VALID_UNDERSCORE_LITERALS = [ + '0_0_0', + '4_2', + '1_0000_0000', + '0b1001_0100', + '0xffff_ffff', + '0o5_7_7', + '1_00_00.5', + '1_00_00.5e5', + '1_00_00e5_1', + '1e1_0', + '.1_4', + '.1_4e1', + '0b_0', + '0x_f', + '0o_5', + '1_00_00j', + '1_00_00.5j', + '1_00_00e5_1j', + '.1_4j', + '(1_2.5+3_3j)', + '(.5_6j)', +] +INVALID_UNDERSCORE_LITERALS = [ + # Trailing underscores: + '0_', + '42_', + '1.4j_', + '0x_', + '0b1_', + '0xf_', + '0o5_', + '0 if 1_Else 1', + # Underscores in the base selector: + '0_b0', + '0_xf', + '0_o5', + # Old-style octal, still disallowed: + '0_7', + '09_99', + # Multiple consecutive underscores: + '4_______2', + '0.1__4', + '0.1__4j', + '0b1001__0100', + '0xffff__ffff', + '0x___', + '0o5__77', + '1e1__0', + '1e1__0j', + # Underscore right before a dot: + '1_.4', + '1_.4j', + # Underscore right after a dot: + '1._4', + '1._4j', + '._5', + '._5j', + # Underscore right after a sign: + '1.0e+_1', + '1.0e+_1j', + # Underscore right before j: + '1.4_j', + '1.4e5_j', + # Underscore right before e: + '1_e1', + '1.4_e1', + '1.4_e1j', + # Underscore right after e: + '1e_1', + '1.4e_1', + '1.4e_1j', + # Complex cases with parens: + '(1+1.5_j_)', + '(1+1.5_j)', +] + class TokenTests(unittest.TestCase): + #from test.support import check_syntax_error + check_syntax_error = check_syntax_error + def test_backslash(self): # Backslash means line continuation: x = 1 \ @@ -158,6 +275,40 @@ class TokenTests(unittest.TestCase): self.assertEqual(1 if 0else 0, 0) self.assertRaises(SyntaxError, eval, "0 if 1Else 0") + @skip("Done more efficiently in TestGrammar") + def test_underscore_literals(self): + for lit in VALID_UNDERSCORE_LITERALS: + self.assertEqual(eval(lit), eval(lit.replace('_', ''))) + for lit in INVALID_UNDERSCORE_LITERALS: + self.assertRaises(SyntaxError, eval, lit) + # Sanity check: no literal begins with an underscore + self.assertRaises(NameError, eval, "_0") + + def test_bad_numerical_literals(self): + check = self.check_syntax_error + check("0b12", "invalid digit '2' in binary literal") + check("0b1_2", "invalid digit '2' in binary literal") + check("0b2", "invalid digit '2' in binary literal") + check("0b1_", "invalid binary literal") + check("0b", "invalid binary literal") + check("0o18", "invalid digit '8' in octal literal") + check("0o1_8", "invalid digit '8' in octal literal") + check("0o8", "invalid digit '8' in octal literal") + check("0o1_", "invalid octal literal") + check("0o", "invalid octal literal") + check("0x1_", "invalid hexadecimal literal") + check("0x", "invalid hexadecimal literal") + check("1_", "invalid decimal literal") + # FIXME: not currently a syntax error + """ + check("012", + "leading zeros in decimal integer literals are not permitted; " + "use an 0o prefix for octal integers") + """ + check("1.2_", "invalid decimal literal") + check("1e2_", "invalid decimal literal") + check("1e+", "invalid decimal literal") + def test_string_literals(self): x = ''; y = ""; self.assertTrue(len(x) == 0 and x == y) x = '\''; y = "'"; self.assertTrue(len(x) == 1 and x == y and ord(x) == 39) @@ -201,7 +352,8 @@ the \'lazy\' dog.\n\ def test_ellipsis(self): x = ... self.assertTrue(x is Ellipsis) - self.assertRaises(SyntaxError, eval, ".. .") + # FIXME: why is this not rejected ??? + #self.assertRaises(SyntaxError, eval, ".. .") def test_eof_error(self): samples = ("def foo(", "\ndef foo(", "def foo(\n") @@ -210,7 +362,7 @@ 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 +var_annot_global: int # a global annotated is necessary for test_var_annot # custom namespace for testing __annotations__ @@ -225,6 +377,18 @@ class CNS: class GrammarTests(unittest.TestCase): + #from test.support import check_syntax_error, check_syntax_warning + check_syntax_error, check_syntax_warning = check_syntax_error, check_syntax_warning + + if not hasattr(unittest.TestCase, 'subTest'): + @contextlib.contextmanager + def subTest(self, source=None, case=None, **kwargs): + try: + yield + except Exception: + print(source or case) + raise + # single_input: NEWLINE | simple_stmt | compound_stmt NEWLINE # XXX can't test in a script -- this rule is only used when interactive @@ -250,7 +414,7 @@ class GrammarTests(unittest.TestCase): my_lst[one()-1]: int = 5 self.assertEqual(my_lst, [5]) - @skip("Bug: global vs. local declarations do not currently raise an error") + @skip("Cython Bug: global vs. local declarations do not currently raise an error") def test_var_annot_syntax_errors(self): # parser pass check_syntax_error(self, "def f: int") @@ -271,7 +435,6 @@ class GrammarTests(unittest.TestCase): " global x\n" " x: int\n") - @skip("Class annotations not implemented") def test_var_annot_basic_semantics(self): # execution order with self.assertRaises(ZeroDivisionError): @@ -289,21 +452,21 @@ class GrammarTests(unittest.TestCase): def f_OK(): x: 1/0 f_OK() - - ### The following are compile time errors in Cython. - - #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) + # Compile-time errors in Cython: + """ + 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: @@ -312,7 +475,8 @@ class GrammarTests(unittest.TestCase): z = 2 def __init__(self, x): self.x: int = x - self.assertEqual(C.__annotations__, {'_C__foo': int, 's': str}) + + self.assertEqual(C.__annotations__, {'_C__foo': 'int', 's': 'str'}) with self.assertRaises(NameError): class CBad: no_such_name_defined.attr: int = 0 @@ -321,7 +485,7 @@ class GrammarTests(unittest.TestCase): x: int x.y: list = [] - @skip("Class annotations not implemented") + @skip("Not currently supported: https://github.com/cython/cython/issues/3839") def test_var_annot_metaclass_semantics(self): class CMeta(type): @classmethod @@ -403,9 +567,23 @@ class GrammarTests(unittest.TestCase): exec('X: str', {}, CNS2()) self.assertEqual(nonloc_ns['__annotations__']['x'], str) + @skip("Depends on 3-args compiled exec()") + def test_var_annot_rhs(self): + ns = {} + exec('x: tuple = 1, 2', ns) + self.assertEqual(ns['x'], (1, 2)) + stmt = ('def f():\n' + ' x: int = yield') + exec(stmt, ns) + self.assertEqual(list(ns['f']()), [None]) + + ns = {"a": 1, 'b': (2, 3, 4), "c":5, "Tuple": typing.Tuple} + exec('x: Tuple[int, ...] = a,*b,c', ns) + self.assertEqual(ns['x'], (1, 2, 3, 4, 5)) + def test_funcdef(self): ### [decorators] 'def' NAME parameters ['->' test] ':' suite - ### decorator: '@' dotted_name [ '(' [arglist] ')' ] NEWLINE + ### decorator: '@' namedexpr_test NEWLINE ### decorators: decorator+ ### parameters: '(' [typedargslist] ')' ### typedargslist: ((tfpdef ['=' test] ',')* @@ -544,9 +722,10 @@ class GrammarTests(unittest.TestCase): pos2key2dict(1,2,k2=100,tokwarg1=100,tokwarg2=200) pos2key2dict(1,2,tokwarg1=100,tokwarg2=200, k2=100) - self.assertRaises(SyntaxError, eval, "def f(*): pass") - self.assertRaises(SyntaxError, eval, "def f(*,): pass") - self.assertRaises(SyntaxError, eval, "def f(*, **kwds): pass") + # FIXME: currently does not raise an error + #self.assertRaises(SyntaxError, eval, "def f(*): pass") + #self.assertRaises(SyntaxError, eval, "def f(*,): pass") + #self.assertRaises(SyntaxError, eval, "def f(*, **kwds): pass") # keyword arguments after *arglist def f(*args, **kwargs): @@ -566,39 +745,72 @@ class GrammarTests(unittest.TestCase): # argument annotation tests def f(x) -> list: pass - self.assertEqual(f.__annotations__, {'return': list}) + self.assertEqual(f.__annotations__, {'return': 'list'}) def f(x: int): pass - self.assertEqual(f.__annotations__, {'x': int}) + self.assertEqual(f.__annotations__, {'x': 'int'}) + def f(x: int, /): pass + self.assertEqual(f.__annotations__, {'x': 'int'}) + def f(x: int = 34, /): pass + self.assertEqual(f.__annotations__, {'x': 'int'}) def f(*x: str): pass - self.assertEqual(f.__annotations__, {'x': str}) + self.assertEqual(f.__annotations__, {'x': 'str'}) def f(**x: float): pass - self.assertEqual(f.__annotations__, {'x': float}) + self.assertEqual(f.__annotations__, {'x': 'float'}) def f(x, y: 1+2): pass - self.assertEqual(f.__annotations__, {'y': 3}) + self.assertEqual(f.__annotations__, {'y': '1 + 2'}) + def f(x, y: 1+2, /): pass + self.assertEqual(f.__annotations__, {'y': '1 + 2'}) def f(a, b: 1, c: 2, d): pass - self.assertEqual(f.__annotations__, {'b': 1, 'c': 2}) + self.assertEqual(f.__annotations__, {'b': '1', 'c': '2'}) + def f(a, b: 1, /, c: 2, d): pass + self.assertEqual(f.__annotations__, {'b': '1', 'c': '2'}) def f(a, b: 1, c: 2, d, e: 3 = 4, f=5, *g: 6): pass self.assertEqual(f.__annotations__, - {'b': 1, 'c': 2, 'e': 3, 'g': 6}) + {'b': '1', 'c': '2', 'e': '3', 'g': '6'}) def f(a, b: 1, c: 2, d, e: 3 = 4, f=5, *g: 6, h: 7, i=8, j: 9 = 10, **k: 11) -> 12: pass self.assertEqual(f.__annotations__, - {'b': 1, 'c': 2, 'e': 3, 'g': 6, 'h': 7, 'j': 9, - 'k': 11, 'return': 12}) + {'b': '1', 'c': '2', 'e': '3', 'g': '6', 'h': '7', 'j': '9', + 'k': '11', 'return': '12'}) + # FIXME: compile failure on positional-only argument declaration + """ + def f(a, b: 1, c: 2, d, e: 3 = 4, f: int = 5, /, *g: 6, h: 7, i=8, j: 9 = 10, + **k: 11) -> 12: pass + self.assertEqual(f.__annotations__, + {'b': 1, 'c': 2, 'e': 3, 'f': int, 'g': 6, 'h': 7, 'j': 9, + 'k': 11, 'return': 12}) + """ # Check for issue #20625 -- annotations mangling class Spam: def f(self, *, __kw: 1): pass class Ham(Spam): pass - self.assertEqual(Spam.f.__annotations__, {'_Spam__kw': 1}) - self.assertEqual(Ham.f.__annotations__, {'_Spam__kw': 1}) + # FIXME: not currently mangled + """ + self.assertEqual(Spam.f.__annotations__, {'_Spam__kw': '1'}) + self.assertEqual(Ham.f.__annotations__, {'_Spam__kw': '1'}) + """ # Check for SF Bug #1697248 - mixing decorators and a return annotation def null(x): return x @null def f(x) -> list: pass - self.assertEqual(f.__annotations__, {'return': list}) - - # test MAKE_CLOSURE with a variety of oparg's + self.assertEqual(f.__annotations__, {'return': 'list'}) + + # Test expressions as decorators (PEP 614): + @False or null + def f(x): pass + @d := null + def f(x): pass + @lambda f: null(f) + def f(x): pass + @[..., null, ...][1] + def f(x): pass + @null(null)(null) + def f(x): pass + @[null][0].__call__.__call__ + def f(x): pass + + # test closures with a variety of opargs closure = 1 def f(): return closure def f(x=1): return closure @@ -626,7 +838,7 @@ class GrammarTests(unittest.TestCase): ### lambdef: 'lambda' [varargslist] ':' test l1 = lambda : 0 self.assertEqual(l1(), 0) - l2 = lambda : a[d] # XXX just testing the expression + l2 = lambda : a[d] # XXX just testing the expression l3 = lambda : [2 < x for x in [-1, 3, 0]] self.assertEqual(l3(), [0, 1, 0]) l4 = lambda x = lambda y = lambda z=1 : z : y() : x() @@ -703,11 +915,13 @@ class GrammarTests(unittest.TestCase): for case in cases: source = case.format(keyword) with self.subTest(source=source): - with self.assertRaisesRegex(SyntaxError, custom_msg): + #with self.assertRaisesRegex(SyntaxError, custom_msg): + with self.assertRaises(SyntaxError): exec(source) source = source.replace("foo", "(foo.)") with self.subTest(source=source): - with self.assertRaisesRegex(SyntaxError, "invalid syntax"): + #with self.assertRaisesRegex(SyntaxError, "invalid syntax"): + with self.assertRaises(SyntaxError): exec(source) def test_del_stmt(self): @@ -719,6 +933,24 @@ class GrammarTests(unittest.TestCase): del abc del x, y, (z, xyz) + x, y, z = "xyz" + del x + del y, + del (z) + del () + + a, b, c, d, e, f, g = "abcdefg" + del a, (b, c), (d, (e, f)) + + a, b, c, d, e, f, g = "abcdefg" + del a, [b, c], (d, [e, f]) + + abcd = list("abcd") + del abcd[1:2] + + # FIXME: currently fails to compile + #compile("del a, (b[0].c, (d.e, f.g[1:2])), [h.i.j], ()", "<testcase>", "exec") + def test_pass_stmt(self): # 'pass' pass @@ -848,6 +1080,59 @@ class GrammarTests(unittest.TestCase): break self.assertEqual(count, 0) + def test_continue_in_finally(self): + count = 0 + while count < 2: + count += 1 + try: + pass + finally: + continue + break + self.assertEqual(count, 2) + + count = 0 + while count < 2: + count += 1 + try: + break + finally: + continue + self.assertEqual(count, 2) + + count = 0 + while count < 2: + count += 1 + try: + 1/0 + finally: + continue + break + self.assertEqual(count, 2) + + for count in [0, 1]: + try: + pass + finally: + continue + break + self.assertEqual(count, 1) + + for count in [0, 1]: + try: + break + finally: + continue + self.assertEqual(count, 1) + + for count in [0, 1]: + try: + 1/0 + finally: + continue + break + self.assertEqual(count, 1) + def test_return_in_finally(self): def g1(): try: @@ -870,6 +1155,62 @@ class GrammarTests(unittest.TestCase): return 4 self.assertEqual(g3(), 4) + @skip("FIXME: currently crashes because the iterable is cleaned up on 'return', not on loop exit") + def test_break_in_finally_after_return(self): + # See issue #37830 + def g1(x): + for count in [0, 1]: + count2 = 0 + while count2 < 20: + count2 += 10 + try: + return count + count2 + finally: + if x: + break + return 'end', count, count2 + self.assertEqual(g1(False), 10) + self.assertEqual(g1(True), ('end', 1, 10)) + + def g2(x): + for count in [0, 1]: + for count2 in [10, 20]: + try: + return count + count2 + finally: + if x: + break + return 'end', count, count2 + self.assertEqual(g2(False), 10) + self.assertEqual(g2(True), ('end', 1, 10)) + + @skip("FIXME: currently crashes because the iterable is cleaned up on 'return', not on loop exit") + def test_continue_in_finally_after_return(self): + # See issue #37830 + def g1(x): + count = 0 + while count < 100: + count += 1 + try: + return count + finally: + if x: + continue + return 'end', count + self.assertEqual(g1(False), 1) + self.assertEqual(g1(True), ('end', 100)) + + def g2(x): + for count in [0, 1]: + try: + return count + finally: + if x: + continue + return 'end', count + self.assertEqual(g2(False), 0) + self.assertEqual(g2(True), ('end', 1)) + def test_yield(self): # Allowed as standalone statement def g(): yield 1 @@ -907,23 +1248,15 @@ class GrammarTests(unittest.TestCase): check_syntax_error(self, "class foo:yield 1") check_syntax_error(self, "class foo:yield from ()") # Check annotation refleak on SyntaxError - check_syntax_error(self, "def g(a:(yield)): pass") + #check_syntax_error(self, "def g(a:(yield)): pass") # no longer a syntax error with PEP-563 - @skip("DeprecationWarning not implemented") + @skip("Not currently a syntax error") def test_yield_in_comprehensions(self): # Check yield in comprehensions def g(): [x for x in [(yield 1)]] def g(): [x for x in [(yield from ())]] - def check(code, warntext): - with self.assertWarnsRegex(DeprecationWarning, warntext): - compile(code, '<test string>', 'exec') - import warnings - with warnings.catch_warnings(): - warnings.filterwarnings('error', category=DeprecationWarning) - with self.assertRaisesRegex(SyntaxError, warntext): - compile(code, '<test string>', 'exec') - + check = self.check_syntax_error check("def g(): [(yield x) for x in ()]", "'yield' inside list comprehension") check("def g(): [x for x in () if not (yield x)]", @@ -1014,6 +1347,15 @@ class GrammarTests(unittest.TestCase): else: self.fail("AssertionError not raised by 'assert False'") + self.check_syntax_warning('assert(x, "msg")', + 'assertion is always true') + # FIXME: currently fails to compile + """ + with warnings.catch_warnings(): + warnings.simplefilter('error', SyntaxWarning) + compile('assert x, "msg"', '<testcase>', 'exec') + """ + ### compound_stmt: if_stmt | while_stmt | for_stmt | try_stmt | funcdef | classdef # Tested below @@ -1076,7 +1418,7 @@ class GrammarTests(unittest.TestCase): def test_try(self): ### try_stmt: 'try' ':' suite (except_clause ':' suite)+ ['else' ':' suite] ### | 'try' ':' suite 'finally' ':' suite - ### except_clause: 'except' [expr ['as' expr]] + ### except_clause: 'except' [expr ['as' NAME]] try: 1/0 except ZeroDivisionError: @@ -1094,6 +1436,9 @@ class GrammarTests(unittest.TestCase): except (EOFError, TypeError, ZeroDivisionError) as msg: pass try: pass finally: pass + with self.assertRaises(SyntaxError): + compile("try:\n pass\nexcept Exception as a.b:\n pass", "?", "exec") + compile("try:\n pass\nexcept Exception as a[b]:\n pass", "?", "exec") def test_suite(self): # simple_stmt | NEWLINE INDENT NEWLINE* (stmt NEWLINE*)+ DEDENT @@ -1132,11 +1477,116 @@ class GrammarTests(unittest.TestCase): if 1 > 1: pass if 1 <= 1: pass if 1 >= 1: pass - if 1 is 1: pass - if 1 is not 1: pass + if x is x: pass + if x is not x: pass if 1 in (): pass if 1 not in (): pass - if 1 < 1 > 1 == 1 >= 1 <= 1 != 1 in 1 not in 1 is 1 is not 1: pass + if 1 < 1 > 1 == 1 >= 1 <= 1 != 1 in 1 not in x is x is not x: pass + + def test_comparison_is_literal(self): + def check(test, msg='"is" with a literal'): + self.check_syntax_warning(test, msg) + + check('x is 1') + check('x is "thing"') + check('1 is x') + check('x is y is 1') + check('x is not 1', '"is not" with a literal') + + # FIXME: this fails to compile + """ + with warnings.catch_warnings(): + warnings.simplefilter('error', SyntaxWarning) + compile('x is None', '<testcase>', 'exec') + compile('x is False', '<testcase>', 'exec') + compile('x is True', '<testcase>', 'exec') + compile('x is ...', '<testcase>', 'exec') + """ + + def test_warn_missed_comma(self): + # FIXME: would be nice if this could actually raise a compile time warning as well + def check(test): + self.check_syntax_warning(test, msg) + + msg=r'is not callable; perhaps you missed a comma\?' + check('[(1, 2) (3, 4)]') + check('[(x, y) (3, 4)]') + check('[[1, 2] (3, 4)]') + check('[{1, 2} (3, 4)]') + check('[{1: 2} (3, 4)]') + check('[[i for i in range(5)] (3, 4)]') + check('[{i for i in range(5)} (3, 4)]') + check('[(i for i in range(5)) (3, 4)]') + check('[{i: i for i in range(5)} (3, 4)]') + check('[f"{x}" (3, 4)]') + check('[f"x={x}" (3, 4)]') + check('["abc" (3, 4)]') + check('[b"abc" (3, 4)]') + check('[123 (3, 4)]') + check('[12.3 (3, 4)]') + check('[12.3j (3, 4)]') + check('[None (3, 4)]') + check('[True (3, 4)]') + check('[... (3, 4)]') + + msg=r'is not subscriptable; perhaps you missed a comma\?' + check('[{1, 2} [i, j]]') + check('[{i for i in range(5)} [i, j]]') + check('[(i for i in range(5)) [i, j]]') + check('[(lambda x, y: x) [i, j]]') + check('[123 [i, j]]') + check('[12.3 [i, j]]') + check('[12.3j [i, j]]') + check('[None [i, j]]') + check('[True [i, j]]') + check('[... [i, j]]') + + msg=r'indices must be integers or slices, not tuple; perhaps you missed a comma\?' + check('[(1, 2) [i, j]]') + check('[(x, y) [i, j]]') + check('[[1, 2] [i, j]]') + check('[[i for i in range(5)] [i, j]]') + check('[f"{x}" [i, j]]') + check('[f"x={x}" [i, j]]') + check('["abc" [i, j]]') + check('[b"abc" [i, j]]') + + msg=r'indices must be integers or slices, not tuple;' + check('[[1, 2] [3, 4]]') + msg=r'indices must be integers or slices, not list;' + check('[[1, 2] [[3, 4]]]') + check('[[1, 2] [[i for i in range(5)]]]') + msg=r'indices must be integers or slices, not set;' + check('[[1, 2] [{3, 4}]]') + check('[[1, 2] [{i for i in range(5)}]]') + msg=r'indices must be integers or slices, not dict;' + check('[[1, 2] [{3: 4}]]') + check('[[1, 2] [{i: i for i in range(5)}]]') + msg=r'indices must be integers or slices, not generator;' + check('[[1, 2] [(i for i in range(5))]]') + msg=r'indices must be integers or slices, not function;' + check('[[1, 2] [(lambda x, y: x)]]') + msg=r'indices must be integers or slices, not str;' + check('[[1, 2] [f"{x}"]]') + check('[[1, 2] [f"x={x}"]]') + check('[[1, 2] ["abc"]]') + msg=r'indices must be integers or slices, not' + check('[[1, 2] [b"abc"]]') + check('[[1, 2] [12.3]]') + check('[[1, 2] [12.3j]]') + check('[[1, 2] [None]]') + check('[[1, 2] [...]]') + + """ + with warnings.catch_warnings(): + warnings.simplefilter('error', SyntaxWarning) + compile('[(lambda x, y: x) (3, 4)]', '<testcase>', 'exec') + compile('[[1, 2] [i]]', '<testcase>', 'exec') + compile('[[1, 2] [0]]', '<testcase>', 'exec') + compile('[[1, 2] [True]]', '<testcase>', 'exec') + compile('[[1, 2] [1:2]]', '<testcase>', 'exec') + compile('[{(1, 2): 3} [i, j]]', '<testcase>', 'exec') + """ def test_binary_mask_ops(self): x = 1 & 1 @@ -1244,13 +1694,27 @@ class GrammarTests(unittest.TestCase): def meth2(self, arg): pass def meth3(self, a1, a2): pass - # decorator: '@' dotted_name [ '(' [arglist] ')' ] NEWLINE + # decorator: '@' namedexpr_test NEWLINE # decorators: decorator+ # decorated: decorators (classdef | funcdef) def class_decorator(x): return x @class_decorator class G: pass + # Test expressions as decorators (PEP 614): + @False or class_decorator + class H: pass + @d := class_decorator + class I: pass + @lambda c: class_decorator(c) + class J: pass + @[..., class_decorator, ...][1] + class K: pass + @class_decorator(class_decorator)(class_decorator) + class L: pass + @[class_decorator][0].__call__.__call__ + class M: pass + def test_dictcomps(self): # dictorsetmaker: ( (test ':' test (comp_for | # (',' test ':' test)* [','])) | @@ -1347,7 +1811,7 @@ class GrammarTests(unittest.TestCase): self.assertEqual(sum(b), sum([x for x in range(10)])) self.assertEqual(sum(x**2 for x in range(10)), sum([x**2 for x in range(10)])) - self.assertEqual(sum(x*x for x in range(10) if x%2), sum([x*x for x in range(10) if x%2])) + self.assertEqual(sum(x*x for x in range(10) if x % 2), sum([x*x for x in range(10) if x % 2])) self.assertEqual(sum(x for x in (y for y in range(10))), sum([x for x in range(10)])) self.assertEqual(sum(x for x in (y for y in (z for z in range(10)))), sum([x for x in range(10)])) self.assertEqual(sum(x for x in [y for y in (z for z in range(10))]), sum([x for x in range(10)])) @@ -1358,6 +1822,8 @@ class GrammarTests(unittest.TestCase): def test_comprehension_specials(self): # test for outmost iterable precomputation + # FIXME: https://github.com/cython/cython/issues/1159 + """ x = 10; g = (i for i in range(x)); x = 5 self.assertEqual(len(list(g)), 10) @@ -1365,6 +1831,7 @@ class GrammarTests(unittest.TestCase): x = 10; t = False; g = ((i,j) for i in range(x) if t for j in range(x)) x = 5; t = True; self.assertEqual([(i,j) for i in range(10) for j in range(5)], list(g)) + """ # Grammar allows multiple adjacent 'if's in listcomps and genexps, # even though it's silly. Make sure it works (ifelse broke this.) @@ -1395,11 +1862,60 @@ class GrammarTests(unittest.TestCase): with manager() as x, manager(): pass + with ( + manager() + ): + pass + + with ( + manager() as x + ): + pass + + with ( + manager() as (x, y), + manager() as z, + ): + pass + + with ( + manager(), + manager() + ): + pass + + with ( + manager() as x, + manager() as y + ): + pass + + with ( + manager() as x, + manager() + ): + pass + + with ( + manager() as x, + manager() as y, + manager() as z, + ): + pass + + with ( + manager() as x, + manager() as y, + manager(), + ): + pass + + def test_if_else_expr(self): # Test ifelse expressions in various cases def _checkeval(msg, ret): "helper to check that evaluation of expressions is done correctly" - print(x) + print(msg) return ret # the next line is not allowed anymore @@ -1426,9 +1942,11 @@ class GrammarTests(unittest.TestCase): self.assertEqual(16 // (4 // 2), 8) self.assertEqual((16 // 4) // 2, 2) self.assertEqual(16 // 4 // 2, 2) - self.assertTrue(False is (2 is 3)) - self.assertFalse((False is 2) is 3) - self.assertFalse(False is 2 is 3) + x = 2 + y = 3 + self.assertTrue(False is (x is y)) + self.assertFalse((False is x) is y) + self.assertFalse(False is x is y) def test_matrix_mul(self): # This is not intended to be a comprehensive test, rather just to be few @@ -1516,54 +2034,5 @@ class GrammarTests(unittest.TestCase): foo().send(None) -### END OF COPY ### - -GrammarTests.assertRaisesRegex = lambda self, exc, msg: self.assertRaises(exc) - -if sys.version_info < (2, 7): - def assertRaises(self, exc_type, func=None, *args, **kwargs): - if func is not None: - return unittest.TestCase.assertRaises(self, exc_type, func, *args, **kwargs) - @contextlib.contextmanager - def assertRaisesCM(): - class Result(object): - exception = exc_type("unexpected EOF") # see usage above - try: - yield Result() - except exc_type: - self.assertTrue(True) - else: - self.assertTrue(False) - return assertRaisesCM() - GrammarTests.assertRaises = assertRaises - TokenTests.assertRaises = assertRaises - - -if not hasattr(unittest.TestCase, 'subTest'): - @contextlib.contextmanager - def subTest(self, source, **kwargs): - try: - yield - except Exception: - print(source) - raise - GrammarTests.subTest = subTest - - -if not hasattr(unittest.TestCase, 'assertIn'): - def assertIn(self, member, container, msg=None): - self.assertTrue(member in container, msg) - TokenTests.assertIn = assertIn - - -# FIXME: disabling some tests for real Cython bugs here -del GrammarTests.test_comprehension_specials # iterable pre-calculation in generator expression -del GrammarTests.test_funcdef # annotation mangling - -# this test is difficult to enable in Py2.6 -if sys.version_info < (2,7): - del GrammarTests.test_former_statements_refer_to_builtins - - if __name__ == '__main__': unittest.main() |