fix!: Dagger flags cancelation inside daggarable functions - #2003
Conversation
|
This PR contains breaking changes to the public Python API. Breaking changes summary |
|
| Branch | na/1984-flags |
| Testbed | Linux |
Click to view all benchmark results
| Benchmark | hugr_bytes | Benchmark Result bytes x 1e3 (Result Δ%) | Upper Boundary bytes x 1e3 (Limit %) | hugr_nodes | Benchmark Result nodes (Result Δ%) | Upper Boundary nodes (Limit %) |
|---|---|---|---|---|---|---|
| tests/benchmarks/test_big_array.py::test_big_array_compile | 📈 view plot 🚷 view threshold | 120.67 x 1e3(0.00%)Baseline: 120.67 x 1e3 | 121.87 x 1e3 (99.01%) | 📈 view plot 🚷 view threshold | 5,659.00(0.00%)Baseline: 5,659.00 | 5,715.59 (99.01%) |
| tests/benchmarks/test_ctrl_flow.py::test_many_ctrl_flow_compile | 📈 view plot 🚷 view threshold | 36.19 x 1e3(0.00%)Baseline: 36.19 x 1e3 | 36.55 x 1e3 (99.01%) | 📈 view plot 🚷 view threshold | 1,582.00(0.00%)Baseline: 1,582.00 | 1,597.82 (99.01%) |
| tests/benchmarks/test_queue_push_pop.py::test_queue_push_benchmark_compile | 📈 view plot 🚷 view threshold | 9.95 x 1e3(0.00%)Baseline: 9.95 x 1e3 | 10.05 x 1e3 (99.01%) | 📈 view plot 🚷 view threshold | 347.00(0.00%)Baseline: 347.00 | 350.47 (99.01%) |
| tests/benchmarks/test_queue_push_pop.py::test_queue_push_pop_benchmark_compile | 📈 view plot 🚷 view threshold | 14.11 x 1e3(0.00%)Baseline: 14.11 x 1e3 | 14.25 x 1e3 (99.01%) | 📈 view plot 🚷 view threshold | 470.00(0.00%)Baseline: 470.00 | 474.70 (99.01%) |
There was a problem hiding this comment.
Pull request overview
This PR fixes unitary-flag inference for modifier (with ...) blocks so that unitary flags declared on a function via the @guppy(...) decorator are preserved inside nested modifier blocks (preventing accidental cancellation via UnitaryFlags.accumulate, e.g. with dagger).
Changes:
- Split function-level decorator unitary flags from block-accumulated unitary flags in
CFGBuilder, and ensure decorator flags are preserved when constructing modifier-block CFGs. - Add a regression test ensuring a
@guppy(daggerable=True)function containing awith dagger:block is still treated as daggerable for type checking. - Add the corresponding expected error output for the new regression test.
Reviewed changes
Copilot reviewed 3 out of 3 changed files in this pull request and generated 5 comments.
| File | Description |
|---|---|
| tests/error/modifier_errors/decorator_dagger_is_preserved.py | New regression test exercising daggerable decorator preservation through a dagger modifier block. |
| tests/error/modifier_errors/decorator_dagger_is_preserved.err | Expected error output confirming the dagger-context constraint is enforced. |
| guppylang-internals/src/guppylang_internals/cfg/builder.py | Adjust CFG construction to track decorator flags separately from accumulated modifier flags and preserve decorator flags in nested blocks. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
Codecov Report✅ All modified and coverable lines are covered by tests. Additional details and impacted files@@ Coverage Diff @@
## main #2003 +/- ##
=======================================
Coverage 93.02% 93.02%
=======================================
Files 152 152
Lines 14609 14615 +6
=======================================
+ Hits 13590 13596 +6
Misses 1019 1019 ☔ View full report in Codecov by Harness. 🚀 New features to boost your workflow:
|
daggarable functionsdaggarable functions
| cfg = CFGBuilder().build(node.body, True, self.globals, accumulated_flags) | ||
| cfg = CFGBuilder().build( |
There was a problem hiding this comment.
Wouldn't the easier fix be to just pass self.unitary_flags here instead of accumulated_flags?
There was a problem hiding this comment.
do you mean self.function_unitary_flags?
There was a problem hiding this comment.
I mean keep the single unitary_flags field on this class as before and set accumulated_flags = self.unitary_flags | modifiers.flags() here?
There was a problem hiding this comment.
When there are more nested modifier blocks, I need one variable to store the outer function flag and another one to store the "dynamic" flag of the modifiers.
If I have only one variable, how do I know if self.unitary_flags is related to the function (thus we need to do self.unitary_flags | modifiers.flags()) or to the previous modifier (we need to use the .accumulate method that deletes even daggers)
There was a problem hiding this comment.
Why do we need to delete even daggers? If I add with dagger, then the body ought to be daggerable imo, even if it's an even dagger
There was a problem hiding this comment.
with dagger:
with control:
with dagger:
Does the dagger constraint apply here?
There was a problem hiding this comment.
Nevermind, I changed my mind. I think this is ok as is 👍
Closes #1984.
This PR fixes an issue where
daggerblocks incorrectly cancelled thedaggerableproperty of the enclosing function: adaggerablefunction should always enforce thedaggerableconstraint within its body. Previously, adaggerblock inside adaggerablefunction was treated as non-daggerablebecause the block'sdaggermodifier cleared thedaggerableflag inherited from the function decorator. However, since the context in which a function is called is not known when checking its body, the unitary constraints declared in the function signature must always be enforced.The bug was generating a bad
cfg.unitary_flagsin the modifier blocks, affecting both the metadata and the type checking.BREAKING CHANGE: guppylang-internals: remaned parameter
unitary_flagsCFGBuilder.buildtofunction_unitary_flags