bytecode 0.17.0__tar.gz → 0.18.1__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.1}/.github/workflows/cis.yml +6 -10
  2. {bytecode-0.17.0 → bytecode-0.18.1}/.github/workflows/docs.yml +2 -2
  3. {bytecode-0.17.0 → bytecode-0.18.1}/.github/workflows/frameworks.yml +3 -3
  4. {bytecode-0.17.0 → bytecode-0.18.1}/.github/workflows/release.yml +10 -10
  5. {bytecode-0.17.0 → bytecode-0.18.1}/.readthedocs.yaml +2 -2
  6. {bytecode-0.17.0/src/bytecode.egg-info → bytecode-0.18.1}/PKG-INFO +2 -4
  7. {bytecode-0.17.0 → bytecode-0.18.1}/doc/changelog.rst +33 -0
  8. {bytecode-0.17.0 → bytecode-0.18.1}/pyproject.toml +1 -3
  9. {bytecode-0.17.0 → bytecode-0.18.1}/src/bytecode/bytecode.py +32 -18
  10. {bytecode-0.17.0 → bytecode-0.18.1}/src/bytecode/cfg.py +91 -55
  11. {bytecode-0.17.0 → bytecode-0.18.1}/src/bytecode/concrete.py +246 -385
  12. {bytecode-0.17.0 → bytecode-0.18.1}/src/bytecode/flags.py +21 -28
  13. {bytecode-0.17.0 → bytecode-0.18.1}/src/bytecode/instr.py +281 -207
  14. {bytecode-0.17.0 → bytecode-0.18.1}/src/bytecode/utils.py +0 -2
  15. {bytecode-0.17.0 → bytecode-0.18.1}/src/bytecode/version.py +2 -2
  16. {bytecode-0.17.0 → bytecode-0.18.1/src/bytecode.egg-info}/PKG-INFO +2 -4
  17. {bytecode-0.17.0 → bytecode-0.18.1}/tests/__init__.py +13 -54
  18. {bytecode-0.17.0 → bytecode-0.18.1}/tests/exception_handling_cases.py +2 -19
  19. {bytecode-0.17.0 → bytecode-0.18.1}/tests/frameworks/module.py +3 -1
  20. {bytecode-0.17.0 → bytecode-0.18.1}/tests/test_bytecode.py +28 -160
  21. {bytecode-0.17.0 → bytecode-0.18.1}/tests/test_cfg.py +146 -108
  22. {bytecode-0.17.0 → bytecode-0.18.1}/tests/test_code.py +13 -0
  23. {bytecode-0.17.0 → bytecode-0.18.1}/tests/test_concrete.py +61 -217
  24. {bytecode-0.17.0 → bytecode-0.18.1}/tests/test_flags.py +12 -43
  25. {bytecode-0.17.0 → bytecode-0.18.1}/tests/test_instr.py +12 -22
  26. {bytecode-0.17.0 → bytecode-0.18.1}/tests/test_misc.py +7 -148
  27. {bytecode-0.17.0 → bytecode-0.18.1}/.coveragerc +0 -0
  28. {bytecode-0.17.0 → bytecode-0.18.1}/.github/FUNDING.yml +0 -0
  29. {bytecode-0.17.0 → bytecode-0.18.1}/.github/dependabot.yml +0 -0
  30. {bytecode-0.17.0 → bytecode-0.18.1}/.gitignore +0 -0
  31. {bytecode-0.17.0 → bytecode-0.18.1}/.pre-commit-config.yaml +0 -0
  32. {bytecode-0.17.0 → bytecode-0.18.1}/COPYING +0 -0
  33. {bytecode-0.17.0 → bytecode-0.18.1}/MANIFEST.in +0 -0
  34. {bytecode-0.17.0 → bytecode-0.18.1}/README.rst +0 -0
  35. {bytecode-0.17.0 → bytecode-0.18.1}/TODO.rst +0 -0
  36. {bytecode-0.17.0 → bytecode-0.18.1}/codecov.yml +0 -0
  37. {bytecode-0.17.0 → bytecode-0.18.1}/doc/Makefile +0 -0
  38. {bytecode-0.17.0 → bytecode-0.18.1}/doc/api.rst +0 -0
  39. {bytecode-0.17.0 → bytecode-0.18.1}/doc/byteplay_codetransformer.rst +0 -0
  40. {bytecode-0.17.0 → bytecode-0.18.1}/doc/cfg.rst +0 -0
  41. {bytecode-0.17.0 → bytecode-0.18.1}/doc/conf.py +0 -0
  42. {bytecode-0.17.0 → bytecode-0.18.1}/doc/index.rst +0 -0
  43. {bytecode-0.17.0 → bytecode-0.18.1}/doc/make.bat +0 -0
  44. {bytecode-0.17.0 → bytecode-0.18.1}/doc/requirements.txt +0 -0
  45. {bytecode-0.17.0 → bytecode-0.18.1}/doc/todo.rst +0 -0
  46. {bytecode-0.17.0 → bytecode-0.18.1}/doc/usage.rst +0 -0
  47. {bytecode-0.17.0 → bytecode-0.18.1}/scripts/frameworks/boto3/run.sh +0 -0
  48. {bytecode-0.17.0 → bytecode-0.18.1}/scripts/frameworks/boto3/setup.sh +0 -0
  49. {bytecode-0.17.0 → bytecode-0.18.1}/setup.cfg +0 -0
  50. {bytecode-0.17.0 → bytecode-0.18.1}/src/bytecode/__init__.py +0 -0
  51. {bytecode-0.17.0 → bytecode-0.18.1}/src/bytecode/py.typed +0 -0
  52. {bytecode-0.17.0 → bytecode-0.18.1}/src/bytecode.egg-info/SOURCES.txt +0 -0
  53. {bytecode-0.17.0 → bytecode-0.18.1}/src/bytecode.egg-info/dependency_links.txt +0 -0
  54. {bytecode-0.17.0 → bytecode-0.18.1}/src/bytecode.egg-info/requires.txt +0 -0
  55. {bytecode-0.17.0 → bytecode-0.18.1}/src/bytecode.egg-info/top_level.txt +0 -0
  56. {bytecode-0.17.0 → bytecode-0.18.1}/tests/cell_free_vars_cases.py +0 -0
  57. {bytecode-0.17.0 → bytecode-0.18.1}/tests/frameworks/function.py +0 -0
  58. {bytecode-0.17.0 → bytecode-0.18.1}/tests/frameworks/sitecustomize.py +0 -0
  59. {bytecode-0.17.0 → bytecode-0.18.1}/tests/long_lines_example.py +0 -0
  60. {bytecode-0.17.0 → bytecode-0.18.1}/tests/util_annotation.py +0 -0
  61. {bytecode-0.17.0 → bytecode-0.18.1}/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.1
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,39 @@
1
1
  ChangeLog
