this repo has no description
at trunk 1490 lines 35 kB view raw
1#!/usr/bin/env python3 2# Copyright (c) Facebook, Inc. and its affiliates. (http://www.facebook.com) 3import asyncio 4import io 5import unittest 6from types import CodeType 7 8try: 9 from _compiler_opcode import opcode 10except ImportError: 11 import opcode 12from test_support import pyro_only 13 14 15def _dis_instruction(opcode, code: CodeType, op: int, oparg: int): # noqa: C901 16 result = opcode.opname[op] 17 if op < opcode.HAVE_ARGUMENT and oparg == 0: 18 oparg_str = None 19 elif op in opcode.hasconst: 20 cnst = code.co_consts[oparg] 21 co_name = getattr(cnst, "co_name", None) 22 if co_name: 23 oparg_str = f"{oparg}:code object {co_name}" 24 else: 25 oparg_str = repr(code.co_consts[oparg]) 26 elif op in opcode.hasname: 27 oparg_str = code.co_names[oparg] 28 elif op in ( 29 opcode.LOAD_FAST_REVERSE_UNCHECKED, 30 opcode.STORE_FAST_REVERSE, 31 opcode.DELETE_FAST_REVERSE_UNCHECKED, 32 ): 33 total_locals = ( 34 len(code.co_varnames) + len(code.co_cellvars) + len(code.co_freevars) 35 ) 36 oparg_str = code.co_varnames[total_locals - oparg - 1] 37 elif op in opcode.haslocal: 38 oparg_str = code.co_varnames[oparg] 39 elif op in opcode.hascompare: 40 oparg_str = opcode.CMP_OP[oparg] 41 elif result == "FORMAT_VALUE": 42 fvc = oparg & opcode.FVC_MASK 43 if fvc == opcode.FVC_STR: 44 oparg_str = "str" 45 elif fvc == opcode.FVC_REPR: 46 oparg_str = "repr" 47 elif fvc == opcode.FVC_ASCII: 48 oparg_str = "ascii" 49 else: 50 assert fvc == opcode.FVC_NONE 51 oparg_str = None 52 if (oparg & opcode.FVS_HAVE_SPEC) != 0: 53 oparg_str = "" if oparg_str is None else (oparg_str + " ") 54 oparg_str += "have_spec" 55 else: 56 oparg_str = str(oparg) 57 if oparg_str is not None: 58 result = f"{result} {oparg_str}" 59 return result 60 61 62def _dis_code(opcode, lines, code: CodeType): 63 bytecode = code.co_code 64 bytecode_len = len(bytecode) 65 i = 0 66 while i < bytecode_len: 67 op = bytecode[i] 68 oparg = bytecode[i + 1] 69 i += opcode.CODEUNIT_SIZE 70 while op == opcode.EXTENDED_ARG: 71 op = bytecode[i] 72 oparg = oparg << 8 | bytecode[i + 1] 73 i += opcode.CODEUNIT_SIZE 74 lines.append(_dis_instruction(opcode, code, op, oparg)) 75 lines.append("") 76 for idx, cnst in enumerate(code.co_consts): 77 co_code = getattr(cnst, "co_code", None) 78 if co_code: 79 lines.append(f"# {idx}:code object {cnst.co_name}") 80 _dis_code(opcode, lines, cnst) 81 82 83def simpledis(opcode, code: CodeType): 84 """Simple disassembler, showing only opcode names + args. 85 Compare to the `dis` module: This producers simpler output making it easier 86 to match in tests. The `opcode` module is parameterized.""" 87 lines = [] 88 _dis_code(opcode, lines, code) 89 return "\n".join(lines) 90 91 92def test_compile(source, filename="<test>", mode="eval", flags=0, optimize=True): 93 return compile(source, filename, mode, flags, None, optimize) 94 95 96def compile_function(source, func_name): 97 module_code = test_compile(source, mode="exec") 98 globals = {} 99 locals = {} 100 exec(module_code, globals, locals) 101 return locals[func_name] 102 103 104def dis(code): 105 return simpledis(opcode, code) 106 107 108@pyro_only 109class StrModOptimizer(unittest.TestCase): 110 def test_constant_folds(self): 111 source = "'%s %% foo %r bar %a %s' % (1, 'baz', 3, 4)" 112 self.assertEqual( 113 dis(test_compile(source)), 114 """\ 115LOAD_CONST "1 % foo 'baz' bar 3 4" 116RETURN_VALUE 117""", 118 ) 119 120 def test_empty_args(self): 121 source = "'foo' % ()" 122 self.assertEqual( 123 dis(test_compile(source)), 124 """\ 125LOAD_CONST 'foo' 126RETURN_VALUE 127""", 128 ) 129 130 @pyro_only 131 def test_empty_args_executes(self): 132 source = "'foo' % ()" 133 self.assertEqual(eval(test_compile(source)), "foo") # noqa: P204 134 135 def test_percent_a_uses_format_value(self): 136 source = "'%a' % (x,)" 137 self.assertEqual( 138 dis(test_compile(source)), 139 """\ 140LOAD_NAME x 141FORMAT_VALUE ascii 142RETURN_VALUE 143""", 144 ) 145 146 @pyro_only 147 def test_percent_a_executes(self): 148 source = "'%a' % (x,)" 149 self.assertEqual( 150 eval(test_compile(source), {"x": "\u1234"}), "'\\u1234'" # noqa: P204 151 ) 152 153 def test_percent_percent(self): 154 source = "'T%%est' % ()" 155 self.assertEqual( 156 dis(test_compile(source)), 157 """\ 158LOAD_CONST 'T%est' 159RETURN_VALUE 160""", 161 ) 162 163 def test_percent_r_uses_format_value(self): 164 source = "'%r' % (x,)" 165 self.assertEqual( 166 dis(test_compile(source)), 167 """\ 168LOAD_NAME x 169FORMAT_VALUE repr 170RETURN_VALUE 171""", 172 ) 173 174 @pyro_only 175 def test_percent_r_executes(self): 176 source = "'%r' % (x,)" 177 self.assertEqual( 178 eval(test_compile(source), {"x": "bar"}), "'bar'" # noqa: P204 179 ) 180 181 def test_percent_s_uses_format_value(self): 182 source = "'%s' % (x,)" 183 self.assertEqual( 184 dis(test_compile(source)), 185 """\ 186LOAD_NAME x 187FORMAT_VALUE str 188RETURN_VALUE 189""", 190 ) 191 192 @pyro_only 193 def test_percent_s_executes(self): 194 source = "'%s' % (x,)" 195 self.assertEqual(eval(test_compile(source), {"x": "bar"}), "bar") # noqa: P204 196 197 def test_percent_s_with_width_uses_format_value(self): 198 source = "'%13s' % (x,)" 199 self.assertEqual( 200 dis(test_compile(source)), 201 """\ 202LOAD_NAME x 203LOAD_CONST '>13' 204FORMAT_VALUE str have_spec 205RETURN_VALUE 206""", 207 ) 208 209 @pyro_only 210 def test_percent_s_with_width_executes(self): 211 source = "'%7s' % (x,)" 212 self.assertEqual( 213 eval(test_compile(source), {"x": 4231}), " 4231" # noqa: P204 214 ) 215 216 def test_percent_d_i_u_calls_convert_and_formats_value(self): 217 expected = """\ 218LOAD_CONST '' 219LOAD_METHOD _mod_convert_number_int 220LOAD_NAME x 221CALL_METHOD 1 222FORMAT_VALUE 223RETURN_VALUE 224""" 225 source0 = "'%d' % (x,)" 226 source1 = "'%i' % (x,)" 227 source2 = "'%u' % (x,)" 228 self.assertEqual(dis(test_compile(source0)), expected) 229 self.assertEqual(dis(test_compile(source1)), expected) 230 self.assertEqual(dis(test_compile(source2)), expected) 231 232 @pyro_only 233 def test_percent_d_i_u_executes(self): 234 class C: 235 def __index__(self): 236 raise Exception("should not be called") 237 238 def __int__(self): 239 return 13 240 241 source0 = "'%d' % (x,)" 242 source1 = "'%i' % (x,)" 243 source2 = "'%u' % (x,)" 244 self.assertEqual(eval(test_compile(source0), {"x": -42}), "-42") # noqa: P204 245 self.assertEqual( 246 eval(test_compile(source1), {"x": 0x1234}), "4660" # noqa: P204 247 ) 248 self.assertEqual(eval(test_compile(source2), {"x": C()}), "13") # noqa: P204 249 250 @pyro_only 251 def test_percent_d_raises_type_error(self): 252 class C: 253 pass 254 255 source = "'%d' % (x,)" 256 code = test_compile(source) 257 with self.assertRaisesRegex(TypeError, "format requires a number, not C"): 258 eval(code, None, {"x": C()}) # noqa: P204 259 260 def test_percent_i_with_width_and_zero_uses_format_value_with_spec(self): 261 source = "'%05d' % (x,)" 262 self.assertEqual( 263 dis(test_compile(source)), 264 """\ 265LOAD_CONST '' 266LOAD_METHOD _mod_convert_number_int 267LOAD_NAME x 268CALL_METHOD 1 269LOAD_CONST '05' 270FORMAT_VALUE have_spec 271RETURN_VALUE 272""", 273 ) 274 275 def test_percent_o_calls_convert_and_formats_value(self): 276 source = "'%5o' % (x,)" 277 self.assertEqual( 278 dis(test_compile(source)), 279 """\ 280LOAD_CONST '' 281LOAD_METHOD _mod_convert_number_index 282LOAD_NAME x 283CALL_METHOD 1 284LOAD_CONST '5o' 285FORMAT_VALUE have_spec 286RETURN_VALUE 287""", 288 ) 289 290 @pyro_only 291 def test_percent_o_executes(self): 292 class C: 293 def __index__(self): 294 return -93 295 296 def __int__(self): 297 raise Exception("should not be called") 298 299 source = "'%5o' % (x,)" 300 self.assertEqual(eval(test_compile(source), {"x": C()}), " -135") # noqa: P204 301 302 def test_percent_x_calls_convert_and_formats_value(self): 303 source = "'%03x' % (x,)" 304 self.assertEqual( 305 dis(test_compile(source)), 306 """\ 307LOAD_CONST '' 308LOAD_METHOD _mod_convert_number_index 309LOAD_NAME x 310CALL_METHOD 1 311LOAD_CONST '03x' 312FORMAT_VALUE have_spec 313RETURN_VALUE 314""", 315 ) 316 317 @pyro_only 318 def test_percent_x_executes(self): 319 class C: 320 def __index__(self): 321 return -11 322 323 def __int__(self): 324 raise Exception("should not be called") 325 326 source = "'%04x' % (x,)" 327 self.assertEqual(eval(test_compile(source), {"x": C()}), "-00b") # noqa: P204 328 329 def test_percent_X_calls_convert_and_formats_value(self): 330 source = "'%4X' % (x,)" 331 self.assertEqual( 332 dis(test_compile(source)), 333 """\ 334LOAD_CONST '' 335LOAD_METHOD _mod_convert_number_index 336LOAD_NAME x 337CALL_METHOD 1 338LOAD_CONST '4X' 339FORMAT_VALUE have_spec 340RETURN_VALUE 341""", 342 ) 343 344 @pyro_only 345 def test_percent_X_executes(self): 346 class C: 347 def __index__(self): 348 return 51966 349 350 def __int__(self): 351 raise Exception("should not be called") 352 353 source = "'%04X' % (x,)" 354 self.assertEqual(eval(test_compile(source), {"x": C()}), "CAFE") # noqa: P204 355 356 def test_mixed_format_uses_format_value_and_build_string(self): 357 source = "'%s %% foo %r bar %a %s' % (a, b, c, d)" 358 self.assertEqual( 359 dis(test_compile(source)), 360 """\ 361LOAD_NAME a 362FORMAT_VALUE str 363LOAD_CONST ' ' 364LOAD_CONST '% foo ' 365LOAD_NAME b 366FORMAT_VALUE repr 367LOAD_CONST ' bar ' 368LOAD_NAME c 369FORMAT_VALUE ascii 370LOAD_CONST ' ' 371LOAD_NAME d 372FORMAT_VALUE str 373BUILD_STRING 8 374RETURN_VALUE 375""", 376 ) 377 378 @pyro_only 379 def test_mixed_format_executes(self): 380 source = "'%s %% foo %r bar %a %s' % (a, b, c, d)" 381 locals = {"a": 1, "b": 2, "c": 3, "d": 4} 382 self.assertEqual( 383 eval(test_compile(source), locals), "1 % foo 2 bar 3 4" # noqa: P204 384 ) 385 386 def test_no_tuple_arg_calls_check_single_arg(self): 387 source = "'%s' % x" 388 self.assertEqual( 389 dis(test_compile(source)), 390 """\ 391LOAD_CONST '' 392LOAD_METHOD _mod_check_single_arg 393LOAD_NAME x 394CALL_METHOD 1 395LOAD_CONST 0 396BINARY_SUBSCR 397FORMAT_VALUE str 398RETURN_VALUE 399""", 400 ) 401 402 @pyro_only 403 def test_no_tuple_arg_executes(self): 404 source = "'%s' % x" 405 self.assertEqual(eval(test_compile(source), {"x": 5.5}), "5.5") # noqa: P204 406 407 @pyro_only 408 def test_no_tuple_raises_type_error_not_enough_arguments(self): 409 source = "'%s' % x" 410 with self.assertRaisesRegex( 411 TypeError, "not enough arguments for format string" 412 ): 413 eval(test_compile(source), {"x": ()}) # noqa: P204 414 415 @pyro_only 416 def test_no_tuple_raises_type_error_not_all_converted(self): 417 source = "'%s' % x" 418 with self.assertRaisesRegex( 419 TypeError, "not all arguments converted during string formatting" 420 ): 421 eval(test_compile(source), {"x": (1, 2)}) # noqa: P204 422 423 def test_trailing_percent_it_not_optimized(self): 424 source = "'foo%' % (x,)" 425 self.assertIn("BINARY_MODULO", dis(test_compile(source))) 426 427 def test_multiple_specs_without_tuple_is_not_optimized(self): 428 source = "'%s%s' % x" 429 self.assertIn("BINARY_MODULO", dis(test_compile(source))) 430 431 def test_spec_with_mapping_key_is_not_optimized(self): 432 source = "'%(%s)' % x" 433 self.assertIn("BINARY_MODULO", dis(test_compile(source))) 434 435 def test_with_too_small_arg_tuple_is_not_optimized(self): 436 source = "'%s%s%s' % (x,y)" 437 self.assertIn("BINARY_MODULO", dis(test_compile(source))) 438 439 def test_with_too_large_arg_tuple_is_not_optimized(self): 440 source = "'%s%s%s' % (w,x,y,z)" 441 self.assertIn("BINARY_MODULO", dis(test_compile(source))) 442 443 def test_with_unknown_specifier_is_not_optimized(self): 444 source = "'%Z' % (x,)" 445 self.assertIn("BINARY_MODULO", dis(test_compile(source))) 446 447 448@pyro_only 449class CustomOpcodeTests(unittest.TestCase): 450 def test_is_generates_COMPARE_IS(self): 451 source = "a is b" 452 self.assertEqual( 453 dis(test_compile(source)), 454 """\ 455LOAD_NAME a 456LOAD_NAME b 457COMPARE_IS 0 458RETURN_VALUE 459""", 460 ) 461 462 def test_is_not_generates_COMPARE_IS_NOT(self): 463 source = "a is not b" 464 self.assertEqual( 465 dis(test_compile(source)), 466 """\ 467LOAD_NAME a 468LOAD_NAME b 469COMPARE_IS_NOT 0 470RETURN_VALUE 471""", 472 ) 473 474 475@pyro_only 476class InlineComprehensionTests(unittest.TestCase): 477 def test_list_comprehension_code_is_inline(self): 478 source = "[x for x in y]" 479 self.assertEqual( 480 dis(test_compile(source)), 481 """\ 482BUILD_LIST 0 483LOAD_NAME y 484GET_ITER 485FOR_ITER 8 486STORE_NAME _gen$x 487LOAD_NAME _gen$x 488LIST_APPEND 2 489JUMP_ABSOLUTE 6 490RETURN_VALUE 491""", 492 ) 493 494 @pyro_only 495 def test_list_comprehension_code_executes(self): 496 source = "[x for x in y]" 497 self.assertEqual( 498 eval(test_compile(source), {"y": range(4)}), [0, 1, 2, 3] # noqa: P204 499 ) 500 501 def test_set_comprehension_code_is_inline(self): 502 source = "{x for x in y}" 503 self.assertEqual( 504 dis(test_compile(source)), 505 """\ 506BUILD_SET 0 507LOAD_NAME y 508GET_ITER 509FOR_ITER 8 510STORE_NAME _gen$x 511LOAD_NAME _gen$x 512SET_ADD 2 513JUMP_ABSOLUTE 6 514RETURN_VALUE 515""", 516 ) 517 518 @pyro_only 519 def test_set_comprehension_code_executes(self): 520 source = "{x for x in y}" 521 self.assertEqual( 522 eval(test_compile(source), {"y": range(4)}), {0, 1, 2, 3} # noqa: P204 523 ) 524 525 def test_dict_comprehension_code_is_inline(self): 526 source = "{x:-x for x in y}" 527 self.assertEqual( 528 dis(test_compile(source)), 529 """\ 530BUILD_MAP 0 531LOAD_NAME y 532GET_ITER 533FOR_ITER 12 534STORE_NAME _gen$x 535LOAD_NAME _gen$x 536LOAD_NAME _gen$x 537UNARY_NEGATIVE 538MAP_ADD 2 539JUMP_ABSOLUTE 6 540RETURN_VALUE 541""", 542 ) 543 544 @pyro_only 545 def test_dict_comprehension_code_executes(self): 546 source = "{x:-x for x in y}" 547 self.assertEqual( 548 eval(test_compile(source), {"y": range(4)}), # noqa: P204 549 {0: 0, 1: -1, 2: -2, 3: -3}, 550 ) 551 552 def test_nested_comprehension_in_iterator_code_is_inline(self): 553 source = "[-x for x in [x for x in x if x > 3]]" 554 self.assertEqual( 555 (dis(test_compile(source))), 556 """\ 557BUILD_LIST 0 558BUILD_LIST 0 559LOAD_NAME x 560GET_ITER 561FOR_ITER 16 562STORE_NAME _gen$x 563LOAD_NAME _gen$x 564LOAD_CONST 3 565COMPARE_OP > 566POP_JUMP_IF_FALSE 8 567LOAD_NAME _gen$x 568LIST_APPEND 2 569JUMP_ABSOLUTE 8 570GET_ITER 571FOR_ITER 10 572STORE_NAME _gen1$x 573LOAD_NAME _gen1$x 574UNARY_NEGATIVE 575LIST_APPEND 2 576JUMP_ABSOLUTE 28 577RETURN_VALUE 578""", 579 ) 580 581 @pyro_only 582 def test_nested_comprehension_in_iterator_executes(self): 583 source = "[-x for x in [x for x in x if x > 3]]" 584 self.assertEqual( 585 eval(test_compile(source), {"x": range(8)}), [-4, -5, -6, -7] # noqa: P204 586 ) 587 588 def test_nested_comprehension_in_element_code_is_inline(self): 589 source = "[[x for x in range(x) if x & 1 == 1] for x in x]" 590 self.assertEqual( 591 (dis(test_compile(source))), 592 """\ 593BUILD_LIST 0 594LOAD_NAME x 595GET_ITER 596FOR_ITER 38 597STORE_NAME _gen$x 598BUILD_LIST 0 599LOAD_NAME range 600LOAD_NAME _gen$x 601CALL_FUNCTION 1 602GET_ITER 603FOR_ITER 20 604STORE_NAME _gen1$_gen$x 605LOAD_NAME _gen1$_gen$x 606LOAD_CONST 1 607BINARY_AND 608LOAD_CONST 1 609COMPARE_OP == 610POP_JUMP_IF_FALSE 20 611LOAD_NAME _gen1$_gen$x 612LIST_APPEND 2 613JUMP_ABSOLUTE 20 614LIST_APPEND 2 615JUMP_ABSOLUTE 6 616RETURN_VALUE 617""", 618 ) 619 620 @pyro_only 621 def test_nested_comprehension_in_element_executes(self): 622 source = "[[x for x in range(x) if x & 1 == 1] for x in x]" 623 self.assertEqual( 624 eval(test_compile(source), {"x": range(5)}), # noqa: P204 625 [[], [], [1], [1], [1, 3]], 626 ) 627 628 def test_nested_lambda_in_comprehension_has_arg_renamed(self): 629 source = "[lambda x: x for x in r]" 630 self.assertEqual( 631 (dis(test_compile(source))), 632 """\ 633BUILD_LIST 0 634LOAD_NAME r 635GET_ITER 636FOR_ITER 12 637STORE_NAME _gen$x 638LOAD_CONST 0:code object <lambda> 639LOAD_CONST '<lambda>' 640MAKE_FUNCTION 0 641LIST_APPEND 2 642JUMP_ABSOLUTE 6 643RETURN_VALUE 644 645# 0:code object <lambda> 646LOAD_FAST_REVERSE_UNCHECKED _gen$x 647RETURN_VALUE 648""", 649 ) 650 651 @pyro_only 652 def test_nested_lambda_in_comprehension_executes(self): 653 source = "[lambda x: x for x in r]" 654 lambdas = eval(test_compile(source), {"r": range(3)}) # noqa: P204 655 self.assertEqual(lambdas[0](22), 22) 656 self.assertEqual(lambdas[1]("foo"), "foo") 657 self.assertEqual(lambdas[2](int), int) 658 659 def test_multiple_comprehensions_use_unique_prefixes(self): 660 source = "[lambda: x for x in r], [x for x in (42,)]" 661 self.assertEqual( 662 (dis(test_compile(source))), 663 """\ 664BUILD_LIST 0 665LOAD_NAME r 666GET_ITER 667FOR_ITER 12 668STORE_NAME _gen$x 669LOAD_CONST 0:code object <lambda> 670LOAD_CONST '<lambda>' 671MAKE_FUNCTION 0 672LIST_APPEND 2 673JUMP_ABSOLUTE 6 674BUILD_LIST 0 675LOAD_CONST (42,) 676GET_ITER 677FOR_ITER 8 678STORE_NAME _gen1$x 679LOAD_NAME _gen1$x 680LIST_APPEND 2 681JUMP_ABSOLUTE 26 682BUILD_TUPLE 2 683RETURN_VALUE 684 685# 0:code object <lambda> 686LOAD_GLOBAL _gen$x 687RETURN_VALUE 688""", 689 ) 690 691 @pyro_only 692 def test_multiple_comprehensions_executes(self): 693 source = "[lambda: x for x in r], [x for x in (42,)]" 694 lambdas, dummy = eval(test_compile(source), {"r": range(13, 15)}) # noqa: P204 695 self.assertEqual(lambdas[0](), 14) 696 self.assertEqual(lambdas[1](), 14) 697 698 def test_renamer_with_locals_in_list_comprehension_does_not_rename(self): 699 iterable = [1] 700 result = [locals().keys() for item in iterable][0] 701 self.assertEqual(sorted(result), [".0", "item"]) 702 703 def test_renamer_with_locals_in_dict_comprehension_does_not_rename(self): 704 iterable = [1] 705 result = {"key": locals().keys() for item in iterable}["key"] 706 self.assertEqual(sorted(result), [".0", "item"]) 707 708 def test_renamer_with_locals_in_set_comprehension_does_not_rename(self): 709 iterable = [1] 710 result = {locals().keys() for item in iterable}.pop() 711 self.assertEqual(sorted(result), [".0", "item"]) 712 713 def test_renamer_with_locals_in_gen_comprehension_does_not_rename(self): 714 iterable = [1] 715 result = tuple(locals().keys() for item in iterable)[0] 716 self.assertEqual(sorted(result), [".0", "item"]) 717 718 719@pyro_only 720class DefiniteAssignmentAnalysisTests(unittest.TestCase): 721 def test_load_parameter_is_unchecked(self): 722 source = """ 723def foo(x): 724 return x 725""" 726 func = compile_function(source, "foo") 727 self.assertEqual( 728 dis(func.__code__), 729 """\ 730LOAD_FAST_REVERSE_UNCHECKED x 731RETURN_VALUE 732""", 733 ) 734 self.assertEqual(func(123), 123) 735 736 def test_load_kwonly_parameter_is_unchecked(self): 737 source = """ 738def foo(*, x): 739 return x 740""" 741 func = compile_function(source, "foo") 742 self.assertEqual( 743 dis(func.__code__), 744 """\ 745LOAD_FAST_REVERSE_UNCHECKED x 746RETURN_VALUE 747""", 748 ) 749 self.assertEqual(func(x=123), 123) 750 751 def test_load_varargs_parameter_is_unchecked(self): 752 source = """ 753def foo(*x): 754 return x 755""" 756 func = compile_function(source, "foo") 757 self.assertEqual( 758 dis(func.__code__), 759 """\ 760LOAD_FAST_REVERSE_UNCHECKED x 761RETURN_VALUE 762""", 763 ) 764 self.assertEqual(func(123), (123,)) 765 766 def test_load_varkeyargs_parameter_is_unchecked(self): 767 source = """ 768def foo(**x): 769 return x 770""" 771 func = compile_function(source, "foo") 772 self.assertEqual( 773 dis(func.__code__), 774 """\ 775LOAD_FAST_REVERSE_UNCHECKED x 776RETURN_VALUE 777""", 778 ) 779 self.assertEqual(func(a=123), {"a": 123}) 780 781 def test_load_parameter_with_del_is_checked(self): 782 source = """ 783def foo(x): 784 del x 785 return x 786""" 787 func = compile_function(source, "foo") 788 self.assertEqual( 789 dis(func.__code__), 790 """\ 791DELETE_FAST x 792LOAD_FAST x 793RETURN_VALUE 794""", 795 ) 796 with self.assertRaisesRegex( 797 UnboundLocalError, "local variable 'x' referenced before assignment" 798 ): 799 func(123) 800 801 def test_load_parameter_with_del_after_is_unchecked(self): 802 source = """ 803def foo(x): 804 y = x 805 del x 806 return y 807""" 808 func = compile_function(source, "foo") 809 self.assertEqual( 810 dis(func.__code__), 811 """\ 812LOAD_FAST_REVERSE_UNCHECKED x 813STORE_FAST_REVERSE y 814DELETE_FAST x 815LOAD_FAST_REVERSE_UNCHECKED y 816RETURN_VALUE 817""", 818 ) 819 self.assertEqual(func(123), 123) 820 821 def test_variable_defined_in_local_is_unchecked(self): 822 source = """ 823def foo(): 824 x = 3 825 return x 826""" 827 func = compile_function(source, "foo") 828 self.assertEqual( 829 dis(func.__code__), 830 """\ 831LOAD_CONST 3 832STORE_FAST_REVERSE x 833LOAD_FAST_REVERSE_UNCHECKED x 834RETURN_VALUE 835""", 836 ) 837 self.assertEqual(func(), 3) 838 839 def test_variable_defined_in_one_branch_is_checked(self): 840 source = """ 841def foo(cond): 842 if cond: 843 x = 3 844 return x 845""" 846 func = compile_function(source, "foo") 847 self.assertEqual( 848 dis(func.__code__), 849 """\ 850DELETE_FAST_REVERSE_UNCHECKED x 851LOAD_FAST_REVERSE_UNCHECKED cond 852POP_JUMP_IF_FALSE 10 853LOAD_CONST 3 854STORE_FAST_REVERSE x 855LOAD_FAST x 856RETURN_VALUE 857""", 858 ) 859 self.assertEqual(func(True), 3) 860 with self.assertRaisesRegex( 861 UnboundLocalError, "local variable 'x' referenced before assignment" 862 ): 863 func(False) 864 865 def test_variable_defined_in_one_branch_if_else_is_checked(self): 866 source = """ 867def foo(cond): 868 if cond: 869 x = 3 870 else: 871 pass 872 return x 873""" 874 func = compile_function(source, "foo") 875 self.assertEqual( 876 dis(func.__code__), 877 """\ 878DELETE_FAST_REVERSE_UNCHECKED x 879LOAD_FAST_REVERSE_UNCHECKED cond 880POP_JUMP_IF_FALSE 12 881LOAD_CONST 3 882STORE_FAST_REVERSE x 883JUMP_FORWARD 0 884LOAD_FAST x 885RETURN_VALUE 886""", 887 ) 888 self.assertEqual(func(True), 3) 889 with self.assertRaisesRegex( 890 UnboundLocalError, "local variable 'x' referenced before assignment" 891 ): 892 func(False) 893 894 def test_variable_defined_in_both_branches_if_else_is_unchecked(self): 895 source = """ 896def foo(cond): 897 if cond: 898 x = 3 899 else: 900 x = 4 901 return x 902""" 903 func = compile_function(source, "foo") 904 self.assertEqual( 905 dis(func.__code__), 906 """\ 907LOAD_FAST_REVERSE_UNCHECKED cond 908POP_JUMP_IF_FALSE 10 909LOAD_CONST 3 910STORE_FAST_REVERSE x 911JUMP_FORWARD 4 912LOAD_CONST 4 913STORE_FAST_REVERSE x 914LOAD_FAST_REVERSE_UNCHECKED x 915RETURN_VALUE 916""", 917 ) 918 self.assertEqual(func(True), 3) 919 self.assertEqual(func(False), 4) 920 921 def test_loop_is_unchecked(self): 922 source = """ 923def foo(cond): 924 while True: 925 print(cond) 926""" 927 self.assertEqual( 928 dis(compile_function(source, "foo").__code__), 929 """\ 930LOAD_GLOBAL print 931LOAD_FAST_REVERSE_UNCHECKED cond 932CALL_FUNCTION 1 933POP_TOP 934JUMP_ABSOLUTE 0 935LOAD_CONST None 936RETURN_VALUE 937""", 938 ) 939 940 def test_loop_with_del_is_checked(self): 941 source = """ 942def foo(cond): 943 while True: 944 1 + cond # use it 945 del cond 946""" 947 func = compile_function(source, "foo") 948 self.assertEqual( 949 dis(func.__code__), 950 """\ 951LOAD_CONST 1 952LOAD_FAST cond 953BINARY_ADD 954POP_TOP 955DELETE_FAST cond 956JUMP_ABSOLUTE 0 957LOAD_CONST None 958RETURN_VALUE 959""", 960 ) 961 with self.assertRaisesRegex( 962 UnboundLocalError, "local variable 'cond' referenced before assignment" 963 ): 964 func(True) 965 966 def test_loop_variable_in_for_loop_is_unchecked(self): 967 source = """ 968def foo(n): 969 result = [] 970 for i in range(n): 971 result.append(i) 972 return result 973""" 974 func = compile_function(source, "foo") 975 self.assertEqual( 976 dis(func.__code__), 977 """\ 978BUILD_LIST 0 979STORE_FAST_REVERSE result 980LOAD_GLOBAL range 981LOAD_FAST_REVERSE_UNCHECKED n 982CALL_FUNCTION 1 983GET_ITER 984FOR_ITER 14 985STORE_FAST_REVERSE i 986LOAD_FAST_REVERSE_UNCHECKED result 987LOAD_METHOD append 988LOAD_FAST_REVERSE_UNCHECKED i 989CALL_METHOD 1 990POP_TOP 991JUMP_ABSOLUTE 12 992LOAD_FAST_REVERSE_UNCHECKED result 993RETURN_VALUE 994""", 995 ) 996 self.assertEqual(func(3), [0, 1, 2]) 997 998 def test_loop_variable_after_for_loop_is_checked(self): 999 source = """ 1000def foo(n): 1001 for i in range(n): 1002 pass 1003 return i 1004""" 1005 func = compile_function(source, "foo") 1006 self.assertEqual( 1007 dis(func.__code__), 1008 """\ 1009DELETE_FAST_REVERSE_UNCHECKED i 1010LOAD_GLOBAL range 1011LOAD_FAST_REVERSE_UNCHECKED n 1012CALL_FUNCTION 1 1013GET_ITER 1014FOR_ITER 4 1015STORE_FAST_REVERSE i 1016JUMP_ABSOLUTE 10 1017LOAD_FAST i 1018RETURN_VALUE 1019""", 1020 ) 1021 self.assertEqual(func(3), 2) 1022 1023 def test_loop_variable_in_async_for_loop_is_unchecked(self): 1024 source = """ 1025async def foo(n): 1026 result = [] 1027 async for i in range(n): 1028 result.append(i) 1029 return result 1030""" 1031 func = compile_function(source, "foo") 1032 self.assertEqual( 1033 dis(func.__code__), 1034 """\ 1035BUILD_LIST 0 1036STORE_FAST_REVERSE result 1037LOAD_GLOBAL range 1038LOAD_FAST_REVERSE_UNCHECKED n 1039CALL_FUNCTION 1 1040GET_AITER 1041SETUP_FINALLY 22 1042GET_ANEXT 1043LOAD_CONST None 1044YIELD_FROM 1045POP_BLOCK 1046STORE_FAST_REVERSE i 1047LOAD_FAST_REVERSE_UNCHECKED result 1048LOAD_METHOD append 1049LOAD_FAST_REVERSE_UNCHECKED i 1050CALL_METHOD 1 1051POP_TOP 1052JUMP_ABSOLUTE 12 1053END_ASYNC_FOR 1054LOAD_FAST_REVERSE_UNCHECKED result 1055RETURN_VALUE 1056""", 1057 ) 1058 # TODO(emacs): Figure out how to get the NameError for CM to go away. 1059 return 1060 self.assertEqual(asyncio.run(func(3)), [0, 1, 2]) 1061 1062 def test_loop_variable_after_async_for_loop_is_checked(self): 1063 source = """ 1064import asyncio 1065 1066async def arange(n): 1067 for i in range(n): 1068 yield i 1069 asyncio.sleep(0) 1070 1071async def foo(n): 1072 async for i in arange(n): 1073 pass 1074 return i 1075""" 1076 func = compile_function(source, "foo") 1077 self.assertEqual( 1078 dis(func.__code__), 1079 """\ 1080DELETE_FAST_REVERSE_UNCHECKED i 1081LOAD_GLOBAL arange 1082LOAD_FAST_REVERSE_UNCHECKED n 1083CALL_FUNCTION 1 1084GET_AITER 1085SETUP_FINALLY 12 1086GET_ANEXT 1087LOAD_CONST None 1088YIELD_FROM 1089POP_BLOCK 1090STORE_FAST_REVERSE i 1091JUMP_ABSOLUTE 10 1092END_ASYNC_FOR 1093LOAD_FAST i 1094RETURN_VALUE 1095""", 1096 ) 1097 # TODO(emacs): Figure out how to get the NameError for CM to go away. 1098 return 1099 self.assertEqual(asyncio.run(func(3)), 2) 1100 1101 def test_try_with_use_in_try_is_unchecked(self): 1102 source = """ 1103def foo(): 1104 try: 1105 x = 123 1106 return x 1107 except: 1108 pass 1109""" 1110 func = compile_function(source, "foo") 1111 self.assertEqual( 1112 dis(func.__code__), 1113 """\ 1114SETUP_FINALLY 10 1115LOAD_CONST 123 1116STORE_FAST_REVERSE x 1117LOAD_FAST_REVERSE_UNCHECKED x 1118POP_BLOCK 1119RETURN_VALUE 1120POP_TOP 1121POP_TOP 1122POP_TOP 1123POP_EXCEPT 1124JUMP_FORWARD 2 1125END_FINALLY 1126LOAD_CONST None 1127RETURN_VALUE 1128""", 1129 ) 1130 self.assertEqual(func(), 123) 1131 1132 @unittest.skip( 1133 "TODO(emacs): This should be unchecked but we need to tail-duplicate finally blocks" 1134 ) 1135 def test_try_with_use_in_else_is_unchecked(self): 1136 source = """ 1137def foo(): 1138 try: 1139 x = 123 1140 except: 1141 pass 1142 else: 1143 return x 1144""" 1145 func = compile_function(source, "foo") 1146 self.assertEqual( 1147 dis(func.__code__), 1148 """\ 1149SETUP_FINALLY 8 1150LOAD_CONST 123 1151STORE_FAST_REVERSE x 1152POP_BLOCK 1153JUMP_FORWARD 12 1154POP_TOP 1155POP_TOP 1156POP_TOP 1157POP_EXCEPT 1158JUMP_FORWARD 6 1159END_FINALLY 1160LOAD_FAST_REVERSE_UNCHECKED x 1161RETURN_VALUE 1162LOAD_CONST None 1163RETURN_VALUE 1164""", 1165 ) 1166 self.assertEqual(func(), 123) 1167 1168 def test_try_with_use_after_except_is_checked(self): 1169 source = """ 1170def foo(): 1171 try: 1172 x = 123 1173 except: 1174 pass 1175 return x 1176""" 1177 func = compile_function(source, "foo") 1178 self.assertEqual( 1179 dis(func.__code__), 1180 """\ 1181DELETE_FAST_REVERSE_UNCHECKED x 1182SETUP_FINALLY 8 1183LOAD_CONST 123 1184STORE_FAST_REVERSE x 1185POP_BLOCK 1186JUMP_FORWARD 12 1187POP_TOP 1188POP_TOP 1189POP_TOP 1190POP_EXCEPT 1191JUMP_FORWARD 2 1192END_FINALLY 1193LOAD_FAST x 1194RETURN_VALUE 1195""", 1196 ) 1197 self.assertEqual(func(), 123) 1198 1199 def test_try_with_use_in_except_is_checked(self): 1200 source = """ 1201def foo(): 1202 try: 1203 x = 123 1204 except: 1205 return x 1206""" 1207 func = compile_function(source, "foo") 1208 self.assertEqual( 1209 dis(func.__code__), 1210 """\ 1211DELETE_FAST_REVERSE_UNCHECKED x 1212SETUP_FINALLY 8 1213LOAD_CONST 123 1214STORE_FAST_REVERSE x 1215POP_BLOCK 1216JUMP_FORWARD 16 1217POP_TOP 1218POP_TOP 1219POP_TOP 1220LOAD_FAST x 1221ROT_FOUR 1222POP_EXCEPT 1223RETURN_VALUE 1224END_FINALLY 1225LOAD_CONST None 1226RETURN_VALUE 1227""", 1228 ) 1229 self.assertEqual(func(), None) 1230 1231 def test_try_with_use_in_finally_is_checked(self): 1232 source = """ 1233def foo(): 1234 try: 1235 x = 123 1236 finally: 1237 return x 1238""" 1239 func = compile_function(source, "foo") 1240 self.assertEqual( 1241 dis(func.__code__), 1242 """\ 1243DELETE_FAST_REVERSE_UNCHECKED x 1244LOAD_CONST None 1245SETUP_FINALLY 8 1246LOAD_CONST 123 1247STORE_FAST_REVERSE x 1248POP_BLOCK 1249BEGIN_FINALLY 1250LOAD_FAST x 1251POP_FINALLY 1 1252ROT_TWO 1253POP_TOP 1254RETURN_VALUE 1255END_FINALLY 1256POP_TOP 1257""", 1258 ) 1259 self.assertEqual(func(), 123) 1260 1261 def test_try_with_use_after_finally_is_unchecked(self): 1262 source = """ 1263def foo(): 1264 try: 1265 x = 123 1266 finally: 1267 x = 456 1268 return x 1269""" 1270 func = compile_function(source, "foo") 1271 self.assertEqual( 1272 dis(func.__code__), 1273 """\ 1274SETUP_FINALLY 8 1275LOAD_CONST 123 1276STORE_FAST_REVERSE x 1277POP_BLOCK 1278BEGIN_FINALLY 1279LOAD_CONST 456 1280STORE_FAST_REVERSE x 1281END_FINALLY 1282LOAD_FAST_REVERSE_UNCHECKED x 1283RETURN_VALUE 1284""", 1285 ) 1286 self.assertEqual(func(), 456) 1287 1288 def test_try_with_use_after_finally_is_unchecked_2(self): 1289 source = """ 1290def foo(): 1291 try: 1292 pass 1293 finally: 1294 x = 456 1295 return x 1296""" 1297 func = compile_function(source, "foo") 1298 self.assertEqual( 1299 dis(func.__code__), 1300 """\ 1301SETUP_FINALLY 4 1302POP_BLOCK 1303BEGIN_FINALLY 1304LOAD_CONST 456 1305STORE_FAST_REVERSE x 1306END_FINALLY 1307LOAD_FAST_REVERSE_UNCHECKED x 1308RETURN_VALUE 1309""", 1310 ) 1311 self.assertEqual(func(), 456) 1312 1313 def test_use_in_with_is_unchecked(self): 1314 source = """ 1315class CM(object): 1316 def __init__(self): 1317 self.state = "init" 1318 def __enter__(self): 1319 self.state = "enter" 1320 def __exit__(self, type, value, traceback): 1321 self.state = "exit" 1322 1323def foo(): 1324 with CM() as cm: 1325 return cm 1326""" 1327 func = compile_function(source, "foo") 1328 self.assertEqual( 1329 dis(func.__code__), 1330 """\ 1331LOAD_GLOBAL CM 1332CALL_FUNCTION 0 1333SETUP_WITH 18 1334STORE_FAST_REVERSE cm 1335LOAD_FAST_REVERSE_UNCHECKED cm 1336POP_BLOCK 1337ROT_TWO 1338BEGIN_FINALLY 1339WITH_CLEANUP_START 1340WITH_CLEANUP_FINISH 1341POP_FINALLY 0 1342RETURN_VALUE 1343WITH_CLEANUP_START 1344WITH_CLEANUP_FINISH 1345END_FINALLY 1346LOAD_CONST None 1347RETURN_VALUE 1348""", 1349 ) 1350 # TODO(emacs): Figure out how to get the NameError for CM to go away. 1351 return 1352 self.assertEqual(func(), 456) 1353 1354 def test_use_after_with_is_checked(self): 1355 source = """ 1356class CM(object): 1357 def __init__(self): 1358 self.state = "init" 1359 def __enter__(self): 1360 self.state = "enter" 1361 def __exit__(self, type, value, traceback): 1362 self.state = "exit" 1363 1364def foo(): 1365 with CM() as cm: 1366 pass 1367 return cm 1368""" 1369 func = compile_function(source, "foo") 1370 self.assertEqual( 1371 dis(func.__code__), 1372 """\ 1373DELETE_FAST_REVERSE_UNCHECKED cm 1374LOAD_GLOBAL CM 1375CALL_FUNCTION 0 1376SETUP_WITH 6 1377STORE_FAST_REVERSE cm 1378POP_BLOCK 1379BEGIN_FINALLY 1380WITH_CLEANUP_START 1381WITH_CLEANUP_FINISH 1382END_FINALLY 1383LOAD_FAST cm 1384RETURN_VALUE 1385""", 1386 ) 1387 # TODO(emacs): Figure out how to get the NameError for CM to go away. 1388 return 1389 self.assertEqual(func(), 456) 1390 1391 def test_use_in_async_with_is_unchecked(self): 1392 source = """ 1393class CM(object): 1394 def __init__(self): 1395 self.state = "init" 1396 def __aenter__(self): 1397 self.state = "enter" 1398 def __aexit__(self, type, value, traceback): 1399 self.state = "exit" 1400 1401async def foo(): 1402 async with CM() as cm: 1403 return cm 1404""" 1405 func = compile_function(source, "foo") 1406 self.assertEqual( 1407 dis(func.__code__), 1408 """\ 1409LOAD_GLOBAL CM 1410CALL_FUNCTION 0 1411BEFORE_ASYNC_WITH 1412GET_AWAITABLE 1413LOAD_CONST None 1414YIELD_FROM 1415SETUP_ASYNC_WITH 24 1416STORE_FAST_REVERSE cm 1417LOAD_FAST_REVERSE_UNCHECKED cm 1418POP_BLOCK 1419ROT_TWO 1420BEGIN_FINALLY 1421WITH_CLEANUP_START 1422GET_AWAITABLE 1423LOAD_CONST None 1424YIELD_FROM 1425WITH_CLEANUP_FINISH 1426POP_FINALLY 0 1427RETURN_VALUE 1428WITH_CLEANUP_START 1429GET_AWAITABLE 1430LOAD_CONST None 1431YIELD_FROM 1432WITH_CLEANUP_FINISH 1433END_FINALLY 1434LOAD_CONST None 1435RETURN_VALUE 1436""", 1437 ) 1438 # TODO(emacs): Figure out how to get the NameError for CM to go away. 1439 return 1440 self.assertEqual(func(), 456) 1441 1442 def test_use_after_async_with_is_checked(self): 1443 source = """ 1444class CM(object): 1445 def __init__(self): 1446 self.state = "init" 1447 def __aenter__(self): 1448 self.state = "enter" 1449 def __aexit__(self, type, value, traceback): 1450 self.state = "exit" 1451 1452async def foo(): 1453 async with CM() as cm: 1454 pass 1455 return cm 1456""" 1457 func = compile_function(source, "foo") 1458 self.assertEqual( 1459 dis(func.__code__), 1460 """\ 1461DELETE_FAST_REVERSE_UNCHECKED cm 1462LOAD_GLOBAL CM 1463CALL_FUNCTION 0 1464BEFORE_ASYNC_WITH 1465GET_AWAITABLE 1466LOAD_CONST None 1467YIELD_FROM 1468SETUP_ASYNC_WITH 6 1469STORE_FAST_REVERSE cm 1470POP_BLOCK 1471BEGIN_FINALLY 1472WITH_CLEANUP_START 1473GET_AWAITABLE 1474LOAD_CONST None 1475YIELD_FROM 1476WITH_CLEANUP_FINISH 1477END_FINALLY 1478LOAD_FAST cm 1479RETURN_VALUE 1480""", 1481 ) 1482 # TODO(emacs): Figure out how to get the NameError for CM to go away. 1483 return 1484 self.assertEqual(func(), 456) 1485 1486 # TODO(emacs): Test with (multiple context managers) 1487 1488 1489if __name__ == "__main__": 1490 unittest.main()