From 2e46e939e1fef6db101b5eb582d7bd8b2ed746b6 Mon Sep 17 00:00:00 2001 From: Ryan Hill Date: Thu, 26 Mar 2026 17:20:40 -0500 Subject: [PATCH 1/4] Fix classical register visibility inside box scope (#302) Box blocks were treated the same as function/gate scopes in `get_from_visible_scope()`, restricting access to only constants and qubits from the global scope. This caused measurement statements inside box blocks to fail with "Missing clbit register declaration" even when the classical register was declared at the top level. Per the OpenQASM 3.0 spec, `box` is a timing directive that executes in the enclosing scope, not an isolated scope. Move `in_box_scope()` from the restricted group (function/gate/pulse) to the block group which has full parent scope visibility. Closes #302 Co-Authored-By: Claude Opus 4.6 (1M context) --- src/pyqasm/scope.py | 3 +-- tests/qasm3/test_box.py | 24 ++++++++++++++++++++++++ 2 files changed, 25 insertions(+), 2 deletions(-) diff --git a/src/pyqasm/scope.py b/src/pyqasm/scope.py index 12484eed..9f2d4417 100644 --- a/src/pyqasm/scope.py +++ b/src/pyqasm/scope.py @@ -196,7 +196,6 @@ def get_from_visible_scope(self, var_name: str) -> Variable | None: if ( self.in_function_scope() or self.in_gate_scope() - or self.in_box_scope() or self.in_pulse_scope() ): if var_name in curr_scope: @@ -207,7 +206,7 @@ def get_from_visible_scope(self, var_name: str) -> Variable | None: # we also need to return the variable if it is a constant or qubit # in the global scope, as it can be used in the function or gate return global_scope[var_name] - if self.in_block_scope(): + if self.in_block_scope() or self.in_box_scope(): var_found = None for scope, context in zip(reversed(self._scope), reversed(self._context)): if context != Context.BLOCK: diff --git a/tests/qasm3/test_box.py b/tests/qasm3/test_box.py index d8ef0d1a..771447b0 100644 --- a/tests/qasm3/test_box.py +++ b/tests/qasm3/test_box.py @@ -232,3 +232,27 @@ def test_box_statement_error(qasm_code, error_message, error_span, caplog): loads(qasm_code).unroll() assert error_message in str(err.value) assert error_span in caplog.text + + +def test_box_measurement_with_classical_register(): + """Measurement inside a box should have access to classical registers + declared in the enclosing (global) scope. + + Regression: box scope was treated like function/gate scope, restricting + access to only constants and qubits from global scope. Classical registers + were invisible, causing 'Missing clbit register declaration' errors. + See: https://github.com/qBraid/pyqasm/issues/302 + """ + qasm_str = """ + OPENQASM 3.0; + include "stdgates.inc"; + qubit[4] q; + bit[3] c; + + box [100ns] { + h q[0]; + c[1] = measure q[1]; + } + """ + result = loads(qasm_str) + result.validate() From 0ba9da2f1691b3c3bcd2c2e57f36a66da3ab7555 Mon Sep 17 00:00:00 2001 From: Ryan Hill Date: Thu, 26 Mar 2026 17:21:52 -0500 Subject: [PATCH 2/4] Update CHANGELOG.md for #306 Co-Authored-By: Claude Opus 4.6 (1M context) --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 75ddc052..3a646b12 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -23,6 +23,7 @@ Types of changes: ### Removed ### Fixed +- Fixed classical register declarations not being visible inside `box` scope, causing "Missing clbit register declaration" errors for measurement statements inside box blocks. ([#306](https://github.com/qBraid/pyqasm/pull/306)) ### Dependencies From 50bff13030cfcaeadec2e7380b2bddeaede88eb0 Mon Sep 17 00:00:00 2001 From: Ryan Hill Date: Thu, 26 Mar 2026 17:30:05 -0500 Subject: [PATCH 3/4] Update pylintrc and format scope.py Remove deprecated `suggestion-mode` option from .pylintrc (removed in pylint 3.3.0) and reformat multi-line condition in scope.py. Co-Authored-By: Claude Opus 4.6 (1M context) --- .pylintrc | 4 ---- src/pyqasm/scope.py | 6 +----- 2 files changed, 1 insertion(+), 9 deletions(-) diff --git a/.pylintrc b/.pylintrc index 26998d87..44744d8e 100644 --- a/.pylintrc +++ b/.pylintrc @@ -104,10 +104,6 @@ recursive=no # source root. source-roots= -# When enabled, pylint would attempt to guess common misconfiguration and emit -# user-friendly hints instead of false-positive error messages. -suggestion-mode=yes - # Allow loading of arbitrary C extensions. Extensions are imported into the # active Python interpreter and may run arbitrary code. unsafe-load-any-extension=no diff --git a/src/pyqasm/scope.py b/src/pyqasm/scope.py index 9f2d4417..e804942a 100644 --- a/src/pyqasm/scope.py +++ b/src/pyqasm/scope.py @@ -193,11 +193,7 @@ def get_from_visible_scope(self, var_name: str) -> Variable | None: curr_scope = self.get_curr_scope() if self.in_global_scope(): return global_scope.get(var_name, None) - if ( - self.in_function_scope() - or self.in_gate_scope() - or self.in_pulse_scope() - ): + if self.in_function_scope() or self.in_gate_scope() or self.in_pulse_scope(): if var_name in curr_scope: return curr_scope[var_name] if var_name in global_scope and ( From b13d4e7862ff163563fdf0e0f88007327e6a6c95 Mon Sep 17 00:00:00 2001 From: Ryan Hill Date: Fri, 27 Mar 2026 11:39:29 -0500 Subject: [PATCH 4/4] Add test for box measurement with enclosing block scope Test that a box nested inside a non-global scope (e.g. an if-block) can still access classical registers from the enclosing global scope. Co-Authored-By: Claude Opus 4.6 (1M context) --- tests/qasm3/test_box.py | 26 ++++++++++++++++++++++++++ 1 file changed, 26 insertions(+) diff --git a/tests/qasm3/test_box.py b/tests/qasm3/test_box.py index 771447b0..90a8de91 100644 --- a/tests/qasm3/test_box.py +++ b/tests/qasm3/test_box.py @@ -256,3 +256,29 @@ def test_box_measurement_with_classical_register(): """ result = loads(qasm_str) result.validate() + + +def test_box_measurement_with_enclosing_block_scope(): + """Measurement inside a box nested within a non-global scope (e.g. an + if-block) should still have access to classical registers declared in + the enclosing global scope. + + Complements test_box_measurement_with_classical_register by ensuring the + scope walker traverses intermediate BLOCK contexts to reach the global + scope where the classical register is declared. + """ + qasm_str = """ + OPENQASM 3.0; + include "stdgates.inc"; + qubit[4] q; + bit[3] c; + + if (true) { + box { + h q[0]; + c[1] = measure q[1]; + } + } + """ + result = loads(qasm_str) + result.validate()