bytecode 0.16.0__tar.gz → 0.16.2__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (61) hide show
  1. {bytecode-0.16.0 → bytecode-0.16.2}/.github/workflows/cis.yml +1 -1
  2. {bytecode-0.16.0 → bytecode-0.16.2}/.github/workflows/release.yml +2 -2
  3. {bytecode-0.16.0/src/bytecode.egg-info → bytecode-0.16.2}/PKG-INFO +3 -2
  4. {bytecode-0.16.0 → bytecode-0.16.2}/doc/changelog.rst +15 -0
  5. {bytecode-0.16.0 → bytecode-0.16.2}/src/bytecode/cfg.py +9 -3
  6. {bytecode-0.16.0 → bytecode-0.16.2}/src/bytecode/concrete.py +2 -2
  7. {bytecode-0.16.0 → bytecode-0.16.2}/src/bytecode/flags.py +98 -48
  8. {bytecode-0.16.0 → bytecode-0.16.2}/src/bytecode/instr.py +14 -19
  9. {bytecode-0.16.0 → bytecode-0.16.2}/src/bytecode/version.py +2 -2
  10. {bytecode-0.16.0 → bytecode-0.16.2/src/bytecode.egg-info}/PKG-INFO +3 -2
  11. {bytecode-0.16.0 → bytecode-0.16.2}/tests/test_cfg.py +17 -9
  12. {bytecode-0.16.0 → bytecode-0.16.2}/tests/test_flags.py +96 -40
  13. {bytecode-0.16.0 → bytecode-0.16.2}/.coveragerc +0 -0
  14. {bytecode-0.16.0 → bytecode-0.16.2}/.github/FUNDING.yml +0 -0
  15. {bytecode-0.16.0 → bytecode-0.16.2}/.github/dependabot.yml +0 -0
  16. {bytecode-0.16.0 → bytecode-0.16.2}/.github/workflows/docs.yml +0 -0
  17. {bytecode-0.16.0 → bytecode-0.16.2}/.github/workflows/frameworks.yml +0 -0
  18. {bytecode-0.16.0 → bytecode-0.16.2}/.gitignore +0 -0
  19. {bytecode-0.16.0 → bytecode-0.16.2}/.pre-commit-config.yaml +0 -0
  20. {bytecode-0.16.0 → bytecode-0.16.2}/.readthedocs.yaml +0 -0
  21. {bytecode-0.16.0 → bytecode-0.16.2}/COPYING +0 -0
  22. {bytecode-0.16.0 → bytecode-0.16.2}/MANIFEST.in +0 -0
  23. {bytecode-0.16.0 → bytecode-0.16.2}/README.rst +0 -0
  24. {bytecode-0.16.0 → bytecode-0.16.2}/TODO.rst +0 -0
  25. {bytecode-0.16.0 → bytecode-0.16.2}/codecov.yml +0 -0
  26. {bytecode-0.16.0 → bytecode-0.16.2}/doc/Makefile +0 -0
  27. {bytecode-0.16.0 → bytecode-0.16.2}/doc/api.rst +0 -0
  28. {bytecode-0.16.0 → bytecode-0.16.2}/doc/byteplay_codetransformer.rst +0 -0
  29. {bytecode-0.16.0 → bytecode-0.16.2}/doc/cfg.rst +0 -0
  30. {bytecode-0.16.0 → bytecode-0.16.2}/doc/conf.py +0 -0
  31. {bytecode-0.16.0 → bytecode-0.16.2}/doc/index.rst +0 -0
  32. {bytecode-0.16.0 → bytecode-0.16.2}/doc/make.bat +0 -0
  33. {bytecode-0.16.0 → bytecode-0.16.2}/doc/requirements.txt +0 -0
  34. {bytecode-0.16.0 → bytecode-0.16.2}/doc/todo.rst +0 -0
  35. {bytecode-0.16.0 → bytecode-0.16.2}/doc/usage.rst +0 -0
  36. {bytecode-0.16.0 → bytecode-0.16.2}/pyproject.toml +0 -0
  37. {bytecode-0.16.0 → bytecode-0.16.2}/scripts/frameworks/boto3/run.sh +0 -0
  38. {bytecode-0.16.0 → bytecode-0.16.2}/scripts/frameworks/boto3/setup.sh +0 -0
  39. {bytecode-0.16.0 → bytecode-0.16.2}/setup.cfg +0 -0
  40. {bytecode-0.16.0 → bytecode-0.16.2}/src/bytecode/__init__.py +7 -7
  41. {bytecode-0.16.0 → bytecode-0.16.2}/src/bytecode/bytecode.py +0 -0
  42. {bytecode-0.16.0 → bytecode-0.16.2}/src/bytecode/py.typed +0 -0
  43. {bytecode-0.16.0 → bytecode-0.16.2}/src/bytecode/utils.py +0 -0
  44. {bytecode-0.16.0 → bytecode-0.16.2}/src/bytecode.egg-info/SOURCES.txt +0 -0
  45. {bytecode-0.16.0 → bytecode-0.16.2}/src/bytecode.egg-info/dependency_links.txt +0 -0
  46. {bytecode-0.16.0 → bytecode-0.16.2}/src/bytecode.egg-info/requires.txt +0 -0
  47. {bytecode-0.16.0 → bytecode-0.16.2}/src/bytecode.egg-info/top_level.txt +0 -0
  48. {bytecode-0.16.0 → bytecode-0.16.2}/tests/__init__.py +0 -0
  49. {bytecode-0.16.0 → bytecode-0.16.2}/tests/cell_free_vars_cases.py +0 -0
  50. {bytecode-0.16.0 → bytecode-0.16.2}/tests/exception_handling_cases.py +0 -0
  51. {bytecode-0.16.0 → bytecode-0.16.2}/tests/frameworks/function.py +0 -0
  52. {bytecode-0.16.0 → bytecode-0.16.2}/tests/frameworks/module.py +0 -0
  53. {bytecode-0.16.0 → bytecode-0.16.2}/tests/frameworks/sitecustomize.py +0 -0
  54. {bytecode-0.16.0 → bytecode-0.16.2}/tests/long_lines_example.py +0 -0
  55. {bytecode-0.16.0 → bytecode-0.16.2}/tests/test_bytecode.py +0 -0
  56. {bytecode-0.16.0 → bytecode-0.16.2}/tests/test_code.py +0 -0
  57. {bytecode-0.16.0 → bytecode-0.16.2}/tests/test_concrete.py +0 -0
  58. {bytecode-0.16.0 → bytecode-0.16.2}/tests/test_instr.py +0 -0
  59. {bytecode-0.16.0 → bytecode-0.16.2}/tests/test_misc.py +0 -0
  60. {bytecode-0.16.0 → bytecode-0.16.2}/tests/util_annotation.py +0 -0
  61. {bytecode-0.16.0 → bytecode-0.16.2}/tox.ini +0 -0
