Repository: microsoft/qsharp
Pull request #636 (2023-08-30 19:38:12)

Fix panic when QIR entry expr defines items
This fixes a missed case after the changes in #628 that meant `compile_expr` could panic if the given expression was a block that included defining new items.


Patch:
diff --git a/Cargo.lock b/Cargo.lock
index 88a62bd8..660d4b2a 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -801,6 +801,7 @@ dependencies = [
  "clap 4.1.8",
  "criterion",
  "env_logger",
+ "expect-test",
  "indoc 2.0.0",
  "log",
  "miette",
diff --git a/compiler/qsc/Cargo.toml b/compiler/qsc/Cargo.toml
index 99544cb4..6a5c4c13 100644
--- a/compiler/qsc/Cargo.toml
+++ b/compiler/qsc/Cargo.toml
@@ -27,6 +27,7 @@ qsc_passes = { path = "../qsc_passes" }
 thiserror = { workspace = true }
 
 [dev-dependencies]
+expect-test = { workspace = true }
 criterion = { workspace = true, features = ["cargo_bench_support"] }
 indoc = { workspace = true }
 
diff --git a/compiler/qsc/src/interpret/stateful.rs b/compiler/qsc/src/interpret/stateful.rs
index 03e2b26b..a3d0e070 100644
--- a/compiler/qsc/src/interpret/stateful.rs
+++ b/compiler/qsc/src/interpret/stateful.rs
@@ -438,7 +438,7 @@ impl Interpreter {
             ))]);
         }
 
-        let mut fragment = self.compiler.compile_expr(expr).map_err(|errors| {
+        let mut fragments = self.compiler.compile_expr(expr).map_err(|errors| {
             let source = expr.into();
             errors
                 .into_iter()
@@ -446,11 +446,13 @@ impl Interpreter {
                 .collect::<Vec<_>>()
         })?;
 
-        let pass_errors = self.passes.run(
-            self.store.core(),
-            self.compiler.assigner_mut(),
-            &mut fragment,
-        );
+        let pass_errors = fragments
+            .iter_mut()
+            .flat_map(|fragment| {
+                self.passes
+                    .run(self.store.core(), self.compiler.assigner_mut(), fragment)
+            })
+            .collect::<Vec<_>>();
         if !pass_errors.is_empty() {
             let source = expr.into();
             return Err(pass_errors
@@ -459,33 +461,49 @@ impl Interpreter {
                 .collect());
         }
 
-        let Fragment::Stmt(stmt) = fragment else {
-            panic!("only a stmt fragment should reach here.")
-        };
-
-        let stmt_id = self.lower_stmt(&stmt);
-        let globals = Lookup {
-            fir_store: &self.fir_store,
-            package: self.package,
-            udts: &self.udts,
-            callables: &self.callables,
-        };
+        let mut ret = Ok(String::new());
+        for fragment in fragments {
+            match fragment {
+                Fragment::Item(item) => match item.kind {
+                    qsc_hir::hir::ItemKind::Callable(callable) => {
+                        let callable = self.lower_callable_decl(&callable);
 
-        generate_qir_for_stmt(stmt_id, &globals, &mut self.env, self.package).map_err(
-            |(error, call_stack)| {
-                let stack_trace = if call_stack.is_empty() {
-                    None
-                } else {
-                    Some(self.render_call_stack(call_stack, &error))
-                };
+                        self.callables
+                            .insert(qsc_eval::lower::lower_local_item_id(item.id), callable);
+                    }
+                    qsc_hir::hir::ItemKind::Namespace(..) => {}
+                    qsc_hir::hir::ItemKind::Ty(..) => {
+                        self.udts
+                            .insert(qsc_eval::lower::lower_local_item_id(item.id));
+                    }
+                },
+                Fragment::Stmt(stmt) => {
+                    let stmt_id = self.lower_stmt(&stmt);
+                    let globals = Lookup {
+                        fir_store: &self.fir_store,
+                        package: self.package,
+                        udts: &self.udts,
+                        callables: &self.callables,
+                    };
+
+                    ret = generate_qir_for_stmt(stmt_id, &globals, &mut self.env, self.package)
+                        .map_err(|(error, call_stack)| {
+                            let stack_trace = if call_stack.is_empty() {
+                                None
+                            } else {
+                                Some(self.render_call_stack(call_stack, &error))
+                            };
 
-                vec![LineError(WithSource::new(
-                    expr.into(),
-                    error.into(),
-                    stack_trace,
-                ))]
-            },
-        )
+                            vec![LineError(WithSource::new(
+                                expr.into(),
+                                error.into(),
+                                stack_trace,
+                            ))]
+                        });
+                }
+            }
+        }
+        ret
     }
 
     fn lower_callable_decl(&mut self, callable: &qsc_hir::hir::CallableDecl) -> CallableDecl {
diff --git a/compiler/qsc_frontend/src/incremental.rs b/compiler/qsc_frontend/src/incremental.rs
index 53430a56..8f40f450 100644
--- a/compiler/qsc_frontend/src/incremental.rs
+++ b/compiler/qsc_frontend/src/incremental.rs
@@ -82,7 +82,7 @@ impl Compiler {
     /// Compile a string with a single fragment of Q# code that is an expression.
     /// # Errors
     /// Returns a vector of errors if the input fails compilation.
-    pub fn compile_expr(&mut self, input: &str) -> Result<Fragment, Vec<Error>> {
+    pub fn compile_expr(&mut self, input: &str) -> Result<Vec<Fragment>, Vec<Error>> {
         let (expr, errors) = qsc_parse::expr(input);
         if !errors.is_empty() {
             return Err(errors
@@ -98,15 +98,18 @@ impl Compiler {
         };
         self.ast_assigner.visit_stmt(&mut stmt);
         self.resolver.with(&mut self.hir_assigner).visit_stmt(&stmt);
+        self.checker
+            .collect_stmt_items(self.resolver.names(), &stmt);
         self.checker
             .check_stmt_fragment(self.resolver.names(), &stmt);
         self.checker.solve(self.resolver.names());
 
-        let fragment = self.lower_stmt(&stmt);
+        let fragments = self.lower_fragment(qsc_parse::Fragment::Stmt(Box::new(stmt)));
         let errors = self.drain_errors();
         if errors.is_empty() {
-            Ok(fragment.expect("lowering an expression should not produce None"))
+            Ok(fragments)
         } else {
+            self.lowerer.clear_items();
             Err(errors)
         }
     }


Test patch:
diff --git a/compiler/qsc/src/interpret/stateful/tests.rs b/compiler/qsc/src/interpret/stateful/tests.rs
index 5ee825c1..0ad14d48 100644
--- a/compiler/qsc/src/interpret/stateful/tests.rs
+++ b/compiler/qsc/src/interpret/stateful/tests.rs
@@ -29,6 +29,7 @@ mod given_interpreter {
     }
 
     mod without_sources {
+        use expect_test::expect;
         use indoc::indoc;
 
         use super::*;
@@ -375,6 +376,398 @@ mod given_interpreter {
             );
             is_only_value(&result, &output, &Value::unit());
         }
+
+        #[test]
+        fn normal_qirgen() {
+            let mut interpreter = Interpreter::new(
+                true,
+                SourceMap::default(),
+                PackageType::Lib,
+                TargetProfile::Base,
+            )
+            .expect("interpreter should be created");
+            let (result, output) = line(
+                &mut interpreter,
+                indoc! {"operation Foo() : Result { use q = Qubit(); let r = M(q); Reset(q); return r; } "},
+            );
+            is_only_value(&result, &output, &Value::unit());
+            let res = interpreter.qirgen("Foo()").expect("expected success");
+            expect![[r#"
+                %Result = type opaque
+                %Qubit = type opaque
+
+                define void @ENTRYPOINT__main() #0 {
+                  call void @__quantum__qis__m__body(%Qubit* inttoptr (i64 0 to %Qubit*), %Result* inttoptr (i64 0 to %Result*)) #1
+                  call void @__quantum__qis__reset__body(%Qubit* inttoptr (i64 0 to %Qubit*))
+                  call void @__quantum__rt__result_record_output(%Result* inttoptr (i64 0 to %Result*), i8* null)
+                  ret void
+                }
+
+                declare void @__quantum__qis__ccx__body(%Qubit*, %Qubit*, %Qubit*)
+                declare void @__quantum__qis__cx__body(%Qubit*, %Qubit*)
+                declare void @__quantum__qis__cy__body(%Qubit*, %Qubit*)
+                declare void @__quantum__qis__cz__body(%Qubit*, %Qubit*)
+                declare void @__quantum__qis__rx__body(double, %Qubit*)
+                declare void @__quantum__qis__rxx__body(double, %Qubit*, %Qubit*)
+                declare void @__quantum__qis__ry__body(double, %Qubit*)
+                declare void @__quantum__qis__ryy__body(double, %Qubit*, %Qubit*)
+                declare void @__quantum__qis__rz__body(double, %Qubit*)
+                declare void @__quantum__qis__rzz__body(double, %Qubit*, %Qubit*)
+                declare void @__quantum__qis__h__body(%Qubit*)
+                declare void @__quantum__qis__s__body(%Qubit*)
+                declare void @__quantum__qis__s__adj(%Qubit*)
+                declare void @__quantum__qis__t__body(%Qubit*)
+                declare void @__quantum__qis__t__adj(%Qubit*)
+                declare void @__quantum__qis__x__body(%Qubit*)
+                declare void @__quantum__qis__y__body(%Qubit*)
+                declare void @__quantum__qis__z__body(%Qubit*)
+                declare void @__quantum__qis__swap__body(%Qubit*, %Qubit*)
+                declare void @__quantum__qis__reset__body(%Qubit*)
+                declare void @__quantum__qis__mresetz__body(%Qubit*, %Result* writeonly) #1
+                declare void @__quantum__qis__m__body(%Qubit*, %Result* writeonly) #1
+                declare void @__quantum__rt__result_record_output(%Result*, i8*)
+                declare void @__quantum__rt__array_record_output(i64, i8*)
+                declare void @__quantum__rt__tuple_record_output(i64, i8*)
+
+                attributes #0 = { "entry_point" "output_labeling_schema" "qir_profiles"="custom" }
+                attributes #1 = { "irreversible" }
+
+                ; module flags
+
+                !llvm.module.flags = !{!0, !1, !2, !3}
+
+                !0 = !{i32 1, !"qir_major_version", i32 1}
+                !1 = !{i32 7, !"qir_minor_version", i32 0}
+                !2 = !{i32 1, !"dynamic_qubit_management", i1 false}
+                !3 = !{i32 1, !"dynamic_result_management", i1 false}
+            "#]].assert_eq(&res);
+        }
+
+        #[test]
+        fn qirgen_entry_expr_in_block() {
+            let mut interpreter = Interpreter::new(
+                true,
+                SourceMap::default(),
+                PackageType::Lib,
+                TargetProfile::Base,
+            )
+            .expect("interpreter should be created");
+            let (result, output) = line(
+                &mut interpreter,
+                indoc! {"operation Foo() : Result { use q = Qubit(); let r = M(q); Reset(q); return r; } "},
+            );
+            is_only_value(&result, &output, &Value::unit());
+            let res = interpreter.qirgen("{Foo()}").expect("expected success");
+            expect![[r#"
+                %Result = type opaque
+                %Qubit = type opaque
+
+                define void @ENTRYPOINT__main() #0 {
+                  call void @__quantum__qis__m__body(%Qubit* inttoptr (i64 0 to %Qubit*), %Result* inttoptr (i64 0 to %Result*)) #1
+                  call void @__quantum__qis__reset__body(%Qubit* inttoptr (i64 0 to %Qubit*))
+                  call void @__quantum__rt__result_record_output(%Result* inttoptr (i64 0 to %Result*), i8* null)
+                  ret void
+                }
+
+                declare void @__quantum__qis__ccx__body(%Qubit*, %Qubit*, %Qubit*)
+                declare void @__quantum__qis__cx__body(%Qubit*, %Qubit*)
+                declare void @__quantum__qis__cy__body(%Qubit*, %Qubit*)
+                declare void @__quantum__qis__cz__body(%Qubit*, %Qubit*)
+                declare void @__quantum__qis__rx__body(double, %Qubit*)
+                declare void @__quantum__qis__rxx__body(double, %Qubit*, %Qubit*)
+                declare void @__quantum__qis__ry__body(double, %Qubit*)
+                declare void @__quantum__qis__ryy__body(double, %Qubit*, %Qubit*)
+                declare void @__quantum__qis__rz__body(double, %Qubit*)
+                declare void @__quantum__qis__rzz__body(double, %Qubit*, %Qubit*)
+                declare void @__quantum__qis__h__body(%Qubit*)
+                declare void @__quantum__qis__s__body(%Qubit*)
+                declare void @__quantum__qis__s__adj(%Qubit*)
+                declare void @__quantum__qis__t__body(%Qubit*)
+                declare void @__quantum__qis__t__adj(%Qubit*)
+                declare void @__quantum__qis__x__body(%Qubit*)
+                declare void @__quantum__qis__y__body(%Qubit*)
+                declare void @__quantum__qis__z__body(%Qubit*)
+                declare void @__quantum__qis__swap__body(%Qubit*, %Qubit*)
+                declare void @__quantum__qis__reset__body(%Qubit*)
+                declare void @__quantum__qis__mresetz__body(%Qubit*, %Result* writeonly) #1
+                declare void @__quantum__qis__m__body(%Qubit*, %Result* writeonly) #1
+                declare void @__quantum__rt__result_record_output(%Result*, i8*)
+                declare void @__quantum__rt__array_record_output(i64, i8*)
+                declare void @__quantum__rt__tuple_record_output(i64, i8*)
+
+                attributes #0 = { "entry_point" "output_labeling_schema" "qir_profiles"="custom" }
+                attributes #1 = { "irreversible" }
+
+                ; module flags
+
+                !llvm.module.flags = !{!0, !1, !2, !3}
+
+                !0 = !{i32 1, !"qir_major_version", i32 1}
+                !1 = !{i32 7, !"qir_minor_version", i32 0}
+                !2 = !{i32 1, !"dynamic_qubit_management", i1 false}
+                !3 = !{i32 1, !"dynamic_result_management", i1 false}
+            "#]].assert_eq(&res);
+        }
+
+        #[test]
+        fn qirgen_entry_expr_defines_operation() {
+            let mut interpreter = Interpreter::new(
+                true,
+                SourceMap::default(),
+                PackageType::Lib,
+                TargetProfile::Base,
+            )
+            .expect("interpreter should be created");
+            let (result, output) = line(
+                &mut interpreter,
+                indoc! {"operation Foo() : Result { use q = Qubit(); let r = M(q); Reset(q); return r; } "},
+            );
+            is_only_value(&result, &output, &Value::unit());
+            let res = interpreter
+                .qirgen("{operation Bar() : Unit {}; Foo()}")
+                .expect("expected success");
+            expect![[r#"
+                %Result = type opaque
+                %Qubit = type opaque
+
+                define void @ENTRYPOINT__main() #0 {
+                  call void @__quantum__qis__m__body(%Qubit* inttoptr (i64 0 to %Qubit*), %Result* inttoptr (i64 0 to %Result*)) #1
+                  call void @__quantum__qis__reset__body(%Qubit* inttoptr (i64 0 to %Qubit*))
+                  call void @__quantum__rt__result_record_output(%Result* inttoptr (i64 0 to %Result*), i8* null)
+                  ret void
+                }
+
+                declare void @__quantum__qis__ccx__body(%Qubit*, %Qubit*, %Qubit*)
+                declare void @__quantum__qis__cx__body(%Qubit*, %Qubit*)
+                declare void @__quantum__qis__cy__body(%Qubit*, %Qubit*)
+                declare void @__quantum__qis__cz__body(%Qubit*, %Qubit*)
+                declare void @__quantum__qis__rx__body(double, %Qubit*)
+                declare void @__quantum__qis__rxx__body(double, %Qubit*, %Qubit*)
+                declare void @__quantum__qis__ry__body(double, %Qubit*)
+                declare void @__quantum__qis__ryy__body(double, %Qubit*, %Qubit*)
+                declare void @__quantum__qis__rz__body(double, %Qubit*)
+                declare void @__quantum__qis__rzz__body(double, %Qubit*, %Qubit*)
+                declare void @__quantum__qis__h__body(%Qubit*)
+                declare void @__quantum__qis__s__body(%Qubit*)
+                declare void @__quantum__qis__s__adj(%Qubit*)
+                declare void @__quantum__qis__t__body(%Qubit*)
+                declare void @__quantum__qis__t__adj(%Qubit*)
+                declare void @__quantum__qis__x__body(%Qubit*)
+                declare void @__quantum__qis__y__body(%Qubit*)
+                declare void @__quantum__qis__z__body(%Qubit*)
+                declare void @__quantum__qis__swap__body(%Qubit*, %Qubit*)
+                declare void @__quantum__qis__reset__body(%Qubit*)
+                declare void @__quantum__qis__mresetz__body(%Qubit*, %Result* writeonly) #1
+                declare void @__quantum__qis__m__body(%Qubit*, %Result* writeonly) #1
+                declare void @__quantum__rt__result_record_output(%Result*, i8*)
+                declare void @__quantum__rt__array_record_output(i64, i8*)
+                declare void @__quantum__rt__tuple_record_output(i64, i8*)
+
+                attributes #0 = { "entry_point" "output_labeling_schema" "qir_profiles"="custom" }
+                attributes #1 = { "irreversible" }
+
+                ; module flags
+
+                !llvm.module.flags = !{!0, !1, !2, !3}
+
+                !0 = !{i32 1, !"qir_major_version", i32 1}
+                !1 = !{i32 7, !"qir_minor_version", i32 0}
+                !2 = !{i32 1, !"dynamic_qubit_management", i1 false}
+                !3 = !{i32 1, !"dynamic_result_management", i1 false}
+            "#]].assert_eq(&res);
+
+            // Operation should not be visible from global scope
+            let (result, output) = line(&mut interpreter, indoc! {"Bar()"});
+            is_only_error(&result, &output, "name error: `Bar` not found");
+        }
+
+        #[test]
+        fn qirgen_multiple_exprs_parse_fail() {
+            let mut interpreter = Interpreter::new(
+                true,
+                SourceMap::default(),
+                PackageType::Lib,
+                TargetProfile::Base,
+            )
+            .expect("interpreter should be created");
+            let (result, output) = line(
+                &mut interpreter,
+                indoc! {"operation Foo() : Result { use q = Qubit(); let r = M(q); Reset(q); return r; } "},
+            );
+            is_only_value(&result, &output, &Value::unit());
+            let res = interpreter
+                .qirgen("Foo(); operation Bar() : Unit {}; Foo()")
+                .expect_err("expected error");
+            expect![[r#"
+                [
+                    LineError(
+                        WithSource {
+                            source: Some(
+                                "Foo(); operation Bar() : Unit {}; Foo()",
+                            ),
+                            error: Compile(
+                                Error(
+                                    Parse(
+                                        Error(
+                                            Token(
+                                                Eof,
+                                                Semi,
+                                                Span {
+                                                    lo: 5,
+                                                    hi: 6,
+                                                },
+                                            ),
+                                        ),
+                                    ),
+                                ),
+                            ),
+                            stack_trace: None,
+                        },
+                    ),
+                ]
+            "#]]
+            .assert_debug_eq(&res);
+        }
+
+        #[test]
+        fn qirgen_entry_expr_defines_operation_then_more_operations() {
+            let mut interpreter = Interpreter::new(
+                true,
+                SourceMap::default(),
+                PackageType::Lib,
+                TargetProfile::Base,
+            )
+            .expect("interpreter should be created");
+            let (result, output) = line(
+                &mut interpreter,
+                indoc! {"operation Foo() : Result { use q = Qubit(); let r = M(q); Reset(q); return r; } "},
+            );
+            is_only_value(&result, &output, &Value::unit());
+            let res = interpreter
+                .qirgen("{operation Bar() : Unit {}; Foo()}")
+                .expect("expected success");
+            expect![[r#"
+                %Result = type opaque
+                %Qubit = type opaque
+
+                define void @ENTRYPOINT__main() #0 {
+                  call void @__quantum__qis__m__body(%Qubit* inttoptr (i64 0 to %Qubit*), %Result* inttoptr (i64 0 to %Result*)) #1
+                  call void @__quantum__qis__reset__body(%Qubit* inttoptr (i64 0 to %Qubit*))
+                  call void @__quantum__rt__result_record_output(%Result* inttoptr (i64 0 to %Result*), i8* null)
+                  ret void
+                }
+
+                declare void @__quantum__qis__ccx__body(%Qubit*, %Qubit*, %Qubit*)
+                declare void @__quantum__qis__cx__body(%Qubit*, %Qubit*)
+                declare void @__quantum__qis__cy__body(%Qubit*, %Qubit*)
+                declare void @__quantum__qis__cz__body(%Qubit*, %Qubit*)
+                declare void @__quantum__qis__rx__body(double, %Qubit*)
+                declare void @__quantum__qis__rxx__body(double, %Qubit*, %Qubit*)
+                declare void @__quantum__qis__ry__body(double, %Qubit*)
+                declare void @__quantum__qis__ryy__body(double, %Qubit*, %Qubit*)
+                declare void @__quantum__qis__rz__body(double, %Qubit*)
+                declare void @__quantum__qis__rzz__body(double, %Qubit*, %Qubit*)
+                declare void @__quantum__qis__h__body(%Qubit*)
+                declare void @__quantum__qis__s__body(%Qubit*)
+                declare void @__quantum__qis__s__adj(%Qubit*)
+                declare void @__quantum__qis__t__body(%Qubit*)
+                declare void @__quantum__qis__t__adj(%Qubit*)
+                declare void @__quantum__qis__x__body(%Qubit*)
+                declare void @__quantum__qis__y__body(%Qubit*)
+                declare void @__quantum__qis__z__body(%Qubit*)
+                declare void @__quantum__qis__swap__body(%Qubit*, %Qubit*)
+                declare void @__quantum__qis__reset__body(%Qubit*)
+                declare void @__quantum__qis__mresetz__body(%Qubit*, %Result* writeonly) #1
+                declare void @__quantum__qis__m__body(%Qubit*, %Result* writeonly) #1
+                declare void @__quantum__rt__result_record_output(%Result*, i8*)
+                declare void @__quantum__rt__array_record_output(i64, i8*)
+                declare void @__quantum__rt__tuple_record_output(i64, i8*)
+
+                attributes #0 = { "entry_point" "output_labeling_schema" "qir_profiles"="custom" }
+                attributes #1 = { "irreversible" }
+
+                ; module flags
+
+                !llvm.module.flags = !{!0, !1, !2, !3}
+
+                !0 = !{i32 1, !"qir_major_version", i32 1}
+                !1 = !{i32 7, !"qir_minor_version", i32 0}
+                !2 = !{i32 1, !"dynamic_qubit_management", i1 false}
+                !3 = !{i32 1, !"dynamic_result_management", i1 false}
+            "#]].assert_eq(&res);
+
+            let (result, output) = line(
+                &mut interpreter,
+                indoc! {"operation Baz() : Result { use q = Qubit(); let r = M(q); Reset(q); return r; } "},
+            );
+            is_only_value(&result, &output, &Value::unit());
+
+            let (result, output) = line(&mut interpreter, indoc! {"Bar()"});
+            is_only_error(&result, &output, "name error: `Bar` not found");
+        }
+
+        #[test]
+        fn qirgen_define_operation_use_it() {
+            let mut interpreter = Interpreter::new(
+                true,
+                SourceMap::default(),
+                PackageType::Lib,
+                TargetProfile::Base,
+            )
+            .expect("interpreter should be created");
+            let res = interpreter
+                .qirgen("{ operation Foo() : Result { use q = Qubit(); let r = M(q); Reset(q); return r; }; Foo() }")
+                .expect("expected success");
+            expect![[r#"
+                %Result = type opaque
+                %Qubit = type opaque
+
+                define void @ENTRYPOINT__main() #0 {
+                  call void @__quantum__qis__m__body(%Qubit* inttoptr (i64 0 to %Qubit*), %Result* inttoptr (i64 0 to %Result*)) #1
+                  call void @__quantum__qis__reset__body(%Qubit* inttoptr (i64 0 to %Qubit*))
+                  call void @__quantum__rt__result_record_output(%Result* inttoptr (i64 0 to %Result*), i8* null)
+                  ret void
+                }
+
+                declare void @__quantum__qis__ccx__body(%Qubit*, %Qubit*, %Qubit*)
+                declare void @__quantum__qis__cx__body(%Qubit*, %Qubit*)
+                declare void @__quantum__qis__cy__body(%Qubit*, %Qubit*)
+                declare void @__quantum__qis__cz__body(%Qubit*, %Qubit*)
+                declare void @__quantum__qis__rx__body(double, %Qubit*)
+                declare void @__quantum__qis__rxx__body(double, %Qubit*, %Qubit*)
+                declare void @__quantum__qis__ry__body(double, %Qubit*)
+                declare void @__quantum__qis__ryy__body(double, %Qubit*, %Qubit*)
+                declare void @__quantum__qis__rz__body(double, %Qubit*)
+                declare void @__quantum__qis__rzz__body(double, %Qubit*, %Qubit*)
+                declare void @__quantum__qis__h__body(%Qubit*)
+                declare void @__quantum__qis__s__body(%Qubit*)
+                declare void @__quantum__qis__s__adj(%Qubit*)
+                declare void @__quantum__qis__t__body(%Qubit*)
+                declare void @__quantum__qis__t__adj(%Qubit*)
+                declare void @__quantum__qis__x__body(%Qubit*)
+                declare void @__quantum__qis__y__body(%Qubit*)
+                declare void @__quantum__qis__z__body(%Qubit*)
+                declare void @__quantum__qis__swap__body(%Qubit*, %Qubit*)
+                declare void @__quantum__qis__reset__body(%Qubit*)
+                declare void @__quantum__qis__mresetz__body(%Qubit*, %Result* writeonly) #1
+                declare void @__quantum__qis__m__body(%Qubit*, %Result* writeonly) #1
+                declare void @__quantum__rt__result_record_output(%Result*, i8*)
+                declare void @__quantum__rt__array_record_output(i64, i8*)
+                declare void @__quantum__rt__tuple_record_output(i64, i8*)
+
+                attributes #0 = { "entry_point" "output_labeling_schema" "qir_profiles"="custom" }
+                attributes #1 = { "irreversible" }
+
+                ; module flags
+
+                !llvm.module.flags = !{!0, !1, !2, !3}
+
+                !0 = !{i32 1, !"qir_major_version", i32 1}
+                !1 = !{i32 7, !"qir_minor_version", i32 0}
+                !2 = !{i32 1, !"dynamic_qubit_management", i1 false}
+                !3 = !{i32 1, !"dynamic_result_management", i1 false}
+            "#]].assert_eq(&res);
+        }
     }
 
     #[cfg(test)]