feat: Enable panic stack traces in the emulator - #2271
Conversation
There was a problem hiding this comment.
🟡 Changes recommended
The committed uv dependency override points to a developer-local Desktop wheel path, which will break installs/CI for other environments.
Once you've addressed the issues Copilot identified, you can request another Copilot review.
Pull request overview
Adds support for rendering Selene-provided panic stack traces in the Guppy emulator when debug_mode=True, and enforces constraints needed for meaningful source locations.
Changes:
- Render and surface Selene panic stack traces through
EmulatorErrorwhen available. - Require minimal optimization when building an emulator in
debug_mode, and propagate a debug build flag to the emulator builder. - Add snapshot tests and optimization-level tests covering stack traces and debug-mode constraints.
File summaries
| File | Description |
|---|---|
| tests/test_stack_traces.py | Adds snapshot-based harness to validate emulator panic stack trace formatting. |
| tests/test_optimization.py | Adds tests asserting debug-mode requires minimal optimization for emulation and that debug compilation still emits debug metadata. |
| tests/stack_traces/init.py | Makes tests.stack_traces importable for the snapshot test harness. |
| tests/stack_traces/test_helper.py | Shared helper used to validate multi-file stack traces. |
| tests/stack_traces/multi_file_out_of_bounds.py | Multi-frame/multi-file panic scenario exercised via import-time emulation run. |
| tests/stack_traces/multi_file_out_of_bounds.err | Snapshot expectation for the multi-file out-of-bounds panic trace. |
| tests/stack_traces/global_panic_unwrap_error.py | Panic generated via global compiler path to ensure traces work beyond directly annotated panics. |
| tests/stack_traces/global_panic_unwrap_error.err | Snapshot expectation for the unwrap panic trace. |
| tests/stack_traces/always_panic.py | Simple panic scenario for baseline stack trace rendering. |
| tests/stack_traces/always_panic.err | Snapshot expectation for the simple panic trace. |
| guppylang/src/guppylang/optimizer.py | Documents/debug-gates emulator debug mode to minimal opt and forwards debug_mode into emulator building. |
| guppylang/src/guppylang/emulator/stack_trace.py | New stack trace renderer that converts Selene stack frames into source-annotated snippets. |
| guppylang/src/guppylang/emulator/exceptions.py | Enhances EmulatorError string rendering to include panic header + rendered Guppy traceback when available. |
| guppylang/src/guppylang/defs.py | Extends internal emulator builder plumbing to accept debug_mode and pass a Selene build arg. |
| guppylang/pyproject.toml | Adds a local-path uv source override for selene-sim (problematic for CI/portability). |
Review details
- Files reviewed: 14/16 changed files
- Comments generated: 2
- Review effort level: Lite
💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.
| [tool.uv.sources] | ||
| selene-sim = { path = "../../../../Desktop/selene_sim-0.3.0-py3-none-macosx_12_0_arm64.whl" } |
| FILES = [ | ||
| path | ||
| for path in TEST_CASES_DIR.glob("*.py") | ||
| if path.name != "__init__.py" and path.name != "test_helper.py" | ||
| ] |
aborgna-q
left a comment
There was a problem hiding this comment.
Looks great!
Just some suggestions
| """Compile this function for emulation with the configured optimizations.""" | ||
| """Compile this function for emulation with the configured optimizations. | ||
|
|
||
| Emulation in debug mode (enabling panic traces) requires minimal optimization. |
There was a problem hiding this comment.
What happens when optimization is enabled?
Do stack traces get mangled, or do we lose all the info?
Could this be a warning instead (once we have those #1654)?
There was a problem hiding this comment.
The main issues arising so far is stack traces being squashed due to LLVM tail calls and also Quantinuum/tket2#1964 - I think at least until the latter is solved I think having it error is better as showing wrong code snippets just looks confusing even with a warning, but later I think keeping it as a warning and just showing partial traces when some of the info is optimised is an option.
|
|
||
| def append_frame_snippet(span: Span, label: str | None, function_name: str) -> None: | ||
| renderer = DiagnosticsRenderer(source) | ||
| renderer.render_snippet( |
There was a problem hiding this comment.
If the source code has been updated since compilation this may fail and hide the actual panic.
Could we put it in a try block?
| header = EmulatorError._panic_header(underlying_exception) | ||
| return f"{header}\n{rendered}" |
There was a problem hiding this comment.
This hides any --- stdout --- or --- stderr --- in the underlying selene exception (coming after the stack trace).
Closes #1761
Note that the debug mode only works if no optimisation has been done on the HUGR (and there is only an automatic check and error for this when using it through the emulator interface, otherwise this is on the user).
Requires a selene release with Quantinuum/selene#210 for CI / merging, so draft until then.