@@ -74,7 +74,7 @@ jobs:
74
74
  run: |
75
75
  tox
76
76
  - name: Upload coverage to Codecov
77
- uses: codecov/codecov-action@v4
77
+ uses: codecov/codecov-action@v5
78
78
  if: github.event_name != 'schedule'
79
79
  with:
80
80
  token: ${{ secrets.CODECOV_TOKEN }}
@@ -74,7 +74,7 @@ jobs:
74
74
  runs-on: ubuntu-latest
75
75
  steps:
76
76
  - name: Download all the dists
77
- uses: actions/download-artifact@v4.1.8
77
+ uses: actions/download-artifact@v4.2.1
78
78
  with:
79
79
  pattern: cibw-*
80
80
  path: dist
@@ -101,7 +101,7 @@ jobs:
101
101
 
102
102
  steps:
103
103
  - name: Download all the dists
104
- uses: actions/download-artifact@v4.1.8
104
+ uses: actions/download-artifact@v4.2.1
105
105
  with:
106
106
  pattern: cibw-*
107
107
  path: dist
@@ -1,6 +1,6 @@
1
- Metadata-Version: 2.1
1
+ Metadata-Version: 2.4
2
2
  Name: bytecode
3
- Version: 0.16.0
3
+ Version: 0.16.2
4
4
  Summary: Python module to generate and modify bytecode
5
5
  Author-email: Victor Stinner <victor.stinner@gmail.com>
6
6
  Maintainer-email: "Matthieu C. Dartiailh" <m.dartiailh@gmail.com>
@@ -47,6 +47,7 @@ Requires-Python: >=3.8
47
47
  Description-Content-Type: text/x-rst
48
48
  License-File: COPYING
49
49
  Requires-Dist: typing_extensions; python_version < "3.10"
50
+ Dynamic: license-file
50
51
 
51
52
  ********
52
53
  bytecode
@@ -1,6 +1,21 @@
1
1
  ChangeLog
2
2
  =========
3
3
 
4
+ 2025-04-14: Version 0.16.2
5
+ --------------------------
6
+
7
+ Bugfixes:
8
+
9
+ - fix ControlFlowGraph dead block detection by accounting for fall-through
10
+ edges. PR #161
11
+
12
+ 2025-01-21: Version 0.16.1
13
+ --------------------------
14
+
15
+ Bugfixes:
16
+
17
+ - fix flag inference for async code PR #157
18
+
4
19
 
5
20
  2024-10-28: Version 0.16.0
6
21
  --------------------------
@@ -58,7 +58,7 @@ class BasicBlock(_bytecode._InstrList[Union[Instr, SetLineno, TryBegin, TryEnd]]
58
58
  isinstance(self[i], Instr) for i in range(index, len(self))
59
59
  ):
60
60
  raise ValueError(
61
- "Only the last instruction of a basic " "block can be a jump"
61
+ "Only the last instruction of a basic block can be a jump"
62
62
  )
63
63
 
64
64
  if not isinstance(instr.arg, BasicBlock):
@@ -731,12 +731,18 @@ class ControlFlowGraph(_bytecode.BaseBytecode):
731
731
  if id(block) in seen_block_ids:
