Skip to content

Fix/ Statement copying in functions and added loop limit in for loops #223

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 5 commits into from
Jul 1, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ Types of changes:
### Fixed
- Fixed multiple axes error in circuit visualization of decomposable gates in `draw` method. ([#209](https://github.com/qBraid/pyqasm/pull/210))
- Fixed depth calculation for decomposable gates by computing depth of each constituent quantum gate.([#211](https://github.com/qBraid/pyqasm/pull/211))
- Optimized statement copying in `_visit_function_call` with shallow-copy fallback to deepcopy and added `max_loop_iters` loop‐limit check in for loops.([#223](https://github.com/qBraid/pyqasm/pull/223))
### Dependencies

### Other
Expand Down
17 changes: 15 additions & 2 deletions src/pyqasm/visitor.py
Original file line number Diff line number Diff line change
Expand Up @@ -1924,6 +1924,16 @@ def _visit_forin_loop(self, statement: qasm3_ast.ForInLoop) -> list[qasm3_ast.St
span=statement.span,
)

# Check if the loop range exceeds the maximum allowed iterations
if len(irange) > self._loop_limit:
raise_qasm3_error(
# pylint: disable-next=line-too-long
f"Loop range '{len(irange)-1}' exceeded max allowed '{self._loop_limit}' iterations",
err_type=LoopLimitExceededError,
error_node=statement,
span=statement.span,
)

i: Optional[Variable] # will store iteration Variable to update to loop scope

result = []
Expand Down Expand Up @@ -2068,9 +2078,12 @@ def _visit_function_call(
result = []
for function_op in subroutine_def.body:
if isinstance(function_op, qasm3_ast.ReturnStatement):
return_statement = copy.deepcopy(function_op)
return_statement = copy.copy(function_op)
break
result.extend(self.visit_statement(copy.deepcopy(function_op)))
try:
result.extend(self.visit_statement(copy.copy(function_op)))
except (TypeError, copy.Error):
result.extend(self.visit_statement(copy.deepcopy(function_op)))

return_value = None
if return_statement:
Expand Down
37 changes: 36 additions & 1 deletion tests/qasm3/test_loop.py
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@
import pytest

from pyqasm.entrypoint import loads
from pyqasm.exceptions import ValidationError
from pyqasm.exceptions import LoopLimitExceededError, ValidationError
from tests.utils import (
check_single_qubit_gate_op,
check_single_qubit_rotation_op,
Expand Down Expand Up @@ -332,3 +332,38 @@ def test_convert_qasm3_for_loop_unsupported_type(caplog):

assert "Error at line 9, column 16" in caplog.text
assert 'for bit b in "001"' in caplog.text


def test_for_loop_limit_exceeded():
"""Test that exceeding the loop limit raises LoopLimitExceededError for for loops."""
qasm_str = """
OPENQASM 3.0;
include "stdgates.inc";
qubit[4] q;
bit[4] c;

for int i in [0:1000] {
h q[0];
}
"""
result = loads(qasm_str)
with pytest.raises(LoopLimitExceededError):
result.unroll(max_loop_iters=100)


def test_for_loop_discrete_set_limit_exceeded():
"""Test that exceeding the loop limit raises LoopLimitExceededError
for for loops with discrete sets."""
qasm_str = """
OPENQASM 3.0;
include "stdgates.inc";
qubit[4] q;
bit[4] c;

for int i in {0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15} {
h q[0];
}
"""
result = loads(qasm_str)
with pytest.raises(LoopLimitExceededError):
result.unroll(max_loop_iters=10)