Skip to content

Commit

Permalink
Use a pass to detect test attribute errors and report them nicely
Browse files Browse the repository at this point in the history
  • Loading branch information
sezna committed Dec 16, 2024
1 parent c22d14d commit 5a28ef1
Show file tree
Hide file tree
Showing 3 changed files with 130 additions and 0 deletions.
6 changes: 6 additions & 0 deletions compiler/qsc_passes/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ mod measurement;
mod replace_qubit_allocation;
mod reset;
mod spec_gen;
mod test_attribute;

use callable_limits::CallableLimits;
use capabilitiesck::{check_supported_capabilities, lower_store, run_rca_pass};
Expand Down Expand Up @@ -52,6 +53,7 @@ pub enum Error {
Measurement(measurement::Error),
Reset(reset::Error),
SpecGen(spec_gen::Error),
TestAttribute(test_attribute::TestAttributeError),
}

#[derive(Clone, Copy, Debug, PartialEq)]
Expand Down Expand Up @@ -121,6 +123,9 @@ impl PassContext {
ReplaceQubitAllocation::new(core, assigner).visit_package(package);
Validator::default().visit_package(package);

let test_attribute_errors = test_attribute::validate_test_attributes(package);
Validator::default().visit_package(package);

callable_errors
.into_iter()
.map(Error::CallableLimits)
Expand All @@ -130,6 +135,7 @@ impl PassContext {
.chain(entry_point_errors)
.chain(measurement_decl_errors.into_iter().map(Error::Measurement))
.chain(reset_decl_errors.into_iter().map(Error::Reset))
.chain(test_attribute_errors.into_iter().map(Error::TestAttribute))
.collect()
}

Expand Down
45 changes: 45 additions & 0 deletions compiler/qsc_passes/src/test_attribute.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT License.

use miette::Diagnostic;
use qsc_data_structures::span::Span;
use qsc_hir::{hir::Attr, visit::Visitor};
use thiserror::Error;

#[cfg(test)]
mod tests;

#[derive(Clone, Debug, Diagnostic, Error)]
pub enum TestAttributeError {
#[error("This callable has parameters. Tests cannot have parameters.")]
CallableHasParameters(#[label] Span),
#[error("This callable has type parameters. Tests cannot have type parameters.")]
CallableHaSTypeParameters(#[label] Span),
}

pub(crate) fn validate_test_attributes(
package: &mut qsc_hir::hir::Package,
) -> Vec<TestAttributeError> {
let mut validator = TestAttributeValidator { errors: Vec::new() };
validator.visit_package(package);
validator.errors
}

struct TestAttributeValidator {
errors: Vec<TestAttributeError>,
}

impl<'a> Visitor<'a> for TestAttributeValidator {
fn visit_callable_decl(&mut self, decl: &'a qsc_hir::hir::CallableDecl) {
if decl.attrs.iter().any(|attr| matches!(attr, Attr::Test)) {
if !decl.generics.is_empty() {
self.errors
.push(TestAttributeError::CallableHaSTypeParameters(decl.span));
}
if decl.input.ty != qsc_hir::ty::Ty::UNIT {
self.errors
.push(TestAttributeError::CallableHasParameters(decl.span));
}
}
}
}
79 changes: 79 additions & 0 deletions compiler/qsc_passes/src/test_attribute/tests.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT License.

use expect_test::{expect, Expect};
use indoc::indoc;
use qsc_data_structures::{language_features::LanguageFeatures, target::TargetCapabilityFlags};
use qsc_frontend::compile::{self, compile, PackageStore, SourceMap};
use qsc_hir::{validate::Validator, visit::Visitor};

use crate::test_attribute::validate_test_attributes;

fn check(file: &str, expect: &Expect) {
let store = PackageStore::new(compile::core());
let sources = SourceMap::new([("test".into(), file.into())], None);
let mut unit = compile(
&store,
&[],
sources,
TargetCapabilityFlags::all(),
LanguageFeatures::default(),
);
assert!(unit.errors.is_empty(), "{:?}", unit.errors);

let errors = validate_test_attributes(&mut unit.package);
Validator::default().visit_package(&unit.package);
if errors.is_empty() {
expect.assert_eq(&unit.package.to_string());
} else {
expect.assert_debug_eq(&errors);
}
}

#[test]
fn callable_cant_have_params() {
check(
indoc! {"
namespace test {
@Test()
operation A(q : Qubit) : Unit {
}
}
"},
&expect![[r#"
[
CallableHasParameters(
Span {
lo: 33,
hi: 71,
},
),
]
"#]],
);
}

#[test]
fn callable_cant_have_type_params() {
check(
indoc! {"
namespace test {
@Test()
operation A<'T>() : Unit {
}
}
"},
&expect![[r#"
[
CallableHaSTypeParameters(
Span {
lo: 33,
hi: 66,
},
),
]
"#]],
);
}

0 comments on commit 5a28ef1

Please sign in to comment.