Skip to content

Commit f4adb97

Browse files
authored
GH-96793: Implement PEP 479 in bytecode. (GH-99006)
* Handle converting StopIteration to RuntimeError in bytecode. * Add custom instruction for converting StopIteration into RuntimeError.
1 parent e9ac890 commit f4adb97

File tree

14 files changed

+220
-96
lines changed

14 files changed

+220
-96
lines changed

Doc/library/dis.rst

+9
Original file line numberDiff line numberDiff line change
@@ -607,6 +607,15 @@ the original TOS1.
607607
.. versionadded:: 3.12
608608

609609

610+
.. opcode:: STOPITERATION_ERROR
611+
612+
Handles a StopIteration raised in a generator or coroutine.
613+
If TOS is an instance of :exc:`StopIteration`, or :exc:`StopAsyncIteration`
614+
replace it with a :exc:`RuntimeError`.
615+
616+
.. versionadded:: 3.12
617+
618+
610619
.. opcode:: BEFORE_ASYNC_WITH
611620

612621
Resolves ``__aenter__`` and ``__aexit__`` from the object on top of the

Include/internal/pycore_opcode.h

+13-13
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Include/opcode.h

+37-36
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Lib/importlib/_bootstrap_external.py

+2-1
Original file line numberDiff line numberDiff line change
@@ -424,6 +424,7 @@ def _write_atomic(path, data, mode=0o666):
424424
# Python 3.12a1 3508 (Add CLEANUP_THROW)
425425
# Python 3.12a1 3509 (Conditional jumps only jump forward)
426426
# Python 3.12a1 3510 (FOR_ITER leaves iterator on the stack)
427+
# Python 3.12a1 3511 (Add STOPITERATION_ERROR instruction)
427428

428429
# Python 3.13 will start with 3550
429430

@@ -436,7 +437,7 @@ def _write_atomic(path, data, mode=0o666):
436437
# Whenever MAGIC_NUMBER is changed, the ranges in the magic_values array
437438
# in PC/launcher.c must also be updated.
438439

439-
MAGIC_NUMBER = (3510).to_bytes(2, 'little') + b'\r\n'
440+
MAGIC_NUMBER = (3511).to_bytes(2, 'little') + b'\r\n'
440441

441442
_RAW_MAGIC_NUMBER = int.from_bytes(MAGIC_NUMBER, 'little') # For import.c
442443

Lib/opcode.py

+2
Original file line numberDiff line numberDiff line change
@@ -111,6 +111,8 @@ def pseudo_op(name, op, real_ops):
111111
def_op('STORE_SUBSCR', 60)
112112
def_op('DELETE_SUBSCR', 61)
113113

114+
def_op('STOPITERATION_ERROR', 63)
115+
114116
def_op('GET_ITER', 68)
115117
def_op('GET_YIELD_FROM_ITER', 69)
116118
def_op('PRINT_EXPR', 70)

Lib/test/test_dis.py

