From e9a7fd5c039b0929bbdbd90840f3033faa7925f1 Mon Sep 17 00:00:00 2001 From: vinayswamik Date: Wed, 11 Jun 2025 08:37:10 -0500 Subject: [PATCH 1/9] Update test_classical.py and variables.py Added new test cases for explicit casting --- tests/qasm3/declarations/test_classical.py | 27 ++++- tests/qasm3/resources/variables.py | 124 +++++++++++++++++++++ 2 files changed, 150 insertions(+), 1 deletion(-) diff --git a/tests/qasm3/declarations/test_classical.py b/tests/qasm3/declarations/test_classical.py index b600f6a2..b4424019 100644 --- a/tests/qasm3/declarations/test_classical.py +++ b/tests/qasm3/declarations/test_classical.py @@ -20,7 +20,12 @@ from pyqasm.entrypoint import loads from pyqasm.exceptions import ValidationError -from tests.qasm3.resources.variables import ASSIGNMENT_TESTS, DECLARATION_TESTS +from tests.qasm3.resources.variables import ( + ASSIGNMENT_TESTS, + CASTING_TESTS, + DECLARATION_TESTS, + FAIL_CASTING_TESTS, +) from tests.utils import check_single_qubit_rotation_op @@ -389,3 +394,23 @@ def test_incorrect_assignments(test_name, caplog): assert f"Error at line {line_num}, column {col_num}" in caplog.text assert err_line in caplog.text + + +@pytest.mark.parametrize("test_name", CASTING_TESTS.keys()) +def test_explicit_casting(test_name): + qasm_input = CASTING_TESTS[test_name] + loads(qasm_input).validate() + + +@pytest.mark.parametrize("test_name", FAIL_CASTING_TESTS.keys()) +def test_incorrect_casting(test_name, caplog): + qasm_input, error_message, line_num, col_num, err_line = FAIL_CASTING_TESTS[test_name] + with pytest.raises(ValidationError) as excinfo: + loads(qasm_input).validate() + + first = excinfo.value.__cause__ or excinfo.value.__context__ + assert first is not None, "Expected a chained ValidationError" + msg = str(first) + assert error_message in msg + assert f"Error at line {line_num}, column {col_num}" in caplog.text + assert err_line in caplog.text diff --git a/tests/qasm3/resources/variables.py b/tests/qasm3/resources/variables.py index 2f1c7494..885a0cce 100644 --- a/tests/qasm3/resources/variables.py +++ b/tests/qasm3/resources/variables.py @@ -361,3 +361,127 @@ "x[3] = 3;", ), } + +CASTING_TESTS = { + "General test": ( + """ + OPENQASM 3.0; + include "stdgates.inc"; + const float[64] f1 = 2.5; + uint[8] runtime_u = 7; + int[32] i2 = 2 * int[32](float[64](int[16](f1))); + const int[8] i1 = int[8](f1); + const uint u1 = 2 * uint(f1); + """ + ), + "Bool test": ( + """ + OPENQASM 3.0; + include "stdgates.inc"; + + bool b_false = false; + bool b_true = true; + + int i1 = int(b_false); + uint[16] u1 = uint[16](b_true); + float[32] f0 = float[32](b_false); + + bit b; + b = b_true; + + bit[4] bits_from_true = bit[4](b_true); + + bool b_nested = bool(float[32](uint[8](int[8](bit[8](bool(true)))))); + """ + ), + "Int test": ( + """ + OPENQASM 3.0; + include "stdgates.inc"; + + int[4] x = -3; + bool b = bool(x); + uint[8] ux = uint[8](x); + float[32] f = float[32](x); + bit[4] bits = bit[4](x); + """ + ), + "Unsigned Int test": ( + """ + OPENQASM 3.0; + include "stdgates.inc"; + + uint[8] x = 3; + bool b = bool(x); + int[8] i = int[8](x); + float[32] f = float[32](x); + bit[4] bits = bit[4](x); + """ + ), + "Float test": ( + """ + OPENQASM 3.0; + include "stdgates.inc"; + + const float[64] two_pi = 6.283185307179586; + float[64] f = two_pi * (127. / 512.); + bool b = bool(f); + int i = int(f); + uint u = uint(f); + // angle[8] a = angle[8](f); + """ + ), + "Bit test": ( + """ + OPENQASM 3.0; + include "stdgates.inc"; + + int v = 15; + bit[4] x = v; + bool b = bool(x); + int[32] i = int[32](x); + uint[32] u = uint[32](x); + // angle[4] a = angle[4](x); + """ + ), +} + +FAIL_CASTING_TESTS = { + "Float to Bit test": ( + """ + OPENQASM 3.0; + include "stdgates.inc"; + const float[64] f1 = 2.5; + const bit[2] b1 = bit[2](f1); + """, + "Cannot cast to . Invalid assignment " + "of type to variable f1 of type ", + 5, + 8, + "const bit[2] b1 = bit[2](f1);", + ), + "Const to non-Const test": ( + """ + OPENQASM 3.0; + include "stdgates.inc"; + uint[8] runtime_u = 7; + const int[16] i2 = int[16](runtime_u); + """, + "Expected variable 'runtime_u' to be constant in given expression", + 5, + 35, + "const int[16] i2 = int[16](runtime_u);", + ), + "Declaration vs Cast": ( + """ + OPENQASM 3.0; + include "stdgates.inc"; + int v = 15; + int[32] i = uint[32](v); + """, + "Declaration type: 'Int[32]' and Cast type: 'Uint[32]', should be same for 'i'", + 5, + 8, + "int[32] i = uint[32](v);", + ), +} From 5199b24d38258953544fb4835a49755eb678e778 Mon Sep 17 00:00:00 2001 From: vinayswamik Date: Wed, 11 Jun 2025 08:38:56 -0500 Subject: [PATCH 2/9] Update expressions.py Added explicit casting logic in evaluate_expression function --- src/pyqasm/expressions.py | 67 +++++++++++++++++++++++++++++++++++++++ 1 file changed, 67 insertions(+) diff --git a/src/pyqasm/expressions.py b/src/pyqasm/expressions.py index f89ad620..1c4fa6e2 100644 --- a/src/pyqasm/expressions.py +++ b/src/pyqasm/expressions.py @@ -18,8 +18,10 @@ """ from openqasm3.ast import ( BinaryExpression, + BitType, BooleanLiteral, BoolType, + Cast, DurationLiteral, Expression, FloatLiteral, @@ -40,6 +42,7 @@ ) from pyqasm.analyzer import Qasm3Analyzer +from pyqasm.elements import Variable from pyqasm.exceptions import ValidationError, raise_qasm3_error from pyqasm.maps.expressions import CONSTANTS_MAP, qasm3_expression_op_map from pyqasm.validator import Qasm3Validator @@ -49,6 +52,7 @@ class Qasm3ExprEvaluator: """Class for evaluating QASM3 expressions.""" visitor_obj = None + cast_var_name = "" @classmethod def set_visitor_obj(cls, visitor_obj) -> None: @@ -205,8 +209,34 @@ def _process_variable(var_name: str, indices=None): Qasm3ExprEvaluator._check_var_initialized(var_name, var_value, expression) return _check_and_return_value(var_value) + def _check_type_size(expression, var_name, base_type): + base_size = 1 + if not isinstance(base_type, BoolType): + initial_size = 1 if isinstance(base_type, BitType) else 32 + try: + base_size = ( + initial_size + if not hasattr(base_type, "size") or base_type.size is None + else Qasm3ExprEvaluator.evaluate_expression( + base_type.size, const_expr=True + )[0] + ) + if not isinstance(base_size, int) or base_size <= 0: + raise ValidationError( + f"Invalid base size {base_size} for variable '{var_name}'" + ) + except ValidationError as err: + raise_qasm3_error( + f"Invalid base size for variable '{var_name}'", + error_node=expression, + span=expression.span, + raised_from=err, + ) + return base_size + if isinstance(expression, Identifier): var_name = expression.name + cls.cast_var_name = var_name if var_name in CONSTANTS_MAP: if not reqd_type or reqd_type == Qasm3FloatType: return _check_and_return_value(CONSTANTS_MAP[var_name]) @@ -283,6 +313,12 @@ def _process_variable(var_name: str, indices=None): return _check_and_return_value(expression.value) if isinstance(expression, UnaryExpression): + if validate_only: + if isinstance(expression.expression, Cast): + return cls.evaluate_expression( + expression.expression, const_expr, reqd_type, validate_only + ) + operand, returned_stats = cls.evaluate_expression( expression.expression, const_expr, reqd_type ) @@ -298,6 +334,16 @@ def _process_variable(var_name: str, indices=None): return _check_and_return_value(qasm3_expression_op_map(op_name, operand)) if isinstance(expression, BinaryExpression): + if validate_only: + if isinstance(expression.lhs, Cast): + return cls.evaluate_expression( + expression.lhs, const_expr, reqd_type, validate_only + ) + if isinstance(expression.rhs, Cast): + return cls.evaluate_expression( + expression.rhs, const_expr, reqd_type, validate_only + ) + lhs_value, lhs_statements = cls.evaluate_expression( expression.lhs, const_expr, reqd_type ) @@ -317,6 +363,27 @@ def _process_variable(var_name: str, indices=None): statements.extend(ret_stmts) return _check_and_return_value(ret_value) + if isinstance(expression, Cast): + if validate_only: + return (expression.type, statements) + var_value, cast_stmts = cls.evaluate_expression( + expression=expression.argument, const_expr=const_expr + ) + cast_type_size = _check_type_size(expression, cls.cast_var_name, expression.type) + variable = Variable( + name=cls.cast_var_name, + base_type=expression.type, + base_size=cast_type_size, + dims=[], + value=var_value, + is_constant=const_expr, + ) + cast_var_value = Qasm3Validator.validate_variable_assignment_value( + variable, var_value, expression + ) + statements.extend(cast_stmts) + return _check_and_return_value(cast_var_value) + raise_qasm3_error( f"Unsupported expression type {type(expression)}", err_type=ValidationError, From 259889a638e6b73879d97849a8a6541c762526ca Mon Sep 17 00:00:00 2001 From: vinayswamik Date: Wed, 11 Jun 2025 08:41:54 -0500 Subject: [PATCH 3/9] updated visitor.py Added explicit casting logic flow and cleanup the repeated code --- src/pyqasm/visitor.py | 134 ++++++++++++++++++++++++++++++------------ 1 file changed, 95 insertions(+), 39 deletions(-) diff --git a/src/pyqasm/visitor.py b/src/pyqasm/visitor.py index 0fd6d1ec..e6c8c694 100644 --- a/src/pyqasm/visitor.py +++ b/src/pyqasm/visitor.py @@ -462,6 +462,80 @@ def _get_op_bits( return openqasm_bits + def _check_type_size( + self, statement: qasm3_ast.Statement, var_name: str, base_type: Any, is_const: bool + ) -> int: + """Get the size of the given variable type. + + Args: + statement: current statement to get span. + var_name(str): variable name of the current operation. + 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. + """ + base_size = 1 + if not isinstance(base_type, qasm3_ast.BoolType): + initial_size = 1 if isinstance(base_type, qasm3_ast.BitType) else 32 + try: + base_size = ( + initial_size + if not hasattr(base_type, "size") or base_type.size is None + else Qasm3ExprEvaluator.evaluate_expression(base_type.size, const_expr=True)[0] + ) + if is_const: + if not isinstance(base_size, int) or base_size <= 0: + raise ValidationError( + f"Invalid base size {base_size} for variable '{var_name}'" + ) + except ValidationError as err: + err_str = "variable" + if is_const: + err_str = "constant" + raise_qasm3_error( + f"Invalid base size for {err_str} '{var_name}'", + error_node=statement, + span=statement.span, + raised_from=err, + ) + return base_size + + # pylint: disable-next=too-many-arguments + def _check_type_for_cast( + self, + statement: qasm3_ast.Statement, + val_type: Any, + var_name: str, + base_type: Any, + base_size: Any, + is_const: bool, + ) -> None: + """Checks the declaration type and cast type of current variable. + + Args: + statement: current statement to get span. + val_type(Any): type of cast to apply on variable. + var_name(str): declaration variable name. + base_type (Any): Base type of the declaration variable. + base_size(Any): literal to get the base size of the declaration variable. + is_const (bool): whether the statement is constant declaration or not. + Returns: + None + """ + if not val_type: + val_type = base_type + val_type_size = self._check_type_size(statement, var_name, val_type, is_const) + if not isinstance(val_type, type(base_type)) or val_type_size != base_size: + raise_qasm3_error( + f"Declaration type: " + f"'{(type(base_type).__name__).replace('Type', '')}[{base_size}]' and " + f"Cast type: '{(type(val_type).__name__).replace('Type', '')}[{val_type_size}]'," + f" should be same for '{var_name}'", + error_node=statement, + span=statement.span, + ) + def _visit_measurement( # pylint: disable=too-many-locals self, statement: qasm3_ast.QuantumMeasurementStatement ) -> list[qasm3_ast.QuantumMeasurementStatement]: @@ -1289,28 +1363,11 @@ def _visit_constant_declaration( statements.extend(stmts) base_type = statement.type - if isinstance(base_type, qasm3_ast.BoolType): - base_size = 1 - elif hasattr(base_type, "size"): - if base_type.size is None: - base_size = 32 # default for now - else: - try: - base_size = Qasm3ExprEvaluator.evaluate_expression( - base_type.size, const_expr=True - )[0] - if not isinstance(base_size, int) or base_size <= 0: - raise ValidationError( - f"Invalid base size {base_size} for variable '{var_name}'" - ) - except ValidationError as err: - raise_qasm3_error( - f"Invalid base size for constant '{var_name}'", - error_node=statement, - span=statement.span, - raised_from=err, - ) - + base_size = self._check_type_size(statement, var_name, base_type, True) + val_type, _ = Qasm3ExprEvaluator.evaluate_expression( + statement.init_expression, validate_only=True + ) + self._check_type_for_cast(statement, val_type, var_name, base_type, base_size, True) variable = Variable(var_name, base_type, base_size, [], init_value, is_constant=True) # cast + validation @@ -1368,22 +1425,7 @@ def _visit_classical_declaration( dimensions = base_type.dimensions base_type = base_type.base_type - base_size = 1 - if not isinstance(base_type, qasm3_ast.BoolType): - initial_size = 1 if isinstance(base_type, qasm3_ast.BitType) else 32 - try: - base_size = ( - initial_size - if not hasattr(base_type, "size") or base_type.size is None - else Qasm3ExprEvaluator.evaluate_expression(base_type.size, const_expr=True)[0] - ) - except ValidationError as err: - raise_qasm3_error( - f"Invalid base size for variable '{var_name}'", - error_node=statement, - span=statement.span, - raised_from=err, - ) + base_size = self._check_type_size(statement, var_name, base_type, False) Qasm3Validator.validate_classical_type(base_type, base_size, var_name, statement) # initialize the bit register @@ -1436,6 +1478,12 @@ def _visit_classical_declaration( statement.init_expression ) statements.extend(stmts) + val_type, _ = Qasm3ExprEvaluator.evaluate_expression( + statement.init_expression, validate_only=True + ) + self._check_type_for_cast( + statement, val_type, var_name, base_type, base_size, False + ) except ValidationError as err: raise_qasm3_error( f"Invalid initialization value for variable '{var_name}'", @@ -1553,7 +1601,15 @@ def _visit_classical_assignment( rvalue ) # consists of scope check and index validation statements.extend(rhs_stmts) - + val_type, _ = Qasm3ExprEvaluator.evaluate_expression(rvalue, validate_only=True) + self._check_type_for_cast( + statement, + val_type, + lvar_name, + lvar.base_type, # type: ignore[union-attr] + lvar.base_size, # type: ignore[union-attr] + False, + ) # cast + validation rvalue_eval = None if not isinstance(rvalue_raw, np.ndarray): From 4d17e018f83c0133c9f74ffeaa011b27bf17edd7 Mon Sep 17 00:00:00 2001 From: vinayswamik Date: Fri, 13 Jun 2025 21:31:42 -0500 Subject: [PATCH 4/9] update README.md Added Cast to pyqasm supported operations --- src/README.md | 1 + 1 file changed, 1 insertion(+) diff --git a/src/README.md b/src/README.md index d781dab6..8950e0fe 100644 --- a/src/README.md +++ b/src/README.md @@ -26,6 +26,7 @@ Source code for OpenQASM 3 program validator and semantic analyzer | ForLoops | ✅ | Completed | | RangeDefinition | ✅ | Completed | | QuantumGate | ✅ | Completed | +| Cast | ✅ | Completed | | QuantumGateModifier (ctrl) | 📋 | Planned | | WhileLoop | 📋 | Planned | | IODeclaration | 📋 | Planned | From f38f17dd13a5eed346fa56ff4cb20428cf155720 Mon Sep 17 00:00:00 2001 From: vinayswamik Date: Fri, 13 Jun 2025 21:32:44 -0500 Subject: [PATCH 5/9] Update CHANGELOG.md --- CHANGELOG.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 5bdecd93..23678e8c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -50,7 +50,7 @@ Types of changes: ``` - Previously, each gate inside an `if`/`else` block would advance only its own wire depth. Now, when any branching statement is encountered, all qubit‐ and clbit‐depths used inside that block are first incremented by one, then set to the maximum of those new values. This ensures the entire conditional block counts as single “depth” increment, rather than letting individual gates within the same branch float ahead independently. - In the above snippet, c[0], q[0], and q[1] all jump together to a single new depth for that branch. - +- Added initial support to explicit casting by converting the declarations into implicit casting logic. ([#205](https://github.com/qBraid/pyqasm/pull/205)) ### Dependencies ### Other From 531f856f9e7391574e77fdd9be73fd782ece843a Mon Sep 17 00:00:00 2001 From: vinayswamik Date: Fri, 13 Jun 2025 21:37:49 -0500 Subject: [PATCH 6/9] update expressions.py Performed minor cleanups to improve readability and maintainability. --- src/pyqasm/expressions.py | 37 +++++++++++++++++++++++++++---------- 1 file changed, 27 insertions(+), 10 deletions(-) diff --git a/src/pyqasm/expressions.py b/src/pyqasm/expressions.py index 1c4fa6e2..937200ba 100644 --- a/src/pyqasm/expressions.py +++ b/src/pyqasm/expressions.py @@ -52,7 +52,6 @@ class Qasm3ExprEvaluator: """Class for evaluating QASM3 expressions.""" visitor_obj = None - cast_var_name = "" @classmethod def set_visitor_obj(cls, visitor_obj) -> None: @@ -209,7 +208,7 @@ def _process_variable(var_name: str, indices=None): Qasm3ExprEvaluator._check_var_initialized(var_name, var_value, expression) return _check_and_return_value(var_value) - def _check_type_size(expression, var_name, base_type): + def _check_type_size(expression, var_name, var_format, base_type): base_size = 1 if not isinstance(base_type, BoolType): initial_size = 1 if isinstance(base_type, BitType) else 32 @@ -221,22 +220,23 @@ def _check_type_size(expression, var_name, base_type): base_type.size, const_expr=True )[0] ) - if not isinstance(base_size, int) or base_size <= 0: - raise ValidationError( - f"Invalid base size {base_size} for variable '{var_name}'" - ) except ValidationError as err: raise_qasm3_error( - f"Invalid base size for variable '{var_name}'", + f"Invalid base size for {var_format} '{var_name}'", error_node=expression, span=expression.span, raised_from=err, ) + if not isinstance(base_size, int) or base_size <= 0: + raise_qasm3_error( + f"Invalid base size '{base_size}' for {var_format} '{var_name}'", + error_node=expression, + span=expression.span, + ) return base_size if isinstance(expression, Identifier): var_name = expression.name - cls.cast_var_name = var_name if var_name in CONSTANTS_MAP: if not reqd_type or reqd_type == Qasm3FloatType: return _check_and_return_value(CONSTANTS_MAP[var_name]) @@ -318,6 +318,7 @@ def _check_type_size(expression, var_name, base_type): return cls.evaluate_expression( expression.expression, const_expr, reqd_type, validate_only ) + return (None, []) operand, returned_stats = cls.evaluate_expression( expression.expression, const_expr, reqd_type @@ -335,6 +336,8 @@ def _check_type_size(expression, var_name, base_type): if isinstance(expression, BinaryExpression): if validate_only: + if isinstance(expression.lhs, Cast) and isinstance(expression.rhs, Cast): + return (None, statements) if isinstance(expression.lhs, Cast): return cls.evaluate_expression( expression.lhs, const_expr, reqd_type, validate_only @@ -343,6 +346,7 @@ def _check_type_size(expression, var_name, base_type): return cls.evaluate_expression( expression.rhs, const_expr, reqd_type, validate_only ) + return (None, statements) lhs_value, lhs_statements = cls.evaluate_expression( expression.lhs, const_expr, reqd_type @@ -366,12 +370,25 @@ def _check_type_size(expression, var_name, base_type): if isinstance(expression, Cast): if validate_only: return (expression.type, statements) + + var_name = "" + if isinstance(expression.argument, Identifier): + var_name = expression.argument.name + var_value, cast_stmts = cls.evaluate_expression( expression=expression.argument, const_expr=const_expr ) - cast_type_size = _check_type_size(expression, cls.cast_var_name, expression.type) + + var_format = "variable" + if var_name == "": + var_name = f"{var_value}" + var_format = "value" + + cast_type_size = _check_type_size( + expression, var_name, var_format, expression.type + ) variable = Variable( - name=cls.cast_var_name, + name=var_name, base_type=expression.type, base_size=cast_type_size, dims=[], From 0ab1fb62d43d2951610e52d366e832774d67feb1 Mon Sep 17 00:00:00 2001 From: vinayswamik Date: Fri, 13 Jun 2025 21:39:24 -0500 Subject: [PATCH 7/9] update visitor.py Performed minor cleanups to improve readability and maintainability. --- src/pyqasm/visitor.py | 51 +++++++++++++++++++++++++++---------------- 1 file changed, 32 insertions(+), 19 deletions(-) diff --git a/src/pyqasm/visitor.py b/src/pyqasm/visitor.py index e6c8c694..d126df1d 100644 --- a/src/pyqasm/visitor.py +++ b/src/pyqasm/visitor.py @@ -462,8 +462,8 @@ def _get_op_bits( return openqasm_bits - def _check_type_size( - self, statement: qasm3_ast.Statement, var_name: str, base_type: Any, is_const: bool + def _check_variable_type_size( + self, statement: qasm3_ast.Statement, var_name: str, var_format: str, base_type: Any ) -> int: """Get the size of the given variable type. @@ -482,27 +482,27 @@ def _check_type_size( base_size = ( initial_size if not hasattr(base_type, "size") or base_type.size is None - else Qasm3ExprEvaluator.evaluate_expression(base_type.size, const_expr=True)[0] + else Qasm3ExprEvaluator.evaluate_expression( + base_type.size, const_expr=True + )[0] ) - if is_const: - if not isinstance(base_size, int) or base_size <= 0: - raise ValidationError( - f"Invalid base size {base_size} for variable '{var_name}'" - ) except ValidationError as err: - err_str = "variable" - if is_const: - err_str = "constant" raise_qasm3_error( - f"Invalid base size for {err_str} '{var_name}'", + f"Invalid base size for {var_format} '{var_name}'", error_node=statement, span=statement.span, raised_from=err, ) + if not isinstance(base_size, int) or base_size <= 0: + raise_qasm3_error( + f"Invalid base size '{base_size}' for {var_format} '{var_name}'", + error_node=statement, + span=statement.span, + ) return base_size # pylint: disable-next=too-many-arguments - def _check_type_for_cast( + def _check_variable_cast_type( self, statement: qasm3_ast.Statement, val_type: Any, @@ -525,7 +525,14 @@ def _check_type_for_cast( """ if not val_type: val_type = base_type - val_type_size = self._check_type_size(statement, var_name, val_type, is_const) + + var_format = "variable" + if is_const: + var_format = "constant" + + val_type_size = self._check_variable_type_size( + statement, var_name, var_format, val_type + ) if not isinstance(val_type, type(base_type)) or val_type_size != base_size: raise_qasm3_error( f"Declaration type: " @@ -1363,11 +1370,15 @@ def _visit_constant_declaration( statements.extend(stmts) base_type = statement.type - base_size = self._check_type_size(statement, var_name, base_type, True) + base_size = self._check_variable_type_size( + statement, var_name, "constant", base_type + ) val_type, _ = Qasm3ExprEvaluator.evaluate_expression( statement.init_expression, validate_only=True ) - self._check_type_for_cast(statement, val_type, var_name, base_type, base_size, True) + self._check_variable_cast_type( + statement, val_type, var_name, base_type, base_size, True + ) variable = Variable(var_name, base_type, base_size, [], init_value, is_constant=True) # cast + validation @@ -1425,7 +1436,9 @@ def _visit_classical_declaration( dimensions = base_type.dimensions base_type = base_type.base_type - base_size = self._check_type_size(statement, var_name, base_type, False) + base_size = self._check_variable_type_size( + statement, var_name, "variable", base_type + ) Qasm3Validator.validate_classical_type(base_type, base_size, var_name, statement) # initialize the bit register @@ -1481,7 +1494,7 @@ def _visit_classical_declaration( val_type, _ = Qasm3ExprEvaluator.evaluate_expression( statement.init_expression, validate_only=True ) - self._check_type_for_cast( + self._check_variable_cast_type( statement, val_type, var_name, base_type, base_size, False ) except ValidationError as err: @@ -1602,7 +1615,7 @@ def _visit_classical_assignment( ) # consists of scope check and index validation statements.extend(rhs_stmts) val_type, _ = Qasm3ExprEvaluator.evaluate_expression(rvalue, validate_only=True) - self._check_type_for_cast( + self._check_variable_cast_type( statement, val_type, lvar_name, From ba13d0915ec79788d8b97c754983f963468063f1 Mon Sep 17 00:00:00 2001 From: vinayswamik Date: Fri, 13 Jun 2025 21:40:39 -0500 Subject: [PATCH 8/9] update variables.py Added new and all possible test cases. --- tests/qasm3/resources/variables.py | 60 ++++++++++++++++++++++++------ 1 file changed, 49 insertions(+), 11 deletions(-) diff --git a/tests/qasm3/resources/variables.py b/tests/qasm3/resources/variables.py index 885a0cce..263ead09 100644 --- a/tests/qasm3/resources/variables.py +++ b/tests/qasm3/resources/variables.py @@ -94,7 +94,7 @@ include "stdgates.inc"; int[32.1] x; """, - "Invalid base size 32.1 for variable 'x'", + "Invalid base size '32.1' for variable 'x'", 4, 8, "int[32.1] x;", @@ -105,7 +105,7 @@ include "stdgates.inc"; const int[32.1] x = 3; """, - "Invalid base size for constant 'x'", + "Invalid base size '32.1' for constant 'x'", 4, 8, "const int[32.1] x = 3;", @@ -363,7 +363,7 @@ } CASTING_TESTS = { - "General test": ( + "General_test": ( """ OPENQASM 3.0; include "stdgates.inc"; @@ -372,9 +372,12 @@ int[32] i2 = 2 * int[32](float[64](int[16](f1))); const int[8] i1 = int[8](f1); const uint u1 = 2 * uint(f1); + int ccf1 = float(runtime_u) * int(f1); + uint ul1 = uint(float[64](int[16](f1))) * 2; + const int un = -int(u1); """ ), - "Bool test": ( + "Bool_test": ( """ OPENQASM 3.0; include "stdgates.inc"; @@ -394,7 +397,7 @@ bool b_nested = bool(float[32](uint[8](int[8](bit[8](bool(true)))))); """ ), - "Int test": ( + "Int_test": ( """ OPENQASM 3.0; include "stdgates.inc"; @@ -406,7 +409,7 @@ bit[4] bits = bit[4](x); """ ), - "Unsigned Int test": ( + "Unsigned_Int_test": ( """ OPENQASM 3.0; include "stdgates.inc"; @@ -418,7 +421,7 @@ bit[4] bits = bit[4](x); """ ), - "Float test": ( + "Float_test": ( """ OPENQASM 3.0; include "stdgates.inc"; @@ -431,7 +434,7 @@ // angle[8] a = angle[8](f); """ ), - "Bit test": ( + "Bit_test": ( """ OPENQASM 3.0; include "stdgates.inc"; @@ -447,7 +450,7 @@ } FAIL_CASTING_TESTS = { - "Float to Bit test": ( + "Float_to_Bit_test": ( """ OPENQASM 3.0; include "stdgates.inc"; @@ -460,7 +463,7 @@ 8, "const bit[2] b1 = bit[2](f1);", ), - "Const to non-Const test": ( + "Const_to_non-Const_test": ( """ OPENQASM 3.0; include "stdgates.inc"; @@ -472,7 +475,7 @@ 35, "const int[16] i2 = int[16](runtime_u);", ), - "Declaration vs Cast": ( + "Declaration_vs_Cast": ( """ OPENQASM 3.0; include "stdgates.inc"; @@ -484,4 +487,39 @@ 8, "int[32] i = uint[32](v);", ), + "Incorrect_base_size_for_cast_variable": ( + """ + OPENQASM 3.0; + include "stdgates.inc"; + const float[64] f1 = 2.5; + const int[32] i1 = int[32.5](f1); + """, + "Invalid base size '32.5' for variable 'f1'", + 5, + 27, + "int[32.5](f1);", + ), + "Unsupported_expression": ( + """ + OPENQASM 3.0; + include "stdgates.inc"; + + duration d1 = 1ns; + """, + "Unsupported expression type ''", + 5, + 22, + "1.0ns", + ), + "Incorrect_base_size_for_direct_value_in_cast": ( + """ + OPENQASM 3.0; + include "stdgates.inc"; + const uint[32] iu = uint[12.2](24); + """, + "Invalid base size '12.2' for value '24'", + 4, + 28, + "uint[12.2](24);", + ), } From 690823434399b15273dc1b2ae8855d05037238d6 Mon Sep 17 00:00:00 2001 From: vinayswamik Date: Fri, 13 Jun 2025 21:46:25 -0500 Subject: [PATCH 9/9] update expressions.py and visitor.py --- src/pyqasm/expressions.py | 6 ++---- src/pyqasm/visitor.py | 20 +++++--------------- 2 files changed, 7 insertions(+), 19 deletions(-) diff --git a/src/pyqasm/expressions.py b/src/pyqasm/expressions.py index 937200ba..62926e13 100644 --- a/src/pyqasm/expressions.py +++ b/src/pyqasm/expressions.py @@ -232,7 +232,7 @@ def _check_type_size(expression, var_name, var_format, base_type): f"Invalid base size '{base_size}' for {var_format} '{var_name}'", error_node=expression, span=expression.span, - ) + ) return base_size if isinstance(expression, Identifier): @@ -384,9 +384,7 @@ def _check_type_size(expression, var_name, var_format, base_type): var_name = f"{var_value}" var_format = "value" - cast_type_size = _check_type_size( - expression, var_name, var_format, expression.type - ) + cast_type_size = _check_type_size(expression, var_name, var_format, expression.type) variable = Variable( name=var_name, base_type=expression.type, diff --git a/src/pyqasm/visitor.py b/src/pyqasm/visitor.py index d126df1d..be563d7d 100644 --- a/src/pyqasm/visitor.py +++ b/src/pyqasm/visitor.py @@ -482,9 +482,7 @@ def _check_variable_type_size( base_size = ( initial_size if not hasattr(base_type, "size") or base_type.size is None - else Qasm3ExprEvaluator.evaluate_expression( - base_type.size, const_expr=True - )[0] + else Qasm3ExprEvaluator.evaluate_expression(base_type.size, const_expr=True)[0] ) except ValidationError as err: raise_qasm3_error( @@ -530,9 +528,7 @@ def _check_variable_cast_type( if is_const: var_format = "constant" - val_type_size = self._check_variable_type_size( - statement, var_name, var_format, val_type - ) + val_type_size = self._check_variable_type_size(statement, var_name, var_format, val_type) if not isinstance(val_type, type(base_type)) or val_type_size != base_size: raise_qasm3_error( f"Declaration type: " @@ -1370,15 +1366,11 @@ def _visit_constant_declaration( statements.extend(stmts) base_type = statement.type - base_size = self._check_variable_type_size( - statement, var_name, "constant", base_type - ) + base_size = self._check_variable_type_size(statement, var_name, "constant", base_type) val_type, _ = Qasm3ExprEvaluator.evaluate_expression( statement.init_expression, validate_only=True ) - self._check_variable_cast_type( - statement, val_type, var_name, base_type, base_size, True - ) + self._check_variable_cast_type(statement, val_type, var_name, base_type, base_size, True) variable = Variable(var_name, base_type, base_size, [], init_value, is_constant=True) # cast + validation @@ -1436,9 +1428,7 @@ def _visit_classical_declaration( dimensions = base_type.dimensions base_type = base_type.base_type - base_size = self._check_variable_type_size( - statement, var_name, "variable", base_type - ) + base_size = self._check_variable_type_size(statement, var_name, "variable", base_type) Qasm3Validator.validate_classical_type(base_type, base_size, var_name, statement) # initialize the bit register