From 4c18bf3e33a9bdb92d7cf3cd01f545a777016500 Mon Sep 17 00:00:00 2001 From: Oscar Puente Date: Thu, 24 Apr 2025 09:29:02 -0700 Subject: [PATCH 1/2] add division by zero error to const evaluator --- compiler/qsc_qasm/src/semantic/const_eval.rs | 35 +++-- compiler/qsc_qasm/src/tests/fuzz.rs | 7 + .../src/tests/statement/const_eval.rs | 132 ++++++++++++++++++ 3 files changed, 165 insertions(+), 9 deletions(-) diff --git a/compiler/qsc_qasm/src/semantic/const_eval.rs b/compiler/qsc_qasm/src/semantic/const_eval.rs index 25fd08accb..e6077b0c8a 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,39 @@ 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)) } 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, }, diff --git a/compiler/qsc_qasm/src/tests/fuzz.rs b/compiler/qsc_qasm/src/tests/fuzz.rs index b506740a90..5e487bd697 100644 --- a/compiler/qsc_qasm/src/tests/fuzz.rs +++ b/compiler/qsc_qasm/src/tests/fuzz.rs @@ -77,3 +77,10 @@ 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); +} diff --git a/compiler/qsc_qasm/src/tests/statement/const_eval.rs b/compiler/qsc_qasm/src/tests/statement/const_eval.rs index 2fc29c7f72..7af56477cd 100644 --- a/compiler/qsc_qasm/src/tests/statement/const_eval.rs +++ b/compiler/qsc_qasm/src/tests/statement/const_eval.rs @@ -2186,3 +2186,135 @@ 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; } + `---- + + Qasm.Lowerer.ExprMustBeConst + + x a captured variable must be a const expression + ,-[Test.qasm:3:19] + 2 | const int a = 2 / 0; + 3 | def f() { a; } + : ^ + 4 | + `---- + "#]] + .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; } + `---- + + Qasm.Lowerer.ExprMustBeConst + + x a captured variable must be a const expression + ,-[Test.qasm:4:19] + 3 | const angle b = a / 0; + 4 | def f() { b; } + : ^ + 5 | + `---- + "#]] + .assert_eq(&errs_string); +} + +#[test] +fn division_by_zero_float() -> miette::Result<(), Vec> { + let source = r#" + const float a = 2.0 / 0.0; + def f() { a; } + "#; + + let qsharp = compile_qasm_to_qsharp(source)?; + expect![[r#" + import QasmStd.Angle.*; + import QasmStd.Convert.*; + import QasmStd.Intrinsic.*; + let a = 2. / 0.; + function f() : Unit { + inf; + } + "#]] + .assert_eq(&qsharp); + Ok(()) +} + +#[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; } + `---- + + Qasm.Lowerer.ExprMustBeConst + + x a captured variable must be a const expression + ,-[Test.qasm:5:19] + 4 | const uint c = a / b; + 5 | def f() { c; } + : ^ + 6 | + `---- + "#]] + .assert_eq(&errs_string); +} From 3288c17c09694581bc9d23c2122c3d31241b16f8 Mon Sep 17 00:00:00 2001 From: Oscar Puente Date: Thu, 24 Apr 2025 09:34:42 -0700 Subject: [PATCH 2/2] add float division error --- compiler/qsc_qasm/src/semantic/const_eval.rs | 8 +++- .../src/tests/statement/const_eval.rs | 37 +++++++++++++------ 2 files changed, 33 insertions(+), 12 deletions(-) diff --git a/compiler/qsc_qasm/src/semantic/const_eval.rs b/compiler/qsc_qasm/src/semantic/const_eval.rs index e6077b0c8a..ab5673e40f 100644 --- a/compiler/qsc_qasm/src/semantic/const_eval.rs +++ b/compiler/qsc_qasm/src/semantic/const_eval.rs @@ -469,7 +469,13 @@ impl BinaryOpExpr { }) } 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(..) => { diff --git a/compiler/qsc_qasm/src/tests/statement/const_eval.rs b/compiler/qsc_qasm/src/tests/statement/const_eval.rs index 7af56477cd..e79c85c66e 100644 --- a/compiler/qsc_qasm/src/tests/statement/const_eval.rs +++ b/compiler/qsc_qasm/src/tests/statement/const_eval.rs @@ -2261,24 +2261,39 @@ fn division_of_angle_by_zero_int_errors() { } #[test] -fn division_by_zero_float() -> miette::Result<(), Vec> { +fn division_by_zero_float_errors() { let source = r#" const float a = 2.0 / 0.0; def f() { a; } "#; - let qsharp = compile_qasm_to_qsharp(source)?; + 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#" - import QasmStd.Angle.*; - import QasmStd.Convert.*; - import QasmStd.Intrinsic.*; - let a = 2. / 0.; - function f() : Unit { - inf; - } + 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; } + `---- + + Qasm.Lowerer.ExprMustBeConst + + x a captured variable must be a const expression + ,-[Test.qasm:3:19] + 2 | const float a = 2.0 / 0.0; + 3 | def f() { a; } + : ^ + 4 | + `---- "#]] - .assert_eq(&qsharp); - Ok(()) + .assert_eq(&errs_string); } #[test]