Skip to content

Add angle() casts, bit[n] -> int[n], and an explicit cast table - #409

Open
TheGupta2012 wants to merge 1 commit into
feature-385-391-bit-repr-negative-indicesfrom
feature-399-angle-and-bit-casts
Open

Add angle() casts, bit[n] -> int[n], and an explicit cast table#409
TheGupta2012 wants to merge 1 commit into
feature-385-391-bit-repr-negative-indicesfrom
feature-399-angle-and-bit-casts

Conversation

@TheGupta2012

Copy link
Copy Markdown
Member

Closes #399

Problem

angle(x) / angle[n](x) from a float or an angle, and int[n](b) / uint[n](b) from a bit[n], were all rejected with a generic Invalid initialization value for variable '...'. The spec's allowed-casts table permits all of them, and its own example narrows an angle[20] to an angle[10].

Approach

Fixed-point angle representation. AngleValue (src/pyqasm/elements.py) is a float subclass that carries the register width alongside the value. The float itself stays the unrounded angle, so a declared angle reaches a gate call with the value it always had; bits and resize apply the spec's round(value / (2*pi) * 2**n) quantization on demand. Narrowing truncates the low-order bits, the hardware-friendly of the spec's two permitted behaviours: angle[4] holding "0111" narrows to angle[2] as "01", where rounding would give "10". This makes the spec's worked bit patterns directly assertable, and it keeps the existing Variable.angle_bit_string field as the single place the pattern is read from.

Cast table. ALLOWED_CASTS (src/pyqasm/maps/expressions.py) records the supported source -> target pairs in one readable place, so an unsupported cast reports Cannot cast 'angle' to 'bit' instead of the generic message. Rows follow the spec's table, with pyqasm's long-standing differences listed inline rather than silently changed — this PR flips no currently-accepted cast to rejected. A cast argument whose type cannot be resolved (a binary expression, a function call) skips the check entirely, so no program that validated before is rejected on this basis.

bit[n] to int[n]. Built on BitValue from #404. An equal-width source is read as two's complement, so bit[8] b = "11111111"; int[8](b) is -1, not an out-of-range error. A wider target has no sign bit to reinterpret and stays unsigned.

Size-less angle(...). A designator-less angle(x) now takes its fixed-point width from the assignment context, which the spec's angle[10] c; c = angle(a + b); requires. Other types still require the cast width to match the declaration, because their casts must commit to a width before the assignment site is reached; generalizing that rule is a separate change.

Tests

tests/qasm3/test_casting.py, 22 cases: bit[n] to int[n]/uint[n] including the two's-complement and wider-target paths; the spec's worked angle bit patterns (angle[4](pi) as "1000", angle[8](7 * (pi / 8)) as "01110000", angle[6](pi / 2) as "010000"); truncating narrowing; lossless widening; both of the issue's angle examples; and each unsupported cast asserting a ValidationError that names both types. The casts that already worked are pinned with their values.

Float_test in tests/qasm3/resources/variables.py has its previously commented-out angle[8] a = angle[8](f); restored. Float_to_Bit_test's expected message changes from the value-level wording to Cannot cast 'float' to 'bit'.

Full suite: 850 passed, 3 skipped. pylint 10.00/10, isort, black, and mypy clean apart from the pre-existing tabulate stub error.

Notes

  • float(angle) is deliberately untouched. The cast table marks it No, but the spec's own angle comparison example uses it, and pyqasm accepts it today. This should be raised on openqasm/openqasm to settle the inconsistency before anyone changes the behaviour either way. It is recorded as a deviation in ALLOWED_CASTS and pinned by a test.
  • float(duration): the issue states pyqasm rejects this and that the rejection is correct. It does not — duration v = 10ns; float[32] w = float[32](v); validates today, as do duration casts to bool, int, uint, and angle. Behaviour is unchanged here, but the issue's premise is wrong and ALLOWED_CASTS records the reality.
  • Pre-existing, not addressed: Qasm3ExprEvaluator.angle_var_in_expr leaks across statements, so angle[4] a = pi; angle[2] b = pi / 2; angle[4] c = a; angle[2] d = b; raises All 'Angle' variables in binary expression must have the same size. This reproduces on main. The new tests keep one angle width per program to avoid it.
  • No sibling repo (qbraid, qbraid-qir) imports the changed internals, and unrolled gate-parameter output is byte-identical to main.

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

@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: 586c1b35-5069-4f0c-8627-364d14454a29

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.

`angle(x)` / `angle[n](x)` from a float or an angle, and `int[n](b)` /
`uint[n](b)` from a `bit[n]`, were all rejected with a generic "Invalid
initialization value".

Angles now carry their fixed-point width. `AngleValue` is a `float`
subclass holding the width alongside the value: the float stays the
unrounded angle, so a declared angle reaches a gate call unchanged, while
`bits` and `resize` apply the spec's `round(value / (2*pi) * 2**n)`
quantization on demand. Narrowing truncates the low-order bits, so
`angle[4]` "0111" narrows to `angle[2]` "01".

`int[n](b)` reads a `bit[n]` register of equal width as two's complement,
so "11111111" is -1 rather than out of range.

A designator-less `angle(x)` now takes its width from the assignment
context, as the spec's own `angle[10] c; c = angle(a + b);` requires.
Other types still require the cast width to match the declaration,
because their casts must commit to a width before the assignment site.

`ALLOWED_CASTS` records the supported source -> target pairs in one place,
so an unsupported cast reports `Cannot cast 'angle' to 'bit'` instead of
the generic message. Its rows follow the spec's allowed-casts table, with
pyqasm's long-standing differences listed inline. A cast argument whose
type cannot be resolved skips the check, so no program that validated
before is rejected on this basis.

Closes #399

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
@TheGupta2012
TheGupta2012 force-pushed the feature-399-angle-and-bit-casts branch from 12adee1 to 07f691a Compare August 24, 2026 13:48
@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 97.22222% with 2 lines in your changes missing coverage. Please review.

Files with missing lines Patch % Lines
src/pyqasm/elements.py 95.45% 1 Missing ⚠️
src/pyqasm/maps/expressions.py 94.11% 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