diff options
Diffstat (limited to 'Lib')
-rw-r--r-- | Lib/_compat_pickle.py | 1 | ||||
-rw-r--r-- | Lib/symbol.py | 77 | ||||
-rw-r--r-- | Lib/test/exception_hierarchy.txt | 1 | ||||
-rw-r--r-- | Lib/test/test_named_expressions.py | 415 | ||||
-rw-r--r-- | Lib/test/test_parser.py | 46 | ||||
-rw-r--r-- | Lib/test/test_tokenize.py | 1 | ||||
-rw-r--r-- | Lib/token.py | 14 |
7 files changed, 506 insertions, 49 deletions
diff --git a/Lib/_compat_pickle.py b/Lib/_compat_pickle.py index f68496a..8bb1cf8 100644 --- a/Lib/_compat_pickle.py +++ b/Lib/_compat_pickle.py @@ -128,6 +128,7 @@ PYTHON2_EXCEPTIONS = ( "SystemError", "SystemExit", "TabError", + "TargetScopeError", "TypeError", "UnboundLocalError", "UnicodeDecodeError", diff --git a/Lib/symbol.py b/Lib/symbol.py index 40d0ed1..b3fa089 100644 --- a/Lib/symbol.py +++ b/Lib/symbol.py @@ -61,44 +61,45 @@ with_stmt = 301 with_item = 302 except_clause = 303 suite = 304 -test = 305 -test_nocond = 306 -lambdef = 307 -lambdef_nocond = 308 -or_test = 309 -and_test = 310 -not_test = 311 -comparison = 312 -comp_op = 313 -star_expr = 314 -expr = 315 -xor_expr = 316 -and_expr = 317 -shift_expr = 318 -arith_expr = 319 -term = 320 -factor = 321 -power = 322 -atom_expr = 323 -atom = 324 -testlist_comp = 325 -trailer = 326 -subscriptlist = 327 -subscript = 328 -sliceop = 329 -exprlist = 330 -testlist = 331 -dictorsetmaker = 332 -classdef = 333 -arglist = 334 -argument = 335 -comp_iter = 336 -sync_comp_for = 337 -comp_for = 338 -comp_if = 339 -encoding_decl = 340 -yield_expr = 341 -yield_arg = 342 +namedexpr_test = 305 +test = 306 +test_nocond = 307 +lambdef = 308 +lambdef_nocond = 309 +or_test = 310 +and_test = 311 +not_test = 312 +comparison = 313 +comp_op = 314 +star_expr = 315 +expr = 316 +xor_expr = 317 +and_expr = 318 +shift_expr = 319 +arith_expr = 320 +term = 321 +factor = 322 +power = 323 +atom_expr = 324 +atom = 325 +testlist_comp = 326 +trailer = 327 +subscriptlist = 328 +subscript = 329 +sliceop = 330 +exprlist = 331 +testlist = 332 +dictorsetmaker = 333 +classdef = 334 +arglist = 335 +argument = 336 +comp_iter = 337 +sync_comp_for = 338 +comp_for = 339 +comp_if = 340 +encoding_decl = 341 +yield_expr = 342 +yield_arg = 343 #--end constants-- sym_name = {} diff --git a/Lib/test/exception_hierarchy.txt b/Lib/test/exception_hierarchy.txt index 763a6c8..15f4491 100644 --- a/Lib/test/exception_hierarchy.txt +++ b/Lib/test/exception_hierarchy.txt @@ -42,6 +42,7 @@ BaseException | +-- NotImplementedError | +-- RecursionError +-- SyntaxError + | +-- TargetScopeError | +-- IndentationError | +-- TabError +-- SystemError diff --git a/Lib/test/test_named_expressions.py b/Lib/test/test_named_expressions.py new file mode 100644 index 0000000..e49fd7d --- /dev/null +++ b/Lib/test/test_named_expressions.py @@ -0,0 +1,415 @@ +import os +import unittest + + +class NamedExpressionInvalidTest(unittest.TestCase): + + def test_named_expression_invalid_01(self): + code = """x := 0""" + + with self.assertRaisesRegex(SyntaxError, "invalid syntax"): + exec(code, {}, {}) + + def test_named_expression_invalid_02(self): + code = """x = y := 0""" + + with self.assertRaisesRegex(SyntaxError, "invalid syntax"): + exec(code, {}, {}) + + def test_named_expression_invalid_03(self): + code = """y := f(x)""" + + with self.assertRaisesRegex(SyntaxError, "invalid syntax"): + exec(code, {}, {}) + + def test_named_expression_invalid_04(self): + code = """y0 = y1 := f(x)""" + + with self.assertRaisesRegex(SyntaxError, "invalid syntax"): + exec(code, {}, {}) + + def test_named_expression_invalid_06(self): + code = """((a, b) := (1, 2))""" + + with self.assertRaisesRegex(SyntaxError, "cannot use named assignment with tuple"): + exec(code, {}, {}) + + def test_named_expression_invalid_07(self): + code = """def spam(a = b := 42): pass""" + + with self.assertRaisesRegex(SyntaxError, "invalid syntax"): + exec(code, {}, {}) + + def test_named_expression_invalid_08(self): + code = """def spam(a: b := 42 = 5): pass""" + + with self.assertRaisesRegex(SyntaxError, "invalid syntax"): + exec(code, {}, {}) + + def test_named_expression_invalid_09(self): + code = """spam(a=b := 'c')""" + + with self.assertRaisesRegex(SyntaxError, "invalid syntax"): + exec(code, {}, {}) + + def test_named_expression_invalid_10(self): + code = """spam(x = y := f(x))""" + + with self.assertRaisesRegex(SyntaxError, "invalid syntax"): + exec(code, {}, {}) + + def test_named_expression_invalid_11(self): + code = """spam(a=1, b := 2)""" + + with self.assertRaisesRegex(SyntaxError, + "positional argument follows keyword argument"): + exec(code, {}, {}) + + def test_named_expression_invalid_12(self): + code = """spam(a=1, (b := 2))""" + + with self.assertRaisesRegex(SyntaxError, + "positional argument follows keyword argument"): + exec(code, {}, {}) + + def test_named_expression_invalid_13(self): + code = """spam(a=1, (b := 2))""" + + with self.assertRaisesRegex(SyntaxError, + "positional argument follows keyword argument"): + exec(code, {}, {}) + + def test_named_expression_invalid_14(self): + code = """(x := lambda: y := 1)""" + + with self.assertRaisesRegex(SyntaxError, "invalid syntax"): + exec(code, {}, {}) + + def test_named_expression_invalid_15(self): + code = """(lambda: x := 1)""" + + with self.assertRaisesRegex(SyntaxError, + "cannot use named assignment with lambda"): + exec(code, {}, {}) + + def test_named_expression_invalid_16(self): + code = "[i + 1 for i in i := [1,2]]" + + with self.assertRaisesRegex(SyntaxError, "invalid syntax"): + exec(code, {}, {}) + + def test_named_expression_invalid_17(self): + code = "[i := 0, j := 1 for i, j in [(1, 2), (3, 4)]]" + + with self.assertRaisesRegex(SyntaxError, "invalid syntax"): + exec(code, {}, {}) + + def test_named_expression_invalid_18(self): + code = """class Foo(): + [(42, 1 + ((( j := i )))) for i in range(5)] + """ + + with self.assertRaisesRegex(TargetScopeError, + "named expression within a comprehension cannot be used in a class body"): + exec(code, {}, {}) + + +class NamedExpressionAssignmentTest(unittest.TestCase): + + def test_named_expression_assignment_01(self): + (a := 10) + + self.assertEqual(a, 10) + + def test_named_expression_assignment_02(self): + a = 20 + (a := a) + + self.assertEqual(a, 20) + + def test_named_expression_assignment_03(self): + (total := 1 + 2) + + self.assertEqual(total, 3) + + def test_named_expression_assignment_04(self): + (info := (1, 2, 3)) + + self.assertEqual(info, (1, 2, 3)) + + def test_named_expression_assignment_05(self): + (x := 1, 2) + + self.assertEqual(x, 1) + + def test_named_expression_assignment_06(self): + (z := (y := (x := 0))) + + self.assertEqual(x, 0) + self.assertEqual(y, 0) + self.assertEqual(z, 0) + + def test_named_expression_assignment_07(self): + (loc := (1, 2)) + + self.assertEqual(loc, (1, 2)) + + def test_named_expression_assignment_08(self): + if spam := "eggs": + self.assertEqual(spam, "eggs") + else: self.fail("variable was not assigned using named expression") + + def test_named_expression_assignment_09(self): + if True and (spam := True): + self.assertTrue(spam) + else: self.fail("variable was not assigned using named expression") + + def test_named_expression_assignment_10(self): + if (match := 10) is 10: + pass + else: self.fail("variable was not assigned using named expression") + + def test_named_expression_assignment_11(self): + def spam(a): + return a + input_data = [1, 2, 3] + res = [(x, y, x/y) for x in input_data if (y := spam(x)) > 0] + + self.assertEqual(res, [(1, 1, 1.0), (2, 2, 1.0), (3, 3, 1.0)]) + + def test_named_expression_assignment_12(self): + def spam(a): + return a + res = [[y := spam(x), x/y] for x in range(1, 5)] + + self.assertEqual(res, [[1, 1.0], [2, 1.0], [3, 1.0], [4, 1.0]]) + + def test_named_expression_assignment_13(self): + length = len(lines := [1, 2]) + + self.assertEqual(length, 2) + self.assertEqual(lines, [1,2]) + + def test_named_expression_assignment_14(self): + """ + Where all variables are positive integers, and a is at least as large + as the n'th root of x, this algorithm returns the floor of the n'th + root of x (and roughly doubling the number of accurate bits per + iteration):: + """ + a = 9 + n = 2 + x = 3 + + while a > (d := x // a**(n-1)): + a = ((n-1)*a + d) // n + + self.assertEqual(a, 1) + + +class NamedExpressionScopeTest(unittest.TestCase): + + def test_named_expression_scope_01(self): + code = """def spam(): + (a := 5) +print(a)""" + + with self.assertRaisesRegex(NameError, "name 'a' is not defined"): + exec(code, {}, {}) + + def test_named_expression_scope_02(self): + total = 0 + partial_sums = [total := total + v for v in range(5)] + + self.assertEqual(partial_sums, [0, 1, 3, 6, 10]) + self.assertEqual(total, 10) + + def test_named_expression_scope_03(self): + containsOne = any((lastNum := num) == 1 for num in [1, 2, 3]) + + self.assertTrue(containsOne) + self.assertEqual(lastNum, 1) + + def test_named_expression_scope_04(self): + def spam(a): + return a + res = [[y := spam(x), x/y] for x in range(1, 5)] + + self.assertEqual(y, 4) + + def test_named_expression_scope_05(self): + def spam(a): + return a + input_data = [1, 2, 3] + res = [(x, y, x/y) for x in input_data if (y := spam(x)) > 0] + + self.assertEqual(res, [(1, 1, 1.0), (2, 2, 1.0), (3, 3, 1.0)]) + self.assertEqual(y, 3) + + def test_named_expression_scope_06(self): + res = [[spam := i for i in range(3)] for j in range(2)] + + self.assertEqual(res, [[0, 1, 2], [0, 1, 2]]) + self.assertEqual(spam, 2) + + def test_named_expression_scope_07(self): + len(lines := [1, 2]) + + self.assertEqual(lines, [1, 2]) + + def test_named_expression_scope_08(self): + def spam(a): + return a + + def eggs(b): + return b * 2 + + res = [spam(a := eggs(b := h)) for h in range(2)] + + self.assertEqual(res, [0, 2]) + self.assertEqual(a, 2) + self.assertEqual(b, 1) + + def test_named_expression_scope_09(self): + def spam(a): + return a + + def eggs(b): + return b * 2 + + res = [spam(a := eggs(a := h)) for h in range(2)] + + self.assertEqual(res, [0, 2]) + self.assertEqual(a, 2) + + def test_named_expression_scope_10(self): + res = [b := [a := 1 for i in range(2)] for j in range(2)] + + self.assertEqual(res, [[1, 1], [1, 1]]) + self.assertEqual(a, 1) + self.assertEqual(b, [1, 1]) + + def test_named_expression_scope_11(self): + res = [j := i for i in range(5)] + + self.assertEqual(res, [0, 1, 2, 3, 4]) + self.assertEqual(j, 4) + + def test_named_expression_scope_12(self): + res = [i := i for i in range(5)] + + self.assertEqual(res, [0, 1, 2, 3, 4]) + self.assertEqual(i, 4) + + def test_named_expression_scope_13(self): + res = [i := 0 for i, j in [(1, 2), (3, 4)]] + + self.assertEqual(res, [0, 0]) + self.assertEqual(i, 0) + + def test_named_expression_scope_14(self): + res = [(i := 0, j := 1) for i, j in [(1, 2), (3, 4)]] + + self.assertEqual(res, [(0, 1), (0, 1)]) + self.assertEqual(i, 0) + self.assertEqual(j, 1) + + def test_named_expression_scope_15(self): + res = [(i := i, j := j) for i, j in [(1, 2), (3, 4)]] + + self.assertEqual(res, [(1, 2), (3, 4)]) + self.assertEqual(i, 3) + self.assertEqual(j, 4) + + def test_named_expression_scope_16(self): + res = [(i := j, j := i) for i, j in [(1, 2), (3, 4)]] + + self.assertEqual(res, [(2, 2), (4, 4)]) + self.assertEqual(i, 4) + self.assertEqual(j, 4) + + def test_named_expression_scope_17(self): + b = 0 + res = [b := i + b for i in range(5)] + + self.assertEqual(res, [0, 1, 3, 6, 10]) + self.assertEqual(b, 10) + + def test_named_expression_scope_18(self): + def spam(a): + return a + + res = spam(b := 2) + + self.assertEqual(res, 2) + self.assertEqual(b, 2) + + def test_named_expression_scope_19(self): + def spam(a): + return a + + res = spam((b := 2)) + + self.assertEqual(res, 2) + self.assertEqual(b, 2) + + def test_named_expression_scope_20(self): + def spam(a): + return a + + res = spam(a=(b := 2)) + + self.assertEqual(res, 2) + self.assertEqual(b, 2) + + def test_named_expression_scope_21(self): + def spam(a, b): + return a + b + + res = spam(c := 2, b=1) + + self.assertEqual(res, 3) + self.assertEqual(c, 2) + + def test_named_expression_scope_22(self): + def spam(a, b): + return a + b + + res = spam((c := 2), b=1) + + self.assertEqual(res, 3) + self.assertEqual(c, 2) + + def test_named_expression_scope_23(self): + def spam(a, b): + return a + b + + res = spam(b=(c := 2), a=1) + + self.assertEqual(res, 3) + self.assertEqual(c, 2) + + def test_named_expression_scope_24(self): + a = 10 + def spam(): + nonlocal a + (a := 20) + spam() + + self.assertEqual(a, 20) + + def test_named_expression_scope_25(self): + ns = {} + code = """a = 10 +def spam(): + global a + (a := 20) +spam()""" + + exec(code, ns, {}) + + self.assertEqual(ns["a"], 20) + + +if __name__ == "__main__": + unittest.main() diff --git a/Lib/test/test_parser.py b/Lib/test/test_parser.py index 9b58bb9..ac3899b 100644 --- a/Lib/test/test_parser.py +++ b/Lib/test/test_parser.py @@ -115,6 +115,7 @@ class RoundtripLegalSyntaxTestCase(unittest.TestCase): self.check_expr("foo * bar") self.check_expr("foo / bar") self.check_expr("foo // bar") + self.check_expr("(foo := 1)") self.check_expr("lambda: 0") self.check_expr("lambda x: 0") self.check_expr("lambda *y: 0") @@ -421,6 +422,40 @@ class RoundtripLegalSyntaxTestCase(unittest.TestCase): self.check_expr('{x**2:x[3] for x in seq if condition(x)}') self.check_expr('{x:x for x in seq1 for y in seq2 if condition(x, y)}') + def test_named_expressions(self): + self.check_suite("(a := 1)") + self.check_suite("(a := a)") + self.check_suite("if (match := pattern.search(data)) is None: pass") + self.check_suite("[y := f(x), y**2, y**3]") + self.check_suite("filtered_data = [y for x in data if (y := f(x)) is None]") + self.check_suite("(y := f(x))") + self.check_suite("y0 = (y1 := f(x))") + self.check_suite("foo(x=(y := f(x)))") + self.check_suite("def foo(answer=(p := 42)): pass") + self.check_suite("def foo(answer: (p := 42) = 5): pass") + self.check_suite("lambda: (x := 1)") + self.check_suite("(x := lambda: 1)") + self.check_suite("(x := lambda: (y := 1))") # not in PEP + self.check_suite("lambda line: (m := re.match(pattern, line)) and m.group(1)") + self.check_suite("x = (y := 0)") + self.check_suite("(z:=(y:=(x:=0)))") + self.check_suite("(info := (name, phone, *rest))") + self.check_suite("(x:=1,2)") + self.check_suite("(total := total + tax)") + self.check_suite("len(lines := f.readlines())") + self.check_suite("foo(x := 3, cat='vector')") + self.check_suite("foo(cat=(category := 'vector'))") + self.check_suite("if any(len(longline := l) >= 100 for l in lines): print(longline)") + self.check_suite( + "if env_base := os.environ.get('PYTHONUSERBASE', None): return env_base" + ) + self.check_suite( + "if self._is_special and (ans := self._check_nans(context=context)): return ans" + ) + self.check_suite("foo(b := 2, a=1)") + self.check_suite("foo(b := 2, a=1)") + self.check_suite("foo((b := 2), a=1)") + self.check_suite("foo(c=(b := 2), a=1)") # # Second, we take *invalid* trees and make sure we get ParserError @@ -694,16 +729,16 @@ class IllegalSyntaxTestCase(unittest.TestCase): def test_illegal_encoding(self): # Illegal encoding declaration tree = \ - (340, + (341, (257, (0, ''))) self.check_bad_tree(tree, "missed encoding") tree = \ - (340, + (341, (257, (0, '')), b'iso-8859-1') self.check_bad_tree(tree, "non-string encoding") tree = \ - (340, + (341, (257, (0, '')), '\udcff') with self.assertRaises(UnicodeEncodeError): @@ -776,8 +811,9 @@ class ParserStackLimitTestCase(unittest.TestCase): return "["*level+"]"*level def test_deeply_nested_list(self): - # XXX used to be 99 levels in 2.x - e = self._nested_expression(93) + # This has fluctuated between 99 levels in 2.x, down to 93 levels in + # 3.7.X and back up to 99 in 3.8.X. Related to MAXSTACK size in Parser.h + e = self._nested_expression(99) st = parser.expr(e) st.compile() diff --git a/Lib/test/test_tokenize.py b/Lib/test/test_tokenize.py index 04a1254..4c90092 100644 --- a/Lib/test/test_tokenize.py +++ b/Lib/test/test_tokenize.py @@ -1429,6 +1429,7 @@ class TestTokenize(TestCase): self.assertExactTypeEqual('**=', token.DOUBLESTAREQUAL) self.assertExactTypeEqual('//', token.DOUBLESLASH) self.assertExactTypeEqual('//=', token.DOUBLESLASHEQUAL) + self.assertExactTypeEqual(':=', token.COLONEQUAL) self.assertExactTypeEqual('...', token.ELLIPSIS) self.assertExactTypeEqual('->', token.RARROW) self.assertExactTypeEqual('@', token.AT) diff --git a/Lib/token.py b/Lib/token.py index 5af7e6b..7224eca 100644 --- a/Lib/token.py +++ b/Lib/token.py @@ -56,13 +56,14 @@ AT = 49 ATEQUAL = 50 RARROW = 51 ELLIPSIS = 52 -OP = 53 +COLONEQUAL = 53 +OP = 54 # These aren't used by the C tokenizer but are needed for tokenize.py -ERRORTOKEN = 54 -COMMENT = 55 -NL = 56 -ENCODING = 57 -N_TOKENS = 58 +ERRORTOKEN = 55 +COMMENT = 56 +NL = 57 +ENCODING = 58 +N_TOKENS = 59 # Special definitions for cooperation with parser NT_OFFSET = 256 @@ -96,6 +97,7 @@ EXACT_TOKEN_TYPES = { '//=': DOUBLESLASHEQUAL, '/=': SLASHEQUAL, ':': COLON, + ':=': COLONEQUAL, ';': SEMI, '<': LESS, '<<': LEFTSHIFT, |