python (3.12.0)

(root)/
lib/
python3.12/
test/
test_peepholer.py
       1  import dis
       2  from itertools import combinations, product
       3  import sys
       4  import textwrap
       5  import unittest
       6  
       7  from test import support
       8  from test.support.bytecode_helper import BytecodeTestCase, CfgOptimizationTestCase
       9  
      10  
      11  def compile_pattern_with_fast_locals(pattern):
      12      source = textwrap.dedent(
      13          f"""
      14          def f(x):
      15              match x:
      16                  case {pattern}:
      17                      pass
      18          """
      19      )
      20      namespace = {}
      21      exec(source, namespace)
      22      return namespace["f"].__code__
      23  
      24  
      25  def count_instr_recursively(f, opname):
      26      count = 0
      27      for instr in dis.get_instructions(f):
      28          if instr.opname == opname:
      29              count += 1
      30      if hasattr(f, '__code__'):
      31          f = f.__code__
      32      for c in f.co_consts:
      33          if hasattr(c, 'co_code'):
      34              count += count_instr_recursively(c, opname)
      35      return count
      36  
      37  
      38  class ESC[4;38;5;81mTestTranforms(ESC[4;38;5;149mBytecodeTestCase):
      39  
      40      def check_jump_targets(self, code):
      41          instructions = list(dis.get_instructions(code))
      42          targets = {instr.offset: instr for instr in instructions}
      43          for instr in instructions:
      44              if 'JUMP_' not in instr.opname:
      45                  continue
      46              tgt = targets[instr.argval]
      47              # jump to unconditional jump
      48              if tgt.opname in ('JUMP_BACKWARD', 'JUMP_FORWARD'):
      49                  self.fail(f'{instr.opname} at {instr.offset} '
      50                            f'jumps to {tgt.opname} at {tgt.offset}')
      51              # unconditional jump to RETURN_VALUE
      52              if (instr.opname in ('JUMP_BACKWARD', 'JUMP_FORWARD') and
      53                  tgt.opname == 'RETURN_VALUE'):
      54                  self.fail(f'{instr.opname} at {instr.offset} '
      55                            f'jumps to {tgt.opname} at {tgt.offset}')
      56  
      57      def check_lnotab(self, code):
      58          "Check that the lnotab byte offsets are sensible."
      59          code = dis._get_code_object(code)
      60          lnotab = list(dis.findlinestarts(code))
      61          # Don't bother checking if the line info is sensible, because
      62          # most of the line info we can get at comes from lnotab.
      63          min_bytecode = min(t[0] for t in lnotab)
      64          max_bytecode = max(t[0] for t in lnotab)
      65          self.assertGreaterEqual(min_bytecode, 0)
      66          self.assertLess(max_bytecode, len(code.co_code))
      67          # This could conceivably test more (and probably should, as there
      68          # aren't very many tests of lnotab), if peepholer wasn't scheduled
      69          # to be replaced anyway.
      70  
      71      def test_unot(self):
      72          # UNARY_NOT POP_JUMP_IF_FALSE  -->  POP_JUMP_IF_TRUE'
      73          def unot(x):
      74              if not x == 2:
      75                  del x
      76          self.assertNotInBytecode(unot, 'UNARY_NOT')
      77          self.assertNotInBytecode(unot, 'POP_JUMP_IF_FALSE')
      78          self.assertInBytecode(unot, 'POP_JUMP_IF_TRUE')
      79          self.check_lnotab(unot)
      80  
      81      def test_elim_inversion_of_is_or_in(self):
      82          for line, cmp_op, invert in (
      83              ('not a is b', 'IS_OP', 1,),
      84              ('not a is not b', 'IS_OP', 0,),
      85              ('not a in b', 'CONTAINS_OP', 1,),
      86              ('not a not in b', 'CONTAINS_OP', 0,),
      87              ):
      88              with self.subTest(line=line):
      89                  code = compile(line, '', 'single')
      90                  self.assertInBytecode(code, cmp_op, invert)
      91                  self.check_lnotab(code)
      92  
      93      def test_global_as_constant(self):
      94          # LOAD_GLOBAL None/True/False  -->  LOAD_CONST None/True/False
      95          def f():
      96              x = None
      97              x = None
      98              return x
      99          def g():
     100              x = True
     101              return x
     102          def h():
     103              x = False
     104              return x
     105  
     106          for func, elem in ((f, None), (g, True), (h, False)):
     107              with self.subTest(func=func):
     108                  self.assertNotInBytecode(func, 'LOAD_GLOBAL')
     109                  self.assertInBytecode(func, 'LOAD_CONST', elem)
     110                  self.check_lnotab(func)
     111  
     112          def f():
     113              'Adding a docstring made this test fail in Py2.5.0'
     114              return None
     115  
     116          self.assertNotInBytecode(f, 'LOAD_GLOBAL')
     117          self.assertInBytecode(f, 'RETURN_CONST', None)
     118          self.check_lnotab(f)
     119  
     120      def test_while_one(self):
     121          # Skip over:  LOAD_CONST trueconst  POP_JUMP_IF_FALSE xx
     122          def f():
     123              while 1:
     124                  pass
     125              return list
     126          for elem in ('LOAD_CONST', 'POP_JUMP_IF_FALSE'):
     127              self.assertNotInBytecode(f, elem)
     128          for elem in ('JUMP_BACKWARD',):
     129              self.assertInBytecode(f, elem)
     130          self.check_lnotab(f)
     131  
     132      def test_pack_unpack(self):
     133          for line, elem in (
     134              ('a, = a,', 'RETURN_CONST',),
     135              ('a, b = a, b', 'SWAP',),
     136              ('a, b, c = a, b, c', 'SWAP',),
     137              ):
     138              with self.subTest(line=line):
     139                  code = compile(line,'','single')
     140                  self.assertInBytecode(code, elem)
     141                  self.assertNotInBytecode(code, 'BUILD_TUPLE')
     142                  self.assertNotInBytecode(code, 'UNPACK_SEQUENCE')
     143                  self.check_lnotab(code)
     144  
     145      def test_folding_of_tuples_of_constants(self):
     146          for line, elem in (
     147              ('a = 1,2,3', (1, 2, 3)),
     148              ('("a","b","c")', ('a', 'b', 'c')),
     149              ('a,b,c = 1,2,3', (1, 2, 3)),
     150              ('(None, 1, None)', (None, 1, None)),
     151              ('((1, 2), 3, 4)', ((1, 2), 3, 4)),
     152              ):
     153              with self.subTest(line=line):
     154                  code = compile(line,'','single')
     155                  self.assertInBytecode(code, 'LOAD_CONST', elem)
     156                  self.assertNotInBytecode(code, 'BUILD_TUPLE')
     157                  self.check_lnotab(code)
     158  
     159          # Long tuples should be folded too.
     160          code = compile(repr(tuple(range(10000))),'','single')
     161          self.assertNotInBytecode(code, 'BUILD_TUPLE')
     162          # One LOAD_CONST for the tuple, one for the None return value
     163          load_consts = [instr for instr in dis.get_instructions(code)
     164                                if instr.opname == 'LOAD_CONST']
     165          self.assertEqual(len(load_consts), 1)
     166          self.check_lnotab(code)
     167  
     168          # Bug 1053819:  Tuple of constants misidentified when presented with:
     169          # . . . opcode_with_arg 100   unary_opcode   BUILD_TUPLE 1  . . .
     170          # The following would segfault upon compilation
     171          def crater():
     172              (~[
     173                  0, 1, 2, 3, 4, 5, 6, 7, 8, 9,
     174                  0, 1, 2, 3, 4, 5, 6, 7, 8, 9,
     175                  0, 1, 2, 3, 4, 5, 6, 7, 8, 9,
     176                  0, 1, 2, 3, 4, 5, 6, 7, 8, 9,
     177                  0, 1, 2, 3, 4, 5, 6, 7, 8, 9,
     178                  0, 1, 2, 3, 4, 5, 6, 7, 8, 9,
     179                  0, 1, 2, 3, 4, 5, 6, 7, 8, 9,
     180                  0, 1, 2, 3, 4, 5, 6, 7, 8, 9,
     181                  0, 1, 2, 3, 4, 5, 6, 7, 8, 9,
     182                  0, 1, 2, 3, 4, 5, 6, 7, 8, 9,
     183              ],)
     184          self.check_lnotab(crater)
     185  
     186      def test_folding_of_lists_of_constants(self):
     187          for line, elem in (
     188              # in/not in constants with BUILD_LIST should be folded to a tuple:
     189              ('a in [1,2,3]', (1, 2, 3)),
     190              ('a not in ["a","b","c"]', ('a', 'b', 'c')),
     191              ('a in [None, 1, None]', (None, 1, None)),
     192              ('a not in [(1, 2), 3, 4]', ((1, 2), 3, 4)),
     193              ):
     194              with self.subTest(line=line):
     195                  code = compile(line, '', 'single')
     196                  self.assertInBytecode(code, 'LOAD_CONST', elem)
     197                  self.assertNotInBytecode(code, 'BUILD_LIST')
     198                  self.check_lnotab(code)
     199  
     200      def test_folding_of_sets_of_constants(self):
     201          for line, elem in (
     202              # in/not in constants with BUILD_SET should be folded to a frozenset:
     203              ('a in {1,2,3}', frozenset({1, 2, 3})),
     204              ('a not in {"a","b","c"}', frozenset({'a', 'c', 'b'})),
     205              ('a in {None, 1, None}', frozenset({1, None})),
     206              ('a not in {(1, 2), 3, 4}', frozenset({(1, 2), 3, 4})),
     207              ('a in {1, 2, 3, 3, 2, 1}', frozenset({1, 2, 3})),
     208              ):
     209              with self.subTest(line=line):
     210                  code = compile(line, '', 'single')
     211                  self.assertNotInBytecode(code, 'BUILD_SET')
     212                  self.assertInBytecode(code, 'LOAD_CONST', elem)
     213                  self.check_lnotab(code)
     214  
     215          # Ensure that the resulting code actually works:
     216          def f(a):
     217              return a in {1, 2, 3}
     218  
     219          def g(a):
     220              return a not in {1, 2, 3}
     221  
     222          self.assertTrue(f(3))
     223          self.assertTrue(not f(4))
     224          self.check_lnotab(f)
     225  
     226          self.assertTrue(not g(3))
     227          self.assertTrue(g(4))
     228          self.check_lnotab(g)
     229  
     230  
     231      def test_folding_of_binops_on_constants(self):
     232          for line, elem in (
     233              ('a = 2+3+4', 9),                   # chained fold
     234              ('"@"*4', '@@@@'),                  # check string ops
     235              ('a="abc" + "def"', 'abcdef'),      # check string ops
     236              ('a = 3**4', 81),                   # binary power
     237              ('a = 3*4', 12),                    # binary multiply
     238              ('a = 13//4', 3),                   # binary floor divide
     239              ('a = 14%4', 2),                    # binary modulo
     240              ('a = 2+3', 5),                     # binary add
     241              ('a = 13-4', 9),                    # binary subtract
     242              ('a = (12,13)[1]', 13),             # binary subscr
     243              ('a = 13 << 2', 52),                # binary lshift
     244              ('a = 13 >> 2', 3),                 # binary rshift
     245              ('a = 13 & 7', 5),                  # binary and
     246              ('a = 13 ^ 7', 10),                 # binary xor
     247              ('a = 13 | 7', 15),                 # binary or
     248              ):
     249              with self.subTest(line=line):
     250                  code = compile(line, '', 'single')
     251                  self.assertInBytecode(code, 'LOAD_CONST', elem)
     252                  for instr in dis.get_instructions(code):
     253                      self.assertFalse(instr.opname.startswith('BINARY_'))
     254                  self.check_lnotab(code)
     255  
     256          # Verify that unfoldables are skipped
     257          code = compile('a=2+"b"', '', 'single')
     258          self.assertInBytecode(code, 'LOAD_CONST', 2)
     259          self.assertInBytecode(code, 'LOAD_CONST', 'b')
     260          self.check_lnotab(code)
     261  
     262          # Verify that large sequences do not result from folding
     263          code = compile('a="x"*10000', '', 'single')
     264          self.assertInBytecode(code, 'LOAD_CONST', 10000)
     265          self.assertNotIn("x"*10000, code.co_consts)
     266          self.check_lnotab(code)
     267          code = compile('a=1<<1000', '', 'single')
     268          self.assertInBytecode(code, 'LOAD_CONST', 1000)
     269          self.assertNotIn(1<<1000, code.co_consts)
     270          self.check_lnotab(code)
     271          code = compile('a=2**1000', '', 'single')
     272          self.assertInBytecode(code, 'LOAD_CONST', 1000)
     273          self.assertNotIn(2**1000, code.co_consts)
     274          self.check_lnotab(code)
     275  
     276      def test_binary_subscr_on_unicode(self):
     277          # valid code get optimized
     278          code = compile('"foo"[0]', '', 'single')
     279          self.assertInBytecode(code, 'LOAD_CONST', 'f')
     280          self.assertNotInBytecode(code, 'BINARY_SUBSCR')
     281          self.check_lnotab(code)
     282          code = compile('"\u0061\uffff"[1]', '', 'single')
     283          self.assertInBytecode(code, 'LOAD_CONST', '\uffff')
     284          self.assertNotInBytecode(code,'BINARY_SUBSCR')
     285          self.check_lnotab(code)
     286  
     287          # With PEP 393, non-BMP char get optimized
     288          code = compile('"\U00012345"[0]', '', 'single')
     289          self.assertInBytecode(code, 'LOAD_CONST', '\U00012345')
     290          self.assertNotInBytecode(code, 'BINARY_SUBSCR')
     291          self.check_lnotab(code)
     292  
     293          # invalid code doesn't get optimized
     294          # out of range
     295          code = compile('"fuu"[10]', '', 'single')
     296          self.assertInBytecode(code, 'BINARY_SUBSCR')
     297          self.check_lnotab(code)
     298  
     299      def test_folding_of_unaryops_on_constants(self):
     300          for line, elem in (
     301              ('-0.5', -0.5),                     # unary negative
     302              ('-0.0', -0.0),                     # -0.0
     303              ('-(1.0-1.0)', -0.0),               # -0.0 after folding
     304              ('-0', 0),                          # -0
     305              ('~-2', 1),                         # unary invert
     306              ('+1', 1),                          # unary positive
     307          ):
     308              with self.subTest(line=line):
     309                  code = compile(line, '', 'single')
     310                  self.assertInBytecode(code, 'LOAD_CONST', elem)
     311                  for instr in dis.get_instructions(code):
     312                      self.assertFalse(instr.opname.startswith('UNARY_'))
     313                  self.check_lnotab(code)
     314  
     315          # Check that -0.0 works after marshaling
     316          def negzero():
     317              return -(1.0-1.0)
     318  
     319          for instr in dis.get_instructions(negzero):
     320              self.assertFalse(instr.opname.startswith('UNARY_'))
     321          self.check_lnotab(negzero)
     322  
     323          # Verify that unfoldables are skipped
     324          for line, elem, opname in (
     325              ('-"abc"', 'abc', 'UNARY_NEGATIVE'),
     326              ('~"abc"', 'abc', 'UNARY_INVERT'),
     327          ):
     328              with self.subTest(line=line):
     329                  code = compile(line, '', 'single')
     330                  self.assertInBytecode(code, 'LOAD_CONST', elem)
     331                  self.assertInBytecode(code, opname)
     332                  self.check_lnotab(code)
     333  
     334      def test_elim_extra_return(self):
     335          # RETURN LOAD_CONST None RETURN  -->  RETURN
     336          def f(x):
     337              return x
     338          self.assertNotInBytecode(f, 'LOAD_CONST', None)
     339          returns = [instr for instr in dis.get_instructions(f)
     340                            if instr.opname == 'RETURN_VALUE']
     341          self.assertEqual(len(returns), 1)
     342          self.check_lnotab(f)
     343  
     344      def test_elim_jump_to_return(self):
     345          # JUMP_FORWARD to RETURN -->  RETURN
     346          def f(cond, true_value, false_value):
     347              # Intentionally use two-line expression to test issue37213.
     348              return (true_value if cond
     349                      else false_value)
     350          self.check_jump_targets(f)
     351          self.assertNotInBytecode(f, 'JUMP_FORWARD')
     352          self.assertNotInBytecode(f, 'JUMP_BACKWARD')
     353          returns = [instr for instr in dis.get_instructions(f)
     354                            if instr.opname == 'RETURN_VALUE']
     355          self.assertEqual(len(returns), 2)
     356          self.check_lnotab(f)
     357  
     358      def test_elim_jump_to_uncond_jump(self):
     359          # POP_JUMP_IF_FALSE to JUMP_FORWARD --> POP_JUMP_IF_FALSE to non-jump
     360          def f():
     361              if a:
     362                  # Intentionally use two-line expression to test issue37213.
     363                  if (c
     364                      or d):
     365                      foo()
     366              else:
     367                  baz()
     368          self.check_jump_targets(f)
     369          self.check_lnotab(f)
     370  
     371      def test_elim_jump_to_uncond_jump2(self):
     372          # POP_JUMP_IF_FALSE to JUMP_BACKWARD --> POP_JUMP_IF_FALSE to non-jump
     373          def f():
     374              while a:
     375                  # Intentionally use two-line expression to test issue37213.
     376                  if (c
     377                      or d):
     378                      a = foo()
     379          self.check_jump_targets(f)
     380          self.check_lnotab(f)
     381  
     382      def test_elim_jump_to_uncond_jump3(self):
     383          # Intentionally use two-line expressions to test issue37213.
     384          # POP_JUMP_IF_FALSE to POP_JUMP_IF_FALSE --> POP_JUMP_IF_FALSE to non-jump
     385          def f(a, b, c):
     386              return ((a and b)
     387                      and c)
     388          self.check_jump_targets(f)
     389          self.check_lnotab(f)
     390          self.assertEqual(count_instr_recursively(f, 'POP_JUMP_IF_FALSE'), 2)
     391          # POP_JUMP_IF_TRUE to POP_JUMP_IF_TRUE --> POP_JUMP_IF_TRUE to non-jump
     392          def f(a, b, c):
     393              return ((a or b)
     394                      or c)
     395          self.check_jump_targets(f)
     396          self.check_lnotab(f)
     397          self.assertEqual(count_instr_recursively(f, 'POP_JUMP_IF_TRUE'), 2)
     398          # JUMP_IF_FALSE_OR_POP to JUMP_IF_TRUE_OR_POP --> POP_JUMP_IF_FALSE to non-jump
     399          def f(a, b, c):
     400              return ((a and b)
     401                      or c)
     402          self.check_jump_targets(f)
     403          self.check_lnotab(f)
     404          self.assertEqual(count_instr_recursively(f, 'POP_JUMP_IF_FALSE'), 1)
     405          self.assertEqual(count_instr_recursively(f, 'POP_JUMP_IF_TRUE'), 1)
     406          # POP_JUMP_IF_TRUE to POP_JUMP_IF_FALSE --> POP_JUMP_IF_TRUE to non-jump
     407          def f(a, b, c):
     408              return ((a or b)
     409                      and c)
     410          self.check_jump_targets(f)
     411          self.check_lnotab(f)
     412          self.assertEqual(count_instr_recursively(f, 'POP_JUMP_IF_FALSE'), 1)
     413          self.assertEqual(count_instr_recursively(f, 'POP_JUMP_IF_TRUE'), 1)
     414  
     415      def test_elim_jump_to_uncond_jump4(self):
     416          def f():
     417              for i in range(5):
     418                  if i > 3:
     419                      print(i)
     420          self.check_jump_targets(f)
     421  
     422      def test_elim_jump_after_return1(self):
     423          # Eliminate dead code: jumps immediately after returns can't be reached
     424          def f(cond1, cond2):
     425              if cond1: return 1
     426              if cond2: return 2
     427              while 1:
     428                  return 3
     429              while 1:
     430                  if cond1: return 4
     431                  return 5
     432              return 6
     433          self.assertNotInBytecode(f, 'JUMP_FORWARD')
     434          self.assertNotInBytecode(f, 'JUMP_BACKWARD')
     435          returns = [instr for instr in dis.get_instructions(f)
     436                            if instr.opname == 'RETURN_VALUE']
     437          self.assertLessEqual(len(returns), 6)
     438          self.check_lnotab(f)
     439  
     440      def test_make_function_doesnt_bail(self):
     441          def f():
     442              def g()->1+1:
     443                  pass
     444              return g
     445          self.assertNotInBytecode(f, 'BINARY_OP')
     446          self.check_lnotab(f)
     447  
     448      def test_constant_folding(self):
     449          # Issue #11244: aggressive constant folding.
     450          exprs = [
     451              '3 * -5',
     452              '-3 * 5',
     453              '2 * (3 * 4)',
     454              '(2 * 3) * 4',
     455              '(-1, 2, 3)',
     456              '(1, -2, 3)',
     457              '(1, 2, -3)',
     458              '(1, 2, -3) * 6',
     459              'lambda x: x in {(3 * -5) + (-1 - 6), (1, -2, 3) * 2, None}',
     460          ]
     461          for e in exprs:
     462              with self.subTest(e=e):
     463                  code = compile(e, '', 'single')
     464                  for instr in dis.get_instructions(code):
     465                      self.assertFalse(instr.opname.startswith('UNARY_'))
     466                      self.assertFalse(instr.opname.startswith('BINARY_'))
     467                      self.assertFalse(instr.opname.startswith('BUILD_'))
     468                  self.check_lnotab(code)
     469  
     470      def test_in_literal_list(self):
     471          def containtest():
     472              return x in [a, b]
     473          self.assertEqual(count_instr_recursively(containtest, 'BUILD_LIST'), 0)
     474          self.check_lnotab(containtest)
     475  
     476      def test_iterate_literal_list(self):
     477          def forloop():
     478              for x in [a, b]:
     479                  pass
     480          self.assertEqual(count_instr_recursively(forloop, 'BUILD_LIST'), 0)
     481          self.check_lnotab(forloop)
     482  
     483      def test_condition_with_binop_with_bools(self):
     484          def f():
     485              if True or False:
     486                  return 1
     487              return 0
     488          self.assertEqual(f(), 1)
     489          self.check_lnotab(f)
     490  
     491      def test_if_with_if_expression(self):
     492          # Check bpo-37289
     493          def f(x):
     494              if (True if x else False):
     495                  return True
     496              return False
     497          self.assertTrue(f(True))
     498          self.check_lnotab(f)
     499  
     500      def test_trailing_nops(self):
     501          # Check the lnotab of a function that even after trivial
     502          # optimization has trailing nops, which the lnotab adjustment has to
     503          # handle properly (bpo-38115).
     504          def f(x):
     505              while 1:
     506                  return 3
     507              while 1:
     508                  return 5
     509              return 6
     510          self.check_lnotab(f)
     511  
     512      def test_assignment_idiom_in_comprehensions(self):
     513          def listcomp():
     514              return [y for x in a for y in [f(x)]]
     515          self.assertEqual(count_instr_recursively(listcomp, 'FOR_ITER'), 1)
     516          def setcomp():
     517              return {y for x in a for y in [f(x)]}
     518          self.assertEqual(count_instr_recursively(setcomp, 'FOR_ITER'), 1)
     519          def dictcomp():
     520              return {y: y for x in a for y in [f(x)]}
     521          self.assertEqual(count_instr_recursively(dictcomp, 'FOR_ITER'), 1)
     522          def genexpr():
     523              return (y for x in a for y in [f(x)])
     524          self.assertEqual(count_instr_recursively(genexpr, 'FOR_ITER'), 1)
     525  
     526      @support.requires_resource('cpu')
     527      def test_format_combinations(self):
     528          flags = '-+ #0'
     529          testcases = [
     530              *product(('', '1234', 'абвг'), 'sra'),
     531              *product((1234, -1234), 'duioxX'),
     532              *product((1234.5678901, -1234.5678901), 'duifegFEG'),
     533              *product((float('inf'), -float('inf')), 'fegFEG'),
     534          ]
     535          width_precs = [
     536              *product(('', '1', '30'), ('', '.', '.0', '.2')),
     537              ('', '.40'),
     538              ('30', '.40'),
     539          ]
     540          for value, suffix in testcases:
     541              for width, prec in width_precs:
     542                  for r in range(len(flags) + 1):
     543                      for spec in combinations(flags, r):
     544                          fmt = '%' + ''.join(spec) + width + prec + suffix
     545                          with self.subTest(fmt=fmt, value=value):
     546                              s1 = fmt % value
     547                              s2 = eval(f'{fmt!r} % (x,)', {'x': value})
     548                              self.assertEqual(s2, s1, f'{fmt = }')
     549  
     550      def test_format_misc(self):
     551          def format(fmt, *values):
     552              vars = [f'x{i+1}' for i in range(len(values))]
     553              if len(vars) == 1:
     554                  args = '(' + vars[0] + ',)'
     555              else:
     556                  args = '(' + ', '.join(vars) + ')'
     557              return eval(f'{fmt!r} % {args}', dict(zip(vars, values)))
     558  
     559          self.assertEqual(format('string'), 'string')
     560          self.assertEqual(format('x = %s!', 1234), 'x = 1234!')
     561          self.assertEqual(format('x = %d!', 1234), 'x = 1234!')
     562          self.assertEqual(format('x = %x!', 1234), 'x = 4d2!')
     563          self.assertEqual(format('x = %f!', 1234), 'x = 1234.000000!')
     564          self.assertEqual(format('x = %s!', 1234.5678901), 'x = 1234.5678901!')
     565          self.assertEqual(format('x = %f!', 1234.5678901), 'x = 1234.567890!')
     566          self.assertEqual(format('x = %d!', 1234.5678901), 'x = 1234!')
     567          self.assertEqual(format('x = %s%% %%%%', 1234), 'x = 1234% %%')
     568          self.assertEqual(format('x = %s!', '%% %s'), 'x = %% %s!')
     569          self.assertEqual(format('x = %s, y = %d', 12, 34), 'x = 12, y = 34')
     570  
     571      def test_format_errors(self):
     572          with self.assertRaisesRegex(TypeError,
     573                      'not enough arguments for format string'):
     574              eval("'%s' % ()")
     575          with self.assertRaisesRegex(TypeError,
     576                      'not all arguments converted during string formatting'):
     577              eval("'%s' % (x, y)", {'x': 1, 'y': 2})
     578          with self.assertRaisesRegex(ValueError, 'incomplete format'):
     579              eval("'%s%' % (x,)", {'x': 1234})
     580          with self.assertRaisesRegex(ValueError, 'incomplete format'):
     581              eval("'%s%%%' % (x,)", {'x': 1234})
     582          with self.assertRaisesRegex(TypeError,
     583                      'not enough arguments for format string'):
     584              eval("'%s%z' % (x,)", {'x': 1234})
     585          with self.assertRaisesRegex(ValueError, 'unsupported format character'):
     586              eval("'%s%z' % (x, 5)", {'x': 1234})
     587          with self.assertRaisesRegex(TypeError, 'a real number is required, not str'):
     588              eval("'%d' % (x,)", {'x': '1234'})
     589          with self.assertRaisesRegex(TypeError, 'an integer is required, not float'):
     590              eval("'%x' % (x,)", {'x': 1234.56})
     591          with self.assertRaisesRegex(TypeError, 'an integer is required, not str'):
     592              eval("'%x' % (x,)", {'x': '1234'})
     593          with self.assertRaisesRegex(TypeError, 'must be real number, not str'):
     594              eval("'%f' % (x,)", {'x': '1234'})
     595          with self.assertRaisesRegex(TypeError,
     596                      'not enough arguments for format string'):
     597              eval("'%s, %s' % (x, *y)", {'x': 1, 'y': []})
     598          with self.assertRaisesRegex(TypeError,
     599                      'not all arguments converted during string formatting'):
     600              eval("'%s, %s' % (x, *y)", {'x': 1, 'y': [2, 3]})
     601  
     602      def test_static_swaps_unpack_two(self):
     603          def f(a, b):
     604              a, b = a, b
     605              b, a = a, b
     606          self.assertNotInBytecode(f, "SWAP")
     607  
     608      def test_static_swaps_unpack_three(self):
     609          def f(a, b, c):
     610              a, b, c = a, b, c
     611              a, c, b = a, b, c
     612              b, a, c = a, b, c
     613              b, c, a = a, b, c
     614              c, a, b = a, b, c
     615              c, b, a = a, b, c
     616          self.assertNotInBytecode(f, "SWAP")
     617  
     618      def test_static_swaps_match_mapping(self):
     619          for a, b, c in product("_a", "_b", "_c"):
     620              pattern = f"{{'a': {a}, 'b': {b}, 'c': {c}}}"
     621              with self.subTest(pattern):
     622                  code = compile_pattern_with_fast_locals(pattern)
     623                  self.assertNotInBytecode(code, "SWAP")
     624  
     625      def test_static_swaps_match_class(self):
     626          forms = [
     627              "C({}, {}, {})",
     628              "C({}, {}, c={})",
     629              "C({}, b={}, c={})",
     630              "C(a={}, b={}, c={})"
     631          ]
     632          for a, b, c in product("_a", "_b", "_c"):
     633              for form in forms:
     634                  pattern = form.format(a, b, c)
     635                  with self.subTest(pattern):
     636                      code = compile_pattern_with_fast_locals(pattern)
     637                      self.assertNotInBytecode(code, "SWAP")
     638  
     639      def test_static_swaps_match_sequence(self):
     640          swaps = {"*_, b, c", "a, *_, c", "a, b, *_"}
     641          forms = ["{}, {}, {}", "{}, {}, *{}", "{}, *{}, {}", "*{}, {}, {}"]
     642          for a, b, c in product("_a", "_b", "_c"):
     643              for form in forms:
     644                  pattern = form.format(a, b, c)
     645                  with self.subTest(pattern):
     646                      code = compile_pattern_with_fast_locals(pattern)
     647                      if pattern in swaps:
     648                          # If this fails... great! Remove this pattern from swaps
     649                          # to prevent regressing on any improvement:
     650                          self.assertInBytecode(code, "SWAP")
     651                      else:
     652                          self.assertNotInBytecode(code, "SWAP")
     653  
     654  
     655  class ESC[4;38;5;81mTestBuglets(ESC[4;38;5;149munittestESC[4;38;5;149m.ESC[4;38;5;149mTestCase):
     656  
     657      def test_bug_11510(self):
     658          # folded constant set optimization was commingled with the tuple
     659          # unpacking optimization which would fail if the set had duplicate
     660          # elements so that the set length was unexpected
     661          def f():
     662              x, y = {1, 1}
     663              return x, y
     664          with self.assertRaises(ValueError):
     665              f()
     666  
     667      def test_bpo_42057(self):
     668          for i in range(10):
     669              try:
     670                  raise Exception
     671              except Exception or Exception:
     672                  pass
     673  
     674      def test_bpo_45773_pop_jump_if_true(self):
     675          compile("while True or spam: pass", "<test>", "exec")
     676  
     677      def test_bpo_45773_pop_jump_if_false(self):
     678          compile("while True or not spam: pass", "<test>", "exec")
     679  
     680  
     681  class ESC[4;38;5;81mTestMarkingVariablesAsUnKnown(ESC[4;38;5;149mBytecodeTestCase):
     682  
     683      def setUp(self):
     684          self.addCleanup(sys.settrace, sys.gettrace())
     685          sys.settrace(None)
     686  
     687      def test_load_fast_known_simple(self):
     688          def f():
     689              x = 1
     690              y = x + x
     691          self.assertInBytecode(f, 'LOAD_FAST')
     692  
     693      def test_load_fast_unknown_simple(self):
     694          def f():
     695              if condition():
     696                  x = 1
     697              print(x)
     698          self.assertInBytecode(f, 'LOAD_FAST_CHECK')
     699          self.assertNotInBytecode(f, 'LOAD_FAST')
     700  
     701      def test_load_fast_unknown_because_del(self):
     702          def f():
     703              x = 1
     704              del x
     705              print(x)
     706          self.assertInBytecode(f, 'LOAD_FAST_CHECK')
     707          self.assertNotInBytecode(f, 'LOAD_FAST')
     708  
     709      def test_load_fast_known_because_parameter(self):
     710          def f1(x):
     711              print(x)
     712          self.assertInBytecode(f1, 'LOAD_FAST')
     713          self.assertNotInBytecode(f1, 'LOAD_FAST_CHECK')
     714  
     715          def f2(*, x):
     716              print(x)
     717          self.assertInBytecode(f2, 'LOAD_FAST')
     718          self.assertNotInBytecode(f2, 'LOAD_FAST_CHECK')
     719  
     720          def f3(*args):
     721              print(args)
     722          self.assertInBytecode(f3, 'LOAD_FAST')
     723          self.assertNotInBytecode(f3, 'LOAD_FAST_CHECK')
     724  
     725          def f4(**kwargs):
     726              print(kwargs)
     727          self.assertInBytecode(f4, 'LOAD_FAST')
     728          self.assertNotInBytecode(f4, 'LOAD_FAST_CHECK')
     729  
     730          def f5(x=0):
     731              print(x)
     732          self.assertInBytecode(f5, 'LOAD_FAST')
     733          self.assertNotInBytecode(f5, 'LOAD_FAST_CHECK')
     734  
     735      def test_load_fast_known_because_already_loaded(self):
     736          def f():
     737              if condition():
     738                  x = 1
     739              print(x)
     740              print(x)
     741          self.assertInBytecode(f, 'LOAD_FAST_CHECK')
     742          self.assertInBytecode(f, 'LOAD_FAST')
     743  
     744      def test_load_fast_known_multiple_branches(self):
     745          def f():
     746              if condition():
     747                  x = 1
     748              else:
     749                  x = 2
     750              print(x)
     751          self.assertInBytecode(f, 'LOAD_FAST')
     752          self.assertNotInBytecode(f, 'LOAD_FAST_CHECK')
     753  
     754      def test_load_fast_unknown_after_error(self):
     755          def f():
     756              try:
     757                  res = 1 / 0
     758              except ZeroDivisionError:
     759                  pass
     760              return res
     761          # LOAD_FAST (known) still occurs in the no-exception branch.
     762          # Assert that it doesn't occur in the LOAD_FAST_CHECK branch.
     763          self.assertInBytecode(f, 'LOAD_FAST_CHECK')
     764  
     765      def test_load_fast_unknown_after_error_2(self):
     766          def f():
     767              try:
     768                  1 / 0
     769              except:
     770                  print(a, b, c, d, e, f, g)
     771              a = b = c = d = e = f = g = 1
     772          self.assertInBytecode(f, 'LOAD_FAST_CHECK')
     773          self.assertNotInBytecode(f, 'LOAD_FAST')
     774  
     775      def test_load_fast_too_many_locals(self):
     776          # When there get to be too many locals to analyze completely,
     777          # later locals are all converted to LOAD_FAST_CHECK, except
     778          # when a store or prior load occurred in the same basicblock.
     779          def f():
     780              a00 = a01 = a02 = a03 = a04 = a05 = a06 = a07 = a08 = a09 = 1
     781              a10 = a11 = a12 = a13 = a14 = a15 = a16 = a17 = a18 = a19 = 1
     782              a20 = a21 = a22 = a23 = a24 = a25 = a26 = a27 = a28 = a29 = 1
     783              a30 = a31 = a32 = a33 = a34 = a35 = a36 = a37 = a38 = a39 = 1
     784              a40 = a41 = a42 = a43 = a44 = a45 = a46 = a47 = a48 = a49 = 1
     785              a50 = a51 = a52 = a53 = a54 = a55 = a56 = a57 = a58 = a59 = 1
     786              a60 = a61 = a62 = a63 = a64 = a65 = a66 = a67 = a68 = a69 = 1
     787              a70 = a71 = a72 = a73 = a74 = a75 = a76 = a77 = a78 = a79 = 1
     788              del a72, a73
     789              print(a73)
     790              print(a70, a71, a72, a73)
     791              while True:
     792                  print(a00, a01, a62, a63)
     793                  print(a64, a65, a78, a79)
     794  
     795          for i in 0, 1, 62, 63:
     796              # First 64 locals: analyze completely
     797              self.assertInBytecode(f, 'LOAD_FAST', f"a{i:02}")
     798              self.assertNotInBytecode(f, 'LOAD_FAST_CHECK', f"a{i:02}")
     799          for i in 64, 65, 78, 79:
     800              # Locals >=64 not in the same basicblock
     801              self.assertInBytecode(f, 'LOAD_FAST_CHECK', f"a{i:02}")
     802              self.assertNotInBytecode(f, 'LOAD_FAST', f"a{i:02}")
     803          for i in 70, 71:
     804              # Locals >=64 in the same basicblock
     805              self.assertInBytecode(f, 'LOAD_FAST', f"a{i:02}")
     806              self.assertNotInBytecode(f, 'LOAD_FAST_CHECK', f"a{i:02}")
     807          # del statements should invalidate within basicblocks.
     808          self.assertInBytecode(f, 'LOAD_FAST_CHECK', "a72")
     809          self.assertNotInBytecode(f, 'LOAD_FAST', "a72")
     810          # previous checked loads within a basicblock enable unchecked loads
     811          self.assertInBytecode(f, 'LOAD_FAST_CHECK', "a73")
     812          self.assertInBytecode(f, 'LOAD_FAST', "a73")
     813  
     814      def test_setting_lineno_no_undefined(self):
     815          code = textwrap.dedent("""\
     816              def f():
     817                  x = y = 2
     818                  if not x:
     819                      return 4
     820                  for i in range(55):
     821                      x + 6
     822                  L = 7
     823                  L = 8
     824                  L = 9
     825                  L = 10
     826          """)
     827          ns = {}
     828          exec(code, ns)
     829          f = ns['f']
     830          self.assertInBytecode(f, "LOAD_FAST")
     831          self.assertNotInBytecode(f, "LOAD_FAST_CHECK")
     832          co_code = f.__code__.co_code
     833          def trace(frame, event, arg):
     834              if event == 'line' and frame.f_lineno == 9:
     835                  frame.f_lineno = 3
     836                  sys.settrace(None)
     837                  return None
     838              return trace
     839          sys.settrace(trace)
     840          result = f()
     841          self.assertIsNone(result)
     842          self.assertInBytecode(f, "LOAD_FAST")
     843          self.assertNotInBytecode(f, "LOAD_FAST_CHECK")
     844          self.assertEqual(f.__code__.co_code, co_code)
     845  
     846      def test_setting_lineno_one_undefined(self):
     847          code = textwrap.dedent("""\
     848              def f():
     849                  x = y = 2
     850                  if not x:
     851                      return 4
     852                  for i in range(55):
     853                      x + 6
     854                  del x
     855                  L = 8
     856                  L = 9
     857                  L = 10
     858          """)
     859          ns = {}
     860          exec(code, ns)
     861          f = ns['f']
     862          self.assertInBytecode(f, "LOAD_FAST")
     863          self.assertNotInBytecode(f, "LOAD_FAST_CHECK")
     864          co_code = f.__code__.co_code
     865          def trace(frame, event, arg):
     866              if event == 'line' and frame.f_lineno == 9:
     867                  frame.f_lineno = 3
     868                  sys.settrace(None)
     869                  return None
     870              return trace
     871          e = r"assigning None to 1 unbound local"
     872          with self.assertWarnsRegex(RuntimeWarning, e):
     873              sys.settrace(trace)
     874              result = f()
     875          self.assertEqual(result, 4)
     876          self.assertInBytecode(f, "LOAD_FAST")
     877          self.assertNotInBytecode(f, "LOAD_FAST_CHECK")
     878          self.assertEqual(f.__code__.co_code, co_code)
     879  
     880      def test_setting_lineno_two_undefined(self):
     881          code = textwrap.dedent("""\
     882              def f():
     883                  x = y = 2
     884                  if not x:
     885                      return 4
     886                  for i in range(55):
     887                      x + 6
     888                  del x, y
     889                  L = 8
     890                  L = 9
     891                  L = 10
     892          """)
     893          ns = {}
     894          exec(code, ns)
     895          f = ns['f']
     896          self.assertInBytecode(f, "LOAD_FAST")
     897          self.assertNotInBytecode(f, "LOAD_FAST_CHECK")
     898          co_code = f.__code__.co_code
     899          def trace(frame, event, arg):
     900              if event == 'line' and frame.f_lineno == 9:
     901                  frame.f_lineno = 3
     902                  sys.settrace(None)
     903                  return None
     904              return trace
     905          e = r"assigning None to 2 unbound locals"
     906          with self.assertWarnsRegex(RuntimeWarning, e):
     907              sys.settrace(trace)
     908              result = f()
     909          self.assertEqual(result, 4)
     910          self.assertInBytecode(f, "LOAD_FAST")
     911          self.assertNotInBytecode(f, "LOAD_FAST_CHECK")
     912          self.assertEqual(f.__code__.co_code, co_code)
     913  
     914      def make_function_with_no_checks(self):
     915          code = textwrap.dedent("""\
     916              def f():
     917                  x = 2
     918                  L = 3
     919                  L = 4
     920                  L = 5
     921                  if not L:
     922                      x + 7
     923                      y = 2
     924          """)
     925          ns = {}
     926          exec(code, ns)
     927          f = ns['f']
     928          self.assertInBytecode(f, "LOAD_FAST")
     929          self.assertNotInBytecode(f, "LOAD_FAST_CHECK")
     930          return f
     931  
     932      def test_deleting_local_warns_and_assigns_none(self):
     933          f = self.make_function_with_no_checks()
     934          co_code = f.__code__.co_code
     935          def trace(frame, event, arg):
     936              if event == 'line' and frame.f_lineno == 4:
     937                  del frame.f_locals["x"]
     938                  sys.settrace(None)
     939                  return None
     940              return trace
     941          e = r"assigning None to unbound local 'x'"
     942          with self.assertWarnsRegex(RuntimeWarning, e):
     943              sys.settrace(trace)
     944              f()
     945          self.assertInBytecode(f, "LOAD_FAST")
     946          self.assertNotInBytecode(f, "LOAD_FAST_CHECK")
     947          self.assertEqual(f.__code__.co_code, co_code)
     948  
     949      def test_modifying_local_does_not_add_check(self):
     950          f = self.make_function_with_no_checks()
     951          def trace(frame, event, arg):
     952              if event == 'line' and frame.f_lineno == 4:
     953                  frame.f_locals["x"] = 42
     954                  sys.settrace(None)
     955                  return None
     956              return trace
     957          sys.settrace(trace)
     958          f()
     959          self.assertInBytecode(f, "LOAD_FAST")
     960          self.assertNotInBytecode(f, "LOAD_FAST_CHECK")
     961  
     962      def test_initializing_local_does_not_add_check(self):
     963          f = self.make_function_with_no_checks()
     964          def trace(frame, event, arg):
     965              if event == 'line' and frame.f_lineno == 4:
     966                  frame.f_locals["y"] = 42
     967                  sys.settrace(None)
     968                  return None
     969              return trace
     970          sys.settrace(trace)
     971          f()
     972          self.assertInBytecode(f, "LOAD_FAST")
     973          self.assertNotInBytecode(f, "LOAD_FAST_CHECK")
     974  
     975  
     976  class ESC[4;38;5;81mDirectCfgOptimizerTests(ESC[4;38;5;149mCfgOptimizationTestCase):
     977  
     978      def cfg_optimization_test(self, insts, expected_insts,
     979                                consts=None, expected_consts=None,
     980                                nlocals=0):
     981          if expected_consts is None:
     982              expected_consts = consts
     983          opt_insts, opt_consts = self.get_optimized(insts, consts, nlocals)
     984          expected_insts = self.normalize_insts(expected_insts)
     985          self.assertInstructionsMatch(opt_insts, expected_insts)
     986          self.assertEqual(opt_consts, expected_consts)
     987  
     988      def test_conditional_jump_forward_non_const_condition(self):
     989          insts = [
     990              ('LOAD_NAME', 1, 11),
     991              ('POP_JUMP_IF_TRUE', lbl := self.Label(), 12),
     992              ('LOAD_CONST', 2, 13),
     993              lbl,
     994              ('LOAD_CONST', 3, 14),
     995              ('RETURN_VALUE', 14),
     996          ]
     997          expected_insts = [
     998              ('LOAD_NAME', 1, 11),
     999              ('POP_JUMP_IF_TRUE', lbl := self.Label(), 12),
    1000              ('LOAD_CONST', 1, 13),
    1001              lbl,
    1002              ('RETURN_CONST', 2, 14),
    1003          ]
    1004          self.cfg_optimization_test(insts,
    1005                                     expected_insts,
    1006                                     consts=[0, 1, 2, 3, 4],
    1007                                     expected_consts=[0, 2, 3])
    1008  
    1009      def test_conditional_jump_forward_const_condition(self):
    1010          # The unreachable branch of the jump is removed, the jump
    1011          # becomes redundant and is replaced by a NOP (for the lineno)
    1012  
    1013          insts = [
    1014              ('LOAD_CONST', 3, 11),
    1015              ('POP_JUMP_IF_TRUE', lbl := self.Label(), 12),
    1016              ('LOAD_CONST', 2, 13),
    1017              lbl,
    1018              ('LOAD_CONST', 3, 14),
    1019              ('RETURN_VALUE', 14),
    1020          ]
    1021          expected_insts = [
    1022              ('NOP', 11),
    1023              ('NOP', 12),
    1024              ('RETURN_CONST', 1, 14),
    1025          ]
    1026          self.cfg_optimization_test(insts,
    1027                                     expected_insts,
    1028                                     consts=[0, 1, 2, 3, 4],
    1029                                     expected_consts=[0, 3])
    1030  
    1031      def test_conditional_jump_backward_non_const_condition(self):
    1032          insts = [
    1033              lbl1 := self.Label(),
    1034              ('LOAD_NAME', 1, 11),
    1035              ('POP_JUMP_IF_TRUE', lbl1, 12),
    1036              ('LOAD_NAME', 2, 13),
    1037              ('RETURN_VALUE', 13),
    1038          ]
    1039          expected = [
    1040              lbl := self.Label(),
    1041              ('LOAD_NAME', 1, 11),
    1042              ('POP_JUMP_IF_TRUE', lbl, 12),
    1043              ('LOAD_NAME', 2, 13),
    1044              ('RETURN_VALUE', 13),
    1045          ]
    1046          self.cfg_optimization_test(insts, expected, consts=list(range(5)))
    1047  
    1048      def test_conditional_jump_backward_const_condition(self):
    1049          # The unreachable branch of the jump is removed
    1050          insts = [
    1051              lbl1 := self.Label(),
    1052              ('LOAD_CONST', 3, 11),
    1053              ('POP_JUMP_IF_TRUE', lbl1, 12),
    1054              ('LOAD_CONST', 2, 13),
    1055              ('RETURN_VALUE', 13),
    1056          ]
    1057          expected_insts = [
    1058              lbl := self.Label(),
    1059              ('NOP', 11),
    1060              ('JUMP', lbl, 12),
    1061          ]
    1062          self.cfg_optimization_test(insts, expected_insts, consts=list(range(5)))
    1063  
    1064      def test_no_unsafe_static_swap(self):
    1065          # We can't change order of two stores to the same location
    1066          insts = [
    1067              ('LOAD_CONST', 0, 1),
    1068              ('LOAD_CONST', 1, 2),
    1069              ('LOAD_CONST', 2, 3),
    1070              ('SWAP', 3, 4),
    1071              ('STORE_FAST', 1, 4),
    1072              ('STORE_FAST', 1, 4),
    1073              ('POP_TOP', 0, 4),
    1074              ('RETURN_VALUE', 5)
    1075          ]
    1076          self.cfg_optimization_test(insts, insts, consts=list(range(3)), nlocals=1)
    1077  
    1078  if __name__ == "__main__":
    1079      unittest.main()