Skip to content
Merged
Show file tree
Hide file tree
Changes from 7 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 9 additions & 0 deletions Doc/library/dis.rst
Original file line number Diff line number Diff line change
Expand Up @@ -607,6 +607,15 @@ the original TOS1.
.. versionadded:: 3.12


.. opcode:: STOPITERATION_ERROR

Handles a StopIteration raised in a generator or coroutine.
If TOS is an instance of :exc:`StopIteration`, or :exc:`StopAsyncIteration`
replace it with a :exc:`RuntimeError`.

.. versionadded:: 3.12


.. opcode:: BEFORE_ASYNC_WITH

Resolves ``__aenter__`` and ``__aexit__`` from the object on top of the
Expand Down
26 changes: 13 additions & 13 deletions Include/internal/pycore_opcode.h

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

77 changes: 39 additions & 38 deletions Include/opcode.h

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

3 changes: 2 additions & 1 deletion Lib/importlib/_bootstrap_external.py
Original file line number Diff line number Diff line change
Expand Up @@ -424,6 +424,7 @@ def _write_atomic(path, data, mode=0o666):
# Python 3.12a1 3508 (Add CLEANUP_THROW)
# Python 3.12a1 3509 (Conditional jumps only jump forward)
# Python 3.12a1 3510 (FOR_ITER leaves iterator on the stack)
# Python 3.12a1 3511 (Add STOPITERATION_ERROR instruction)

# Python 3.13 will start with 3550

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

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

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

Expand Down
2 changes: 2 additions & 0 deletions Lib/opcode.py
Original file line number Diff line number Diff line change
Expand Up @@ -111,6 +111,8 @@ def pseudo_op(name, op, real_ops):
def_op('STORE_SUBSCR', 60)
def_op('DELETE_SUBSCR', 61)

def_op('STOPITERATION_ERROR', 63)

def_op('GET_ITER', 68)
def_op('GET_YIELD_FROM_ITER', 69)
def_op('PRINT_EXPR', 70)
Expand Down
4 changes: 3 additions & 1 deletion Lib/test/test_dis.py
Original file line number Diff line number Diff line change
Expand Up @@ -543,8 +543,10 @@ async def _asyncwith(c):
>> COPY 3
POP_EXCEPT
RERAISE 1
>> STOPITERATION_ERROR
RERAISE 1
ExceptionTable:
6 rows
12 rows
""" % (_asyncwith.__code__.co_firstlineno,
_asyncwith.__code__.co_firstlineno + 1,
_asyncwith.__code__.co_firstlineno + 2,
Expand Down
2 changes: 1 addition & 1 deletion Lib/test/test_sys.py
Original file line number Diff line number Diff line change
Expand Up @@ -1439,7 +1439,7 @@ def bar(cls):
check(bar, size('PP'))
# generator
def get_gen(): yield 1
check(get_gen(), size('P2P4P4c7P2ic??P'))
check(get_gen(), size('P2P4P4c7P2ic??2P'))
# iterator
check(iter('abc'), size('lP'))
# callable-iterator
Expand Down
2 changes: 1 addition & 1 deletion Lib/test/test_sys_settrace.py
Original file line number Diff line number Diff line change
Expand Up @@ -346,7 +346,7 @@ def make_tracer():
return Tracer()

def compare_events(self, line_offset, events, expected_events):
events = [(l - line_offset, e) for (l, e) in events]
events = [(l - line_offset if l is not None else None, e) for (l, e) in events]
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I introduced for debugging this PR. It is no longer strictly necessary, but it gives nicer output when tests fail.

if events != expected_events:
self.fail(
"events did not match expectation:\n" +
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
Handle StopIteration and StopAsyncIteration raised in generator or
coroutines in the bytecode, rather than in wrapping C code.
22 changes: 3 additions & 19 deletions Objects/genobject.c
Original file line number Diff line number Diff line change
Expand Up @@ -247,25 +247,9 @@ gen_send_ex2(PyGenObject *gen, PyObject *arg, PyObject **presult,
}
}
else {
if (PyErr_ExceptionMatches(PyExc_StopIteration)) {
const char *msg = "generator raised StopIteration";
if (PyCoro_CheckExact(gen)) {
msg = "coroutine raised StopIteration";
}
else if (PyAsyncGen_CheckExact(gen)) {
msg = "async generator raised StopIteration";
}
_PyErr_FormatFromCause(PyExc_RuntimeError, "%s", msg);
}
else if (PyAsyncGen_CheckExact(gen) &&
PyErr_ExceptionMatches(PyExc_StopAsyncIteration))
{
/* code in `gen` raised a StopAsyncIteration error:
raise a RuntimeError.
*/
const char *msg = "async generator raised StopAsyncIteration";
_PyErr_FormatFromCause(PyExc_RuntimeError, "%s", msg);
}
assert(!PyErr_ExceptionMatches(PyExc_StopIteration));
assert(!PyAsyncGen_CheckExact(gen) ||
!PyErr_ExceptionMatches(PyExc_StopAsyncIteration));
}

/* generator can't be rerun, so release the frame */
Expand Down
42 changes: 42 additions & 0 deletions Python/ceval.c
Original file line number Diff line number Diff line change
Expand Up @@ -2175,6 +2175,48 @@ _PyEval_EvalFrameDefault(PyThreadState *tstate, _PyInterpreterFrame *frame, int
goto exception_unwind;
}

TARGET(STOPITERATION_ERROR) {
assert(frame->owner == FRAME_OWNED_BY_GENERATOR);
PyObject *exc = TOP();
assert(PyExceptionInstance_Check(exc));
const char *msg = NULL;
if (PyErr_GivenExceptionMatches(exc, PyExc_StopIteration)) {
msg = "generator raised StopIteration";
if (frame->f_code->co_flags & CO_ASYNC_GENERATOR) {
msg = "async generator raised StopIteration";
}
else if (frame->f_code->co_flags & CO_COROUTINE) {
msg = "coroutine raised StopIteration";
}
}
else if ((frame->f_code->co_flags & CO_ASYNC_GENERATOR) &&
PyErr_GivenExceptionMatches(exc, PyExc_StopAsyncIteration))
{
/* code in `gen` raised a StopAsyncIteration error:
raise a RuntimeError.
*/
msg = "async generator raised StopAsyncIteration";
}
if (msg != NULL) {
PyObject *message = _PyUnicode_FromASCII(msg, strlen(msg));
if (message == NULL) {
goto error;
}
PyObject *error = PyObject_CallOneArg(PyExc_RuntimeError, message);
if (error == NULL) {
Py_DECREF(message);
goto error;
}
assert(PyExceptionInstance_Check(error));
SET_TOP(error);
PyException_SetCause(error, exc);
Py_INCREF(exc);
PyException_SetContext(error, exc);
Py_DECREF(message);
}
DISPATCH();
}

TARGET(LOAD_ASSERTION_ERROR) {
PyObject *value = PyExc_AssertionError;
Py_INCREF(value);
Expand Down
Loading