bytecode 0.17.0__tar.gz → 0.18.0__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.17.0 → bytecode-0.18.0}/.github/workflows/cis.yml +6 -10
  2. {bytecode-0.17.0 → bytecode-0.18.0}/.github/workflows/docs.yml +2 -2
  3. {bytecode-0.17.0 → bytecode-0.18.0}/.github/workflows/frameworks.yml +3 -3
  4. {bytecode-0.17.0 → bytecode-0.18.0}/.github/workflows/release.yml +10 -10
  5. {bytecode-0.17.0 → bytecode-0.18.0}/.readthedocs.yaml +2 -2
  6. {bytecode-0.17.0/src/bytecode.egg-info → bytecode-0.18.0}/PKG-INFO +2 -4
  7. {bytecode-0.17.0 → bytecode-0.18.0}/doc/changelog.rst +22 -0
  8. {bytecode-0.17.0 → bytecode-0.18.0}/pyproject.toml +1 -3
  9. {bytecode-0.17.0 → bytecode-0.18.0}/src/bytecode/bytecode.py +32 -18
  10. {bytecode-0.17.0 → bytecode-0.18.0}/src/bytecode/cfg.py +80 -54
  11. {bytecode-0.17.0 → bytecode-0.18.0}/src/bytecode/concrete.py +246 -385
  12. {bytecode-0.17.0 → bytecode-0.18.0}/src/bytecode/flags.py +21 -28
  13. {bytecode-0.17.0 → bytecode-0.18.0}/src/bytecode/instr.py +281 -207
  14. {bytecode-0.17.0 → bytecode-0.18.0}/src/bytecode/utils.py +0 -2
  15. {bytecode-0.17.0 → bytecode-0.18.0}/src/bytecode/version.py +2 -2
  16. {bytecode-0.17.0 → bytecode-0.18.0/src/bytecode.egg-info}/PKG-INFO +2 -4
  17. {bytecode-0.17.0 → bytecode-0.18.0}/tests/__init__.py +13 -54
  18. {bytecode-0.17.0 → bytecode-0.18.0}/tests/exception_handling_cases.py +2 -19
  19. {bytecode-0.17.0 → bytecode-0.18.0}/tests/frameworks/module.py +3 -1
  20. {bytecode-0.17.0 → bytecode-0.18.0}/tests/test_bytecode.py +28 -160
  21. {bytecode-0.17.0 → bytecode-0.18.0}/tests/test_cfg.py +73 -108
  22. {bytecode-0.17.0 → bytecode-0.18.0}/tests/test_code.py +13 -0
  23. {bytecode-0.17.0 → bytecode-0.18.0}/tests/test_concrete.py +61 -217
  24. {bytecode-0.17.0 → bytecode-0.18.0}/tests/test_flags.py +12 -43
  25. {bytecode-0.17.0 → bytecode-0.18.0}/tests/test_instr.py +12 -22
  26. {bytecode-0.17.0 → bytecode-0.18.0}/tests/test_misc.py +7 -148
  27. {bytecode-0.17.0 → bytecode-0.18.0}/.coveragerc +0 -0
  28. {bytecode-0.17.0 → bytecode-0.18.0}/.github/FUNDING.yml +0 -0
  29. {bytecode-0.17.0 → bytecode-0.18.0}/.github/dependabot.yml +0 -0
  30. {bytecode-0.17.0 → bytecode-0.18.0}/.gitignore +0 -0
  31. {bytecode-0.17.0 → bytecode-0.18.0}/.pre-commit-config.yaml +0 -0
  32. {bytecode-0.17.0 → bytecode-0.18.0}/COPYING +0 -0
  33. {bytecode-0.17.0 → bytecode-0.18.0}/MANIFEST.in +0 -0
  34. {bytecode-0.17.0 → bytecode-0.18.0}/README.rst +0 -0
  35. {bytecode-0.17.0 → bytecode-0.18.0}/TODO.rst +0 -0
  36. {bytecode-0.17.0 → bytecode-0.18.0}/codecov.yml +0 -0
  37. {bytecode-0.17.0 → bytecode-0.18.0}/doc/Makefile +0 -0
  38. {bytecode-0.17.0 → bytecode-0.18.0}/doc/api.rst +0 -0
  39. {bytecode-0.17.0 → bytecode-0.18.0}/doc/byteplay_codetransformer.rst +0 -0
  40. {bytecode-0.17.0 → bytecode-0.18.0}/doc/cfg.rst +0 -0
  41. {bytecode-0.17.0 → bytecode-0.18.0}/doc/conf.py +0 -0
  42. {bytecode-0.17.0 → bytecode-0.18.0}/doc/index.rst +0 -0
  43. {bytecode-0.17.0 → bytecode-0.18.0}/doc/make.bat +0 -0
  44. {bytecode-0.17.0 → bytecode-0.18.0}/doc/requirements.txt +0 -0
  45. {bytecode-0.17.0 → bytecode-0.18.0}/doc/todo.rst +0 -0
  46. {bytecode-0.17.0 → bytecode-0.18.0}/doc/usage.rst +0 -0
  47. {bytecode-0.17.0 → bytecode-0.18.0}/scripts/frameworks/boto3/run.sh +0 -0
  48. {bytecode-0.17.0 → bytecode-0.18.0}/scripts/frameworks/boto3/setup.sh +0 -0
  49. {bytecode-0.17.0 → bytecode-0.18.0}/setup.cfg +0 -0
  50. {bytecode-0.17.0 → bytecode-0.18.0}/src/bytecode/__init__.py +0 -0
  51. {bytecode-0.17.0 → bytecode-0.18.0}/src/bytecode/py.typed +0 -0
  52. {bytecode-0.17.0 → bytecode-0.18.0}/src/bytecode.egg-info/SOURCES.txt +0 -0
  53. {bytecode-0.17.0 → bytecode-0.18.0}/src/bytecode.egg-info/dependency_links.txt +0 -0
  54. {bytecode-0.17.0 → bytecode-0.18.0}/src/bytecode.egg-info/requires.txt +0 -0
  55. {bytecode-0.17.0 → bytecode-0.18.0}/src/bytecode.egg-info/top_level.txt +0 -0
  56. {bytecode-0.17.0 → bytecode-0.18.0}/tests/cell_free_vars_cases.py +0 -0
  57. {bytecode-0.17.0 → bytecode-0.18.0}/tests/frameworks/function.py +0 -0
  58. {bytecode-0.17.0 → bytecode-0.18.0}/tests/frameworks/sitecustomize.py +0 -0
  59. {bytecode-0.17.0 → bytecode-0.18.0}/tests/long_lines_example.py +0 -0
  60. {bytecode-0.17.0 → bytecode-0.18.0}/tests/util_annotation.py +0 -0
  61. {bytecode-0.17.0 → bytecode-0.18.0}/tox.ini +0 -0
