PR #1551: Fixing Nested Modifier Propagation
Today I want to share a fix I contributed to Quantinuum’s Guppy that went deeper into the compiler than anything I’d touched before.
The problem: Guppy’s with modifier blocks — used to apply quantum operations like dagger, control, and power — would silently ignore outer modifier constraints when nested. Code that should have raised unitary violation errors compiled without complaint. This was a genuine correctness bug: programs that were semantically invalid were passing through the compiler as if they were fine.
The root cause: CFGBuilder.visit_With was creating inner CFGs without passing accumulated modifier flags from outer blocks. Each nested scope started fresh, with no awareness of the modifiers already in effect above it. There was an existing FIXME patch attempting to address this, but it was applied too late in the compilation pipeline and didn’t handle recursive nesting at all.
The fix: PR #1551 involved three architectural changes:
- A new
Modifiersclass innodes.py— a dedicated structure that collects dagger, control, and power modifiers together, with aflags()method that derives the correspondingUnitaryFlags, handles dagger cancellation, and exposes consistenthas_<modifier>()methods for querying modifier state. - Refactored
visit_Withincfg/builder.py— now processes modifier objects before constructing inner CFGs and properly accumulates flags viaself.cfg.unitary_flags | modifiers.flags(), so outer context is always propagated into nested scopes. - Removal of the old workaround — with the new approach in place, the incomplete FIXME patch was removed entirely rather than patched further.
The PR closes Issue #1324 and adds 14 integration tests covering nested combinations of dagger, control, and power modifiers, including triple-nesting and scenarios that previously compiled silently.
Thanks to the maintainers at Quantinuum for the patient and thorough review process.
For more technical details, refer to PR #1551.