+3-1
Original file line numberDiff line numberDiff line change
@@ -543,8 +543,10 @@ async def _asyncwith(c):
543543
>> COPY 3
544544
POP_EXCEPT
545545
RERAISE 1
546+
>> STOPITERATION_ERROR
547+
RERAISE 1
546548
ExceptionTable:
547-
6 rows
549+
12 rows
548550
""" % (_asyncwith.__code__.co_firstlineno,
549551
_asyncwith.__code__.co_firstlineno + 1,
550552
_asyncwith.__code__.co_firstlineno + 2,

Lib/test/test_sys.py

+1-1
Original file line numberDiff line numberDiff line change
@@ -1439,7 +1439,7 @@ def bar(cls):
14391439
check(bar, size('PP'))
14401440
# generator
14411441
def get_gen(): yield 1
1442-
check(get_gen(), size('P2P4P4c7P2ic??P'))
1442+
check(get_gen(), size('P2P4P4c7P2ic??2P'))
14431443
# iterator
14441444
check(iter('abc'), size('lP'))
14451445
# callable-iterator

Lib/test/test_sys_settrace.py

+1-1
Original file line numberDiff line numberDiff line change
@@ -346,7 +346,7 @@ def make_tracer():
346346
return Tracer()
347347

348348
def compare_events(self, line_offset, events, expected_events):
349-
events = [(l - line_offset, e) for (l, e) in events]
349+
events = [(l - line_offset if l is not None else None, e) for (l, e) in events]
350350
if events != expected_events:
351351
self.fail(
352352
"events did not match expectation:\n" +
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
Handle StopIteration and StopAsyncIteration raised in generator or
2+
coroutines in the bytecode, rather than in wrapping C code.

Objects/genobject.c

+3-19
Original file line numberDiff line numberDiff line change
@@ -246,25 +246,9 @@ gen_send_ex2(PyGenObject *gen, PyObject *arg, PyObject **presult,
246246
}
247247
}
248248
else {
249-
if (PyErr_ExceptionMatches(PyExc_StopIteration)) {
250-
const char *msg = "generator raised StopIteration";
251-
if (PyCoro_CheckExact(gen)) {
252-
msg = "coroutine raised StopIteration";
253-
}
254-
else if (PyAsyncGen_CheckExact(gen)) {
255-
msg = "async generator raised StopIteration";
256-
}
257-
_PyErr_FormatFromCause(PyExc_RuntimeError, "%s", msg);
258-
}
259-
else if (PyAsyncGen_CheckExact(gen) &&
260-
PyErr_ExceptionMatches(PyExc_StopAsyncIteration))
261-
{
262-
/* code in `gen` raised a StopAsyncIteration error:
263-
raise a RuntimeError.
264-
*/
265-
const char *msg = "async generator raised StopAsyncIteration";
266-
_PyErr_FormatFromCause(PyExc_RuntimeError, "%s", msg);
267-
}
249+
assert(!PyErr_ExceptionMatches(PyExc_StopIteration));
250+
assert(!PyAsyncGen_CheckExact(gen) ||
251+
!PyErr_ExceptionMatches(PyExc_StopAsyncIteration));
268252
}
269253

270254
/* generator can't be rerun, so release the frame */

Python/bytecodes.c

+43
Original file line numberDiff line numberDiff line change
@@ -1085,6 +1085,49 @@ dummy_func(
10851085
goto exception_unwind;
10861086
}
10871087

1088+
inst(STOPITERATION_ERROR) {
1089+
assert(frame->owner == FRAME_OWNED_BY_GENERATOR);
1090+
PyObject *exc = TOP();
1091+
assert(PyExceptionInstance_Check(exc));
1092+
const char *msg = NULL;
1093+
if (PyErr_GivenExceptionMatches(exc, PyExc_StopIteration)) {
1094+
msg = "generator raised StopIteration";
1095+
if (frame->f_code->co_flags & CO_ASYNC_GENERATOR) {
1096+
msg = "async generator raised StopIteration";
1097+
}
1098+
else if (frame->f_code->co_flags & CO_COROUTINE) {
1099+
msg = "coroutine raised StopIteration";
1100+
}
1101+
}
1102+
else if ((frame->f_code->co_flags & CO_ASYNC_GENERATOR) &&
1103+
PyErr_GivenExceptionMatches(exc, PyExc_StopAsyncIteration))
1104+
{
1105+
/* code in `gen` raised a StopAsyncIteration error:
1106+
raise a RuntimeError.
1107+
*/
1108+
msg = "async generator raised StopAsyncIteration";
1109+
}
1110+
if (msg != NULL) {
1111+
PyObject *message = _PyUnicode_FromASCII(msg, strlen(msg));
1112+
if (message == NULL) {
1113+
goto error;
1114+
}
1115+
PyObject *error = PyObject_CallOneArg(PyExc_RuntimeError, message);
1116+
if (error == NULL) {
1117+
Py_DECREF(message);
1118+
goto error;
1119+
}
1120+
assert(PyExceptionInstance_Check(error));
1121+
SET_TOP(error);
1122+
PyException_SetCause(error, exc);
1123+
Py_INCREF(exc);
1124+
PyException_SetContext(error, exc);
1125+
Py_DECREF(message);
1126+
}
1127+
DISPATCH();
1128+
}
1129+
1130+
10881131
// stack effect: ( -- __0)
10891132
inst(LOAD_ASSERTION_ERROR) {
10901133
PyObject *value = PyExc_AssertionError;

0 commit comments

Comments
 (0)