diff --git a/compiler/qsc_qasm/src/semantic/const_eval.rs b/compiler/qsc_qasm/src/semantic/const_eval.rs index 25fd08accb..840130773c 100644 --- a/compiler/qsc_qasm/src/semantic/const_eval.rs +++ b/compiler/qsc_qasm/src/semantic/const_eval.rs @@ -25,6 +25,9 @@ use thiserror::Error; #[derive(Clone, Debug, Diagnostic, Eq, Error, PartialEq)] pub enum ConstEvalError { + #[error("division by error during const evaluation")] + #[diagnostic(code("Qasm.Lowerer.DivisionByZero"))] + DivisionByZero(#[label] Span), #[error("expression must be const")] #[diagnostic(code("Qasm.Lowerer.ExprMustBeConst"))] ExprMustBeConst(#[label] Span), @@ -457,25 +460,45 @@ impl BinaryOpExpr { }, BinOp::Div => match lhs_ty { Type::Int(..) | Type::UInt(..) => { - rewrap_lit!((lhs, rhs), (Int(lhs), Int(rhs)), Int(lhs / rhs)) + rewrap_lit!((lhs, rhs), (Int(lhs), Int(rhs)), { + if rhs == 0 { + ctx.push_const_eval_error(ConstEvalError::DivisionByZero(self.span())); + return None; + } + Int(lhs / rhs) + }) } Type::Float(..) => { - rewrap_lit!((lhs, rhs), (Float(lhs), Float(rhs)), Float(lhs / rhs)) + rewrap_lit!((lhs, rhs), (Float(lhs), Float(rhs)), { + if rhs == 0. { + ctx.push_const_eval_error(ConstEvalError::DivisionByZero(self.span())); + return None; + } + Float(lhs / rhs) + }) } Type::Angle(..) => match &self.rhs.ty { Type::UInt(..) => { - rewrap_lit!( - (lhs, rhs), - (Angle(lhs), Int(rhs)), + rewrap_lit!((lhs, rhs), (Angle(lhs), Int(rhs)), { + if rhs == 0 { + ctx.push_const_eval_error(ConstEvalError::DivisionByZero( + self.span(), + )); + return None; + } Angle(lhs / u64::try_from(rhs).ok()?) - ) + }) } Type::Angle(..) => { - rewrap_lit!( - (lhs, rhs), - (Angle(lhs), Angle(rhs)), + rewrap_lit!((lhs, rhs), (Angle(lhs), Angle(rhs)), { + if rhs.value == 0 { + ctx.push_const_eval_error(ConstEvalError::DivisionByZero( + self.span(), + )); + return None; + } Int((lhs / rhs).try_into().ok()?) - ) + }) } _ => None, }, @@ -483,7 +506,13 @@ impl BinaryOpExpr { }, BinOp::Mod => match lhs_ty { Type::Int(..) | Type::UInt(..) => { - rewrap_lit!((lhs, rhs), (Int(lhs), Int(rhs)), Int(lhs % rhs)) + rewrap_lit!((lhs, rhs), (Int(lhs), Int(rhs)), { + if rhs == 0 { + ctx.push_const_eval_error(ConstEvalError::DivisionByZero(self.span())); + return None; + } + Int(lhs % rhs) + }) } _ => None, }, diff --git a/compiler/qsc_qasm/src/semantic/lowerer.rs b/compiler/qsc_qasm/src/semantic/lowerer.rs index ef4ec4a099..6a35877865 100644 --- a/compiler/qsc_qasm/src/semantic/lowerer.rs +++ b/compiler/qsc_qasm/src/semantic/lowerer.rs @@ -627,24 +627,28 @@ impl Lowerer { self.symbols.is_scope_rooted_in_gate_or_subroutine(); // This is true if the symbol is outside the most inner gate or function scope. - let is_symbol_outside_most_inner_gate_or_function_scope = self + let is_symbol_declaration_outside_gate_or_function_scope = self .symbols .is_symbol_outside_most_inner_gate_or_function_scope(symbol_id); - let is_const_evaluation_necessary = symbol.is_const() - && is_symbol_inside_gate_or_function_scope - && is_symbol_outside_most_inner_gate_or_function_scope; + let need_to_capture_symbol = is_symbol_inside_gate_or_function_scope + && is_symbol_declaration_outside_gate_or_function_scope; - let kind = if is_const_evaluation_necessary { + let kind = if need_to_capture_symbol && symbol.is_const() { if let Some(val) = symbol.get_const_expr().const_eval(self) { semantic::ExprKind::Lit(val) } else { - self.push_semantic_error(SemanticErrorKind::ExprMustBeConst( - "a captured variable".into(), - ident.span, - )); - semantic::ExprKind::Err + // If the const evaluation fails, we return Err but don't push + // any additional error. The error was already pushed in the + // const_eval function. + semantic::ExprKind::Ident(symbol_id) } + } else if need_to_capture_symbol && !symbol.is_const() { + self.push_semantic_error(SemanticErrorKind::ExprMustBeConst( + "a captured variable".into(), + ident.span, + )); + semantic::ExprKind::Ident(symbol_id) } else { semantic::ExprKind::Ident(symbol_id) }; diff --git a/compiler/qsc_qasm/src/tests/declaration/def.rs b/compiler/qsc_qasm/src/tests/declaration/def.rs index c80d3979bb..0bf44220e0 100644 --- a/compiler/qsc_qasm/src/tests/declaration/def.rs +++ b/compiler/qsc_qasm/src/tests/declaration/def.rs @@ -207,6 +207,15 @@ fn capturing_non_const_external_variable_fails() { : ^ 5 | } `---- + , Qasm.Lowerer.ExprMustBeConst + + x a captured variable must be a const expression + ,-[Test.qasm:4:20] + 3 | def f() -> int { + 4 | return a; + : ^ + 5 | } + `---- , Qasm.Lowerer.CannotCast x cannot cast expression of type Err to type Int(None, false) @@ -243,15 +252,6 @@ fn capturing_non_const_evaluatable_external_variable_fails() { : ^^^^^^^^^ 3 | def f() -> int { `---- - , Qasm.Lowerer.ExprMustBeConst - - x a captured variable must be a const expression - ,-[Test.qasm:4:20] - 3 | def f() -> int { - 4 | return a; - : ^ - 5 | } - `---- ]"#]] .assert_eq(&format!("{errors:?}")); } diff --git a/compiler/qsc_qasm/src/tests/declaration/gate.rs b/compiler/qsc_qasm/src/tests/declaration/gate.rs index e1715edf78..a250080ea0 100644 --- a/compiler/qsc_qasm/src/tests/declaration/gate.rs +++ b/compiler/qsc_qasm/src/tests/declaration/gate.rs @@ -129,6 +129,15 @@ fn capturing_non_const_external_variable_fails() { : ^ 5 | } `---- + , Qasm.Lowerer.ExprMustBeConst + + x a captured variable must be a const expression + ,-[Test.qasm:4:21] + 3 | gate my_gate q { + 4 | int x = a; + : ^ + 5 | } + `---- , Qasm.Lowerer.CannotCast x cannot cast expression of type Err to type Int(None, false) @@ -165,15 +174,6 @@ fn capturing_non_const_evaluatable_external_variable_fails() { : ^^^^^^^^^ 3 | gate my_gate q { `---- - , Qasm.Lowerer.ExprMustBeConst - - x a captured variable must be a const expression - ,-[Test.qasm:4:21] - 3 | gate my_gate q { - 4 | int x = a; - : ^ - 5 | } - `---- ]"#]] .assert_eq(&format!("{errors:?}")); } diff --git a/compiler/qsc_qasm/src/tests/fuzz.rs b/compiler/qsc_qasm/src/tests/fuzz.rs index b506740a90..d98fccc8b9 100644 --- a/compiler/qsc_qasm/src/tests/fuzz.rs +++ b/compiler/qsc_qasm/src/tests/fuzz.rs @@ -77,3 +77,17 @@ fn fuzz_2313() { super::compare_qasm_and_qasharp_asts(source); compile_qasm_best_effort(source, Profile::Unrestricted); } + +#[test] +fn fuzz_2332() { + let source = r#"ctrl(0/0)@s"#; + super::compare_qasm_and_qasharp_asts(source); + compile_qasm_best_effort(source, Profile::Unrestricted); +} + +#[test] +fn fuzz_2348() { + let source = r#"ctrl(0%0)@s"#; + super::compare_qasm_and_qasharp_asts(source); + compile_qasm_best_effort(source, Profile::Unrestricted); +} diff --git a/compiler/qsc_qasm/src/tests/statement/const_eval.rs b/compiler/qsc_qasm/src/tests/statement/const_eval.rs index 2fc29c7f72..0762d4da0c 100644 --- a/compiler/qsc_qasm/src/tests/statement/const_eval.rs +++ b/compiler/qsc_qasm/src/tests/statement/const_eval.rs @@ -2164,16 +2164,6 @@ fn binary_op_with_non_supported_types_fails() { 3 | def f() { a; } `---- - Qasm.Lowerer.ExprMustBeConst - - x a captured variable must be a const expression - ,-[Test.qasm:3:19] - 2 | const int a = 2 / 0s; - 3 | def f() { a; } - : ^ - 4 | - `---- - Qasm.Compiler.NotSupported x timing literals are not supported @@ -2186,3 +2176,136 @@ fn binary_op_with_non_supported_types_fails() { "#]] .assert_eq(&errs_string); } + +#[test] +fn division_of_int_by_zero_int_errors() { + let source = r#" + const int a = 2 / 0; + def f() { a; } + "#; + + let Err(errs) = compile_qasm_to_qsharp(source) else { + panic!("should have generated an error"); + }; + let errs: Vec<_> = errs.iter().map(|e| format!("{e:?}")).collect(); + let errs_string = errs.join("\n"); + expect![[r#" + Qasm.Lowerer.DivisionByZero + + x division by error during const evaluation + ,-[Test.qasm:2:23] + 1 | + 2 | const int a = 2 / 0; + : ^^^^^ + 3 | def f() { a; } + `---- + "#]] + .assert_eq(&errs_string); +} + +#[test] +fn division_of_angle_by_zero_int_errors() { + let source = r#" + const angle a = 2.0; + const angle b = a / 0; + def f() { b; } + "#; + + let Err(errs) = compile_qasm_to_qsharp(source) else { + panic!("should have generated an error"); + }; + let errs: Vec<_> = errs.iter().map(|e| format!("{e:?}")).collect(); + let errs_string = errs.join("\n"); + expect![[r#" + Qasm.Lowerer.DivisionByZero + + x division by error during const evaluation + ,-[Test.qasm:3:25] + 2 | const angle a = 2.0; + 3 | const angle b = a / 0; + : ^^^^^ + 4 | def f() { b; } + `---- + "#]] + .assert_eq(&errs_string); +} + +#[test] +fn division_by_zero_float_errors() { + let source = r#" + const float a = 2.0 / 0.0; + def f() { a; } + "#; + + let Err(errs) = compile_qasm_to_qsharp(source) else { + panic!("should have generated an error"); + }; + let errs: Vec<_> = errs.iter().map(|e| format!("{e:?}")).collect(); + let errs_string = errs.join("\n"); + expect![[r#" + Qasm.Lowerer.DivisionByZero + + x division by error during const evaluation + ,-[Test.qasm:2:25] + 1 | + 2 | const float a = 2.0 / 0.0; + : ^^^^^^^^^ + 3 | def f() { a; } + `---- + "#]] + .assert_eq(&errs_string); +} + +#[test] +fn division_by_zero_angle_errors() { + let source = r#" + const angle a = 2.0; + const angle b = 0.0; + const uint c = a / b; + def f() { c; } + "#; + + let Err(errs) = compile_qasm_to_qsharp(source) else { + panic!("should have generated an error"); + }; + let errs: Vec<_> = errs.iter().map(|e| format!("{e:?}")).collect(); + let errs_string = errs.join("\n"); + expect![[r#" + Qasm.Lowerer.DivisionByZero + + x division by error during const evaluation + ,-[Test.qasm:4:24] + 3 | const angle b = 0.0; + 4 | const uint c = a / b; + : ^^^^^ + 5 | def f() { c; } + `---- + "#]] + .assert_eq(&errs_string); +} + +#[test] +fn modulo_of_int_by_zero_int_errors() { + let source = r#" + const int a = 2 % 0; + def f() { a; } + "#; + + let Err(errs) = compile_qasm_to_qsharp(source) else { + panic!("should have generated an error"); + }; + let errs: Vec<_> = errs.iter().map(|e| format!("{e:?}")).collect(); + let errs_string = errs.join("\n"); + expect![[r#" + Qasm.Lowerer.DivisionByZero + + x division by error during const evaluation + ,-[Test.qasm:2:23] + 1 | + 2 | const int a = 2 % 0; + : ^^^^^ + 3 | def f() { a; } + `---- + "#]] + .assert_eq(&errs_string); +}