Skip to content

Commit 5bfb3c3

Browse files
authored
GH-90997: Wrap yield from/await in a virtual try/except StopIteration (GH-96010)
1 parent 2d9f252 commit 5bfb3c3

File tree

11 files changed

+142
-106
lines changed

11 files changed

+142
-106
lines changed

Doc/library/dis.rst

Lines changed: 18 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -567,6 +567,17 @@ the original TOS1.
567567
.. versionchanged:: 3.11
568568
Exception representation on the stack now consist of one, not three, items.
569569

570+
571+
.. opcode:: CLEANUP_THROW
572+
573+
Handles an exception raised during a :meth:`~generator.throw` or
574+
:meth:`~generator.close` call through the current frame. If TOS is an
575+
instance of :exc:`StopIteration`, pop three values from the stack and push
576+
its ``value`` member. Otherwise, re-raise TOS.
577+
578+
.. versionadded:: 3.12
579+
580+
570581
.. opcode:: BEFORE_ASYNC_WITH
571582

572583
Resolves ``__aenter__`` and ``__aexit__`` from the object on top of the
@@ -1344,10 +1355,14 @@ iterations of the loop.
13441355
.. versionadded:: 3.11
13451356

13461357

1347-
.. opcode:: SEND
1358+
.. opcode:: SEND (delta)
1359+
1360+
Equivalent to ``TOS = TOS1.send(TOS)``. Used in ``yield from`` and ``await``
1361+
statements.
13481362

1349-
Sends ``None`` to the sub-generator of this generator.
1350-
Used in ``yield from`` and ``await`` statements.
1363+
If the call raises :exc:`StopIteration`, pop both items, push the
1364+
exception's ``value`` attribute, and increment the bytecode counter by
1365+
*delta*.
13511366

13521367
.. versionadded:: 3.11
13531368

Include/internal/pycore_opcode.h

Lines changed: 14 additions & 14 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Include/opcode.h

Lines changed: 43 additions & 42 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Lib/importlib/_bootstrap_external.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -411,10 +411,10 @@ def _write_atomic(path, data, mode=0o666):
411411
# Python 3.12a1 3505 (Specialization/Cache for FOR_ITER)
412412
# Python 3.12a1 3506 (Add BINARY_SLICE and STORE_SLICE instructions)
413413
# Python 3.12a1 3507 (Set lineno of module's RESUME to 0)
414+
# Python 3.12a1 3508 (Add CLEANUP_THROW)
414415

415416
# Python 3.13 will start with 3550
416417

417-
#
418418
# MAGIC must change whenever the bytecode emitted by the compiler may no
419419
# longer be understood by older implementations of the eval loop (usually
420420
# due to the addition of new opcodes).
@@ -424,7 +424,7 @@ def _write_atomic(path, data, mode=0o666):
424424
# Whenever MAGIC_NUMBER is changed, the ranges in the magic_values array
425425
# in PC/launcher.c must also be updated.
426426

427-
MAGIC_NUMBER = (3507).to_bytes(2, 'little') + b'\r\n'
427+
MAGIC_NUMBER = (3508).to_bytes(2, 'little') + b'\r\n'
428428

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

Lib/opcode.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -104,6 +104,7 @@ def pseudo_op(name, op, real_ops):
104104
def_op('BEFORE_ASYNC_WITH', 52)
105105
def_op('BEFORE_WITH', 53)
106106
def_op('END_ASYNC_FOR', 54)
107+
def_op('CLEANUP_THROW', 55)
107108

108109
def_op('STORE_SUBSCR', 60)
109110
def_op('DELETE_SUBSCR', 61)

Lib/test/test_dis.py

Lines changed: 11 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -507,26 +507,31 @@ async def _asyncwith(c):
507507
LOAD_CONST 0 (None)
508508
RETURN_VALUE
509509
510-
%3d >> PUSH_EXC_INFO
510+
%3d >> CLEANUP_THROW
511+
JUMP_BACKWARD 24 (to 22)
512+
>> CLEANUP_THROW
513+
JUMP_BACKWARD 9 (to 56)
514+
>> PUSH_EXC_INFO
511515
WITH_EXCEPT_START
512516
GET_AWAITABLE 2
513517
LOAD_CONST 0 (None)
514-
>> SEND 3 (to 82)
518+
>> SEND 4 (to 92)
515519
YIELD_VALUE 6
516520
RESUME 3
517-
JUMP_BACKWARD_NO_INTERRUPT 4 (to 74)
518-
>> POP_JUMP_FORWARD_IF_TRUE 1 (to 86)
521+
JUMP_BACKWARD_NO_INTERRUPT 4 (to 82)
522+
>> CLEANUP_THROW
523+
>> POP_JUMP_FORWARD_IF_TRUE 1 (to 96)
519524
RERAISE 2
520525
>> POP_TOP
521526
POP_EXCEPT
522527
POP_TOP
523528
POP_TOP
524-
JUMP_BACKWARD 19 (to 58)
529+
JUMP_BACKWARD 24 (to 58)
525530
>> COPY 3
526531
POP_EXCEPT
527532
RERAISE 1
528533
ExceptionTable:
529-
2 rows
534+
6 rows
530535
""" % (_asyncwith.__code__.co_firstlineno,
531536
_asyncwith.__code__.co_firstlineno + 1,
532537
_asyncwith.__code__.co_firstlineno + 2,
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
Compile virtual :keyword:`try`/:keyword:`except` blocks to handle exceptions
2+
raised during :meth:`~generator.close` or :meth:`~generator.throw` calls
3+
through a suspended frame.

Objects/genobject.c

Lines changed: 1 addition & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -485,26 +485,7 @@ _gen_throw(PyGenObject *gen, int close_on_genexit,
485485
}
486486
Py_DECREF(yf);
487487
if (!ret) {
488-
PyObject *val;
489-
/* Pop subiterator from stack */
490-
assert(gen->gi_frame_state < FRAME_CLEARED);
491-
ret = _PyFrame_StackPop((_PyInterpreterFrame *)gen->gi_iframe);
492-
assert(ret == yf);
493-
Py_DECREF(ret);
494-
// XXX: Performing this jump ourselves is awkward and problematic.
495-
// See https://github.com/python/cpython/pull/31968.
496-
/* Termination repetition of SEND loop */
497-
assert(_PyInterpreterFrame_LASTI(frame) >= 0);
498-
/* Backup to SEND */
499-
assert(_Py_OPCODE(frame->prev_instr[-1]) == SEND);
500-
int jump = _Py_OPARG(frame->prev_instr[-1]);
501-
frame->prev_instr += jump - 1;
502-
if (_PyGen_FetchStopIterationValue(&val) == 0) {
503-
ret = gen_send(gen, val);
504-
Py_DECREF(val);
505-
} else {
506-
ret = gen_send_ex(gen, Py_None, 1, 0);
507-
}
488+
ret = gen_send_ex(gen, Py_None, 1, 0);
508489
}
509490
return ret;
510491
}

0 commit comments

Comments
 (0)