732
732
  continue
733
733
  seen_block_ids.add(id(block))
734
+ fall_through = True
734
735
  for i in block:
735
- if isinstance(i, Instr) and isinstance(i.arg, BasicBlock):
736
- stack.append(i.arg)
736
+ if isinstance(i, Instr):
737
+ if isinstance(i.arg, BasicBlock):
738
+ stack.append(i.arg)
739
+ if i.is_final():
740
+ fall_through = False
737
741
  elif isinstance(i, TryBegin):
738
742
  assert isinstance(i.target, BasicBlock)
739
743
  stack.append(i.target)
744
+ if fall_through and block.next_block:
745
+ stack.append(block.next_block)
740
746
 
741
747
  return [b for b in self if id(b) not in seen_block_ids]
742
748
 
@@ -83,7 +83,7 @@ class ConcreteInstr(BaseInstr[int]):
83
83
  # For ConcreteInstr the argument is always an integer
84
84
  _arg: int
85
85
 
86
- __slots__ = ("_size", "_extended_args")
86
+ __slots__ = ("_extended_args", "_size")
87
87
 
88
88
  def __init__(
89
89
  self,
@@ -221,7 +221,7 @@ class ExceptionTableEntry:
221
221
  #: before the exception itself (which is pushed as a single value)).
222
222
  push_lasti: bool
223
223
 
224
- __slots__ = ("start_offset", "stop_offset", "target", "stack_depth", "push_lasti")
224
+ __slots__ = ("push_lasti", "stack_depth", "start_offset", "stop_offset", "target")
225
225
 
226
226
  def __init__(
227
227
  self,
@@ -1,11 +1,13 @@
1
- import opcode
2
- import sys
1
+ import opcode as _opcode
3
2
  from enum import IntFlag
4
3
  from typing import Optional, Union
5
4
 
6
5
  # alias to keep the 'bytecode' variable free
7
6
  import bytecode as _bytecode
8
7
 
8
+ from .instr import DUAL_ARG_OPCODES, CellVar, FreeVar
9
+ from .utils import PY311, PY312, PY313
10
+
9
11
 
10
12
  class CompilerFlags(IntFlag):
11
13
  """Possible values of the co_flags attribute of Code object.
@@ -32,14 +34,31 @@ class CompilerFlags(IntFlag):
32
34
  # Generator defined in an async def function
33
35
  ASYNC_GENERATOR = 0x00200
34
36
 
35
- # __future__ flags
36
- # future flags changed in Python 3.9
37
- if sys.version_info < (3, 9):
38
- FUTURE_GENERATOR_STOP = 0x80000
39
- FUTURE_ANNOTATIONS = 0x100000
40
- else:
41
- FUTURE_GENERATOR_STOP = 0x800000
42
- FUTURE_ANNOTATIONS = 0x1000000
37
+ FUTURE_GENERATOR_STOP = 0x800000
38
+ FUTURE_ANNOTATIONS = 0x1000000
39
+
40
+
41
+ UNOPTIMIZED_OPCODES = (
42
+ _opcode.opmap["STORE_NAME"],
43
+ _opcode.opmap["LOAD_NAME"],
44
+ _opcode.opmap["DELETE_NAME"],
45
+ )
46
+
47
+ ASYNC_OPCODES = (
48
+ _opcode.opmap["GET_AWAITABLE"],
49
+ _opcode.opmap["GET_AITER"],
50
+ _opcode.opmap["GET_ANEXT"],
51
+ _opcode.opmap["BEFORE_ASYNC_WITH"],
52
+ *((_opcode.opmap["SETUP_ASYNC_WITH"],) if not PY311 else ()), # Removed in 3.11+
53
+ _opcode.opmap["END_ASYNC_FOR"],
54
+ *((_opcode.opmap["ASYNC_GEN_WRAP"],) if PY311 and not PY312 else ()), # New in 3.11
55
+ )
56
+
57
+ YIELD_VALUE_OPCODE = _opcode.opmap["YIELD_VALUE"]
58
+ GENERATOR_LIKE_OPCODES = (
59
+ *((_opcode.opmap["YIELD_FROM"],) if not PY311 else ()), # Removed in 3.11+
60
+ *((_opcode.opmap["RETURN_GENERATOR"],) if PY311 else ()), # Added in 3.11+
61
+ )
43
62
 
44
63
 
45
64
  def infer_flags(
@@ -70,8 +89,7 @@ def infer_flags(
70
89
  (_bytecode.Bytecode, _bytecode.ConcreteBytecode, _bytecode.ControlFlowGraph),
71
90
  ):
72
91
  msg = (
73
- "Expected a Bytecode, ConcreteBytecode or ControlFlowGraph "
74
- "instance not %s"
92
+ "Expected a Bytecode, ConcreteBytecode or ControlFlowGraph instance not %s"
75
93
  )
76
94
  raise ValueError(msg % bytecode)
77
95
 
@@ -80,26 +98,73 @@ def infer_flags(
80
98
  if isinstance(bytecode, _bytecode.ControlFlowGraph)
81
99
  else bytecode
82
100
  )
83
- instr_names = {
84
- i.name
85
- for i in instructions
86
- if not isinstance(
87
- i,
101
+
102
+ # Iterate over the instructions and inspect the arguments
103
+ is_concrete = isinstance(bytecode, _bytecode.ConcreteBytecode)
104
+ optimized = True
105
+ has_free = False if not is_concrete else bytecode.cellvars and bytecode.freevars
106
+ known_async = False
107
+ known_generator = False
108
+ possible_generator = False
109
+ instr_iter = iter(instructions)
110
+ for instr in instr_iter:
111
+ if isinstance(
112
+ instr,
88
113
  (
89
114
  _bytecode.SetLineno,
90
115
  _bytecode.Label,
91
116
  _bytecode.TryBegin,
92
117
  _bytecode.TryEnd,
93
118
  ),
94
- )
95
- }
119
+ ):
120
+ continue
121
+ opcode = instr.opcode
122
+ if opcode in UNOPTIMIZED_OPCODES:
123
+ optimized = False
124
+ elif opcode in ASYNC_OPCODES:
125
+ known_async = True
126
+ elif opcode == YIELD_VALUE_OPCODE:
127
+ if PY311:
128
+ while isinstance(
129
+ ni := next(instr_iter),
130
+ (
131
+ _bytecode.SetLineno,
132
+ _bytecode.Label,
133
+ _bytecode.TryBegin,
134
+ _bytecode.TryEnd,
135
+ ),
136
+ ):
137
+ pass
138
+ assert ni.name == "RESUME"
139
+ if (ni.arg & 3) != 3:
140
+ known_generator = True
141
+ else:
142
+ known_async = True
143
+ else:
144
+ known_generator = True
145
+ elif opcode in GENERATOR_LIKE_OPCODES:
146
+ possible_generator = True
147
+ elif opcode in _opcode.hasfree:
148
+ has_free = True
149
+ elif (
150
+ not is_concrete
151
+ and opcode in DUAL_ARG_OPCODES
152
+ and (isinstance(instr.arg[0], CellVar) or isinstance(instr.arg[1], CellVar))
153
+ ):
154
+ has_free = True
155
+ elif (
156
+ PY313
157
+ and opcode in _opcode.haslocal
158
+ and isinstance(instr.arg, (CellVar, FreeVar))
159
+ ):
160
+ has_free = True
96
161
 
97
162
  # Identify optimized code
98
- if not (instr_names & {"STORE_NAME", "LOAD_NAME", "DELETE_NAME"}):
163
+ if optimized:
99
164
  flags |= CompilerFlags.OPTIMIZED
100
165
 
101
166
  # Check for free variables
102
- if not (instr_names & {opcode.opname[i] for i in opcode.hasfree}):
167
+ if not has_free:
103
168
  flags |= CompilerFlags.NOFREE
104
169
 
105
170
  # Copy flags for which we cannot infer the right value
@@ -110,29 +175,19 @@ def infer_flags(
110
175
  | CompilerFlags.NESTED
111
176
  )
112
177
 
113
- sure_generator = instr_names & {"YIELD_VALUE"}
114
- maybe_generator = instr_names & {"YIELD_VALUE", "YIELD_FROM", "RETURN_GENERATOR"}
115
-
116
- sure_async = instr_names & {
117
- "GET_AWAITABLE",
118
- "GET_AITER",
119
- "GET_ANEXT",
120
- "BEFORE_ASYNC_WITH",
121
- "SETUP_ASYNC_WITH",
122
- "END_ASYNC_FOR",
123
- "ASYNC_GEN_WRAP", # New in 3.11
124
- }
125
-
126
178
  # If performing inference or forcing an async behavior, first inspect
127
179
  # the flags since this is the only way to identify iterable coroutines
128
180
  if is_async in (None, True):
129
- if bytecode.flags & CompilerFlags.COROUTINE:
130
- if sure_generator:
181
+ if (
182
+ bytecode.flags & CompilerFlags.COROUTINE
183
+ or bytecode.flags & CompilerFlags.ASYNC_GENERATOR
184
+ ):
185
+ if known_generator:
131
186
  flags |= CompilerFlags.ASYNC_GENERATOR
132
187
  else:
133
188
  flags |= CompilerFlags.COROUTINE
134
189
  elif bytecode.flags & CompilerFlags.ITERABLE_COROUTINE:
135
- if sure_async:
190
+ if known_async:
136
191
  msg = (
137
192
  "The ITERABLE_COROUTINE flag is set but bytecode that"
138
193
  "can only be used in async functions have been "
@@ -141,25 +196,20 @@ def infer_flags(
141
196
  )
142
197
  raise ValueError(msg)
143
198
  flags |= CompilerFlags.ITERABLE_COROUTINE
144
- elif bytecode.flags & CompilerFlags.ASYNC_GENERATOR:
145
- if not sure_generator:
146
- flags |= CompilerFlags.COROUTINE
147
- else:
148
- flags |= CompilerFlags.ASYNC_GENERATOR
149
199
 
150
200
  # If the code was not asynchronous before determine if it should now be
151
201
  # asynchronous based on the opcode and the is_async argument.
152
202
  else:
153
- if sure_async:
203
+ if known_async:
154
204
  # YIELD_FROM is not allowed in async generator
155
- if sure_generator:
205
+ if known_generator:
156
206
  flags |= CompilerFlags.ASYNC_GENERATOR
157
207
  else:
158
208
  flags |= CompilerFlags.COROUTINE
159
209
 
160
- elif maybe_generator:
210
+ elif known_generator or possible_generator:
161
211
  if is_async:
162
- if sure_generator:
212
+ if known_generator:
163
213
  flags |= CompilerFlags.ASYNC_GENERATOR
164
214
  else:
165
215
  flags |= CompilerFlags.COROUTINE
@@ -172,14 +222,14 @@ def infer_flags(
172
222
  # If the code should not be asynchronous, check first it is possible and
173
223
  # next set the GENERATOR flag if relevant
174
224
  else:
175
- if sure_async:
225
+ if known_async:
176
226
  raise ValueError(
177
227
  "The is_async argument is False but bytecodes "
178
228
  "that can only be used in async functions have "
179
229
  "been detected."
180
230
  )
181
231
 
182
- if maybe_generator:
232
+ if known_generator or possible_generator:
183
233
  flags |= CompilerFlags.GENERATOR
184
234
 
185
235
  flags |= bytecode.flags & CompilerFlags.FUTURE_GENERATOR_STOP
@@ -269,13 +269,12 @@ class FreeVar(_Variable):
269
269
  def _check_arg_int(arg: Any, name: str) -> TypeGuard[int]:
270
270
  if not isinstance(arg, int):
271
271
  raise TypeError(
272
- "operation %s argument must be an int, "
273
- "got %s" % (name, type(arg).__name__)
272
+ "operation %s argument must be an int, got %s" % (name, type(arg).__name__)
274
273
  )
275
274
 
276
275
  if not (0 <= arg <= 2147483647):
277
276
  raise ValueError(
278
- "operation %s argument must be in " "the range 0..2,147,483,647" % name
277
+ "operation %s argument must be in the range 0..2,147,483,647" % name
279
278
  )
280
279
 
281
280
  return True
@@ -337,15 +336,15 @@ STATIC_STACK_EFFECTS: Dict[str, Tuple[int, int]] = {
337
336
  "CHECK_EXC_MATCH": (-2, 2), # (TOS1, TOS) -> (TOS1, bool)
338
337
  "CHECK_EG_MATCH": (-2, 2), # (TOS, TOS1) -> non-matched, matched or TOS1, None)
339
338
  "PREP_RERAISE_STAR": (-2, 1), # (TOS1, TOS) -> new exception group)
340
- **{k: (-1, 1) for k in (o for o in _opcode.opmap if (o.startswith("UNARY_")))},
341
- **{
342
- k: (-2, 1)
343
- for k in (
339
+ **dict.fromkeys((o for o in _opcode.opmap if o.startswith("UNARY_")), (-1, 1)),
340
+ **dict.fromkeys(
341
+ (
344
342
  o
345
343
  for o in _opcode.opmap
346
- if (o.startswith("BINARY_") or o.startswith("INPLACE_"))
347
- )
348
- },
344
+ if o.startswith("BINARY_") or o.startswith("INPLACE_")
345
+ ),
346
+ (-2, 1),
347
+ ),
349
348
  # Python 3.12 changes not covered by dis.stack_effect
350
349
  "BINARY_SLICE": (-3, 1),
351
350
  # "STORE_SLICE" handled by dis.stack_effect
@@ -441,7 +440,7 @@ class InstrLocation:
441
440
  #: End column offset at which the instruction corresponds (Python 3.11+ only)
442
441
  end_col_offset: Optional[int]
443
442
 
444
- __slots__ = ["lineno", "end_lineno", "col_offset", "end_col_offset"]
443
+ __slots__ = ["col_offset", "end_col_offset", "end_lineno", "lineno"]
445
444
 
446
445
  def __init__(
447
446
  self,
@@ -523,7 +522,7 @@ class SetLineno:
523
522
 
524
523
 
525
524
  class TryBegin:
526
- __slots__ = ("target", "push_lasti", "stack_depth")
525
+ __slots__ = ("push_lasti", "stack_depth", "target")
527
526
 
528
527
  def __init__(
529
528
  self,
@@ -556,7 +555,7 @@ A = TypeVar("A", bound=object)
556
555
  class BaseInstr(Generic[A]):
557
556
  """Abstract instruction."""
558
557
 
559
- __slots__ = ("_name", "_opcode", "_arg", "_location")
558
+ __slots__ = ("_arg", "_location", "_name", "_opcode")
560
559
 
561
560
  # Work around an issue with the default value of arg
562
561
  def __init__(
@@ -900,13 +899,9 @@ class Instr(BaseInstr[InstrArg]):
900
899
 
901
900
  elif opcode in _opcode.hasconst:
902
901
  if isinstance(arg, Label):
903
- raise ValueError(
904
- "label argument cannot be used " "in %s operation" % name
905
- )
902
+ raise ValueError("label argument cannot be used in %s operation" % name)
906
903
  if isinstance(arg, _bytecode.BasicBlock):
907
- raise ValueError(
908
- "block argument cannot be used " "in %s operation" % name
909
- )
904
+ raise ValueError("block argument cannot be used in %s operation" % name)
910
905
 
911
906
  elif opcode in _opcode.hascompare:
912
907
  if not isinstance(arg, Compare):
@@ -5,7 +5,7 @@ from collections import namedtuple
5
5
  #: A namedtuple of the version info for the current release.
6
6
  _version_info = namedtuple("_version_info", "major minor micro status")
7
7
 
8
- parts = "0.16.0".split(".", 3)
8
+ parts = "0.16.2".split(".", 3)
9
9
  version_info = _version_info(
10
10
  int(parts[0]),
11
11
  int(parts[1]),
@@ -16,4 +16,4 @@ version_info = _version_info(
16
16
  # Remove everything but the 'version_info' from this module.
17
17
  del namedtuple, _version_info, parts
18
18
 
19
- __version__ = "0.16.0"
19
+ __version__ = "0.16.2"
@@ -1,6 +1,6 @@
1
- Metadata-Version: 2.1
1
+ Metadata-Version: 2.4
2
2
  Name: bytecode
3
- Version: 0.16.0
3
+ Version: 0.16.2
4
4
  Summary: Python module to generate and modify bytecode
5
5
  Author-email: Victor Stinner <victor.stinner@gmail.com>
6
6
  Maintainer-email: "Matthieu C. Dartiailh" <m.dartiailh@gmail.com>
@@ -47,6 +47,7 @@ Requires-Python: >=3.8
47
47
  Description-Content-Type: text/x-rst
48
48
  License-File: COPYING
49
49
  Requires-Dist: typing_extensions; python_version < "3.10"
50
+ Dynamic: license-file
50
51
 
51
52
  ********
52
53
  bytecode
@@ -668,18 +668,12 @@ class BytecodeBlocksFunctionalTests(TestCase):
668
668
  )
669
669
  elif PY311:
670
670
  # jump is relative not absolute
671
- expected = (
672
- b"|\x05" b"r\x02" b"|\x00" b"}\x05" b"d\x01" b"}\x05" b"|\x05" b"S\x00"
673
- )
671
+ expected = b"|\x05r\x02|\x00}\x05d\x01}\x05|\x05S\x00"
674
672
  elif OFFSET_AS_INSTRUCTION:
675
673
  # The argument of the jump is divided by 2
676
- expected = (
677
- b"|\x05" b"r\x04" b"|\x00" b"}\x05" b"d\x01" b"}\x05" b"|\x05" b"S\x00"
678
- )
674
+ expected = b"|\x05r\x04|\x00}\x05d\x01}\x05|\x05S\x00"
679
675
  else:
680
- expected = (
681
- b"|\x05" b"r\x08" b"|\x00" b"}\x05" b"d\x01" b"}\x05" b"|\x05" b"S\x00"
682
- )
676
+ expected = b"|\x05r\x08|\x00}\x05d\x01}\x05|\x05S\x00"
683
677
 
684
678
  code = bytecode.to_code()
685
679
  self.assertEqual(code.co_consts, (None, 3))
@@ -718,6 +712,20 @@ class BytecodeBlocksFunctionalTests(TestCase):
718
712
  other_block = BasicBlock()
719
713
  self.assertRaises(ValueError, blocks.get_block_index, other_block)
720
714
 
715
+ def test_get_dead_blocks(self):
716
+ def condition():
717
+ pass
718
+
719
+ def test():
720
+ if condition():
721
+ print("1")
722
+ else:
723
+ print("2")
724
+
725
+ bytecode = Bytecode.from_code(test.__code__)
726
+ cfg = ControlFlowGraph.from_bytecode(bytecode)
727
+ assert len(cfg.get_dead_blocks()) == 0
728
+
721
729
 
722
730
  class CFGStacksizeComputationTests(TestCase):
723
731
  def check_stack_size(self, func):
@@ -1,6 +1,7 @@
1
1
  #!/usr/bin/env python3
2
2
  import sys
3
3
  import unittest
4
+ from copy import copy
4
5
 
5
6
  from bytecode import (
6
7
  Bytecode,
@@ -11,10 +12,52 @@ from bytecode import (
11
12
  )
12
13
  from bytecode.flags import infer_flags
13
14
  from bytecode.instr import UNSET, FreeVar, Instr
15
+ from bytecode.utils import PY311, PY312
14
16
 
15
- # Py 3.11
16
- # - new opcodes could modify inference:
17
- # - SEND, ASYNC_GEN_WRAP, RETURN_GENERATOR,
17
+
18
+ def trivial():
19
+ pass
20
+
21
+
22
+ def _fact(a):
23
+ def inner():
24
+ return a
25
+
26
+ return inner
27
+
28
+
29
+ hasfree = _fact(1)
30
+
31
+
32
+ def gen():
33
+ yield 1
34
+
35
+
36
+ async def trivial_async():
37
+ pass
38
+
39
+
40
+ async def async_await(a):
41
+ await a
42
+
43
+
44
+ async def async_with_comprehension():
45
+ return [await i for i in range(10)]
46
+
47
+
48
+ async def async_generator():
49
+ yield 1
50
+
51
+
52
+ FLAG_INFERENCE_TEST_CASES = [
53
+ trivial,
54
+ hasfree,
55
+ gen,
56
+ trivial_async,
57
+ async_await,
58
+ async_with_comprehension,
59
+ async_generator,
60
+ ]
18
61
 
19
62
 
20
63
  class FlagsTests(unittest.TestCase):
@@ -58,16 +101,31 @@ class FlagsTests(unittest.TestCase):
58
101
  self.assertFalse(bool(code.flags & CompilerFlags.OPTIMIZED))
59
102
  self.assertFalse(bool(code.flags & CompilerFlags.NOFREE))
60
103
 
104
+ def test_function_rountrip(self):
105
+ for f in FLAG_INFERENCE_TEST_CASES:
106
+ for cls in (Bytecode, ConcreteBytecode):
107
+ with self.subTest(f"Testing {f.__name__} with {cls}"):
108
+ b = cls.from_code(f.__code__)
109
+ existing = copy(b.flags)
110
+ b.update_flags()
111
+ # NOTE: as far as I can tell NOFREE is not used by CPython anymore
112
+ # it shows up nowhere in the interpreter logic and only exist in
113
+ # dis and inspect...
114
+ self.assertEqual(
115
+ existing & ~CompilerFlags.NOFREE,
116
+ b.flags & ~CompilerFlags.NOFREE,
117
+ )
118
+
61
119
  def test_async_gen_no_flag_is_async_None(self):
62
120
  # Test inference in the absence of any flag set on the bytecode
63
121
 
64
122
  # Infer generator
65
123
  code = ConcreteBytecode()
66
124
  code.append(
67
- ConcreteInstr("YIELD_VALUE", 0)
68
- if sys.version_info >= (3, 12)
69
- else ConcreteInstr("YIELD_VALUE")
125
+ ConcreteInstr("YIELD_VALUE", 0) if PY312 else ConcreteInstr("YIELD_VALUE")
70
126
  )
127
+ if PY311:
128
+ code.append(ConcreteInstr("RESUME", 1))
71
129
  code.update_flags()
72
130
  self.assertTrue(bool(code.flags & CompilerFlags.GENERATOR))
73
131
 
@@ -80,24 +138,22 @@ class FlagsTests(unittest.TestCase):
80
138
  self.assertTrue(bool(code.flags & CompilerFlags.COROUTINE))
81
139
 
82
140
  # Infer coroutine or async generator
83
- for i, expected in (
84
- ("YIELD_VALUE", CompilerFlags.ASYNC_GENERATOR),
85
- ("YIELD_FROM", CompilerFlags.COROUTINE),
141
+ for i, r, expected in (
142
+ ("YIELD_VALUE", 1, CompilerFlags.ASYNC_GENERATOR),
143
+ ("YIELD_VALUE", 2, CompilerFlags.ASYNC_GENERATOR),
144
+ # YIELD_VALUE is used for normal await flow in Py 3.11+ when followed
145
+ # by a RESUME whose lowest two bits are set to 3
146
+ *((("YIELD_VALUE", 3, CompilerFlags.COROUTINE),) if PY311 else ()),
147
+ ("YIELD_FROM", 0, CompilerFlags.COROUTINE),
86
148
  ):
87
149
  with self.subTest(i):
88
- if sys.version_info >= (3, 11) and i == "YIELD_FROM":
150
+ if PY311 and i == "YIELD_FROM":
89
151
  self.skipTest("YIELD_FROM does not exist on 3.11")
90
152
  code = ConcreteBytecode()
91
- code.append(
92
- ConcreteInstr(
93
- "GET_AWAITABLE", 0 if sys.version_info >= (3, 11) else UNSET
94
- )
95
- )
96
- code.append(
97
- ConcreteInstr(i, 0)
98
- if sys.version_info >= (3, 12)
99
- else ConcreteInstr(i)
100
- )
153
+ code.append(ConcreteInstr("GET_AWAITABLE", 0 if PY311 else UNSET))
154
+ code.append(ConcreteInstr(i, 0) if PY312 else ConcreteInstr(i))
155
+ if PY311:
156
+ code.append(ConcreteInstr("RESUME", r))
101
157
  code.update_flags()
102
158
  self.assertTrue(bool(code.flags & expected))
103
159
 
@@ -110,21 +166,23 @@ class FlagsTests(unittest.TestCase):
110
166
  self.assertTrue(bool(code.flags & CompilerFlags.COROUTINE))
111
167
 
112
168
  # Infer coroutine or async generator
113
- for i, expected in (
114
- ("YIELD_VALUE", CompilerFlags.ASYNC_GENERATOR),
115
- ("YIELD_FROM", CompilerFlags.COROUTINE),
169
+ for i, r, expected in (
170
+ ("YIELD_VALUE", 1, CompilerFlags.ASYNC_GENERATOR),
171
+ ("YIELD_VALUE", 2, CompilerFlags.ASYNC_GENERATOR),
172
+ # YIELD_VALUE is used for normal await flow in Py 3.11+ when followed
173
+ # by a RESUME whose lowest two bits are set to 3
174
+ *((("YIELD_VALUE", 3, CompilerFlags.COROUTINE),) if PY311 else ()),
175
+ ("YIELD_FROM", 0, CompilerFlags.COROUTINE),
116
176
  ):
117
177
  with self.subTest(i):
118
- if sys.version_info >= (3, 11) and i == "YIELD_FROM":
178
+ if PY311 and i == "YIELD_FROM":
119
179
  self.skipTest("YIELD_FROM does not exist on 3.11")
120
180
  code = ConcreteBytecode()
121
- code.append(
122
- ConcreteInstr(i, 0)
123
- if sys.version_info >= (3, 12)
124
- else ConcreteInstr(i)
125
- )
181
+ code.append(ConcreteInstr(i, 0) if PY312 else ConcreteInstr(i))
182
+ if PY311:
183
+ code.append(ConcreteInstr("RESUME", r))
126
184
  code.update_flags(is_async=True)
127
- self.assertTrue(bool(code.flags & expected))
185
+ self.assertEqual(code.flags & expected, expected)
128
186
 
129
187
  def test_async_gen_no_flag_is_async_False(self):
130
188
  # Test inference when we request a non-async function
@@ -132,10 +190,10 @@ class FlagsTests(unittest.TestCase):
132
190
  # Infer generator
133
191
  code = ConcreteBytecode()
134
192
  code.append(
135
- ConcreteInstr("YIELD_VALUE", 0)
136
- if sys.version_info >= (3, 12)
137
- else ConcreteInstr("YIELD_VALUE")
193
+ ConcreteInstr("YIELD_VALUE", 0) if PY312 else ConcreteInstr("YIELD_VALUE")
138
194
  )
195
+ if PY311:
196
+ code.append(ConcreteInstr("RESUME", 1))
139
197
  code.flags = CompilerFlags(CompilerFlags.COROUTINE)
140
198
  code.update_flags(is_async=False)
141
199
  self.assertTrue(bool(code.flags & CompilerFlags.GENERATOR))
@@ -157,9 +215,11 @@ class FlagsTests(unittest.TestCase):
157
215
  code = ConcreteBytecode()
158
216
  code.append(
159
217
  ConcreteInstr("YIELD_VALUE", 0)
160
- if sys.version_info >= (3, 12)
218
+ if PY312
161
219
  else ConcreteInstr("YIELD_VALUE")
162
220
  )
221
+ if PY311:
222
+ code.append(ConcreteInstr("RESUME", 1))
163
223
  for f, expected in (
164
224
  (CompilerFlags.COROUTINE, CompilerFlags.ASYNC_GENERATOR),
165
225
  (CompilerFlags.ASYNC_GENERATOR, CompilerFlags.ASYNC_GENERATOR),
@@ -170,7 +230,7 @@ class FlagsTests(unittest.TestCase):
170
230
  self.assertTrue(bool(code.flags & expected))
171
231
 
172
232
  # Infer coroutine
173
- if sys.version_info < (3, 11):
233
+ if not PY311:
174
234
  code = ConcreteBytecode()
175
235
  code.append(ConcreteInstr("YIELD_FROM"))
176
236
  for f, expected in (
@@ -187,11 +247,7 @@ class FlagsTests(unittest.TestCase):
187
247
 
188
248
  # Crash on ITERABLE_COROUTINE with async bytecode
189
249
  code = ConcreteBytecode()
190
- code.append(
191
- ConcreteInstr(
192
- "GET_AWAITABLE", 0 if sys.version_info >= (3, 11) else UNSET
193
- )
194
- )
250
+ code.append(ConcreteInstr("GET_AWAITABLE", 0 if PY311 else UNSET))
195
251
  code.flags = CompilerFlags(CompilerFlags.ITERABLE_COROUTINE)
196
252
  with self.assertRaises(ValueError):
197
253
  code.update_flags(is_async=is_async)
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
@@ -1,14 +1,14 @@
1
1
  __all__ = [
2
- "Label",
3
- "Instr",
4
- "SetLineno",
2
+ "BinaryOp",
5
3
  "Bytecode",
6
- "ConcreteInstr",
4
+ "Compare",
5
+ "CompilerFlags",
7
6
  "ConcreteBytecode",
7
+ "ConcreteInstr",
8
8
  "ControlFlowGraph",
9
- "CompilerFlags",
10
- "Compare",
11
- "BinaryOp",
9
+ "Instr",
10
+ "Label",
11
+ "SetLineno",
12
12
  "__version__",
13
13
  ]
14
14
 
File without changes
File without changes
File without changes
File without changes
File without changes