diff --git a/src/decompiler.rs b/src/decompiler.rs index 33a3465d..7e1768f5 100644 --- a/src/decompiler.rs +++ b/src/decompiler.rs @@ -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 diff --git a/tests/behavior/cases/b_sequential_generator_handlers.py b/tests/behavior/cases/b_sequential_generator_handlers.py new file mode 100644 index 00000000..54ae3d4c --- /dev/null +++ b/tests/behavior/cases/b_sequential_generator_handlers.py @@ -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())) diff --git a/tests/fixtures/sequential_generator_handlers.py b/tests/fixtures/sequential_generator_handlers.py new file mode 100644 index 00000000..ffc228ba --- /dev/null +++ b/tests/fixtures/sequential_generator_handlers.py @@ -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 diff --git a/tests/integration.rs b/tests/integration.rs index f4af5082..7e54b7fc 100644 --- a/tests/integration.rs +++ b/tests/integration.rs @@ -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}"); + } +} diff --git a/tests/pyc/sequential_generator_handlers.3.11.pyc b/tests/pyc/sequential_generator_handlers.3.11.pyc new file mode 100644 index 00000000..008ddc55 Binary files /dev/null and b/tests/pyc/sequential_generator_handlers.3.11.pyc differ diff --git a/tests/pyc/sequential_generator_handlers.3.12.pyc b/tests/pyc/sequential_generator_handlers.3.12.pyc new file mode 100644 index 00000000..ccfe238b Binary files /dev/null and b/tests/pyc/sequential_generator_handlers.3.12.pyc differ diff --git a/tests/pyc/sequential_generator_handlers.3.13.pyc b/tests/pyc/sequential_generator_handlers.3.13.pyc new file mode 100644 index 00000000..3c083b3a Binary files /dev/null and b/tests/pyc/sequential_generator_handlers.3.13.pyc differ diff --git a/tests/pyc/sequential_generator_handlers.3.14.pyc b/tests/pyc/sequential_generator_handlers.3.14.pyc new file mode 100644 index 00000000..fa151051 Binary files /dev/null and b/tests/pyc/sequential_generator_handlers.3.14.pyc differ