2
2
  =========
3
3
 
4
+ 03-06-2026: Version 0.18.1
5
+ --------------------------
6
+
7
+ Bugfixes:
8
+
9
+ - Fix an ``AssertionError`` in stack-size computation when a single exception
10
+ region is split into multiple ``TryBegin`` instances sharing one handler (as
11
+ produced by bytecode-rewriting tools that wrap a whole function body in a
12
+ single handler). ``TryBegin``/``TryEnd`` are now matched on the handler block
13
+ rather than on ``TryBegin`` identity.
14
+
15
+ 03-06-2026: Version 0.18.0
16
+ --------------------------
17
+
18
+ - drop support for Python 3.9 and 3.10 PR #180
19
+ - Replace string literal type annotations with postponed evaluation using
20
+ ``from __future__ import annotations`` PR #191
21
+ - multiple performance improvements
22
+ PRs #172, #193, #196, #194, #197, #198, #199, #200, #201,
23
+ #202, #203
24
+
25
+ Breaking changes:
26
+
27
+ - ``BasicBlock``, ``Bytecode``, and ``ConcreteBytecode`` now validate inserted
28
+ instructions at insertion time (``append``, ``extend``, ``insert``,
29
+ ``__setitem__``) rather than during iteration. Code that relied on catching
30
+ ``ValueError`` from ``list(block)`` or ``for instr in block:`` must wrap the
31
+ insertion call instead. PR #199
32
+
33
+ Bugfixes:
34
+
35
+ - Fix handling of END_ASYNC_FOR which is a backward jump PR #179
36
+
4
37
  03-09-2025: Version 0.17.0
