Skip to content

Support classical value bit slicing on int / uint - #408

Open
TheGupta2012 wants to merge 1 commit into
feature-385-391-bit-repr-negative-indicesfrom
feature-395-classical-bit-slicing
Open

Support classical value bit slicing on int / uint#408
TheGupta2012 wants to merge 1 commit into
feature-385-391-bit-repr-negative-indicesfrom
feature-395-classical-bit-slicing

Conversation

@TheGupta2012

Copy link
Copy Markdown
Member

Closes #395

Problem

Indexing an int[n] / uint[n] at the bit level was unsupported. Reads failed with Invalid initialization value for variable ..., slice assignment failed with the misleading Cannot cast 'str' to 'IntType', and intArr[0][0] failed with Invalid index for variable.

Approach

  • Qasm3Analyzer.resolve_index is extracted from analyze_classical_indices, so one subscript can be resolved against a bit width instead of an array dimension. normalize_index gains an optional type_name, so an out-of-range bit index reports the declared width: Index 32 out of range for 'int[32]' variable 'myInt'.
  • read_int_bits / write_int_bits work in LSB-first integer space, since myInt[0] is the least-significant bit — the opposite of the bit[n] convention, where bit 0 is the most-significant bit of the stored value. Conversion happens at the boundary, and a slice read returns a width-carrying BitValue.
  • A chained subscript is split into element indices plus one trailing bit subscript rather than flattened, so intArr[0][0] and a[0][0][3] resolve.
  • _visit_classical_assignment detects a bit-slice lvalue up front and bypasses the scalar cast that produced the str-to-IntType error.
  • Writing the top bit of a signed int[n] re-reads as negative (int[4] x = 0; x[3] = 1; gives -8). uint[n] stays unsigned.

Reversed slice: myInt[-1:-16] is rejected

The spec is self-inconsistent here. Its normative range rule says a:b implies step 1 and the set is {a, a+c, ...} with a+mc <= b, so 31:16 is empty — and "a register cannot be indexed by an empty index set". Yet the same section's example assigns myInt[-1:-16] to a bit[16].

pyqasm follows the normative rule and rejects the ambiguous form, which also keeps descending ranges consistent with every other slice in the codebase. myInt[-1:-1:-16] is well-defined by the same formula and gives the reversed slice. Both are covered by a test.

Tested

tests/qasm3/test_expressions.py: the spec example end to end (1, 0, 0, 3, 0xAF), single-bit read, stepped slice read, slice assignment, signed wrap on a top-bit write, 1-D and 2-D array-element bit access, uint variants, both reversed-slice forms, out-of-range index naming the width, and RHS width mismatch. Also a regression guard for a descending bit[n] slice, which dropped its final position before this change.

840 passed, 3 skipped. pylint 10.00/10; isort, black, and mypy clean apart from the pre-existing tabulate stub error.

The indexing_non_array fixture moves from int to float: int x; x[0] = 4; is now a valid lvalue with an out-of-range value, so it no longer exercises that path.

Out of scope

  • The spec also allows bit slicing on angle[n]; not implemented here.
  • myInt[{0,2}] (a DiscreteSet subscript) still raises TypeError: 'DiscreteSet' object is not subscriptable, unchanged from main.
  • Pre-existing in Give bit[n] a width-carrying int representation; normalize negative indices #404: qasm_variable_type_cast keeps the RHS width when a BitValue initializes a bit[n], so bit[8] w = <16-bit slice> is accepted silently. Left for a reviewer call on where that belongs.

Stacked on #404 (feature-385-391-bit-repr-negative-indices); rebases onto main once #404 merges.

@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: 6
  • Diff lines (±): 606
  • 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

Important

Review skipped

Auto reviews are disabled on this repository. Please check the settings in the CodeRabbit UI or the .coderabbit.yaml file in this repository. To trigger a single review, invoke the @coderabbitai review command.

⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro Plus

Run ID: 1ad68b1f-9459-4482-8bbb-22a6258762a6

You can disable this status message by setting the reviews.review_status to false in the CodeRabbit configuration file.

Use the checkbox below for a quick retry:

  • 🔍 Trigger review

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.

Implements the OpenQASM 3 classical value bit slicing rules for `int[n]`
and `uint[n]`, as reads and as assignment targets, including elements of
arrays of integers.

Index 0 of an integer is its least-significant bit, the opposite of the
`bit[n]` register convention, where bit 0 is the most-significant bit of
the stored value. The two conventions are converted at the boundary:
`read_int_bits` / `write_int_bits` work in LSB-first integer space and
hand back a width-carrying `BitValue`.

Three sites changed:

- `Qasm3Analyzer.resolve_index` is extracted from
  `analyze_classical_indices`, so a single subscript can be resolved
  against a bit width rather than an array dimension. The extraction is
  behaviour-preserving for the container path; `normalize_index` gains
  an optional `type_name` so an out-of-range bit index names the
  declared width: `Index 32 out of range for 'int[32]' variable 'myInt'`.
- `Qasm3ExprEvaluator._get_var_value` takes an integer bit-view path
  before the container path.
- `_visit_classical_assignment` detects a bit-slice lvalue up front and
  bypasses the scalar cast, which is what produced
  `Cannot cast 'str' to 'IntType'`.

A chained subscript is split as element indices plus one trailing bit
subscript rather than flattened, so `intArr[0][0] = 1` and
`a[0][0][3] = 1` resolve instead of failing with
`Invalid index for variable`.

A descending range needs an explicit negative step. `myInt[-1:-16]` is
an empty index set under the spec's normative range definition, even
though the spec's own example assigns it to a `bit[16]`; the ambiguous
form is rejected and `myInt[-1:-1:-16]` gives the reversed slice.

The `indexing_non_array` fixture moves from `int` to `float`, because
`int x; x[0] = 4;` is now a valid lvalue with an out-of-range value.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
@TheGupta2012
TheGupta2012 force-pushed the feature-395-classical-bit-slicing branch from f373eac to aba4c79 Compare August 26, 2026 11:11
@TheGupta2012
TheGupta2012 marked this pull request as ready for review August 26, 2026 11:11
@codecov-commenter

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 94.56522% with 5 lines in your changes missing coverage. Please review.

Files with missing lines Patch % Lines
src/pyqasm/analyzer.py 96.07% 2 Missing ⚠️
src/pyqasm/visitor.py 93.93% 2 Missing ⚠️
src/pyqasm/expressions.py 87.50% 1 Missing ⚠️

📢 Thoughts on this report? Let us know!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants