From 1a297c52e2a684b37ce5a1f4d67aa65408f843d4 Mon Sep 17 00:00:00 2001 From: Michael Papadopoulos Date: Wed, 5 Aug 2026 00:22:39 -0400 Subject: [PATCH 1/5] Start on visitor cleanup --- src/pyqasm/pulse/validator.py | 8 +++---- src/pyqasm/visitor.py | 39 ++++++++++++++++++++++------------- 2 files changed, 29 insertions(+), 18 deletions(-) diff --git a/src/pyqasm/pulse/validator.py b/src/pyqasm/pulse/validator.py index 888f818..fea9f7b 100644 --- a/src/pyqasm/pulse/validator.py +++ b/src/pyqasm/pulse/validator.py @@ -114,10 +114,10 @@ def validate_duration_or_stretch_statements( Generic validation function for DurationType and StretchType declarations or assignments. Args: - statement: The AST statement node - statement_type: The expected AST node type - base_type: The declared type (DurationType or StretchType) - rvalue: The initializer or assigned value + statement: The AST statement node. + statement_type: The expected AST node type. + base_type: The declared type, function does nothing if not DurationType or StretchType + rvalue: The initializer or assigned value. global_scope: Global symbol table. Raises: diff --git a/src/pyqasm/visitor.py b/src/pyqasm/visitor.py index fd4d0dd..836ac9a 100644 --- a/src/pyqasm/visitor.py +++ b/src/pyqasm/visitor.py @@ -1606,6 +1606,24 @@ def _visit_generic_gate_operation( # pylint: disable=too-many-branches, too-man return result + def _validate_duration_or_stretch_statements( + self, statement: qasm3_ast.Statement, base_type: Any, rvalue: Any + ) -> None: + """Validate statements that declare or assign duration or stretch values. + + Args: + statement: The statement to validate. + base_type: The declared type. + rvalue: The initializer or assigned value. + """ + global_scope = self._scope_manager.get_global_scope() + PulseValidator.validate_duration_or_stretch_statements( + statement, + base_type, + rvalue, + global_scope + ) + def _visit_constant_declaration( self, statement: qasm3_ast.ConstantDeclaration ) -> list[qasm3_ast.Statement]: @@ -1639,12 +1657,10 @@ def _visit_constant_declaration( ) if statement.init_expression: - global_scope = self._scope_manager.get_global_scope() - PulseValidator.validate_duration_or_stretch_statements( + self._validate_duration_or_stretch_statements( statement=statement, base_type=statement.type, - rvalue=statement.init_expression, - global_scope=global_scope, + rvalue=statement.init_expression ) try: @@ -1825,13 +1841,10 @@ def _visit_classical_declaration( # populate the variable if statement.init_expression: - - global_scope = self._scope_manager.get_global_scope() - PulseValidator.validate_duration_or_stretch_statements( + self._validate_duration_or_stretch_statements( statement=statement, base_type=base_type, rvalue=statement.init_expression, - global_scope=global_scope, ) if isinstance(statement.init_expression, qasm3_ast.ArrayLiteral): @@ -1895,7 +1908,7 @@ def _visit_classical_declaration( angle_bit_string=angle_val_bit_string, ) - if isinstance(base_type, qasm3_ast.DurationType): + if isinstance(base_type, (qasm3_ast.DurationType, qasm3_ast.StretchType)): PulseValidator.validate_duration_literal_value(init_value, statement, base_type) if self._module._device_cycle_time: variable.time_unit = "dt" @@ -2017,12 +2030,10 @@ def _visit_classical_assignment( rvalue = statement.rvalue lvar_base_type = lvar.base_type # type: ignore[union-attr] if rvalue: - global_scope = self._scope_manager.get_global_scope() - PulseValidator.validate_duration_or_stretch_statements( + self._validate_duration_or_stretch_statements( statement=statement, base_type=lvar_base_type, - rvalue=rvalue, - global_scope=global_scope, + rvalue=rvalue ) if binary_op is not None: rvalue = qasm3_ast.BinaryExpression( @@ -2083,7 +2094,7 @@ def _visit_classical_assignment( ) rvalue_eval = rvalue_raw - if isinstance(lvar_base_type, qasm3_ast.DurationType): + if isinstance(lvar_base_type, (qasm3_ast.DurationType, qasm3_ast.StretchType)): PulseValidator.validate_duration_literal_value(rvalue_eval, statement, lvar_base_type) if lvar.readonly: # type: ignore[union-attr] From 46fd472b2b45d7cfafa9d7ccc38cdb031191799d Mon Sep 17 00:00:00 2001 From: Michael Papadopoulos Date: Wed, 5 Aug 2026 01:29:58 -0400 Subject: [PATCH 2/5] remove a single-use function, simplify check_only --- src/pyqasm/visitor.py | 129 +++++++++++++++++------------------------- 1 file changed, 53 insertions(+), 76 deletions(-) diff --git a/src/pyqasm/visitor.py b/src/pyqasm/visitor.py index 836ac9a..471f172 100644 --- a/src/pyqasm/visitor.py +++ b/src/pyqasm/visitor.py @@ -20,6 +20,7 @@ """ import copy +import functools import logging import re import sys @@ -83,6 +84,20 @@ logger = logging.getLogger(__name__) logger.propagate = False +def easy_check_only(func): + """Decorator for functions which use check_only to return an empty list.""" + + @functools.wraps(func) + def wrapper(self, *args, **kwargs): + """Wrapper that intercepts the return value and replaces it with an empty list.""" + result = func(self, *args, **kwargs) + if self.check_only: + return [] + else: + return result + + return wrapper + # pylint: disable-next=too-many-instance-attributes class QasmVisitor: @@ -193,6 +208,7 @@ def _construct_visit_map(self): qasm3_ast.CalibrationGrammarDeclaration: self._visit_calibration_grammar_declaration, } + @easy_check_only def _visit_quantum_register( self, register: qasm3_ast.QubitDeclaration ) -> list[qasm3_ast.QubitDeclaration]: @@ -273,8 +289,6 @@ def _visit_quantum_register( logger.debug("Added labels for register '%s'", str(register)) - if self._check_only: - return [] return [register] # pylint: disable-next=too-many-locals,too-many-branches,too-many-statements @@ -558,6 +572,7 @@ def _validate_bitstring_literal_width(self, init_value, base_size, var_name, sta span=statement.span, ) + @easy_check_only def _visit_measurement( # pylint: disable=too-many-locals,too-many-branches,too-many-statements self, statement: qasm3_ast.QuantumMeasurementStatement ) -> list[qasm3_ast.QuantumMeasurementStatement]: @@ -696,41 +711,9 @@ def _visit_measurement( # pylint: disable=too-many-locals,too-many-branches,too ), ) - if self._check_only: - return [] - return unrolled_measurements - def _resolve_unindexed_reset_qubit(self, statement: qasm3_ast.QuantumReset) -> bool: - """Resolve a reset whose operand is a bare ``Identifier`` rather than a - register slot: a physical qubit ("$n") or the internal pulse register. - - Args: - statement (qasm3_ast.QuantumReset): The reset statement whose operand - is being resolved. Renamed in place for OpenPulse programs. - - Returns: - bool: True if the operand was resolved and the statement needs no - further unrolling. - """ - if not isinstance(statement.qubits, qasm3_ast.Identifier): - return False - - qubit_name = statement.qubits.name - if qubit_name.startswith("$") and qubit_name[1:].isdigit(): - if self._openpulse_grammar_declared: - # OpenPulse program: rename to the internal virtual register used by the - # pulse visitor. - statement.qubits.name = f"{INTERNAL_QUBIT_REGISTER}[{qubit_name[1:]}]" - else: - # Plain QASM program: keep the physical qubit identifier as-is, the same - # as gate and measurement operands do, so the statement still serialises - # as "reset $2;" and the qubit is counted. - self._register_physical_qubit(qubit_name) - return True - - return is_internal_qubit_register(qubit_name) - + @easy_check_only def _visit_reset(self, statement: qasm3_ast.QuantumReset) -> list[qasm3_ast.QuantumReset]: """Visit a reset statement element. @@ -738,15 +721,32 @@ def _visit_reset(self, statement: qasm3_ast.QuantumReset) -> list[qasm3_ast.Quan statement (qasm3_ast.QuantumReset): The reset statement to visit. Returns: - None + list[qasm3_ast.QuantumReset] - A list of unrolled resets. """ logger.debug("Visiting reset statement '%s'", str(statement)) - if self._resolve_unindexed_reset_qubit(statement): - return [statement] - if len(self._function_qreg_size_map) > 0: # atleast in SOME function scope + # Resolve a reset whose operand is a bare ``Identifier`` rather than a + # register slot: a physical qubit ("$n") or the internal pulse register. + if isinstance(statement.qubits, qasm3_ast.Identifier): + qubit_name = statement.qubits.name + if qubit_name.startswith("$") and qubit_name[1:].isdigit(): + if self._openpulse_grammar_declared: + # OpenPulse program: rename to the internal virtual register used by the + # pulse visitor. + statement.qubits.name = f"{INTERNAL_QUBIT_REGISTER}[{qubit_name[1:]}]" + else: + # Plain QASM program: keep the physical qubit identifier as-is, the same + # as gate and measurement operands do, so the statement still serialises + # as "reset $2;" and the qubit is counted. + self._register_physical_qubit(qubit_name) + return [statement] + + if is_internal_qubit_register(qubit_name): + return [statement] + + if len(self._function_qreg_size_map) > 0: # at least in SOME function scope # since we may have multiple function scopes, we need to transform the qubits - # to use the global qreg identifiers + # to use the global qreg identifiers. for transform_map, size_map in zip( reversed(self._function_qreg_transform_map), reversed(self._function_qreg_size_map) ): @@ -784,9 +784,6 @@ def _visit_reset(self, statement: qasm3_ast.QuantumReset) -> list[qasm3_ast.Quan ), ) - if self._check_only: - return [] - return unrolled_resets def _expand_barrier_ranges( @@ -1086,6 +1083,7 @@ def _update_qubit_depth_for_gate( qubit_node = self._module._qubit_depths[(qubit_name, qubit_id)] qubit_node.depth = max_involved_depth + @easy_check_only # pylint: disable=too-many-branches, too-many-locals def _visit_basic_gate_operation( self, @@ -1188,9 +1186,6 @@ def _visit_basic_gate_operation( for final_gate in result: Qasm3Analyzer.verify_gate_qubits(final_gate, operation.span) - if self._check_only: - return [] - return result def _visit_break(self, statement: qasm3_ast.BreakStatement) -> None: @@ -1205,6 +1200,7 @@ def _visit_continue(self, statement: qasm3_ast.ContinueStatement) -> None: error_node=statement, ) + @easy_check_only def _visit_custom_gate_operation( self, operation: qasm3_ast.QuantumGate, @@ -1304,11 +1300,9 @@ def _visit_custom_gate_operation( self._scope_manager.pop_scope() self._scope_manager.restore_context() - if self._check_only: - return [] - return result + @easy_check_only def _visit_external_gate_operation( self, operation: qasm3_ast.QuantumGate, @@ -1380,11 +1374,10 @@ def gate_function(*qubits): Qasm3Analyzer.verify_gate_qubits(final_gate, operation.span) self._scope_manager.restore_context() - if self._check_only: - return [] return result + @easy_check_only def _visit_phase_operation( self, operation: qasm3_ast.QuantumPhase, @@ -1439,11 +1432,9 @@ def _visit_phase_operation( # if it were in function scope, then the args would have been evaluated and added to the # qubit list - if self._check_only: - return [] - return [operation] + @easy_check_only def _visit_generic_gate_operation( # pylint: disable=too-many-branches, too-many-statements self, operation: qasm3_ast.QuantumGate | qasm3_ast.QuantumPhase, @@ -1601,9 +1592,6 @@ def _visit_generic_gate_operation( # pylint: disable=too-many-branches, too-man ), ) - if self._check_only: - return [] - return result def _validate_duration_or_stretch_statements( @@ -1624,6 +1612,7 @@ def _validate_duration_or_stretch_statements( global_scope ) + @easy_check_only def _visit_constant_declaration( self, statement: qasm3_ast.ConstantDeclaration ) -> list[qasm3_ast.Statement]: @@ -1737,11 +1726,9 @@ def _visit_constant_declaration( ) self._handle_extern_function_cleanup(statements, statement) - if self._check_only: - return [] - return statements + @easy_check_only # pylint: disable=too-many-branches, too-many-statements, too-many-locals def _visit_classical_declaration( self, statement: qasm3_ast.ClassicalDeclaration @@ -1973,11 +1960,9 @@ def _visit_classical_declaration( or statement.init_expression ) - if self._check_only: - return [] - return statements + @easy_check_only def _visit_classical_assignment( self, statement: qasm3_ast.ClassicalAssignment ) -> list[qasm3_ast.Statement]: @@ -2146,9 +2131,6 @@ def _visit_classical_assignment( self._handle_extern_function_cleanup(statements, statement) - if self._check_only: - return [] - return statements def _evaluate_array_initialization( @@ -2193,6 +2175,7 @@ def _update_branching_gate_depths(self) -> None: self._is_branch_clbits.clear() self._is_branch_qubits.clear() + @easy_check_only def _visit_branching_statement( self, statement: qasm3_ast.BranchingStatement ) -> list[qasm3_ast.Statement]: @@ -2321,9 +2304,6 @@ def ravel(bit_ind): if not self._in_branching_statement: self._update_branching_gate_depths() - if self._check_only: - return [] - return result # type: ignore[return-value] def _visit_forin_loop(self, statement: qasm3_ast.ForInLoop) -> list[qasm3_ast.Statement]: @@ -2402,6 +2382,7 @@ def _visit_forin_loop(self, statement: qasm3_ast.ForInLoop) -> list[qasm3_ast.St return [] return result + @easy_check_only def _visit_subroutine_definition( self, statement: qasm3_ast.SubroutineDefinition | qasm3_ast.ExternDeclaration ) -> Sequence[None | qasm3_ast.ExternDeclaration]: @@ -2448,8 +2429,6 @@ def _visit_subroutine_definition( statements.append(statement) self._subroutine_defns[fn_name] = statement - if self._check_only: - return [] return statements @@ -2655,7 +2634,7 @@ def _visit_alias_statement(self, statement: qasm3_ast.AliasStatement) -> list[No # this will only build a global alias map - # whenever we are referring to qubits , we will first check in the global map of registers + # whenever we are referring to qubits, we will first check in the global map of registers # if the register is present, we will use the global map to get the qubit labels # if not, we will check the alias map for the labels @@ -2791,6 +2770,7 @@ def _visit_switch_statement( # type: ignore[return] # each element in the list of the values # should be of const int type and no duplicates should be present + @easy_check_only def _evaluate_case(statements): # can not put 'context' outside # BECAUSE the case expression CAN CONTAIN VARS from global scope @@ -2803,8 +2783,6 @@ def _evaluate_case(statements): self._scope_manager.pop_scope() self._scope_manager.restore_context() - if self._check_only: - return [] return result case_fulfilled = False @@ -3256,6 +3234,7 @@ def _visit_calibration_grammar_declaration( return [statement] + @easy_check_only def _visit_include(self, include: qasm3_ast.Include) -> list[qasm3_ast.Statement]: """Visit an include statement element. @@ -3271,8 +3250,6 @@ def _visit_include(self, include: qasm3_ast.Include) -> list[qasm3_ast.Statement f"File '{filename}' already included", error_node=include, span=include.span ) self._included_files.add(filename) - if self._check_only: - return [] return [include] From aeccc52278be6bed19f0c066ed162926456dee85 Mon Sep 17 00:00:00 2001 From: Michael Papadopoulos Date: Wed, 5 Aug 2026 09:19:49 -0400 Subject: [PATCH 3/5] Apply comments and lint file --- src/pyqasm/visitor.py | 119 ++++++++++++++++++++++-------------------- 1 file changed, 61 insertions(+), 58 deletions(-) diff --git a/src/pyqasm/visitor.py b/src/pyqasm/visitor.py index 471f172..5059ac7 100644 --- a/src/pyqasm/visitor.py +++ b/src/pyqasm/visitor.py @@ -84,7 +84,8 @@ logger = logging.getLogger(__name__) logger.propagate = False -def easy_check_only(func): + +def semantic_check_gate(func): """Decorator for functions which use check_only to return an empty list.""" @functools.wraps(func) @@ -208,7 +209,7 @@ def _construct_visit_map(self): qasm3_ast.CalibrationGrammarDeclaration: self._visit_calibration_grammar_declaration, } - @easy_check_only + @semantic_check_gate def _visit_quantum_register( self, register: qasm3_ast.QubitDeclaration ) -> list[qasm3_ast.QubitDeclaration]: @@ -422,7 +423,7 @@ def _check_variable_type_size( base_type (Any): Base type of the variable. is_const (bool): whether the statement is constant declaration or not. Returns: - Int: size of the variable base type. + int: size of the variable base type. """ base_size = 1 if not isinstance(base_type, qasm3_ast.BoolType): @@ -572,7 +573,7 @@ def _validate_bitstring_literal_width(self, init_value, base_size, var_name, sta span=statement.span, ) - @easy_check_only + @semantic_check_gate def _visit_measurement( # pylint: disable=too-many-locals,too-many-branches,too-many-statements self, statement: qasm3_ast.QuantumMeasurementStatement ) -> list[qasm3_ast.QuantumMeasurementStatement]: @@ -713,7 +714,37 @@ def _visit_measurement( # pylint: disable=too-many-locals,too-many-branches,too return unrolled_measurements - @easy_check_only + def _resolve_unindexed_reset_qubit(self, statement: qasm3_ast.QuantumReset) -> bool: + """Resolve a reset whose operand is a bare ``Identifier`` rather than a + register slot: a physical qubit ("$n") or the internal pulse register. + + Args: + statement (qasm3_ast.QuantumReset): The reset statement whose operand + is being resolved. Renamed in place for OpenPulse programs. + + Returns: + bool: True if the operand was resolved and the statement needs no + further unrolling. + """ + if not isinstance(statement.qubits, qasm3_ast.Identifier): + return False + + qubit_name = statement.qubits.name + if qubit_name.startswith("$") and qubit_name[1:].isdigit(): + if self._openpulse_grammar_declared: + # OpenPulse program: rename to the internal virtual register used by the + # pulse visitor. + statement.qubits.name = f"{INTERNAL_QUBIT_REGISTER}[{qubit_name[1:]}]" + else: + # Plain QASM program: keep the physical qubit identifier as-is, the same + # as gate and measurement operands do, so the statement still serialises + # as "reset $2;" and the qubit is counted. + self._register_physical_qubit(qubit_name) + return True + + return is_internal_qubit_register(qubit_name) + + @semantic_check_gate def _visit_reset(self, statement: qasm3_ast.QuantumReset) -> list[qasm3_ast.QuantumReset]: """Visit a reset statement element. @@ -725,24 +756,8 @@ def _visit_reset(self, statement: qasm3_ast.QuantumReset) -> list[qasm3_ast.Quan """ logger.debug("Visiting reset statement '%s'", str(statement)) - # Resolve a reset whose operand is a bare ``Identifier`` rather than a - # register slot: a physical qubit ("$n") or the internal pulse register. - if isinstance(statement.qubits, qasm3_ast.Identifier): - qubit_name = statement.qubits.name - if qubit_name.startswith("$") and qubit_name[1:].isdigit(): - if self._openpulse_grammar_declared: - # OpenPulse program: rename to the internal virtual register used by the - # pulse visitor. - statement.qubits.name = f"{INTERNAL_QUBIT_REGISTER}[{qubit_name[1:]}]" - else: - # Plain QASM program: keep the physical qubit identifier as-is, the same - # as gate and measurement operands do, so the statement still serialises - # as "reset $2;" and the qubit is counted. - self._register_physical_qubit(qubit_name) - return [statement] - - if is_internal_qubit_register(qubit_name): - return [statement] + if self._resolve_unindexed_reset_qubit(statement): + return [statement] if len(self._function_qreg_size_map) > 0: # at least in SOME function scope # since we may have multiple function scopes, we need to transform the qubits @@ -1083,7 +1098,7 @@ def _update_qubit_depth_for_gate( qubit_node = self._module._qubit_depths[(qubit_name, qubit_id)] qubit_node.depth = max_involved_depth - @easy_check_only + @semantic_check_gate # pylint: disable=too-many-branches, too-many-locals def _visit_basic_gate_operation( self, @@ -1200,7 +1215,7 @@ def _visit_continue(self, statement: qasm3_ast.ContinueStatement) -> None: error_node=statement, ) - @easy_check_only + @semantic_check_gate def _visit_custom_gate_operation( self, operation: qasm3_ast.QuantumGate, @@ -1302,7 +1317,7 @@ def _visit_custom_gate_operation( return result - @easy_check_only + @semantic_check_gate def _visit_external_gate_operation( self, operation: qasm3_ast.QuantumGate, @@ -1377,7 +1392,7 @@ def gate_function(*qubits): return result - @easy_check_only + @semantic_check_gate def _visit_phase_operation( self, operation: qasm3_ast.QuantumPhase, @@ -1434,7 +1449,7 @@ def _visit_phase_operation( # qubit list return [operation] - @easy_check_only + @semantic_check_gate def _visit_generic_gate_operation( # pylint: disable=too-many-branches, too-many-statements self, operation: qasm3_ast.QuantumGate | qasm3_ast.QuantumPhase, @@ -1594,25 +1609,7 @@ def _visit_generic_gate_operation( # pylint: disable=too-many-branches, too-man return result - def _validate_duration_or_stretch_statements( - self, statement: qasm3_ast.Statement, base_type: Any, rvalue: Any - ) -> None: - """Validate statements that declare or assign duration or stretch values. - - Args: - statement: The statement to validate. - base_type: The declared type. - rvalue: The initializer or assigned value. - """ - global_scope = self._scope_manager.get_global_scope() - PulseValidator.validate_duration_or_stretch_statements( - statement, - base_type, - rvalue, - global_scope - ) - - @easy_check_only + @semantic_check_gate def _visit_constant_declaration( self, statement: qasm3_ast.ConstantDeclaration ) -> list[qasm3_ast.Statement]: @@ -1646,10 +1643,12 @@ def _visit_constant_declaration( ) if statement.init_expression: - self._validate_duration_or_stretch_statements( + global_scope = self._scope_manager.get_global_scope() + PulseValidator.validate_duration_or_stretch_statements( statement=statement, base_type=statement.type, - rvalue=statement.init_expression + rvalue=statement.init_expression, + global_scope=global_scope, ) try: @@ -1728,7 +1727,7 @@ def _visit_constant_declaration( return statements - @easy_check_only + @semantic_check_gate # pylint: disable=too-many-branches, too-many-statements, too-many-locals def _visit_classical_declaration( self, statement: qasm3_ast.ClassicalDeclaration @@ -1828,10 +1827,12 @@ def _visit_classical_declaration( # populate the variable if statement.init_expression: - self._validate_duration_or_stretch_statements( + global_scope = self._scope_manager.get_global_scope() + PulseValidator.validate_duration_or_stretch_statements( statement=statement, base_type=base_type, rvalue=statement.init_expression, + global_scope=global_scope, ) if isinstance(statement.init_expression, qasm3_ast.ArrayLiteral): @@ -1962,7 +1963,7 @@ def _visit_classical_declaration( return statements - @easy_check_only + @semantic_check_gate def _visit_classical_assignment( self, statement: qasm3_ast.ClassicalAssignment ) -> list[qasm3_ast.Statement]: @@ -2015,10 +2016,12 @@ def _visit_classical_assignment( rvalue = statement.rvalue lvar_base_type = lvar.base_type # type: ignore[union-attr] if rvalue: - self._validate_duration_or_stretch_statements( + global_scope = self._scope_manager.get_global_scope() + PulseValidator.validate_duration_or_stretch_statements( statement=statement, base_type=lvar_base_type, - rvalue=rvalue + rvalue=rvalue, + global_scope=global_scope, ) if binary_op is not None: rvalue = qasm3_ast.BinaryExpression( @@ -2175,7 +2178,7 @@ def _update_branching_gate_depths(self) -> None: self._is_branch_clbits.clear() self._is_branch_qubits.clear() - @easy_check_only + @semantic_check_gate def _visit_branching_statement( self, statement: qasm3_ast.BranchingStatement ) -> list[qasm3_ast.Statement]: @@ -2382,7 +2385,7 @@ def _visit_forin_loop(self, statement: qasm3_ast.ForInLoop) -> list[qasm3_ast.St return [] return result - @easy_check_only + @semantic_check_gate def _visit_subroutine_definition( self, statement: qasm3_ast.SubroutineDefinition | qasm3_ast.ExternDeclaration ) -> Sequence[None | qasm3_ast.ExternDeclaration]: @@ -2770,7 +2773,7 @@ def _visit_switch_statement( # type: ignore[return] # each element in the list of the values # should be of const int type and no duplicates should be present - @easy_check_only + @semantic_check_gate def _evaluate_case(statements): # can not put 'context' outside # BECAUSE the case expression CAN CONTAIN VARS from global scope @@ -3234,7 +3237,7 @@ def _visit_calibration_grammar_declaration( return [statement] - @easy_check_only + @semantic_check_gate def _visit_include(self, include: qasm3_ast.Include) -> list[qasm3_ast.Statement]: """Visit an include statement element. From 36384886e95aeebac454d413d5a7c88c683c489d Mon Sep 17 00:00:00 2001 From: Michael Papadopoulos Date: Wed, 5 Aug 2026 10:42:50 -0400 Subject: [PATCH 4/5] Refactor _handle_function_init_expression Function is simple but has unintuitive logic; the "return None" approach used here should be removed if we decide not to use this refactoring. --- src/pyqasm/visitor.py | 38 ++++++++++---------------------------- 1 file changed, 10 insertions(+), 28 deletions(-) diff --git a/src/pyqasm/visitor.py b/src/pyqasm/visitor.py index 5059ac7..dbfa0d3 100644 --- a/src/pyqasm/visitor.py +++ b/src/pyqasm/visitor.py @@ -534,22 +534,6 @@ def _qubit_register_consolidation( return _valid_statements - def _handle_function_init_expression( - self, expression: Any, init_value: Any - ) -> None | qasm3_ast.Expression: - """Handle function initialization expression. - - Args: - statement (Any): The statement to handle function initialization expression. - init_value (Any): The value to handle function initialization expression. - """ - if isinstance(expression, qasm3_ast.FunctionCall): - func_name = expression.name.name - if func_name in FUNCTION_MAP: - if isinstance(init_value, (float, int)): - return qasm3_ast.FloatLiteral(init_value) - return None - def _handle_extern_function_cleanup( self, statements: list, statement: qasm3_ast.Statement ) -> None: @@ -1719,10 +1703,10 @@ def _visit_constant_declaration( statement.init_expression = PulseValidator.make_complex_binary_expression(init_value) if isinstance(statement.init_expression, qasm3_ast.FunctionCall): - statement.init_expression = ( - self._handle_function_init_expression(statement.init_expression, init_value) - or statement.init_expression - ) + function_name = statement.init_expression.name.name + if function_name in FUNCTION_MAP and isinstance(init_value, (float, int)): + statement.init_expression = qasm3_ast.FloatLiteral(init_value) + self._handle_extern_function_cleanup(statements, statement) return statements @@ -1956,10 +1940,9 @@ def _visit_classical_declaration( statement.init_expression = PulseValidator.make_complex_binary_expression(init_value) if isinstance(statement.init_expression, qasm3_ast.FunctionCall): - statement.init_expression = ( - self._handle_function_init_expression(statement.init_expression, init_value) - or statement.init_expression - ) + function_name = statement.init_expression.name.name + if function_name in FUNCTION_MAP and isinstance(init_value, (float, int)): + statement.init_expression = qasm3_ast.FloatLiteral(init_value) return statements @@ -2127,10 +2110,9 @@ def _visit_classical_assignment( ) if isinstance(statement.rvalue, qasm3_ast.FunctionCall): - statement.rvalue = ( - self._handle_function_init_expression(statement.rvalue, rvalue_eval) - or statement.rvalue - ) + function_name = statement.rvalue.name.name + if function_name in FUNCTION_MAP and isinstance(rvalue_eval, (float, int)): + statement.rvalue = qasm3_ast.FloatLiteral(rvalue_eval) self._handle_extern_function_cleanup(statements, statement) From 453cee40565f2d10976a4960e2e3e7c7f8f3abe1 Mon Sep 17 00:00:00 2001 From: Michael Papadopoulos Date: Thu, 6 Aug 2026 12:54:18 -0400 Subject: [PATCH 5/5] more changes requested --- src/pyqasm/pulse/validator.py | 9 +++---- src/pyqasm/visitor.py | 51 ++++++++++++++++++----------------- 2 files changed, 31 insertions(+), 29 deletions(-) diff --git a/src/pyqasm/pulse/validator.py b/src/pyqasm/pulse/validator.py index fea9f7b..2c95764 100644 --- a/src/pyqasm/pulse/validator.py +++ b/src/pyqasm/pulse/validator.py @@ -114,11 +114,10 @@ def validate_duration_or_stretch_statements( Generic validation function for DurationType and StretchType declarations or assignments. Args: - statement: The AST statement node. - statement_type: The expected AST node type. - base_type: The declared type, function does nothing if not DurationType or StretchType - rvalue: The initializer or assigned value. - global_scope: Global symbol table. + statement (Statement): The AST statement node. + base_type (Any): The declared type, function does nothing if not DurationType or StretchType. + rvalue (Any): The initializer or assigned value. + global_scope (dict): Global symbol table. Raises: ValidationError: If the assigned value is not a DurationLiteral, diff --git a/src/pyqasm/visitor.py b/src/pyqasm/visitor.py index dbfa0d3..9ccbe1a 100644 --- a/src/pyqasm/visitor.py +++ b/src/pyqasm/visitor.py @@ -27,7 +27,7 @@ from collections import OrderedDict, deque from functools import partial from io import StringIO -from typing import Any, Callable, Optional, Sequence, Union, cast +from typing import Any, Callable, Optional, Sequence, TypeVar, Union, cast import numpy as np import openqasm3.ast as qasm3_ast @@ -84,20 +84,21 @@ logger = logging.getLogger(__name__) logger.propagate = False +F = TypeVar("F", bound=Callable[..., Any]) -def semantic_check_gate(func): + +def check_only_return_empty(func: F) -> F: """Decorator for functions which use check_only to return an empty list.""" @functools.wraps(func) def wrapper(self, *args, **kwargs): """Wrapper that intercepts the return value and replaces it with an empty list.""" result = func(self, *args, **kwargs) - if self.check_only: + if self._check_only: return [] - else: - return result + return result - return wrapper + return wrapper # type: ignore # pylint: disable-next=too-many-instance-attributes @@ -209,7 +210,7 @@ def _construct_visit_map(self): qasm3_ast.CalibrationGrammarDeclaration: self._visit_calibration_grammar_declaration, } - @semantic_check_gate + @check_only_return_empty def _visit_quantum_register( self, register: qasm3_ast.QubitDeclaration ) -> list[qasm3_ast.QubitDeclaration]: @@ -557,7 +558,6 @@ def _validate_bitstring_literal_width(self, init_value, base_size, var_name, sta span=statement.span, ) - @semantic_check_gate def _visit_measurement( # pylint: disable=too-many-locals,too-many-branches,too-many-statements self, statement: qasm3_ast.QuantumMeasurementStatement ) -> list[qasm3_ast.QuantumMeasurementStatement]: @@ -696,6 +696,8 @@ def _visit_measurement( # pylint: disable=too-many-locals,too-many-branches,too ), ) + if self._check_only: + return [] return unrolled_measurements def _resolve_unindexed_reset_qubit(self, statement: qasm3_ast.QuantumReset) -> bool: @@ -728,7 +730,6 @@ def _resolve_unindexed_reset_qubit(self, statement: qasm3_ast.QuantumReset) -> b return is_internal_qubit_register(qubit_name) - @semantic_check_gate def _visit_reset(self, statement: qasm3_ast.QuantumReset) -> list[qasm3_ast.QuantumReset]: """Visit a reset statement element. @@ -736,7 +737,7 @@ def _visit_reset(self, statement: qasm3_ast.QuantumReset) -> list[qasm3_ast.Quan statement (qasm3_ast.QuantumReset): The reset statement to visit. Returns: - list[qasm3_ast.QuantumReset] - A list of unrolled resets. + list[qasm3_ast.QuantumReset]: A list of unrolled resets. """ logger.debug("Visiting reset statement '%s'", str(statement)) @@ -783,6 +784,8 @@ def _visit_reset(self, statement: qasm3_ast.QuantumReset) -> list[qasm3_ast.Quan ), ) + if self._check_only: + return [] return unrolled_resets def _expand_barrier_ranges( @@ -1082,8 +1085,8 @@ def _update_qubit_depth_for_gate( qubit_node = self._module._qubit_depths[(qubit_name, qubit_id)] qubit_node.depth = max_involved_depth - @semantic_check_gate # pylint: disable=too-many-branches, too-many-locals + @check_only_return_empty def _visit_basic_gate_operation( self, operation: qasm3_ast.QuantumGate, @@ -1199,7 +1202,7 @@ def _visit_continue(self, statement: qasm3_ast.ContinueStatement) -> None: error_node=statement, ) - @semantic_check_gate + @check_only_return_empty def _visit_custom_gate_operation( self, operation: qasm3_ast.QuantumGate, @@ -1301,7 +1304,7 @@ def _visit_custom_gate_operation( return result - @semantic_check_gate + @check_only_return_empty def _visit_external_gate_operation( self, operation: qasm3_ast.QuantumGate, @@ -1376,7 +1379,7 @@ def gate_function(*qubits): return result - @semantic_check_gate + @check_only_return_empty def _visit_phase_operation( self, operation: qasm3_ast.QuantumPhase, @@ -1433,7 +1436,6 @@ def _visit_phase_operation( # qubit list return [operation] - @semantic_check_gate def _visit_generic_gate_operation( # pylint: disable=too-many-branches, too-many-statements self, operation: qasm3_ast.QuantumGate | qasm3_ast.QuantumPhase, @@ -1591,9 +1593,11 @@ def _visit_generic_gate_operation( # pylint: disable=too-many-branches, too-man ), ) + if self._check_only: + return [] return result - @semantic_check_gate + @check_only_return_empty def _visit_constant_declaration( self, statement: qasm3_ast.ConstantDeclaration ) -> list[qasm3_ast.Statement]: @@ -1711,8 +1715,8 @@ def _visit_constant_declaration( return statements - @semantic_check_gate # pylint: disable=too-many-branches, too-many-statements, too-many-locals + @check_only_return_empty def _visit_classical_declaration( self, statement: qasm3_ast.ClassicalDeclaration ) -> list[qasm3_ast.Statement]: @@ -1880,7 +1884,7 @@ def _visit_classical_declaration( angle_bit_string=angle_val_bit_string, ) - if isinstance(base_type, (qasm3_ast.DurationType, qasm3_ast.StretchType)): + if isinstance(base_type, qasm3_ast.DurationType): PulseValidator.validate_duration_literal_value(init_value, statement, base_type) if self._module._device_cycle_time: variable.time_unit = "dt" @@ -1946,7 +1950,7 @@ def _visit_classical_declaration( return statements - @semantic_check_gate + @check_only_return_empty def _visit_classical_assignment( self, statement: qasm3_ast.ClassicalAssignment ) -> list[qasm3_ast.Statement]: @@ -2065,7 +2069,7 @@ def _visit_classical_assignment( ) rvalue_eval = rvalue_raw - if isinstance(lvar_base_type, (qasm3_ast.DurationType, qasm3_ast.StretchType)): + if isinstance(lvar_base_type, qasm3_ast.DurationType): PulseValidator.validate_duration_literal_value(rvalue_eval, statement, lvar_base_type) if lvar.readonly: # type: ignore[union-attr] @@ -2160,7 +2164,7 @@ def _update_branching_gate_depths(self) -> None: self._is_branch_clbits.clear() self._is_branch_qubits.clear() - @semantic_check_gate + @check_only_return_empty def _visit_branching_statement( self, statement: qasm3_ast.BranchingStatement ) -> list[qasm3_ast.Statement]: @@ -2367,7 +2371,7 @@ def _visit_forin_loop(self, statement: qasm3_ast.ForInLoop) -> list[qasm3_ast.St return [] return result - @semantic_check_gate + @check_only_return_empty def _visit_subroutine_definition( self, statement: qasm3_ast.SubroutineDefinition | qasm3_ast.ExternDeclaration ) -> Sequence[None | qasm3_ast.ExternDeclaration]: @@ -2755,7 +2759,6 @@ def _visit_switch_statement( # type: ignore[return] # each element in the list of the values # should be of const int type and no duplicates should be present - @semantic_check_gate def _evaluate_case(statements): # can not put 'context' outside # BECAUSE the case expression CAN CONTAIN VARS from global scope @@ -3219,7 +3222,7 @@ def _visit_calibration_grammar_declaration( return [statement] - @semantic_check_gate + @check_only_return_empty def _visit_include(self, include: qasm3_ast.Include) -> list[qasm3_ast.Statement]: """Visit an include statement element.