Hacktoberfest 2026: los issues que los mantenedores marcaron para octubre, abiertos y aptos para principiantes. Explorar issues de Hacktoberfest

`break` / `continue`: escapes as `BreakSignal` from a `for` loop, silently drops the iteration body in a `while` loop

Abierto
#386 0 comentarios 0 reacciones 1 asignado Ver en GitHub

Los mantenedores suelen responder en 1 día

@TheGupta2012 ya está trabajando en esto.

Desde el 21/8/2026.

  • #402 de @TheGupta2012 — abierto

Evaluación

Este issue todavía no se ha evaluado.

Descripción

bug error-handling llm-assisted qasm3

Limitation

Breaking and continuing loops
defines break and continue inside both for and while bodies. pyqasm implements them
as internal control-flow exceptions, and both loop visitors mishandle them — in two different
ways, from one shared root cause.

  • for loops: no handler at all. The signal propagates out of validate() / unroll()
    to the caller as BreakSignal / ContinueSignal — internal types that are not
    ValidationError and are not part of the public API.
  • while loops: a handler exists, but it discards every statement of the iteration in
    which the break occurred. The gates executed before the break vanish from the unrolled
    program with no error raised.

The second is the more dangerous of the two: it is silent wrong output.

Example QASM failure

for — internal signal escapes
import pyqasm

m = pyqasm.loads('''
OPENQASM 3.0;
for int i in [0:3] {
    break;
}
''')
m.validate()
pyqasm.exceptions.BreakSignal: None

Same for continue, and for the common nested-in-a-branch form:

OPENQASM 3.0;
for int i in [0:3] { continue; }              // ContinueSignal: None
OPENQASM 3.0;
for int i in [0:3] { if (i == 1) { break; } }   // BreakSignal: None
while — silent loss of the iteration body
OPENQASM 3.0;
include "stdgates.inc";
qubit[1] q;
int[8] i = 0;
while (i < 3) { h q[0]; i += 1; break; }
>>> m = pyqasm.loads(qasm); m.unroll(); print(pyqasm.dumps(m))
OPENQASM 3.0;
include "stdgates.inc";
qubit[1] q;

The h q[0] executed before the break is gone, and depth() reports 0. Expected output is
a single h q[0].

The same while loop without the break unrolls correctly to three h q[0], which
confirms the loss is caused by the signal handling and not by while-loop unrolling in general.

Change Requested

  1. for loop unrolling must catch both signals. continue abandons the rest of the current
    iteration and proceeds to the next value; break abandons the rest of the current
    iteration and stops iterating.
  2. Both loops must retain the statements already emitted in the interrupted iteration.
    Only the statements after the break / continue are skipped.
  3. Neither signal type may be observable from validate(), unroll(), or any other public
    entry point.

Implementation Details

  • _visit_while_loop (src/pyqasm/visitor.py:2700) has the handler at visitor.py:2739:

    try:
        result.extend(self.visit_basic_block(statement.block))
    except LoopControlSignal as lcs:
        ...
    

    visit_basic_block raises from inside the call, so it never returns and result.extend
    never runs — the whole iteration's output is discarded. That is the silent-drop bug.

  • _visit_forin_loop (src/pyqasm/visitor.py:2432) has no try/except around its
    result.extend(self.visit_basic_block(...)) calls at visitor.py:2500, so the signal
    escapes.

  • The shared fix is in visit_basic_block, not in either loop. It must be able to hand
    back the statements it produced before the signal was raised. Two workable shapes:
    attach the partial result to the signal object (lcs.partial_result), or have
    visit_basic_block return (statements, signal_or_None) instead of raising. The second is
    cleaner but touches every caller.

  • LoopControlSignal already exists as a common base in src/pyqasm/exceptions.py:59, so a
    single except clause covers both signals — no new exception types are needed.

  • Minor, in the same file: ContinueSignal.__init__ ignores its msg argument and always
    passes the literal "continue" to super(), and neither signal calls Exception.__init__
    with a message — which is why the escaping error prints as BreakSignal: None. Worth
    tidying while here.

  • Check the same handling inside a switch case body, which is another block-scoped
    statement list that can legally contain break.

  • Tests: tests/qasm3/test_loop.py — break/continue directly in a for body, nested one
    and two if levels deep, inside a nested for, the while+break case above asserting the
    pre-break gate survives, and an assertion that the unrolled gate sequence matches the
    truncated iteration count.

Lenguaje dominante
Python
Estrellas
29
Forks
33
Merge medio
2 d 4 h
PR fusionados (30 d)
15

Preparar el entorno

Primeros pasos

  1. Lee el issue completo y luego la guía de contribución del proyecto.
  2. Comenta en el issue que vas a ocuparte — evita que dos personas hagan lo mismo.
  3. Haz un fork del repositorio y trabaja en una rama.
  4. Abre un pull request que haga referencia al número del issue.

Más de qBraid/pyqasm

Todos los issues de qBraid/pyqasm

Issues similares

Más issues de Python

Recibe los nuevos issues en tu correo

Un resumen breve de issues de GitHub para principiantes.