Skip to content

Commit

Permalink
Add semantic validation pass (#264)
Browse files Browse the repository at this point in the history
  • Loading branch information
swernli authored May 2, 2023
1 parent 3555242 commit 28c2715
Show file tree
Hide file tree
Showing 3 changed files with 302 additions and 0 deletions.
8 changes: 8 additions & 0 deletions compiler/qsc_passes/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ pub mod entry_point;
mod invert_block;
mod logic_sep;
pub mod loop_unification;
pub mod semantics;
pub mod spec_gen;

use miette::Diagnostic;
Expand All @@ -25,12 +26,19 @@ pub enum Error {
EntryPoint(entry_point::Error),
SpecGen(spec_gen::Error),
ConjInvert(conjugate_invert::Error),
Semantic(semantics::Error),
}

/// Run the default set of passes required for evaluation.
pub fn run_default_passes(unit: &mut CompileUnit) -> Vec<Error> {
let mut errors = Vec::new();

errors.extend(
semantics::validate_semantics(unit)
.into_iter()
.map(Error::Semantic),
);

errors.extend(
spec_gen::generate_specs(unit)
.into_iter()
Expand Down
113 changes: 113 additions & 0 deletions compiler/qsc_passes/src/semantics.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,113 @@
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT License.

#[cfg(test)]
mod tests;

use miette::Diagnostic;
use qsc_data_structures::span::Span;
use qsc_frontend::compile::CompileUnit;
use qsc_hir::{
hir::{CallableBody, CallableDecl, CallableKind, Expr, ExprKind, Spec, Stmt, StmtKind, Ty},
visit::{walk_callable_decl, walk_expr, walk_stmt, Visitor},
};
use thiserror::Error;

#[derive(Clone, Debug, Diagnostic, Error)]
pub enum Error {
#[error("functions cannot use conjugate expressions")]
ConjInFunc(#[label] Span),

#[error("functions cannot have functor expressions")]
FunctorInFunc(#[label] Span),

#[error("functions cannot call operations")]
OpCallInFunc(#[label] Span),

#[error("functions cannot allocate qubits")]
QubitAllocInFunc(#[label] Span),

#[error("functions cannot use repeat-loop expressions")]
RepeatInFunc(#[label] Span),

#[error("functions cannot have specializations")]
SpecInFunc(#[label] Span),

#[error("operations cannot use while-loop expressions")]
WhileInOp(#[label] Span),
}

#[allow(clippy::module_name_repetitions)]
#[must_use]
pub fn validate_semantics(unit: &CompileUnit) -> Vec<Error> {
let mut pass = Validate {
errors: Vec::new(),
in_func: false,
in_op: false,
};
pass.visit_package(&unit.package);
pass.errors
}

struct Validate {
errors: Vec<Error>,
in_func: bool,
in_op: bool,
}

impl<'a> Visitor<'a> for Validate {
fn visit_callable_decl(&mut self, decl: &'a CallableDecl) {
self.in_func = decl.kind == CallableKind::Function;
self.in_op = decl.kind == CallableKind::Operation;

if self.in_func {
match &decl.body {
CallableBody::Specs(specs) => {
if specs.iter().any(|spec| spec.spec != Spec::Body) {
self.errors.push(Error::SpecInFunc(decl.span));
}
}
CallableBody::Block(_) => {}
}
match &decl.functors {
Some(functors) => self.errors.push(Error::FunctorInFunc(functors.span)),
None => {}
}
}

walk_callable_decl(self, decl);
self.in_func = false;
self.in_op = false;
}

fn visit_stmt(&mut self, stmt: &'a Stmt) {
if let StmtKind::Qubit(..) = &stmt.kind {
if self.in_func {
self.errors.push(Error::QubitAllocInFunc(stmt.span));
}
}
walk_stmt(self, stmt);
}

fn visit_expr(&mut self, expr: &'a Expr) {
match &expr.kind {
ExprKind::Call(callee, _)
if matches!(callee.ty, Ty::Arrow(CallableKind::Operation, _, _, _))
&& self.in_func =>
{
self.errors.push(Error::OpCallInFunc(expr.span));
}
ExprKind::Conjugate(..) if self.in_func => {
self.errors.push(Error::ConjInFunc(expr.span));
}
ExprKind::Repeat(..) if self.in_func => {
self.errors.push(Error::RepeatInFunc(expr.span));
}
ExprKind::While(..) if self.in_op => {
self.errors.push(Error::WhileInOp(expr.span));
}
_ => {}
}
walk_expr(self, expr);
}
}
181 changes: 181 additions & 0 deletions compiler/qsc_passes/src/semantics/tests.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,181 @@
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT License.

#![allow(clippy::too_many_lines)]

use expect_test::{expect, Expect};
use indoc::indoc;
use qsc_frontend::compile::{compile, PackageStore, SourceMap};

use crate::semantics::validate_semantics;

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

let errors = validate_semantics(&unit);
expect.assert_debug_eq(&errors);
}

#[test]
fn funcs_cannot_use_conj() {
check(
indoc! {"
namespace Test {
function A() : Unit {
within {} apply {}
}
}
"},
&expect![[r#"
[
ConjInFunc(
Span {
lo: 51,
hi: 69,
},
),
]
"#]],
);
}

#[test]
fn funcs_cannot_have_functors() {
check(
indoc! {"
namespace Test {
function A() : Unit is Adj {}
}
"},
&expect![[r#"
[
FunctorInFunc(
Span {
lo: 44,
hi: 47,
},
),
]
"#]],
);
}

#[test]
fn funcs_cannot_call_ops() {
check(
indoc! {"
namespace Test {
operation A() : Unit {}
function B() : Unit {
A();
}
}
"},
&expect![[r#"
[
OpCallInFunc(
Span {
lo: 79,
hi: 82,
},
),
]
"#]],
);
}

#[test]
fn funcs_cannot_allocate_qubits() {
check(
indoc! {"
namespace Test {
function A() : Unit {
use q = Qubit();
}
}
"},
&expect![[r#"
[
QubitAllocInFunc(
Span {
lo: 51,
hi: 67,
},
),
]
"#]],
);
}

#[test]
fn funcs_cannot_use_repeat() {
check(
indoc! {"
namespace Test {
function A() : Unit {
repeat {} until true;
}
}
"},
&expect![[r#"
[
RepeatInFunc(
Span {
lo: 51,
hi: 71,
},
),
]
"#]],
);
}

#[test]
fn funcs_cannot_have_specs() {
check(
indoc! {"
namespace Test {
function A() : Unit {
body ... {}
adjoint self;
}
}
"},
&expect![[r#"
[
SpecInFunc(
Span {
lo: 21,
hi: 90,
},
),
]
"#]],
);
}

#[test]
fn ops_cannot_use_while() {
check(
indoc! {"
namespace Test {
operation B() : Unit {
while true {}
}
}
"},
&expect![[r#"
[
WhileInOp(
Span {
lo: 52,
hi: 65,
},
),
]
"#]],
);
}

0 comments on commit 28c2715

Please sign in to comment.