5
38
  --------------------------
6
39
 
@@ -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)
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)
63
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
@@ -307,7 +343,17 @@ class _StackSizeComputer:
307
343
  # current try begin. However inside the CFG some blocks may
308
344
  # start with a TryEnd relevant only when reaching this block
309
345
  # through a particular jump. So we are lenient here.
310
- if instr.entry is not self._current_try_begin:
346
+ #
347
+ # We match on the exception handler (the TryBegin target block)
348
+ # rather than on the TryBegin instance: a single exception region
349
+ # can be split into several TryBegin copies that share the same
350
+ # handler (see ``from_bytecode``), and the copy carried over as
351
+ # ``pending_try_begin`` through a jump is not necessarily the same
352
+ # instance as the one referenced by this block's leading TryEnd.
353
+ if (
354
+ self._current_try_begin is None
355
+ or instr.entry.target is not self._current_try_begin.target
356
+ ):
311
357
  continue
312
358
 
313
359
  # Compute the stack usage of the exception handler
@@ -344,9 +390,11 @@ class _StackSizeComputer:
344
390
  None,
345
391
  # Do not propagate the TryBegin if a final instruction is followed
346
392
  # by a TryEnd.
347
- None
348
- if instr.is_final() and self.block.get_trailing_try_end(i)
349
- else self._current_try_begin,
393
+ (
394
+ None
395
+ if instr.is_final() and self.block.get_trailing_try_end(i)
396
+ else self._current_try_begin
397
+ ),
350
398
  )
351
399
 
352
400
  # Update the maximum used size by the usage implied by the following
@@ -363,8 +411,10 @@ class _StackSizeComputer:
363
411
  # start with a TryEnd relevant only when reaching this block
364
412
  # through a particular jump. So we are lenient here.
365
413
  if (
366
- te := self.block.get_trailing_try_end(i)
367
- ) and te.entry is self._current_try_begin:
414
+ (te := self.block.get_trailing_try_end(i))
415
+ and self._current_try_begin is not None
416
+ and te.entry.target is self._current_try_begin.target
417
+ ):
368
418
  assert isinstance(te.entry.target, BasicBlock)
369
419
  yield from self._compute_exception_handler_stack_usage(
370
420
  te.entry.target,
@@ -427,7 +477,7 @@ class _StackSizeComputer:
427
477
 
428
478
  def _compute_exception_handler_stack_usage(
429
479
  self, block: BasicBlock, push_lasti: bool
430
- ) -> Generator[Union["_StackSizeComputer", int], int, None]:
480
+ ) -> Generator[Union[_StackSizeComputer, int], int, None]:
431
481
  b_id = id(block)
432
482
  if self.minsize < self.common.exception_block_startsize[b_id]:
433
483
  block_size = yield _StackSizeComputer(
@@ -447,18 +497,10 @@ class _StackSizeComputer:
447
497
  def _is_stacksize_computation_relevant(
448
498
  self, block_id: int, fingerprint: Tuple[int, Optional[bool]]
449
499
  ) -> 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
500
+ # The computation is relevant if the block was not visited previously
501
+ # with the same starting size and exception handler status than the
502
+ # one in use
503
+ return fingerprint not in self.common.blocks_startsizes[block_id]
462
504
 
463
505
 
464
506
  class ControlFlowGraph(_bytecode.BaseBytecode):
@@ -521,11 +563,10 @@ class ControlFlowGraph(_bytecode.BaseBytecode):
521
563
  )
