Skip to content

Give bit[n] a width-carrying int representation; normalize negative indices - #404

Open
TheGupta2012 wants to merge 1 commit into
mainfrom
feature-385-391-bit-repr-negative-indices
Open

Give bit[n] a width-carrying int representation; normalize negative indices#404
TheGupta2012 wants to merge 1 commit into
mainfrom
feature-385-391-bit-repr-negative-indices

Conversation

@TheGupta2012

@TheGupta2012 TheGupta2012 commented Aug 24, 2026

Copy link
Copy Markdown
Member

Fixes #385
Fixes #391

Combined because both rewrite the same index-resolution path.

#385bit[n] operators

A bit[n] value was stored as a Python str, so every bitwise, shift and index operation reached an operator str does not implement and escaped a raw TypeError through the public API.

BitValue (an int subclass carrying an explicit width) is now the internal representation; the "1010" string form is produced only at serialisation. Bit 0 is the most-significant bit, matching the existing angle_bit_string convention. |, &, ^, ~, <<, >> evaluate and re-mask to n bits, b[i] and b[a:c] read, and operands of unequal width raise a ValidationError.

Shared helpers bits_to_int / int_to_bits keep the conversion in one place.

#391 — negative indices

Qasm3Analyzer.normalize_index resolves -1 to the last element and is applied at every index-resolution site, so behaviour cannot drift between paths. Qasm3Validator.validate_register_index now returns the normalized index, which callers write back into the AST — passes downstream of unroll expect concrete non-negative integers.

Covers arrays (including multi-dimensional and assignment targets), bit[n], qubit[n], let aliases, and both endpoints of a range. An index still out of bounds after normalization raises, reporting the index as written.

Tests

tests/qasm3/test_expressions.py — each operator with its expected value and width, width-mismatch errors, single-bit and ranged reads, and a dumps() round-trip. New tests/qasm3/test_negative_indices.py — negative reads, writes, ranges, stepped ranges, and an out-of-range negative that must still error.

One pre-existing test changed: test_extern_function_call asserted a bool-initialized bit[2] leaking Python True into the serialized extern-arg AST; it now emits the correct "01".

828 passed, 3 skipped. pylint 10.00/10, black + isort clean.

🤖 Generated with Claude Code

@argus-eye

argus-eye Bot commented Aug 24, 2026

Copy link
Copy Markdown

Argus review

Auto-review is off for this repo. Tick the box below to run a review on this PR.

  • Trigger Argus review

Estimated cost

  • Files changed: 12
  • Diff lines (±): 882
  • Historical avg: ~243.6k tokens · ~$0.95 · across last 10 review(s)

Tip: you can also comment @argus-eye review at any time.

@coderabbitai

coderabbitai Bot commented Aug 24, 2026

Copy link
Copy Markdown

Warning

Review limit reached

Next included review available in 59 minutes.

View limit details

Limit details: You’ve used the included review currently available.

You've used all free OSS reviews for now. Wait for the free limit to reset to keep reviewing this public repository.

Learn how review limits work.

Review configuration:

⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro Plus

Run ID: 48a35e97-0764-41c0-8599-6ba3e48045ac

📥 Commits

Reviewing files that changed from the base of the PR and between 9d278a0 and cc80ec7.

📒 Files selected for processing (12)
  • CHANGELOG.md
  • src/pyqasm/analyzer.py
  • src/pyqasm/elements.py
  • src/pyqasm/expressions.py
  • src/pyqasm/maps/expressions.py
  • src/pyqasm/pulse/validator.py
  • src/pyqasm/transformer.py
  • src/pyqasm/validator.py
  • src/pyqasm/visitor.py
  • tests/qasm3/subroutines/test_subroutines.py
  • tests/qasm3/test_expressions.py
  • tests/qasm3/test_negative_indices.py

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.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@codecov-commenter

codecov-commenter commented Aug 24, 2026

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 87.19512% with 21 lines in your changes missing coverage. Please review.

Files with missing lines Patch % Lines
src/pyqasm/analyzer.py 78.04% 9 Missing ⚠️
src/pyqasm/pulse/validator.py 44.44% 5 Missing ⚠️
src/pyqasm/elements.py 69.23% 4 Missing ⚠️
src/pyqasm/maps/expressions.py 96.42% 1 Missing ⚠️
src/pyqasm/transformer.py 93.33% 1 Missing ⚠️
src/pyqasm/validator.py 91.66% 1 Missing ⚠️

📢 Thoughts on this report? Let us know!

@TheGupta2012
TheGupta2012 force-pushed the feature-385-391-bit-repr-negative-indices branch from 11f8de3 to f65532d Compare August 24, 2026 08:59
…e 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>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

2 participants