Support bit and bit[n] subroutine return values, including return measure q - #410
Conversation
`bit x = f();` crashed with `AttributeError: 'NoneType' object has no attribute 'value'` because `validate_return_statement` read `return_type.size.value` unconditionally, and `bit` has no size. The declared width is now evaluated by the caller and passed in, and a cast failure is re-raised as `Return type mismatch for subroutine 'f'. Expected bit[2] but got float`, naming both types instead of leaking an `AttributeError` or a bare cast message. A bit declaration is emitted verbatim while the subroutine definition is not, so a `bit` return also left an unresolved `f()` call in the output. The call is now replaced by the expression standing in for its return value: a `BitstringLiteral` when the register is statically known, and a reference when it is not. The substitution is applied to a copy of the statement so the source AST keeps its call and the module can be unrolled again -- `depth()` re-unrolls a copy. `return measure q;` needed both halves. `_visit_measurement` never applied the function qubit transform, so any measurement on a formal argument raised "Missing register declaration". It now rewrites the operand innermost scope outwards, exactly as `_visit_reset` does. The measurement itself has no compile-time value, so it is bound to a temporary bit register declared at the call site; the caller's variable is then initialised from that register, which keeps the emitted program valid OpenQASM and gives the measurement a classical target. Assign- then-return returns the local bit by reference instead, and a nested subroutine forwards whichever the inner call produced. Closes #387 Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
|
Important Review skippedAuto reviews are disabled on this repository. Please check the settings in the CodeRabbit UI or the ⚙️ Run configurationConfiguration used: Path: .coderabbit.yaml Review profile: CHILL Plan: Pro Plus Run ID: You can disable this status message by setting the Use the checkbox below for a quick retry:
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
`_visit_function_call` only shallow-copies each body statement, so the
inner `QuantumMeasurement` node stays shared with the subroutine
definition. Rewriting `statement.measure.qubit` in place therefore wrote
the caller's qubits onto the definition itself, and the next visit of
that body read a list where an `Identifier` was expected:
AttributeError: 'list' object has no attribute 'name'
The shallow copy protects `statement.qubits` for `_visit_reset`, which
rebinds one level up; a measurement's operand sits one level deeper.
Reported as `validate()` followed by `unroll()` on an assign-then-return
subroutine, but the trigger is any second visit of a subroutine body
containing a measurement, with or without a return: calling such a
subroutine twice in a single `unroll()` failed the same way.
`_visit_measurement` now copies the statement and its `measure` node
before applying the transform. `return measure q;` was already immune
because `_synthesize_measurement_return` deep-copies the expression.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Argus reviewAuto-review is off for this repo. Tick the box below to run a review on this PR.
Estimated cost
Tip: you can also comment |
Codecov Report✅ All modified and coverable lines are covered by tests. 📢 Thoughts on this report? Let us know! |
Closes #387
Problem
bit x = f();crashed withAttributeError: 'NoneType' object has no attribute 'value'—validate_return_statementreadreturn_type.size.valueunconditionally, andbitcarries no size.bit[n]returns did not crash but emitted an unresolvedf()call, since a bit declaration survives unrolling while the subroutine definition does not.return measure q;failed earlier still:_visit_measurementnever applied the function qubit transform, so any measurement on a formal argument raised "Missing register declaration for 'q'".Approach
validate_return_statement. A cast failure is re-raised asReturn type mismatch for subroutine 'f'. Expected bit[2] but got float, naming both types.BitstringLiteralwhen the register is statically known, a reference when it is not. The substitution is applied to a copy of the declaration/assignment, so the source AST keeps its call and the module can be unrolled again (depth()re-unrolls a copy)._visit_measurementnow rewrites its qubit operand innermost scope outwards, exactly as_visit_resetalready does. This removes the# TODO: handle in-function measurements.Temp-bit decision for
return measure q;A measurement has no compile-time value, so the return expression cannot fold to a literal. A temporary bit register is synthesised at the call site (
__<fn>_return_<n>, sized from the declared return type) and the measurement targets it; the caller's variable is then initialised from that register:Reasoning: the emitted program stays valid OpenQASM — a measurement needs a classical target, and folding a runtime value into the caller's variable would emit a stale
"0". Each call gets its own numbered register, so repeated calls do not collide.Assign-then-return (
bit c; c = measure q; return c;) needs no temp — the localbit c;already reaches the caller's output, so the identifier is returned by reference. A nested subroutine forwards whichever expression the inner call produced.Tests
tests/qasm3/subroutines/test_subroutine_returns.py(new module;test_subroutines.pyis near the 1000-line pylint cap):-> bit,-> bit[n],return measure q, a multi-qubitreturn measure q, assign-then-return, assignment to an already-declared caller variable, nested forwarding of both a measured and a literal bit return, repeated calls getting distinct temp registers, unrolling the same module twice, and declared/returned type mismatches (includingreturn measure q;from a void subroutine).Checks
pytest842 passed / 3 skipped (baseline 828 / 3).pylint10.00/10,isort,black,mypyclean;tox -e docsbuilds.Stacked on #404 (
feature-385-391-bit-repr-negative-indices); rebases ontomainonce #404 merges.🤖 Generated with Claude Code