522
564
 
523
565
  # Starting with Python 3.10, generator and coroutines start with one object
524
- # on the stack (None, anything is an error).
566
+ # on the stack (None, anything else is an error).
525
567
  initial_stack_size = 0
526
568
  if (
527
569
  not PY313 # under 3.13+ RETURN_GENERATOR make this explicit
528
- and PY310
529
570
  and self.flags
530
571
  & (
531
572
  CompilerFlags.GENERATOR
@@ -588,7 +629,6 @@ class ControlFlowGraph(_bytecode.BaseBytecode):
588
629
  if compute_exception_stack_depths:
589
630
  for tb in common.try_begins:
590
631
  size = common.exception_block_startsize[id(tb.target)]
591
- assert size >= 0
592
632
  tb.stack_depth = size
593
633
 
594
634
  return args
@@ -629,7 +669,7 @@ class ControlFlowGraph(_bytecode.BaseBytecode):
629
669
  # argument rather than a Label. This is fine for comparison
630
670
  # purposes which is our sole goal here.
631
671
  c_instr = ConcreteInstr(
632
- instr.name,
672
+ instr._name,
633
673
  self.get_block_index(target_block),
634
674
  location=instr.location,
635
675
  )
@@ -748,7 +788,7 @@ class ControlFlowGraph(_bytecode.BaseBytecode):
748
788
  return [b for b in self if id(b) not in seen_block_ids]
749
789
 
750
790
  @staticmethod
751
- def from_bytecode(bytecode: _bytecode.Bytecode) -> "ControlFlowGraph":
791
+ def from_bytecode(bytecode: _bytecode.Bytecode) -> ControlFlowGraph:
752
792
  # label => instruction index
753
793
  label_to_block_index = {}
754
794
  jumps = []
@@ -841,7 +881,7 @@ class ControlFlowGraph(_bytecode.BaseBytecode):
841
881
  # The last instruction is final, if the current instruction is a
842
882
  # TryEnd insert it in the same block and move to the next instruction
843
883
  if last_instr.is_final() and isinstance(instr, TryEnd):
844
- assert active_try_begin
884
+ assert active_try_begin is not None
845
885
  nte = instr.copy()
846
886
  nte.entry = try_begins[active_try_begin][-1]
847
887
  old_block.append(nte)
@@ -888,7 +928,6 @@ class ControlFlowGraph(_bytecode.BaseBytecode):
888
928
  if isinstance(instr, (Instr, TryBegin, TryEnd)):
889
929
  new = instr.copy()
890
930
  if isinstance(instr, TryBegin):
891
- assert active_try_begin is None
892
931
  active_try_begin = instr
893
932
  try_begin_inserted_in_block = True
894
933
  assert isinstance(new, TryBegin)
@@ -982,9 +1021,7 @@ class ControlFlowGraph(_bytecode.BaseBytecode):
982
1021
  # If due to jumps and split TryBegin, we encounter a TryBegin
983
1022
  # while we still have a TryBegin ensure they can be fused.
984
1023
  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
1024
+ _, byt_tb = last_try_begin
988
1025
  byt_tb.stack_depth = min(
989
1026
  byt_tb.stack_depth, instr.stack_depth
990
1027
  )
@@ -1003,7 +1040,6 @@ class ControlFlowGraph(_bytecode.BaseBytecode):
1003
1040
  # If we did not yet compute the required stack depth
1004
1041
  # keep the value as UNSET
1005
1042
  if entry.stack_depth is UNSET:
1006
- assert instr.stack_depth is UNSET
1007
1043
  byt_te.entry.stack_depth = UNSET
1008
1044
  else:
1009
1045
  byt_te.entry.stack_depth = min(