Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
51 changes: 40 additions & 11 deletions compiler/qsc_qasm/src/semantic/const_eval.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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),
Expand Down Expand Up @@ -457,33 +460,59 @@ 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,
},
_ => None,
},
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,
},
Expand Down
24 changes: 14 additions & 10 deletions compiler/qsc_qasm/src/semantic/lowerer.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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)
};
Expand Down
18 changes: 9 additions & 9 deletions compiler/qsc_qasm/src/tests/declaration/def.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down Expand Up @@ -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:?}"));
}
18 changes: 9 additions & 9 deletions compiler/qsc_qasm/src/tests/declaration/gate.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down Expand Up @@ -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:?}"));
}
14 changes: 14 additions & 0 deletions compiler/qsc_qasm/src/tests/fuzz.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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);
}
143 changes: 133 additions & 10 deletions compiler/qsc_qasm/src/tests/statement/const_eval.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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);
}
Loading