From e8b824c661fe60524d3ec423e42c16f70d1afd0b Mon Sep 17 00:00:00 2001 From: "Stefan J. Wernli" Date: Thu, 25 Apr 2024 16:33:49 -0700 Subject: [PATCH] Fail partial eval with error on Result literals (#1409) This change adds an error case that fails partial evaluation if any part of the output includes Result literals `One` or `Zero`. This matches the support in QIR, where result literals cannot be recorded as output. --- compiler/qsc_partial_eval/src/lib.rs | 49 +++++++++---- .../tests/output_recording.rs | 70 +++++++++++++++++++ 2 files changed, 107 insertions(+), 12 deletions(-) diff --git a/compiler/qsc_partial_eval/src/lib.rs b/compiler/qsc_partial_eval/src/lib.rs index 1cc325d57a..2bdc7c321e 100644 --- a/compiler/qsc_partial_eval/src/lib.rs +++ b/compiler/qsc_partial_eval/src/lib.rs @@ -106,6 +106,13 @@ pub enum Error { #[error("failed to evaluate: {0} not yet implemented")] #[diagnostic(code("Qsc.PartialEval.Unimplemented"))] Unimplemented(String, #[label] Span), + + #[error("unsupported Result literal in output")] + #[diagnostic(help( + "Result literals `One` and `Zero` cannot be included in generated QIR output recording." + ))] + #[diagnostic(code("Qsc.PartialEval.OutputResultLiteral"))] + OutputResultLiteral(#[label] Span), } struct PartialEvaluator<'a> { @@ -235,10 +242,12 @@ impl<'a> PartialEvaluator<'a> { // Get the final value from the execution context. let ret_val = self.eval_context.get_current_scope().last_expr_value(); - let output_recording: Vec = self.generate_output_recording_instructions( - ret_val, - &self.get_expr(self.entry.expr.expr).ty, - ); + let output_recording: Vec = self + .generate_output_recording_instructions( + ret_val, + &self.get_expr(self.entry.expr.expr).ty, + ) + .map_err(|()| Error::OutputResultLiteral(self.get_expr(self.entry.expr.expr).span))?; // Insert the return expression and return the generated program. let current_block = self.get_current_block_mut(); @@ -1044,12 +1053,14 @@ impl<'a> PartialEvaluator<'a> { &mut self, ret_val: Value, ty: &Ty, - ) -> Vec { + ) -> Result, ()> { let mut instrs = Vec::new(); match ret_val { - Value::Array(vals) => self.record_array(ty, &mut instrs, &vals), - Value::Tuple(vals) => self.record_tuple(ty, &mut instrs, &vals), + Value::Result(val::Result::Val(_)) => return Err(()), + + Value::Array(vals) => self.record_array(ty, &mut instrs, &vals)?, + Value::Tuple(vals) => self.record_tuple(ty, &mut instrs, &vals)?, Value::Result(res) => self.record_result(&mut instrs, res), Value::Var(var) => self.record_variable(ty, &mut instrs, var), Value::Bool(val) => self.record_bool(&mut instrs, val), @@ -1065,7 +1076,7 @@ impl<'a> PartialEvaluator<'a> { | Value::String(_) => panic!("unsupported value type in output recording"), } - instrs + Ok(instrs) } fn record_int(&mut self, instrs: &mut Vec, val: i64) { @@ -1127,7 +1138,12 @@ impl<'a> PartialEvaluator<'a> { )); } - fn record_tuple(&mut self, ty: &Ty, instrs: &mut Vec, vals: &Rc<[Value]>) { + fn record_tuple( + &mut self, + ty: &Ty, + instrs: &mut Vec, + vals: &Rc<[Value]>, + ) -> Result<(), ()> { let Ty::Tuple(elem_tys) = ty else { panic!("expected tuple type for tuple value"); }; @@ -1145,11 +1161,18 @@ impl<'a> PartialEvaluator<'a> { None, )); for (val, elem_ty) in vals.iter().zip(elem_tys.iter()) { - instrs.extend(self.generate_output_recording_instructions(val.clone(), elem_ty)); + instrs.extend(self.generate_output_recording_instructions(val.clone(), elem_ty)?); } + + Ok(()) } - fn record_array(&mut self, ty: &Ty, instrs: &mut Vec, vals: &Rc>) { + fn record_array( + &mut self, + ty: &Ty, + instrs: &mut Vec, + vals: &Rc>, + ) -> Result<(), ()> { let Ty::Array(elem_ty) = ty else { panic!("expected array type for array value"); }; @@ -1167,8 +1190,10 @@ impl<'a> PartialEvaluator<'a> { None, )); for val in vals.iter() { - instrs.extend(self.generate_output_recording_instructions(val.clone(), elem_ty)); + instrs.extend(self.generate_output_recording_instructions(val.clone(), elem_ty)?); } + + Ok(()) } fn get_array_record_callable(&mut self) -> CallableId { diff --git a/compiler/qsc_partial_eval/tests/output_recording.rs b/compiler/qsc_partial_eval/tests/output_recording.rs index 15677e2647..dc81681058 100644 --- a/compiler/qsc_partial_eval/tests/output_recording.rs +++ b/compiler/qsc_partial_eval/tests/output_recording.rs @@ -517,3 +517,73 @@ fn output_recording_for_mix_of_literal_and_variable() { num_results: 1"#]] .assert_eq(&program.to_string()); } + +#[test] +#[should_panic( + expected = "partial evaluation failed: OutputResultLiteral(Span { lo: 50, hi: 54 })" +)] +fn output_recording_fails_with_result_literal_one() { + let _ = compile_and_partially_evaluate(indoc! { + r#" + namespace Test { + @EntryPoint() + operation Main() : Result { + One + } + } + "#, + }); +} + +#[test] +#[should_panic( + expected = "partial evaluation failed: OutputResultLiteral(Span { lo: 50, hi: 54 })" +)] +fn output_recording_fails_with_result_literal_zero() { + let _ = compile_and_partially_evaluate(indoc! { + r#" + namespace Test { + @EntryPoint() + operation Main() : Result { + Zero + } + } + "#, + }); +} + +#[test] +#[should_panic( + expected = "partial evaluation failed: OutputResultLiteral(Span { lo: 50, hi: 54 })" +)] +fn output_recording_fails_with_result_literal_in_array() { + let _ = compile_and_partially_evaluate(indoc! { + r#" + namespace Test { + @EntryPoint() + operation Main() : Result[] { + use q = Qubit(); + [QIR.Intrinsic.__quantum__qis__mresetz__body(q), Zero] + } + } + "#, + }); +} + +#[test] +#[should_panic( + expected = "partial evaluation failed: OutputResultLiteral(Span { lo: 50, hi: 54 })" +)] +fn output_recording_fails_with_result_literal_in_tuple() { + let _ = compile_and_partially_evaluate(indoc! { + r#" + namespace Test { + @EntryPoint() + operation Main() : (Result, Result) { + use q = Qubit(); + (QIR.Intrinsic.__quantum__qis__mresetz__body(q), Zero) + } + } + "#, + }); +}