Skip to content
Merged
Show file tree
Hide file tree
Changes from all 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
11 changes: 11 additions & 0 deletions src/decompiler.rs
Original file line number Diff line number Diff line change
Expand Up @@ -8108,6 +8108,17 @@ impl<'a> Ctx<'a> {
loop {
let mut grew = false;
for e in &self.raw_exc_entries {
// The generator/coroutine StopIteration handler spans the
// whole frame, not this source-level except/finally chain.
// Following its redirects would swallow later main-flow
// statements into the current handler region.
let frame_handler = self.idx_of.get(&e.target)
.and_then(|&index| self.instrs.get(index))
.map_or(false, |instruction| instruction.op == Op::CALL_INTRINSIC_1
&& instruction.arg == 3);
if frame_handler {
continue;
}
let seed = e.start == from
|| (e.target == from && e.start < from);
let inside = e.start >= from
Expand Down
41 changes: 41 additions & 0 deletions tests/behavior/cases/b_sequential_generator_handlers.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
# MIN_VERSION: 3.12
import asyncio

def synchronous(work):
try:
yield work("first")
except ValueError:
yield 2
yield 3
try:
yield work("last")
except KeyError:
yield 5
yield 6


async def asynchronous(work):
try:
yield work("first")
except ValueError:
yield 2
yield 3
try:
yield work("last")
except KeyError:
yield 5
yield 6


def work(stage):
print(stage)
if stage == "first":
raise ValueError(stage)
return 9
print(tuple(synchronous(work)))
async def consume():
result = []
async for value in asynchronous(work):
result.append(value)
return result
print(asyncio.run(consume()))
29 changes: 29 additions & 0 deletions tests/fixtures/sequential_generator_handlers.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
# Compile with each named CPython version; the committed .pyc files are
# exercised by the existing Rust integration suite.

# The frame-wide StopIteration intrinsic is emitted starting in Python 3.12.

def synchronous(work):
try:
yield work("first")
except ValueError:
yield 2
yield 3
try:
yield work("last")
except KeyError:
yield 5
yield 6


async def asynchronous(work):
try:
yield work("first")
except ValueError:
yield 2
yield 3
try:
yield work("last")
except KeyError:
yield 5
yield 6
16 changes: 16 additions & 0 deletions tests/integration.rs
Original file line number Diff line number Diff line change
Expand Up @@ -169,3 +169,19 @@ fn py314_hello() {
"py3.14 hello",
);
}

#[test]
fn sequential_generator_handlers_keep_the_shared_mainline() {
// CALL_INTRINSIC_1 3 starts in 3.12; 3.11 has no frame-wide handler.
for version in ["3.11", "3.12", "3.13", "3.14"] {
let (src, clean) = decompile_fixture(
&format!("sequential_generator_handlers.{version}.pyc"));
assert!(clean, "Python {version}: output marked incomplete:\n{src}");
let body = " try:\n yield work('first')\n except ValueError:\n yield 2\n yield 3\n try:\n yield work('last')\n except KeyError:\n yield 5\n yield 6";
for definition in ["def synchronous(work):", "async def asynchronous(work):"] {
let expected = format!("{definition}\n{body}");
assert_contains(&src, &[&expected], version);
}
assert!(!src.contains("else:"), "Python {version}: spurious handler else:\n{src}");
}
}
Binary file added tests/pyc/sequential_generator_handlers.3.11.pyc
Binary file not shown.
Binary file added tests/pyc/sequential_generator_handlers.3.12.pyc
Binary file not shown.
Binary file added tests/pyc/sequential_generator_handlers.3.13.pyc
Binary file not shown.
Binary file added tests/pyc/sequential_generator_handlers.3.14.pyc
Binary file not shown.