@@ -20,9 +20,9 @@ jobs:
20
20
  name: Lint code
21
21
  runs-on: ubuntu-latest
22
22
  steps:
23
- - uses: actions/checkout@v5
23
+ - uses: actions/checkout@v6
24
24
  - name: Set up Python
25
- uses: actions/setup-python@v5
25
+ uses: actions/setup-python@v6
26
26
  with:
27
27
  python-version: "3.12"
28
28
  - name: Install tools
@@ -42,26 +42,22 @@ jobs:
42
42
  fail-fast: false
43
43
  matrix:
44
44
  include:
45
- - python-version: "3.9"
46
- toxenv: py39
47
- - python-version: "3.10"
48
- toxenv: py310
49
45
  - python-version: "3.11"
50
46
  toxenv: py311
51
47
  - python-version: "3.12"
52
48
  toxenv: py312
53
49
  - python-version: "3.13"
54
50
  toxenv: py313
55
- - python-version: "3.14-dev"
51
+ - python-version: "3.14"
56
52
  toxenv: py314
57
53
  steps:
58
- - uses: actions/checkout@v5
54
+ - uses: actions/checkout@v6
59
55
  - name: Get history and tags for SCM versioning to work
60
56
  run: |
61
57
  git fetch --prune --unshallow
62
58
  git fetch --depth=1 origin +refs/tags/*:refs/tags/*
63
59
  - name: Set up Python ${{ matrix.python-version }}
64
- uses: actions/setup-python@v5
60
+ uses: actions/setup-python@v6
65
61
  with:
66
62
  python-version: ${{ matrix.python-version }}
67
63
  - name: Install dependencies
@@ -74,7 +70,7 @@ jobs:
74
70
  run: |
75
71
  tox
76
72
  - name: Upload coverage to Codecov
77
- uses: codecov/codecov-action@v5
73
+ uses: codecov/codecov-action@v6
78
74
  if: github.event_name != 'schedule'
79
75
  with:
80
76
  token: ${{ secrets.CODECOV_TOKEN }}
@@ -19,13 +19,13 @@ jobs:
19
19
  name: Docs building
20
20
  runs-on: ubuntu-latest
21
21
  steps:
22
- - uses: actions/checkout@v5
22
+ - uses: actions/checkout@v6
23
23
  - name: Get history and tags for SCM versioning to work
24
24
  run: |
25
25
  git fetch --prune --unshallow
26
26
  git fetch --depth=1 origin +refs/tags/*:refs/tags/*
27
27
  - name: Set up Python
28
- uses: actions/setup-python@v5
28
+ uses: actions/setup-python@v6
29
29
  with:
30
30
  python-version: '3.x'
31
31
  - name: Install dependencies
@@ -21,15 +21,15 @@ jobs:
21
21
  strategy:
22
22
  fail-fast: false
23
23
  matrix:
24
- python-version: ["3.9", "3.10", "3.11", "3.12", "3.13", "3.14"]
24
+ python-version: ["3.11", "3.12", "3.13", "3.14"]
25
25
 
26
26
  steps:
27
- - uses: actions/checkout@v5
27
+ - uses: actions/checkout@v6
28
28
  with:
29
29
  fetch-depth: 0
30
30
 
31
31
  - name: Set up Python
32
- uses: actions/setup-python@v5
32
+ uses: actions/setup-python@v6
33
33
  with:
34
34
  python-version: ${{ matrix.python-version }}-dev
35
35
 
@@ -13,13 +13,13 @@ jobs:
13
13
  runs-on: ubuntu-latest
14
14
  steps:
15
15
  - name: Checkout
16
- uses: actions/checkout@v5
16
+ uses: actions/checkout@v6
17
17
  - name: Get history and tags for SCM versioning to work
18
18
  run: |
19
19
  git fetch --prune --unshallow
20
20
  git fetch --depth=1 origin +refs/tags/*:refs/tags/*
21
21
  - name: Setup Python
22
- uses: actions/setup-python@v5
22
+ uses: actions/setup-python@v6
23
23
  with:
24
24
  python-version: '3.x'
25
25
  - name: Build sdist
@@ -33,7 +33,7 @@ jobs:
33
33
  pip install dist/*.tar.gz
34
34
  python -X dev -m pytest tests
35
35
  - name: Store artifacts
36
- uses: actions/upload-artifact@v4
36
+ uses: actions/upload-artifact@v7
37
37
  with:
38
38
  name: cibw-sdist
39
39
  path: dist/*
@@ -43,13 +43,13 @@ jobs:
43
43
  runs-on: ubuntu-latest
44
44
  steps:
45
45
  - name: Checkout
46
- uses: actions/checkout@v5
46
+ uses: actions/checkout@v6
47
47
  - name: Get history and tags for SCM versioning to work
48
48
  run: |
49
49
  git fetch --prune --unshallow
50
50
  git fetch --depth=1 origin +refs/tags/*:refs/tags/*
51
51
  - name: Setup Python
52
- uses: actions/setup-python@v5
52
+ uses: actions/setup-python@v6
53
53
  with:
54
54
  python-version: '3.x'
55
55
  - name: Build wheels
@@ -63,7 +63,7 @@ jobs:
63
63
  pip install dist/*.whl
64
64
  python -X dev -m pytest tests
65
65
  - name: Store artifacts
66
- uses: actions/upload-artifact@v4
66
+ uses: actions/upload-artifact@v7
67
67
  with:
68
68
  name: cibw-wheel
69
69
  path: dist/*.whl
@@ -74,12 +74,12 @@ jobs:
74
74
  runs-on: ubuntu-latest
75
75
  environment:
76
76
  name: pypi
77
- url: https://pypi.org/p/kiwisolver
77
+ url: https://pypi.org/p/bytecode
78
78
  permissions:
79
79
  id-token: write
80
80
  steps:
81
81
  - name: Download all the dists
82
- uses: actions/download-artifact@v5.0.0
82
+ uses: actions/download-artifact@v8.0.1
83
83
  with:
84
84
  pattern: cibw-*
85
85
  path: dist
@@ -101,13 +101,13 @@ jobs:
101
101
 
102
102
  steps:
103
103
  - name: Download all the dists
104
- uses: actions/download-artifact@v5.0.0
104
+ uses: actions/download-artifact@v8.0.1
105
105
  with:
106
106
  pattern: cibw-*
107
107
  path: dist
108
108
  merge-multiple: true
109
109
  - name: Sign the dists with Sigstore
110
- uses: sigstore/gh-action-sigstore-python@v3.0.1
110
+ uses: sigstore/gh-action-sigstore-python@v3.3.0
111
111
  with:
112
112
  inputs: >-
113
113
  ./dist/*.tar.gz
@@ -7,9 +7,9 @@ version: 2
7
7
 
8
8
  # Set the version of Python and other tools you might need
9
9
  build:
10
- os: ubuntu-20.04
10
+ os: ubuntu-24.04
11
11
  tools:
12
- python: "3.9"
12
+ python: "3.12"
13
13
 
14
14
  # Build documentation in the docs/source directory with Sphinx
15
15
  sphinx:
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: bytecode
3
- Version: 0.17.0
3
+ Version: 0.18.0
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>
@@ -36,14 +36,12 @@ Classifier: License :: OSI Approved :: MIT License
36
36
  Classifier: Natural Language :: English
37
37
  Classifier: Operating System :: OS Independent
38
38
  Classifier: Programming Language :: Python :: 3
39
- Classifier: Programming Language :: Python :: 3.9
40
- Classifier: Programming Language :: Python :: 3.10
41
39
  Classifier: Programming Language :: Python :: 3.11
42
40
  Classifier: Programming Language :: Python :: 3.12
43
41
  Classifier: Programming Language :: Python :: 3.13
44
42
  Classifier: Programming Language :: Python :: 3.14
45
43
  Classifier: Topic :: Software Development :: Libraries :: Python Modules
46
- Requires-Python: >=3.9
44
+ Requires-Python: >=3.11
47
45
  Description-Content-Type: text/x-rst
48
46
  License-File: COPYING
49
47
  Requires-Dist: typing_extensions; python_version < "3.10"
@@ -1,6 +1,28 @@
1
1
  ChangeLog
2
2
  =========
3
3
 
4
+ 03-06-2026: Version 0.18.0
5
+ --------------------------
6
+
7
+ - drop support for Python 3.9 and 3.10 PR #180
8
+ - Replace string literal type annotations with postponed evaluation using
9
+ ``from __future__ import annotations`` PR #191
10
+ - multiple performance improvements
11
+ PRs #172, #193, #196, #194, #197, #198, #199, #200, #201,
12
+ #202, #203
13
+
14
+ Breaking changes:
15
+
16
+ - ``BasicBlock``, ``Bytecode``, and ``ConcreteBytecode`` now validate inserted
17
+ instructions at insertion time (``append``, ``extend``, ``insert``,
18
+ ``__setitem__``) rather than during iteration. Code that relied on catching
19
+ ``ValueError`` from ``list(block)`` or ``for instr in block:`` must wrap the
20
+ insertion call instead. PR #199
21
+
22
+ Bugfixes:
23
+
24
+ - Fix handling of END_ASYNC_FOR which is a backward jump PR #179
25
+
4
26
  03-09-2025: Version 0.17.0
5
27
  --------------------------
6
28
 
@@ -2,7 +2,7 @@
2
2
  name = "bytecode"
3
3
  description = "Python module to generate and modify bytecode"
4
4
  readme = "README.rst"
5
- requires-python = ">=3.9"
5
+ requires-python = ">=3.11"
6
6
  license = { file = "COPYING" }
7
7
  authors = [{ name = "Victor Stinner", email = "victor.stinner@gmail.com" }]
8
8
  maintainers = [{ name = "Matthieu C. Dartiailh", email = "m.dartiailh@gmail.com" }]
@@ -13,8 +13,6 @@
13
13
  "Natural Language :: English",
14
14
  "Operating System :: OS Independent",
15
15
  "Programming Language :: Python :: 3",
16
- "Programming Language :: Python :: 3.9",
17
- "Programming Language :: Python :: 3.10",
18
16
  "Programming Language :: Python :: 3.11",
19
17
  "Programming Language :: Python :: 3.12",
20
18
  "Programming Language :: Python :: 3.13",
@@ -1,5 +1,5 @@
1
- # alias to keep the 'bytecode' variable free
2
- import sys
1
+ from __future__ import annotations
2
+
3
3
  import types
4
4
  from abc import abstractmethod
5
5
  from typing import (
@@ -16,6 +16,7 @@ from typing import (
16
16
  overload,
17
17
  )
18
18
 
19
+ # alias to keep the 'bytecode' variable free
19
20
  import bytecode as _bytecode
20
21
  from bytecode.flags import CompilerFlags, infer_flags
21
22
  from bytecode.instr import (
@@ -28,7 +29,6 @@ from bytecode.instr import (
28
29
  TryBegin,
29
30
  TryEnd,
30
31
  )
31
- from bytecode.utils import PY311
32
32
 
33
33
 
34
34
  class BaseBytecode:
@@ -50,7 +50,7 @@ class BaseBytecode:
50
50
  self.freevars: List[str] = []
51
51
  self._flags: CompilerFlags = CompilerFlags(0)
52
52
 
53
- def _copy_attr_from(self, bytecode: "BaseBytecode") -> None:
53
+ def _copy_attr_from(self, bytecode: BaseBytecode) -> None:
54
54
  self.argcount = bytecode.argcount
55
55
  self.posonlyargcount = bytecode.posonlyargcount
56
56
  self.kwonlyargcount = bytecode.kwonlyargcount
@@ -164,15 +164,33 @@ class _BaseBytecodeList(BaseBytecode, list, Generic[U]):
164
164
  for i in reversed(lineno_pos):
165
165
  del self[i]
166
166
 
167
- def __iter__(self) -> Iterator[U]:
168
- instructions = super().__iter__()
169
- for instr in instructions:
170
- self._check_instr(instr)
171
- yield instr
172
-
173
167
  def _check_instr(self, instr):
174
168
  raise NotImplementedError()
175
169
 
170
+ def append(self, instr: U) -> None: # type: ignore[override]
171
+ self._check_instr(instr)
172
+ super().append(instr)
173
+
174
+ def insert(self, index: SupportsIndex, instr: U) -> None: # type: ignore[override]
175
+ self._check_instr(instr)
176
+ super().insert(index, instr)
177
+
178
+ def extend(self, instrs) -> None: # type: ignore[override]
179
+ instrs = list(instrs)
180
+ for instr in instrs:
181
+ self._check_instr(instr)
182
+ super().extend(instrs)
183
+
184
+ def __setitem__(self, index, value):
185
+ if isinstance(index, slice):
186
+ values = list(value)
187
+ for v in values:
188
+ self._check_instr(v)
189
+ super().__setitem__(index, values)
190
+ else:
191
+ self._check_instr(value)
192
+ super().__setitem__(index, value)
193
+
176
194
 
177
195
  V = TypeVar("V")
178
196
 
@@ -242,15 +260,11 @@ class Bytecode(
242
260
  ) -> None:
243
261
  BaseBytecode.__init__(self)
244
262
  self.argnames: List[str] = []
245
- for instr in instructions:
246
- self._check_instr(instr)
247
263
  self.extend(instructions)
248
264
 
249
265
  def __iter__(self) -> Iterator[Union[Instr, Label, TryBegin, TryEnd, SetLineno]]:
250
- instructions = super().__iter__()
251
266
  seen_try_begin = False
252
- for instr in instructions:
253
- self._check_instr(instr)
267
+ for instr in super().__iter__():
254
268
  if isinstance(instr, TryBegin):
255
269
  if seen_try_begin:
256
270
  raise RuntimeError("TryBegin pseudo instructions cannot be nested.")
@@ -277,7 +291,7 @@ class Bytecode(
277
291
  code: types.CodeType,
278
292
  prune_caches: bool = True,
279
293
  conserve_exception_block_stackdepth: bool = False,
280
- ) -> "Bytecode":
294
+ ) -> Bytecode:
281
295
  concrete = _bytecode.ConcreteBytecode.from_code(code)
282
296
  return concrete.to_bytecode(
283
297
  prune_caches=prune_caches,
@@ -298,7 +312,7 @@ class Bytecode(
298
312
  ) -> types.CodeType:
299
313
  # Prevent reconverting the concrete bytecode to bytecode and cfg to do the
300
314
  # calculation if we need to do it.
301
- if stacksize is None or (PY311 and compute_exception_stack_depths):
315
+ if stacksize is None or compute_exception_stack_depths:
302
316
  cfg = _bytecode.ControlFlowGraph.from_bytecode(self)
303
317
  stacksize = cfg.compute_stacksize(
304
318
  check_pre_and_post=check_pre_and_post,
@@ -319,7 +333,7 @@ class Bytecode(
319
333
  self,
320
334
  compute_jumps_passes: Optional[int] = None,
321
335
  compute_exception_stack_depths: bool = True,
322
- ) -> "_bytecode.ConcreteBytecode":
336
+ ) -> _bytecode.ConcreteBytecode:
323
337
  converter = _bytecode._ConvertBytecodeToConcrete(self)
324
338
  return converter.to_concrete_bytecode(
325
339
  compute_jumps_passes=compute_jumps_passes,
@@ -1,4 +1,5 @@
1
- import sys
1
+ from __future__ import annotations
2
+
2
3
  import types
3
4
  from collections import defaultdict
4
5
  from dataclasses import dataclass
@@ -23,7 +24,7 @@ import bytecode as _bytecode
23
24
  from bytecode.concrete import ConcreteInstr
24
25
  from bytecode.flags import CompilerFlags
25
26
  from bytecode.instr import UNSET, Instr, Label, SetLineno, TryBegin, TryEnd
26
- from bytecode.utils import PY310, PY311, PY313
27
+ from bytecode.utils import PY313
27
28
 
28
29
  T = TypeVar("T", bound="BasicBlock")
29
30
  U = TypeVar("U", bound="ControlFlowGraph")
@@ -37,43 +38,79 @@ class BasicBlock(_bytecode._InstrList[Union[Instr, SetLineno, TryBegin, TryEnd]]
37
38
  ] = None,
38
39
  ) -> None:
39
40
  # a BasicBlock object, or None
40
- self.next_block: Optional["BasicBlock"] = None
41
+ self.next_block: Optional[BasicBlock] = None
41
42
  if instructions:
42
43
  super().__init__(instructions)
43
44
 
44
- def __iter__(self) -> Iterator[Union[Instr, SetLineno, TryBegin, TryEnd]]:
45
- index = 0
46
- while index < len(self):
47
- instr = self[index]
48
- index += 1
45
+ _VALID_TYPES = (SetLineno, Instr, TryBegin, TryEnd)
46
+
47
+ @staticmethod
48
+ def _check_instr(instr: Any) -> None:
49
+ if not isinstance(instr, (SetLineno, Instr, TryBegin, TryEnd)):
50
+ raise ValueError(
51
+ "BasicBlock must only contain SetLineno and Instr objects, "
52
+ "but %s was found" % instr.__class__.__name__
53
+ )
49
54
 
50
- if not isinstance(instr, (SetLineno, Instr, TryBegin, TryEnd)):
55
+ def append(self, instr: Union[Instr, SetLineno, TryBegin, TryEnd]) -> None:
56
+ self._check_instr(instr)
57
+ if isinstance(instr, Instr):
58
+ last = self.get_last_non_artificial_instruction()
59
+ if last is not None and last.has_jump():
51
60
  raise ValueError(
52
- "BasicBlock must only contain SetLineno and Instr objects, "
53
- "but %s was found" % instr.__class__.__name__
61
+ "Only the last instruction of a basic block can be a jump"
54
62
  )
63
+ super().append(instr)
55
64
 
56
- if isinstance(instr, Instr) and instr.has_jump():
57
- if index < len(self) and any(
58
- isinstance(self[i], Instr) for i in range(index, len(self))
65
+ def insert(
66
+ self, index: SupportsIndex, instr: Union[Instr, SetLineno, TryBegin, TryEnd]
67
+ ) -> None:
68
+ self._check_instr(instr)
69
+ super().insert(index, instr)
70
+
71
+ def extend(
72
+ self, instrs: Iterable[Union[Instr, SetLineno, TryBegin, TryEnd]]
73
+ ) -> None:
74
+ instrs = list(instrs)
75
+ for instr in instrs:
76
+ self._check_instr(instr)
77
+ existing_last = self.get_last_non_artificial_instruction()
78
+ last_new_instr: Optional[Instr] = None
79
+ for instr in instrs:
80
+ if isinstance(instr, Instr):
81
+ if (existing_last is not None and existing_last.has_jump()) or (
82
+ last_new_instr is not None and last_new_instr.has_jump()
59
83
  ):
60
84
  raise ValueError(
61
85
  "Only the last instruction of a basic block can be a jump"
62
86
  )
87
+ last_new_instr = instr
88
+ super().extend(instrs)
63
89
 
90
+ def __setitem__(self, index, value):
91
+ if isinstance(index, slice):
92
+ values = list(value)
93
+ for instr in values:
94
+ self._check_instr(instr)
95
+ super().__setitem__(index, values)
96
+ else:
97
+ self._check_instr(value)
98
+ super().__setitem__(index, value)
99
+
100
+ def __iter__(self) -> Iterator[Union[Instr, SetLineno, TryBegin, TryEnd]]:
101
+ for instr in super().__iter__():
102
+ if isinstance(instr, Instr) and instr.has_jump():
64
103
  if not isinstance(instr.arg, BasicBlock):
65
104
  raise ValueError(
66
- "Jump target must a BasicBlock, got %s",
67
- type(instr.arg).__name__,
105
+ "Jump target must a BasicBlock, got %s"
106
+ % type(instr.arg).__name__
68
107
  )
69
-
70
- if isinstance(instr, TryBegin):
108
+ elif isinstance(instr, TryBegin):
71
109
  if not isinstance(instr.target, BasicBlock):
72
110
  raise ValueError(
73
- "TryBegin target must a BasicBlock, got %s",
74
- type(instr.target).__name__,
111
+ "TryBegin target must a BasicBlock, got %s"
112
+ % type(instr.target).__name__
75
113
  )
76
-
77
114
  yield instr
78
115
 
79
116
  @overload
@@ -93,10 +130,9 @@ class BasicBlock(_bytecode._InstrList[Union[Instr, SetLineno, TryBegin, TryEnd]]
93
130
  return value
94
131
 
95
132
  def get_last_non_artificial_instruction(self) -> Optional[Instr]:
96
- for instr in reversed(self):
133
+ for instr in super().__reversed__():
97
134
  if isinstance(instr, Instr):
98
135
  return instr
99
-
100
136
  return None
101
137
 
102
138
  def copy(self: T) -> T:
@@ -130,7 +166,7 @@ class BasicBlock(_bytecode._InstrList[Union[Instr, SetLineno, TryBegin, TryEnd]]
130
166
 
131
167
  return current_lineno
132
168
 
133
- def get_jump(self) -> Optional["BasicBlock"]:
169
+ def get_jump(self) -> Optional[BasicBlock]:
134
170
  if not self:
135
171
  return None
136
172
 
@@ -244,7 +280,7 @@ class _StackSizeComputer:
244
280
  self.pending_try_begin = pending_try_begin
245
281
  self._current_try_begin = pending_try_begin
246
282
 
247
- def run(self) -> Generator[Union["_StackSizeComputer", int], int, None]:
283
+ def run(self) -> Generator[Union[_StackSizeComputer, int], int, None]:
248
284
  """Iterate over the block instructions to compute stack usage."""
249
285
  # Blocks are not hashable but in this particular context we know we won't be
250
286
  # modifying blocks in place so we can safely use their id as hash rather than
@@ -344,9 +380,11 @@ class _StackSizeComputer:
344
380
  None,
345
381
  # Do not propagate the TryBegin if a final instruction is followed
346
382
  # by a TryEnd.
347
- None
348
- if instr.is_final() and self.block.get_trailing_try_end(i)
349
- else self._current_try_begin,
383
+ (
384
+ None
385
+ if instr.is_final() and self.block.get_trailing_try_end(i)
386
+ else self._current_try_begin
387
+ ),
350
388
  )
351
389
 
352
390
  # Update the maximum used size by the usage implied by the following
@@ -363,8 +401,10 @@ class _StackSizeComputer:
363
401
  # start with a TryEnd relevant only when reaching this block
364
402
  # through a particular jump. So we are lenient here.
365
403
  if (
366
- te := self.block.get_trailing_try_end(i)
367
- ) and te.entry is self._current_try_begin:
404
+ (te := self.block.get_trailing_try_end(i))
405
+ and self._current_try_begin is not None
406
+ and te.entry is self._current_try_begin
407
+ ):
368
408
  assert isinstance(te.entry.target, BasicBlock)
369
409
  yield from self._compute_exception_handler_stack_usage(
370
410
  te.entry.target,
@@ -427,7 +467,7 @@ class _StackSizeComputer:
427
467
 
428
468
  def _compute_exception_handler_stack_usage(
429
469
  self, block: BasicBlock, push_lasti: bool
430
- ) -> Generator[Union["_StackSizeComputer", int], int, None]:
470
+ ) -> Generator[Union[_StackSizeComputer, int], int, None]:
431
471
  b_id = id(block)
432
472
  if self.minsize < self.common.exception_block_startsize[b_id]:
433
473
  block_size = yield _StackSizeComputer(
@@ -447,18 +487,10 @@ class _StackSizeComputer:
447
487
  def _is_stacksize_computation_relevant(
448
488
  self, block_id: int, fingerprint: Tuple[int, Optional[bool]]
449
489
  ) -> bool:
450
- if PY311:
451
- # The computation is relevant if the block was not visited previously
452
- # with the same starting size and exception handler status than the
453
- # one in use
454
- return fingerprint not in self.common.blocks_startsizes[block_id]
455
- else:
456
- # The computation is relevant if the block was only visited with smaller
457
- # starting sizes than the one in use
458
- if sizes := self.common.blocks_startsizes[block_id]:
459
- return fingerprint[0] > max(f[0] for f in sizes)
460
- else:
461
- return True
490
+ # The computation is relevant if the block was not visited previously
491
+ # with the same starting size and exception handler status than the
492
+ # one in use
493
+ return fingerprint not in self.common.blocks_startsizes[block_id]
462
494
 
463
495
 
464
496
  class ControlFlowGraph(_bytecode.BaseBytecode):
@@ -521,11 +553,10 @@ class ControlFlowGraph(_bytecode.BaseBytecode):
521
553
  )
522
554
 
523
555
  # Starting with Python 3.10, generator and coroutines start with one object
524
- # on the stack (None, anything is an error).
556
+ # on the stack (None, anything else is an error).
525
557
  initial_stack_size = 0
526
558
  if (
527
559
  not PY313 # under 3.13+ RETURN_GENERATOR make this explicit
528
- and PY310
529
560
  and self.flags
530
561
  & (
531
562
  CompilerFlags.GENERATOR
@@ -588,7 +619,6 @@ class ControlFlowGraph(_bytecode.BaseBytecode):
588
619
  if compute_exception_stack_depths:
589
620
  for tb in common.try_begins:
590
621
  size = common.exception_block_startsize[id(tb.target)]
591
- assert size >= 0
592
622
  tb.stack_depth = size
593
623
 
594
624
  return args
@@ -629,7 +659,7 @@ class ControlFlowGraph(_bytecode.BaseBytecode):
629
659
  # argument rather than a Label. This is fine for comparison
630
660
  # purposes which is our sole goal here.
631
661
  c_instr = ConcreteInstr(
632
- instr.name,
662
+ instr._name,
633
663
  self.get_block_index(target_block),
634
664
  location=instr.location,
635
665
  )
@@ -748,7 +778,7 @@ class ControlFlowGraph(_bytecode.BaseBytecode):
748
778
  return [b for b in self if id(b) not in seen_block_ids]
749
779
 
750
780
  @staticmethod
751
- def from_bytecode(bytecode: _bytecode.Bytecode) -> "ControlFlowGraph":
781
+ def from_bytecode(bytecode: _bytecode.Bytecode) -> ControlFlowGraph:
752
782
  # label => instruction index
753
783
  label_to_block_index = {}
754
784
  jumps = []
@@ -841,7 +871,7 @@ class ControlFlowGraph(_bytecode.BaseBytecode):
841
871
  # The last instruction is final, if the current instruction is a
842
872
  # TryEnd insert it in the same block and move to the next instruction
843
873
  if last_instr.is_final() and isinstance(instr, TryEnd):
844
- assert active_try_begin
874
+ assert active_try_begin is not None
845
875
  nte = instr.copy()
846
876
  nte.entry = try_begins[active_try_begin][-1]
847
877
  old_block.append(nte)
@@ -888,7 +918,6 @@ class ControlFlowGraph(_bytecode.BaseBytecode):
888
918
  if isinstance(instr, (Instr, TryBegin, TryEnd)):
889
919
  new = instr.copy()
890
920
  if isinstance(instr, TryBegin):
891
- assert active_try_begin is None
892
921
  active_try_begin = instr
893
922
  try_begin_inserted_in_block = True
894
923
  assert isinstance(new, TryBegin)
@@ -982,9 +1011,7 @@ class ControlFlowGraph(_bytecode.BaseBytecode):
982
1011
  # If due to jumps and split TryBegin, we encounter a TryBegin
983
1012
  # while we still have a TryBegin ensure they can be fused.
984
1013
  if last_try_begin is not None:
985
- cfg_tb, byt_tb = last_try_begin
986
- assert instr.target is cfg_tb.target
987
- assert instr.push_lasti == cfg_tb.push_lasti
1014
+ _, byt_tb = last_try_begin
988
1015
  byt_tb.stack_depth = min(
989
1016
  byt_tb.stack_depth, instr.stack_depth
990
1017
  )
@@ -1003,7 +1030,6 @@ class ControlFlowGraph(_bytecode.BaseBytecode):
1003
1030
  # If we did not yet compute the required stack depth
1004
1031
  # keep the value as UNSET
1005
1032
  if entry.stack_depth is UNSET:
1006
- assert instr.stack_depth is UNSET
1007
1033
  byt_te.entry.stack_depth = UNSET
1008
1034
  else:
1009
1035
  byt_te.entry.stack_depth = min(