From 6a4d4b727cf1aa30250381dff652257f92b6bd92 Mon Sep 17 00:00:00 2001 From: ShaharNaveh <50263213+ShaharNaveh@users.noreply.github.com> Date: Sun, 31 Aug 2025 11:47:32 +0300 Subject: [PATCH] Uodate `test_grammar.py` from 3.13.7 --- Lib/test/test_grammar.py | 450 +++++++++++++++++++++++++++------------ 1 file changed, 310 insertions(+), 140 deletions(-) diff --git a/Lib/test/test_grammar.py b/Lib/test/test_grammar.py index e40f569d2..323f4ee4c 100644 --- a/Lib/test/test_grammar.py +++ b/Lib/test/test_grammar.py @@ -2,7 +2,7 @@ # This just tests whether the parser accepts them all. from test.support import check_syntax_error -from test.support.warnings_helper import check_syntax_warning +from test.support import import_helper import inspect import unittest import sys @@ -12,97 +12,19 @@ from sys import * # different import patterns to check that __annotations__ does not interfere # with import machinery -# import test.ann_module as ann_module +import test.typinganndata.ann_module as ann_module import typing -from collections import ChainMap -# from test import ann_module2 +from test.typinganndata 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)', -] - +from test.support.numbers import ( + VALID_UNDERSCORE_LITERALS, + INVALID_UNDERSCORE_LITERALS, +) class TokenTests(unittest.TestCase): from test.support import check_syntax_error + from test.support.warnings_helper import check_syntax_warning def test_backslash(self): # Backslash means line continuation: @@ -164,7 +86,7 @@ class TokenTests(unittest.TestCase): x = 3.14 x = 314. x = 0.314 - # XXX x = 000.314 + x = 000.314 x = .314 x = 3e14 x = 3E14 @@ -176,8 +98,10 @@ class TokenTests(unittest.TestCase): def test_float_exponent_tokenization(self): # See issue 21642. - self.assertEqual(1 if 1else 0, 1) - self.assertEqual(1 if 0else 0, 0) + with warnings.catch_warnings(): + warnings.simplefilter('ignore', SyntaxWarning) + self.assertEqual(eval("1 if 1else 0"), 1) + self.assertEqual(eval("1 if 0else 0"), 0) self.assertRaises(SyntaxError, eval, "0 if 1Else 0") def test_underscore_literals(self): @@ -188,8 +112,7 @@ class TokenTests(unittest.TestCase): # Sanity check: no literal begins with an underscore self.assertRaises(NameError, eval, "_0") - # TODO: RUSTPYTHON - @unittest.expectedFailure + @unittest.expectedFailure # TODO: RUSTPYTHON def test_bad_numerical_literals(self): check = self.check_syntax_error check("0b12", "invalid digit '2' in binary literal") @@ -212,6 +135,47 @@ class TokenTests(unittest.TestCase): check("1e2_", "invalid decimal literal") check("1e+", "invalid decimal literal") + @unittest.expectedFailure # TODO: RUSTPYTHON + def test_end_of_numerical_literals(self): + def check(test, error=False): + with self.subTest(expr=test): + if error: + with warnings.catch_warnings(record=True) as w: + with self.assertRaisesRegex(SyntaxError, + r'invalid \w+ literal'): + compile(test, "", "eval") + self.assertEqual(w, []) + else: + self.check_syntax_warning(test, + errtext=r'invalid \w+ literal') + + for num in "0xf", "0o7", "0b1", "9", "0", "1.", "1e3", "1j": + compile(num, "", "eval") + check(f"{num}and x", error=(num == "0xf")) + check(f"{num}or x", error=(num == "0")) + check(f"{num}in x") + check(f"{num}not in x") + check(f"{num}if x else y") + check(f"x if {num}else y", error=(num == "0xf")) + check(f"[{num}for x in ()]") + check(f"{num}spam", error=True) + + # gh-88943: Invalid non-ASCII character following a numerical literal. + with self.assertRaisesRegex(SyntaxError, r"invalid character '⁄' \(U\+2044\)"): + compile(f"{num}⁄7", "", "eval") + + with self.assertWarnsRegex(SyntaxWarning, r'invalid \w+ literal'): + compile(f"{num}is x", "", "eval") + with warnings.catch_warnings(): + warnings.simplefilter('error', SyntaxWarning) + with self.assertRaisesRegex(SyntaxError, + r'invalid \w+ literal'): + compile(f"{num}is x", "", "eval") + + check("[0x1ffor x in ()]") + check("[0x1for x in ()]") + check("[0xfor x in ()]") + 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) @@ -257,12 +221,13 @@ the \'lazy\' dog.\n\ self.assertTrue(x is Ellipsis) self.assertRaises(SyntaxError, eval, ".. .") + @unittest.expectedFailure # TODO: RUSTPYTHON def test_eof_error(self): samples = ("def foo(", "\ndef foo(", "def foo(\n") for s in samples: with self.assertRaises(SyntaxError) as cm: compile(s, "", "exec") - self.assertIn("unexpected EOF", str(cm.exception)) + self.assertIn("was never closed", str(cm.exception)) var_annot_global: int # a global annotated is necessary for test_var_annot @@ -281,6 +246,7 @@ class GrammarTests(unittest.TestCase): from test.support import check_syntax_error from test.support.warnings_helper import check_syntax_warning + from test.support.warnings_helper import check_no_warnings # single_input: NEWLINE | simple_stmt | compound_stmt NEWLINE # XXX can't test in a script -- this rule is only used when interactive @@ -307,14 +273,18 @@ class GrammarTests(unittest.TestCase): my_lst[one()-1]: int = 5 self.assertEqual(my_lst, [5]) - # TODO: RUSTPYTHON - @unittest.expectedFailure + @unittest.expectedFailure # TODO: RUSTPYTHON 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") + check_syntax_error(self, "def f():\n" + " global x: int\n") + check_syntax_error(self, "x: int = y = 1") + check_syntax_error(self, "z = w: int = 1") + check_syntax_error(self, "x: int = y: int = 1") # AST pass check_syntax_error(self, "[x, 0]: int\n") check_syntax_error(self, "f(): int\n") @@ -328,9 +298,14 @@ class GrammarTests(unittest.TestCase): check_syntax_error(self, "def f():\n" " global x\n" " x: int\n") + check_syntax_error(self, "def f():\n" + " x: int\n" + " nonlocal x\n") + check_syntax_error(self, "def f():\n" + " nonlocal x\n" + " x: int\n") - # TODO: RUSTPYTHON - @unittest.expectedFailure + @unittest.expectedFailure # TODO: RUSTPYTHON def test_var_annot_basic_semantics(self): # execution order with self.assertRaises(ZeroDivisionError): @@ -377,6 +352,28 @@ class GrammarTests(unittest.TestCase): x: int x.y: list = [] + def test_annotations_inheritance(self): + # Check that annotations are not inherited by derived classes + class A: + attr: int + class B(A): + pass + class C(A): + attr: str + class D: + attr2: int + class E(A, D): + pass + class F(C, A): + pass + self.assertEqual(A.__annotations__, {"attr": int}) + self.assertEqual(B.__annotations__, {}) + self.assertEqual(C.__annotations__, {"attr" : str}) + self.assertEqual(D.__annotations__, {"attr2" : int}) + self.assertEqual(E.__annotations__, {}) + self.assertEqual(F.__annotations__, {}) + + def test_var_annot_metaclass_semantics(self): class CMeta(type): @classmethod @@ -386,13 +383,11 @@ class GrammarTests(unittest.TestCase): XX: 'ANNOT' self.assertEqual(CC.__annotations__['xx'], 'ANNOT') - # TODO: RUSTPYTHON - @unittest.expectedFailure + @unittest.expectedFailure # TODO: RUSTPYTHON def test_var_annot_module_semantics(self): - with self.assertRaises(AttributeError): - print(test.__annotations__) + self.assertEqual(test.__annotations__, {}) self.assertEqual(ann_module.__annotations__, - {1: 2, 'x': int, 'y': str, 'f': typing.Tuple[int, int]}) + {1: 2, 'x': int, 'y': str, 'f': typing.Tuple[int, int], 'u': int | float}) self.assertEqual(ann_module.M.__annotations__, {'123': 123, 'o': type}) self.assertEqual(ann_module2.__annotations__, {}) @@ -400,13 +395,13 @@ class GrammarTests(unittest.TestCase): 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.typinganndata.ann_module3 import f_bad_ann, g_bad_ann, D_bad_ann + ann_module3 = import_helper.import_fresh_module("test.typinganndata.ann_module3") with self.assertRaises(NameError): - f_bad_ann() + ann_module3.f_bad_ann() with self.assertRaises(NameError): - g_bad_ann() + ann_module3.g_bad_ann() with self.assertRaises(NameError): - D_bad_ann(5) + ann_module3.D_bad_ann(5) def test_var_annot_simple_exec(self): gns = {}; lns= {} @@ -417,8 +412,7 @@ class GrammarTests(unittest.TestCase): with self.assertRaises(KeyError): gns['__annotations__'] - # TODO: RUSTPYTHON - @unittest.expectedFailure + @unittest.expectedFailure # TODO: RUSTPYTHON def test_var_annot_custom_maps(self): # tests with custom locals() and __annotations__ ns = {'__annotations__': CNS()} @@ -440,8 +434,7 @@ class GrammarTests(unittest.TestCase): exec('x: int = 1', {}, CNS2()) self.assertEqual(nonloc_ns['__annotations__']['x'], int) - # TODO: RUSTPYTHON - @unittest.expectedFailure + @unittest.expectedFailure # TODO: RUSTPYTHON def test_var_annot_refleak(self): # complex case: custom locals plus custom __annotations__ # this was causing refleak @@ -459,7 +452,6 @@ class GrammarTests(unittest.TestCase): exec('X: str', {}, CNS2()) self.assertEqual(nonloc_ns['__annotations__']['x'], str) - def test_var_annot_rhs(self): ns = {} exec('x: tuple = 1, 2', ns) @@ -475,7 +467,7 @@ class GrammarTests(unittest.TestCase): 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] ',')* @@ -597,12 +589,14 @@ class GrammarTests(unittest.TestCase): d22v(1, *(2, 3), **{'d': 4}) # keyword argument type tests - try: - str('x', **{b'foo':1 }) - except TypeError: - pass - else: - self.fail('Bytes should not work as keyword argument names') + with warnings.catch_warnings(): + warnings.simplefilter('ignore', BytesWarning) + try: + str('x', **{b'foo':1 }) + except TypeError: + pass + else: + self.fail('Bytes should not work as keyword argument names') # keyword only argument tests def pos0key1(*, key): return key pos0key1(key=100) @@ -681,6 +675,20 @@ class GrammarTests(unittest.TestCase): def f(x) -> list: pass 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 @@ -768,10 +776,9 @@ class GrammarTests(unittest.TestCase): check_syntax_error(self, "x + 1 = 1") check_syntax_error(self, "a + 1 = b + 2") - # TODO: RUSTPYTHON - @unittest.expectedFailure # Check the heuristic for print & exec covers significant cases # As well as placing some limits on false positives + @unittest.expectedFailure # TODO: RUSTPYTHON def test_former_statements_refer_to_builtins(self): keywords = "print", "exec" # Cases where we want the custom error @@ -804,6 +811,23 @@ 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] + + compile("del a, (b[0].c, (d.e, f.g[1:2])), [h.i.j], ()", "", "exec") + def test_pass_stmt(self): # 'pass' pass @@ -1062,8 +1086,7 @@ class GrammarTests(unittest.TestCase): self.assertEqual(g2(False), 0) self.assertEqual(g2(True), ('end', 1)) - # TODO: RUSTPYTHON - @unittest.expectedFailure + @unittest.expectedFailure # TODO: RUSTPYTHON def test_yield(self): # Allowed as standalone statement def g(): yield 1 @@ -1103,8 +1126,7 @@ class GrammarTests(unittest.TestCase): # Check annotation refleak on SyntaxError check_syntax_error(self, "def g(a:(yield)): pass") - # TODO: RUSTPYTHON - @unittest.expectedFailure + @unittest.expectedFailure # TODO: RUSTPYTHON def test_yield_in_comprehensions(self): # Check yield in comprehensions def g(): [x for x in [(yield 1)]] @@ -1184,11 +1206,9 @@ class GrammarTests(unittest.TestCase): self.fail("'assert True, msg' should not have " "raised an AssertionError") - # TODO: RUSTPYTHON - @unittest.expectedFailure # these tests fail if python is run with -O, so check __debug__ @unittest.skipUnless(__debug__, "Won't work if __debug__ is False") - def testAssert2(self): + def test_assert_failures(self): try: assert 0, "msg" except AssertionError as e: @@ -1203,11 +1223,38 @@ class GrammarTests(unittest.TestCase): else: self.fail("AssertionError not raised by 'assert False'") + @unittest.expectedFailure # TODO: RUSTPYTHON + def test_assert_syntax_warnings(self): + # Ensure that we warn users if they provide a non-zero length tuple as + # the assertion test. self.check_syntax_warning('assert(x, "msg")', 'assertion is always true') + self.check_syntax_warning('assert(False, "msg")', + 'assertion is always true') + self.check_syntax_warning('assert(False,)', + 'assertion is always true') + + with self.check_no_warnings(category=SyntaxWarning): + compile('assert x, "msg"', '', 'exec') + compile('assert False, "msg"', '', 'exec') + + @unittest.expectedFailure # TODO: RUSTPYTHON + def test_assert_warning_promotes_to_syntax_error(self): + # If SyntaxWarning is configured to be an error, it actually raises a + # SyntaxError. + # https://bugs.python.org/issue35029 with warnings.catch_warnings(): warnings.simplefilter('error', SyntaxWarning) - compile('assert x, "msg"', '', 'exec') + try: + compile('assert x, "msg" ', '', 'exec') + except SyntaxError: + self.fail('SyntaxError incorrectly raised for \'assert x, "msg"\'') + with self.assertRaises(SyntaxError): + compile('assert(x, "msg")', '', 'exec') + with self.assertRaises(SyntaxError): + compile('assert(False, "msg")', '', 'exec') + with self.assertRaises(SyntaxError): + compile('assert(False,)', '', 'exec') ### compound_stmt: if_stmt | while_stmt | for_stmt | try_stmt | funcdef | classdef @@ -1268,10 +1315,16 @@ class GrammarTests(unittest.TestCase): result.append(x) self.assertEqual(result, [1, 2, 3]) + result = [] + a = b = c = [1, 2, 3] + for x in *a, *b, *c: + result.append(x) + self.assertEqual(result, 3 * a) + 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: @@ -1289,6 +1342,36 @@ 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") + + # TODO: RUSTPYTHON + ''' + def test_try_star(self): + ### try_stmt: 'try': suite (except_star_clause : suite) + ['else' ':' suite] + ### except_star_clause: 'except*' expr ['as' NAME] + try: + 1/0 + except* ZeroDivisionError: + pass + else: + pass + try: 1/0 + except* EOFError: pass + except* ZeroDivisionError as msg: pass + else: pass + try: 1/0 + except* (EOFError, TypeError, ZeroDivisionError): pass + try: 1/0 + 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") + compile("try:\n pass\nexcept*:\n pass", "?", "exec") + ''' def test_suite(self): # simple_stmt | NEWLINE INDENT NEWLINE* (stmt NEWLINE*)+ DEDENT @@ -1333,17 +1416,24 @@ class GrammarTests(unittest.TestCase): if 1 not in (): pass if 1 < 1 > 1 == 1 >= 1 <= 1 != 1 in 1 not in x is x is not x: pass - # TODO: RUSTPYTHON - @unittest.expectedFailure + @unittest.expectedFailure # TODO: RUSTPYTHON def test_comparison_is_literal(self): - def check(test, msg='"is" with a literal'): + def check(test, msg): 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') + check('x is 1', '"is" with \'int\' literal') + check('x is "thing"', '"is" with \'str\' literal') + check('1 is x', '"is" with \'int\' literal') + check('x is y is 1', '"is" with \'int\' literal') + check('x is not 1', '"is not" with \'int\' literal') + check('x is not (1, 2)', '"is not" with \'tuple\' literal') + check('(1, 2) is not x', '"is not" with \'tuple\' literal') + + check('None is 1', '"is" with \'int\' literal') + check('1 is None', '"is" with \'int\' literal') + + check('x == 3 is y', '"is" with \'int\' literal') + check('x == "thing" is y', '"is" with \'str\' literal') with warnings.catch_warnings(): warnings.simplefilter('error', SyntaxWarning) @@ -1351,9 +1441,12 @@ class GrammarTests(unittest.TestCase): compile('x is False', '', 'exec') compile('x is True', '', 'exec') compile('x is ...', '', 'exec') + compile('None is x', '', 'exec') + compile('False is x', '', 'exec') + compile('True is x', '', 'exec') + compile('... is x', '', 'exec') - # TODO: RUSTPYTHON - @unittest.expectedFailure + @unittest.expectedFailure # TODO: RUSTPYTHON def test_warn_missed_comma(self): def check(test): self.check_syntax_warning(test, msg) @@ -1483,7 +1576,7 @@ class GrammarTests(unittest.TestCase): s = a[-5:] s = a[:-1] s = a[-4:-3] - # A rough test of SF bug 1333982. http://python.org/sf/1333982 + # A rough test of SF bug 1333982. https://bugs.python.org/issue1333982 # The testing here is fairly incomplete. # Test cases should include: commas with 1 and 2 colons d = {} @@ -1530,6 +1623,8 @@ class GrammarTests(unittest.TestCase): ### testlist: test (',' test)* [','] # These have been exercised enough above + # TODO: RUSTPYTHON + ''' def test_classdef(self): # 'class' NAME ['(' [testlist] ')'] ':' suite class B: pass @@ -1542,13 +1637,28 @@ 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)* [','])) | @@ -1693,6 +1803,54 @@ 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): @@ -1815,6 +1973,18 @@ class GrammarTests(unittest.TestCase): with self.assertRaises(Done): foo().send(None) + def test_complex_lambda(self): + def test1(foo, bar): + return "" + + def test2(): + return f"{test1( + foo=lambda: '、、、、、、、、、、、、、、、、、', + bar=lambda: 'abcdefghijklmnopqrstuvwxyz 123456789 123456789', + )}" + + self.assertEqual(test2(), "") + if __name__ == '__main__': unittest.main()