You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
Give bit[n] a width-carrying int representation and normalize negative indices
Fixes#385 and #391 in one branch because both rewrite the same
index-resolution path.
#385: `bit[n]` values were stored inconsistently as `str` (from
bitstring literals) or `np.ndarray` (uninitialized), so every bitwise,
shift, or index op reached a Python operator that `str` cannot handle
and escaped the public API as a raw `TypeError`. The internal
representation is now a `BitValue` — an `int` subclass carrying the
register width — with shared `bits_to_int` / `int_to_bits` helpers in
`pyqasm.analyzer`. `qasm3_expression_op_map` recognizes `BitValue`
operands, enforces equal-width for `|`, `&`, `^`, re-masks `~` /
shift / binary results to the declared width, and raises
`ValidationError` for width-mismatched bitwise ops (the evaluator
attaches the source span so the error is properly located). `b[i]`
returns a single-bit `int`; `b[a:c]` returns a `BitValue` of the sliced
width. Indexed writes (`b[i] = ...`, `b[-1] = ...`) rebuild the
integer via a shared `_write_bit_slice` helper. The serialized AST is
unchanged: `bit[4] a = "1010";` still round-trips through `dumps()`.
#391: Added `Qasm3Analyzer.normalize_index`, applied at every
index-resolution site (arrays incl. multi-dim and assignment targets,
qubit registers, classical registers, `bit[n]`, `let` aliases,
branch conditions, and the transformer's range-expansion helpers).
`validate_register_index` now returns the normalized index so callers
rewrite the emitted `IntegerLiteral`; downstream passes
(`remove_idle_qubits`, `reverse_qubit_order`, and the register
consolidator) only see concrete non-negative indices. An index still
outside `[-size, size)` after normalization raises the existing
out-of-range error and reports the index **as written in the source**.
Range endpoints normalize per-endpoint and keep each function's
existing convention: qubit ranges stay end-exclusive (matching Python
slice semantics), classical array ranges stay end-inclusive.
Two deliberate behavior changes fall out of the new representation:
- `test_extern_function_call` expected output changed. A `bit[2]
b1 = true` extern arg now serializes as `"01"` (the canonical
bitstring for the register's value) rather than leaking Python
`True`. The test's expected output was the pre-existing bug.
- Oversized int inits to a `bit[n]` (e.g. `bit[4] c = 999`) now mask
to width. Previously the raw value was stored uncapped; casts
through `qasm_variable_type_cast` now go through `BitValue`.
Test suite: 795 passed, 3 skipped (all pre-existing). Two CLI tests
(`test_validate_qasm_with_invalid_file`, `test_validate_command_with_invalid_file`)
fail on this branch and equally on `main` — pre-existing terminal-width
truncation in Rich console output, unrelated to this change. Ran
`black`, `isort`, `pylint`, `mypy` directly rather than through `tox`
because `tox` would `pip install` into the shared environment.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Copy file name to clipboardExpand all lines: CHANGELOG.md
+2Lines changed: 2 additions & 0 deletions
Display the source diff
Display the rich diff
Original file line number
Diff line number
Diff line change
@@ -15,6 +15,7 @@ Types of changes:
15
15
## Unreleased
16
16
17
17
### Added
18
+
- Negative indices are now honored across arrays, `bit[n]`, `qubit[n]`, and `let` aliases, including ranges: `myArray[-1]`, `a[-1] = 10`, `h q[-1]`, `bit c = b[-1]`, `let last_three = two[-4:-1]`. An index still outside `[-size, size)` after normalization raises `ValidationError` and names the index as written. ([#391](https://github.com/qBraid/pyqasm/issues/391))
18
19
19
20
### Improved / Modified
20
21
@@ -26,6 +27,7 @@ Types of changes:
26
27
- Fixed `pyqasm validate` wrapping its diagnostics at the console width, which split a file path longer than the width across lines mid-token and left it neither copyable nor clickable. The error console now uses `soft_wrap`, keeping one diagnostic per line.
27
28
- Fixed an indirect cycle between gate definitions exhausting the Python stack: `gate a q { b q; }` with `gate b q { a q; }` raised a bare `RecursionError` naming nothing, while the direct case was already reported cleanly. The guard compared the body's gate name against one name, so it saw only a cycle of length one. It now tests membership of the whole expansion chain, and names the path: `Recursive definitions not allowed for gate 'a' (a -> b -> a)`. A gate reached twice down separate paths is a diamond, not a cycle, and still expands. ([#369](https://github.com/qBraid/pyqasm/issues/369))
28
29
- Fixed a nested external custom gate counting the depth of the decomposition it skipped, the shape the [#352](https://github.com/qBraid/pyqasm/issues/352) fix did not reach: `unroll(external_gates=["outer"])` on a gate whose body calls another custom gate emitted one statement but reported `depth() == 13`. The suppression flag was assigned and cleared without save-restore, so the inner gate clobbered the outer gate's state in both directions. It is now saved and restored, and the depth is recorded once, from the outermost external gate. ([#367](https://github.com/qBraid/pyqasm/issues/367))
30
+
- Fixed `|`, `&`, `^`, `~`, `<<`, `>>` and indexing on `bit[n]` escaping a raw `TypeError`, since the value was stored as a Python `str`. A `bit[n]` now carries its width internally, so these operators evaluate and re-mask to `n` bits, `b[i]` and `b[a:c]` read, and mismatched widths raise a `ValidationError`. The `"1010"` literal form still round-trips through `dumps()`. ([#385](https://github.com/qBraid/pyqasm/issues/385))
0 commit comments