Repository navigation
Bare expression statements crash with AttributeError instead of raising ValidationError #388
Description
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
- Never surface an
AttributeErrorfrom a statementpyqasmdoes not handle. Every
unhandled statement or expression form must raiseValidationErrorcarrying the source
span. - 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. - 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
dereferencesx.expression.name— which exists on anIdentifierbut not on a
BinaryExpression, aUnaryExpression, anIndexExpression, or a literal. -
The fix is to branch on the expression type before dispatching: route
FunctionCallto
_visit_function_calland give every other form the chosen behaviour from point 2 above.
The generic fallback atvisitor.py:3492already 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 asValidationErrorwith 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— assertValidationError(notAttributeError)
for a bare binary expression, a bare function call, a bare identifier, and an undefined gate
with zero and with one qubit operand.
Activity
danielgaskins commentedon Sep 10, 2026
I'm happy to handle this one. I reproduced the remaining failures on current
main: bare literals, identifiers, and binary expressions raiseAttributeError, while a standalone built-in function call raisesKeyError. Undefined gates already returnValidationError. 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.