Skip to content

Bare expression statements crash with AttributeError instead of raising ValidationError #388

Closed

Description

@TheGupta2012

Limitation

An expression followed by a semicolon is a syntactically valid OpenQASM 3 statement — the
classical types chapter uses this form throughout to
illustrate operator results, for example under
Integers and
Angles.

pyqasm reaches these statements and dereferences .name on the expression node. For any
non-Identifier expression this raises an AttributeError that escapes the public API. The
same dereference fails for an undeclared gate applied to zero qubits.

Example QASM failure

Straight from the spec's Integers section:

import pyqasm

m = pyqasm.loads('''
OPENQASM 3.0;
int[32] a = 2;
int[32] b = 3;

a * b;   // 6
b / a;   // 1
b % a;   // 1
''')
m.validate()
AttributeError: 'BinaryExpression' object has no attribute 'name'

The spec's Angles section fails identically:

OPENQASM 3.0;
angle[4] a = 7 * (pi / 8);
angle[4] b = pi / 8;
a + b;
// AttributeError: 'BinaryExpression' object has no attribute 'name'

And the gate-application example in
Applying gates:

OPENQASM 3.0;
g1 q1;
// AttributeError: 'str' object has no attribute 'name'

Change Requested

  1. Never surface an AttributeError from a statement pyqasm does not handle. Every
    unhandled statement or expression form must raise ValidationError carrying the source
    span.
  2. Decide and document the semantics of a bare expression statement. It has no effect on the
    quantum program, so the reasonable options are to evaluate and discard it, or to reject it
    with a clear message such as "Expression statement has no effect". Either is acceptable;
    the current crash is not.
  3. An undefined gate identifier must report the undefined name, not fail on an attribute
    lookup.

Implementation Details

  • The root cause is the visitor's dispatch table in src/pyqasm/visitor.py:193:

    qasm3_ast.ExpressionStatement: lambda x: self._visit_function_call(x.expression),

    Every expression statement is assumed to be a function call, so _visit_function_call
    dereferences x.expression.name — which exists on an Identifier but not on a
    BinaryExpression, a UnaryExpression, an IndexExpression, or a literal.

  • The fix is to branch on the expression type before dispatching: route FunctionCall to
    _visit_function_call and give every other form the chosen behaviour from point 2 above.
    The generic fallback at visitor.py:3492 already raises the correct error shape and can be
    reused.

  • The 'str' object has no attribute 'name' variant comes from the gate-operation path, where
    the operation name has already been reduced to a string before the undefined-gate check
    runs. Reorder so the lookup failure is reported first.

  • Add a defensive try/except (AttributeError, TypeError) around the visitor's top-level
    dispatch that re-raises as ValidationError with the statement span. This converts any
    remaining internal slips in the tree into the documented error type — a cheap safety net
    beyond this specific fix.

  • Tests: tests/qasm3/test_validation.py — assert ValidationError (not AttributeError)
    for a bare binary expression, a bare function call, a bare identifier, and an undefined gate
    with zero and with one qubit operand.

Activity

  1. self-assigned this
    on Aug 21, 2026
  2. added
    bugSomething isn't working
    program-validationIssues related to program validation
    error-handlingIssues related to error handing and error propagation
    llm-assistedUsed LLMs to fine tune issue description.
    on Aug 21, 2026
  3. removed their assignment
    on Aug 24, 2026
  4. danielgaskins commented on Sep 10, 2026

    @danielgaskins
    Contributor

    I'm happy to handle this one. I reproduced the remaining failures on current main: bare literals, identifiers, and binary expressions raise AttributeError, while a standalone built-in function call raises KeyError. Undefined gates already return ValidationError. I would route expression statements through the existing expression evaluator, which evaluates and discards pure values while retaining statements emitted by subroutine or extern calls. I would add focused validation and unrolling tests for pure expressions, built-in calls, user subroutine calls, unknown calls, and undefined gates. This keeps the fix at the failing dispatch boundary without a broad exception wrapper.

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

Metadata

Metadata

Assignees

No one assigned

    Labels

    bugSomething isn't workingerror-handlingIssues related to error handing and error propagationllm-assistedUsed LLMs to fine tune issue description.program-validationIssues related to program validation

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions