Skip to content

Commit db6607c

Browse files
committed
add HIR pass verifying @NoiseIntrinsic() is an intrinsic
1 parent 1e2716d commit db6607c

File tree

4 files changed

+157
-1
lines changed

4 files changed

+157
-1
lines changed

source/compiler/qsc_frontend/src/lower/tests.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2241,7 +2241,7 @@ fn test_noise_intrinsic_attr_on_function_issues_error() {
22412241
indoc! {r#"
22422242
namespace Test {
22432243
@NoiseIntrinsic()
2244-
function Foo(q: Qubit) : Result {
2244+
function Foo(q: Qubit) : Unit {
22452245
body intrinsic;
22462246
}
22472247
}

source/compiler/qsc_passes/src/lib.rs

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ mod invert_block;
1313
mod logic_sep;
1414
mod loop_unification;
1515
mod measurement;
16+
mod noise_intrinsic;
1617
mod replace_qubit_allocation;
1718
mod reset;
1819
mod spec_gen;
@@ -53,6 +54,7 @@ pub enum Error {
5354
ConjInvert(conjugate_invert::Error),
5455
EntryPoint(entry_point::Error),
5556
Measurement(measurement::Error),
57+
NoiseIntrinsic(noise_intrinsic::Error),
5658
Reset(reset::Error),
5759
SpecGen(spec_gen::Error),
5860
TestAttribute(test_attribute::TestAttributeError),
@@ -118,6 +120,8 @@ impl PassContext {
118120

119121
let measurement_decl_errors = measurement::validate_measurement_declarations(package);
120122
let reset_decl_errors = reset::validate_reset_declarations(package);
123+
let noise_intrinsic_decl_errors =
124+
noise_intrinsic::validate_noise_intrinsic_declarations(package);
121125

122126
let entry_point_errors = generate_entry_expr(package, assigner, package_type);
123127
Validator::default().visit_package(package);
@@ -140,6 +144,11 @@ impl PassContext {
140144
.chain(entry_point_errors)
141145
.chain(measurement_decl_errors.into_iter().map(Error::Measurement))
142146
.chain(reset_decl_errors.into_iter().map(Error::Reset))
147+
.chain(
148+
noise_intrinsic_decl_errors
149+
.into_iter()
150+
.map(Error::NoiseIntrinsic),
151+
)
143152
.chain(test_attribute_errors.into_iter().map(Error::TestAttribute))
144153
.collect()
145154
}
Lines changed: 75 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,75 @@
1+
// Copyright (c) Microsoft Corporation.
2+
// Licensed under the MIT License.
3+
4+
#[cfg(test)]
5+
mod tests;
6+
7+
use miette::Diagnostic;
8+
use qsc_data_structures::span::Span;
9+
use qsc_hir::{
10+
hir::{Attr, CallableDecl, Item, ItemKind, Package, SpecBody, SpecGen},
11+
ty::Ty,
12+
visit::Visitor,
13+
};
14+
use thiserror::Error;
15+
16+
#[derive(Clone, Debug, Diagnostic, Error)]
17+
pub enum Error {
18+
#[error("a callable with the @NoiseIntrinsic() attribute should have output of type Unit")]
19+
#[diagnostic(code("Qsc.NoiseIntrinsic.NonResultOutput"))]
20+
NonUnitOutput(#[label] Span),
21+
22+
#[error("a callable with the @NoiseIntrinsic() attribute should be an intrinsic")]
23+
#[diagnostic(code("Qsc.NoiseIntrinsic.NotIntrinsic"))]
24+
NotIntrinsic(#[label] Span),
25+
}
26+
27+
/// For each noise intrinsic declaration check that:
28+
/// 1. It only outputs Unit.
29+
/// 2. It is an intrinsic.
30+
pub(super) fn validate_noise_intrinsic_declarations(package: &Package) -> Vec<Error> {
31+
let mut validator = NoiseIntrinsicValidator { errors: Vec::new() };
32+
validator.visit_package(package);
33+
validator.errors
34+
}
35+
36+
fn validate_noise_intrinsic_declaration(
37+
decl: &CallableDecl,
38+
attrs: &[Attr],
39+
errors: &mut Vec<Error>,
40+
) {
41+
// 1. Check that the declaration only outputs Unit.
42+
if decl.output != Ty::UNIT {
43+
errors.push(Error::NonUnitOutput(decl.span));
44+
}
45+
46+
// 2. Check that the declaration is an intrinsic.
47+
if !decl_is_intrinsic(decl, attrs) {
48+
errors.push(Error::NotIntrinsic(decl.name.span));
49+
}
50+
}
51+
52+
/// Returns `true` if a declaration is an intrinsic. A declaration is
53+
/// an intrinsic if it has `body intrinsic;` in its body or if it has
54+
/// the `@SimulatableIntrinsic()` attribute.
55+
fn decl_is_intrinsic(decl: &CallableDecl, attrs: &[Attr]) -> bool {
56+
matches!(decl.body.body, SpecBody::Gen(SpecGen::Intrinsic))
57+
|| attrs
58+
.iter()
59+
.any(|attr| matches!(attr, Attr::SimulatableIntrinsic))
60+
}
61+
62+
/// A helper structure to find and validate noise intrinsic callables in a Package.
63+
struct NoiseIntrinsicValidator {
64+
errors: Vec<Error>,
65+
}
66+
67+
impl<'a> Visitor<'a> for NoiseIntrinsicValidator {
68+
fn visit_item(&mut self, item: &'a Item) {
69+
if let ItemKind::Callable(callable) = &item.kind
70+
&& item.attrs.contains(&Attr::NoiseIntrinsic)
71+
{
72+
validate_noise_intrinsic_declaration(callable, &item.attrs, &mut self.errors);
73+
}
74+
}
75+
}
Lines changed: 72 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,72 @@
1+
// Copyright (c) Microsoft Corporation.
2+
// Licensed under the MIT License.
3+
4+
use crate::noise_intrinsic::validate_noise_intrinsic_declarations;
5+
use expect_test::{Expect, expect};
6+
use indoc::indoc;
7+
use qsc_data_structures::{
8+
language_features::LanguageFeatures, source::SourceMap, target::TargetCapabilityFlags,
9+
};
10+
use qsc_frontend::compile::{self, PackageStore, compile};
11+
12+
fn check(file: &str, expect: &Expect) {
13+
let sources = SourceMap::new([("test".into(), file.into())], Some("".into()));
14+
let unit = compile(
15+
&PackageStore::new(compile::core()),
16+
&[],
17+
sources,
18+
TargetCapabilityFlags::all(),
19+
LanguageFeatures::default(),
20+
);
21+
22+
let errors = validate_noise_intrinsic_declarations(&unit.package);
23+
expect.assert_debug_eq(&errors);
24+
}
25+
26+
#[test]
27+
fn test_noise_intrinsic_attr_on_non_intrinsic_issues_error() {
28+
check(
29+
indoc! {r#"
30+
namespace Test {
31+
@NoiseIntrinsic()
32+
operation Foo(q: Qubit) : Unit {
33+
34+
}
35+
}
36+
"#},
37+
&expect![[r#"
38+
[
39+
NotIntrinsic(
40+
Span {
41+
lo: 54,
42+
hi: 57,
43+
},
44+
),
45+
]
46+
"#]],
47+
);
48+
}
49+
50+
#[test]
51+
fn test_noise_intrinsic_with_non_unit_return_issues_error() {
52+
check(
53+
indoc! {r#"
54+
namespace Test {
55+
@NoiseIntrinsic()
56+
operation Foo(q: Qubit) : Int {
57+
body intrinsic;
58+
}
59+
}
60+
"#},
61+
&expect![[r#"
62+
[
63+
NonUnitOutput(
64+
Span {
65+
lo: 44,
66+
hi: 105,
67+
},
68+
),
69+
]
70+
"#]],
71+
);
72+
}

0 commit comments

Comments
 (0)